刘浩南 刘浩南-AI实战派 2026年9月8日 17:11
先把 HaoEMR 现在的技术结构放在这里。前端、后端、数据和算法都比较符合目前国内技术要求。架构这一块我让Codex和Deepseek harness相互争论并结合国内信创要求所得。技术方面在各位大佬面前实在是“班门弄斧”了🙏🏻
前端:一套工作台,兼容临床医生和执行或管理部门使用
Vue 3、TypeScript 和 Vite 负责页面与构建,Vue Router 管理路由,Pinia 保存共享状态,TanStack Vue Query 管理服务端数据的查询与刷新。OpenAPI 生成 TypeScript 契约,Zod 参与数据校验;D3 用于知识关系等可视化,Markdown-it 渲染知识正文。
门诊、急诊、住院有各自的页面,不影响患者、就诊、文书和岗位上下文,都能接得上。医生从队列进入病历,再打开结果或 EVA,系统都要知道当前正在处理哪个对象。界面可以提前校验输入,最终是否允许保存、签署和执行,由服务端决定。
后端:Java 21 + Spring Boot 4.1,服务按业务分模块
Spring Modulith 用来组织模块边界,JdbcClient 执行显式 SQL,事务负责把业务状态、审计和待投递事件一起保存。核心模块围绕患者与就诊、病历、医嘱与结果、诊疗执行、质量与病案、任务、配置、知识和医助展开。身份授权、患者上下文租约、幂等请求、并发版本检查贯穿这些模块。耗时任务交给后台 worker,外部系统通过连接器和投递记录接入。
数据:临床业务数据、历史版本、资产归档分别保存
PostgreSQL 保存业务数据,Flyway 管理数据库结构变化。主要关系可以顺着一次诊疗管理:
患者 → 就诊 → 病历与版本 → 诊断 / 医嘱 → 执行 / 结果 → 审签 / 更正 → 病案资产
其中,病历当前状态、历史正文、来源引用、签名摘要各有自己的记录;审计和 Outbox 记录谁做了什么,以及还有哪些消息需要投递。机构、岗位、用途和患者关系参与访问检查。
配置保存定义、发布版本与运行引用。知识保存文档、版本、切片、概念和关系。AI 保存运行、步骤、工具与模型调用、候选及后续采用关系。看板和科研再从相应范围的事实取数,保留队列快照、口径和授权记录。
扫描件等原始文件通过存储接口管理,数据库保存元数据和内容哈希。开发环境有文件系统实现,生产配置有 S3 适配实现;真实部署的存储连通、保留策略和恢复能力仍需单独验证。
算法与 AI:规则判断、资料检索、模型生成分开处理
| 处理什么 | 当前怎么做 | 研发交付什么 |
|---|---|---|
| 业务与质量规则 | 根据发布配置、字段条件、阈值和来源状态判断 | 允许的状态变化、质控发现、缺失项与待办 |
| 知识检索 | 知识中心按标题、编码和切片正文做关键词匹配;医助工具从任务抽取受支持的关键词,再按机构、版本、分类和有效期筛选 | 带版本、切片标识与哈希的来源片段 |
| 知识关系 | 从概念与关系记录中查询、展开节点 | 可继续查看正文与出处的关系视图 |
| 医助生成 | 按已发布组合顺序执行节点,加载版本化岗位技能skill,经受控工具tool取数,再通过模型网关调用模型 | 摘要、文书段落、质控意见或提醒等候选 |
| 输出与采用检查 | 检查返回结构、允许字段、来源及版本;采用时重新检查患者、就诊和目标文书 | 可审阅、可编辑、可追溯的业务输入 |
| 运行控制 | worker 租约、用量与时间预算、取消检查点、错误分类和运行记录 | 能定位到具体步骤的成功、失败或部分结果 |
当前检索主要还是关键词与条件过滤,检索页里的分值也不能直接当作临床相关性概率。向量检索、重排模型和更复杂的图推理,可以继续探索,后续继续优化。
目前接入模型服务主要是Deepseek。HaoEMR 自己负责资料怎么选、工具怎么用、结果怎么检查和采用,没有训练一套新的医学基础模型。58 个子医助共享这套运行机制,各自承担明确的任务角色。
HaoEMR 核心就是围绕同一份病历展开各种业务。从页面找到文书,查到保存的版本、当时引用的来源、质控结果,再看签署、更正和后续事件。
这套系统由 Vue 前端、Spring Boot 后端和 PostgreSQL 组成。我负责产品和技术判断,Codex、DeepSeek harness 参与实现。需求、原型、架构、契约、计划和验收材料,都留在项目里。
“一个人能不能用 AI 做复杂项目”,这个问题我反复想了26 天。
我发现:AI 今天改了一个模块,怎么保证没有破坏昨天已验收的模块?换一个会话,怎么继续?一个页面显示似乎功能都有了,真的不是静态demo?
这篇把我选的架构、SDD 怎么用,以及最贵的几次教训一起讲清楚。
1、先决定业务,做哪些功能,绝不能临场发挥
HaoEMR 采用模块化单体。患者与就诊、文书、医嘱、执行、任务、配置、知识和医助分别组织,但临床主链先留在同一个应用和事务边界里。
原因很简单,保存病历、更改状态、记录审计、登记后续事件,常常需要一起成功或一起失败。我不想在项目刚开始时,就额外处理一堆跨服务的一致性问题。
数据访问使用 JdbcClient 和显式 SQL,结构变化通过 Flyway 管理。由PostgreSQL 保存业务数据。
举个例子,已经签署的文书不能直接覆盖。前端把按钮禁用,只解决了界面这一个入口;接口和数据库同样要遵守这个状态机规则。状态变化是否合法、两个核对岗位能不能是同一个人、对象是否属于当前患者,也需要在相应服务和数据库约束中检查。
我希望 AI 每接一个任务,都在这些明确条件里工作。它可以把 SQL 写得更好,也可以补实现细节,不能为了让测试通过,把清晰的业务约束删了。
2、病历为什么要保留那么多“版本”
这套系统里,文书当前行、文书内容版本、来源版本和签署证据,各自解决不同的问题。
当前行的版本号用来处理并发。医生打开的是 v3,提交前别人已经保存成 v4,服务端就需要拒绝旧表单直接覆盖。
内容版本则保留当时的正文。签署证据绑定具体内容和哈希,后续更正通过新的记录表达,原来签过的那份内容仍可追溯。
版本页保留文书版本及对应状态。不同版本的签名和来源可以继续检查。
外部来源又有自己的时间。检验报告可能更正,引用它的病历不应被静默改写。因此,引用关系需要保留原来的来源版本,重新处理时再校验新旧关系。
这样做会多出一些对象和检查,但排查问题时很有用:可以分清是医生改了正文、外部报告更新了,还是前端拿着旧版本提交。
我觉得这是 EMR 架构里值得花精力的部分。后面换编辑器、换模型、换界面布局,这些事实关系仍然要成立。
3、一次操作,从“收到请求”到“真的完成”
项目的写操作围绕幂等、审计和事务事件来设计。
同一个请求重试,不能重复建立两份业务对象。业务修改和待投递事件放在同一事务里,避免数据已经保存、消息却没登记。投递后的状态和回执另行记录,出现失败可以重试和对账。
HaoEMR是面对的是一个长业务流程系统,在诊疗执行中,这个问题尤其明显。护士记录了执行,不代表所有下游都已经更新;本地登记了事件,也不代表外部系统已经接收。
因此任务中心不会随意把专业业务当作完成。任务可以被领取、委托或升级,但检查有没有复核、药物有没有核验、来源事项有没有处理,仍由相应业务对象决定。
集成中心把另一层状态拿出来:本地待投递、外部通道已接受、存在对账差异,是不同的事情。
集成页面展示连接器与对账差异。消息是否送达,还要继续核对投递与回执记录。
Outbox 不能保证外部医院系统永远在线。它解决的是:当前系统至少知道自己欠着哪些投递,失败发生在哪一段,恢复以后从哪里接着做。
4、配置的价值,在运行时就看得出来
表单设计器可以定义字段、分组、必填项、签署要求和术语映射。流程设计器表达节点、条件、责任和时限。规则与职责范围,则参与具体业务动作的检查。
表单字段与分组是有版本的配置对象,发布后由对应业务使用。
我把“草稿”“发布”和“运行实例”分开处理。运行记录需要保存采用的配置标识和版本,后面改了配置,仍能知道上一条业务为什么作出那样的决定。
能力包继续往上一层组织:某类机构或专科启用哪些模块,继承哪份配置,当前依赖是否满足,如何灰度和回退。
能力包展示模块组合、继承和启用状态。页面中的内部就绪度用于配置检查,不是医院评级结论。
这一层的目的是减少不同医院对核心代码的分叉。它也有边界:能力包不能把患者隔离、签署或审计规则覆盖掉。
配置自由度给得越大,版本约束和生效范围就越要具体。否则同样一段代码,昨天能解释的行为,今天就说不清了。
5、权限管理,要方便管理员
医院里的账户、人员、角色、岗位、院区、科室和临床资质,并不是同一个概念。
HaoEMR 把机构、人员账户、角色工作组、权限策略、主数据和后台任务放在系统管理中。业务配置另有入口,避免所有事情都归到一个无所不能的管理员下面。
实际访问时,还要检查当前机构、岗位、患者关系、用途和资源范围。需要紧急访问时,理由、时限与事后复核也应留下来。
权限策略可以按主体、资源、动作和条件检查。权限模拟与实际业务访问需要使用一致的判定规则。
这套基础后来直接影响了医疗 Agent 的设计:医助不能因为是 AI 就拿到更高权限的资料范围,它同样要带着上下文和授权调用工具。
6、SDD 要解决的是接续开发时的歧义
我在这里使用 SDD,主要是把需求、实现约定和验收条件写清楚,再交给 AI 开发。
PRD 描述使用者、流程和完成条件;原型说明页面和交互;HLD、LLD 解释模块、数据和技术选择;OpenAPI 约定请求响应;实施计划把当前要做的范围固定下来;测试报告保留实际结果。
有这些材料还不够,它们需要互相理解,就像我们团队内部开发联调一样。某个页面对应哪条需求,调用哪个接口,改变什么状态,最后怎么验收,应当能沿着材料查到。
我后来使用的任务边界:从用户动作开始,穿过页面、接口与数据,直到下游结果。
接口契约尤其适合做明确约定。前后端使用生成的制品,字段名、可空性和枚举放在一起管理。否则换一次会话,就可能多出一套相近但不相同的状态名称。
在不同模型配合,或者不同研发阶段,或者关联的模块开发,我都会让大模型交接工作,交接时,我会带上当前目标、开发原则、最近报告和下一步任务,并说明运行地址、构建和数据库。哪些东西已经做完、哪些只验证了一部分,也要写进去。
Codex 和 DeepSeek harness 并不会自动共享一套准确记忆。它们之间能接续,靠的是这些被保存在项目里的代码和文档,交接文档就特别重要了。
7、477 项测试通过,却只有 33 个原生页面demo
这是我最不想重来的一段经历。
8 月下旬codex做了一个长任务/goal,交接记录里,数据库已经有 132 个迁移,477 项自动化测试通过。但 194 条规划路由里,只有 33 个原生 Vue 页面,其余大量页面缺后端接口和数据库打通。
看到页面以后,我问过:花了一周,用了两套 AI,怎么还是几个 demo 级别的菜单?要不要推翻重来?
后来没有推翻架构。发现AI对“完成”的定义完全理解错了,根本不符合验收标准。
原来的任务太容易理解成“建表、服务、接口、测试”。每个任务都能交差,实际业务流程功能却没有真开发出来。调整以后,一条任务需要从浏览器里的操作出发,连接真实 API、保存状态,再检查后续处理。
数据中心很适合检验这件事。一个看板上的数字,要能说明从哪些事实计算、使用什么口径、什么时候生成,而不只是画几张卡片。
数据总览按来源展示集成、迁移、质量、设备和科研状态。
科研也是一样。项目、纳排标准、队列快照、数据申请和统计需要彼此关联。导出之前,还要经过相应授权,不能因为同在一个数据库里就全部放行。
8、我发现也给开发留了一个明确的停止条件
医院接口、认证、签名服务、设备和外部存储,不可能只靠本机代码验证完。
HaoEMR 为这类依赖保留了模拟接口工作台。按照约定的请求响应,检查成功、降级和不可用的处理,再把真实适配器接进来。模拟与真实通道需要在材料和页面上分清楚。
模拟接口用于接口契约和异常场景验证,页面明确标注合成环境。
收口时,我会把这一轮要完成的业务范围固定下来。缺真实医院环境的事项单列,已知问题记录影响,不能一边验收一边无限加入新需求。
AI 能一直干活,决定什么时候停仍然需要人。不然有时改bug改出无底洞。
这 26 天让我看到,个人借助 AI 已经能把一套复杂系统推进到相当具体的程度。但越往后,越需要自己判断:产品值不值得做,约束有没有生效,开发任务怎么拆解,测试不能是过场,哪些地方必须返工。
下一次再做,我会更早把第一条完整业务流程在浏览器里验收了。让它实际跑通,再扩第二条。这个顺序,比我再写一份更详细的计划有用。
对于复杂业务流程,复杂项目的开发,欢迎交流。
微信扫一扫赞赏作者
收录于刘浩南AI产品