- 28 Aug, 2026 8 commits
-
-
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
原记「抛光隐形义齿只写在处置里」有误:抛光本身就是一条带牙位的治疗记录, 只是归在预防类目。真正的机制是该牙位上的抛光不被当作治疗。已修复。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed
-
- 27 Aug, 2026 13 commits
-
-
「抛光」在共享词表(treatment-category-core.yaml:189)精确映射为 preventive, 不在任何 resolver 集里 → 对缺口判定等同于没有。全库裸「抛光」2,499 条: 洁牙 590 / 充填 502 / 义齿仅 14 —— 归 preventive 是合理默认, 错的是对义齿抛光那 14 条也一视同仁。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
DW 原始 fact_emr_image_analysis_out 核实:tooth_loss=['25'], 而医生同次检查所见「24 牙位图缺失,口内未见缺牙间隙」「25 牙龈轻度萎缩」。 AI 的 impacted_tooth=['38'] 判对了,这条召回准确。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
同第 6 例(王志荣)的模式:修复体在位、无对应治疗记录、复诊重记诊断。 本次治疗是冠修复不是复查,今日上线的「种植复查」分支接不住。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
总院(6213d0a8)在 DW 里 appointment/settlement/emr 均为 0,只有 25.9 万条回访 —— 是集团的回访中心,不看诊。fact_client_out 的 last_dept_name/last_visit_time 会被 回访更新,所以主档显示"末诊总院 2026-06-18",实际是那天被回访了。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
原稿写"证据写在检查所见里,只读诊断码接不住",暗示补一层文本匹配就能解决。 核实原始数据后不成立:该条 examine 是一条记录、一个牙位串(全口 28 颗)、 一句混合描述(「下颌固位可,上颌固位稍差」),牙位字段不分上下颌 —— 关键词匹配只能全解或全不解,没法按颌切开。信息不在结构化字段里。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
原稿把她当残根误判个案归错了类。核实:27;45 是 2026-01-10 诊断的后天性 牙齿缺失,医生给了简单种植术和固定桥两套方案,27;45 上的实际治疗 0 条, 召回判得准(35 被 35-38 固定桥覆盖,引擎已正确排除)。 她身上的残根误判(16;18;28;47)确实也存在,但已随今日上线修复,不是本例要害。 要害是:真实的种植需求被一次"已治疗"误标压到 2126-07-30。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed
-
- 26 Aug, 2026 4 commits
-
-
新增「处理」列区分已修复与待定。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
一患者一行,后续个案追加。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
luoqi committed
-
【症状】容器 exitCode=139 自动重启,08-23~08-26 共 **11 次**,间隔约 4 小时。 页面无感(自动重启 + health 200),所以一直没人发现。 【根因】每轮增量同步结束会在**主进程内**跑一次 runAllForHost 全量召回。补摄把患者 灌到 111 万、召回池 54 万之后,selectHits 一次出 **96 万条命中**,连同 prefetch 的 latest/snoozed/persona 全在堆里 —— 而主进程启动命令 `node --enable-source-maps dist/main.js` **没带 max-old-space-size**, 取 Node 默认 **4144 MB**,装不下: Mark-Compact 3985.6 → 3953.5 MB (mu = 0.008) ← GC 已经回收不动 FATAL ERROR: Reached heap limit — JavaScript heap out of memory 【代价】那三天 `Result(ruier-grp)` 在日志里出现 **0 次** —— 一次完整的全量 plans 都没跑成过。每轮崩在 selectHits 之后、写库之前:✔ 增量导入成功落库✔ persona 重算成功落库✖ plans 整轮作废 数据不丢,丢的是"召回池的定时刷新"。另外每次崩溃留一个僵尸 sync 锁(重启后被回收), 且会带走同容器里正在跑的 docker exec —— bj04 第一次尝试就是这么死的(白跑 1h27m, 靠 batch.sh 的 3 次重试救回)。 【为什么现在才发现】补摄期间增量被 sync 并发锁挡下(00:15/08:15/10:15 全是 skip), 不跑全量 plans 就不崩 —— 实测连续 12 小时零崩溃。补摄一停,下一轮就会再崩。⚠ ️ 排查时别往"容器内存不够"上找:容器**没有内存上限**(HostConfig.Memory=0), 宿主 15.36G 一直有 7-11G 空闲。撑爆的是 V8 自己的堆,与宿主余量无关。⚠ ️ 同一容器里 CLI 那条路(cold-import / recompute-plans)一直是 8192, **两条路配置不一致**,崩的一直是没配的这条。这次补齐。⚠ ️ 宿主内存账写进注释了:8G(服务) + 8G(CLI) = 16G > 15.36G。实测两者不会同时顶到 上限(max-old-space 是天花板不是预留;实测峰值 服务 4G / CLI 4.9G),但⛔ 别手工并发跑全量 `recompute-plans`(它不走 sync 锁)。⚠ ️ **这是止血不是根治**:池子还在涨,8G 迟早也会到顶。根治是「增量之后别跑全量」—— recompute-plans 已经有 --clinics 收窄能力,同样的思路搬到 SyncIncrementalSchedulerService 那一处即可。另开分支做。 改动只有一行环境变量(docker-compose.prod.yml 的 pac-service),不动代码不动数据。 YAML 已校验;managed overlay 只合并 DATABASE_URL/REDIS_URL(非 !override),不影响本项。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 23 Aug, 2026 4 commits
-
-
luoqi committed
-
2026-08-23 测试环境实测:瑞泰患者点跳转,还是去了 arrail。 根因:上一版把 hostname 拆成标签、找**等于** `arrail` 的那一段。而测试环境的域名是 `arrail-test.5i5ya.com`,标签是 `arrail-test` —— 精确匹配匹配不上,一个字都没换。 各环境的域名前缀会带后缀(test / uat / …),枚举不完。 ⇒ 认这个**词**本身,不认它周围是什么:hostname 里出现的别的品牌片段整词替换 (arrail-test → rytime-test)。
⚠ ️ 仍然只换 hostname,⛔ 不碰路径和查询串(`?from=arrail` 是宿主自己的业务参数)。⚠ ️ 解不出 URL(相对路径 / 哨兵值 'postMessage')仍原样返回。 判据本身没问题 —— 同一时刻查过那个患者:sourceUnit='瑞泰',取值是对的, 只是替换规则太严。补回归用例(带环境后缀的域名),前端 15 条全绿。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
luoqi committed
-
jvs-dw 底下两个品牌各有一套门户:瑞尔 arrail.5i5ya.com / 瑞泰 rytime.5i5ya.com, 两边**只差域名这一段**,路径参数完全一样。而 host.actionUrls 是 host 级配置、只有一套 (现在配的是瑞尔那套)。于是瑞泰患者点「查看档案 / 病历 / 新建预约 / 回访」, 会被带到瑞尔门户 —— 那边查不到这个人。 ⇒ 打开的那一刻按**患者所属品牌**把域名换掉(lib/host-brand-url.ts)。 【为什么按患者判、不按登录人判】 登录人的品牌前端**拿不到**:/auth/session 下发的 sourceUnits 对诊所级用户是哨兵 `__pac_deny_no_brand__`(那条路按 clinicIds 过滤,不走品牌维)。2026-08-23 拿生产 三家诊所的 token 实测,全是这个值 —— 判不出品牌。而患者的 sourceUnit 是实打实的品牌名。 语义上也是患者对:这些链接要打开的就是**那个患者**在宿主里的页面。 【改了什么】 - 后端 plan-aggregate:患者 + **关系人**都下发 sourceUnit (关系人用他自己的,
⛔ 不拿本人的顶替 —— 亲属跨品牌时会跳错门户) - 新增 lib/host-brand-url.ts:applyBrandHost(url, sourceUnit) - resolveActionUrl / openHostAction 加可选的品牌参数;plan-detail-app 六个调用点全传上 【两个容易漏的点】 -🔴 **HOST_ORIGIN 也必须换** —— 它是 postMessage 的 targetOrigin。PAC 嵌在 rytime 里 而 targetOrigin 写着 arrail,浏览器**静默丢弃**这条消息:按钮点了没反应,控制台连个错都没有。 -⚠ ️ **只换域名,不碰路径和查询串**:`?from=arrail` 是宿主自己的业务参数,替换它属于越界。 URL 解不出来(相对路径 / 哨兵值 'postMessage')原样返回 —— 这条补丁不许把能用的链接变没。 【纪律】⛔ 别把它做成通用的"多品牌路由"。它成立的前提是"两个门户只差一个域名片段"。 真要支持多品牌,正解是 actionUrls 按品牌分组配置(host 管理页加一层),那时删掉这个文件。 【验证】 - 新增 host-brand-url.test.ts 14 条:替换 / 幂等 / 只换域名不碰路径查询串 / 哨兵值与 相对路径原样 / 未知品牌与空值不动 / **每个调用点都带上了品牌参数**(源码断言, 防的是"新加调用点时忘了传" —— 参数可选,漏了 tsc 不报) - 本地实测 /plans/:id/full:瑞泰患者 sourceUnit='瑞泰'、瑞尔患者 ='瑞尔' - 两端 tsc + 后端 1443 用例 + 前端 46 用例全绿 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 22 Aug, 2026 5 commits
-
-
luoqi committed
-
产品确认(2026-08-22):分母判据只有一条——**他有没有真打**。 所以「没进展」那一桶(未接通 / 待跟进 / 需要找医生 / 已发短信 / 改约) 照样进分母:打了没人接,那也是打了。
⚠ ️ 未接通往往占分母的大头,所以这个数读的是「触达 × 转化」合起来的效率,⛔ 不是"聊上了的人里谈成几成"。⛔ 别为了好看把 keep 组从分母里摘掉—— 那会变成另一个指标,而名字没变。 唯一不在分母里的是「没有结果」:那些单一条执行记录都没有,天然不在 plan_executions 里,不需要额外排除。 代码一行没动,只是把这条决定写进 schema 注释 + 加一条回归(分母 SQL 里 出现 outcome 就红)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
主管走查第二轮: ① 「还没收尾」说明补「(含已经超期的)」 到期不再自动回池(回收器 2026-08-19 删了),过了时限的单就压在客服手上, 批次靠 inHand>0 留在这一组。不写明的话主管会以为过了时限的都沉到历史里了, 而那恰恰是最该动手的一批。判据一个字没动。 ② 「约上」→「预约上」 「约上」仍有歧义:约好了既可以是约了预约、也可以是约了下次再联系, 而这两列分的正是这件事。 ③ 「完成」→「近 N 天已处置」 主管问「完成和已处置是同一个意思吗」—— 是。同一个判据(真的落了通话结果), 差的只是范围(本批 vs 近 N 天),而同一屏上叫了两个词。 而且「完成」听起来像"做成了",是这套口径一直在躲的读法。 ⇒ 统一叫「已处置」,并带上窗口前缀(预约成功率同)—— 不带的话切了 7天/30天,数字变了却看不出为什么。 ④ 预约成功率只出一个百分比 下面那行 rateNote(退回 N · 没动 M)删掉。
⛔ released / idle 没有消失,仍在响应里 —— 只是不该挤在这一列下面。 顺带拆掉一颗雷:AgentWorkloadRow.handled(= done + released)删了。 它和界面上的「已处置」(只算写了结果的)是两个意思,同一个词两个含义 —— 主管问的正是这个。服务端留局部量 touched 算「没动」,语义不变。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
四处口径/措辞,主管实测提的: ① 「还在跑的」→「还没收尾」 被读成"分配还没完成",像有个后台任务在跑。它说的是**批次**的生命周期。
⚠ ️ 没叫「时限内」:判据有两支,另一支(还有单压在客服手上)在时限过了 之后仍然成立,叫「时限内」会让过了期、单还压着的批次莫名待在这一组。 判据一个字没动。 ② 「约上」收窄成**只数转化新预约**,约定下次回访单列「约下次」 两者原来都算 close 组、合成一个数,靠小字解释「含约定下次回访」。 而两件事差着一整个台阶 —— 前者患者进了预约表、有个日子会来; 后者只是客服答应"那天再联系您",患者什么都没答应,单子还挂在他手上 (drivesStatus 一个 completed 一个 keep,状态机上就是两回事)。 主管看到「约上 6」会当成 6 个人要来了。 ③ 抽屉「通话成效」四个桶 → 五个 第一格「成功」拆成「约上 / 约下次」。要靠小字解释的数,本身就该拆开摆。 五个桶仍然穷尽(= 条数),回归里锁着。 ④ 「退回率」→「预约成功率」 分子 = 真的约上的(⛔ 不含约定下次回访 —— 算进来的话,一个只会往后拖的人 能拖出一条漂亮的成功率); 分母 = 「完成」他真打过的(⛔ 不是"已处置" —— 退回的单他压根没打, 摆进分母等于因为他退单而扣他的成功率)。⛔ 退回和没动没有消失,降级成后面那行小字:两者仍是主管调分配的输入(T7)。 - schema: brief/detail 加 bookedNext,outcomes 加 appointed/scheduledNext (success 保留为两者合计,⛔ 但界面不再单独摆它),workload row 加 booked - SQL 由「close 组 IN (...)」改成按 outcome 逐项 FILTER,DISTINCT ON 不动 - 助手解读规范同步(guides.ts),旧那句「outcomes.success 含约定下次回访」删掉 - 新增 booked-vs-scheduled-next.spec.ts(21 条) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
本批 5 人,主管说「这5人都分配给韩维」。界面回「已把整批铺给 1 位客服()」, 括号是空的:一条都没动,而助手接着说「这版已经改成 5 人都归韩维」。 三层: ① schema 判断本身就是错的 `batch + assign` 被 refine 判非法。而 batch 是这句话唯一自然的表达, 韩维就印在卡片下面那行「本批未分到」里。 改判:batch + balance 合法;只有 batch + owner 不行 —— 专属关系只有「待分配」那份数据带着(pending[].ownerUserId), 已排好的条目卡片上查不到它归谁专属。 ② 那条 refine 一次都没跑过(根因,不是 ① ) 两个改单工具的入参用的是**手写的 JSON Schema**(给模型看的那份), zod 那份从没进过运行时。两份 schema 各写各的, 写在没跑的那份里的约束只是注释。 ⇒ 加 `checkEditOps`:推给界面之前按 zod 校一遍,校不过整组退回给模型, 并说清哪一条错在哪(只说「参数错误」它会原样再发一遍)。 确认单、调整单同一条路。 ③ 界面把 batch 解成空名单 `return { planIds: [], label: '整批' }`,注释说"只有 set_expiry / set_benefit 走得到这里" —— 而那两件在上面就短路了(走批次级控件), 真正走到这一行的恰恰是 assign / remove。 改:整批 = 生效条目 + 还留在待分配里的,与顶栏「共 N 人」同一口径。 另加一道总闸:选中 0 人就到此为止 —— 下面每个分支都会 push 一句 "已如何如何",一条没动而主管读到的是成功。 顺带(同一类):owner 分不下去的两种原因分开报。 「卡片没带这条的专属关系」被报成「查不到在岗的专属客服」, 那是一句关于数据的断言,而卡片没有资格下这个断言。 - 整批移出仍然拦掉,但界面要吭声,⛔ 不许静默什么都不做 - 工具描述补 batch 那一格该怎么填;ASSISTANT_PROMPT_VERSION → 2026-08-22-a - 新增 sheet-edit-batch-assign.spec.ts(14 条),改判 mcp-clinic-scope 两条旧断言 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 21 Aug, 2026 6 commits
-
-
luoqi committed
-
luoqi committed
-
luoqi committed
-
luoqi committed
-
主管工作台此前没有任何使用数据可查,而 plan_event_logs 的 plan_id 是 NOT NULL, 工作台没有「单」这个实体可挂 —— 这是「没有表可以落」的由来。 不为工作台单独建表,建一张通用的:后面再有页面要看数据,加两个枚举值即可,不用迁移。 【一张表回答三类问题】 ① 谁在用 —— 按 (day, surface) 聚合;PV=条数,UV=distinct user_id ② 卡在哪一步 —— 同 surface 下 action 逐级衰减(打开 → 点格子 → 出确认单 → 确认) ③ 登录审计 —— surface='auth' 的行。PAC 此前**不记录登录**(没有 session/login 表, role 只活在 JWT 里),「谁何时以什么角色进来的」事后无从查证,这次补上。 【几条纪律,都写进了 docs/design/user-activities.md】 -⛔ 身份不由客户端给:userId/role/orgScope 一律从 JWT 取。上报体是 strictObject, 前端多传一个 userId 被 Zod 直接拒(已实测:code=10002 unrecognized_keys)。 -⛔ 页面 PV 不许塞进 plan_event_logs:那是「单的归属账本」,放空 plan_id 会让所有 按 plan 聚合的查询悄悄多算,且不报错。 -⛔ surface/action 在库里是 text 不是 PG enum:加埋点不该要迁移,且改枚举不会让 历史行变非法。role 同理存快照文本 → 读取侧 roles 是 string[] 而非 UserRole[], 否则会出现「写得进读不出」。 -⚠ ️ 埋点是旁路:前端不 await 不抛(微批 400ms + pagehide 冲刷),后端写失败只落日志。 埋点挂了不能让主管点不了确认。 【已埋 6 个点】 auth/token_exchange 宿主后端换票(无 Origin —— 服务器间调用) auth/code_exchange 浏览器换 code(有 Origin = 从哪个宿主系统进来);只记首次 supervisor_workbench/open 权限判定之后 —— 被弹走的客服不算一次访问 supervisor_workbench/matrix_cell {treatment,temperature,count};⛔ 不记 rect(屏幕坐标换分辨率就没意义) supervisor_workbench/proposal_shown 按 requestId 去重,只在 active 记 supervisor_workbench/assign_confirm 服务端返回之后 —— 点了但失败不算确认 【本地实测(pac-service:3101 + pac-web:3100 + 真浏览器)】 - 正常上报 accepted=2;伪造 userId 被拒 10002;无 token 拦在 10106(库里无脏行) - 真走一次换票+换 code:两条 auth 行落库,Origin 只出现在 code_exchange 上(符合预期) - 浏览器打开工作台 + 点格子:open/matrix_cell 落库,payload 正确 - stats 端点:PV/UV 正确;leader 调被 platform:manage 拦下(10107) 【过程中修掉的三个自己的坑】 - track 路径漏了 /pac/v1 前缀(api-client 用 new URL(path,base) 不自动补)→ 请求打到 /activities,envelope 里其实是 404 但 HTTP 仍 200(项目约定),网络面板一看才发现 - open 记了两条:React 18 StrictMode 双挂载 + canDispatch 翻转 → 加 ref 守卫 - session_id 恒空:access token 没有 jti(只有 refresh token 有)→ 改用 sub:iat, 同一次登录内所有行为共享,漏斗能串起来 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
生产实遇(杭州大厦 2026-08-20):23 位客服按默认值 23×15×3=1035,提案出了 537 条, 主管点确认只回一句「请求字段校验失败」,在卡片上改什么都没用。 根因不是业务,是**写法**:落库那条 UPDATE 用 `VALUES (…),(…),…`,每行 9 个 bind, 而 PG 单条语句上限 32,767 → 32767/9 ≈ 3,640 行就是硬墙。测试机实测 N=4000 直接报 `too many bind variables in prepared statement, expected maximum of 32767, received 36000`。 ── 改法 ────────────────────────────────────────────────── 每列打成一个数组,`unnest($1::uuid[], $2::text[], …)` —— **不管多少行永远 9 个 bind**。 墙没了,而且顺带更快(PG 不用再解析几千个占位符)。 测试机实测(事务内跑完回滚,一行数据没改): N=500 VALUES 444ms → unnest 74ms N=2000 VALUES 458ms → unnest 220ms N=4000 VALUES
❌ 炸 → unnest 433ms 忠实形状(13 列全写 + 同事务账本写入),线性到 5 万条: 500→0.25s 2千→0.6s 5千→1.4s 1万→2.7s 2万→5.6s 5万→13.6s(≈0.27ms/条) ── 事务保持不变 ────────────────────────────────────────── · 仍然是**一个事务**,全成或全不成 ——⛔ 没有拆成多批提交 · 并发安全的那三个 where 守卫(status='active' / assignee IS NULL / superseded_at IS NULL) 和 RETURNING 一字未动 —— 抢先认领的行照旧落不上、照旧出现在 skipped 里 · 超时改成**随条数放宽**:30 秒打底 + 每条 3ms,上限 180 秒。 一刀切 30 秒的话小批次白留 100 倍余量、大批次又不够。⛔ 不设无限:事务开着就持有行锁。 ── 一起改的 ────────────────────────────────────────────── · 调整在手的三条写路径(改派/改时限/移出)同样换 unnest · 那条 `fp.id IN (Prisma.join(...))` 换 `= ANY($1::uuid[])`(3 万个 id:521ms → 219ms) · 报错文案说清他能动哪个旋钮 —— 他手上只有时限和每天几通,没有"本批人数"那个输入框⚠ ️ 上限没敢一次拉满。再往上调之前要先量这三样(注释里列了):事务外那几个 Prisma `id:{in:[]}` 读(~32,000 个 bind 就炸)、推给浏览器的 6.3MB 载荷、卡片渲染 2 万行。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-