尧图精选

慢性病数据追踪可视化:从MySQL建模到ECharts大屏实践

🕒 发布时间:2026/10/2 15:27:01 📁 来源:尧图网络
简介慢性病管理数据追踪与可视化系统资源包定位为课程报告配套资料面向需要完成健康数据分析与Web可视化项目的学生或开发者重点解决生理指标采集、数据清洗与统计、异常预警和交互图表展示的完整实现问题。压缩包共3个文件包含PDF、Markdown和HTML三种格式总体积约1.28MB其中PDF与Markdown用于梳理系统设计、测试过程及实现思路HTML可直接在浏览器中查看可视化演示效果。目前已有63人学习/下载内容基于Flask构建数据采集接口利用Pandas完成缺失值和异常值清洗再通过基于规则的引擎实现连续高血糖、持续高血压等预警最后以PyEcharts绘制趋势折线图与箱线图。总体上覆盖RESTful API接收、角色访问控制RBAC及数据安全设计等关键环节对课程报告撰写、系统复现和二次开发有直接参考价值。1. 慢性病数据追踪与可视化不是先画大屏而是先把“追踪”拆清楚很多做慢病管理系统的团队第一次汇报都喜欢搬出一块可视化大屏出来血压折线、血糖面积图、地图上的患者点位。真正接上数据才发现大屏上的数字对不上原因往往不是图表画错而是“追踪”这件事没定义清楚——一天量几次血压、几点量的、用的什么单位、达标按什么标准算全都散在 Excel 和微信群里。这套慢性病管理数据追踪与可视化系统就是把患者院外的体征、用药、复诊行为变成结构化数据再按天聚合、按周生成报告最后用 ECharts 和可视化大屏呈现给医生和管理员。适合正在做公卫随访、慢病管理平台或院后康复工具的开发与产品也适合想把“能录入”升级成“能追踪”的团队。2. 数据模型与采集血压血糖和用药记录怎么存才能被追踪做追踪系统前我习惯先回答三个问题这条数据有什么用谁上报将来怎么查如果三个问题答不上来就不建表宁可先用 CSV 临时收。下面是我在慢病管理项目里沉淀下来的数据模型覆盖高血压、糖尿病这两个最常见的病种。2.1 追踪一个慢性病患者最少要盯哪几类数据慢病追踪的核心不是“采得多”而是“采得准、能比较”。我一般把数据分成四层第一层是体征数据。血压的收缩压、舒张压、心率血糖的空腹、餐后两小时、随机血糖再加上体重和身高质量。这里有一个关键点患者不可能每天都规律测量所以系统不要强制“今天必须测一次”而要记录“今天测了几次、分别是多少”。数据模型里保留原始记录用日维度上的avg/max/min表达一天的波动比只存一个“今日值”可靠得多。第二层是用药数据。药名、剂量、频次、实际服用时间。这部分直接服务依从性分析医生让他一天吃两次他一周实际吃了几次是不是总在晚上补服很多慢病管理项目把用药记录做成一个“打卡”功能但打卡按钮不能替代数据模型必须落到一张独立的用药日志表里否则后面做依从率的时候只能拍脑袋。第三层是复诊和检查数据。下次复诊日期、实际复诊日期、糖化血红蛋白、尿微量白蛋白、血脂等检验结果。这些数据频率低但对调整用药方案最重要。追踪系统如果只看每天的血压看不到三个月一次的糖化就等于让医生蒙着眼开车。第四层是生活方式数据。步数、睡眠时长、是否吸烟饮酒能作为异常波动的解释维度。这部分主观性强通常从小程序问卷或手环接口进来不作为核心指标只做联动展示。还有一张很容易漏的表患者个体化达标目标。同一个血压值对普通患者可能是“达标”对合并肾病的患者可能不是。把达标阈值写死在代码里是翻车源头我一般单独建patient_targets表让随访医生可以为每个患者调整收缩压上下限、空腹血糖上下限。2.2 用 MySQL 建表患者、体征、用药、复诊四张核心表下面是这套系统的核心表结构。我用 MySQL 落地字符集统一utf8mb4避免 emoji 或生僻姓名插入报错。CREATE DATABASE IF NOT EXISTS chronic_disease DEFAULT CHARACTER SET utf8mb4; CREATE TABLE patient ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, gender TINYINT NOT NULL DEFAULT 0 COMMENT 0未知 1男 2女, birth_date DATE DEFAULT NULL, diagnosis VARCHAR(64) NOT NULL COMMENT 高血压/糖尿病/合并, is_active TINYINT NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE patient_targets ( patient_id BIGINT PRIMARY KEY, sbp_low DECIMAL(5,1) DEFAULT 90.0, sbp_high DECIMAL(5,1) DEFAULT 140.0, dbp_low DECIMAL(5,1) DEFAULT 60.0, dbp_high DECIMAL(5,1) DEFAULT 90.0, fbg_low DECIMAL(4,1) DEFAULT 3.9, fbg_high DECIMAL(4,1) DEFAULT 7.0, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE vital_signs ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, patient_id BIGINT NOT NULL, vital_type VARCHAR(32) NOT NULL COMMENT sbp/dbp/heart_rate/fbg/pbg/weight, value_orig DECIMAL(10,2) NOT NULL COMMENT 原始数值, unit VARCHAR(16) NOT NULL COMMENT 原始单位, value_standard DECIMAL(10,2) NOT NULL COMMENT 统一单位后的数值, measure_time DATETIME NOT NULL COMMENT 患者实际测量时间, business_date DATE NOT NULL COMMENT 按患者时区折算的业务日期, source TINYINT NOT NULL DEFAULT 1 COMMENT 1手录 2设备 3批量导入, device_no VARCHAR(64) DEFAULT NULL, is_abnormal TINYINT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_patient_time (patient_id, measure_time), KEY idx_business_date (business_date), KEY idx_vital_type_time (vital_type, business_date) ) ENGINEInnoDB; CREATE TABLE medication_log ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, patient_id BIGINT NOT NULL, drug_name VARCHAR(64) NOT NULL, dose_value DECIMAL(10,2) NOT NULL, dose_unit VARCHAR(16) NOT NULL, plan_times INT NOT NULL DEFAULT 0 COMMENT 当日应服用次数, taken_time DATETIME DEFAULT NULL COMMENT 实际服用时间, is_taken TINYINT NOT NULL DEFAULT 0, business_date DATE NOT NULL, KEY idx_patient_date (patient_id, business_date) ) ENGINEInnoDB; CREATE TABLE daily_summary ( patient_id BIGINT NOT NULL, business_date DATE NOT NULL, avg_sbp DECIMAL(5,1) DEFAULT NULL, max_sbp DECIMAL(5,1) DEFAULT NULL, min_sbp DECIMAL(5,1) DEFAULT NULL, avg_fbg DECIMAL(5,1) DEFAULT NULL, avg_heart_rate DECIMAL(5,1) DEFAULT NULL, med_times INT DEFAULT 0 COMMENT 实际服用次数, med_plan_times INT DEFAULT 0 COMMENT 应服用次数, reach_sbp TINYINT DEFAULT 0 COMMENT 当日平均收缩压是否达标, reach_fbg TINYINT DEFAULT 0 COMMENT 当日平均空腹血糖是否达标, PRIMARY KEY (patient_id, business_date), KEY idx_business_date (business_date) ) ENGINEInnoDB;几个字段在新手项目里经常被简化我这里单独解释。value_orig和value_standard双份存储不是冗余。很多血压计返回的是 kPa手录的是 mmHg如果不统一单位后面做“连续达标天数”时会被单位搅乱。保留原始值是为了数据溯源统一值是为了计算和展示。business_date是专门为“按天统计”准备的日期字段它必须在前端传offset_minutes后换算得到不能直接取数据库的CURRENT_DATE否则跨时区随访时每天的数据会错位。daily_summary不存MAX(med_times)这类过度加工字段只存“实际次数”和“应服次数”达标率由前端或周报接口现算。这样字段含义清楚以后改动达标算法也不用改表结构。索引方面要克制。vital_signs上我只建了三个最常用的组合索引按患者查趋势、按业务日期做全量日聚合、按类型和日期做病种统计。索引不是越多越好尤其这类写入频繁的表索引过多会放大写放大。如果后面报表要按医生维度统计再补(doctor_id, business_date)的冗余字段和索引而不是在五张表之间反复 join。2.3 采集接口手机手录、设备对接的入参设计与校验数据入口决定了整个系统的数据质量。我一般用 Flask 写一个POST /api/v1/vitals接口无论来自小程序手录、蓝牙血压计、还是第三方可穿戴设备都统一走这一个入口。来一个伪代码骨架from flask import Flask, request, jsonify from datetime import datetime, timedelta app Flask(__name__) def to_business_date(dt, offset_minutes0): # 患者本地的业务日期不是 UTC 日期 return (dt timedelta(minutesoffset_minutes)).date() app.post(/api/v1/vitals) def upload_vital(): data request.get_json(forceTrue) patient_id data.get(patient_id) vital_type data.get(vital_type) value data.get(value) unit data.get(unit, mmHg) measure_time_str data.get(measure_time) offset_minutes int(data.get(offset_minutes, 0)) source int(data.get(source, 1)) device_no data.get(device_no, ) if not patient_id or vital_type not in {sbp, dbp, heart_rate, fbg, pbg, weight}: return jsonify({code: 400, msg: 参数不完整}), 400 # 单位换算 if vital_type in {sbp, dbp} and unit kPa: value_standard round(value / 7.5, 1) else: value_standard round(value, 1) # 极值兜底防止手滑 if value_standard 0 or value_standard 300: return jsonify({code: 400, msg: 数值超出允许范围}), 400 business_date to_business_date(measure_time, offset_minutes) # TODO: 写入 vital_signs并根据 is_abnormal 更新 Redis 统计 return jsonify({ code: 0, data: {business_date: str(business_date)} })这段代码有三个点容易被忽略。一个是datetime.fromisoformat要包一层try/except设备端传入的时间格式五花八门一个是offset_minutes是患者所在时区相对 UTC 的偏移中国项目通常是 480 分钟但不能写死因为有些老人跟随子女住在国外或跨境地区另一个是source和device_no要保留下来后续排查“某一批设备数据不准”的时候靠这两个字段能快速隔离。在真正生产环境里我会在这个接口后面加一层简单的事前校验收缩压 30~260 mmHg舒张压 30~200 mmHg空腹血糖 1.0~33.0 mmol/L。超出区间的值不直接报错而是标记is_abnormal1并入库。原因是慢病患者偶发一次危急值反而需要提醒如果直接拒绝写入会丢掉最关键的异常信号。注意业务日期必须在数据入口算好不要等到聚合时再用数据库时区猜。否则凌晨 0 点前后测的数据会被算到错误的一天后续连续达标天数全错。3. 追踪算法与后端接口用 MySQL 聚合趋势、用 Redis 盯实时状态数据落库只是第一步。要支撑“追踪”必须回答三个问题过去三十天血压趋势是什么连续几天达标今天有多少患者出现异常这三个问题分别由日聚合、窗口函数统计和 Redis 实时计数来解决。3.1 为什么要做日聚合原始表扛不住长期趋势查询vital_signs表是明细表一位患者一天可能产生 5~10 条记录一年接近 2000 条。如果每次可视化大屏都扫明细表做GROUP BY并发一上来MySQL 的 CPU 就会被打满。常见做法是每天凌晨用定时任务把明细表聚合成daily_summary趋势接口只读聚合表。聚合任务我倾向于用 Python 脚本控制而不是完全放在 MySQL 存储过程里。原因是团队维护 Python 更顺手日志和回滚都更好处理。INSERT INTO daily_summary ( patient_id, business_date, avg_sbp, max_sbp, min_sbp, avg_fbg, avg_heart_rate ) SELECT patient_id, business_date, ROUND(AVG(CASE WHEN vital_type sbp THEN value_standard END), 1), MAX(CASE WHEN vital_type sbp THEN value_standard END), MIN(CASE WHEN vital_type sbp THEN value_standard END), ROUND(AVG(CASE WHEN vital_type fbg THEN value_standard END), 1), ROUND(AVG(CASE WHEN vital_type heart_rate THEN value_standard END), 1) FROM vital_signs WHERE business_date %s GROUP BY patient_id, business_date ON DUPLICATE KEY UPDATE avg_sbp VALUES(avg_sbp), max_sbp VALUES(max_sbp), min_sbp VALUES(min_sbp), avg_fbg VALUES(avg_fbg), avg_heart_rate VALUES(avg_heart_rate);这里的%s是日期参数建议由 Python 调用时传入例如run_daily_aggregate(2025-06-01)。不要让 SQL 里写死CURDATE()否则补数的时候没法重跑某一天。ON DUPLICATE KEY UPDATE保证任务重复执行不会产生重复行而会把旧聚合值覆盖成最新结果。聚合完成后还要单独更新达标字段因为达标阈值来自patient_targets表不同患者标准不同。我一般用一条 UPDATE JOINUPDATE daily_summary d JOIN patient_targets t ON d.patient_id t.patient_id SET d.reach_sbp IF(d.avg_sbp BETWEEN t.sbp_low AND t.sbp_high, 1, 0), d.reach_fbg IF(d.avg_fbg BETWEEN t.fbg_low AND t.fbg_high, 1, 0) WHERE d.business_date %s;3.2 用 SQL 计算连续达标天数窗口函数怎么用“连续达标天数”是慢病管理里最常被问到的指标也是新手最容易写错的地方。有人写循环逐天判断数据量小的时候问题不大患者量过万就非常慢。MySQL 8.0 里可以用窗口函数一次算完WITH daily_target AS ( SELECT d.patient_id, d.business_date, d.avg_sbp, CASE WHEN d.avg_sbp BETWEEN t.sbp_low AND t.sbp_high THEN 1 ELSE 0 END AS sbp_reach FROM daily_summary d JOIN patient_targets t ON d.patient_id t.patient_id WHERE d.business_date CURDATE() - INTERVAL 90 DAY ), flagged AS ( SELECT *, CASE WHEN LAG(sbp_reach) OVER (PARTITION BY patient_id ORDER BY business_date) sbp_reach THEN 0 ELSE 1 END AS flag_change FROM daily_target ), groups AS ( SELECT *, SUM(flag_change) OVER (PARTITION BY patient_id ORDER BY business_date) AS grp_id FROM flagged ) SELECT patient_id, MAX(business_date) AS end_date, COUNT(*) AS consecutive_days FROM groups WHERE sbp_reach 1 GROUP BY patient_id, grp_id ORDER BY consecutive_days DESC LIMIT 50;逻辑拆开说daily_target先把每天是否达标变成 0/1flagged用LAG看“昨天是否达标”如果和今天不一样就标记为 1groups把这些标记累加形成一个分组 ID连续相同状态的行会落在同一个组里。最后只取sbp_reach1的组统计每组的天数就是连续达标天数。这里有一个值得注意的边界第一行没有前一行LAG返回NULLNULL sbp_reach不为真所以flag_change会被置为 1新分组从第一天开始。这个行为是对的。如果团队还在用 MySQL 5.7没有窗口函数也可以把同样逻辑搬到 Python 列表里逐行算但窗口函数明显更省事性能也更稳。3.3 用 Redis 缓存今日统计与异常计数不把压力全给数据库趋势是“慢数据”实时状态是“快数据”。比如大屏上“今日异常患者数”“今日新增血压记录数”如果每次都查 MySQL数据量一大就是灾难。我的做法是在采集接口写完数据库后顺手把计数同步到 Redis。import redis r redis.Redis(host127.0.0.1, port6379, db0, decode_responsesTrue) def sync_vital_stats(patient_id, business_date, vital_type, is_abnormal): pipe r.pipeline(transactionFalse) # 当日某种体征的测量次数 key fvital:{patient_id}:{business_date}:{vital_type} pipe.incr(key) pipe.expire(key, 86400 * 3) # 当日异常次数 if is_abnormal: ab_key fabnormal:{patient_id}:{business_date} pipe.incr(ab_key) pipe.expire(ab_key, 86400 * 3) # HyperLogLog 统计当日活跃患者去重人数 pipe.pfadd(active:today, str(patient_id)) pipe.expire(active:today, 86400) pipe.execute()这里用pipeline把多次 Redis 命令打包发送减少网络往返。expire的时间设为 3 天是为了防止那些只记一次就再也不用的 key 永久残留。aff active:today用 HyperLogLog 做去重统计内存占用极小误差在 0.81% 左右做大屏“今日活跃患者数”足够。调试的时候可以用 Redis 客户端可视化工具看 key 的增长情况能一眼发现某个患者是否在短时间内被重复推送了几十条记录。如果发现某类vital的 key 数量异常膨胀第一步要查设备端是否在循环重传历史数据而不是先改代码。关于缓存一致性我的原则是写接口更新 Redis而不是等缓存过期后再靠查询回源。因为慢病数据按天汇总缓存更新频率不高完全可以在写入时同步更新如果真出现 Redis 和 MySQL 不一致重跑一天的聚合任务就能修复不需要搞复杂双写方案。4. 可视化落地ECharts 图表接口与可视化大屏的刷新策略可视化是用户最直观感受到的部分但也是被误解最深的。很多团队一上来就买大屏可视化编辑器拖拽模板结果发现模板上的组件很难接真实数据。我一般先用原生 ECharts 把单患者趋势图跑通再考虑大屏布局。4.1 图表选型折线图、日历热力图、漏斗图分别盯什么ECharts 数据可视化不是把数据塞进任意图表而是按“问题”选图。我做慢病系统时的选型如下监控对象推荐图表说明收缩压/舒张压长期趋势折线图 markLine达标线画在图上医生一眼看出超标时段空腹血糖分布散点图或箱线图看波动范围比只看平均值更有用用药依从性漏斗图或堆叠柱状图从应服次数到实际服用的每一步转化近 30 天达标日历日历热力图红色块标注异常日适合可视化大屏血压趋势我强烈建议加markLine作为达标线。不要小看这条线很多医生习惯先看是否越线再看具体波形。如果后台不把patient_targets的阈值传出来前端画不了这条线图表的价值就少了一半。4.2 给图表造一个稳定接口Flask 返回的 JSON 结构怎么设计可视化层最怕后端接口结构三天两头变。我习惯在 Flask 里直接把接口返回结构设计成和 ECharts 的series对齐前端拿到就能用不用二次组装。app.get(/api/v1/patient/int:patient_id/trend) def patient_trend(patient_id): days min(int(request.args.get(days, 30)), 365) sql SELECT business_date, avg_sbp, max_sbp, min_sbp, avg_fbg FROM daily_summary WHERE patient_id %s AND business_date DATE_SUB(CURDATE(), INTERVAL %s DAY) ORDER BY business_date rows query_db(sql, (patient_id, days)) return jsonify({ code: 0, data: { dates: [str(r[business_date]) for r in rows], target: { sbp_low: 90, sbp_high: 140 }, series: [ { name: 平均收缩压, type: line, data: [float(r[avg_sbp]) for r in rows] }, { name: 平均空腹血糖, type: line, data: [float(r[avg_fbg]) for r in rows] } ] } })接口里把dates单独拎出来而不是塞到每个 series 里是因为 ECharts 的xAxis.data只需要一份日期。target单独返回前端可以动态画达标线不用在后端拼markLine。days参数做了范围限制最大 365避免一次拉五年数据把浏览器拖死。4.3 可视化大屏布局与自动刷新不用大屏编辑器也能拼出来可视化大屏不需要一上来就上付费大屏可视化编辑器。用 CSS Grid 搭一个 12 列、4 行的布局左侧放区域统计中间放趋势图右侧放异常患者列表语义足够清晰。先保证每个区块都能拉到真实接口再去谈拖拽美化。前端代码用一个定时刷新函数控制const chart echarts.init(document.getElementById(trend-chart)); async function refreshTrend() { const res await fetch(/api/v1/patient/1/trend?days90); const json await res.json(); chart.setOption({ title: { text: 近90天血压与血糖趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: json.data.dates }, yAxis: [ { type: value, name: 血压(mmHg) }, { type: value, name: 血糖(mmol/L) } ], series: json.data.series.map(s ({ ...s, smooth: true })) }, true); } refreshTrend(); setInterval(refreshTrend, 300000);重点在setOption的第二个参数true它表示notMerge也就是下一次刷新时不要保留上一次的 series。如果不传这个参数定时刷新时旧曲线会残留页面越跑越花。大屏不需要秒级刷新慢病数据 5 分钟刷一次足够刷新太频繁反而会让医生觉得系统不稳定。大屏上如果还要展示“今日异常患者数”“今日新增记录数”直接读取前一章 Redis 统计接口不要再去查询 MySQL 明细。我一般会为这些 KPI 写一个独立接口从 Redis 取值后返回接口内部用try/except兜底Redis 挂了就返回null前端显示--而不是让整个大屏报错。5. 慢性病追踪系统的常见坑与排查从日期错位到缓存不一致可视化大屏做好只是开始真正折磨人的是上线后数据对不上。以下四个坑是我在慢病数据追踪系统里反复遇到的每条都按“现象 → 原因 → 解决”来说明。5.1 日期边界错位连续达标天数为什么总差一天现象患者晚上 23 点在手机里量了血压第二天早上汇总时这条记录被算到了前一天或后一天连续达标天数比手算少一天。原因前端把测量时间转成时间戳传后端后端直接用date(now())或者用 MySQL 的CURRENT_DATE来判断业务日期。一旦患者所在地与服务器不在同一时区半夜测量数据就会落到错误的业务日期。解决接口必须接收测量时间和offset_minutes在数据入口把时间偏移计算好生成business_date字段再写入数据库。测试用例至少要包含 23:50 和 00:10 两条记录分别验证它们落在正确日期。这个坑不解决后面所有“周达标率”“连续达标天数”都会出问题。5.2 血压单位混用mmHg 和 kPa 让图表出现离群值现象血压趋势图里突然出现一个“120/80”的离群点或者“12/8”这种看起来血压极低的值医生以为患者病情剧烈波动。原因部分进口血压计或老式血压计返回的是 kPa手录人员却把单位写成 mmHg有些设备厂家文档里写“单位为 kPa”但 JSON 字段里没带unit。数据入库时不做单位归一化图表就会直接把两条不同量纲的数据画在一起。解决在vital_signs表里同时保留value_orig、unit、value_standard三个字段接口层完成单位转换。转换关系很简单1 kPa ≈ 7.5 mmHg。展示端统一输出 mmHg存储端保留原始值备查。同时加范围校验收缩压 30~260 mmHg舒张压 30~200 mmHg超出范围直接标记异常。5.3 趋势接口越用越慢日聚合表没建、缓存又没命中现象上线两周后30 天趋势接口从 200ms 涨到 2 秒并发一高就超时。查看监控发现vital_signs表已经几百万行每次查询都在做全表GROUP BY。原因可视化图表直接查询明细表没有走daily_summary聚合表Redis 里虽然设了缓存但只在过期后被动更新某一天没有新数据写入缓存就一直不命中。解决第一所有趋势接口只读daily_summary聚合表明细表只用于溯源第二缓存更新策略改成写接口同步更新不依赖过期第三补数任务重跑某一天后主动删除或更新该患者对应时间段的缓存 key。调试时用 Redis 客户端可视化工具查看 key 是否存在能快速确认是缓存没写还是根本没触发。5.4 大屏接口跨域与 setOption 合并刷新几次图表就乱了现象可视化大屏在另一台机器上访问接口报 CORS 错误偶尔不报错但折线图刷新几次后出现了两条几乎重叠的曲线旧数据没有消失。原因Flask 服务端没有允许前端域名跨域访问ECharts 的setOption默认是合并配置新数据和旧数据叠加而不是替换。解决Flask 端用 flask-cors 允许指定来源不要用*放行所有来源from flask_cors import CORS CORS(app, resources{ r/api/*: { origins: [http://screen.local:8080] } })前端setOption(option, true)强制不合并。定时器还要注意一个细节页面切到后台浏览器会节流定时器但 ECharts 实例还持有旧 DOM回到前台后再刷新可能两次请求同时返回所以在document.visibilitychange里做一次手动刷新比单纯依赖setInterval更可靠。6. 从“能看”到“能用”周报、异常提醒与随访提醒的进阶做法图表和大屏解决的是“看”的问题但医生和管理员真正需要的是“看完之后做什么”。我的进阶做法是把追踪结果生成周报、把异常状态转成提醒、把复诊计划转成待办。周报可以按周聚合daily_summary生成一段简洁文本再通过微信公众号或企业微信推送给健康管理师。核心代码不复杂from datetime import date, timedelta def build_weekly_report(patient_id): end date.today() - timedelta(days1) start end - timedelta(days6) sql SELECT ROUND(AVG(avg_sbp), 1) AS week_avg_sbp, SUM(CASE WHEN reach_sbp 1 THEN 1 ELSE 0 END) AS reach_days, COUNT(*) AS total_days FROM daily_summary WHERE patient_id %s AND business_date BETWEEN %s AND %s row query_one(sql, (patient_id, start, end)) return f本周血压均值 {row[week_avg_sbp]} mmHg达标 {row[reach_days]}/{row[total_days]} 天。异常提醒我习惯用 Redis 里的连续异常计数当某位患者连续 3 天reach_sbp 0就生成一条提醒任务推给签约医生。别小看这个 3 天阈值设置定得太紧会产生大量无效提醒定得太松又失去干预价值最好由医生在后台按病种配置。随访提醒更简单也更重要followup_plan表记录每位患者的下次复诊日期每天扫一遍未来 3 天到期的记录生成待办清单。这样可以关联到数据质量如果患者连续两周没有上传任何血压数据随访提醒会优先出现。把“没数据”也当成一种需要追踪的信号系统才算真正闭环。我现在接到新项目第一件事不是打开大屏编辑器而是把一个指标从采集、聚合、接口、可视化的完整链路跑通再横向扩量。这个习惯帮我少踩了非常多数据错位的坑。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →