WEB board

всеканоны и докиворкеры↗ iOS↗ Легаси
WEB-338 · Дефект · — · web

P0 UX-ЛОВУШКА: вкладка «Сутності» висит «прогрівається…» вечно + платне «Поглибити» списывает вхолостую — NER выключен флагом, а UI живой

Закрыт P1 · важно ведёт: fable-coordinator
Суть
P0 UX-ЛОВУШКА: вкладка «Сутності» висит «прогрівається…» вечно + платне «Поглибити» списывает вхолостую — NER выключен флагом, а UI живой

## 2026-08-23 16:35 UTC — РЕПРО OWNER + КОРЕНЬ (живьё, прод v4-081ff4fb5)
Owner: док «Эйфель» (43 chars), Аналітика → вкладка «Сутності» = «прогрівається… / Базові сутності ще витягуються» >30 мин; нажал платное «Поглибити (~1 кредит)» — попап «списано 0 кредитів», вкладка висит, доступ к базовым сущностям заблокирован.

КОРЕНЬ (SELECT прод, source 1787498419553):
- metadata.entities ОТСУТСТВУЕТ, длина 0. meta keys = [topics, deepenAnalysisJob].
- deepenAnalysisJob: status=succeeded, percent=100, updatedAt 15:44:38Z, error=null, skippedReason=disabled, jobEntities=[], mode=maximum.
- maybePersistDocumentEntities вернул skippedReason=disabled: ENABLE_DOC_NER OFF в проде (documentEntityIngestRuntime.ts:90-107, default OFF, isDocNerEnabled=false, hard-return до OpenAI). Извлечение сущностей выключено флагом (память: NER-в-проде = owner-spend).

ДВА UI-БАГА (независимо от флага):
1. Вечное warming: StatsDialog.tsx:1616-1618 entityReady=(entities.length>0), entityWarming=(NOT entityReady). Нет терминального/disabled-состояния -> при NER OFF (или 0 сущностей, или succeeded-empty) вкладка ВЕЧНО disabled (:1904 disabled=(NOT entityReady)) и показывает warming. Юзер не откроет вкладку, не увидит «сущностей нет», не сделает retry.
2. Платная кнопка вхолостую: «Поглибити» (:1949/:1998) НЕ гейтится на entityReady/доступность NER. Job -> мгновенный skip(disabled) -> succeeded -> списание ~0 -> toast.success, хотя ничего не извлечено. Платное действие врёт про успех.

ЧТО НАДО (UI-честность, БЕЗ флипа флага — флип = отдельное owner-spend-решение):
- Пробросить в stats/documentStats API признак entitiesFeature=enabled|disabled. При disabled: не показывать warming вечно -> честный стейт «Розпізнавання сутностей вимкнено» или скрыть вкладку.
- «Поглибити» при disabled — не предлагать/дизейбл; никогда toast.success при skippedReason=disabled -> честный тост «функція недоступна», без списания.
- При NER ON: succeeded с 0 сущностей = открываемая вкладка с пустым стейтом (не warming); terminal fail = открываемый с retry.
- Связь: ENTITY-0750 (вынос сущностей во вкладку), WEB-095 (админ Мозг/NER), GRAPH-1093.

OWNER-РЕШЕНИЕ (телега): включать платный NER в проде ИЛИ чинить только честность UI, фичу держать тёмной. Рекомендация: сперва UI-честность, включение NER — отдельно по канарейке.

## 2026-08-23 16:42 UTC — OWNER-РЕШЕНИЕ: включить платный NER + честный UI (оба)
Owner (телега id=13004→ответ): «включаем и интерфейс тоже честный… как иначе проверять, всё выглядит багом, а должно списывать с баланса». → NER включён в проде.
СДЕЛАНО: SystemSetting ENABLE_DOC_NER='true' (owner-approved конфиг, не денежная таблица; upsert 16:40:39Z). Капы по умолчанию: месячный бюджет NER $2.0 (DEFAULT_MONTHLY_BUDGET_USD), модель gpt-5.4-nano (дешёвая), лимит 24 сущности/док (жёсткий 240), окна 8. Конфиг читается из БД на КАЖДОМ извлечении (loadDocNerDbConfig) — рестарт не нужен. NER-клиент = meterOpenAIClient (тот же guarded/egress-путь, что чат/фактчек, уже работают в проде) → egress+биллинг ок. Ключ OPENAI_API_KEY в проде есть.
ОСТАЁТСЯ (эта волна web338, на A1): честный UI обязателен ДАЖЕ с включённым NER — пустой стейт (0 сущностей), terminal-fail с retry, budget_exhausted, и не показывать вечный warming/ложный success. Owner проверит вживую (повторный клик «Поглибити» на «Эйфель» → реальное извлечение + микро-списание). Статус остаётся todo до посадки web338.

## 2026-08-23 17:09 UTC — web338 СДАН (A1, WEB338_FIX_READY), коммит 3fb54493e
Честный UI по всем состояниям: NER disabled → вкладка ОТКРЫВАЕТСЯ «Entity recognition is off», без спиннера, Deepen скрыт; enabled+running → warming только пока job активен; enabled+succeeded+0 → «No entities found» (не warming); enabled+failed → retry-стейт; enabled+>0 → готово. Клиент читает capability из /api/stats (loadDocNerDbConfig+isDocNerEnabled, БЕЗ импорта OpenAI; fail-closed → Deepen не показан). Skip/zero-fact → нейтральный тост «недоступно», НИКОГДА success/usage; job runner: любой ingest skippedReason = non-billable, skip settleHold, RELEASE preauth hold до succeeded (не списывает). Тесты 36/36 (вкл. негативы disabled/succeeded-empty/failed-retry/preauth-release), scoped tsc rc=0, generated без диффа. Груз посадки 38 (вместе с web340). ПРИМЕЧАНИЕ: preauth-release overlap с web340 hold-логикой — при сборке 38 мержить аккуратно.
Лента
2026-09-06T10:26:23.256Z · coordinator
[06.09 10:26Z координатор] [2026-09-06T10:26Z координатор] WEB-499: волна 2246 (A1, luna xhigh) сдала c157a3e1 (GO) поверх revive-identity v4 646e4967 — детерминированная identity операции doc_ner между возобновлениями, гвард считает попытки одного operationId. Приёмка 2264-accweb499 (A2, real PG).
2026-09-06T10:47:26.027Z · coordinator
[06.09 10:47Z координатор] [2026-09-06T10:47Z координатор] Приёмка WEB-499 (2264, A2, sol xhigh, real PG) = GO c157a3e1 (поверх 646e4967). В посадку пойдёт вместе с revive-identity кругом 5 (2272), т. к. база — 646e4967.
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \ -d '{"issueId":"WEB-338","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-23T21:38:17.378Z