养老院服务推荐系统开发记从需求梳理到前后端落地的完整复盘这两年养老信息化相关的项目越来越常见身边不少做Python开发的朋友都在接类似的单子或课题。去年我完整做了一个基于Vue Django的养老院服务推荐系统前后折腾了差不多两个月。今天不写教科书式的项目答辩稿就按我实际开发的顺序把这套系统从需求分析、技术选型、后端设计、推荐算法落地到前端联调的完整过程复盘一遍。我尽量把每个决策背后的理由讲清楚这样不管是准备课程设计、毕业设计还是想往智慧养老方向转型的开发者都能少走点弯路。这套系统的本质并不复杂养老院有老人、护工、护理套餐、康复服务、文化活动等多类资源老人和家属在选服务的时候往往一头雾水靠前台人工推荐效率极低而且容易出错。系统要做的就是把服务货架数字化把老人的身体状况和兴趣偏好变成可计算的标签再根据标签和历史行为数据给每位老人推荐最合适的服务组合。技术形态上就是典型的前后端分离Vue做管理端和老人端界面Python后端提供REST API数据库存放业务数据推荐模块以独立服务的形式解耦在业务逻辑之外。如果你正准备动手做类似系统我建议先别急着敲代码。这个项目最大的坑不在某个函数写不出来而在业务需求没理清就动手后面每改一个数据字段都要牵连一堆接口和页面。下面我就按实际推进节奏把每个阶段的思考和实现细节拆开说。1. 这个业务场景到底要解决什么问题1.1 养老院服务推荐的三大痛点我调研的时候跑过两家不同规模的养老机构发现服务推荐这个场景和电商推荐有本质差别。电商推荐是流量逻辑用户随便逛逛推荐错了最多损失一次点击养老院服务推荐则是强决策逻辑推荐错了直接影响老人的健康管理和家属的信任感。具体痛点集中在三块。第一是服务品类杂且非标准化。养老院的服务不是简单的商品护理套餐分自理、半自理、全护理三个等级康复项目又细分为肢体康复、认知训练、中医理疗等文化娱乐服务更是五花八门。同一个老人可能在多个维度都有需求人工排班推荐时很难综合判断。第二是决策链条长。下单的人不一定是使用的人很多时候是子女在帮父母挑选服务。子女关注服务质量和价格透明度老人关注舒适度和熟悉感所以系统不能只服务一个角色。第三是数据沉淀几乎为零。大部分养老院还在用纸质表格记录老人档案服务反馈更是停留在口头上。没有历史数据再好的推荐算法也无从谈起。1.2 角色划分决定系统边界做完痛点梳理我意识到这个系统的用户模型必须分三层设计不能学电商系统只做一个用户端。老人端是最终体验方。他们操作能力有限界面要大字、大按钮、少层级最好首页就能直接看到为您推荐的服务卡片。家属端是实际决策方。他们要看服务明细、价格清单、评估报告还要能在线咨询和预约。管理端是运营方。他们要维护服务项目、管理护工排班、查看推荐效果和订单数据。这个三层角色划分直接决定了后端的权限设计和前端的页面结构。后面开发中我做了一个小小的模块划分老人端另做简化版管理端和家属端因为操作密度高用完整的台式页面。如果你的时间有限可以先砍掉老人端专注家属端和管理端业务逻辑一样能闭环。1.3 一个典型的使用流程我用一个例子来说明系统跑起来之后是什么样子。某位家属注册登录后先填写老人的基本信息年龄、自理能力、既往病史、兴趣爱好。系统基于这些信息给老人打上糖尿病护理轻度失能等初始标签然后调用推荐模块生成一份服务推荐清单包括护理等级建议、适合的康复项目、食堂菜谱偏好等。家属可以浏览清单、对比价格、在线预约试住或单项体验服务。老人入住后护工定期在系统里记录服务反馈和健康数据这些行为数据又会反过来优化下一次推荐。这就是整个系统的业务闭环。理解了这个闭环你就知道数据表至少要包含哪些内容老人档案、服务项目、标签表、推荐结果表、订单表、反馈表缺一不可。2. 技术选型里的几个实际决策2.1 Django还是Flask不是二选一是看场景标题里同时出现了django和flask很多刚开始做这个项目的同学都会纠结到底用哪个。我的意见是这个项目场景下Django的主流派会稳妥得多。理由很直接。养老院服务推荐系统的实体关系复杂老人、家属、服务项目、订单、评价、推荐记录之间有一大堆外键关联Django的ORM帮你省掉大量手写SQL的功夫admin后台在开发期还能直接当数据管理工具用。Flask框架灵活、轻量适合接口数量少、业务逻辑独立的场景但要自己组装ORM、表单校验、分页、权限这些基础设施项目做到中后期很容易出现地基不齐的问题。当然Flask也不是不能用。如果你本身就是Flask的老手或者项目只做推荐接口Demo不涉及多少管理功能Flask的轻量反而让代码更清爽。我见过有人用Flask SQLAlchemy同样把这个系统做得很完整只是开发周期会比Django长一些。没有必要迷信框架适合你自己熟悉度才是关键。我自己选的是Django版本用的4.x LTS配合Django REST FrameworkDRF)来做API层。DRF自带的序列化器、视图集和权限控制在前后端分离的项目里几乎是标配能省掉很多重复工作。2.2 为什么前端选了Vue而非传统模板这个项目最早的技术方案里前端其实是Django自带的模板系统用Jinja2语法渲染页面。开发到第二周我就推翻了这个方案原因就一条推荐系统的界面交互太复杂了。前端需要实现首页推荐流、筛选面板、服务详情弹窗、预约表单、后台图表等一堆动态交互。用服务端模板渲染的话每换一次筛选条件都要刷新整个页面体验很糟糕。Vue的双向绑定和组件化正好解决这个问题推荐列表是一个组件筛选栏是一个组件弹窗详情又是一个组件数据通过接口异步获取页面局部刷新体验和用手机App差不多。Vue具体用哪个版本也有讲究。Vue 3 Vite是我目前的默认组合启动速度快组合式API写起来逻辑更集中。如果你电脑配置一般或者想少踩点生态兼容的坑Vue 2 Element UI也是一套非常成熟的方案。我的建议是看团队熟悉情况没有绝对的好坏。2.3 PyCharm在项目里的角色标题里特意提到pycharm说明很多同学是在PyCharm里开发这个项目的。我的经验是凡是涉及Python后端PyCharm Professional版值得一直用下来不要中途换编辑器。原因有三。一是它的Django支持很完善模型类里定义字段时右下角会自动提示迁移命令ORM查询也有语法高亮和自动补全。二是内置的REST Client可以直接调试接口不用开Postman改完代码马上在IDE里发请求看返回。三是数据库工具的集成直接可视化管理MySQL或SQLite里的表数据排查数据异常非常方便。不过我也要提醒一点PyCharm的前端支持只能算能用Vue的单文件组件语法高亮和补全远不如VSCode舒服。我的做法是PyCharm跑后端VSCode开着前端项目两个IDE各干各的。听起来麻烦但实际体验反而最顺畅。2.4 推荐算法用不用搞机器学习很多人在看到推荐系统四个字时第一反应就是上协同过滤、深度学习模型。我的建议是冷静一点。养老场景的数据量通常很小一家中等规模的养老院可能只有几百位老人、几十项服务这个量级下深度模型不仅跑不起来也没有足够的数据做训练。我把推荐模块设计成了三个由浅入深、逐级叠加的策略层级也是在工程里更务实的方案基于标签的内容推荐打底基于用户行为的协同过滤做进阶人工规则兜底冷启动。这个设计既能保证系统上线第一天就能推荐也能随着数据积累逐步提升精度学习成本也低。后面第4节我会展开讲具体实现。3. 后端核心设计把数据模型和接口规划想清楚3.1 数据表设计我踩过的坑和建议先讲讲我第一版数据模型犯的错。我当时想要快速跑通顺手就把推荐记录直接写进了服务订单表里表结构超级简陋后续统计推荐转化率的时候完全没法区分用户自主选择和系统推荐后点击。返工一次之后表结构才稳定成下面这套分享出来供参考。第一张核心表是老人档案表字段包含姓名、年龄、性别、身份证号、联系方式、入住时间、护理等级、疾病史、兴趣爱好等基础信息。这里要注意兴趣爱好建议用单独的关联表来存而不是在一行里用逗号拼接因为后续做标签匹配时结构化数据才能直接参与计算。第二张是服务项目表这是整个推荐的商品池。字段除了服务名称、分类、价格还必须有一个标签字段的关联表。比如康复理疗服务可能关联糖尿病偏瘫轻度失能三个标签文化手工课服务关联手工社交认知训练。这个关联关系是内容推荐的基础设计时宁可多拆几张表也不要图省事塞JSON字段。第三张是标签表这个设计很容易被忽略。推荐系统本质上是在人和服务之间搭桥桥就是标签。我把标签分为两大类身体状态类标签如半自理、高血压、轻度认知障碍和兴趣偏好类标签如棋牌、园艺、阅读、合唱。这两类标签在老人档案和服务项目上都要打推荐时的核心匹配就发生在两组标签的交集上。第四张是行为记录表记录用户对服务的点击、收藏、预约、评价等行为。注意行为类型字段要规范化点击和预约的权重差别很大后面算协同过滤时用得上。另外还有订单表、护工排班表、护理记录表这些属于标准的业务表按常规逻辑设计即可。整个数据模型设计下来我最大的体会是先想清楚系统要给谁用、要回答哪些问题再动手建表不然每改一次模型都牵连到前端一堆组件和接口返工成本极高。3.2 REST API怎么规划才不返工API设计上我按照资源拆出了几个主要模块。老人管理模块提供/elder/、/elder/{id}/接口包括老人档案的增删改查和标签列表的读写。服务管理模块提供/service/、/service/{id}/接口服务项目维护、上下架、标签维护都在这里。推荐模块有两个接口一个是/recommend/{elder_id}/返回针对某位老人的推荐服务列表带推荐理由一个是/recommend/feedback/用于前端上报推荐结果的点击和转化情况。这个反馈接口非常重要没有它推荐效果就是一笔糊涂账。订单模块提供/order/接口创建、取消、查询预约单。老人端的首页个人信息接口独立出来一个/user/profile/一次请求返回老人档案、推荐列表和待办事项减少小屏设备的请求次数。这里给用Django的同学几个DRF使用的具体建议。视图集用ModelViewSet可以少写大量样板代码但重写create和update方法时要小心很多业务逻辑比如订单号生成、状态流转都需要在序列化器层拦截处理别把所有逻辑堆到视图里。权限控制用Django自带的Group和Permission做粗粒度控制再用自定义permission类做细粒度控制比如家属只能看到自己绑定老人的数据。如果你用的是Flask建议至少用Flask-RESTful或蓝图来组织接口不然项目一旦超过二十个接口就很容易变成一个巨型路由文件。接口返回值格式统一成{code: 0, data: {}, msg: success}前端也可以少写很多兼容逻辑。3.3 用SQLite还是MySQL这个项目前期开发阶段用SQLite完全够了。数据库文件单独放目录里PyCharm直接就能打开查看不用配置账号密码开发效率最高。但有两个地方必须提前注意。一是并发写入。SQLite对并发写支持很弱如果前端预约功能模拟多用户同时下单数据库可能直接报锁错误。建议一旦开始做并发测试和部署上线就切换到MySQL或PostgreSQL。二是模型字段的差异。比如Django里JSONField在不同数据库的语法是有差异的早期在SQLite里能跑通的查询换到MySQL后不一定完全一致。我的建议是开发中期就切换MySQL留足兼容性调试的时间别等部署前一周再换库。4. 推荐模块的工程化落地三层推荐策略4.1 第一层基于标签的内容推荐让系统上线第一天就能干活这一层是整条推荐模块的地基逻辑非常直白老人打过标签服务项目也打过标签计算匹配度就变成了求两组标签集合的交集和权重得分。我当时的实现思路是这样的。给每个标签设定一个权重身体状态类标签的权重高于兴趣类标签因为健康需求优先级更高。匹配得分公式大概如此score sum(老人与服务项目共同标签的权重) / sum(老人所有标签的权重) * 服务项目的热度系数热度系数是服务项目近30天被预约次数的归一化值用来解决两个服务匹配度相同但实际受欢迎程度不同的问题。这个公式不复杂但在数据量小时稳定性很好而且可以给前端提供清晰的推荐理由比如因为老人有糖尿病标签所以推荐低糖营养餐服务。代码实现上没有直接用ORM裸写而是单独写了一个推荐引擎模块。输入是老人的标签ID列表输出是带打分和推荐理由的服务项目列表按分数降序取TopN。这样把推荐逻辑和Django的业务层解耦后续换算法或加规则都不用改视图代码。4.2 第二层协同过滤让相似老人帮你做推荐数据积累一段时间后内容推荐的局限性就出来了它只能推荐服务标签匹配的项目发现不了虽然标签不像但类似的老人都在用的项目。例如认知训练课程标签可能标得不太准但好几个与这位老人类似的老人都在预约在标签匹配时就容易被漏掉。这就要用到协同过滤。我在实现中选用的是基于物品的协同过滤ItemCF核心逻辑是两步先计算服务项目之间的相似度再根据老人历史行为中选过的项目把相似项目推荐出去。服务项目之间的相似度我用的是同现矩阵法。简单来说同时被同一位老人预约过的两个服务项目相似度就高。公式大概是sim(i, j) 同时预约服务i和服务j的老人数 / sqrt(预约服务i的老人数 * 预约服务j的老人数)第二个公式在Python里写起来不复杂。训练过程放到后台任务里异步执行每天定时跑一次把相似度矩阵存到单独的缓存表里查询时直接读表。用Django写的话推荐用Celery做定时任务前期数据量小也可以简单用一个cron脚本替代。这段实现里容易出两个坑。一是相似度矩阵的稀疏性。养老院服务项目可能就几十个但行为数据一开始很少矩阵里很多元素为零计算时注意不要产生除零错误。二是评分归一化问题。不同老人预约服务的行为频率差异很大有的老人一个月预约十次有的三个月才一次。做协同过滤前一定要对行为次数做归一化处理否则高频用户的偏好会主导计算结果。4.3 第三层人工规则兜底处理冷启动和边界情况新老人首次登录时标签可能还没填完整行为数据为零内容推荐和协同过滤都派不上用场。这时人工规则兜底就很关键。我的做法是管理端配置了一批默认推荐位比如首月入住欢迎服务、免费健康评估、院内文化活动周报每个推荐位可以设置对应用户的分组条件。冷启动老人进来时系统把这三个规则配置的服务直接展示在首页推荐位。还有一个很重要的边界情况部分服务项目不适合推荐给所有老人比如重度失能老人不应该被推荐需要一定活动能力的运动课程。这一约束我用硬性过滤规则来实现在推荐引擎入口处先做一轮不可推荐服务黑名单过滤满足条件的服务直接剔除再走内容推荐和协同过滤流程。这个细节在真实业务里非常关键推荐系统可以推荐得不精准但不能推荐出有害选项。4.4 效果评估怎么做推荐系统不能只看上线那一刻的准确率我更关注的是转化指标。我在后台埋的数据看板里有这样几个指标推荐位曝光率、推荐位点击率、推荐服务预约转化率、推荐结果满意度评分。这个数据看板帮我发现了一个很有价值的问题推荐服务预约转化率在第五周开始出现明显下滑。排查后发现原因不在算法而是热门服务的库存排班不够老人想约约不上。后来在推荐结果里增加了当前可预约时间字段并做排序权重加成转化率才回升。这个经历值得记录推荐系统的效果约束不只是算法精度还包括业务端的供给能力做工程时一定要把这个因素考虑到。5. Vue前端的关键页面与联调细节5.1 页面结构和组件划分前端我按三种角色做了三套界面布局。管理端用的是左右结构左侧菜单、右侧内容区菜单包含老人管理、服务管理、标签管理、订单管理、推荐配置、数据看板。家属端采用顶部导航加卡片式内容区核心是服务推荐页、服务详情页、预约管理页和个人中心。老人端则是极简的大图标宫格布局首页直接展示今日推荐和待办提醒。组件划分上有几个关键页面值得说。推荐列表页是核心我设计成一个伸缩面板组件上半部分是推荐结果卡片流每个卡片上有服务图片、名称、价格、推荐理由标签和两个按钮查看详情、立即预约。下半部分是可按护理等级、服务分类、价格区间筛选的筛选栏。这里一定要把推荐理由单独做成一个小组件前端展示上因为您更偏好安静环境为您推荐单人调养房这种具体文案比冷冰冰的为您推荐效果好得多。服务详情页是一个抽屉式弹窗组件从推荐列表点过去后不跳转页面直接在右侧滑出详情这种交互对老人端和家属端都很友好减少了页面切换的迷失感。管理端的数据看板我用了大屏组件推荐转化率做成趋势折线图服务热度做成条形图标签分布做成词云。5.2 前后端联调的那些细节前后端联调是这个阶段最耗时的部分多数问题不在接口本身而在约定和规范。第一个问题是跨域。Django后端默认不允许跨域请求前端Vite的dev server跑在5173端口API在后端8000端口。解决方案是装django-cors-headers并配置白名单。这个操作很简单但新手经常漏掉结果所有请求都被浏览器拦截还以为是接口写错了。第二个问题是时间格式。Django默认返回的datetime格式是ISO 8601字符串前端Element UI的日期组件需要的是带时区的格式两边不统一会导致预约单的日期显示偏差。联调前要先把时间格式约定好建议后端统一序列化为YYYY-MM-DD HH:mm:ss格式给前端前端不做任何转换直接展示。第三个问题是分页参数。DRF的分页默认参数是page和page_size前端组件库的分页默认参数可能是current和pageSize两者对不上就会导致翻页失败。这个看似小问题排查起来却很气人。第四个问题是接口报错信息展示。后端DRF在参数校验失败时返回的默认错误结构是字典格式字段名是英文直接展示给家属看非常不友好。我后来做了统一错误码处理后端返回中文code码表前端根据code码渲染对应文案不管哪个接口出错用户看到的都是网络繁忙请稍后重试这样通顺的提示。5.3 老人端体验优化要注意的细节老人端这个模块虽然是后期加的但我建议核心功能一定不要省。老年人用手机的特点是大屏优先、点按容错率低、功能层级越浅越好。我实现上做了这么几个设计。首页字体默认调到18号以上关键按钮的点击区域不小于88px这是移动端的基础可用性规范。推荐卡片做成上下翻页模式老人不需要理解网格布局一次只看一张卡。页面只保留三个一级入口今日推荐、我的预约、紧急联系。所有详情都以大字号弹窗形式出现不用跳转。操作要有震动或声音反馈预约成功后前端弹出确认提示并播放提示音。这些改动在代码上并不复杂但在用户测试阶段的意义非常大。我找过几位不同年龄段的老人试操作最大的发现是预约流程中步骤数量只要超过三步老人就会犹豫不决。后来我把预约流程改成了两步先选时间再确认提交最多加一个备注可选项。这个改动对转化率的提升比任何算法调参都明显。6. 部署和实测阶段绕不开的那些坑6.1 本地开发环境配置整个项目的本地开发环境是我咬着牙搭起来的。如果你也和我一样在Windows上同时开发前端Vue项目和后端Django项目建议按下面这套来可以少踩很多坑。Python环境用Anaconda创建独立的虚拟环境Python版本选3.10不追求最高版本。原因在于Django生态里有些依赖在3.12上还有兼容问题3.10是目前兼容性最好的折中点。前端环境用Node.js 18 LTS版本配npm镜像加速包下载。项目目录建议采用monorepo结构一个根目录下分backend和frontend两个子目录后端用requirements.txt管理依赖前端用package.json管理依赖。根目录放一个README.md记录环境配置步骤和数据初始化说明。这样不管是自己回看还是交给别人接手都能快速跑起来。6.2 生产部署的教训部署环节我吃了点亏也总结了几条比较能说明问题的经验。第一条是不要在Windows服务器上直接跑Django。生产环境我用的是Linux服务器Django用Gunicorn作为WSGI容器前端Vue项目打包成静态文件后交给Nginx托管。Nginx同时配置反向代理把/api/路径的请求转发给Gunicorn。这个组合非常稳定无非就是需要自己补点Nginx配置知识。第二条是静态文件和媒体文件的处理。老人档案照片、服务项目图片这些上传文件在开发环境直接存本地目录生产环境必须配置单独的存储路径Nginx里也要映射好媒体文件夹访问路径。我第一版部署时漏了这步结果图片全都打不开排查了半天才发现是静态目录根本没配。第三条是数据库密码和环境变量不要直接写在settings.py里。我用的是django-environ库把数据库连接、密钥、调试开关都放在.env文件里并且把.env加入.gitignore。这个习惯能让你在多人协作或代码公开时不至于泄露敏感信息。第四条是HTTPS证书配置。现在浏览器对未加密的请求限制越来越严如果系统要正式给家属用建议配置Nginx的SSL证书。我用的是代理转发到内网的方式证书托管在反向代理层这样后端代码不需要处理证书相关逻辑。好处是部署简单缺点是如果家属反馈页面显示不安全要排查网络链路。6.3 实测数据带来的思考系统上线试运行一个月后我统计了一批真实可参考的数据。总共管理了200多名老人档案录入服务项目45项累计产生推荐请求3000多次。推荐位点击率稳定在38%左右推荐服务预约转化率大约11%家属端满意度评分平均4.2分满分5分。相比之前人工推荐的口头推荐模式服务预约响应时间从平均大半天缩短到十几分钟管理端处理预约订单的效率提升很明显。还有一个比较出人意料的发现是老人端日均活跃度比家属端高不少。这让我意识到养老院场景下真正的使用者才是最有粘性的群体系统设计时不能只盯着家属的决策行为老人端的体验优化值得投入更多时间。后来我干脆把老人端的版本迭代优先级排到了家属端前面。7. 再分享几个值得长期留存的优化思路这个项目做完并不是终点后来我跟朋友讨论又整理了几个有潜力的扩展方向如果读者有精力可以继续延伸。7.1 推荐效果要建立闭环反馈我见过很多推荐系统上线后就没有下文了光有推荐没有反馈收集算法永远无法迭代。在这个系统里我把反馈分成两层隐性反馈是行为日志老人浏览了哪个服务、停留多久、有没有点收藏这些数据自动落库显性反馈是每次预约完成后的评价弹窗用非常简单的三档表情满意、一般、不满意采集态度数据。这两层数据比任何算法优化都宝贵是后续迭代的根本依据。7.2 引入时间衰减因子老人在养老院的需求不是静态的。刚入住的老人更需要环境适应类和健康评估类服务住了一段时间后社交需求会上升身体状态变化后护理等级也会调整。当前版本的推荐引擎没有考虑时间因素只是个静态匹配。更优的设计是给行为数据加上时间衰减权重越近的行为对推荐结果的影响越大推荐结果就能跟着老人状态动态变化。7.3 管理端的推荐配置模块值得再丰富现在的推荐配置只是管理员维护几个兜底推荐位功能比较单一。可以扩展成可配置的规则引擎比如所有标签包含糖尿病的老人每周一统一推荐一次营养科讲座轻度失能老人优先推荐康复训练而非全护套餐。这部分配置化做扎实了系统的运营空间会大很多管理员不需要改代码就能调整推荐策略。7.4 多端发布目前老人端是以Web页面形式实现的实际使用中我发现部分老人还是更习惯用平板或大屏终端。如果条件允许可以把老人端做成响应式适配平板设备或者用HBuilderX uni-app做一套跨端小程序版本。这样家属在微信里就能帮助老人完成预约操作入口更浅使用频率会更高。最后说说我对这个项目整体的体会。养老院服务推荐系统表面看是一个全栈Web项目真正打磨下来你会发现它的难点不在框架或语法而在对业务场景的深度理解。推荐算法不是越复杂越好能解决冷启动、能表达推荐理由、能持续从反馈中优化的方案才是这个场景里真正有用的方案。全程做下来我最大的成长不是更熟悉了Vue或Django而是学会了先想清楚业务再谈技术。给同样在做这类项目的你一个建议尽早去真实的养老机构看一看听一听护理员和家属的日常诉求哪怕只是在旁边观察一小时也比闭门设计三天收获大得多。