from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import Optional, Any from app.agents.report_parse_agent import ReportParseAgent import logging import os import json logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1", tags=["report_parse"]) _agent = None def get_agent(): global _agent if _agent is None: _agent = ReportParseAgent() return _agent class ParseRequest(BaseModel): file_path: str family_id: Optional[int] = None user_id: Optional[int] = None class ParseResponse(BaseModel): code: int = 200 message: str = "ok" data: dict = {} @router.post("/report/parse", response_model=ParseResponse) async def parse_report(req: ParseRequest): if not os.path.exists(req.file_path): raise HTTPException(status_code=400, detail=f"文件不存在: {req.file_path}") logger.info("report_parse: file_path=%s family_id=%s user_id=%s", req.file_path, req.family_id, req.user_id) agent = get_agent() try: result = await agent.parse(req.file_path) return ParseResponse(data=result) except Exception as e: logger.error("报告解析失败: %s", e, exc_info=True) return ParseResponse(code=500, message=f"解析失败: {str(e)}", data={}) class GenericParseRequest(BaseModel): file_path: str extra_context: Optional[dict] = None class GenericParseResponse(BaseModel): code: int = 200 message: str = "ok" data: dict = {} @router.post("/report/parse-generic", response_model=GenericParseResponse) async def parse_report_generic(req: GenericParseRequest): """通用报告 LLM 兜底解析。 适用于指纹检测未匹配的未知类型报告。 直接交给 LLM 提取结构化数据,不经过算法预解析。 """ if not os.path.exists(req.file_path): raise HTTPException(status_code=400, detail=f"文件不存在: {req.file_path}") logger.info("report_parse_generic: file_path=%s", req.file_path) agent = get_agent() try: result = await agent.parse_generic(req.file_path, req.extra_context) return GenericParseResponse(data=result) except Exception as e: logger.error("通用报告解析失败: %s", e, exc_info=True) return GenericParseResponse(code=500, message=f"解析失败: {str(e)}", data={}) class TypedParseRequest(BaseModel): file_path: str report_type: str = "auto" extra_context: Optional[dict] = None class TypedParseResponse(BaseModel): code: int = 200 message: str = "ok" data: dict = {} @router.post("/report/parse-typed", response_model=TypedParseResponse) async def parse_report_typed(req: TypedParseRequest): """按报告类型解析。 支持的报告类型: - auto: 自动检测(默认) - brain_status: 脑状态测量报告 - cognitive_aptitude: 先天智力潜能/皮纹学测评报告 - scanned_image: 扫描图片 PDF(无文字层,需多模态 LLM) - generic: 通用 LLM 解析 - gut_flora: 肠道菌群报告(算法解析 + LLM 兜底) """ if not os.path.exists(req.file_path): raise HTTPException(status_code=400, detail=f"文件不存在: {req.file_path}") logger.info("report_parse_typed: file_path=%s type=%s", req.file_path, req.report_type) agent = get_agent() try: result = await agent.parse_by_type(req.file_path, req.report_type, req.extra_context) return TypedParseResponse(data=result) except Exception as e: logger.error("类型报告解析失败: %s", e, exc_info=True) return TypedParseResponse(code=500, message=f"解析失败: {str(e)}", data={}) class TypedParseRequest2(BaseModel): file_path: str @router.post("/report/detect-type", response_model=TypedParseResponse) async def detect_report_type(req: TypedParseRequest2): """检测报告类型,返回检测结果(不执行解析)。""" if not os.path.exists(req.file_path): raise HTTPException(status_code=400, detail=f"文件不存在: {req.file_path}") agent = get_agent() try: detected = agent._detect_report_type(req.file_path) return TypedParseResponse(data={"detectedType": detected}) except Exception as e: logger.error("报告类型检测失败: %s", e, exc_info=True) return TypedParseResponse(code=500, message=f"检测失败: {str(e)}", data={})