Improve L2 market parsing and data handling

This commit is contained in:
2026-08-11 22:24:36 +03:00
parent de41230b9b
commit af9cd1e92d
58 changed files with 2474 additions and 444 deletions
+69
View File
@@ -0,0 +1,69 @@
# Dynamic Tooltip Crop Implementation Plan
**Intent:** Сделать ручную калибровку пиксель-точной при масштабе 2× и читать строки tooltip переменной ширины без OCR всего кадра.
**Current Behavior:** Калибратор показывает кадр 1:1, а runtime после поиска `Price :` использует фиксированную ширину калиброванных строк названия и цены.
**Expected Outcome:** Кадр размечается в масштабе 2×; найденные строки названия и итоговой цены tooltip локально подгоняются вправо по фактическим белым и жёлтым пикселям до первого устойчивого пустого промежутка, включая текст длиннее калиброванного образца.
**Target-Perspective Output:** Пользователь выделяет мелкие области на увеличенном кадре и получает название и цену как для короткого, так и для длинного tooltip без постороннего текста за его правой границей.
**Truth Owner:** `src/vision.js` владеет определением фактической правой границы OCR-строки; калибровка сохраняет стабильный левый край, положение и высоту строки относительно `Price :`.
**Contract Boundary:** Чистый `findTextRunEnd(columns, gap)` находит конец первого текстового пробега. Canvas-wrapper `fitOcrTextRect(canvas, rect, options)` сканирует от калиброванного левого края до правой границы кадра, использует gap 12 px и padding 6 px, применяет white mask для названия и white+yellow для цены и возвращает frame-clamped rect либо `null`, если пробег не найден.
**Cutover:** `analyzeFrame` подгоняет `nameRect` и `priceRect` перед `combineRects`; прямое использование фиксированной ширины прекращается.
**Displaced Path:** Фиксированные ширины `itemNameRegion` и `itemPriceRegion` больше не задают окончательную границу и не используются как широкий OCR fallback; при пустой маске item OCR пропускается с диагностикой.
**Value Density:** Один локальный пиксельный проход по двум тонким строкам вместо OCR широкой области или нескольких OCR-попыток.
**Evidence Gate:** На реальном тестовом кадре 1920×1080 canvas отображается 3840×2160; выделение в 2× визуально совпадает с целевыми source-пикселями; реальный tooltip читается при искусственно короткой калиброванной ширине, а удалённый соседний текст не попадает в diagnostics crop/OCR.
**Acceptance Evidence:** Browser geometry и screenshot выделения; границы двух реальных строк разной длины из предоставленного tooltip; diagnostics crop + parsed name/price этого tooltip при намеренно узкой калиброванной ширине; runnable-тест чистого определения конца текстового пробега; production build.
**Evidence Lane:** Локальный браузер и `node --test`.
**Kill Criteria:** В runtime нет второго OCR-пути по широкой области или широкого фиксированного fallback; `combineRects` не вызывается до успешной подгонки обеих строк.
**Architecture Slice:** `src/main.js`, `src/vision.js`, `src/style.css`, `test/parser.test.js`, `README.md`; документация калибровки уточняет различие зоны поиска и OCR-строк.
**Plan Review Gate:** Requires PRE review before execution.
## Architecture Slice
- Files to create: только этот goal-пакет.
- Files to modify: `src/main.js`, `src/vision.js`, `src/style.css`, `test/parser.test.js`, `README.md`.
- Files to avoid: сохранённый формат калибровки, парсер текста tooltip, market outbox и backend.
- Source of truth: найденный `Price :` задаёт позицию; пиксели текущего кадра задают фактическую ширину.
- Read path: кадр → поиск магазина → поиск `Price :` → калиброванные строки → подгонка по пикселям → OCR → parser.
- Write path: без новых сохраняемых полей; существующая калибровка остаётся совместимой.
- Contract boundary: чистый `findTextRunEnd` и canvas-wrapper `fitOcrTextRect` в vision-слое.
- Integration points: два прямоугольника в `analyzeFrame`, CSS-размер canvas.
- Migration/cutover: немедленный, без миграции данных.
- Displaced path: фиксированная ширина перестаёт быть OCR fallback; пустая маска становится наблюдаемым `not-found` в diagnostics.
- Acceptance evidence gate: 2× browser geometry + screenshot точного выделения + diagnostics реального tooltip + unit test длинной строки + full build.
## Runtime Invariants
- Левый край строк названия и цены стабилен относительно найденного `Price :`; переменной является правая граница.
- Продукт сохраняет только название, количество и итоговую цену; остальные описательные строки tooltip не являются данными продукта.
- `tooltipSearchRegion` может быть широкой горизонтальной полосой за пределами окна трейда, но исключает нижний `Price` основного окна; эта полоса используется только для template matching.
- Сканирование OCR-строки начинается с её калиброванного левого края, идёт не дальше правой границы кадра, допускает внутренние промежутки меньше 12 px и добавляет не более 6 px после последнего текстового столбца.
- Название использует белую маску; цена использует белую и жёлтую маски.
- Если текстовый пробег не найден, OCR предмета не запускается, а diagnostics явно сообщает причину.
## Tasks
1. Последовательно изменить `src/main.js` и `src/style.css`: отображать calibration canvas ровно в 2× intrinsic size; координаты остаются корректными через существующий `canvasPoint`.
- Allowed scope: только display size и crisp integer scaling.
- Verification: браузер показывает intrinsic 1920×1080 и rendered 3840×2160; screenshot известного выделения совпадает с source-пикселями.
2. Затем изменить `src/vision.js` и `test/parser.test.js`: добавить чистый `findTextRunEnd` и локальную подгонку правого края с раздельными масками, gap 12, padding 6 и `null` для пустой маски.
- Allowed scope: preprocessing границы; без нового OCR-прохода и без зависимости canvas в Node.
- Verification: `node --test` покрывает длинный текст, внутренние пробелы, удалённый шум и пустую маску.
3. После задачи 2 интегрировать подгонку в `src/main.js` до `combineRects`; при `null` остановить item OCR и записать причину в diagnostics.
- Allowed scope: существующий item OCR branch.
- Verification: diagnostics показывают фактический локальный crop, а широкий search region и удалённый соседний текст не попадают в OCR.
4. Обязательно обновить `README.md`: широкая полоса ищет только `Price :`; ширины строк являются начальными левыми/вертикальными ориентирами и подгоняются runtime.
- Allowed scope: только инструкции выбора `tooltipSearch`, `itemName` и `itemPrice`.
- Verification: README больше не требует калибровать ширину по самому длинному предмету.
5. Прогнать прямые эквиваленты `npm test`, `npm run build`, `git diff --check` и визуальную проверку большого кадра; записать реальные результаты в `EVIDENCE.md`.
- Acceptance evidence: geometry 2×, screenshot точного выделения, границы двух реальных строк, diagnostics OCR/parse и вывод проверочных команд.
## Non-goals
- Не определять всю рамку tooltip и не сохранять его описательные строки, потому что продукту нужны название, количество и итоговая цена.
- Не искать `Price :` по всему кадру без ограничивающей полосы.
- Не добавлять настройку масштаба или новый UI-контрол в этом срезе.
- Не менять формат сохранённой калибровки.
## Risks
- Слишком короткий допустимый пробел обрежет текст; слишком длинный захватит соседний UI.
- Горизонтальная рамка внутри строки может выглядеть как непрерывный текст; точные вертикальные калиброванные строки остаются обязательными, а пустой/неограниченный пробег должен завершаться `not-found`, не широким OCR.