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 开发)
This commit is contained in:
selfrelease
2026-07-19 11:53:38 +08:00
parent 734a16a7f3
commit fad458b2a7
243 changed files with 19898 additions and 658 deletions
+48
View File
@@ -0,0 +1,48 @@
"""AAR 路由。"""
from fastapi import APIRouter, Depends, Query
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.aar import AARRecord
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.aar_agent import generate_aar
router = APIRouter(prefix="/aars", tags=["aars"])
@router.get("", response_model=ApiResponse[list])
async def list_aars(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取 AAR 复盘列表。"""
query = select(AARRecord)
if company_id:
query = query.where(AARRecord.company_id == company_id)
result = await db.execute(query.order_by(AARRecord.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"trigger_event": i.trigger_event,
"original_plan": i.original_plan,
"actual_result": i.actual_result,
"gap_analysis": i.gap_analysis,
"lessons": i.lessons,
"improvements": i.improvements,
}
for i in items
])
@router.post("/generate", response_model=ApiResponse[dict])
async def generate_aar_report(req: dict, user: User = Depends(get_current_user)):
"""AI 生成五问复盘。"""
result = await generate_aar(
req.get("trigger_event", ""),
req.get("original_plan", ""),
req.get("actual_result", ""),
)
return success(data=result)
+88
View File
@@ -0,0 +1,88 @@
"""Admin 管理后台路由 — 租户 CRUD + 用户管理 + 审计日志。"""
from fastapi import APIRouter, Depends, Query, status
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, require_role
from app.models.audit import AuditLog
from app.models.tenant import Tenant
from app.models.user import User
from app.schemas.common import ApiResponse, success
router = APIRouter(prefix="/admin", tags=["admin"])
@router.get("/overview", response_model=ApiResponse[dict])
async def admin_overview(
db: AsyncSession = Depends(get_db),
user: User = Depends(require_role("admin")),
):
"""系统概览。"""
tenants_result = await db.execute(select(Tenant))
tenants = tenants_result.scalars().all()
users_result = await db.execute(select(User))
users = users_result.scalars().all()
return success(data={
"tenant_count": len(tenants),
"user_count": len(users),
"tenants": [{"id": str(t.id), "name": t.name} for t in tenants],
})
@router.get("/tenants", response_model=ApiResponse[list])
async def list_tenants(
db: AsyncSession = Depends(get_db),
user: User = Depends(require_role("admin")),
):
"""租户管理列表。"""
result = await db.execute(select(Tenant))
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "name": i.name, "created_at": i.created_at.isoformat() if i.created_at else None}
for i in items
])
@router.get("/users", response_model=ApiResponse[list])
async def list_users(
db: AsyncSession = Depends(get_db),
user: User = Depends(require_role("admin")),
):
"""用户管理列表。"""
result = await db.execute(select(User))
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "email": i.email, "name": i.name, "role": i.role, "tenant_id": str(i.tenant_id), "is_active": i.is_active}
for i in items
])
@router.get("/audit-logs", response_model=ApiResponse[list])
async def list_audit_logs(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
user: User = Depends(require_role("admin")),
):
"""审计日志查看。"""
offset = (page - 1) * page_size
result = await db.execute(
select(AuditLog).order_by(AuditLog.created_at.desc()).offset(offset).limit(page_size)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"user_id": str(i.user_id) if i.user_id else None,
"action": i.action,
"target_type": i.resource_type,
"target_id": i.resource_id,
"detail": i.detail_json,
"created_at": i.created_at.isoformat() if i.created_at else None,
}
for i in items
])
+24
View File
@@ -0,0 +1,24 @@
"""高级分析路由 — 约束点 + BML + 鸿沟诊断。"""
from fastapi import APIRouter, Depends
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.constraint_analyzer import identify_constraints
from app.services.chasm_diagnostic import diagnose_chasm
router = APIRouter(prefix="/advanced-analysis", tags=["advanced-analysis"])
@router.post("/constraints", response_model=ApiResponse[dict])
async def analyze_constraints(req: dict, user: User = Depends(get_current_user)):
"""TOC 约束点识别。"""
result = await identify_constraints(req.get("company_data", ""))
return success(data=result)
@router.post("/chasm", response_model=ApiResponse[dict])
async def analyze_chasm(req: dict, user: User = Depends(get_current_user)):
"""鸿沟诊断。"""
result = await diagnose_chasm(req.get("company_data", ""))
return success(data=result)
+80
View File
@@ -0,0 +1,80 @@
"""Agent 执行记录路由。"""
from fastapi import APIRouter, Depends, Query
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.agent_execution import AgentExecution
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.agent_orchestrator import orchestrate_agent
router = APIRouter(prefix="/agent-executions", tags=["agent-executions"])
@router.get("", response_model=ApiResponse[list])
async def list_executions(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取 Agent 执行记录列表。"""
offset = (page - 1) * page_size
result = await db.execute(
select(AgentExecution)
.where(AgentExecution.tenant_id == user.tenant_id)
.order_by(AgentExecution.created_at.desc())
.offset(offset)
.limit(page_size)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"agent_name": i.agent_name,
"autonomy_level": i.autonomy_level,
"input_summary": i.input_summary,
"output_summary": i.output_summary,
"review_status": i.review_status,
"reviewer_id": i.reviewer_id,
"duration_ms": i.duration_ms,
}
for i in items
])
@router.post("/orchestrate", response_model=ApiResponse[dict])
async def orchestrate(req: dict, user: User = Depends(get_current_user)):
"""编排 Agent 执行。"""
result = await orchestrate_agent(
req.get("agent_name", ""),
req.get("autonomy_level", "L1"),
req.get("input_data", {}),
)
return success(data=result)
@router.put("/{execution_id}/review", response_model=ApiResponse[dict])
async def review_execution(
execution_id: str,
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""审核 Agent 执行。"""
result = await db.execute(
select(AgentExecution).where(AgentExecution.id == execution_id, AgentExecution.tenant_id == user.tenant_id)
)
execution = result.scalar_one_or_none()
if not execution:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="执行记录不存在")
execution.review_status = req.get("review_status", "approved")
execution.reviewer_id = str(user.id)
from datetime import datetime, timezone
execution.reviewed_at = datetime.now(timezone.utc)
await db.flush()
return success(data={"id": str(execution.id), "review_status": execution.review_status}, message="审核完成")
+91
View File
@@ -0,0 +1,91 @@
"""投资协议路由:CRUD + 条款预警。"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
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.agreement import InvestmentAgreement
from app.models.company import Company
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.agreement_monitor import check_clause_triggers
router = APIRouter(prefix="/agreements", tags=["agreements"])
@router.get("", response_model=ApiResponse[list])
async def list_agreements(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取投资协议列表。"""
query = (
select(InvestmentAgreement)
.join(Company, InvestmentAgreement.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(InvestmentAgreement.company_id == company_id)
result = await db.execute(query.order_by(InvestmentAgreement.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"title": i.title,
"signed_at": i.signed_at.isoformat() if i.signed_at else None,
"key_clauses": i.key_clauses,
"monitoring_rules": i.monitoring_rules,
"status": i.status,
}
for i in items
])
@router.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_agreement(
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""创建投资协议。"""
company_result = await db.execute(
select(Company).where(Company.id == req.get("company_id"), Company.tenant_id == user.tenant_id)
)
if not company_result.scalar_one_or_none():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
agreement = InvestmentAgreement(
company_id=req.get("company_id"),
title=req.get("title"),
signed_at=req.get("signed_at"),
file_url=req.get("file_url"),
key_clauses=req.get("key_clauses"),
monitoring_rules=req.get("monitoring_rules"),
)
db.add(agreement)
await db.flush()
return success(data={"id": str(agreement.id)}, message="创建成功")
@router.get("/{agreement_id}/alerts", response_model=ApiResponse[list])
async def get_clause_alerts(
agreement_id: str,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取协议条款预警。"""
result = await db.execute(
select(InvestmentAgreement)
.join(Company, InvestmentAgreement.company_id == Company.id)
.where(InvestmentAgreement.id == agreement_id, Company.tenant_id == user.tenant_id)
)
agreement = result.scalar_one_or_none()
if not agreement:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="协议不存在")
alerts = await check_clause_triggers(db, str(agreement.company_id))
return success(data=alerts)
+66
View File
@@ -0,0 +1,66 @@
"""Alpha 归因路由。"""
from fastapi import APIRouter, Depends, Query
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.intervention import InterventionEvent, InterventionResult
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.alpha_attribution import attribute_alpha
router = APIRouter(prefix="/alpha", tags=["alpha"])
@router.get("", response_model=ApiResponse[list])
async def list_interventions(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取干预事件列表。"""
query = (
select(InterventionEvent)
.join(Company, InterventionEvent.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(InterventionEvent.company_id == company_id)
result = await db.execute(query.order_by(InterventionEvent.executed_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"intervention_type": i.intervention_type,
"title": i.title,
"description": i.description,
"executed_at": i.executed_at.isoformat(),
}
for i in items
])
@router.post("", response_model=ApiResponse[dict])
async def create_intervention(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""记录干预事件。"""
event = InterventionEvent(
company_id=req.get("company_id"),
intervention_type=req.get("intervention_type"),
title=req.get("title"),
description=req.get("description"),
executed_by=str(user.id),
)
db.add(event)
await db.flush()
return success(data={"id": str(event.id)}, message="创建成功")
@router.post("/{intervention_id}/attribute", response_model=ApiResponse[dict])
async def attribute(intervention_id: str, req: dict, user: User = Depends(get_current_user)):
"""AI Alpha 归因分析。"""
result = await attribute_alpha(req.get("intervention", {}), req.get("metric_changes", {}))
return success(data=result)
+96
View File
@@ -0,0 +1,96 @@
"""董事会路由:CRUD + 决议追踪。"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
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.board import BoardMeeting
from app.models.company import Company
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.board_agent import generate_meeting_summary, generate_questions
router = APIRouter(prefix="/board", tags=["board"])
@router.get("", response_model=ApiResponse[list])
async def list_board_meetings(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取董事会会议列表。"""
query = (
select(BoardMeeting)
.join(Company, BoardMeeting.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(BoardMeeting.company_id == company_id)
result = await db.execute(query.order_by(BoardMeeting.meeting_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"title": i.title,
"meeting_at": i.meeting_at.isoformat() if i.meeting_at else None,
"status": i.status,
"agenda": i.agenda,
"materials_summary": i.materials_summary,
"minutes": i.minutes,
"resolutions": i.resolutions,
"questions": i.questions,
}
for i in items
])
@router.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_board_meeting(
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""创建董事会会议。"""
company_result = await db.execute(
select(Company).where(Company.id == req.get("company_id"), Company.tenant_id == user.tenant_id)
)
if not company_result.scalar_one_or_none():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
meeting = BoardMeeting(
company_id=req.get("company_id"),
title=req.get("title"),
meeting_at=req.get("meeting_at"),
agenda=req.get("agenda"),
)
db.add(meeting)
await db.flush()
return success(data={"id": str(meeting.id)}, message="创建成功")
@router.post("/{meeting_id}/generate-summary", response_model=ApiResponse[str])
async def generate_meeting_summary_endpoint(
meeting_id: str,
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""AI 生成会前材料摘要。"""
summary = await generate_meeting_summary(req.get("materials_text", ""))
return success(data=summary)
@router.post("/{meeting_id}/generate-questions", response_model=ApiResponse[list])
async def generate_questions_endpoint(
meeting_id: str,
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""AI 生成提问清单。"""
questions = await generate_questions(req.get("materials_text", ""))
return success(data=questions)
+161
View File
@@ -11,10 +11,24 @@ 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"])
@@ -76,6 +90,153 @@ async def get_company(
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,
+43
View File
@@ -0,0 +1,43 @@
"""客户增长路由。"""
from fastapi import APIRouter, Depends, Query, status
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.customer_plan import CustomerAcquisitionPlan
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.customer_growth_agent import generate_customer_plan
router = APIRouter(prefix="/customer-plans", tags=["customer-plans"])
@router.get("", response_model=ApiResponse[list])
async def list_plans(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取客户获取方案列表。"""
query = select(CustomerAcquisitionPlan)
if company_id:
query = query.where(CustomerAcquisitionPlan.company_id == company_id)
result = await db.execute(query.order_by(CustomerAcquisitionPlan.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"target_customer": i.target_customer,
"entry_angle": i.entry_angle,
"pricing_strategy": i.pricing_strategy,
"execution_status": i.execution_status,
"result": i.result,
}
for i in items
])
@router.post("/generate", response_model=ApiResponse[dict])
async def generate_plan(req: dict, user: User = Depends(get_current_user)):
"""AI 生成客户获取方案。"""
result = await generate_customer_plan(req.get("company_context", ""), req.get("lp_resources", ""))
return success(data=result)
+35
View File
@@ -0,0 +1,35 @@
"""客户成功运营路由 — QBR + Expansion + Churn Risk。"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.qbr_generator import generate_qbr
from app.services.expansion_play import identify_expansion_opportunities
from app.services.churn_risk_detector import detect_churn_risk
router = APIRouter(prefix="/customer-success", tags=["customer-success"])
@router.post("/qbr", response_model=ApiResponse[dict])
async def generate_qbr_report(req: dict, user: User = Depends(get_current_user)):
"""自动生成 QBR 季度业务回顾。"""
result = await generate_qbr(req.get("company_id", ""), req.get("quarter_data", ""))
return success(data=result)
@router.post("/expansion", response_model=ApiResponse[list])
async def identify_expansion(req: dict, user: User = Depends(get_current_user)):
"""识别扩展机会。"""
result = await identify_expansion_opportunities(req.get("company_data", ""))
return success(data=result)
@router.get("/churn-risk", response_model=ApiResponse[list])
async def get_churn_risk(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取流失风险预警。"""
result = await detect_churn_risk(db, user.tenant_id)
return success(data=result)
+146
View File
@@ -13,9 +13,18 @@ from app.models.risk import RiskEvent
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.schemas.health_score import DashboardSummary, HealthScoreResponse
from app.services.predictor import predict_trend, detect_anomalies
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
# 14 维度 key 列表
_DIMENSION_KEYS = [
"financial_score", "operational_score", "ai_commercial_score", "ai_cost_score",
"org_talent_score", "product_tech_score", "market_compete_score", "governance_score",
"financing_score", "synergy_score", "ai_model_product_score",
"data_compliance_score", "team_tech_score", "customer_success_score",
]
@router.get("/summary", response_model=ApiResponse[DashboardSummary])
async def get_dashboard_summary(
@@ -105,3 +114,140 @@ async def list_health_scores(
for s in result.scalars().all()
]
return success(data=scores)
@router.get("/heatmap", response_model=ApiResponse[list[dict]])
async def get_health_heatmap(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取健康度热力图数据 — 企业 × 维度评分矩阵。
返回格式:[{ company_id, company_name, scores: { dimension: score } }]
"""
# 获取租户下所有企业
companies_result = await db.execute(
select(Company).where(Company.tenant_id == user.tenant_id).order_by(Company.name)
)
companies = companies_result.scalars().all()
# 获取每个企业最新评分
heatmap = []
for company in companies:
score_result = await db.execute(
select(HealthScore)
.where(HealthScore.company_id == company.id)
.order_by(HealthScore.calculated_at.desc())
.limit(1)
)
score = score_result.scalar_one_or_none()
scores_dict = {}
if score:
for dim_key in _DIMENSION_KEYS:
val = getattr(score, dim_key, None)
if val is not None:
scores_dict[dim_key] = val
heatmap.append({
"company_id": company.id,
"company_name": company.name,
"total_score": score.total_score if score else None,
"scores": scores_dict,
})
return success(data=heatmap)
@router.get("/trends", response_model=ApiResponse[list[dict]])
async def get_health_trends(
company_id: str | None = Query(default=None, description="指定企业 ID,不传则汇总"),
months: int = Query(default=6, ge=1, le=24, description="趋势月数"),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取健康度趋势对比数据 — 按月汇总评分变化。
返回格式:[{ period, avg_score, company_count, dimension_avgs: { dimension: avg } }]
"""
query = (
select(HealthScore)
.join(Company, HealthScore.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(HealthScore.company_id == company_id)
query = query.order_by(HealthScore.calculated_at.desc()).limit(months * 50)
result = await db.execute(query)
scores = result.scalars().all()
# 按月分组
monthly: dict[str, list[HealthScore]] = {}
for s in scores:
period = s.calculated_at.strftime("%Y-%m")
monthly.setdefault(period, []).append(s)
trends = []
for period in sorted(monthly.keys()):
month_scores = monthly[period]
count = len(month_scores)
avg_total = sum(s.total_score for s in month_scores) / count if count else 0
dim_avgs = {}
for dim_key in _DIMENSION_KEYS:
vals = [getattr(s, dim_key) for s in month_scores if getattr(s, dim_key) is not None]
if vals:
dim_avgs[dim_key] = round(sum(vals) / len(vals), 1)
trends.append({
"period": period,
"avg_score": round(avg_total, 1),
"company_count": count,
"dimension_avgs": dim_avgs,
})
return success(data=trends)
@router.get("/forecasts", response_model=ApiResponse[dict])
async def get_health_forecasts(
company_id: str | None = Query(default=None, description="指定企业 ID,不传则汇总全租户"),
months_ahead: int = Query(default=3, ge=1, le=6, description="预测月数"),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取健康度预测数据 — 基于历史评分预测未来趋势 + 异常检测。
返回格式:{ predictions: [...], trend_direction, confidence, anomalies: [...] }
"""
query = (
select(HealthScore)
.join(Company, HealthScore.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(HealthScore.company_id == company_id)
query = query.order_by(HealthScore.calculated_at.asc()).limit(24)
result = await db.execute(query)
scores = result.scalars().all()
historical = [s.total_score for s in scores]
forecast = predict_trend(historical, months_ahead)
# 异常检测 — 各维度
anomalies_by_dim: dict[str, list[int]] = {}
for dim_key in _DIMENSION_KEYS:
dim_values = [getattr(s, dim_key) for s in scores if getattr(s, dim_key) is not None]
if len(dim_values) >= 3:
dim_anomalies = detect_anomalies(dim_values)
if dim_anomalies:
anomalies_by_dim[dim_key] = dim_anomalies
return success(data={
"predictions": forecast.get("predicted", []),
"slope": forecast.get("slope", 0),
"confidence": forecast.get("confidence", 0),
"trend_direction": "up" if forecast.get("slope", 0) > 1 else "down" if forecast.get("slope", 0) < -1 else "stable",
"anomalies": anomalies_by_dim,
"historical_count": len(historical),
})
+54
View File
@@ -0,0 +1,54 @@
"""数据源管理路由。"""
from fastapi import APIRouter, Depends, Query
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.data_source import DataSource
from app.models.user import User
from app.schemas.common import ApiResponse, success
router = APIRouter(prefix="/data-sources", tags=["data-sources"])
@router.get("", response_model=ApiResponse[list])
async def list_data_sources(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取数据源列表。"""
query = select(DataSource).where(DataSource.tenant_id == user.tenant_id)
if company_id:
query = query.where(DataSource.company_id == company_id)
result = await db.execute(query)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id) if i.company_id else None,
"source_type": i.source_type,
"name": i.name,
"status": i.status,
"last_synced_at": i.last_synced_at.isoformat() if i.last_synced_at else None,
}
for i in items
])
@router.post("", response_model=ApiResponse[dict])
async def create_data_source(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""配置数据源。"""
ds = DataSource(
tenant_id=user.tenant_id,
company_id=req.get("company_id"),
source_type=req.get("source_type"),
name=req.get("name"),
api_endpoint=req.get("api_endpoint"),
config=req.get("config"),
)
db.add(ds)
await db.flush()
return success(data={"id": str(ds.id)}, message="创建成功")
+68
View File
@@ -0,0 +1,68 @@
"""决策前哨路由:CRUD + 场景分析查询。"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
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.decision_sentinel import DecisionSentinel
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.decision_sentinel_agent import analyze_scenarios, identify_decision_points
router = APIRouter(prefix="/decision-sentinels", tags=["decision-sentinels"])
@router.get("", response_model=ApiResponse[list])
async def list_sentinels(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取决策前哨列表。"""
query = (
select(DecisionSentinel)
.join(Company, DecisionSentinel.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(DecisionSentinel.company_id == company_id)
result = await db.execute(query.order_by(DecisionSentinel.identified_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"decision_type": i.decision_type,
"title": i.title,
"description": i.description,
"signals": i.signals,
"scenarios": i.scenarios,
"status": i.status,
"identified_at": i.identified_at.isoformat(),
}
for i in items
])
@router.post("/identify", response_model=ApiResponse[list])
async def identify_sentinels(
req: dict,
user: User = Depends(get_current_user),
):
"""AI 识别决策岔路口。"""
points = await identify_decision_points(req.get("company_context", ""))
return success(data=points)
@router.post("/{sentinel_id}/analyze", response_model=ApiResponse[dict])
async def analyze_sentinel_scenarios(
sentinel_id: str,
req: dict,
user: User = Depends(get_current_user),
):
"""AI 生成场景分析。"""
scenarios = await analyze_scenarios(req.get("decision", {}))
return success(data=scenarios)
+47
View File
@@ -0,0 +1,47 @@
"""数字孪生路由。"""
from fastapi import APIRouter, Depends, Query
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.digital_twin import DigitalTwinModel
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.digital_twin_engine import build_twin_model, simulate_scenario
router = APIRouter(prefix="/digital-twins", tags=["digital-twins"])
@router.get("", response_model=ApiResponse[list])
async def list_twins(company_id: str = Query(...), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取数字孪生模型列表。"""
result = await db.execute(
select(DigitalTwinModel).where(DigitalTwinModel.company_id == company_id)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"model_params": i.model_params,
"scenarios": i.scenarios,
"accuracy_score": i.accuracy_score,
}
for i in items
])
@router.post("/build", response_model=ApiResponse[dict])
async def build_twin(req: dict, user: User = Depends(get_current_user)):
"""构建数字孪生模型。"""
result = await build_twin_model(req.get("company_data", ""))
return success(data=result)
@router.post("/simulate", response_model=ApiResponse[dict])
async def simulate(req: dict, user: User = Depends(get_current_user)):
"""模拟决策场景。"""
result = await simulate_scenario(req.get("model_params", {}), req.get("scenario", ""))
return success(data=result)
+106
View File
@@ -0,0 +1,106 @@
"""重大事项 + 追问清单路由。"""
from fastapi import APIRouter, Depends, Query, status
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.inquiry import InquiryList
from app.models.major_event import MajorEvent
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.event_detector import detect_major_events
from app.services.inquiry_generator import generate_inquiry_questions
router = APIRouter(prefix="/events", tags=["events"])
@router.get("", response_model=ApiResponse[list])
async def list_events(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取重大事项列表。"""
query = (
select(MajorEvent)
.join(Company, MajorEvent.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(MajorEvent.company_id == company_id)
result = await db.execute(query.order_by(MajorEvent.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"event_type": i.event_type,
"title": i.title,
"description": i.description,
"severity": i.severity,
"source": i.source,
"evidence": i.evidence,
"status": i.status,
"occurred_at": i.occurred_at.isoformat() if i.occurred_at else None,
}
for i in items
])
@router.post("/detect", response_model=ApiResponse[list])
async def detect_events(
req: dict,
user: User = Depends(get_current_user),
):
"""AI 从月报中识别重大事项。"""
events = await detect_major_events(req.get("report_content", ""))
return success(data=events)
router_inquiries = APIRouter(prefix="/inquiries", tags=["inquiries"])
@router_inquiries.get("", response_model=ApiResponse[list])
async def list_inquiries(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取追问清单列表。"""
query = (
select(InquiryList)
.join(Company, InquiryList.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(InquiryList.company_id == company_id)
result = await db.execute(query.order_by(InquiryList.sent_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"report_id": i.report_id,
"questions": i.questions,
"status": i.status,
"sent_at": i.sent_at.isoformat(),
"answered_at": i.answered_at.isoformat() if i.answered_at else None,
}
for i in items
])
@router_inquiries.post("/generate", response_model=ApiResponse[list])
async def generate_inquiries(
req: dict,
user: User = Depends(get_current_user),
):
"""AI 生成追问清单。"""
questions = await generate_inquiry_questions(
req.get("report_content", ""),
req.get("structured_data"),
)
return success(data=questions)
+54
View File
@@ -0,0 +1,54 @@
"""退出预测路由。"""
from fastapi import APIRouter, Depends, Query
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.exit_prediction import ExitPrediction
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.exit_predictor import predict_exit
router = APIRouter(prefix="/exit-predictions", tags=["exit-predictions"])
@router.get("", response_model=ApiResponse[list])
async def list_exit_predictions(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取退出预测列表。"""
query = (
select(ExitPrediction)
.join(Company, ExitPrediction.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(ExitPrediction.company_id == company_id)
result = await db.execute(query.order_by(ExitPrediction.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"exit_path": i.exit_path,
"timing_window": i.timing_window,
"expected_return": i.expected_return,
"hold_return": i.hold_return,
"confidence": i.confidence,
"signals": i.signals,
"recommendation": i.recommendation,
}
for i in items
])
@router.post("/predict", response_model=ApiResponse[dict])
async def predict(req: dict, user: User = Depends(get_current_user)):
"""AI 退出时机预测。"""
result = await predict_exit(req.get("company_data", ""))
return success(data=result)
+89
View File
@@ -0,0 +1,89 @@
"""财务数据路由:CRUD + 校验。"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
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.financial_data import FinancialData
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.financial_validator import validate_financial_data
router = APIRouter(prefix="/financial", tags=["financial"])
@router.get("", response_model=ApiResponse[list])
async def list_financial_data(
company_id: str = Query(...),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取企业财务数据列表。"""
result = await db.execute(
select(FinancialData)
.join(Company, FinancialData.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id, FinancialData.company_id == company_id)
.order_by(FinancialData.period_year.desc(), FinancialData.period_month.desc())
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"period_year": i.period_year,
"period_month": i.period_month,
"statement_type": i.statement_type,
"data_json": i.data_json,
"credibility_score": i.credibility_score,
"validation_result": i.validation_result,
}
for i in items
])
@router.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_financial_data(
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""创建财务数据并自动校验。"""
company_id = req.get("company_id")
company_result = await db.execute(
select(Company).where(Company.id == company_id, Company.tenant_id == user.tenant_id)
)
if not company_result.scalar_one_or_none():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
fd = FinancialData(
company_id=company_id,
period_year=req.get("period_year"),
period_month=req.get("period_month"),
statement_type=req.get("statement_type", "balance_sheet"),
data_json=req.get("data_json"),
source=req.get("source"),
)
validation = await validate_financial_data(db, company_id, fd.period_year, fd.period_month)
fd.credibility_score = validation["credibility_score"]
fd.validation_result = validation
db.add(fd)
await db.flush()
return success(data={"id": str(fd.id), "credibility_score": fd.credibility_score, "validation_result": validation}, message="创建成功")
@router.get("/validate", response_model=ApiResponse[dict])
async def validate_financial(
company_id: str = Query(...),
period_year: int = Query(...),
period_month: int = Query(...),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""校验财务数据。"""
result = await validate_financial_data(db, company_id, period_year, period_month)
return success(data=result)
+148
View File
@@ -0,0 +1,148 @@
"""创始人专属 API — 经营概览 + 自身健康度 + AI 副驾驶完整版。"""
from fastapi import APIRouter, Depends, Query
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.user import User
from app.schemas.common import ApiResponse, success
from app.services.founder_copilot import financing_planner, org_diagnostic, investor_comm_prep
router = APIRouter(prefix="/founder", tags=["founder"])
@router.get("/overview", response_model=ApiResponse[dict])
async def founder_overview(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""创始人经营概览。"""
# 获取创始人关联的企业
company_result = await db.execute(
select(Company).where(Company.tenant_id == user.tenant_id).limit(1)
)
company = company_result.scalar_one_or_none()
if not company:
return success(data={"message": "暂无关联企业"})
# 获取最新健康度
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()
# 获取最新月报
report_result = await db.execute(
select(MonthlyReport)
.where(MonthlyReport.company_id == company.id)
.order_by(MonthlyReport.period_year.desc(), MonthlyReport.period_month.desc())
.limit(1)
)
report = report_result.scalar_one_or_none()
return success(data={
"company": {"id": str(company.id), "name": company.name, "industry": company.industry, "stage": company.stage},
"health_score": {
"total_score": health.total_score if health else None,
"trend": health.trend if health else None,
} if health else None,
"latest_report": {
"id": str(report.id),
"period": f"{report.period_year}-{report.period_month:02d}",
"status": report.status,
} if report else None,
})
@router.get("/health", response_model=ApiResponse[dict])
async def founder_health(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""创始人查看自身健康度。"""
company_result = await db.execute(
select(Company).where(Company.tenant_id == user.tenant_id).limit(1)
)
company = company_result.scalar_one_or_none()
if not company:
return success(data=None)
result = await db.execute(
select(HealthScore)
.where(HealthScore.company_id == company.id)
.order_by(HealthScore.calculated_at.desc())
.limit(1)
)
health = result.scalar_one_or_none()
if not health:
return success(data=None)
return success(data={
"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,
"trend": health.trend,
"recommendations": health.recommendations_json,
})
@router.post("/financing-plan", response_model=ApiResponse[dict])
async def founder_financing_plan(
req: dict,
user: User = Depends(get_current_user),
):
"""AI 融资规划 — 节奏/估值/投资人画像。
Args:
req: 包含 company_data 字段,描述企业当前融资情况
Returns:
AI 生成的融资规划建议
"""
result = await financing_planner(req.get("company_data", ""))
return success(data=result)
@router.post("/org-diagnostic", response_model=ApiResponse[dict])
async def founder_org_diagnostic(
req: dict,
user: User = Depends(get_current_user),
):
"""AI 组织诊断 — 团队结构/关键岗位风险/人才缺口。
Args:
req: 包含 team_data 字段,描述团队当前情况
Returns:
AI 生成的组织诊断报告
"""
result = await org_diagnostic(req.get("team_data", ""))
return success(data=result)
@router.post("/investor-comm-prep", response_model=ApiResponse[dict])
async def founder_investor_comm_prep(
req: dict,
user: User = Depends(get_current_user),
):
"""AI 投资人沟通准备 — 董事会材料/投资人问答。
Args:
req: 包含 board_context 字段,描述董事会/投资人会议背景
Returns:
AI 生成的投资人沟通准备材料
"""
result = await investor_comm_prep(req.get("board_context", ""))
return success(data=result)
+24
View File
@@ -0,0 +1,24 @@
"""基金管理路由。"""
from fastapi import APIRouter, Depends
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.fund_strategy_analyzer import analyze_fund_strategy
from app.services.lp_report_generator import generate_lp_report
router = APIRouter(prefix="/funds", tags=["funds"])
@router.post("/analyze-strategy", response_model=ApiResponse[dict])
async def analyze_strategy(req: dict, user: User = Depends(get_current_user)):
"""基金策略分析。"""
result = await analyze_fund_strategy(req.get("funds_data", ""))
return success(data=result)
@router.post("/lp-report", response_model=ApiResponse[str])
async def generate_lp(req: dict, user: User = Depends(get_current_user)):
"""LP 报告自动生成。"""
result = await generate_lp_report(req.get("fund_data", ""), req.get("portfolio_summary", ""))
return success(data=result)
+16
View File
@@ -0,0 +1,16 @@
"""行业研究路由。"""
from fastapi import APIRouter, Depends
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.industry_research_agent import research_industry
router = APIRouter(prefix="/industry-research", tags=["industry-research"])
@router.post("/research", response_model=ApiResponse[dict])
async def research(req: dict, user: User = Depends(get_current_user)):
"""AI 行业研究。"""
result = await research_industry(req.get("industry", ""), req.get("companies", ""))
return success(data=result)
+16
View File
@@ -0,0 +1,16 @@
"""组合创新路由。"""
from fastapi import APIRouter, Depends
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.innovation_lab import discover_innovation_opportunities
router = APIRouter(prefix="/innovation", tags=["innovation"])
@router.post("/discover", response_model=ApiResponse[list])
async def discover_innovation(req: dict, user: User = Depends(get_current_user)):
"""AI 发现组合创新机会。"""
results = await discover_innovation_opportunities(req.get("portfolio_capabilities", ""))
return success(data=results)
+32
View File
@@ -0,0 +1,32 @@
"""RAG 知识库路由。"""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.rag import semantic_search, build_context
router = APIRouter(prefix="/knowledge", tags=["knowledge"])
@router.get("/search", response_model=ApiResponse[list])
async def search_knowledge(
q: str = Query(..., min_length=1),
top_k: int = Query(default=5, ge=1, le=20),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""语义搜索知识库。"""
results = await semantic_search(db, user.tenant_id, q, top_k)
return success(data=results)
@router.post("/context", response_model=ApiResponse[str])
async def get_context(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""构建 RAG 上下文。"""
results = await semantic_search(db, user.tenant_id, req.get("query", ""))
context = await build_context(results)
return success(data=context)
+23
View File
@@ -0,0 +1,23 @@
"""知识图谱路由。"""
from fastapi import APIRouter, Depends
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.knowledge_graph_builder import build_knowledge_graph, match_best_strategy
router = APIRouter(prefix="/knowledge-graph", tags=["knowledge-graph"])
@router.post("/build", response_model=ApiResponse[dict])
async def build_graph(req: dict, user: User = Depends(get_current_user)):
"""构建知识图谱。"""
result = await build_knowledge_graph(req.get("management_experiences", ""))
return success(data=result)
@router.post("/match-strategy", response_model=ApiResponse[dict])
async def match_strategy(req: dict, user: User = Depends(get_current_user)):
"""为新企业匹配最佳管理策略。"""
result = await match_best_strategy(req.get("new_company_profile", ""), req.get("knowledge_graph", {}))
return success(data=result)
+45
View File
@@ -0,0 +1,45 @@
"""里程碑路由。"""
from fastapi import APIRouter, Depends, Query
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.milestone import MilestoneTree
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.milestone_agent import suggest_path_switch
router = APIRouter(prefix="/milestones", tags=["milestones"])
@router.get("", response_model=ApiResponse[list])
async def list_milestones(company_id: str = Query(...), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取里程碑树。"""
result = await db.execute(
select(MilestoneTree).where(MilestoneTree.company_id == company_id)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"name": i.name,
"parent_id": i.parent_id,
"is_current": i.is_current,
"status": i.status,
"target_date": i.target_date.isoformat() if i.target_date else None,
"actual_date": i.actual_date.isoformat() if i.actual_date else None,
"description": i.description,
"ai_analysis": i.ai_analysis,
}
for i in items
])
@router.post("/suggest-switch", response_model=ApiResponse[dict])
async def suggest_switch(req: dict, user: User = Depends(get_current_user)):
"""AI 建议路径切换。"""
result = await suggest_path_switch(req.get("milestone_context", ""), req.get("env_changes", ""))
return success(data=result)
+42
View File
@@ -0,0 +1,42 @@
"""行为助推路由。"""
from fastapi import APIRouter, Depends, Query
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.nudge import NudgeRecord
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.nudge_agent import select_nudge_strategy
router = APIRouter(prefix="/nudges", tags=["nudges"])
@router.get("", response_model=ApiResponse[list])
async def list_nudges(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取助推记录列表。"""
query = select(NudgeRecord)
if company_id:
query = query.where(NudgeRecord.company_id == company_id)
result = await db.execute(query.order_by(NudgeRecord.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"nudge_type": i.nudge_type,
"message": i.message,
"accepted": i.accepted,
"effect_result": i.effect_result,
}
for i in items
])
@router.post("/select", response_model=ApiResponse[dict])
async def select_nudge(req: dict, user: User = Depends(get_current_user)):
"""AI 选择助推策略。"""
result = await select_nudge_strategy(req.get("context", ""))
return success(data=result)
+59
View File
@@ -0,0 +1,59 @@
"""OKR 路由。"""
from fastapi import APIRouter, Depends, Query, status
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.okr import OKR
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.okr_agent import track_okr_progress
router = APIRouter(prefix="/okrs", tags=["okrs"])
@router.get("", response_model=ApiResponse[list])
async def list_okrs(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取 OKR 列表。"""
query = select(OKR)
if company_id:
query = query.where(OKR.company_id == company_id)
result = await db.execute(query.order_by(OKR.quarter.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"quarter": i.quarter,
"objective": i.objective,
"key_results": i.key_results,
"alignment_score": i.alignment_score,
"deviation_alerts": i.deviation_alerts,
"review_notes": i.review_notes,
"status": i.status,
}
for i in items
])
@router.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_okr(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""创建 OKR。"""
okr = OKR(
company_id=req.get("company_id"),
quarter=req.get("quarter"),
objective=req.get("objective"),
key_results=req.get("key_results"),
)
db.add(okr)
await db.flush()
return success(data={"id": str(okr.id)}, message="创建成功")
@router.post("/{okr_id}/track", response_model=ApiResponse[dict])
async def track_okr(okr_id: str, req: dict, user: User = Depends(get_current_user)):
"""AI 追踪 KR 进展。"""
result = await track_okr_progress(req.get("key_results", []))
return success(data=result)
+43
View File
@@ -0,0 +1,43 @@
"""Peer Learning Circles 路由。"""
from fastapi import APIRouter, Depends, Query
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.peer_circle import PeerLearningCircle
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.peer_matching import match_founders
router = APIRouter(prefix="/peer-circles", tags=["peer-circles"])
@router.get("", response_model=ApiResponse[list])
async def list_circles(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取 Peer Learning Circle 列表。"""
result = await db.execute(
select(PeerLearningCircle).where(PeerLearningCircle.tenant_id == user.tenant_id)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"topic": i.topic,
"description": i.description,
"members": i.members,
"discussion_framework": i.discussion_framework,
"conclusions": i.conclusions,
"action_commitments": i.action_commitments,
"status": i.status,
}
for i in items
])
@router.post("/match", response_model=ApiResponse[dict])
async def match_peer_circle(req: dict, user: User = Depends(get_current_user)):
"""AI 匹配创始人。"""
result = await match_founders(req.get("founders_context", ""))
return success(data=result)
+24
View File
@@ -0,0 +1,24 @@
"""组合管理路由 — 再平衡 + Monte Carlo。"""
from fastapi import APIRouter, Depends
from app.core.dependencies import get_current_user
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.portfolio_rebalancer import rebalance_portfolio
from app.services.monte_carlo import simulate_portfolio
router = APIRouter(prefix="/portfolio", tags=["portfolio"])
@router.post("/rebalance", response_model=ApiResponse[dict])
async def rebalance(req: dict, user: User = Depends(get_current_user)):
"""组合再平衡建议。"""
result = rebalance_portfolio(req.get("company_returns", []))
return success(data=result)
@router.post("/monte-carlo", response_model=ApiResponse[dict])
async def monte_carlo(req: dict, user: User = Depends(get_current_user)):
"""Monte Carlo 模拟。"""
result = await simulate_portfolio(req.get("company_returns", []), req.get("iterations", 10000))
return success(data=result)
+58
View File
@@ -0,0 +1,58 @@
"""Pre-mortem + Red Team 路由。"""
from fastapi import APIRouter, Depends, Query
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.pre_mortem import PreMortemRecord, RedTeamRecord
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.pre_mortem_agent import run_pre_mortem
from app.services.red_team_agent import run_red_team
router_pre_mortem = APIRouter(prefix="/pre-mortems", tags=["pre-mortems"])
router_red_team = APIRouter(prefix="/red-teams", tags=["red-teams"])
@router_pre_mortem.get("", response_model=ApiResponse[list])
async def list_pre_mortems(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取 Pre-mortem 列表。"""
query = select(PreMortemRecord)
if company_id:
query = query.where(PreMortemRecord.company_id == company_id)
result = await db.execute(query.order_by(PreMortemRecord.created_at.desc()))
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "company_id": str(i.company_id), "decision_context": i.decision_context, "failure_paths": i.failure_paths, "risk_checklist": i.risk_checklist, "mitigations": i.mitigations}
for i in items
])
@router_pre_mortem.post("/run", response_model=ApiResponse[dict])
async def run_pre_mortem_analysis(req: dict, user: User = Depends(get_current_user)):
"""AI Pre-mortem 失败推演。"""
result = await run_pre_mortem(req.get("decision_context", ""))
return success(data=result)
@router_red_team.get("", response_model=ApiResponse[list])
async def list_red_teams(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取 Red Team 列表。"""
query = select(RedTeamRecord)
if company_id:
query = query.where(RedTeamRecord.company_id == company_id)
result = await db.execute(query.order_by(RedTeamRecord.created_at.desc()))
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "company_id": str(i.company_id), "perspective": i.perspective, "analysis": i.analysis, "vulnerabilities": i.vulnerabilities, "counterarguments": i.counterarguments}
for i in items
])
@router_red_team.post("/run", response_model=ApiResponse[dict])
async def run_red_team_analysis(req: dict, user: User = Depends(get_current_user)):
"""AI Red Team 对抗分析。"""
result = await run_red_team(req.get("company_context", ""), req.get("perspective", "competitor"))
return success(data=result)
@@ -0,0 +1,43 @@
"""产品竞争力诊断路由。"""
from fastapi import APIRouter, Depends, Query
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.product_diagnostic import ProductDiagnostic
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.product_diagnostic_agent import diagnose_product
router = APIRouter(prefix="/product-diagnostics", tags=["product-diagnostics"])
@router.get("", response_model=ApiResponse[list])
async def list_diagnostics(company_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取产品诊断列表。"""
query = select(ProductDiagnostic)
if company_id:
query = query.where(ProductDiagnostic.company_id == company_id)
result = await db.execute(query.order_by(ProductDiagnostic.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"product_name": i.product_name,
"dimensions": i.dimensions,
"heatmap_data": i.heatmap_data,
"competitors": i.competitors,
"roadmap_suggestions": i.roadmap_suggestions,
}
for i in items
])
@router.post("/diagnose", response_model=ApiResponse[dict])
async def diagnose(req: dict, user: User = Depends(get_current_user)):
"""AI 产品竞争力诊断。"""
result = await diagnose_product(req.get("product_info", ""), req.get("competitor_info", ""))
return success(data=result)
+72
View File
@@ -0,0 +1,72 @@
"""多主体画像路由。"""
from fastapi import APIRouter, Depends, Query, status
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.profile import FirmProfile, FundProfile, ManagerProfile
from app.models.user import User
from app.schemas.common import ApiResponse, success
router = APIRouter(prefix="/profiles", tags=["profiles"])
@router.get("/firms", response_model=ApiResponse[list])
async def list_firms(
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取投资机构列表。"""
result = await db.execute(
select(FirmProfile).where(FirmProfile.tenant_id == user.tenant_id)
)
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "name": i.name, "focus_areas": i.focus_areas, "stage_preference": i.stage_preference, "description": i.description}
for i in items
])
@router.post("/firms", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_firm(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""创建投资机构画像。"""
firm = FirmProfile(
tenant_id=user.tenant_id,
name=req.get("name"),
focus_areas=req.get("focus_areas"),
stage_preference=req.get("stage_preference"),
description=req.get("description"),
)
db.add(firm)
await db.flush()
return success(data={"id": str(firm.id)}, message="创建成功")
@router.get("/funds", response_model=ApiResponse[list])
async def list_funds(firm_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取基金列表。"""
query = select(FundProfile)
if firm_id:
query = query.where(FundProfile.firm_id == firm_id)
result = await db.execute(query)
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "firm_id": str(i.firm_id), "name": i.name, "fund_size": i.fund_size, "vintage_year": i.vintage_year, "strategy": i.strategy}
for i in items
])
@router.get("/managers", response_model=ApiResponse[list])
async def list_managers(firm_id: str | None = Query(default=None), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取投资经理列表。"""
query = select(ManagerProfile)
if firm_id:
query = query.where(ManagerProfile.firm_id == firm_id)
result = await db.execute(query)
items = result.scalars().all()
return success(data=[
{"id": str(i.id), "firm_id": str(i.firm_id), "name": i.name, "focus_areas": i.focus_areas, "portfolio_count": i.portfolio_count}
for i in items
])
+45 -1
View File
@@ -3,7 +3,7 @@
import json
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, status
from fastapi.responses import StreamingResponse
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -24,7 +24,9 @@ from app.schemas.report import (
)
from app.services.ai_parser import parse_report
from app.services.health_calculator import calculate_health_score, determine_trend
from app.services.report_tracker import compute_timeliness
from app.services.risk_engine import detect_risks
from app.services.file_parser import parse_file
router = APIRouter(prefix="/reports", tags=["reports"])
@@ -369,3 +371,45 @@ async def parse_report_stream(
"X-Accel-Buffering": "no",
},
)
@router.get("/timeliness", response_model=ApiResponse[list])
async def get_timeliness(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取月报提交及时性和数据质量评分。"""
items = await compute_timeliness(db, user.tenant_id, company_id)
return success(data=items)
@router.post("/upload", response_model=ApiResponse[dict])
async def upload_report_file(
file: UploadFile = File(...),
user: User = Depends(get_current_user),
):
"""上传月报文件 — 自动解析提取文本内容。
支持 .xlsx、.pdf、.txt、.md、.csv 格式。
"""
if not file.filename:
raise HTTPException(status_code=400, detail="文件名不能为空")
allowed_extensions = {".xlsx", ".xls", ".pdf", ".txt", ".md", ".csv"}
ext = file.filename.rsplit(".", 1)[-1].lower() if "." in file.filename else ""
if f".{ext}" not in allowed_extensions:
raise HTTPException(status_code=400, detail=f"不支持的文件格式: .{ext}")
content = await file.read()
if len(content) > 10 * 1024 * 1024:
raise HTTPException(status_code=400, detail="文件大小不能超过 10MB")
extracted_text = await parse_file(content, file.filename)
return success(data={
"filename": file.filename,
"file_type": ext,
"extracted_text": extracted_text[:10000],
"char_count": len(extracted_text),
})
+85
View File
@@ -0,0 +1,85 @@
"""协同机会路由。"""
from fastapi import APIRouter, Depends, Query, status
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.synergy import SynergyOpportunity
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.synergy_matcher import match_synergy
router = APIRouter(prefix="/synergies", tags=["synergies"])
@router.get("", response_model=ApiResponse[list])
async def list_synergies(
company_id: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取协同机会列表。"""
query = select(SynergyOpportunity).where(SynergyOpportunity.tenant_id == user.tenant_id)
if company_id:
query = query.where(
(SynergyOpportunity.company_a_id == company_id) | (SynergyOpportunity.company_b_id == company_id)
)
result = await db.execute(query.order_by(SynergyOpportunity.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"type": i.type,
"company_a_id": str(i.company_a_id),
"company_b_id": str(i.company_b_id),
"title": i.title,
"description": i.description,
"match_reason": i.match_reason,
"status": i.status,
"authorized": i.authorized,
"effect_result": i.effect_result,
}
for i in items
])
@router.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_synergy(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""创建协同机会。"""
synergy = SynergyOpportunity(
tenant_id=user.tenant_id,
type=req.get("type"),
company_a_id=req.get("company_a_id"),
company_b_id=req.get("company_b_id"),
title=req.get("title"),
description=req.get("description"),
match_reason=req.get("match_reason"),
)
db.add(synergy)
await db.flush()
return success(data={"id": str(synergy.id)}, message="创建成功")
@router.post("/match", response_model=ApiResponse[list])
async def match_synergies(req: dict, user: User = Depends(get_current_user)):
"""AI 匹配协同机会。"""
results = await match_synergy(req.get("company_a_context", ""), req.get("portfolio_context", ""))
return success(data=results)
@router.put("/{synergy_id}/authorize", response_model=ApiResponse[dict])
async def authorize_synergy(synergy_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""授权信息交换。"""
result = await db.execute(
select(SynergyOpportunity).where(SynergyOpportunity.id == synergy_id, SynergyOpportunity.tenant_id == user.tenant_id)
)
synergy = result.scalar_one_or_none()
if not synergy:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="协同机会不存在")
synergy.authorized = True
synergy.status = "authorized"
await db.flush()
return success(data={"id": str(synergy.id), "authorized": True}, message="授权成功")
+72
View File
@@ -0,0 +1,72 @@
"""人才路由。"""
from fastapi import APIRouter, Depends, Query, status
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.talent import TalentProfile, TeamMember
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.services.talent_agent import predict_talent_flow, recommend_talent
router = APIRouter(prefix="/talents", tags=["talents"])
@router.get("", response_model=ApiResponse[list])
async def list_talents(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取人才池列表。"""
result = await db.execute(
select(TalentProfile).where(TalentProfile.tenant_id == user.tenant_id)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"name": i.name,
"current_role": i.current_role,
"current_company": i.current_company,
"skills": i.skills,
"performance_rating": i.performance_rating,
"potential_rating": i.potential_rating,
"nine_box": i.nine_box,
"flow_prediction": i.flow_prediction,
"status": i.status,
}
for i in items
])
@router.post("/predict-flow", response_model=ApiResponse[dict])
async def predict_flow(req: dict, user: User = Depends(get_current_user)):
"""AI 预测人才流动。"""
result = await predict_talent_flow(req.get("talent_data", ""))
return success(data=result)
@router.post("/recommend", response_model=ApiResponse[list])
async def recommend(req: dict, user: User = Depends(get_current_user)):
"""AI 推荐人才。"""
results = await recommend_talent(req.get("company_need", ""), req.get("talent_pool", ""))
return success(data=results)
@router.get("/team-members", response_model=ApiResponse[list])
async def list_team_members(company_id: str = Query(...), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""获取企业团队成员列表。"""
result = await db.execute(
select(TeamMember).where(TeamMember.company_id == company_id)
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"name": i.name,
"role": i.role,
"is_key_person": i.is_key_person,
"stability_score": i.stability_score,
"joined_at": i.joined_at.isoformat() if i.joined_at else None,
}
for i in items
])
+124
View File
@@ -0,0 +1,124 @@
"""任务 + 评论路由。"""
from fastapi import APIRouter, Depends, Query, status
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.task import Comment, Task
from app.models.user import User
from app.schemas.common import ApiResponse, success
router_tasks = APIRouter(prefix="/tasks", tags=["tasks"])
router_comments = APIRouter(prefix="/comments", tags=["comments"])
@router_tasks.get("", response_model=ApiResponse[list])
async def list_tasks(
company_id: str | None = Query(default=None),
status_filter: str | None = Query(default=None, alias="status"),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取任务列表。"""
query = select(Task).where(Task.tenant_id == user.tenant_id)
if company_id:
query = query.where(Task.company_id == company_id)
if status_filter:
query = query.where(Task.status == status_filter)
result = await db.execute(query.order_by(Task.created_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id) if i.company_id else None,
"title": i.title,
"description": i.description,
"status": i.status,
"priority": i.priority,
"assigned_to": i.assigned_to,
"source_type": i.source_type,
"due_at": i.due_at.isoformat() if i.due_at else None,
}
for i in items
])
@router_tasks.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_task(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""创建任务。"""
task = Task(
tenant_id=user.tenant_id,
company_id=req.get("company_id"),
title=req.get("title"),
description=req.get("description"),
priority=req.get("priority", "medium"),
assigned_to=req.get("assigned_to"),
source_type=req.get("source_type"),
source_ref=req.get("source_ref"),
due_at=req.get("due_at"),
)
db.add(task)
await db.flush()
return success(data={"id": str(task.id)}, message="创建成功")
@router_tasks.put("/{task_id}", response_model=ApiResponse[dict])
async def update_task(task_id: str, req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""更新任务状态。"""
result = await db.execute(
select(Task).where(Task.id == task_id, Task.tenant_id == user.tenant_id)
)
task = result.scalar_one_or_none()
if not task:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="任务不存在")
for key, value in req.items():
setattr(task, key, value)
await db.flush()
return success(data={"id": str(task.id)}, message="更新成功")
@router_comments.get("", response_model=ApiResponse[list])
async def list_comments(
target_type: str = Query(...),
target_id: str = Query(...),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取评论列表。"""
result = await db.execute(
select(Comment)
.where(Comment.tenant_id == user.tenant_id, Comment.target_type == target_type, Comment.target_id == target_id)
.order_by(Comment.created_at.asc())
)
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"target_type": i.target_type,
"target_id": i.target_id,
"user_id": str(i.user_id),
"content": i.content,
"parent_id": i.parent_id,
"created_at": i.created_at.isoformat(),
}
for i in items
])
@router_comments.post("", response_model=ApiResponse[dict], status_code=status.HTTP_201_CREATED)
async def create_comment(req: dict, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""创建评论。"""
comment = Comment(
tenant_id=user.tenant_id,
target_type=req.get("target_type"),
target_id=req.get("target_id"),
user_id=str(user.id),
content=req.get("content"),
parent_id=req.get("parent_id"),
)
db.add(comment)
await db.flush()
return success(data={"id": str(comment.id)}, message="创建成功")
+64
View File
@@ -0,0 +1,64 @@
"""弱信号路由:列表 + 关联结果查询。"""
from fastapi import APIRouter, Depends, Query, status
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.user import User
from app.models.weak_signal import WeakSignal
from app.schemas.common import ApiResponse, success
from app.services.signal_correlator import correlate_signals
router = APIRouter(prefix="/weak-signals", tags=["weak-signals"])
@router.get("", response_model=ApiResponse[list])
async def list_weak_signals(
company_id: str | None = Query(default=None),
signal_type: str | None = Query(default=None),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取弱信号列表。"""
query = (
select(WeakSignal)
.join(Company, WeakSignal.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(WeakSignal.company_id == company_id)
if signal_type:
query = query.where(WeakSignal.signal_type == signal_type)
result = await db.execute(query.order_by(WeakSignal.detected_at.desc()))
items = result.scalars().all()
return success(data=[
{
"id": str(i.id),
"company_id": str(i.company_id),
"signal_type": i.signal_type,
"source": i.source,
"content": i.content,
"confidence": i.confidence,
"correlation_id": i.correlation_id,
"correlation_result": i.correlation_result,
"risk_probability": i.risk_probability,
"status": i.status,
"detected_at": i.detected_at.isoformat(),
}
for i in items
])
@router.post("/correlate", response_model=ApiResponse[list])
async def correlate_weak_signals(
req: dict,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""对弱信号进行关联分析。"""
signals = req.get("signals", [])
results = await correlate_signals(signals)
return success(data=results)