diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md
index f9f0397..cf98c4d 100644
--- a/.claude/memory/MEMORY.md
+++ b/.claude/memory/MEMORY.md
@@ -1,4 +1,4 @@
-_Last synced: 2026-07-24 | Base commit: `1e7c5a1`_
+_Last synced: 2026-07-24 | Base commit: `91621bc`_
# 记忆索引
| 文件 | 描述 | 类型 | 最后更新 |
diff --git a/.claude/memory/lint_report.md b/.claude/memory/lint_report.md
index 6ba220f..1f43c5d 100644
--- a/.claude/memory/lint_report.md
+++ b/.claude/memory/lint_report.md
@@ -7,7 +7,7 @@ last_updated: 2026-07-24
# 记忆健康检查报告
-> _执行时间: 2026-07-24 | Base commit: `1e7c5a1` | Last synced: 2026-07-24_
+> _执行时间: 2026-07-24 | Base commit: `91621bc` | Last synced: 2026-07-24_
## 健康概览
diff --git a/.claude/memory/project_progress.md b/.claude/memory/project_progress.md
index 70b30a6..58cabea 100644
--- a/.claude/memory/project_progress.md
+++ b/.claude/memory/project_progress.md
@@ -1,3 +1,11 @@
+---
+name: 项目进度
+description: 各模块原型开发进度与里程碑
+type: project
+last_updated: 2026-07-24
+commit: 91621bc
+---
+
# 项目进度
## 已完成原型的模块
@@ -16,7 +24,7 @@
- `health-consult/` — 健康咨询(32 页)✅
- `emergency-medical/` — 应急就医(25+ 页)✅
- `health-checkup/` — 健康体检(20+ 页)✅
-- `weight-management/` — 体重管理(25 页)✅
+- `weight-management/` — 体重管理(29 页)✅
- `weight-main.html` — 体重管理主页(三态切换/英雄区/三态卡片/吃动平衡/体重记录/排行榜/推荐视频)
- `goal-commitment.html` — 目标承诺签订(活动/个人双类型+URL参数状态同步)
- `goal-management.html` — 目标管理(7种状态多卡片显隐+自我/活动目标切换)
@@ -40,6 +48,10 @@
- `weight-report.html` — 体重报告
- `eat-checkin-v2.html` — 饮食打卡v2(重构版)
- `weight-main-v2.html` — 体重管理主页v2(重构版)
+ - `diet-advice-v2.html` — 膳食建议v2(重构版)
+ - `eat-detail-v2.html` — 饮食详情v2(重构版)
+ - `scan-checkin.html` — 扫码打卡
+ - `weight-detail-v2.html` — 体重详情v2(重构版)
- `health-monitor/` — 健康监测(20+ 页)🔨 设计中
- `wearable/` — 穿戴设备监测(16 页:血氧/心率/压力/体温/步数/运动/睡眠/血压/心电+告警)
- `weight/` — 体重监测(3 页,已独立为 `weight-management/` 模块)
@@ -87,11 +99,11 @@
Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、健康评估、知识普及、健康数据、心脑血管/糖尿病/癌症预防等)均为待开发状态。
## 总计
-- 已完成/设计中:563+ 个 HTML 原型页面
+- 已完成/设计中:567+ 个 HTML 原型页面
- 覆盖模块:18 个(含三端+其他终端)
## 近期里程碑
-- 2026-07-24:营养管理后台净菜订单/外售功能增强——prod-clean-order 新增订单类型列/筛选、加工类型重命名、食谱快速创建订单弹窗(选日期→展示餐次菜品→勾选+人数→汇总表);sup-clean-external 新增预定配送时间列/表单字段/详情抽屉;小程序 eat-nutrition 膳食宝塔重构(饮水移至塔基+横排极矮样式+食物分类明细列表);体重管理新增 eat-checkin-v2 饮食打卡v2 和 weight-main-v2 体重管理主页v2。
+- 2026-07-24:营养管理后台主子表重构——净菜订单/外售/内供三页面数据模型全面重构为主子表(一单多品),表格展开显示菜品明细,弹窗重构为动态明细行构建器(支持多菜品添加/删除/联动/汇总),确认收货改为行级逐条确认;采购管理新增验收数量列及验收明细弹窗(逐项校验);净菜进柜/组配/烹制新增关联订单列与订单进度展示;净菜加工移除质检结果列。小程序端订单详情新增支付信息卡片(含支付方式/流水号/时间)与餐线订单渲染;我的订单新增餐线订单卡片与Tab切换过滤(全部/待取餐/待配送/已完成)。体重管理新增 diet-advice-v2 膳食建议v2、eat-detail-v2 饮食详情v2、scan-checkin 扫码打卡、weight-detail-v2 体重详情v2 四个重构版页面。
- 2026-07-23:营养管理后台与小程序多项增强——后台:场所新增/编辑页重构(营业时段/场所图片/资质证照 5 卡片区)、食材与餐品总库新增分类管理弹窗(树形增删改查)、食材新增/编辑页支持从已有食材复制;小程序:净菜包订单按状态差异化展示(待配送/配送中/已完成)、履约信息时间选择器重构为日期+时段联动下拉面板、新增收货地址管理页 me-address.html、健康档案移除营养目标分区;同步更新 CHANGELOG 及导航页。
- 2026-07-21:导航页 popover 事件冒泡回归再次修复(同一根因第二次出现)——0603113 提交将 popover 重构为延迟渲染模式(首次点击"X 页"才动态构建 DOM),但 `togglePop` 函数内 `insertAdjacentHTML` 模板漏加 `onclick="event.stopPropagation()"`,导致同样的双 tab 打开回归。修复方式:在延迟构建模板中补上 stopPropagation(commit f671764)。系统性教训:嵌套 onclick 结构中子容器的 stopPropagation 在代码重构时极易遗漏,后续涉及 popover/弹层 DOM 创建位置变更时需检查事件冒泡拦截。
- 2026-07-20:导航页 popover 事件冒泡回归修复——8a96ead 提交重构 popover 关闭逻辑时误删 `
` 上的 `onclick="event.stopPropagation()"`,导致 popover 内 `
` 点击事件冒泡到父级 `module-card` 触发 `window.open` 打开模块入口页,与 `` 默认跳转叠加造成点击"页面总览"等列表项时同时打开两个 tab。修复方式:恢复 popover 容器上的 stopPropagation,与新版本 document click 监听器(靠 `closest` 判断)兼容(commit 5c518cb)。
diff --git a/CLAUDE.md b/CLAUDE.md
index 84ef9bf..4ff0cdb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,4 +1,4 @@
-
+
# 项目约束
本项目所有工作必须严格遵守 `项目背景.md` 中定义的全部规范,包括:
diff --git a/doc/各模块异常数据分析.md b/doc/各模块异常数据分析.md
new file mode 100644
index 0000000..97459fe
--- /dev/null
+++ b/doc/各模块异常数据分析.md
@@ -0,0 +1,809 @@
+# 异常数据分析
+
+> **文档目的**:根据业务表结构,预先梳理各模块可能产生脏数据的数据库表及对应的脏数据类型,供后续脏数据检测开发使用。
+
+---
+
+## 目录
+
+- [一、脏数据类型汇总](#一脏数据类型汇总)
+- [二、健康评估](#二健康评估)
+ - [2.1 涉及数据库表](#21-涉及数据库表)
+ - [2.2 系统脏数据检测规则](#22-系统脏数据检测规则)
+ - [2.3 检测频率建议](#23-检测频率建议)
+- [三、健康咨询](#三健康咨询)
+ - [3.1 涉及数据库表](#31-涉及数据库表)
+ - [3.2 系统脏数据检测规则](#32-系统脏数据检测规则)
+ - [3.3 现有后端实现评估](#33-现有后端实现评估)
+ - [3.4 检测频率建议](#34-检测频率建议)
+- [四、应急就医](#四应急就医)
+ - [4.1 涉及数据库表](#41-涉及数据库表)
+ - [4.2 系统脏数据检测规则](#42-系统脏数据检测规则)
+ - [4.3 检测频率建议](#43-检测频率建议)
+- [五、营养管理](#五营养管理)
+ - [5.1 涉及数据库表](#51-涉及数据库表)
+ - [5.2 系统脏数据检测规则](#52-系统脏数据检测规则)
+ - [5.3 现有后端实现评估](#53-现有后端实现评估)
+ - [5.4 检测频率建议](#54-检测频率建议)
+- [六、健康监测](#六健康监测)
+ - [6.1 涉及数据库表](#61-涉及数据库表)
+ - [6.2 系统脏数据检测规则](#62-系统脏数据检测规则)
+ - [6.3 检测频率建议](#63-检测频率建议)
+
+---
+
+## 一、脏数据类型汇总
+
+| 脏数据类型 | 健康评估 | 健康咨询 | 应急就医 | 营养管理 | 健康监测 |
+|------------|:--:|:--:|:--:|:--:|:--:|
+| 数据缺失 | ✅ | ✅ | — | ✅ | ✅ |
+| 格式错误 | ✅ | ✅ | — | ✅ | ✅ |
+| 逻辑矛盾 | ✅ | ✅ | ✅ | ✅ | ✅ |
+| 重复数据 | ✅ | — | — | ✅ | ✅ |
+| 范围越界 | ✅ | ✅ | — | ✅ | ✅ |
+| 关联缺失 | ✅ | ✅ | ✅ | ✅ | ✅ |
+| 状态异常 | ✅ | ✅ | ✅ | — | — |
+| 非法数据 | — | — | ✅ | — | — |
+| 异常坐标 | — | — | ✅ | — | — |
+
+---
+
+## 二、健康评估
+
+> **后端服务**:`platform-assessment`(端口 24803)| **原型参考**:评估模块原型使用摹刀独立设计
+>
+> **开发状态**:⚠️ 后端已有 `AssDataAbnormalController` 占位(返回硬编码 mock 数据)。
+
+健康评估模块对员工进行多维度健康评估,包括风险评估、心理评估、规律起居评估、人群分类等。
+
+### 2.1 涉及数据库表
+
+评估模块共 21 张业务表,按子域分类如下。
+
+#### 风险评估(7+2 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `ass_risk_evaluation` | 风险评估主表(问卷+自动双模式) | 高 — `eval_source`(评估来源:1=问卷/2=自动)、`status`(任务状态:1=成功/2=处理中/3=失败)、`is_exception`(是否异常:0=否/1=是)、`user_id`(用户ID) |
+| `ass_risk_result` | 风险评估结果(疾病等级、BMI、生活习惯标记) | 高 — `bmi`(BMI值)、`sbp`(收缩压)/`dbp`(舒张压)、`fasting_bg`(空腹血糖)、`diabetes_level`(糖尿病等级)、`bmi_mark`(BMI标记格式) |
+| `ass_risk_evaluation_statistics` | 风险评估统计(按用户/年份汇总) | 中 — `statics_num`(统计评估次数)、`exception_num`(异常评估次数) |
+| `ass_risk_factor` | 风险因素明细 | 中 — `current_value`(当前检测值)、`reference_range`(参考范围)、`is_abnormal`(是否异常) |
+| `ass_risk_questionnaire_info` | 问卷基础信息(病史、生活方式) | 高 — `height`(身高cm)/`weight`(体重kg)/`waist_line`(腰围cm)、`smoking_status`(吸烟状态:1-3) |
+| `ass_risk_diet_sports_prescription` | 饮食运动处方 | 中 — `f_elements`(食物元素格式)、`aerobic_*`(有氧运动)、`strength_*`(力量训练)、`flexibility_*`(柔韧训练) |
+| `ass_risk_hms_log` | 第三方HMS接口调用日志 | 低 — XML 格式完整性 |
+| `ass_risk_request_cache` | MD5请求去重缓存 | 低 — 缓存过期/碰撞 |
+| `ass_risk_food_element` | 食物营养元素字典 | 低 — 字典数据完整性 |
+
+#### 心理评估(3 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `ass_psychology` | 心理评估主表(综合/压力/焦虑/抑郁) | 高 — `type`(评估类型:1=综合/2=压力/3=焦虑/4=抑郁)、`grade`(评估得分)、`phy_grade_level`(健康等级)、`is_exception`(是否异常)、`status`(任务状态) |
+| `ass_psychology_statistics` | 心理评估统计 | 中 — 统计值与实际记录数一致性 |
+| `ass_influence_factor` | 心理影响因素评估 | 中 — 因子数据完整性 |
+
+#### 规律起居(2 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `ass_daily_routine_online` | 在线穿戴数据评估(睡眠/运动/心率/血氧/压力) | 高 — `sleep_hour`(睡眠时长分钟)、`sport_minute`(运动时长分钟)、`resting_heart_rate`(静息心率bpm)、`oxygen_saturation`(血氧饱和度%)、`week_start_date`(周开始日期)/`week_end_date`(周结束日期) |
+| `ass_daily_routine_paper` | 问卷规律起居(体力活动/睡眠/上网) | 中 — `sport_value`(运动值)、`sleep_value`(睡眠值)、`time_of_net_value`(上网时长)、`questions_id`(关联问卷ID) |
+
+#### 人群分类(4 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `ass_group_user` | 人群分类主表(A/B/C/D/E五类) | 高 — `user_group`(人群分类枚举:A/B/C/D/E)、`audit_status`(审核状态:0-2)、`is_archived`(是否归档)、`latest_assessment_date`(最近评估日期) |
+| `ass_group_user_detail` | 人群分类明细(疾病/体检项) | 中 — `detail_type`(明细类型:A/B/C/D)、`item_code`(项目编码)、`risk_level_flag`(风险等级标记) |
+| `ass_group_user_change_his` | 分类变更历史 | 低 — 变更记录完整性 |
+| `ass_group_user_audit_his` | 分类审核历史 | 低 — 审核记录完整性 |
+
+#### 共享/基础(3 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `ass_questions` | 问卷答案(JSON) | 中 — `questions_json`(问卷JSON)、`status`(状态:卡处理中) |
+| `ass_algorithm_model` | 算法模型文档 | 低 |
+| `ass_trigger_config` | 评估触发器配置 | 低 |
+
+### 2.2 系统脏数据检测规则
+
+> 参照营养模块 `nut_ano_dirty` 模式。mock 数据中定义了四种异常类型:缺失值、超范围值、重复数据、格式错误。
+
+#### 风险评估
+
+**评估主表(`ass_risk_evaluation`)**
+
+| 异常类型 | 检测规则 | 涉及字段 | 说明 |
+|----------|----------|----------|------|
+| 数据缺失 | `user_id` 为 NULL | `user_id`(用户ID) | 评估记录无归属人 |
+| 范围越界 | `eval_source` 不在 1-2 范围内 | `eval_source`(评估来源) | 评估来源异常(1=问卷/2=自动) |
+| 状态异常 | `status` = 2(处理中)或 3(失败)超过 24 小时 | `status`(任务状态) | 评估任务卡住未完成 |
+| 逻辑矛盾 | `is_exception` = 1 但 `ass_risk_result` 中所有疾病等级均为正常 | `is_exception`(是否异常) ↔ `ass_risk_result.*_level`(疾病等级) | 异常标记与结果数据不一致 |
+
+**评估结果(`ass_risk_result`)**
+
+| 异常类型 | 检测规则 | 涉及字段 | 说明 |
+|----------|----------|----------|------|
+| 范围越界 | BMI 值 < 10 或 > 80 | `bmi`(BMI值) | 超出人体可能范围 |
+| 范围越界 | 收缩压 < 50 或 > 300 mmHg | `sbp`(收缩压) | 血压值异常 |
+| 范围越界 | 舒张压 < 30 或 > 200 mmHg | `dbp`(舒张压) | 血压值异常 |
+| 逻辑矛盾 | `sbp` < `dbp` | `sbp`(收缩压)、`dbp`(舒张压) | 收缩压低于舒张压 |
+| 范围越界 | `fasting_bg`(空腹血糖)< 0 或 > 50 mmol/L | `fasting_bg`(空腹血糖) | 血糖值异常 |
+| 范围越界 | `health_age` / `potential_health_age` 计算为负数或 > 150 | `health_age`(健康年龄)、`potential_health_age`(潜在健康年龄) | 健康年龄异常 |
+| 格式错误 | `bmi_mark` / `smoke_mark` / `sleep_mark` 等不符合 `state,score,fullmark` 格式 | `*_mark` 字段(生活习惯标记) | 生活习惯标记格式异常 |
+| 范围越界 | 疾病等级(`diabetes_level`、`hypertension_level` 等)不在预期枚举范围内 | `*_level` 字段(疾病等级) | 等级编码越界 |
+| 逻辑矛盾 | `cholesterol`、`triglycerin`、`hdlc`、`ldlc` 均为 0 或 NULL 但疾病等级非空 | 血脂四项 + `*_level`(疾病等级) | 血脂数据缺失但给出了疾病评级 |
+
+**问卷信息(`ass_risk_questionnaire_info`)**
+
+| 异常类型 | 检测规则 | 涉及字段 | 说明 |
+|----------|----------|----------|------|
+| 范围越界 | 身高 < 50cm 或 > 250cm | `height`(身高cm) | 录入异常 |
+| 范围越界 | 体重 < 20kg 或 > 300kg | `weight`(体重kg) | 录入异常 |
+| 范围越界 | 腰围 < 30cm 或 > 200cm | `waist_line`(腰围cm) | 录入异常 |
+| 范围越界 | `smoking_status` 不在 1-3 | `smoking_status`(吸烟状态) | 吸烟状态枚举越界 |
+| 范围越界 | 个人病史字段(`personal_diabetes` 等)不为 0 或 1 | `personal_*` 字段(个人病史) | 病史枚举越界 |
+| 数据缺失 | `height`、`weight`、`waist_line` 均为 NULL | `height`(身高)、`weight`(体重)、`waist_line`(腰围) | 基础体测数据完全缺失 |
+| 格式错误 | 问卷 JSON 原始数据格式异常 | `questions_json`(问卷JSON,通过 `ass_questions`) | JSON 解析失败 |
+
+**评估统计(`ass_risk_evaluation_statistics`)**
+
+| 异常类型 | 检测规则 | 涉及字段 | 说明 |
+|----------|----------|----------|------|
+| 逻辑矛盾 | `statics_num` 与 `ass_risk_evaluation` 中实际评估次数不一致 | `statics_num`(统计评估次数) | 统计数据过期 |
+| 逻辑矛盾 | `is_bmi_abnormal` 等疾病异常标记与 `last_evaluation_id` 对应结果数据不一致 | `is_*_abnormal` 字段(疾病异常标记) | 统计标记与源数据脱节 |
+| 逻辑矛盾 | `exception_num` 与实际 `is_exception=1` 的记录数不一致 | `exception_num`(异常计数) | 异常计数错误 |
+
+#### 心理评估
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 范围越界 | `type` 不在 1-4(综合/压力/焦虑/抑郁) | `ass_psychology` | `type`(评估类型) |
+| 范围越界 | `grade` 超出该量表合理分值范围 | `ass_psychology` | `grade`(评估得分) |
+| 数据缺失 | `grade` 或 `phy_grade_level` 为 NULL | `ass_psychology` | `grade`(评估得分)、`phy_grade_level`(健康等级) |
+| 格式错误 | `phy_grade_level` 编码不在数据字典范围内 | `ass_psychology` | `phy_grade_level`(健康等级编码) |
+| 状态异常 | `status` = 2(分析中)超过 24 小时 | `ass_psychology` | `status`(任务状态) |
+| 逻辑矛盾 | `is_exception` = 1 但 `grade` 在正常范围内 | `ass_psychology` | `is_exception`(是否异常) ↔ `grade`(评估得分) |
+| 关联缺失 | `questions_id` 在 `ass_questions` 中不存在 | `ass_psychology` | `questions_id`(关联问卷ID) |
+| 逻辑矛盾 | `ass_psychology_statistics` 统计值与实际评估记录数不一致 | `ass_psychology_statistics` | — |
+
+#### 规律起居
+
+**在线穿戴评估(`ass_daily_routine_online`)**
+
+| 异常类型 | 检测规则 | 涉及字段 | 说明 |
+|----------|----------|----------|------|
+| 范围越界 | `sleep_hour` < 0 或 > 1440 分钟 | `sleep_hour`(睡眠时长分钟) | 睡眠时长不合理 |
+| 范围越界 | `sport_minute` < 0 或 > 1440 分钟 | `sport_minute`(运动时长分钟) | 运动时长不合理 |
+| 范围越界 | `resting_heart_rate` < 30 或 > 220 bpm | `resting_heart_rate`(静息心率bpm) | 静息心率异常 |
+| 范围越界 | `oxygen_saturation` < 60% 或 > 100% | `oxygen_saturation`(血氧饱和度%) | 血氧值异常 |
+| 范围越界 | `pressure_value` 超出传感器量程 | `pressure_value`(压力值) | 压力值异常 |
+| 逻辑矛盾 | `week_end_date` < `week_start_date` | `week_end_date`(周结束日期)、`week_start_date`(周开始日期) | 周起止日期倒置 |
+| 逻辑矛盾 | `sleep_hour` + `sport_minute` > 1440 | `sleep_hour`(睡眠时长)、`sport_minute`(运动时长) | 睡眠+运动超过24小时 |
+| 状态异常 | `status` = 2(分析中)超过 24 小时 | `status`(任务状态) | 任务卡住 |
+| 数据缺失 | `is_exception` 为 NULL | `is_exception`(是否异常) | 异常标记缺失 |
+
+**问卷评估(`ass_daily_routine_paper`)**
+
+| 异常类型 | 检测规则 | 涉及字段 | 说明 |
+|----------|----------|----------|------|
+| 范围越界 | `sport_value` / `sleep_value` / `time_of_net_value` 为负值 | `sport_value`(运动值)、`sleep_value`(睡眠值)、`time_of_net_value`(上网时长) | 负数无意义 |
+| 范围越界 | `time_of_net_value` > 24 小时 | `time_of_net_value`(上网时长) | 上网时长不合理 |
+| 范围越界 | `sport_level` / `sleep_type` / `time_of_net_level` 不在枚举范围 | `sport_level`(运动等级)、`sleep_type`(睡眠类型)、`time_of_net_level`(上网等级) | 等级编码越界 |
+| 关联缺失 | `questions_id` 在 `ass_questions` 中不存在 | `questions_id`(关联问卷ID) | 孤立问卷关联 |
+| 数据缺失 | `is_exception` 为 NULL | `is_exception`(是否异常) | 异常标记缺失 |
+
+#### 人群分类
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 范围越界 | `user_group` 不在 A/B/C/D/E 范围内 | `ass_group_user` | `user_group`(人群分类) |
+| 范围越界 | `group_change_type` 不在 1-3 | `ass_group_user` | `group_change_type`(变更类型) |
+| 范围越界 | `audit_status` 不在 0-2 | `ass_group_user` | `audit_status`(审核状态) |
+| 逻辑矛盾 | `is_archived` = 1 且 `archived_time` 为 NULL | `ass_group_user` | `is_archived`(是否归档)、`archived_time`(归档时间) |
+| 范围越界 | `date_year` 为未来年份或早于 2020 | `ass_group_user` | `date_year`(归属年份) |
+| 范围越界 | `latest_assessment_date` 为未来日期 | `ass_group_user` | `latest_assessment_date`(最近评估日期) |
+| 关联缺失 | `group_user_id` 在 `ass_group_user` 中不存在 | `ass_group_user_detail` | `group_user_id`(人群分类ID) |
+| 范围越界 | `detail_type` 不在 A/B/C/D | `ass_group_user_detail` | `detail_type`(明细类型) |
+| 范围越界 | `data_type` 不在 1-2 | `ass_group_user_detail` | `data_type`(数据类型) |
+| 重复数据 | 同一用户同一年度存在多条活跃分类记录 | `ass_group_user` | `user_id`(用户ID) + `date_year`(归属年份) |
+
+#### 问卷与处方
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 格式错误 | `questions_json` 格式非法(JSON 解析失败) | `ass_questions` | `questions_json`(问卷JSON) |
+| 状态异常 | `status` = 2(分析中)超过 24 小时 | `ass_questions` | `status`(任务状态) |
+| 格式错误 | `f_elements` 不符合 `meal_foodCode_amount;...` 格式 | `ass_risk_diet_sports_prescription` | `f_elements`(食物元素格式) |
+| 范围越界 | `energy_level` 为负值或 > 10000 kcal | `ass_risk_diet_sports_prescription` | `energy_level`(能量等级) |
+| 数据缺失 | 评估正常但处方中所有运动字段为 NULL | `ass_risk_diet_sports_prescription` | `aerobic_*`(有氧运动)、`strength_*`(力量训练)、`flexibility_*`(柔韧训练) |
+
+### 2.3 检测频率建议
+
+| 检测类型 | 建议频率 | 说明 |
+|----------|----------|------|
+| 评估任务卡住检测 | 每小时 | `status=2` 超过 24 小时的记录 |
+| 数值范围越界检测 | 每日凌晨(评估任务完成后) | 全量扫描 BMI/血压/心率等 |
+| 统计一致性校验 | 每日凌晨 | `statics_num` vs 实际记录数 |
+| 格式校验(JSON/mark) | 数据入库时实时 | 问卷JSON、`f_elements`、`*_mark` |
+
+---
+
+## 三、健康咨询
+
+> **后端服务**:`platform-consult`(端口 24806)| **原型参考**:无独立原型页面
+>
+> **开发状态**:✅ 已实现 `con_exception_dirty` 表 + 3 种异常类型定时检测,管理端分页查询/忽略/导出功能完整。
+
+健康咨询模块管理员工在线图文/视频咨询全流程,涵盖咨询会话、专家管理、病历档案、IM 消息、评价结算等。
+
+### 3.1 涉及数据库表
+
+#### 咨询核心(4 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `con_consult` | 咨询会话主表(图文/视频统一) | 高 — `consult_no`(咨询单号)、`content_status`(预约/咨询状态:1-8)、`session_status`(会话状态:1=进行中/2=已结束)、`start_time`(开始时间)/`end_time`(结束时间)、`duration_minutes`(咨询时长秒)、`score`(用户评分1-5)、`cost`(咨询费用) |
+| `con_evaluate` | 咨询评价表 | 高 — `score`(评分1-5)、`consult_id`(关联咨询ID)、`expert_id`(被评价专家ID) |
+| `con_medical_record` | 病历档案表 | 中 — `medical_describe`(病情描述)、`duration_type`(持续时间:1-4)、`images`(检查资料图片URL) |
+| `con_family_member` | 家庭成员/咨询人表 | 中 — `member_name`(成员姓名)、`birthday`(出生日期)、`height`(身高cm)、`weight`(体重kg)、`relation`(家庭关系) |
+
+#### 专家与排班(2 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `con_expert` | 专家信息表 | 高 — `expert_no`(专家编号)、`expert_type`(专家类型:1=收费/2=免费/3=其他)、`overall_merit`(综合评分)、`response_rate`(24h回复率)、`hospital_id`(所属医院ID)、`dept_id`(所属科室ID)、`reg_date`(入职日期)/`off_date`(离职日期) |
+| `con_expert_schedule_rule` | 专家排班规则表(按周设置) | 中 — `week_day`(周几:1-7)、`period`(时段:1-4)、`start_time`(出诊开始时间)/`end_time`(出诊结束时间)、`capacity`(接诊人数上限) |
+
+#### IM 消息(2 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `con_im_message` | IM消息记录表 | 中 — `msg_key`(腾讯IM消息唯一标识)、`msg_type`(消息类型:1=单聊/2=群聊/3=应急群聊/4=音视频)、`msg_content`(消息内容)、`send_time`(发送时间)、`consult_id`(关联咨询ID) |
+| `con_im_chat` | IM聊天会话表(通讯录单聊) | 中 — `session_id`(会话ID)、`pair_id`(会话对唯一标识)、`from_user_id`(发起者)、`to_user_id`(接受者) |
+
+#### 结算(4 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `con_settle_main` | 结算主表 | 中 — `settle_status`(结算状态:0=待结算/1=结算中/2=已结算/3=异常)、`com_fee_per_month`(每月通讯费)、`error_msg`(异常信息) |
+| `con_settle_result` | 结算结果 | 中 — 金额一致性 |
+| `con_settle_result_item` | 结算结果明细 | 中 — 明细与汇总一致性 |
+| `con_settle_cost_config` | 费用配置 | 低 — 配置值合理性 |
+
+#### 基础数据(4 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `con_hospital` | 医院关联配置 | 中 — `ref_id`(关联外部医院ID) |
+| `con_hospital_dept` | 医院-科室关联表 | 低 — `hospital_id`(医院ID)、`dept_id`(科室ID) |
+| `con_department` | 科室表 | 中 — `dept_name`(科室名称)、`parent_id`(上级科室ID) |
+| `con_disease` | 疾病信息表 | 低 — `disease_name`(疾病名称)、`dept_id`(所属科室ID) |
+
+#### 其他(4 张表)
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `con_notice` | 咨询须知表 | 低 |
+| `con_phrase` | 常用语表 | 低 |
+| `con_settle_period_config` | 结算周期配置 | 低 |
+
+### 3.2 系统脏数据检测规则
+
+> 后端已实现 `con_exception_dirty` 表,目前定义了 3 种异常类型:咨询时长异常(`exception_type=1`)、长时间未回复(`exception_type=2`)、评价分数极低(`exception_type=3`)。
+>
+> ⚠️ **已知问题**:`ConConsultTimeoutJob` 每小时自动关闭超时 7 天的会话,因此"咨询时长异常"检测的阈值必须设置在 7 天以内,否则异常记录永远无法生成。
+>
+> ⚠️ **已知缺陷**:`ConExceptionDirtyMapper.xml` 第 29 行 `work_no` 被硬编码为 `NULL`,前端展示员工工号列为空。
+>
+> ⚠️ **已知缺陷**:`ConExceptionDirtyMapper.xml` 中 `h."name" AS hospital_name` 引用了 `con_hospital` 表中可能不存在的 `name` 列(`con_hospital` 实体仅有 `ref_id` 无 `name`),需确认运行时是否会报错或从外部服务取数。
+
+#### 咨询会话(已有)
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 状态异常 | 咨询时长异常:会话进行中但 `duration_minutes` 超过合理阈值(如 > 60 分钟) | `con_consult` | `duration_minutes`(咨询时长)、`content_status`(预约/咨询状态) | `exception_type=1`,需在超时 Job(7天)前触发 |
+| 状态异常 | 长时间未回复:专家超过 N 小时未回复用户消息 | `con_consult` | `last_reply_time`(最后回复时间)、`content_status`(预约/咨询状态) | `exception_type=2` |
+| 范围越界 | 评价分数极低:`score` < 2 分 | `con_evaluate` / `con_consult` | `score`(评分) | `exception_type=3` |
+
+#### 咨询会话(建议新增)
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 逻辑矛盾 | `start_time` > `end_time` | `con_consult` | `start_time`(开始时间)、`end_time`(结束时间) | 咨询结束时间早于开始时间 |
+| 逻辑矛盾 | `session_status` = 进行中(1) 但 `end_time` 不为空 | `con_consult` | `session_status`(会话状态)、`end_time`(结束时间) | 状态与时间矛盾 |
+| 逻辑矛盾 | `content_status` = 已完成(3) 但 `end_time` 为空 | `con_consult` | `content_status`(预约/咨询状态)、`end_time`(结束时间) | 已完成但无结束时间 |
+| 逻辑矛盾 | `content_status` = 已拒绝(8) 但 `reject_reason` 为空 | `con_consult` | `content_status`(预约/咨询状态)、`reject_reason`(拒绝原因) | 拒绝但无理由 |
+| 范围越界 | `duration_minutes` < 0 | `con_consult` | `duration_minutes`(咨询时长) | 咨询时长为负数 |
+| 范围越界 | `consult_type` 不在 1-2 | `con_consult` | `consult_type`(咨询类型:1=图文/2=视频) | 咨询类型枚举越界 |
+| 范围越界 | `content_status` 不在 1-8 | `con_consult` | `content_status`(预约/咨询状态) | 内容状态枚举越界 |
+| 范围越界 | `cost` < 0 | `con_consult` | `cost`(咨询费用) | 咨询费用为负数 |
+| 关联缺失 | `expert_id` 在 `con_expert` 中不存在 | `con_consult` | `expert_id`(专家ID) | 关联专家已删除 |
+| 关联缺失 | `member_id` 不为空但在 `con_family_member` 中不存在 | `con_consult` | `member_id`(咨询人ID) | 关联咨询人已删除 |
+| 数据缺失 | `consult_no` 为空 | `con_consult` | `consult_no`(咨询单号) | 咨询单号缺失 |
+
+#### 专家信息
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 范围越界 | `overall_merit` < 0 或 > 5 | `con_expert` | `overall_merit`(综合评分) | 综合评分越界 |
+| 范围越界 | `response_rate` < 0 或 > 100 | `con_expert` | `response_rate`(24h回复率%) | 回复率越界 |
+| 范围越界 | `expert_type` 不在 1-3 | `con_expert` | `expert_type`(专家类型) | 专家类型枚举越界 |
+| 数据缺失 | `expert_no` 为空 | `con_expert` | `expert_no`(专家编号) | 专家编号缺失 |
+| 逻辑矛盾 | `off_date` < `reg_date`(均已填写时) | `con_expert` | `off_date`(离职日期)、`reg_date`(入职日期) | 离职日期早于入职日期 |
+| 关联缺失 | `hospital_id` 在 `con_hospital` 中不存在 | `con_expert` | `hospital_id`(所属医院ID) | 关联医院不存在 |
+| 关联缺失 | `dept_id` 在 `con_department` 中不存在 | `con_expert` | `dept_id`(所属科室ID) | 关联科室不存在 |
+
+#### 咨询评价
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 范围越界 | `score` < 0 或 > 5 | `con_evaluate` | `score`(评分) | 评分越界 |
+| 关联缺失 | `consult_id` 在 `con_consult` 中不存在 | `con_evaluate` | `consult_id`(关联咨询ID) | 评价关联的咨询不存在 |
+| 关联缺失 | `expert_id` 在 `con_expert` 中不存在 | `con_evaluate` | `expert_id`(被评价专家ID) | 评价关联的专家不存在 |
+
+#### 家庭成员
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 范围越界 | `height` < 50 或 > 250 (cm) | `con_family_member` | `height`(身高cm) | 身高越界 |
+| 范围越界 | `weight` < 20 或 > 300 (kg) | `con_family_member` | `weight`(体重kg) | 体重越界 |
+| 范围越界 | `sex` 不在 1-2 | `con_family_member` | `sex`(性别:1=男/2=女) | 性别枚举越界 |
+| 范围越界 | `birthday` 为未来日期 | `con_family_member` | `birthday`(出生日期) | 生日为未来日期 |
+
+#### 病历档案
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 数据缺失 | `medical_describe` 为空 | `con_medical_record` | `medical_describe`(病情描述) | 病情描述缺失 |
+| 范围越界 | `duration_type` 不在 1-4 | `con_medical_record` | `duration_type`(持续时间:1=几天内/2=1-2周/3=1-3月/4=3月以上) | 持续时间枚举越界 |
+
+#### IM 消息
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 数据缺失 | `msg_content` 为空且非撤回消息 | `con_im_message` | `msg_content`(消息内容) | 消息内容缺失 |
+| 范围越界 | `msg_type` 不在 1-4 | `con_im_message` | `msg_type`(消息类型) | 消息类型枚举越界 |
+| 关联缺失 | `consult_id` 不为空但在 `con_consult` 中不存在 | `con_im_message` | `consult_id`(关联咨询ID) | 消息关联的咨询不存在 |
+
+#### 排班规则
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 范围越界 | `week_day` 不在 1-7 | `con_expert_schedule_rule` | `week_day`(星期:1=周一~7=周日) | 星期枚举越界 |
+| 范围越界 | `period` 不在 1-4 | `con_expert_schedule_rule` | `period`(时段:1-4) | 时段枚举越界 |
+| 范围越界 | `capacity` < 0 | `con_expert_schedule_rule` | `capacity`(接诊人数上限) | 接诊人数为负 |
+| 逻辑矛盾 | `end_time` < `start_time` | `con_expert_schedule_rule` | `start_time`(出诊开始时间)、`end_time`(出诊结束时间) | 排班结束时间早于开始时间 |
+
+### 3.3 现有后端实现评估
+
+#### 已实现
+
+| 组件 | 状态 | 说明 |
+|------|:--:|------|
+| `con_exception_dirty` 表 | ✅ 已上线 | 5个字段:consult_id、consult_no、exception_type、exception_value、alarm_time |
+| `ConExceptionDirtyController` | ✅ 已上线 | `/page` 分页查询 + `/ignore` 批量忽略 + `/export` 导出 |
+| `ConExceptionDirtyService` | ✅ 已上线 | 分页 JOIN 咨询/专家/医院 + Feign 批量查用户名 |
+| `ExceptionTypeEnum` | ✅ 已上线 | 3 种异常类型:时长异常/长时间未回复/低分评价 |
+| `ConConsultTimeoutJob` | ✅ 已上线 | 每小时扫描,7 天超时自动关闭会话 |
+
+#### 待完善
+
+| 项目 | 优先级 | 说明 |
+|------|:--:|------|
+| 异常数据自动检测定时任务 | 高 | 需实现定时扫描生成 `con_exception_dirty` 记录 |
+| `work_no` 字段补充 | 高 | 当前 Mapper XML 硬编码为 NULL,需 JOIN `con_user_snapshot` 获取 |
+| 咨询时长异常阈值与超时 Job 的协调 | 高 | 时长异常应在超时 Job 触发前检测(如 > 60 分钟即告警,而非等 7 天) |
+| 专家/家庭成员/病历/排班等数据校验 | 中 | 当前仅覆盖咨询会话维度的 3 种异常 |
+| IM 消息数据完整性校验 | 低 | 消息内容、关联关系 |
+
+### 3.4 检测频率建议
+
+| 检测类型 | 建议频率 | 说明 |
+|----------|----------|------|
+| 咨询时长异常检测 | 每 30 分钟 | 需在超时 Job(7天)前触发,建议 > 60 分钟即告警 |
+| 长时间未回复检测 | 每小时 | 检查 `last_reply_time` 超过阈值 |
+| 低分评价检测 | 评价生成时实时 | 评价提交后即时判断 |
+| 枚举/范围/逻辑矛盾校验 | 每日凌晨 | 全量扫描各表枚举字段和数值范围 |
+
+---
+
+## 四、应急就医
+
+> **后端服务**:`platform-emergency`(端口 24808)| **原型参考**:`web-admin/emergency-dispatch/admin/exception-dirty.html`
+>
+> **开发状态**:⚠️ 后端已有 `EmDirtyDataController` 占位(返回空数据),`EmExceptionDictConstant` 定义常量;尚未实现具体检测和存储。
+
+应急就医管理员工应急工单、云坐席通话、AED 设备部署与管理、救护车调度等。脏数据类型与其他模块差异较大,主要关注 **AED 设备**数据质量和**通话记录**完整性。
+
+### 4.1 涉及数据库表
+
+#### 核心应急表
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `em_emergency_center` | 应急中心 | 中 — `longitude`(经度)/`latitude`(纬度)、`center_phone`(中心电话)、`status`(状态) |
+| `em_user_profile` | 应急人员档案 | 中 — `cert_number`(证书编号)、`license_number`(执照编号)、`station_status`(驻场状态) |
+| `em_call_record` | 通话记录(七陌云客服) | 高 — `incoming_phone`(来电号码)、`match_status`(匹配状态:1=唯一匹配/2=多重匹配)、`employee_id`(匹配员工ID)、`duration`(通话时长) |
+| `em_emergency_order` | 应急工单 | 高 — `order_no`(工单编号)、`status`(工单状态)、`location_desc`(位置描述)、`location_lat`(纬度)/`location_lng`(经度)、`closed_at`(关闭时间) |
+| `em_work_order_dispatch` | 工单调度日志 | 中 — 调度链路完整性 |
+
+#### AED 设备管理
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `em_aed_device` | AED 设备主表 | 高 — `battery_level`(电池电量百分比)、`battery_status`(电池状态枚举) |
+| `em_aed_deploy_point` | AED 部署点位 | 高 — `longitude`(经度)、`latitude`(纬度) |
+| `em_aed_maintenance` | AED 维护记录 | 中 — 维护日期、到期提醒 |
+| `em_aed_latest` | AED 最新状态 | 高 — `battery_power`(厂商原始电池数据String)、实时状态聚合 |
+| `em_aed_warn_info` | AED 告警信息 | 中 — 告警记录 |
+| `em_aed_usage` | AED 使用记录 | 中 — 使用时间、使用人 |
+
+#### 其他应急表
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `em_ambulance` | 救护车 | 中 — `plate_no`(车牌号)、`driver`(驾驶员)、`status`(状态) |
+| `em_schedule_record` | 值班排班记录 | 中 — 排班时间冲突 |
+| `em_emergency_contact` | 紧急联系人 | 中 — 联系方式有效性 |
+| `em_emergency_center_call_record` | 应急中心通话记录 | 中 — `call_id`(通话ID)、`employee_id`(员工ID)、`begin_time`(开始时间)、`end_time`(结束时间)、`record_file`(录音文件) |
+| `em_emergency_center_call_join` | 应急中心通话参与人 | 中 — 参与人关联完整性、`employee_id`(员工ID) |
+| `em_hospital` | 应急合作医院 | 低 |
+| `em_first_aid_kit` | 急救箱 | 中 — 过期检查 |
+| `em_drill_record` | 演练记录 | 低 |
+
+### 4.2 系统脏数据检测规则
+
+> 应急模块使用独特的异常类型分类:非法数据 / 异常坐标 / 状态异常。
+>
+> **注意**:后端 `EmExceptionDictConstant` 当前定义了 `MISSING_FIELD` / `INVALID_FORMAT` / `ORPHAN_RECORD`,与原型中的 `非法数据` / `异常坐标` / `状态异常` 分类不一致,需后续对齐。
+
+#### 通话记录
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 非法数据 | 电话号码格式不合法(非国内手机号/非座机号) | `em_call_record` | `incoming_phone`(来电号码) | 云客服传入的号码非法 |
+| 状态异常 | `match_status` 为"多重匹配"(=2),存在多个员工对应同一号码 | `em_call_record` | `match_status`(匹配状态)、`employee_id`(匹配员工ID) | 一个号码匹配到多名员工,需人工确认 |
+| 关联缺失 | 通话记录关联的 `order_id` 在 `em_emergency_order` 中不存在 | `em_call_record` | `order_id`(关联工单ID) | 孤立的工单关联 |
+| 逻辑矛盾 | 通话结束时间早于开始时间 | `em_call_record` | `begin_time`(开始时间)、`end_time`(结束时间) | 时间戳异常 |
+| 逻辑矛盾 | 通话状态为"已接听"但时长为 0 | `em_call_record` | `answer_status`(接听状态) + `duration`(通话时长) | 状态与数据矛盾 |
+
+#### AED 设备管理
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 | 说明 |
+|----------|----------|--------|----------|------|
+| 非法数据 | 电池电量百分比超出 0-100% 范围(如 = 260%) | `em_aed_device` | `battery_level`(电池电量百分比) | 硬件上传了不可能的电量值 |
+| 非法数据 | 设备上报的原始电池电压/电量(String 类型,厂商原始值) | `em_aed_latest` | `battery_power`(厂商原始电池数据) | 厂商原始电池数据,非 0-100% 百分比 |
+| 非法数据 | 电池状态值不在预定义枚举范围内 | `em_aed_device` | `battery_status`(电池状态) | 状态码异常,如"未知" |
+| 异常坐标 | GPS 坐标不在国内范围(经度 73-135°E,纬度 18-54°N) | `em_aed_deploy_point` | `longitude`(经度)、`latitude`(纬度) | 如坐标指向海外(悉尼) |
+| 异常坐标 | GPS 坐标为 (0,0) 或 NULL(未获取到定位) | `em_aed_deploy_point` | `longitude`(经度)、`latitude`(纬度) | AED 部署点位无有效坐标 |
+| 关联缺失 | AED 部署点位关联的应急中心不存在 | `em_aed_deploy_point` ↔ `em_emergency_center` | 中心关联字段 | |
+| 逻辑矛盾 | AED 最近一次维护日期晚于当前日期 | `em_aed_maintenance` | 维护日期 | 未来日期,数据录入错误 |
+| 数据缺失 | AED 设备缺少维护记录 | `em_aed_device` ↔ `em_aed_maintenance` | | 设备部署后从未维护 |
+
+> **注意**:AED 设备离线检测(基于心跳时间差)当前暂不可用,因为 `em_aed_latest` 表缺少 `update_time` 字段。需后续新增该字段。
+
+### 4.3 检测频率建议
+
+| 检测类型 | 建议频率 | 说明 |
+|----------|----------|------|
+| 系统脏数据 — AED 电池/坐标 | 每日凌晨 | AED 设备数据量小 |
+| 系统脏数据 — 通话记录非法数据 | 每日凌晨 | 通话记录增量扫描 |
+| AED 设备离线检测 | 每小时 | `em_aed_latest` 心跳超时判断 |
+
+---
+
+## 五、营养管理
+
+> **后端服务**:`platform-nutrition`(端口 24810)| **原型参考**:`web-admin/nutrition/ano-dirty.html`
+>
+> **开发状态**:✅ 已实现脏数据存储表 `nut_ano_dirty`,服务已上线。
+
+营养管理是系统最大的模块(约 130 张表),涵盖菜品/食谱管理、订餐/取餐、农场种植养殖、原材料采购库存、中央厨房生产、营养算法与 AI 推荐等全链路。
+
+### 5.1 涉及数据库表
+
+#### 用户与档案
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_user` | 营养用户(用餐者) | 高 — `phone`(手机号)、`bmi`(BMI值)、`health_tags`(健康标签) |
+| `nut_user_profile` | 用户健康档案 | 高 — `height`(身高)、`weight`(体重)、`target_weight`(目标体重)、`allergies`(过敏原)、`dietary_restrictions`(饮食限制) |
+| `nut_user_account` | 用户账户/充值 | 中 — 账户余额字段 |
+| `nut_user_face` | 面部识别数据 | 中 — 面部嵌入向量有效性 |
+| `nut_member` | 会员记录 | 中 — 会员有效期 |
+
+#### 食品与食谱
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_food` | 菜品主表 | 高 — `price`(价格)、`nutrition_info`(营养信息)、`status`(状态) |
+| `nut_food_class` | 菜品分类 | 中 — `parent_id`(上级分类ID) 自引用完整性 |
+| `nut_food_composition` | 食物成分 | 高 — `value_per_100g`(每100g营养值) |
+| `nut_food_nutri` | 食物营养数据 | 高 — 详细营养分解 |
+| `nut_food_spec` | 食物规格/份量 | 中 — `weight`(份量重量) |
+| `nut_food_check` | 食品检测记录 | 中 — `result`(检测结果)、`passed`(是否通过) |
+| `nut_recipe` | 食谱 | 中 — `total_calories`(总热量)、`nutrition_summary`(营养汇总JSON) |
+| `nut_recipe_food` | 食谱-菜品关联 | 中 — `portion`(份数) |
+| `nut_cookbook` | 菜谱 | 低 — `steps`(步骤) |
+
+#### 膳食与订餐
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_order` | 订单主表 | 高 — `total_amount`(订单总金额)、`status`(订单状态)、`order_type`(订单类型) |
+| `nut_order_item` | 订单明细 | 高 — `quantity`(数量)、`price`(单价)、`subtotal`(小计) |
+| `nut_canteen` | 餐厅/食堂 | 中 — `capacity`(容量)、`status`(状态) |
+| `nut_canteen_pickup_record` | 取餐记录 | 中 — `portion`(份量)、`calories`(热量) |
+| `nut_pickup_order` | 取餐订单 | 中 — `nutrition_info`(营养汇总) |
+| `nut_consume_record` | 消费记录 | 中 — 金额一致性 |
+| `nut_pay_error` | 支付错误日志 | 低 — 支付异常记录 |
+
+#### 原材料与供应
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_mater_base` | 原材料主表 | 高 — `price`(价格)、`unit`(单位) |
+| `nut_raw_inbound` | 原材料入库 | 高 — `quantity`(数量)、`weight`(重量) |
+| `nut_sup_inventory` | 供应库存 | 高 — `quantity`(库存数量) |
+| `nut_sup_inventory_flow` | 库存流水 | 中 — `qty`(操作量)、`current_qty_after`(操作后在库量) |
+| `nut_sup_hygiene_test` | 卫生检测 | 中 — 检测结果 |
+| `nut_sup_warehouse` | 仓库 | 低 — `location`(位置)、`manager`(管理员) |
+
+> **注**:`nut_sup_inventory_flow` 的 SQL DDL 文件缺失(`db/nutrition/` 目录下未找到对应建表脚本),需补充。
+
+#### 生产与中央厨房
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_prod_combo_task` | 组合生产任务 | 中 — `plan_date`(计划日期)、`status`(任务状态) |
+| `nut_prod_qc_sample` | 质检样品 | 高 — `status`(状态:0=留样中/1=已处置/2=已检验) |
+| `nut_prod_qc_violation` | 质检违规 | 中 — 违规记录完整性 |
+| `nut_prod_serve_record` | 供餐记录 | 中 — 实际供餐与实际订单一致性 |
+| `nut_prod_stat_cost` | 生产成本统计 | 中 — 金额合理性 |
+
+#### 农场
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_farm_plot` | 地块 | 中 — `area`(面积)、`soil_type`(土壤类型) |
+| `nut_farm_crop_batch` | 作物批次 | 中 — `plant_date`(种植日期)、`expected_harvest`(预计采收日期) |
+| `nut_farm_harvest` | 采收记录 | 中 — `quantity`(采收数量) |
+| `nut_farm_livestock_batch` | 牲畜批次 | 中 — `count`(数量) |
+| `nut_farm_supplier_rating` | 供应商评级 | 低 — 评分范围 |
+
+#### 算法与 AI
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_algo_personal` | 个性化算法结果 | 高 — 营养计算结果 |
+| `nut_algo_mass` | 批量算法结果 | 高 — 批量计算结果 |
+| `nut_algo_standard` | 算法标准 | 中 — 标准值合理性 |
+| `nut_model_llm` | LLM 模型配置 | 中 — 配置有效性 |
+| `nut_model_small_version` | 模型版本 | 低 — 版本管理 |
+
+#### 设备与终端
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `nut_terminal` | 终端设备 | 中 — 设备状态 |
+| `nut_terminal_fault` | 终端故障记录 | 低 |
+| `nut_meter_device` | 计量设备(秤) | 中 — 计量精度 |
+| `nut_meter_reading` | 计量读数 | 高 — 读数合理性 |
+
+### 5.2 系统脏数据检测规则
+
+> 后端已实现 `nut_ano_dirty` 表,以下按原型业务模块分类列出需要检测的规则。
+
+#### 用户档案
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 逻辑矛盾 | 年龄为负数 | `nut_user_profile` | `birthday`(生日) → 计算年龄 |
+| 范围越界 | BMI 值超出合理范围 | `nut_user` / `nut_user_profile` | `bmi`(BMI值) |
+| 数据缺失 | 手机号为空 | `nut_user` | `phone`(手机号) |
+| 重复数据 | 同一 `user_id` 存在多条档案记录 | `nut_user_profile` | `user_id`(用户ID) |
+| 逻辑矛盾 | 健康目标与饮食限制冲突(如目标为增重但设置了减脂限制) | `nut_user_profile` | `health_goal`(健康目标) ↔ `dietary_restrictions`(饮食限制) |
+
+#### 食谱管理
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 数据缺失 | 热量值字段为空 | `nut_recipe` / `nut_food_nutri` | `total_calories`(总热量) |
+| 关联缺失 | 食谱引用的食材ID在食材库中不存在 | `nut_recipe_food` | `food_id`(食材ID) → `nut_food.id` |
+| 格式错误 | 食谱营养汇总JSON格式非法 | `nut_recipe` | `nutrition_summary`(营养汇总JSON) |
+
+#### 订餐管理
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 格式错误 | 订餐日期格式非法 | `nut_order` | 订单日期字段 |
+| 范围越界 | 单次订餐份数超出系统上限 | `nut_order_item` | `quantity`(数量) |
+| 逻辑矛盾 | 订单金额与明细小计之和不一致 | `nut_order.total_amount` vs `SUM(nut_order_item.subtotal)` | |
+
+#### 营养分析
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 重复数据 | 同一用户同日存在多条重复营养分析记录 | `nut_algo_personal` | `user_id`(用户ID) + 日期 |
+| 范围越界 | 营养素计算结果超出合理范围 | `nut_algo_personal` / `nut_algo_mass` | 营养结果字段 |
+
+#### 食材库存
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 范围越界 | 库存数量为负值 | `nut_sup_inventory` | `quantity`(库存数量) |
+| 逻辑矛盾 | 库存流水余额与当前库存不一致 | `nut_sup_inventory_flow` ↔ `nut_sup_inventory` | `current_qty_after`(操作后在库量) vs `quantity`(库存数量) |
+| 关联缺失 | 库存记录的原材料在 `nut_mater_base` 中不存在 | `nut_sup_inventory` | `mater_id`(原材料ID) |
+
+#### 健康生产
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 格式错误 | 生产批次号含非法字符 | `nut_prod_combo_task` | `task_no`(任务编号) |
+| 逻辑矛盾 | 生产任务完成但无供餐记录 | `nut_prod_combo_task` ↔ `nut_prod_serve_record` | |
+| 数据缺失 | 质检样品无检测结果 | `nut_prod_qc_sample` | `status`(状态:0=留样中/1=已处置/2=已检验) |
+
+#### 卫生供应
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 数据缺失 | 供应商资质证书编号为空 | `nut_farm_supplier_external` | 证书字段 |
+| 格式错误 | 卫生检测报告编号格式非法 | `nut_sup_hygiene_test` | 检测编号字段 |
+| 关联缺失 | 供应记录关联的供应商已删除 | `nut_raw_inbound` | `supplier_id`(供应商ID) |
+
+### 5.3 现有后端实现评估
+
+#### 已实现
+
+| 组件 | 状态 | 说明 |
+|------|:--:|------|
+| `nut_ano_dirty` 表 | ✅ 已上线 | 5个索引,支持分页/筛选/导出 |
+| `NutAnoDirtyService` | ✅ 已上线 | 完整 CRUD + 异步导出 |
+| `NutAnoDirtyController` | ✅ 已上线 | 与原型一致的查询接口 |
+
+#### 待完善
+
+| 项目 | 优先级 | 说明 |
+|------|:--:|------|
+| 系统脏数据自动检测定时任务 | 高 | 需实现每日全量扫描 Job |
+| 订餐管理、食材库存、健康生产、卫生供应的脏数据规则 | 高 | 原型有定义但后端可能未覆盖全 |
+| 农场子域的脏数据检测 | 中 | 原型未明确涉及,但表结构已存在 |
+| 设备/终端数据脏数据检测 | 低 | 秤读数、终端故障等 |
+
+### 5.4 检测频率建议
+
+| 检测类型 | 建议频率 | 说明 |
+|----------|----------|------|
+| 系统脏数据全面扫描 | 每日凌晨 2:00 | 全量扫描所有7个业务模块 |
+| 库存数量负值检查 | 每日凌晨 + 每次出入库后 | 实时性要求较高 |
+| 订单金额一致性 | 每日凌晨 | 财务敏感性数据 |
+
+---
+
+## 六、健康监测
+
+> **后端服务**:`platform-monitor`(端口 24805)| **原型参考**:`web-admin/health-monitor/exception-dirty.html`
+>
+> **开发状态**:❌ 未实现独立的脏数据检测表,已有 `mon_alert_record`(穿戴告警)和 `mon_env_warn_info`(环境告警)。
+
+健康监测是系统第二大模块,涵盖可穿戴设备数据采集、环境监测、健康调查问卷等。
+
+### 6.1 涉及数据库表
+
+#### 设备与告警
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `mon_device` | 可穿戴设备主表 | 高 — `device_code`(设备编码)、`bind_status`(绑定状态)、`bind_user_id`(绑定用户ID)、`online_status`(在线状态)、`last_data_time`(最后数据上报时间) |
+| `mon_device_config` | 设备监测配置 | 高 — `threshold_min`(阈值下限)、`threshold_max`(阈值上限)、`upload_frequency`(上报频率) |
+| `mon_bind_record` | 设备绑定/解绑记录 | 中 — `bind_date`(绑定日期)、`unbind_date`(解绑日期) |
+| `mon_alert_record` | 告警记录 | 中 — `alert_value`(告警值)、`threshold_range`(阈值范围)、GPS 坐标 |
+| `mon_notification` | 通知消息 | 低 — `send_time`(发送时间)、`notice_status`(通知状态) |
+
+#### 每日聚合统计(9张表)
+
+| 表名 | 关键字段 | 脏数据风险 |
+|------|----------|:--:|
+| `mon_daily_stats_summary` | `wear_flag`(佩戴标记)、`last_upload_time`(最后上报时间) | 中 |
+| `mon_daily_stats_heart_rate` | `hr_max`(最大心率)/`hr_min`(最小心率)/`hr_avg`(平均心率)/`hr_latest`(最新心率)、`silence_hr_*`(静息心率) | 高 |
+| `mon_daily_stats_spo2` | `spo2_max`(最大血氧)/`spo2_min`(最小血氧)/`spo2_avg`(平均血氧)/`spo2_latest`(最新血氧) | 高 |
+| `mon_daily_stats_stress` | `stress_max`(最大压力)/`stress_min`(最小压力)/`stress_avg`(平均压力)/`stress_latest`(最新压力) | 中 |
+| `mon_daily_stats_temperature` | `temp_max`(最高体温)/`temp_min`(最低体温)/`temp_avg`(平均体温)/`temp_latest`(最新体温)、`skin_temp_*`(皮肤温度) | 高 |
+| `mon_daily_stats_blood_pressure` | `systolic_max`(最高收缩压)/`systolic_min`(最低收缩压)、`diastolic_max`(最高舒张压)/`diastolic_min`(最低舒张压) | 高 |
+| `mon_daily_stats_sleep` | `total_min`(总睡眠时长分钟)、`deep_min`(深睡时长)、`score`(睡眠评分) | 中 |
+| `mon_daily_stats_exercise` | `steps`(步数)、`exercise_calorie`(运动消耗卡路里)、`steps_distance`(步行距离)、`exercise_distance`(运动距离) | 中 |
+| `mon_daily_stats_ecg` | `ecg_count`(心电测量次数)、`latest_value`(最新值) | 低 |
+
+#### 原始监测数据(12张表)
+
+| 表名 | 关键字段 | 脏数据风险 |
+|------|----------|:--:|
+| `mon_data_heart_rate` | `data_value`(心率值bpm)、`silence_value`(静息心率值) | 高 |
+| `mon_data_spo2` | `data_value`(血氧值%) | 高 |
+| `mon_data_stress` | `data_value`(压力值)、`start_time`(测量开始时间)、`end_time`(测量结束时间) | 中 |
+| `mon_data_temperature` | `data_value`(体温值℃)、`skin_value`(皮肤温度) | 高 |
+| `mon_data_sleep` | `fall_asleep_time`(入睡时间)、`wake_up_time`(醒来时间)、`sleep_score`(睡眠评分) | 中 |
+| `mon_data_sleep_stage` | `stage_type`(睡眠阶段类型)、`start_time`(开始时间)、`end_time`(结束时间)、`duration`(持续时间) | 中 |
+| `mon_data_ecg` | `avg_heart_rate`(平均心率)、`arrhy_type`(心律失常类型)、`data_points_url`(数据点文件URL) | 中 |
+| `mon_data_exercise` | `calorie`(消耗卡路里)、`distance`(运动距离)、`duration`(运动时长) | 中 |
+| `mon_data_exercise_trace` | `longitude`(经度)、`latitude`(纬度)、`altitude`(海拔) | 中 |
+| `mon_data_steps` | `step_count`(步数)、`calorie`(消耗卡路里)、`distance`(距离) | 中 |
+| `mon_data_blood_pressure` | `systolic_value`(收缩压)、`diastolic_value`(舒张压) | 高 |
+| `mon_data_gps` | `longitude`(经度)、`latitude`(纬度)、`address`(地址) | 中 |
+
+#### 环境监测(16张表)
+
+| 类别 | 数据表 | 日数据表 | 月数据表 | 使用记录表 |
+|------|--------|----------|----------|-----------|
+| 室内 | `mon_env_data_indoor` | `mon_env_day_data_indoor` | `mon_env_month_data_indoor` | `mon_env_use_record_indoor` |
+| 室外 | `mon_env_data_outdoor` | `mon_env_day_data_outdoor` | `mon_env_month_data_outdoor` | `mon_env_use_record_outdoor` |
+| 花粉 | `mon_env_data_pollen` | `mon_env_day_data_pollen` | `mon_env_month_data_pollen` | `mon_env_use_record_pollen` |
+| 水质 | `mon_env_data_water` | `mon_env_day_data_water` | `mon_env_month_data_water` | `mon_env_use_record_water` |
+
+附加表:`mon_env_category_indoor/outdoor/pollen/water`(指标类别定义)、`mon_env_meter_info`(环境仪表元数据)、`mon_env_warn_info`(环境告警记录)、`mon_env_warn_sync_record`(告警同步记录)
+
+#### 调查问卷
+
+| 表名 | 说明 | 脏数据风险 |
+|------|------|:--:|
+| `mon_survey` | 调查主表 | 低 |
+| `mon_survey_activity` | 调查活动 | 低 |
+| `mon_survey_answer_record` | 回答记录 | 中 |
+| `mon_survey_answer_detail` | 回答详情 | 中 |
+
+### 6.2 系统脏数据检测规则
+
+> 按原型业务模块分类:健康排查、穿戴监测、体重监测、环境监测
+
+#### 健康排查(问卷)
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 数据缺失 | 积分字段异常(`mon_survey_answer_record` 无 score 字段,积分在别处计算) | `mon_survey_answer_record` + 积分相关表 | — |
+| 逻辑矛盾 | 问卷回答时间早于活动发布时间 | `mon_survey_answer_record` ↔ `mon_survey_activity` | 时间字段 |
+| 重复数据 | 同一用户同一问卷出现多条回答记录 | `mon_survey_answer_record` | `user_id`(用户ID) + `survey_id`(问卷ID) |
+
+#### 穿戴监测
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 格式错误 | 设备绑定日期格式非法 | `mon_bind_record` | `bind_date`(绑定日期) |
+| 重复数据 | 同一设备同一时间段出现重复数据记录 | `mon_data_heart_rate` / `mon_data_spo2` / ... | `device_id`(设备ID) + `data_time`(数据时间) |
+| 范围越界 | 心率值超出人体可能范围(如 <20 或 >300 bpm) | `mon_data_heart_rate` | `data_value`(心率值bpm) |
+| 范围越界 | 血氧值超出合理范围(如 <50% 或 >100%) | `mon_data_spo2` | `data_value`(血氧值%) |
+| 范围越界 | 体温值超出人体可能范围(如 <30℃ 或 >45℃) | `mon_data_temperature` | `data_value`(体温值℃) |
+| 逻辑矛盾 | 静息心率高于活动心率 | `mon_daily_stats_heart_rate` | `silence_hr_avg`(静息心率均值) > `hr_avg`(心率均值) |
+| 逻辑矛盾 | 设备在线但超过 N 天无数据上报 | `mon_device` | `online_status`(在线状态) + `last_data_time`(最后上报时间) |
+| 范围越界 | GPS 坐标超出国内范围 | `mon_data_gps` | `longitude`(经度)、`latitude`(纬度) |
+| 格式错误 | 穿戴设备原始数据 `data_points_url` 指向的 URL 不可访问 | `mon_data_ecg` | `data_points_url`(数据点文件URL) |
+
+#### 体重监测
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 逻辑矛盾 | 日统计中的最大心率 < 最小心率 | `mon_daily_stats_heart_rate` | `hr_max`(最大心率) < `hr_min`(最小心率) |
+| 逻辑矛盾 | 日统计中的收缩压 < 舒张压 | `mon_daily_stats_blood_pressure` | `systolic_*`(收缩压) < `diastolic_*`(舒张压) |
+| 逻辑矛盾 | 睡眠时长 > 24小时 | `mon_daily_stats_sleep` | `total_min`(总睡眠时长) > 1440 |
+
+#### 环境监测
+
+| 异常类型 | 检测规则 | 涉及表 | 涉及字段 |
+|----------|----------|--------|----------|
+| 逻辑矛盾 | 室内温度高于室外温度超过10℃(且空调未在制热) | `mon_env_data_indoor` + `mon_env_data_outdoor` | 温度字段 |
+| 范围越界 | 环境温度超出设备量程 | 各环境数据表 | 温度字段 |
+| 范围越界 | PM2.5 值为负数 | `mon_env_data_indoor` / `mon_env_data_outdoor` | PM2.5 字段 |
+| 范围越界 | ph 值超出 0-14 范围 | `mon_env_data_water` | `ph`(酸碱度) |
+| 数据缺失 | 环境监测设备超过 N 天未上报数据 | 各环境数据表 | `read_time`(读取时间) |
+| 关联缺失 | 环境数据关联的仪表在 `mon_env_meter_info` 中不存在 | 各环境数据表 | `meter_code`(仪表编码) |
+
+### 6.3 检测频率建议
+
+| 检测类型 | 建议频率 | 说明 |
+|----------|----------|------|
+| 系统脏数据全面扫描 | 每日凌晨 3:00 | 穿戴/环境数据量大,错峰执行 |
+| 原始数据范围越界检查 | 数据入库时实时 | 在数据接收层做第一道校验 |
+| 聚合统计一致性检查 | 每日凌晨(聚合任务完成后) | 校验日统计 = 原始数据汇总 |
+| 设备在线但无数据告警 | 每小时 | `mon_device.online_status` + `last_data_time` |
+| GPS 坐标越界检查 | 每日凌晨 | 全量扫描 `mon_data_gps` |
diff --git a/doc/数据质量筛查规则说明书.md b/doc/数据质量筛查规则说明书.md
new file mode 100644
index 0000000..9af56bb
--- /dev/null
+++ b/doc/数据质量筛查规则说明书.md
@@ -0,0 +1,966 @@
+# 异常事件数据质量控制规则说明书
+
+> **文档版本**:v1.0
+> **编写日期**:2026-07-28
+> **适用范围**:系统管理、健康咨询、应急就医、健康体检、营养管理、健康监测(共 6 个业务板块)
+> **执行主体**:数据中台(于数据同步环节内嵌执行质量校验)
+
+---
+
+## 目录
+
+- [一、文档概述](#一文档概述)
+- [二、系统脏数据类型定义](#二系统脏数据类型定义)
+- [三、脏数据与结果异常的边界](#三脏数据与结果异常的边界)
+- [四、数据中台质量校验流程](#四数据中台质量校验流程)
+- [五、各模块检测规则](#五各模块检测规则)
+ - [5.1 系统管理](#51-系统管理)
+ - [5.2 健康咨询](#52-健康咨询)
+ - [5.3 应急就医](#53-应急就医)
+ - [5.4 健康体检](#54-健康体检)
+ - [5.5 营养管理](#55-营养管理)
+ - [5.6 健康监测](#56-健康监测)
+- [六、附录](#六附录)
+
+---
+
+## 一、文档概述
+
+### 1.1 编写目的
+
+本文档定义平台各业务板块"系统脏数据"的统一质量校验规则,作为数据中台实现自动化数据质量检测(DQC, Data Quality Check)的依据。核心目标:
+
+1. **统一标准**:收敛各板块原型和研发文档中分散、不一致的脏数据类型定义,建立全平台统一的异常分类体系
+2. **明确边界**:厘清"系统脏数据"与"结果异常"的判定标准,确保两类异常各归其位
+3. **指导落地**:为数据中台在 CDC / 增量同步链路中嵌入质量校验提供规则输入
+
+### 1.2 适用范围
+
+| 板块 | 管理端模块 | 当前状态 |
+|------|-----------|---------|
+| 健康咨询 | `web-admin/health-consult/` | 原型已定稿,后端已部分实现 |
+| 应急就医 | `web-admin/emergency-dispatch/` | 原型已定稿,后端有占位 |
+| 健康体检 | `web-admin/health-checkup/` | 原型已定稿 |
+| 营养管理 | `web-admin/nutrition/` | 原型已定稿,后端已部分实现 |
+| 健康监测 | `web-admin/health-monitor/` | 原型已定稿 |
+| 系统管理 | `platform-java/platform-system/` | 后端已实现 |
+
+### 1.3 术语定义
+
+| 术语 | 定义 |
+|------|------|
+| **系统脏数据** | 写入数据库后,因其字段值违反数据完整性约束(非空、格式、取值范围、外键关联、唯一性、逻辑一致性)而不可信或不可用的数据记录 |
+| **结果异常** | 数据本身在存储层面合法、完整,但所反映的业务结果偏离正常范围(如超时未关闭、评分异常偏低、指标超出医学参考范围等),属于业务流程或健康结果的异常,**不属于系统脏数据范畴** |
+| **数据中台 DQC** | Data Quality Check,在数据同步管道(CDC 或定时增量)中内嵌执行的数据质量校验环节 |
+| **CDC** | Change Data Capture,基于数据库变更日志的实时增量数据捕获机制 |
+
+---
+
+## 二、系统脏数据类型定义
+
+全平台统一使用以下 6 种脏数据类型。各板块的具体检测规则均以此分类为准。
+
+### 2.1 类型一览
+
+| 编码 | 类型名称 | 判定标准 | 严重级别 |
+|------|----------|----------|:--:|
+| D01 | **数据缺失** | 业务规则要求非空的字段,其值为 NULL 或空字符串 | 高 |
+| D02 | **格式违规** | 字段值不符合预定义的格式规范(正则、JSON Schema、日期格式等) | 高 |
+| D03 | **取值越界** | 字段值超出物理极限或逻辑合理范围(含连续数值越界和离散枚举越界) | 高 |
+| D04 | **逻辑冲突** | 同一记录内多个字段之间,或存在业务关联的多条记录之间,字段值存在不可调和的逻辑矛盾 | 高 |
+| D05 | **关联断裂** | 外键字段值非空,但其引用的目标记录在关联表中不存在(孤儿记录) | 中 |
+| D06 | **数据重复** | 违反业务唯一性约束,存在多条本应唯一的记录 | 中 |
+
+### 2.2 各类型详细定义
+
+#### D01 — 数据缺失
+
+**定义**:业务语义上必须具备值的字段,在数据库层面为 NULL 或等效空值。
+
+**判定逻辑**:
+- 单字段规则:`field IS NULL OR field = ''`
+- 组合规则:同一记录内多个关联字段全部为空(如身高、体重、腰围三者均为空)
+
+**典型示例**:
+- 用户档案中姓名为空
+- 订单主表中订单编号为空
+- 体检报告中检查结果值为空
+- 设备编码为空
+
+**注意事项**:
+- 允许为空的字段(如选填的备注、非必填的附件URL)不在检测范围
+- 需结合业务规则确认哪些字段为"必填",不能仅凭数据库 DDL 的 `NOT NULL` 约束判断(有些字段在 DDL 层面允许 NULL,但业务上必须具备值)
+
+---
+
+#### D02 — 格式违规
+
+**定义**:字段值已填充,但其字符串形式不符合约定的格式规范。
+
+**判定逻辑**:
+- 正则匹配失败(手机号、身份证号、邮箱等)
+- JSON/XML 解析失败(如 `questions_json`、`nutrition_summary` 等)
+- 日期/时间字符串无法按预期格式解析
+- URL 格式不合法
+
+**典型示例**:
+- 手机号字段值为 `"abc123"` 或长度不为 11 位
+- 常规体检指标数据出现汉字,空腹血糖值 `"无记录"`
+- 问卷 JSON 字段 `parse()` 抛出异常
+- 日期字段值为 `"2026-13-40"`(无效日期)
+- 生活习惯标记字段不符合 `state,score,fullmark` 格式
+
+**注意事项**:
+- 格式校验的正则表达式需与业务端输入校验规则保持一致
+- 对于允许自由文本的字段(如备注、描述),不执行格式校验
+
+---
+
+#### D03 — 取值越界
+
+**定义**:字段值已填充且格式合法,但数值大小或枚举编码超出了物理/逻辑合理范围。
+
+**判定逻辑**:
+- 连续数值越界:`value < min OR value > max`(min/max 基于人体生理极限或业务规则)
+- 离散枚举越界:`value NOT IN (enum_set)`(enum_set 为字段的合法枚举值集合)
+
+**典型示例**:
+- BMI 值为 85.3 或 -5(合理范围 10~80)
+- 收缩压为 350 mmHg(合理范围 50~300)
+- 库存数量为 -100(不应为负)
+- 性别字段值为 3(枚举仅 1=男 / 2=女)
+- 人群分类字段值为 `"X"`(枚举仅 A/B/C/D/E)
+- 经纬度超出中国版图范围,北纬4°-53°33′,东经135°5′-73°40′
+
+#### 统一取值越界参考标准
+
+以下为跨模块通用的数值边界定义。各模块检测规则中的对应阈值统一以此表为准,避免同一业务含义的字段在不同模块中出现不一致的校验边界。
+
+**人体测量类**
+
+| 字段 | 单位 | 下限 | 上限 | 说明 |
+|------|------|:--:|:--:|------|
+| 身高 | cm | 50 | 250 | 低于50cm不具备成年人体征,高于250cm超出人类身高纪录 |
+| 体重 | kg | 20 | 300 | 低于20kg不具备成年人体征,300kg为临床极端值上限 |
+| BMI | — | 10 | 80 | 10以下为生命维持极限,80以上为世界纪录的约3倍 |
+| 体脂率 | % | 2 | 60 | 2%为男性必需脂肪下限,60%超出临床病态肥胖上限 |
+| 腰围 | cm | 30 | 200 | 30cm为新生儿水平,200cm超出临床极端值 |
+| 年龄 | 岁 | 0 | 150 | 0岁为出生当年,150岁超出人类寿命极限 |
+
+**生命体征类**
+
+| 字段 | 单位 | 下限 | 上限 | 说明 |
+|------|------|:--:|:--:|------|
+| 收缩压 | mmHg | 50 | 300 | 50mmHg为濒危休克值,300mmHg超出血压计量程 |
+| 舒张压 | mmHg | 30 | 200 | 30mmHg为濒危休克值,200mmHg超出临床极端值 |
+| 心率 | bpm | 20 | 300 | 20bpm为深度休眠/濒危值,300bpm超出心室颤动极限 |
+| 血氧饱和度 | % | 50 | 100 | 50%以下为致命性低氧,100%为理论饱和上限 |
+| 体温 | ℃ | 30 | 45 | 30℃为致命性低温,45℃超出人体存活上限 |
+| 空腹血糖 | mmol/L | 0 | 50 | 0为理论下界,50mmol/L超出临床极端高值 |
+
+**时间度量类**
+
+| 字段 | 单位 | 下限 | 上限 | 说明 |
+|------|------|:--:|:--:|------|
+| 睡眠时长 | 分钟 | 0 | 1440 | 0为未睡眠,1440为一天总分钟数 |
+
+**地理坐标类**
+
+| 字段 | 单位 | 下限 | 上限 | 说明 |
+|------|------|:--:|:--:|------|
+| 经度(中国境内) | 度 | 73 | 136 | 东经73°40′(新疆阿克赛钦)~ 东经135°5′(黑龙江抚远),取整 |
+| 纬度(中国境内) | 度 | 4 | 54 | 北纬4°(南沙群岛曾母暗沙)~ 北纬53°33′(黑龙江漠河),取整 |
+
+**评分与占比类**
+
+| 字段 | 单位 | 下限 | 上限 | 说明 |
+|------|------|:--:|:--:|------|
+| 评分(5分制) | 分 | 0 | 5 | 适用于专家评分、咨询评分等五级评分体系 |
+| 百分比 | % | 0 | 100 | 适用于回复率、电量百分比、完成率等百分比字段 |
+| pH 值 | — | 0 | 14 | 水溶液酸碱度理论范围 |
+
+**与格式违规的区别**:
+- 格式违规关注"值的形态"(如"abc"不是数字)
+- 取值越界关注"值的量级"(如 999 是合法数字但超出了合理上限)
+
+---
+
+#### D04 — 逻辑冲突
+
+**定义**:单条记录内多个字段之间存在不可调和的值矛盾,或多条关联记录之间的汇总/引用关系不成立。
+
+**判定逻辑**:
+- 同一行内两字段比较(如 `end_time < start_time`、`sbp < dbp`)
+- 一对多汇总不一致(如 `total_amount != SUM(subtotal)`)
+- 状态与时间戳矛盾(如状态=已完成但 end_time 为空)
+- 标记与明细不一致(如 is_exception=1 但所有子项指标均正常)
+
+**典型示例**:
+- 咨询结束时间早于开始时间
+- 订单总金额不等于明细小计之和
+- 会话状态为"进行中"但结束时间已填充
+- 收缩压(110)低于舒张压(85)
+- 睡眠时长 + 运动时长 > 1440 分钟(超过一天)
+
+**注意事项**:
+- 逻辑冲突规则需要深入理解业务语义,建议由产品经理与研发联合确认
+- 统计类字段(如 `statics_num`)与源表记录数的一致性校验属于此类,但因实时性要求低,可在每日全量对账时执行
+
+---
+
+#### D05 — 关联断裂
+
+**定义**:记录中的外键字段指向了一个在关联表中已不存在的 ID。
+
+**判定逻辑**:
+- `foreign_key IS NOT NULL AND foreign_key NOT IN (SELECT id FROM referenced_table)`
+
+**典型示例**:
+- 咨询记录中的 `expert_id` 在专家表中不存在(专家已删除)
+- 订单中的 `user_id` 在用户表中不存在
+- 评价记录中的 `consult_id` 在咨询表中不存在
+- AED 设备部署点位关联的应急中心 ID 无效
+
+**注意事项**:
+- 仅在外键值非空时检测(允许 NULL 的外键不触发此规则,因为 NULL 表示"未关联"而非"关联无效")
+- 软删除场景需确认关联表的过滤条件(如 `del_falg IS NULL`)
+
+---
+
+#### D06 — 数据重复
+
+**定义**:同一业务实体在违反唯一性约束的情况下出现了多条记录。
+
+**判定逻辑**:
+- 按业务唯一键分组计数:`COUNT(*) > 1 GROUP BY business_unique_key`
+
+**典型示例**:
+- 同一用户同一年度存在多条活跃人群分类记录
+- 同一员工同一活动重复报名
+- 同一设备同一时间点存在多条监测数据记录
+- 同一用户同一天存在多条营养分析结果记录
+
+**注意事项**:
+- 业务唯一键 ≠ 数据库唯一索引。需根据业务语义定义去重维度
+- 区分"合理的多条记录"(如一个用户多次咨询)与"异常的多条记录"(如一个用户同一时刻两条档案)
+
+---
+
+## 三、脏数据与结果异常的边界
+
+### 3.1 判定原则
+
+> **核心原则**:系统脏数据关注"数据本身是否合法",结果异常关注"业务结果是否正常"。前者是数据质量的范畴,后者是业务监控的范畴。
+
+### 3.2 边界对照表
+
+| 场景 | 数据是否合法 | 分类 | 理由 |
+|------|:--:|------|------|
+| 血压值为 -50 mmHg | 否 | **脏数据 (D03)** | 数值超出物理极限 |
+| JSON 字段解析失败 | 否 | **脏数据 (D02)** | 格式不符合规范 |
+| 员工编号为空 | 否 | **脏数据 (D01)** | 必填字段缺失 |
+| 订单关联的用户已删除 | 否 | **脏数据 (D05)** | 外键关联失效 |
+| 同一用户同日两条档案 | 否 | **脏数据 (D06)** | 违反唯一性约束 |
+| 收缩压 110 < 舒张压 85 | 否 | **脏数据 (D04)** | 字段间逻辑矛盾 |
+| 血压值 185/110 mmHg | **是** | 结果异常 | 值在物理范围内,但超出医学正常参考范围 |
+| 工单创建后 7 天未关闭 | **是** | 结果异常 | 状态值和时长均合法,属业务流程超时 |
+| 专家评分 1.5 分(满分 5) | **是** | 结果异常 | 评分值在合法范围内,属服务质量问题 |
+| 咨询会话超过 60 分钟 | **是** | 结果异常 | 时长值合法,属服务效率问题 |
+| 设备超过 3 天未上报数据 | **是** | 结果异常 | 数据本身无异常,属设备离线告警 |
+| ICU 患者血糖 22 mmol/L | **是** | 结果异常 | 值本身合法,属危急值预警 |
+
+### 3.3 典型易混淆场景澄清
+
+**场景一:咨询时长异常**
+
+研发文档中健康咨询模块的 `exception_type=1`(咨询时长异常 > 60 分钟)被归类为系统脏数据,实为**结果异常**。`duration_minutes` 字段的值 61、120 等在格式和范围内均合法,只是反映了"这个咨询用时很长"这一业务事实——这是服务质量监控指标,不是数据质量问题。
+
+**场景二:任务状态卡住**
+
+研发文档中多处出现 `status = 处理中 超过 24 小时` 的规则。若状态字段值本身合法(确实为"处理中"),则属于结果异常(任务卡住);但若状态值与时间戳存在矛盾(如状态=已完成但 end_time 为空),则为脏数据(D04 逻辑冲突)。
+
+**场景三:评价分数极低**
+
+评分字段值 1 在 1~5 范围内合法,属于结果异常(服务评价差)。只有当 score 值超出 1~5 范围时,才属于脏数据(D03 取值越界)。
+
+---
+
+## 四、数据中台质量校验流程
+
+### 4.1 整体架构
+
+系统脏数据的检测由数据中台统一执行,不依赖各业务服务自行实现定时任务。质量校验嵌入在数据中台从业务数据库同步数据的管道中,实现"同步即校验"。
+
+```
+┌──────────────┐ CDC/增量同步 ┌────────────────────┐
+│ 业务数据库 │ ──────────────────> │ 数据中台 │
+│ (postgreSQL) │ │ │
+│ │ │ ┌──────────────┐ │
+│ health_ │ │ │ DQC 规则引擎 │ │
+│ consult │ │ │ (同步即校验) │ │
+│ emergency │ │ └──────┬───────┘ │
+│ exam │ │ │ │
+│ nutrition │ │ ┌────▼───────┐ │
+│ monitor │ │ │ 脏数据存储库 │ │
+│ weight │ │ │ (汇总查询) │ │
+│ ... │ │ └────────────┘ │
+└──────────────┘ └────────────────────┘
+```
+
+### 4.2 校验触发时机
+
+| 同步模式 | 触发时机 | 适用场景 |
+|----------|----------|----------|
+| **CDC 实时同步** | 每捕获一条 INSERT/UPDATE 变更事件,即对该记录执行 DQC 规则校验 | 数据缺失、格式违规、取值越界、逻辑冲突(单行)、枚举非法 |
+| **增量批次同步** | 每个批次(如每 5 分钟一批)同步完成后,对批次内记录执行跨表校验 | 关联断裂 |
+| **每日全量对账** | 每日凌晨,对全量/增量数据进行汇总级校验 | 逻辑冲突(汇总不一致)、数据重复 |
+
+### 4.3 校验管线
+
+```
+数据变更事件 (CDC Event)
+ │
+ ▼
+┌─────────────────┐
+│ 1. 字段映射 │ 将业务表字段映射为标准 DQC 字段模型
+└────────┬────────┘
+ ▼
+┌─────────────────┐
+│ 2. 规则匹配 │ 根据「表名.字段名」匹配对应 DQC 规则集
+└────────┬────────┘
+ ▼
+┌─────────────────┐
+│ 3. 规则执行 │ 逐条执行规则 → 输出 (通过 / 不通过 + 异常详情)
+└────────┬────────┘
+ ▼
+┌─────────────────┐
+│ 4. 结果路由 │ 通过 → 数据入仓
+│ │ 不通过 → 写入脏数据表 + 同步告警(可选)
+└─────────────────┘
+```
+
+### 4.4 脏数据记录模型
+
+校验发现脏数据后,统一写入以下结构的异常记录:
+
+| 字段 | 类型 | 说明 |
+|--------------------|------|------------------------------------------------------------------------|
+| `id` | bigint | 主键 |
+| `module` | varchar | 业务板块(health_consult / emergency / exam / nutrition / monitor / weight) |
+| `dirty_type` | varchar | 脏数据类型编码(D01 ~ D06) |
+| `table_name` | varchar | 来源表名 |
+| `table_desc` | varchar | 业务表描述(设备主表、咨询记录表、应急工单表等) |
+| `field_name` | varchar | 异常字段名(多字段时用逗号分隔) |
+| `record_id` | varchar | 异常记录的主键/业务编号 |
+| `abnormal_value` | text | 异常值 |
+| `rule_description` | text | 触发的规则描述 |
+| `detected_at` | datetime | 检测时间 |
+| `data_created_at` | datetime | 异常数据的业务产生时间(来自源表时间戳字段) |
+| `status` | varchar | 处理状态(待处理 / 已忽略 / 已修复) |
+
+### 4.5 告警与处置
+
+- **系统脏数据查询**:在各业务版块下的异常事件-系统脏数据中提供脏查询功能
+- **实时告警(暂不实现)**:D01~D03 类型(数据缺失、格式违规、取值越界)发现后即时推送告警,通知对应板块的数据负责人
+- **定时报告(暂不实现)**:D04~D06 类型(逻辑冲突、关联断裂、数据重复)汇总至每日数据质量日报,统一呈现
+- **处置闭环((暂不实现))**:业务端通过"异常事件 → 系统脏数据"页面查看、筛选、导出,确认后可标记为"已忽略"或修复源数据后自动复检清除
+
+---
+
+## 五、各模块检测规则
+
+> 以下规则按「业务域 → 表 → 规则明细」三层结构组织,每个模块统一格式。
+
+### 5.1 系统管理
+
+#### 涉及数据表
+
+系统管理是平台的基础支撑模块,管理用户、角色、组织架构、字典、配置等核心公共数据。以下仅列出直接影响业务数据完整性的核心校验表。
+
+| 业务子域 | 核心表 | 关键校验字段 |
+|----------|--------|-------------|
+| 用户与角色 | `sys_user`, `sys_user_extend`, `sys_role`, `sys_user_role` | username, workNo, phone, idCard, sex, status, accountStatus, personType, birthday, height, weight, bmi, roleName, roleCode |
+| 组织机构 | `sys_depart` | departName, orgCode, orgCategory, orgType, status, lng, lat, parentId |
+| 字典与配置 | `sys_dict`, `sys_dict_item`, `sys_config` | name, code, itemName, itemValue, configKey, configName |
+
+#### 检测规则
+
+**sys_user(用户信息)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S01-D01 | 登录账号缺失 | `username` | `IS NULL OR = ''` |
+| S01-D01 | 工号缺失 | `workNo` | `IS NULL OR = ''` |
+| S01-D02 | 手机号格式不合法 | `phone` | 不匹配 11 位手机号正则(如 `"888"` 或非数字字符) |
+| S01-D02 | 身份证号格式不合法 | `idCard` | 长度不为 18 位,或含非法字符,或校验位不通过 |
+| S01-D03 | 性别枚举越界 | `sex` | `NOT IN (0, 1, 2)`(0=未知, 1=男, 2=女) |
+| S01-D03 | 员工状态枚举越界 | `status` | `NOT IN (1, 2, 3)`(1=在岗, 2=封存, 3=注销) |
+| S01-D03 | 账号状态枚举越界 | `accountStatus` | `NOT IN (0, 1)`(0=锁定, 1=激活) |
+| S01-D03 | 用户类型枚举越界 | `personType` | `NOT IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17)` |
+| S01-D03 | 生日为未来日期 | `birthday` | `birthday > CURRENT_DATE` |
+| S01-D06 | 工号重复 | `workNo` | 存在多条记录使用同一工号(排除已逻辑删除记录) |
+
+**sys_role(角色)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S02-D01 | 角色名称缺失 | `roleName` | `IS NULL OR = ''` |
+| S02-D01 | 角色编码缺失 | `roleCode` | `IS NULL OR = ''` |
+| S02-D03 | 角色类型枚举越界 | `roleType` | `NOT IN (0, 1)`(0=自定义, 1=系统内置) |
+| S02-D06 | 角色编码重复 | `roleCode` | 存在多条记录使用同一角色编码 |
+
+**sys_user_role(用户角色关联)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S03-D05 | 关联用户不存在 | `userId` | 引用的用户不存在 |
+| S03-D05 | 关联角色不存在 | `roleId` | 引用的角色不存在 |
+| S03-D06 | 用户角色重复分配 | `userId`, `roleId` | 同一用户被重复分配同一角色 |
+
+**sys_depart(组织机构)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S04-D01 | 机构名称缺失 | `departName` | `IS NULL OR = ''` |
+| S04-D01 | 机构编码缺失 | `orgCode` | `IS NULL OR = ''` |
+| S04-D03 | 机构类别枚举越界 | `orgCategory` | `NOT IN ('1', '2', '3')`(1=公司, 2=组织机构, 3=岗位) |
+| S04-D03 | 机构类型枚举越界 | `orgType` | `NOT IN ('1', '2')`(1=一级部门, 2=子部门) |
+| S04-D03 | 状态枚举越界 | `status` | `NOT IN ('0', '1')`(0=不启用, 1=启用) |
+| S04-D03 | 经纬度超出国内范围 | `lng`, `lat` | `lng < 73 OR lng > 136 OR lat < 4 OR lat > 54`(含 (0,0) 未初始化场景) |
+| S04-D04 | 父机构指向自身 | `id`, `parentId` | `id = parentId` |
+| S04-D05 | 关联父机构不存在 | `parentId` | 引用的父机构不存在 |
+| S04-D06 | 机构编码重复 | `orgCode` | 存在多条记录使用同一机构编码 |
+
+**sys_dict(字典主表)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S05-D01 | 字典名称缺失 | `name` | `IS NULL OR = ''` |
+| S05-D01 | 字典编码缺失 | `code` | `IS NULL OR = ''` |
+| S05-D06 | 字典编码重复 | `code` | 存在多条记录使用同一字典编码 |
+
+**sys_dict_item(字典项)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S06-D01 | 字典项名称缺失 | `itemName` | `IS NULL OR = ''` |
+| S06-D01 | 字典项值缺失 | `itemValue` | `IS NULL OR = ''` |
+| S06-D03 | 字典项状态枚举越界 | `status` | `NOT IN (0, 1)`(0=不启用, 1=启用) |
+| S06-D05 | 关联字典不存在 | `dictId` | 引用的字典不存在 |
+| S06-D06 | 同一字典下字典项值重复 | `dictId`, `itemValue` | 同一字典下存在重复的字典项值 |
+
+**sys_config(参数配置)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S07-D01 | 配置名称缺失 | `configName` | `IS NULL OR = ''` |
+| S07-D01 | 配置键缺失 | `configKey` | `IS NULL OR = ''` |
+| S07-D06 | 配置键重复 | `configKey` | 存在多条记录使用同一配置键 |
+
+**sys_user_extend(用户扩展信息)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| S08-D03 | 身高越界 | `height` | `< 50 OR > 250` (cm) |
+| S08-D03 | 体重越界 | `weight` | `< 20 OR > 300` (kg) |
+| S08-D03 | BMI 越界 | `bmi` | `< 10 OR > 80` |
+| S08-D05 | 关联用户不存在 | `userId` | 引用的用户不存在 |
+
+---
+
+### 5.2 健康咨询
+
+#### 涉及数据表
+
+| 表名 | 说明 | 核心校验字段 |
+|------|------|-------------|
+| `con_consult` | 咨询会话主表 | consult_no, consult_type, content_status, session_status, start_time, end_time, duration_minutes, cost, expert_id, member_id |
+| `con_expert` | 专家信息表 | expert_no, expert_type, overall_merit, response_rate, reg_date, off_date, hospital_id, dept_id |
+| `con_evaluate` | 咨询评价表 | score, consult_id, expert_id |
+| `con_family_member` | 家庭成员表 | member_name, birthday, height, weight, sex |
+| `con_medical_record` | 病历档案表 | medical_describe, duration_type |
+| `con_im_message` | IM 消息记录表 | msg_content, msg_type, consult_id |
+| `con_expert_schedule_rule` | 专家排班规则表 | week_day, period, start_time, end_time, capacity |
+| `con_settle_main` | 结算主表 | settle_status, com_fee_per_month |
+
+#### 检测规则
+
+**con_consult(咨询会话)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C01-D01 | 咨询单号缺失 | `consult_no` | `IS NULL OR = ''` |
+| C01-D03 | 咨询类型枚举越界 | `consult_type` | `NOT IN (1, 2)` |
+| C01-D03 | 内容状态枚举越界 | `content_status` | `NOT IN (1,2,3,4,5,6,7,8)` |
+| C01-D03 | 咨询费用为负数 | `cost` | `< 0` |
+| C01-D04 | 结束时间早于开始时间 | `start_time`, `end_time` | 结束时间早于开始时间 |
+| C01-D04 | 会话进行中但已有结束时间 | `session_status`, `end_time` | 会话状态为进行中但已有结束时间 |
+| C01-D04 | 内容已完成但无结束时间 | `content_status`, `end_time` | 内容已完成但无结束时间 |
+| C01-D04 | 已拒绝但无拒绝原因 | `content_status`, `reject_reason` | 已拒绝但未填写拒绝原因 |
+| C01-D05 | 关联专家不存在 | `expert_id` | 引用的专家不存在 |
+| C01-D05 | 关联咨询人不存在 | `member_id` | 引用的家庭成员不存在 |
+
+**con_expert(专家信息)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C02-D01 | 专家编号缺失 | `expert_no` | `IS NULL OR = ''` |
+| C02-D03 | 专家类型枚举越界 | `expert_type` | `NOT IN (1, 2, 3)` |
+| C02-D03 | 综合评分越界 | `overall_merit` | `< 0 OR > 5` |
+| C02-D03 | 回复率越界 | `response_rate` | `< 0 OR > 100` |
+| C02-D04 | 离职日期早于入职日期 | `reg_date`, `off_date` | 离职日期早于入职日期 |
+| C02-D05 | 关联医院不存在 | `hospital_id` | 引用的医院不存在 |
+| C02-D05 | 关联科室不存在 | `dept_id` | 引用的科室不存在 |
+
+**con_evaluate(咨询评价)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C03-D03 | 评分越界 | `score` | `< 0 OR > 5` |
+| C03-D05 | 关联咨询不存在 | `consult_id` | 引用的咨询记录不存在 |
+| C03-D05 | 关联专家不存在 | `expert_id` | 引用的专家不存在 |
+
+**con_family_member(家庭成员)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C04-D03 | 身高越界 | `height` | `< 50 OR > 250` |
+| C04-D03 | 体重越界 | `weight` | `< 20 OR > 300` |
+| C04-D03 | 性别枚举越界 | `sex` | `NOT IN (1, 2)` |
+| C04-D03 | 生日为未来日期 | `birthday` | `birthday > CURRENT_DATE` |
+
+**con_medical_record(病历档案)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C05-D01 | 病情描述缺失 | `medical_describe` | `IS NULL OR = ''` |
+| C05-D03 | 持续时间类型枚举越界 | `duration_type` | `NOT IN (1, 2, 3, 4)` |
+
+**con_im_message(IM 消息)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C06-D01 | 消息内容缺失(非撤回消息) | `msg_content` | 消息内容为空且消息类型非撤回 |
+| C06-D03 | 消息类型枚举越界 | `msg_type` | `NOT IN (1, 2, 3, 4)` |
+| C06-D05 | 关联咨询不存在 | `consult_id` | 引用的咨询记录不存在 |
+
+**con_expert_schedule_rule(排班规则)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| C07-D03 | 星期枚举越界 | `week_day` | `NOT IN (1,2,3,4,5,6,7)` |
+| C07-D03 | 时段枚举越界 | `period` | `NOT IN (1, 2, 3, 4)` |
+| C07-D03 | 接诊人数为负数 | `capacity` | `< 0` |
+| C07-D04 | 排班结束时间早于开始时间 | `start_time`, `end_time` | `end_time < start_time` |
+
+---
+
+### 5.3 应急就医
+
+#### 涉及数据表
+
+| 表名 | 说明 | 核心校验字段 |
+|------|------|-------------|
+| `em_emergency_order` | 应急工单 | order_no, status, location_lat, location_lng, closed_at |
+| `em_call_record` | 通话记录 | incoming_phone, match_status, order_id, begin_time, end_time, answer_status, duration |
+| `em_aed_device` | AED 设备 | battery_level, battery_status |
+| `em_aed_deploy_point` | AED 部署点位 | longitude, latitude |
+| `em_aed_latest` | AED 最新状态 | battery_power |
+| `em_aed_maintenance` | AED 维护记录 | maintenance_date |
+| `em_user_profile` | 应急人员档案 | cert_number, license_number, station_status |
+| `em_ambulance` | 救护车 | plate_no, status |
+| `em_emergency_center` | 应急中心 | longitude, latitude, center_phone, status |
+| `em_emergency_center_call_record` | 应急中心通话 | call_id, employee_id, begin_time, end_time |
+| `em_emergency_center_call_join` | 通话参与人 | employee_id, call_record_id |
+
+#### 检测规则
+
+**em_call_record(通话记录)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E01-D02 | 来电号码格式不合法 | `incoming_phone` | 不匹配手机号/座机号正则 |
+| E01-D03 | 通话时长负数 | `duration` | `< 0` |
+| E01-D04 | 结束时间早于开始时间 | `begin_time`, `end_time` | 结束时间早于开始时间 |
+| E01-D04 | 已接听但通话时长为 0 | `answer_status`, `duration` | 已接听但通话时长为零 |
+| E01-D05 | 关联工单不存在 | `order_id` | 引用的应急工单不存在 |
+
+**em_aed_device / em_aed_latest(AED 设备)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E02-D03 | 电池电量百分比越界 | `battery_level` | 电池电量百分比不在 0~100 范围内 |
+| E02-D03 | 电池状态枚举越界 | `battery_status` | 不在预定义电池状态枚举集合内 |
+
+**em_aed_deploy_point(AED 部署点位)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E03-D03 | GPS 坐标超出国内范围 | `longitude`, `latitude` | `longitude < 73 OR longitude > 136 OR latitude < 4 OR latitude > 54` |
+| E03-D01 | GPS 坐标为 (0, 0) 或 NULL | `longitude`, `latitude` | GPS 坐标为空值或为 (0, 0) |
+| E03-D05 | 关联应急中心不存在 | 中心关联字段 | 引用的应急中心不存在 |
+
+**em_aed_maintenance(AED 维护记录)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E04-D03 | 维护日期为未来日期 | `maintenance_date` | `maintenance_date > CURRENT_DATE` |
+| E04-D01 | 设备缺少维护记录 | — | 设备已部署超过阈值天数且无维护记录 |
+
+**em_emergency_order(应急工单)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E05-D01 | 工单编号缺失 | `order_no` | `IS NULL OR = ''` |
+| E05-D03 | GPS 坐标超出国内范围 | `location_lat`, `location_lng` | 同 E03-D03 |
+| E05-D04 | 已关闭但无关闭时间 | `status`, `closed_at` | 已关闭但无关闭时间 |
+
+**em_user_profile(应急人员档案)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E06-D01 | 证书编号缺失 | `cert_number` | `IS NULL OR = ''` |
+| E06-D03 | 驻场状态枚举越界 | `station_status` | 不在预定义状态枚举集合内 |
+
+**em_ambulance(救护车)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E07-D01 | 车牌号缺失 | `plate_no` | `IS NULL OR = ''` |
+| E07-D03 | 车辆状态枚举越界 | `status` | 不在预定义状态枚举集合内 |
+
+**em_emergency_center_call_record(应急中心通话)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| E08-D04 | 结束时间早于开始时间 | `begin_time`, `end_time` | 结束时间早于开始时间 |
+| E08-D01 | 通话ID缺失 | `call_id` | `IS NULL OR = ''` |
+
+---
+
+### 5.4 健康体检
+
+#### 涉及数据表
+
+| 表名 | 说明 | 核心校验字段 |
+|------|------|-------------|
+| `exam_appointment` | 体检预约 | phone, employee_id, appointment_date, hospital_id, status |
+| `employee_archive` | 员工档案 | id_card, name, gender, birth_date |
+| `exam_report` | 体检报告 | report_status, employee_id, exam_date, hospital_id |
+| `exam_item_result` | 体检项目结果 | result_value, item_code, reference_range, is_abnormal, exam_report_id |
+| `exam_standard_item` | 体检基本项目 | item_code, item_name, is_key_indicator, has_reference |
+| `exam_hospital_item` | 医院体检项目 | hospital_id, item_code, linked_standard_item_id |
+| `exam_hospital` | 医院信息 | hospital_name, address, enable_occupational |
+| `exam_report_template` | 报告模板 | template_name, version, status |
+| `exam_plan` | 体检计划 | plan_name, year, date_range, hospital_id, status |
+| `exam_quota` | 体检名额 | plan_id, unit_id, total_quota, used_quota |
+
+#### 检测规则
+
+**exam_appointment(体检预约)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H01-D02 | 手机号格式不合法 | `phone` | 不匹配 11 位手机号正则(如 `"888"` 或非数字字符) |
+| H01-D01 | 预约员工ID缺失 | `employee_id` | `IS NULL OR = ''` |
+| H01-D03 | 预约日期为过去年份(录入错误) | `appointment_date` | 年份早于 2020 或晚于当前年份+2 |
+| H01-D05 | 关联医院不存在 | `hospital_id` | 引用的医院不存在 |
+
+**employee_archive(员工档案)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H02-D02 | 身份证号格式不合法 | `id_card` | 长度不为 18 位,或含非法字符,或校验位不通过 |
+| H02-D01 | 姓名缺失 | `name` | `IS NULL OR = ''` |
+| H02-D03 | 性别枚举越界 | `gender` | `NOT IN (1, 2)` 或 `NOT IN ('男', '女')` |
+| H02-D03 | 出生日期为未来日期 | `birth_date` | `birth_date > CURRENT_DATE` |
+
+**exam_report(体检报告)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H03-D03 | 报告状态枚举越界 | `report_status` | 不在预定义报告状态枚举集合内 |
+| H03-D05 | 关联员工不存在 | `employee_id` | 引用的员工档案不存在 |
+| H03-D04 | 报告状态为"已生成"但无结果明细 | `report_status` ↔ `exam_item_result` | 报告状态为已生成但无关联的检查结果明细 |
+
+**exam_item_result(体检项目结果)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H04-D01 | 检查结果值为空 | `result_value` | `IS NULL OR = ''` |
+| H04-D03 | 结果值为负数(仅适用于不应为负的指标) | `result_value` | `result_value < 0` 且项目类型非偏移量类指标 |
+| H04-D05 | 关联体检项目不存在 | `item_code` | 引用的标准体检项目不存在 |
+| H04-D05 | 关联报告不存在 | `exam_report_id` | 引用的体检报告不存在 |
+| H04-D04 | 异常标记与结果值矛盾 | `is_abnormal`, `result_value`, `reference_range` | 标记为异常但结果值在参考范围内,或标记为正常但结果值明显超出参考范围 |
+
+**exam_standard_item(体检基本项目)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H05-D01 | 项目编码缺失 | `item_code` | `IS NULL OR = ''` |
+| H05-D01 | 项目名称缺失 | `item_name` | `IS NULL OR = ''` |
+| H05-D06 | 项目编码重复 | `item_code` | 存在多条记录使用同一项目编码 |
+
+**exam_hospital_item(医院体检项目)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H06-D05 | 关联标准项目不存在 | `linked_standard_item_id` | 引用的标准体检项目不存在 |
+| H06-D05 | 关联医院不存在 | `hospital_id` | 引用的医院不存在 |
+
+**exam_report_template(报告模板)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H07-D01 | 模板名称缺失 | `template_name` | `IS NULL OR = ''` |
+| H07-D03 | 模板状态枚举越界 | `status` | `NOT IN (0, 1)`(0=无效, 1=有效) |
+
+**exam_plan(体检计划)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H08-D04 | 计划结束日期早于开始日期 | `date_range` (start, end) | `end_date < start_date` |
+| H08-D05 | 关联医院不存在 | `hospital_id` | 引用的医院不存在 |
+
+**exam_quota(体检名额)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| H09-D03 | 名额总数为负数 | `total_quota` | `< 0` |
+| H09-D03 | 已用名额大于总数 | `used_quota` | `used_quota > total_quota` |
+| H09-D05 | 关联计划不存在 | `plan_id` | 引用的体检计划不存在 |
+
+---
+
+### 5.5 营养管理
+
+#### 涉及数据表
+
+营养管理是系统中表数量最多的模块。以下按业务子域列出核心校验表。
+
+| 业务子域 | 核心表 | 关键校验字段 |
+|----------|--------|-------------|
+| 用户档案 | `nut_user`, `nut_user_profile` | phone, bmi, height, weight, birthday, health_goal, dietary_restrictions |
+| 食品管理 | `nut_food`, `nut_food_composition`, `nut_food_nutri`, `nut_food_spec` | price, nutrition_info, value_per_100g, weight |
+| 食谱管理 | `nut_recipe`, `nut_recipe_food` | total_calories, nutrition_summary, food_id, portion |
+| 订餐管理 | `nut_order`, `nut_order_item` | total_amount, status, order_type, quantity, price, subtotal |
+| 原材料 | `nut_mater_base`, `nut_raw_inbound` | price, unit, quantity, weight, supplier_id |
+| 库存 | `nut_sup_inventory`, `nut_sup_inventory_flow` | quantity, qty, current_qty_after |
+| 生产 | `nut_prod_combo_task`, `nut_prod_qc_sample`, `nut_prod_serve_record` | task_no, plan_date, status, qc_status |
+| 算法 | `nut_algo_personal`, `nut_algo_mass` | user_id, 营养结果字段 |
+| 农场 | `nut_farm_plot`, `nut_farm_crop_batch`, `nut_farm_harvest`, `nut_farm_livestock_batch` | area, plant_date, expected_harvest, quantity, count |
+| 设备 | `nut_meter_reading` | 读数合理性 |
+
+#### 检测规则
+
+**nut_user / nut_user_profile(用户档案)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N01-D01 | 手机号为空 | `phone` | `IS NULL OR = ''` |
+| N01-D03 | BMI 值越界 | `bmi` | `< 10 OR > 80` |
+| N01-D03 | 身高越界 | `height` | `< 50 OR > 250` |
+| N01-D03 | 体重越界 | `weight` | `< 20 OR > 300` |
+| N01-D04 | 计算年龄为负数 | `birthday` | 生日晚于当前日期 |
+| N01-D06 | 同一 user_id 存在多条档案记录 | `user_id` | 同一用户存在多条档案记录 |
+
+**nut_food / nut_food_composition / nut_food_nutri / nut_food_spec(食品管理)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N02-D03 | 价格为负数 | `price` | `< 0` |
+| N02-D03 | 每 100g 营养值为负数 | `value_per_100g` | `< 0` |
+| N02-D01 | 营养信息JSON为空 | `nutrition_info` | `IS NULL OR = '' OR = '{}'` |
+| N02-D02 | 营养信息JSON解析失败 | `nutrition_info` | JSON 格式不合法,无法解析 |
+| N02-D03 | 份量重量为负数或零 | `weight` | `<= 0` |
+
+**nut_recipe / nut_recipe_food(食谱管理)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N03-D01 | 总热量为空 | `total_calories` | `IS NULL` |
+| N03-D02 | 营养汇总JSON格式非法 | `nutrition_summary` | JSON 格式不合法,无法解析 |
+| N03-D05 | 食谱引用的食材ID不存在 | `food_id` | 引用的食品不存在 |
+
+**nut_order / nut_order_item(订餐管理)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N04-D03 | 订单金额为负数 | `total_amount` | `< 0` |
+| N04-D03 | 单次订餐份数超出上限 | `quantity` | `quantity > 系统设定的最大份数` |
+| N04-D03 | 单价为负数 | `price` | `< 0` |
+| N04-D04 | 订单金额与明细小计之和不一致 | `total_amount` ↔ `SUM(subtotal)` | 订单金额与明细小计之和不相等 |
+
+**nut_mater_base / nut_raw_inbound / nut_sup_inventory / nut_sup_inventory_flow(原材料与库存)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N05-D03 | 入库数量为负数或零 | `quantity` | `<= 0` |
+| N05-D03 | 入库重量为负数 | `weight` | `< 0` |
+| N05-D03 | 库存数量为负数 | `quantity` | `< 0` |
+| N05-D04 | 库存流水余额与当前库存不一致 | `current_qty_after` ↔ `quantity` | 库存流水余额与当前库存数量不相等 |
+| N05-D05 | 原材料ID在原料库中不存在 | `mater_id` | 引用的原材料不存在 |
+| N05-D05 | 供应商ID不存在 | `supplier_id` | 引用的供应商不存在 |
+
+**nut_prod_combo_task / nut_prod_qc_sample / nut_prod_serve_record(健康生产)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N06-D02 | 生产批次号含非法字符 | `task_no` | 不匹配批次号正则 |
+| N06-D04 | 生产任务完成但无供餐记录 | `status` ↔ `nut_prod_serve_record` | 生产任务已完成但无关联供餐记录 |
+| N06-D01 | 质检样品状态为"留样中"超过阈值天数 | `qc_status`, `sample_date` | 留样中状态持续超过阈值天数 |
+| N06-D03 | 计划日期为未来年份 | `plan_date` | 年份超出合理范围 |
+
+**nut_algo_personal / nut_algo_mass(算法与AI)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N07-D06 | 同一用户同日存在多条分析记录 | `user_id` + 日期 | 同一用户同日存在多条分析记录 |
+| N07-D03 | 营养素计算结果超出合理范围 | 营养结果字段 | 各营养素值 < 0 或超过人体每日上限的 N 倍 |
+
+**nut_farm_plot / nut_farm_crop_batch / nut_farm_harvest / nut_farm_livestock_batch(农场)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| N08-D03 | 地块面积为负数 | `area` | `< 0` |
+| N08-D04 | 预计采收日期早于种植日期 | `plant_date`, `expected_harvest` | `expected_harvest < plant_date` |
+| N08-D03 | 采收数量为负数 | `quantity` | `< 0` |
+| N08-D03 | 牲畜数量为负数 | `count` | `< 0` |
+
+---
+
+### 5.6 健康监测
+
+#### 涉及数据表
+
+| 表名 | 说明 | 核心校验字段 |
+|------|------|-------------|
+| `mon_device` | 可穿戴设备主表 | device_code, bind_status, bind_user_id, online_status, last_data_time |
+| `mon_device_config` | 设备监测配置 | threshold_min, threshold_max, upload_frequency |
+| `mon_bind_record` | 设备绑定/解绑记录 | bind_date, unbind_date |
+| `mon_daily_stats_heart_rate` | 心率日统计 | hr_max, hr_min, hr_avg, silence_hr_avg |
+| `mon_daily_stats_spo2` | 血氧日统计 | spo2_max, spo2_min, spo2_avg |
+| `mon_daily_stats_temperature` | 体温日统计 | temp_max, temp_min, temp_avg |
+| `mon_daily_stats_blood_pressure` | 血压日统计 | systolic_max, systolic_min, diastolic_max, diastolic_min |
+| `mon_daily_stats_sleep` | 睡眠日统计 | total_min, deep_min, score |
+| `mon_data_heart_rate` | 心率原始数据 | data_value |
+| `mon_data_spo2` | 血氧原始数据 | data_value |
+| `mon_data_temperature` | 体温原始数据 | data_value |
+| `mon_data_blood_pressure` | 血压原始数据 | systolic_value, diastolic_value |
+| `mon_data_gps` | GPS 原始数据 | longitude, latitude |
+| `mon_data_ecg` | 心电原始数据 | avg_heart_rate, data_points_url |
+| `mon_survey_answer_record` | 问卷回答记录 | user_id, survey_id, answer_time |
+| 环境监测各表 | 室内/室外/花粉/水质 | 温度、PM2.5、pH、read_time、meter_code |
+
+#### 检测规则
+
+**mon_device(设备主表)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M01-D01 | 设备编码缺失 | `device_code` | `IS NULL OR = ''` |
+| M01-D03 | 绑定状态枚举越界 | `bind_status` | 不在预定义枚举集合内 |
+| M01-D03 | 在线状态枚举越界 | `online_status` | 不在预定义枚举集合内 |
+| M01-D04 | 设备在线但最后上报时间为未来 | `online_status`, `last_data_time` | 设备在线但最后数据上报时间为未来时间 |
+
+**mon_device_config(设备配置)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M02-D03 | 阈值下限大于阈值上限 | `threshold_min`, `threshold_max` | `threshold_min > threshold_max` |
+| M02-D03 | 上报频率为负数或零 | `upload_frequency` | `<= 0` |
+
+**mon_bind_record(绑定记录)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M03-D04 | 解绑日期早于绑定日期 | `bind_date`, `unbind_date` | 解绑日期早于绑定日期 |
+| M03-D02 | 绑定日期格式非法 | `bind_date` | 无法解析为有效日期 |
+
+**mon_data_heart_rate / mon_data_spo2 / mon_data_temperature / mon_data_blood_pressure / mon_data_ecg(穿戴原始数据)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M04-D03 | 心率值越界 | `data_value` | `< 20 OR > 300` (bpm) |
+| M04-D03 | 血氧值越界 | `data_value` | `< 50 OR > 100` (%) |
+| M04-D03 | 体温值越界 | `data_value` | `< 30 OR > 45` (℃) |
+| M04-D03 | 收缩压越界 | `systolic_value` | `< 50 OR > 300` (mmHg) |
+| M04-D03 | 舒张压越界 | `diastolic_value` | `< 30 OR > 200` (mmHg) |
+| M04-D04 | 收缩压低于舒张压 | `systolic_value`, `diastolic_value` | `systolic_value < diastolic_value` |
+| M04-D06 | 同一设备同一时间点重复数据 | `device_id` + `data_time` | 同一设备同一时间点存在多条数据 |
+| M04-D02 | 心电数据文件URL不可达 | `data_points_url` | URL格式非法或指向不存在资源 |
+
+**mon_data_gps(GPS 数据)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M05-D03 | GPS 坐标超出国内范围 | `longitude`, `latitude` | `longitude < 73 OR longitude > 136 OR latitude < 4 OR latitude > 54` |
+
+**mon_daily_stats_heart_rate / mon_daily_stats_spo2 / mon_daily_stats_temperature / mon_daily_stats_blood_pressure / mon_daily_stats_sleep(日统计聚合数据)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M06-D04 | 最大心率低于最小心率 | `hr_max`, `hr_min` | `hr_max < hr_min` |
+| M06-D04 | 最大收缩压低于最小收缩压 | `systolic_max`, `systolic_min` | `systolic_max < systolic_min` |
+| M06-D04 | 最大舒张压低于最小舒张压 | `diastolic_max`, `diastolic_min` | `diastolic_max < diastolic_min` |
+| M06-D04 | 静息心率高于平均心率 | `silence_hr_avg`, `hr_avg` | `silence_hr_avg > hr_avg` |
+| M06-D03 | 睡眠总时长越界 | `total_min` | `< 0 OR > 1440` |
+| M06-D04 | 深睡时长大于总时长 | `deep_min`, `total_min` | `deep_min > total_min` |
+
+**mon_survey_answer_record / mon_survey_activity(调查问卷)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M07-D04 | 回答时间早于活动发布时间 | `answer_time` ↔ `mon_survey_activity.publish_time` | `answer_time < publish_time` |
+| M07-D06 | 同一用户同一问卷多条回答 | `user_id`, `survey_id` | 同一用户对同一问卷存在多条回答 |
+
+**mon_env_data_indoor / mon_env_data_outdoor / mon_env_data_pollen / mon_env_data_water(环境监测)**
+
+| 编码 | 规则描述 | 涉及字段 | 判定逻辑 |
+|------|----------|----------|----------|
+| M08-D03 | PM2.5 值为负数 | PM2.5字段 | `< 0` |
+| M08-D03 | pH 值越界 | `ph` | `< 0 OR > 14` |
+| M08-D03 | 环境温度超出设备量程 | 温度字段 | 超出传感器标定量程范围 |
+| M08-D05 | 环境数据关联仪表不存在 | `meter_code` | 引用的环境监测仪表不存在 |
+
+---
+
+## 六、附录
+
+### 6.1 脏数据类型编码速查
+
+| 编码 | 名称 | 一句话描述 |
+|------|------|--------------|
+| D01 | 数据缺失 | 该有的值没有 |
+| D02 | 格式违规 | 数据值的形态不对 |
+| D03 | 取值越界 | 值的量级不对 |
+| D04 | 逻辑冲突 | 多个值互相矛盾 |
+| D05 | 关联断裂 | 外键指向了不存在的记录 |
+| D06 | 数据重复 | 本应唯一的记录出现了多条 |
+
+### 6.2 规则编号说明
+
+规则编号格式:`{模块前缀}{序号}-{脏数据类型编码}`
+
+| 模块 | 前缀 |
+|------|------|
+| 健康咨询 | C (Consult) |
+| 应急就医 | E (Emergency) |
+| 健康体检 | H (Health Checkup) |
+| 营养管理 | N (Nutrition) |
+| 健康监测 | M (Monitor) |
+| 系统管理 | S (System) |
+
+示例:`C01-D03` = 健康咨询模块第 1 组规则,脏数据类型为 D03(取值越界)。
+
+### 6.3 各模块规则数量统计
+
+| 模块 | D01 | D02 | D03 | D04 | D05 | D06 | 合计 |
+|------|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
+| 系统管理 | 12 | 2 | 14 | 1 | 5 | 7 | **41** |
+| 健康咨询 | 3 | 0 | 12 | 5 | 8 | 0 | **28** |
+| 应急就医 | 5 | 2 | 10 | 4 | 3 | 0 | **24** |
+| 健康体检 | 6 | 2 | 9 | 5 | 9 | 2 | **33** |
+| 营养管理 | 4 | 3 | 14 | 5 | 4 | 2 | **32** |
+| 健康监测 | 1 | 2 | 14 | 9 | 1 | 3 | **30** |
+| **合计** | **31** | **11** | **73** | **29** | **30** | **14** | **188** |
diff --git a/index.html b/index.html
index bb1f5ce..209431b 100644
--- a/index.html
+++ b/index.html
@@ -917,6 +917,11 @@ const PROJECT_DOCS = [
title: '缓存中转服务器产品需求说明书',
file: 'web-admin/nutrition/doc/缓存中转服务器产品需求说明书.md',
desc: '缓存中转服务器的业务目标、功能范围、页面与交互需求说明。'
+ },
+ {
+ title: '异常事件数据质量控制规则说明书',
+ file: 'doc/数据质量筛查规则说明书.md',
+ desc: '定义平台各业务板块系统脏数据的统一质量校验规则,含 6 个板块共 188 条检测规则。'
}
];
diff --git a/web-admin/nutrition/doc/营养管理功能说明.md b/web-admin/nutrition/doc/营养管理功能说明.md
index 91e47d9..90d8904 100644
--- a/web-admin/nutrition/doc/营养管理功能说明.md
+++ b/web-admin/nutrition/doc/营养管理功能说明.md
@@ -1,8 +1,8 @@
# 营养管理功能说明书
-> 文档版本:v2.0 | 更新日期:2026-07-15
+> 文档版本:v2.1 | 更新日期:2026-07-27
-> 本说明书基于 `web-admin/nutrition/` 后台原型与 `other-module/mini-program/nutrition/` 小程序原型重新梳理,描述营养管理版块的功能清单与业务概况。各页面的逐页调整明细见同目录《原型调整记录.md》。
+> 本说明书基于 `web-admin/nutrition/` 后台原型与 `other-module/mini-program/nutrition/` 小程序原型重新梳理,描述营养管理版块的功能清单与业务概况。硬件终端设备原型参见 `other-module/canteen-device/`。各页面的逐页调整明细见同目录《原型调整记录.md》。
---
@@ -667,3 +667,83 @@ flowchart LR
| 双端协同 | B 端精细化管理 + C 端便捷化服务,数据实时互通 |
| 边缘云端协同 | 食堂边缘节点缓存中转,终端设备断网可用;数据下发/上报任务化追踪;异常告警集中处置 |
| 多终端设备生态 | 餐线营养秤、净菜包装秤、净菜组配秤、毛菜验收秤、加工毛菜秤等专用终端设备覆盖生产全流程 |
+
+---
+
+## 七、硬件设备生态
+
+### 7.1 硬件设备清单与后台功能映射
+
+> 下表按业务域分组列出全部硬件终端设备,说明其核心硬件组件、关联后台模块及在业务链路中的上下游关系。
+> 关联后台模块名称对应本文第五章「模块功能概要说明」中的各模块。
+
+#### 7.1.1 卫生供应域(8 款)
+
+| 序号 | 设备名称 | 研发状态 | 核心硬件组件 | 关联后台模块 | 上游业务 | 下游业务 |
+|------|---------|---------|-------------|-------------|---------|---------|
+| 1 | 毛菜卫检设备 | 待硬件选型 | 农残/重金属/微生物检测传感器 | 卫生供应 | 绿色种采(采供管理→毛菜入库) | 卫生供应(卫生检测→仓储管理) |
+| 2 | 毛菜验收秤 | 已设计 | 称重传感器、食材识别摄像头、人脸识别摄像头、场景监控摄像头 | 卫生供应、绿色种采 | 绿色种采(采供管理、供商管理) | 卫生供应(毛菜入库、卫生检测) |
+| 3 | 毛菜库存柜 | 无需设计 | 温湿度传感器、库存货架 | 卫生供应 | 卫生供应(毛菜入库、卫生检测) | 卫生供应(净菜加工出库) |
+| 4 | 加工毛菜秤 | 已设计 | 称重传感器、AI识别摄像头、人脸识别摄像头 | 卫生供应 | 卫生供应(仓储管理→出库领料) | 卫生供应(净菜加工→净菜入库) |
+| 5 | 净菜加工秤 | 已设计 | 称重传感器、AI识别摄像头、人脸识别摄像头 | 卫生供应 | 卫生供应(仓储管理→毛菜出库) | 卫生供应(净菜入库、净菜包装) |
+| 6 | 净菜库存柜 | 无需设计 | 温湿度传感器、库存货架 | 卫生供应 | 卫生供应(净菜加工→净菜入库) | 卫生供应(净菜包装)、健康生产(净菜下单→配菜组合) |
+| 7 | 净菜组配秤 | 已设计 | 称重传感器(主辅材+调料双区)、食材识别摄像头、人脸识别摄像头 | 卫生供应、健康生产 | 卫生供应(净菜包装出库)、健康生产(净菜下单) | 卫生供应(净菜包装→内供/外售)、健康生产(配菜组合→烹饪加工) |
+| 8 | 净菜包装贴标秤 | 已设计 | 称重传感器、标签打印机、食材识别摄像头、人脸识别摄像头 | 卫生供应、健康生产 | 卫生供应(净菜组配)、健康生产(配菜组合) | 卫生供应(净菜内供配送、净菜外售发货) |
+
+#### 7.1.2 健康生产域(4 款)
+
+| 序号 | 设备名称 | 研发状态 | 核心硬件组件 | 关联后台模块 | 上游业务 | 下游业务 |
+|------|---------|---------|-------------|-------------|---------|---------|
+| 9 | 食安监控设备 | 待硬件选型 | 视频监控摄像头、温湿度传感器、烟感/燃气传感器 | 健康生产、异常事件 | 健康生产(烹饪加工、出餐管理) | 健康生产(日常质检)、异常事件(异常告警) |
+| 10 | 水电气计量表 | 无需设计 | 水表/电表/燃气表传感器 | 健康生产 | 健康生产(烹饪加工→能耗消耗) | 健康生产(加工统计→成本核算、日常质检→能耗检查) |
+| 11 | 净菜厨房柜 | 无需设计 | 温湿度传感器、冷藏/保鲜模块 | 健康生产 | 卫生供应(净菜内供→配送收货) | 健康生产(配菜组合→领料出柜、烹饪加工) |
+| 12 | 加工提示屏 | 无需设计 | 显示屏 | 健康生产 | 健康生产(配菜组合→烹制任务下发) | 健康生产(烹饪加工→操作指引展示) |
+
+#### 7.1.3 营养用餐域(7 款)
+
+| 序号 | 设备名称 | 研发状态 | 核心硬件组件 | 关联后台模块 | 上游业务 | 下游业务 |
+|------|---------|---------|-------------|-------------|---------|---------|
+| 13 | 营养检测设备 | 待硬件选型 | 近红外光谱/图像分析传感器 | 营养用餐、健康生产 | 营养用餐(菜品总库→餐品档案)、健康生产(烹饪加工→出品) | 健康生产(营养质检→四维检测)、营养用餐(营养数据→摄入统计) |
+| 14 | 吐盘机 | 无需设计 | 餐盘自动分配机构 | 营养用餐 | 营养用餐(餐线管理→餐线配置) | 营养用餐(排队取餐→自助取盘、取餐管理→回盘核销) |
+| 15 | 餐线充值屏 | 待产品设计 | 触摸屏、IC卡/NFC读卡器 | 营养用餐 | 营养用餐(用户管理→账户体系) | 营养用餐(消费数据→扣款流水)、小程序(我的余额→账户同步) |
+| 16 | 营养秤 | 已设计 | 称重传感器、AI菜品识别摄像头、人脸识别摄像头 | 营养用餐、健康生产 | 营养用餐(菜品总库、食谱管理)、健康生产(食谱排期→当日菜单) | 营养用餐(取餐管理→核销记录、营养数据→摄入统计)、档案(员工营养数据) |
+| 17 | 档口营养秤 | 已设计 | 称重传感器、AI菜品识别摄像头、人脸识别摄像头 | 营养用餐 | 营养用餐(菜品总库→菜品信息、食谱管理→当日食谱) | 营养用餐(点餐订单→下单、营养数据→营养素计算)、档案(员工营养数据) |
+| 18 | 结算设备 | 无需设计 | 触摸屏、IC卡/NFC/二维码读卡器 | 营养用餐 | 营养用餐(取餐管理→取餐核销、点餐订单→订单确认) | 营养用餐(消费数据→支付流水)、小程序(我的订单→记录、我的余额→扣款) |
+| 19 | 营养得分屏 | 无需设计 | 显示屏 | 营养用餐、档案管理 | 营养用餐(营养数据→摄入分析)、档案(员工营养数据→历史趋势) | 小程序(健康档案→数据同步、AI营养咨询→建议依据) |
+
+### 7.2 设备与业务链路对照
+
+下图为四条业务主线上各硬件设备的部署位置,展示设备在"从田间到餐桌"全链路中的节点角色。
+
+```
+食材供应链(田间→仓库)
+ 土地环境 → 种养殖监控 → 收成入库 → 采供管理 → 供商管理
+ ↓
+ 毛菜验收秤 ②
+ ↓
+ 毛菜入库
+ ↓
+食材加工链(仓库→厨房) 毛菜卫检设备 ①
+ 毛菜入库 → 卫生检测 → 仓储管理 → 净菜加工 → 净菜入库 → 净菜包装 → 内供/外售
+ ↑ ↑ ↑ ↑ ↑ ↑ ↑
+ 毛菜库存柜 ③ 毛菜卫检 毛菜库存柜 加工毛菜秤 ④ 净菜库存柜 ⑥ 净菜包装 净菜组配秤 ⑦
+ 净菜加工秤 ⑤ 贴标秤 ⑧ 净菜包装贴标秤 ⑧
+
+配餐生产链(厨房→餐桌)
+ 食谱排期 → 净菜下单 → 配菜组合 → 烹饪加工 → 出餐管理 → 余量处理
+ ↑ ↑
+ 净菜厨房柜 ⑪ 加工提示屏 ⑫
+ 净菜组配秤 ⑦ 食安监控设备 ⑨
+ 水电气计量表 ⑩
+
+员工服务链(餐桌→健康)
+ 浏览食谱 → 在线订餐 → 取餐核销 → 营养分析 → 健康档案 → AI 咨询
+ ↑ ↑ ↑ ↑
+ 档口营养秤 ⑰ 档口营养秤 营养秤 ⑯ 营养得分屏 ⑲
+ 结算设备 ⑱ 吐盘机 ⑭ 营养检测设备 ⑬
+ 结算设备 ⑱
+ 餐线充值屏 ⑮
+```
+
+> 图中编号对应 7.1 节硬件清单中的序号。同一设备可能横跨多个业务链环节(如净菜组配秤同时服务于食材加工链和配餐生产链)。
+
diff --git a/web-admin/nutrition/doc/规格管理体系分析与设计方案.md b/web-admin/nutrition/doc/规格管理体系分析与设计方案.md
new file mode 100644
index 0000000..bd6a7c8
--- /dev/null
+++ b/web-admin/nutrition/doc/规格管理体系分析与设计方案.md
@@ -0,0 +1,386 @@
+# 营养管理——规格管理体系分析与设计方案
+
+> 撰写日期:2026-07-24 | 状态:**待评审**
+
+---
+
+## 一、背景
+
+营养管理从餐品总库、食物成分到后续的各个订单业务、采购、验收、入库等环节中都涉及"规格"概念,但目前:
+
+- 规格没有统一维护入口
+- 各业务环节之间规格如何转换完全没有考虑
+- 同一概念在不同页面用不同数据格式表达,非常混乱
+
+本文档对 10 个业务环节中"规格"的现状进行完整梳理,并给出统一设计方案。
+
+---
+
+## 二、全景总览:一个"规格",十种表达
+
+```
+采购环节 加工/包装环节 消费/生产环节
+─────── ──────────── ────────────
+farm-purchase sup-in-cleanin prod-clean-order
+ spec: "25kg/件" (自由文本) 入库规格: "袋装/500g" (下拉) specGram: 500 (自由输入)
+ 净菜类型: "丁(1.5cm)" (联动下拉) specLabel: "500g/袋"
+
+raw-veg-inspection-scale sup-in-raw sup-clean-external
+ 采购规格: "斤" (14种下拉) 入库规格: "25kg/件" (7种下拉) spec: "5kg/箱" (自由输入+下拉)
+ 实收规格: "斤" (14种下拉)
+ sup-pkg-label sup-clean-internal
+din-dish-all 净菜包: 500 (纯数字输入) spec: "500g/袋" (自由输入+下拉)
+ specs: [{name:'标准份', 餐品包: "标准份(500g)" (下拉)
+ weight:200, price:12}] prod-proc-combo
+ 餐品规格: "标准份(300g)" (硬编码下拉)
+din-ingredient
+ vegTypes: [{name:'肉丁', prod-proc-cook
+ yieldRate:'92'}] 餐品规格: "标准份(300g)" (只读)
+ (此处的 vegTypes 实际是净菜类型)
+ prod-proc-cabinet
+ 包装规格: "5kg/袋" (从订单回显)
+```
+
+**问题本质**:缺少一个统一的规格主数据中心,导致:
+1. 同一概念(如"包装规格")在 6 个页面中以 6 种不同方式表达
+2. 不同层级规格之间(采购→净菜→餐品)完全没有转换规则
+3. 每个页面各自维护一套下拉选项,互不同步
+
+---
+
+## 三、各环节规格现状详析
+
+### 3.1 餐品总库 `din-dish-all.html`——规格的定义源头(但只覆盖餐品层)
+
+```javascript
+// 每个餐品对象内嵌 specs 数组
+specs: [
+ { name:'标准份', weight:200, price:12, cleanPrice:9, cleanSale:false, isDefault:true },
+ { name:'小份', weight:120, price:8, cleanPrice:6, cleanSale:false },
+ { name:'大份', weight:300, price:18, cleanPrice:13, cleanSale:false }
+]
+```
+
+- 规格名称枚举:`标准份` / `小份` / `大份` / `迷你份` / `加大份`(在 `din-dish-add.html` 的下拉中定义)
+- 关键字段:name(份型名称)、weight(成品重量g)、price(餐品售价)、cleanPrice(净菜包售价)、cleanSale(是否外售上架)
+- **问题**:组配/烹制等下游环节使用另一套硬编码的规格下拉,与总库中定义的不联动
+
+### 3.2 食物成分库 `din-ingredient.html`——食材级"规格"
+
+```javascript
+// vegTypes 实际表达的是"净菜类型"而非规格
+vegTypes: [
+ { name:'肉片', yieldRate:'95' }, // 猪里脊可加工为肉片,出净率95%
+ { name:'肉丁', yieldRate:'92' }, // 可加工为肉丁,出净率92%
+ { name:'肉块', yieldRate:'97' } // 可加工为肉块,出净率97%
+]
+```
+
+- 这里的"规格"实际上是**净菜加工类型 + 出净率**
+- **问题**:出净率数据存在食材库中,但采购→净菜入库的换算从未使用出净率;净菜类型字典散落在 3-4 个页面中重复定义(`cleanFoodTypeOptions`、`cleanTypeMap` 等)
+
+### 3.3 采供管理 `farm-purchase.html`
+
+```
+采购明细子表:溯源码 | 食材名称 | 食材分类 | 采购数量 | 规格 | 单价 | 小计
+
+规格取值(自由文本输入,无约束):
+ 蔬菜/水产 → "散装(kg)"
+ 肉类 → "25kg/件"
+ 粮油 → "5L/桶" / "20kg/箱"
+```
+
+- **问题**:完全自由文本,既不下拉也不关联食材目录。采购写了 "25kg/件",到毛菜入库环节还得重新选
+
+### 3.4 毛菜验收秤 `01-verify-inspection.html`
+
+```
+表格8列:食材 | 采购规格 | 采购量 | 采购单价 | 应付金额 | 实收规格 | 实收量 | 操作
+
+食材目录(20种可称重食材): { price:3.5, unit:'斤' }
+调料目录(8种按规格验收): { price:12, unit:'瓶', spec:'500ml/瓶' }
+
+UNIT_OPTIONS = ['斤','公斤','克','千克','袋','瓶','箱','桶','罐','份','个','只','条','把']
+```
+
+- 采购规格 = 14 种单位下拉,默认取食材目录的 unit
+- 实收规格 = 同上,默认跟随采购规格但可修改
+- **核心差异**:调料类走"规格验收"(不能称重,直接点收数量),食材类走"称重验收"
+- **问题**:`UNIT_OPTIONS` 混在一起的东西太多——有重量单位(斤/公斤/克/千克)、容器单位(袋/瓶/箱/桶/罐)、计数单位(份/个/只/条/把),语义完全不同
+
+### 3.5 毛菜入库 `sup-in-raw.html`
+
+```
+入库规格下拉(7个固定选项):
+ 25kg/件 | 20kg/箱 | 15kg/箱 | 10kg/筐 | 5kg/袋 | 1kg/包 | 散装(kg)
+```
+
+- **问题**:固定下拉与采购规格不联动,采购中选的 "25kg/件" 在这里要重新选一遍
+
+### 3.6 净菜入库 `sup-in-cleanin.html`
+
+```
+入库规格下拉:袋装/500g | 袋装/400g | 盒装/300g | 散装/整棵 | 其他
+
+食材→净菜类型联动:
+ 土豆 → [土豆丝, 土豆块, 土豆泥]
+ 猪里脊 → [丁(1.5cm), 丝(0.8cm), 片(2mm)]
+ 西兰花 → [小朵, 西兰花段, 西兰花梗]
+```
+
+- 两层选择:先选食材→联动净菜类型→再选入库规格(袋/盒/散装+克数组合)
+- **问题**:食材→净菜类型的映射(`cleanFoodTypeOptions`)在本页面独立维护,与 `din-ingredient.html` 中的 `vegTypes` 不联动
+
+### 3.7 净菜订单 `prod-clean-order.html`
+
+```
+nc模式(净菜包): 食材 | 净菜名称 | 包装规格(g) | 份数 | 计量(kg)
+cp模式(餐品包): 餐品名称 | 包装规格 | 份数 | 计量(kg)
+
+nc: 包装规格 = 自由输入整数克数 → specLabel = "500g/袋"(自动拼接)
+cp: 包装规格 = 从 cpSpecOptions 下拉选择 → specLabel = "标准份(500g)"
+
+重量公式: weight = specGram × qty / 1000
+```
+
+```javascript
+// 硬编码的餐品规格选项(与餐品总库中的 specs 不联动)
+cpSpecOptions = {
+ '宫保鸡丁': [{label:'标准份(500g)', grams:500}, {label:'加量份(750g)', grams:750}],
+ '清蒸鱼': [{label:'标准份(600g)', grams:600}, {label:'大份(900g)', grams:900}],
+ ...
+}
+```
+
+- **问题**:nc 模式的规格自由输入整数,可能出现 499g、501g 等非标准包装;cp 模式的选项硬编码,总库改了这里不会同步
+
+### 3.8 外售/内供 `sup-clean-external.html` / `sup-clean-internal.html`
+
+与净菜订单结构相同(nc/cp 两套模式),但 Mock 数据规格格式不同:
+
+| 环节 | nc 规格示例 | cp 规格示例 |
+|------|------------|------------|
+| 外售 | `5kg/箱`, `1kg/箱` | `标准份(500g)` |
+| 内供 | `500g/袋`, `2kg/盘` | `标准份(500g)` |
+
+- **问题**:外售用 `箱`,内供用 `袋/盘`,包装容器类型没有统一约束
+
+### 3.9 净菜组配 + 按单烹制 `prod-proc-combo.html` / `prod-proc-cook.html`
+
+```
+组配: 餐品规格下拉 → 小份(200g) | 标准份(300g) | 大份(500g)【硬编码】
+烹制: 餐品规格只读显示 + 生重(kg)/熟重(kg)/生熟比
+```
+
+```javascript
+// 组配中每份餐品的食材配方(重量以克为单位)
+recipeData = {
+ '宫保鸡丁': [
+ {trace:'NC-PROC-JXR-...', name:'鸡胸肉', type:'主材', amount:120, unit:'g'},
+ {trace:'NC-PROC-HUS-...', name:'花生米', type:'辅材', amount:30, unit:'g'},
+ ...
+ ]
+}
+```
+
+- **问题**:规格下拉选项 "小份(200g)/标准份(300g)/大份(500g)" 与餐品总库中定义的 specs(标准份=200g, 小份=120g, 大份=300g)完全不一致
+
+### 3.10 净菜进柜 `prod-proc-cabinet.html`
+
+- 规格从订单数据回显,不独立编辑
+- 跟踪 "初始重量(kg)" 和 "当前余量(kg)",规格只作为辅助标识
+
+---
+
+## 四、加工规格的额外维度
+
+在净菜配比秤设置页 `clean-veg-ratio-scale/03-settings.html` 中:
+
+```
+丝条粗细 → 丝条规格的标准粗细
+滚刀块大小 → 滚刀块规格的标准大小
+方丁大小 → 方丁规格的标准大小
+```
+
+这些与净菜类型中的 "丁(1.5cm)"、"丝(0.8cm)" 概念重复但未统一。
+
+---
+
+## 五、核心问题总结
+
+| # | 问题 | 严重程度 | 涉及页面数 |
+|---|------|---------|-----------|
+| 1 | **采购规格自由文本,无约束无联动**——下游入库要重选 | 🔴 高 | 3 |
+| 2 | **餐品总库定义了 specs,但组配/烹制用另一套硬编码下拉** | 🔴 高 | 3 |
+| 3 | **包装容器类型(袋/盒/箱/盘/筐/桶/瓶)散落各处,无统一枚举** | 🔴 高 | 6 |
+| 4 | **食材→净菜类型映射在 3+ 页面重复定义,互不同步** | 🟡 中 | 4 |
+| 5 | **采购→净菜→餐品三级规格之间没有任何转换规则** | 🔴 高 | 全部 |
+| 6 | **加工规格(切配参数)与净菜类型概念重复** | 🟡 中 | 2 |
+| 7 | **单位混用:重量单位、容器单位、计数单位混在一起** | 🟡 中 | 2 |
+
+---
+
+## 六、设计方案:四级规格主数据体系
+
+### 6.1 架构总览
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ 规格主数据(统一维护入口) │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ L1 采购规格 L2 净菜加工规格 L3 净菜包装规格 │
+│ ────────── ────────────── ────────────── │
+│ 采购单位 + 包装量 净菜类型 + 切配参数 容器类型 + 净含量 │
+│ 例: 25kg/件 例: 丁(1.5cm) 例: 袋装/500g │
+│ 散装(kg) 丝(0.8cm) 盒装/300g │
+│ 5L/桶 片(2mm) 箱装/5kg │
+│ │
+│ L4 餐品规格 │
+│ ────────── │
+│ 份型 + 成品重量(g) │
+│ 例: 标准份(300g) │
+│ 大份(500g) │
+│ │
+│ 转换链: L1 ──(出净率)──→ L2 ──(包装组合)──→ L3 ──(配方)──→ L4 │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### 6.2 各级规格数据模型
+
+#### L1 — 采购规格(在食材目录中维护)
+
+| 字段 | 类型 | 说明 | 示例 |
+|------|------|------|------|
+| `purchaseUnit` | enum | 采购计量单位 | `kg`, `件`, `斤`, `L` |
+| `packSize` | number | 单件包装量 | `25`(25kg/件) |
+| `specLabel` | string | 规格标签(自动生成) | `25kg/件`, `散装(kg)` |
+| `allowLoose` | bool | 是否允许散装采购 | true/false |
+| `isWeightBased` | bool | 是否按重量验收(否则按规格验收) | true=称重, false=点收 |
+
+#### L2 — 净菜加工规格(在食材目录中维护)
+
+| 字段 | 类型 | 说明 | 示例 |
+|------|------|------|------|
+| `cleanTypeName` | string | 净菜类型名称 | `丁`, `丝`, `片`, `块`, `段` |
+| `cutParam` | string | 切配参数 | `1.5cm`, `0.8cm`, `2mm` |
+| `yieldRate` | number | 出净率(%) | `92` |
+| `displayName` | string | 完整显示名(自动生成) | `丁(1.5cm)` |
+
+#### L3 — 净菜包装规格(独立字典)
+
+| 字段 | 类型 | 说明 | 示例 |
+|------|------|------|------|
+| `containerType` | enum | 包装容器类型 | `袋`, `盒`, `箱`, `盘`, `筐`, `桶` |
+| `netWeight` | number | 净含量(g) | `500`, `300`, `5000` |
+| `specLabel` | string | 规格标签(自动生成) | `袋装/500g` |
+
+#### L4 — 餐品规格(在餐品总库中维护,现有结构基本正确)
+
+| 字段 | 类型 | 说明 | 示例 |
+|------|------|------|------|
+| `specName` | string | 份型名称 | `标准份`, `大份`, `小份` |
+| `finishedWeight` | number | 成品重量(g) | `300` |
+| `price` | number | 餐品售价(元) | `12` |
+| `cleanPrice` | number | 净菜包售价(元) | `9` |
+
+### 6.3 规格转换规则
+
+```
+采购规格(L1) → 净菜加工规格(L2):
+ 净料重量 = 采购量 × 出净率(来自食材目录 vegTypes.yieldRate)
+ 例:25kg/件 猪里脊 → 切肉丁(出净率92%) → 净料 = 25 × 0.92 = 23kg
+
+净菜加工规格(L2) → 净菜包装规格(L3):
+ 可包装份数 = 净料重量(g) ÷ 包装规格净含量(g)
+ 例:23kg = 23000g → 袋装/500g → 可包 23000 ÷ 500 = 46 袋
+
+净菜包装规格(L3) → 餐品规格(L4):
+ 可产出份数 = (袋数 × 净含量) ÷ 单品食材用量
+ 例:46袋 × 500g/袋 ÷ 120g(宫保鸡丁每份需肉丁量) ≈ 191 份
+```
+
+### 6.4 规格字典管理页面(新增)
+
+建议在营养管理后台新增"规格字典"维护入口:
+
+```
+┌─ 规格字典管理 ──────────────────────────────────────┐
+│ │
+│ Tab: [包装容器] [切配类型] [份型] [包装规格组合] │
+│ │
+│ ▸ 包装容器字典(L3 基础) │
+│ 袋 | 盒 | 箱 | 盘 | 筐 | 桶 | 瓶 | 罐 │
+│ │
+│ ▸ 切配类型字典(L2 基础) │
+│ 丁 | 丝 | 片 | 块 | 段 | 末 | 泥 | 条 │
+│ └─ 可配置默认切配参数(丁→1.5cm, 丝→0.8cm, ...) │
+│ │
+│ ▸ 份型字典(L4 基础) │
+│ 标准份 | 小份 | 大份 | 迷你份 | 加大份 │
+│ │
+│ ▸ 包装规格组合(L3 = 容器 × 净含量) │
+│ 袋装/500g, 袋装/1000g, 盒装/300g, 箱装/5kg, ... │
+│ │
+│ ▸ 食材-净菜类型关联(在食材基础档案中维护,L1→L2) │
+│ 猪里脊 → 肉丁(1.5cm/出净率92%) │
+│ 肉丝(0.8cm/出净率95%) │
+│ 肉片(2mm/出净率97%) │
+└──────────────────────────────────────────────────────┘
+```
+
+### 6.5 各环节改造对照表
+
+| 环节 | 当前做法 | 改造后 |
+|------|---------|--------|
+| **食材目录** `din-ingredient` | vegTypes 表达净菜类型 | 增加 L1 采购规格字段 + L2 净菜类型(含切配参数、出净率) |
+| **餐品总库** `din-dish-all` | specs 基本正确 | 规格名称从份型字典读取,保持现有数据模型 |
+| **采供管理** `farm-purchase` | 规格自由文本 | 从食材目录 L1 自动带出,不可自由输入 |
+| **毛菜验收秤** | 14种单位混用 | 采购规格从订单 L1 带出;实收规格默认跟随,允许修正;区分重量/容器/计数三类单位 |
+| **毛菜入库** `sup-in-raw` | 7种固定下拉 | 从采购订单继承 L1,无需重选 |
+| **净菜入库** `sup-in-cleanin` | 净菜类型+入库规格分开选 | 从 L2+L3 字典联动选择 |
+| **净菜订单** `prod-clean-order` | nc 自由输克数 / cp 硬编码下拉 | nc 从 L3 包装规格字典选择;cp 从餐品总库 L4 动态拉取 |
+| **外售/内供** | nc 自由输克数 | 同上,从 L3 选择 |
+| **净菜组配** `prod-proc-combo` | 硬编码规格下拉 | 从餐品总库 L4 动态拉取 |
+| **按单烹制** `prod-proc-cook` | 规格只读 + 硬编码 | 规格从 L4 带出,生熟比关联 L2 出净率 |
+| **包装标签** `sup-pkg-label` | 纯数字输入 | 从 L3 选择 |
+| **加工秤设置** `clean-veg-ratio-scale/03-settings` | 丝条/滚刀块/方丁独立配置 | 从 L2 切配参数字典读取,作为默认值可覆盖 |
+
+---
+
+## 七、需要新增/修改的文件
+
+### 新增
+
+| 文件 | 说明 |
+|------|------|
+| `web-admin/nutrition/din-dict-spec.html` | 规格字典管理页面(容器/切配/份型/组合) |
+
+### 修改
+
+| 文件 | 改造内容 |
+|------|---------|
+| `web-admin/nutrition/din-ingredient.html` | 增加 L1 采购规格字段、L2 净菜类型(含切配参数+出净率) |
+| `web-admin/nutrition/din-dish-all.html` | 规格名称从份型字典读取 |
+| `web-admin/nutrition/din-dish-add.html` | 规格名称下拉从份型字典读取 |
+| `web-admin/nutrition/farm-purchase.html` | 规格从自由文本改为 L1 自动带出 |
+| `web-admin/nutrition/sup-in-raw.html` | 入库规格从采购订单继承 L1 |
+| `web-admin/nutrition/sup-in-cleanin.html` | 联动 L2+L3 字典 |
+| `web-admin/nutrition/prod-clean-order.html` | nc 从 L3 选 / cp 从 L4 拉取 |
+| `web-admin/nutrition/sup-clean-external.html` | nc 从 L3 选 / cp 从 L4 拉取 |
+| `web-admin/nutrition/sup-clean-internal.html` | nc 从 L3 选 / cp 从 L4 拉取 |
+| `web-admin/nutrition/sup-pkg-label.html` | 包装规格从 L3 选择 |
+| `web-admin/nutrition/prod-proc-combo.html` | 餐品规格从 L4 动态拉取 |
+| `web-admin/nutrition/prod-proc-cook.html` | 餐品规格从 L4 动态拉取 |
+| `other-module/canteen-device/raw-veg-inspection-scale/01-verify-inspection.html` | 单位下拉分类重构 |
+| `other-module/canteen-device/clean-veg-ratio-scale/03-settings.html` | 加工参数从 L2 读取 |
+
+---
+
+## 八、待讨论事项
+
+1. **规格字典管理页面放在哪个菜单下?** —— 建议放在"营养用餐 > 字典管理"下的子页面,或新建 "基础数据 > 规格字典"
+2. **历史数据兼容?** —— 如果已有真实生产数据,规格升级需要迁移脚本
+3. **L1 采购规格的单位细分** —— 当前 14 种混用的 UNIT_OPTIONS 拆分后,是单独建表还是用标签分类?
+4. **转换规则的计算精度** —— 出净率% → 净料重量 → 可包装份数 → 可产出份数,这条链上每一步的取整规则(向上/向下/四舍五入)需要明确