e15b554ba3b8ff4a2997e911938ea60681db9300
events/standings 创建 Team 前均经 team_names.normalize(已有,确认), TeamRepository.get_or_create 收敛为归一化唯一咽喉 + info 日志。 新增 team_aliases 表(NFKD 归一别名 → teams.id FK CASCADE), 定位三步链:normalize(name) → teams.name → team_aliases → insert。 不自动合并历史重复队;提供 POST /api/v1/admin/teams/aliases 显式添加。 迁移 0020_team_aliases + Admin 别名管理端点(admin_teams.py)。 全量测试 270 通过。
Profeto — 先知
给 LLM 提供结构化数据,让 LLM 预测足球比分。
架构
┌─────────────────────────────────────────────────────┐
│ 前端 (React + Vite + Tailwind) │
│ http://localhost:5173 │
└──────────────────────┬──────────────────────────────┘
│ REST API
┌──────────────────────▼──────────────────────────────┐
│ FastAPI │
│ ├── /api/v1/matches 比赛查询(公开只读) │
│ ├── /api/v1/predict LLM 预测 (单/多 Agent) │
│ ├── /api/v1/ingest/* 数据采集(需管理员) │
│ ├── /api/v1/eval/* 评估回填(需管理员) │
│ └── /api/v1/backtest 回测(需管理员) │
└──────────┬─────────────────────────────┬────────────┘
│ │
┌──────────▼──────────┐ ┌─────────────▼────────────┐
│ PostgreSQL │ │ LLM (OpenAI-compatible) │
│ 12 张表 │ │ OpenAI / Deepseek / │
│ leagues/teams/ │ │ Ollama / 任意网关 │
│ matches/match_ │ └──────────────────────────┘
│ stats/standings/ │
│ predictions/ │
│ app_settings/ │
│ schedules + │
│ raw_events 等 4 张 │
│ 数据治理表 │
└─────────────────────┘
▲
│ 采集
┌──────────┴─────────────────────────────────────────┐
│ 数据源 (DataSource 协议 + 注册表) │
│ └── bzzoiro 比分 / 赛程 / 统计 / 积分榜 │
└────────────────────────────────────────────────────┘
分层架构
API Route → Application Service → Repository → UnitOfWork → DB
- UnitOfWork: 统一事务边界,业务层不再自行 commit
- Repository: 封装数据访问,提供类型化查询接口
- DataSource: 采集外部数据,通过注册表动态分发
多 Agent 预测
默认模式 (mode=multi) 采用 5 专家 + 终裁 架构:
比赛数据 → 切片 ─┬─→ A 近期状态专家 ─┐
├─→ B 攻防数据专家 ─┤
├─→ C 主客因素专家 ─┼─→ 终裁专家 ─→ 最终预测
├─→ D 联赛排名专家 ─┤
└─→ E 历史交锋专家 ─┘
- 各专家只看到自己维度的数据切片,避免信息过载
- fail-open: 单个专家失败不影响整体
- no_data 门控: 无数据维度跳过 LLM 调用,省 token 防幻觉
- 终裁根据各报告的
subjective_confidence/data_sufficiency输出agent_weights
数据正确性保障
- Cutoff 机制: 回测时只使用
cutoff_at之前已采集的数据(近况/交锋/统计/积分榜切片统一生效) - LLM 输出校验: Pydantic 严格校验 + 语义一致性检查
- 数据库约束: CHECK 约束作为最后一道防线
快速开始
前置条件
- Python >= 3.11
- Docker (运行 PostgreSQL)
- LLM API Key (OpenAI / Deepseek / Ollama 等)
方式一:Docker Compose 部署(推荐)
# 1. 克隆仓库
git clone https://git.bilidili.cn/shangfangjian/Profeto.git
cd Profeto
# 2. 配置环境变量
cp .env.example .env
# 编辑 .env,填入 LLM_API_KEY 和 BZZOIRO_KEY
# 3. 启动全部服务(自动构建 + 执行迁移)
docker compose up -d --build
# 4. 验证
curl http://localhost:8000/health
启动后访问:
- API 文档: http://localhost:8000/docs
- 前端界面: http://localhost:3000
说明:
api容器启动时自动执行alembic upgrade head,无需手动运行迁移。
方式二:本地开发部署
# 1. 克隆 + 安装
git clone https://git.bilidili.cn/shangfangjian/Profeto.git
cd Profeto
pip install -e ".[dev]"
# 2. 配置环境变量
cp .env.example .env
# 编辑 .env,填入 LLM_API_KEY 和 BZZOIRO_KEY
# 3. 启动 PostgreSQL
docker compose up -d postgres
# 4. 执行迁移
alembic upgrade head
# 5. 启动后端 (终端 1)
uvicorn src.api.app:app --reload
# 6. 启动前端 (终端 2)
cd frontend && npm install && npm run dev
后端运行在 http://localhost:8000,前端在 http://localhost:5173。
生产上线检查清单
公网部署前逐项确认(第 1–4 项由启动校验强制,不满足拒绝启动;详见 docs/06-deployment.md):
APP_ENV=production(安全校验 / CookieSecure/ 管理端点 fail-closed 的总开关)SECRET_KEY强随机:openssl rand -base64 32,禁止弱值ADMIN_PASSWORD或ADMIN_API_KEY至少配置其一- 数据库强密码,禁止
football:football等示例弱密码 - HTTPS(反代终结 TLS;production 下会话 Cookie 自动
Secure) - 反代后设
TRUST_PROXY_HEADERS=True,仅可信反代可达 API,并配置X-Forwarded-For/X-Real-IP - 限流前置到 Nginx
limit_req;应用内限流与 KeyRing 仅单进程有效,多 worker 会放大配额 - uvicorn 单 worker(默认);需扩容先网关统一限流再起多实例
- 启动后验证
/health与/health/ready均 200 - 数据库迁移已内置:compose/Dockerfile 启动即执行
alembic upgrade head
安全与限流
/api/v1/predict: 内存滑动窗口限流(10 次/分钟/IP),多 worker 时每进程独立计数- 登录防爆破: 进程内内存计数,同上
- 公网部署建议 Nginx 层限流 +
TRUST_PROXY_HEADERS=True - 生产环境必须配置
ADMIN_PASSWORD或ADMIN_API_KEY(否则管理接口 503)
API 概览
公开只读(无需登录;predict 带内存限流):
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/leagues |
联赛列表(仅 id/code/name/country) |
| GET | /api/v1/matches |
比赛查询(筛选/游标分页) |
| GET | /api/v1/matches/{id} |
比赛详情(含统计与最近预测) |
| GET | /api/v1/matches/{id}/context |
比赛上下文(双方近况 + 历史交锋) |
| GET | /api/v1/standings |
联赛积分榜 |
| POST | /api/v1/predict |
LLM 预测 (mode=single/multi/baseline) |
| GET | /health、/health/ready |
存活 / 就绪检查(含 DB) |
需管理员(Cookie 会话或 X-API-Key):
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/ingest/bzzoiro |
采集赛果/赛程/统计/积分榜 |
| GET | /api/v1/predictions |
预测历史(列表) |
| GET | /api/v1/predictions/{id} |
单条预测详情 |
| POST | /api/v1/eval/settle |
回填实际结果 |
| GET | /api/v1/eval/summary |
准确率汇总 |
| POST | /api/v1/backtest |
历史回测 |
/api/v1/admin/** |
配置/采集状态/日志/定时任务/死信等 | 管理后台(router 级鉴权) |
项目结构
Profeto/
├── src/
│ ├── api/ # FastAPI 路由层
│ │ ├── app.py # 应用工厂 + lifespan
│ │ ├── deps.py # 依赖注入:鉴权 / 限流
│ │ ├── schemas.py # Pydantic 请求/响应模型
│ │ └── routes/
│ │ ├── matches.py # 比赛查询(公开只读)
│ │ ├── predict.py # 预测入口 + 预测历史
│ │ ├── ingest.py # 数据采集(需管理员)
│ │ ├── eval.py # 评估回填(需管理员)
│ │ ├── backtest.py # 回测(需管理员)
│ │ ├── auth.py # 登录/登出/改密
│ │ ├── admin_settings.py # /admin/** 配置/日志/数据质量(router 级鉴权)
│ │ └── schedules.py # 定时任务 + 死信重试(router 级鉴权)
│ ├── core/ # 基础设施
│ │ ├── config.py # pydantic-settings 配置
│ │ ├── crypto.py # 加密/哈希
│ │ ├── http_client.py # 共享 httpx 客户端
│ │ ├── log_buffer.py # 内存日志缓冲(admin 日志页)
│ │ ├── runtime_config.py # DB 配置覆盖(.env → app_settings)
│ │ ├── scheduler.py # 进程内 cron 调度器
│ │ └── security_check.py # 启动安全校验
│ ├── data/ # 数据层
│ │ ├── sources.py # DataSource 协议 + 注册表
│ │ ├── bzzoiro.py # bzzoiro 数据源(events/standings/stats)
│ │ ├── normalize.py # 数据规范化契约
│ │ ├── config.py # 联赛映射常量
│ │ ├── key_ring.py # API Key 轮换环(429 冷却)
│ │ ├── team_names.py # 队名归一化
│ │ └── team_names_zh.py # 队名中文映射
│ ├── db/ # 数据库
│ │ ├── base.py # SQLAlchemy async engine
│ │ ├── models.py # ORM 模型 (12 表)
│ │ ├── unit_of_work.py # UnitOfWork 事务封装
│ │ └── repositories.py # Repository 数据访问
│ └── llm/ # LLM 预测核心
│ ├── predict.py # 预测服务 (缓存 + 单/多/基线模式)
│ ├── context_builder.py # 数据切片 + 上下文拼接
│ ├── baseline.py # 基线预测(均值模型)
│ ├── eval.py # 评估统计
│ ├── backtest.py # 回测框架
│ ├── provider.py # 多提供商 LLM 抽象
│ ├── utils.py # LLM 工具函数
│ ├── validation.py # LLM 输出校验
│ ├── agents/
│ │ ├── base.py # Agent 基础设施 + 解析
│ │ └── orchestrator.py # 多 Agent 编排
│ └── prompts/ # Prompt 模板
├── alembic/ # 数据库迁移
├── frontend/ # React 前端
│ └── src/
│ ├── pages/ # 公开站(赛程 Matches + 积分榜 Standings)
│ ├── admin/ # 管理后台(布局/页面/数据访问层 dal.ts)
│ ├── components/ # 共享组件
│ └── lib/http.ts # 唯一 HTTP 实现(带凭据/超时/错误处理)
├── docs/ # 详细文档
├── tests/ # 单元测试
├── docker-compose.yml
├── Dockerfile
└── pyproject.toml
核心模块
| 模块 | 作用 |
|---|---|
context_builder.py |
最重要: 数据切片 + 拼接 LLM 看到的上下文 |
prompts/ |
Prompt 模板 (迭代最频繁) |
provider.py |
OpenAI-compatible 多提供商抽象 |
sources.py |
数据源协议 + 注册表 |
unit_of_work.py |
统一事务边界 |
repositories.py |
数据访问封装 |
validation.py |
LLM 输出严格校验 |
backtest.py |
回测框架(防未来数据泄漏) |
配置
通过 .env 或环境变量配置:
| 变量 | 说明 | 默认值 |
|---|---|---|
DATABASE_URL |
PostgreSQL 连接 | postgresql+asyncpg://football:football@localhost:5432/football |
LLM_PROVIDER |
提供商标识 | openai |
LLM_API_KEY |
API 密钥 | (必填) |
LLM_BASE_URL |
API 地址 | https://api.openai.com/v1 |
LLM_MODEL |
模型名 | gpt-4o |
LLM_SPECIALIST_MODEL |
专家模型 (空=回落 LLM_MODEL) | |
LLM_AGGREGATOR_MODEL |
终裁模型 (空=回落 LLM_MODEL) | |
BZZOIRO_KEY |
bzzoiro API Key | (必填) |
CORS_ORIGINS |
允许的跨域来源 | http://localhost:5173 |
测试
pytest
数据库迁移
alembic upgrade head
License
个人研究项目,预测结果不构成投注建议。
Languages
Python
64.3%
TypeScript
34.8%
CSS
0.5%
JavaScript
0.2%