"""投后报告生成 + PDF 导出路由。""" import json import logging from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import StreamingResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.dependencies import get_current_user from app.models.company import Company from app.models.health_score import HealthScore from app.models.report import MonthlyReport from app.models.risk import RiskEvent from app.models.user import User from app.schemas.common import ApiResponse, success logger = logging.getLogger(__name__) router = APIRouter(prefix="/reports-export", tags=["reports-export"]) @router.get("/{company_id}/summary") async def get_company_summary( company_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """获取企业投后报告汇总数据(用于前端展示或导出)。""" # 验证企业 company_result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id) ) company = company_result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") # 获取最新月报 reports_result = await db.execute( select(MonthlyReport) .where(MonthlyReport.company_id == company_id) .order_by(MonthlyReport.period_year.desc(), MonthlyReport.period_month.desc()) .limit(12) ) reports = reports_result.scalars().all() # 获取最新健康度评分 score_result = await db.execute( select(HealthScore) .where(HealthScore.company_id == company_id) .order_by(HealthScore.calculated_at.desc()) .limit(12) ) scores = score_result.scalars().all() # 获取风险事件 risk_result = await db.execute( select(RiskEvent) .where(RiskEvent.company_id == company_id) .order_by(RiskEvent.identified_at.desc()) .limit(20) ) risks = risk_result.scalars().all() summary = { "company": { "name": company.name, "industry": company.industry, "stage": company.stage, "description": company.description, "website": company.website, }, "latest_score": { "total_score": scores[0].total_score if scores else None, "financial_score": scores[0].financial_score if scores else None, "operational_score": scores[0].operational_score if scores else None, "ai_commercial_score": scores[0].ai_commercial_score if scores else None, "ai_cost_score": scores[0].ai_cost_score if scores else None, "trend": scores[0].trend if scores else None, } if scores else None, "score_history": [ { "total_score": s.total_score, "calculated_at": s.calculated_at.isoformat(), "trend": s.trend, } for s in scores ], "recent_reports": [ { "period": f"{r.period_year}-{r.period_month:02d}", "status": r.status, "ai_summary": r.ai_summary, } for r in reports ], "risks": [ { "title": r.title, "severity": r.severity, "status": r.status, "type": r.type, "description": r.description, } for r in risks ], "generated_at": datetime.now(timezone.utc).isoformat(), } return success(data=summary) @router.get("/{company_id}/pdf") async def export_pdf( company_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """导出企业投后报告为 JSON 格式(前端可转 PDF)。 当前返回 JSON 格式的完整报告数据,前端可使用浏览器打印功能生成 PDF。 后续可集成 weasyprint 等库生成服务端 PDF。 """ # 复用 summary 逻辑 company_result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id) ) company = company_result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") # 获取数据(与 summary 相同逻辑) reports_result = await db.execute( select(MonthlyReport) .where(MonthlyReport.company_id == company_id) .order_by(MonthlyReport.period_year.desc(), MonthlyReport.period_month.desc()) .limit(12) ) reports = reports_result.scalars().all() score_result = await db.execute( select(HealthScore) .where(HealthScore.company_id == company_id) .order_by(HealthScore.calculated_at.desc()) .limit(12) ) scores = score_result.scalars().all() risk_result = await db.execute( select(RiskEvent) .where(RiskEvent.company_id == company_id) .order_by(RiskEvent.identified_at.desc()) .limit(20) ) risks = risk_result.scalars().all() report_data = { "title": f"投后管理报告 — {company.name}", "generated_at": datetime.now(timezone.utc).isoformat(), "company": { "name": company.name, "industry": company.industry, "stage": company.stage, "description": company.description, }, "health_scores": [ { "total": s.total_score, "financial": s.financial_score, "operational": s.operational_score, "ai_commercial": s.ai_commercial_score, "ai_cost": s.ai_cost_score, "trend": s.trend, "date": s.calculated_at.isoformat(), } for s in scores ], "monthly_reports": [ { "period": f"{r.period_year}年{r.period_month}月", "status": r.status, "summary": r.ai_summary, } for r in reports ], "risk_events": [ { "title": r.title, "severity": r.severity, "status": r.status, "description": r.description, "suggested_action": r.suggested_action, } for r in risks ], } # 返回可下载的 JSON 文件 json_str = json.dumps(report_data, ensure_ascii=False, indent=2) from urllib.parse import quote safe_name = quote(company.name) return StreamingResponse( iter([json_str.encode("utf-8")]), media_type="application/json", headers={ "Content-Disposition": f'attachment; filename="report_{safe_name}_{datetime.now().strftime("%Y%m%d")}.json"', }, )