1. 20 Jul, 2026 11 commits
    • docs(integration): FRIDAY 推送契约补齐 — 品牌主档 source、诊所名列、语义澄清记录 · 777fd744
      对齐 2026-07-20 的新落地内容:
      - 新增 §2 `tenant_info` 品牌主档 source(品牌 GUID→中文名;PAC 靠它避免前端显示 GUID),
        source 总数 15→16
      - `med_emr_info` / `med_check` 补 `organization_name` 列(诊所展示名来源;此前导出漏列,
        导致工作台诊所显示 GUID)
      - 补回丢失的 §7:改为「语义澄清记录」——时间口径/金额/两套关系枚举/treat vs dispose/
        结算 status 全 9 值 + 为何不收 7/8,逐条附源码或数据依据;另列三条推送方留意事项
        (organization_name 仅覆盖有病历的诊所、字典表全量重推、测试环境沙盒数据待生产校准)
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(auth): 模拟登录按宿主分 tab;FRIDAY 数据范围用真实品牌/诊所 · 426bbfa4
      两宿主此前挤在一个面板(FRIDAY 只有一条集团级横条),数据概念互相干扰。改为宿主 tab:
      - 瑞尔集团(jvs-dw):原客服花名册路径,一字未动
      - FRIDAY 市场:身份是造的(该宿主本就无客服花名册),但**数据范围必须真实** ——
        品牌/诊所来自新端点 GET /auth/mock-orgs:结构取 org 树(派生自 patient_transactions),
        名字取 host.sourceUnitNames / clinicNames(派生自摄入源),零硬编码
      
      - 契约:MockLoginRequest 加 orgId(任意 org 节点 = 品牌 GUID / 诊所 id);新增 MockOrgs 响应
      - service:listMockOrgs(org 树 + 名字典,有名品牌与多诊所品牌排前);friday 分支支持
        orgScope=[orgId],不传则市场级
      - web:FridayPanel(角色 + 市场级 + 真实品牌/诊所两级选择 + 搜索)
      
      验证:FRIDAY tab 显示 19 品牌 / 35 诊所(瑞诚齿科 12 诊所、希尔德 3…);点"瑞诚齿科"
      品牌级登录 → 右上角 12 个诊所、召回池由 11,723 收窄到 5 人、患者诊所均属该品牌 ✓;
      瑞尔 tab 花名册与登录行为不变。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(auth): 品牌/诊所展示名从摄入数据派生 — 多品牌宿主不再显示 GUID · 5ac83ffb
      FRIDAY 的 source_unit / clinic_id 都是 GUID(jvs-dw 是"瑞尔/瑞泰"中文名),工作台此前
      只能显示 GUID。诊所名机制(host.clinicNames + clinic_directory + CLI 派生)本就存在且
      支持文件源 —— 缺的是①导出漏了 organization_name 列 ②friday manifest 没配 directory
      ③品牌名没有对应机制。
      
      - 契约:TokenDictionary 加 brands 档;Host 加 sourceUnitNames(对称 clinicNames,
        迁移 20260720104938)
      - manifest:新增 brand_directory 声明(与 clinic_directory 同形)
      - CLI refresh-clinic-names 泛化:一次派生两类字典,单侧声明不误清另一侧;空结果不覆盖
      - session:getDisplayNames 一次取两类,dictionary.brands 与 clinics 同款合并
        (服务端派生打底、宿主换票传的覆盖优先)
      - friday export.sh:med_check/Mongo 补 organization_name,新增 tenant_info 品牌主档
        (apply 服务 tenant_apply.tenant_info,列名实测 name/name_ab 非 short_name)
      - ️ export.sh 加固:所有导出改 tmp→校验非空→mv,裸重定向在库抖动时会清空好文件
        (2026-07-20 踩,62MB med_emr_info.json 被清零)
      
      验证:派生诊所名 11 家 / 品牌名 45 个;friday session 下发 brands 45(成都安玉牙种植
      医院/欣美/森德口腔)+ clinics 11;jvs-dw 对照 brands 键不下发、clinics 5 家照旧。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(auth): 模拟登录支持 FRIDAY 市场(第二宿主,集团级) · 17ae97bd
      工作台此前只能登 jvs-dw 租户,FRIDAY 摄入的 10.7 万患者 / 1.17 万召回计划无入口查看。
      - MockLoginRequestSchema.tenant 加 'friday'
      - auth.service 独立 early 分支:host=friday / tenantId=friday-market /
        orgScope=['friday-market'](集团根 → 展开后 sourceUnits/clinicIds 均不限)。
        多品牌 SaaS 无在岗花名册,mock 只做集团级;诊所名未摄入 → dictionary.clinics 空,
        UI 回退显示 id。jvs-dw 路径(MOCK_PRESETS)完全不变
      - 弹窗集团级横条下加青色 FRIDAY 入口,角色沿用①所选
      
      验证:mock-login 签发 token(hostId=friday/tenantId=friday-market)、瑞尔对照未受影响;
      浏览器登入实测召回池 11,723 人,详情页画像 9 项 / 治疗历史 1,621 项 / 累计消费 ¥3,320
      / AI 话术均正常。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/friday): 语义全实锤(源码+数据双证) + 患者主档 push 支持(partial upsert) · 4f176611
      一、时间/金额实证(2026-07-20,契约文档同步):
      - 金额=元(洁治单价均值 ¥310/客单 ¥1,730 双证);MySQL datetime=北京墙钟(_gmt_ 命名惯例误导)
      - ️ Mongo Date=北京墙钟伪装 UTC(clinicTime/createdGmtAt 小时分布双峰直读=营业时段;按真 UTC
        解释一半病历落深夜)→ export.sh Date 改直读墙钟输出 naive 字符串;EMR 链 35.6 万 fact
        清层重灌校正(-8h),修后小时分布恢复营业双峰。契约文档明令宿主 push 勿 toISOString 带 Z
      
      二、官方字典(friday-saas 源码考古,待确认清单清零):
      - contacts.relationship = PhoneRelationshipEnum(1本人…9其他)→ patient.yaml 解码,
        preferences.contactPhone 带 relationship 标签(mother/father…)
      - referee_relationship = RecommendRelationshipEnum 17 码;reverseValue+年龄差数据双证
        语义方向=customer 是 referee 的 X → patient_relation.yaml 按逆关系重写(码8 其他亲属
        纠正 friend→other;新增 5/6/11/12/13/15/16 映射;码3 按对方性别拆 father/mother)。
        关系表清层重灌:friend 3,511→541(纯码7),官方映射干净落地
      - settlement status 全 9 值:实体注释 7=流程结束/8=欠款补缴克隆单(savePatientSettlementQk
        证实 receivable 原样复制→入消费必双计)→ 现行不收 0/2/5/6/7/8 全部获官方背书
      - treat=本次治疗/dispose=处置(EmrInfoResDto 注释);预约 status 7=到诊患者变更
      
      三、患者主档 push(此前 form A 仅支持 fact 资源):
      - patient-upsert.util:upsert 数据构造抽纯函数,full(cold-import 历史语义,分毫不变)/
        partial(push 单表:未提供字段不进 update 集合、phone 永不合成假号)双语义 —— 解决
        push 单表时 contacts lookup 缺席导致存量真号被假号覆盖的根因
      - ingestRawTables 路由 upsert 资源(patient partial/patient_relation/patient_return_visit);
        纯主档推送不产 txn 不触发重算(注释明示)
      - 测试 8/8(partial 不覆盖/无清空语义/preferences 透传);E2E 冒烟:push 单行改名,
        存量 phone 15725591111 原样保留 ✓。契约文档待确认清单清零
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • test: canonical-fact-layer emits 检查豁免全部 upsert 资源(与 cold-import 三元组对齐) · 1d8a663b
      原测试只豁免 patient,后来新增的 upsert 资源 patient_relation/patient_return_visit
      (设计上无 emits)一直误报;friday/patient_relation.yaml 落地后同一缺陷第三次命中。
      豁免集合改为与 importDirectory 排除 subjectCfgs 的三元组一致。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • fix(sync): synthesizer occurredAt 缺失时确定性回退 updatedAt→createdAt,墙钟仅最终兜底 · 4b2a7b52
      原 occurredAt ?? new Date() 在 occurredAtField 缺失/不可解析时每次摄入产生新墙钟值 —
      时间锚纳入 fact 变更检测后会被放大成伪 supersede 版本抖动。改为确定性回退链
      (occurredAtField → updatedAt → createdAt),三者皆缺才落墙钟并 warn。含回退顺序测试。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • fix(sync): fact 变更检测纳入四时间锚 — 宿主纠正事件时间可传导为新版本 · 2cfc043c
      FactWriter 此前只比 content hash + status:occurredAt/plannedFor/validFrom/validUntil
      不参与 → 宿主纠正事件时间(如 FRIDAY 伪 UTC 整体 -8h)重摄永远 evidence_appended,
      时间锚永不更新。时间锚是时间轴/召回窗口 COALESCE(occurred_at,planned_for)/过期 cron
      的直接输入,属事实实质 → 纳入等值判定(毫秒精度,双 null 视为等),单写与 bulk 两路同步。
      不纳入 title/summary(展示层)。含 supersede 传导测试。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • chore(design-sync): 组件库首次同步到 claude.ai/design(18 组件) · e1697a0d
      - .design-sync/:config(锚定 PAC Design System 项目)+ 18 个富预览 + 用法约定 + NOTES
      - src/ds/index.ts:设计系统导出桶(ui 全集 + plan-detail 原语 + toast)
      - ui/*.tsx、shared.tsx:补 JSDoc(@category 分组,转换器生成 prompt.md 用)
      - .gitignore:忽略同步暂存/构建产物
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
  2. 19 Jul, 2026 7 commits
    • fix(recall): 终态抑制加时间锚 — 结案后新发同类信号放行复活,'永久'不再误杀 · 05cdd46d
      问题:信号级抑制集只按 (scenario|subKey) 一刀切,外院/无效的永久 snooze(36500d)会把
      结案后新发的同类诊断也永久压死 —— 同牙位复发(外院种植失败回院再诊断)和 @whole 病种
      (外院牙周,多年后新发)整类终身沉默。且 cluster lead 锚最早诊断,老 fact 永远 active
      (外院治疗不进本院数据)→ 新旧必聚同 cluster,单看 lead 时间无法区分新发。
      
      修法:
      - scenario:cluster 注入 cluster_latest_occurred_at(成员 max),hit 透传
        latestSignalOccurredAt(lead/daysSince 仍锚最早,紧迫度口径不变)
      - engine:抑制集 Set<key> → Map<key, 结案锚点>;锚=结案 execution.createdAt(不可变,
        不用 updatedAt——会被召回反馈等后续写顶后);同 key 多次结案取最新锚
      - 过滤:key 命中且 latest > anchor → 放行复活;latest 缺省(旧 scenario)/锚不可解析
        (远未来哨兵)→ 维持旧行为全压,宁可多压不误放
      
      测试:tests/plan-engine-snooze-anchor.spec.ts(锚点构建 5 例 + 逃逸判定端到端);
      存量 plan-engine-batch 通过(mock 无 executions/updatedAt 走哨兵路径)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • refactor(sync/friday): 宿主字段零改名 — 撤销 tenant_id→brand_id 别名,差异由 PAC 适配层消化 · 8ebe45ab
      原则修正(用户指出):要求宿主把 tenant_id 别名成 brand_id 是把 PAC 的概念洁癖转嫁给
      宿主;字段名差异的消化本就是适配层职责。宿主(导出与将来 push)一列不改名。
      
      - export.sh:撤销全部 AS brand_id;Mongo 平铺同步 tenant_id
      - manifest:identity_namespace_field: tenant_id(指向宿主原生列)。无功能冲突:PAC 租户
        由 manifest 静态 tenant_id: friday-market 决定,StaticTenantResolver 不读行,行级
        tenant_id(品牌 GUID)仅作 source_unit 命名空间源
      - push 契约文档:brand_id → tenant_id(宿主原生列),通用约定写明概念区分责任在 PAC
      
      零数据翻腾证明:幂等键/患者唯一键用 source_unit 的值(品牌 GUID),列名不参与 ——
      原生列名重导+重摄实测:0 新 txn/357,931 全量幂等命中/0 新 fact/failed=0,
      source_unit 分布逐品牌不变。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • docs(integration): FRIDAY 推送数据契约 — 形态 A 的 15 个 source 字段定义 · 638d27fb
      Push 通道形态 A 在 FRIDAY 宿主的具体化:每个 source 的 JSON 字段清单(类型/必填/语义),
      形状与已验证的存量摄入(10.7 万患者/36 万事实)完全一致 → 宿主按此推送与存量无缝衔接
      (幂等去重/版本演进/重叠无害)。
      
      - 通用约定:id 一律字符串(存量实测坑)/updated_gmt_at 必须 bump(幂等键一半)/
        brand_id=源 tenant_id 改名/at-least-once
      - 患者关系 3 source(含 upsert 主体待 PAC 排期的如实标注)/预约/病历链 3(含 diag/treat
        数组元素结构与 status∈{3,4} 推送范围)/结算链 4(status 拆分规则钉死)/计划咨询 3
      - 待 FRIDAY 确认清单:两套关系码枚举/treat 语义/结算剩余 status
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • refactor(sync): 增量水位声明与源类型解耦 — file 源存量跑同产 cursor(与拉模式同一段逻辑) · 9059386b
      统一动机:jvs-dw(拉)存量跑写 cursor_after=run_start 供首次增量接力;FRIDAY(file)
      此前不写 —— 差异根源不是流程分叉(finally 记账本就单点共用),而是 incremental.per_query
      声明寄生在 sql_source 之下,file 源无处声明水位列。
      
      - manifest.schema:IncrementalDeclSchema 抽名;sql_source.incremental 引用之(形状不动),
        manifest 顶层新增可选 incremental(file 源用;两处都写时 sql_source 内优先)
      - cold-import:入口一行 ?? 兜底(sql_source 优先短路)——之后读写 cursor 走完全同一段
        finally,零新逻辑。file 装载路径不消费水位(loadAllTables 文件分支忽略 incremental
        参数,全量装载幂等去重),水位纯记账,供 delta 导出 WHERE 模板 / push 回放起点
      - friday manifest:顶层声明 12 张事件表 updated_gmt_at(字典表/contacts 无更新列不列)
      
      验证:tsc 0 err;jvs-dw 零影响(manifest 未动 + ?? 短路 + dry-run Cursor 解析行为不变);
      friday 实跑 sync_logs 首次产出 cursor_after={12 表 × run_start ISO}。
      心智模型统一:所有宿主存量跑即水位创世写入,差别只剩水位消费方。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/friday): 患者关系边 + 咨询接入;退费第三轨 200 单补摄收口 · 8b556c4a
      - patient_relation ← customer_referee_circle(10,169 行,双向成对存):患者-患者边进
        patient_relations 独立表(同 jvs-dw 契约,电话簿语义不冒充关系边)。
        ️ 关系码字典 DB 无表(疑代码硬编码)→ 统计推断解码(互逆对+年龄差+性别三重证据,
        依据全在 yaml 注释):1=spouse(92%异性同龄)/8,7=friend/3=父母按对方性别拆
        father,mother(transforms lookup 对方 sex 拼"码|性别"键)/2,10=child(儿×女)/
        17,16=grandparent,grandchild/4,9=sibling/其余 other。待 FRIDAY 官方枚举校正,
        改 enum_mapping 重摄即覆盖。
      - consultation ← customer_consult(64 行):意向 potential_treatment + FRIDAY 独有
        未成交原因 unsettled_reason(强召回信号)passthrough 进 content。
      - 落库:关系边 7,996 条(friend 3511/other 2322/spouse 639/child 630/mother 349/
        father 266/sibling 229/祖孙 50),100% linked(对方均在册患者)→ guardian 链路
        (儿童触达取家长真名真号)对 FRIDAY 直接激活;consultation 64;failed=0。
      - 退费第三轨(status=3 负额 200 单)测试库恢复后补摄完成:refund_record 齐 880
        (99 整单反向+581 行级+200 负额冲减),全程幂等。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/friday): 默认联系号关系语义 — preferences.contactPhone(本人号/代接号判定) · f608db9a
      customer_contacts.relationship(13.3 万行,57.6% 填充,host sys_dict 编码 1-9)随 phone
      lookup 同源带出 → patient canonical passthrough → preferences.contactPhone
      {relationshipCode, isSelf: 码=='1'}。零 schema 迁移(preferences Json 收纳,同 dedicatedCs)。
      
      编码语义用本地数据统计自证(年龄分桶):1=本人(成人 78.7%/儿童仅 51.4%——儿童过半
      代接号,触达该打家长);3/2 儿童特异=父母类;8 老人特异=子女类;9 全人群恒定≈其他。
      完整标签待测试库恢复读 sys_dict 后在 patient.yaml enum_mapping 补解码,管道零改动。
      jvs-dw 不映射 phoneRelationshipCode → 行为分毫不变。
      
      落库验证:107,227 患者 upsert,contactPhone 44,293(本人 40,059/代接 6,234... 按码分布
      与统计一致);儿童样例(2015/2018 生)代接码 2/3 符合预期。
      消费端(guardian/话术标注)待字典解码后一并接,避免半吊子 UX。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/friday): 消费/退费接入 — payment + 退费三轨(整单反向/行级/负额冲减) · c57b0806
      结算源 arrail-settlement-server(全品牌覆盖,实测钉死语义):
      - payment ← patient_settlement status∈{1,3} 且金额≥0(67,355 单):amount=receivable_this
        (应收=业务价值,jvs-dw 同款 LTV 决策);主导支付通道 lookup settlement_modes(金额最大者);
        encounterExternalId=registration_id。
      - 退费三轨(比 jvs-dw 双轨多一轨,宿主表达差异):
        ① status=4 整单反向(99 单,全负额) paymentExternalId=ref_settlement_id 精确挂回原单
          (兑现 jvs-dw refund.yaml 预留的升级);
        ② spec is_refund=1 行级部分退费(581 行,全归属 status=3 头单,头单仍作 payment,LTV 自然对账);
        ③ status=3 负金额整单冲减(200 单,2ac96f6d 品牌不用 status=4 的表达)——被 amount_cents>=0
          校验正确拦截后归轨,导出侧 WHERE 按金额正负切分(transforms filter 不支持数值比较)。
      - 其余 status(0/2/5/6/7/8)语义未明先不收。不从 settlement 反推治疗(财务≠临床)。
      
      已验证:payment 67,155 + refund 99+581 facts 落库 failed=0。
      待收尾:轨③ 200 单补摄等 FRIDAY 测试库恢复(宕机中,已挂探针;导出为空防护拦住了脏摄入)。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
  3. 18 Jul, 2026 11 commits
    • feat(sync/friday): treat[] 三路分流补全 — review/recommendation 两资源接入 · 04443ff8
      补 Phase 1 缺口(与 jvs-dw C.3 同语义):treat[] normalize 后 route_by_pattern
      ① 建议/推荐 → recommendation(共享 recommendation-code 字典;实测 153 行全部翻出召回码:
         拔除 69 / RCT 49 / 冠 15 / 充填 9 / 种植 3 / SRP 1,仅 7 行长尾落空)
      ② 流程/复查/行政词 → treatment_review(category=review 哨兵;chain S4"已复查"信号)
      ③ 其余 → actual。拆线(50 行)按 jvs-dw 2026-06-11 一线判例排除出 review 词表 →
         actual→本地 enum 拆线→surgical(判例与 route 语境耦合,留宿主本地不入共享)。
      
      external_id 沿用原始 value(与已摄入数据幂等键连续):重摄 failed=0,老 fact 全量
      unchanged;之前被 keyword 误收进 actual 的"牙周复查"类 6 行经版本流 supersede 自动
      纠正为 review,无残留。终态新增 recommendation_record 153 + review 56。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/friday): 病历摄入 Phase 1 — EMR/诊断/治疗/影像/治疗计划 五资源接入 · 6e2b549e
      FRIDAY 病历链落地(直连勘查 2026-07-18 定案,零新算子,纯配置+导出脚本):
      - export.sh:host 等价单表 dump(MySQL 7 表 CSV + Mongo med_emr_info status∈{3,4}
        平铺 JSON;id 字段字符串化对齐 CSV 契约;数组字段双形态:原生数组给 split,
        JSON 字符串给 emr 自由文本位)。凭据走 env 不入 git。
      - manifest:EMR 链 transforms —— diag[] 拆行→std_diag lookup(linkCode→std_code,
        K 开头截 K 码,非 K/未命中自然落 code=null 自由文本)→normalize/coalesce;
        treat[] 拆行(=本次治疗 actual,病历无计划字段已用全字段枚举证实);
        med_check lookup 类型字典;customer_treat_plan 行 join 头表(无诊所行显式 filter 丢弃)。
      - assemblers ×5:emr/diagnosis/treatment_actual/treatment_planned/image,
        全部 dict_includes 共享字典(diagnosis-kcode / treatment-category-core+
        actual/planned-rules / image-modality)—— 分层重构的首个第二宿主消费者。
      - 锚点:临床事实 sourceEncounterExternalId=emr_sub_id(同诊聚合);
        emr 挂 appointment_id(56.5%);影像挂 emr_id 病历号。
      
      全量实跑验证(本地):107,227 患者 / 288,383 txns / 285,986 facts,failed=0;
      旧 450 样本幂等去重 ✓。诊断 83.5% 拿到 K 码(共享名称字典生效),治疗 11 类闭集
      分布合理。周元策(987d743a)persona v1 + 召回计划与其 K04 诊断临床吻合,闭环通。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • refactor(sync): assembler 字典分层 — 临床通用层抽 _shared/dict,宿主怪癖留本地 · c9c1e778
      多宿主字典治理:enum/keyword 映射词分两类 —— 临床通用知识(慢性牙髓炎→K04、
      开髓→pulp_extirpation)拷贝必漂移(实证:jvs-dw 自己的 treatment_planned 字典
      已是 treatment_actual 的陈旧拷贝);宿主怪癖(价格表项名/标点残留/用词判例)共用
      会互相污染。→ 新增 dict_includes 机制:loader 在 zod 校验前解析合并,引擎零改动。
      
      - dict-include.ts:include 解析 + 合并语义(enum 本地覆盖/keyword 本地优先/
        strip 去重并集/_default 禁入共享层 strict 校验)
      - data/_shared/dict/ 七份共享字典:diagnosis-kcode(187 词+9 keyword 规则)、
        treat-stages(45)、treatment-category-core(177=actual∩planned 干净交集)、
        category keyword 按 kind 分 actual/planned 两个语义变体、image-modality(9)、
        recommendation-code(27+11)
      - jvs-dw 五份 yaml 改用 include,本地只留判例(牙齿缺少→K08、拆线→surgical、
        单据类 modality、价格表项名、标点残留死条目)
      
      生产零变化证明(jvs-dw 每日增量无感):20/20 assembler 新旧解析结果 deep-equal
      (vs git HEAD);tsc 0 err;测试与 HEAD 基线完全一致(存量 3 挂套件与本次无关);
      reparse dry-run 真实 loader 全链冒烟通过。
      
      已知遗留(有意不修,行为保真):planned keyword 裸「二期」误吞正畸(共享文件已标
      TODO);planned 残差中文标点死条目待清理 —— 均属行为变更须评估+reparse,另行提交。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • chore(deploy): 托管环境额外验私网 IP 可达(拦 loopback-only 绑定 → 网关 502) · f7cdf226
      原验证只 curl 127.0.0.1(loopback),应用绑成 loopback 时也过,但远端网关(经 ECS 私网 IP)
      够不到 → 公网 502(2026-07-18 friday 踩过)。COMPOSE_MANAGED=1 时额外 curl 私网 IP:3100/3101,
      不通即失败退出,把漂移在部署当场拦下。本机代理型(非 managed)不受影响。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(deploy): 托管环境 web/service/docs 绑 0.0.0.0(远端网关经私网 IP 到达) · 2db31f27
      friday 生产经独立网关 lb1.friday.tech 转发,网关走 ECS 私网 IP 到达应用;base compose 绑
      127.0.0.1(本机代理型部署用)会让网关连不上 → 502。之前生产容器是旧 compose 的 0.0.0.0,
      force-recreate 应用了 loopback 绑定才暴露此问题。managed override(仅托管环境叠加)用 !override
      改回 0.0.0.0;安全由云安全组保证(只放行网关/VPC)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(web): Sentry 网络面包屑 URL 归一化(去患者标识,保留路由形状) · 189bdee1
      医疗系统 fetch/xhr 面包屑 URL 带 patientId/病历号等 PII,整段被服务端打 [Filtered] 又看不出接口。
      新增 sentry-scrub.sanitizeUrl:段级 id(uuid/纯数字/长hex/病历号)→ :id、query 全去掉;
      client/server/edge 三处 Sentry.init 挂 beforeBreadcrumb。既能看调了哪个接口,又不落患者标识。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • build: 前端 Sentry NEXT_PUBLIC_* 走 build-arg 注入(同 API_BASE_URL) · fb67dc3b
      pac-web 的 NEXT_PUBLIC_SENTRY_DSN/ENVIRONMENT/TRACES_SAMPLE_RATE 需 build-time inline,
      故 Dockerfile 加 ARG/ENV、compose build.args 加对应项(未设则空→前端 Sentry no-op,不阻塞构建)。
      后端 SENTRY_* 是运行期 env,已由 env_file: apps/pac-service/.env 注入,无需改。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat: 接入自部署 Sentry(前后端分开,错误+基础tracing) · 6a3714a9
      自部署 Sentry 23.6.1(am.jwsmed.com),两个项目/DSN/SDK,环境用 SENTRY_ENVIRONMENT 区分(test/prod)。
      已冒烟验证:老服务器接受现代 SDK v10 envelope(flush=true);pac-web next build(Turbopack)通过;pac-service 启动正常。
      
      后端 pac-service(@sentry/nestjs):
      - src/instrument.ts:Sentry.init(DSN 缺省则 no-op),main.ts 首行 import;profiling 关。
      - app.module 注册 SentryModule.forRoot()(请求级 tracing)。
      - AllExceptionsFilter 仅对真·500(instanceof Error 分支)captureException,业务错误不上报。
      
      前端 pac-web(@sentry/nextjs,Next16/App Router):
      - instrumentation-client.ts / sentry.server.config.ts / sentry.edge.config.ts / instrumentation.ts。
      - next.config.mjs 用 withSentryConfig 包裹;sentryUrl 指自部署;source map 上传仅 SENTRY_AUTH_TOKEN 存在时(secret,不入库);telemetry 关;不开 Replay。
      
      env:两端 .env.example 补 Sentry 段;DSN 走 env,真值在各机 .env(gitignored),auth token 仅构建期 env。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(web): 助手最大化占满窗口(inset-3,不写死宽高) · 8217f5ab
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(web): 详情页顶栏收口潜在/预约/回访三动作 + 宠物引导评价 · 1e1d5ac9
      - 潜在治疗/新建预约/回访 从身份卡·执行表单·通话结果头 上移到顶栏右上角动作区
        (潜在·预约=默认样式,回访=主按钮);删除已成死代码 call-widget.tsx。
      - 宠物引导:话术生成后停留 15s→引导给话术打是否好用;hover 回访/通话结果→引导做召回反馈
        (60s 冷却、已反馈不扰),复用 emitPetEvent 气泡,不代评价。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(web): 认领/返池后原地移除该行(不再留在错 tab) · c1267e65
      召回池认领→行离开池;我的返池→行离开我的。新增 usePatientPicker.removeItem
      (过滤 + total-1),claim/recycle 按当前 view 决定 removeItem 还是 patchItem。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  4. 17 Jul, 2026 4 commits
    • feat(web): 助手隐藏语音输入 + 右上角加最大化 · a8eb0ff1
      - 语音输入(麦克风)暂时隐藏:VOICE_INPUT_ENABLED=false 门控按钮,听写链路代码保留待恢复。
      - 助手小窗头部加「最大化/还原」按钮(Maximize2Minimize2):点击在 400×620 吸附窗 
        1000×634 居中大窗间切换(inset-0 m-auto 定宽高居中);收起时自动还原为小窗。
        本地实测:麦克风不渲染;最大化后面板 1000×634 居中、图标切还原,链路通。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(plan): 隐藏回收倒计时 + 返池放宽到 active(被认领即可回收) · 0ce83421
      - 头部「回收倒计时」隐藏:暂无自动回收机制(返池改手动),RecycleCountdown 组件保留待恢复。
      - recycle 后端只拒终态(completed/abandoned/superseded);assigned + active 都可返池 —— 无自动
        回收后 plan 可能停在 active 却仍挂客服名下,原「只 assigned 可回收」会误拒(图2 报错场景)。
      - 前端「返池」按钮 gate 由 status==='assigned' 改为「有 assignee 且非终态」,与「认领」(无 assignee)
        互斥。本地实测:active+挂名下 → 点返池成功、DB active/assignee=null、toast 正常。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(web): 召回池已认领患者加「返池」按钮(回收→退回召回池) · 45096f85
      已认领(status=assigned)的患者行,PLAN_RECYCLE 权限(leader/admin)下 hover 出「返池」按钮:
      调 plansApi.recycle → 后端置 active + 清 assignee → 原地 patchItem 改回「待认领」(不重拉)。
      与「认领」共用同一 grid 叠层 hover-swap 结构(不重排);amber 区别 teal。本地 dev 实测:
      点返池 → 进行中变待认领+现认领按钮、toast「已返池」、DB status=active/assignee=null,链路通。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(plan): selectHits 大数组 spread 炸栈,plans 全量重算 28万患者规模下崩溃 · 1bbf0774
      hits.push(...subHits) 把每个元素当实参压栈,V8 实参上限 ~6.5万;华北灌完(host ~28万患者)后
      单子场景命中超限 → RangeError: Maximum call stack size exceeded,recompute-plans 中途崩、
      无汇总输出。改逐个 push。parser-pipeline 的 metrics.writes.push(...results) 同型隐患
      (单资源单批已 5万+ 行)一并修掉。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  5. 15 Jul, 2026 7 commits
    • fix(plan): orgScope→clinicIds 作硬边界,view_all 不再豁免越权(修 leader 仍能跨诊所) · 67668e9e
      上版按 !PLAN_VIEW_ALL 豁免,但 leader 角色本就带 PLAN_VIEW_ALL → 漏洞对 leader 仍在。
      clinicIds 由 orgScope 派生,本应是硬边界:list 去掉 view!=='all' 豁免、detail/aggregate 去掉
      view_all 豁免,始终按 clinicIds 圈诊所(null 集团池放行)。宽 scope(集团 leader/admin)clinicIds
      覆盖全部 → no-op 不受影响;窄 scope 才被真正圈住。view_all 语义=本 scope 内看所有经办人,不跨诊所。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(plan): 详情按 ID 直取补 clinic 隔离,修切诊所后越权看别诊所患者 · c4bb0a94
      召回池列表按 orgScope(clinicIds)过滤诊所,但 detail / aggregate(/plans/:id/full)按 ID 直取只
      校验 host/tenant/sourceUnit,漏了 clinicIds → 切诊所后残留的旧 planId 仍能拉到别诊所患者详情。
      现补齐:无 PLAN_VIEW_ALL 时目标诊所须在 clinicIds 内(null 集团池放行),否则 aggregate 抛
      PLAN_NOT_FOUND(前端已有该码处理 → 自动跳回当前 scope 首个患者),detail/recompute 抛 404。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(web): 助手工具卡隐藏改 loading 文案 + 日历 shadcn 中文化修翻页 + 回访时分下拉 · d946dfc8
      - assistant:工具调用不再渲染卡片,运行中的工具以"正在xxx…"喂给下方 loading 指示(替换"生成中"),
        空档默认"生成中…";英文工具名彻底不露出。
      - calendar:react-day-picker 换官方 shadcn Tailwind classNames(teal 主题);中文化(日一二…/2026年7月,
        周日起始);修 month_caption 盖住翻页箭头导致点不动(z-10 + pointer-events)。
      - outcome-form:下次回访时间由预设下拉改 时(00-23)+分(00-59)两下拉,每分钟粒度,默认 00:00。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(web): 修左栏默认 tab 回落竞态(首帧误切 pool 致 tab/数据错位) · 19278f18
      上个改动的 auto-fallback 未卡「加载完成」转变:usePatientPicker 的 loading 初值 false、
      total 初值 0,首帧即满足 !loading&&total===0 → 抢在「我的」加载前切 pool,而并发守卫又跳过
      pool 的 fetch → tab=召回池 但数据仍是「我的」。改为等 loading true→false 完成一次再判空。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(web): 召回工作台左栏默认进「我的」,空则回落「召回池」 · 0de83ea9
      原 rail 默认 tab 写死 pool,而落地页选默认患者是 mine 优先 → 左栏 tab 与所选患者来源不符。
      改为默认 view=mine,加载完成后若 total=0 自动回落 pool(autoFellBackRef 只一次,尊重后续手动切换)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(web): 召回池行 hover 不再位移(认领按钮与状态药丸叠同格切 visibility) · 34283d42
      原实现 hover 时「认领」按钮 group-hover:inline-block 冒出、StatusPill group-hover:hidden 藏起,
      二者尺寸不同 → 右块宽度变 → flex-1 诊所名重排,整行看着位移。
      改为二者叠在同一 grid 格,槽位恒为较大值,hover 只切 visibility(非 display),零重排。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(web): 召回池列表项有原始档案时隐藏假手机号(对齐详情页) · 6f971dfd
      详情页已按 !originalArchiveUrl 隐藏手机号,召回池左侧列表项遗漏。
      PatientRow 增 hidePhone,调用处传 hasPatientArchive(=VIEW_PATIENT 模板),
      与详情页同条件:宿主配了原始档案入口时真号在宿主档案页看,PAC 侧为造数假号故隐藏。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed