# 健康管家 — 技术设计文档 V2

> 基于需求规格文档 V2，技术栈确定为 .NET 10 + Flutter + PostgreSQL。

---

## 一、整体架构

```
Flutter App（iOS + Android）
    │
    ├── HTTPS REST（业务 CRUD）
    ├── SSE（AI 对话流式输出）
    └── HTTPS 轮询（医患消息，15s间隔）
    
    ▼
.NET 10 ASP.NET Core Minimal API
    │
    ├── HttpClient → DeepSeek（LLM 对话 + Tool Calling）
    ├── HttpClient → 千问 VL（食物图片识别）
    ├── EF Core → PostgreSQL（主数据库）
    ├── MinIO SDK → MinIO（文件/图片存储）
    └── 极光推送 SDK → 极光推送（Android/iOS 通知）
```

- 后端：.NET 10，C# 12，4 层 Clean Architecture
- 前端：Flutter，Dart，Riverpod
- AI 调用：纯 HttpClient 直连各模型厂商 API（OpenAI 兼容协议），不使用任何模型厂商 SDK
- 不允许前端直接调 AI 模型 API，全部经 .NET 后端中转

---

## 二、后端架构

### 2.1 目录结构

```
HealthManager/
├── src/
│   ├── Health.Domain/              # 领域层
│   │   ├── Entities/               #   实体：User, HealthRecord, Medication, DietRecord 等
│   │   ├── Enums/                  #   枚举：HealthMetricType, MealType, ReportStatus 等
│   │   └── Interfaces/             #   仓储接口
│   │
│   ├── Health.Application/         # 应用层
│   │   ├── DTOs/                   #   数据传输对象
│   │   ├── Services/               #   业务服务
│   │   │   ├── AuthService.cs          #   认证
│   │   │   ├── HealthService.cs        #   健康数据
│   │   │   ├── DietService.cs          #   饮食记录
│   │   │   ├── MedicationService.cs    #   用药管理
│   │   │   ├── ReportService.cs        #   报告管理
│   │   │   ├── ConsultationService.cs  #   问诊
│   │   │   ├── ExerciseService.cs      #   运动计划
│   │   │   └── UserService.cs          #   用户/健康档案
│   │   └── Interfaces/             #   服务接口
│   │
│   ├── Health.Infrastructure/      # 基础设施层
│   │   ├── Data/
│   │   │   ├── AppDbContext.cs         #   EF Core 上下文
│   │   │   └── Migrations/            #   数据库迁移
│   │   ├── Services/
│   │   │   ├── JwtProvider.cs          #   JWT 生成与验证
│   │   │   ├── SmsService.cs           #   短信发送
│   │   │   ├── PushService.cs          #   极光推送
│   │   │   └── MinioStorageService.cs  #   文件存储
│   │   └── AI/                         #   AI 服务模块
│   │       ├── OpenAiCompatibleClient.cs   #   OpenAI 兼容 HTTP 客户端（统一调 DeepSeek/千问）
│   │       ├── AiChatService.cs            #   AI 对话编排（意图识别 + Tool Calling）
│   │       ├── AgentHandlers/              #   7 个 Agent 的处理器
│   │       │   ├── DefaultAgentHandler.cs      #   默认对话
│   │       │   ├── ConsultationAgentHandler.cs #   AI 问诊
│   │       │   ├── HealthDataAgentHandler.cs   #   记数据
│   │       │   ├── DietAgentHandler.cs         #   拍饮食
│   │       │   ├── MedicationAgentHandler.cs   #   药管家
│   │       │   ├── ReportAgentHandler.cs       #   看报告
│   │       │   └── ExerciseAgentHandler.cs     #   运动计划
│   │       └── PromptManager.cs            #   System Prompt 模板管理
│   │
│   └── Health.WebApi/              # 接口层
│       ├── Program.cs                  #   启动配置
│       ├── Endpoints/                  #   Minimal API 端点
│       │   ├── AuthEndpoints.cs
│       │   ├── HealthEndpoints.cs
│       │   ├── DietEndpoints.cs
│       │   ├── MedicationEndpoints.cs
│       │   ├── ReportEndpoints.cs
│       │   ├── ConsultationEndpoints.cs
│       │   ├── ExerciseEndpoints.cs
│       │   ├── UserEndpoints.cs
│       │   ├── AiChatEndpoints.cs       #   AI 对话 SSE
│       │   └── FileEndpoints.cs
│       ├── Middleware/               #   中间件
│       │   └── ExceptionMiddleware.cs
│       └── BackgroundServices/       #   后台服务
│           └── MedicationReminderService.cs  #   用药提醒定时扫描
```

### 2.2 API 响应格式

所有 API 返回统一结构：

```json
// 成功
{ "code": 0, "data": { ... }, "message": null }

// 业务错误
{ "code": 40001, "data": null, "message": "验证码错误" }

// 系统异常
{ "code": 50000, "data": null, "message": "服务器内部错误" }
```

- `code = 0`：成功
- `code = 4xxxx`：客户端错误
- `code = 5xxxx`：服务端错误

---

### 2.3 7 个 Agent 设计

每个 Agent 本质上是：一个 SSE 端点 + 独立的 System Prompt + 独立的 Tool Set。

#### Agent 架构模式

```
Flutter 端 → GET /api/ai/{agent_type}/chat?message=xxx
                │
                ▼
         AgentHandler.Process(message, userId, sessionId)
                │
                ├── 1. 加载上下文（System Prompt + 患者数据 + 对话历史）
                ├── 2. 调用 DeepSeek（stream: true）
                ├── 3. Tool Calling 循环（如果 LLM 返回 tool_calls）
                │       ├── 执行 Tool
                │       ├── 结果追加到消息历史
                │       └── 再次调用 LLM
                ├── 4. 流式输出回复（SSE data: 逐 token）
                └── 5. 保存对话记录到 PostgreSQL
```

#### 7 个 Agent 对照表

| Agent | Path | System Prompt 定位 | Tool Set |
|-------|------|-------------------|----------|
| 默认对话 | `/api/ai/default/chat` | 通用健康管家 | query_health_records, check_abnormal |
| AI 问诊 | `/api/ai/consultation/chat` | 医生助手，多轮追问 | query_health_records, check_archive, request_doctor |
| 记数据 | `/api/ai/health/chat` | 健康数据录入助手 | record_health_data, query_health_records |
| 拍饮食 | `/api/ai/diet/chat` | 营养分析专家 | analyze_food_image（VLM）, estimate_food_text, check_archive |
| 药管家 | `/api/ai/medication/chat` | 用药管理专家 | manage_medication（create/query/confirm）, check_archive |
| 看报告 | `/api/ai/report/chat` | 报告解读专家 | analyze_report（VLM）, query_health_records |
| 运动计划 | `/api/ai/exercise/chat` | 运动康复教练 | manage_exercise_plan（create/query/checkin） |

### 2.4 Tool Calling 设计

| Tool | 功能 | 参数 |
|------|------|------|
| `record_health_data` | 录入健康数据 | type, systolic?, diastolic?, heart_rate?, glucose?, spo2?, weight?, recorded_at |
| `query_health_records` | 查询近期健康数据 | type?, days? |
| `check_abnormal` | 检查是否有异常指标 | — |
| `check_archive` | 查询患者档案 | — |
| `analyze_food_image` | VLM 食物识别 | image_url |
| `estimate_food_text` | 文字描述估算食物 | text |
| `manage_medication` | 用药管理 | action(create/query/confirm/update), name?, dosage?, time? |
| `manage_exercise_plan` | 运动计划 | action(create/query/checkin), exercises?, day? |
| `analyze_report` | 报告 AI 预解读 | image_url |
| `request_doctor` | 转医生 | reason, urgency_level |

### 2.5 AI 服务模块

#### OpenAiCompatibleClient

统一的 OpenAI 兼容协议 HTTP 客户端，支持：
- Chat Completions（含 streaming）
- Vision（图片理解）
- Tool Calling（Function Calling）
- JSON Mode
- 多 BaseUrl 切换（DeepSeek / 千问 VL）
- 自动重试

```csharp
// 调用示例
var client = new OpenAiCompatibleClient(new()
{
    BaseUrl = "https://api.deepseek.com/v1",
    ApiKey = config["DEEPSEEK_API_KEY"],
    Model = "deepseek-chat"
});

await foreach (var token in client.ChatStreamAsync(messages, tools))
{
    // SSE 输出给前端
}
```

#### AiChatService

每个 Agent 对话的核心编排逻辑：

```csharp
public async IAsyncEnumerable<string> ProcessAgentChat(
    AgentType agentType,
    string message,
    string userId,
    string? conversationId)
{
    // 1. 加载 System Prompt
    var systemPrompt = _promptManager.GetSystemPrompt(agentType);
    
    // 2. 注入患者上下文
    var patientContext = await LoadPatientContext(userId);
    
    // 3. 加载对话历史
    var history = await LoadConversationHistory(conversationId, userId);
    
    // 4. 构建消息
    var messages = BuildMessages(systemPrompt, patientContext, history, message);
    
    // 5. Tool Calling 循环
    var tools = _toolRegistry.GetTools(agentType);
    var maxIterations = 5;
    
    for (int i = 0; i < maxIterations; i++)
    {
        var response = await _aiClient.ChatAsync(messages, tools, stream: true);
        
        if (response.FinishReason == "stop")
        {
            // 流式输出文本
            await foreach (var token in response.Stream)
                yield return token;
            break;
        }
        else if (response.FinishReason == "tool_calls")
        {
            // 执行 Tool
            foreach (var toolCall in response.ToolCalls)
            {
                var result = await _toolExecutor.Execute(toolCall);
                messages.Add(new { role = "tool", content = result, tool_call_id = toolCall.Id });
            }
            // 继续循环
        }
    }
    
    // 6. 保存对话记录
    await SaveConversation(userId, conversationId, messages);
}
```

#### PromptManager

System Prompt 模板管理：

```
默认对话:
  你是一个心脏术后康复患者的私人 AI 健康管家。
  你的名字叫"阿福"，语气温暖专业。
  每次回复末尾，如果有需要提醒的事项，简短提醒一句。

AI 问诊:
  你是一个心血管内科医生助手，负责对患者进行多轮问诊。
  规则：
  1. 每次只问一个问题，不要一次问多个
  2. 给出快捷选项让患者点击
  3. 遇到胸痛/呼吸困难/心悸 → 建议立即就医
  4. 血压持续>160/100 或心率异常 → 建议转医生
  5. 问诊结束后给出初步分析

记数据:
  你是一个健康数据录入助手。
  规则：
  1. 解析用户口中的指标和数值
  2. 指标模糊时追问确认
  3. 时间模糊时取当前时间并告知用户
  4. 异常值时附带提醒
  5. 录入后展示确认卡片

拍饮食:
  你是一个营养分析专家。
  规则：
  1. 等待VLM识别结果后再做分析
  2. 结合患者档案判断"能不能吃"
  3. 给出单项警告和整体建议
  4. 追问餐次归类
  ...

（其余 Agent 同理）
```

### 2.6 配置管理

所有敏感配置放环境变量（`.env` / appsettings）：

```
# LLM
DEEPSEEK_API_KEY=sk-xxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_MODEL=deepseek-chat

# VLM
QWEN_API_KEY=sk-xxxxx
QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
QWEN_VISION_MODEL=qwen-vl-max

# 基础设施
JWT_SECRET=xxxxx
DB_CONNECTION=Host=localhost;Database=health_manager;...
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=xxxxx
MINIO_SECRET_KEY=xxxxx

# 推送
JPUSH_APP_KEY=xxxxx
JPUSH_MASTER_SECRET=xxxxx

# 短信
SMS_PROVIDER=xxx
SMS_API_KEY=xxxxx
```

---

## 三、数据库设计

### 3.1 核心表

| 表名 | 说明 |
|------|------|
| `users` | 用户表 |
| `health_records` | 健康记录（血压/心率/血糖/血氧/体重） |
| `medications` | 用药计划 |
| `medication_logs` | 用药打卡记录 |
| `diet_records` | 饮食记录 |
| `diet_food_items` | 饮食记录中的食物条目 |
| `exercise_plans` | 运动计划（按周） |
| `exercise_plan_items` | 运动计划每日条目 |
| `reports` | 检查报告 |
| `conversations` | AI 对话会话 |
| `conversation_messages` | AI 对话消息 |
| `consultations` | 问诊会话 |
| `consultation_messages` | 问诊消息 |
| `follow_ups` | 复查/随访计划 |
| `health_archives` | 健康档案 |
| `refresh_tokens` | 刷新令牌 |
| `verification_codes` | 验证码 |
| `notification_preferences` | 通知偏好 |
| `device_tokens` | 设备推送 token |
| `devices` | 绑定设备（预留） |

### 3.2 关键表结构

#### users

```sql
id            UUID PRIMARY KEY
phone         VARCHAR(20) UNIQUE NOT NULL
name          VARCHAR(50)
gender        VARCHAR(10)
birth_date    DATE
avatar_url    VARCHAR(500)
created_at    TIMESTAMP
updated_at    TIMESTAMP
```

#### health_records

```sql
id            UUID PRIMARY KEY
user_id       UUID REFERENCES users(id)
metric_type   VARCHAR(20) NOT NULL  -- blood_pressure / heart_rate / glucose / spo2 / weight
systolic      INTEGER               -- 血压专用
diastolic     INTEGER               -- 血压专用
value         DECIMAL               -- 通用数值（心率/血糖/血氧/体重）
unit          VARCHAR(20)
source        VARCHAR(20) NOT NULL  -- ai_entry / device_sync / manual
is_abnormal   BOOLEAN
recorded_at   TIMESTAMP
created_at    TIMESTAMP
```

#### medications

```sql
id            UUID PRIMARY KEY
user_id       UUID REFERENCES users(id)
name          VARCHAR(100) NOT NULL
dosage        VARCHAR(50)
frequency     VARCHAR(50)           -- daily / twice_daily / weekly 等
time_of_day   TIME[]                -- PostgreSQL 数组，如 {08:00,20:00}
start_date    DATE
end_date      DATE
is_active     BOOLEAN
source        VARCHAR(20)           -- prescription / ai_entry / manual
created_at    TIMESTAMP
updated_at    TIMESTAMP
```

#### diet_records

```sql
id            UUID PRIMARY KEY
user_id       UUID REFERENCES users(id)
meal_type     VARCHAR(10)           -- breakfast / lunch / dinner / snack
total_calories INTEGER
health_score  INTEGER               -- 1-5
recorded_at   DATE
created_at    TIMESTAMP
```

#### diet_food_items

```sql
id            UUID PRIMARY KEY
diet_record_id UUID REFERENCES diet_records(id)
name          VARCHAR(100)
portion       VARCHAR(50)
calories      INTEGER
protein_g     DECIMAL
carbs_g       DECIMAL
fat_g         DECIMAL
warning       TEXT
sort_order    INTEGER
```

#### health_archives

```sql
id            UUID PRIMARY KEY
user_id       UUID REFERENCES users(id) UNIQUE
diagnosis     TEXT                   -- 主要诊断
surgery_type  VARCHAR(100)           -- 手术类型
surgery_date  DATE                   -- 手术日期
allergies     TEXT[]                 -- 过敏信息
diet_restrictions TEXT[]             -- 饮食限制
chronic_diseases TEXT[]             -- 慢病史
family_history TEXT                  -- 家族病史
updated_at    TIMESTAMP
```

#### medication_logs

```sql
id              UUID PRIMARY KEY
medication_id   UUID REFERENCES medications(id)
user_id         UUID REFERENCES users(id)
status          VARCHAR(20) NOT NULL   -- taken / missed / skipped
scheduled_time  TIME NOT NULL
confirmed_at    TIMESTAMP
created_at      TIMESTAMP
```

#### exercise_plans

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
week_start_date DATE NOT NULL          -- 本周一
created_at      TIMESTAMP
updated_at      TIMESTAMP
```

#### exercise_plan_items

```sql
id              UUID PRIMARY KEY
plan_id         UUID REFERENCES exercise_plans(id)
day_of_week     INTEGER NOT NULL       -- 0=周一, 6=周日
exercise_type   VARCHAR(100) NOT NULL  -- 散步/慢跑/游泳 等
duration_minutes INTEGER NOT NULL
is_completed    BOOLEAN DEFAULT FALSE
completed_at    TIMESTAMP
is_rest_day     BOOLEAN DEFAULT FALSE  -- 休息日
```

#### reports

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
file_url        VARCHAR(500) NOT NULL
file_type       VARCHAR(20)            -- image / pdf
report_type     VARCHAR(50)            -- blood_test / ecg / ultrasound / other
ai_summary      TEXT                   -- AI 预解读结果（JSON）
ai_indicators   JSONB                  -- 提取的指标 [{name, value, unit, range, status}]
status          VARCHAR(20) NOT NULL   -- pending_doctor / doctor_reviewed
doctor_comment  TEXT                   -- 医生审核意见
doctor_name     VARCHAR(50)            -- 审核医生
reviewed_at     TIMESTAMP
created_at      TIMESTAMP
```

#### conversations

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
agent_type      VARCHAR(20) NOT NULL   -- default / consultation / health / diet 等
title           VARCHAR(200)
summary         VARCHAR(500)           -- 侧滑抽屉显示用的摘要
message_count   INTEGER DEFAULT 0
created_at      TIMESTAMP
updated_at      TIMESTAMP
```

#### conversation_messages

```sql
id              UUID PRIMARY KEY
conversation_id UUID REFERENCES conversations(id)
role            VARCHAR(10) NOT NULL   -- user / assistant / tool
content         TEXT NOT NULL
intent          VARCHAR(30)            -- health_record / diet / medication / exercise / report / chat
metadata_json   JSONB                  -- 结构化数据（录入数值、食物列表、卡片数据等）
created_at      TIMESTAMP
```

#### consultations

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
doctor_id       UUID REFERENCES doctors(id)
status          VARCHAR(20) NOT NULL   -- ai_talking / waiting_doctor / doctor_replied / closed
month           INTEGER NOT NULL       -- 所属月份，用于配额计算
created_at      TIMESTAMP
closed_at       TIMESTAMP
```

#### consultation_messages

```sql
id              UUID PRIMARY KEY
consultation_id UUID REFERENCES consultations(id)
sender_type     VARCHAR(10) NOT NULL   -- user / doctor / ai
sender_name     VARCHAR(50)
content         TEXT NOT NULL
created_at      TIMESTAMP
```

#### doctors

```sql
id              UUID PRIMARY KEY
name            VARCHAR(50) NOT NULL
title           VARCHAR(50)            -- 主任医师/副主任医师
department      VARCHAR(50)            -- 心血管内科/营养科 等
avatar_url      VARCHAR(500)
introduction    TEXT
is_active       BOOLEAN DEFAULT TRUE
created_at      TIMESTAMP
```

#### follow_ups

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
title           VARCHAR(200) NOT NULL
doctor_name     VARCHAR(50)
department      VARCHAR(50)
scheduled_at    TIMESTAMP NOT NULL
notes           TEXT
status          VARCHAR(20) NOT NULL   -- upcoming / completed / cancelled
created_at      TIMESTAMP
```

#### refresh_tokens

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
token           VARCHAR(500) UNIQUE NOT NULL
expires_at      TIMESTAMP NOT NULL
is_revoked      BOOLEAN DEFAULT FALSE
created_at      TIMESTAMP
```

#### verification_codes

```sql
id              UUID PRIMARY KEY
phone           VARCHAR(20) NOT NULL
code            VARCHAR(6) NOT NULL
expires_at      TIMESTAMP NOT NULL
is_used         BOOLEAN DEFAULT FALSE
created_at      TIMESTAMP
```

#### notification_preferences

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id) UNIQUE
medication_reminder   BOOLEAN DEFAULT TRUE
followup_reminder     BOOLEAN DEFAULT TRUE
doctor_reply          BOOLEAN DEFAULT TRUE
abnormal_alert        BOOLEAN DEFAULT TRUE
updated_at      TIMESTAMP
```

#### device_tokens

```sql
id              UUID PRIMARY KEY
user_id         UUID REFERENCES users(id)
platform        VARCHAR(10) NOT NULL    -- ios / android
push_token      VARCHAR(500) NOT NULL
is_active       BOOLEAN DEFAULT TRUE
created_at      TIMESTAMP
updated_at      TIMESTAMP
```

### 3.3 索引设计

```sql
-- 健康记录：按用户+时间查询高频
CREATE INDEX idx_health_records_user_time ON health_records(user_id, recorded_at DESC);
CREATE INDEX idx_health_records_user_type ON health_records(user_id, metric_type);

-- 用药：按时段扫描提醒
CREATE INDEX idx_medications_active_time ON medications(user_id, is_active);
CREATE INDEX idx_medication_logs_medication ON medication_logs(medication_id, created_at DESC);

-- 对话：按用户+时间
CREATE INDEX idx_conversations_user_time ON conversations(user_id, updated_at DESC);
CREATE INDEX idx_conversation_messages_conv ON conversation_messages(conversation_id, created_at);

-- 问诊：轮询新消息
CREATE INDEX idx_consultation_messages_consult ON consultation_messages(consultation_id, created_at DESC);

-- 饮食：按日期查询
CREATE INDEX idx_diet_records_user_date ON diet_records(user_id, recorded_at DESC);

-- 验证码：清理过期
CREATE INDEX idx_verification_codes_phone ON verification_codes(phone, created_at DESC);
```

---

## 四、API 设计

### 4.1 认证 API

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/auth/send-sms` | 发送短信验证码 `{phone}`（开发阶段直接返回成功，不做真实发送） |
| POST | `/api/auth/login` | 验证码登录 `{phone, smsCode}`（开发阶段任意6位数字通过） |
| POST | `/api/auth/refresh` | 刷新 token `{refreshToken}` → `{accessToken, refreshToken}` |
| POST | `/api/auth/logout` | 登出 `{refreshToken}` |

**Token 策略：**
- `access_token`：30 分钟，前端内存持有
- `refresh_token`：30 天，flutter_secure_storage 存储
- 前端收到 401 → Dio 拦截器自动用 refresh_token 换新 access_token → 重试原请求
- 刷新时同时下发新的 refresh_token（续期 30 天），旧 refresh_token 立即失效
- 连续 30 天未使用 App 导致 refresh_token 过期 → 需重新登录
- 一次登录长期有效，除非主动退出或 30 天未使用

### 4.2 AI 对话 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/ai/{agent_type}/chat` | SSE 流式对话 |
| GET | `/api/ai/conversations` | 获取对话列表 |
| GET | `/api/ai/conversations/{id}` | 获取对话历史 |
| DELETE | `/api/ai/conversations/{id}` | 删除对话 |

**查询参数：** `message`, `conversationId?`（不传则新建对话）

**agent_type**：`default` / `consultation` / `health` / `diet` / `medication` / `report` / `exercise`

**SSE 事件格式：**
```
data: {"action":"notice","message":"正在分析..."}          // Tool Calling 前先发提示
data: {"action":"notice","message":"正在记录血压数据..."}   // 每次调 Tool 前都发
data: {"action":"answer","data":"你"}
data: {"action":"answer","data":"的"}
data: {"action":"answer","data":"血压"}
data: {"action":"answer","data":"是"}
...
data: {"action":"tool_result","tool":"record_health_data","data":{...}}
data: {"action":"answer","data":"已记录"}
...
data: {"action":"conversation_id","data":"abc123"}
data: [DONE]
```

每次 Tool Calling 循环调 Tool 前，先发一条 notice 事件，避免用户对着空白等待。

### 4.3 VLM 食物识别 API

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/ai/analyze-food-image` | 上传食物照片 |

**请求：** `multipart/form-data`，字段 `images`（支持多张）

**响应：**
```json
{
  "foods": [
    {
      "name": "米饭", "portion": "约1碗", "calories": 174,
      "proteinGrams": 3.9, "carbsGrams": 38.9, "fatGrams": 0.3
    }
  ],
  "totalCalories": 644,
  "warnings": ["红烧肉脂肪偏高"],
  "score": 3
}
```

### 4.4 健康数据 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/health-records` | 查询 `?type=&days=` |
| POST | `/api/health-records` | 新增（AI Tool 或手动） |
| PUT | `/api/health-records/{id}` | 修改 |
| GET | `/api/health-records/latest` | 获取各指标最新值 |
| GET | `/api/health-records/trend` | 趋势数据 `?type=&period=7/30/90` |

### 4.5 用药管理 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/medications` | 获取用药列表 |
| POST | `/api/medications` | 添加用药 |
| PUT | `/api/medications/{id}` | 修改用药 |
| DELETE | `/api/medications/{id}` | 删除用药 |
| POST | `/api/medications/{id}/confirm` | 确认服药打卡 |

### 4.6 饮食记录 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/diet-records` | 查询 `?date=&meal_type=` |
| POST | `/api/diet-records` | 新增 |
| PUT | `/api/diet-records/{id}` | 修改 |
| DELETE | `/api/diet-records/{id}` | 删除 |

### 4.7 运动计划 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/exercise-plans/current` | 获取本周计划 |
| POST | `/api/exercise-plans` | 创建计划 |
| PUT | `/api/exercise-plans/{id}` | 修改计划 |
| POST | `/api/exercise-plans/items/{id}/checkin` | 打卡某日运动 |

### 4.8 报告管理 API

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/reports/upload` | 上传报告文件 |
| GET | `/api/reports` | 报告列表 |
| GET | `/api/reports/{id}` | 报告详情（含 AI 解读） |
| PUT | `/api/reports/{id}/doctor-review` | 医生审核（医生端） |

### 4.9 问诊 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/doctors` | 可咨询的医生列表 |
| POST | `/api/consultations` | 创建问诊 |
| GET | `/api/consultations` | 问诊列表 |
| GET | `/api/consultations/{id}` | 问诊详情（含消息） |
| POST | `/api/consultations/{id}/messages` | 发送消息 |
| GET | `/api/consultations/{id}/messages?after={msgId}` | 轮询新消息 |
| GET | `/api/user/consultation-quota` | 剩余问诊次数 |

### 4.10 用户与档案 API

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/user/profile` | 获取个人信息 |
| PUT | `/api/user/profile` | 修改资料 |
| GET | `/api/user/health-archive` | 获取健康档案 |
| PUT | `/api/user/health-archive` | 更新健康档案 |
| DELETE | `/api/user/account` | 注销账号 |

### 4.11 文件上传 API

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/files/upload` | 上传文件（食物图/报告/处方） |
| GET | `/api/files/{id}` | 获取文件 |
| DELETE | `/api/files/{id}` | 删除文件 |

### 4.12 推送与通知 API

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/notifications/register-device` | 注册设备推送 token |
| GET | `/api/notifications/preferences` | 获取通知偏好 |
| PUT | `/api/notifications/preferences` | 更新通知偏好 |

---

## 五、前端架构（Flutter）

### 5.1 目录结构

参考 YYZ 项目的组织方式：

```
lib/
├── main.dart                         # 入口
├── app.dart                          # MaterialApp + 主题 + 路由
│
├── core/                             # 全局基础设施
│   ├── api_client.dart               #   Dio 封装：token 注入、401 自动刷新、统一报错
│   ├── app_theme.dart                #   薰衣草紫主题
│   ├── app_router.dart               #   GoRouter 配置
│   └── secure_storage.dart           #   Token 安全存储
│
├── models/                           # 数据模型（freezed）
│   ├── user.dart
│   ├── health_record.dart
│   ├── food_record.dart
│   ├── medication.dart
│   ├── exercise_plan.dart
│   ├── consultation.dart
│   ├── report.dart
│   └── ai_message.dart
│
├── providers/                        # Riverpod 状态管理
│   ├── auth_provider.dart            #   认证状态
│   ├── chat_provider.dart            #   对话状态（当前 Agent + 消息列表）
│   ├── health_data_provider.dart     #   健康数据
│   ├── medication_provider.dart      #   用药
│   ├── diet_provider.dart            #   饮食
│   ├── exercise_provider.dart        #   运动计划
│   ├── consultation_provider.dart    #   问诊
│   └── report_provider.dart          #   报告
│
├── services/                         # 业务服务层
│   ├── auth_service.dart             #   认证
│   ├── ai_service.dart               #   AI 对话（SSE 流式）
│   ├── health_service.dart           #   健康数据 CRUD
│   ├── diet_service.dart             #   饮食 CRUD
│   ├── medication_service.dart       #   用药 CRUD
│   ├── exercise_service.dart         #   运动计划
│   ├── consultation_service.dart     #   问诊
│   ├── report_service.dart           #   报告
│   └── user_service.dart             #   用户/档案
│
├── pages/                            # 页面
│   ├── auth/
│   │   └── login_page.dart           #   登录
│   │
│   ├── home/
│   │   ├── home_page.dart            #   首页骨架（抽屉 + 卡片 + 对话流 + 输入框）
│   │   └── widgets/                  #   首页专属组件
│   │       ├── task_card_area.dart   #     任务卡片区
│   │       ├── agent_panel.dart      #     Agent 功能面板
│   │       ├── chat_bubble.dart      #     聊天气泡
│   │       ├── data_confirm_card.dart#     数据确认卡片
│   │       ├── diet_result_card.dart #     饮食分析卡片
│   │       ├── food_edit_sheet.dart  #     食物编辑面板
│   │       ├── medication_card.dart  #     用药确认卡片
│   │       ├── report_card.dart      #     报告解读卡片
│   │       └── health_drawer.dart    #     侧滑抽屉
│   │
│   ├── profile/
│   │   ├── profile_page.dart         #   个人中心
│   │   ├── edit_profile_page.dart    #   编辑资料
│   │   └── health_archive_page.dart  #   健康档案
│   │
│   ├── chart/
│   │   └── trend_page.dart           #   趋势图表
│   │
│   ├── calendar/
│   │   └── health_calendar_page.dart #   健康日历
│   │
│   ├── medication/
│   │   ├── medication_list_page.dart #   用药列表
│   │   └── medication_edit_page.dart #   编辑用药
│   │
│   ├── report/
│   │   ├── report_list_page.dart     #   报告列表
│   │   └── report_detail_page.dart   #   报告详情
│   │
│   ├── consultation/
│   │   ├── doctor_list_page.dart     #   医生列表
│   │   └── doctor_chat_page.dart     #   问诊对话
│   │
│   ├── exercise/
│   │   └── exercise_plan_page.dart   #   运动计划管理
│   │
│   ├── diet/
│   │   └── diet_record_list_page.dart#   饮食记录列表
│   │
│   ├── followup/
│   │   └── followup_list_page.dart   #   复查列表
│   │
│   └── settings/
│       ├── settings_page.dart        #   设置
│       ├── notification_prefs_page.dart
│       └── static_text_page.dart     #   隐私/协议/关于（纯文本）
│
├── widgets/                          # 通用组件
│   ├── app_button.dart               #   主/次/文字按钮
│   ├── app_card.dart                 #   卡片容器
│   ├── app_input.dart                #   输入框
│   ├── capsule_button.dart           #   胶囊按钮
│   ├── loading_widget.dart           #   加载动画
│   └── empty_state.dart              #   空状态
│
└── utils/
    ├── sse_handler.dart               #   SSE 流处理
    ├── markdown_utils.dart            #   Markdown 渲染
    └── format.dart                    #   日期/数值格式化
```

### 5.2 状态管理

使用 Riverpod（Notifier + NotifierProvider），参考 YYZ 模式：

```dart
// 认证
final authProvider = NotifierProvider<AuthNotifier, AuthState>(AuthNotifier.new);

// 聊天（核心）
final chatProvider = NotifierProvider<ChatNotifier, ChatState>(ChatNotifier.new);
// ChatState: 当前 agent_type、消息列表、conversationId、是否流式中

// 各业务域
final healthDataProvider = NotifierProvider<HealthDataNotifier, HealthDataState>(...);
final medicationProvider = NotifierProvider<MedicationNotifier, MedicationState>(...);
...
```

### 5.3 HTTP 请求层

```dart
// Dio 封装
class ApiClient {
  late final Dio _dio;
  
  // 拦截器 1：自动带 Authorization: Bearer {accessToken}
  // 拦截器 2：收到 401 → 自动调 /api/auth/refresh → 重试
  // 拦截器 3：统一错误处理
}
```

所有 Service 层返回类型安全的模型对象，页面不直接处理 JSON。

### 5.4 路由设计

| 路由 | 页面 | 说明 |
|------|------|------|
| `/login` | 登录页 | 手机号+验证码登录 |
| `/home` | 首页 | 对话流 + 任务卡片区 + Agent 面板 + 输入框 |
| `/trend/:type` | 趋势图表 | 单指标折线图，7/30/90天切换 |
| `/calendar` | 健康日历 | 月视图，标记用药/运动/复查 |
| `/medications` | 用药列表 | 当前用药计划列表 |
| `/medications/:id/edit` | 编辑用药 | 修改药品名/剂量/时间 |
| `/reports` | 报告列表 | 按时间倒序 |
| `/reports/:id` | 报告详情 | AI解读 + 医生审核 + 原始图片 |
| `/doctors` | 医生列表 | 可选医生（3-4名） |
| `/consultation/:id` | 问诊对话 | 患者与医生文字聊天 |
| `/exercise-plan` | 运动计划 | 本周每天的运动安排+打卡 |
| `/diet-records` | 饮食记录列表 | 按日期查看历史饮食 |
| `/profile` | 个人中心 | 头像+诊断+菜单入口 |
| `/profile/edit` | 编辑资料 | 姓名/性别/出生日期 |
| `/health-archive` | 健康档案 | 完整健康信息（6大类） |
| `/followups` | 复查列表 | 即将到来/已完成 |
| `/settings` | 设置 | 隐私/通知/字体/协议/关于/退出 |
| `/settings/notifications` | 通知偏好 | 四类推送独立开关 |
| `/page/:type` | 静态文本页 | 隐私政策/服务协议/关于 |

### 5.5 首页组件交互

- **任务卡片区**：`AnimatedContainer` + `GestureDetector` 折叠/展开
- **对话流**：`ListView.builder` + `reverse: true`
- **Agent 面板**：底部弹出 `AnimatedContainer`，面板内容随 agent_type 切换
- **输入框**：`TextField` + `Row`，附件按钮 📎 + 系统键盘语音转文字
- **侧滑抽屉**：`Scaffold` + `Drawer`

### 5.6 Agent 切换流程

```
用户点击胶囊 [记数据]
    → 胶囊高亮
    → chatProvider.setAgentType(AgentType.health)
    → 底部弹出记数据面板（手动录入入口）
    → AI 对话上下文切换到记数据
    → 用户输入 → POST /api/ai/health/chat → SSE 流式

用户再次点击 [记数据]
    → 胶囊恢复
    → 面板收起
    → chatProvider.setAgentType(AgentType.default)
```

### 5.7 消息类型 Widget

| 消息类型 | Widget | 说明 |
|---------|--------|------|
| 纯文本 | ChatBubble | AI 左白底紫左边框，用户右紫底白字 |
| 数据确认 | DataConfirmCard | "已记录：血压 135/85 ✅" + 可点击编辑 |
| 饮食分析 | DietResultCard | 食物列表 + 热量 + 评分 + 警告 + "能不能吃" |
| 用药确认 | MedicationConfirmCard | 药名 + 剂量 + [确认] [修改] |
| 报告解读 | ReportCard | 指标列表 + AI 分析 + "待医生确认" |
| 快捷选项 | QuickOptionsRow | 一行可点击按钮，如 [闷痛] [刺痛] |

### 5.8 SSE 流处理

参考 YYZ 的 `ChatStreamHandler` 模式：

```dart
class SseHandler {
  // 订阅 Dio 流式响应
  // 解析 SSE 事件：answer / notice / tool_result / conversation_id / [DONE]
  // 逐步追加 token 到当前 AI 消息
  // 处理 tool_result 更新消息中的结构化数据
  // onDone → 标记消息 status: 'done'
}
```

### 5.9 平台支持

- Android 最低：7.0（API 24）
- iOS 最低：15.0
- 不做暗黑模式

### 5.10 Flutter 依赖

```yaml
dependencies:
  flutter:
    sdk: flutter
  
  # 状态管理
  flutter_riverpod: ^3.2.0
  
  # HTTP
  dio: ^5.4.0
  
  # 安全存储
  flutter_secure_storage: ^9.2.0
  
  # 路由
  go_router: ^14.0.0
  
  # 图表
  fl_chart: ^0.68.0
  
  # 本地存储
  shared_preferences: ^2.2.0    # 轻量键值存储（字体大小等设置）
  
  # 相机 & 图片
  image_picker: ^1.0.0
  file_picker: ^10.3.7
  
  # 推送
  jpush_flutter: ^4.0.0    # 极光推送

dev_dependencies:
  flutter_test:
    sdk: flutter
  freezed: ^2.5.0
  json_serializable: ^6.8.0
  build_runner: ^2.4.0
```

---

## 六、定时任务

### 6.1 用药提醒扫描

.NET `BackgroundService` 每分钟扫描一次：

```csharp
public class MedicationReminderService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 1. 查询：reminder_enabled = true AND 服药时间 ≤ 当前时间 AND 今天未打卡
            // 2. 对每条 → 查用户 device_token → 调用极光推送
            // 3. 记录推送日志
            // 4. 检查漏服（超时未打卡 → 再推 → 标记漏服）
            
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}
```

### 6.2 漏服检测

- 服药时间已过 15 分钟未打卡 → 再推一次
- 服药时间已过 30 分钟未打卡 → 再推一次
- 服药时间已过 1 小时未打卡 → 标记漏服，停止提醒

### 6.3 其他定时任务

| 任务 | 频率 | 说明 |
|------|------|------|
| 复查提醒 | 每天 1 次 | 检查未来 3 天内的复查计划 → 推送 |
| 对话记录清理 | 每天 1 次 | 删除 30 天前的对话 |
| 验证码清理 | 每小时 1 次 | 删除过期验证码 |

---

## 七、文件存储

### 7.1 MinIO 存储桶

| 存储桶 | 内容 | 生命周期 |
|------|------|------|
| `food-images` | 食物照片 | 30 天 |
| `report-images` | 报告图片/PDF | 永久 |
| `avatars` | 用户头像 | 永久 |

### 7.2 上传流程

```
Flutter → Dio multipart → .NET API → 存入 MinIO → 返回文件 URL
    → VLM 食物识别时：文件 URL 传给千问 VL
```

### 7.3 图片上传规格

| 参数 | 食物照片 | 报告图片/PDF |
|------|---------|------------|
| 支持格式 | JPEG / PNG / HEIC | JPEG / PNG / HEIC / PDF |
| 最大文件 | 10MB | 10MB |
| 前端处理 | 原图上传，不压缩 | 原图上传，不压缩 |

---

## 八、推送通知

### 8.1 推送架构

```
.NET BackgroundService → 极光推送 SDK → 极光服务器 → APNs (iOS) / 极光通道 (Android)
```

- 设备注册时 Flutter 获取极光 registration ID → 上报后端存入 device_tokens 表
- 后端推送时查 device_tokens → 调用极光推送 API

### 8.2 推送类型

| 类型 | 触发 | iOS | Android |
|------|------|-----|---------|
| 用药提醒 | BackgroundService | 极光 → APNs | 极光通道 |
| 复查提醒 | BackgroundService | 同上 | 同上 |
| 医生回复 | 医生发消息时 | 同上 | 同上 |
| 异常警告 | 硬件上传异常时（后期） | 同上 | 同上 |

### 8.3 点击行为

| 通知类型 | 点击后跳转 |
|---------|----------|
| 用药提醒 | 首页 + 自动打开药管家 |
| 复查提醒 | 复查详情 |
| 医生回复 | 该问诊对话 |
| 异常警告 | 该指标趋势页 |

---

## 九、AI 模型配置

| 用途 | 模型 | API 地址 |
|------|------|------|
| LLM 对话 | DeepSeek `deepseek-chat` | `https://api.deepseek.com/v1` |
| VLM 食物识别 | 千问 `qwen-vl-max` | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
| VLM 备用 | 火山引擎 `doubao-vision-pro` | `https://ark.cn-beijing.volces.com/api/v3` |

---

## 十、部署

### 10.1 本地开发

- `dotnet run` 启动后端
- PostgreSQL + MinIO 用 Docker 本地拉起
- Flutter `flutter run` 连模拟器/真机调试

### 10.2 后期上云

- .NET 10 Web API，Docker 单容器部署
- PostgreSQL → 阿里云 RDS
- MinIO → 阿里云 OSS
- 配置通过环境变量注入

---

## 十一、开发顺序

### 第一阶段：基础设施

| # | 任务 |
|---|------|
| 1 | .NET 后端项目搭建 + Clean Architecture 骨架 |
| 2 | PostgreSQL 数据库初始化 + EF Core 迁移 |
| 3 | JWT 认证系统 |
| 4 | Flutter 项目搭建 + 路由 + 主题 |
| 5 | Dio 封装 + 401 拦截器 |
| 6 | OpenAiCompatibleClient 实现 |

### 第二阶段：核心 AI

| # | 任务 |
|---|------|
| 7 | PromptManager + 7 个 Agent 的 System Prompt |
| 8 | Tool Calling 引擎 |
| 9 | 默认对话 Agent + SSE 流式 |
| 10 | 记数据 Agent |
| 11 | 药管家 Agent |
| 12 | 拍饮食 Agent（含 VLM） |
| 13 | AI 问诊 Agent |
| 14 | 看报告 Agent |
| 15 | 运动计划 Agent |

### 第三阶段：业务功能

| # | 任务 |
|---|------|
| 16 | 健康数据 CRUD API |
| 17 | 饮食记录 API |
| 18 | 用药管理 API + 服药提醒后台任务 |
| 19 | 运动计划 API |
| 20 | 报告上传 + AI 解读 API |
| 21 | 问诊 API（医生列表 + 消息轮询） |

### 第四阶段：前端

| # | 任务 |
|---|------|
| 22 | 登录页 |
| 23 | 首页骨架（抽屉 + 卡片区 + 对话流 + 输入框） |
| 24 | 6 个 Agent 面板 UI |
| 25 | SSE 流处理 + 消息气泡 |
| 26 | 数据确认卡片 / 饮食分析卡片 / 用药确认卡片 |
| 27 | 侧滑抽屉（健康概览 + 历史对话） |
| 28 | 各功能页面（个人中心、健康档案、趋势图、报告等） |

### 第五阶段：联调 + 完善

| # | 任务 |
|---|------|
| 29 | 极光推送集成 |
| 30 | MinIO 文件存储集成 |
| 31 | 短信服务对接 |
| 32 | 异常处理完善 |
| 33 | 联调测试 |
