from pydantic import BaseModel from typing import Literal, Optional, List, Dict, Any class HealthPlanGenerateRequest(BaseModel): member_ids: Optional[str] = None dimensions: Optional[str] = None goal: str = "" family_id: Optional[int] = None class PlanTask(BaseModel): action_type: Literal["buy", "read", "exercise", "checkin", "diet", "activity"] title: str dimension: Literal["body", "mind", "wisdom", "action", "wealth"] frequency: Literal["once", "daily"] = "daily" notes: Optional[str] = None class HealthPlanSection(BaseModel): key: str # "nutrition" | "diet" | "exercise" title: str content: str # markdown text items: List[dict] = [] # structured items for UI rendering tasks: List[PlanTask] = [] # machine-readable task intents class HealthPlanResponse(BaseModel): """单成员方案 / 兼容旧结构(扁平 sections)。 多成员(common+individual)请使用 MultiMemberPlanResponse。 本类保留以向后兼容单人方案路径(len(member_ids) <= 1)。 """ overview: str = "" sections: List[HealthPlanSection] = [] abnormal_indicators: List[dict] = [] knowledge_sources: List[dict] = [] class PlanPart(BaseModel): """方案的一个逻辑部分:可承载共性(common)或个性(individual)。""" overview: str = "" sections: List[HealthPlanSection] = [] abnormal_indicators: List[dict] = [] knowledge_sources: List[dict] = [] class MultiMemberPlanResponse(BaseModel): """多人方案:共性 + 各成员个性。 - common: 适用于全体成员的共性方案部分 - individual: 按 member_id 分组的个性方案部分 """ common: PlanPart = PlanPart() individual: Dict[str, PlanPart] = {} def build_rendered_content(self, member_names: Dict[str, str]) -> str: """把 common+individual 组合渲染为 Markdown 文本(供 planContent 使用)。 member_names: {memberId: 成员名},用于在标题里标注个性部分归属。 """ out: List[str] = [] out.append("# 健康方案\n\n") common = self.common out.append("## 共性方案\n") if common.overview: out.append("### 方案概述\n\n" + common.overview + "\n\n") for sec in common.sections: out.append(f"## {sec.title}\n\n{sec.content}\n\n") if common.abnormal_indicators: out.append("## 共性异常指标\n") for ind in common.abnormal_indicators: out.append(f"- {ind.get('member','?')} - {ind.get('indicator','')}: {ind.get('value','')} {ind.get('unit','')}") if self.individual: out.append("\n---\n\n# 个性方案\n") for mid, part in self.individual.items(): name = member_names.get(mid, f"成员{mid}") out.append(f"\n## {name} 个性方案\n") if part.overview: out.append("### 概述\n\n" + part.overview + "\n") for sec in part.sections: out.append(f"### {sec.title}\n\n{sec.content}\n") if part.abnormal_indicators: out.append("### 异常指标\n") for ind in part.abnormal_indicators: out.append(f"- {ind.get('member','?')} - {ind.get('indicator','')}: {ind.get('value','')} {ind.get('unit','')}") return "".join(out)