Files
AIPortPilot/backend/app/routers/companies.py
T
selfrelease fad458b2a7 docs(uiux): UIUX 设计方案大改 + 5 份作业指导书对齐 + 开发任务文档
- UIUX 文档:填充 19 个缺口(多主体画像/健康度/AI+看板/增长域/洞察域/创始人端/OODA/助推/商密)
- UIUX 文档:插入 6 个新章节(十四~十九),旧章节重编号为二十~三十一,更新目录和交叉引用
- 作业指导书 x5:导航改为 6 域分组,新增 Context Bar/工作模式/Insight Rail/决策线程/多工作区等 UI 概念
- 新建 docs/2-task-uiux.md:50 个代码落地开发任务,按 P0-P6 分优先级 + 8 Sprint 规划
- 后端/前端:大量新增模型、路由、组件(来自之前 Phase 开发)
2026-07-19 11:53:38 +08:00

298 lines
10 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,
ReportBrief,
RiskBrief,
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.report import MonthlyReport
from app.models.risk import RiskEvent
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
return success(data=CompanyDetailResponse(
company=CompanyResponse.model_validate(company, from_attributes=True),
health_score=health_brief,
recent_reports=report_briefs,
open_risks=risk_briefs,
recent_weak_signals=signal_briefs,
active_agreements=agreement_briefs,
recent_board_meetings=meeting_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="删除成功")