# PROGRESS · banksys_szai4 〔本项目活记忆 · 状态机〕

> **作用**:这是项目的"存档点"。任意 AI、任意重启会话,读它即可知道当前做到哪、下一步做什么、踩过什么坑。
> **更新时机**:每完成一个有意义步骤、每次会话结束前。
> **格式要求**:时间倒序,最新在上;短、准、可接力。

---

## 当前状态 (最后更新: 2026-06-07 · by AI)

- **阶段**: `PR #4 已合并；首次 CD 因 apt 超时失败（坑-007），已修 Dockerfile，准备发修复 PR`
- **开发策略**: 按用户要求 US-3~US-6 全部本地开发并测试完成后，再统一进 CI/CD（一条大分支一个 PR）
- **上一步完成**: 定位 CD 失败根因（apt-get 装 curl 在国内服务器超时），改为 Python 健康检查移除 apt
- **下一步**: fix/cd-apt-timeout 分支 → 发 PR → 你合并 → CD 重跑 → 验证端口 8004 健康检查
- **PR**: #4 已合并；修复 PR 待建
- **阻塞项**: 无（端口已固定 8004，见坑-005）

---

## 待办清单 (TODO,按优先级)

### 第一批：初始化项目工程化与 CI/CD（US-1）✅ 已完成
- [x] ① 建仓 + 配 Secrets
- [x] ② 开 feature 分支
- [x] ③ 本地模块化开发（8 个模块）
- [x] ④ 本地 CI 自检
- [x] ⑤ 触发 PR（PR #1）
- [x] ⑥ 人工审核 → 合并 → CD 自动部署（端口 8005）

### 第二批：数据加载与预处理模块（US-2）✅ 已完成
- [x] 实现 app/models/data_loader.py（load_train_data、load_test_data、辅助函数）
- [x] 编写 test_data_loader.py（25 个测试，100% 覆盖率）
- [x] 本地自检 + 提 PR（PR #3）
- [x] 修复 ruff 版本锁定问题
- [x] 修复 CD git ownership 问题
- [x] CD 部署成功（端口 8006）

### 第三批：数据分析交互页面（US-3）✅ 已完成（本地）
- [x] 实现数据可视化逻辑（app/models/visualizer.py，10 函数，100% 覆盖）
- [x] 实现 app/pages/01_data_analysis.py（概览/饼图/类别分析/年龄分析/数值分布，交互式）
- [x] 精简 main.py 为首页，改用 Streamlit 原生多页导航
- [x] 编写 test_visualizer.py（24 个测试）+ 真实数据冒烟验证
- [x] 顺手清理 data_loader.py 死代码、对齐 .gitignore 模型路径
- [ ] 提 PR（按策略推迟到 US-6 完成后统一进 CI/CD）

### 第四批：模型训练脚本（US-4）✅ 已完成（本地）
- [x] 实现 app/ml/train.py（ColumnTransformer+OneHot+RandomForest Pipeline，固定种子）
- [x] app/ml/model/ 已在 .gitignore（git check-ignore 验证通过）
- [x] 本地真跑训练：AUC 0.8964 / 准确率 0.8742，产出 model.pkl
- [x] 编写 test_train.py（10 个测试），CLI 支持 --overwrite/--check-auc
- [ ] 提 PR（推迟到统一进 CI/CD）

### 第五批：预测服务核心逻辑（US-5）✅ 已完成（本地）
- [x] 实现 app/models/predictor.py（load_model 带 lru_cache，predict 返回 subscribe/probability/confidence）
- [x] 编写 test_predictor.py（16 个测试，100% 覆盖）：正常/模型缺失/缺失特征/非法数值/未知类别/响应时间
- [x] 真实模型端到端验证：高意愿 0.90/high，低意愿 0.35；预热后单次 <1s
- [ ] 提 PR（推迟到统一进 CI/CD）

### 第六批：在线预测页面（US-6）✅ 已完成（本地）
- [x] 实现 app/pages/02_prediction.py（点选式表单，类别选项从数据动态取值）
- [x] 集成 predictor 模块（结果含标签/概率进度条/置信度/建议文案，含模型缺失友好提示）
- [x] 本地启动 Streamlit 验证：/_stcore/health=200、data_analysis/prediction 页面均 200
- [ ] 提 PR（推迟到统一进 CI/CD）

### 第七批：测试覆盖与质量门禁完善（US-7 & US-8）
- [x] 核心逻辑覆盖率：data_loader/visualizer/predictor 均 100%（UI 页面按规范不计）
- [x] 注册 pytest unit marker，消除测试告警
- [x] 修复 Dockerfile：构建时训练模型 + 装 curl + 健康检查端点 /_stcore/health（坑-006）
- [x] 推分支 + 发 PR（#4），CI 复检全绿（含 docker build）
- [ ] 人工审核 → 合并 → CD 部署 → 验证端口 8004 健康检查

---

## 关键决策记录 (ADR)

| 日期 | 决策 | 理由 |
|---|---|---|
| 2025-06-07 | 选择 Streamlit 作为 Web 框架 | 课程要求；快速构建数据应用；适合数据分析与模型演示场景 |
| 2025-06-07 | 模型训练离线，预测在线 | 训练是重操作不适合实时请求；预测是轻操作需要快速响应 |
| 2025-06-07 | 数据集进 Git，模型产物不进 Git | 教学用公开数据，方便复现；模型产物二进制大文件，不应进版本控制 |
| 2025-06-07 | 端口选择 8004 | 课程指定端口；Streamlit 默认 8501，Docker 映射到主机 8004 |

---

## 已知坑 (GOTCHAS)

- **坑-001**: Dockerfile ARG 作用域问题
  - 现象: CI 构建时 `PIP_INDEX_URL` 为空字符串，pip 安装失败
  - 根因: ARG 在 FROM 之前定义，作用域在 FROM 后结束
  - 解决: 在 FROM 后重新声明 ARG，使用 `--index-url` 替代 `-i`
  - 验证: CI Docker 构建步骤通过

- **坑-002**: CD 部署 git 目录检查
  - 现象: 首次部署时 `/opt/banksys` 存在但不是 git 仓库，`git clone || git pull` 失败
  - 根因: 目录存在非空时 `git clone` 失败；非 git 目录时 `git pull` 失败
  - 解决: 先检查是否为 git 目录（`[ -d .git ]`），是则 pull，否则删除后重新克隆
  - 验证: CD 部署成功，端口 8005，健康检查 ok

- **坑-003**: ruff 版本不一致导致 CI 格式检查失败
  - 现象: 本地 `ruff format --check` 通过，CI 失败（1 file would be reformatted）
  - 根因: CI runner 和本地 ruff 版本不同（0.1.0 vs 0.6.4）
  - 解决: 固定 requirements-dev.txt 中 ruff 版本为 `==0.6.4`，CI 中显式安装相同版本
  - 验证: CI 格式检查通过

- **坑-004**: CD git ownership 错误
  - 现象: `fatal: detected dubious ownership in repository at '/opt/banksys'`
  - 根因: Docker 容器操作后目录所有权变化，git 检测到可疑所有权
  - 解决: CD 脚本开头添加 `git config --global --add safe.directory /opt/banksys`
  - 验证: CD 部署成功

- **坑-008**: Streamlit 多页应用容器内 `ModuleNotFoundError: No module named 'app'`（已修复）
  - 现象: 部署后页面报 `from app.models import visualizer` → No module named 'app'（/app/app/pages/01_data_analysis.py）
  - 根因: 容器 `CMD streamlit run app/main.py` 只把主脚本目录 /app/app 加进 sys.path，而 `import app.*` 需要父目录 /app 在路径上；本地用 `python -m streamlit` 会把 cwd 加进路径所以没暴露
  - 隐蔽点: 本地只验了 HTTP 200，但 Streamlit 页面脚本在 websocket 连接时才执行，200 不代表页面渲染成功
  - 解决: Dockerfile 增加 `ENV PYTHONPATH=/app`；本地复现验证：无 PYTHONPATH 报同样错，设置后三模块导入 OK
  - 教训: 验证 Streamlit 多页应用要真正打开每个页面触发脚本执行，不能只看健康检查/HTTP 200
  - 验证: 待 PR #5 合并后 CD 确认

- **坑-007**: Docker 内 apt-get 装 curl 在国内服务器超时导致 CD 失败（已修复）
  - 现象: CD "Deploy to server" 跑 10 分钟后 `Run Command Timeout`；日志 `Fetched 9975 kB in 8min 6s (20.5 kB/s)`
  - 根因: 为容器 HEALTHCHECK 装 curl 加了 `apt-get update/install`，服务器访问 deb.debian.org 极慢（20.5 kB/s），超过 appleboy/ssh-action 默认 10 分钟命令超时。CI 在国外 runner 上 apt 快所以没暴露
  - 解决: Dockerfile 彻底移除 apt-get/curl，容器 HEALTHCHECK 改用镜像自带 Python（urllib）访问 /_stcore/health，完全绕开 apt
  - 教训: 国内服务器构建镜像要避免走 Debian 官方源；能用镜像已有运行时（python）就别额外 apt 装包
  - 验证: 待新 PR 合并后 CD 确认

- **坑-006**: 模型产物不进镜像导致生产预测失效（已修复）
  - 现象: model.pkl 被 .gitignore 排除，CD 克隆代码后镜像内无模型，预测页报"模型未找到"
  - 根因: 模型是离线产物不进 Git，但 Dockerfile 未在构建时训练
  - 解决: Dockerfile 在 COPY app/ 与 data/ 后增加 `RUN python -m app.ml.train --overwrite`，把模型烤进镜像；同时 slim 镜像补装 curl、HEALTHCHECK 端点改为 /_stcore/health
  - 验证: 待 CI docker build 与 CD 部署确认

- **坑-005**: 端口回退问题（已修复）
  - 现象: 每次部署端口不同（8004→8005→8006），端口递增
  - 根因: 端口检查在容器删除之前；多个旧容器占用不同端口
  - 解决: 先停止所有占用 8000-8010 端口的容器，等待释放，固定使用 8004
  - 验证: CD 成功，固定部署在端口 8004

---

## 里程碑 (DONE)

- [x] 2025-06-07：填写项目规范文档（00/01/PROGRESS）
- [x] 2025-06-07：创建 GitHub 仓库（banksys_szai4）
- [x] 2025-06-07：US-1 完成 - 初始化项目工程化与 CI/CD
  - 完整跑通六步交付流程
  - CI/CD 流水线配置完成
  - CD 成功部署到服务器（端口 8005）
- [x] 2025-06-07：US-2 完成 - 数据加载与预处理模块
  - 实现 data_loader.py 模块（4 个函数，100% 覆盖率）
  - 编写 25 个测试用例
  - 修复 ruff 版本锁定问题
  - 修复 CD git ownership 问题
  - CD 部署成功（端口 8006）

> 反臃肿:里程碑超过 15 条时,把更早内容合并成一行摘要,保持本文件可快速阅读。
