Runbook: Systeme — обогащение товарных карточек
Severity (default): P2
Owner: Ассортимент / Catalog Core
Связанные логи: ingestion.bulk.card_details.fetched, ingestion.bulk.card_details.skipped, ingestion.bulk.card_details.freshness_failed
Симптом
У карточек Systeme есть только артикул или несколько базовых полей, хотя в публичной карточке https://api.systeme.ru/catalog/view/<артикул> видны ТН ВЭД, native-характеристики, ETIM, нетто-габариты и уровни брутто-упаковки. Либо карточки перестали обновляться после включения enrichment.
Диагностика
Сначала убедиться, что production-конфигурация включена через deploy-managed template, а не локальный override контейнера:
rg -n '^SUPPLIER_SYSTEME_CARD_DETAILS_(MAX_ITEMS_PER_TICK|REFRESH_TTL)=' deploy/prod-env-template.envПроверить, что существует ровно одна уже заведённая общая характеристика для ТН ВЭД. До её ручной модерации не создавать новую с похожим именем.
SELECT c.id, c.code, c.name, c.data_type, c.status,
count(m.id) AS mapping_count
FROM characteristics c
LEFT JOIN char_name_mappings m
ON lower(m.canonical_name) = lower(c.code)
OR lower(m.canonical_name) = lower(c.name)
WHERE lower(c.code) IN ('tnved', 'tn_ved', 'код_тн_вэд', 'код_тнвэд')
OR lower(c.name) IN ('код тн вэд', 'код тнвэд')
GROUP BY c.id, c.code, c.name, c.data_type, c.status
ORDER BY c.status DESC, c.code;Проверить целевую mapping и данные двух контрольных SKU после прохождения supplier-sync → charnorm → canonical assignment:
SELECT m.supplier, m.supplier_code, m.canonical_name, m.display_name_ru,
m.value_type, m.confidence, m.needs_review
FROM char_name_mappings m
WHERE m.supplier = 'systeme'
AND m.supplier_code = 'systeme/tnved';
SELECT o.supplier_sku, r.source_ref, r.supplier_code, r.display_name,
r.raw_value, r.observed_at
FROM supplier_offers o
JOIN offer_characteristic_raw r ON r.offer_id = o.id
WHERE o.supplier = 'systeme'
AND o.supplier_sku IN ('0N-1046SE', 'A9D49625')
ORDER BY o.supplier_sku, r.source_ref, r.supplier_code;Ожидаемое: у 0N-1046SE есть systeme/tnved = 8532220000; у A9D49625 — systeme/tnved = 8536201007, native/ETIM факты и source_ref = systeme_card. Полные брутто-упаковки и hierarchy находятся losslessly в supplier_offers.raw_attributes -> 'systeme_card'.
В live API поле systeme_card.product.brand приходит объектом с name, а не
строкой. Маппер обязан принимать обе формы; иначе ошибка декодирования одного
поля отбрасывает всю карточку, хотя lossless raw-снимок уже сохранён. Быстрая
проверка формы ответа:
SELECT supplier_sku,
jsonb_typeof(raw_attributes #> '{systeme_card,product,brand}') AS brand_shape,
raw_attributes #>> '{systeme_card,product,brand,name}' AS brand_name
FROM supplier_offers
WHERE supplier = 'systeme'
AND supplier_sku IN ('0N-1046SE', 'A9D49625');Ожидаемое production-значение brand_shape = object. После исправления
контракта повторный card tick должен создать rows с source_ref = systeme_card
и поставить отдельную freshness-метку этого source.
SELECT supplier_sku,
raw_attributes #> '{systeme_card,packingGross}' AS packing_gross,
raw_attributes #> '{systeme_card,hierarchy}' AS hierarchy
FROM supplier_offers
WHERE supplier = 'systeme'
AND supplier_sku IN ('0N-1046SE', 'A9D49625');Смягчение
Если официальный API возвращает 429/5xx, временно остановить только дополнительный карточечный поток: установить в deploy-managed конфигурации SUPPLIER_SYSTEME_CARD_DETAILS_MAX_ITEMS_PER_TICK=0 и выполнить обычный deployment. Базовый getdata/цена/остатки Systeme продолжат работать.
После deployment проверить, что прекращаются новые события ingestion.bulk.card_details.fetched, а catalog job завершается без ошибок. Не добавлять proxy pool и не переключаться на HTML-scraping.
Устранение root cause
- Исправить доступность/лимит официального
api.systeme.ruлибо контракт endpoint’а. - Вернуть ограниченный budget (начинать с
500) черезdeploy/prod-env-template.env; TTL720hозначает обновление только собственногоsysteme_cardsnapshot примерно раз в 30 дней. - Если
systeme/tnvedостался unmapped, повторить read-only query выше. После того как он вернул ровно одну существующую характеристику, вручную подставить её точныйcodeвместо<existing_tnved_code>:
INSERT INTO char_name_mappings
(supplier, supplier_code, canonical_name, display_name_ru,
unit_symbol, canonical_unit, value_type, confidence, needs_review,
version, inputs_hash, source, llm_model, updated_at)
VALUES
('systeme', 'systeme/tnved', '<existing_tnved_code>', 'Код ТН ВЭД',
NULL, NULL, 'string', 1.0, false,
1, 'manual:systeme/tnved:v1', 'manual', NULL, now())
ON CONFLICT (supplier, supplier_code) DO UPDATE SET
canonical_name = EXCLUDED.canonical_name,
display_name_ru = EXCLUDED.display_name_ru,
unit_symbol = NULL,
canonical_unit = NULL,
value_type = 'string',
confidence = 1.0,
needs_review = false,
version = char_name_mappings.version + 1,
inputs_hash = EXCLUDED.inputs_hash,
source = 'manual',
llm_model = NULL,
updated_at = now();- Run the normal charnorm/projector workload; do not write
canonical_assignmentsdirectly. The category classifier receives the resulting shared facts on its next unassigned-category pass.
Эскалация
- Если
api.systeme.rugives persistent 429/403/5xx for more than 30 minutes: Ассортимент + owner of supplier integration. - If the preflight query returns zero or more than one TН ВЭД characteristic: Catalog Core data steward; do not choose or create a duplicate automatically.
- If raw rows exist but canonical assignments do not appear after charnorm/projector: Charnorm / Catalog projector on-call.
Связано
rate-limit-exhausted.mdbackend/internal/core/ingestion/infra/systeme/card_details.gobackend/internal/core/normalization/infra/systeme/offer_mapper.gobackend/internal/core/categoryclass/infra/postgres/unassigned_reader.go