Files
atlas-seller-kit/docs/architecture/exchange-rates.md
T

81 lines
5.4 KiB
Markdown

# 汇率:数据模型、数据源与更新策略
> 首期定价只需要 `CNY → RUB`,但表结构按任意法币对设计。汇率是全局参考数据,不属于平台、门店或工作空间。
## 1. 口径与使用边界
`exchange_rates.rate` 始终表示:**1 单位 `base_currency_code` 可兑换多少单位 `quote_currency_code`**。例如 CBR 返回 `Nominal=1`、`Value=12.3343` 的 CNY 数据,标准化保存为:
```text
base_currency_code = CNY
quote_currency_code = RUB
rate = 12.3343
```
这是一种报价参考口径,不等同于卖家实际购汇、物流商结算或提现的成交汇率。因此定价保留三种来源:
| 报价来源 | 读取方式 | 是否写入 `exchange_rates` |
|---|---|---|
| 系统汇率 | 读取当前有效的正式参考汇率 | 是。首期为 CBR 的 `official_reference`。 |
| 方案固定汇率 | 读取 `pricing_policies.fixed_cny_to_rub_rate` | 否,报价快照记录该固定值。 |
| 本次覆盖 | 用户在定价弹窗输入 | 否,报价快照记录覆盖值和原因。 |
无论哪种来源,`pricing_quotes` 都固化 `fx_cny_to_rub`、`fx_rate_origin`、可空的 `exchange_rate_id` 和完整输入快照。后续系统汇率更新绝不改写已确认报价。
## 2. 数据库设计
DDL 位于 [`sql/001_foundation.sql`](./sql/001_foundation.sql) 的 `exchange_rates` 表:
| 字段 | 含义 |
|---|---|
| `base_currency_code` / `quote_currency_code` | 币种方向;严禁把 `CNY/RUB` 与 `RUB/CNY` 混用。 |
| `rate` | 标准化后的“1 base = rate quote”。使用 `NUMERIC`,不使用浮点数。 |
| `kind` | `official_reference`、`market_reference` 或 `manual`。 |
| `source` | `cbr`、`exchange_rate_api_open`、`frankfurter`、`manual`。 |
| `effective_on` | 数据源声明的适用日期;不是系统抓取日期。 |
| `observed_at` / `fetched_at` | 来源观测时间与 Atlas 抓取/录入时间。 |
| `source_reference` / `raw_payload` | 可追溯的来源链接、批次说明和原始响应。 |
同一个“币种方向 + 口径 + 来源 + 适用日期”只能有一条记录。若来源在同一适用日修订汇率,Worker 应在事务中更新原始记录并保留修订审计,或另行建立修订表;首期 CBR 日汇率正常情况下不需要重复插入。
## 3. 首选数据源:俄罗斯央行 CBR
首期正式系统汇率使用 CBR 的 CNY 对 RUB 官方参考汇率:
```text
GET https://www.cbr.ru/scripts/XML_daily.asp
GET https://www.cbr.ru/scripts/XML_daily.asp?date_req=dd/mm/yyyy
```
- 不传 `date_req` 返回最近一个已发布工作日;传日期可补历史数据。
- 解析 XML 中 `CharCode=CNY` 的 `Nominal` 与 `Value`;将逗号小数转换为 `NUMERIC`,按 `Value / Nominal` 标准化。
- 记录 `kind=official_reference`、`source=cbr`、来源 XML URL、响应原文、响应对应的适用日期和抓取时间。
- CBR 在周末与节假日可能返回上一工作日数据,这是正常结果,不创建重复记录也不标记请求失败。
CBR 是 Ozon-RU 定价的首选,因为它直接发布 CNY 对 RUB 的官方口径。它不保证等于卖家实际结算汇率,用户仍可选择固定或本次覆盖汇率。
## 4. 免费备用与未来扩展
| 服务 | 调用方式 | 适用范围 | 使用规则 |
|---|---|---|---|
| CBR | `https://www.cbr.ru/scripts/XML_daily.asp` | 首期 CNY→RUB 正式口径 | 主来源。免费、官方、按工作日发布。 |
| ExchangeRate-API Open Access | `https://open.er-api.com/v6/latest/CNY` | CNY→多币种可用性备用 | 无 Key、日更、需按其条款归属且有频率限制。只能标记为参考或备用,不能静默覆盖 CBR 正式记录。 |
| Frankfurter | `https://api.frankfurter.dev/v2/rate/CNY/RUB` | 后续多币种、历史查询和数据源对照 | 无 Key;默认可能混合多个来源。需要时应记录 provider 元数据,不能把混合口径伪装为 CBR 官方口径。 |
不要在前端直接调用上述接口,也不要把第三方响应直接当作报价结果。所有请求只由后端 Worker 发起、校验、标准化、持久化后供报价引擎读取。
## 5. BullMQ 更新与故障策略
1. 定时 Worker 在俄罗斯工作日每 2 小时请求一次 CBR;不依赖未经确认的固定发布时间。
2. 以 `effective_on` 去重:没有新适用日期时直接结束;有新日期时插入一条 `exchange_rates` 记录。
3. 每次任务记录 HTTP 状态、解析结果、原始响应摘要和耗时;禁止记录无关敏感信息。
4. CBR 请求失败时保留上一条成功汇率并告警;不得自动以备用来源覆盖正式 CBR 数据。
5. 若最近 CBR 有效记录距当前超过 72 小时,报价界面显示“系统汇率可能过期”,但仍允许使用上次值、方案固定值或本次覆盖值。
6. 定价服务只读取当前有效的 `CNY → RUB + official_reference + cbr` 最新记录;若不存在则不能输出系统汇率报价,应提示选择固定/覆盖汇率或等待更新任务成功。
## 6. 实施接口边界
- Worker 内部:`ExchangeRateProvider` 适配器接口,首期实现 `CbrProvider`;备用提供方独立实现,不影响报价公式。
- 后台管理:查看当前系统汇率、历史记录、来源、抓取时间、异常与人工录入 `manual` 汇率;人工记录不得冒充 `cbr`。
- 定价 API:返回最终汇率、来源、适用日期、抓取时间和是否过期;确认报价时只引用快照,不在请求中重新抓取第三方。