☰
协同过滤算法实战:从零搭建SpringBoot+Vue诊所管理系统
2026/10/21 18:13:29 网站建设 项目流程

最近我窝在电脑前做了一个私人诊所信息管理系统,核心卖点是把协同过滤算法塞进了业务里:后端SpringBoot,前端Vue,数据库MySQL,初始化好库后整个项目能直接跑起来。这个系统不是那种纯增删改查的管理软件,它会根据老患者的就诊历史,自动推荐合适的医生和常用药,帮助复诊患者更快完成挂号开方。文章我想系统梳理这套系统从设计到落地的完整过程,尤其是协同过滤算法在这类中小型医疗管理系统中的真实玩法、工程实现细节,以及最后跑通时踩过的那些坑。如果你正准备开发类似的门诊管理系统,或者刚接触协同过滤想找个业务场景练手,这篇内容应该能省下你不少时间。

1. 项目为什么这样做:需求与定位拆解

1.1 私人诊所的业务痛点与管理边界

我调研了好几家中小型私人诊所,发现它们的日常管理和综合医院完全不是一个量级,核心需求无非是患者建档、医生排班、挂号收费、门诊开方、药房发药和库存盘点。但普遍的问题是:复诊患者很多,医生对患者的记忆全靠纸质病历和脑子,药房库存靠肉眼估,营业数据靠晚上手工汇总。负责人其实没指望系统能搞多少高大上的统计报表,他们最迫切的需求是“别把患者信息搞丢,别把药算错,别排班排重”。

这就决定了系统边界不能做成大型HIS(医院信息系统),而是轻量、单机可部署、操作不能太复杂。因此功能模块我收敛为:系统登录与权限、患者档案管理、医生信息与排班、挂号预约、门诊病历与处方开立、收费结算、药房发药、库存管理、数据看板,外加一个特色模块“智能推荐助手”。智能推荐助手包含两个入口:一个是患者登录后可以看到“相似患者推荐的医生”,另一个是医生开方时可以看到基于历史处方的“常用药品搭配推荐”。这两个入口都用了协同过滤算法,这也是这个系统区别于普通管理源码的地方。

1.2 技术选型为什么是SpringBoot + Vue + MySQL

这套组合现在已经成为中小型管理系统的“标准套餐”,原因很实在:SpringBoot让后端开发效率高,内置Tomcat、RESTful支持好,打包成jar就能跑;Vue前端生态成熟,配合Element UI这类组件库可以快速搭出后台界面;MySQL免费、稳定,对于诊所这种日均千级记录的数据量完全够用。

在选型时我也纠结过要不要用微服务或者NoSQL,后来想通了:私人诊所系统就一个后台,根本没有必要把Redis、RabbitMQ、Nacos都拉进来。引入分布式组件虽然显得“高级”,但对于实际部署和后期运维都是负担。真正的落地逻辑应该是:技术服务于业务,而不是为了用新技术而创造新需求。最终架构就是单体SpringBoot应用 + Vue单页应用 + MySQL单库,通过RESTful接口交互。认证方面用JWT做无状态登录,密码通过BCrypt加密,整体满足诊所使用的安全需求。

1.3 协同过滤算法在这里扮演什么角色

初看“协同过滤”这个词,你会想到电商的“猜你喜欢”,但在这个系统里它不是搞营销,而是辅助医疗决策。私人诊所医生数量少,少则两三个,多则十来个,但是患者复诊比例高,很多都是街坊邻居。如果系统能根据“相似患者”的就诊记录推荐医生,患者就不用每次都问“我该挂谁的号”,诊所也能引导患者分流到合适的医生。

药品推荐场景更有价值:同一个诊断下,哪些药经常被搭配着开,老医生心里有数,年轻医生可能欠缺经验。协同过滤使用历史处方数据,把“诊断”作为用户,“药品”作为物品,药品共现频率作为矩阵值,找出诊断与药品之间的潜在关联,开方时给出参考,可以有效降低漏开、错开的比例。需要注意的是,这里的推荐只做参考,医生必须保留完全的手动选择权。算法是辅助工具,不能喧宾夺主,这是医疗场景项目必须坚持的底线。

2. 协同过滤算法的落地细节

2.1 推荐场景建模:用户和物品怎么定义

协同过滤算法经典的两种思路是User-based CF(基于用户的协同过滤)和Item-based CF(基于物品的协同过滤)。在诊所系统里,我没有死板套用电商模型,而是按场景重新定义了“用户”和“物品”。

第一个场景是“患者找医生”。我构建了一个“患者-医生就诊矩阵”,行是患者ID,列是医生ID,矩阵的值是该患者到该医生处就诊的次数。但单纯用次数不够平滑,患者可能只是感冒了随便挂个号,次数多不代表评价高。我引入了一个加权公式:矩阵值 = 就诊次数 × 0.6 + 最近一次就诊距今月数的衰减系数 × 0.2 + 病历完整度 × 0.2。病历完整度是指该就诊记录中是否填写了主诉、诊断、处方,用于过滤无效操作。这样矩阵值其实是一个“综合亲近度”的得分,而不是简单的计数值。

第二个场景是“药品搭配推荐”。这个我采用了Item-based CF的变体:构建“诊断-药品”共现矩阵,行是标准化后的诊断名称(比如“上呼吸道感染”),列是药品ID,值是该诊断下处方中出现该药品的次数。药品推荐的逻辑是:当医生选择了一个诊断,系统基于共现矩阵计算与该诊断最相似的诊断,再把这些相似诊断下高频使用的药品推荐出来;当医生已选了一个药品A,算法会找“包含药品A的处方”中经常搭配出现的药品B、C,作为联合用药提示。这样既照顾了诊断维度,又照顾了药品组合维度。

2.2 相似度计算选型:余弦还是皮尔逊

相似度计算是协同过滤的核心步骤。我对比了两种常见方式:余弦相似度和皮尔逊相关系数。公式上,余弦相似度计算两个向量方向上的相似,适合评分数据比较完整的情况;皮尔逊相关系数则先对向量做中心化处理,消除用户打分尺度差异的影响。

在诊所数据场景下,大多数患者只接触过少数几位医生,矩阵稀疏度超过85%。如果直接用标准余弦相似度,两个患者没有共同就诊记录时相似度就是0,推荐效果很差。所以我采用了“中心化后的余弦相似度”,其实就是皮尔逊相关系数的变体:取两个患者共同评分过的医生集合,计算各自的平均分,再用去均值后的向量做余弦计算。这样做的好处是,即使两个患者打分尺度不同,只要相对偏好一致,相似度也能体现出来。

核心代码里我封装了一个相似度工具类,输入是两个Map<Long, Double>类型的评分稀疏向量,输出是相似度值。如果共同评分的物品数小于2,直接返回0,避免小样本噪声。

public double pearsonSimilarity(Map<Long, Double> vectorA, Map<Long, Double> vectorB) { Set<Long> commonKeys = new HashSet<>(vectorA.keySet()); commonKeys.retainAll(vectorB.keySet()); if (commonKeys.size() < 2) { return 0.0; } double sumA = 0.0, sumB = 0.0; for (Long key : commonKeys) { sumA += vectorA.get(key); sumB += vectorB.get(key); } double avgA = sumA / commonKeys.size(); double avgB = sumB / commonKeys.size(); double numerator = 0.0, denomA = 0.0, denomB = 0.0; for (Long key : commonKeys) { double a = vectorA.get(key) - avgA; double b = vectorB.get(key) - avgB; numerator += a * b; denomA += a * a; denomB += b * b; } if (denomA == 0 || denomB == 0) { return 0.0; } return numerator / (Math.sqrt(denomA) * Math.sqrt(denomB)); }

如果你需要更快的计算速度,还可以用Apache Commons Math的库,但自己实现几十行就够了,避免引入额外依赖。

2.3 冷启动与数据稀疏:诊所场景的特殊对策

任何推荐系统都绕不开两个经典问题:冷启动和数据稀疏。对于新安装这个系统的诊所,数据库里根本没有历史处方,协同过滤一开始跑不出任何推荐。我调研后发现,诊所系统上线前往往已经有纸质处方记录,但数字化录入工作量大,所以系统设计了两种冷启动方案。

一种是在系统初始化时,由诊所负责人手动维护一个“诊断-默认药品”关系表,比如“上呼吸道感染”对应“感冒灵颗粒、阿莫西林胶囊、对乙酰氨基酚片”。当协同过滤没有足够数据时,直接走这张表兜底。另一种方案是基于内容属性计算相似度,比如医生推荐的替代实现是:根据医生的科室、职称、从业年限做规则匹配,把同科室、职称高的医生排在前面。

我在算法服务里加了一个开关recommend.mode,有三个值:CF(只用协同过滤)、RULE(只用规则冷启动)、MIX(两者加权混合)。默认是MIX,这样当患者历史记录少时,规则兜底,保证推荐列表不为空。数据积累到一定程度后,可以切到CF模式。这种设计在实际部署时非常管用,因为诊所老板大多没有耐心看“算法需要冷启动”的解释,他们只关心系统上线第一周能不能看到推荐效果。

2.4 推荐结果的可解释性与业务限制

协同过滤算法的输出如果只是个医生ID列表,医生和患者都不敢用。所以在设计推荐接口时,我要求每条推荐结果必须附带“推荐理由”。比如推荐某位医生的理由是“与您情况相似的患者中,有70%选择该医生”;推荐某种药品的理由是“治疗该诊断的处方中,有30%同时使用该药品”。理由字段在前端展示为一个浅灰色的气泡,点击可以看到相似患者数量、共现次数等详细数值。这一设计让推荐不再是黑盒,使用者可以判断推荐是否合理。

同时,推荐结果必须经过业务规则过滤:下架药品、停用医生、非当前排班时段内的医生都不能出现在结果里。这些过滤我放在算法推荐之后、返回前端之前的过滤管道里,算法本身不做业务判断,保持纯算法逻辑的独立性。为了可追溯,每次推荐请求都会记录推荐的输入矩阵大小、候选数量、过滤后数量、响应耗时,输出到日志文件,方便排查“为什么这个月推荐结果和上月不一样”。

3. 系统架构与核心实现

3.1 后端工程结构与数据库表设计

后端工程采用经典的分层结构,模块边界清晰,这对后续维护非常重要。顶层目录如下:

  • config:存放JWT拦截器、CORS配置、MyBatis配置。
  • controller:RESTful接口层,只负责参数接收和结果包装。
  • service:业务逻辑层,包含患者、医生、药品、处方、推荐等核心服务。
  • mapper:数据访问层,使用MyBatis的Mapper接口和XML文件。
  • entity:数据库实体类,与表结构一一对应。
  • dto:数据传输对象,用于接收前端请求参数和返回定制信息。
  • algorithm:协同过滤算法核心包,里面是矩阵构建、相似度计算、推荐引擎。
  • common:统一返回结果、异常处理、常量类。
  • util:工具类,如JWT工具、日期工具。

数据库我设计了14张表,核心的有:用户表、角色表、患者表、医生表、药品表、排班表、挂号表、门诊病历表、处方表、处方明细表、库存流水表、推荐配置表、推荐结果日志表。简要列出关键字段:

  • patient:id,name,gender,age,phone,medical_history,allergy_info,created_time
  • doctor:id,name,department,title,intro,available_flag
  • medicine:id,code,name,specification,factory,unit,stock_num,price,status
  • prescription:id,diagnosis_code,diagnosis_name,patient_id,doctor_id,create_time
  • prescription_item:id,prescription_id,medicine_id,quantity,dose,frequency
  • visit_record:id,patient_id,doctor_id,visit_time,diagnosis_id,score

其中prescription_item是药品推荐最核心的数据来源,visit_record用来构建医生推荐矩阵。所有表都加了创建时间和更新时间字段,方便后期排查数据同步和报表统计。

3.2 后端SpringBoot关键代码:登录鉴权与统一返回

系统使用JWT做身份认证,登录接口接收用户名和密码,校验通过后签发token。为了减少重复代码,我封装了统一返回类Result,格式固定为{ code: 200, message: "success", data: ... }。前端通过拦截器统一判断code,只要不是200就弹出错误提示,不用每个接口单独处理错误逻辑。

JWT拦截器是关键,我实现了Spring的HandlerInterceptor,在preHandle中从请求头取Authorization,解析token,校验签名和有效期,再把用户ID存入ThreadLocal,方便Controller获取当前操作人。对于登录接口、静态资源路径,则在WebMvcConfigurer里排除拦截。

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); } Long userId = JwtUtil.parseToken(token); if (userId == null) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"message\":\"登录已过期,请重新登录\"}"); return false; } UserContext.set(userId); return true; } }

使用ThreadLocal保存用户上下文时,一定要记得在afterCompletion中调用clear(),否则在高并发下会出现用户数据串号的问题。这是我实际开发中踩过的坑,后来加上finally清理才解决。

3.3 协同过滤推荐接口的实现细节

推荐接口路径设计为/api/recommend/doctors/{patientId}和/api/recommend/medicines/{prescriptionId}。以医生推荐为例,接口内部流程如下:

  1. 查询该患者的所有就诊记录,得到他评分过的医生列表。
  2. 查询所有患者的就诊记录,构建“患者-医生”矩阵。
  3. 调用协同过滤算法,计算与当前患者最相似的K个患者(K默认10)。
  4. 合并这K个患者的就诊医生集合,排除当前患者已经就诊过的医生。
  5. 对候选医生计算预测评分,预测评分=相似度加权平均。
  6. 用业务过滤器过滤停用医生、非排班医生。
  7. 按预测评分降序返回TopN,N默认5。

每一步我都在代码里打了log,方便追踪推荐效果。预测评分的计算公式是:

predictScore = sum(相似度 * 该患者对医生的评分) / sum(相似度绝对值)

这个公式是User-based CF的标准预测方法,但对相似度做绝对值归一化,避免负相似度导致评分逻辑混乱。实际效果中,推荐列表第一位的医生往往确实是同科室患者经常选择的,看起来不算离谱。

对于药品推荐,由于药品数量可能上百,我采用先计算“诊断相似度”的方式:以当前诊断向量为基准,计算与其他诊断的相似度,找到Top5相似诊断,把它们的高频药品集合合并,再按总频次排序。这样就算某个药品在当前诊断下从未出现过,只要在相似诊断下出现过,也能被推荐出来,扩大了召回范围。

3.4 Vue前端实现:页面布局与交互逻辑

前端我用的Vue2 + Element UI,因为这套技术栈对后端开发者最友好,文档多、组件全。页面结构采用经典的左侧菜单+右侧内容区布局,顶部是用户信息和退出按钮。菜单包括:工作台、患者管理、医生排班、挂号取号、门诊病历、处方开方、药房管理、药品库存、推荐配置。

为了统一管理API调用,我在src/utils/request.js中封装了Axios实例,设置基础URL、请求超时时间,并在请求拦截器里附加token,在响应拦截器里统一处理401跳转和错误提示。

service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => Promise.reject(error))

推荐模块的展示是前端的亮点。在患者详情页,右侧嵌了一个“猜你想找的医生”卡片,卡片上动态展示医生姓名、科室、职称,以及推荐理由。在门诊处方页,当医生选择诊断后,药品选择弹窗中会多一个“智能推荐”页签,展示算法推荐的药品列表,点击每行末尾的“+”按钮,即可把药品加入处方明细。整个交互没有让推荐信息干扰医生的正常操作流程,保持“你不点开,推荐就静静躺在那里”的原则。

前端跨域问题是联调时的老大难。我在vue.config.js中配置了开发代理:

devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端请求/api/...时,开发环境会自动代理到后端,不需要后端额外开启CORS。等前端打包部署后,由Nginx统一反向代理,也不会有跨域问题。

4. 数据库初始化与项目运行全流程

4.1 环境准备与版本匹配

这个项目对环境的依赖并不苛刻,但版本不匹配会浪费很多时间。我使用的版本组合是:JDK 1.8(221及以上)、Maven 3.6.3、Node.js 14.17、MySQL 5.7。如果你用JDK11或者JDK17,SpringBoot 2.3.0版本的兼容性也没问题,但某些老版本的Maven插件会报错,建议严格按下面列表安装:

  • JDK 1.8(或11)
  • Maven 3.6+
  • Node 14+(16也可,但不要用17以上,End of Life)
  • MySQL 5.7(8.0需要修改驱动配置,也能兼容)
  • 开发工具任意主流IDE即可,不影响运行。

数据库初始化文件放在源码根目录下的sql/init.sql,包含建库、建表、插入演示数据三步。演示数据包括10个患者、5个医生、30种药品、20条历史处方,以及80条处方明细。这些演示数据是我自己造的,完全虚构,唯一目的是让协同过滤算法能跑出结果。

4.2 初始化数据库与配置数据源

初始化数据库很简单,命令行执行:

mysql -u root -p < sql/init.sql

如果你的MySQL装在Docker里,也可以先把init.sql复制进容器再执行。执行完成后,库名是clinic_db,账号建议创建一个专用账号,不要直接用root,避免安全隐患。SQL文件里已经有创建用户的语句,默认账号clinic_user,密码Clinic@2024,连接权限仅限本机。

后端配置文件在src/main/resources/application.yml,关键配置如下:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/clinic_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: clinic_user password: Clinic@2024 driver-class-name: com.mysql.cj.jdbc.Driver redis: # 本项目未使用Redis,不需要配置 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true recommend: mode: MIX similar-user-count: 10 top-n: 5 max-candidate-size: 100

serverTimezone=Asia/Shanghai必须加上,否则MySQL 8.0驱动会报时区错误。map-underscore-to-camel-case开启后,数据库字段create_time可以自动映射到Java属性createTime,省去大量手写映射。

4.3 后端启动步骤与常见启动报错

后端启动过程就是Maven标准流程。进入项目根目录后,先执行依赖下载:

mvn clean install -DskipTests

然后启动:

mvn spring-boot:run

如果你已经打包好jar包,则用:

java -jar target/clinic-system-1.0.0.jar

常见启动报错有三种:一是端口被占用,把server.port换一个,或者杀掉占用进程;二是数据库连不上,报Communications link failure,检查账号密码、URL、MySQL服务是否启动;三是启动时找不到Mapper XML,检查mapper-locations路径是否和实际目录一致,我一开始就放错了路径,导致一直提示Invalid bound statement (not found)。

4.4 前端启动与联调配置

前端目录和源码后端平级,叫clinic-web。打开终端进入项目目录,安装依赖:

npm install

如果安装速度慢,使用镜像源,但注意不要使用任何非官方工具,直接换registry地址即可。依赖安装完成后,执行:

npm run dev

默认监听端口是9528,浏览器访问http://localhost:9528。登录页内置了测试账号:管理员账号admin,密码admin123;医生账号doctor,密码doctor123。前端环境变量文件.env.development中设置VUE_APP_BASE_URL = '/api',配合代理配置即可访问后端接口。

启动后建议先把患者、排班、药品等基础数据过一遍,点击“推荐配置”按钮切换推荐模式,看看三种模式下的推荐结果差异。只有在有一定就诊记录后,协同过滤模式的推荐结果才更有参考性。

5. 实际运行中的问题排查与避坑记

5.1 协同过滤推荐结果为空的排查路径

这是整个项目运行中最容易遇到的问题,没有之一。现象是推荐模块返回空列表,而日志没有报错。排查顺序可以按以下路径推进:

  • 第一步:检查数据库中visit_record和prescription_item表是否有数据。如果只有基础档案、没有业务流水,协同过滤矩阵为空,返回结果必然是空。演示库必须确保导入了20条处方和80条明细。
  • 第二步:检查当前患者是否有历史就诊记录。如果患者ID不存在矩阵中,算法找不到相似用户,返回空是正常逻辑。这种情况系统会自动切换规则推荐模式,如果规则表也是空的,那就需要先维护diagnosis_default_medicine表。
  • 第三步:检查计算相似度时是否出现了NaN。当分母为0时,0/0=NaN会被Jackson序列化为null,前端拿到null列表会显示为空。我后来在算法里统一加了判断,如果相似度为NaN,强制置为-1,不再参与候选排序。
  • 第四步:检查业务过滤器是否把所有候选都过滤掉了。比如候选医生本周没排班、候选药品全部下架,这属于配置问题,需要调整过滤规则。

我在日志里加了一条“推荐原因日志”,格式是[recommend] mode=CF, matrix=10x5, similar=5, candidates=12, filtered=3, result=9,一行日志就能看出问题出在哪一步,省去了很多猜谜的时间。

5.2 跨域与接口联调问题

开发环境使用代理就足够了,但有些同事习惯直接用axios.get('http://localhost:8080/api/...'),这时后端必须开启CORS。我最初在后端写了个CorsFilter,允许所有来源、所有请求头、所有方法,后来发现这样不太安全,就把允许的来源限定为前端开发地址:

registry.addMapping("/**") .allowedOrigins("http://localhost:9528") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600);

这里有一个坑:如果allowedOrigins配置了具体的地址,就不能再把allowCredentials(true)和"*"一起使用,否则前端会报The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'。解决方案是前端代理优先,后端CORS作为兜底。

5.3 数据隐私与合规注意事项

私人诊所里跑的是真实的患者健康信息,这部分数据一旦泄露,后果很严重。系统在设计层面做了几个约束:第一,所有患者手机号在列表中默认脱敏显示,只展示前三位和后四位;第二,密码通过BCrypt加密存储,绝对不允许明文;第三,操作日志表记录谁在什么时间查看过哪个患者档案;第四,推荐算法只使用脱敏后的患者ID和科室信息,不涉及姓名、身份证号等敏感字段。系统提供了一键“数据化名”功能,可以将演示库中的所有姓名替换为“患者A、患者B”这样的代号,方便做技术演示时保护数据隐私。

这里多说一句,医疗场景的推荐算法不需要也不应该读取患者的病历全文,更不需要保存体重、过敏史等敏感维度。在模型设计阶段就要有“数据最小化”意识,算法只取构建矩阵必须的字段:患者ID、医生ID、诊断ID、药品ID。如果后期想增加年龄、性别特征,也建议先在脱敏层做离散化处理,比如把年龄归入年龄段,而不是直接暴露精确出生日期。

5.4 如何扩展为定时预计算任务

当前推荐接口是实时计算的:每次请求都扫描历史表、构建矩阵、算相似度。在演示数据量(500条)下没什么压力,响应时间在100毫秒左右。但如果诊所用了两三年,历史数据达到几十万条,实时计算就会变慢。这时可以引入定时预计算方案:每天凌晨2点跑一次推荐任务,把结果存到recommend_result表,日间查询直接读表,响应时间可以降到10毫秒以内。

SpringBoot中可以用@Scheduled注解实现这个任务,在主类增加@EnableScheduling,然后在推荐服务里写一个定时方法:

@Component public class RecommendScheduler { @Autowired private RecommendService recommendService; @Scheduled(cron = "0 0 2 * * ?") public void refreshAllRecommendations() { recommendService.rebuildAllRecommendationCache(); } }

预计算结果存储时,要记录算法版本、数据快照时间、推荐结果列表的JSON串。前端调用接口时,如果推荐结果表里有当天的数据,就直接返回;如果没有,就触发实时计算。这种“先查缓存、后实时计算”的策略,兼顾了性能和数据新鲜度,也是我目前认为最适合中小诊所系统的推荐方案。

6. 最后再说几句

真正把协同过滤跑进诊所系统之后,我发现难点从来不在算法公式本身,而在于怎么让医生和患者愿意信任这个推荐。医疗这件事容错率太低,推荐错了不会只是“猜你喜欢失败”,而是可能影响治疗。所以我最后做了一个非常关键的开关:在所有推荐模块的右上角,加了一个“关闭智能推荐”的按钮。医生在开方界面可以一键隐藏整个推荐区域,完全不看算法建议。这个功能在真实场景中很受欢迎,因为并不是每个医生都喜欢被系统“指导”用药。

项目最开始的做法是我自己拍脑袋定的,后来和好几位诊所运营者聊天,才明白用户真正关心的是系统是否增加了他们的工作负担。如果推荐结果总是需要人工二次核实,那推荐不但没帮忙,反而添乱。因此我把推荐结果的最大数量控制在5条以内,且每条都给出了简短的推荐理由,让使用者花两秒钟就能判断合不合理。

如果你想在这个源码上继续扩展,建议优先做两个方向:一是把推荐理由做得更丰富,比如引入季节因素或者流行病趋势;二是增加医生反馈机制,当医生明确忽略某条推荐时,将这些负反馈记录加入矩阵修正,让推荐结果越来越贴近实际偏好。不过要注意,无论怎么扩展,都不要把算法当成决策者,它只能做决策辅助。如果你也正在折腾类似的项目,希望这篇博客帮你少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询