Files
Profeto/docs/01-architecture.md
T
WorkBuddy 80616cf459 docs: 全套文档与代码对齐(P1-1,数据源收敛 bzzoiro 后的存量漂移清理)
- 数据源:全部文档统一为 bzzoiro 唯一来源,删除 understat/api-football
  作为现行数据源的表述(仅保留历史注记);.env.example/docs/06 移除
  失效的 API_FOOTBALL_KEY
- 多 Agent:injuries 阵容完整性 → standings 联赛排名
  (README/01/04 的专家表、数据流图、no_data 示例、agent_weights、
  prompt 清单全部对齐 form/stats/home_away/standings/h2h)
- 数据库:6 张表 → 12 张表,补 standings/app_settings/schedules 与
  4 张治理表说明(标注 raw_events/data_quality_checks/data_lineage
  为预留未启用,ingest_failures 已启用),predictions 补 agent_weights 列
- API 文档(03):新增鉴权模型三档表;context/standings/leagues 公开端点
  补全;predictions/eval/backtest 标注需管理员;ingest/bzzoiro 补
  task/limit/season 参数契约,删除 understat/injuries 端点小节
- 目录树(README/01/07):与真实 src/、frontend/src 结构一致
  (core 7 文件、data 7 文件、移除不存在的 retry.py)
- 采集命令(02/05):understat 回填 → task=stats/standings;
  补管理员凭据提示;docs/08 文首标注历史/过时
2026-09-21 20:55:48 +08:00

8.1 KiB

01 · 架构总览

系统架构

┌─────────────────────────────────────────────────────┐
│  前端(单页 React + Vite + Tailwind)                   │
│  比赛列表 → 选比赛 → LLM 预测 → 专家报告 + 最终结论     │
└──────────────────────────▲──────────────────────────┘
                           │ REST (/api/v1)
┌──────────────────────────┴──────────────────────────┐
│  FastAPI(单进程,全 async)                            │
│                                                     │
│  数据查询   预测编排          采集(手动/cron 触发)    │
│  ┌──────┐  ┌────────────┐  ┌───────────────────┐    │
│  │matches│  │ orchestrator│  │ bzzoiro (唯一源)  │    │
│  │leagues│  │  ┌─ 5 专家并行(便宜模型)            │    │
│  └──┬───┘  │  │   h2h / form / stats /         │    │
│     │      │  │   home_away / standings        │    │
│     │      │  └─ aggregator 终裁(强模型)        │    │
│     │      └────────────┘  │ events / standings │    │
│  ┌──┴──────────────┴──┐   │ /stats 三条管线     │    │
│  │ PostgreSQL (12 张表)│   └───────────────────┘    │
│  └────────────────────┘        httpx → 外部 API     │
└─────────────────────────────────────────────────────┘

核心数据流(一次多 Agent 预测)

  1. POST /predict {match_id} → orchestrator
  2. load_match_header: 查比赛 + 双方 + 联赛(一次 eager load)
  3. 5 个专家 agent 并行(asyncio.gather),每个:
    • 各自的数据切片函数查库(近况/交锋/积分榜聚合/射门控球/xG)
    • 切片无数据 → 跳过 LLM,直接 no_data stub(省 token、防幻觉)
    • 有数据 → 专属 prompt(专家模型,便宜快)→ 结构化 JSON 报告(home_edge 方向性评分 + 证据)
  4. 终裁 agent:5 份报告 + 比赛信息 → 权衡采信度(agent_weights)→ 最终预测 JSON
  5. predictions 表(含 agent_outputs 全部报告)
  6. 赛后 POST /eval/settle 回填实际比分 → GET /eval/summary 按 模型×prompt 版本 聚合准确率

关键设计决策

决策 理由
多专家并行而非单次大 prompt 每维度独立迭代 prompt;报告可归因(哪个维度分析错了);总延迟 ≈ 2 次串行调用
专家/终裁模型分档 专家用便宜模型快速分析,终裁用强模型汇总决策,成本与质量平衡(LLM_SPECIALIST_MODEL / LLM_AGGREGATOR_MODEL)
no_data 门控 无数据维度(如积分榜未采集)不调 LLM,终裁知道维度缺失,不编造
fail-open 单个专家失败只标记 status=error,其余照常;研究场景可用性优先
match_date_date 天级去重 不同源时间精度不同,秒级匹配会产生重复行;天级 + 数据库唯一约束
积分榜 SQL 聚合 + season 过滤 UNION ALL 主客双视角 + GROUP BY 在库内算,只算当前赛季(修复过跨赛季 bug)
单 agent 模式保留 mode="single" 走旧单次路径,与 multi 形成天然 A/B(eval 按 prompt_version 分组)
无 worker/redis/队列 采集是 cron 触发的短任务,单进程足够;违背简化初衷的基础设施一律不加

目录结构

Profeto/
├── src/
│   ├── api/
│   │   ├── app.py               # FastAPI 工厂(lifespan:迁移校验/定时任务/生产限流提醒)
│   │   ├── deps.py              # 依赖:管理接口鉴权(Cookie/X-API-Key)+ 限流
│   │   ├── schemas.py           # Pydantic v2 请求/响应
│   │   └── routes/
│   │       ├── matches.py       # 联赛/比赛/上下文/积分榜(公开只读)
│   │       ├── predict.py       # 预测(限流)+ 预测历史(需鉴权)
│   │       ├── ingest.py        # 采集触发(需鉴权)
│   │       ├── eval.py          # 赛后回填 + 准确率汇总(需鉴权)
│   │       ├── backtest.py      # 历史回测(需鉴权)
│   │       ├── auth.py          # 登录/登出/改密
│   │       ├── admin_settings.py # /admin/** 配置/日志/数据质量(router 级鉴权)
│   │       └── schedules.py     # 定时任务 + 死信重试(router 级鉴权)
│   ├── db/
│   │   ├── base.py              # async engine + get_db/get_db_read
│   │   ├── models.py            # 12 张表 ORM
│   │   ├── repositories.py      # 仓储层
│   │   └── unit_of_work.py      # 事务边界
│   ├── data/
│   │   ├── bzzoiro.py           # 唯一数据源:events/standings/stats 三管线 + Bronze 层
│   │   ├── normalize.py         # NormalizedMatch 清洗契约
│   │   ├── team_names.py        # 队名归一映射
│   │   ├── team_names_zh.py     # 队名中文名映射
│   │   ├── key_ring.py          # 多 key 轮换(429 冷却,进程内)
│   │   ├── sources.py           # 数据源注册表
│   │   └── config.py            # 联赛代码映射
│   ├── llm/
│   │   ├── provider.py          # OpenAI-compatible 抽象(共享连接池/JSON 兜底解析)
│   │   ├── context_builder.py   # 数据切片(h2h/form/stats/home_away/standings)+ 单 agent 拼接
│   │   ├── predict.py           # 预测入口(mode 分派 + 缓存)
│   │   ├── baseline.py          # 基线预测(均值模型,mode=baseline)
│   │   ├── eval.py              # 准确率统计
│   │   ├── utils.py             # LLM 工具函数
│   │   ├── validation.py        # LLM 输出严格校验(Pydantic)
│   │   ├── backtest.py          # 回测执行
│   │   ├── agents/
│   │   │   ├── base.py          # AgentSpec / AgentReport / run_agent
│   │   │   └── orchestrator.py  # 并行专家 → 终裁 → 存库
│   │   └── prompts/
│   │       ├── match_prediction_v1/v2.md   # 单 agent 模板
│   │       └── agents/{form,stats,home_away,standings,h2h,aggregator}_v1.md
│   └── core/
│       ├── config.py            # pydantic-settings
│       ├── crypto.py            # 加密/哈希
│       ├── http_client.py       # 共享 httpx 客户端
│       ├── log_buffer.py        # 内存日志缓冲(admin 日志页)
│       ├── runtime_config.py    # DB 配置覆盖(app_settings)
│       ├── scheduler.py         # 进程内 cron 调度器
│       └── security_check.py    # 启动安全校验
├── alembic/versions/            # 0001~0018(建表 → Bronze 层 → 单一数据源 → 基线模式等)
├── frontend/src/                # pages/(公开站) + admin/(管理后台) + lib/http.ts(唯一 HTTP 实现)
├── tests/                       # 核心 + agent 测试(250+ 项,自包含)
├── docker-compose.yml           # api + postgres 两容器
└── docs/                        # 本文档

技术栈

选型
API FastAPI + uvicorn(全 async)
ORM SQLAlchemy 2.0 async + asyncpg
数据库 PostgreSQL 16(JSONB 存 agent 报告)
HTTP httpx(共享连接池)/ urllib(bzzoiro 同步限速)
LLM OpenAI-compatible 接口(openai/deepseek/ollama 等任一)
前端 Vite + React 18 + TypeScript + Tailwind
测试 pytest + pytest-asyncio(250+ 项,自包含)
部署 Docker Compose(api + postgres)