Runbook: cold-start product completeness (empty prod DB → full DoD)

Как на ПУСТОЙ prod-базе довести каждый товар до полноты: категория + характеристики + цена + остаток. Основано на live-диагностике 2026-05-29/30. DoD-запросы внизу — проверяй РЕАЛЬНЫЕ карточки, не «воркеры запущены».

Архитектурный принцип (важно понять)

Большая часть LLM-работы — построение СЛОВАРЕЙ, ограниченных числом УНИКАЛЬНЫХ кодов, НЕ числом товаров. Это разовый bootstrap + малый инкремент:

СлойLLM маппитОбъёмРеюз
charnormsupplier char-код → каноническая характеристика~уникальные коды (тысячи)1М+ raw-chars проецируются детерминированно
valuenormenum-значение → каноническоеуникальные значенияреюз
taxonomysupplier_native категория → canonical дерево~уникальные nativeреюз
classifierтовар → категорияper-товардолжен идти fast-path через mapping, LLM только fallback

После словарей обогащение детерминированное (без LLM). LLM на каждый товар — только classifier fallback когда native→canonical mapping пуст.

LLM throughput (критично для bootstrap скорости)

  • Модель PER-TASK (важно, live-замерено):
    • charnorm / classifierclaude-sonnet-4-6: ~2x быстрее gpt-5.4 на batch (40s vs 84s/chunk), НЕ ловит context-deadline, И classifier assign-rate 21% vs 2% у gpt-5.4 (10x точнее категоризирует). gpt-5.x reasoning на больших output (4300 токенов) = 84s + таймауты + ДРОП работы.
    • taxonomy-buildergpt-5.4 (TAXONOMY_BUILDER_LLM_MODEL, ОБЯЗАТЕЛЬНО): gpt-5.4 отдаёт строгий JSON. Если основной JSON-strict model уходит в cooling-down/rate-limit, переключай на live-проверенную fallback-модель через TAXONOMY_BUILDER_LLM_MODEL; builder умеет извлекать JSON из fenced/prose ответа, но downstream LLM всё равно держи на паузе, чтобы не съесть quota.
    • Коннект к gateway быстрый (минимальный вызов 2.7s — НЕ bottleneck).
  • Параллелизм: CHARNORM_CONCURRENCY/CATEGORY_CLASSIFIER_CONCURRENCY задаются явно под текущий provider pool. Если gateway отдаёт 429 Too Many Requests / all credentials ... cooling down, сначала снижай LLM_MAX_CONCURRENCY и per-worker concurrency, а не увеличивай batch size.
  • valuenorm throughput: VALUENORM_BATCH_SIZE задаёт сколько unmapped rows берём за tick, VALUENORM_CONCURRENCY параллелит LLM-группы внутри tick. Для cold-start держи VALUENORM_BATCH_SIZE=1000, VALUENORM_MAX_GROUP_SIZE=50, но подбирай VALUENORM_CONCURRENCY по фактическим 429/cooldown логам.
  • category reclassify throughput: после tree-rebuild очередь canonical_reclassify_queue должна разгребаться тысячами строк за tick: CATEGORY_RECLASSIFY_BATCH_SIZE=5000+. Исторический batch 50 превращает крупный rebuild в многодневный backlog и визуально выглядит как «категории не работают».
  • matcher Tier 3 output budget: WorkloadMatcher использует увеличенный JSON output budget (4096 tokens). Если видишь tier3 chunk dropped + unexpected end of JSON input, это обычно не provider outage, а усечённый JSON: не уменьшай весь MATCHER_BATCH_SIZE вслепую, сначала проверь output budget и chunk size.
  • Template parity: deploy/prod-env-template.env должен оставаться на той же стратегии: CATEGORY_CLASSIFIER_LLM_MODELS=claude-sonnet-4-6, а gpt добавляется только временным fallback при provider cooldown.
  • LLM JSON tolerance: taxonomy-builder, category-classifier и valuenorm должны принимать не только чистый JSON, но и fenced/prose-wrapped JSON. Live Sonnet иногда отвечает коротким текстом перед блоком JSON даже при JSONMode; при логах invalid character 'I' looking for beginning of value не меняй модель вслепую, сначала проверь extractor/парсер. Если ответ содержит ранний не-JSON code fence перед итоговым объектом, extractor должен брать первый полноценный JSON object, а не первый fence. category-classifier дополнительно делает parse-retry и пишет только короткий response_preview, чтобы понять, вернула ли модель JSON, без вывода полного payload. Если превью говорит I don't see a specific question or task, значит gateway/model не использовал system prompt как инструкцию: продублируй задачу и JSON-схему в начале user prompt.
  • Taxonomy rebuild parallelism: TAXONOMY_BUILDER_LLM_CONCURRENCY ограничивает одновременные LLM-вызовы по независимым builder chunks. Default 1 сохраняет старый безопасный режим; для cold-start/rebuild поднимать до 2-4, если LLM_MAX_CONCURRENCY и provider rate limit оставляют запас.
  • Round-robin моделей: CHARNORM_LLM_MODELS/CATEGORY_CLASSIFIER_LLM_MODELS csv — распределяет нагрузку на gateway между провайдерами.
  • Timeout: LLM_TIMEOUT_SECONDS=240, CHUNK_SIZE=40 (>40 рискует deadline).

Cold-start последовательность

Используй deploy/prod-env-template.env cold-start значения (в скобках комментов: tick 1-2m, RUN_ON_START=true, batch крупнее, concurrency 8-16).

  1. Миграции — первый boot с POSTGRES_MIGRATE_ON_START=true (потом → false).
  2. Credentials — supplier creds через Vault → cmd/seed-credentials → active rows в supplier_credentials (system scope). Без них supplier-sync не тянет.
  3. supplier-sync (catalog + commerce + details тики):
    • catalog → supplier_offers + цена.
    • details (ETM SUPPLIER_ETM_DETAILS_SKUS/enumerator; IEK details entity=all) → offer_characteristic_raw (богатые сырые характеристики).
    • commerce → offer_observations.stock_current (ETM warehouse qty, russvet residue, systeme; IEK остаток зависит от наличия stock-endpoint — см. iek connector). Остаток у товара = столько, сколько отдаёт поставщик.
    • category source различается: systeme отдаёт native-категорию; russvet — в specs-фазе (RS_CATALOG/ETIM_CLASS, rate-limited — дать добежать; raw_attributes.category ЗКЗ/НОВ = статус наличия, НЕ таксономия); iek — нет в фиде (только LLM-классификатор).
  4. charnorm-worker (parallel, sonnet, multi-model): дренирует characteristic_value_unmapped/char-коды → char_name_mappings. Bounded уникальными кодами — завершается (live: ~3259 кодов → 0). После — facts проецируются. Pause-флаг WORKER_PAUSED_CHARNORM_WORKER должен быть проброшен в compose так же, как у matcher/valuenorm/category: иначе при llm_global_pause=true worker будет каждую минуту стартовать, получать platform.llm.paused и писать error-такты без полезного прогресса.
  5. taxonomy bootstrap (КРИТИЧНО для категорий):
    • cmd/category-tree-builder --mode=full строит canonical дерево из текущих supplier_native → cmd/category-tree-publishcategory_canonical_mapping (native→canonical). Без этого classifier падает в медленный per-товар LLM (~2% assign rate) вместо fast-path.
    • На время tree build/publish держи downstream LLM-потребителей на паузе, но не останавливай non-LLM assignment fill: WORKER_PAUSED_VALUENORM_WORKER=true, WORKER_PAUSED_MATCHER_WORKER=true, WORKER_PAUSED_CANONICAL_ASSIGNMENT_WORKER=, WORKER_PAUSED_CANONICAL_ASSIGNMENT_WORKER_RESOLVER=true, WORKER_PAUSED_CATEGORY_CLASSIFIER=true, WORKER_PAUSED_CHARNORM_WORKER=true, CANONICAL_ASSIGNMENT_BATCH_SIZE=1000, CATEGORY_CLASSIFIER_MAX_BATCHES=1. Иначе deploy может перезапустить LLM-потребителей, они начнут тратить лимит на pre-tree fallback и ловить 429; если поставить на паузу сам canonical-assignment-worker, карточки не будут получать уже готовые facts.
    • После publish дерева возвращай category warm-up в post-tree режим: WORKER_PAUSED_CATEGORY_CLASSIFIER=false, CATEGORY_CLASSIFIER_BATCH_SIZE=50, CATEGORY_CLASSIFIER_MAX_BATCHES=4, CATEGORY_CLASSIFIER_CONCURRENCY=1. На prod 2026-06-15 этот режим держал примерно 130 canonical/min без OOM; MAX_BATCHES=1 оставляет только 50 canonical/tick и растягивает прогрев.
    • Builder перед валидацией чинит LLM-slug в ASCII kebab-case и синхронно переписывает parent_slug/canonical_slug. Это защищает full build от confusable-символов вроде кириллической о внутри визуально латинского slug и восстанавливает отсутствующий slug из name, чтобы один неполный LLM-node не валил весь multi-chunk build перед persist.
    • Если full build упал до persist на validator error, перезапускай builder тем же входом после hotfix: taxonomy_builds и category_canonical_mapping остаются пустыми.
    • taxonomy-orchestrator (P3) мониторит drift + уведомляет о готовности к rebuild (manual-default).
    • Ошибки builder CLI/LLM adapter должны оставаться typed platform/errors с safe details (env, attempt, reason), иначе CI backend-checks блокирует релиз: это нужно, чтобы prod-runbook видел причину падения без вывода секретов или сырого payload.
  6. category-classifier (parallel, sonnet): fast-path через mapping (без LLM, массово) + LLM fallback для непокрытых. CONFIDENCE_FLOOR=0.25. После tree-rebuild проверь canonical_reclassify_queue: если pending сотни тысяч, подними CATEGORY_RECLASSIFY_BATCH_SIZE до тысяч перед запуском FIFO LLM.
  7. valuenorm-worker: нормализует enum-значения характеристик. На холодном старте не оставляй последовательный режим без причины, но при provider cooldown держи concurrency низким и меряй tick summaries вместо слепого увеличения параллелизма.
  8. canonical-assignment-worker: обогащает canonical_assignments (характеристики на canonical-уровень из mapped facts) — детерминированно, без LLM. В production composition worker обязан подключать catalog-projector.queue emitter: каждый успешный upsert характеристик должен добавлять assignment.upserted в catalog_projector_queue. Иначе Postgres уже наполнен, но ES-backed admin/catalog остаётся пустым по характеристикам. После исторического facts-projector seed пустые canonical без единого canonical_assignments должны выбираться отдельным unassigned-priority probe перед recent-first dirty rows. Иначе новые supplier updates могут постоянно отодвигать старые карточки с готовыми facts, и UI будет выглядеть пустым несмотря на заполненный offer_characteristic_facts.
  9. catalog-projector / reconciler: проекция в ES для search/analogs. При большом backlog catalog_projector_queue сначала подними batch/poll (CATALOG_PROJECTOR_BATCH_SIZE=2000, CATALOG_PROJECTOR_POLL_INTERVAL_MS=500), затем временно масштабируй catalog-projector через docker compose up -d --scale. Очередь использует FOR UPDATE SKIP LOCKED + CATALOG_PROJECTOR_CLAIM_TTL, поэтому несколько реплик берут непересекающиеся пачки; после drain верни одну реплику. Ручная кнопка POST /api/v1/admin/catalog/projector/reindex должна быть смонтирована через reconciler control-модуль в api-server: status-only wiring показывает метрики, но не может поставить hot reindex run.

Порядок зависимостей: offers+raw (3) → char-mappings (4) → facts → canonical chars (8); параллельно native→canonical (5) → categories (6). Не запускай pre-tree category-classifier/valuenorm/matcher; после publish сними WORKER_PAUSED_* и дренируй downstream очереди.

DoD-проверка (РЕАЛЬНЫЕ карточки, не статус воркеров)

-- Полнота по всем активным товарам
SELECT count(*) AS total,
  count(*) FILTER (WHERE EXISTS(SELECT 1 FROM canonical_category_assignments c WHERE c.canonical_id=cp.id)) AS with_category,
  count(*) FILTER (WHERE EXISTS(SELECT 1 FROM canonical_assignments a WHERE a.canonical_id=cp.id)) AS with_chars,
  count(*) FILTER (WHERE EXISTS(SELECT 1 FROM supplier_offers so JOIN offer_observations o ON o.offer_id=so.id WHERE so.canonical_id=cp.id AND o.prices<>'{}'::jsonb)) AS with_price,
  count(*) FILTER (WHERE EXISTS(SELECT 1 FROM supplier_offers so JOIN offer_observations o ON o.offer_id=so.id WHERE so.canonical_id=cp.id AND o.stock_current<>'{}'::jsonb)) AS with_stock
FROM canonical_products cp WHERE status='active';
 
-- char-mapping словарь добит? (0 = готово)
SELECT count(DISTINCT supplier_code) FROM offer_characteristic_raw WHERE mapping_id IS NULL;
 
-- native→canonical mapping coverage
SELECT (SELECT count(*) FROM category_canonical_mapping) AS mapped,
       (SELECT count(*) FROM categories WHERE tree_type='supplier_native' AND state='active') AS native_total;
 
-- drift / health
SELECT * FROM v_taxonomy_health;

Критерий готовности: with_chars/with_category → высокий % (остаток/цена ограничены тем, что отдают поставщики). Конкретный товар в admin: «Характеристики» не пусто, «Категория» не «—».

Известные ограничения по поставщикам

  • Остаток: ETM (warehouse qty) ✓, russvet residue (частично) ✓, systeme ✓, IEK — зависит от stock-endpoint (см. iek connector capabilities).
  • Категория в фиде: systeme ✓, russvet (в specs, rate-limited), IEK ✗ (LLM).
  • Характеристики: богатые у IEK details (34/товар) — но требуют charnorm mapping; ETM details; russvet specs.

Ссылки

  • deploy/prod-env-template.env — cold-start значения per-worker.
  • docs/superpowers/plans/2026-05-29-prod-2m-readiness-plan.md — 2M scale.
  • Memory: tracium-dod-completeness-diagnosis-2026-05-29 (per-supplier per-link диагноз).