← 返回工作台

1总体概览

不良事件模块是护理漏测系统的第5个业务模块,数据从轻流平台同步到 MySQL,再通过报表页面展示统计分析结果。

轻流平台 (care.yckycn.com) │ └── openApi ──────── 不良事件 (adverse_events) └─ AppKey: dtonmhf02801 └─ 脚本: sync_adverse_events.py └─ 认证: Header accessToken(不放URL参数) │ ▼ MySQL (nursing.adverse_events) │ ┌───────┴───────┐ │ │ 报表页面 基本信息 adverse-events- resident_info(入住协议) report.html 通过 id_number JOIN
模块说明
数据源轻流 openApi,AppKey: dtonmhf02801
认证方式Header 传 accessToken,永久 Token 共用
同步脚本/opt/leakcheck/sync_adverse_events.py
目标表MySQL nursing.adverse_events(32 个业务字段)
报表页面adverse-events-report.html,路由 /adverse-events-report
定时同步每日凌晨 03:00 全量同步
当前数据量约 149 条记录
API 每页上限200 条(openApi 限制,传 500 也只返回 200)

2数据源与同步规则

2.1 API 调用方式

项目
请求地址POST https://care.yckycn.com/openApi/app/dtonmhf02801/apply/filter
认证方式Header: accessToken: 187f968c-48a4-4874-95bc-edb084a4083d
Content-Typeapplication/json
分页参数pageNum 从1开始, pageSize 最大200
⚠️ 关键踩坑:accessToken 必须放在 Header 中,不能放在 URL 参数里!之前护理/评估用的 view API 是放 URL,但 openApi 必须用 Header。

2.2 同步模式

ℹ️ 不良事件为永久保留数据,始终采用全量同步模式,不做增量、不清理旧数据。
模式说明
全量(唯一模式)不设日期过滤,拉取轻流所有记录;同步后删除轻流中已不存在的记录,确保 DB 与轻流完全一致

2.3 数据一致性保障

每次同步完成后,会比对本次拉取到的所有 apply_id,删除 DB 中存在但轻流中已不存在的记录:

DELETE FROM adverse_events WHERE apply_id NOT IN (本次拉取的所有apply_id)
✅ 这样确保DB 数据与轻流业务表完全一致:轻流新增的会插入,轻流修改的会更新,轻流删除的也会从 DB 删除。

2.4 数据写入策略

  • 使用 INSERT ... ON DUPLICATE KEY UPDATE(upsert 模式)
  • 唯一键:apply_id(轻流申请ID),同一条记录重复同步会覆盖更新
  • 每次同步记录 sync_time 字段
  • 自动从 event_time 截取 event_date(yyyy-mm-dd)存入独立字段

2.5 脏数据防护

  • 跳过 event_date 在未来超过1天的异常记录
  • 若 API 返回的 apply_id 列表为空,跳过删除操作(防止误删全表)

2.6 定时任务

任务Cron 表达式说明
全量同步00 3 * * *每日凌晨 3:00 执行

2.7 手动同步

  • 命令行:python3 sync_adverse_events.py
  • 页面:同步工具页面 → "不良事件同步" 按钮(调用 /api/adverse-events/sync

3MySQL 表结构

表名:adverse_events,引擎 InnoDB,字符集 utf8mb4_unicode_ci

字段名类型说明索引
idINT AUTO_INCREMENT自增主键PRIMARY
apply_idBIGINT轻流申请IDUNIQUE
id_numberVARCHAR(18)身份证号码(关联键)INDEX
event_numberVARCHAR(100)编号
caregiver_nameVARCHAR(100)长者姓名
institutionVARCHAR(200)机构名称INDEX
areaVARCHAR(200)休养区INDEX
care_levelVARCHAR(50)护理等级
genderVARCHAR(10)性别
ageVARCHAR(10)年龄
birth_dateVARCHAR(20)出生日期
bed_numberVARCHAR(100)床位号
admission_dateVARCHAR(20)入住日期
id_file_noVARCHAR(100)档案编号
event_typeVARCHAR(100)不良事件类型INDEX
event_timeVARCHAR(50)事发时间(精确到分钟)
event_dateVARCHAR(20)事发日期(yyyy-mm-dd)INDEX
event_shiftVARCHAR(200)事发班次(原始值)
event_locationVARCHAR(200)事发地点
event_activityTEXT事发时长者活动(多值逗号分隔)
event_resident_statusTEXT事发时长者状态(多值逗号分隔)
staff_statusVARCHAR(200)事发时工作人员状态
staff_yearsVARCHAR(50)员工年限
restraint_usedVARCHAR(100)约束具使用情况
restraint_agreementVARCHAR(50)约束协议是否签订
fall_gradeVARCHAR(100)跌倒分级
pipe_typeVARCHAR(100)管路类型
severity_classVARCHAR(200)严重程度分类
damage_levelVARCHAR(200)给长者造成损害的程度
weightVARCHAR(50)体重(KG)
past_historyTEXT既往史
apply_timeVARCHAR(50)申请时间
update_timeVARCHAR(50)更新时间
flow_statusVARCHAR(50)当前流程状态
sync_timeDATETIME同步时间INDEX
💡 设计说明:当前报表用不到的字段(既往史、档案编号、严重程度分类、损害程度、跌倒分级等)也全量存储,为后续功能预留。

4字段映射(queId → 数据库列)

轻流表单中每个字段对应一个 queId,同步脚本将 queId 映射到 MySQL 列名。

queId轻流字段名MySQL 列名备注
208906738身份证号码id_number🔑 关联键
0编号event_number
208906730长者姓名caregiver_name
208906622机构名称institution
208906703休养区area
208906965护理等级care_level
208906629性别gender
208906958年龄age
208906957出生日期birth_date
208906664床位号bed_number
208906966入住日期admission_date
208907423档案编号id_file_no预留
208908821不良事件类型event_type跌倒/坠床/非计划性拔管
208915924事发时间event_time精确到分钟
208940856事发班次event_shift原始值含岗位+时段
208940857事发地点event_location
208940858事发时长者活动event_activity🔀 多值字段
208940859事发时长者状态event_resident_status🔀 多值字段
208940862事发时工作人员状态staff_status🔀 多值字段
208940863员工年限staff_years
208940855约束具使用情况restraint_used
208907428约束协议是否签订restraint_agreement
208940860跌倒分级fall_grade仅跌倒类有值
208940861管路类型pipe_type仅拔管类有值,新字段
208907441严重程度分类severity_class预留
208907442给长者造成损害的程度damage_level🔀 多值字段,预留
208907425体重(KG)weight
208907084既往史past_history预留
2申请时间apply_time从 answers 外提取
3更新时间update_time从 answers 外提取
4当前流程状态flow_status从 answers 外提取

5报表指标取数规则

报表通过 /api/adverse-events/report-data 接口获取统计数据,以下为各指标的取数规则。

#指标API Key数据源字段统计方式展示范围
1总事件数totalevent_type对非空 event_type 计数求和全部
2按事件类型by_typeevent_typeGROUP BY event_type,COUNT 降序全部
3事发时间分段by_periodevent_time提取小时 → 映射时段名 → COUNT(见第6节)全部
4事发班次by_shiftevent_shift按原始值 GROUP BY 分组统计(不再归一化)全部
5事发地点 TOP5by_locationevent_locationGROUP BY event_location,COUNT 降序前5名
6长者活动 TOP5by_activityevent_activity拆分多值 → 单项 COUNT 降序(见第8节)前5名
7长者状态 TOP5by_resident_statusevent_resident_status拆分多值 → 单项 COUNT 降序(见第8节)前5名
8工作人员状态 TOP5by_staff_statusstaff_status拆分多值 → 单项 COUNT 降序(见第8节)前5名
9员工年限by_staff_yearsstaff_yearsGROUP BY staff_years,COUNT 降序全部
10约束具使用by_restraintrestraint_usedGROUP BY restraint_used,COUNT 降序全部
11约束协议签订by_restraint_agreementrestraint_agreementGROUP BY restraint_agreement,COUNT 降序全部
12跌倒分级by_fall_gradefall_gradeevent_type='跌倒' 时统计全部
13管路类型by_pipe_typepipe_typeevent_type='非计划性拔管' 时统计全部
14严重程度分类by_severityseverity_classGROUP BY severity_class,COUNT 降序全部
15损害程度by_damagedamage_level拆分多值 → 单项 COUNT 降序(见第8节)全部
💡 专项过滤说明
  • 跌倒分级:只统计 event_type = '跌倒' 的记录,其他事件类型不展示此指标
  • 管路类型:只统计 event_type = '非计划性拔管' 的记录

6事发时间分段规则

event_time 字段提取小时(0-23),映射到固定时段名称。映射在 _segment_time_period() 函数中实现。

时段名称时间范围小时值示例
意外高发2:00 - 4:592, 3, 404:30 → 意外高发
晨间护理5:00 - 6:595, 606:15 → 晨间护理
早餐7:00 - 7:59707:30 → 早餐
上午活动8:00 - 10:598, 9, 1009:45 → 上午活动
午餐11:00 - 11:591111:20 → 午餐
午休12:00 - 13:5912, 1313:00 → 午休
下午活动14:00 - 16:5914, 15, 1615:30 → 下午活动
晚餐17:00 - 17:591717:45 → 晚餐
晚间护理18:00 - 19:5918, 1919:00 → 晚间护理
睡眠20:00 - 1:590, 1, 20, 21, 22, 2322:30 → 睡眠
💡 时段划分暂固定不变,后续如需调整再沟通修改 _segment_time_period() 函数即可。

代码逻辑

def _segment_time_period(hour):
    if 2 <= hour <= 4:      return "意外高发"
    elif hour in (5, 6):     return "晨间护理"
    elif hour == 7:          return "早餐"
    elif 8 <= hour <= 10:   return "上午活动"
    elif hour == 11:         return "午餐"
    elif hour in (12, 13):   return "午休"
    elif 14 <= hour <= 16:  return "下午活动"
    elif hour == 17:         return "晚餐"
    elif hour in (18, 19):   return "晚间护理"
    else:                    return "睡眠"  # 20-23, 0-1

7班次取值规则

轻流中 event_shift 字段原始值包含岗位和时段信息,v2.1起按原始值直接分组统计,不再做归一化合并。

变更说明(v2.1):此前按关键字归一化为"白班/夜班/其他班次"3种,导致不同岗位的班次被错误合并(如"护士:白班"和"照护师:白班"混在一起)。现在按原始值分别统计,保留完整的岗位+班次信息。

当前数据分布

原始值数量
护士:白班(8:31-17:30)62
护士:夜班(17:31-8:30)54
照护师:白班(6:31-18:30)3
护士早交接班时段2
照护师:夜班(18:31-6:30)2
其他:早上06:03分1
医生/药剂师/康复师:白班(8:31-17:30)1
护士晚交接班时段1
护理员早交接班时段1

SQL逻辑(v2.1)

SELECT event_shift as name, COUNT(*) as cnt
FROM adverse_events
WHERE event_shift IS NOT NULL AND event_shift != ''
GROUP BY event_shift
ORDER BY cnt DESC

8多值字段拆分规则

部分字段在轻流中为多选,存储时用英文逗号拼接,统计时需拆分为单项分别计数

涉及字段

字段MySQL列存储格式统计方式
事发时长者活动event_activity"上下床,如厕,坐下"拆分为 3 条单项各 +1
事发时长者状态event_resident_status"坐轮椅,使用助行器"拆分为 2 条单项各 +1
事发时工作人员状态staff_status"护理其他长者,巡视中"拆分为 2 条单项各 +1
给长者造成损害的程度damage_level"擦伤,淤青"拆分为 2 条单项各 +1

拆分逻辑

# 以事发时长者活动为例
for r in cur.fetchall():
    for a in r['event_activity'].split(','):
        a = a.strip()
        if a: act_cnt[a] = act_cnt.get(a, 0) + 1
⚠️ 注意:拆分后各项计数之和可能大于总记录数(一条记录可能包含多个活动项)。

举例说明

记录event_activity 原始值拆分后计数
#1"上下床,如厕"上下床 +1, 如厕 +1
#2"上下床"上下床 +1
#3"坐下,如厕,站立"坐下 +1, 如厕 +1, 站立 +1
统计结果上下床:2, 如厕:2, 坐下:1, 站立:1

9筛选条件逻辑

报表页面提供3个筛选条件,所有条件为 AND 关系,空值表示"不限制"。

筛选条件前端控件对应字段SQL 条件数据来源
机构名称下拉框institutionWHERE institution = ?org_area_mapping 表
休养区下拉框(联动)areaWHERE area = ?org_area_mapping 表(按选中机构过滤)
统计周期日期区间选择器event_dateWHERE event_date >= ? AND event_date <= ?用户手动选择

机构/休养区取值逻辑

✅ 与护理/评估页面保持一致,从 org_area_mapping 表获取机构-休养区对应关系。
  • API: /api/leak/org-areas(共用,非不良事件独有)
  • 优先从 org_area_mapping 表查询(排除名称含"测试"的机构)
  • 如果 org_area_mapping 为空,降级从 resident_info 表查询(排除离院)
  • 机构下拉变化时,休养区下拉联动刷新

日期区间说明

  • 关联字段:event_date(从 event_time 截取的 yyyy-mm-dd)
  • 开始日期:包含(>=
  • 结束日期:包含(<=
  • 默认值:无(不选则不限制日期范围)

10基本信息关联规则

报表中需要的老人基本信息(姓名、机构、休养区、护理等级等),统一通过身份证号关联入住协议获取。

关联方式

adverse_events resident_info ───────────── ───────────── id_number ────── JOIN ──────► id_number ├── caregiver_name (姓名) ├── institution (机构) ├── area (休养区) ├── care_level (护理等级) ├── status (状态:在院/离院) └── ...

关联规则

  • 关联键adverse_events.id_number = resident_info.id_number
  • 数据来源优先级:入住协议(resident_info)> 不良事件表自身字段
  • 原因:入住协议是最新、最准的基础信息,且每日定时同步更新
  • 与护理/评估一致:之前护理漏测和评估漏评均采用此关联方式
⚠️ 当前实现说明:目前报表查询直接使用 adverse_events 表中的 institution/area/care_level 字段进行筛选和展示(因为不良事件表本身包含这些字段且与 resident_info 一致)。如果后续需要关联 resident_info 获取更准确的信息(如离院状态过滤),需改用 JOIN 查询。

数据验证

来源姓名机构休养区护理等级
不良事件表李琴琴梅苑颐养中心颐养楼2区4层3
resident_info李琴琴梅苑颐养中心颐养楼2区4层3
✅ 交叉验证一致
── 文档结束 · 不良事件业务规则 v2.1 · 2026-06-08 ──