Маппинг ETM → Canonical
NOTE
Статус: Target service boundary. Документ описывает целевую сервисную границу. Код либо полностью отсутствует, либо существует только как scaffold — смотрите секцию «Статус документа» ниже для точного указания на код. Правила маркировки — в
50-processes/documentation-standard.md.
Как поля ETM API ложатся на нашу внутреннюю модель.
Важно: модель разделяет SupplierOffer (товарная канва) и SupplierOfferObservation (наблюдения per credential). Цены и остатки от ETM ложатся в Observation, а характеристики/MPN/упаковка — в Offer. См. ../../../../10-business/contexts/credentials.md.
Высокоуровнево — что куда
В SupplierOffer (товарная канва, общая для всех credentials)
| ETM | Наша модель |
|---|---|
gdscode | supplier_offer.supplier_sku |
mnf_code + mnf_name | manufacturer_alias (supplier_id="etm", external_code=mnf_code) → manufacturer_id |
art | supplier_offer.manufacturer_sku_parsed |
gdsClassTree[] | supplier_offer.classification_tags[] |
gdsChars[] | supplier_offer.characteristics_from_supplier[] |
certificates[] | supplier_offer.certificates[] |
gdsPacks[] | supplier_offer.packaging[] + производные характеристики (вес, габариты) |
gdsImages[] | supplier_offer.media[type=image] |
gdsVideos[] | supplier_offer.media[type=video] |
В SupplierOfferObservation (per credential, append-only)
| ETM | Наша модель |
|---|---|
price, pricewnds, price_tarif, price_retail | observation.price_* (см. ниже) |
| Все цены = 0 | observation.pricing_mode = on_request |
InfoStores[] | observation.stock_by_warehouse[] |
InforDeliveryTime | observation.delivery_terms[] |
Ключевое: одна и та же позиция (gdscode) даёт один SupplierOffer, но N observations по числу credentials, через которые она была опрошена.
Ценообразование
| ETM цена | Поле в observation |
|---|---|
price (без НДС) | price_base, currency=RUB, vat_included=false |
pricewnds (с НДС) | price_with_vat |
price_tarif (тариф производителя) | price_tariff |
price_retail (розничная) | price_retail |
| Все = 0 | pricing_mode = on_request, все цены = null |
Структура observation по цене:
supplier_offer_observation
├── id
├── supplier_offer_ref
├── credential_ref # КАКОЙ credential дал эту цену
├── credential_scope # system | customer
├── customer_ref # денорм. для запросов
├── pricing_mode # fixed | on_request
├── currency # "RUB"
├── price_base # = ETM price
├── price_with_vat # = ETM pricewnds
├── price_tariff # = ETM price_tarif
├── price_retail # = ETM price_retail
├── observed_at
└── raw_payload_ref
Важно: одна позиция через 5 разных credentials (1 system + 4 customer) даёт 5 разных observations с потенциально разными ценами — и это нормально, это и есть смысл договорных цен ETM.
Остатки
Также пишутся в supplier_offer_observation (вместе с ценами или отдельным observation, в зависимости от того, через какой endpoint получены).
Из /goods/{id}/remains:
| Поле ETM | Наше поле |
|---|---|
InfoStores[].StoreCode | observation.stock_by_warehouse[].warehouse_code |
InfoStores[].StoreType | observation.stock_by_warehouse[].warehouse_type (rc/crs/op) |
InfoStores[].StoreName | observation.stock_by_warehouse[].warehouse_name (кэшируется) |
InfoStores[].StoreQuantRem | observation.stock_by_warehouse[].quantity |
InfoStores[].StoreDate | observation.stock_by_warehouse[].observed_at |
InfoSuppStores[] | observation.stock_by_warehouse[warehouse_type=manufacturer] |
InforDeliveryTime.DeliveryTimeInPres | observation.delivery_terms.days_in_stock |
InforDeliveryTime.DeliveryProductionTerm | observation.delivery_terms.days_production |
Из /goods/remains?store=X:
| Поле ETM | Наше поле |
|---|---|
StoreCode | observation.stock_by_warehouse[].warehouse_code |
GdsCode | supplier_sku |
Article | — (сверка) |
RemInfo | observation.stock_by_warehouse[].quantity (в минимальных единицах упаковки!) |
Важно: warehouse_scope credential может ограничивать список складов, к которым у этой credential есть доступ. Соответственно у разных credentials наблюдаемые stock_by_warehouse[] могут отличаться.
Внимание: RemInfo в минимальных единицах упаковки (см. gdsPacks.minPack), а не в штуках. Нужна нормализация в штуки для унификации.
Характеристики (gdsChars)
Самая богатая часть. Маппинг ведётся через дополнительную таблицу supplier_attribute_alias:
supplier_attribute_alias
├── supplier_id : "etm"
├── external_key : ConfigCharCode (например, "132")
├── external_name : gdsCharName (например, "Напряжение, В")
├── our_characteristic : characteristic.key (например, "voltage")
├── value_transform : правило трансформации значения (см. ниже)
├── source : manual / ml_inferred / from_catalog
├── confidence
└── approved_by
Трансформация значений
Примеры:
voltage: gdsCharVal=“660” → распарсить как число → 660 V (единица из gdsCharName «В»).protection_class: gdsCharVal=“IP54” → нормализовать «IP54» → enum-значение «IP54».current_type: gdsCharVal=“Переменный (AC)” → нормализовать → enum-значениеac.weight: gdsCharVal=“0.71” → value=0.71, unit=kg (kg из gdsCharName «Масса, кг»).
Значения с ConfigCharIdVal
ETM даёт ConfigCharIdVal — числовой id значения в их классификаторе. Это полезно для enum-характеристик: мы можем хранить соответствие (etm_config_char_code, etm_config_char_id_val) → our_characteristic_value_id и использовать для быстрого матчинга значений «Серый» = «Gray» = «Graphite» между поставщиками.
Пример характеристик (из реального ответа)
Вход:
{"gdsCharName": "Напряжение, В", "gdsCharVal": "660", "ConfigCharCode": "132", "ConfigCharIdVal": 135}Маппинг (после approval):
{
"supplier_id": "etm",
"external_key": "132",
"external_name": "Напряжение, В",
"our_characteristic": "voltage",
"value_transform": {"type": "number_with_unit", "unit": "V"},
"source": "manual",
"approved_by": "moderator-ivan"
}На выходе supplier_offer.characteristics содержит:
{"key": "voltage", "value": 660, "unit": "V", "original": "660"}Производители (mnf_code → manufacturer)
Маппинг через таблицу manufacturer_alias:
manufacturer_alias
├── manufacturer_id : uuid нашего canonical manufacturer
├── supplier_id : "etm"
├── external_code : mnf_code (например, "48")
├── external_name : mnf_name (например, "Электротехник")
├── source : from_dict / from_offer / manual
└── confidence
Инициализация dictionary: ручной прогон /info/search/r-manuf/ и занесение всех производителей в manufacturer_alias (со статусом from_dict, высокий confidence). Новые производители, появляющиеся в offer’ах, но отсутствующие в dictionary, попадают в очередь модерации.
Классификация (gdsClassTree)
Не используется как иерархия категорий. Сохраняется как набор tags:
[
{"source": "etm", "key": "class_50", "value": "Оборудование низковольтное"},
{"source": "etm", "key": "class_5030", "value": "Кнопки, кнопочные посты, ..."},
{"source": "etm", "key": "class_503025", "value": "Кнопочные посты"}
]Используется для:
- UX-фильтров в админке («покажи все с class_50»).
- Подсказок для matcher’а Identity Profile (
object_type == "пост кнопочный"может подтверждаться наличиемclass_503025).
НЕ используется для identity_signature.
Упаковка и весогабариты (gdsPacks)
packaging
├── supplier_id : "etm"
├── supplier_sku
├── pack_code : "1"
├── pack_name : "шт"
├── quantity : 1
├── weight_kg : 0.694
├── length_m : 0.16
├── width_m : 0.1
├── height_m : 0.1
└── volume_l : 1.6
Плюс производные характеристики:
weight= 0.694 kg.dimensions= {L:0.16, W:0.1, H:0.1, unit: m}.
Медиа (gdsImages, gdsVideos)
Для каждого изображения / видео:
- Берём URL (префикс
https://cdn.etm.ru+gdsImgRef/gdsVidSrc). - Скачиваем (по расписанию, не при каждом просмотре).
- Сохраняем в наш S3
media/etm/<yyyy>/<mm>/<dd>/<hash>.<ext>. - Получаем наш URL, прокидываем в CDN.
Изображения с водяными знаками ETM использовать нельзя → запросить у менеджера ETM доступ к изображениям без водяных знаков (см. spec.md, раздел Goods).
Сертификаты
Для каждого certificates[]:
- Скачиваем PDF.
- Сохраняем в S3
certificates/etm/<hash>.pdf. - Сохраняем метаданные (name, type) в таблицу
certificate. - Связываем с canonical product (если там нет более канонического).
Единицы измерения (edizm)
ETM edizm:
"p"→unit = "pcs"(штука)."m"→unit = "m"."kg"→unit = "kg".- … (полный список — запросить у менеджера / собрать эмпирически).
Хранится как единица продажи в supplier_offer.unit_of_sale.
Стратегия «тип кода»
- Работаем по
type=etm(стабильный, числовой). type=cliиспользовать только если заведены соответствия в системе ETM.type=mnf— для быстрой проверки артикула производителя, но не для регулярных fetch’ей.
Нормализация артикулов
ETM art (артикул) часто имеет префикс «ET», «К» и т. п. При матчинге:
- Сохраняем оригинал в
supplier_offer.manufacturer_sku_parsed. - Нормализуем для сравнения (удаляем внутренние ET-префиксы вида «ET054487»), более каноничным считаем
mnf_code + artбез префикса поставщика.