# SupplyAgent 代码仓库结构总结

## 1. 阅读基线

本文档完全基于当前代码仓库重新阅读后形成，不继承此前对项目的业务推演或可视化设计理解。

代码快照：

- 分支：`feature/zhangbo`
- 阅读时提交：`2ab77ce`
- Python 要求：`>= 3.11`
- 后端：FastAPI + SQLAlchemy 2.x + PyMySQL
- Agent：OpenRouter/OpenAI 兼容 API + 自研 Tool/Skill/ReAct 框架
- 数据源：ODPS（MaxCompute）
- 数据库：MySQL
- 对象存储：阿里云 OSS
- 前端：Vue 3 + TypeScript + Vite

`.env` 已被 `.gitignore` 忽略。本文不会记录其中的密码、密钥、数据库地址或 Token。

---

## 2. 当前项目的实际定位

当前 SupplyAgent 由两部分组成：

1. 一套通用 AI Agent 运行框架；
2. 一套围绕“需求词、全局分类树、热度、视频和需求生成”的业务系统。

业务系统当前主要完成：

- 从 ODPS 同步全局分类树和树下元素；
- 从 ODPS 同步多策略需求池；
- 将需求词挂到分类树节点；
- 汇总四项先验热度和真实 ROV/VOV；
- 把词级热度沿分类树向上聚合；
- 按单一热度维度生成并保存平台需求；
- 把需求词连接到真实视频和视频最终选题；
- 通过 API 和 Vue 页面展示分类树、热力图和下钻证据；
- 记录 Agent 完整执行过程，生成 HTML，上传 OSS 并在 MySQL 留档。

当前系统的核心业务链路是：

```text
ODPS 全局分类与元素
        ↓
MySQL 全局分类树
        ↑
需求池词语 → 归类 Agent → 需求词挂靠
        ↓
四项先验 + 真实 ROV/VOV
        ↓
词级统计 → 分类树节点聚合 → 四维全局排名
        ↓
需求生成 Agent → generated_demand
        ↓
FastAPI → Vue 分类树/热力图/证据下钻
```

---

## 3. 仓库分层

```text
SupplyAgent/
├── supply_agent/       通用 Agent 框架
├── agents/             业务 Agent 和专属工具
├── supply_infra/       MySQL、ODPS、OSS、定时任务
├── api/                FastAPI 查询接口和静态前端托管
├── web/                Vue 3 业务前端
├── jobs/               数据任务的手动 CLI 入口
├── scripts/            部署、日志上传、日志可视化脚本
├── visualization/      早期/独立的业务设计可视化材料
├── Dockerfile          前端构建 + Python 运行镜像
├── pyproject.toml      Python 包、依赖和命令入口
└── requirements.txt    另一份运行依赖清单
```

模块之间的依赖方向：

```text
web
  ↓ HTTP
api
  ↓
supply_infra.db.repositories
  ↓
MySQL

agents
  ↓
supply_agent（Agent 框架）
  ↓
supply_infra（数据库/ODPS/OSS）

jobs / scheduler
  ↓
supply_infra.scheduler.jobs
  ↓
ODPS + MySQL + 业务 Agent
```

---

## 4. 通用 Agent 框架：`supply_agent/`

### 4.1 `supply_agent/config.py`

负责 Agent 侧配置：

- OpenRouter API Key、模型和 Base URL；
- Agent 最大迭代次数和温度；
- Skills 目录；
- 日志目录和日志开关。

配置来自环境变量或项目根目录 `.env`，相对路径会解析到项目根目录。

### 4.2 `supply_agent/llm/client.py`

基于 OpenAI Python SDK 连接 OpenRouter，提供：

- 同步对话 `chat`；
- 异步对话 `achat`；
- 同步文本流 `stream`；
- 异步文本流 `astream`；
- Tool Calling；
- OpenRouter reasoning effort；
- LLM 输入和输出日志。

模型可以在运行时切换。

### 4.3 `supply_agent/tools/`

`base.py` 提供：

- `@tool` 装饰器；
- 从 Python 函数签名生成 JSON Schema；
- Optional 和基础类型转换；
- 同步/异步工具执行；
- 异常转为 JSON 错误结果。

`registry.py` 提供：

- 工具注册、删除和查询；
- OpenAI ToolDefinition 列表；
- 根据模型返回的工具名执行工具；
- 同步和异步执行；
- 批量注册被 `@tool` 装饰的函数。

### 4.4 `supply_agent/skills/`

Skill 机制读取指定目录下的 `SKILL.md`：

- 支持 YAML frontmatter 的名称和描述；
- 启动时把 Skill 目录作为目录索引加入系统提示词；
- Agent 通过内置 `load_skill` 工具按需加载全文；
- Skill 加载后会重建系统消息并注入当前上下文。

当前仓库根目录的 `skills/` 被 `.gitignore` 忽略，因此代码支持 Skill，但仓库没有可随代码分发的业务 Skill 内容。

### 4.5 `supply_agent/agent/`

`core.py` 的 `Agent` 负责组装：

- LLMClient；
- ToolRegistry；
- SkillRegistry；
- AgentLoop；
- AgentLogger。

对外提供：

- `run`；
- `arun`；
- `stream`；
- `astream`。

`loop.py` 实现标准 ReAct/Tool Calling 循环：

```text
系统消息 + 对话历史
        ↓
调用 LLM
        ↓
有工具调用？──否──→ 返回最终结果
        │是
        ↓
执行工具并写入 Tool Message
        ↓
下一轮 LLM
```

达到最大迭代次数时，同步和普通异步运行会追加一条用户消息，要求模型给出当前最佳答案；流式路径则直接发出 Max iterations reached。

### 4.6 `supply_agent/logging/`

每次 Agent 运行生成：

- 人类可读 `.log`；
- 结构化 `.jsonl`。

日志记录：

- run_start；
- 完整 LLM 输入；
- 模型输出和 reasoning；
- 工具调用参数和结果；
- Skill 加载；
- run_end。

运行结束后：

1. 将日志渲染成 HTML；
2. 上传 `.log`、`.jsonl`、`.html` 到 OSS；
3. 把 HTML 公网地址写入 `oss_logs`；
4. 上传失败只记录错误，不影响 Agent 主结果。

---

## 5. 三个业务 Agent

## 5.1 `demand_belong_category_agent`

目标：把需求词挂到真实存在的全局分类树节点。

工具：

- `query_global_tree_category`：读取整棵树或指定子树；
- `batch_insert_demand_belong_category`：批量写入需求词、分类 ID 和原因。

业务流程：

1. 查看顶层分类；
2. 选择最相关分支；
3. 逐层下钻；
4. 不确定时停在更可靠的上层节点；
5. 将结果写入 `demand_belong_category`。

系统提示允许一个词选择一个或多个高置信节点，但当前数据库和写入工具以 `name` 唯一：

- `demand_belong_category.name` 有唯一约束；
- 同一次请求也按 name 去重；
- 因此当前实现实际上只能保存“一词一个分类节点”；
- 多挂靠尚未在当前表结构中实现。

定时任务会把新需求词按 100 个一批交给该 Agent。

## 5.2 `generate_demand_agent`

目标：从分类树局部热度和已挂靠需求词中，按单一维度生成平台需求结果。

支持的来源维度只有四项先验：

- `ext_pop`：外部热度；
- `plat_sust_pop`：平台持续热度；
- `plat_ly_pop`：平台去年同期热度；
- `recent_pop`：近期热度。

一次运行只允许处理一个维度。

工具：

- `query_latest_biz_dt`：取两张统计表都有数据的最新日期；
- `query_category_tree_by_dim`：查看指定维度有数据的树；
- `query_category_leaves_by_dim`：定位有数据的叶子节点；
- `query_demand_words_by_category`：查询节点或子树下的真实需求词；
- `query_category_path`：补充根到节点的路径；
- `batch_save_generated_demands`：写入 `generated_demand`。

输出结构固定为：

```text
source_dim
  └── overall_direction
        └── summary_event
              └── demand_name
```

重要约束：

- `demand_name` 必须原样来自 `demand_belong_category.name`；
- Agent 不能新造需求词；
- `summary_event` 尽量对应一个需求，最多轻量合并 2～3 个强相关需求；
- 写入时保存类目、路径、维度 avg/count 快照、reason、业务日和 run_id；
- 同一次工具调用按 `(source_dim, demand_name)` 去重；
- 当前 `generated_demand` 没有数据库唯一约束，不同 run 可以重复生成相同需求。

真实 ROV/VOV 当前不属于该 Agent 的 `source_dim`，也没有参与其单维生成工具链。

## 5.3 `find_agent`

目标：搜索和解析抖音视频。

当前真正注册的工具只有：

- `douyin_search`：调用外部抖音关键词搜索服务；
- `douyin_detail`：按 content_id 获取视频详情和可播放地址；
- `qwen_video_analyze`：调用千问视频模型解析视频。

搜索和详情接口有约 10 秒的请求间隔限制。

当前系统提示还要求调用：

- `save_video_content`；
- `query_video_content`。

但仓库中没有这两个工具，也没有被注册。因此当前 `find_agent` 能搜索、取详情和解析视频，但不能按提示完成内容入库或历史查询。

---

## 6. 基础设施：`supply_infra/`

### 6.1 配置

`supply_infra/config.py` 管理：

- MySQL；
- ODPS；
- APScheduler；
- 阿里云 OSS；
- Agent 日志上传开关。

配置文件固定从项目根目录 `.env` 读取，不依赖当前工作目录。

### 6.2 数据库会话

`supply_infra/db/session.py`：

- 懒加载全局 SQLAlchemy Engine；
- 使用连接池和 `pool_pre_ping`；
- `get_session()` 自动提交；
- 异常时自动回滚；
- `init_db()` 通过 ORM metadata 创建缺失表。

### 6.3 Repository 规则

业务 Agent、API 和定时任务原则上不直接执行 MySQL SQL，而是通过 Repository：

- 基础 CRUD；
- 批量插入；
- insert ignore；
- upsert；
- 条件查询；
- 批量更新。

ODPS 查询仍在 `supply_infra/odps/client.py` 内直接组织 SQL。

### 6.4 ODPS 客户端

当前读取的主要 ODPS 数据：

- `public_pattern_mining_category`：全局分类；
- `pattern_mining_element`：树下元素；
- `dwd_multi_demand_pool_di`：多策略需求池；
- `dwd_topic_decode_result_di`：视频解析和最终选题；
- `dwd_video_produce_plan_stat_hour`：真实 ROV/VOV。

### 6.5 OSS 客户端

负责：

- 生成对象路径；
- 上传本地文件；
- 返回 CDN 公网地址。

---

## 7. MySQL 数据模型

当前 ORM 定义 10 张表。

| 表 | 作用 | 关键唯一性/关联 |
|---|---|---|
| `global_tree_category` | 全局分类树节点 | `source_id` 唯一；`parent_id` 形成树 |
| `global_tree_element` | ODPS 元素与分类挂靠 | `(name, category_id)` 唯一 |
| `multi_demand_pool_di` | 按日同步的策略需求池 | 代码按 `(strategy, demand_id, biz_dt)` 管理差异 |
| `demand_belong_category` | 需求词归属分类 | `name` 唯一；当前一词只能保存一个分类 |
| `demand_belong_pool_rel` | 需求词与需求池行的匹配边 | `(demand_belong_category_id, multi_demand_pool_di_id)` 唯一 |
| `demand_popularity_stats` | 词级六维 avg/count | `(demand_category_id, biz_dt)` 唯一 |
| `category_tree_weight` | 分类树节点六维聚合和四维排名分 | `(category_id, biz_dt)` 唯一 |
| `multi_demand_video_detail` | 视频标题与最终选题 JSON | `vid` 唯一 |
| `generated_demand` | 需求生成 Agent 输出 | 按 run_id 记录，无数据库唯一约束 |
| `oss_logs` | Agent 日志 HTML 的 OSS 地址 | 按 agent_name 查询 |

### 7.1 关键关系

```text
global_tree_category.id
  ├── global_tree_category.parent_id
  ├── global_tree_element.category_id
  ├── demand_belong_category.category_id
  ├── category_tree_weight.category_id
  └── generated_demand.category_id

demand_belong_category.id
  ├── demand_popularity_stats.demand_category_id
  ├── demand_belong_pool_rel.demand_belong_category_id
  └── generated_demand.demand_belong_id

multi_demand_pool_di.id
  └── demand_belong_pool_rel.multi_demand_pool_di_id
```

这些关联在 ORM 中主要以整数 ID 表达，没有使用 SQLAlchemy relationship，也没有显式数据库 ForeignKey。

---

## 8. 热度数据的真实代码口径

### 8.1 策略到四项先验的映射

代码把需求池策略映射为：

| ODPS strategy | 统计字段 | 页面含义 |
|---|---|---|
| `新热事件` | `ext_pop` | 外部热度 |
| `逐月` | `plat_sust_pop` | 平台持续热度 |
| `去年同期阳历` | `plat_ly_pop` | 去年同期热度 |
| `去年同期阴历` | `plat_ly_pop` | 去年同期热度 |
| `当下供需gap` | `recent_pop` | 近期热度 |

### 8.2 真实 ROV/VOV

从近 7 日 `dwd_video_produce_plan_stat_hour` 获取人工 AGC 和自动 AGC 数据：

- ROV = 回流 UV / 分发曝光 PV；
- VOV = 拉回曝光 PV / 分发曝光 PV；
- 同一特征值存在多行时，优先保留 `rov_diff`、`vov_diff` 较高的一行；
- 按特征值与 `demand_name` 精确匹配，回填 `multi_demand_pool_di`。

### 8.3 词级统计

对每个 `demand_belong_category.name`：

1. 在当日 `multi_demand_pool_di.demand_name` 中执行子串 LIKE 匹配；
2. 按策略收集非零 weight；
3. 分别计算四项先验的 avg/count；
4. 汇总匹配行的真实 ROV/VOV avg/count；
5. 写入 `demand_popularity_stats`。

因此当前词与需求池的匹配核心是字符串包含关系，不是分词索引、向量匹配或显式语义关系。

### 8.4 分类树节点聚合

分类节点聚合六个独立维度：

- 外部热度；
- 平台持续热度；
- 去年同期热度；
- 近期热度；
- 真实 ROV；
- 真实 VOV。

每个节点统计其整个子树内有需求词挂靠的节点：

```text
节点维度 avg = Σ(挂靠点 avg × count) / Σ(count)
节点维度 count = Σ(count)
```

结果写入 `category_tree_weight`。

### 8.5 四维排名分和全局热度

`update_category_tree_rank_scores.py` 只对四项先验排名：

- 各维只让 `count > 0` 的节点参与；
- 按 avg 降序排名；
- 同分取平均名次；
- 映射到 `(0, 1]`；
- 无数据节点为 0；
- `total_score` 是四个排名分直接相加，范围 `[0, 4]`。

真实 ROV/VOV 会被聚合并通过 API 返回，但当前不进入 `total_score`。

前端显示“全局热度”时再使用 `total_score / 4` 映射为 `0～1` 色阶。

单一维度热度则由前端根据该维所有有数据节点的 avg 做百分位色阶，不直接使用数据库中的四维 rank score 字段。

---

## 9. 定时任务和数据流水线

### 9.1 已注册的两个定时任务

`supply_infra/scheduler/app.py` 当前只注册：

1. 每天 `02:30`：同步昨天的全局树和元素；
2. 每天 `12:00`：同步当天的策略需求池并执行后续完整流水线。

时区来自 `SCHEDULER_TIMEZONE`。

### 9.2 全局树同步

`sync_global_tree_odps_to_mysql`：

1. 拉取有效元素；
2. 拉取全部分类；
3. 从元素命中的分类向上补齐祖先；
4. 按 ODPS source_id 去重；
5. 已有分类保持不变，只插入新分类；
6. 给新分类分配 MySQL ID 并转换 parent_id；
7. 写入元素及其 MySQL category_id。

该任务是增量插入，不会根据 ODPS 当前状态自动删除或更新历史分类。

### 9.3 策略需求池完整流水线

`sync_multi_demand_pool_odps_to_mysql` 实际执行顺序：

1. 比较 ODPS 与 MySQL 当日去重行数；
2. 行数不同时执行差异同步；
3. 对当日所有 demand_name 按空格拆词；
4. 调用归类 Agent 处理未出现过的新词；
5. 建立需求词与需求池的子串匹配边，并回填词级 video_list；
6. 获取并回填近 7 日真实 ROV/VOV；
7. 计算词级六维 avg/count；
8. 计算整棵分类树六维聚合；
9. 计算四项先验全局排名分和 total_score；
10. 同步全部待处理视频的标题和最终选题。

当前有一个同步判断风险：只要 ODPS 和 MySQL 行数相同，就跳过需求池差异拉取；如果内容发生变化但总行数不变，该轮不会发现这些变化。

### 9.4 手动任务入口

`jobs/` 提供：

- 初始化数据库；
- 启动 Scheduler；
- 单独同步需求池视频；
- 回填视频列表和标题；
- 单独计算词级热度；
- 单独计算树节点权重；
- 单独计算四维排名；
- 单独同步需求词与需求池关系。

---

## 10. FastAPI：`api/`

服务端口：`8080`。

| 接口 | 作用 |
|---|---|
| `GET /health` | 健康检查 |
| `GET /api/category-tree?biz_dt=YYYYMMDD` | 返回嵌套分类树、六维 avg/count、total_score 和挂靠词数 |
| `GET /api/demand-belong-category` | 返回所有有效需求词挂靠 |
| `GET /api/demand-belong-category/{id}/videos` | 返回需求词关联的视频标题和最终选题 JSON |
| `GET /api/demand-belong-oss-logs` | 返回归类 Agent 的 OSS 日志列表 |

如果 `web/dist` 存在，FastAPI 会把它挂到 `/`，同一个 8080 服务同时提供 API 和生产前端。

API 当前是同步 SQLAlchemy 查询，没有分页、鉴权或缓存。

---

## 11. Vue 前端：`web/`

### 11.1 路由

| 路由 | 页面 |
|---|---|
| `/` | 平台全局需求地图 |
| `/demand-tree` | 传统横向分类树 |
| `/demand-process` | 需求归类 Agent 日志 |
| `/demand-map` | 重定向到 `/` |

当前顶部导航只显示“平台全局需求地图”，另外两个页面有路由但没有导航入口。

### 11.2 平台全局需求地图

`GlobalDemandMapView.vue` 并行读取：

- 完整分类树和热度；
- 全部需求词挂靠。

`IcicleHeatTree.vue` 使用 Canvas 绘制从左到右的冰柱树：

- 每层固定 `200px` 宽；
- 全局状态适配视口高度；
- 选择层级或聚焦节点后按可读高度纵向扩展；
- 最大逻辑画布高度 `24000px`；
- 实际 Canvas 始终只有视口大小，只绘制可见区域；
- 普通滚轮滚动；
- 拖拽平移；
- `Ctrl/Command + 滚轮` 缩放；
- 点击节点聚焦子树；
- 支持层级起点和祖先面包屑。

热力标签：

- 全局树结构；
- 全局热度 `total_score`；
- 外部热度；
- 平台持续热度；
- 去年同期热度；
- 近期热度。

API 虽然返回真实 ROV/VOV，但当前冰柱图标签没有提供真实 ROV/VOV 的独立切换入口。

### 11.3 节点证据下钻

分类节点存在需求词时显示搜索标识。点击后通过 `DemandPathPanel.vue` 展开：

```text
当前分类节点
  → 细节元素/需求词
  → 真实视频实例
  → 最终选题 JSON
```

这里展示的是 `demand_belong_category` 需求词，不是 `generated_demand` 中由生成 Agent 产出的四层平台需求。

当前前端没有读取或展示 `generated_demand` 的 API。

### 11.4 传统分类树页面

`CategoryTree.vue` 使用 Vue DOM 递归组件展示：

- 默认展开 3 层；
- 支持选择展开深度；
- 支持手动展开和收起；
- 支持按维度过滤有数据的节点；
- 支持拖动画布；
- 支持导出包含数据的独立 HTML；
- 同样可以下钻需求词、视频和最终选题。

### 11.5 需求归类过程页面

读取 `demand_belong_category_agent` 的 `oss_logs`，按时间倒序显示，点击打开 Agent 运行过程 HTML。

---

## 12. 部署和运行

### 12.1 Python 命令入口

`pyproject.toml` 注册：

- `supply-api`；
- `supply-scheduler`；
- `supply-visualize`。

### 12.2 本地开发

后端：

```bash
python -m api
```

前端：

```bash
cd web
npm install
npm run dev
```

Vite 在 `5173`，将 `/api` 代理到 `127.0.0.1:8080`。

### 12.3 Docker

Docker 使用两阶段构建：

1. Node 20 构建 Vue；
2. Python 3.11 安装项目和 ODPS 可选依赖；
3. 把 `web/dist` 放入 Python 镜像；
4. 通过 `python -m api` 启动 8080。

`scripts/docker-deploy.sh` 可以构建并向指定镜像仓库推送时间戳标签和 `latest`。

---

## 13. 当前代码已经实现的能力

- 通用同步/异步 Agent 和 Tool Calling；
- 动态 Skill 加载机制；
- 三个业务 Agent；
- 全量 Agent 日志和 OSS 发布；
- ODPS 到 MySQL 的全局树同步；
- 多策略需求池增量同步；
- 新词自动归类；
- 需求词与需求池匹配边；
- 真实 ROV/VOV 回填；
- 词级六维统计；
- 分类树六维向上聚合；
- 四项先验排名和 total_score；
- 单维度平台需求生成与落库；
- 视频标题和最终选题同步；
- FastAPI 查询接口；
- Vue 全局冰柱热力图；
- 分类节点到需求词、视频和最终选题的下钻；
- Docker 构建和部署脚本。

---

## 14. 当前代码未完成或存在偏差的部分

### 14.1 需求多挂靠尚未实现

`demand_belong_category.name` 唯一，当前一个需求词只能保存一个 category_id。系统提示中的“一词多个节点”无法真实落库。

### 14.2 `generated_demand` 未进入产品展示

需求生成 Agent 已能写 `generated_demand`，但：

- 没有对应 API；
- 前端没有需求清单；
- 没有与内容、反馈的展示闭环；
- 当前全局图下钻的是需求词，不是最终生成需求。

### 14.3 后验没有进入全局综合分

真实 ROV/VOV 已采集、聚合并由 API 返回，但：

- 不进入 `total_score`；
- 不进入 generate_demand_agent 的 source_dim；
- 当前主热力图没有 ROV/VOV 标签；
- 尚未形成“后验反向调整最终需求排序”的完整实现。

### 14.4 `find_agent` 提示和工具不一致

提示要求保存和查询视频内容，但相应工具不存在。

### 14.5 关系语义较弱

当前主要关系是：

- 树父子；
- 需求词到单一分类；
- 需求词和需求池的字符串子串匹配；
- 需求词到视频列表。

尚没有独立的语义关系表、关系类型、关系 reason、置信度和人工审核状态。

### 14.6 同步完整性风险

需求池同步先比较总行数。总行数相同但内容变化时，会跳过 ODPS 明细同步。

### 14.7 测试体系缺失

当前仓库没有 `tests/`，并且 `.gitignore` 直接忽略 `tests/`，会阻止正常提交测试目录。

### 14.8 文档已滞后

现有 `README.md`、`ARCHITECTURE.md` 和 `agents/README.md` 包含已经不存在或未实现的结构，例如：

- `video_content` 模型/Repository；
- find_agent 的保存和历史查询工具；
- 旧的 Agent 数量和数据流。

后续应以本文和源码为准，并更新旧文档。

---

## 15. 当前本地运行环境与数据库状态

### 15.1 本地 Python 环境不匹配

当前机器默认环境：

- Python `3.10.19`；
- SQLAlchemy `1.4.51`；
- 项目根目录没有 `.venv`。

源码要求：

- Python `>=3.11`；
- SQLAlchemy `>=2.0`。

因此直接使用当前默认 `python3` 导入数据库层会在 `DeclarativeBase` 处失败。应建立 Python 3.11+ 虚拟环境后安装项目依赖。

### 15.2 `.env` 的 MySQL 配置问题

只读连接检查发现：

1. `MYSQL_HOST` 的值开头多了一个 `=`，会导致主机名解析失败；
2. 临时去掉该字符后可以到达 MySQL 服务，但返回错误 `1045 Access denied`；
3. 需要核对用户名/密码、RDS 白名单或该用户允许连接的 Host 范围。

由于未通过鉴权，本次无法确认数据库真实表数量、行数和最新业务日。本文的数据结构来自当前 ORM 和 Repository 源码，不冒充线上运行态数据。

### 15.3 前端依赖未安装

当前没有 `web/node_modules`。Node `20.11.1`、npm `10.2.4` 已存在，执行前需要先在 `web/` 运行 `npm install` 或 `npm ci`。

---

## 16. 建议的后续阅读顺序

如果继续开发，建议按以下顺序进入代码：

1. `supply_infra/scheduler/jobs/sync_multi_demand_pool_odps_to_mysql.py`：理解主业务流水线；
2. `supply_infra/db/models/`：理解真实数据对象；
3. `supply_infra/scheduler/jobs/compute_category_tree_weight.py`：理解树上热度；
4. `agents/demand_belong_category_agent/`：理解需求词挂树；
5. `agents/generate_demand_agent/`：理解平台需求生成；
6. `api/services/category_tree.py`：理解后端对前端的数据形态；
7. `web/src/components/IcicleHeatTree.vue`：理解全局热力图；
8. `web/src/components/DemandPathPanel.vue`：理解节点证据下钻；
9. `supply_agent/agent/`：理解底层 Agent 执行机制；
10. `supply_agent/logging/`：理解可追溯运行日志。

---

## 17. 一句话总结

当前 SupplyAgent 是一套以 ODPS 和 MySQL 为数据底座、以全局分类树为组织骨架、以自研 Agent 框架完成需求词归类和单维需求生成、并通过 FastAPI/Vue 展示分类热度与视频证据的业务系统；核心数据流水线已经形成，但多挂靠、最终需求产品化、后验反馈进入综合决策、语义关系图和自动化测试仍未完成。
