Живая актуализация цен и остатков (live-refresh)

Публичное API отдаёт цены/остатки по состоянию базы Tracium. Когда клиенту нужна актуальная цена и наличие «на сейчас» (просчёт сметы перед коммерческим предложением), система по запросу опрашивает поставщика напрямую:

  • под кредами клиента (кабинет → «Учётные данные») — приоритет: поставщик отдаёт договорные цены клиента;
  • под системными кредами Tracium — fallback: публичные цены поставщика;
  • деградация до базы — если живой опрос невозможен: позиция возвращается с cached-данными и причиной.

Каждый успешный живой ответ прогревает базу (записывается наблюдение) — данные для всех последующих запросов становятся свежее.

Метки источника (на каждый оффер каждой позиции)

sourceЗначение
live_customerЖивой ответ поставщика под кредами клиента (договорная цена)
live_systemЖивой ответ под системным кредом (публичная цена)
cachedЖивой опрос не удался — данные из базы; поле reason объясняет причину

reason: no_credentials (нет ни клиентских, ни системных кредов), rate_limited (бюджет запросов к поставщику исчерпан), supplier_error, supplier_timeout, not_supported (поставщик без point-опроса: сейчас russvet, dkc; или неизвестный), not_found (позиция не раскрылась в офферы). При source=cached поля price/stock в ответе refresh-контура НЕ заполняются (observed_at=null) — клиент сохраняет ранее полученные cached-значения из сметы/каталога.

Сценарии

1. Точечная синхронная проверка (до 20 позиций)

POST /v1/offers/refresh — ждёт до timeout_seconds (1–30, дефолт 15) и возвращает 200 с частичным результатом: что успело — live, что нет — cached с причиной, partial=true. job_id в ответе продолжает жить — недостающее дочитывается опросом job-endpoint’а.

curl -sS https://api.tracium.ru/v1/offers/refresh \
  -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
  -d '{"positions":[{"supplier":"etm","supplier_sku":"9145661"},
                    {"product_id":"<canonical-uuid>"}],
       "targets":["price","stock"],"timeout_seconds":15}'

2. Асинхронная актуализация списка (до 500 позиций)

# создать задачу → 202 {job_id,...}
curl -sS -X POST https://api.tracium.ru/v1/offers/refresh-jobs \
  -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
  -d '{"positions":[...],"targets":["price","stock"]}'
# инкрементальный опрос по курсору (как estimate jobs)
curl -sS "https://api.tracium.ru/v1/offers/refresh-jobs/$JOB?after=$CURSOR" \
  -H "Authorization: Bearer $API_KEY"
# отмена
curl -sS -X POST "https://api.tracium.ru/v1/offers/refresh-jobs/$JOB/cancel" \
  -H "Authorization: Bearer $API_KEY"

Scope ключа: offers:refresh.

3. Актуализация внутри расчёта сметы

POST /v1/estimates/jobs, в estimate-request.v1 добавляется:

"refresh": {"enabled": true, "targets": ["price","stock"], "include_alternatives": true}

Каждая строка досчитывается живыми данными по выбранному предложению (+ до 3 альтернатив-аналогов при include_alternatives) ДО записи строки — опрос задачи и WS-поток сразу отдают актуализированные строки: price.source_mode="live", price.live_source, price.refreshed_at; при неуспехе — прежние cached-значения + stock.live_attempt_reason. Синхронный POST /v1/estimates/proposals флаг игнорирует (обратная совместимость 1С).

4. Подписка на события задач (не-1С клиенты): WebSocket

GET wss://api.tracium.ru/v1/jobs/{kind}/{id}/events, kind: offers-refresh | estimates. Auth — тот же Bearer API-ключ.

Протокол: после upgrade клиент МОЖЕТ первым сообщением послать {"after": <seq>} (5с на решение; молчание = 0). Сервер отдаёт replay строк с seq > after, затем live-хвост:

{"type":"line","seq":7,"line":{…тот же объект, что lines[] в REST…}}
{"type":"progress","seq":7,"job":{"status":"running","progress":{"total":20,"done":7,"percent":35}}}
{"type":"final","seq":20,"job":{"status":"completed"}}   → Close 1000
{"type":"lagged"}   → переподключиться с последним увиденным seq

Дедупликация — по seq (at-least-once). Ping/pong стандартные, idle 60с. 1С WebSocket не поддерживает — остаётся REST-опрос по курсору (эквивалентные данные).

5. gRPC (контракт, сервер позже)

docs/docs/20-architecture/schemas/api/tracium/v1/job_events.proto + README рядом — та же схема событий, JobEvents.Watch(job_kind, job_id, after_seq) → stream JobEvent.

Гарантии и ограничения

  • Позиция никогда не «роняет» задачу: худший исход — cached + reason.
  • Живые ответы идемпотентно прогревают offer_observations (клиентский кред → наблюдение уровня клиента, системный → общее).
  • Rate-бюджеты кредов общие с ingestion — refresh не может «выесть» лимиты поставщика бесконтрольно.
  • Поставщики v1 с живым опросом: etm, iek, systeme. russvet (bulk-only API) и dkc (auth-модель) — not_supported, follow-up.