# 07 · 开发指南 ## 本地开发环境搭建 ```bash # 1. 克隆并进入项目 cd Profeto # 2. 安装依赖(含 dev) pip install -e ".[dev]" # 3. 启动 PostgreSQL docker run -d --name profeto-pg \ -e POSTGRES_USER=football -e POSTGRES_PASSWORD=football -e POSTGRES_DB=football \ -p 5432:5432 postgres:16-alpine # 4. 配置环境变量 cp .env.example .env # 编辑 .env 填 LLM_API_KEY / BZZOIRO_KEY # 5. 建表 alembic upgrade head # 6. 启动 API(热重载) uvicorn src.api.app:app --reload # 7. 启动前端(另一个终端) cd frontend && npm install && npm run dev ``` ## 项目结构 ``` Profeto/ ├── src/ # 后端源码 │ ├── api/ │ │ ├── routes/ # FastAPI 路由 │ │ │ ├── leagues.py # 联赛/比赛查询 │ │ │ ├── predict.py # 预测入口 │ │ │ ├── ingest.py # 数据采集 │ │ │ └── eval.py # 评估回填 │ │ ├── schemas.py # Pydantic 模型 │ │ └── app.py # FastAPI 工厂 │ ├── db/ │ │ ├── base.py # SQLAlchemy async engine + session │ │ └── models.py # 6 张表 ORM │ ├── data/ │ │ ├── bzzoiro.py # bzzoiro 采集 + 入库 │ │ ├── understat.py # understat xG 回填 │ │ ├── injuries.py # 伤停采集 │ │ ├── normalize.py # 数据清洗契约 │ │ ├── team_names.py # 队名归一化映射 │ │ └── config.py # 联赛映射常量 │ ├── llm/ │ │ ├── provider.py # LLM 提供商抽象(OpenAI-compatible) │ │ ├── context_builder.py # 数据切片 + 拼接 │ │ ├── predict.py # 预测入口(单/多模式分派) │ │ ├── eval.py # 评估统计 │ │ ├── agents/ │ │ │ ├── base.py # AgentSpec + run_agent │ │ │ └── orchestrator.py # 多 agent 编排 │ │ └── prompts/ │ │ ├── match_prediction_v1.md # 单 agent prompt │ │ └── agents/ # 多 agent prompt │ │ ├── form_v1.md │ │ ├── stats_v1.md │ │ ├── home_away_v1.md │ │ ├── injuries_v1.md │ │ ├── h2h_v1.md │ │ └── aggregator_v1.md │ └── core/ │ └── config.py # pydantic-settings 配置 ├── frontend/ # React 单页前端 ├── alembic/ # 数据库迁移 │ └── versions/ │ ├── 0001_initial.py │ └── 0002_agent_outputs.py ├── tests/ # 测试 │ ├── test_core.py # 核心逻辑测试 │ └── test_agents.py # 多 agent 测试 ├── docs/ # 本文档 ├── pyproject.toml # 依赖 + 构建配置 ├── alembic.ini # Alembic 配置 ├── Dockerfile # 生产镜像 ├── docker-compose.yml # 本地/生产编排 └── .env.example # 环境变量模板 ``` ## 测试 ```bash # 跑全部测试 pytest # 详细输出 pytest -v # 只跑 agent 测试 pytest tests/test_agents.py -v # 覆盖率 pytest --cov=src --cov-report=term-missing ``` 当前测试覆盖: - `test_core.py`(13 项):数据清洗、队名归一、赛季标签、LLM 解析 - `test_agents.py`(20 项):no_data 门控、fail-open、prompt 加载、报告解析、终裁渲染 ### 写新测试的模式 mock LLM 提供商(避免真实调用): ```python class MockProvider: model = "test-model" async def chat(self, system, user, **kw): from src.llm.provider import LLMResponse return LLMResponse( content="{}", parsed={"data_sufficiency": "high", "analysis": "ok", "home_edge": 0.5, "subjective_confidence": 0.8, "key_evidence": ["证据"]}, prompt_tokens=10, completion_tokens=5, latency_ms=100, ) ``` ## 常见开发任务 ### 1. 修改 Agent Prompt 直接编辑 `src/llm/prompts/agents/{name}_v1.md`,无需重启(有 `lru_cache`,改完清缓存或重启)。 迭代版本: ```bash cp src/llm/prompts/agents/h2h_v1.md src/llm/prompts/agents/h2h_v2.md # 编辑 h2h_v2.md # 调用时传 prompt_version: "v2" ``` ### 2. 新增 Agent 见 [04-agents.md § 如何新增一个专家 Agent](04-agents.md)。 ### 3. 新增数据源 1. 在 `src/data/` 写采集模块(参考 `understat.py`) 2. 在 `normalize.py` 加清洗函数 3. 在 `context_builder.py` 加切片函数 4. 在 `api/routes/ingest.py` 加端点 5. 在 `api/schemas.py` 加请求/响应模型 ### 4. 新增联赛 编辑 `src/data/config.py`: ```python BZZOIRO_LEAGUE_IDS["新代码"] = league_id LEAGUE_NAMES["新代码"] = "联赛名" LEAGUE_COUNTRIES["新代码"] = "国家" ``` ### 5. 数据库 Schema 变更 ```bash # 1. 改 src/db/models.py # 2. 生成迁移 alembic revision --autogenerate -m "描述" # 3. 检查生成的迁移文件(自动推断不完美) # 4. 应用 alembic upgrade head ``` ## 前端开发 ```bash cd frontend npm install npm run dev # 开发(热重载,代理 /api → localhost:8000) npm run build # 生产构建 → dist/ npm run preview # 预览生产构建 ``` 技术栈:React + TypeScript + Tailwind CSS + Vite。 主要页面:`src/pages/Matches.tsx`(比赛列表 + 预测 + agent 报告展示)。 ## 编码约定 - **异步优先**:所有 IO 用 `async/await`,SQLAlchemy 用 async session - **session 管理**: - 路由读操作:依赖注入 `get_db_read`(不自动 commit) - 路由写操作:依赖注入 `get_db`(自动 commit) - 内部/ingest:直接用 `AsyncSessionLocal()` 自己管事务 - **错误处理**:领域层抛 `ValueError`/`RuntimeError`,路由层转 HTTP 状态码 - **日志**:用 `logging.getLogger(__name__)`,关键路径打 info/debug - **类型提示**:全量标注,`Mapped[T]` + `mapped_column` ## 提交前检查清单 ```bash # 1. 测试全绿 pytest # 2. 语法检查 python -m py_compile src/**/*.py # 3. 确认 .env 不提交(已在 .gitignore) # 4. 文档同步(改功能时更新 docs/) ```