-
✓
-
少食多餐:建议每日4-5餐,控制单餐摄入量,避免暴饮暴食。
+
+
🏥 慢病管控
+
+
!
限钠控盐:高血压1级,每日食盐≤5g(≈2000mg钠),每餐不超过800mg钠。
+
!
低嘌呤饮食:高尿酸无症状期,每日嘌呤≤300mg。已排除动物内脏、沙丁鱼、浓肉汤等高嘌呤食材。
-
-
!
-
低盐低油:每日食盐摄入不超过6g,烹调油控制在25g以内。
+
+
+
🥗 通用健康建议
+
+
✓
少食多餐:建议每日4餐(含加餐),控制单餐摄入量。
+
i
饮水充足:每日饮水量建议1500-2000ml,高尿酸者建议2000ml以上。
+
✓
食材多样:每日摄入食物种类不少于12种,每周不少于25种。
-
-
i
-
饮水充足:每日饮水量建议1500-2000ml,少量多次饮用。
-
-
-
✓
-
粗细搭配:主食中粗粮占比不低于1/3,增加膳食纤维摄入。
-
-
-
!
-
控糖限酒:减少含糖饮料摄入,男性每日酒精不超过25g。
-
-
-
i
-
食材多样:每日摄入食物种类不少于12种,每周不少于25种。
+
+
+
🌍 地区适配
+
+
i
本地食材优先:食谱优先使用本地常见食材,兼顾口味与营养。
@@ -271,117 +437,438 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
diff --git a/employee-app/weight-management/doc/AI-REQUIREMENTS.md b/employee-app/weight-management/doc/AI-REQUIREMENTS.md
new file mode 100644
index 0000000..3461195
--- /dev/null
+++ b/employee-app/weight-management/doc/AI-REQUIREMENTS.md
@@ -0,0 +1,307 @@
+# 膳食推荐AI算法 — 需求说明
+
+> 面向AI工程师 | 基于《高血压、高尿酸干预行动项目方案(修订版)》| 2026-06-29
+
+---
+
+## 一、算法架构概述
+
+采用 **"三层规则 + AI推理"混合架构**:
+
+```
+用户输入 → 第一层:基础营养层(固定公式)→ 第二层:慢病管控层(AI动态调整)→ 第三层:个性化适配层(AI推理)→ 输出食谱
+```
+
+- **第一层**:基于权威指南的固定公式(BMR、供能比),无需模型参与,确定性计算
+- **第二层**:AI根据慢病组合类型动态调整营养阈值优先级,需要模型推理
+- **第三层**:AI根据口味/地区/场景生成个性化食谱,需要模型推理 + 规则库
+
+---
+
+## 二、数据需求清单
+
+### 2.1 系统自动获取(无需员工手动输入)
+
+| 序号 | 数据字段 | 类型 | 数据来源 | 获取方式 | 用途(哪一层) |
+|------|----------|------|----------|----------|-------------|
+| 1 | 员工ID(哈希化) | string | 健康CQ平台 | 登录态自动获取,脱敏后传入 | 全链路标识 |
+| 2 | 身高 | number(cm) | 员工档案 | 从 `basic-info.html` 录入的档案读取 | 第一层:计算BMR |
+| 3 | 体重 | number(kg) | 体重打卡记录 | 取最近一次 `weight-checkin.html` 的测量值 | 第一层:计算BMR |
+| 4 | 年龄 | number | 员工档案 | 从身份证号/档案计算 | 第一层:BMR公式参数 |
+| 5 | 性别 | enum(男/女) | 员工档案 | 档案读取 | 第一层:BMR公式选择 |
+| 6 | 体力活动等级 | enum | 员工档案 | 员工在 `basic-info.html` 中自行填写,5个选项之一 | 第一层:活动系数 |
+| 7 | 血压-收缩压 | number(mmHg) | 体检报告/指标录入 | 从 `indicators-diseases.html` 取最近值 | 第二层:高血压分级 |
+| 8 | 血压-舒张压 | number(mmHg) | 体检报告/指标录入 | 同上 | 第二层:高血压分级 |
+| 9 | 血尿酸值 | number(μmol/L) | 体检报告/指标录入 | 从 `indicators-diseases.html` 取最近值 | 第二层:高尿酸分期 |
+| 10 | 智能餐线消费记录 | array | 智能餐线系统 | 近6个月消费记录,脱敏后仅提供"菜品类型+消费频次",不含姓名/工号 | 第三层:提取口味偏好 |
+| 11 | 肾病合并 | boolean | 体检报告 | 从年度体检报告自动获取,影响蛋白质和钠阈值 | 第二层:合并慢病调整 |
+| 12 | 糖尿病合并 | boolean | 体检报告 | 从年度体检报告自动获取,影响碳水分配和GI选择 | 第二层:合并慢病调整 |
+
+### 2.2 需问卷采集(员工主动填写)
+
+| 序号 | 数据字段 | 类型 | 采集方式 | 问卷内容示例 | 用途(哪一层) |
+|------|----------|------|----------|------------|-------------|
+| 13 | 所在地区 | enum | 健康问卷 | "您常驻工作地区?" 用于匹配当地常见食材库(如西安→西北食材库) | 第三层:地区食材匹配 |
+| 14 | 口味偏好 | enum(清淡/辛辣/咸鲜/酸甜) | 健康问卷 | "您偏好什么口味?" 单选 | 第三层:口味适配 |
+| 15 | 规避食材 | array(string) | 健康问卷 / 膳食建议页标记 | 过敏/不喜欢食材多选(羊肉、海鲜、动物内脏、香菜、…),可自定义添加 | 第三层:排除特定食材 |
+| 16 | 上班时间 | enum | 健康问卷 | "您通常的上班时间?" 正常白班 / 早班 / 晚班 / 倒班 | 第三层:场景适配 |
+
+### 2.3 外部数据(AI团队自行整理,非实时调用)
+
+| 序号 | 数据内容 | 来源 | 用途 |
+|------|----------|------|------|
+| 17 | 西北地区常见食材库 | 地方疾控《居民膳食报告》+ 本地餐饮协会 | 第三层:地区食材匹配(莜面、羊肉、土豆、菠菜…) |
+| 18 | 餐品嘌呤等级标注 | AI团队预标注 | 第二层:食材嘌呤分级(高/中/低) |
+| 19 | 餐品钠含量数据 | AI团队预标注 | 第二层:钠摄入计算(mg/100g) |
+| 20 | 菜品-口味标签映射 | AI团队预标注 | 第三层:从菜品消费记录提取口味偏好 |
+
+---
+
+## 三、数据校验规则
+
+| 字段 | 校验规则 |
+|------|----------|
+| 血尿酸值 | 150-1000 μmol/L,超出范围接口返回"数据异常" |
+| 口味偏好 | 仅接受枚举值,传入"微辣"等非标值→自动映射为"辛辣" |
+| 身高 | 100-250 cm |
+| 体重 | 30-300 kg |
+| 年龄 | 18-70 岁 |
+| 血压 | 收缩压60-250 mmHg,舒张压30-150 mmHg |
+
+---
+
+## 四、第一层:基础营养计算规则(确定性公式,无需模型)
+
+### 4.1 BMR计算
+
+```
+男性:BMR = 10 × 体重(kg) + 6.25 × 身高(cm) - 5 × 年龄 - 161 + 166
+女性:BMR = 10 × 体重(kg) + 6.25 × 身高(cm) - 5 × 年龄 - 161
+```
+
+### 4.2 活动系数(员工自行填写,系统直接获取)
+
+| 员工选项 | 活动系数 |
+|----------|----------|
+| 久坐不运动 | 1.2 |
+| 久坐运动或久站 | 1.375 |
+| 体力劳动者 | 1.55 |
+| 久站+运动 | 1.725 |
+| 健身+体力劳动者 | 1.9 |
+
+### 4.3 总热量
+
+```
+总热量 = BMR × 活动系数 - 300(固定热量缺口)
+```
+
+### 4.4 三大宏量营养素供能比(固定)
+
+| 营养素 | 供能比 | 每克热量 | 计算公式 |
+|--------|--------|----------|----------|
+| 蛋白质 | 15% | 4 kcal/g | 总热量×15%÷4 |
+| 碳水化合物 | 55% | 4 kcal/g | 总热量×55%÷4 |
+| 脂肪 | 30% | 9 kcal/g | 总热量×30%÷9 |
+
+---
+
+## 五、第二层:慢病管控规则(AI动态调整)
+
+### 5.1 高血压 → 钠控制
+
+| 高血压等级 | 判定条件 | 钠上限/日 | 相当于盐 |
+|-----------|----------|----------|----------|
+| 正常 | 收缩压<140 且 舒张压<90 | 无特殊限制 | — |
+| 1级 | 收缩压140-159 或 舒张压90-99 | ≤2000mg | ≈5g盐 |
+| 2级 | 收缩压≥160 或 舒张压≥100 | ≤1500mg | ≈3.75g盐 |
+| 合并肾病 | 任意级别 + 肾病 | ≤1200mg | ≈3g盐 |
+
+**额外约束**:每餐钠摄入不超过日总上限的40%(避免单次超标)
+
+### 5.2 高尿酸 → 嘌呤控制
+
+| 高尿酸分期 | 判定条件 | 嘌呤上限/日 | 高嘌呤食材 |
+|-----------|----------|------------|----------|
+| 无症状期 | 血尿酸420-600 μmol/L | ≤300mg | 动物内脏、沙丁鱼直接排除 |
+| 急性发作期 | 血尿酸>600 或 近期发作 | ≤150mg | 海鲜类也排除 |
+| 痛风石 | 已确诊痛风石 | ≤100mg | 仅保留极低嘌呤食材 |
+
+### 5.3 合并慢病 → AI动态调整优先级
+
+- **高血压 + 高尿酸**:优先控制钠和嘌呤,允许钾从2500→2800mg/日
+- **高尿酸 + 肾病**:优先控制嘌呤和蛋白质,蛋白质从1.2g/kg→0.8g/kg体重
+- **高血压 + 糖尿病**:碳水中低GI占比≥60%,钠按相应等级控制
+
+---
+
+## 六、第三层:个性化适配规则(AI推理 + 规则库)
+
+### 6.1 口味适配
+
+- 从消费记录提取高频菜品→匹配口味标签
+- 例如:员工高频消费"油泼面"→口味标签=辛辣→推荐菜品时保留辛辣风味但减少钠(如"低盐油泼面",钠减少30%)
+- **原则**:改良而非替代,不强制推荐清淡菜品给辛辣口味员工
+
+### 6.2 地区适配
+
+- 食谱生成时优先从西北本地食材库选材(目标占比≥70%)
+- 例如:早餐优先"杂粮粥+全麦馒头"而非"白粥+包子"
+- 本地食材库包含:莜面、土豆、菠菜、羊肉、小米、全麦粉、沙棘果等
+
+### 6.3 场景适配(根据员工填写的上班时间)
+
+| 上班时间 | 适配策略 |
+|----------|----------|
+| 正常白班 | 三餐正常分配,早/午/晚餐热量比 30%/40%/30% |
+| 早班 | 早餐热量适当上调至35%,凌晨餐推荐低GI主食 |
+| 晚班 | 晚餐为主餐(40%热量),夜间加餐推荐低GI主食(燕麦、荞麦)+ 高蛋白菜品,避免夜间血糖波动 |
+| 倒班 | 根据当日实际班次动态调整餐次分配,优先保证蛋白质摄入稳定 |
+
+---
+
+## 七、API接口规范
+
+### 7.1 请求
+
+```
+POST /api/v1/diet/recommend
+Content-Type: application/json
+```
+
+```json
+{
+ "user_id": "a3f2d...", // 必填,脱敏哈希ID
+ "date": "2026-06-29", // 必填,推荐日期
+ "height": 175, // 必填 cm
+ "weight": 72.5, // 必填 kg
+ "age": 45, // 必填
+ "gender": "male", // 必填 male/female
+ "activity_level": "manual_labor", // 必填,员工自行填写的体力活动等级
+ // 取值:sedentary(久坐不运动) / sedentary_exercise(久坐运动或久站)
+ // manual_labor(体力劳动者) / standing_exercise(久站+运动)
+ // fitness_labor(健身+体力劳动者)
+ "sbp": 145, // 收缩压 mmHg,可为null
+ "dbp": 92, // 舒张压 mmHg,可为null
+ "uric_acid": 526, // 血尿酸 μmol/L,可为null
+ "region": "xian", // 必填,员工填写的所在地区,决定食材库匹配
+ "taste": "spicy", // 必填 bland/spicy/salty/sweet_sour
+ "avoid_foods": ["lamb", "organ_meat"], // 规避食材列表(过敏/不喜欢)
+ "has_kidney_disease": false, // 是否合并肾病
+ "has_diabetes": false, // 是否合并糖尿病
+ "work_schedule": "normal" // 必填 normal/early/late/rotating
+ // 正常白班/早班/晚班/倒班
+}
+```
+
+### 7.2 响应
+
+```json
+{
+ "user_id": "a3f2d...",
+ "date": "2026-06-29",
+ "total_calorie": 2645,
+ "calorie_consumed": 1486,
+ "calorie_remaining": 1159,
+ "bmr": 1900,
+ "activity_factor": 1.55,
+ "macros": {
+ "protein": { "target_g": 99, "current_g": 61, "ratio": 0.15 },
+ "carbs": { "target_g": 364, "current_g": 186, "ratio": 0.55 },
+ "fat": { "target_g": 88, "current_g": 34, "ratio": 0.30 }
+ },
+ "chronic_control": {
+ "sodium": {
+ "limit_mg": 2000,
+ "current_mg": 1040,
+ "level": "ok", // ok / warn / danger
+ "rule": "高血压1级,≤2000mg/日"
+ },
+ "purine": {
+ "limit_mg": 300,
+ "current_mg": 204,
+ "level": "warn", // ok / warn / danger
+ "rule": "无症状高尿酸,≤300mg/日,已排除高嘌呤食材"
+ }
+ },
+ "personalization": {
+ "taste_label": "辛辣",
+ "region": "西安·西北",
+ "work_schedule": "正常白班",
+ "local_ratio": 0.76 // 本地食材占比
+ },
+ "meals": {
+ "breakfast": [
+ {
+ "dish_name": "杂粮粥",
+ "amount": "200g",
+ "ingredients": ["燕麦30g", "小米20g", "枸杞5g"],
+ "calorie_kcal": 185,
+ "nutrient": { "sodium_mg": 50, "purine_mg": 15 },
+ "badges": ["local", "low-purine"],
+ "cooking_tip": "少放糖,加少量枸杞增加风味"
+ }
+ ],
+ "lunch": [ /* ... */ ],
+ "dinner": [ /* ... */ ],
+ "snack": [ /* ... */ ]
+ },
+ "warning": "昨日尿酸值偏高(526 μmol/L),今日已自动剔除高嘌呤食材(动物内脏、沙丁鱼)。建议增加饮水至2000ml。",
+ "weekly_plan": [
+ { "day": "周一", "date": "08-10", "total_kcal": 2645, "meals": [ /* 早中晚加 */ ] }
+ ]
+}
+```
+
+### 7.3 性能要求
+
+| 指标 | 要求 |
+|------|------|
+| 单次响应时间 | ≤500ms |
+| 1000并发响应时间 | 平均≤500ms,峰值≤1s |
+| 批量查询(≤100用户) | ≤3s |
+| 稳定性(72h连续) | ≥99.9%成功率 |
+
+---
+
+## 八、膳食建议页(diet-advice.html)展示内容映射
+
+页面上每个展示区对应算法输出的字段:
+
+| 页面区域 | 数据来源(响应字段) | 说明 |
+|----------|---------------------|------|
+| 慢病标签条 | 根据sbp/dbp/uric_acid推导的等级 | 展示"高血压1级""高尿酸无症状期" |
+| AI慢病提醒 | `warning` | 红色/黄色横幅,突出显示 |
+| 口味/地区/场景标签 | `personalization` | 三个pill标签 |
+| 热量目标卡片 | `total_calorie` + `calorie_consumed` + `calorie_remaining` + `bmr` + `activity_factor` | 大数字+进度条 |
+| 三大宏量营养素环 | `macros.protein/carbs/fat` | 环形图 |
+| 钠指标卡 | `chronic_control.sodium` | 进度条+状态标识 |
+| 嘌呤指标卡 | `chronic_control.purine` | 进度条+状态标识 |
+| 本地食材占比 | `personalization.local_ratio` | 进度条 |
+| 三餐推荐(含加餐) | `meals.breakfast/lunch/dinner/snack` | 每道菜:名称+份量+营养素+标签+烹饪建议 |
+| 一周食谱 | `weekly_plan` | 7天切换 |
+| 规避食材标记 | 前端交互(回传avoid_foods给接口) | 员工自行勾选 |
+| 份量参考 | 静态内容(非算法输出) | 手掌/拳头等图示 |
+| 分层注意事项 | `chronic_control.*.rule` + 静态内容 | 按三层分组 |
+
+---
+
+## 九、模型训练与交付要求
+
+| 阶段 | 工作内容 | 验收标准 |
+|------|----------|----------|
+| 预训练 | 用公开膳食数据集训练基础营养计算能力 | 偏差率≤12% |
+| 微调1(慢病) | 用500条员工健康数据微调慢病管控 | 符合率≥90% |
+| 微调2(个性化) | 用口味/地区数据微调,加入本地食材库 | 适配率≥88% |
+| 迭代优化 | 根据员工反馈调整规则库 | 满意度≥85% |
+
+**最终精度目标:**
+- 膳食建议符合《高血压/高尿酸膳食指导》率 ≥ 95%
+- 口味/地区适配符合率 ≥ 92%
+- 钠/嘌呤计算偏差率 ≤ 8%
+
+**交付物:**
+- 算法模型(含API接口,RESTful/HTTPS/JSON)
+- 规则配置文件(XML,支持非技术人员修改食材库/营养阈值/口味维度)
+- 《模型使用手册》(含接口调用指南+规则配置教程+维护周期建议)
+- 《数据对接规范》(含输入输出格式+校验规则)
+- 《测试报告》(≥10类场景测试+性能测试)
diff --git a/final.js b/final.js
new file mode 100644
index 0000000..27080f0
--- /dev/null
+++ b/final.js
@@ -0,0 +1,224 @@
+const fs=require('fs'),f='other-module/emergency-screen/xian-center/index-v3.html';
+let d=fs.readFileSync(f,'utf8');
+const ok=(s,desc)=>{if(!s){console.log('FAIL: '+desc);process.exit(1);}console.log('OK: '+desc);};
+
+// ═══ CSS changes (no Chinese text needed) ═══
+d=d.replace('grid-template-columns:1fr 1fr;gap:10px;','grid-template-columns:1fr 1fr 1fr;gap:8px;');
+d=d.replace('.stat-card{background:rgba(24,144,255,0.06);border:1px solid var(--panel-border);border-radius:8px;\r\n padding:12px;text-align:center;transition:all .2s;cursor:pointer;}',
+ '.stat-card{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:3px;\r\n padding:10px 6px;min-height:66px;text-align:center;position:relative;overflow:hidden;cursor:pointer;\r\n background:linear-gradient(135deg,rgba(24,144,255,0.10),rgba(10,30,60,0.45));\r\n border:1px solid var(--panel-border);border-radius:8px;transition:all .25s;}\r\n .stat-card::before{content:\"\";position:absolute;left:0;top:20%;bottom:20%;width:3px;\r\n background:var(--primary);border-radius:0 2px 2px 0;box-shadow:0 0 8px rgba(24,144,255,0.55);}\r\n .stat-card::after{content:\"\";position:absolute;inset:0;pointer-events:none;\r\n background:radial-gradient(ellipse at 100% 0%,rgba(24,144,255,0.06),transparent 55%);}');
+d=d.replace('.stat-card:hover{border-color:rgba(24,144,255,0.35);box-shadow:0 0 12px rgba(24,144,255,0.1);}',
+ '.stat-card:hover{border-color:rgba(24,144,255,0.45);box-shadow:0 0 16px rgba(24,144,255,0.15);\r\n transform:translateY(-2px);background:linear-gradient(135deg,rgba(24,144,255,0.16),rgba(10,30,60,0.50));}');
+d=d.replace('.stat-card__num{font-size:24px;font-weight:700;color:#fff;line-height:1;text-shadow:0 0 8px rgba(255,255,255,0.2);}',
+ '.stat-card__num{font-size:20px;font-weight:700;color:#fff;line-height:1;\r\n text-shadow:0 0 10px rgba(24,144,255,0.35);font-variant-numeric:tabular-nums;letter-spacing:0.5px;}');
+d=d.replace('.stat-card__label{font-size:11px;color:var(--text-sub);margin-top:6px;}',
+ '.stat-card__label{font-size:10px;color:var(--text-sub);white-space:nowrap;letter-spacing:0.3px;}');
+for(const[old,nw] of[
+ ['.stat-card--danger .stat-card__num{color:var(--danger);text-shadow:0 0 8px rgba(255,77,79,0.3);}',
+ '.stat-card--danger::before{background:var(--danger);box-shadow:0 0 8px rgba(255,77,79,0.55);}\r\n .stat-card--danger .stat-card__num{color:var(--danger);text-shadow:0 0 10px rgba(255,77,79,0.4);}\r\n .stat-card--danger::after{background:radial-gradient(ellipse at 100% 0%,rgba(255,77,79,0.06),transparent 55%);}\r\n .stat-card--danger:hover{border-color:rgba(255,77,79,0.3);box-shadow:0 0 16px rgba(255,77,79,0.15);}'],
+ ['.stat-card--warning .stat-card__num{color:var(--warning);text-shadow:0 0 8px rgba(250,173,20,0.3);}',
+ '.stat-card--warning::before{background:var(--warning);box-shadow:0 0 8px rgba(250,173,20,0.55);}\r\n .stat-card--warning .stat-card__num{color:var(--warning);text-shadow:0 0 10px rgba(250,173,20,0.4);}\r\n .stat-card--warning::after{background:radial-gradient(ellipse at 100% 0%,rgba(250,173,20,0.06),transparent 55%);}\r\n .stat-card--warning:hover{border-color:rgba(250,173,20,0.3);box-shadow:0 0 16px rgba(250,173,20,0.15);}'],
+ ['.stat-card--success .stat-card__num{color:var(--success);text-shadow:0 0 8px rgba(82,196,26,0.3);}',
+ '.stat-card--success::before{background:var(--success);box-shadow:0 0 8px rgba(82,196,26,0.55);}\r\n .stat-card--success .stat-card__num{color:var(--success);text-shadow:0 0 10px rgba(82,196,26,0.4);}\r\n .stat-card--success::after{background:radial-gradient(ellipse at 100% 0%,rgba(82,196,26,0.06),transparent 55%);}\r\n .stat-card--success:hover{border-color:rgba(82,196,26,0.3);box-shadow:0 0 16px rgba(82,196,26,0.15);}']
+]) d=d.replace(old,nw);
+d=d.replace('/* ========== 左侧:服务详情 ========== */','/* ========== 左侧:应急事件统计卡片 ========== */');
+
+// CSS: alarm-bars + radar
+d=d.replace(' /* ========== 左侧:体检数据排行 ========== */',
+` /* ========== 左侧:穿戴告警统计柱状图 ========== */
+ .panel--alarm-bars .panel__body{padding:8px 10px 10px;overflow:hidden;}
+ .alarm-bars{display:flex;flex-direction:column;gap:7px;}
+ .alarm-bars__row{display:flex;align-items:center;gap:8px;min-height:22px;}
+ .alarm-bars__label{width:54px;font-size:10px;color:var(--text-sub);text-align:right;flex-shrink:0;white-space:nowrap;line-height:1.2;}
+ .alarm-bars__track{flex:1;height:12px;background:rgba(24,144,255,0.06);border-radius:6px;overflow:hidden;position:relative;}
+ .alarm-bars__bar{height:100%;border-radius:6px;transition:width .6s ease;position:relative;min-width:4px;}
+ .alarm-bars__bar::after{content:"";position:absolute;right:0;top:0;bottom:0;width:2px;background:rgba(255,255,255,0.25);border-radius:0 6px 6px 0;}
+ .alarm-bars__count{width:32px;font-size:11px;font-weight:700;color:#fff;text-align:right;flex-shrink:0;font-variant-numeric:tabular-nums;}
+
+ /* ========== 右侧:体检数据雷达图 ========== */
+ .panel--checkup-radar .panel__body{padding:6px 10px 8px;overflow:hidden;}
+ .checkup-radar{display:flex;align-items:center;justify-content:center;}
+ .checkup-radar svg{width:100%;max-width:280px;height:auto;display:block;}
+ .radar-dot{animation:radarPulse 2s ease-in-out infinite;filter:drop-shadow(0 0 4px rgba(24,144,255,0.6));}
+ @keyframes radarPulse{0%,100%{opacity:0.6;r:2.5;}50%{opacity:1;r:3.5;}}
+
+ /* ========== 左侧:体检数据排行 ========== */`);
+
+// CSS: AI risk tooltip
+d=d.replace('.ai-risk-col{min-width:0;display:flex;flex-direction:column;align-items:center;gap:4px;}',
+ '.ai-risk-col{min-width:0;display:flex;flex-direction:column;align-items:center;gap:4px;position:relative;}');
+d=d.replace('.ai-risk-stack{height:72px;width:32px;display:flex;flex-direction:column;justify-content:flex-end;\r\n border-radius:5px 5px 3px 3px;overflow:hidden;background:rgba(24,144,255,0.06);\r\n box-shadow:inset 0 0 0 1px rgba(24,144,255,0.12);}',
+ '.ai-risk-stack{height:72px;width:32px;display:flex;flex-direction:column;justify-content:flex-end;\r\n border-radius:5px 5px 3px 3px;overflow:hidden;background:rgba(24,144,255,0.06);\r\n box-shadow:inset 0 0 0 1px rgba(24,144,255,0.12);cursor:pointer;}\r\n .ai-risk-stack:hover{border-color:rgba(24,144,255,0.4);}\r\n .ai-risk-tip{position:absolute;bottom:80px;left:50%;transform:translateX(-50%);z-index:30;\r\n background:rgba(10,22,48,0.96);border:1px solid rgba(24,144,255,0.35);border-radius:8px;\r\n padding:8px 10px;white-space:nowrap;pointer-events:none;\r\n opacity:0;transition:opacity .2s,bottom .2s;\r\n box-shadow:0 4px 18px rgba(0,0,0,0.5),0 0 12px rgba(24,144,255,0.15);\r\n font-size:11px;color:var(--text-sub);line-height:1.7;text-align:left;}\r\n .ai-risk-tip::after{content:\"\";position:absolute;top:100%;left:50%;transform:translateX(-50%);\r\n width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;\r\n border-top:5px solid rgba(10,22,48,0.96);}\r\n .ai-risk-tip b{font-weight:600;}\r\n .ai-risk-col:hover .ai-risk-tip{opacity:1;bottom:84px;}');
+console.log('CSS all done');
+
+// ═══ HTML: Read 4th stat-card directly from file ═══
+const ljsl=d.indexOf('累计受理次数');
+const cardBeg=d.lastIndexOf('
\r\n
';
+d=d.replace(card4,card4n);
+ok(d.includes('待处理工单'),'6 stat cards');
+
+// ═══ HTML: Replace help events with alarm bars ═══
+const helpBeg=d.indexOf(' ');
+const healthBeg=d.indexOf(' ');
+const helpBlock=d.substring(helpBeg,healthBeg);
+const alarmBars=`
+
+
+`;
+d=d.replace(helpBlock,alarmBars);
+ok(d.includes('alarmBarsChart')&&!d.includes('应急求助事件(自动滚动列表)'),'alarm bars + remove help');
+
+// ═══ HTML: Move AI from right to left ═══
+const aiBeg=d.indexOf('a id="aiPanel"');
+const aiBlockStart=d.lastIndexOf(' ',aiBeg);
+const rAside=d.lastIndexOf(' ');
+const aiBlock=d.substring(aiBlockStart,rAside);
+d=d.split(aiBlock).join('');// Remove from right
+// Insert before 人员现状监测 on left
+const healthPos=d.indexOf(' ');
+d=d.substring(0,healthPos)+aiBlock+'\r\n'+d.substring(healthPos);
+ok(d.includes('AI智能预警分析'),'AI moved to left');
+
+// ═══ HTML: AI rename + dropdown + tooltips ═══
+d=d.replace('AI智能预警分析','心脑血管预警统计');
+d=d.replace(' ',' ');
+// Remove old history link, add dropdown
+const histBeg=d.indexOf('pan>历史记录');
+const linkLineStart=d.lastIndexOf('');
+const rpEnd=d.indexOf(' ',rpBeg);
+const nrp=`
+
'),'right:',cnt(''),'left:',cnt(''));
+console.log('6cards:',cnt('待处理工单'),'alarm:',cnt('alarmBarsChart'),'help:',cnt('应急求助事件(自动滚动列表)'));
+console.log('CV:',cnt('心脑血管预警统计'),'radar:',cnt('checkupRadar'),'consult:',cnt('咨询数据'));
+console.log('health:',cnt('人员现状监测(环形图)'),'renderAlarmBars:',cnt('renderAlarmBars'),'updateCvStats:',cnt('updateCvStats'));
+fs.writeFileSync(f,d,'utf8');
+console.log('DONE. Size:',d.length);
diff --git a/other-module/emergency-screen/xian-center/cv-monitor.html b/other-module/emergency-screen/xian-center/cv-monitor.html
index 9ac1c11..4559f6f 100644
--- a/other-module/emergency-screen/xian-center/cv-monitor.html
+++ b/other-module/emergency-screen/xian-center/cv-monitor.html
@@ -460,7 +460,7 @@