1. 24 Jul, 2026 7 commits
    • fix(recall): 缺牙建议治疗按年龄排序 —— >70 岁活动义齿在前、种植并行 · 3ff9c827
      现象(业务反馈):90 岁(吕学文)、78 岁(李石明)的缺牙召回,界面一律显示
      「目标 · 种植」,话术也推种植。业务反弹"为什么给 90 岁老人推种植"。
      
      根因:UI 取 signals.expectedCategories[0],而 K08 的 categories 首项恒为 implant。
           整条链路(目标标签 / 理由文案 / goal / 话术)都没有年龄维度 ——
           召回场景 SQL 与 priority-scorer 里一个年龄条件都没有。
      
      ## 修法:改在诊断→治疗的源头,不在流程末端打特例
      
      canonical-codes.ts 新增 recommendedCategoriesForAge(categories, age) 作建议侧单一源:
      候选同时含 implant + prosthodontic 时,age > DENTURE_FIRST_AGE(70)→ prosthodontic 提前。
      scenario / 前端 reason-line / 话术 prompt 全部调这一个 resolver,不各写年龄分支。
      
      ## 业务方口径(对齐后修正)
      
      初版做成了"高龄禁推种植",过重。业务反馈:**70-80 不是种植禁忌**,能否种植取决于
      骨量骨质 / 全身状况 / 患者意愿,由医生评估。故改为:
        - 建议**只调顺序**:活动义齿在前,**种植并行保留**(不删)
        - 话术从"严禁推荐种植"改为"先讲活动义齿,种植可并行提及;不主推手术、
          不承诺能不能种 —— 落到来院让医生按身体条件评估"
        - PAC 不替医生下禁忌结论
      
      ## 三条设计红线(各有用例锁住)
      
      1. 只排序不增删 —— 任何年龄下返回集合恒等于入参(种植不会被抹掉)
      2. 不碰排除闸 —— DiagnosisTreatmentMap[].categories 仍是完整候选集
         (否则高龄患者做过种植反而排除不掉 → 重复召回)
      3. 不碰"要不要召回" —— 缺牙影响咀嚼营养,高龄同样该管
      
      ## 改动
      
      - canonical-codes:DENTURE_FIRST_AGE=70 + recommendedCategoriesForAge + treatmentCategoryNameZhFor
      - reason-signals:signals 加 focusCategory / patientAge(均 optional,旧 plan 回落原行为)
      - scenario:SQL 取年龄 → 走 resolver;高龄 goal 文案(义齿为主、种植并行)
      - 前端:目标标签用 focusCategory;理由行调同一 resolver 显示「活动义齿 / 种植」
      - 话术:fact-block + stable prompt 加高龄沟通约束;三档 promptVersion bump
      
      实测(本地 75 岁真实患者重算):focusCategory=prosthodontic · patientAge=75 ·
      理由「…未启动活动义齿 / 种植」。308 tests passed,service + web typecheck 干净。
      
      ️ 存量:plan 变化判定只比 (scenario, subKey),signals 变化不升版本 →
         已有 plan 不会自动拿到新字段(前端回落原行为)。生产 775 个 80+ 缺牙患者
         需回填或强制重算才生效,另行处理。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(push): 推送记录支持 HMAC 免登录查询 + API 文档补全 dryRun/limit 参数 · ec026cb2
      对接反馈:① 文档站 API 参考没更新 ② 查推送记录还要登录,联调不便。
      
      1) 新增 GET /pac/v1/push/logs(HMAC 验签,免登录)
         复用宿主推送时已在用的 X-PAC-Host-Id / Timestamp / Signature 三件套,联调不必另开账号;
         GET 无 body,签名对空串算 hex(HMAC-SHA256(`<ts>.`, secret))。
         **不是公开接口** —— 签名错误返回 10106,拿不到任何数据,不泄露宿主推送情况。
         与登录态 /admin/host/self/push-logs 同一实现(sync.module 引入 AdminModule 复用
         HostsService.getPushLogs,两模块无环),返回同一份数据。
      
      2) API 文档补全:@Query 缺 @ApiQuery 导致 Swagger 采不到 —— push/rows 的 dryRun、
         push/logs 的 limit 现已进 spec;重新 dump 静态 spec(apps/pac-docs/openapi/pac.json,
         59 paths),文档站 API 参考随之更新。
      
      3) 契约文档 §7.2 改写为「两种查法」:免登录(HMAC,联调推荐)+ 登录态,并说明签名对空串计算。
      
      验证:正确签名返回推送记录、错误签名被拒(10106 签名不匹配);typecheck 干净,299 测试绿。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(push): 宿主对接自助反馈 — push 预检(dryRun)+ 推送记录查询 · aaf8805f
      FRIDAY 开发对接 push 时需要"我推了什么 / PAC 收到什么 / 成了几条 / 错在哪",补两个自助能力:
      
      1) 预检 POST /pac/v1/push/rows?dryRun=1
         跑完整管线(transforms + 装配 + 归一 + 租户解析)但**不落任何库、不触发重算**,
         响应带 dryRun 标志与各资源**样本 canonical** —— 宿主可直接核对 PAC 把原生行翻译成了什么。
         复用 cold-import 既有 dryRun 通路(processSubject/processPatients 已支持),
         ingestRawTables 此前硬编码 false,现按 opts.dryRun 透传;预检批次 syncLog 以
         triggeredBy `push-dryrun:` 前缀标识,不与正式推送混淆。
      
      2) 推送记录 GET /pac/v1/admin/host/self/push-logs?limit=50(宿主自助权限)
         按时间倒序列最近 N 批(上限 200):source / status / dryRun / 收行数 / 落库数 /
         去重 / 失败 / 报错原因。sync_logs 本已记全,此前未对宿主暴露 —— 当时的同步响应
         没留存也能事后查"哪批错了、错在哪"。
      
      文档补 §7「对接自测」:预检用法与通过标准(failed=0 且 mappingMisses/suspectFields=0)、
      推送记录、可用面(API 文档 /api/docs、宿主自助页、队列面板)、常见错误码速查。
      
      验证:本地实推 —— 预检 6 行→"若推会落 5"(1 噪音被过滤)、返回 payment/refund 样本、
      **患者数仍为 0 确认零落库**;push-logs 正确区分预检(dryRun=true)与正式批次。
      typecheck 干净,299 测试绿。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • refactor(friday): inline 列沿用宿主原表列名 + 契约去掉「推送范围」 · 96d37e38
      两条纪律收紧(宿主零改名、零行过滤):
      
      1) inline 列不再用 PAC 自造名,一律沿用宿主源列名:
         - customer_basic_info:phone/phone_relationship → contacts_tel/relationship(customer_contacts 原名)
         - med_emr_info.diag[]:stdCode → std_code(std_diag 原名)
         - class_name / organization_id / plan_name 本就是宿主原名,不变
         宿主无需为 PAC 做任何字段改名。
      
      2) 文档删除各表「推送范围」:med_emr_info 那条 status∈{3,4} 是残留的宿主侧过滤,
         而 manifest E.0 已在 PAC 侧排草稿 —— 重复且违背单一真理源。改为 §1 统一声明
         「整表照推,宿主不做任何行过滤;status/金额切分全部由 PAC 完成」。
      
      验证:元和王永 dry-run 与改前逐项一致(patients=16472 txns=117718 failed=0;
      phone/K04/modality/退费 190+308 全对);typecheck 干净,299 测试绿。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  2. 23 Jul, 2026 10 commits
    • Merge branch 'feat/friday-ingestion' into test · 1a924b2d
      # Conflicts:
      #	apps/pac-docs/content/docs/integration/friday-push-payload.mdx
      luoqi committed
    • feat(friday): refund_item 身份 inline(宿主反填真 patient_id)+ drift 误报修复 · ab9ee7d5
      push 自洽收尾(判据:PAC 已有主体的不 inline,值不可信的才 inline):
      - refund_item:spec.patient_id 部分品牌是诊所本地垃圾 id、不可信 → 宿主导出时 JOIN 结算头
        把真 patient_id 反填进 spec(inline);删 S.3.1 跨表 lookup,refund_item 单表 push 自洽。
        (patient_relation 不动:referee 本身是 PAC 患者实体,referee_sex 可从自有实体解析,无需 inline)
      - drift 误报修复:refund_full/refund_item 共用 subjectType='refund' 但源表列集不同,
        detectRawColumnDrift 按「与本批列签名 Jaccard≥0.5」自选同源样本,排除跨源混列(消除 suspectFields 虚警)
      
      验证:元和王永 dry-run refund_item=308(样本 patientExternalId=659325 真 6 位 id,无 lookup)、
      refund_full=190、failed=0;299 测试绿、typecheck 干净。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(friday): 摄入契约收敛为 9 张自洽表 — 宿主 inline 自己的引用(全路径一致) · c4636e36
      manifest 共用于所有摄入路径(cold-import/file/pull/push/reparse),改它一处全生效。
      删 5 处跨表 lookup + 5 张源表,宿主导出时 within-host join 后 inline:
      - 私有字典(std_diag/std_check_class)→ diag[].stdCode / med_check.class_name(删 lookup + 源表)
      - 联系方式 1:N(customer_contacts)→ customer_basic_info.phone/phone_relationship(挑默认号)
      - 计划头(customer_treat_plan)→ 计划行 organization_id/plan_name(删 lookup + 头源表 + group)
      - 支付通道(settlement_modes)→ 不摄入(金额在结算头 net_receipts_this;payment 去掉 method)
      - incremental 清废弃表名(patient_settlement_refund/spec_refund)+ 补 patient_settlement_spec
      
      export.sh 同步改成自洽形态(within-host join;含 diag stdCode 跨库预取 map);
      image.yaml primary image_rows→med_check;payment.yaml 去 method。
      
      验证:元和王永 16472 患者 dry-run 逐资源计数与 inline 前**完全一致**
      (patient/diagnosis 4458/treatment 5781/image 6170/payment 20995/refund 190+308);
      中间表 41→33;typecheck 干净;全量 299 测试绿。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • docs(friday): push 契约收敛为 9 张自洽 source(宿主 inline 自己的引用) · 08c515b6
      确立职责边界:宿主推送前解析好自己的引用、inline 进相关行;PAC 只做跨宿主临床归一。
      14 → 9 个 source,删掉 5 张 lookup-only 源表:
      - std_diag / std_check_class(纯翻译字典)→ 宿主解析 stdCode/class_name inline 进 diag[]/med_check;
        省掉大表全量重推 + PAC 持久化字典存储(仅留 _shared 跨宿主临床字典)
      - customer_contacts(PAC 只要默认号+归属)→ inline phone/phone_relationship 进 customer_basic_info
      - settlement_modes(总值在结算头 net_receipts_this,24% 多通道拆付暂不需要)→ 不摄入
      - customer_treat_plan 头(PAC 只取诊所/方案名)→ inline organization_id/plan_name 进计划行
      
      结果:每张 source 自洽,增量推变更行天然成立,无跨表依赖/无新表/无 hydration。
      注:manifest + export.sh 的对应改动(删 lookup、宿主侧 join inline)随后跟进。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • docs(friday): 补 std_check_class 独立小节 + 字典改增量推 + 作用域澄清 · 4ca27401
      - std_check_class 此前仅在 med_check 末尾一行带过 → 补成与 std_diag 对齐的正式字段小节;
        §4 病历链 3→4 个 source(14 张源表全部有独立小节)
      - 两字典作用域澄清:按品牌维护、但 code(diag_code/class_code)全局唯一 → lookup 不需租户限定
      - 推送方式从「变更时全量重推」改为「按 code upsert、增量推变更行」(与其他表一致);
        规模更正:std_diag 生产多品牌合计 1.5万~3万+行(非测试库 4 品牌的 2739),全推确实浪费。
        依赖 PAC 侧持久化字典存储(落地中)
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(sync): traceRawSourceTable 支持 union 回溯 — 修 refund_full 在 push/reparse 静默跳过 · b19f0f3b
      union-primary 资源(refund_full_rows ← union[status4, status3负额])此前回溯不到源表
      (traceRawSourceTable 只认单 input/output),导致 push source=patient_settlement 时
      refund_full assembler 不入选、reparse 也跳过 → **整单退费在这两条路径静默丢财务数据**。
      
      修:union 各输入独立回溯,全部收敛到同一根表(两路都 → patient_settlement)才返回该根;
      发散到多根则停在 union 输出(维持旧行为不误判)。export 供单测。
      
      验证:108467 重推 patient_settlement → refund_full 落库(txn=1 新增,2 payment 幂等 dup);
      单测 6 绿(union 收敛/发散/单链/route/lookup);全量 299 绿无回归。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • docs(friday): push 契约对齐迁移后原表 + 冷导入/对账文档 · dc8add18
      - friday-push-payload:结算段从「宿主分流 4 source」改为「推 3 张原表、WHERE 全在 PAC」;
        删 patient_settlement_refund/spec_refund 两个已废弃 source;新增 spec.patient_id 不可信说明;
        source 计数 15→14
      - 新增 data-reconciliation:源  PAC 计数对账口径
      - ingestion / meta:配套更新
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(friday): 结算WHERE迁移单一真理源 + 冷导入网页通道 + 数据对账 · b3e3a6c2
      - 结算/病历 WHERE 从导出侧迁入 manifest transforms(单一真理源):宿主推原表全 status,
        PAC 侧 filter 切消费/退费/退费明细;transforms 新增数值算子 lt/lte/gt/gte
      - 退费明细身份纠正(S.3.1):spec.patient_id 部分品牌是诊所本地 id 不可信,
        改从结算头 settlement_id→patient_id 继承(元和王永实测 26421 行全不符)
      - 冷导入网页上传通道:上传 zip → yauzl 白名单解压 → 迷你 data 根(含 _shared)→
        队列 cold-import;宽容缺独立表 + 成组完整性校验 + dry-run 预检
      - 数据对账:日报复用摄入查询做「源  PAC 去重患者数」比对(clickhouse countDistinct)
      - 测试 18 绿:friday-settlement-where / filter-numeric / cold-import-extract / cold-import-groups
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(sync): 启动时回收僵尸同步锁,根治部署重启后 DW 滞后告警 · a91f8a37
      现象:测试服 2026-07-23 8:00 报「DW 数据滞后」,增量游标 33.7h 未推进。
      
      根因:sync_logs 用 partial UNIQUE(host_id) WHERE status='running' 作并发锁。
           部署 force-recreate 重启 service 容器时,正在跑的同步进程被杀,但那行
           running sync_log 来不及标终态 → 永久占住锁 → 之后每次 cron 增量都撞锁被
           skip → 游标不动。代码注释早写了"进程崩需依赖 stale 清理",但该清理从未实现。
      
      修复:SyncIncrementalSchedulerService.onModuleInit 注册 cron 前,先把 startedAt
           早于本进程启动时刻(PROCESS_STARTED_AT)的 running 行标 failed。
      
      判据安全性:sync 只在本 service 进程的 cron 回调、或一次性 CLI(跑完即退)里跑,
           两者都不可能比本进程启动得更早还活着。反过来,本进程启动后新建的 running
           (可能是并存的手动 CLI 真锁)用 lt 严格排除,绝不误清。
           幂等:标 failed 只释放锁,不动数据 —— 游标没推进,下次增量靠 48h 回看窗补齐。
      
      数据零丢失:回看窗默认 48h > 本次空档 33.7h,靠 source_event_id 幂等去重完整补回。
      
      测试:sync-lock-reap.spec 覆盖 回收/lt判据锁/空启动/失败不抛;295 全绿。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  3. 22 Jul, 2026 6 commits
    • feat(recall): 话术自报家门去人名 + 关闭机会闭环 + 执行表字段清理 · 1f20bae4
      三件事,因为改动在 plan.service / execution.service / types 里交织,合成一次提交。
      
      ## 1. fix:话术自报家门串名(线上 bug)
      
      现象:登录人是李倩,话术却写"我是本诊所的客服主管晋芳辰"。
      根因:登录客服姓名被烤进 LLM 生成的正文,而 plan_scripts 的 UNIQUE 是 plan_id
           (一个 plan 一份话术、不分人),召回池又是共享的 —— 谁先打开触发生成,
           姓名就固化给后面所有人看。线上 44 条话术 100% 中招,是必然不是偶发。
      
      解法:姓名不进 LLM,改「生成期占位 → 渲染期回填」
        - 新增 shared/agent-identity.ts 作单一源(占位符 + resolveScriptAgent + renderAgentIdentity)
        - 稳健/标准/深度三档 prompt 与 format.md 统一输出 【回访客服】(沿用 v8 立的
          `{}`=填空 / `【】`=原样保留 的约定,归第二类)
        - 四个出口渲染回填:详情聚合、strict Zod 详情、同步重生成、SSE 流式
        - DraftPlanScriptInput 去掉 agent 字段
      
      副作用(正向):input_hash 不再含人名 → 同一 plan 不同客服共享 AI 缓存;
      工单转派后名字自动跟着换,不用重生成。
      
      三档实跑验证模型都保留了占位符;浏览器验证同一份缓存对两个登录人显示各自姓名。
      
      ️ 存量 44 条仍是旧正文,需部署后逐条重生成(dist/cli/ai-gen-script.cli.js)。
      
      ## 2. refactor:删 plan_executions 两个死字段
      
        - invalid_reason  从没被写过(pac-web 零引用),读回路径也没带它
        - abandon_other   写得进没人读,且信息恒冗余 —— 放弃原因是固定复选框无自由文本,
                          该列只可能取到字面量"其他",而 abandon_reasons 已含 'other'
      
      生产 0 行受影响(执行总数 2,两列全空)。
      
      ## 3. feat:宿主回访模式的「关闭机会」闭环
      
      瑞尔侧回访在宿主系统做,PAC 只提供关闭入口做闭环。**刻意不引入新概念**:
      不加 closed outcome、不加 close_reasons 列、不加 plan 状态 —— 关闭就是
      outcome='abandoned' + abandon_reasons,跟 PAC 自带表单同一条路、同一列。
      分叉只在展示层(ABANDON_REASON_META.shownIn,两个面板各渲染各的子集)。
      
        - AbandonReason 扩成两种模式的并集;近义项不合并(两模式按 host 配置二选一、
          运行时不共存,同一 host 只出现自己那套 key,统计不会碎)
        - 关闭弹窗改多选无上限;「其他」说明写 notes,不另立列
        - 原因级抑制窗:各原因 suppressDays 取 max(每个原因独立成立,取最严的);
          未配的沿用 outcome 默认 60d,行为跟改造前一致
        - 「识别不准确」不再双写 recall_feedback(该表留给拇指控件那条路)
        - outcome-form 的中文硬编码数组 + 中译映射表删除,key/label 改为同源
          (那张表里「已转介他人→treated_elsewhere」是语义错位)
      
      ## 文档
      
      新增「取数说明」(pac-docs)—— 给 DW 团队自助查生产 PG:召回池筛选条件、
      关闭操作落表、数据范围过滤(品牌/诊所/时间)、字段字典、常用 SQL。
      
      291 tests passed;service + web 双端 typecheck 干净。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • docs(ops): 重算对照表去掉「清简报缓存」列 — 它不是重算,降级为表下注记 · 2e31eeb7
      清缓存与 reparse/persona/plan 不是一类动作(不重算、秒级、按需重生成),
      放进同一张表会误导"改 prompt 也要跑重算"。改为表下引用块说明。
      
      顺带修补编辑残留:表格首行多一列、③ 代码块缺收尾 ```。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • docs(ops): 部署速查手册 — 本地/测试/生产三环境操作指南 · 60b0b705
      面向"自己动手部署"的 runbook,不重复 deployment.mdx 的架构介绍,直接给命令:
      - 三环境速查表(位置/目录/分支/DB/部署方式)+ 分支规约
      - 本地开发(docker 基础设施 + 原生跑应用)+ 4 个常见问题
      - 测试/生产部署命令(生产强调 COMPOSE_MANAGED=1 与 source .env)
      - 部署后要不要重算的对照表 + 固定顺序(reparse→persona→plan→清缓存)+ 耗时参考
      - 定向补数据(PAC_COHORT_ONLY_PATIENT @file)参数与分批阈值
      - 失败处理/回滚、7 个踩过的坑(含 pkill 自杀、ClickHouse 256KB 上限)、日常巡检
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
  4. 21 Jul, 2026 12 commits
    • merge: feat/return-visit-entry → main(宿主回访模式 + 关闭机会闭环) · 9d063987
      # Conflicts:
      #	apps/pac-service/tests/recall-suppression.spec.ts
      luoqi committed
    • fix(sync): 定向重摄不推进增量游标 + 大名单走文件(污染修复前置) · 736cc610
      背景:DW 病历表 patient_id 错位污染(高逸铭案例),需按名单重摄 3 万患者。
      沿用现有 PAC_COHORT_ONLY_PATIENT(cohort 收窄)时发现两个阻塞问题:
      
      1)  静默丢数据:只要 manifest 配了 incremental.per_query(jvs-dw 配了),
         cold-import 任何模式都写 cursor_after=run_start。定向轮只覆盖名单内患者,
         却推进全局水位 → 名单外患者在本轮期间的 DW 新写入被永久埋在游标下方。
         (与本次排查发现的"12,595 个患者符合 cohort 却从未摄入"是同类机制)
         修:定向模式下 cursor 保持上次水位不推进,daily 增量照常从原点接力。
      
      2) 大名单 E2BIG:3 万个 id 的逗号串约 275KB > 环境变量单值 128KB(MAX_ARG_STRLEN)。
         修:支持 PAC_COHORT_ONLY_PATIENT=@/path/to/file(每行一个 id);逗号列表照旧兼容。
      
      顺带:定向生效时打印命中患者数,便于核对名单规模。
      测试 7 例:空/空白不误判定向、逗号列表、@file(含 3 万行)、文件缺失须抛错不静默退化。
      
      生产验证(高逸铭组 7 人小批):删除→重摄后
        高逸铭 62 事实/6 诊所 → 21/1(上海时代),别人的数据清光
        刘晓旭 13 → 25、徐嘉悦 126 → 130,被吞的数据补回,诊所归属全部正确
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • merge: feat/incremental-sync-cadence → test(简报四问 + 消费口径实付 + 增量2h + 诊所重归属返池) · aac39357
      # Conflicts:
      #	apps/pac-service/tests/canonical-fact-layer.spec.ts
      luoqi committed
    • fix(plan): 归属漂移的已认领单顺带返池 — 防诊所硬隔离下的孤儿单 · 1242f896
      跟进诊所重归属(7b4f0d93)的补丁:诊所隔离是硬边界(list/detail 都强制
      targetClinicId ∈ scope.clinicIds),已认领单漂出原认领人 scope 后会变成
      谁都看不见的孤儿单(assigned 不进池、新店客服非 assignee、原客服出 scope)。
      
      - unchanged 就地改归属:若 status=assigned 且诊所变了 → 返池(assignee/assignedAt/recycleAt 清空)
      - 升版本路径:clinicMoved 时 assigned 不继承,新版本直接回池(与上同规则)
      - 两路径都记 log(哪单从哪店改到哪店),上线后可巡检
      - 测试 +3:两路径返池 + 归属未变时继承不误伤(12/12);顺带修 mock seed 缺 targetClinicId
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • fix(plan): 跟进诊所归属改为患者最后到诊诊所(与诊断诊所解耦) · 7b4f0d93
      业务口径(2026-07):运营按客户最后一次到诊的诊所做跟进分类,谁家最后来过就归谁家客服
      ——原口径 target_clinic_id = 触发诊断 fact 所在诊所,会把主诊所患者顺路在别处拍片/被诊断
      的人错分到那家只去过一次的诊所(反馈案例 BJ0D046171:150+次天使大厦、1次印象城影像AI诊断
      → 却进了印象城列表)。适用全部召回场景,不止影像 AI。
      
      - target_clinic_id = 患者最近一条 encounter/emr(到诊)所在诊所;无到诊记录兜底回退诊断诊所(不置空)
      - unchanged 分支(reason 未变)也就地改归属,不升版本 → 普通重算即可修复存量错分
      - 批量 prefetch 修 uuid bug:patient_id IN (text 参数) 对 uuid 列在 PG 报类型错,
        批量整体静默退回诊断诊所(修不动);改 = ANY(::uuid[])。本地 2470 患者全量重算后残留错分 0
      - 单刷路径 resolveLastVisitClinic 直查
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(sync): 增量摄入提频至每 2 小时 — cron 0,8-22/2 + 空轮短路 · 07bc2845
      方案 A 落地(jvs-dw):
      - incremental_cron: '15 0,8-22/2 * * *'(08:15~22:15 每 2h + 过天 00:15;
        cron 小时域无 24,'0' 即用户说的"24点"次日凌晨轮)
      - 空轮短路:transactionsWritten=0 且 patientsUpserted=0 → 跳过 persona/org-tree/plan,
        空轮成本收敛到每表一条 cursor 探查(秒级)。条件保守:患者主档变化不产事务但进画像,不跳。
      
      背景:DW 当前每日 08:00 一批(实测 13:56 时三表 updated_date 仍停昨日 23 点),
      日内轮询换来的是 ① DW 迟到/失败当天自愈(原等 24h)② 手动补摄自动收口
      ③ 瑞尔 DW 计划提频 2h,PAC 先就绪。防重前提已具备:并发锁 + cursor 幂等 + 同行去重。
      计划重算链路不动(空轮短路后不会被空跑)。
      
      验证:cron 库实测 next 14 次触发序列正确;manifest 解析 auto_sync/cron 正常;tsc 干净。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/payment): 消费口径切实付 — amount=实付,应收降级 receivableAmount · 6dd7aefa
      业务定调(张雪反馈):一线关注真金白银,应收对免费套餐/团购客严重虚高
      (胡新丽:应收¥1,270 实付¥0;张弦:应收¥77,795 实付¥57,887)。
      
      - jvs-dw payment.yaml:amount settlement_money(实付);friday:net_receipts_this
      - 新增 receivableAmount(应收)辅助字段:canonical schema 显式声明 + moneyFields
        登记同口径归一 + payment_record.content.receivable_cents
      - discount-anchor 折扣率分母换 receivable_cents(旧事实 fallback amount_cents)
      - 顺带修掉两个隐藏口径 bug:
        ① refund 一直是实收冲减 → 原 LTV 实为"应收收入−实收退款"混口径,现自洽
        ② 储值客户双计:充值(实付)计一次 + 扣卡消费应收又计一次,现扣卡单实付0不再重复
      
      下游自动切换:profile.ltv(UI 累计消费)/ rfm.M 值 / lifecycle 消费档 → 实付口径。
      金额守恒已对账(张弦 mode 表 17 行:实付57,887/应收77,795,无多通道重复计)。
      
      上线步骤:部署后跑 reparse(rawPayload 完整保留源列,无需重拉 DW)→ persona 重算。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • feat(ai/brief): 召回简报三问→四问 — 复查切入建议(事实锚定,防编造) · ae0b29c0
      第④问「以什么切入」:打电话的低门槛开口台阶,依据按优先级取第一个可用档:
      ① 医生计划/建议/医嘱(最优先,统一 verbatim 原话属性):
         - 治疗计划 subtype 原话(如「待22萌出后择期早期矫治」)
         - 医生建议 recommendation_record.name 原文
         - 医嘱 emr_record.doctor_advice(2.2万条真实医嘱;≥6字过滤"常嘱"水词,取最近2条)
         规则:必须优先引用「」原话要点(过长精简保留医嘱词),别只报大类;
         通用套话(定期复查不适随诊)视同无原话降档;多条挑与召回原因最相关的一条
      ② 洁牙/检查锚点(①为空才用):最近一次 preventive/periodontic 距今,
         超6个月才可引用时间,不足视同没有
      ③ 通用复查邀约(不带时间数字);严禁编"该洁牙了/上次洁牙已X个月"
      
      防重复:同一天数整句只出现一次;锚点与召回原因"拖了X"同源时切入不再报数。
      字数放宽 ≤65 最佳/90 上限;兜底模板同优先级;prompt 版本 2026-07-21-d。
      
      注:已缓存旧简报不自动重生成,上线时按 type=recall_brief 清一遍缓存即可。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • docs(integration): 推送契约去掉存量快照数字(随时间失真,不入契约) · a9d230cb
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
    • merge: refactor/assembler-shared-dict → main(FRIDAY 摄入:共享字典分层 + 推送契约 +… · 86f5028f
      merge: refactor/assembler-shared-dict → main(FRIDAY 摄入:共享字典分层 + 推送契约 + 关系边/咨询/退费三轨 + export cohort 过滤)
      
      # Conflicts:
      #	apps/pac-service/tests/canonical-fact-layer.spec.ts
      luoqi committed
    • docs(integration): FRIDAY 推送数据契约上架文档站 — 自 refactor/assembler-shared-dict 摘取 · e152489a
      形态 A 的 15 个 source 字段定义,放接入板块、紧随 Push 通道(它是推送通道的宿主侧具体化)。
      
      Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
      luoqi committed
  5. 20 Jul, 2026 5 commits