Charnorm

NOTE

Статус: Live (HEAD 5e4ce54, 2026-05-06). BC введён в цикле Charnorm LLM gateway (ADR-0044, CLOSED 2026-04-29). Отдельный worker cmd/charnorm-worker обрабатывает batch’и характеристик.

LLM-нормализация имён характеристик: на входе сырые (supplier, supplierCode, displayName) от ingestion; на выходе — единое имя характеристики из catalog’а. Использует Claude Sonnet через platform/llm (CLIProxy + sashabaranov SDK).

Назначение

Привести зоопарк имён характеристик от поставщиков (например, «Цвет», «Color», «Окраска корпуса», «Цветовая гамма») к каноническому имени из catalog/characteristic. Без charnorm’а matcher Tier-1/2/3 захлёбываются на noise’е поставщикских названий.

Статус документа

  • Тип знания: current service
  • Статус реализации: cmd/charnorm-worker отдельный процесс + internal/core/charnorm/{domain,app,infra} BC. Idempotency через inputs_hash.
  • Текущее место кода: backend/internal/core/charnorm/, backend/cmd/charnorm-worker/, backend/internal/platform/llm/ (CLIProxy adapter).
  • Что читать дальше: ../../20-architecture/adr/0044-llm-gateway-char-pipeline.md.

Область действия

Входит:

  • Чтение offer_characteristic_raw (наполняется ingestion’ом).
  • Промпт-инжиниринг и retries для Sonnet через platform/llm.
  • Маппинг (supplier, supplierCode, displayName) → CharacteristicName.
  • Запись результата в char_name_mapping с inputs_hash (идемпотентность).
  • Метрики: charnorm_requests_total, charnorm_llm_tokens_used_total.

Не входит:

  • Нормализация значений характеристик (это catalog/characteristic).
  • Сами LLM вызовы — abstracted через platform/llm.Client.
  • Принятие решений по матчингу — это matching BC.

Публичный контракт

Вход

  • Ticker внутри cmd/charnorm-worker запускает Normalizer.RunBatch каждые N минут (env CHARNORM_TICK_INTERVAL).
  • RunBatch читает unprocessed строки offer_characteristic_raw.

Выход

  • Запись в char_name_mapping (PG).
  • Outbox events charnorm.mapping_resolved.v1.
  • Метрики Prometheus.

Внутренняя архитектура

  • domain/mapping.goCharNameMapping aggregate + ComputeInputsHash(supplier, supplierCode, displayName, model, promptVersion).
  • domain/prompt.go — versioned prompt template (promptVersion участвует в hash).
  • app/Normalizer.RunBatch — основной use case.
  • infra/llm/ — wrapper над platform/llm.Client.
  • infra/pg/ — repos для raw + mapping.

Malformed LLM responses

PromptVersion=6 явно требует от LLM возвращать JSON даже для неясных характеристик: низкая confidence, needs_review=true, без вопросов и пояснений вне JSON. Это защищает прогрев от ответов вида “нужно больше контекста”.

Если после всех parse-retry ответ всё равно невалидный JSON, Normalizer не дропает chunk бесконечно. Он создаёт низкоуверенный review-mapping:

  • canonical_name = fallback_<supplier>_<supplier_code> (или hash suffix, если код нельзя безопасно привести к canonical key);
  • value_type = string;
  • confidence = 0.051, needs_review = true.

Значение 0.051 намеренно выше stale-retry порога ListUnmapped (<=0.050), чтобы один и тот же supplier code не возвращался в LLM на каждом тике и не сжигал токены. Такие записи остаются видимыми для модерации и не считаются автоодобренными.

Зависимости

  • PostgreSQLoffer_characteristic_raw, char_name_mapping.
  • platform/llm — Anthropic Claude Sonnet через CLIProxy.
  • catalog/characteristic — read-only словарь канонических characteristics.

Хранилище

ТаблицаRead/Write
offer_characteristic_rawRead (наполняется ingestion’ом)
char_name_mappingWrite (inputs_hash idempotency UNIQUE)
outbox_eventsWrite

Конфигурация

Env varDefaultОписание
CHARNORM_TICK_INTERVAL5mИнтервал между батчами
CHARNORM_BATCH_SIZE50Размер батча на тик
CHARNORM_LLM_MODELclaude-sonnet-4-6Имя модели; меняет inputs_hash
CHARNORM_LLM_TIMEOUT30sTimeout на одну mapping-операцию

Локальный запуск

cd backend
go run ./cmd/charnorm-worker

Требует CLI_PROXY_URL (CLIProxy LLM-gateway, см. tracium_charnorm_llm_gateway_execution).

Тестирование

  • Unit: go test ./internal/core/charnorm/...
  • Integration: -tags=integration для infra/pg/ (testcontainers).
  • Prompt regression: prompt_test.go snapshot’ит шаблон, изменение требует bump promptVersion.

Наблюдаемость

  • Metrics: charnorm_requests_total{outcome}, charnorm_llm_tokens_used_total{direction}, charnorm_llm_calls_total{result}.
  • Phase logs: charnorm.batch.phase через StartPhase.

Связанные документы

  • ADR: 0044.
  • Memory: tracium_charnorm_llm_gateway_execution.md.
  • Соседи: matching BC (потребитель), catalog/characteristic (словарь).