|
@@ -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*
|