1. 15 Jul, 2026 4 commits
    • fix(web): 召回池搜索兼容中文输入法组字(避免拿拼音中间态误报"没有匹配") · 6a13bdb4
      现象:搜"李石明"时请求 keyword=李石ming(拼音"明"未上屏),ILIKE 不命中 → 空结果;
      搜单字"李"反而能出。根因:input onChange 在 IME 组字中就触发 300ms 防抖搜索,
      拿到中间态"李石ming",组字完成(→明)后没用最终中文重搜。
      修:composingRef 标记组字态,组字中 effect 跳过搜索;onCompositionEnd 解锁 +
      用最终中文重新触发防抖。仅前端。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(sync): cold-import 新增 --since/--months 按时间收窄存量摄入 cohort · 2359bbf7
      和 --clinics 同套路:只收窄"列哪些患者"(按 cohort.list_cursor_column=last_visit_time),
      被选中患者的全部历史仍全摄;可与 --clinics 叠加(AND)。不用改 manifest。
        --since=2026-01-01   今年有来诊的患者
        --months=12         最近 12 个月有来诊(CLI 换算成 cutoff 日期,--since 显式优先)
      DW 实测:全量 277万 → 今年 46万 / 近12月 75万 / 近24月 118万。
      last_visit_time 是 ISO 文本,字典序=时间序,直接串比较(避 NO_COMMON_TYPE)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(auth): 修运行期会话掉线("登录已过期")—— 治竞态 + 补埋点告警 · fcd77227
      根因(对接方反复报的"用着用着登录已过期"):
      - 竞态:bootstrap 的"过期前 60s 自动刷"定时器直接调 refreshToken(),绕过 api-client 的
        单飞锁 → 定时器与请求 401 兜底刷可能同时拿同一个一次性 refresh token 去换 → 一个成功
        轮换(del 旧 jti),另一个拿已作废 jti → 10103 revoked → 误清会话。
      - 盲区:api-client 运行期 clear() 路径无 client-diag 信标 → 服务端无日志、不触发企微告警。
      
      改动:
      - lib/auth-diag.ts:抽出共享诊断信标(bootstrap 与 api-client 共用)。
      - api-client.ts:两个 clear 点(refresh 被拒 / 受保护请求鉴权失败)补运行期掉线信标。
      - use-auth-bootstrap.ts:过期定时器改走共享单飞 tryRefresh,消除双刷竞态。
      - auth.service.ts:clientDiag 告警门控纳入 runtime-refresh-fail / runtime-auth-clear,
        alertSessionExpiry 增运行期根因文案与标题。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(persona): recompute-persona --clinics 大诊所分块查患者(避开 PG 32767 绑定参数上限) · 607d7f7f
      建国门补摄(~5万患者)时 prisma.patient.findMany({ id: { in: 50383 } }) 触发
      "too many bind variables, max 32767"。改为按 2万/块分批查再合并(排序保持分片稳定序)。
      小诊所行为不变;不传 --clinics 仍走原全量路径。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  2. 14 Jul, 2026 15 commits
    • feat(persona): recompute-persona 新增 --clinics=X,Y 按诊所收窄重算范围 · 3fb81481
      按诊所补摄(cold-import --clinics)后,只重算新灌入那批患者,免全 host 白扫。
      从 patient_transactions.clinic_id 反查患者集(与 cold-import --clinics 同口径),
      与 --concurrency / --shard 组合。plans 侧不需要:runAllForHost 已并发+预取,
      全量 ~5min 且幂等,直接全量跑即可。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(sync): cold-import 新增 --clinics=X,Y 按诊所收窄 cohort(不动 manifest SQL) · 953d321e
      需求:数仓加了新诊所数据,要挑几家追加摄入,不清空重摄、且摄该诊所患者的全部相关主体。
      - manifest schema:cohort 新增可选 clinic_scope {from_table, org_column}(被动声明)。
      - cold-import CLI:--clinics=X,Y;service 透传;listPatientPairs 注入诊所过滤。
        只收窄"列哪些患者"(挂在有 org 的事实表上求交集),不碰任何资源查询 →
        被选中患者的全部主体(跨所有诊所)照常摄入,幂等追加不清空、不误删。
      - jvs-dw manifest:一次性配 clinic_scope→fact_emr_treatment_out.organization_id。
      - 默认(不传 --clinics)全量行为完全不变。DW 实测:单家 27194 / 两家 60992 vs 全量 569 万,收窄正确。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(deploy): 预置托管 DB/Redis 切换(compose override + COMPOSE_MANAGED 开关) · 09211dc4
      走阿里云 RDS/Redis 时应用代码零改动,只需:
      - docker-compose.managed.yml:profile 排除自带 postgres/redis,pac-service/migrate 的
        DATABASE_URL/REDIS_URL 走 .env 托管端点,!reset/!override 清掉对自带 DB 的 depends_on。
      - deploy-prod.sh:COMPOSE_MANAGED=1 自动叠加该 override(默认自带模式不受影响)。
      - README:托管用法 + 两个坑(Redis 用标准版非集群、RDS 与应用同 VPC/同可用区)。
      compose config 已验证:托管模式服务清单无 postgres/redis,依赖/URL 均正确。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(auth): 会话失效告警只对真·嵌入/换票失败推,直接访问不再误报 · dda05086
      现象:直接浏览器打开 /plans(无 code、非嵌入)也推 [PAC CRITICAL],且根因误判成
      "code 到手即死"(其实压根没 code)。
      - 告警门控:仅 hadCode=true 或 isEmbedded=true 才推;直接非嵌入访问只落日志。
      - freshButDead 根因加 hadCode=true 前置(没 code 不判时间差);
        新增"嵌入内 URL 无 code"分支。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(deploy): env 补齐 AI 多提供方(Qwen/DashScope/Gemini),不止 DeepSeek · b8b6497c
      configuration.ts 早已读 QWEN_API_KEY/DASHSCOPE_API_KEY/GEMINI_API_KEY(生产四把 key 全在用),
      但 .env.example 只列了 DeepSeek,gen-env 也漏了 → 换新环境会漏配 qwen 话术/实时教练。
      - .env.example:补 Qwen(话术可选 provider)+ DashScope/Gemini(实时坐席教练,可选)。
      - gen-env.sh:DEEPSEEK+QWEN 必填(__FILL_ME__),DASHSCOPE/GEMINI 可选(留空不报占位)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • docs(deploy): 修正 host 凭据命令(rotate-secret)+ 补 nginx 反代步骤 · bc2fc77e
      - rotate-app-secret --host= → 实际 CLI 是 rotate-secret <name>(只换 appSecret,appId 不变)
      - compose 全绑 127.0.0.1,补「反向代理(nginx)必需」一节:TLS + /pac/→3101、/→3100,
        SSE 关缓冲、不禁 iframe 嵌入。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(deploy): gen-env 的 REDIS_URL 默认 compose 内置 redis:6379 · 65b2028d
      compose 模式下 pac-service 的 REDIS_URL 由 compose environment 覆盖为内网 redis:6379,
      之前留 __FILL_ME__ 会误导。改为默认此值(走托管 redis 传环境变量覆盖),
      让"仍需手填"列表只剩真正的外部凭据(DeepSeek / DW / 企微)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • docs(sync/jvs-dw): 更正"5 试点诊所"旧注释 → 全瑞尔按 DW 全量(逻辑无改) · 6f561707
      摄入刻意不限诊所:cohort 无诊所过滤;consult/return_visit 用
      `org IN (SELECT DISTINCT organization_id FROM fact_emr_treatment_out)`
      = EMR 出现的所有诊所(2026-07 实测 64 家,非 5 家)。原"5 试点"注释误导,
      易被当作 scope 越权 bug —— 全部改成"全瑞尔/随 DW 全量";头部 5 家 id 保留作参考,
      非过滤白名单。纯注释,一行逻辑未动。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(deploy): 新环境上线脚本化(gen-env / first-import)+ README 精简 + ASR 默认不部署 · c1659544
      - deploy/gen-env.sh:生成 .env —— JWT 密钥就地强随机、生产开关一把设、
        业务值从环境变量注入、缺失报 __FILL_ME__;防覆盖已有(FORCE=1)。
      - deploy/first-import.sh:数据首灌一条龙(seed → 存量 cold-import → 重算画像/计划),
        支持 DRY_RUN、ALLOW_PARTIAL 守卫。
      - deploy/README.md:精简为维护部署手册(日常/新环境/运维/回滚/监控 5 节),
        systemd 长流程折叠;明确代码根任意目录(脚本相对定位,不写死 /opt/pac)。
      - docker-compose.prod.yml:pac-asr 加 profile=asr → 默认不启(省 2G,可 --profile asr 恢复)。
      - deploy-prod.sh:默认 SERVICES 去掉 pac-asr,改 WITH_ASR=1 才带上。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(auth): code 由 TTL 控失效(去消费即删)+ 嵌入首开失效诊断/企微告警 · 6e42a683
      - exchange-code 由 getDel(消费一次即删)改为 get:code 在 60s TTL 内可重复换、
        返同一票,到点自然失效 → 根治宿主重载/双开的「登录已过期」(旧逻辑消费即删
        + 20s 宽限,超窗重载即 MISS)。去掉 grace 副本。
      - 新增 bootstrap 全分支诊断埋点(前端 client-diag 信标 → 服务端 [client-diag] 日志),
        带存储/环境探针 storageWritable/isEmbedded/hostOrigin/isReload/ua。
      - 会话失效(no-credentials/refresh-fail)推企微监控群(复用 AlertService,不去重):
        四类根因自动判(存储分区/重载复用/旧码/全新码到手即死),含 code 足迹
        (最新那张/已消费过/code 龄);全码只落服务端 MISS 日志(脱敏,不进群)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(embed): postMessage 信封补 type:"action" 字段,对齐与宿主约定的契约 · d5ee4979
      契约:{ source:"pac", type:"action", action, payload:{patientId} };此前代码漏了 type。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(plans): 患者卡片头右侧动作链接改顶对齐(items-start) · 2ab6aa38
      潜在治疗/原始档案链接原垂直居中于两行左栏(名字+年龄),改为与第一行(名字)顶对齐。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(plans): 患者卡片加「原始档案 →」插槽(潜在治疗右侧,VIEW_PATIENT 跳转) · 84787808
      患者卡片右上角「潜在治疗」后加「原始档案 →」—— 走宿主 VIEW_PATIENT 跳转,
      占位 {patientId}(userId)/{medicalRecordNumber}(fileNumber),与 EMR「原始病历」同一套
      (resolveActionUrl + target=_top);未配 VIEW_PATIENT 则不渲染。
      HOST_ACTION_META.VIEW_PATIENT 占位更新为 {patientId}+{medicalRecordNumber}。
      本地验证:配 /customerInfo?userId={patientId}&fileNumber={medicalRecordNumber} 后链接正常渲染。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(auth): exchange-code 加路径埋点(consumed/IDEMPOTENT/MISS)取真证据 · 3c6f3372
      首开「登录已过期」到底是「宿主首屏双消费」还是「code 过期」,现有日志判不了
      (BizError 不落日志)。加三条日志:
      - consumed(首次):正常首次换码
      - IDEMPOTENT 幂等命中:同一 code 宽限窗内被换第二次 = 双消费实锤(幂等修复已静默兜住)
      - MISS 失效:code 既不在池也无幂等副本 = 过期/未知(非双消费,幂等治不了)
      只记 code 前 8 位,不泄全码。对方下次一复现即可从生产日志判定根因。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(auth): exchange-code 幂等(短宽限窗),根治首开「登录已过期」 · c8592cdc
      根因:全新浏览器(无 localStorage)下,幸存 iframe 拿到的 code 已被消费(宿主预取/
      首屏双渲染中首个已消费但没落地/宿主自己先验一次)→ 第二次 exchange 得 10102 → 无票
      可退 → SessionExpired。90aefc7d 只挡了"首个成功落地"的情况,挡不住这种。
      
      改:exchangeCode 首次消费后落一份短期幂等副本(CODE_RESULT_PREFIX,EXCHANGE_GRACE_SECONDS=20s);
      同一 code 在宽限窗内重复换返回同一票,不报 10102;过窗即真失效,把可重放窗口压到最小。
      
      模拟宿主(4100)复现 + 验证:已消费 code + 清 localStorage 首开,修复前报「登录已过期」,
      修复后工作台正常加载;API 层同一 code 两次 exchange 均返回 code=0。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  3. 13 Jul, 2026 8 commits
    • feat(plans): 患者卡片手机号直接显示明文,去掉脱敏与查看图标 · c3a8f1a5
      详情页患者卡片:手机号从「脱敏 + 眼睛 toggle」改为直接显示 patient.phone 明文;
      移除 eye/eye-off 按钮及 reveal 状态。保留真/假角标 + 复制按钮。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(plans): EMR 跳转按钮文案改「原始病历 →」 · 2b11ccc5
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(plans): EMR 原文跳转(治疗历史「详情」槽)+ 通用 actionUrl 占位引擎;修左栏待认领换行 · e2bd5460
      - 左栏 PatientRow:诊所名变长后「待认领」被挤换行 → StatusPill 加 whitespace-nowrap、
        诊所名 min-w-0 truncate、电话定宽,布局稳住
      - 通用占位替换 lib/action-url.ts(fillActionUrl/resolveActionUrl):扫 {key} 从 ctx 取值
        URL 编码,缺失清空;替代原来写死的 {patientId} 单占位
      - 治疗历史卡「详情」槽:配了 VIEW_MEDICAL_RECORD 就打开宿主电子病历原文(占位
        {brandId}/{clinicId}/{medicalRecordNumber}/{patientId}),隐藏原 PAC 时间轴抽屉;
        未配置回退原详情。CREATE_APPOINTMENT 也切到通用引擎(同样支持这些 id 变量)
      - 后端 plan-aggregate 下发 patient.brandId(source_unit → host.orgAliases 反解品牌 GUID);
        前端补 patient.brandId / plan.targetClinicId 贯穿 raw→adapt→mock 类型链
      - HOST_ACTION_META.VIEW_MEDICAL_RECORD 占位更新为 brandId/clinicId/medicalRecordNumber/patientId
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(auth): 诊所名走服务端派生(host.clinicNames),显示不再依赖登录传 dictionary · d929aaee
      新增 Host.clinicNames(诊所 id→名),从摄入源派生;/auth/session 合并进
      dictionary.clinics 下发(宿主换票传的 dictionary.clinics 优先覆盖,服务端补齐其余)。
      前端 loadSession 合并 session.dictionary → visibleClinics 直接显示中文名,GUID 不再裸露。
      
      - manifest 加 clinic_directory: { table, id_field, name_field } 声明诊所名来源
      - refresh-clinic-names CLI:按 clinic_directory 从 DW(sql_source)或 CSV(文件源)
        SELECT DISTINCT 出 id→名,幂等 upsert host.clinicNames;可接 cron 定期刷
      - jvs-dw 源 = DW fact_emr_treatment_out.organization_name
      
      顺带发现 preset 把 dad2f04a 硬编码成"杭州高德",DW 真名是"欧美中心诊所"——
      再证手维护/登录传的名字会过时,派生才是单一真理源。
      
      本地验证:CLI 派生 5 家写入;合并逻辑(登录覆盖+服务端补齐)已测;typecheck 干净。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(auth): 数据权限支持契约品牌 GUID —— org 树品牌节点认别名,修复品牌主管 orgScope 空数据 · 0bf6a25c
      契约约定 orgScope 传宿主组织节点 id(品牌级=品牌 GUID),但 PAC org 树品牌节点
      id 用的是名字(source_unit=瑞尔/瑞泰),宿主传品牌 GUID(ba67e6cf…=瑞尔)对不上
      → 品牌制租户 fail-closed → 品牌主管(如岳丽丽 leader)登录后一条数据看不到。
      诊所层无此问题(节点 id 本就是诊所 GUID)。
      
      改动:
      - OrgNode 加 aliases[];expandOrgScope 按 id+aliases 建索引(名字/GUID 等价命中)
      - buildOrgTree 接受 brandAliases(品牌名→GUID),挂到品牌节点 aliases
      - Host 加 org_aliases(Json):PAC 节点 id→宿主契约 id 映射;deriveTree 读它传入
      - jvs-dw-preset 补品牌 GUID;seed 回填 jvs-dw.org_aliases(瑞尔=ba67e6cf/瑞泰=77057aed)
      - 模拟登录品牌级改传品牌 GUID(真正模拟契约,旧版传品牌名自洽掩盖了此 bug)
      不动 source_unit、不影响诊所层与 friday(source_unit 本就是 GUID)。
      
      本地验证:真实 OrgTreeService.expandScope(品牌 GUID ba67e6cf) → sourceUnits=[瑞尔]
      + 瑞尔全部诊所;品牌名/诊所 GUID 照常;typecheck 干净。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(sync/friday): 接入 appointment + encounter(到诊从预约拆,同 jvs-dw) · 7235b228
      FRIDAY appointment_base → PAC appointment(全部预约)+ encounter(已到诊接诊事件),
      与第一个宿主 jvs-dw 同款设计(一个预约源喂两张 canonical)。要点:
      - status 编码两家一致(1/2/3/4/5/6/8/9);status=10=草稿(查 friday-saas
        origin/hotfix AppointmentDataCommons draft==1 实锤)→ filter 丢弃
      - scheduledAt 用 appointment_date + appointment_start 经 derive 拼(datetime_str
        是区间且 14% 空,不用)
      - 已到诊(status 4/5/6 且 in_time 非空)拆 appointment_encounter_rows → encounter,
        startedAt=in_time;externalId=预约 id,将来 EMR(med_emr_info.appointmentId)据此回挂
      本地验证:809 appointment + 640 encounter,1449 txns/1449 facts,0 失败 0 drift;
      patient timeline 正确重建「预约→到诊」就诊史。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(sync): 新增 lookup transform + FRIDAY patient 首版接入 · 4f228575
      lookup operator(Layer A.5 第 8 个):按外键 join 子表、按 order_by 挑一条、
      把字段附加到主表——补上"1:N 子表归约成标量"的空(join_arrays 只产数组,
      其余 operator 全是单表内变换)。纯增量,不改任何现有 operator,不影响 jvs-dw。
      
      FRIDAY(第二宿主)patient 首版:方案 B(tenant=friday-market + 品牌 source_unit),
      宿主纯导原始表 customer_basic_info + customer_contacts,phone 由 lookup 挑默认号
      (is_default→本人→最早)填入。本地 450 患者/2 品牌真实落库验证:source_unit、
      挑号(is_default 胜出)、gender/病历号/时区全对。CSV 含 PII 不入 git。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(auth): 公开端点(auth:false)失败不再清会话,修复嵌入首屏「登录已过期」 · 90aefc7d
      api-client 清 session 分支未按 auth 门控,导致一次性 code 被重放/过期(10102)时
      误清刚建立的会话。宿主 iframe 首屏用同一 code 换两次时,第二次 10102 把第一次
      存好的有效会话清空 → SessionExpired。改为仅「带 token 的受保护请求」失败才清;
      公开端点失败交由 bootstrap / doRefresh 各自按语义决定,兜底 fall-through 生效。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  4. 09 Jul, 2026 2 commits
    • feat(plans): 左栏患者列表加「回访结果」角标(成功/不成功/保持) · 23e18d92
      有提交过执行记录的患者,在列表行左上角按最近一次 outcome 的分组打标签,
      分组/文案/配色复用 @pac/types EXECUTION_OUTCOME_META 单一真理源。
      列表接口批量带出每 plan 最近 outcome;详情页提交回访后左栏角标即时反映(不重拉)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(embed): 患者卡片「潜在治疗」/ 通话结果「回访」走 postMessage 通知宿主 · d98dcbd4
      - 新增两个 postMessage 动作 OPEN_POTENTIAL_TREATMENT / OPEN_RETURN_VISIT(入 HostActionKey
        + HOST_ACTION_META,admin 下拉可选);HOST_ORIGIN 作 postMessage targetOrigin,走 host 配置
        (会话下发的 actionUrls.HOST_ORIGIN),禁用 '*'
      - 患者卡片右上角「影像/档案」→「潜在治疗」;通话结果「拨打」→「回访」,固定就是这套(不判断 iframe)
      - 信封只含 { source:'pac', action, payload:{patientId} };patientId=external_id
      - postToHost 只在未配 HOST_ORIGIN 时提示;宿主侧监听校验 origin + source 后弹自己的组件
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  5. 08 Jul, 2026 1 commit
    • feat(action-urls): 会话下发 actionUrls + 「新建预约」按钮消费(配置驱动,不写死) · 1506e2fa
      - /auth/session 返回当前 host 的 actionUrls(AuthService.getActionUrls),所有角色可拿,
        不再依赖 admin 端点;SessionResponse 加 actionUrls 字段
      - 前端 loadSession 把 actionUrls 存进会话 user;「新建预约」读 actionUrls.CREATE_APPOINTMENT
        → 替换 {patientId}(=external_id)/{doctorId} → window.open(_top);未配置则提示去配
      - 宿主动作 URL 仍是宿主在 admin 配的数据,代码零硬编码
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  6. 07 Jul, 2026 3 commits
    • fix(embed): 去掉「新建预约」插槽写死的 localhost 兜底(demo 接线泄漏) · 6182b2f5
      上次把按钮从占位 toast 改成了 window.open('http://localhost:4100/appointment','_top'),
      硬编码 URL 随部署上了生产 —— 线上点击会 _top 跳到用户本机 localhost 死链。
      回退成原占位 toast;删除 action-urls.ts(带写死默认值的配置)。真实接入时 URL 由
      宿主 actionUrls / env 下发,代码不写死。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore: 入库会话前既有的工作区改动 · 9cdf04ef
      会话前已存在、本次一并提交:cold-import.service / sync-incremental scheduler /
      clinical-gap potential-treatment-gap SQL / cold-import manifest.schema /
      dw-lag-monitor / canonical-codes / ingestion·monitoring 文档 / jvs-dw manifest.yaml /
      package.json / pnpm-lock / .gitignore(next-env.d.ts)/ docs/friday-saas-mapping-temp。
      非本次会话产出,随此次部署一并落地。
      luoqi committed
    • feat(embed): 宿主嵌入体验 —— 隐藏 PAC 品牌/用户、会话失效兜底、动作走链接;org-tree 冷派生优化 · 1cf137ac
      - 嵌入检测(window.top):隐藏 PAC 品牌/用户/登出(IdentityCluster),会话失效显示
        「登录已过期+重试」而非 dev 快速登录(SessionExpired);模拟登录嵌入态不出现
      - 动作插槽(新建预约等):PAC 直接 window.open 宿主链接,不 iframe 嵌、不 postMessage、
        无 returnUrl;患者上下文经 query 传,回跳靠 history.back()/宿主自己的召回页
      - org-tree 派生:启动预热(OnApplicationBootstrap)+ loose index-scan 改写,
        冷派生 ~3.8s→~1ms,消除首个 scoped 用户登录尾延迟
      - docs: overview §4/§5 补 actionUrls 打开方式/回跳、sandbox 权限、嵌入隐藏、会话失效
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  7. 03 Jul, 2026 1 commit
    • feat(push): 存量/增量同一通道 —— 归一统一 / 乱序裁决 / stub 自建 / 并发闸 + 断流告警 · 33a826e7
      把 push(形态 A/C)打磨成宿主唯一接入通道,存量=按批重放的增量:
      
      摄入一致性(消除同源分叉):
      - 形态 C 复用 normalizeCanonical(金额元→分 / 无时区时间补 offset),含 envelope
        occurredAt/updatedAt —— 与形态 A/pull 同一转,宿主用自己系统的格式发,不做转换。
      - 业务事件先于患者主档到达 → dispatcher stub 自建空壳患者(对齐 form A),主档后到补全
        同一行,不双人;push 跨批顺序无关。
      
      幂等/乱序:
      - patient_facts 加 source_updated_at,fact-writer 按它裁决:更旧版本晚到 → stale_skipped
        跳过,不旧覆新。关掉"补推快照 vs 实时推送并发交错"的乱序窗口。
      
      体制/性能:
      - 限批 500(A 从 2000 对齐 C);persona 入队去抖(5min 桶去重,存量批次合并重算)。
      - 同 host 并发闸(信号量,PAC_PUSH_CONCURRENCY 默认 4;幂等+裁决保证并发语义=串行)。
      - 单飞锁索引收窄 WHERE direction='pull' —— 原索引把并发 push 批次拦成 P2002/500。
      
      异常/可观测:
      - 整批全失败 → 拒收 30802 + SyncLog FAILED;失败率/疑似改表 → 告警。
      - 未知 source → 10001(非 90000,避免宿主对永久错误无限重试)。
      - push 断流监控(超 26h 无 push 告警);form A 响应透出 mappingMisses/suspectFields 供自检。
      
      文档 channel-push 按宿主视角重写(subjectType 单一口径、金额时间统一"原样发 PAC 归一")。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  8. 02 Jul, 2026 2 commits
    • docs(integration): 登录授权补请求参数字段表 + 修 dictionary 必填/orgScope 拼写 · 984e05cc
      请求参数按 role 表那样逐字段列出(字段/类型/必填/说明),对接方更易懂。
      dictionary 标注为选填(schema 是 .optional(),不传换票仍成功,仅显示原始 id)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(callback): 执行结果回调(PAC → 宿主)+ 修 push 密钥存储 + 修凭据页刷新 · 4c8c70a7
      执行结果回调(opt-in,宿主配 callbackUrl 才投递):
      - Host 加 callbackUrl + callbackSecrets(存原文,PAC 签名用);迁移加两列。
      - ExecutionService.submit 提交后入 execution-callback 队列(6 次退避重试,死信可见)。
      - ExecutionCallbackService:查 execution→plan→patient 拼 payload,HMAC-SHA256("{ts}.{body}")
        签名(与 push 入站同配方),fetch+8s 超时,非 2xx/超时抛错交 BullMQ 重试;jobId=executionId 幂等。
      - 凭据页新增「执行结果回执」区:callbackUrl 输入 + Callback Secrets(add/rotate/grace/删),
        suffix 用 sha256 指纹不回显原文。对接文档同步。
      
      顺手修 push 密钥存储 bug:
      - create/rotate 原本存 scrypt 哈希,但 HmacVerifier 把它当原文做 HMAC key → 宿主拿明文、
        PAC 拿哈希,两边 key 不一致,push 验签永远过不了。改为存原文(对齐 seed 与 verifier 预期)。
      - 本地实测:轮换后用返回的明文签名 → 验签通过;错误签名 → 10106 拒绝。
      
      修凭据页「刷新即整页重载」:
      - useHostAdmin.refresh 一进来就切 loading,父组件把整页换成占位、卸载 HostAdminApp,
        连带把"仅显示一次"的明文 secret 弹窗刷没(来不及复制)。改 stale-while-revalidate:
        已有数据不切 loading,旧数据留屏后台重取。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
  9. 01 Jul, 2026 4 commits
    • docs(integration): 登录授权补凭证有效期表 + 链到接入总览 iframe 嵌入 · 29f27fe0
      code/accessToken/refreshToken 有效期不写清楚,接入方不知道过期后该调哪个接口;
      另外"用 code 拼 URL"具体怎么拼、嵌哪条路径此前只字未提,补链接到 §5。
      luoqi committed
    • perf(admin): GET /admin/host/self 卡 28s —— 补 2 个复合索引 · 3d02ac03
      getStats() 并发跑 10 条查询,其中两条缺覆盖索引:
      - patient_transactions.count(hostId, occurredAt>=X):(patient_id, occurred_at)
        索引带不动"只按时间不带 patient_id"的过滤,近乎全扫索引。线上冷执行 8.3s。
      - patient_facts.count(hostId, status='active'):(hostId, tenantId) 没 status、
        (patientId, status) 没 hostId,两个都覆盖不了。线上冷执行 18.9s。
        三列(非二列):tenant-guard 扩展对未带 tenantId 的查询自动注入 tenantId 过滤,
        二列索引配上这条自动追加的谓词后 planner 直接弃用索引退回全表扫,三列才稳定命中
        index-only scan。
      
      两条索引都用 CREATE INDEX CONCURRENTLY(表已 379万/371万行,避免锁表阻塞摄入写入)。
      线上手动建索引 + 验证已完成(index-only scan,查询从 8~19s 降到 <1s;端到端
      GET /admin/host/self 28s → 0.9s);IF NOT EXISTS 保证下次 `prisma migrate deploy`
      在 pac-migrate 里重跑时是空操作,只补 `_prisma_migrations` 记录。
      luoqi committed
    • perf(auth): 非 admin 首次登录仍撞 org-tree 冷派生 —— JIT 关闭 + 日志 + 同步后预热 · 5f063b0a
      上次修复(a3cbc827)只短路了空 orgScope(admin/集团级),非 admin(品牌/诊所级)
      用户第一次撞冷缓存仍全表扫。线上实测(379万行事务表)单次冷派生约 4.9s,JIT
      编译占近 20%。三处改:①派生查询关 JIT(SET LOCAL,不影响连接池其它请求)
      ②耗时打日志(>1s warn)方便下次卡顿时对时间戳 ③每日增量同步跑完主动
      forceRefresh 预热,把冷派生代价转给后台 cron,真实登录请求稳态下不再承担。
      TTL 6h→24h(配合每日预热,兜底而非决定性)。
      luoqi committed
    • perf(auth): orgScope 展开冷启动慢 —— 空 scope 短路 + 树缓存单飞/TTL · a3cbc827
      tenant-group 重构后每请求都派生 org 树(全扫 patient_transactions 数十万行)解析用户 scope,
      冷缓存时登录长时间卡(首次慢、之后正常)。修:
      - 空 orgScope(全集团用户,最常见)直接短路返回不限,不派生树(400ms→50ms)
      - 非空 scope 仍需树:getTree 加单飞(并发 miss 共享一次,防惊群)+ TTL 10min→6h(org 数据日更)
      
      Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
      luoqi committed