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).
| Path | Kind | Purpose |
|---|---|---|
GET /rs/stocks | dictionary | словарь stocks (warehouses) |
GET /rs/position/{stock}/all | catalog | позиции каталога по stock+category |
GET /rs/massprice | price | bulk-цены (используется в scheduled refresh) |
GET /rs/residue/all/{stock} | stock | все остатки по stock |
GET /rs/partnerwhstock/all/{stock} | stock | partner-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
- Catalog:
- 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.go | REST → offers.Observation (основной mapper, ~335 строк) |
price_mapper.go | massprice rows → money.Amount set с net/gross/list_tarif/retail_rec |
stock_mapper.go | residue + 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:
| Phase | Result |
|---|---|
| session | success, 7 ms |
| stocks dictionary | 7 warehouses, 950 ms |
| catalog | 1000 items, 2641 ms |
| prices | 1000 prices, 15380 ms |
| specs | 1000 specs, 355974 ms |
| stock | 938 pages, 309 non-empty stock payloads, 340623 ms |
| summary | success, 767665 ms |
raw_remains=1000 после полного цикла означает, что stock axis обработана для
каждого SKU текущего catalog window; with_stock_payload=309 — только SKU с
ненулевыми остатками. Это нормальная разница, а не потеря 691 SKU.
Связанные документы
pipelines.md— BulkSnapshotPipeline + CursorPipeline + legacy.framework.md— общий connector framework.etm.md/iek.md— другие batch-first поставщики.../../../20-architecture/adr/0024-supplier-connector-contract.md— контракт connector’а.