""" PregoPal - 饮食记录模块 ======================== 记录孕妇的饮食习惯,同时保存为 JSON(结构化)+ Markdown(可读)。 数据流向: MiniCPM-o 回复 (含 [DIET_RECORD] 标记) → 正则解析 → JSON + Markdown 当前:手动表单输入 后续:AI 对话中自动提取 [DIET_RECORD] 标记 """ import json import datetime import hashlib import re from pathlib import Path from config import DIET_LOG_FILE, LOGS_DIR, DIET_LOG_SCHEMA_VERSION class DietLogger: """记录孕妇的饮食习惯并保存为 Markdown""" def __init__(self): self.log_file = DIET_LOG_FILE self.logs = self._load_logs() def _load_logs(self): """加载饮食记录""" if self.log_file.exists(): with open(self.log_file, 'r', encoding='utf-8') as f: return json.load(f) return {"schema_version": DIET_LOG_SCHEMA_VERSION, "records": []} def _save_logs(self): """保存饮食记录""" with open(self.log_file, 'w', encoding='utf-8') as f: json.dump(self.logs, f, ensure_ascii=False, indent=2) def add_record(self, member_name: str, member_relation: str, date: str, meals: dict, mood: str = "", notes: str = ""): """添加一条饮食记录""" record = { "id": hashlib.md5(f"{date}_{member_name}_{datetime.datetime.now()}".encode()).hexdigest()[:8], "member_name": member_name, "member_relation": member_relation, "date": date, "meals": meals, "mood": mood, "notes": notes, "extensions": {}, # 预留扩展字段 "created_at": datetime.datetime.now().isoformat() } self.logs["records"].append(record) self._save_logs() # 同时生成 Markdown 文件 md_path = self._generate_markdown(record) return record, md_path def _generate_markdown(self, record: dict) -> Path: """生成 Markdown 格式的饮食日志""" date_str = record["date"] md_filename = LOGS_DIR / f"饮食日志_{date_str}.md" content = f"""# 🥗 孕期饮食日志 ## 📋 基本信息 - **日期**: {date_str} - **记录人**: {record['member_relation']} - {record['member_name']} - **记录时间**: {record['created_at'][:19]} ## 🍽️ 今日饮食记录 """ for meal_time, food in record["meals"].items(): content += f"### {meal_time}\n- {food}\n\n" if record["mood"]: content += f"## 😊 今日心情\n{record['mood']}\n\n" if record["notes"]: content += f"## 📝 备注\n{record['notes']}\n\n" content += """--- *由 PregoPal 自动生成* """ with open(md_filename, 'w', encoding='utf-8') as f: f.write(content) return md_filename def get_recent_records(self, days: int = 7) -> list: """获取最近几天的记录""" today = datetime.date.today() cutoff = today - datetime.timedelta(days=days) recent = [] for r in self.logs["records"]: try: record_date = datetime.date.fromisoformat(r["date"]) if record_date >= cutoff: recent.append(r) except ValueError: continue return sorted(recent, key=lambda x: x["date"], reverse=True) def get_all_markdown_files(self) -> list: """获取所有 Markdown 日志文件""" return sorted(LOGS_DIR.glob("*.md"), reverse=True) # ============================================================ # [DIET_RECORD] 标记解析(后续 AI 版本使用) # ============================================================ @staticmethod def parse_diet_record(text: str) -> dict | None: """ 从 AI 回复中解析 [DIET_RECORD] 标记 Args: text: AI 模型的回复文本 Returns: 解析出的饮食记录字典,或 None(未找到标记) 示例输入: [DIET_RECORD] 日期: 2026-06-07 早餐: 全麦面包+鸡蛋+牛奶 午餐: 清蒸鱼+糙米饭+炒时蔬 晚餐: 加餐: 酸奶+坚果 心情: 挺好 备注: [/DIET_RECORD] """ pattern = r'\[DIET_RECORD\](.*?)\[/DIET_RECORD\]' match = re.search(pattern, text, re.DOTALL) if not match: return None content = match.group(1).strip() record = {"meals": {}, "mood": "", "notes": ""} for line in content.split('\n'): line = line.strip() if not line: continue if ':' in line: key, value = line.split(':', 1) key = key.strip() value = value.strip() if key == '日期': record['date'] = value elif key == '心情': record['mood'] = value elif key == '备注': record['notes'] = value elif key in ['早餐', '午餐', '晚餐', '加餐']: if value: # 只记录非空值 record['meals'][key] = value return record if record['meals'] else None