Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
14 KiB
PROGRESS · 生时校正匿名聚合统计(2026-09-26)
- 任务书:
docs/tasks/TASK-rectification-telemetry-20260926.md - 分支:
codex/rectification-telemetry-20260926,基线origin/staging7203d94e(fewer-probes-card 合入之后);提交前变基到f74825a2(其后只多了离线研究脚本与文档,不涉及本单文件) - 执行:Claude 子代理(直接执行模式,产品授权)。未推送。
- 产品新增能力,任务书未要求 BUG 号,不开 BUG 号。Skill 不 bump(用户可见行为不变)。
结论先行
| 任务 | 做了什么 | 状态 |
|---|---|---|
| T1 迁移 + RLS + 保留期 | frontend/supabase/migrations/20260926010000_rectification_telemetry.sql:两张新表(统计行 rectification_telemetry、去重台账 rectification_telemetry_reported_cases)、三个 SECURITY DEFINER 函数(写 / 清理 / 汇总)。只加不改 |
完成;test:db 本机无 Docker 未跑,以门禁 DB job 为准 |
| T2 写入点 + 纯函数 + 白名单测试 | frontend/src/lib/rectification-agentic/v9/telemetry.ts;挂在 GET /api/rectification/cases/[caseId],复用这次响应已算好的决策与卡片;setImmediate 后写、不 await、吞错 |
完成 |
| T3 后台汇总页 | 「校正统计」/admin/rectification-telemetry + GET /api/admin/rectification-telemetry:宽度分布、提问数分布(含分类均值)、停止原因占比、门槛达标率、按周趋势、差距分布(=卡上是否显示百分比)、出生时间来源、版本 |
完成 |
| T4 记录 | CHANGELOG、DESIGN(后台节)、本文件、docs/testing/rectification-telemetry-20260926.md、docs/operations/rectification-telemetry-privacy.md、BLOCKED |
完成 |
字段表(行里只有这些,外加 id 随机 uuid 和 recorded_week)
| 字段 | 取值 / 口径 | 来源 |
|---|---|---|
recorded_week |
记录所在 ISO 周的周一(UTC),库里默认值生成 | DB default |
window_radius_minutes |
出卡时搜索窗口宽度的一半,0–720 | case.candidateRange |
birth_time_source |
hospital_record / approximate / period_only / unknown(block_scan 阶段或来源为 unknown 记 unknown;family_exact、legacy_import、空都并入 approximate,与 normalizeBirthTimeSource 一致) |
case.birthTimeSource、case.stage |
questions_total |
已问过的问题数:焦点状态为 active / resolved / declined / skipped;superseded(未答就被换掉)不算 |
焦点列表 |
questions_targeted |
问题 id 以 collect:targeted: 开头 |
同上 |
questions_guided |
以 collect:guided: 开头(D1 之后每个校正最多 2 道,可直接看新流程是否生效) |
同上 |
questions_dated_probe |
schema 带 probe_year 的点选探针 |
同上 |
questions_personality |
choice_kind = varga_style、tie_break_round、D9/D10 风格题,以及不带年份的引擎探针(09-09 起按性格题处理) |
同上 |
questions_open |
其余 collect:*(开放采集、邀请补经历、职业、其他) |
同上 |
| 其他类 | 不单独存;= 总数 − 五类之和(时段选择、扩窗等),汇总页显示 | 派生 |
experiences_added |
仍有效的经历件数(confirmed / draft / pending_confirmation) | 证据台账 |
range_width_minutes |
卡片范围宽度(卡片没有范围时用决策的可信范围),0–1440 | range_delivery.range |
candidate_count |
仍在比较的候选分钟数 | decision.separation.ranked |
top_two_gap_points |
卡片三列相对可能性第一与第二之差(百分点);只有一列记空 | range_delivery.columns |
stop_reason |
见下 | 决策 + 焦点 + 推断回执 |
precision_gate_met |
决策层本轮测得的 D1 门槛 | decision.precisionGateMet |
duration_seconds |
从最早一轮到现在的秒数,封顶 1 年 | 轮次时间 |
algorithm_version / policy_version / skill_version |
只允许 [A-Za-z0-9._:+-]{1,64},否则记空 |
快照 / Case |
停止原因(先中先得):
user_stopped:sessionOutcome = provisional_range_user_stopped(Case 为 paused,点「停止」或说不想继续)。converged:精度门槛达标。round_cap:决策层自己的熔断budgetExhausted(8 轮 / 有效答题上限 / 平台期),从推断回执算,和决策用的是同一个函数(本单只加了export)。user_no_more:stopReason = user_uncertainty_too_high(多半答「说不好」),或出卡前最后一道已关闭的采集题被用户答了「没有 / 记不清」。pool_exhausted:其余(题源问空,包括probe_pool_exhausted、tied_first、七条线问完就出卡)。error:枚举保留,当前没有任何路径会写(产品里不存在「因错误结束一个校正」的事件)。
与新流程(fewer-probes-card,7203d94e)的对应:引导题数量(D1 上限 2)直接是 questions_guided;「卡上是否显示百分比」(D4,差距 ≥ 5)没有单独存,由 top_two_gap_points 派生——汇总页的差距分布按 0–2 / 3–4 / 5–9 / 10+ / 只有一列分桶,5–9 与 10+ 即显示了百分比。这样以后阈值变了,历史行仍能按新阈值重算,也没有在任务书字段表之外另加字段。
偏离任务书之处(请验收方裁决)
- 不存用户 id(D4 更严)。任务书 D4 允许为去重与删除联动存用户 id;协调方的硬红线是行里不存任何用户 / 案例 / 会话 id。落法:统计行没有任何 id;去重另用
rectification_telemetry_reported_cases(case_id),外键on delete cascade到校正记录——账户删除 → 身份用户 → Case 级联 → 台账行删除。台账没有时间列也没有指向统计行的列,统计行无法关联回人;它本来就不含个人信息,所以账户删除后留下也不违反 D4 的目的。 - 只在「第一次出卡」时写,不覆盖放弃 / 超时(D2 的后半句未做)。产品里没有「放弃」或「超时结束」事件:Case 一直可续,
POST /api/rectification/cases/[caseId]/close前端没有调用方。没出卡就离开的会话目前不入统计。要覆盖需另开单(例如新开校正时把旧 Case 记为放弃,或定时扫描长时间无活动的 Case),届时可沿用error/ 新增abandoned枚举(加枚举要改 CHECK,属收紧/放宽约束,需按 §7.6 拆两轮)。 - 时间只到周。按周趋势只需要周;记录日期精确到天会让统计行能和 Case 的活动时间对上,所以只存周一日期。
- 权限用
admin.customers.read。现有权限里没有「只给产品负责人」的读权限;能看单个客户资料的角色看汇总不增加暴露面。若要只给 owner,需新增权限(动 RBAC 表),本单未做。
迁移(为什么向后兼容)
- 只有
create table×2、create index×1、create or replace function×3(均为新函数名)、RLS 开启与 grant / revoke(只作用于新对象)。没有改任何旧表、旧函数、旧权限。 - 当前已部署的代码不引用这些对象;门禁「Migrate Staging Database」先迁移后部署,旧代码在迁移后照常运行;新代码若在迁移前运行,写入失败也只记一行日志(吞错),不影响用户。
- 表权限:两张表 RLS 开启,
anon/authenticated/app_runtime/admin_runtime/service_role全部 revoke,没有 policy;写只能走record_rectification_telemetry(service_role),读只能走rectification_telemetry_summary(admin_runtime),清理purge_expired_rectification_telemetry(service_role)。 - 保留期 180 天:每次写入先删「周一早于今天 − 180 天」的行,汇总不读 180 天以前的行,另有手动清理函数。没有定时任务。
- 写入函数先核对 Case 属于该用户;任一 CHECK 不过(枚举、范围、版本格式、五类之和不超过总数)整笔回滚,去重标记不会残留。
测试
新增
| 文件 | 条数 | 内容 |
|---|---|---|
frontend/tests/rectification-telemetry.test.ts |
18 | 行键 = 白名单(新增字段必须改测试);写入参数 = 白名单 + 两个 id;行里只有整数 / 布尔 / 枚举 / 版本号,虚构姓名、生日、地点、原话、id、HH:MM 都不出现;各字段取值;非法版本与缺值;问题分类;来源分类;停止原因全分支(含 budgetExhausted 同源);资格(非交付、无卡、历史读、停止);调度:不在本 tick 写、同 Case 只写一次、只读 / 不合格不写、写失败 / 同步抛错 / build 抛错都不外抛且日志只有原因码;迁移源文本:表列 = 白名单、函数参数 = 白名单、只加不改、RLS、授权、180 天;GET 路由 void 调度不 await |
frontend/tests/admin-rectification-telemetry-contract.test.ts |
5 | 后台接口只读、只调汇总函数、不直接查表、admin 无表权限;菜单与权限;无导出;解析器固定形状;周数只收 4 / 12 / 26;标签覆盖全部停止原因、差距分桶与卡片 5 个百分点规则对齐 |
frontend/tests/database-rectification-telemetry.test.ts |
1(Docker) | 真实 PG17:列白名单、RLS、各角色零表权限、函数执行权限矩阵、admin 直接查表被拒、写入 / 重复写入 no-op、他人 Case 被拒、五种非法值整笔回滚后仍可写、180 天清理、admin 汇总内容且不含 id / 生日 / 地点、周数封顶 26、账户删除级联台账而统计行保留。本机 docker unavailable 跳过 |
改动的既有断言(原值 / 新值 / 原因)
| 文件 | 原值 | 新值 | 原因 |
|---|---|---|---|
frontend/tests/database-local-business.test.ts(public 表白名单) |
profiles 后直接 redemption_attempts |
中间加 rectification_telemetry、rectification_telemetry_reported_cases |
本迁移新增两张表;该断言要求与 pg_tables 一致(Docker 测试,本机跳过) |
tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources |
admin/products 后直接 admin/roles |
中间加 admin/rectification-telemetry |
新后台页是新的 app 路由,能力审计扫描到 |
两处都在代码里写了三栏注释。未弱化任何断言。
前端全量(Node 22.14.0,/exec-daemon/node)
- 基线:
origin/staging7203d94e同代码日志 4012 条 / 24 失败(全部 Docker / DB)。 - 本分支:4036 条 / pass 3984 / fail 24 / skipped 28。失败名单与基线逐条
diff为空(新增失败 0);测试名列表无消失,新增 24 个名字(18 + 5 + 1)。 tsc --noEmit0 错;npm run lint0 error(126 warning,均为既有文件,本单改动文件单独 eslint 0 输出)。
Python 门禁集合
python3 -m pytest $(cat gate-pytest-args.txt):948 passed / 1 skipped,与基线一致,0 失败。
构建
npm run build -- --webpack:成功;/仍○ Static;新增ƒ /admin/rectification-telemetry、ƒ /api/admin/rectification-telemetry。- rootMainFiles gzip:zlib 默认级 131,255 B / 9 级 130,950 B;基线 130,933 B → +0.25%(默认级)/ +0.01%(9 级),在 ±2% 内。增量来自 webpack runtime 里多了新后台页 chunk 的映射,首页代码未变。
- 构建产生的
frontend/frontend/已删除,未提交。
截图(虚构汇总数据)
后台布局要求服务端管理员会话,本机没有;截图用一个临时页面(未提交,已删除)把同一个 AdminApp + RectificationTelemetrySummaryView 渲染出来,next start + Chrome 无头,CDP 拦截 /api/admin/session 与 /api/admin/rectification-telemetry,后者返回经真实 parseRectificationTelemetrySummary 处理过的虚构汇总。控制台 0 error。
docs/testing/rectification-telemetry-20260926/telemetry-data-1280.png:桌面,98 个虚构会话docs/testing/rectification-telemetry-20260926/telemetry-data-390.png:手机宽度(按周趋势表横向滚动)docs/testing/rectification-telemetry-20260926/telemetry-empty-1280.png:无数据时
首轮截图后按手机宽度把分布表「占比」列从固定 200px 改为 36% 并重截,上面三张是改后版本。最终干净构建复测 gzip 不变(131,255 / 130,950 B)。
环境缺口
- 无 Docker:
npm run test:db未跑(database-rectification-telemetry.test.ts与database-local-business.test.ts的新表白名单都待门禁 DB job;run 号推送后补记)。 - 无受控后台账号:登录态、真实数据按
docs/testing/rectification-telemetry-20260926.md待产品走。 - 未部署。
没做的
- 放弃 / 超时结束的会话不入统计(见偏离 2)。
error停止原因无写入路径。- 没有定时清理任务(写入时清理 + 汇总只读 180 天内)。
验收(Claude,2026-09-26)
基于 origin/staging f74825a2 独立复跑(Node 22.14):tsc 0;lint 0 error;npm test 4036 / 24 fail,失败名单与 7203d94e 基线(4012 / 24)逐条一致;Python 门禁集退出 0;next build --webpack /、/chart、/ephemeris、/people 仍 ○ Static,rootMainFiles gzip(level 9)130933 B,±0%。迁移逐段审读:只加表/索引/函数,begin/commit 与授权写法与 0925 系列迁移一致,RLS 开、运行时角色无表权限、写只经 SECURITY DEFINER RPC、读只出聚合。
偏差裁定:
- 统计行不存用户 id、改用按 case_id 级联删除的去重账本——比任务书 D4 更严,认可。
- 放弃/超时的会话不记录——产品里没有这个事件,认可;汇总只覆盖「出过卡」的会话,读数时须记住这个偏差。
- 周粒度日期、
admin.customers.read权限——认可。
环境缺口:npm run test:db 以本次推送的门禁 DB job 为准(run 号见下方补记);管理员真实登录走查见 docs/testing/rectification-telemetry-20260926.md。
补记(2026-09-26):40d7930a 的门禁 run 2937 三次都挂在 npm ci(镜像挂起,900s 超时,非代码问题);产品侧修复 CI(e801fcf5)后门禁 run 2938 通过:Python 948 passed / 1 skipped,前端含 DB 4049 / 0 fail / 0 skip,其中 database-rectification-telemetry 在真实 PostgreSQL 通过;migrate run 2940 应用 20260926010000_rectification_telemetry.sql;deploy run 2941,/api/health gitCommit = e801fcf5。剩余:管理员登录态真实页面走查。