1. 11 Jun, 2026 23 commits
    • fix(recall): make_interval 参数加 ::int 转型 — Prisma 数字绑成 bigint 致 42883 · 66703e8f
      上个 commit 验证结论有误(管道 tail 吞了退出码,实际 SQL 报错)。本次修正后重验:
      recompute-plans 全量 exit=0;recompute-persona 单患者(共用 gap 核心)success=1。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • fix(recall): 复述诊断不重新拉弓 — 排除谓词放宽黄金窗回看 + 一线反馈核验文档 · cb5e4bc1
      一线反馈①(李然 BA38586 牙47):1/25 种植 actual 已入库,2/7 拆线复诊 EMR 惯性复述
      "牙齿缺少47"→ 最新诊断晚于治疗 13 天 → 时间方向判"未启动"→ 误召(92分被认领)。
      
      根因模式:EMR 复述诊断 re-arm — 术后复诊把历史诊断再录一遍(录入惯性非新发现),
      "复活"已启动治疗的 gap;任何术后复诊都可能触发,非个例。
      
      修复(gap 核心单一源,召回+画像同步生效):
        治疗 ≥ 诊断日  →  治疗 ≥ 诊断日 − 黄金窗(rule.windowDays,K08=180天)
      - 复述(治疗后黄金窗内再录同码)= 同一治疗 episode,不 re-arm;
      - 真复发(末次治疗距再诊断超黄金窗)照常召回;
      - 取舍:治疗后窗内"真二次新发"延后到下次诊断 — 该场景临床通常当场处理。
      
      验证:服务器只读对照(李然47 新谓词 t / 旧谓词 f);本地全量 plan 重算无错。
      新增 docs/algorithm/frontline-feedback-verification.md 逐条记录 21 例核验(①完结,②-㉑待核)。
      仅本地,未部署。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • fix(sync): 增量列患者改为各游标表变更患者的并集 — 修"约诊未到诊"类永久漏数 · 48d9aee0
      根因(2026-06-11 哨兵空转告警,服务器 DW 逐行核验):
      增量 cohort 列患者只查 patient_list_from.last_visit_time > cursor(=近期来过诊的人),
      漏掉"没来就诊但数据有变"的患者 — 典型即 PAC 自身业务闭环:客服电话约复诊(预约
      updated_date 变了,人没来)、医生回头补写病历。当日 24 行预约 + 3 行 EMR 超游标,
      27/27 全属此类患者(去重 22 人,老口径列出 0 人);且等患者到诊时游标已越过这些行
      → 永久漏数。
      
      修复:增量模式下列患者 = 每张配了 cursor 的源表按各自游标取变更行患者,UNION ALL
      去重(manifest 驱动,FROM 主表正则提取,不写死表名;宿主无关)。
      - 全量/无游标首跑:保持原 patient_list_from 行为不变;
      - 采样 LIMIT 的 list_cursor_column 排序在 union 模式回退按 patient key(仅 dev 用);
      - refund 分支与 settlement 同表重复,DISTINCT 吃掉,无害。
      
      验证(只读):服务器 DW 实跑新 union SQL → 22 患者全部捞回;service tsc 0。
      仅本地修复,未部署(待统一打包)。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(persona): 标签覆盖率日报 — 每天 08:00 企微,显著下跌升级 warning · 838d26b9
      标签质量运营第一道防线:规则标签会静默腐烂(上游字段改名/枚举漂移/断供时悄悄变空)。
      - 每天北京 08:00(增量+重算之后)按租户算 16 标签覆盖人数/占比,对比 24h 前;
      - "昨天覆盖率"从画像版本流(computed_at/superseded_at)直接反推 — 零快照表零迁移;
      - 显著下跌(相对 ≥10% 且绝对 ≥20 人)→ warning 并在标题点名,平时 info 日报;
      - 走现有 AlertService(企微 webhook);PAC_COVERAGE_CRON 可调。
      
      验证:本地库 SQL 实跑(16 标签覆盖与设计样本一致,24h 反推出数);
      真实格式日报已发企微测试 errcode=0。service tsc 0。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • fix(types)+docs: referral_champion 时间语义 snapshot→lifetime + 补判别准则 · f3277ba4
      - referral_champion 读 DW recommend_num/amount 全史累计计数器(单调只增)→ lifetime
        (纯声明字段修正,不影响计算逻辑);提取器头注释同步。
      - acquisition_channel 注明"实为 static(不变历史定格),退化场景归 snapshot"。
      - 速览文档补 snapshot vs lifetime 判别准则:分界看值的演化方向(lifetime 只增不减 /
        snapshot 可逆可变),非评估时点;并注明 entitlement_status 是"曾有"非"现有"。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • docs: 时间语义对齐 persona-design-v2 正式模型 — 注册表 timeSemantics 五值 + 标签表加列 · 42a5b7ab
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • docs: 画像速览补时间语义 — 四类口径(静态/全史/滚动窗/距今切档)+ gap 悬置特例 · 3c8123c1
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • docs: 画像实现速览 — 面向开发交流(16标签/刷新策略/幂等版本化/特色) · b1717ed1
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • style(web): 卡片边框 slate-200 → slate-100 发丝线 — 降噪去"框感"(只动边框色) · 89a3b72c
      现代清爽审美的常见做法:保留 1px 边框但压到极低对比(~6%),靠 bg 明度差+阴影分区。
      全局 21 处 border-slate-200 统一软化为 slate-100,结构/阴影/间距/标签/文字一律不动。
      
      web tsc 0。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 根路径跳转上移到 next.config redirects — 消除 HomePage 负时间戳报错 · d66dc855
      server 组件里 redirect('/plans') 让 Next dev 给未开始计时的 HomePage 打渲染耗时
      → "Performance measure cannot have a negative time stamp"。改用路由层 redirects()
      (/ → /plans),渲染前重定向、不渲染该组件,从源头消除;删冗余 (app)/page.tsx。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 助手窗口跟随钮位弹出 — 上方优先/左右半屏对齐/视窗内夹紧 · de08d506
      点开瞬间按钮当前位置算窗口落点:水平按钮在左/右半屏决定对齐边;垂直优先钮上方,
      放不下弹下方;整体 clamp 视窗内。拖钮避让区域的意义由此成立。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 助手钮/小窗 z-40→z-[60] — 高于左栏容器(z-50),拖到左侧不再被挡 · b46a8211
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 详情 TopBar 加退出登录 + 助手悬浮钮可拖移 · 4a58cd0c
      - 退出:TopBar 最右(头像后)LogOut 钮,与原列表页同口径(clear token → AuthGate 弹回登录)。
      - 助手钮:pointer 拖动,4px 位移阈值区分点击/拖动(拖完不误开窗);
        位置 localStorage 记忆(pac-assistant-fab-pos),clamp 视口内;默认右下角。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): /plans 改工作台入口解析器 — 直落详情(列表页弃用,组件保留未挂载) · 0ebda9bc
      根路径/旧链接进来直接到详情工作台,选人规则(纯规则):
        ① 我的「进行中」优先级 TOP1(续上手头工作)
        ② 无 → 召回池 TOP1(开新工作)
        ③ 都无 → 空工作台:左栏照常可筛(自行找人)+ 右侧整体空态(说明 + 刷新)
      解析期间居中过场;PLAN_VIEW_OWN 权限门同列表页。旧 PlansListApp 保留在仓库不再挂载。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): artifact 放大窗 portal 到 body — 小窗 transform 祖先把 fixed 圈住致其困在助手窗内 · dc5de128
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): artifact 放大改本屏居中大窗 — 流式生成时大窗同步实时"长" · 9dc2093f
      - iframe 渲染抽成 ArtifactFrame(持久壳 + postMessage 注入)可多实例:
        内联卡 + 居中大窗各挂一个,同吃一份流式 html → 放大后内容继续实时增长;
        生成中大窗头部带 ⟳ 提示。
      - 放大键不再开新 tab → 居中大窗(min(980px,94vw) × 86vh,遮罩/✕ 关闭,z-[70]);
        大窗内保留「新窗口打开」小按钮;内联卡 autoHeight 原行为,大窗锁高内部滚动。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 助手小窗开场建议场景化(规则生成,与 /assistant 页不共用) · 74735286
      - AssistantChat 加 examples 覆盖参数(不传 = 页面版默认三条 → 两版不共用)。
      - 详情页 ready 时把 {planId, 患者名} 发布到 plan-sync-store.current;
        小窗按规则出建议(非 AI):有当前患者 → ①为什么被召回/捋关键事实 ②画像+潜在治疗
        ③这通电话怎么开口;无上下文 → 召回池TOP/今日工作 兜底。切患者建议自动跟随。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 助手抽公用 — 详情页右下角吸附小窗(与 /assistant 同能力) · ce6bec2d
      - AssistantChat 加 variant('page'|'widget'):page 整页原样(/assistant 保留零回归);
        widget 紧凑头部(助手 + 模型切换 + ⌄收起)+ h-full 适配容器。
      - 新 AssistantWidget:右下角悬浮圆钮 ⇄ 400×620(max 78vh)吸附窗;
        收起为 CSS 隐藏不卸载 → 对话/工具步骤/artifact/听写状态保留;
        挂在 plans/layout(路由段之上)→ 切患者对话不丢;权限 AGENT_INVOKE 同 /assistant。
      - 功能与 /assistant 完全一致(MCP 工具透明步骤 / markdown / HTML 卡片 / 实时听写)。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(web): 工作台抽屉临界点 1280→1024(lg) + 把手 z-50 防被详情面板覆盖 · 4c0af7a8
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 工作台跨栏状态同步 + 小屏左栏抽屉化(左缘把手点击点) · ee05257d
      1. 右侧操作 → 左栏实时同步:新增 plan-sync-store(zustand 事件总线);
         详情提交回写成功(submitExecution → planStatus)→ notify → 左栏 patchItem
         原地更新该行状态 pill(不重拉、不丢滚动/筛选)。
      2. <xl 左栏收成固定抽屉:单实例 CSS 平移(不重挂 → 筛选/分页/滚动保留);
         左缘 teal 竖把手「选患者」作明确点击点;遮罩点击/抽屉沿 ✕/选中患者 三种方式收起;
         xl+ 常驻不变。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 画像标签全量圈人(14维)+ 左栏认领按钮 + 切患者不再白闪 · 01663a75
      1. 画像标签筛选扩到全部枚举型标签(4→14 维):新增 年龄段9/性别2/家庭构成4/权益身份5/
         治疗史4/治疗敏感4/特殊关注4/时间偏好5/转介绍达人2/禁忌1;code中文逐一对齐各 extractor
         的 data 写入值。未收录并注明原因:获客渠道(取值随宿主非稳定枚举)、折扣锚点(数值型)。
         后端零改动(字典驱动,plan.service 通吃)。
      2. 左栏行 hover 出「认领」(仅 待认领+未分配)→ assign 给自己 → patchItem 原地改"进行中"
         (不重拉、不丢滚动/筛选)+ toast;usePatientPicker 加 patchItem。
      3. 切患者白闪修复 — 保留 URL 路由(可刷新/分享/回退),loader 加 stale-while-loading:
         模块级缓存上一份聚合,加载期旧详情垫底(60% 透明)+ "切换中…"小罩,新数据到无缝替换。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 工作台布局改上下(header 全宽到最左)+ 左栏行/筛选五处打磨 · 4d04c361
      按反馈:
      1. 上下大布局:详情 TopBar 经 createPortal 渲到 plans/layout 顶部全宽槽
         (HeaderSlotPortal;无槽独立打开时原位渲染兜底)→ header 延伸到最左,左栏在其下;
         PlanDetailApp 根 h-screen→h-full,详情页包裹补高度链。
      2. 左栏行加性别(formatGender,与列表页同源)+ 诊所名(token 字典翻译)。
      3. 加诊所筛选(popover 多选 → 服务端 targetClinicIds,与列表页同款交互)。
      4. 优先级展示对齐列表页:五格色条 + 10 分制数值 + PriorityHover 算分明细。
      5. 行内去掉场景标签;状态改彩色 pill(待认领/进行中/已完成/已放弃,STATUS_META 同款)。
      
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
  2. 10 Jun, 2026 14 commits
    • feat(web): 详情页一页式工作台 — 固定左栏选患者列(4列)+ 画像标签筛选 · 21645e8e
      把"我的任务"hover 抽屉升级为详情页固定左栏(列表页将弃用,核心要素并入;KPI 面板按需求不带):
      
      - plans/layout.tsx:布局挂在动态段之上 — 选中 planId 时渲染「左栏 300px + 详情(原三列)」=4列;
        切患者只重渲染右侧,左栏不重挂(筛选/已加载分页/滚动位置全保留);/plans 列表页不受影响。
      - PatientPickerRail:召回池/我的/全部(权限)tab + 搜索(300ms 防抖)+ 排序 + 真实号码开关 +
        画像标签筛选(popover 四维分组多选:价值分群8/生命周期7/紧迫度3/潜在治疗8,选中 chips 可单删);
        紧凑患者卡(名/性别年龄/真角标/优先级分色/场景/掩码号/状态),触底+按钮双加载;选中 teal 高亮。
      - usePatientPicker:use-my-tasks 泛化版 — 全套筛选上服务端,筛选变化自动回第一页。
      - 后端:ListPlansQuery.personaTags("key:value" 逗号串)→ plan.service 匹配患者当前版画像
        (personas.supersededAt IS NULL)的 features:同维多选 OR、跨维 AND;数组型(潜在治疗)
        用 JSON array_contains;非法 key/value 静默丢弃。
      - @pac/types persona-tag-filters.ts:可筛维度字典(code中文,单一真理源,与 extractor 枚举对齐)。
      - 详情页移除 TaskDrawer(被左栏取代)。
      
      验证:API 对 DB 直查口径一致(重要价值 57==57;OR 173 / AND 10 / 数组 contains 40 / 非法=全量);
      web tsc 0 + Next 生产构建过。仅本地,未部署。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • perf(assistant): 听写提速三处 + 底部提示简化(固定文案) · c52200fe
      服务器实测体感"启动慢"对症:
      1. ASR 容器启动即预热(0.5s 静音跑一遍)— onnxruntime 首推建图/分配比稳态慢很多,
         之前重启后用户第一句要付这笔冷启动。
      2. 网关首句快路径:攒够 ~0.6s 音频立刻出第一个 partial(不等节奏),开口更快上屏。
      3. partial 节奏 700→450ms、tick 250→150ms(inFlight 防重叠不变,服务器解码 ~0.3s 扛得住)。
      
      底部提示按用户简化为固定"结果仅供参考"(去掉听写状态行;修正引号原样渲染)。两端 tsc 0。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • docs: push 契约措辞修订(回执码表精简/示例数值统一)+ W5 周报 · 1a8496a4
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(deploy): pull 后 re-exec 新版脚本 — 本轮才会用上 pull 进来的脚本变更(SERVICES 等) · ab3d047a
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(assistant): 语音输入升级实时听写 — 点击开启,边说边出字,再点纯退出 · 91f79dc6
      交互(用户要求):点 🎤 开始 → 说话时文字实时滚动进输入框 → 再点 = 纯退出(已上屏文字保留)。
      
      实现(复用 realtime-coach 成熟模式,ASR 容器零改动):
      - 前端:PCM16 16k 采音 + RMS 静音门控(无声不发帧)→ socket.io 推帧;
        dictation:partial(当前句滚动覆盖)/ dictation:final(句定稿累加)→ setInput 实时渲染;
        base 保留输入框已有文字,听写追加其后。
      - 后端 DictationGateway(socket.io,JWT 握手鉴权同 coach):按"帧到达间隙"断句 ——
        说话中每 700ms 把当前句 PCM 包 44 字节 WAV 头调 TranscribeService 出 partial;
        停顿 ≥800ms / 超 30s 整句 final 并清缓冲。inFlight 防解码重叠;先清缓冲再 final
        解码(下一句帧不混入)。SenseVoice 离线模型 RTF~0.1 → 句级重解码远快于实时。
      
      实测(模拟浏览器推帧):开口 1.3s 首个 partial,~0.7s/次滚动更新,停顿 1.3s 出整句 final,
      文本与一次性识别完全一致。两端 tsc 0。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(assistant): 语音输入 — 自部署 SenseVoice-small 听写(PII 不出内网) · 5644d1af
      三层:
      - apps/asr-sensevoice:sherpa-onnx + SenseVoice-small int8(纯 CPU,无 torch,镜像 <400MB);
        FastAPI /transcribe:任意浏览器音频(webm/mp4)→ ffmpeg 16k 单声道 → 离线识别(ITN 标点)。
        模型不进镜像/git —— 卷挂载 ${PAC_MODELS_DIR:-../pac-models}/sensevoice(model.int8.onnx + tokens.txt,
        来源 sherpa-onnx releases sense-voice-zh-en-ja-ko-yue-2024-07-17)。内网专用不发布端口(prod)。
      - 后端:POST /assistant/transcribe(全局 JWT 鉴权,multipart ≤15MB)→ TranscribeService 转发
        PAC_ASR_URL(provider 单一出口,以后切云 ASR 改这一处)。
      - 前端:composer 加 🎤(点击录音/再点结束,红色脉冲态)→ MediaRecorder(webm/opus,Safari mp4)
        → 上传转写 → 文字落输入框可编辑再发;错误/录音中提示在底部状态行。
      
      compose:dev + prod 都加 pac-asr 服务(prod 限 cpus:2/mem:2g,阿里云镜像源构建参数);
      pac-service 注入 PAC_ASR_URL=http://pac-asr:8000;deploy 脚本 SERVICES 加 pac-asr。
      
      本地验证:容器直测 3.9s 音频 0.46s 出字"帮我查一下患者孙科的画像和召回计划。";
      经后端鉴权链路同样通过;无 token 10106 拒。两端 tsc 0。暂不部署服务器(模型待 scp)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(alerting): 企微 webhook 告警 + 增量空转探针 + DW 滞后告警接通 · 4c93c669
      背景:增量游标 ISO 格式 bug 空转三天才被人工发现 — "success+fetched=0" 无人知晓。
      
      - AlertService:识别企微 webhook(qyapi.weixin.qq.com)→ markdown 格式(级别 emoji/颜色,
        context 引用块,body 截断);企微 HTTP 永远 200 → 检查 body.errcode 才算送达。
      - 增量空转探针(ColdImportService 收尾):带游标的增量跑出 0 写入时,反查 DW
        「比游标新的行数」— DW 也 0 = 真没数据(静默);DW>0 = 拉不到但有 → 当天 critical 告警。
        探针(ClickHouseSourceService.probeNewRowCounts)任何异常吞掉,绝不影响同步主流程。
      - DwLagMonitorService:🟡/🔴 滞后从"只打日志"接到 AlertService(企微同收)。
      
      验证:本地用编译产物真发企微群,payload 被接受(无 errcode 报错)。tsc 0。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(sync): 增量游标 ISO 格式与 DW String 列字典序比较不兼容 — 增量三天空转 · 7375f010
      诊断(2026-06-10 服务器):增量 cron 每天 success 但 fetched=0 × 3 天;DW 实际有新数据
      (fact_appointment_out max updated_date=06-09 22:50)。根因:DW 的 cursor 列是
      Nullable(String)('YYYY-MM-DD HH:mm:ss'),WHERE 比较走字典序;游标存的是 run_start ISO
      ('2026-06-09T03:30:00.012Z'),第 11 字符 'T'(0x54) > ' '(0x20) → 所有真实行字典序都
      小于游标 → 永远 0 行。实测同一时刻 ISO 谓词 0 行 / 普通格式 1178 行。
      
      修复:读取侧规范化(toDwCursorLiteral)— 构造 IncrementalConfig 时把 ISO 游标按
      manifest.timezone 转成 'YYYY-MM-DD HH:mm:ss'(sv-SE locale 恰好此格式);
      旧游标无需迁移;已是普通格式/垃圾值原样返回;秒精度(同秒 .SSS 行值字典序更大仍被选中,
      轻微重叠由 source_event_id 幂等吃掉)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(deploy): 逻辑包进 main() — 脚本 git pull 自更新时 bash 边读边执行会错位 · bd442595
      bash 按字节偏移惰性读脚本;本脚本运行中会 pull 更新自己 → 后半段按新文件偏移执行旧/错位代码
      (实测:验证逻辑跑的是旧版)。包进 main() 并在文件末尾调用,bash 先整体解析完再执行,免疫自更新。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(deploy): migrate status 校验改先落变量再 grep — pipefail+grep -q SIGPIPE 误报 · 55444ced
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(deploy): migrate status 校验不再 tail 截断 — prisma 版本提示框位置不固定致误报 · c8c6943a
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(deploy): 确定性生产部署脚本 — 根治 compose "不切新镜像" 缺陷 · 87741f59
      docker compose `up -d --build` 有跨版本反复复发的已知缺陷(docker/compose#9308 #9259):
      镜像构建成功但运行中容器不重建(仍跑旧镜像)。本项目 2026-06-10 部署实测中招:
      service 容器还在旧镜像 → migrate 报 "no pending" → phone_verified 列没建。
      
      根治思路:生产部署不依赖 compose 的 diff 启发式判定。deploy/deploy-prod.sh:
        git pull --ff-only → 显式 build → up -d --force-recreate → 三道硬验证
        (① 容器镜像 ID == 新构建镜像 ID;② migrate exit 0 + migrate status 无 pending;
         ③ service health 200 + web 200),任一不过即非零退出。
      README 把日常更新指到脚本,并警告勿直接 up -d --build。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(web): 详情页手机号加 真/假 角标(假号也明确标出) · cfaca4c4
      - plan-aggregate 聚合 patient 透出 phoneVerified;
      - 详情页手机号旁角标:真(teal,外部对照表已核实)/ 假(灰,宿主同步造数号),
        tooltip 注明含义;类型/adapter/mock 同步。
      
      验证:真号患者(刘倍磊)phoneVerified=true、假号患者(吴小燕)false,两端 tsc 0。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • feat(patient): 真实号码导入(病历号对照)+ phoneVerified 标记 + 列表"真实号码"筛选 · 5671371c
      测试库 phone 全为造数假号;业务提供 1500 名患者的真实手机号(按病历号 file_num 对照)。
      
      - schema: Patient 加 phoneVerified(默认 false)+ migration;true = 外部对照表核实替换的真号。
      - cli/import-real-phones: 读 CSV(file_num,client_phone)→ 按 medical_record_number 匹配 →
        phone 改真号 + phoneVerified=true;支持 --dry-run;号码做基本卫生(去非数字)。
      - 列表: ListPlansQuerySchema 加 phoneVerified(query 串 'true' preprocess 还原布尔);
        plan.service 把 keyword 与 phoneVerified 合并进 patient 子查询;PlanPatientBrief 透出 phoneVerified。
      - web: 筛选条加"真实号码"开关(teal 高亮);行内手机号旁加"真"角标(tooltip 注明已核实)。
      
      本地验证:1500 行对照表匹配 12 名(本地数据不全属预期)→ 更新 12;
      列表 all=274 → phoneVerified=true 筛出 12,行内标记正确。两端 tsc 0。
      
      注:重新全量摄入会被宿主假号覆盖 phone(phoneVerified 不回退)→ 重摄后需重跑导入脚本。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
  3. 09 Jun, 2026 3 commits
    • chore(web): 助手空态示例文案改为 患者信息/应治未治机会/今日工作准备 · be08cd62
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • fix(assistant): artifact 流式空白 — 改持久壳 iframe + postMessage 注入(不重载) · d4a8fbfd
      上版每次流式增量都换 srcDoc → iframe 整段重载 → 反复重新下载 Tailwind/Chart.js CDN,
      还没加载完下一帧又来 → 整个流式期间一直"重载中"=空白(用户实测空白)。
      
      改为持久壳方案:
      - iframe 只加载一次(srcDoc=固定壳:CSP+Tailwind+Chart.js+#root+消息监听),CDN 只下一次;
      - 内容通过 postMessage 注入 #root.innerHTML(Tailwind 的 MutationObserver 自动给新内容上样式)→
        流式内容实时"长出",不重载、不白闪;
      - Artifact 加 streaming 标志:流式中只 set innerHTML(图表脚本不执行,避免半截脚本报错);
        最终 tool_call(streaming=false)时重建 <script> 节点 → Chart.js 执行、图表渲染。
      - 高度由 iframe 内 ResizeObserver 实时回传(initial 120,随内容增长),不再是空大白框。
      - openFull 用自包含整页(buildStandaloneDoc)。
      
      web tsc 0。纯前端改动。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed
    • perf(assistant): artifact 流式渲染 — 卡片边生成边"长出",不再干等整段 · 5737df69
      诊断:render_artifact 慢点全在"模型逐字吐 ~5.5KB HTML"(~32s),而 tool-call 要等整段
      生成完才触发 → 用户对着"生成中"干等 30s+。MCP/工具往返仅 0.2s,非瓶颈;默认模型已是
      deepseek-v4-flash(已是快的)。
      
      优化(感知为主):
      - 后端:消费 AI SDK 的 tool-input-delta,按 toolCallId 累积入参 JSON,增量提取 html 字段
        (extractHtmlField 容忍半截转义),250ms 节流推 artifact_html 事件。
      - 前端:artifact_html 按 callId upsert 到 artifact 块(实时增长),最终 tool_call 覆盖完整 html+title;
        ArtifactView 对 srcDoc 重建做 600ms 节流(避免每增量都重载 iframe 白闪 / Tailwind 反复重扫)。
      - 提示词加"HTML 力求紧凑"以略减 token。
      
      实测:卡片 html 从 ~8s 起流式到达(35→5537 字符,65 次增量),界面从第 ~8s 起"长出"卡片,
      而非 ~35s 整张蹦出。两端 tsc 0。
      
      注:总时长仍受模型出字速度限制(~40s);若要"秒开"常见列表,需走机制 A(预制组件直渲工具结果)。
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed