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:
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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"},
|
||||
)
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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="建议动作")
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
])
|
||||
@@ -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)
|
||||
@@ -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="审核完成")
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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="创建成功")
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
])
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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="授权成功")
|
||||
@@ -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
|
||||
])
|
||||
@@ -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="创建成功")
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
"""AAR 触发条件检测。"""
|
||||
|
||||
from app.services.aar_agent import check_aar_triggers
|
||||
@@ -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],
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": []}
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,3 @@
|
||||
"""异常检测服务。"""
|
||||
|
||||
from app.services.predictor import detect_anomalies
|
||||
@@ -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 ["请补充会议材料以生成提问清单"]
|
||||
@@ -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 {}
|
||||
@@ -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
|
||||
@@ -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 []
|
||||
@@ -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 []
|
||||
@@ -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 {}
|
||||
@@ -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 []
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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
Reference in New Issue
Block a user