Files
AIPortPilot/backend/app/routers/companies.py
T

395 lines
14 KiB
Python

"""企业档案路由: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="删除成功")