feat(compare): 比价记录落库 + 战绩里程碑(真发金币)
- comparison_record 表 + 上报(幂等 user+trace)/列表/详情 接口;服务端派生 best/saved/is_source_best/status + information(done 帧文案/失败具体原因) - 战绩里程碑:comparison_milestone_claim 表 + 进度/领取接口,按成功比价次数 解锁,复用 grant_coins 真发金币(RECORD_MILESTONES 120/180/300/500/800/1200) - 迁移 record/information/milestone 三个,单 head;rewards.py 集中档位配置 - 测试 test_compare_record(8)+test_compare_milestone(7) 全绿;docs/api 12a-12e + 索引 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -3,7 +3,7 @@
|
||||
> Base URL:生产 `https://app-api.shaguabijia.com`;本地联调 `http://<开发机>:8770`
|
||||
> 协议:HTTP / JSON,请求与响应体均 `application/json`,字段统一 **snake_case**
|
||||
> 鉴权:需鉴权的接口在请求头带 `Authorization: Bearer <access_token>`
|
||||
> 最后更新:2026-05-31
|
||||
> 最后更新:2026-05-31(+ 比价战绩里程碑 12d/12e)
|
||||
> 架构:`app/api/v1/` 只放很轻的接口层;穿山甲/微信支付/极光/短信/美团等 SDK 集成的重逻辑在 `app/integrations/`,实现细节见 [docs/integrations/](../integrations/README.md)。
|
||||
|
||||
---
|
||||
@@ -26,6 +26,13 @@
|
||||
| **比价透传**(前缀 `/api/v1`,外卖 MVP;与 `coupon/step` 同为透传 pricebot-backend) |||
|
||||
| 12 | `POST /api/v1/intent/recognize` | 无 | [详情](./compare-intent-recognize.md) |
|
||||
| 13 | `POST /api/v1/price/step` | 无 | [详情](./compare-price-step.md) |
|
||||
| **比价记录**(前缀 `/api/v1/compare`;按用户落库,**鉴权**,区别于上面不鉴权的透传) |||
|
||||
| 12a | `POST /api/v1/compare/record` | Bearer | [详情](./compare-record-report.md) |
|
||||
| 12b | `GET /api/v1/compare/records` | Bearer | [详情](./compare-records.md) |
|
||||
| 12c | `GET /api/v1/compare/records/{id}` | Bearer | [详情](./compare-record-detail.md) |
|
||||
| **比价战绩里程碑**(前缀 `/api/v1/compare`;福利页「记录比价战绩」,按成功比价数解锁逐档发金币) |||
|
||||
| 12d | `GET /api/v1/compare/milestones` | Bearer | [详情](./compare-milestones.md) |
|
||||
| 12e | `POST /api/v1/compare/milestones/{milestone}/claim` | Bearer | [详情](./compare-milestone-claim.md) |
|
||||
| **钱包 / 我的资产**(前缀 `/api/v1/wallet`) |||
|
||||
| 14 | `GET /api/v1/wallet/account` | Bearer | [详情](./wallet-account.md) |
|
||||
| 15 | `GET /api/v1/wallet/coin-transactions` | Bearer | [详情](./wallet-coin-transactions.md) |
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# POST /api/v1/compare/milestones/{milestone}/claim — 领取比价战绩里程碑奖励
|
||||
|
||||
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
||||
|
||||
领取某一档(第 `milestone` 次)的金币奖励,真实入账金币钱包。领取记录写
|
||||
`comparison_milestone_claim`((user_id, milestone) 唯一)+ `grant_coins` 同一事务原子提交,
|
||||
**每档只能领一次**。进度口径见 [milestones 进度接口](./compare-milestones.md)。
|
||||
|
||||
## 入参
|
||||
- 路径参数 `milestone`(int):档位序号,1..6(= `RECORD_MILESTONES` 长度)。
|
||||
|
||||
## 出参
|
||||
响应 `200`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `milestone` | int | 本次领取的档位序号 |
|
||||
| `coin_awarded` | int | 本次发放金币 |
|
||||
| `coin_balance` | int | 领奖后金币余额 |
|
||||
|
||||
```json
|
||||
{ "milestone": 1, "coin_awarded": 120, "coin_balance": 29087 }
|
||||
```
|
||||
|
||||
## 错误
|
||||
- `401` 未鉴权
|
||||
- `404` 档位越界(`unknown milestone`,milestone < 1 或 > 档位总数)
|
||||
- `409` 该档还没解锁(`milestone locked`,成功比价次数 < milestone)
|
||||
- `409` 该档已领过(`milestone already claimed`)
|
||||
|
||||
## 说明
|
||||
- 领取金币后,客户端应刷新钱包余额([`GET /api/v1/wallet/account`](./wallet-account.md))与本档状态。
|
||||
- 幂等:重复领同一档返回 409 且**不会重复发金币**(唯一约束 + 领取前查重)。
|
||||
@@ -0,0 +1,51 @@
|
||||
# GET /api/v1/compare/milestones — 比价战绩里程碑进度
|
||||
|
||||
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
||||
|
||||
福利页「记录比价战绩」的数据源。返回各档(第 1~6 次)解锁/领取状态。
|
||||
|
||||
**解锁进度** = 当前用户 `comparison_record` 里 `status='success'` 的条数(只算成功比价,失败的不计入)。第 N 档在「成功比价次数 ≥ N」时解锁;每档领一次(领取见 [claim 接口](./compare-milestone-claim.md))。
|
||||
|
||||
档位金额是**产品规则**,定义在后端 `app/core/rewards.py` 的 `RECORD_MILESTONES`(当前 `120/180/300/500/800/1200`),客户端**不要写死**,以本接口返回为准。
|
||||
|
||||
## 入参
|
||||
无(用户身份取自 Bearer token)。
|
||||
|
||||
## 出参
|
||||
响应 `200`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `success_count` | int | 累计成功比价次数(解锁进度) |
|
||||
| `claimable_count` | int | 当前可领(state=active)的档数 |
|
||||
| `milestones` | Milestone[] | 各档状态,按 milestone 升序 |
|
||||
|
||||
**Milestone**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `milestone` | int | 档位序号(1-based),= 解锁所需的成功比价次数 |
|
||||
| `coin` | int | 该档奖励金币 |
|
||||
| `state` | string | `claimed`(已领) / `active`(已解锁可领) / `locked`(未解锁) |
|
||||
|
||||
```json
|
||||
{
|
||||
"success_count": 2,
|
||||
"claimable_count": 1,
|
||||
"milestones": [
|
||||
{"milestone": 1, "coin": 120, "state": "claimed"},
|
||||
{"milestone": 2, "coin": 180, "state": "active"},
|
||||
{"milestone": 3, "coin": 300, "state": "locked"},
|
||||
{"milestone": 4, "coin": 500, "state": "locked"},
|
||||
{"milestone": 5, "coin": 800, "state": "locked"},
|
||||
{"milestone": 6, "coin": 1200, "state": "locked"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 错误
|
||||
- `401` 未鉴权
|
||||
|
||||
## 说明
|
||||
- 同时可有多档处于 `active`(如累计 3 次却一档没领,则前 3 档都可领),逐档调 claim。
|
||||
- 进度只增不减:领取不消耗成功次数,只是把对应档从 active→claimed。
|
||||
@@ -0,0 +1,25 @@
|
||||
# GET /api/v1/compare/records/{record_id} — 比价记录详情
|
||||
|
||||
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
||||
|
||||
单条比价记录详情,在列表项基础上额外带 `raw_payload`(客户端上报的原始全量),供未来 UI 展示任意细节。
|
||||
|
||||
## 入参(path)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `record_id` | int | 记录 id |
|
||||
|
||||
## 出参
|
||||
响应 `200`:**ComparisonRecordDetailOut** = [ComparisonRecordOut](./compare-records.md#出参) 全部字段 + 下列:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `raw_payload` | object \| null | 客户端上报的原始 body 全量(calibration + done.params) |
|
||||
|
||||
## 错误
|
||||
- `401` 未鉴权
|
||||
- `404` 记录不存在,或不属于当前用户(限本人,避免越权读他人记录)
|
||||
|
||||
## 说明
|
||||
`404` 同时覆盖「id 不存在」和「id 属于他人」两种情况——不区分以免泄露他人记录是否存在。
|
||||
@@ -0,0 +1,57 @@
|
||||
# POST /api/v1/compare/record — 上报一次比价结果(幂等)
|
||||
|
||||
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
||||
|
||||
比价 `done` 帧后,客户端用**带 JWT 的通道**上报一条比价结果,落 `comparison_record` 表,作为「我的比价记录」数据源 + 用户级行为画像。
|
||||
|
||||
> ⚠️ 与不鉴权的透传端点 [`/api/v1/price/step`](./compare-price-step.md) 不同:那是转发壳,本接口按用户维度落库,**必须鉴权**。
|
||||
> 本轮只做 server 端;客户端在 done 帧后调本接口的改动另起一轮(见 [待办与技术债.md](../待办与技术债.md) P1)。
|
||||
|
||||
## 入参(JSON body)
|
||||
|
||||
字段刻意对齐 pricebot 协议([02_api_protocol.md](../../../pricebot-backend/docs/main/02_api_protocol.md) 的 `calibration` + `done.params`),客户端把 Phase1 的 calibration 和 done 帧 params **零翻译**映射上来即可。
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `trace_id` | string | ✅ | — | pricebot 侧 trace_id。**幂等键**:同用户同 trace_id 重复上报覆盖、返回同一 id |
|
||||
| `business_type` | string | ❌ | `food` | `food`(外卖,当前唯一接通) / `ecom`(电商) / `coupon`(领券) |
|
||||
| `device_id` | string \| null | ❌ | null | 设备号 |
|
||||
| `store_name` | string \| null | ❌ | null | 店铺名(外卖,来自 calibration.result) |
|
||||
| `source_platform_id` | string \| null | ❌ | null | 源平台代号,如 `taobao_flash` |
|
||||
| `source_platform_name` | string \| null | ❌ | null | 源平台中文名 |
|
||||
| `source_package` | string \| null | ❌ | null | 源平台 Android 包名 |
|
||||
| `source_price` | float \| null | ❌ | null | 源平台到手价(**元**) |
|
||||
| `items` | Item[] | ❌ | [] | 下单菜品,`{name, qty, specs?}` |
|
||||
| `comparison_results` | Result[] | ❌ | [] | 逐平台对比,见下表(price 单位**元**) |
|
||||
| `skipped_dish_count` | int \| null | ❌ | null | 目标平台未找到、跳过的菜品数 |
|
||||
| `skipped_dish_names` | string[] | ❌ | [] | 被跳过的菜名 |
|
||||
| `total_dish_count` | int \| null | ❌ | null | 原始菜品总数 |
|
||||
| `information` | string \| null | ❌ | null | done 帧文案(成功摘要 / 失败具体原因),持久化为列并在列表/详情返回,前端失败时当原因展示 |
|
||||
| `status` | string \| null | ❌ | null | `success` / `failed`。**不传则服务端派生** |
|
||||
|
||||
**Item**:`{ name: string, qty: int=1, specs: string[]\|null }`
|
||||
|
||||
**Result(comparison_results 元素)**:`{ platform_id, platform_name, package, price(元,float\|null), is_source(bool), rank(int\|null) }`
|
||||
|
||||
## 服务端派生(客户端不用算)
|
||||
|
||||
从 `comparison_results` 派生并落结构化列:
|
||||
|
||||
- `best_*`:取最便宜的一家(按 `rank` 升序、再按 `price`),写 `best_platform_id/name/best_price_cents`
|
||||
- `source_price_cents`:优先用 `source_price`,缺失则取 `comparison_results` 里 `is_source=true` 行的价
|
||||
- `saved_amount_cents` = `源价 − 最优价`(可为 0 / 负:源平台本来就最便宜)
|
||||
- `is_source_best`:最便宜的一家是否为源平台(= 这次没省到)
|
||||
- `status`(未显式传时):存在「非源且有价」的结果 → `success`,否则 → `failed`
|
||||
- 金额一律 `round(元 × 100)` 存**分**
|
||||
|
||||
`comparison_results` / `items` / `skipped_dish_names` 原样存 JSON;完整上报 body 另存 `raw_payload`(详情接口可取回)。
|
||||
|
||||
## 出参
|
||||
响应 `200`:`{ "id": int }`(写入或已存在记录的 id)
|
||||
|
||||
## 错误
|
||||
- `401` 未鉴权
|
||||
- `422` 缺 `trace_id` 或字段类型不符
|
||||
|
||||
## 说明
|
||||
失败的比价(done 帧只有源/空 comparison_results)也会落一条 `status=failed`——「越详细越好」,是否在 UI 展示由前端过滤。
|
||||
@@ -0,0 +1,47 @@
|
||||
# GET /api/v1/compare/records — 比价记录列表(游标分页)
|
||||
|
||||
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
||||
|
||||
「我的比价记录」列表页数据源。按 `id` 倒序(最新在前)。
|
||||
|
||||
## 入参(query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `limit` | int | ❌ | 20 | 1–100 |
|
||||
| `cursor` | int | ❌ | null | 上一页末条 `id`,首页不传 |
|
||||
|
||||
## 出参
|
||||
响应 `200`:`{ items: ComparisonRecordOut[], next_cursor: int|null }`(分页见 [索引#游标分页约定](./README.md#游标分页约定))
|
||||
|
||||
**ComparisonRecordOut**(列表项,不含 `raw_payload`,减小 payload)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | int | 记录 id(也是游标) |
|
||||
| `business_type` | string | `food` / `ecom` / `coupon` |
|
||||
| `trace_id` | string | pricebot trace_id |
|
||||
| `source_platform_id` | string \| null | 源平台代号 |
|
||||
| `source_platform_name` | string \| null | 源平台中文名 |
|
||||
| `source_package` | string \| null | 源平台包名 |
|
||||
| `source_price_cents` | int \| null | 源平台到手价(分) |
|
||||
| `best_platform_id` | string \| null | 最优平台代号 |
|
||||
| `best_platform_name` | string \| null | 最优平台中文名 |
|
||||
| `best_price_cents` | int \| null | 最优价(分) |
|
||||
| `saved_amount_cents` | int \| null | 省下(分,可 0/负) |
|
||||
| `is_source_best` | bool \| null | 源平台是否最便宜(= 没省到) |
|
||||
| `store_name` | string \| null | 店铺名 |
|
||||
| `total_dish_count` | int \| null | 菜品总数 |
|
||||
| `skipped_dish_count` | int \| null | 跳过菜品数 |
|
||||
| `status` | string | `success` / `failed` |
|
||||
| `information` | string \| null | done 帧文案。成功:"在美团找到同店,到手价 ¥X…";失败:具体原因(如"美团、京东外卖均未找到该商品"),前端在 `status=failed` 时当原因展示 |
|
||||
| `items` | object[] | 下单菜品 `{name, qty, specs?}` |
|
||||
| `comparison_results` | object[] | 逐平台对比(price 单位元,已按 rank 升序) |
|
||||
| `skipped_dish_names` | string[] | 被跳过的菜名 |
|
||||
| `created_at` | datetime | 时间 |
|
||||
|
||||
## 错误
|
||||
- `401` 未鉴权
|
||||
|
||||
## 说明
|
||||
只返回当前登录用户自己的记录。需要单条全量(含 `raw_payload`)走 [详情接口](./compare-record-detail.md)。
|
||||
+6
-1
@@ -61,6 +61,7 @@ app/
|
||||
│ ├── feedback.py # 帮助与反馈 1 端点(提交反馈含截图)
|
||||
│ ├── coupon.py # 领券透传 /coupon/step(转发 pricebot,MVP 不鉴权)
|
||||
│ ├── compare.py # 外卖比价透传 /intent/recognize + /price/step(转发 pricebot,MVP 不鉴权)
|
||||
│ ├── compare_record.py# 比价记录 3 端点(上报 /compare/record + 列表 /compare/records + 详情;鉴权,区别于上面透传)
|
||||
│ ├── meituan.py # 美团 3 端点 + feed 拼接(_interleave / _TOPIC_ROUNDS),未配 MT_CPS 凭证降级返空
|
||||
│ ├── wallet.py # 钱包/提现 11 端点(余额/流水/兑换/绑微信/提现/查单)
|
||||
│ ├── signin.py # 签到 2 端点(状态 / 执行签到)
|
||||
@@ -73,6 +74,7 @@ app/
|
||||
│ ├── feedback.py # 反馈出参(请求是 multipart,在 router 直接校验)
|
||||
│ ├── meituan.py
|
||||
│ ├── welfare.py # 钱包/签到/任务/省钱 收发模型
|
||||
│ ├── compare_record.py # 比价记录上报/列表/详情 收发模型(字段对齐 pricebot calibration + done.params)
|
||||
│ └── ad.py # 看广告发奖收发模型
|
||||
├── integrations/ # 外部服务/SDK 客户端(重逻辑:签名/加解密/外部 HTTP)
|
||||
│ ├── jiguang.py # 极光 REST 验 token + RSA 解密(多 padding 试错)
|
||||
@@ -94,6 +96,7 @@ app/
|
||||
│ ├── signin.py # 签到记录 / 连续天数 / 档位
|
||||
│ ├── task.py # 一次性任务领取
|
||||
│ ├── savings.py # 省钱汇总 / 战绩 / 明细
|
||||
│ ├── comparison.py # 比价记录 upsert(user_id+trace_id 幂等)+ best/saved/status 派生 + 分页
|
||||
│ └── ad_reward.py # 看广告发奖(按 trans_id 幂等 + 每日上限 + 本轮冷却派生)
|
||||
├── models/ # ORM 表结构
|
||||
│ ├── user.py # user(含微信 openid/nickname/avatar)
|
||||
@@ -102,6 +105,7 @@ app/
|
||||
│ ├── signin.py # 签到记录
|
||||
│ ├── task.py # 任务领取记录
|
||||
│ ├── savings.py # 省钱明细 / 店铺菜品 / dishes(PG 上 JSONB)
|
||||
│ ├── comparison.py # 比价记录(完整明细;含 4 个 JSON(B) 列 + raw_payload;独立于 savings)
|
||||
│ └── ad_reward.py # 看广告发奖记录
|
||||
└── db/
|
||||
├── base.py # DeclarativeBase
|
||||
@@ -218,7 +222,7 @@ POST /api/v1/auth/sms/login { phone, code } → 任意 6 位通过 → upsert
|
||||
|
||||
## 7. 数据模型
|
||||
|
||||
10 张业务表 + `alembic_version` 框架表。生产 PG / 开发可回退 SQLite。
|
||||
业务表(下表)+ `alembic_version` 框架表。生产 PG / 开发可回退 SQLite。
|
||||
|
||||
| 表 | models 文件 | 说明 |
|
||||
|---|---|---|
|
||||
@@ -231,6 +235,7 @@ POST /api/v1/auth/sms/login { phone, code } → 任意 6 位通过 → upsert
|
||||
| `signin_record` | `models/signin.py` | 签到记录(每日一行,索引 `(user_id, signin_date)`) |
|
||||
| `user_task` | `models/task.py` | 一次性任务领取记录 |
|
||||
| `savings_record` | `models/savings.py` | 省钱明细(`dishes` 在 PG 上为 JSONB,SQLite 上是 TEXT) |
|
||||
| `comparison_record` | `models/comparison.py` | 比价记录(每次比价完整明细;`(user_id, trace_id)` 唯一幂等;4 个 JSON(B) 列含 `raw_payload`;独立于 savings) |
|
||||
| `ad_reward_record` | `models/ad_reward.py` | 看广告发奖(`trans_id` 唯一,幂等) |
|
||||
|
||||
**user 表字段速查**:
|
||||
|
||||
+6
-2
@@ -11,8 +11,8 @@
|
||||
|
||||
- **现状**:agent 系列接口 MVP 阶段**全部不鉴权**。领券 `coupon/step` **已落地不鉴权**(✅ 已去掉 `CurrentUser` 依赖);比价 4 个(`intent/recognize`、`price/step`、`ecom/intent/recognize`、`ecom/step`)端点尚未建(见 P2),建时同样先不加鉴权。
|
||||
- **代价(为什么记这笔账)**:不验 JWT → 转发时 server 拿不到 `user_id` → agent 行为只能绑到 `device_id`(设备级),**采集不到"哪个用户领了/买了什么"的用户级画像**。而精准人群画像、私域分群运营是商业模式的核心资产,靠的就是这份用户级行为数据。`device_id` 仍照传(后端按设备串领券队列够用)。
|
||||
- **待补**:① agent 接口加 JWT 鉴权;② 建立 `device_id ↔ user_id` 绑定(登录后上报一次即可);③ 领券/比价记录按 user 维度落库。
|
||||
- **连带**:补鉴权后,客户端引擎 `ApiClient` 要接回 JWT(复用 app 现有 `AuthInterceptor` 思路:注入带鉴权头的 OkHttpClient)。
|
||||
- **待补**:① agent 接口加 JWT 鉴权;② 建立 `device_id ↔ user_id` 绑定(登录后上报一次即可);③ ~~领券/比价记录按 user 维度落库~~ → **比价记录已落地**(见文末「已解决」):走**独立的鉴权端点** `POST /api/v1/compare/record`,客户端在 done 帧后用带 JWT 的通道上报、不依赖透传链路。**所以透传端点本身仍不鉴权、本条 P1 主诉求(给透传链路补 JWT + device↔user 绑定)未解**;领券记录尚未落库。
|
||||
- **连带**:补鉴权后,客户端引擎 `ApiClient` 要接回 JWT(复用 app 现有 `AuthInterceptor` 思路:注入带鉴权头的 OkHttpClient)。比价记录上报已**先行复用**这套(`authedClient` 挂 `AuthInterceptor`+`RefreshAuthenticator`),可作透传链路补鉴权的参考样板。
|
||||
|
||||
---
|
||||
|
||||
@@ -87,6 +87,10 @@
|
||||
|
||||
## 已解决
|
||||
|
||||
- ✅ **比价记录落库(server + client,2026-05-31)**:每次比价 done 后客户端上报、按 user 落库,作「我的比价记录」数据源 + 用户级画像沉淀。
|
||||
- **server**(本仓):新表 `comparison_record`(独立于 `savings_record`;结构化列 + `items`/`comparison_results`/`skipped_dish_names`/`raw_payload` 四个 JSON(B) 列)+ 3 个**鉴权**端点 `POST /api/v1/compare/record`(`(user_id,trace_id)` 幂等 upsert,best/saved/is_source_best/status 服务端从 comparison_results 派生)/ `GET /api/v1/compare/records`(游标分页)/ `GET /records/{id}`(含 raw_payload);`models/comparison.py` + `repositories/comparison.py` + `schemas/compare_record.py` + `api/v1/compare_record.py` + 迁移 `comparison_record_table`(head `b2c3d4e5f6a7`)+ `tests/test_compare_record.py`(8 例全过)+ `docs/api/compare-record-*.md`。
|
||||
- **client**(android 仓):`PriceBotService.runTask()` 比价 done 后(`lastDoneParams!=null`,成功/引擎失败都报)用独立 IO 协程尽力上报;`Protocol.CompareRecordRequest.fromComparison()` 从 calibration+done.params 零翻译组装;`ApiClient.reportCompareRecord()` 走新建的 `authedClient`(复用 `AuthInterceptor`+`RefreshAuthenticator`,自动 Bearer+401 刷新)。领券不报(非价格对比,本期范围只外卖)。
|
||||
- **未做**:UI(「我的比价记录」页)等原型;真机端到端待验。
|
||||
- ✅ **领券链路接通**(阶段 1):首页「去领取」→ `CouponPromptDialog` → 权限检查 → `startCouponClaim` → 循环 `/api/v1/coupon/step`;Running 悬浮窗换皮(ComposeView 承载 `CouponProgressPanel`/AgentFloat,`OverlayLifecycleOwner` 撑 Compose)。
|
||||
- ✅ **coupon/step 去鉴权**:去掉 `CurrentUser`,MVP 不鉴权(device_id 透传)。
|
||||
- ✅ **ApiClient 改址**:`BuildConfig.BASE_URL` + `/api/v1/` 前缀。
|
||||
|
||||
Reference in New Issue
Block a user