Russvet connector

backend/internal/core/ingestion/infra/russvet — пятый адаптер supplier-connector контракта (ADR-0024). Интегрирован в HEAD 689fb04 (2026-05-06). Routes через BulkSnapshotPipeline (pipelines.md) благодаря batch-first профилю API.

Internal contract notes — docs/superpowers/specs/2026-05-06-russvet-api-contract-notes.md.

Transport + endpoints

REST/JSON over HTTPS, HTTP Basic auth (login + password).

PathKindPurpose
GET /rs/stocksdictionaryсловарь stocks (warehouses)
GET /rs/position/{stock}/allcatalogпозиции каталога по stock+category
GET /rs/masspricepricebulk-цены (используется в scheduled refresh)
GET /rs/residue/all/{stock}stockвсе остатки по stock
GET /rs/partnerwhstock/all/{stock}stockpartner-warehouse stocks
GET /rs/specs/{code}specхарактеристики per-SKU (throttled)

Per-SKU GET /rs/price/{code} есть, но в scheduled refresh не используется — только для live-lookup или fallback diagnostics.

Auth

Authorization header формата Basic base64(login:password). Helper russvet.BasicAuthHeader(login, password) собирает строку.

SessionManager читает creddom.SecretsRepository ({login,password} JSON), при отсутствии — fallback на env (Russvet.Login / Russvet.Password). SessionHandle.AuthSchema = login_password, ValidUntil заведомо большой (100 лет) — Basic не имеет TTL.

В cabinet supplier_catalog Russvet регистрируется с auth_scheme=basic — те же поля login + password, что у ETM/IEK.

Capabilities (static)

backend/internal/core/ingestion/infra/russvet/capabilities.go:

  • Transport: REST.
  • PriceSet: net + gross + list_tarif + retail_rec.
  • WarehouseKinds: regional_center + manufacturer_warehouse.
  • Media: images + videos + certificates (no watermark).
  • CodeTypes: cli + manufacturer.
  • Taxonomies: ETIM (native), no dictionary export.
  • IncrementalSync: false (full bulk only).
  • ScheduledRefresh:
    • Catalog: paged
    • Prices: batch (massprice)
    • Stock: batch_by_warehouse
    • Specs: per_sku_throttled
  • MultiCurrency: RUB only, parallel fetch.

UsesBulkSnapshot() returns true ⇒ orchestrator маршрутизирует через BulkSnapshotPipeline (см. pipelines.md).

Rate limit

API budget — 150 req / 30 sec per service. Превышение возвращает 403 и блокирует доступ примерно на час.

Конфигурация в platform/config/config.go:

type Russvet struct {
    BaseURL              string
    Login                string  // optional, fallback если SecretsRepository пуст
    Password             string  // optional
    PositionPageSize     int     // 1..1000
    ResiduePageSize      int     // 1..200
    PartnerStockPageSize int     // 1..500
    CatalogMaxItems      int     // 0 = unlimited; >0 только для smoke/debug
    SpecsMaxItemsPerTick int     // 0 = unlimited
    StockPagesPerTick    int     // stock/residue pages per scheduled tick
    DataRPS              int     // 1..5
    DataBurst            int     // 1..10
}

Default sync interval — 12h в supplier-sync schedule.

Prod note: CatalogMaxItems=0 обязателен для production-like/full прогонов: если API отдает/листает полный catalog export, мы не сужаем его SKU-cap’ом. SpecsMaxItemsPerTick=0 полезен для ручного full backfill, но для регулярного scheduler может быть задан bounded window, если specs по SKU монополизируют тик. Stock cursor хранится в supplier_sync_cursor со scope russvet_stock:<credential_ref> и продолжает обход всех warehouses между тиками; после полного цикла возвращается на первую страницу первого склада.

Live contract 2026-05-07: /stocks вернул 7 warehouses, ORGANIZATION_ID приходит number; /position/{warehouse}/all коды товара string; /residue/all/{warehouse} отдает RESIDUE number; /partnerwhstock/all отдает partnerQuantity number; /massprice возвращает массив rows, где RSCode string, а значения в Price number.

Normalization

backend/internal/core/normalization/infra/russvet/:

ФайлНазначение
offer_mapper.goREST → offers.Observation (основной mapper, ~335 строк)
price_mapper.gomassprice rows → money.Amount set с net/gross/list_tarif/retail_rec
stock_mapper.goresidue + partnerwhstock → WarehouseStock
helpers.goпарсеры дат, чисел, кодов
normalizer.goсвязующий код, имплементирует normapp.Normalizer

Не реализовано / future

  • Orders API — out of scope. PDF rs_api_orders.pdf подключается после ship’а /v1/orders/* endpoint’ов.
  • Marketplace flag — false. Russvet не маркетплейс с per-seller breakdown.
  • Incremental cursor — не поддерживается на стороне поставщика; всегда полный bulk.

Live verification 2026-05-06

Полный local-prod прогон с DB-backed credentials и all-warehouses stock:

PhaseResult
sessionsuccess, 7 ms
stocks dictionary7 warehouses, 950 ms
catalog1000 items, 2641 ms
prices1000 prices, 15380 ms
specs1000 specs, 355974 ms
stock938 pages, 309 non-empty stock payloads, 340623 ms
summarysuccess, 767665 ms

raw_remains=1000 после полного цикла означает, что stock axis обработана для каждого SKU текущего catalog window; with_stock_payload=309 — только SKU с ненулевыми остатками. Это нормальная разница, а не потеря 691 SKU.

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