1. 09 Jun, 2026 2 commits
    • docs(deploy): reparse SOP 写入 deployment-data-ingest + 标 task#46 完成 + 诊断 code 长尾待办 · 0ef13ebb
      - 新增 §六bis Reparse SOP:原理(raw_payload 离线重跑、非破坏、不连 DW)/ 用法(dry-run/全量/
        --patient/--no-recompute/docker)/ 实测基线(全量 541k ~28min,内存 ~630MB;单批 274 ~46s)/
        典型场景(诊断漏 code → 漏召,正畸 5554→1)。
      - §五运维表 + §六 yaml 变更表:truncate 重导 → 改指向 reparse。
      - §八已知边界:reparse 模式缺  已落地;新增"诊断 code 长尾待评估"(松动牙/种植体周围炎,
        需业务决策,不是补 code 能解决)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(parser): K07 关键词补"合"简写(反合/开合/对刃合)—— host 高频正畸漏召 · 807ded08
      服务器 reparse 后残留 279 条正畸 null,几乎全是用"合"简写(反合 120+、开合系 ~70、对刃合)
      而非"颌/牙合"的 crossbite/openbite 诊断。补这 3 个特定术语(不误伤:咬合/深覆合 等不含子串),
      预计再 reparse 把残留压到 ~30(剩习惯/TMD 等非正畸,合理留 null)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
  2. 08 Jun, 2026 23 commits
    • perf(sync): reparse 按患者分批,避免全量时把全 host rawPayload 拉进内存 OOM · e2960fa2
      全量 reparse 时一次性 findMany 所有 rawPayload 可达 GB 级(服务器 diagnosis 975MB/541k txn)。
      改为按患者 cohort 分批(PAC_REPARSE_BATCH,默认 3000):每批只重建该批的 distinct 源行 →
      transform → processSubject,内存恒定(本地实测 rss ~460MB);跨批累加 stats,supersededAt 圈出
      变更患者定向重算。dry-run 改报 scope(count,不分批装配)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 牙位事实全口判定保持按 category(方案A)+ 注明依赖诊断已编码 · 84820f32
      正畸/牙周治疗一律归全口泳道,与"诊断按 code 判全口"对齐(K07/K05 诊断 + 同科治疗同泳道,不分叉)。
      前提:正畸/牙周诊断已正确编码(靠 diagnosis.yaml keyword 兜底 + reparse 把存量补成 K07),
      否则空 code 诊断仍按牙、与其全口治疗分叉(孙柯 15/25 案例)。仅补充注释,逻辑同原始。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(parser): 诊断 K07 正畸族 keyword 兜底(牙列间隙/错牙合/开颌/反颌/深覆/拥挤/安氏 → K07) · c0512c74
      精确白名单只列了 错颌畸形/牙列不齐/安氏…错颌畸形,漏掉大量变体写法(98% 诊断无 stdCode,
      全靠中文名匹配)→ 这些正畸诊断 code=null → 进不了 K07 召回(FN)。
      加 keyword_mapping 含词兜底(放最后,K08 缺牙间隙/K09 颌骨囊肿先吃 + none 防误伤)。
      本地 reparse 验证:正畸 null 诊断 36→2,余 2 为 regex-only 边缘变体。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(sync): 加 reparse 离线重解析 —— 改 yaml 字典不再需要全量重摄(task #46) · 9caef1b0
      ColdImportService.reparseFromTransactions + `pnpm reparse` CLI:用已存的
      transaction.rawPayload 重跑 transformEngine + assembler + processSubject(**不连 DW**),
      fact 走版本流(内容变 supersede 升版本,不变幂等 no-op),只对真正变更的患者定向重算 persona/plan。
      
      - 非破坏:不 truncate、不碰 transaction 账本 → plan_executions(客服回写)/ 分配全保住;
        比全量重摄快一个量级(省掉 DW 拉取)。
      - 覆盖 field/enum/keyword_mapping + transforms 算子 + parser 改动(长尾字典修补 99%);
        非 transform 产出的资源(如 image_finding_rows = CH SQL 视图)自动跳过(其 rawPayload 非装配输入,需走 DW)。
      - traceRawSourceTable:沿 transforms output→input 链回溯到原始源表(rawPayload 就是它的行)。
      - 用法:pnpm reparse -- --host=jvs-dw --subject-type=diagnosis [--patient=..] [--dry-run] [--no-recompute]
      - 本地实测:diagnosis 全量 superseded=36 / unchanged=3820 / created=0(零 churn),单患者 end-to-end
        fact 升版本 + 定向重算 + 正畸召回正确出现。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 多值画像标签 chip 显示「首值 +N」而非静默只显示第一个 · 5696e951
      治疗史/权益/禁忌/潜在治疗/时间偏好/治疗敏感/特别关注 这 7 个特征是 `/` 并列多值,
      原 chip 用 shortPersonaValueLabel 在 `/` 处截断 → 只剩第一个值且不提示还有更多。
      改读结构化 data.labels(后端这 7 个都带):多值显示首值 + 灰色 +N,完整列表走 hover 卡片;
      单值特征不变。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(plan): assigned 重算升版本并继承分配 + 缺口解决即关闭 + 刷新回传 outcome/currentPlanId · ee00c6c5
      - assigned plan 不再整体 skip:理由集合变 → supersede + 新版本,新版本继承
        assignee/assignedAt/recycleAt/contactAttempts(客服不丢单/不被抢/熔断计数延续);理由没变只就地刷分。
      - 缺口全解决(0 命中)→ 即使 assigned 也关闭退池(closeStaleActivePlan 放开 assigned)。
      - recomputeForPatient / 单刷 API 回传 planOutcome + currentPlanId。
      - /full:plan superseded 且无活跃后继 = 已关闭 → 报 PLAN_NOT_FOUND(前端提示+退池),不再静默渲染冻结快照。
        (前端零改:既有 router.replace(currentPlanId) 切新版本 + PLAN_NOT_FOUND 提示逻辑自动生效)
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(sync): 单患者刷新兼容嵌套 manifest SQL + 加 dev 抽样开关 · 7a91d425
      - injectPatientFilter 原用 regex 解析 SELECT...FROM table,遇嵌套子查询(image_finding s1/s2 + array join)
        会拖坏括号 → CH 语法错 → 单刷 500。改为包裹原查询为子查询 + 外层 patient_id/brand 过滤(兼容任意复杂 SQL)。
      - 加 PAC_COHORT_LIMIT / PAC_COHORT_SAMPLE(recent/oldest/random)dev 抽样开关,默认全量、行为不变。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(tz): 本地 CH 设 Asia/Shanghai 对齐远程 DW + 移除 arrived_at +8h band-aid · 55f8de0c
      - 本地 pac-clickhouse 原跑 TZ=UTC,裸 DateTime(in_time)读出比北京裸字段早 8h。
        加 clickhouse/config.d/timezone.xml(<timezone>Asia/Shanghai</timezone>)+ compose TZ env,
        重建后本地行为与远程 DW(本就 Shanghai)一致;源 instant 一直正确,无需改数据。
      - special-attention 的 ARRIVED_TZ_FIX_MS(+8h)是为补偿上述本地 UTC 偏差加的;源 CH 统一 Shanghai 后
        会反向多加 8h → 移除(服务器连远程 DW,该 band-aid 本就一直让"迟到"晚 8h,一并修)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • refactor(web): 精简成功 toast(仅留无其它反馈的)+ 模型下拉加风格标签 · d8ea5d52
      - 删冗余成功 toast:单条认领/回收、登录/登出、归档、点赞点踩、召回反馈(界面已就地反映);
        保留:表单保存、批量计数、异步生成完成、"为何无动作"类、占位跳转。错误 toast 全保留。
      - 重生成模型下拉加风格标签:慢·精细 / 快·均衡 / 快·流畅 / 极快·简洁。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(ai-script): qwen3.7-max 结构化输出修复 + gemini 撤回关思考 + 超时兜底 + TTFT/latency 落账 + 价格表修正 · 70b70ce0
      - qwen3.7-max:DashScope 不强制 json_schema → 改 supportsStructuredOutputs:false + fetch 中间件强制
        response_format:json_object + enable_thinking:false;runner 从 outputSchema 自动注入英文 key 骨架
        (withQwenStructuredHint),否则模型自编 key 必 fail。
      - 三档 schema 去掉 .min/.max/.length/.int 硬约束(对中文偏严,qwen 简洁输出被打回 too_small)→ 改 describe 软引导。
      - gemini-3.5-flash 撤回 thinkingBudget:0(关思考会致结构化输出偶发 parse 失败);慢/卡由超时+重试+兜底兜住。
      - 给 generateObject/streamObject 接 180s 超时(AI_REQUEST_TIMEOUT_SEC,原 60→180)防永久挂起。
      - agent_invocations 加 ttft_ms / latency_ms(+migration),流式记首字、收尾记总耗时(对齐 Dify usage)。
      - 价格表按官方页修正(¥/M):deepseek-v4-pro 0.03/3.13/6.26、flash 0.02/1.01/2.02、
        gemini-3.5-flash 1.08/10.8/64.8、qwen3.7-max 1.2/12/36(旗舰价,之前低估 5x)。
      - 三档 promptVersion 随 schema 变更 bump。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(ai-script): qwen3.7-max 结构化输出修复(DashScope 三件套) · 746c5b0a
      接入后报错'No object generated: response did not match schema'。逐项查实 DashScope compatible-mode 三个前提:
      1. supportsStructuredOutputs:true — SDK 才发 response_format=json_schema(DashScope 实测严格按 schema 返;
         松散 json_object 不带 schema → qwen 乱返 → Zod 不符)。
      2. prompt 含 'json' 字样 — DashScope 硬性校验(否则 400 InvalidParameter);runner 对 qwen 注入(withQwenJsonHint)。
      3. enable_thinking:false — qwen3.7-max 是推理模型,流式下 thinking 污染 content 流 → JSON 解析碎;
         SDK 无此参数,用 createOpenAICompatible 的 fetch 中间件往 body 注入。关思考还顺带提速、降本(¥0.031→0.008)。
      验证:standard 流式 source=agent、succeeded、¥0.0078。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(ai-script): 接入通义千问 qwen3.7-max(DashScope 兼容端点)做话术生成 provider · 6dc6a790
      - 装 @ai-sdk/openai-compatible,AiProviderService 加 qwen provider(DashScope compatible-mode 端点)。
      - resolve 支持 qwen 前缀(qwen3.7-max / 裸键 qwen → 默认);config 加 qwenApiKey/qwenBaseUrl/qwenDefaultModel
        (QWEN_API_KEY 缺省回落 DASHSCOPE_API_KEY;默认模型 qwen3.7-max);priceTable 加 qwen3.7-max 估算价。
      - 前端模型下拉加'通义千问 Max'(qwen3.7-max)。
      - 注:该账号 key 仅 qwen3.7-max 可用(qwen-max/qwen3-max 等均 Model.AccessDenied,实测确认)。
      验证(本地 standard 流):start→逐字partial→done,source=agent,¥0.031/4823tok,落库 succeeded。
      - ️ 密钥走 .env(gitignore),服务器部署需单独在 .env 加 QWEN_API_KEY。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(ai-script): 深度档 repair 严格按自检逐条改(喂上一稿 + 强约束) · a0afbd7e
      问题:verify 后的修订阶段,模型没严格按自检 issue 改 —— 根因是 repair 只给了大纲+问题提示、
      没给上一稿,模型是'凭大纲重写'而非'在原稿上逐条改'。
      
      - DeepWriteInput 加 prevDraft;strategy 的 repair(run + runStream)把上一稿传进去。
      - buildWritePrompt repair 分支重写:① 附【上一稿(待修订)】②【必须修正的问题,逐条缺一不可,每条带'必须改成'】
        ③【修订铁律:只动被点名处/其余原样/不得引入新违规/缺一不可】。
      - writeCall promptVersion → v11。
      验证(本地深度生成触发 repair):repair invocation prompt 含上一稿+逐条问题+铁律(4项校验全 t)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 深度过程步骤名精简(接地+安全自检→安全自检,按问题修订→问题修订) · 01508da3
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 伴飞分段条响应式不溢出 + 深度过程步骤箭头更友好 · c482de02
      - 伴飞(copilot)分段进度条:按钮加 min-w-0(flex 子项默认 min-width:auto 不收缩 → truncate 失效 →
        6步时溢出截断),序号圈加 flex-none。现在多段自适应收缩 + 省略,不再被右侧切掉。
      - 深度过程面板:展开标识改成'右指箭头→展开旋90°朝下'的经典 disclosure(更清晰友好)+ 整行 hover 高亮 +
        aria-expanded;detail 缩进对齐按钮 padding。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(ai-script): 深度过程面板精简 — 去标题行/进行中默认展开可折叠/单色背景 · 1f2e3288
      按反馈调整 ScriptDeepProcess:
      - 去掉'深度生成过程'标题行,更简洁。
      - 步骤可折叠:进行中的默认展开、其余收缩;用户手动点过的步骤改由用户控制(in manual map → 不再受程序自动展开影响)。
      - 背景改单色(slate-50 + slate 边框/图标),去掉 indigo 多色。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(ai-script): 深度档流式可见过程(GPT/Claude 式:规划→撰写→自检→修订) · 60ce7324
      深度档原来是 start →(黑盒30-60s)→ done,看不到中间过程。改成逐步可见:
      后端:
      - deep.strategy 加 runStream():逐步 yield step 事件(plan/write/verify/repair 的 running/done +
        各步摘要:大纲/质量分/问题数);撰写/修订步走 runner.stream 逐字 partial(打字机)。run() 保留不动。
      - orchestrator 深度档 SSE 路径改为消费 runStream → 转发 step + partial 事件;新增 PlanScriptStreamEvent.step。
      前端:
      - use-script-stream 解析 step 事件,维护 steps 时间线(done 后保留可回看)。
      - 新增 ScriptDeepProcess 组件:步骤时间线(spinner/✓ + 大纲展示 + 接地安全/质量分 + 修订问题数)。
      - plan-detail-app 在话术区顶部渲染过程面板;正文 sections 在下方逐字流。
      端到端实测(本地 deepseek-v4-flash):start→step plan(running/done)→step write+逐字partial→
        step verify→step repair→done,全程事件正常流出。abort/停止复用现有。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 工作台头部加退出登录(配合模拟登录) · 219f7440
      头像旁加 LogOut 按钮 → useAuthStore.clear() 清 token → AuthGate 自动弹回登录对话框。
      口径与登录一致(mock/SSO 都走 AuthGate);clear() 已存在,纯前端。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • docs(dw-issues): #15 in_time/billing_date 类型违约(DateTime/UTC vs 契约北京naive)→ 交宿主修 · a3b60813
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • revert: 撤销就诊去重的本地时区 hack(1d2759dc)— 根因是宿主违约,PAC 不做兼容 · cd6a9c37
      复盘(用户纠正):王振中'就诊2次'根因不在算法,在宿主数据违约:
      - 契约:该数仓所有时间 = 北京时间(naive)。PAC 一律按北京解析 → 存 UTC(PAC 内部恒 UTC)。
      - 但 fact_appointment_out.in_time 被宿主存成 ClickHouse DateTime(UTC 瞬时,03:09 UTC=北京11:09),
        而非北京裸串。序列化丢 tz 标记 → PAC normalizeDatetime 当北京串又 −8 → UTC 19:09(慢8h)。
      - 这是【宿主违约】(该列应北京 naive,却给了 UTC DateTime),宿主该改。
      - PAC 不为错误数据在算法层塞时区兼容(本末倒置)。UTC 日去重本身没问题:正常诊所时段
        (北京08-24点=UTC00-16点)不跨 UTC 日,只有本例 in_time 被双转成凌晨才误跨日。
      
      → 算法恢复 UTC-pure;in_time 双转/契约违约记入 dw-data-source-issues.md,交宿主修。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(persona): 就诊次数/RFM频次 按诊所本地时区去重(修时区虚增) · 1d2759dc
      问题(用户在王振中发现):画像'就诊2次',但病历只1条。查实=时区 bug。
      王振中所有事实本地(Asia/Shanghai)都在 2025-09-26:encounter 本地03:09(=UTC 09-25 19:09)、
      treatment 本地11:10(=UTC 09-26 03:10)。但 lifecycle/rfm 用 occurredAt.toISOString()(UTC日期)
      去重就诊天 → encounter落09-25、treatment落09-26 → 切成两天 → 虚增'就诊2次'(实1次)。
      
      - 新增 visit-day.util.ts:localDayKey(date, tz='Asia/Shanghai') 按诊所本地时区取 YYYY-MM-DD。
      - lifecycle-stage + rfm 的就诊天去重改用 localDayKey(原 toISOString UTC)。
        (首/末诊用 getTime min/max,时区无关,不动)
      - 影响:lifecycle 生命周期分期 + RFM 频次F + visitsPerYear,凡有本地凌晨(00-08点=UTC前一天)事件的患者。
      - CLINIC_TZ 暂常量(试点全+8);多租户后续从 platform.pullConfig.timezone 接进 feature ctx。
      验证(本地 --force 重算):王振中 lifecycle/rfm 均 '就诊2次'→'就诊1次'。
      注:算法变更需 recompute-persona --force(数据没变,水位闸会 noop)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 病历快读 P(计划)块顺序 → 治疗计划/本次治疗/处置/医生建议/医嘱 · aac46403
      按业务要求调整 SOAP 计划段子块顺序:原 处置→本次治疗→治疗计划→医生建议→医嘱
      改为 治疗计划→本次治疗→处置→医生建议→医嘱。纯 JSX 重排,无逻辑/数据变化。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(recall): 召回原因区分 影像AI vs 医生诊断(triggers 加 image_finding) · d4fbbd45
      问题(用户在邵竞红37发现):卡片标'(诊断)'但其实是影像AI分析(code_source=image_ai)。
      '(诊断)'只是 fact 类型(diagnosis_record,对应'(医生建议)'=recommendation),不区分诊断内部
      医生 vs 影像AI。区分靠 content.code_source,后端打分已用(影像置信度0.85/0.9<医生1.0),但没进 triggers。
      
      - scenario uniqueTriggers + lead triggerType:code_source=image_ai 的诊断 → trigger type='image_finding'
        (医生诊断仍 diagnosis;recommendation 不变);sourceStr 文本同步加 '(影像AI)'(喂 AI 话术更谨慎)。
      - canonical-codes triggerTypeLabelZh: image_finding '影像所见'→'影像AI'(强调AI来源/低置信)。
      - 前端 reason-line 用 triggerTypeLabelZh 自动渲染 → '(影像AI)' / 混合 '(诊断+影像AI)',零改。
      验证(本地清plans重算):image_finding trigger 77 个;姜学英 K03@34(唯一诊断=image_ai)→ type=image_finding。
      注:改 scenario 需 TRUNCATE plans + recompute 才生效(增量 upsert 不重写旧 signals);服务器下次部署随之生效。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
  3. 07 Jun, 2026 15 commits
    • fix(ingest): emr_record occurred_at 用 created_date(精确就诊时刻)替代 rq(纯日期) · b5ffed78
      根因(对原数据 + 本地CH确认):病历快读同日多医生(转诊:吴仲恺先、刘柳后)排序错乱,
      真因不在前端 —— emr 的 occurred_at 被摄成纯日期(00:00:00),同日打平。
      源表 fact_emr_treatment_out 里 rq=Date(只到日)、created_date=datetime(带时分,= 就诊时刻);
      diagnosis/treatment/payment 都用 created_date,唯独 emr 用了 rq(emr.yaml occurredAtField=submittedAt=rq
      + emr.parser occurredAt=submittedAt)→ 丢时分。
      
      修(数据层,单一真理源):
      - emr.yaml: occurredAtField submittedAt → createdAt(transaction.occurred_at 拿到 datetime)
      - emr.parser.ts: occurredAt submittedAt → ctx.transaction.occurredAt(跟 diagnosis/treatment/encounter 一致)
      - 撤回前端 band-aid(emr-soap-view 改回按 occurred_at 简单排序)
      
      验证(重摄1000抽样):王思涵 emr 吴仲恺08:05/刘柳08:58 → 刘柳正确排在后;全体 emr 午夜 2987→0。
      注:image_finding.yaml 也用 rq(影像AI分析日),源是否有 datetime 待查 — 同 pattern、低优先,后续。
      服务器需随部署重摄生效。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 病历快读同日多医生排序错乱 — 用关联事实真实时分做 tiebreak · 750f1f3c
      emr_record 的 occurredAt 只到日期(00:00:00),同日多医生(典型:转诊,吴仲恺先诊断+转诊、
      刘柳后做正畸评估)在 localeCompare 下打平 → 排序不稳定、先后乱。
      修:emr 排序前预计算排序键 = 同次接诊关联事实(diagnosis/treatment,经
      source_encounter_external_id  emr_external_id 关联,带真实时分)的最晚时间,fallback emr.occurredAt。
      例 王思涵 2021-07-23:吴仲恺 emr(关联08:05)< 刘柳 emr(关联08:58)→ 刘柳正确排为第2次(最近)。
      纯前端、渲染期推导,无需重摄。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • test(recall): §F 全口码误召检查(召了但已对症治疗)— 按生产口径,应=0 · 707325e2
      用户口径:'召回说应治未治,但牙位事实里看出来已治了' = 误召。逐颗对齐结果:
      - 牙位级误召 §C2(resolved×recalled t/t 格)= 0
      - 全口码误召 §F(新增,K05/K07)= 0
      §F 与生产同口径:治疗 occurred_at >= 最新【诊断∪建议】(latestDxOfCode,含 recommendation)才算已治。
      规则确认(用户):治疗【之后】又出现诊断/建议(哪怕同日、不同医生)= 新的未治需求 → 应召回,非误召。
        例 王思涵:正畸治疗08:05 < 同日正畸建议08:58 → 治疗早于最新发现 → 正确召回(§F 不计)。
      结论:召回的'应治未治' 与 牙位/全口事实 完全对齐,真误召 = 0。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • test(recall): §C6 软豁免(⑤b/⑤f)显式标需人审 — 不再伪装成干净 · ef71b12a
      复盘:此前我据 §C6 '9_真无法解释=0' 论断'召回完整性可信',危险且过头:
      1) §C6b 漏查 cooldown,差点假报翟俊程漏召(他实为28天<30天冷静期,且本人已因K01智齿进召回);
      2) 更糟:把 ⑤b未来预约/⑤f近期到诊(患者级代理)算进'已解释'。这俩不证明'这颗牙会被处理'
         (沈静芳当年正是被科目级⑤d此类代理误挡)。全新1000抽样里 156 颗只靠⑤b被压
         (K08种植77 + K03修复34 + K01/K02/...),全是未验证的高价值 gap。
      经核实 ⑤b = 纯患者级 blanket(有任何未来预约→压全部gap,不看牙位/科目),比已删的⑤d更粗。
      - §C6 bucket 5/6 改标 '软豁免·需人审',加判读 echo:1-4硬解释=真没问题 / 5-6软豁免≠干净 / 9=bug。
      - 结论修正:未发现硬bug(9=0),但 ⑤b blanket 掩盖 156 颗未验证高价值gap → 与⑤d同类过度排除,待产品决策。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • test(recall): verify-recall §C6/§C6b — 页面事实视角的应治未治全集 vs 召回(独立验证) · faac3a8c
      用户担忧召回算法,补一层从【页面牙位事实视角】的独立验证(差分测试盲区):
      现有 §C 宇宙 vt_diag 预先删了 cooldown、滤了合规闸 → 那些牙不进交叉表,但页面照常展示,
      '看着该召没召'的牙被静默放过。§C6 从原始事实独立重建'应治未治'全集(口径=人眼读页面:
      有结构诊断 + 本牙后续无对应治疗 + 未被更晚诊断取代),不预过滤,逐颗归因。
      
      - §C6 牙位级:全集 vs 召回 + 未召逐层归因(合规/废用乳牙/§E/冷静期/未来预约/近期到诊/9无法解释)。
      - §C6b 全口码 K05/K07:同口径(含 cooldown=30 检查)。
      - 全新独立 1000 抽样(899患者,与旧样本仅重合9)验证:
        §A-§C5 全绿(FP 0/0、真·无法解释 FN 0、怪码哨兵净);
        §C6 应治未治 1762 → 已召 1476 + 全可归因 286(废用63/§E8/冷静期43/未来171/近期1)→ 9_真无法解释=0;
        §C6b K05/K07 全部已召或冷静期内 → 未召无理由=0。
        初判 1 个 K07 疑似(翟俊程)经查 = 诊断28天<30天冷静期,正确不召(非漏召;§C6b 补 cooldown 检查后归零)。
      结论:召回算法与牙位事实视角零真不一致 —— 页面上每颗应治未治牙,要么已召、要么有可见的正当排除。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(types): lifecycle_stage timeSemantics 'window+trend'→'mixed'(修 tsc 报错) · d3b7b70b
      FeatureTimeSemantics union 无 'window+trend';dev 跑 swc 不查类型一直被掩盖,
      turbo build(tsc)时暴露并阻断启动。归入既有 combo 桶 'mixed'(RFM 同款),
      精确语义(window recency + trend 斜率)留注释。纯描述元数据,无 switch 消费,行为不变。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat: 场景名 启治召回→应治未治 + verify-recall §C5 码名怪码哨兵 · 4bc0ebe7
      - UI 场景标签'启治召回'→'应治未治'(labels.ts 单一源,列表/详情 chip 全跟随;mock fallback 同改)。
      - verify-recall §C5:诊断码诊断名 语义不符监测('召回了也可能不一致'的守门 — 召回基于码,
        码≠名的怪码会召错科目)。命中'异类关键词'且'本码自身关键词缺席'(排多病名拼接噪声)。
        DW 全量审计(阈值降到5):唯一系统性怪码 = K07.303 牙体缺损(1083患者),已 recode 修;
        §C5 现 ~0(仅 1 条多行多病名噪声)→ 哨兵留守,未来新怪码会现形。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(ingest): 纠偏宿主 ICD 怪码 K07.303(牙体缺损,非正畸)→ K03 + 新增 recode op · fc481b9e
      鉴定:K07.303 在中文临床版 ICD 系统性指派给'牙体缺损'(WHO K07=错颌畸形),全院 1083 患者。
      PAC substring(0,3) 截成 K07 + coalesce 优先 stdCode 丢掉 message → 误当正畸召回。
      这也是'画像=潜在修复 vs 召回=正畸'矛盾的真根因(沈静芳/许龄心)。
      
      - 新增 derive 'recode' op(覆盖表,未命中透传)— 通用的宿主 ICD 怪码纠偏机制。
      - manifest B.1:substring 前 recode {K07.303: K03},再截短;其余 K07.3xx(牙列不齐/牙拥挤/异位牙)确是正畸不动。
      - 验证(本地 905,重摄重算):K07-牙体缺损残留 0 → K03 牙体缺损 285;沈静芳召回'正畸82'消失、只剩'修复30',
        跟画像一致(矛盾从源头解决,年龄门 band-aid 不再需要);ortho 召回降、hard_tissue 升;
        交叉测试 FP 0/0、真·无法解释 FN 0,零回归。
      - 注:服务器需随部署重摄才生效(reparse)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • test(recall): verify-recall 加 §C4 可疑漏召层(独立临床检查,补差分测试盲区) · 53467054
      差分测试盲区:scanner 与生产用同一套排除规则 → 规则本身太粗(如已废的 ⑤d 按科目误排)会被
      '互相印证'成已解释、静默放过(沈静芳 34;43 当年即此)。§C4 换独立视角:
        '该牙有结构诊断 + 本牙零治疗 + 无任何牙位级硬层(废用/乳牙/§E/更晚诊断)挡',
        却仅因患者级代理(⑤b 未来预约 / ⑤f 近期到诊,均不看牙位)而没召 → 列出人审。
      - 本地 905:可疑漏召 61(57 ⑤b + 4 ⑤f),多为 K01 阻生/K02 龋齿(有未来预约,患者要来当面处理)/
        K00 先天 → 合理但不再静默;真出现'被代理豁免又没人来管'的会现形。
      - FP 硬闸 0/0、FP=0、真·无法解释 FN=0 不变。
      - ⑤b/⑤f 是可辩护的粗代理(物理到诊=全口机会);⑤d(科目≠牙位)才是坏的,已移除。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(recall): 移除 ⑤d 预约科目排除 — 全口/牙位统一靠治疗判定 · c9435b74
      数据验证(905 样本)推翻'全口需要 ⑤d'的假设:
      - 全口(K05/K07):正在治的治疗都有记录 → resolvedTeeth 已全覆盖(K05挡669/K07挡73);
        ⑤d 独占多挡仅 0~1 个,且那个无近期预约=stalled(看过没继续)本就该召。'在治没录'风险=0。
      - 牙位(K02/K03/K08…):⑤d 按科目(非牙位)误排'同科目别牙在治、这颗没治'(沈静芳 34;43 修复被
        25;26 修复预约连带排除)。
      → ⑤d 既冗余(全口)又有害(牙位)→ 移除。全口/牙位统一: resolvedTeeth(治疗) + ⑤b(未来预约) + ⑤f(近期到诊)。
        '已进入链'的细粒度跟踪留 W5+ 治疗链内召回。
      - 顺带删 complaintTexts / APPT_COMPLAINT_TO_CATEGORY import;verify-recall.sql scanner 同步去 ⑤d 桶(oracle 与生产一致)。
      - 验证:FP 硬闸 0/0、FP=0、真·无法解释 FN=0;未治→召 1524→1556(+32 合法);沈静芳 34;43 修复召回(分30,image_ai+老→排末尾,不冲高)。
      - 注:年龄门(K07>40 不召正畸)按用户决定暂不做 → persona/召回在'正畸'上仍有意保留不一致。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 获客渠道 hover 去掉'数仓'内部术语(改客服口径) · 5cbde837
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 画像卡去分割线 + hover 全中文/精简 + 优先级改 10 分制两位小数 · 1b846327
      按反馈:
      - 身份卡画像标签去掉上方空分割线(border-t),直接贴手机号下。
      - 标签 hovercard:① 英文术语全改中文(种植史/正畸史/冠桥贴面嵌体…,去 implant/orthodontic/channel= 等);
        ② 删'不打标签'等'不显示时'的说明(没标签就没 hover,无意义);③ 精简解释性 note(去 valueTier/
        Layer C/snapshot/DW/喂优先级 等内部术语),只留客服有用的。
      - 优先级分数:0-100 → 10 分制两位小数(优先用 breakdown.raw 真精度,无则 score/10)。
        列表 PriorityBar、两页共用的 PriorityHover(头部 + 总分 + /10)同步。
      - web tsc 通过。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat: 隐藏面向用户不该看的内部细节(RFM 原始分/标题/oracle 对账) · f138e5a3
      按反馈,详情页去掉给客服看没意义的开发/QA 细节:
      - RFM:① hover 副标题去掉'→ 八象限'(术语);② 描述去掉 R5F2M1 原始 R/F/M 分
        (rfm.feature.ts;R/F/M 仍留 data 供内部/圈人群用)。
      - 画像标签:去掉'画像标签 / 详情→'标题行,身份卡里直接列 chip(无标题、无 drawer 入口)。
      - 牙位事实抽屉:移除 oracle 召回对账面板 + 每泳道 oracle 徽标 + 子标题'oracle 召回对账'
        (这是 dev/QA 差分验证,不该给客服看;验证走 verify-recall.sql)。
        tooth-timeline 删 ReconPanel/ReconBadge + recall-oracle import;drawer 去 reasons 传参。
      - web tsc 通过;本地 905 --force 重算,rfm 描述已无 RxFxMx。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 画像标签并入身份卡 + hover 合并取值/算法 + 隐藏治疗链卡 · d45b0c86
      详情页左栏调整:
      - 去掉独立'画像标签'卡片,标签云并入左上第一个卡片(IdentityCard)底部(手机号下,带分隔线 + 详情→)。
      - 标签 hover:去掉原生 title(避免 title+hovercard 双浮窗);把 title 内容(该患者实际取值)
        并入 hovercard 顶部(teal 框)→ 一个浮窗同时看'是什么(取值)+ 怎么算的(规则)'。
        PersonaFeatureHover 加 value prop,meta 缺失时也能只显取值。
      - 隐藏'治疗链'卡片(链已弃用;chains 仍传 drawer 备用)+ 删未用 ChainSidebar import。
      - web tsc 通过(next dev 热更,无需重启)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(ai-script): 稳健/标准档也接入画像(精简版:安全 + 定语气) · 02e1e2ed
      原只有深度档喂画像。三档对齐:
      - buildPersonaGuide 加 mode:essential(禁忌/治疗敏感/特别关注 + rfm/生命周期)/ full(再叠切入点)。
      - 稳健 + 标准档 → essential:只灌'护栏'类(安全红线 + 语气),不灌切入点(折扣/转介/家庭),
        避免轻量填空档被诱导加推销、破坏填空纪律。安全类画像(种植禁忌/看牙恐惧)现三档都见得到。
      - 深度档保留 full(default,输出不变 → 无需 bump)。
      - bump promptVersion:standard v13 / stable v26(eval 可按版本对比)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed