# purchases 表字段设计 > 数据模型第三张表:购买记录——每笔支付事件(订阅购买 / 充值)一行,是「实付人民币」这一唯一只有作者知道的信息的落点,也是折算链第二层(额度美元 → 实付人民币)的事实来源。本文记录字段清单、语义与决策理由。 | 项目 | 内容 | |------|------| | 状态 | 已实现(2026-09-15) | | 关联 | `docs/design/qpu_history表字段设计.md`(折算链第一条:quota → 额度美元)· `docs/design/API契约.md` §2.3(登记 / 编辑端点)· `docs/upstream/NewAPI-sk-key接口文档.md`(`other.subscription_*` 字段依据) | | 范围 | 列结构 + 语义 + 写入时机;登记入口见 API 契约 §2.3 | | 字段数 | 9 = 1 主键 + 8 人工录入(登记 4 · 期间 2 · 订阅 2) | --- ## 1. 表定位 - **一行 = 一笔支付事件**(订阅购买 / 充值)——同一模型兼容两种形态;**实现先做订阅路径**(实测当前用量 100% 为订阅) - **折算链全貌**:`日志.quota ÷ quota_per_unit` = 额度美元 → `× 购买汇率` = 实付人民币;两条链的系数**都取该日志所属购买的购买时刻**(整笔订阅一把尺,见 `qpu_history表字段设计.md` §3.3)。购买比率 = `paid_amount ÷ (quota_granted ÷ quota_per_unit)`——**只存事实、不存比率**(见 3.2) - 与日志的分流:日志 `other.billing_source = "subscription"` → 按本表匹配(优先 `subscription_id`,同 ID 跨期时按时间区间);`"wallet"`(充值)→ 池子近似(后议) - 数据规模:年个位数行(订阅按周期购买)——无需索引设计 ## 2. 字段清单 ### 2.1 主键与登记字段(5 个) | 列 | 类型 | 值来源 | 说明 | |----|------|--------|------| | `id` | bigserial PK | 自增 | | | `kind` | text | 人工选择 | `subscription`(订阅)/ `topup`(充值) | | `paid_at` | timestamptz | 人工填写 | 支付时间 | | `paid_amount` | numeric | **人工必填** | **实付人民币**——唯一「只有作者知道」的信息 | | `note` | text | 人工填写 | 备注 | ### 2.2 期间与订阅字段(4 个) | 列 | 类型 | 值来源 | 说明 | |----|------|--------|------| | `period_start` | timestamptz? | **人工录入**(钱包页面) | 仅订阅:该笔覆盖的用量期间起点;左闭 | | `period_end` | timestamptz? | **人工录入**(钱包页面) | 期间终点;左开;未结束可空 | | `subscription_id` | bigint? | **人工录入**(钱包页面「订阅 #N」) | 上游订阅实例 ID;多订阅并存时用于精确分流(与日志 `other.subscription_id` 匹配) | | `quota_granted` | bigint? | **人工录入**(钱包页面「总额度」) | 本笔额度(订阅:钱包页面总额度;充值:充值记录) | ## 3. 决策记录(含理由) ### 3.1 登记 = 手工抄录(来源只有两处) 购买记录全部由作者手工录入,来源仅两处:**支付记录**(`kind` / `paid_at` / `paid_amount`)与**钱包页面**(订阅 ID / 期间 / 总额度)。订阅关键信息只有作者可见(没有免 auth 接口可查)——系统不做回填与推断,日志观测仅用于核对提示(见 §4);必填 / 可空的校验细节归技术稿。 ### 3.2 折算比率不存储——存事实、不存派生数 - `paid_amount` 是唯一不可替代的人工信息;比率是派生数——「记实付」比「记汇率」更省心(不用心算)且可事后核对;只存派生比率会丢失核对依据 - **日志同样不加汇率列**(`usage_logs` 零 schema 改动):入库有滞后(1000 条窗口内可能几天后才补回)→「入库时的配置值」会错标给「更早消费的日志」,且**无从发现**;半准确的数字比没有数字更危险 - 折算全部发生在展示 / 聚合层;关联线索(`billing_source` / `subscription_id` / 金额分解)本就在日志 `other` 里,零 schema 改动即可用 ### 3.3 为什么额度也手工录入(不采用日志感知) 日志逐条自带 `subscription_total`(实测),但回填它需要「订阅 ID → 该 ID 日志 total」的关联逻辑;钱包页面已直接给出总额度——手工抄录一步到位。**系统对购买记录零回填、零推断**,观测仅用于核对(见 §4)。 ### 3.4 命名与类型 - 金额列用 `numeric`(人民币金额不用浮点);时间列统一 `timestamptz` - 期间左闭右开(`[period_start, period_end)`)——与全项目期间惯例一致 - 订阅实例 ID 与日志同名 `subscription_id`——同一体系、同一含义(分流匹配即用此列) ## 4. 写入时机与维护 | 时机 | 动作 | |------|------| | 人工登记(管理通道) | INSERT:录入支付信息(时间 / 实付)与订阅信息(ID / 期间 / 总额度,抄自钱包页面)→ 确认 | | 系统观测(仅核对) | 日志观测到的订阅 ID / `subscription_total` 仅作核对提示(如「观测到 #N 未对应任何购买记录」),不参与回填 | | 编辑 / 删除 | 数据为人工登记,改错即修(见 API 契约 §2.3) | ## 5. 明确不存的(已否决字段与理由) | 字段 | 否决理由 | |------|----------| | 折算比率(购买汇率) | 派生数——存事实(实付 + 额度)即可事后核对,只存比率会丢失核对依据(见 3.2) | ## 6. 展示层边界(界面设计的输入) - **明细应展示「订阅归属」**(数据源 = 日志 `other`):订阅名称、额度实况、(本表登记后)期间范围——给所有使用者可见 - **查看面金额口径(2026-09-13 修订)**:卡片 / 明细的「消费」类金额 = **折算实付人民币**(按购买期间分段,每段 `quota 占比 × 该笔实付`);原「天然不含实付金额」的边界就此放开。**仍不上查看面**:实付总额、购买比率、购买记录(含 `note`)本身——管理面独占 - 已知代价(接受):明细含 quota 列、QPU 公开(500000 可查),使用者理论上可由两者反推购买比率——本工具是成本分担场景、非对抗关系 - 「按订阅维度统计」(本期用量/占比)数据天然支持——**待评估**,暂不列入 - 管理页相关见 `docs/design/交互设计.md` §3.3 与 `docs/design/API契约.md` §2.3 ## 7. 待定事项(关联后续环节) | 事项 | 归属 | |------|------| | 充值(`topup`)路径的折算近似算法 | 池子加权平均等方案,后议 | | 多订阅并存的日志行为 | 按源码结构推理(每笔标所属 ID);实现期以真实场景实测验证 | | 期间与订阅 ID 的匹配细节 | 技术稿细化(ID 优先、同 ID 跨期按时间) | | 核对提示(观测到的订阅 ID ↔ 已录入记录比对)的交互 | 交互设计 |