1. 18 Jul, 2026 7 commits
    • 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
  2. 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
  3. 15 Jul, 2026 15 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
    • fix(web): 生产(mock 禁用)不再弹 dev "快速登录"框,改提示走宿主 SSO · 3f0ac789
      之前 auth-gate 在"非嵌入+未登录"时强制弹 MockLoginDialog(预设瑞尔/瑞泰硬编码前端),
      真生产 PAC_ENABLE_MOCK_LOGIN=false 时框照弹、点了才 10107 报错,体验很怪。
      改:探测后端 GET /auth/mock-users(禁用返空)→ mock 禁用时显示 SessionExpired
      (请从宿主进入),不弹快速登录框;dev/试部署(mock 开)行为不变。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • chore(deploy): 新增 docker-compose.expose.yml —— 端口绑 0.0.0.0 供阿里云 SLB / 内网可达 · 94f211ba
      默认 prod 把 3100/3101/3102 绑 127.0.0.1(本机 nginx 反代)。走 SLB 时 SLB 节点在内网
      其它主机、需可达这些端口 → 用 !override 替换成 0.0.0.0 绑定。️ 需 ECS 安全组把这三个
      端口限制为仅 SLB/VPC 内网入站(禁 0.0.0.0/0)。老 prod(本机 nginx)不用此 override。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • fix(web): 召回池搜索改手动防抖,修中文搜索不发请求(上个 IME 修复的回归) · 00e0378b
      上个修复靠 onCompositionEnd 里 setSearch(最终值) 重触发 useEffect([search]) 防抖;
      但组字中 onChange 已把 search 设成最终值 → compositionEnd 再 setSearch 同值 = 不重渲染
      → effect 不重跑 → keyword 永不更新 → 中文搜索不发请求(列表停在全量)。
      改为手动防抖 scheduleKeyword:组字中(composingRef)onChange 不排;onCompositionEnd
      解锁后直接排一次(不依赖 setSearch 是否引起重渲染)。本地实测:输入"程"→ 请求
      keyword=%E7%A8%8B 发出、列表 236→1(程浩)。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • feat(cli): 新增 import-patient 单患者摄入+重算;AI 默认模型切 qwen;详情页有原始档案时隐藏假手机号 · 09b9dbb2
      - import-patient.cli:单患者精确摄入(importPatient SQL 注入 pid+brand)+ persona/plan 重算,
        补 runPullForPatient 对"未摄入新患者"的死结;cursor 隔离(resource=patient_refresh)不扰增量。
      - assistant / plan-detail:话术&对话默认模型 deepseek → qwen(qwen3.7-max),去掉 Gemini 选项(未配)。
      - plan-detail:配了宿主 VIEW_PATIENT(原始档案)时隐藏 PAC 侧手机号行——真号在宿主档案页,
        PAC 侧为造数假号,防客服误拨。
      
      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
      luoqi committed
    • 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
  4. 14 Jul, 2026 14 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