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
+47
View File
@@ -0,0 +1,47 @@
"""审计日志装饰器。
自动记录 CREATE/UPDATE/DELETE 操作。
"""
import functools
import logging
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.audit import AuditLog
logger = logging.getLogger(__name__)
async def log_audit(
db: AsyncSession,
user_id: str,
action: str,
target_type: str,
target_id: str,
detail: dict | None = None,
tenant_id: str | None = None,
) -> None:
"""记录审计日志。
Args:
db: 数据库会话
user_id: 操作用户 ID
action: 操作类型(login/view/create/update/delete/export/ai_call
target_type: 资源类型(映射到 resource_type 字段)
target_id: 资源 ID(映射到 resource_id 字段)
detail: 操作详情字典
tenant_id: 租户 ID
"""
audit = AuditLog(
tenant_id=tenant_id or "",
user_id=user_id,
action=action,
resource_type=target_type,
resource_id=target_id,
detail_json=detail,
created_at=datetime.now(timezone.utc),
)
db.add(audit)
await db.flush()
+49
View File
@@ -0,0 +1,49 @@
"""缓存装饰器。
为热点接口自动添加 Redis 缓存。
"""
import functools
import hashlib
import logging
from typing import Any, Callable
from app.core.redis import cache_get, cache_set
logger = logging.getLogger(__name__)
def cached(prefix: str, ttl: int = 300):
"""缓存装饰器 — 自动缓存函数返回值。
Args:
prefix: 缓存键前缀
ttl: 缓存过期时间(秒)
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
async def wrapper(*args, **kwargs) -> Any:
# 生成缓存键
key_parts = [prefix]
for arg in args[1:]: # 跳过 self/db
key_parts.append(str(arg))
for k, v in sorted(kwargs.items()):
key_parts.append(f"{k}={v}")
cache_key = hashlib.md5(":".join(key_parts).encode()).hexdigest()
# 尝试获取缓存
cached = await cache_get(f"{prefix}:{cache_key}")
if cached is not None:
return cached
# 执行函数
result = await func(*args, **kwargs)
# 写入缓存
await cache_set(f"{prefix}:{cache_key}", result, ttl)
return result
return wrapper
return decorator
+37
View File
@@ -0,0 +1,37 @@
"""PII 脱敏服务 — 手机/邮箱/身份证 日志脱敏。"""
import re
def mask_phone(phone: str) -> str:
"""手机号脱敏:138****1234"""
if len(phone) >= 11:
return phone[:3] + "****" + phone[-4:]
return phone
def mask_email(email: str) -> str:
"""邮箱脱敏:z***@example.com"""
if "@" in email:
name, domain = email.split("@", 1)
if len(name) > 1:
return name[0] + "***@" + domain
return email
def mask_id_card(id_card: str) -> str:
"""身份证脱敏:110***********1234"""
if len(id_card) >= 18:
return id_card[:3] + "*" * 11 + id_card[-4:]
return id_card
def mask_pii(text: str) -> str:
"""自动识别并脱敏文本中的 PII 信息。"""
# 手机号
text = re.sub(r"1[3-9]\d{9}", lambda m: mask_phone(m.group()), text)
# 邮箱
text = re.sub(r"[\w.+-]+@[\w-]+\.[\w.-]+", lambda m: mask_email(m.group()), text)
# 身份证(18位)
text = re.sub(r"\d{17}[\dXx]", lambda m: mask_id_card(m.group()), text)
return text
+23
View File
@@ -0,0 +1,23 @@
"""敏感字段加密存储。"""
import base64
import hashlib
from app.core.config import settings
def encrypt_field(value: str) -> str:
"""加密敏感字段(简化版 — 实际应使用 KMS/Vault)。"""
key = settings.jwt_secret_key.encode()
data = value.encode()
# XOR 加密(简化版,生产环境应使用 AES)
encrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
return base64.b64encode(encrypted).decode()
def decrypt_field(encrypted: str) -> str:
"""解密敏感字段。"""
key = settings.jwt_secret_key.encode()
data = base64.b64decode(encrypted)
decrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
return decrypted.decode()
+41
View File
@@ -0,0 +1,41 @@
"""限流中间件 — 登录 5 次/min + 写接口按用户限流。"""
import time
from collections import defaultdict
from fastapi import HTTPException, Request, status
class RateLimiter:
"""简单的内存限流器。"""
def __init__(self) -> None:
self._requests: dict[str, list[float]] = defaultdict(list)
def check(self, key: str, max_requests: int, window_seconds: int) -> bool:
"""检查是否超过限流阈值。"""
now = time.time()
window_start = now - window_seconds
# 清理过期记录
self._requests[key] = [t for t in self._requests[key] if t > window_start]
if len(self._requests[key]) >= max_requests:
return False
self._requests[key].append(now)
return True
rate_limiter = RateLimiter()
async def login_rate_limit(request: Request) -> None:
"""登录接口限流 — 5 次/min/IP。"""
client_ip = request.client.host if request.client else "unknown"
if not rate_limiter.check(f"login:{client_ip}", max_requests=5, window_seconds=60):
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="登录尝试过于频繁,请稍后再试",
headers={"Retry-After": "60"},
)
+47
View File
@@ -0,0 +1,47 @@
"""Redis 连接与缓存工具。"""
import json
import logging
from typing import Any
from app.core.config import settings
logger = logging.getLogger(__name__)
try:
import redis.asyncio as redis
_redis_client = redis.from_url(settings.redis_url, decode_responses=True)
except ImportError:
_redis_client = None
logger.warning("redis 未安装,缓存功能不可用")
async def cache_get(key: str) -> Any | None:
"""从 Redis 获取缓存。"""
if not _redis_client:
return None
try:
data = await _redis_client.get(key)
return json.loads(data) if data else None
except Exception:
return None
async def cache_set(key: str, value: Any, ttl: int = 300) -> None:
"""设置 Redis 缓存。"""
if not _redis_client:
return
try:
await _redis_client.setex(key, ttl, json.dumps(value, default=str))
except Exception:
pass
async def cache_delete(key: str) -> None:
"""删除 Redis 缓存。"""
if not _redis_client:
return
try:
await _redis_client.delete(key)
except Exception:
pass
+81
View File
@@ -17,6 +17,45 @@ from app.routers.dashboard import router as dashboard_router
from app.routers.reports import router as reports_router
from app.routers.reports_export import router as reports_export_router
from app.routers.risks import router as risks_router
# Phase 2 路由
from app.routers.financial import router as financial_router
from app.routers.agreements import router as agreements_router
from app.routers.board import router as board_router
from app.routers.weak_signals import router as weak_signals_router
from app.routers.decision_sentinels import router as decision_sentinels_router
from app.routers.events import router as events_router, router_inquiries as inquiries_router
from app.routers.profiles import router as profiles_router
from app.routers.admin import router as admin_router
from app.routers.founder import router as founder_router
# Phase 3 路由
from app.routers.synergies import router as synergies_router
from app.routers.innovation import router as innovation_router
from app.routers.talents import router as talents_router
from app.routers.customer_plans import router as customer_plans_router
from app.routers.okrs import router as okrs_router
from app.routers.nudges import router as nudges_router
from app.routers.peer_circles import router as peer_circles_router
from app.routers.product_diagnostics import router as product_diagnostics_router
from app.routers.milestones import router as milestones_router
from app.routers.advanced_analysis import router as advanced_analysis_router
from app.routers.tasks import router_tasks as tasks_router, router_comments as comments_router
from app.routers.customer_success import router as customer_success_router
# Phase 4 路由
from app.routers.alpha import router as alpha_router
from app.routers.exit_predictions import router as exit_predictions_router
from app.routers.portfolio import router as portfolio_router
from app.routers.digital_twins import router as digital_twins_router
from app.routers.knowledge_graph import router as knowledge_graph_router
from app.routers.aars import router as aars_router
from app.routers.pre_mortems import router_pre_mortem as pre_mortems_router, router_red_team as red_teams_router
from app.routers.agent_executions import router as agent_executions_router
from app.routers.knowledge import router as knowledge_router
from app.routers.data_sources import router as data_sources_router
from app.routers.industry_research import router as industry_research_router
from app.routers.funds import router as funds_router
from app.schemas.common import error
@@ -79,3 +118,45 @@ app.include_router(dashboard_router, prefix="/api/v1")
app.include_router(risks_router, prefix="/api/v1")
app.include_router(copilot_router, prefix="/api/v1")
app.include_router(reports_export_router, prefix="/api/v1")
# Phase 2
app.include_router(financial_router, prefix="/api/v1")
app.include_router(agreements_router, prefix="/api/v1")
app.include_router(board_router, prefix="/api/v1")
app.include_router(weak_signals_router, prefix="/api/v1")
app.include_router(decision_sentinels_router, prefix="/api/v1")
app.include_router(events_router, prefix="/api/v1")
app.include_router(inquiries_router, prefix="/api/v1")
app.include_router(profiles_router, prefix="/api/v1")
app.include_router(admin_router, prefix="/api/v1")
app.include_router(founder_router, prefix="/api/v1")
# Phase 3
app.include_router(synergies_router, prefix="/api/v1")
app.include_router(innovation_router, prefix="/api/v1")
app.include_router(talents_router, prefix="/api/v1")
app.include_router(customer_plans_router, prefix="/api/v1")
app.include_router(okrs_router, prefix="/api/v1")
app.include_router(nudges_router, prefix="/api/v1")
app.include_router(peer_circles_router, prefix="/api/v1")
app.include_router(product_diagnostics_router, prefix="/api/v1")
app.include_router(milestones_router, prefix="/api/v1")
app.include_router(advanced_analysis_router, prefix="/api/v1")
app.include_router(tasks_router, prefix="/api/v1")
app.include_router(comments_router, prefix="/api/v1")
app.include_router(customer_success_router, prefix="/api/v1")
# Phase 4
app.include_router(alpha_router, prefix="/api/v1")
app.include_router(exit_predictions_router, prefix="/api/v1")
app.include_router(portfolio_router, prefix="/api/v1")
app.include_router(digital_twins_router, prefix="/api/v1")
app.include_router(knowledge_graph_router, prefix="/api/v1")
app.include_router(aars_router, prefix="/api/v1")
app.include_router(pre_mortems_router, prefix="/api/v1")
app.include_router(red_teams_router, prefix="/api/v1")
app.include_router(agent_executions_router, prefix="/api/v1")
app.include_router(knowledge_router, prefix="/api/v1")
app.include_router(data_sources_router, prefix="/api/v1")
app.include_router(industry_research_router, prefix="/api/v1")
app.include_router(funds_router, prefix="/api/v1")
+63
View File
@@ -3,20 +3,83 @@
导入所有模型以便 Alembic 自动发现。
"""
from app.models.aar import AARRecord
from app.models.agent_execution import AgentExecution
from app.models.agreement import InvestmentAgreement
from app.models.audit import AuditLog
from app.models.board import BoardMeeting
from app.models.company import Company
from app.models.customer_plan import CustomerAcquisitionPlan
from app.models.data_source import DataSource
from app.models.decision_sentinel import DecisionSentinel
from app.models.digital_twin import DigitalTwinModel
from app.models.exit_prediction import ExitPrediction
from app.models.financial_data import FinancialData
from app.models.health_score import HealthScore
from app.models.hypothesis import Hypothesis
from app.models.inquiry import InquiryList
from app.models.intervention import InterventionEvent, InterventionResult
from app.models.knowledge import KnowledgeChunk
from app.models.knowledge_graph import KnowledgeNode
from app.models.major_event import MajorEvent
from app.models.milestone import MilestoneTree
from app.models.nudge import NudgeRecord
from app.models.okr import OKR
from app.models.peer_circle import PeerLearningCircle
from app.models.portfolio_simulation import MonteCarloSimulation, PortfolioRebalancing
from app.models.pre_mortem import PreMortemRecord, RedTeamRecord
from app.models.product_diagnostic import ProductDiagnostic
from app.models.profile import FirmProfile, FundProfile, ManagerProfile
from app.models.report import MonthlyReport
from app.models.risk import RiskEvent
from app.models.synergy import SynergyOpportunity
from app.models.talent import TalentProfile, TeamMember
from app.models.task import Comment, Task
from app.models.tenant import Tenant
from app.models.user import User
from app.models.weak_signal import WeakSignal
__all__ = [
"AARRecord",
"AgentExecution",
"AuditLog",
"BoardMeeting",
"Comment",
"Company",
"CustomerAcquisitionPlan",
"DataSource",
"DecisionSentinel",
"DigitalTwinModel",
"ExitPrediction",
"FinancialData",
"FirmProfile",
"FundProfile",
"HealthScore",
"Hypothesis",
"InquiryList",
"InterventionEvent",
"InterventionResult",
"InvestmentAgreement",
"KnowledgeChunk",
"KnowledgeNode",
"MajorEvent",
"ManagerProfile",
"MilestoneTree",
"MonteCarloSimulation",
"MonthlyReport",
"NudgeRecord",
"OKR",
"PeerLearningCircle",
"PortfolioRebalancing",
"PreMortemRecord",
"ProductDiagnostic",
"RedTeamRecord",
"RiskEvent",
"SynergyOpportunity",
"TalentProfile",
"Task",
"TeamMember",
"Tenant",
"User",
"WeakSignal",
]
+29
View File
@@ -0,0 +1,29 @@
"""AAR 系统化复盘模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class AARRecord(Base):
"""AAR 复盘记录。"""
__tablename__ = "aar_records"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
trigger_event: Mapped[str] = mapped_column(String(200), nullable=False, comment="触发事件")
original_plan: Mapped[str | None] = mapped_column(Text, nullable=True, comment="原计划")
actual_result: Mapped[str | None] = mapped_column(Text, nullable=True, comment="实际结果")
gap_analysis: Mapped[str | None] = mapped_column(Text, nullable=True, comment="差异分析")
lessons: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="五问复盘结论")
improvements: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="改进措施 + 执行追踪")
knowledge_graph_ref: Mapped[str | None] = mapped_column(String(36), nullable=True, comment="知识图谱节点 ID")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+35
View File
@@ -0,0 +1,35 @@
"""Agent 执行记录模型。
L1-L4 分级自治执行。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class AgentExecution(Base):
"""Agent 执行记录。"""
__tablename__ = "agent_executions"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
agent_name: Mapped[str] = mapped_column(String(100), nullable=False, comment="Agent 名称")
autonomy_level: Mapped[str] = mapped_column(String(10), nullable=False, comment="L1/L2/L3/L4")
input_summary: Mapped[str | None] = mapped_column(Text, nullable=True, comment="输入摘要")
output_summary: Mapped[str | None] = mapped_column(Text, nullable=True, comment="输出摘要")
output_detail: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="输出详情")
review_status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending", comment="pending/approved/rejected/auto_approved")
reviewer_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id"), nullable=True)
reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
model_version: Mapped[str | None] = mapped_column(String(50), nullable=True)
duration_ms: Mapped[int | None] = mapped_column(nullable=True, comment="执行耗时")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+36
View File
@@ -0,0 +1,36 @@
"""投资协议模型。
协议条款提取与持续监控。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class InvestmentAgreement(Base):
"""投资协议。"""
__tablename__ = "investment_agreements"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False, comment="协议名称")
signed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, comment="签署日期")
file_url: Mapped[str | None] = mapped_column(String(500), nullable=True, comment="文件 URL")
key_clauses: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="关键条款 JSON")
monitoring_rules: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="监控规则")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="active", comment="active/expired/terminated")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+38
View File
@@ -0,0 +1,38 @@
"""董事会会议模型。
议程、纪要、决议追踪。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class BoardMeeting(Base):
"""董事会会议。"""
__tablename__ = "board_meetings"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False, comment="会议主题")
meeting_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, comment="会议时间")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="scheduled", comment="scheduled/in_progress/completed")
agenda: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="议程列表")
materials_summary: Mapped[str | None] = mapped_column(Text, nullable=True, comment="AI 会前材料摘要")
minutes: Mapped[str | None] = mapped_column(Text, nullable=True, comment="会议纪要")
resolutions: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="决议列表 — 含状态追踪")
questions: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="AI 提问清单")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+38
View File
@@ -0,0 +1,38 @@
"""客户获取计划模型。
AI 客户增长引擎 — LP 资源匹配 + 客户获取方案。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class CustomerAcquisitionPlan(Base):
"""客户获取计划。"""
__tablename__ = "customer_acquisition_plans"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
target_customer: Mapped[str | None] = mapped_column(Text, nullable=True, comment="目标客户画像")
entry_angle: Mapped[str | None] = mapped_column(Text, nullable=True, comment="切入角度")
decision_chain: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="决策链分析")
pricing_strategy: Mapped[str | None] = mapped_column(Text, nullable=True, comment="定价策略")
competitive_analysis: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="竞争分析")
lp_resources: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="可利用 LP 资源")
execution_status: Mapped[str] = mapped_column(String(20), nullable=False, default="planned", comment="planned/executing/completed/failed")
result: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="执行结果")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+35
View File
@@ -0,0 +1,35 @@
"""数据源模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class DataSource(Base):
"""外部数据源配置。"""
__tablename__ = "data_sources"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("companies.id"), nullable=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
source_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="crunchbase/business_registry/github/custom")
name: Mapped[str] = mapped_column(String(200), nullable=False)
api_endpoint: Mapped[str | None] = mapped_column(String(500), nullable=True)
api_key_encrypted: Mapped[str | None] = mapped_column(Text, nullable=True, comment="加密后的 API Key")
config: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="配置参数")
last_synced_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="inactive", comment="active/inactive/error")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+39
View File
@@ -0,0 +1,39 @@
"""决策前哨模型。
识别企业关键决策岔路口,AI 提前生成场景分析。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class DecisionSentinel(Base):
"""决策前哨。"""
__tablename__ = "decision_sentinels"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
decision_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="pivot/hiring/funding/product/org")
title: Mapped[str] = mapped_column(String(200), nullable=False, comment="决策标题")
description: Mapped[str | None] = mapped_column(Text, nullable=True)
signals: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="触发信号列表")
scenarios: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="场景分析 — A 路线 vs B 路线")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="identified", comment="identified/analyzed/acted/dismissed")
identified_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+31
View File
@@ -0,0 +1,31 @@
"""数字孪生模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class DigitalTwinModel(Base):
"""数字孪生。"""
__tablename__ = "digital_twins"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
model_params: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="模型参数")
scenarios: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="模拟场景列表")
accuracy_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="精度评分(0-1")
last_calibrated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+29
View File
@@ -0,0 +1,29 @@
"""退出预测模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class ExitPrediction(Base):
"""退出时机预测。"""
__tablename__ = "exit_predictions"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
exit_path: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="ipo/acquisition/secondary/merger")
timing_window: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="时机窗口 — 起止时间")
expected_return: Mapped[float | None] = mapped_column(Float, nullable=True, comment="期望收益率")
hold_return: Mapped[float | None] = mapped_column(Float, nullable=True, comment="继续持有预期收益率")
confidence: Mapped[float | None] = mapped_column(Float, nullable=True, comment="置信度")
signals: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="退出信号")
recommendation: Mapped[str | None] = mapped_column(Text, nullable=True, comment="退出建议")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+37
View File
@@ -0,0 +1,37 @@
"""财务数据模型。
资产负债表、利润表、现金流量表、科目余额。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class FinancialData(Base):
"""财务数据。"""
__tablename__ = "financial_data"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
period_year: Mapped[int] = mapped_column(Integer, nullable=False, comment="年份")
period_month: Mapped[int] = mapped_column(Integer, nullable=False, comment="月份")
statement_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="balance_sheet/income/cash_flow")
data_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="财务数据 JSON")
source: Mapped[str | None] = mapped_column(String(100), nullable=True, comment="数据来源")
credibility_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="可信度评分(0-100")
validation_result: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="校验结果")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+17 -1
View File
@@ -1,6 +1,8 @@
"""健康度评分模型。
多维度评分:财务、经营、AI+ 商业化、AI+ 成本。
多维度评分:财务、经营、AI+ 商业化、AI+ 成本(基础 4 维度)
T2.9 扩展:组织人才、产品技术、市场竞争、治理合规、融资资本(9 维度)。
T3.11 扩展:协同赋能、AI 模型产品、数据合规、团队技术、客户成功(14 维度)。
"""
import uuid
@@ -21,10 +23,24 @@ class HealthScore(Base):
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
total_score: Mapped[float] = mapped_column(Float, nullable=False, comment="总分(0-100")
# 基础 4 维度
financial_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="财务健康度")
operational_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="经营健康度")
ai_commercial_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="AI+ 商业化健康度")
ai_cost_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="AI+ 成本健康度")
# T2.9 扩展 5 维度
org_talent_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="组织人才健康度")
product_tech_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="产品技术健康度")
market_compete_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="市场竞争健康度")
governance_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="治理合规健康度")
financing_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="融资资本健康度")
# T3.11 扩展 5 维度
synergy_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="协同赋能健康度")
ai_model_product_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="AI 模型产品健康度")
data_compliance_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="数据合规健康度")
team_tech_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="团队技术健康度")
customer_success_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="客户成功健康度")
# 元数据
trend: Mapped[str | None] = mapped_column(String(20), nullable=True, comment="趋势:up/stable/down")
evidence_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="评分依据")
recommendations_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="建议动作")
+35
View File
@@ -0,0 +1,35 @@
"""BML 认知追踪模型。
假设/实验/数据/结论 — Build-Measure-Learn 循环。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class Hypothesis(Base):
"""BML 认知追踪 — 假设记录。"""
__tablename__ = "hypotheses"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
hypothesis: Mapped[str] = mapped_column(Text, nullable=False, comment="假设")
experiment: Mapped[str | None] = mapped_column(Text, nullable=True, comment="验证实验")
data: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="实验数据")
conclusion: Mapped[str | None] = mapped_column(Text, nullable=True, comment="结论")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="building", comment="building/measuring/learning/validated/invalidated")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+37
View File
@@ -0,0 +1,37 @@
"""追问清单模型。
AI 根据月报数据生成补充问题,企业可回复。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class InquiryList(Base):
"""追问清单。"""
__tablename__ = "inquiry_lists"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
report_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("monthly_reports.id"), nullable=True)
questions: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="问题列表 — 含问题和回答")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="sent", comment="sent/answered/closed")
sent_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
answered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+49
View File
@@ -0,0 +1,49 @@
"""干预事件与结果模型。
投后管理 Alpha 归因 — 干预事件 → 指标变化 → 估值影响 → 回报贡献。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class InterventionEvent(Base):
"""干预事件。"""
__tablename__ = "intervention_events"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
intervention_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="recruitment/customer_intro/strategy/governance/crisis/funding")
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
executed_by: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id"), nullable=True)
executed_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
class InterventionResult(Base):
"""干预结果。"""
__tablename__ = "intervention_results"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
intervention_id: Mapped[str] = mapped_column(String(36), ForeignKey("intervention_events.id"), nullable=False, index=True)
metric_changes: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="指标变化")
valuation_impact: Mapped[float | None] = mapped_column(Float, nullable=True, comment="估值影响")
return_contribution: Mapped[float | None] = mapped_column(Float, nullable=True, comment="回报贡献")
alpha_score: Mapped[float | None] = mapped_column(Float, nullable=True, comment="Alpha 归因评分")
evidence: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="证据链")
measured_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+27
View File
@@ -0,0 +1,27 @@
"""RAG 知识库模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import JSON, DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
class KnowledgeChunk(Base):
"""知识库分块 — 向量化的月报/报告片段。"""
__tablename__ = "knowledge_chunks"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
source_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="report/agreement/board/aar/knowledge_graph")
source_id: Mapped[str | None] = mapped_column(String(36), nullable=True, comment="来源记录 ID")
company_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("companies.id"), nullable=True, index=True)
content: Mapped[str] = mapped_column(Text, nullable=False, comment="文本内容")
embedding: Mapped[dict | None] = mapped_column(JSON, nullable=True, comment="向量嵌入")
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True, comment="元数据")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+30
View File
@@ -0,0 +1,30 @@
"""知识图谱模型。
企业特征 + 管理动作 + 环境上下文 → 结果 → 回报影响。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import JSON, DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class KnowledgeNode(Base):
"""知识图谱节点。"""
__tablename__ = "knowledge_nodes"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
entity_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="company/action/context/result/return")
entity_id: Mapped[str | None] = mapped_column(String(36), nullable=True, comment="关联实体 ID")
attributes: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="实体属性")
relations: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="关系列表 — target_id + relation_type")
embedding: Mapped[dict | None] = mapped_column(JSON, nullable=True, comment="向量嵌入")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+34
View File
@@ -0,0 +1,34 @@
"""重大事项模型。
AI 从月报/弱信号中自动识别重大事项。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class MajorEvent(Base):
"""重大事项。"""
__tablename__ = "major_events"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
event_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="funding/personnel/product/legal/market/org")
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
severity: Mapped[str] = mapped_column(String(20), nullable=False, default="medium", comment="low/medium/high/critical")
source: Mapped[str | None] = mapped_column(String(100), nullable=True, comment="来源:monthly_report/weak_signal/manual")
source_ref: Mapped[str | None] = mapped_column(String(36), nullable=True, comment="来源记录 ID")
evidence: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="证据链")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="identified", comment="identified/confirmed/addressed")
occurred_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+38
View File
@@ -0,0 +1,38 @@
"""里程碑树模型。
分支路径管理 + 环境变化时 AI 建议路径切换。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class MilestoneTree(Base):
"""里程碑树。"""
__tablename__ = "milestone_trees"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="里程碑名称")
parent_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("milestone_trees.id"), nullable=True, comment="父节点")
is_current: Mapped[bool] = mapped_column(default=False, comment="是否当前路径")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="planned", comment="planned/in_progress/completed/abandoned")
target_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
actual_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
ai_analysis: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="AI 路径分析")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+31
View File
@@ -0,0 +1,31 @@
"""行为助推记录模型。
时机判断 + 策略选择 + 效果追踪。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class NudgeRecord(Base):
"""行为助推记录。"""
__tablename__ = "nudge_records"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
nudge_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="anchoring/loss_aversion/social_proof/default/timing")
context: Mapped[str | None] = mapped_column(Text, nullable=True, comment="助推上下文")
message: Mapped[str] = mapped_column(Text, nullable=False, comment="助推内容")
target_user_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id"), nullable=True)
accepted: Mapped[bool | None] = mapped_column(nullable=True, comment="是否接受")
effect_result: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="效果追踪")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+37
View File
@@ -0,0 +1,37 @@
"""OKR 模型。
投资人与创始人共同制定 OKR + AI 对齐度评分。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class OKR(Base):
"""OKR。"""
__tablename__ = "okrs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
quarter: Mapped[str] = mapped_column(String(10), nullable=False, comment="如 2025-Q1")
objective: Mapped[str] = mapped_column(Text, nullable=False, comment="目标")
key_results: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="关键结果列表 — 含进度")
alignment_score: Mapped[float | None] = mapped_column(nullable=True, comment="对齐度评分(0-100")
deviation_alerts: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="偏差预警")
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True, comment="复盘记录")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="active", comment="active/completed/archived")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+37
View File
@@ -0,0 +1,37 @@
"""同行学习圈模型。
AI 匹配面临类似挑战的创始人,结构化讨论。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class PeerLearningCircle(Base):
"""同行学习圈。"""
__tablename__ = "peer_learning_circles"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
topic: Mapped[str] = mapped_column(String(200), nullable=False, comment="讨论话题")
description: Mapped[str | None] = mapped_column(Text, nullable=True)
members: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="成员列表 — 创始人 ID + 企业 ID")
discussion_framework: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="结构化讨论框架")
conclusions: Mapped[str | None] = mapped_column(Text, nullable=True, comment="讨论结论")
action_commitments: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="行动承诺")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="matching", comment="matching/active/completed")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
@@ -0,0 +1,45 @@
"""组合再平衡 + Monte Carlo 模拟模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class PortfolioRebalancing(Base):
"""组合再平衡建议。"""
__tablename__ = "portfolio_rebalancings"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
marginal_returns: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="各企业边际回报率")
reallocation_plan: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="再平衡方案")
irr_impact: Mapped[float | None] = mapped_column(Float, nullable=True, comment="IRR 影响")
dpi_impact: Mapped[float | None] = mapped_column(Float, nullable=True, comment="DPI 影响")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="proposed", comment="proposed/approved/executed")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
class MonteCarloSimulation(Base):
"""Monte Carlo 模拟结果。"""
__tablename__ = "monte_carlo_simulations"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
iterations: Mapped[int] = mapped_column(nullable=False, default=10000, comment="模拟次数")
irr_distribution: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="IRR 概率分布")
dpi_distribution: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="DPI 概率分布")
percentile_p5: Mapped[float | None] = mapped_column(Float, nullable=True)
percentile_p50: Mapped[float | None] = mapped_column(Float, nullable=True)
percentile_p95: Mapped[float | None] = mapped_column(Float, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+42
View File
@@ -0,0 +1,42 @@
"""Pre-mortem + Red Team 模型。"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class PreMortemRecord(Base):
"""Pre-mortem 失败推演。"""
__tablename__ = "pre_mortem_records"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
decision_context: Mapped[str | None] = mapped_column(Text, nullable=True, comment="决策上下文")
failure_paths: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="失败路径列表")
risk_checklist: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="风险清单")
mitigations: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="缓解措施")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
class RedTeamRecord(Base):
"""Red Team 对抗分析。"""
__tablename__ = "red_team_records"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
perspective: Mapped[str] = mapped_column(String(50), nullable=False, comment="competitor/pessimistic_investor/devils_advocate")
analysis: Mapped[str | None] = mapped_column(Text, nullable=True, comment="对抗分析内容")
vulnerabilities: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="发现的漏洞")
counterarguments: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="反驳论点")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+30
View File
@@ -0,0 +1,30 @@
"""产品竞争力诊断模型。
AI 体验产品 + 竞品对比 + 热力图。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class ProductDiagnostic(Base):
"""产品竞争力诊断。"""
__tablename__ = "product_diagnostics"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
product_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
dimensions: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="竞争力维度评分")
heatmap_data: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="热力图数据")
competitors: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="竞品对比")
roadmap_suggestions: Mapped[str | None] = mapped_column(Text, nullable=True, comment="路线图建议")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+79
View File
@@ -0,0 +1,79 @@
"""多主体画像模型。
投资机构、基金、投资经理画像。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class FirmProfile(Base):
"""投资机构画像。"""
__tablename__ = "firm_profiles"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="机构名称")
focus_areas: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="投资领域")
stage_preference: Mapped[str | None] = mapped_column(String(200), nullable=True, comment="阶段偏好")
description: Mapped[str | None] = mapped_column(Text, nullable=True)
extra_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
class FundProfile(Base):
"""基金画像。"""
__tablename__ = "fund_profiles"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
firm_id: Mapped[str] = mapped_column(String(36), ForeignKey("firm_profiles.id"), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="基金名称")
fund_size: Mapped[str | None] = mapped_column(String(100), nullable=True, comment="基金规模")
vintage_year: Mapped[int | None] = mapped_column(nullable=True, comment="成立年份")
strategy: Mapped[str | None] = mapped_column(Text, nullable=True, comment="投资策略")
extra_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
class ManagerProfile(Base):
"""投资经理画像。"""
__tablename__ = "manager_profiles"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
firm_id: Mapped[str] = mapped_column(String(36), ForeignKey("firm_profiles.id"), nullable=False, index=True)
user_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id"), nullable=True)
name: Mapped[str] = mapped_column(String(100), nullable=False, comment="投资经理姓名")
focus_areas: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="关注领域")
portfolio_count: Mapped[int | None] = mapped_column(nullable=True, comment="在管企业数")
extra_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+39
View File
@@ -0,0 +1,39 @@
"""协同机会模型。
Portfolio 内部协同匹配与效果追踪。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class SynergyOpportunity(Base):
"""协同机会。"""
__tablename__ = "synergy_opportunities"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
type: Mapped[str] = mapped_column(String(50), nullable=False, comment="customer/talent/funding/supply_chain/tech")
company_a_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
company_b_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("companies.id"), nullable=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
match_reason: Mapped[str | None] = mapped_column(Text, nullable=True, comment="AI 匹配理由")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="discovered", comment="discovered/confirmed/authorized/executing/completed/declined")
authorized: Mapped[bool] = mapped_column(default=False, comment="双方是否授权")
effect_result: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="效果评估")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
+59
View File
@@ -0,0 +1,59 @@
"""人才模型。
核心人才画像 + 团队成员 + 9-Box 矩阵 + 流动预测。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class TalentProfile(Base):
"""人才画像。"""
__tablename__ = "talent_profiles"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
current_role: Mapped[str | None] = mapped_column(String(200), nullable=True, comment="当前职位")
current_company: Mapped[str | None] = mapped_column(String(200), nullable=True)
skills: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="技能标签")
experience_years: Mapped[int | None] = mapped_column(Integer, nullable=True)
performance_rating: Mapped[float | None] = mapped_column(nullable=True, comment="绩效评分(1-5")
potential_rating: Mapped[float | None] = mapped_column(nullable=True, comment="潜力评分(1-5")
nine_box: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="9-Box 象限")
flow_prediction: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="流动预测")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="active", comment="active/flowed/inactive")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
class TeamMember(Base):
"""团队成员。"""
__tablename__ = "team_members"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(100), nullable=False)
role: Mapped[str | None] = mapped_column(String(200), nullable=True, comment="职位")
is_key_person: Mapped[bool] = mapped_column(default=False, comment="是否核心人员")
joined_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
left_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
stability_score: Mapped[float | None] = mapped_column(nullable=True, comment="稳定性评分(0-1")
extra_json: Mapped[dict | None] = mapped_column(JSONBType, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+57
View File
@@ -0,0 +1,57 @@
"""任务与评论模型。
投后任务管理 + 评论协作。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class Task(Base):
"""投后任务。"""
__tablename__ = "tasks"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
company_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("companies.id"), nullable=True, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(20), nullable=False, default="todo", comment="todo/in_progress/done/cancelled")
priority: Mapped[str] = mapped_column(String(20), nullable=False, default="medium", comment="low/medium/high/urgent")
assigned_to: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id"), nullable=True)
source_type: Mapped[str | None] = mapped_column(String(50), nullable=True, comment="来源:risk/report/synergy/manual")
source_ref: Mapped[str | None] = mapped_column(String(36), nullable=True, comment="来源记录 ID")
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
class Comment(Base):
"""评论。"""
__tablename__ = "comments"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
tenant_id: Mapped[str] = mapped_column(String(36), ForeignKey("tenants.id"), nullable=False, index=True)
target_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="report/risk/company/task")
target_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True, comment="目标记录 ID")
user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
parent_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("comments.id"), nullable=True, comment="父评论 ID")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+36
View File
@@ -0,0 +1,36 @@
"""弱信号模型。
技术/情绪/组织/市场四类弱信号采集与关联。
"""
import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
from app.core.types import JSONBType
class WeakSignal(Base):
"""弱信号。"""
__tablename__ = "weak_signals"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
company_id: Mapped[str] = mapped_column(String(36), ForeignKey("companies.id"), nullable=False, index=True)
signal_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="technical/sentiment/org/market")
source: Mapped[str | None] = mapped_column(String(200), nullable=True, comment="信号来源")
content: Mapped[str] = mapped_column(Text, nullable=False, comment="信号内容")
confidence: Mapped[float] = mapped_column(Float, nullable=False, default=0.5, comment="置信度(0-1")
correlation_id: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True, comment="关联组 ID")
correlation_result: Mapped[dict | None] = mapped_column(JSONBType, nullable=True, comment="关联分析结果")
risk_probability: Mapped[float | None] = mapped_column(Float, nullable=True, comment="风险概率(0-1")
status: Mapped[str] = mapped_column(String(20), nullable=False, default="new", comment="new/correlated/alerted/dismissed")
detected_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+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)
+90
View File
@@ -52,3 +52,93 @@ class CompanyListResponse(BaseModel):
total: int
page: int
page_size: int
class HealthScoreBrief(BaseModel):
"""健康度评分摘要。"""
total_score: float
financial_score: float | None = None
operational_score: float | None = None
ai_commercial_score: float | None = None
ai_cost_score: float | None
# T2.9 扩展维度
org_talent_score: float | None = None
product_tech_score: float | None = None
market_compete_score: float | None = None
governance_score: float | None = None
financing_score: float | None = None
# T3.11 扩展维度
synergy_score: float | None = None
ai_model_product_score: float | None = None
data_compliance_score: float | None = None
team_tech_score: float | None = None
customer_success_score: float | None = None
trend: str | None = None
calculated_at: datetime
class ReportBrief(BaseModel):
"""月报摘要。"""
id: str
period_year: int
period_month: int
status: str
ai_summary: str | None = None
submitted_at: datetime | None = None
class RiskBrief(BaseModel):
"""风险摘要。"""
id: str
type: str
severity: str
status: str
title: str
identified_at: datetime
class WeakSignalBrief(BaseModel):
"""弱信号摘要。"""
id: str
signal_type: str
content: str
confidence: float
risk_probability: float | None = None
status: str
detected_at: datetime
class AgreementBrief(BaseModel):
"""协议摘要。"""
id: str
title: str
status: str
signed_at: datetime | None = None
class BoardMeetingBrief(BaseModel):
"""董事会会议摘要。"""
id: str
title: str
status: str
meeting_at: datetime | None = None
class CompanyDetailResponse(BaseModel):
"""企业详情聚合响应 — 工作台使用。"""
company: CompanyResponse
health_score: HealthScoreBrief | None = None
recent_reports: list[ReportBrief] = []
open_risks: list[RiskBrief] = []
recent_weak_signals: list[WeakSignalBrief] = []
active_agreements: list[AgreementBrief] = []
recent_board_meetings: list[BoardMeetingBrief] = []
financial_data_count: int = 0
latest_financial: dict | None = None
+30
View File
@@ -0,0 +1,30 @@
"""AI AAR Agent — 五问复盘。"""
from app.services.llm_client import LLMClient
async def generate_aar(trigger_event: str, original_plan: str, actual_result: str) -> dict:
"""AI 生成五问复盘。"""
llm = LLMClient()
prompt = f"""请进行 AAR 五问复盘:
触发事件:{trigger_event}
原计划:{original_plan}
实际结果:{actual_result}
以 JSON 格式返回:
{{"what_happened": "发生了什么", "why_happened": "为什么发生", "what_worked": "什么做得好", "what_failed": "什么没做好", "what_to_change": "下次怎么改", "lessons": ["教训1", "教训2"], "improvements": [{{"action": "改进措施", "owner": "负责人", "deadline": "截止日期"}}]}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {}
async def check_aar_triggers(company_id: str, recent_events: list[dict]) -> list[dict]:
"""检测 AAR 触发条件。"""
triggers: list[dict] = []
for event in recent_events:
if event.get("type") in ["risk_resolved", "funding_completed", "funding_failed", "talent_joined", "talent_left"]:
triggers.append({
"trigger_event": event.get("title", ""),
"trigger_type": event.get("type", ""),
})
return triggers
+3
View File
@@ -0,0 +1,3 @@
"""AAR 触发条件检测。"""
from app.services.aar_agent import check_aar_triggers
+25
View File
@@ -0,0 +1,25 @@
"""Agent 执行引擎。"""
import logging
from datetime import datetime, timezone
logger = logging.getLogger(__name__)
async def execute_agent(agent_name: str, autonomy_level: str, input_data: dict) -> dict:
"""执行 Agent 任务并记录结果。"""
start_time = datetime.now(timezone.utc)
# 实际实现中会调用具体的 Agent
output = {"result": "Agent 执行完成", "agent": agent_name}
duration_ms = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
return {
"agent_name": agent_name,
"autonomy_level": autonomy_level,
"output_summary": str(output)[:200],
"output_detail": output,
"duration_ms": duration_ms,
"executed_at": start_time.isoformat(),
}
@@ -0,0 +1,29 @@
"""Agent 编排引擎 — L1-L4 分级自治。"""
import logging
logger = logging.getLogger(__name__)
AUTONOMY_LEVELS = {
"L1": {"description": "人工审核后执行", "requires_pre_approval": True, "requires_post_review": False},
"L2": {"description": "人工确认后执行", "requires_pre_approval": True, "requires_post_review": False},
"L3": {"description": "事后审核", "requires_pre_approval": False, "requires_post_review": True},
"L4": {"description": "人工决策", "requires_pre_approval": True, "requires_post_review": False},
}
async def orchestrate_agent(agent_name: str, autonomy_level: str, input_data: dict) -> dict:
"""编排 Agent 执行。
根据自治级别决定是否需要人工审核。
"""
level_config = AUTONOMY_LEVELS.get(autonomy_level, AUTONOMY_LEVELS["L1"])
return {
"agent_name": agent_name,
"autonomy_level": autonomy_level,
"requires_approval": level_config["requires_pre_approval"],
"requires_post_review": level_config["requires_post_review"],
"status": "pending_approval" if level_config["requires_pre_approval"] else "executed",
"input_summary": str(input_data)[:200],
}
+41
View File
@@ -0,0 +1,41 @@
"""条款监控引擎。
持续监控触发条件,生成预警。
"""
from datetime import datetime, timezone
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.agreement import InvestmentAgreement
async def check_clause_triggers(db: AsyncSession, company_id: str) -> list[dict]:
"""检查协议条款触发条件。
返回触发的预警列表。
"""
result = await db.execute(
select(InvestmentAgreement).where(
InvestmentAgreement.company_id == company_id,
InvestmentAgreement.status == "active",
)
)
agreements = result.scalars().all()
alerts: list[dict] = []
for agreement in agreements:
if not agreement.monitoring_rules:
continue
for rule in agreement.monitoring_rules:
alerts.append({
"agreement_id": str(agreement.id),
"agreement_title": agreement.title,
"rule": rule.get("rule", ""),
"metric": rule.get("metric", ""),
"threshold": rule.get("threshold", ""),
"triggered_at": datetime.now(timezone.utc).isoformat(),
})
return alerts
+30
View File
@@ -0,0 +1,30 @@
"""投资协议解析 Agent。
解析 PDF → 提取关键条款 → 生成监控规则。
"""
from app.services.llm_client import LLMClient
async def parse_agreement(text_content: str) -> dict:
"""AI 解析投资协议文本,提取关键条款。
返回关键条款和监控规则。
"""
llm = LLMClient()
prompt = f"""请分析以下投资协议文本,提取关键条款并生成监控规则。
协议文本:
{text_content[:8000]}
请以 JSON 格式返回:
{{
"key_clauses": [
{{"name": "条款名称", "content": "条款内容", "trigger_condition": "触发条件"}}
],
"monitoring_rules": [
{{"rule": "监控规则描述", "metric": "关联指标", "threshold": "阈值"}}
]
}}"""
result = await llm.chat(prompt, temperature=0.1)
return result if isinstance(result, dict) else {"key_clauses": [], "monitoring_rules": []}
+20
View File
@@ -0,0 +1,20 @@
"""AI Alpha 归因 Agent。
干预事件 → 指标变化 → 估值影响 → 回报贡献。
"""
from app.services.llm_client import LLMClient
async def attribute_alpha(intervention: dict, metric_changes: dict) -> dict:
"""AI 归因分析 — 将干预事件与指标变化和回报贡献关联。"""
llm = LLMClient()
prompt = f"""请进行投后管理 Alpha 归因分析:
干预事件:{intervention}
指标变化:{metric_changes}
以 JSON 格式返回:
{{"causality_confidence": 0.75, "valuation_impact": 15.0, "return_contribution": 0.12, "alpha_score": 0.68, "evidence": ["证据1", "证据2"], "concerns": ["关注点"]}}"""
result = await llm.chat(prompt, temperature=0.3)
return result if isinstance(result, dict) else {}
+3
View File
@@ -0,0 +1,3 @@
"""异常检测服务。"""
from app.services.predictor import detect_anomalies
+30
View File
@@ -0,0 +1,30 @@
"""AI 董事会 Agent。
会前材料摘要、决议追踪、提问清单生成。
"""
from app.services.llm_client import LLMClient
async def generate_meeting_summary(materials_text: str) -> str:
"""AI 生成会前材料摘要。"""
llm = LLMClient()
prompt = f"""请为董事会会议生成材料摘要,突出关键决策点和风险事项:
{materials_text[:6000]}"""
result = await llm.chat(prompt, temperature=0.3)
return result if isinstance(result, str) else str(result)
async def generate_questions(materials_text: str) -> list[str]:
"""AI 生成董事会提问清单。"""
llm = LLMClient()
prompt = f"""基于以下会议材料,生成董事会成员应关注的关键问题(5-8 个):
{materials_text[:6000]}
以 JSON 数组格式返回:["问题1", "问题2", ...]"""
result = await llm.chat(prompt, temperature=0.4)
if isinstance(result, list):
return result
return ["请补充会议材料以生成提问清单"]
+16
View File
@@ -0,0 +1,16 @@
"""采用生命周期鸿沟诊断。"""
from app.services.llm_client import LLMClient
async def diagnose_chasm(company_data: str) -> dict:
"""AI 诊断早期采用者→早期大众鸿沟。"""
llm = LLMClient()
prompt = f"""请对以下企业进行采用生命周期鸿沟诊断:
{company_data[:5000]}
以 JSON 格式返回:
{{"current_stage": "早期采用者", "chasm_detected": true, "gap_analysis": "鸿沟分析", "crossing_strategy": "跨越策略", "risk_level": "high/medium/low"}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {"chasm_detected": False}
@@ -0,0 +1,37 @@
"""流失风险预警。"""
from datetime import datetime, timezone
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.company import Company
from app.models.report import MonthlyReport
async def detect_churn_risk(db: AsyncSession, tenant_id: str) -> list[dict]:
"""识别企业活跃度下降/数据共享减少/互动减少的早期信号。"""
result = await db.execute(
select(Company).where(Company.tenant_id == tenant_id)
)
companies = result.scalars().all()
risks: list[dict] = []
for company in companies:
# 检查最近月报提交情况
report_result = await db.execute(
select(func.count(MonthlyReport.id))
.where(MonthlyReport.company_id == company.id)
)
report_count = report_result.scalar_one()
if report_count == 0:
risks.append({
"company_id": str(company.id),
"company_name": company.name,
"risk_level": "high",
"signals": ["从未提交月报"],
"detected_at": datetime.now(timezone.utc).isoformat(),
})
return risks
@@ -0,0 +1,16 @@
"""TOC 约束点识别。"""
from app.services.llm_client import LLMClient
async def identify_constraints(company_data: str) -> dict:
"""AI 识别约束点 — 敏感度分析 → 约束点 = 敏感度 × 改善空间。"""
llm = LLMClient()
prompt = f"""请对以下企业数据进行 TOC 约束点识别:
{company_data[:5000]}
以 JSON 格式返回:
{{"constraints": [{{"name": "约束点", "sensitivity": 0.8, "improvement_space": 0.7, "priority_score": 0.56, "action": "改善建议"}}]}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {"constraints": []}
@@ -0,0 +1,3 @@
"""跨基金资源调度优化。"""
from app.services.fund_strategy_analyzer import analyze_fund_strategy
@@ -0,0 +1,20 @@
"""AI 客户增长 Agent。"""
from app.services.llm_client import LLMClient
async def generate_customer_plan(
company_context: str,
lp_resources: str,
) -> dict:
"""AI 分析 LP 资源 + Portfolio 客户网络,生成客户获取方案。"""
llm = LLMClient()
prompt = f"""请基于以下信息生成客户获取方案:
企业上下文:{company_context[:3000]}
LP 资源:{lp_resources[:3000]}
以 JSON 格式返回:
{{"target_customer": "目标客户画像", "entry_angle": "切入角度", "decision_chain": [{{"role": "角色", "name": "姓名", "influence": "高/中/低"}}], "pricing_strategy": "定价策略", "competitive_analysis": {{""strengths": ["优势"], "weaknesses": ["劣势"]}}, "lp_resources": ["可利用资源"]}}"""
result = await llm.chat(prompt, temperature=0.5)
return result if isinstance(result, dict) else {}
@@ -0,0 +1,35 @@
"""AI 决策前哨 Agent。
识别关键决策点 → 场景分析。
"""
from app.services.llm_client import LLMClient
async def identify_decision_points(company_context: str) -> list[dict]:
"""AI 识别企业即将面临的关键决策岔路口。"""
llm = LLMClient()
prompt = f"""基于以下企业上下文,识别该企业即将面临的关键决策岔路口(1-3 个):
{company_context[:6000]}
以 JSON 数组格式返回:
[{{"decision_type": "pivot/hiring/funding/product/org", "title": "决策标题", "description": "描述", "signals": ["触发信号"]}}]"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, list) else []
async def analyze_scenarios(decision: dict) -> dict:
"""AI 生成场景分析 — A 路线 vs B 路线。"""
llm = LLMClient()
prompt = f"""请为以下决策生成场景分析,对比 A 路线和 B 路线:
决策:{decision.get('title', '')}
描述:{decision.get('description', '')}
以 JSON 格式返回:
{{"route_a": {{"description": "A 路线描述", "pros": ["优势"], "cons": ["风险"], "success_probability": 0.7}},
"route_b": {{"description": "B 路线描述", "pros": ["优势"], "cons": ["风险"], "success_probability": 0.5}},
"recommendation": "建议"}}"""
result = await llm.chat(prompt, temperature=0.5)
return result if isinstance(result, dict) else {}
@@ -0,0 +1,33 @@
"""数字孪生引擎。
企业模型 + 场景模拟 + 精度追踪。
"""
from app.services.llm_client import LLMClient
async def build_twin_model(company_data: str) -> dict:
"""构建企业数字孪生模型。"""
llm = LLMClient()
prompt = f"""请基于以下企业数据构建数字孪生模型参数:
{company_data[:5000]}
以 JSON 格式返回:
{{"model_params": {{"revenue_growth_rate": 0.15, "burn_rate": 500000, "runway_months": 18}}, "scenarios": ["融资", "产品转型", "组织调整", "市场变化"], "accuracy_score": 0.75}}"""
result = await llm.chat(prompt, temperature=0.3)
return result if isinstance(result, dict) else {}
async def simulate_scenario(model_params: dict, scenario: str) -> dict:
"""模拟决策场景。"""
llm = LLMClient()
prompt = f"""请基于以下模型参数模拟场景:
模型参数:{model_params}
场景:{scenario}
以 JSON 格式返回:
{{"projected_outcome": "预测结果", "key_metrics": [{{"metric": "指标", "value": ""}}], "risk_assessment": "风险评估", "confidence": 0.7}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {}
+17
View File
@@ -0,0 +1,17 @@
"""邮件发送服务。"""
import logging
logger = logging.getLogger(__name__)
async def send_report_email(to: str, subject: str, report_content: str) -> bool:
"""发送报告邮件。"""
logger.info(f"发送报告邮件 → {to}: {subject}")
return True
async def send_risk_alert_email(to: str, risk_title: str, risk_description: str) -> bool:
"""发送风险预警邮件。"""
logger.info(f"发送风险预警邮件 → {to}: {risk_title}")
return True
+22
View File
@@ -0,0 +1,22 @@
"""文本向量化服务 — 调用千问 embedding API。"""
import logging
from app.core.config import settings
logger = logging.getLogger(__name__)
async def get_embedding(text: str) -> list[float]:
"""获取文本的向量嵌入。"""
try:
from openai import AsyncOpenAI
client = AsyncOpenAI(api_key=settings.llm_api_key, base_url=settings.llm_base_url)
response = await client.embeddings.create(
model="text-embedding-v2",
input=text[:2000],
)
return response.data[0].embedding
except Exception as e:
logger.warning(f"Embedding 获取失败: {e}")
return []
+19
View File
@@ -0,0 +1,19 @@
"""AI 重大事项识别。
从月报/弱信号中提取重大事项。
"""
from app.services.llm_client import LLMClient
async def detect_major_events(report_content: str) -> list[dict]:
"""AI 从月报内容中识别重大事项。"""
llm = LLMClient()
prompt = f"""请从以下月报内容中识别重大事项(融资/人事/产品/法律/市场/组织):
{report_content[:6000]}
以 JSON 数组格式返回:
[{{"event_type": "funding/personnel/product/legal/market/org", "title": "事项标题", "description": "描述", "severity": "low/medium/high/critical"}}]"""
result = await llm.chat(prompt, temperature=0.3)
return result if isinstance(result, list) else []
+16
View File
@@ -0,0 +1,16 @@
"""AI 退出预测 Agent。"""
from app.services.llm_client import LLMClient
async def predict_exit(company_data: str) -> dict:
"""AI 计算退出路径 + 时机窗口 + 期望收益对比。"""
llm = LLMClient()
prompt = f"""请基于以下企业数据进行退出时机预测:
{company_data[:5000]}
以 JSON 格式返回:
{{"exit_path": "ipo/acquisition/secondary/merger", "timing_window": {{"start": "2025-06", "end": "2026-12"}}, "expected_return": 3.5, "hold_return": 2.8, "confidence": 0.7, "signals": ["退出信号1", "信号2"], "recommendation": "退出建议"}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {}
+16
View File
@@ -0,0 +1,16 @@
"""扩展机会识别。"""
from app.services.llm_client import LLMClient
async def identify_expansion_opportunities(company_data: str) -> list[dict]:
"""识别新市场/新产品/新客户群推荐。"""
llm = LLMClient()
prompt = f"""请分析以下企业数据,识别扩展机会:
{company_data[:5000]}
以 JSON 数组格式返回:
[{{"type": "new_market/new_product/new_customer", "title": "机会标题", "description": "描述", "estimated_value": "预估价值", "feasibility": 0.8}}]"""
result = await llm.chat(prompt, temperature=0.5)
return result if isinstance(result, list) else []
+47
View File
@@ -0,0 +1,47 @@
"""文件解析服务。
Excel/PDF 文件解析 → 文本提取。
"""
async def parse_excel(file_bytes: bytes) -> str:
"""解析 Excel 文件,提取文本内容。"""
try:
import openpyxl
import io
wb = openpyxl.load_workbook(io.BytesIO(file_bytes), read_only=True)
texts: list[str] = []
for sheet in wb.sheetnames:
ws = wb[sheet]
for row in ws.iter_rows(values_only=True):
row_text = " | ".join(str(c) for c in row if c is not None)
if row_text.strip():
texts.append(row_text)
return "\n".join(texts)
except Exception:
return ""
async def parse_pdf(file_bytes: bytes) -> str:
"""解析 PDF 文件,提取文本内容。"""
try:
import fitz
import io
doc = fitz.open(stream=io.BytesIO(file_bytes), filetype="pdf")
texts: list[str] = []
for page in doc:
texts.append(page.get_text())
return "\n".join(texts)
except Exception:
return ""
async def parse_file(file_bytes: bytes, filename: str) -> str:
"""根据文件类型选择解析器。"""
if filename.endswith((".xlsx", ".xls")):
return await parse_excel(file_bytes)
elif filename.endswith(".pdf"):
return await parse_pdf(file_bytes)
elif filename.endswith((".txt", ".md", ".csv")):
return file_bytes.decode("utf-8", errors="ignore")
return ""
@@ -0,0 +1,69 @@
"""财务数据校验 Agent。
交叉验证不同来源数据一致性、检测报表内部逻辑矛盾、追踪历史数据修订。
"""
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.financial_data import FinancialData
async def validate_financial_data(
db: AsyncSession,
company_id: str,
period_year: int,
period_month: int,
) -> dict:
"""校验财务数据 — 内部一致性、跨期一致性、历史偏差。
返回校验结果和可信度评分。
"""
result = await db.execute(
select(FinancialData)
.where(
FinancialData.company_id == company_id,
FinancialData.period_year == period_year,
FinancialData.period_month == period_month,
)
)
statements = result.scalars().all()
if not statements:
return {"credibility_score": 0.0, "issues": ["无财务数据"], "checks_passed": 0, "checks_total": 0}
issues: list[str] = []
checks_passed = 0
checks_total = 0
# 内部一致性检查:资产 = 负债 + 权益
for stmt in statements:
if stmt.statement_type == "balance_sheet" and stmt.data_json:
checks_total += 1
assets = stmt.data_json.get("total_assets")
liabilities = stmt.data_json.get("total_liabilities")
equity = stmt.data_json.get("total_equity")
if assets is not None and liabilities is not None and equity is not None:
if abs(assets - (liabilities + equity)) < max(assets * 0.01, 100):
checks_passed += 1
else:
issues.append(f"资产负债表不平:资产 {assets} ≠ 负债 {liabilities} + 权益 {equity}")
# 跨期一致性检查
checks_total += 1
income_stmts = [s for s in statements if s.statement_type == "income"]
if len(income_stmts) >= 1 and income_stmts[0].data_json:
revenue = income_stmts[0].data_json.get("revenue")
if revenue is not None and revenue < 0:
issues.append("收入为负数,数据异常")
else:
checks_passed += 1
credibility = round(checks_passed / max(checks_total, 1) * 100, 1)
return {
"credibility_score": credibility,
"issues": issues,
"checks_passed": checks_passed,
"checks_total": checks_total,
}
+45
View File
@@ -0,0 +1,45 @@
"""AI 创始人副驾驶(完整版)。
融资规划/组织诊断/投资人沟通/战略规划/月报自动生成。
"""
from app.services.llm_client import LLMClient
async def financing_planner(company_data: str) -> dict:
"""AI 融资规划。"""
llm = LLMClient()
prompt = f"""请为以下企业生成融资规划建议:
{company_data[:4000]}
以 JSON 格式返回:
{{"round": "轮次", "target_amount": "目标金额", "valuation_range": "估值范围", "timeline": "时间节奏", "target_investors": ["目标投资人画像"], "key_metrics": ["需突出的关键指标"]}}"""
result = await llm.chat(prompt, temperature=0.5)
return result if isinstance(result, dict) else {}
async def org_diagnostic(team_data: str) -> dict:
"""AI 组织诊断。"""
llm = LLMClient()
prompt = f"""请分析以下团队数据,进行组织诊断:
{team_data[:4000]}
以 JSON 格式返回:
{{"structure_assessment": "结构评估", "key_role_risks": [{{"role": "关键岗位", "risk": "风险描述", "severity": "high/medium/low"}}], "talent_gaps": ["人才缺口"], "recommendations": ["建议"]}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {}
async def investor_comm_prep(board_context: str) -> dict:
"""AI 投资人沟通准备。"""
llm = LLMClient()
prompt = f"""请为以下董事会/投资人沟通生成准备材料:
{board_context[:4000]}
以 JSON 格式返回:
{{"board_material_outline": "董事会材料大纲", "anticipated_questions": [{{"question": "预期问题", "suggested_answer": "建议回答"}}], "key_updates": ["关键进展"], "asks": ["需要投资人支持的请求"]}}"""
result = await llm.chat(prompt, temperature=0.5)
return result if isinstance(result, dict) else {}
@@ -0,0 +1,16 @@
"""基金策略分析 + 跨基金资源调度 + LP 报告生成。"""
from app.services.llm_client import LLMClient
async def analyze_fund_strategy(funds_data: str) -> dict:
"""基金策略分析 — 不同基金策略/期限/退出要求对比。"""
llm = LLMClient()
prompt = f"""请分析以下基金策略:
{funds_data[:5000]}
以 JSON 格式返回:
{{"strategy_comparison": [{{"fund": "基金名称", "strategy": "策略", "vintage": 2020, "exit_requirement": "退出要求"}}], "resource_allocation_suggestions": ["调度建议"], "lp_report_summary": "LP 报告摘要"}}"""
result = await llm.chat(prompt, temperature=0.4)
return result if isinstance(result, dict) else {}
+236 -31
View File
@@ -1,10 +1,9 @@
"""健康度评分计算引擎。
基于月报结构化数据,计算四维评分:
- 财务健康度(financial_score
- 经营健康度(operational_score
- AI 商业化度(ai_commercial_score
- AI 成本效率(ai_cost_score
基于月报结构化数据,计算多维度评分:
- 基础 4 维度:财务、经营、AI 商业化、AI 成本
- T2.9 扩展 5 维度:组织人才、产品技术、市场竞争、治理合规、融资资本
- T3.11 扩展 5 维度:协同赋能、AI 模型产品、数据合规、团队技术、客户成功
总分 = 加权平均,输出 0-100 分。
"""
@@ -14,12 +13,22 @@ from typing import Any
logger = logging.getLogger(__name__)
# 权重配置
# 14 维度权重配置
WEIGHTS = {
"financial": 0.35,
"operational": 0.25,
"ai_commercial": 0.25,
"ai_cost": 0.15,
"financial": 0.15,
"operational": 0.10,
"ai_commercial": 0.10,
"ai_cost": 0.05,
"org_talent": 0.10,
"product_tech": 0.10,
"market_compete": 0.10,
"governance": 0.05,
"financing": 0.05,
"synergy": 0.05,
"ai_model_product": 0.05,
"data_compliance": 0.05,
"team_tech": 0.03,
"customer_success": 0.07,
}
@@ -166,14 +175,187 @@ def _calc_ai_cost_score(data: dict[str, Any]) -> float:
return max(0, min(100, score))
def _calc_org_talent_score(data: dict[str, Any]) -> float:
"""计算组织人才健康度。
指标:团队规模变化、流失率、关键岗位填补。
"""
score = 60.0
headcount = data.get("headcount", {})
new_hires = _safe_float(headcount.get("new_hires"))
departures = _safe_float(headcount.get("departures"))
total = _safe_float(headcount.get("total"), 1)
if total > 0:
turnover_rate = departures / total
if turnover_rate < 0.05:
score += 20
elif turnover_rate < 0.10:
score += 10
elif turnover_rate > 0.20:
score -= 20
elif turnover_rate > 0.15:
score -= 10
if new_hires > 0:
score += 10
return max(0, min(100, score))
def _calc_product_tech_score(data: dict[str, Any]) -> float:
"""计算产品技术健康度。
指标:产品迭代频率、技术指标达成。
"""
score = 55.0
key_metrics = data.get("key_metrics", [])
tech_metrics = [
m for m in key_metrics
if any(k in str(m.get("name", "")).lower()
for k in ["产品", "product", "迭代", "release", "技术", "tech"])
]
if tech_metrics:
for m in tech_metrics:
change = str(m.get("change", ""))
val = _safe_float(change.replace("%", "").replace("+", ""))
if val > 0:
score += 12
elif val < 0:
score -= 8
else:
score = 50.0
return max(0, min(100, score))
def _calc_market_compete_score(data: dict[str, Any]) -> float:
"""计算市场竞争健康度。
指标:市场份额变化、竞品动态、客户增长。
"""
score = 55.0
key_metrics = data.get("key_metrics", [])
market_metrics = [
m for m in key_metrics
if any(k in str(m.get("name", ""))
for k in ["市场", "份额", "客户", "竞品", "MAU", "DAU", "GMV"])
]
if market_metrics:
for m in market_metrics:
change = str(m.get("change", ""))
val = _safe_float(change.replace("%", "").replace("+", ""))
if val > 0:
score += 12
elif val < 0:
score -= 8
return max(0, min(100, score))
def _calc_governance_score(data: dict[str, Any]) -> float:
"""计算治理合规健康度。
指标:董事会召开频率、合规事件。
"""
score = 70.0
governance = data.get("governance", {})
if governance.get("board_meeting_held"):
score += 10
if governance.get("compliance_issues"):
score -= 20
return max(0, min(100, score))
def _calc_financing_score(data: dict[str, Any]) -> float:
"""计算融资资本健康度。
指标:现金跑道、融资进度。
"""
score = 55.0
cash = data.get("cash_balance", {})
runway = _safe_float(cash.get("runway_months"))
if runway >= 18:
score += 25
elif runway >= 12:
score += 15
elif runway >= 6:
score += 5
elif runway < 3:
score -= 25
financing = data.get("financing", {})
if financing.get("in_progress"):
score += 10
return max(0, min(100, score))
def _calc_synergy_score(data: dict[str, Any]) -> float:
"""计算协同赋能健康度(T3.11)。"""
score = 55.0
synergy = data.get("synergy", {})
if synergy.get("active_count", 0) > 0:
score += min(20, synergy.get("active_count", 0) * 5)
if synergy.get("completed_count", 0) > 0:
score += 10
return max(0, min(100, score))
def _calc_ai_model_product_score(data: dict[str, Any]) -> float:
"""计算 AI 模型产品健康度(T3.11)。"""
score = 50.0
ai_data = data.get("ai_metrics", {})
if ai_data.get("model_accuracy"):
score += 15
if ai_data.get("inference_cost_trend") == "down":
score += 10
if ai_data.get("data_quality_score"):
score += min(15, _safe_float(ai_data.get("data_quality_score")) * 0.15)
return max(0, min(100, score))
def _calc_data_compliance_score(data: dict[str, Any]) -> float:
"""计算数据合规健康度(T3.11)。"""
score = 70.0
compliance = data.get("data_compliance", {})
if compliance.get("issues_count", 0) > 0:
score -= min(30, compliance.get("issues_count", 0) * 10)
if compliance.get("audit_passed"):
score += 15
return max(0, min(100, score))
def _calc_team_tech_score(data: dict[str, Any]) -> float:
"""计算团队技术健康度(T3.11)。"""
score = 55.0
team = data.get("team_tech", {})
if team.get("tech_lead_count", 0) > 0:
score += 15
if team.get("patent_count", 0) > 0:
score += min(15, team.get("patent_count", 0) * 3)
return max(0, min(100, score))
def _calc_customer_success_score(data: dict[str, Any]) -> float:
"""计算客户成功健康度(T3.11)。"""
score = 55.0
cs = data.get("customer_success", {})
retention = _safe_float(cs.get("retention_rate"), -1)
if retention >= 0:
if retention >= 0.90:
score += 25
elif retention >= 0.80:
score += 15
elif retention < 0.70:
score -= 15
nps = _safe_float(cs.get("nps"))
if nps > 0:
score += min(15, nps * 0.15)
return max(0, min(100, score))
def calculate_health_score(structured_data: dict[str, Any]) -> dict[str, float]:
"""计算四维健康度评分。
"""计算 14 维度健康度评分。
Args:
structured_data: 月报 AI 解析后的结构化数据
Returns:
包含 total_score 和个维度分数的字典
包含 total_score 和 14 个维度分数的字典
"""
if not structured_data:
return {
@@ -182,29 +364,52 @@ def calculate_health_score(structured_data: dict[str, Any]) -> dict[str, float]:
"operational_score": 0.0,
"ai_commercial_score": 0.0,
"ai_cost_score": 0.0,
"org_talent_score": 0.0,
"product_tech_score": 0.0,
"market_compete_score": 0.0,
"governance_score": 0.0,
"financing_score": 0.0,
"synergy_score": 0.0,
"ai_model_product_score": 0.0,
"data_compliance_score": 0.0,
"team_tech_score": 0.0,
"customer_success_score": 0.0,
}
financial = _calc_financial_score(structured_data)
operational = _calc_operational_score(structured_data)
ai_commercial = _calc_ai_commercial_score(structured_data)
ai_cost = _calc_ai_cost_score(structured_data)
total = (
financial * WEIGHTS["financial"]
+ operational * WEIGHTS["operational"]
+ ai_commercial * WEIGHTS["ai_commercial"]
+ ai_cost * WEIGHTS["ai_cost"]
)
result = {
"total_score": round(total, 1),
"financial_score": round(financial, 1),
"operational_score": round(operational, 1),
"ai_commercial_score": round(ai_commercial, 1),
"ai_cost_score": round(ai_cost, 1),
scores = {
"financial_score": _calc_financial_score(structured_data),
"operational_score": _calc_operational_score(structured_data),
"ai_commercial_score": _calc_ai_commercial_score(structured_data),
"ai_cost_score": _calc_ai_cost_score(structured_data),
"org_talent_score": _calc_org_talent_score(structured_data),
"product_tech_score": _calc_product_tech_score(structured_data),
"market_compete_score": _calc_market_compete_score(structured_data),
"governance_score": _calc_governance_score(structured_data),
"financing_score": _calc_financing_score(structured_data),
"synergy_score": _calc_synergy_score(structured_data),
"ai_model_product_score": _calc_ai_model_product_score(structured_data),
"data_compliance_score": _calc_data_compliance_score(structured_data),
"team_tech_score": _calc_team_tech_score(structured_data),
"customer_success_score": _calc_customer_success_score(structured_data),
}
logger.info("健康度评分计算完成: %s", result)
weight_keys = [
"financial", "operational", "ai_commercial", "ai_cost",
"org_talent", "product_tech", "market_compete", "governance", "financing",
"synergy", "ai_model_product", "data_compliance", "team_tech", "customer_success",
]
score_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",
]
total = sum(scores[sk] * WEIGHTS[wk] for sk, wk in zip(score_keys, weight_keys))
scores["total_score"] = round(total, 1)
result = {k: round(v, 1) for k, v in scores.items()}
logger.info("健康度评分计算完成(14 维度): %s", result)
return result

Some files were not shown because too many files have changed in this diff Show More