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>
53 lines
5.1 KiB
Markdown
53 lines
5.1 KiB
Markdown
# comparison_record — 比价记录(每次比价完整明细)
|
||
|
||
> 模型 `app/models/comparison.py` · 仓库 `app/repositories/comparison.py` · 接口 [compare-record-report](../api/compare-record-report.md) / [compare-records](../api/compare-records.md) / [compare-record-detail](../api/compare-record-detail.md) · [← 索引](./README.md) · [总览](./OVERVIEW.md)
|
||
|
||
每完成一次比价(外卖/电商/领券),客户端在 done 帧后用**带 JWT** 的通道上报一条。App「我的比价记录」列表/详情的数据源,也是比价战绩里程碑解锁进度的计数源(`status='success'` 条数),还被「上报更低价」反查原最低价。
|
||
|
||
> 与 `savings_record` 的区别:本表是「每一次**比价行为**的完整明细」(不省钱、甚至失败也记);`savings_record` 是「真正**下单成交**省了多少」。两表独立、互不喂数据。
|
||
|
||
## 用在哪 / 增删改查
|
||
- **C / U(upsert,幂等)**:`POST /compare/record`(`upsert_record`)。按 `(user_id, trace_id)` 查:不存在→新建;已存在→整行覆盖(客户端重试/重复上报时,**更完整的那次胜出**)。`best_*`/`saved_amount_cents`/`is_source_best`/`status` 由 `_derive` 从 `comparison_results` 算出(协议已按 price 升序、rank=1 最便宜),不信客户端自算。
|
||
- **D**:无(关联的 `price_report` 也只把 `comparison_record_id` 置空,不删本表)。
|
||
- **R**:`GET /compare/records`(列表,`created_at` 倒序游标 + 「已下单」标记)、`GET /compare/records/{id}`(详情,限本人);`count_success` 给里程碑;`report.py` 反查 `best_*`;admin 大盘/明细。
|
||
|
||
## 字段
|
||
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
|
||
|---|---|---|---|
|
||
| `id` | Integer | PK, autoincrement | 被 `price_report.comparison_record_id` 引用 |
|
||
| `user_id` | Integer | FK→user.id, index, NOT NULL | 归属用户 |
|
||
| `device_id` | String(64) | nullable | 设备号(多设备区分 / 与不鉴权期对账) |
|
||
| `business_type` | String(16) | NOT NULL, default `food`, index | 取值:`food`(当前唯一接通)/ `ecom` / `coupon` |
|
||
| `trace_id` | String(64) | NOT NULL | pricebot 侧 trace_id(关联调试落盘 + 幂等键) |
|
||
| `source_platform_id` / `_name` | String(32) | nullable | 源平台代号 / 中文名 |
|
||
| `source_package` | String(128) | nullable | 源平台 Android 包名 |
|
||
| `source_price_cents` | Integer | nullable | 源平台到手价(分) |
|
||
| `best_platform_id` / `_name` | String(32) | nullable | 最优平台代号 / 中文名(= rank=1) |
|
||
| `best_price_cents` | Integer | nullable | 最优价(分)。**被 `price_report` 反查作"原最低价",校验上报价必须更低** |
|
||
| `best_deeplink` | String(1024) | nullable | 最优平台商家深链(再次比价/直达用);2026-06 起新比价才有,旧记录 null |
|
||
| `saved_amount_cents` | Integer | nullable | 源价 − 最优价(可 0/负:源平台本就最便宜) |
|
||
| `is_source_best` | Boolean | nullable | 源平台就是最便宜(= 这次没省到) |
|
||
| `store_name` | String(128) | nullable | 店铺名。**与 `savings_record.shop_name` 按字符串相等关联**,给本记录打「已下单」 |
|
||
| `total_dish_count` / `skipped_dish_count` | Integer | nullable | 菜品总数 / 目标平台没找到被跳过数 |
|
||
| `status` | String(16) | NOT NULL, default `success` | 取值:`success`(有非源且有价的目标结果)/ `failed`(出错/没采到目标价)。**里程碑只数 success** |
|
||
| `information` | String(256) | nullable | done 帧文案;成功=摘要,失败=具体原因(前端失败时当原因展示) |
|
||
| `items` | JSON(PG: JSONB) | NOT NULL, default [] | 下单菜品 `[{name, qty, specs?}]` |
|
||
| `comparison_results` | JSON(PG: JSONB) | NOT NULL, default [] | 逐平台对比 `[{platform_id,platform_name,package,price(元),is_source,rank,coupon_saved(元),coupon_name}]`;`coupon_saved`=该平台主优惠额(美团红包/淘宝平台红包/京东百亿补贴,只取一笔),`coupon_name`=优惠来源名(展示用) |
|
||
| `skipped_dish_names` | JSON(PG: JSONB) | NOT NULL, default [] | 被跳过的菜名 |
|
||
| `raw_payload` | JSON(PG: JSONB) | nullable | 客户端原始上报全量(calibration + done.params),取数兜底 |
|
||
| `created_at` | DateTime(tz) | server_default now(), index | 时间 |
|
||
|
||
> `ordered`(已下单)是**瞬态字段**,不在表里:`list_records` 读取时按 `store_name ∈ 该用户 source='compare' 的 savings_record.shop_name 集合` 现挂到实例上供出参用。
|
||
|
||
## 关系 / Join Key
|
||
- `user_id` → `user.id`(多对一)。
|
||
- ← 被 `price_report.comparison_record_id` 引用(选中某条比价记录上报更低价);`price_report` 同时读本表 `best_price_cents`/`best_platform_*`/`store_name`/`items` 做快照与校验。
|
||
- ≈ `savings_record`(语义 join,无 FK):`store_name` == `savings_record.shop_name`(source='compare')→ 标「已下单」,店级。
|
||
- 被 `comparison_milestone_claim` 间接依赖:解锁进度 = 本表 `status='success'` 计数。
|
||
|
||
## 索引与约束
|
||
- PK `id`;index `user_id`、`business_type`、`created_at`;UNIQUE(`user_id`, `trace_id`) = `uq_comparison_user_trace`(幂等覆盖)。
|
||
|
||
## 注意
|
||
- 4 个 JSON 列用 `JSON().with_variant(JSONB(),"postgresql")`(SQLite 退化 JSON)。结构化金额列存「分」,`comparison_results.price`/`coupon_saved` 原样存「元」。
|