Exchange Rate
NOTE
Статус: Live (HEAD
5e4ce54, 2026-05-06). BC закрыт в P6 (ExchangeRate Layer 3a, ADR-0042, CLOSED 2026-04-29). Разблокирует DKC adapter (USD/EUR pricing) и multi-currency proposals.
CBR daily pull → PG storage → 7-day staleness cap. Lossless big.Rat арифметика на storage уровне; конвертация в float64 — только на boundary с domain.
Назначение
Конверсия валют в proposal pipeline. Поставщики типа DKC отдают цены в USD; чтобы платформа считала proposal в RUB, нужен exchange rate.
P6 реализовал Layer 3a — daily refresh от ЦБ РФ + storage. Layer 3b (per-supplier rate routing) — отдельный backlog.
Статус документа
- Тип знания:
current service - Статус реализации: Refresher ticker с prev-success state, identity bypass БД,
ExchangeMetricsв domain (cycle-break). - Текущее место кода:
backend/internal/core/exchange/{domain,app,infra}/. - Что читать дальше:
../../20-architecture/adr/0042-exchange-rate-layer-3a.md.
Область действия
Входит:
- Daily fetch CBR XML feed (
https://www.cbr.ru/scripts/XML_daily.asp). - Storage
exchange_rateсeffective_at+fetched_at. - 7-day staleness cap — отказывать если последний rate старше.
Refresher.LastRefreshAge()— прозрачность для health endpoint.
Не входит:
- Per-supplier rate routing (Layer 3b) — supplier может публиковать свой курс, который перебивает CBR.
- Forex margin — наценка на курс, отложено.
- Historical conversion — на 2026-05-06 рейты конвертируются по
now().
Публичный контракт
Вход
- Ticker (refresher) — внутри api-server’а через
exchange.Module().
Выход
Internal Go API:
Repository.LatestEffective(ctx, from, to)→PersistedRate{Multiplier *big.Rat, EffectiveAt, FetchedAt}.domain.Convert(amount, rate)→money.Amount(proposal pipeline вызывает).
Внутренняя архитектура
domain/rate.go—PersistedRate(storage shape с big.Rat) +Rate(proposal-level wrapper).domain/repository.go— Repository port.domain/metrics.go—ExchangeMetricsinterface (для cycle-break, реализуется в платформенном слое).app/Refresher— ticker с prev-success state.infra/cbr/— HTTP client для XML_daily.infra/pg/— Repository.
Зависимости
- PostgreSQL —
exchange_rateтаблица. - CBR XML feed — внешний.
Хранилище
| Таблица | Назначение |
|---|---|
exchange_rate | (from, to, effective_at) UNIQUE; multiplier как numeric или text-encoded big.Rat |
Конфигурация
| Env var | Default | Описание |
|---|---|---|
EXCHANGE_REFRESH_INTERVAL | 1h | Интервал ticker’а; CBR обновляется раз в день, 1h достаточно overhead’а |
EXCHANGE_STALENESS_CAP | 168h (7d) | Отказывать если latest старше |
CBR_XML_DAILY_URL | https://www.cbr.ru/scripts/XML_daily.asp | Override для тестов |
Тестирование
- Unit:
go test ./internal/core/exchange/... - Integration:
-tags=integrationдля CBR roundtrip + PG.
Наблюдаемость
- Metrics:
exchange_refresh_total{outcome},exchange_last_refresh_age_seconds,exchange_rate_lookups_total{from,to,outcome}. - Phase logs:
exchange.refresh.phase.
Открытые вопросы / TODO
- Layer 3b per-supplier rate — DKC может публиковать свой USD/RUB.
- Historical conversion — для proposal на дату X брать rate X.
- Forex margin — наценка над CBR.
Связанные документы
- ADR:
0042. - Memory:
tracium_p6_execution.md. - DKC adapter (consumer):
../ingestion/connectors/...(USD pricing). - Pricing BC (consumer через
proposal.Convert):../pricing/.