- docs/design:四张表字段设计 · 交互设计(方向层 + 细节层)· API 契约 · 同步引擎设计 - docs/upstream:sk-key 接口实证(430 行)· 接口范围(9 个接口) - README(全景入口)· AGENTS.md(AI 协作者指引) - 废弃旧稿已删除(不随仓库留存)
77 lines
5.3 KiB
Markdown
77 lines
5.3 KiB
Markdown
# qpu_history 表字段设计
|
||
|
||
> 数据模型第四张表:换算参数时间线(表名 `qpu_history`,QPU = QuotaPerUnit 缩写)——`quota_per_unit`(quota → 额度美元)的历史取值,折算链第一层的系数来源。历史值在 sk-key 视角无从观测,本表由同步引擎自动采样、只记变化点;**纯只读**(唯一写入者是同步引擎)。本文记录字段清单、语义与决策理由。
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| 状态 | 定稿(2026-09-13,多轮讨论收敛:只读化、按购买时刻取尺) |
|
||
| 关联 | `docs/design/purchases表字段设计.md`(折算链第二条:额度美元 → 实付人民币)· `docs/design/API契约.md` §2.4(只读查看端点)· `docs/upstream/NewAPI-接口范围.md`(采集来源 `/api/status`) |
|
||
| 范围 | 列结构 + 语义 + 写入规则;采样调度(同步引擎)不在此文 |
|
||
| 字段数 | 3 = 1 主键 + 2 业务字段 |
|
||
|
||
---
|
||
|
||
## 1. 表定位
|
||
|
||
- **一行 = 一次「换算单位取值变化」事件**(值 + 生效时间)——正常情况全表仅 1 行(当前站点为出厂默认值,从未变过)
|
||
- 它是折算链第一条(quota → 额度美元)的**时间线**:任何时刻的换算取值 = 「该时刻之前最近一条」
|
||
- **为什么要自己攒**:`/api/status` 只能看到当前值,历史值无从观测(上游不留历史、管理审计不记值)——没有本表,历史日志只能用今天的尺子量,**账会串**
|
||
- 与日志零耦合:折算全部发生在展示 / 聚合层——本表任何变动都不触发 `usage_logs` 写入
|
||
|
||
## 2. 字段清单
|
||
|
||
| 列 | 类型 | 值来源 | 说明 |
|
||
|----|------|--------|------|
|
||
| `id` | bigserial PK | 自增 | |
|
||
| `qpu` | numeric | `/api/status.quota_per_unit` | 当时的 `quota_per_unit` 值 |
|
||
| `effective_from` | timestamptz | 首次采样或检测到变化的时间 | 生效时间;采样误差 ≤ 同步周期 |
|
||
|
||
## 3. 决策记录(含理由)
|
||
|
||
### 3.1 为什么自动采样(而非人工登记)
|
||
|
||
它是「可登记的事实」的**反面**——历史值无从观测(只能看到当前值);自动采样(每轮同步)是唯一能沿时间轴捕获它的方式。采样误差 ≤ 同步周期(边界影响可忽略)。
|
||
|
||
### 3.2 只记变化点,不记每次采样
|
||
|
||
部署时插基线;此后每轮同步顺带读 `/api/status`,**值变化才追加一行**——行数 = 调价次数 + 1(正常为 1)。
|
||
|
||
### 3.3 取值语义与跨期统计
|
||
|
||
- **取尺基准 = 购买时刻(`paid_at`)**:一条日志的折算 = 先按 `subscription_id` 归属到购买记录(见 `purchases表字段设计.md`),再取**该购买的购买时刻**在本表中的当值——同一笔订阅的全部日志共用一把尺子。完整链:`日志.quota ÷ 购买时刻的 quota_per_unit × 购买汇率` = 实付人民币
|
||
- **为什么不按日志自身的时刻**:订阅期内若发生重定标,同一笔订阅的「已用美元」会在调价点跳变,而额度与实付没变——分子分母尺子不一致,账就不自洽;按购买时刻取尺等价于「按 quota 占比分摊实付」,恒定成立
|
||
- **跨期统计**:把查询时间范围按**购买期间边界**切成若干段,各段用该笔购买的系数聚合——`quota_per_unit` 只在每笔购买处取一次值,调价点不再制造分段
|
||
- **边界**:购买时刻早于本表最早记录时(如补录历史购买),回退用「最早已知值」(近似规则)
|
||
- **充值(`wallet`)路径**:同样取「充值执行时刻」的尺子(池子近似算法后议)
|
||
|
||
### 3.4 `quota_per_unit` 的可变性说明(本表存在的理由)
|
||
|
||
它是「全站计价单位的定义」——改它 ≈ 重定义货币单位(全站一致重定标);管理员极少动(当前站点为出厂默认值)。本设计以「按时间线折算」从根上免疫它的任何变更。
|
||
|
||
### 3.5 为什么不做人工修正(只读设计,2026-09-13 定)
|
||
|
||
- 原设计预留「修正生效时间 / 删除」等人工介入能力;源码评估后判定场景不成立——`quota_per_unit` 在上游**无编辑入口(前台默认隐藏且只读)、管理审计不记值、历史上从未变化**(见 `docs/upstream/NewAPI-接口范围.md`)
|
||
- 本表由此定型为**纯只读时间线**:唯一写入者是同步引擎(值变化才追加);管理通道仅提供只读查看
|
||
- 未来若真出现需要人工介入的场景(如运营方主动告知一次精确切换时刻),再评估启用
|
||
|
||
### 3.6 命名:表名 `qpu_history`、值列 `qpu`(2026-09-13 改)
|
||
|
||
- **表名**:原名 `quota_per_unit_history` 过长、逐处引用啰嗦 → 改为 `qpu_history`——QPU 是 QuotaPerUnit 的通用缩写,短且醒目
|
||
- **值列**:原名 `value` 过泛("什么的值"不自明)→ 改为 `qpu`,与表名一体、一眼即知
|
||
- 通用约定:**表名 / 列名用 QPU 缩写,引用上游字段 `quota_per_unit` 时保持全称**
|
||
|
||
## 4. 写入时机(唯一写入者:同步引擎)
|
||
|
||
| 时机 | 动作 |
|
||
|------|------|
|
||
| 部署(基线) | INSERT 基线一行(当时 `/api/status` 取值) |
|
||
| 每轮同步 | 读 `/api/status`:**值变化才追加一行** |
|
||
|
||
- 除追加外无 UPDATE / DELETE——数据不可变
|
||
|
||
## 5. 待定事项(关联后续环节)
|
||
|
||
| 事项 | 归属 |
|
||
|------|------|
|
||
| 采样调度(读取频率、失败重试) | 同步引擎设计 |
|