Initial commit: 个性化饮食推荐助手 - 包含OCR识别、AI分析、现代化界面等功能
This commit is contained in:
620
core/base.py
Normal file
620
core/base.py
Normal file
@@ -0,0 +1,620 @@
|
||||
"""
|
||||
核心基座架构 - Diet Recommendation App
|
||||
统一的基座设计,支持所有功能模块的扩展
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, List, Optional, Any, Union, Callable
|
||||
from dataclasses import dataclass, field
|
||||
import json
|
||||
import sqlite3
|
||||
import logging
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
import asyncio
|
||||
from enum import Enum
|
||||
import threading
|
||||
from queue import Queue
|
||||
import os
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
except Exception:
|
||||
# 如果没有.env文件或加载失败,使用默认配置
|
||||
pass
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModuleType(Enum):
|
||||
"""模块类型枚举"""
|
||||
DATA_COLLECTION = "data_collection"
|
||||
USER_ANALYSIS = "user_analysis"
|
||||
RECOMMENDATION = "recommendation"
|
||||
GUI_INTERFACE = "gui_interface"
|
||||
NOTIFICATION = "notification"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseConfig:
|
||||
"""基础配置类"""
|
||||
app_name: str = "个性化饮食推荐助手"
|
||||
version: str = "1.0.0"
|
||||
debug: bool = True
|
||||
database_path: str = "data/app.db"
|
||||
model_path: str = "models/"
|
||||
log_level: str = "INFO"
|
||||
|
||||
# API配置
|
||||
qwen_api_key: Optional[str] = None
|
||||
qwen_base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
qwen_model: str = "qwen-plus-latest"
|
||||
|
||||
# 用户配置
|
||||
max_recommendations: int = 5
|
||||
min_training_samples: int = 10
|
||||
model_update_threshold: int = 50
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserData:
|
||||
"""统一用户数据结构"""
|
||||
user_id: str
|
||||
profile: Dict[str, Any] = field(default_factory=dict)
|
||||
meals: List[Dict[str, Any]] = field(default_factory=list)
|
||||
feedback: List[Dict[str, Any]] = field(default_factory=list)
|
||||
preferences: Dict[str, Any] = field(default_factory=dict)
|
||||
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
||||
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisResult:
|
||||
"""统一分析结果结构"""
|
||||
module_type: ModuleType
|
||||
user_id: str
|
||||
input_data: Any
|
||||
result: Dict[str, Any]
|
||||
confidence: float
|
||||
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class BaseModule(ABC):
|
||||
"""基础模块抽象类"""
|
||||
|
||||
def __init__(self, config: BaseConfig, module_type: ModuleType):
|
||||
self.config = config
|
||||
self.module_type = module_type
|
||||
self.is_initialized = False
|
||||
self.logger = logging.getLogger(f"{self.__class__.__name__}")
|
||||
|
||||
@abstractmethod
|
||||
def initialize(self) -> bool:
|
||||
"""初始化模块"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def process(self, input_data: Any, user_data: UserData) -> AnalysisResult:
|
||||
"""处理数据"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cleanup(self) -> bool:
|
||||
"""清理资源"""
|
||||
pass
|
||||
|
||||
def is_ready(self) -> bool:
|
||||
"""检查模块是否就绪"""
|
||||
return self.is_initialized
|
||||
|
||||
|
||||
class DataManager:
|
||||
"""数据管理基座"""
|
||||
|
||||
def __init__(self, config: BaseConfig):
|
||||
self.config = config
|
||||
self.db_path = Path(config.database_path)
|
||||
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._init_database()
|
||||
|
||||
def _init_database(self):
|
||||
"""初始化数据库"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 用户表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
data TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 分析结果表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS analysis_results (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT,
|
||||
module_type TEXT,
|
||||
input_data TEXT,
|
||||
result TEXT,
|
||||
confidence REAL,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
metadata TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||
)
|
||||
''')
|
||||
|
||||
# 系统配置表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def save_user_data(self, user_data: UserData) -> bool:
|
||||
"""保存用户数据"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO users (user_id, data, updated_at)
|
||||
VALUES (?, ?, CURRENT_TIMESTAMP)
|
||||
''', (user_data.user_id, json.dumps(user_data.__dict__)))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"保存用户数据失败: {e}")
|
||||
return False
|
||||
|
||||
def get_user_data(self, user_id: str) -> Optional[UserData]:
|
||||
"""获取用户数据"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 获取用户基本信息
|
||||
cursor.execute('SELECT data FROM users WHERE user_id = ?', (user_id,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
# 解析用户基本信息
|
||||
data_dict = json.loads(result[0])
|
||||
|
||||
# 获取餐食记录
|
||||
cursor.execute('''
|
||||
SELECT date, meal_type, foods, quantities, calories, satisfaction_score, food_items
|
||||
FROM meal_records
|
||||
WHERE user_id = ?
|
||||
ORDER BY date DESC
|
||||
''', (user_id,))
|
||||
|
||||
meal_rows = cursor.fetchall()
|
||||
meals = []
|
||||
for row in meal_rows:
|
||||
meal = {
|
||||
'date': row[0],
|
||||
'meal_type': row[1],
|
||||
'foods': json.loads(row[2]) if row[2] else [],
|
||||
'quantities': json.loads(row[3]) if row[3] else [],
|
||||
'calories': row[4],
|
||||
'satisfaction_score': row[5],
|
||||
'food_items': json.loads(row[6]) if row[6] else []
|
||||
}
|
||||
meals.append(meal)
|
||||
|
||||
# 获取反馈记录
|
||||
cursor.execute('''
|
||||
SELECT date, recommended_foods, user_choice, feedback_type
|
||||
FROM feedback_records
|
||||
WHERE user_id = ?
|
||||
ORDER BY date DESC
|
||||
''', (user_id,))
|
||||
|
||||
feedback_rows = cursor.fetchall()
|
||||
feedback = []
|
||||
for row in feedback_rows:
|
||||
fb = {
|
||||
'date': row[0],
|
||||
'recommended_foods': json.loads(row[1]) if row[1] else [],
|
||||
'user_choice': row[2],
|
||||
'feedback_type': row[3]
|
||||
}
|
||||
feedback.append(fb)
|
||||
|
||||
# 获取问卷数据
|
||||
cursor.execute('''
|
||||
SELECT questionnaire_type, answers
|
||||
FROM questionnaire_records
|
||||
WHERE user_id = ?
|
||||
''', (user_id,))
|
||||
|
||||
questionnaire_rows = cursor.fetchall()
|
||||
preferences = {}
|
||||
for row in questionnaire_rows:
|
||||
preferences[row[0]] = json.loads(row[1]) if row[1] else {}
|
||||
|
||||
conn.close()
|
||||
|
||||
# 构建完整的用户数据
|
||||
user_data = UserData(
|
||||
user_id=data_dict['user_id'],
|
||||
profile=data_dict.get('profile', {}),
|
||||
meals=meals,
|
||||
feedback=feedback,
|
||||
preferences=preferences,
|
||||
created_at=data_dict.get('created_at', ''),
|
||||
updated_at=data_dict.get('updated_at', '')
|
||||
)
|
||||
|
||||
return user_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取用户数据失败: {e}")
|
||||
return None
|
||||
|
||||
def save_analysis_result(self, result: AnalysisResult) -> bool:
|
||||
"""保存分析结果"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO analysis_results
|
||||
(user_id, module_type, input_data, result, confidence, metadata)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
result.user_id,
|
||||
result.module_type.value,
|
||||
json.dumps(result.input_data),
|
||||
json.dumps(result.result),
|
||||
result.confidence,
|
||||
json.dumps(result.metadata)
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"保存分析结果失败: {e}")
|
||||
return False
|
||||
|
||||
def get_analysis_history(self, user_id: str, module_type: Optional[ModuleType] = None,
|
||||
limit: int = 10) -> List[AnalysisResult]:
|
||||
"""获取分析历史"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
if module_type:
|
||||
cursor.execute('''
|
||||
SELECT module_type, input_data, result, confidence, timestamp, metadata
|
||||
FROM analysis_results
|
||||
WHERE user_id = ? AND module_type = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?
|
||||
''', (user_id, module_type.value, limit))
|
||||
else:
|
||||
cursor.execute('''
|
||||
SELECT module_type, input_data, result, confidence, timestamp, metadata
|
||||
FROM analysis_results
|
||||
WHERE user_id = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?
|
||||
''', (user_id, limit))
|
||||
|
||||
results = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
analysis_results = []
|
||||
for row in results:
|
||||
result = AnalysisResult(
|
||||
module_type=ModuleType(row[0]),
|
||||
user_id=user_id,
|
||||
input_data=json.loads(row[1]),
|
||||
result=json.loads(row[2]),
|
||||
confidence=row[3],
|
||||
timestamp=row[4],
|
||||
metadata=json.loads(row[5])
|
||||
)
|
||||
analysis_results.append(result)
|
||||
|
||||
return analysis_results
|
||||
except Exception as e:
|
||||
logger.error(f"获取分析历史失败: {e}")
|
||||
return []
|
||||
|
||||
|
||||
class EventBus:
|
||||
"""事件总线基座"""
|
||||
|
||||
def __init__(self):
|
||||
self.subscribers: Dict[str, List[Callable]] = {}
|
||||
self.event_queue = Queue()
|
||||
self.is_running = False
|
||||
self.worker_thread = None
|
||||
|
||||
def subscribe(self, event_type: str, callback: Callable):
|
||||
"""订阅事件"""
|
||||
if event_type not in self.subscribers:
|
||||
self.subscribers[event_type] = []
|
||||
self.subscribers[event_type].append(callback)
|
||||
|
||||
def unsubscribe(self, event_type: str, callback: Callable):
|
||||
"""取消订阅"""
|
||||
if event_type in self.subscribers:
|
||||
self.subscribers[event_type].remove(callback)
|
||||
|
||||
def publish(self, event_type: str, data: Any):
|
||||
"""发布事件"""
|
||||
self.event_queue.put((event_type, data))
|
||||
|
||||
def start(self):
|
||||
"""启动事件总线"""
|
||||
self.is_running = True
|
||||
self.worker_thread = threading.Thread(target=self._process_events)
|
||||
self.worker_thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止事件总线"""
|
||||
self.is_running = False
|
||||
if self.worker_thread:
|
||||
self.worker_thread.join()
|
||||
|
||||
def _process_events(self):
|
||||
"""处理事件"""
|
||||
while self.is_running:
|
||||
try:
|
||||
event_type, data = self.event_queue.get(timeout=1)
|
||||
|
||||
if event_type in self.subscribers:
|
||||
for callback in self.subscribers[event_type]:
|
||||
try:
|
||||
callback(data)
|
||||
except Exception as e:
|
||||
logger.error(f"事件处理失败: {e}")
|
||||
|
||||
self.event_queue.task_done()
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
class ModuleManager:
|
||||
"""模块管理器基座"""
|
||||
|
||||
def __init__(self, config: BaseConfig):
|
||||
self.config = config
|
||||
self.modules: Dict[ModuleType, BaseModule] = {}
|
||||
self.data_manager = DataManager(config)
|
||||
self.event_bus = EventBus()
|
||||
self.is_initialized = False
|
||||
|
||||
def register_module(self, module: BaseModule) -> bool:
|
||||
"""注册模块"""
|
||||
try:
|
||||
# 检查模块是否已经注册
|
||||
if module.module_type in self.modules:
|
||||
logger.warning(f"模块 {module.module_type.value} 已经注册,跳过重复注册")
|
||||
return True
|
||||
|
||||
if module.initialize():
|
||||
self.modules[module.module_type] = module
|
||||
logger.info(f"模块 {module.module_type.value} 注册成功")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"模块 {module.module_type.value} 初始化失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"注册模块失败: {e}")
|
||||
return False
|
||||
|
||||
def process_request(self, module_type: ModuleType, input_data: Any,
|
||||
user_id: str) -> Optional[AnalysisResult]:
|
||||
"""处理请求"""
|
||||
if module_type not in self.modules:
|
||||
logger.error(f"模块 {module_type.value} 未注册")
|
||||
return None
|
||||
|
||||
module = self.modules[module_type]
|
||||
if not module.is_ready():
|
||||
logger.error(f"模块 {module_type.value} 未就绪")
|
||||
return None
|
||||
|
||||
# 获取用户数据
|
||||
user_data = self.data_manager.get_user_data(user_id)
|
||||
if not user_data:
|
||||
logger.error(f"用户 {user_id} 数据不存在")
|
||||
return None
|
||||
|
||||
try:
|
||||
# 处理请求
|
||||
result = module.process(input_data, user_data)
|
||||
|
||||
# 保存结果
|
||||
self.data_manager.save_analysis_result(result)
|
||||
|
||||
# 发布事件
|
||||
self.event_bus.publish(f"{module_type.value}_completed", result)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"处理请求失败: {e}")
|
||||
return None
|
||||
|
||||
def get_module_status(self) -> Dict[str, bool]:
|
||||
"""获取模块状态"""
|
||||
return {module_type.value: module.is_ready()
|
||||
for module_type, module in self.modules.items()}
|
||||
|
||||
def initialize_all(self) -> bool:
|
||||
"""初始化所有模块"""
|
||||
try:
|
||||
self.event_bus.start()
|
||||
self.is_initialized = True
|
||||
logger.info("模块管理器初始化完成")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"初始化失败: {e}")
|
||||
return False
|
||||
|
||||
def cleanup_all(self) -> bool:
|
||||
"""清理所有模块"""
|
||||
try:
|
||||
self.event_bus.stop()
|
||||
|
||||
for module in self.modules.values():
|
||||
module.cleanup()
|
||||
|
||||
self.is_initialized = False
|
||||
logger.info("模块管理器清理完成")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"清理失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class AppCore:
|
||||
"""应用核心基座"""
|
||||
|
||||
def __init__(self, config: BaseConfig):
|
||||
self.config = config
|
||||
self.module_manager = ModuleManager(config)
|
||||
self.data_manager = DataManager(config)
|
||||
self.is_running = False
|
||||
|
||||
def start(self) -> bool:
|
||||
"""启动应用"""
|
||||
try:
|
||||
if self.module_manager.initialize_all():
|
||||
self.is_running = True
|
||||
logger.info("应用启动成功")
|
||||
return True
|
||||
else:
|
||||
logger.error("应用启动失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"启动应用失败: {e}")
|
||||
return False
|
||||
|
||||
def stop(self) -> bool:
|
||||
"""停止应用"""
|
||||
try:
|
||||
if self.module_manager.cleanup_all():
|
||||
self.is_running = False
|
||||
logger.info("应用停止成功")
|
||||
return True
|
||||
else:
|
||||
logger.error("应用停止失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"停止应用失败: {e}")
|
||||
return False
|
||||
|
||||
def create_user(self, user_id: str, initial_data: Dict[str, Any]) -> bool:
|
||||
"""创建用户"""
|
||||
user_data = UserData(
|
||||
user_id=user_id,
|
||||
profile=initial_data.get('profile', {}),
|
||||
preferences=initial_data.get('preferences', {})
|
||||
)
|
||||
return self.module_manager.data_manager.save_user_data(user_data)
|
||||
|
||||
def process_user_request(self, module_type: ModuleType, input_data: Any,
|
||||
user_id: str) -> Optional[AnalysisResult]:
|
||||
"""处理用户请求"""
|
||||
return self.module_manager.process_request(module_type, input_data, user_id)
|
||||
|
||||
def get_user_data(self, user_id: str) -> Optional[UserData]:
|
||||
"""获取用户数据"""
|
||||
return self.module_manager.data_manager.get_user_data(user_id)
|
||||
|
||||
def get_analysis_history(self, user_id: str, module_type: Optional[ModuleType] = None) -> List[AnalysisResult]:
|
||||
"""获取分析历史"""
|
||||
return self.module_manager.data_manager.get_analysis_history(user_id, module_type)
|
||||
|
||||
|
||||
# 全局应用实例
|
||||
app_core: Optional[AppCore] = None
|
||||
|
||||
|
||||
def get_app_core() -> AppCore:
|
||||
"""获取应用核心实例"""
|
||||
global app_core
|
||||
if app_core is None:
|
||||
config = BaseConfig()
|
||||
app_core = AppCore(config)
|
||||
return app_core
|
||||
|
||||
|
||||
def initialize_app(config: Optional[BaseConfig] = None) -> bool:
|
||||
"""初始化应用"""
|
||||
global app_core
|
||||
if config is None:
|
||||
config = BaseConfig()
|
||||
|
||||
app_core = AppCore(config)
|
||||
return app_core.start()
|
||||
|
||||
|
||||
def cleanup_app() -> bool:
|
||||
"""清理应用"""
|
||||
global app_core
|
||||
if app_core:
|
||||
return app_core.stop()
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试基座架构
|
||||
print("测试核心基座架构...")
|
||||
|
||||
# 初始化应用
|
||||
if initialize_app():
|
||||
print("✅ 应用初始化成功")
|
||||
|
||||
# 创建测试用户
|
||||
test_user_id = "test_user_001"
|
||||
initial_data = {
|
||||
"profile": {
|
||||
"name": "测试用户",
|
||||
"age": 25,
|
||||
"gender": "女"
|
||||
},
|
||||
"preferences": {
|
||||
"taste": "sweet",
|
||||
"diet": "balanced"
|
||||
}
|
||||
}
|
||||
|
||||
app = get_app_core()
|
||||
if app.create_user(test_user_id, initial_data):
|
||||
print("✅ 用户创建成功")
|
||||
|
||||
# 获取用户数据
|
||||
user_data = app.get_user_data(test_user_id)
|
||||
if user_data:
|
||||
print(f"✅ 用户数据获取成功: {user_data.user_id}")
|
||||
|
||||
# 清理应用
|
||||
cleanup_app()
|
||||
print("✅ 应用清理完成")
|
||||
else:
|
||||
print("❌ 应用初始化失败")
|
||||
|
||||
print("基座架构测试完成!")
|
||||
928
core/base_engine.py
Normal file
928
core/base_engine.py
Normal file
@@ -0,0 +1,928 @@
|
||||
"""
|
||||
核心基座架构 - 个性化饮食推荐系统
|
||||
提供统一的基础设施和接口,所有功能模块都基于此基座构建
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import asyncio
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, List, Optional, Any, Union
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserProfile:
|
||||
"""用户画像 - 统一数据结构"""
|
||||
user_id: str
|
||||
name: str
|
||||
age: int
|
||||
gender: str
|
||||
height: float
|
||||
weight: float
|
||||
activity_level: str
|
||||
|
||||
# 偏好和限制
|
||||
taste_preferences: Dict[str, int] = None
|
||||
dietary_preferences: List[str] = None
|
||||
allergies: List[str] = None
|
||||
dislikes: List[str] = None
|
||||
|
||||
# 生理信息
|
||||
is_female: bool = False
|
||||
menstrual_cycle_length: Optional[int] = None
|
||||
last_period_date: Optional[str] = None
|
||||
|
||||
# 个性化因素
|
||||
zodiac_sign: Optional[str] = None
|
||||
personality_traits: List[str] = None
|
||||
health_goals: List[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.taste_preferences is None:
|
||||
self.taste_preferences = {}
|
||||
if self.dietary_preferences is None:
|
||||
self.dietary_preferences = []
|
||||
if self.allergies is None:
|
||||
self.allergies = []
|
||||
if self.dislikes is None:
|
||||
self.dislikes = []
|
||||
if self.personality_traits is None:
|
||||
self.personality_traits = []
|
||||
if self.health_goals is None:
|
||||
self.health_goals = []
|
||||
|
||||
|
||||
@dataclass
|
||||
class MealRecord:
|
||||
"""餐食记录 - 统一数据结构"""
|
||||
user_id: str
|
||||
date: str
|
||||
meal_type: str # breakfast, lunch, dinner
|
||||
foods: List[str]
|
||||
quantities: List[str]
|
||||
calories: Optional[float] = None
|
||||
satisfaction_score: Optional[int] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RecommendationResult:
|
||||
"""推荐结果 - 统一数据结构"""
|
||||
user_id: str
|
||||
date: str
|
||||
meal_type: str
|
||||
recommended_foods: List[str]
|
||||
reasoning: str
|
||||
confidence_score: float
|
||||
special_considerations: List[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.special_considerations is None:
|
||||
self.special_considerations = []
|
||||
|
||||
|
||||
class BaseEngine(ABC):
|
||||
"""基础引擎抽象类 - 所有功能模块的基座"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any] = None):
|
||||
self.config = config or {}
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self._initialized = False
|
||||
|
||||
@abstractmethod
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化引擎"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def process(self, data: Any) -> Any:
|
||||
"""处理数据"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def cleanup(self) -> bool:
|
||||
"""清理资源"""
|
||||
pass
|
||||
|
||||
def is_initialized(self) -> bool:
|
||||
"""检查是否已初始化"""
|
||||
return self._initialized
|
||||
|
||||
def get_config(self, key: str, default: Any = None) -> Any:
|
||||
"""获取配置"""
|
||||
return self.config.get(key, default)
|
||||
|
||||
|
||||
class DataManager(BaseEngine):
|
||||
"""数据管理基座 - 统一的数据存储和访问接口"""
|
||||
|
||||
def __init__(self, db_path: str = "data/app.db"):
|
||||
super().__init__()
|
||||
self.db_path = Path(db_path)
|
||||
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化数据库"""
|
||||
try:
|
||||
await self._create_tables()
|
||||
self._initialized = True
|
||||
self.logger.info("数据管理器初始化成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"数据管理器初始化失败: {e}")
|
||||
return False
|
||||
|
||||
async def _create_tables(self):
|
||||
"""创建数据库表"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 用户表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
profile_data TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 餐食记录表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS meals (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT,
|
||||
date TEXT,
|
||||
meal_type TEXT,
|
||||
foods TEXT,
|
||||
quantities TEXT,
|
||||
calories REAL,
|
||||
satisfaction_score INTEGER,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||
)
|
||||
''')
|
||||
|
||||
# 推荐记录表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS recommendations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT,
|
||||
date TEXT,
|
||||
meal_type TEXT,
|
||||
recommended_foods TEXT,
|
||||
reasoning TEXT,
|
||||
confidence_score REAL,
|
||||
special_considerations TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||
)
|
||||
''')
|
||||
|
||||
# 用户反馈表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS feedback (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT,
|
||||
date TEXT,
|
||||
recommendation_id INTEGER,
|
||||
user_choice TEXT,
|
||||
feedback_type TEXT,
|
||||
satisfaction_score INTEGER,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (user_id),
|
||||
FOREIGN KEY (recommendation_id) REFERENCES recommendations (id)
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
async def process(self, operation: str, data: Any) -> Any:
|
||||
"""处理数据操作"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
|
||||
operations = {
|
||||
'save_user': self._save_user_profile,
|
||||
'get_user': self._get_user_profile,
|
||||
'save_meal': self._save_meal_record,
|
||||
'get_meals': self._get_meal_records,
|
||||
'save_recommendation': self._save_recommendation,
|
||||
'get_recommendations': self._get_recommendations,
|
||||
'save_feedback': self._save_feedback,
|
||||
'get_feedback': self._get_feedback
|
||||
}
|
||||
|
||||
if operation in operations:
|
||||
return await operations[operation](data)
|
||||
else:
|
||||
raise ValueError(f"不支持的操作: {operation}")
|
||||
|
||||
async def _save_user_profile(self, profile: UserProfile) -> bool:
|
||||
"""保存用户画像"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO users (user_id, profile_data, updated_at)
|
||||
VALUES (?, ?, CURRENT_TIMESTAMP)
|
||||
''', (profile.user_id, json.dumps(asdict(profile))))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存用户画像失败: {e}")
|
||||
return False
|
||||
|
||||
async def _get_user_profile(self, user_id: str) -> Optional[UserProfile]:
|
||||
"""获取用户画像"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT profile_data FROM users WHERE user_id = ?', (user_id,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
if result:
|
||||
profile_dict = json.loads(result[0])
|
||||
return UserProfile(**profile_dict)
|
||||
return None
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取用户画像失败: {e}")
|
||||
return None
|
||||
|
||||
async def _save_meal_record(self, meal: MealRecord) -> bool:
|
||||
"""保存餐食记录"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO meals (user_id, date, meal_type, foods, quantities,
|
||||
calories, satisfaction_score, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
meal.user_id, meal.date, meal.meal_type,
|
||||
json.dumps(meal.foods), json.dumps(meal.quantities),
|
||||
meal.calories, meal.satisfaction_score, meal.notes
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存餐食记录失败: {e}")
|
||||
return False
|
||||
|
||||
async def _get_meal_records(self, params: Dict[str, Any]) -> List[MealRecord]:
|
||||
"""获取餐食记录"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
user_id = params.get('user_id')
|
||||
days = params.get('days', 5)
|
||||
|
||||
cursor.execute('''
|
||||
SELECT user_id, date, meal_type, foods, quantities, calories,
|
||||
satisfaction_score, notes
|
||||
FROM meals
|
||||
WHERE user_id = ?
|
||||
ORDER BY date DESC, meal_type
|
||||
LIMIT ?
|
||||
''', (user_id, days * 3))
|
||||
|
||||
results = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
meals = []
|
||||
for row in results:
|
||||
meal = MealRecord(
|
||||
user_id=row[0],
|
||||
date=row[1],
|
||||
meal_type=row[2],
|
||||
foods=json.loads(row[3]),
|
||||
quantities=json.loads(row[4]),
|
||||
calories=row[5],
|
||||
satisfaction_score=row[6],
|
||||
notes=row[7]
|
||||
)
|
||||
meals.append(meal)
|
||||
|
||||
return meals
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取餐食记录失败: {e}")
|
||||
return []
|
||||
|
||||
async def _save_recommendation(self, recommendation: RecommendationResult) -> int:
|
||||
"""保存推荐结果"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO recommendations (user_id, date, meal_type, recommended_foods,
|
||||
reasoning, confidence_score, special_considerations)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
recommendation.user_id, recommendation.date, recommendation.meal_type,
|
||||
json.dumps(recommendation.recommended_foods), recommendation.reasoning,
|
||||
recommendation.confidence_score, json.dumps(recommendation.special_considerations)
|
||||
))
|
||||
|
||||
recommendation_id = cursor.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return recommendation_id
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存推荐结果失败: {e}")
|
||||
return -1
|
||||
|
||||
async def _get_recommendations(self, params: Dict[str, Any]) -> List[RecommendationResult]:
|
||||
"""获取推荐记录"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
user_id = params.get('user_id')
|
||||
days = params.get('days', 7)
|
||||
|
||||
cursor.execute('''
|
||||
SELECT user_id, date, meal_type, recommended_foods, reasoning,
|
||||
confidence_score, special_considerations
|
||||
FROM recommendations
|
||||
WHERE user_id = ?
|
||||
ORDER BY date DESC
|
||||
LIMIT ?
|
||||
''', (user_id, days))
|
||||
|
||||
results = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
recommendations = []
|
||||
for row in results:
|
||||
rec = RecommendationResult(
|
||||
user_id=row[0],
|
||||
date=row[1],
|
||||
meal_type=row[2],
|
||||
recommended_foods=json.loads(row[3]),
|
||||
reasoning=row[4],
|
||||
confidence_score=row[5],
|
||||
special_considerations=json.loads(row[6]) if row[6] else []
|
||||
)
|
||||
recommendations.append(rec)
|
||||
|
||||
return recommendations
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取推荐记录失败: {e}")
|
||||
return []
|
||||
|
||||
async def _save_feedback(self, feedback_data: Dict[str, Any]) -> bool:
|
||||
"""保存用户反馈"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO feedback (user_id, date, recommendation_id, user_choice,
|
||||
feedback_type, satisfaction_score, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
feedback_data['user_id'], feedback_data['date'],
|
||||
feedback_data.get('recommendation_id'), feedback_data['user_choice'],
|
||||
feedback_data['feedback_type'], feedback_data.get('satisfaction_score'),
|
||||
feedback_data.get('notes')
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存用户反馈失败: {e}")
|
||||
return False
|
||||
|
||||
async def _get_feedback(self, params: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""获取用户反馈"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
user_id = params.get('user_id')
|
||||
days = params.get('days', 7)
|
||||
|
||||
cursor.execute('''
|
||||
SELECT user_id, date, recommendation_id, user_choice, feedback_type,
|
||||
satisfaction_score, notes
|
||||
FROM feedback
|
||||
WHERE user_id = ?
|
||||
ORDER BY date DESC
|
||||
LIMIT ?
|
||||
''', (user_id, days))
|
||||
|
||||
results = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
feedbacks = []
|
||||
for row in results:
|
||||
feedback = {
|
||||
'user_id': row[0],
|
||||
'date': row[1],
|
||||
'recommendation_id': row[2],
|
||||
'user_choice': row[3],
|
||||
'feedback_type': row[4],
|
||||
'satisfaction_score': row[5],
|
||||
'notes': row[6]
|
||||
}
|
||||
feedbacks.append(feedback)
|
||||
|
||||
return feedbacks
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取用户反馈失败: {e}")
|
||||
return []
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""清理资源"""
|
||||
self._initialized = False
|
||||
return True
|
||||
|
||||
|
||||
class AIAnalyzer(BaseEngine):
|
||||
"""AI分析基座 - 统一的大模型分析接口"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any] = None):
|
||||
super().__init__(config)
|
||||
self.openai_client = None
|
||||
self.anthropic_client = None
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化AI客户端"""
|
||||
try:
|
||||
import openai
|
||||
import anthropic
|
||||
|
||||
self.openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
self.anthropic_client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
|
||||
|
||||
self._initialized = True
|
||||
self.logger.info("AI分析器初始化成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"AI分析器初始化失败: {e}")
|
||||
return False
|
||||
|
||||
async def process(self, analysis_type: str, data: Any) -> Any:
|
||||
"""处理AI分析请求"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
|
||||
analysis_types = {
|
||||
'user_intent': self._analyze_user_intent,
|
||||
'physiological_state': self._analyze_physiological_state,
|
||||
'nutrition_analysis': self._analyze_nutrition,
|
||||
'recommendation_reasoning': self._generate_reasoning
|
||||
}
|
||||
|
||||
if analysis_type in analysis_types:
|
||||
return await analysis_types[analysis_type](data)
|
||||
else:
|
||||
raise ValueError(f"不支持的分析类型: {analysis_type}")
|
||||
|
||||
async def _analyze_user_intent(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""分析用户意图"""
|
||||
user_input = data.get('user_input', '')
|
||||
context = data.get('context', {})
|
||||
|
||||
prompt = f"""
|
||||
请分析用户的真实意图和需求:
|
||||
|
||||
用户输入: "{user_input}"
|
||||
用户背景: {json.dumps(context, ensure_ascii=False)}
|
||||
|
||||
请分析:
|
||||
1. 真实意图
|
||||
2. 情绪状态
|
||||
3. 营养需求
|
||||
4. 推荐理由
|
||||
|
||||
返回JSON格式结果。
|
||||
"""
|
||||
|
||||
try:
|
||||
response = await self.openai_client.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=[
|
||||
{"role": "system", "content": "你是专业的营养师和心理分析师。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=500
|
||||
)
|
||||
|
||||
result_text = response.choices[0].message.content
|
||||
return self._parse_json_result(result_text)
|
||||
except Exception as e:
|
||||
self.logger.error(f"用户意图分析失败: {e}")
|
||||
return {"intent": "需要饮食建议", "confidence": 0.3}
|
||||
|
||||
async def _analyze_physiological_state(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""分析生理状态"""
|
||||
profile = data.get('profile', {})
|
||||
current_date = data.get('current_date', '')
|
||||
|
||||
if not profile.get('is_female', False):
|
||||
return {"state": "normal", "needs": []}
|
||||
|
||||
# 计算生理周期
|
||||
cycle_info = self._calculate_cycle_state(profile, current_date)
|
||||
|
||||
prompt = f"""
|
||||
作为女性健康专家,分析用户的生理状态:
|
||||
|
||||
用户信息: {json.dumps(profile, ensure_ascii=False)}
|
||||
当前日期: {current_date}
|
||||
生理周期: {json.dumps(cycle_info, ensure_ascii=False)}
|
||||
|
||||
请分析营养需求和饮食建议。
|
||||
"""
|
||||
|
||||
try:
|
||||
response = await self.openai_client.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=[
|
||||
{"role": "system", "content": "你是专业的女性健康专家。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.2,
|
||||
max_tokens=400
|
||||
)
|
||||
|
||||
result_text = response.choices[0].message.content
|
||||
result = self._parse_json_result(result_text)
|
||||
result['cycle_info'] = cycle_info
|
||||
return result
|
||||
except Exception as e:
|
||||
self.logger.error(f"生理状态分析失败: {e}")
|
||||
return {"state": "normal", "needs": [], "cycle_info": cycle_info}
|
||||
|
||||
async def _analyze_nutrition(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""营养分析"""
|
||||
foods = data.get('foods', [])
|
||||
quantities = data.get('quantities', [])
|
||||
|
||||
prompt = f"""
|
||||
请分析以下食物的营养成分:
|
||||
|
||||
食物: {', '.join(foods)}
|
||||
数量: {', '.join(quantities)}
|
||||
|
||||
请分析:
|
||||
1. 总热量
|
||||
2. 主要营养素
|
||||
3. 营养均衡性
|
||||
4. 改进建议
|
||||
|
||||
返回JSON格式结果。
|
||||
"""
|
||||
|
||||
try:
|
||||
response = await self.openai_client.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=[
|
||||
{"role": "system", "content": "你是专业的营养师。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.2,
|
||||
max_tokens=400
|
||||
)
|
||||
|
||||
result_text = response.choices[0].message.content
|
||||
return self._parse_json_result(result_text)
|
||||
except Exception as e:
|
||||
self.logger.error(f"营养分析失败: {e}")
|
||||
return {"calories": 0, "analysis": "分析失败"}
|
||||
|
||||
async def _generate_reasoning(self, data: Dict[str, Any]) -> str:
|
||||
"""生成推荐理由"""
|
||||
recommendations = data.get('recommendations', [])
|
||||
user_profile = data.get('user_profile', {})
|
||||
|
||||
prompt = f"""
|
||||
请为以下推荐生成个性化理由:
|
||||
|
||||
推荐食物: {', '.join(recommendations)}
|
||||
用户画像: {json.dumps(user_profile, ensure_ascii=False)}
|
||||
|
||||
请生成简洁明了的推荐理由。
|
||||
"""
|
||||
|
||||
try:
|
||||
response = await self.openai_client.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=[
|
||||
{"role": "system", "content": "你是专业的营养师。"},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=200
|
||||
)
|
||||
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
self.logger.error(f"生成推荐理由失败: {e}")
|
||||
return "基于您的个人偏好和营养需求推荐"
|
||||
|
||||
def _calculate_cycle_state(self, profile: Dict[str, Any], current_date: str) -> Dict[str, Any]:
|
||||
"""计算生理周期状态"""
|
||||
try:
|
||||
last_period = datetime.strptime(profile.get('last_period_date', ''), '%Y-%m-%d')
|
||||
current = datetime.strptime(current_date, '%Y-%m-%d')
|
||||
cycle_length = profile.get('menstrual_cycle_length', 28)
|
||||
|
||||
days_since_period = (current - last_period).days
|
||||
days_to_next_period = cycle_length - (days_since_period % cycle_length)
|
||||
|
||||
if days_since_period % cycle_length < 5:
|
||||
phase = "月经期"
|
||||
elif days_since_period % cycle_length < 14:
|
||||
phase = "卵泡期"
|
||||
elif days_since_period % cycle_length < 18:
|
||||
phase = "排卵期"
|
||||
else:
|
||||
phase = "黄体期"
|
||||
|
||||
return {
|
||||
"phase": phase,
|
||||
"days_since_period": days_since_period % cycle_length,
|
||||
"days_to_next_period": days_to_next_period,
|
||||
"is_ovulation": phase == "排卵期"
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
"phase": "未知",
|
||||
"days_since_period": 0,
|
||||
"days_to_next_period": 0,
|
||||
"is_ovulation": False
|
||||
}
|
||||
|
||||
def _parse_json_result(self, text: str) -> Dict[str, Any]:
|
||||
"""解析JSON结果"""
|
||||
try:
|
||||
start_idx = text.find('{')
|
||||
end_idx = text.rfind('}') + 1
|
||||
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
json_str = text[start_idx:end_idx]
|
||||
return json.loads(json_str)
|
||||
except Exception as e:
|
||||
self.logger.error(f"解析JSON结果失败: {e}")
|
||||
|
||||
return {}
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""清理资源"""
|
||||
self._initialized = False
|
||||
return True
|
||||
|
||||
|
||||
class RecommendationEngine(BaseEngine):
|
||||
"""推荐引擎基座 - 统一的推荐算法接口"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any] = None):
|
||||
super().__init__(config)
|
||||
self.data_manager = None
|
||||
self.ai_analyzer = None
|
||||
self.food_database = {}
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化推荐引擎"""
|
||||
try:
|
||||
# 初始化依赖组件
|
||||
self.data_manager = DataManager()
|
||||
await self.data_manager.initialize()
|
||||
|
||||
self.ai_analyzer = AIAnalyzer()
|
||||
await self.ai_analyzer.initialize()
|
||||
|
||||
# 加载食物数据库
|
||||
await self._load_food_database()
|
||||
|
||||
self._initialized = True
|
||||
self.logger.info("推荐引擎初始化成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"推荐引擎初始化失败: {e}")
|
||||
return False
|
||||
|
||||
async def process(self, request_type: str, data: Any) -> Any:
|
||||
"""处理推荐请求"""
|
||||
if not self._initialized:
|
||||
await self.initialize()
|
||||
|
||||
request_types = {
|
||||
'generate_recommendation': self._generate_recommendation,
|
||||
'update_model': self._update_model,
|
||||
'get_food_info': self._get_food_info
|
||||
}
|
||||
|
||||
if request_type in request_types:
|
||||
return await request_types[request_type](data)
|
||||
else:
|
||||
raise ValueError(f"不支持的请求类型: {request_type}")
|
||||
|
||||
async def _generate_recommendation(self, data: Dict[str, Any]) -> RecommendationResult:
|
||||
"""生成推荐"""
|
||||
user_id = data.get('user_id')
|
||||
user_input = data.get('user_input', '')
|
||||
current_date = data.get('current_date', datetime.now().strftime('%Y-%m-%d'))
|
||||
meal_type = data.get('meal_type', 'lunch')
|
||||
|
||||
# 获取用户数据
|
||||
user_profile = await self.data_manager.process('get_user', user_id)
|
||||
meal_history = await self.data_manager.process('get_meals', {'user_id': user_id, 'days': 5})
|
||||
|
||||
if not user_profile:
|
||||
raise ValueError(f"用户 {user_id} 不存在")
|
||||
|
||||
# AI分析用户意图
|
||||
intent_analysis = await self.ai_analyzer.process('user_intent', {
|
||||
'user_input': user_input,
|
||||
'context': asdict(user_profile)
|
||||
})
|
||||
|
||||
# AI分析生理状态
|
||||
physiological_analysis = await self.ai_analyzer.process('physiological_state', {
|
||||
'profile': asdict(user_profile),
|
||||
'current_date': current_date
|
||||
})
|
||||
|
||||
# 生成推荐食物
|
||||
recommended_foods = await self._select_foods(
|
||||
user_profile, intent_analysis, physiological_analysis
|
||||
)
|
||||
|
||||
# 生成推荐理由
|
||||
reasoning = await self.ai_analyzer.process('recommendation_reasoning', {
|
||||
'recommendations': recommended_foods,
|
||||
'user_profile': asdict(user_profile)
|
||||
})
|
||||
|
||||
# 创建推荐结果
|
||||
recommendation = RecommendationResult(
|
||||
user_id=user_id,
|
||||
date=current_date,
|
||||
meal_type=meal_type,
|
||||
recommended_foods=recommended_foods,
|
||||
reasoning=reasoning,
|
||||
confidence_score=intent_analysis.get('confidence', 0.5),
|
||||
special_considerations=physiological_analysis.get('considerations', [])
|
||||
)
|
||||
|
||||
# 保存推荐结果
|
||||
await self.data_manager.process('save_recommendation', recommendation)
|
||||
|
||||
return recommendation
|
||||
|
||||
async def _select_foods(self, user_profile: UserProfile,
|
||||
intent_analysis: Dict[str, Any],
|
||||
physiological_analysis: Dict[str, Any]) -> List[str]:
|
||||
"""选择推荐食物"""
|
||||
|
||||
# 基础食物池
|
||||
base_foods = [
|
||||
"米饭", "面条", "馒头", "包子", "饺子",
|
||||
"鸡蛋", "豆腐", "鱼肉", "鸡肉", "瘦肉",
|
||||
"青菜", "西红柿", "胡萝卜", "土豆", "西兰花",
|
||||
"苹果", "香蕉", "橙子", "葡萄", "草莓",
|
||||
"牛奶", "酸奶", "豆浆", "坚果", "红枣"
|
||||
]
|
||||
|
||||
# 根据用户偏好过滤
|
||||
filtered_foods = []
|
||||
for food in base_foods:
|
||||
if not any(dislike in food for dislike in user_profile.dislikes):
|
||||
if not any(allergy in food for allergy in user_profile.allergies):
|
||||
filtered_foods.append(food)
|
||||
|
||||
# 根据生理需求调整
|
||||
physiological_needs = physiological_analysis.get('needs', [])
|
||||
priority_foods = []
|
||||
|
||||
for need in physiological_needs:
|
||||
if need == "铁质":
|
||||
priority_foods.extend(["菠菜", "瘦肉", "红枣"])
|
||||
elif need == "蛋白质":
|
||||
priority_foods.extend(["鸡蛋", "豆腐", "鱼肉"])
|
||||
elif need == "维生素C":
|
||||
priority_foods.extend(["橙子", "柠檬", "西红柿"])
|
||||
|
||||
# 合并推荐
|
||||
recommended = list(set(priority_foods + filtered_foods))[:5]
|
||||
|
||||
return recommended
|
||||
|
||||
async def _update_model(self, data: Dict[str, Any]) -> bool:
|
||||
"""更新模型"""
|
||||
# 这里可以实现机器学习模型的更新逻辑
|
||||
user_id = data.get('user_id')
|
||||
feedback_data = await self.data_manager.process('get_feedback', {'user_id': user_id, 'days': 30})
|
||||
|
||||
# 基于反馈数据更新推荐策略
|
||||
self.logger.info(f"更新用户 {user_id} 的推荐模型")
|
||||
return True
|
||||
|
||||
async def _get_food_info(self, food_name: str) -> Dict[str, Any]:
|
||||
"""获取食物信息"""
|
||||
return self.food_database.get(food_name, {})
|
||||
|
||||
async def _load_food_database(self):
|
||||
"""加载食物数据库"""
|
||||
# 这里可以从文件或API加载食物营养信息
|
||||
self.food_database = {
|
||||
"米饭": {"calories": 130, "protein": 2.7, "carbs": 28},
|
||||
"鸡蛋": {"calories": 155, "protein": 13, "fat": 11},
|
||||
"豆腐": {"calories": 76, "protein": 8, "carbs": 2},
|
||||
# 更多食物数据...
|
||||
}
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""清理资源"""
|
||||
if self.data_manager:
|
||||
await self.data_manager.cleanup()
|
||||
if self.ai_analyzer:
|
||||
await self.ai_analyzer.cleanup()
|
||||
self._initialized = False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试基座架构
|
||||
async def test_base_engine():
|
||||
print("测试基座架构...")
|
||||
|
||||
# 测试数据管理器
|
||||
data_manager = DataManager()
|
||||
await data_manager.initialize()
|
||||
|
||||
# 创建测试用户
|
||||
test_user = UserProfile(
|
||||
user_id="test_001",
|
||||
name="测试用户",
|
||||
age=25,
|
||||
gender="女",
|
||||
height=165.0,
|
||||
weight=55.0,
|
||||
activity_level="moderate",
|
||||
taste_preferences={"sweet": 4, "salty": 3},
|
||||
is_female=True,
|
||||
zodiac_sign="天秤座"
|
||||
)
|
||||
|
||||
# 保存用户
|
||||
await data_manager.process('save_user', test_user)
|
||||
print("用户保存成功")
|
||||
|
||||
# 获取用户
|
||||
retrieved_user = await data_manager.process('get_user', "test_001")
|
||||
if retrieved_user:
|
||||
print(f"获取用户: {retrieved_user.name}")
|
||||
|
||||
# 测试推荐引擎
|
||||
rec_engine = RecommendationEngine()
|
||||
await rec_engine.initialize()
|
||||
|
||||
# 生成推荐
|
||||
recommendation = await rec_engine.process('generate_recommendation', {
|
||||
'user_id': 'test_001',
|
||||
'user_input': '我今天想吃点清淡的',
|
||||
'meal_type': 'lunch'
|
||||
})
|
||||
|
||||
print(f"推荐结果: {recommendation.recommended_foods}")
|
||||
print(f"推荐理由: {recommendation.reasoning}")
|
||||
|
||||
# 清理资源
|
||||
await data_manager.cleanup()
|
||||
await rec_engine.cleanup()
|
||||
print("测试完成")
|
||||
|
||||
# 运行测试
|
||||
asyncio.run(test_base_engine())
|
||||
Reference in New Issue
Block a user