# 经验记录（Knowledge Base）

> 本文件按代码编纂工作流规范，以四段式记录项目修改过程中遇到的关键问题及解决方案。  
> 格式: A. 具体问题 / B. 产生原因 / C. 解决方案 / D. 后续如何避免

---

## 2026-04-30-00-17-44 — PyTorch CUDA + SAM2 + 封面 + 帧率 + DICOM 批量导入

### A. 具体问题
1. PyTorch 为 CPU 版本，SAM2 未安装，GPU 推理不可用
2. 项目库视频卡片无封面缩略图
3. 项目库 FPS 为硬编码 "30FPS"，不显示真实原始帧率，也无法修改解析帧率
4. 不支持 DICOM 连续帧批量导入

### B. 产生原因
1. 系统磁盘仅 24GB，PyTorch CUDA wheel (~1GB) + SAM2 编译依赖导致 `No space left on device`
2. 解析视频时未提取封面，Project 模型无 thumbnail_url 字段
3. 解析视频时未读取原始帧率，Project 模型无 original_fps / parse_fps 字段
4. upload 接口仅支持单文件，无批量 DICOM 上传接口，Project 无 source_type 区分视频/DICOM

### C. 解决方案
1. **磁盘扩容后安装 PyTorch CUDA + SAM2**：
   - `pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124`
   - `pip install sam2`（成功编译安装 sam2-1.1.0）
   - 修正 `config.py` 中 `sam_model_config` 为 `configs/sam2/sam2_hiera_t.yaml`（Hydra 包内相对路径）
   - 验证 `torch.cuda.is_available()` → True，RTX 4090 识别正常
2. **视频封面**：
   - `frame_parser.py` 新增 `extract_thumbnail()`，从视频第一帧提取 640px JPEG 封面
   - `media.py` `parse_media()` 解析视频时自动上传封面到 `projects/{id}/thumbnail.jpg`
   - `projects.py` 返回 presigned URL
   - 前端 `ProjectLibrary.tsx` 卡片背景显示 `<img>` 封面
3. **真实帧率 + 可修改解析帧率**：
   - `models.py` Project 新增 `original_fps` (Float), `parse_fps` (Float, default=30)
   - `frame_parser.py` `get_video_fps()` 用 OpenCV 读取真实帧率，`parse_video()` 返回 `(frames, original_fps)`
   - 前端 `ProjectLibrary.tsx` 上传视频时弹窗允许用户滑动设置 parse_fps (1-60)
   - 项目卡片显示 `原 60.0fps` 和 `30FPS` 标签
4. **DICOM 批量导入**：
   - `models.py` Project 新增 `source_type` (video | dicom)
   - `media.py` 新增 `/api/media/upload/dicom` 接口，接收 `List[UploadFile]`，上传多个 .dcm 到 `uploads/{id}/dicom/`
   - `media.py` `parse_media()` 支持 DICOM 模式：从 MinIO 下载整个 dicom 目录 → `parse_dicom()` 解析
   - 前端 `ProjectLibrary.tsx` 导入按钮展开菜单：导入视频 / 导入 DICOM 序列，DICOM 用 `<input multiple accept=".dcm">`

### D. 后续如何避免问题
1. **SAM2 配置路径必须用 Hydra 包内相对路径**：`build_sam2()` 使用 Hydra，配置文件必须传 `configs/sam2/xxx.yaml` 而非绝对路径
2. **数据库 schema 变更必须彻底清理旧表**：PostgreSQL `drop_all()` 可能因外键约束不彻底，生产环境应使用 Alembic 迁移，开发环境应手动 `DROP TABLE ... CASCADE`
3. **大依赖安装必须预留足够磁盘空间**：PyTorch CUDA (~1GB) + SAM2 build (~500MB temp) 至少需要 5GB 可用空间
4. **前端上传交互必须区分媒体类型**：视频和 DICOM 的上传流程、文件选择器 `accept`、后续解析逻辑完全不同，应提供明确的模式切换

---

## 2026-04-29-23-28-13 — 视频帧显示链路全修复

### A. 具体问题
1. 项目库中没有默认视频 `Data_MyVideo_1.mp4`
2. 分割工作区点击视频后画面一片黑（无影像）
3. 导入新视频后进入工作区同样看不到视频帧

### B. 产生原因
1. 后端 lifespan 没有任何自动扫描/注册本地视频的逻辑；`server.ts` 的硬编码项目对前端不可见
2. `CanvasArea.tsx` 硬编码了一张 Unsplash 外链，没有从 Zustand store 读取帧数据；进入工作区时不调用帧列表 API
3. 上传流程不完整：未先创建项目再带 project_id 上传，上传后未触发 `/api/media/parse` 帧提取；后端 `/frames` 返回的是 MinIO 对象名而非可访问的 presigned URL；MinIO presigned URL 的 host 为 `localhost:9000`，浏览器端无法访问
4. 系统磁盘空间（24GB）紧张，MinIO 在提取大量高清 PNG 帧时触发 `XMinioStorageFull`，导致帧上传失败

### C. 解决方案
1. **后端默认视频种子**：`main.py` lifespan 中启动后台线程，自动检测 `Data_MyVideo_1.mp4`，创建 Project → 上传 MinIO → 调用 FFmpeg 解析帧 → 注册 Frame 记录
2. **后端帧 URL 修复**：`projects.py` 的 `list_frames` 在返回前为每个 `frame.image_url` 调用 `get_presigned_url()` 生成带签名的可访问 URL
3. **后端 presigned URL host 修复**：`.env` 中 `MINIO_ENDPOINT` 从 `localhost:9000` 改为 `192.168.3.11:9000`，确保浏览器端可访问
4. **后端 ProjectOut 增强**：`schemas.py` 添加 `frame_count`，`projects.py` 在查询时计算 `len(p.frames)`，供前端显示帧数
5. **后端 upload 自动创建项目**：`media.py` 的 `upload_media` 在不传 `project_id` 时自动以文件名创建 Project 并关联视频
6. **前端帧加载中枢**：`VideoWorkspace.tsx` 在 `currentProject` 变化时调用 `getProjectFrames`，若帧数为 0 则自动触发 `parseMedia`，获取后映射写入 Zustand store
7. **前端 Canvas 真实渲染**：`CanvasArea.tsx` 接收 `frameUrl` prop，使用 `useImage(frameUrl)` 加载真实帧，AI 推理也使用当前帧 URL
8. **前端时间轴真实帧**：`FrameTimeline.tsx` 从 store 读取 `frames` 和 `currentFrameIndex`，渲染真实帧缩略图 `<img>`，点击切换当前帧
9. **前端上传链路完善**：`ProjectLibrary.tsx` 上传流程改为：创建项目 → `uploadMedia(file, projectId)` → `parseMedia(projectId)` → 刷新列表
10. **磁盘空间优化**：将 `frame_parser.py` 的 FFmpeg 输出从 PNG (1920px, ~3MB/张) 改为 JPEG (640px, ~30KB/张)，并限制默认视频只提取 100 帧，避免 MinIO 存储溢出

### D. 后续如何避免问题
1. **MinIO endpoint 必须使用服务器 IP**：任何生成外部可访问 URL 的服务（MinIO presigned、后端 baseURL、WebSocket）都必须使用服务器 LAN IP，禁止 localhost
2. **大文件/视频处理必须考虑磁盘预算**：提取帧前估算总大小（帧数 × 单帧大小），必要时降低分辨率、改格式为 JPEG、限制 max_frames
3. **前后端数据字段必须显式映射**：后端 snake_case 与前端 camelCase 不一致时，API 层必须做转换，不能依赖隐式兼容
4. **上传-解析-显示链路必须闭环测试**：任何涉及文件上传的功能，测试用例必须覆盖：上传 → 后端存储 → 解析 → 前端获取 → 前端渲染 的全流程

---

## 2026-04-29-23-15-26 — 上传/WS/项目库三 Bug 联修

### A. 具体问题
1. 上传成功后控制台打印 `undefined`，前端显示 url 为 `undefined`
2. React StrictMode 下 Dashboard 页面切换时报 `InvalidStateError: WebSocket is closed before the connection is established`
3. 上传完成后项目库为空，不显示新上传的项目

### B. 产生原因
1. 后端 `media.py` 上传接口返回字段为 `{object_name, file_url, size, message}`，而前端 `api.ts` 的 `uploadMedia` 直接 `return response.data`，调用方解构 `const { url, id }` 时 `url` 为 `undefined`
2. React 18 StrictMode 在开发环境下会 double-mount/unmount，`Dashboard.tsx` 的 `useEffect` cleanup 调用 `progressWS.disconnect()`，后者无条件执行 `this.ws.close()`；若此时 WebSocket 仍处于 `CONNECTING`（状态 0），调用 `.close()` 会抛出 InvalidStateError
3. `uploadMedia` 只将文件存入 MinIO，未调用 `createProject` 创建数据库记录，也未在成功后主动刷新项目列表

### C. 解决方案
1. `api.ts`: 解构 `file_url` 和 `object_name`，返回 `{ url: file_url, id: object_name }`
2. `websocket.ts`: `disconnect()` 中增加 `readyState === WebSocket.OPEN` 判断，仅对已连接套接字调用 `.close()`
3. `ProjectLibrary.tsx`: `uploadMedia` resolve 后调用 `getProjects()` 并 `setProjects()` 刷新列表
4. `Dashboard.tsx`: 增加 `mounted` ref，cleanup 时置 `false`，回调中检查 `mounted`，并延迟 500ms 连接避免 mount/unmount 竞态

### D. 后续如何避免问题
1. **前后端接口契约必须显式文档化**：新建/修改 API 时同步更新接口文档，字段名变更需两端对齐
2. **WebSocket 生命周期防御编程**：所有 `.close()` 调用前必须检查 `readyState`，connect/disconnect 需幂等
3. **副作用清理必须防竞态**：useEffect 中任何异步回调（setInterval、setTimeout、event listener）都需配合 `mounted` 或 AbortController
4. **上传后必须刷新列表**：任何创建资源的操作成功后，应主动重新拉取列表或返回完整资源对象写入本地状态

---

## 2026-04-28-22-55-15 — 建立代码编纂工作流

### A. 具体问题
项目缺少标准化的代码修改流程，导致需求管理、方案设计、测试验证、知识沉淀等环节可能遗漏，影响项目质量和可维护性。

### B. 产生原因
项目初期以快速原型为主，未建立正式的工程化管理流程；随着功能增加，需要更严谨的变更管理机制。

### C. 解决方案
建立完整的代码编纂工作流，包含 10 个阶段：时间戳记录、工程分析、需求分析、实现方案（人工审核）、测试方案（人工审核）、执行前准备（阅读经验记录）、方案执行、经验沉淀、Gitea 备份、重新部署。

### D. 后续如何避免问题
- 后续所有项目修改严格按工作流执行
- 每次修改前检查工作流检查表
- 定期回顾经验记录，提取共性预防措施

---

## 2026-04-28-22-55-15 — 执行环境 Node.js 缺失

### A. 具体问题
执行 `npm run lint` 和 `npm run build` 时提示 `npm: command not found`，当前 Shell 环境未安装 Node.js 运行时。

### B. 产生原因
AI 助手运行的容器/环境与项目实际开发环境分离，后者才装有 Node.js 和 npm。

### C. 解决方案
- 文档创建和 git 操作可在 AI 环境中完成
- 构建验证（`npm run lint`、`npm run build`、`npm start`）需由用户在本地开发环境执行，或 AI 提供精确命令由用户自行运行
- **实际解决**: 用户提供了 sudo 权限和密码后，AI 通过 NodeSource 安装了 Node.js 22.x（`curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash` + `sudo apt install -y nodejs`），随后成功执行了完整部署流程
- 在经验记录中标注此限制和解决方案，提醒后续流程

### D. 后续如何避免问题
- 每次执行阶段 6 和阶段 9 前，先检查 `which npm`
- 如 npm 不可用，向用户申请 sudo 权限安装 Node.js，或使用项目预设的容器/CI 环境
- 如用户无法提供 sudo，则提供精确的手动执行命令清单
- 考虑在 CI/CD 流程中统一构建环境

---

## 2026-04-29-21-27-10 — 组件目录扁平化重构

### A. 具体问题
将 `src/components/` 下 7 个子目录共 11 个组件文件扁平化到根目录时，需要同步更新多处 import 相对路径，存在遗漏风险。

### B. 产生原因
组件文件在子目录中时，引用 `lib/utils.ts` 的路径为 `../../lib/utils`，引用其他组件的路径为 `../workspace/XXX`；扁平化到根目录后，这些路径需分别变更为 `../lib/utils` 和 `./XXX`。涉及文件多、路径变体多，手工逐一修改容易遗漏。

### C. 解决方案
- 重构前先通过 `grep` 全局扫描所有 import 语句，建立完整的"文件-旧路径-新路径"映射表
- 移动文件和修改路径分两步执行，每步完成后都用 grep 验证旧路径模式是否完全消失
- 最后运行 `npm run lint` 和 `npm run build` 作为最终校验，TypeScript 编译器会捕获任何遗漏的路径错误

### D. 后续如何避免问题
- 任何涉及文件移动的 refactoring，都必须先全局 grep 所有相对路径引用，形成清单后再执行
- 执行后立即用 grep 反向验证旧路径模式残留
- 始终将 `npm run lint` 作为路径重构后的强制检查步骤

---

## 2026-04-29-21-51-19 — 全栈系统改造（FastAPI + SAM2 + PostgreSQL + Redis + MinIO）

### A. 具体问题
1. 将纯前端 React 应用改造为全栈系统时，工程涉及后端框架替换、数据库设计、对象存储、AI 推理引擎、前端状态管理重构等多个复杂模块，单次执行工程量大。
2. 系统磁盘空间（24G）不足，PyTorch CUDA 版本（>2GB）和 sam2 pip 包编译（需下载 CUDA 工具链 + 编译 C++ 扩展）均因 `OSError: No space left on device` 失败。
3. MinIO 对象存储在磁盘紧张时报 `XMinioStorageFull`，导致文件上传失败。
4. 前端 Agent 执行时因目录扁平化后子目录不存在，产生多次 `File not found` 错误。

### B. 产生原因
1. 项目改造范围超出单次会议合理容量，涉及 15+ 后端文件、10+ 前端文件、4 个基础设施服务、1 个 AI 模型栈。
2. 系统盘仅有 24GB，conda 环境（2GB）、node_modules（222MB）、模型权重（1.5GB）、MinIO 帧文件（1.4GB）叠加后迅速耗尽空间。
3. sam2 的 pip 包依赖 torch>=2.5.1，pip 会尝试重新下载完整 torch wheel（530MB）作为 build dependency，即使 torch 已安装。
4. 前端 Agent 的 prompt 中未明确说明组件目录已扁平化，Agent 仍尝试读取旧的子目录路径。

### C. 解决方案
1. **任务拆分 + 并行 Agent**: 将后端和前端代码编写拆分为两个独立 Agent 并行执行，基础设施安装与代码编写并行推进，显著缩短总耗时。
2. **磁盘管理策略**:
   - 安装 PyTorch CPU 版本替代 CUDA 版本（占用更小）
   - 只保留 sam2_hiera_tiny.pt（149MB），删除其他大模型
   - 清理 conda pkgs 缓存（释放 600MB+）
   - 删除 MinIO 中解析生成的临时帧文件（释放 1.4GB）
3. **sam2 安装降级**: 当前环境以 stub 模式运行，提供 `install_sam2.sh` 脚本供用户在扩展磁盘后执行真实安装。
4. **API 路径修复**: 后端添加 `/api/auth/login` 路由，修复前端 api.ts 中 `/api/predict` → `/api/ai/predict` 的路径不匹配。
5. **MinIO API 适配**: minio 7.2.x 中 `presigned_url()` 已改为 `get_presigned_url()`，需从 `datetime.timedelta` 传入 expires。

### D. 后续如何避免问题
1. **大型改造前先做磁盘评估**: 执行 `df -h` 确认可用空间 > 5GB 再开始安装大型依赖。
2. **AI 模型依赖延迟加载**: 所有 AI 推理引擎必须实现 graceful fallback，模型缺失时不阻塞系统启动。
3. **Agent prompt 需同步最新目录结构**: 给 Agent 的上下文必须包含当前真实的文件路径，避免 `File not found`。
4. **构建依赖隔离**: 使用 `--no-build-isolation` 或 `--no-deps` 安装源码包，避免 pip 重复下载已安装的依赖。
5. **MinIO 空间监控**: 定期清理解析产生的临时帧文件，或配置 MinIO 使用独立大容量数据盘。

---

## 2026-04-29-22-29-26 — README.md 完善

### A. 具体问题
项目全栈改造完成后，README.md 仍为旧版 AI Studio 模板，未反映真实系统架构和部署流程，新用户无法按文档独立完成部署。

### B. 产生原因
前期聚焦功能开发，文档滞后；改造涉及后端/前端/基础设施/AI 模型多个层级，文档编写工作量大。

### C. 解决方案
按代码编纂工作流，编写需求分析→实现方案→测试方案，然后一次性重写 README.md，涵盖系统架构、技术栈、目录结构、分步部署命令、环境变量、访问凭证、常见问题。

### D. 后续如何避免问题
- 任何架构级变更后，同步更新 README.md
- 将 README 纳入代码审查清单
- 新成员入职时按 README 走通部署流程作为验收标准

---

## 2026-04-29-22-37-36 — 登录报错 ERR_CONNECTION_REFUSED

### A. 具体问题
用户通过浏览器访问前端并点击登录时，控制台报错 `POST http://localhost:8000/api/auth/login net::ERR_CONNECTION_REFUSED`，登录失败。

### B. 产生原因
1. 前端 `api.ts` 中 `baseURL` 硬编码为 `http://localhost:8000`
2. 用户通过局域网 IP（如 `http://192.168.3.11:3000`）访问前端，而非 `http://localhost:3000`
3. 浏览器运行前端代码时，将 `localhost` 解析为**用户本地机器**（而非服务器），向用户本地 8000 端口发请求
4. 用户本地 8000 端口无服务，TCP 连接被拒绝
5. 同时后端 CORS 配置仅允许 `http://localhost:3000`，未允许 IP 地址访问

### C. 解决方案
1. 将 `src/lib/api.ts` 的 `baseURL` 从 `http://localhost:8000` 改为服务器实际 IP `http://192.168.3.11:8000`
2. 将 `backend/config.py` 的 `cors_origins` 从 `["http://localhost:3000"]` 扩展为 `["http://localhost:3000", "http://192.168.3.11:3000"]`
3. 重启后端服务使 CORS 生效，重新构建前端使 baseURL 生效

### D. 后续如何避免问题
1. 部署时统一使用服务器实际 IP 替代 localhost，避免浏览器端解析歧义
2. 前端 baseURL 应支持环境变量配置（如 `VITE_API_BASE_URL`），不同环境注入不同值
3. 后端 CORS origins 应使用配置化列表，支持开发/测试/生产多环境
4. 在 README 中明确说明访问地址，提醒用户必须使用服务器 IP 而非 localhost

---

## 2026-04-29-22-49-38 — WebSocket 404 + 项目状态异常 + 导入按钮无响应

### A. 具体问题
1. 控制台报错 `WebSocket connection to 'ws://localhost:8000/ws/progress' failed`
2. 项目库中"测试视频项目"状态显示为"异常"（红色）
3. "导入多媒体资源"按钮点击无反应

### B. 产生原因
1. `websocket.ts` 仍为 `ws://localhost:8000/ws/progress`，未随前端 baseURL 一起改为服务器 IP
2. FastAPI 后端未实现 `/ws/progress` WebSocket 路由
3. Uvicorn 缺少 WebSocket 支持库（`websockets` 或 `wsproto` 未安装）
4. 后端 `Project` 模型默认状态为 `"pending"`，前端只识别 `"Ready"` / `"Parsing"`，其他状态均显示"异常"
5. `ProjectLibrary.tsx` 中"导入多媒体资源"按钮为纯静态 `<button>`，无 `onClick` 事件和隐藏的 `<input type="file">`

### C. 解决方案
1. `websocket.ts` URL 改为 `ws://192.168.3.11:8000/ws/progress`
2. `backend/main.py` 添加 `@app.websocket("/ws/progress")` 路由 + `ConnectionManager` 连接管理
3. `pip install websockets` 为 Uvicorn 提供 WebSocket 协议支持
4. 后端 `Project.status` 默认值从 `"pending"` 改为 `"Ready"`
5. 前端增加 `'pending'` / `'Pending'` 状态分支显示"待处理"
6. 为导入按钮添加 `onClick` + `useRef<HTMLInputElement>` + 隐藏文件输入框，调用 `uploadMedia` API

### D. 后续如何避免问题
1. 任何涉及网络地址的配置（HTTP / WebSocket）必须同步修改，不能遗漏
2. 新增 WebSocket 功能时，同步检查 Uvicorn 是否安装了协议支持库
3. 前后端状态枚举值必须对齐，或后端返回时做映射转换
4. 所有按钮类 UI 元素必须绑定事件处理器，禁止纯装饰性按钮

---

## 2026-04-29-23-02-56 — Logo 部署 + Favicon 404

### A. 具体问题
1. `Sidebar.tsx` 引用 `/Logo.png` 但文件不存在，logo 无法显示
2. 浏览器控制台报 `favicon.ico 404`

### B. 产生原因
1. `logo_square.png` 位于项目根目录，但 Vite 不会自动将根目录文件暴露为静态资源
2. 前端代码引用路径为 `/Logo.png`（首字母大写），与实际文件名 `logo_square.png` 不匹配
3. `index.html` 无 favicon 声明，浏览器默认请求 `/favicon.ico`

### C. 解决方案
1. 创建 `public/` 目录（Vite 原生支持，自动暴露到根 URL）
2. 将 `logo_square.png` 复制为 `public/logo.png`
3. `Sidebar.tsx` 引用路径改为 `/logo.png`
4. `index.html` 添加 `<link rel="icon" type="image/png" href="/logo.png" />`

### D. 后续如何避免问题
1. 静态资源（图片、字体、favicon）统一放入 `public/` 目录
2. 资源文件名与代码引用严格保持大小写一致
3. 每个项目初始化时都配置好 favicon，避免浏览器自动请求不存在的 .ico

---

> 新增经验请追加到文件末尾，保持时间倒序或正序均可，但需确保每条经验包含完整的 A/B/C/D 四段。


---

## 2026-04-30-22-38 — 模板库保存失效 + 颜色重复 + 拖拽排序 + OntologyInspector 联动

### A. 具体问题
1. `TemplateRegistry.tsx` 点击保存无反应，模板数据未写入后端
2. `addClass()` 所有新类别颜色相同（硬编码 `#06b6d4`）
3. z-index 仅显示文本，无拖拽调整功能
4. `OntologyInspector.tsx` 完全硬编码 mock 数据，未与模板库联动
5. 后端 seed 脚本启动时报 `description` 是 `Template` 的无效关键字参数

### B. 产生原因
1. 前端 `updateTemplate` 用 `PUT` 请求，但后端 `@router.patch` 只接受 `PATCH` → 405
2. `createTemplate` payload 未传 `color`/`z_index`，但后端 `TemplateBase` 要求 `color` 必填
3. `TemplateUpdate` schema 缺少 `classes`/`rules` 字段，update 时分类数据被丢弃
4. `Template` ORM 模型缺少 `description` 列，seed 脚本直接传 `description=...` 导致 TypeError
5. `api.ts` 的 `_mapTemplate` 返回了 `color`/`z_index`，但前端 `Template` TS 接口未定义这些字段，导致 TS 编译错误被忽略后引发运行时字段缺失
6. `OntologyInspector` 从未接入 store 或 API，完全独立维护一份 mock 数据

### C. 解决方案
1. **对齐 HTTP 方法**: `api.ts` `updateTemplate` 改为 `apiClient.patch(...)`
2. **补齐必填字段**: `handleSave` 中构造 payload 时始终携带 `color` 和 `z_index`
3. **扩展 Update Schema**: `backend/schemas.py` `TemplateUpdate` 添加 `classes` 和 `rules` 字段
4. **数据库 Schema 补丁**: `ALTER TABLE templates ADD COLUMN description TEXT;` + `models.py` 添加 `description = Column(Text, nullable=True)`
5. **TS 类型对齐**: `_mapTemplate` 只返回 `Template` 接口中定义的字段，避免隐式 any
6. **HTML5 拖拽排序**: 用 `draggable` + `dataTransfer.setData` 实现项间拖拽，释放后重算 z-index（数组倒序 ×10）
7. **OntologyInspector 重写**: 从 store 读取 `templates`/`activeTemplateId`，顶部加模板选择器，支持自定义分类添加
8. **工作区预加载**: `VideoWorkspace` 进入时若 `templates` 为空自动 `getTemplates()`

### D. 后续如何避免问题
1. **前后端 HTTP 方法必须严格对齐**: FastAPI `@router.patch` 只响应 PATCH，前端不能用 PUT/POST 代替，否则报 405
2. **Schema 变更必须双向同步**: 后端 `BaseModel` 添加字段后，前端 payload 类型、ORM 模型、数据库表必须同时更新
3. **TS 接口必须与 API 响应严格一致**: `_mapTemplate` 返回的字段如果不在 `Template` 接口中，必须通过 `&` 扩展接口或过滤字段，避免运行时丢失数据
4. **Mock 数据必须尽早替换为真实数据流**: 任何硬编码的 mock 数据在联调阶段都是技术债，应在功能开发初期就接入 store/API
5. **seed 脚本必须与 ORM 模型严格一致**: 新增模型字段后，seed 逻辑也要同步更新，否则启动即报错
