Просмотр исходного кода

docs(superpowers): P1 nutritionist feature design spec

Sisyphus 4 месяцев назад
Родитель
Сommit
c42eead000
1 измененных файлов с 579 добавлено и 0 удалено
  1. 579 0
      docs/superpowers/specs/2026-06-24-P1营养师功能设计方案.md

+ 579 - 0
docs/superpowers/specs/2026-06-24-P1营养师功能设计方案.md

@@ -0,0 +1,579 @@
+# P1 营养师核心功能设计方案
+
+## TL;DR
+
+> **核心目标**:完成 P1 阶段 4 个功能的设计——PDF 食材适宜度解析扩展、微量营养素食材库(含 RDI)、购物清单生成系统、营养师-家庭沟通闭环。
+>
+> **设计范围**:4 个功能各包含数据模型、Service/Controller 扩展、前端交互概要。不包含 Danshop 集成(已决策独立开发),不做推送通知(P2)。
+>
+> **关键决策**:food_nutrients 关联表(非横向扩展)、RDI 纳入 P1、混合 AI+固化规则、购物清单为计划工具、扩展 TeacherMessage 实现双向沟通。
+
+---
+
+## Context
+
+### 背景
+
+P0 阶段完成了三个核心功能(孩子营养档案统一视图、结构化营养缺乏分析、家长端报告可视化解读)的设计方案与实现计划。P1 阶段在 P0 基础上,向营养师提供更完整的工作工具。
+
+P1 四个功能在数据层有依赖关系:
+
+```
+PDF上传及解析 (扩展)
+    │
+    ├─→ report_food_suitability (个性化推荐指数)
+    │
+    ├─→ foods 表扩展 (能量KJ/淀粉/胆固醇) ← 标准营养数据
+    │
+    ├─→ food_nutrients + rdi_standards (微量营养素 + RDI)
+    │
+    └─→ shopping_list (从报告高分食材 + 缺乏食材生成)
+         └─→ TeacherMessage 扩展 (建议 → 随访闭环)
+              └─→ follow_up (反馈 → 复查)
+```
+
+### 研究结果
+
+**现有数据模型状态(P1 开始前):**
+
+| 模块 | 状态 | 说明 |
+|------|------|------|
+| PdfParseService | 已有 | 解析菌群/体质/疾病风险,**未解析食材相关部分** |
+| ParsedReportResult | 已有 | 含 score/gutFlora/indicators/diseaseRisks,**无 food 字段** |
+| foods 表 | 已有 | 5 宏量+tag,**缺能量KJ/淀粉/胆固醇** |
+| food_nutrients | ❌ 不存在 | 微量营养素无存储 |
+| rdi_standards | ❌ 不存在 | 无参考摄入量标准 |
+| report_food_suitability | ❌ 不存在 | 食材推荐指数无存储 |
+| user_food_preference | ❌ 不存在 | 用户手动维护偏好无存储 |
+| nutrition_indicator_mapping | 已有 | 指标→营养素→食材建议(15条 seed) |
+| shopping_lists | ❌ 不存在 | 购物清单无存储 |
+| follow_up | ❌ 不存在 | 随访记录无存储 |
+| TeacherMessage | 已有 | 单向 parent→teacher,**需扩展双向+关联+已读** |
+
+**PDF 食材数据格式(来自 募极生物菌群报告):**
+每个食材记录包含:名称、分类、推荐指数(个性化,-100~+100)、能量KJ、蛋白g、脂肪g、碳水化合物g、淀粉g、总膳食纤维g、胆固醇mg
+
+---
+
+## Work Objectives
+
+### Core Objective
+完成 P1 四个功能的设计,覆盖从 PDF 食材数据解析 → 微量营养素分析 → 购物清单生成 → 沟通闭环的全流程。
+
+### Concrete Deliverables
+- `docs/superpowers/specs/2026-06-24-P1营养师功能设计方案.md` (本文档)
+- 后续:`docs/superpowers/plans/2026-06-24-P1营养师功能实现计划.md`
+
+### Scope
+- **IN**: 4 个功能的数据模型、逻辑设计、API 设计、前端交互概要
+- **OUT**: 微信模板消息推送、Danshop 集成、全量食材营养 SEED 数据、前端页面具体样式
+
+---
+
+## Design Decisions
+
+| # | 决策 | 选项 | 理由 |
+|---|------|------|------|
+| 1 | food_nutrients 存储方式 | **关联表**(非横向扩展 foods) | 灵活扩展,与 RDI 表查询模式统一 |
+| 2 | 营养素覆盖范围 | **15+ 种**(中国 DRIs 全套) | 一步到位避免二次迁移 |
+| 3 | RDI 参考摄入量 | **纳入 P1** | 判断缺乏必需的基础数据 |
+| 4 | 食材适宜度映射方式 | **混合:AI 生成规则 + Java 固化** | 兼顾灵活和确定性 |
+| 5 | 权重范围 | **-100~+100** | 正值推荐/负值回避,单字段覆盖 |
+| 6 | 购物清单与商城关系 | **独立开发,不集成 Danshop** | 购物清单是计划工具,非电商购物车 |
+| 7 | 沟通闭环实现方式 | **扩展 TeacherMessage** + 新建 follow_up | 复用现有代码,减少耦合 |
+| 8 | 沟通方向 | **双向**(原仅 parent→teacher) | 营养师主动发起建议 → 家长反馈 → 复查闭环 |
+
+---
+
+## Architecture Overview
+
+```
+┌──────────────────────────────────────────────────────────────────┐
+│                        上层应用(展示侧)                          │
+│  小程序: 食材展示/购物清单/消息/随访  |  Web管理端: 营养师面板    │
+├──────────────────────────────────────────────────────────────────┤
+│                        业务服务层                                 │
+│  ReportFoodSuitabilityService  |  ShoppingListService             │
+│  FoodNutrientService           |  RdiStandardService              │
+│  FollowUpService               |  TeacherMessageService (扩展)    │
+│  NutritionCalculator           |  PdfParseService (扩展)          │
+├──────────────────────────────────────────────────────────────────┤
+│                        数据存储层                                  │
+│  foods(food_nutrients)  |  rdi_standards                          │
+│  report_food_suitability  |  user_food_preference                 │
+│  shopping_lists(items)  |  teacher_messages  |  follow_up          │
+└──────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Part 1: PDF 解析扩展 + 食材适宜度数据模型
+
+### 1.1 foods 表扩展
+
+ALTER TABLE 方式新增 3 列:
+
+```sql
+ALTER TABLE foods
+    ADD COLUMN energy_kj DECIMAL(8,2) COMMENT '能量(kJ/100g)',
+    ADD COLUMN starch DECIMAL(8,2) COMMENT '淀粉(g/100g)',
+    ADD COLUMN cholesterol DECIMAL(8,2) COMMENT '胆固醇(mg/100g)';
+```
+
+**规则**:首次从 PDF 提取后写入 foods 表,后续报告中同名食材不再重复写营养数据。
+
+### 1.2 ParsedFoodSuitability DTO(新)
+
+```java
+public class ParsedFoodSuitability {
+    private String foodName;     // PDF 中的食材名称
+    private String category;     // 分类(蔬菜/水果/肉禽等)
+    private Integer score;       // 推荐指数(-100~+100,个性化)
+    private BigDecimal energyKj;
+    private BigDecimal protein;
+    private BigDecimal fat;
+    private BigDecimal carbs;
+    private BigDecimal starch;
+    private BigDecimal fiber;
+    private BigDecimal cholesterol;
+}
+```
+
+扩展 `ParsedReportResult` 新增 `List<ParsedFoodSuitability> foodSuitability` 字段。
+
+### 1.3 PdfParseService 扩展
+
+新增 `parseFoodSuitability()` 方法,流程:
+
+1. 定位 PDF 中食材评分章节
+2. 逐食材解析:名称、分类、推荐指数、9 项营养数据
+3. 返回 `List<ParsedFoodSuitability>`
+4. 在 `saveReport()` 流程中调用新方法,将数据持久化
+
+### 1.4 report_food_suitability 表
+
+```sql
+CREATE TABLE IF NOT EXISTS report_food_suitability (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_id BIGINT NOT NULL COMMENT '关联健康报告ID',
+    food_id BIGINT COMMENT '匹配到的foods.id',
+    food_name VARCHAR(100) NOT NULL COMMENT 'PDF中食材名称',
+    category VARCHAR(50) COMMENT '分类',
+    score INT NOT NULL COMMENT '推荐指数(-100~+100,个性化)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_report (report_id),
+    INDEX idx_food (food_id)
+);
+```
+
+**匹配逻辑**:PDF 食材名 → `foods.name` 模糊匹配。命中则关联 food_id,未命中则仅存 food_name。
+
+### 1.5 user_food_preference 表
+
+无菌群报告的用户手动维护食材偏好:
+
+```sql
+CREATE TABLE IF NOT EXISTS user_food_preference (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    food_id BIGINT,
+    food_name VARCHAR(100) NOT NULL,
+    score INT NOT NULL DEFAULT 0 COMMENT '用户设置分数(-100~+100)',
+    reason VARCHAR(500),
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user (user_id)
+);
+```
+
+### 1.6 新增文件清单
+
+| 模块 | 文件 | 说明 |
+|------|------|------|
+| DTO | `ParsedFoodSuitability.java` | 食材数据 DTO |
+| DTO | `ParsedReportResult.java` | 扩展 foodSuitability 字段 |
+| Entity | `ReportFoodSuitability.java` | 报告食材适宜度实体 |
+| Entity | `UserFoodPreference.java` | 用户食材偏好实体 |
+| Mapper | `ReportFoodSuitabilityMapper.java` | BaseMapper |
+| Mapper | `UserFoodPreferenceMapper.java` | BaseMapper |
+| Service | `ReportFoodSuitabilityService.java` | CRUD + 按报告查询 |
+| Service interface | `ReportFoodSuitabilityServiceInterface.java` | 接口 |
+| Service | `UserFoodPreferenceService.java` | CRUD |
+| Service | `PdfParseService.java` | 新增 `parseFoodSuitability()` |
+| Controller | `HealthReportController.java` | 新增食材适宜度查询端点 |
+
+---
+
+## Part 2: 微量营养素食材库 + RDI
+
+### 2.1 food_nutrients 表
+
+```sql
+CREATE TABLE IF NOT EXISTS food_nutrients (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    food_id BIGINT NOT NULL COMMENT '关联foods.id',
+    nutrient_code VARCHAR(20) NOT NULL COMMENT '营养素编码',
+    amount_per_100g DECIMAL(10,4) NOT NULL COMMENT '每100g含量',
+    unit VARCHAR(20) NOT NULL COMMENT '单位(mcg/mg/g/μg)',
+    source VARCHAR(50) DEFAULT 'importer' COMMENT '数据来源(importer/pdf/manual)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_food_nutrient (food_id, nutrient_code),
+    INDEX idx_nutrient (nutrient_code)
+);
+```
+
+**营养素编码对照表:**
+
+| 编码 | 名称 | 单位 | 编码 | 名称 | 单位 |
+|------|------|------|------|------|------|
+| VIT_A | 维生素A | μg | FOLATE | 叶酸 | μg |
+| VIT_D | 维生素D | μg | VIT_B12 | 维生素B12 | μg |
+| VIT_E | 维生素E | mg | CALCIUM | 钙 | mg |
+| VIT_C | 维生素C | mg | IRON | 铁 | mg |
+| VIT_B1 | 硫胺素 | mg | ZINC | 锌 | mg |
+| VIT_B2 | 核黄素 | mg | SELENIUM | 硒 | μg |
+| NIACIN | 烟酸 | mg | MAGNESIUM | 镁 | mg |
+| VIT_B6 | 吡哆醇 | mg | POTASSIUM | 钾 | mg |
+| - | - | - | IODINE | 碘 | μg |
+
+### 2.2 rdi_standards 表
+
+```sql
+CREATE TABLE IF NOT EXISTS rdi_standards (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    nutrient_code VARCHAR(20) NOT NULL COMMENT '营养素编码',
+    age_group VARCHAR(10) NOT NULL COMMENT '年龄段(1-3/4-6/7-10/11-13/14-17)',
+    gender VARCHAR(6) DEFAULT 'all' COMMENT '性别(male/female/all)',
+    rdi_type VARCHAR(10) NOT NULL COMMENT '类型(RNI/AI/UL)',
+    amount DECIMAL(10,4) NOT NULL COMMENT '推荐值',
+    unit VARCHAR(20) NOT NULL COMMENT '单位',
+    source VARCHAR(100) COMMENT '数据来源(DRIs2023)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_rdi (nutrient_code, age_group, gender, rdi_type),
+    INDEX idx_age (age_group)
+);
+```
+
+**RDI 类型**:
+| 类型 | 含义 | 用途 |
+|------|------|------|
+| RNI | 推荐摄入量 | 判断缺乏:低于 RNI 为缺乏 |
+| AI | 适宜摄入量 | 数据不充分时的替代标准 |
+| UL | 可耐受最高摄入量 | 避免过量 |
+
+**年龄段划分**:1-3 / 4-6 / 7-10 / 11-13 / 14-17(参考中国 DRIs 2023,覆盖学龄前→青春期)
+
+### 2.3 NutritionCalculator 工具类
+
+```java
+@Component
+public class NutritionCalculator {
+
+    /** 计算食物X的某营养素含量占该年龄段RDI的百分比 */
+    public BigDecimal calculateRdiPercentage(
+        BigDecimal nutrientAmount,           // 食物中含量(每100g)
+        String nutrientCode,
+        String ageGroup,
+        String gender,
+        BigDecimal servingSizeGrams          // 实际食用量(克)
+    );
+
+    /** 给定营养素缺乏列表,返回含量Top-N的食材 */
+    public List<Food> findTopFoodsForDeficiency(
+        String nutrientCode,
+        int limit
+    );
+
+    /** 给出某食材的营养完整画像(宏量+微量) */
+    public NutritionProfile getFullNutritionProfile(
+        Long foodId
+    );
+}
+```
+
+### 2.4 SEED 数据策略
+
+| 数据 | 来源 | 注入方式 |
+|------|------|---------|
+| food_nutrients | 中国食物成分表 / USDA | `DatabaseInitializer` 中 `INSERT IGNORE` 首批约 200 种常见食材 |
+| rdi_standards | 中国 DRIs 2023 | `DatabaseInitializer` 中配置全年龄段 15+ 营养素 |
+
+### 2.5 新增文件清单
+
+| 模块 | 文件 | 说明 |
+|------|------|------|
+| Entity | `FoodNutrient.java` | 食材微量营养素实体 |
+| Entity | `RdiStandard.java` | RDI 标准实体 |
+| Mapper | `FoodNutrientMapper.java` | BaseMapper + 批量查询 |
+| Mapper | `RdiStandardMapper.java` | BaseMapper |
+| Service | `FoodNutrientService.java` | 按食材/营养素查询 |
+| Service | `RdiStandardService.java` | 按年龄段+营养素查标准值 |
+| Config | `DatabaseInitializer.java` | SEED 数据注入 |
+| Util | `NutritionCalculator.java` | RDI 计算工具类 |
+
+---
+
+## Part 3: 购物清单生成
+
+### 3.1 shopping_lists 表
+
+```sql
+CREATE TABLE IF NOT EXISTS shopping_lists (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT COMMENT '孩子ID',
+    report_id BIGINT COMMENT '关联报告ID',
+    title VARCHAR(200) COMMENT '清单标题',
+    source VARCHAR(20) NOT NULL COMMENT '来源(report/deficiency/manual/mixed)',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态(active/completed/archived)',
+    total_items INT DEFAULT 0,
+    purchased_items INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user (user_id),
+    INDEX idx_report (report_id),
+    INDEX idx_status (status)
+);
+```
+
+### 3.2 shopping_list_items 表
+
+```sql
+CREATE TABLE IF NOT EXISTS shopping_list_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    list_id BIGINT NOT NULL COMMENT '关联清单ID',
+    food_id BIGINT COMMENT '关联食材ID',
+    food_name VARCHAR(100) NOT NULL COMMENT '食材名称',
+    category VARCHAR(50) COMMENT '分类',
+    quantity DECIMAL(8,2) DEFAULT 1 COMMENT '建议数量',
+    unit VARCHAR(20) DEFAULT '份' COMMENT '单位',
+    priority VARCHAR(10) DEFAULT 'medium' COMMENT '优先级(high/medium/low)',
+    reason VARCHAR(200) COMMENT '推荐理由',
+    score INT COMMENT '推荐指数',
+    source VARCHAR(20) COMMENT '来源(report/deficiency/manual)',
+    purchased TINYINT DEFAULT 0 COMMENT '已购买(0/1)',
+    purchased_at DATETIME COMMENT '购买时间',
+    sort_order INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_list (list_id),
+    INDEX idx_purchased (purchased)
+);
+```
+
+### 3.3 生成逻辑
+
+**触发源与纳入条件:**
+
+| 触发源 | 触发时机 | 纳入条件 | 初始化方式 |
+|--------|---------|---------|-----------|
+| 菌群报告 | PDF 解析完成 | `report_food_suitability.score >= 50` | 按 score 降序 |
+| 缺乏分析 | 缺乏分析生成(P0) | 缺乏营养素→food_nutrients 取 Top 5 | 按含量降序 |
+| 手动添加 | 用户搜索食材点击"加入清单" | 直接添加 | 用户指定优先级 |
+
+**去重策略:**
+```
+同一清单中相同食材:
+- 多个来源 → 合并为一条,reason 拼接
+- 自动+手动编辑 → 保留手动值
+- quantity = sum(各来源建议量)
+```
+
+**推荐理由示例:**
+- "菌群报告推荐指数 85"
+- "富含铁(每100g含2.9mg),帮助改善缺铁"
+- "用户手动添加"
+
+### 3.4 API 端点
+
+| 端点 | 说明 |
+|------|------|
+| `POST /api/shopping-list/create-from-report` | 基于最新报告生成清单 |
+| `POST /api/shopping-list/create-from-deficiency` | 基于缺乏分析生成清单 |
+| `POST /api/shopping-list/list` | 查询用户清单列表 |
+| `POST /api/shopping-list/{id}/items` | 查清单详情 |
+| `POST /api/shopping-list/item/{id}/purchase` | 标记已购 |
+| `POST /api/shopping-list/item/add` | 手动添加食材 |
+| `POST /api/shopping-list/item/remove` | 手动移除 |
+| `POST /api/shopping-list/{id}/archive` | 归档清单 |
+
+### 3.5 前端交互概要
+
+**小程序端(家长视角):**
+- 入口 Tab:首页或"我的"中"购物清单"
+- 按分类分组展示(蔬菜/水果/肉禽/水产…)
+- 每项:食材名 + 推荐理由 + 分数徽章 + ✅ 勾选
+- 进度条:已购 N/M
+- 按钮:"基于最新报告重新生成"
+- 空状态:"上传菌群报告后自动生成购物清单"
+
+### 3.6 新增文件清单
+
+| 模块 | 文件 | 说明 |
+|------|------|------|
+| Entity | `ShoppingList.java` | 清单主表 |
+| Entity | `ShoppingListItem.java` | 清单明细 |
+| Mapper | `ShoppingListMapper.java` | BaseMapper |
+| Mapper | `ShoppingListItemMapper.java` | BaseMapper |
+| Service | `ShoppingListService.java` | 生成/查询/勾选/归档 |
+| Service interface | `ShoppingListServiceInterface.java` | 接口 |
+| Controller | `ShoppingListController.java` | `/api/shopping-list/*` |
+| 前端 | 小程序页面 | 购物清单页(Vue 2 Options API) |
+
+---
+
+## Part 4: 营养师-家庭沟通闭环
+
+### 4.1 TeacherMessage 扩展
+
+```sql
+ALTER TABLE teacher_messages
+    ADD COLUMN sender_role VARCHAR(20) DEFAULT 'parent'
+        COMMENT '发送方角色(parent/teacher/nutritionist)',
+    ADD COLUMN read_at DATETIME COMMENT '已读时间',
+    ADD COLUMN relates_to_type VARCHAR(50)
+        COMMENT '关联类型(deficiency_report/shopping_list/meal_plan/general)',
+    ADD COLUMN relates_to_id BIGINT COMMENT '关联对象ID',
+    ADD COLUMN message_type VARCHAR(20) DEFAULT 'text'
+        COMMENT '消息类型(text/recommendation/system)';
+```
+
+**现有端点扩展:**
+
+| 端点 | 扩展内容 |
+|------|---------|
+| `POST /api/guide/messages/send` | 新增 sender_role 区分发送方 |
+| `POST /api/guide/messages/list` | 营养师可见发给名下家庭的;按 relates_to_type 筛选 |
+| `POST /api/guide/messages/my` | 家长可见所有消息(含营养师发来的) |
+| `POST /api/guide/messages/{id}/read` | 新增:标记已读(更新 read_at) |
+
+### 4.2 follow_up 表
+
+```sql
+CREATE TABLE IF NOT EXISTS follow_up (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    child_id BIGINT COMMENT '孩子ID',
+    teacher_id BIGINT COMMENT '营养师ID',
+    deficiency_id BIGINT COMMENT '关联缺乏记录ID',
+    message_id BIGINT COMMENT '关联消息ID',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态(pending/in_progress/completed/ignored)',
+    priority VARCHAR(10) DEFAULT 'medium' COMMENT '优先级(high/medium/low)',
+    description VARCHAR(500) COMMENT '建议内容',
+    parent_feedback TEXT COMMENT '家长反馈',
+    teacher_review VARCHAR(500) COMMENT '营养师复查备注',
+    due_date DATETIME COMMENT '计划复查日期',
+    completed_at DATETIME COMMENT '完成时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family (family_id),
+    INDEX idx_teacher (teacher_id),
+    INDEX idx_status (status),
+    INDEX idx_due (due_date)
+);
+```
+
+**状态流转:**
+```
+pending ──→ in_progress ──→ completed
+   │                              ↑
+   └──→ ignored ──────────────────┘
+```
+- `pending`:营养师创建随访,尚未到达复查日期
+- `in_progress`:营养师已提醒,家长已阅读消息
+- `completed`:家长反馈"已采纳",营养师确认
+- `ignored`:家长或营养师标记为忽略
+
+### 4.3 API 端点
+
+| 端点 | 请求方 | 说明 |
+|------|--------|------|
+| `POST /api/follow-up/create` | 营养师 | 创建随访记录 |
+| `POST /api/follow-up/list` | 营养师 | 按家庭/状态/到期筛选 |
+| `POST /api/follow-up/{id}/status` | 营养师 | 更新状态 |
+| `POST /api/follow-up/{id}/feedback` | 家长 | 提交家长反馈 |
+| `POST /api/follow-up/{id}/review` | 营养师 | 填写复查备注 |
+| `POST /api/follow-up/{id}/complete` | 营养师 | 标记完成 |
+| `POST /api/follow-up/due-today` | 营养师 | 今日到期随访列表 |
+
+### 4.4 前端交互概要
+
+**Web 管理端(营养师视角):**
+- "随访管理"页面:展示名下各家庭的随访列表
+- 支持筛选:待复查 / 进行中 / 已完成 / 今日到期
+- 创建随访:选择家庭→关联缺乏分析→填写建议→设定复查日期
+- 随访详情:建议内容 + 家长反馈 + 复查备注
+- 一键发送消息:创建随访时自动通过 TeacherMessage 推送
+
+**小程序端(家长视角):**
+- "消息"页面:显示来自营养师的消息(含 sender_role 区分)
+- 随访通知:显示"营养师建议 + 反馈表单"
+- 反馈表单:是否采纳 + 遇到的问题 + 继续咨询
+
+### 4.5 新增文件清单
+
+| 模块 | 文件 | 说明 |
+|------|------|------|
+| Entity | `FollowUp.java` | 随访记录实体 |
+| Mapper | `FollowUpMapper.java` | BaseMapper |
+| Service | `FollowUpService.java` | CRUD + 状态流转 |
+| Service interface | `FollowUpServiceInterface.java` | 接口 |
+| Controller | `FollowUpController.java` | `/api/follow-up/*` |
+| Service | `TeacherMessageService.java` | 扩展双向发送、已读标记 |
+| DTO | `SendMessageDTO.java` | 扩展 sender_role 等字段 |
+| 前端 | Web 管理端 | 随访管理页面 |
+| 前端 | 小程序 | 消息展示 + 反馈表单 |
+
+---
+
+## Edge Cases & Guardrails
+
+### 数据边界
+
+| 场景 | 处理方式 |
+|------|---------|
+| PDF 食材名匹配不到 foods 表 | `food_id=null`,仅存 food_name。后续管理员可在 Web 端人工匹配 |
+| 同一食材在同报告中出现多次 | 合并为一条,取最后一次 score |
+| PDF 没有食材章节(早期报告类型) | `foodSuitability` 返回空列表,跳过处理 |
+| 用户无任何菌群报告 | 仅用 `user_food_preference` 生成空清单,引导上传报告 |
+| 缺乏分析生成时已有购物清单 | 追加到现有 active 清单,不重复创建 |
+| 营养师发送消息后家长长期未读 | `read_at=null`,随访到期提醒时自动标出 |
+| 单食材含多种缺乏营养素 | `reason` 字段拼接多条理由,取最高优先级 |
+
+### API 约束
+
+- 全部端点使用 `@PostMapping`(项目统一规范)
+- 响应格式统一为 `Result<T>`(code/message/data)
+- JWT 鉴权走 `@RequestAttribute("userId")` + `@RequestAttribute("role")`
+- 新增 Controller 类名需检查不与其他包重名
+
+### 前端约束
+
+- 小程序禁止使用可选链 `?.`(用 `&&` 替代)
+- 小程序禁止 CSS Grid(用 flexbox)
+- 使用 Vue 2 Options API
+- 新增页面需在 `pages.json` 注册
+
+---
+
+## Future Considerations (P2+)
+
+| 功能 | 说明 |
+|------|------|
+| 微信模板消息推送 | 营养师发消息/随访到期时推送 |
+| 食材与商场商品打通 | 购物清单可展示参考价格/附近购买渠道 |
+| 食材库存管理 | 家庭可记录"冰箱里还有",避免重复购买 |
+| 全量营养数据库导入 | 中国食物成分表 2000+ 食材 + USDA 交叉引用 |
+| RDI 可视化 | 雷达图展示营养素达标率 |
+| 沟通模板库 | 营养师可保存常用建议模板 |
+
+---
+
+## Open Questions
+
+(本次设计范围明确,无待定项)
+
+---
+
+*设计文档版本:v1.0*
+*上次更新:2026-06-24*