Files
meida/docs/开发任务包/T2b-任务换额度-端侧闭环.md
xsl f244f14ab8 feat(billing): 任务换额度端侧闭环(T2b 定稿 4 任务)
- TaskBonusPanel 重写:每日任务/新手任务分组、五态按钮、真实「去完成」跳转(first_report→诊断落地页;report_feedback/share_report→报告结果页)、40311/40310 文案、panel_view/task_tap 埋点
- ReportPdfPage 分享成功→静默 claim share_report;ReportResultPage SUCCEEDED 展示→静默 claim first_report(后端 40910 幂等兜底,老用户首进补发一次属预期回填)
- ChatPage/ReportResultPage 修 bonus_granted 类型误写(boolean→number)
- docs:T2b 任务书(验收 11 条)+ 产品方案 §3.2 任务目录定稿(签到对话+10/日、报告反馈发型+2/日、分享换装+3/日、首测重生成+1/终身;邀请好友砍)

偏差说明:share_report 落点为报告结果页而非 PDF 预览页(该页强依赖本地 pdfPath,直达需下迭代加持久化)。
2026-09-12 00:25:08 +08:00

14 KiB
Raw Permalink Blame History

T2b · 任务换额度:端侧闭环与完成判定

对应任务:T-P1-26 收尾件之二(T2 的后续迭代)| 需求:FR-BILL-03、FR-BILL-04、FR-BILL-07 | 跨端任务:后端(meida_backend)+ 端侧(app/) 先读《README.md(协作规程)》与《README-FE批次.md》,遵守全部纪律。 产品定稿依据:产品方案 §3.2「任务奖励额度的获取方式」(2026-09-11 定稿表与决策理由)。

1. 目标

把「做任务换额度」从「只有签到能真领」补齐为完整闭环:

  1. 任务目录按产品定稿落地(4 项上线 + invite_friend 下架),奖励数值由占位值 1 改为定稿值;
  2. 有服务端可信信号的任务(report_feedback / first_report)在 claim 时做完成校验,无凭据拒绝;
  3. 端侧面板从「toast 占位」升级为真实「去完成」跳转 + 完成点自动 claim;
  4. 全程遵守 FR-BILL-07:文案不出现价格/购买/会员/升级字样。

2. 现状事实(2026-09-11 读码核实)

后端(meida_backend)

  • ✅ POST /quota/tasks/{task_key}/claim 已实现(T2):幂等(bl_ledger.idempotency_key 快捷路径 + 唯一约束并发兜底)、封顶(grant_bonus 对 task_bonus_max)、配置化(TASK_BONUS_RULES_JSON)、错误码 40410/40910/40310 文案合规。见 apps/api/routers/quota.py:65、packages/billing/service.py:306、packages/domain/billing/quota.py:146。
  • ✅ 配额种子与产品方案 §3.2 对齐(migrations/versions/0003_align_quota_with_product_ssot.py):agent_chat 10/10/40、tryon 3/3/8、hairstyle 3/2/6、hd_export 1/0/3、report_basic 终身 1、report_regen 月 1/1/2。
  • ❌ claim 不做任何完成凭据校验:任何登录用户对任何 task_key 直接 POST 即领。report_feedback(rp_feedback 表)与 first_report(rp_report 表)明明有可信信号却没用上。
  • ❌ task_bonus_rules 默认值(packages/shared/config.py:136)amount 全是占位值 1,且映射与 §3.2 总量对不上:换装最多 +2/日(表定 +3)、发型 0(表定 +2)、对话 +2(表定 +10)。
  • ❌ 无任务目录下发接口——V1 决定端侧静态表,不做。
  • 事实备注:rp_share 表已建结构但无路由(models.py 注释「V1 只建结构」);POST /events/batch 无事件白名单(事件字典待 TD-23)。

端侧(app/entry/src/main/ets/)

  • ✅ components/f1/TaskBonusPanel.ets:五项静态任务、已领态走 core/TaskBonusState.ets(preferences meida_task_bonus,跨重启)、40910 同步已领态、文案合规;已挂载 5 处(ProfileContent / ChatPage / FittingRoomPage / HairDesignPage / HairColorPage)。
  • ✅ 真实闭环已有两条:sign_in 点击即 claim(面板内);report_feedback 在报告页反馈提交成功后静默自动 claim(ReportResultPage.ets:435)。
  • ✅ DailyLimiter.setBonus 已把 bonus_granted 计入试穿/试戴当日上限;ChatPage 对话额度服务端口径(remaining 含 bonus)。
  • ❌ 其余 3 项任务是 toast 占位(「即将上线」),无「去完成」跳转。
  • ❌ share_report 无联动:ReportPdfPage.ets:90 已有 Share Kit systemShare 分享 PDF,但分享动作与 claim 之间没有凭据、没有联动。
  • ❌ 报告生成成功处无 first_report claim 联动。
  • ❌ 面板不展示每项的奖励数值与周期(任务表纪律是"数值不硬编码",定稿后可写入展示文案,到账仍以 claim 响应为准)。
  • 🐞 ChatPage.ets:36 QuotaWireResp.bonus_granted 类型误写为 boolean(后端返回 number;该字段暂未消费,本迭代顺手修正)。

判定与埋点

  • ✅ 埋点链路通:Tracker → POST /events/batch → ob_event(整批 fail-close 校验)。
  • ❌ 任务完成事件目前只有 task_bonus.claim_result;无面板曝光、任务点击、分享调起事件。
  • 产品决策(已写进 §3.2):埋点只作观测,不作发放凭据——埋点本身可伪造且有 20 条/30s 节流延迟,拿它做校验是自欺。

3. V1 任务目录(产品定稿,2026-09-11)

task_key 名称 quota_key amount claim_period 完成判定 状态
sign_in 每日签到 agent_chat 10 day 点击签到即完成,无额外校验 上
report_feedback 给报告结论打反馈 hairstyle 2 day 服务端校验当日 rp_feedback 存在 上
share_report 分享我的报告 tryon 3 day 端侧自报,无服务端校验(内测水位) 上
first_report 完成首次形象速测 report_regen 1 lifetime 服务端校验 rp_report 存在已完成报告 上
invite_friend 邀请好友 — — — 无可信信号 下架(env 不下发该键)

变更点:删除 profile_complete(资料维护 FR-ACC-06 V1 未实现,无可信信号),新增 first_report;report_feedback 由 report_regen 改挂 hairstyle(理由见产品方案 §3.2 决策理由第 3 条)。

4. 实现要点

后端(worktree 纪律见《README.md》)

允许改动:packages/billing/service.py、packages/billing/repo.py(如需)、packages/billing/schemas.py(如需)、测试新文件。

  1. B1 · 定稿数值走 env:部署/本地 .env 的 TASK_BONUS_RULES_JSON 改为:
    {
      "sign_in":         {"quota_key": "agent_chat",   "amount": 10, "claim_period": "day"},
      "report_feedback": {"quota_key": "hairstyle",    "amount": 2,  "claim_period": "day"},
      "share_report":    {"quota_key": "tryon",        "amount": 3,  "claim_period": "day"},
      "first_report":    {"quota_key": "report_regen", "amount": 1,  "claim_period": "lifetime"}
    }
    
    不含 invite_friend——不下发即下架(claim 返回 40410「任务不存在或暂未开放」),这是配置化设计的红利,不要为它写分支代码。 ⚠️ config.py 的默认值是占位且禁改;在提交信息里写明「默认值与定稿不一致,建议协调提交同步默认值与 .env.example」,由协调者统一处理。
  2. B2 · claim 完成校验钩子:BillingService.claim_task_bonus 在解析规则后、幂等快捷路径之前,按 task_key 执行完成校验(映射写死在 service 内即可,不做配置化):
    • report_feedback:rp_feedback 存在 user_id 匹配且 created_at 落在当日(UTC+8,与 period_key 同时区口径)的行;
    • first_report:rp_report 存在 user_id 匹配且状态为已完成的行;
    • sign_in / share_report:不校验,写注释说明产品决策(签到行为本身即任务;分享内测期自报,hard_cap 兜底,见 §3.2 决策理由 4)。
    • 校验不过 → AppError(ErrorCode.TASK_BONUS_NOT_ELIGIBLE)。该错误码尚不存在(建议 40311,meta「先完成任务,再来领奖励」)——errors.py 禁改,在提交信息中列出需求,由协调提交统一加;本地开发可先在 service 里 raise AppError(...) 引用占位并在提交信息标注。
  3. B3 · 幂等与封顶语义不变:40910/40310 路径、delta=0 流水口径不动;校验逻辑不得影响既有测试。

端侧(纯 Windows,构建门禁见《README-FE批次.md》§1)

允许改动:components/f1/TaskBonusPanel.ets、pages/ReportResultPage.ets、pages/ReportPdfPage.ets、pages/ChatPage.ets(仅 B 端 wire 类型修正)。其余页面如需挂回调点,在提交信息里说明。

  1. E1 · 面板任务表更新为定稿 4 项:删 invite_friend;profile_complete 替换为 first_report(名称「完成首次形象速测」);每项 desc 写清奖励内容(如「领 10 轮 AI 对话额度」「领 2 次发型试戴额度」)并标注周期(「每日」/「一次性」)。到账数值仍以 claim 响应 amount 为准,desc 只是展示。
  2. E2 · 「去完成」真实跳转(router.pushUrl,禁止 import 页面):
    • first_report → pages/DiagnosisLandingPage;进入面板时若已可领(服务端已有报告且未领过)则按钮直接为「领额度」走 claim;
    • report_feedback → 报告结果页(带 reportId 参数);无报告时 toast「先生成你的形象速测,再来反馈」;
    • share_report → pages/ReportPdfPage(分享入口所在页);无报告时同上引导;
    • sign_in 保持点击即领。
  3. E3 · 完成点自动 claim(与既有 claimReportFeedback 同款静默闭环:成功 toast 到账、40910 静默同步已领态、其他失败静默):
    • ReportPdfPage.share() 调起系统分享面板成功后:静默 claim share_report + TaskBonusState.markClaimed('share_report', PERIOD_DAY) + Tracker.track('report.share_invoke', ...);
    • 报告生成任务成功(ReportResultPage 展示 SUCCEEDED 结果的入口路径):静默 claim first_report + markClaimed。
  4. E4 · 状态与文案:见 §5,逐字遵守。
  5. E5 · 埋点补齐(事件名字面量、props 只放低维枚举/数值):task_bonus.panel_view、task_bonus.task_tap(props: task_key)、report.share_invoke、task_bonus.claim_result(已有,保留)。
  6. E6 · 修正 ChatPage.ets:36 bonus_granted: boolean → number。

5. UX 与文案规格(FR-BILL-07,逐字遵守)

面板信息架构:标题「做任务换额度」;分组一「每日任务」(签到 / 打反馈 / 分享报告);分组二「新手任务」(完成首次形象速测);底部说明保留「奖励以实际到账为准,额度每日重置」。

按钮五态:

状态 文案
可领取(签到 / 已满足条件) 「领额度」
需跳转完成 「去完成」
领取中 「领取中…」
已领(日周期) 「今日已领」
已领(终身) 「已领取」

toast:

场景 文案
领取成功 「签到成功,额度 +{amount}」/「感谢反馈,额度 +{amount}」(amount 取响应值,不硬编码)
40910 本周期已领 「今天已经领过啦」
40310 奖励封顶 「今日任务奖励已达上限,明天再来」
40311 未完成 「先完成任务,再来领奖励」
无报告时去做反馈/分享 「先生成你的形象速测,再来反馈」

红线:全部新增文案不得出现价格、购买、会员、升级、充值等字样(FR-BILL-07);额度耗尽入口文案维持「今日额度已用完,明天再来,或做个任务多换几次」口径。

6. 测试要求

后端

  • 单测:校验钩子纯逻辑(若抽纯函数)覆盖有/无凭据两分支。
  • 集成测试新文件 tests/integration/test_task_bonus_eligibility.py(允许跑,MEIDA_RUN_INTEGRATION=1):
    1. 无报告用户 claim first_report → 40311;创建报告完成后 → 200 且 bonus_granted=1;
    2. 无反馈 claim report_feedback → 40311;提交一条反馈后 → 200 且发型 bonus_granted=2;
    3. sign_in claim → 200,agent_chat bonus_granted=10;当日再 claim → 40910;
    4. invite_friend(不在 env)→ 40410;
    5. 40311 与 40310 路径均不写流水(查 bl_ledger 无新行)。
  • 提交前全绿:ruff check / ruff format --check / mypy(范围同 T2)/ lint-imports / pytest -q。

端侧

  • 构建门禁(唯一验收命令,见《README-FE批次.md》§1)BUILD SUCCESSFUL;
  • ArkTS 严格模式铁律(README-FE批次 §3)逐条遵守:显式泛型、具名 wire interface、throw 只抛 ApiError。

7. 禁改清单(通用禁改之外)

  • 后端:packages/shared/config.py、packages/shared/errors.py、packages/shared/models.py、migrations/**、apps/api/routers/events.py、packages/analytics/**。新错误码与默认值同步只写进提交信息,由协调者处理。
  • 端侧:core/**(含 TaskBonusState.ets、Tracker.ets、DailyLimiter.ets——本迭代需求已够,不要顺手扩展)、module.json5、main_pages.json、resources、oh-package*.json5。
  • 不改配额种子数值、不动 grant_bonus 语义、不改既有测试断言语义。

8. 验收标准(按组,每条可验收)

后端

  1. 四个 task_key 按 §3 定稿数值 claim 成功,响应 amount 与定稿一致;invite_friend 返回 40410。
  2. 无凭据 claim report_feedback / first_report 返回 40311 且无流水;有凭据后 claim 成功。
  3. 重复 claim 40910、封顶 40310 行为与 T2 验收一致,无回归(既有 test_task_bonus.py 全绿)。
  4. 后端检查全绿(§6)。

端侧

  1. 面板只展示 4 项定稿任务,奖励文案、周期标注、五态按钮与 §5 一致。
  2. 三项「去完成」跳转可达目标页;无报告场景给引导 toast 而非报错。
  3. 分享调起成功、报告生成成功两处自动 claim 生效,面板已领态同步(含杀进程重启后)。
  4. ChatPage wire 类型修正后构建绿;埋点新增事件在 ob_event 可见。

联调

  1. 全链路走查(真机或模拟器连本地后端):签到 → 对话额度卡 +10;报告页打反馈 → 发型当日上限 +2(DailyLimiter 生效);PDF 页分享 → 换装当日上限 +3;完成首次速测 → 重生成额度 +1(个人中心额度卡可见)。
  2. 异常路径走查:同日重复领取 →「今天已经领过啦」;未打反馈直接在面板点领取(若 UI 可达)→「先完成任务,再来领奖励」。
  3. 文案红线检查:任务面板、toast、额度耗尽提示全文检索无「价格/购买/会员/升级/充值/¥」字样。

9. 提交信息

后端:

feat(billing): task bonus final rules + eligibility check (T2b)

TASK_BONUS_RULES_JSON 定稿(签到+10对话/反馈+2发型/分享+3换装/首测+1重生成,
invite_friend 下架);report_feedback/first_report claim 前完成校验(rp_feedback/
rp_report 可信信号),sign_in/share_report 不校验(产品决策见产品方案 §3.2)。

偏差说明:TASK_BONUS_NOT_ELIGIBLE(40311) 错误码与 config.py 默认值同步需协调提交;
<其余偏差写这里,无则写"无">

端侧:

feat(f1): task bonus panel final catalog + auto-claim hooks (T2b)

面板 4 项定稿任务与「去完成」真实跳转;分享调起/报告生成成功自动 claim;
新增 panel_view/task_tap/share_invoke 埋点;修正 ChatPage bonus_granted 类型。

偏差说明:<如有写在这里,无则写"无">