b4780e256e
- 重写 15 张已有表文档:每张补「用在 App 哪/增删改查时机/字段取值/join key 指向」 - 新建 4 张缺失表文档:wechat_transfer_authorization / ad_watch_log / price_report / app_config - 新增 OVERVIEW.md:功能↔表映射 + 写入路径 + 表间关系(含语义 join key)+ ER + 资金模型 - README 索引补齐到 19 表 + 置顶 OVERVIEW + 通用约定补全 - 纠正过时:withdraw_order(reviewing/rejected/user_name)、savings_record(/order/report 真实写入)、coin_transaction(biz_type) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
savings_record — 省钱记录(profile 省钱战绩源)
模型
app/models/savings.py· 仓库app/repositories/savings.py· 接口 savings-summary / savings-battle / savings-records · 写入源POST /api/v1/order/report(compare-record 相关) · ← 索引 · 总览
profile 页「累计帮你省了 / 省钱战绩 / 省钱明细」的唯一数据源:真正下单成交后省了多少记一行。是「记账闭环」的落库终点——客户端比价后点结果链接跳走付款,无障碍 watcher 捕获支付成功 + 金额归因(±1 元/5 分钟窗口)→ 上报 POST /order/report。
两种来源(
source字段区分):
source='compare':真实下单上报(经/order/report的create_from_report写,2026-06 已接通)。source='demo':用户从无任何记录时,首次进省钱页懒种子ensure_seeded按 user_id 幂等灌 ~23 条演示数据。 统计口径:只要有 ≥1 条compare记录,就只用 compare(demo 全忽略);否则用 demo 兜底。聚合(SUM/分组/连续天数/分位)都是生产级真实计算。
用在哪 / 增删改查
- C(插入):
POST /order/report(create_from_report)→ 写一行source='compare',省额 =original_price_cents − paid(下限 0)。按(user_id, client_event_id)幂等,重复上报返回旧行不新增。ensure_seeded→ 用户无任何 savings 记录时,懒灌 demo(source='demo'),进 summary/battle/records 接口时触发。
- U / D:无。
- R:
GET /savings/summary(累计省/单数/均省)、/savings/battle(本周省/连续天数/超过 X% 用户/比价次数)、/savings/records(明细分页);comparison.py读本表shop_name(source=compare)集合给比价记录打「已下单」。
字段
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
|---|---|---|---|
id |
Integer | PK, autoincrement | |
user_id |
Integer | FK→user.id, index, NOT NULL | 归属用户 |
order_amount_cents |
Integer | NOT NULL | 订单到手价/实付(分) |
saved_amount_cents |
Integer | NOT NULL | 本单省下(分,可为 0;= original − 实付,下限 0) |
platform |
String(32) | nullable | 下单平台(美团外卖/淘宝闪购/京东外卖) |
title |
String(128) | nullable | 明细卡标题(compare 来源 = 门店名) |
shop_name |
String(128) | nullable | 店铺名。与 comparison_record.store_name 字符串相等关联(打「已下单」) |
dishes |
JSON(PG: JSONB) | NOT NULL, default [] | 菜名列表(前 2 道展示,其余"还有 N 道") |
source |
String(16) | NOT NULL, default compare |
取值:compare(真实下单上报)/ demo(演示种子) |
original_price_cents |
Integer | nullable | 源平台原价(分);compare 上报带,demo 为 null |
compared_price_cents |
Integer | nullable | 我们当时给出的比价价(分,审计/备用) |
pay_channel |
String(16) | nullable | 支付渠道:wechat / alipay(归因 watcher 判定) |
platform_package |
String(128) | nullable | 实际下单平台包名 |
source_platform_name |
String(32) | nullable | 源平台展示名(如「美团」),用于"原价 ¥X(美团)" |
source_deeplink |
String(512) | nullable | 源平台重进链接(预留) |
client_event_id |
String(64) | nullable | 客户端幂等键(UUID);demo 行为 null |
device_id |
String(128) | nullable | 上报设备号 |
created_at |
DateTime(tz) | server_default now(), index | 时间 |
关系 / Join Key
user_id→user.id(多对一)。- ≈
comparison_record(语义 join,无 FK):本表shop_name(source=compare)==comparison_record.store_name→ 给那条比价记录标「已下单」。注意是店名对齐、店级(下单上报不带 trace_id,无法逐条精确对应)。
索引与约束
- PK
id;indexuser_id、created_at;UNIQUE(user_id,client_event_id) =uq_savings_user_event(真实上报幂等;demo 行client_event_id=NULL不参与冲突,允许多 NULL)。
注意
dishes用JSON().with_variant(JSONB(),"postgresql")(SQLite 退化 JSON)。beat_percent(超过百分之多少用户)按各用户累计省下金额做真实分位,每用户口径与展示一致(有 compare 用 compare 之和,否则 demo 之和)。