"""企业档案路由:CRUD + 列表分页。""" from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import func, 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.user import User from app.schemas.common import ApiResponse, success from app.schemas.company import ( CompanyCreate, CompanyDetailResponse, CompanyListResponse, CompanyResponse, CompanyUpdate, AgreementBrief, BoardMeetingBrief, HealthScoreBrief, HealthScoreHistoryPoint, MajorEventBrief, MilestoneBrief, ReportBrief, RiskBrief, SynergyBrief, TeamMemberBrief, WeakSignalBrief, ) from app.models.agreement import InvestmentAgreement from app.models.board import BoardMeeting from app.models.financial_data import FinancialData from app.models.health_score import HealthScore from app.models.major_event import MajorEvent from app.models.milestone import MilestoneTree from app.models.report import MonthlyReport from app.models.risk import RiskEvent from app.models.synergy import SynergyOpportunity from app.models.talent import TeamMember from app.models.weak_signal import WeakSignal router = APIRouter(prefix="/companies", tags=["companies"]) @router.get("", response_model=ApiResponse[CompanyListResponse]) async def list_companies( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), keyword: str | None = Query(default=None, description="按名称搜索"), industry: str | None = Query(default=None, description="按行业筛选"), stage: str | None = Query(default=None, description="按融资阶段筛选"), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """获取企业列表(分页 + 筛选)。""" query = select(Company).where(Company.tenant_id == user.tenant_id) if keyword: query = query.where(Company.name.ilike(f"%{keyword}%")) if industry: query = query.where(Company.industry == industry) if stage: query = query.where(Company.stage == stage) # 总数 count_query = select(func.count()).select_from(query.subquery()) total_result = await db.execute(count_query) total = total_result.scalar_one() # 分页 offset = (page - 1) * page_size query = query.order_by(Company.created_at.desc()).offset(offset).limit(page_size) result = await db.execute(query) companies = result.scalars().all() return success( data=CompanyListResponse( items=[CompanyResponse.model_validate(c, from_attributes=True) for c in companies], total=total, page=page, page_size=page_size, ) ) @router.get("/{company_id}", response_model=ApiResponse[CompanyResponse]) async def get_company( company_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """获取企业详情。""" result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id) ) company = result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") return success(data=CompanyResponse.model_validate(company, from_attributes=True)) @router.get("/{company_id}/detail", response_model=ApiResponse[CompanyDetailResponse]) async def get_company_detail( company_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """获取企业详情聚合数据 — 工作台使用。 聚合:企业基本信息 + 最新健康度 + 最近月报 + 未解决风险 + 弱信号 + 活跃协议 + 董事会会议 + 财务数据。 """ # 企业基本信息 result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id) ) company = result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") # 最新健康度评分 health_result = await db.execute( select(HealthScore) .where(HealthScore.company_id == company_id) .order_by(HealthScore.calculated_at.desc()) .limit(1) ) health = health_result.scalar_one_or_none() health_brief = HealthScoreBrief( total_score=health.total_score, financial_score=health.financial_score, operational_score=health.operational_score, ai_commercial_score=health.ai_commercial_score, ai_cost_score=health.ai_cost_score, org_talent_score=getattr(health, "org_talent_score", None), product_tech_score=getattr(health, "product_tech_score", None), market_compete_score=getattr(health, "market_compete_score", None), governance_score=getattr(health, "governance_score", None), financing_score=getattr(health, "financing_score", None), synergy_score=getattr(health, "synergy_score", None), ai_model_product_score=getattr(health, "ai_model_product_score", None), data_compliance_score=getattr(health, "data_compliance_score", None), team_tech_score=getattr(health, "team_tech_score", None), customer_success_score=getattr(health, "customer_success_score", None), trend=health.trend, calculated_at=health.calculated_at, ) if health else None # 最近 5 条月报 reports_result = await db.execute( select(MonthlyReport) .where(MonthlyReport.company_id == company_id) .order_by(MonthlyReport.period_year.desc(), MonthlyReport.period_month.desc()) .limit(5) ) reports = reports_result.scalars().all() report_briefs = [ ReportBrief( id=r.id, period_year=r.period_year, period_month=r.period_month, status=r.status, ai_summary=r.ai_summary, submitted_at=r.submitted_at, ) for r in reports ] # 未解决风险 risks_result = await db.execute( select(RiskEvent) .where(RiskEvent.company_id == company_id, RiskEvent.status.in_(["open", "assigned", "in_progress"])) .order_by(RiskEvent.identified_at.desc()) .limit(10) ) risks = risks_result.scalars().all() risk_briefs = [ RiskBrief( id=r.id, type=r.type, severity=r.severity, status=r.status, title=r.title, identified_at=r.identified_at, ) for r in risks ] # 最近弱信号 signals_result = await db.execute( select(WeakSignal) .where(WeakSignal.company_id == company_id) .order_by(WeakSignal.detected_at.desc()) .limit(10) ) signals = signals_result.scalars().all() signal_briefs = [ WeakSignalBrief( id=s.id, signal_type=s.signal_type, content=s.content, confidence=s.confidence, risk_probability=s.risk_probability, status=s.status, detected_at=s.detected_at, ) for s in signals ] # 活跃协议 agreements_result = await db.execute( select(InvestmentAgreement) .where(InvestmentAgreement.company_id == company_id, InvestmentAgreement.status == "active") .order_by(InvestmentAgreement.created_at.desc()) ) agreements = agreements_result.scalars().all() agreement_briefs = [ AgreementBrief(id=a.id, title=a.title, status=a.status, signed_at=a.signed_at) for a in agreements ] # 最近董事会会议 board_result = await db.execute( select(BoardMeeting) .where(BoardMeeting.company_id == company_id) .order_by(BoardMeeting.created_at.desc()) .limit(5) ) meetings = board_result.scalars().all() meeting_briefs = [ BoardMeetingBrief(id=m.id, title=m.title, status=m.status, meeting_at=m.meeting_at) for m in meetings ] # 财务数据统计 fin_count_result = await db.execute( select(func.count()).select_from( select(FinancialData).where(FinancialData.company_id == company_id).subquery() ) ) fin_count = fin_count_result.scalar_one() latest_fin_result = await db.execute( select(FinancialData) .where(FinancialData.company_id == company_id) .order_by(FinancialData.period_year.desc(), FinancialData.period_month.desc()) .limit(1) ) latest_fin = latest_fin_result.scalar_one_or_none() latest_financial = latest_fin.data_json if latest_fin else None # 健康度历史趋势(最近 12 条) history_result = await db.execute( select(HealthScore) .where(HealthScore.company_id == company_id) .order_by(HealthScore.calculated_at.asc()) .limit(12) ) history_scores = history_result.scalars().all() health_score_history = [ HealthScoreHistoryPoint( period=s.calculated_at.strftime("%Y-%m"), total_score=s.total_score, calculated_at=s.calculated_at, ) for s in history_scores ] # 重大事项(最近 10 条) events_result = await db.execute( select(MajorEvent) .where(MajorEvent.company_id == company_id) .order_by(MajorEvent.created_at.desc()) .limit(10) ) events = events_result.scalars().all() major_events = [ MajorEventBrief( id=e.id, event_type=e.event_type, title=e.title, description=e.description, severity=e.severity, status=e.status, occurred_at=e.occurred_at, ) for e in events ] # 里程碑 milestone_result = await db.execute( select(MilestoneTree) .where(MilestoneTree.company_id == company_id) .order_by(MilestoneTree.target_date.desc().nulls_last()) .limit(20) ) milestones = milestone_result.scalars().all() milestone_briefs = [ MilestoneBrief( id=m.id, name=m.name, status=m.status, is_current=m.is_current, target_date=m.target_date, actual_date=m.actual_date, description=m.description, ) for m in milestones ] # 团队成员 team_result = await db.execute( select(TeamMember) .where(TeamMember.company_id == company_id) .order_by(TeamMember.is_key_person.desc(), TeamMember.joined_at.desc()) .limit(20) ) team_members_data = team_result.scalars().all() team_members = [ TeamMemberBrief( id=t.id, name=t.name, role=t.role, is_key_person=t.is_key_person, stability_score=t.stability_score, joined_at=t.joined_at, ) for t in team_members_data ] # 协同机会(涉及该企业的) synergy_result = await db.execute( select(SynergyOpportunity) .where( (SynergyOpportunity.company_a_id == company_id) | (SynergyOpportunity.company_b_id == company_id) ) .order_by(SynergyOpportunity.created_at.desc()) .limit(10) ) synergies = synergy_result.scalars().all() synergy_briefs = [ SynergyBrief( id=s.id, type=s.type, title=s.title, description=s.description, status=s.status, match_reason=s.match_reason, ) for s in synergies ] return success(data=CompanyDetailResponse( company=CompanyResponse.model_validate(company, from_attributes=True), health_score=health_brief, health_score_history=health_score_history, recent_reports=report_briefs, open_risks=risk_briefs, recent_weak_signals=signal_briefs, active_agreements=agreement_briefs, recent_board_meetings=meeting_briefs, major_events=major_events, milestones=milestone_briefs, team_members=team_members, synergy_opportunities=synergy_briefs, financial_data_count=fin_count, latest_financial=latest_financial, )) @router.post("", response_model=ApiResponse[CompanyResponse], status_code=status.HTTP_201_CREATED) async def create_company( req: CompanyCreate, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """创建企业。""" company = Company(tenant_id=user.tenant_id, **req.model_dump()) db.add(company) await db.flush() return success( data=CompanyResponse.model_validate(company, from_attributes=True), message="创建成功", ) @router.put("/{company_id}", response_model=ApiResponse[CompanyResponse]) async def update_company( company_id: str, req: CompanyUpdate, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """更新企业信息。""" result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id) ) company = result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") update_data = req.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(company, key, value) await db.flush() return success( data=CompanyResponse.model_validate(company, from_attributes=True), message="更新成功", ) @router.delete("/{company_id}", response_model=ApiResponse[None]) async def delete_company( company_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """删除企业。""" result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id) ) company = result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") await db.delete(company) return success(message="删除成功")