# qpu_history 表字段设计 > 数据模型第四张表:换算参数时间线(表名 `qpu_history`,QPU = QuotaPerUnit 缩写)——`quota_per_unit`(quota → 额度美元)的历史取值。历史值在 sk-key 视角无从观测,本表由同步引擎自动采样、只记变化点;**纯只读**(唯一写入者是同步引擎)。本文记录字段清单、语义与决策理由。 | 项目 | 内容 | |------|------| | 状态 | 定稿(2026-10-01) | | 关联 | `docs/design/usage_logs表字段设计.md`(消费方:quota → 美元的展示换算)· `docs/references/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 取值语义:按日志自身时刻取值 - **一条日志的美元换算 = 该日志 `created_at` 时刻的当值 QPU**(「该时刻之前最近一条」)——这是纯粹的历史单位换算:这条日志发生时一单位 quota 值多少美元额度,就用当时的换算率 - **为什么按日志时刻**:换算的唯一目的是「这条日志当时值多少美元额度」——历史正确性要求用历史当值;不存在「一笔订阅一把尺」的分摊自洽诉求(分摊计算已整体裁撤) - **边界**:日志时刻早于本表最早记录时(如补录历史数据),回退用「最早已知值」(近似规则,正常部署起点的日志不会触发) ### 3.4 `quota_per_unit` 的可变性说明(本表存在的理由) - 它是「全站计价单位的定义」——改它 ≈ 重定义货币单位(全站一致重定标);管理员极少动(当前站点为出厂默认值)。本设计以「按时间线换算」从根上免疫它的任何变更 - **存在必要性审视(2026-09-29,作者裁决保留)**:改动概率极低(历史上零次)× 权限极高(Root,深思熟虑的定价动作而非误操作)× **不可逆**(一旦发生且未被捕捉,「当时的率」永久不存在)——风险结构与日志 1000 条窗口同构;备选方案「砍表 + 保留观测告警、事后人工重建」被否决——不可逆的历史由自动化保障,不依赖人工响应;全部代价 = 每轮一次 `/api/status` 采样 + 十行时点查询(`qpuAt(t)`,3 行内存缓存 + 区间判断) ### 3.5 为什么不做人工修正(只读设计) - 人工介入的场景不成立——`quota_per_unit` 在上游**无编辑入口(前台默认隐藏且只读)、管理审计不记值、历史上从未变化**(见 `docs/references/NewAPI-接口范围.md`) - 本表由此定型为**纯只读时间线**:唯一写入者是同步引擎(值变化才追加);管理通道仅提供只读查看 - 未来若真出现需要人工介入的场景(如运营方主动告知一次精确切换时刻),再评估启用 ### 3.6 命名:表名 `qpu_history`、值列 `qpu` - **表名**:`quota_per_unit_history` 过长、逐处引用啰嗦 → `qpu_history`——QPU 是 QuotaPerUnit 的通用缩写,短且醒目 - **值列**:`value` 过泛("什么的值"不自明)→ 改为 `qpu`,与表名一体、一眼即知 - 通用约定:**表名 / 列名用 QPU 缩写,引用上游字段 `quota_per_unit` 时保持全称** ## 4. 写入时机(唯一写入者:同步引擎) | 时机 | 动作 | |------|------| | 部署(基线) | INSERT 基线一行(当时 `/api/status` 取值) | | 每轮同步 | 读 `/api/status`:**值变化才追加一行** | - 除追加外无 UPDATE / DELETE——数据不可变 ## 5. 待定事项(关联后续环节) | 事项 | 归属 | |------|------| | 采样调度(读取频率、失败重试) | 同步引擎设计(待立) |