137 lines
9.5 KiB
Markdown
137 lines
9.5 KiB
Markdown
# ADR-017: 模組化收尾路線圖(Phase 3f)
|
||
|
||
- **狀態**: Accepted
|
||
- **日期**: 2026-04-29
|
||
- **觸發**: 12 Agent 全景盤點(debugger / refactor-specialist / critic / db-expert / explorer)
|
||
- **相關 ADR**: ADR-008(188 拓撲)、ADR-011(資源隔離)、ADR-016(cache fingerprint)
|
||
- **相關 Memory**: `docs/memory/project_phase3f_cleanup_roadmap.md`、`docs/memory/feedback_db_metadata_import.md`、`docs/memory/history_logs.md`
|
||
|
||
## 背景
|
||
|
||
Phase 3e(4/28-29)完成 app.py 7,386→6,590 行(-10.8%),但**僅完成「搬檔」,未完成「拆乾淨」**。12 Agent 盤點揭露 6 個面向的殘留問題,且發現新的 critical 風險(DB metadata import 漏洞、孤兒表)。
|
||
|
||
## 完成度真相(2026-04-29 盤點基線)
|
||
|
||
| 維度 | 完成度 | 真相 |
|
||
|------|--------|------|
|
||
| 路由搬檔(41 條 → 28 BP)| 68% | 13 條 app.py 獨有未遷移 |
|
||
| 路由「拆乾淨」 | **0%** | app.py 41 條一條未刪,28 條與 BP 同 URL 雙寫,由 first-registered-wins 決定行為 |
|
||
| `USE_MODULAR_ROUTES` 開關 | **0%** | `register_blueprints()` 從未被 app.py 呼叫,整套設計死碼 |
|
||
| services/ 模組化 | ~95% | 唯一乾淨;3 個孤兒 service(0 引用)|
|
||
| 模板統一 | ~50% | 三目錄並存 + 1 空檔 + 3 死檔 + 2 TemplateNotFound 風險 |
|
||
| DB schema vs Model | ~60% | manager.py import 漏 3 模組;6 張表有 SQL 無 ORM;realtime_sales_monthly 孤兒 |
|
||
|
||
## 實作狀態補記(2026-05-13)
|
||
|
||
上表是 2026-04-29 立案時的盤點基線;後續整改已把多個 HIGH 項目落地並加上回歸守門:
|
||
|
||
- `app.py` 已收斂為 Flask bootstrap / Blueprint registration / 啟動自檢,active `@app.route` 為 0,並由 `tests/test_phase3f_cleanup_contracts.py::test_app_py_stays_blueprint_only_for_routes` 守住。
|
||
- 舊 `USE_MODULAR_ROUTES`、`register_blueprints()`、`MODULAR_ENDPOINTS`、duplicate cleanup shim 已移除;`routes/__init__.py` 僅保留 package docstring。
|
||
- DB metadata / migration 覆蓋已由 `tests/test_migration_metadata_coverage.py` 與啟動自檢守住,v5 observability / Market Intel 等表不再只靠手動口述。
|
||
- 模板路徑已收斂為 `templates/` 與 `web/templates/vendor_stockout/`;根層 placeholder `templates/list.html` 已刪除並有測試防回歸。
|
||
- 大檔治理仍未結案:`routes/openclaw_bot_routes.py`、`routes/admin_observability_routes.py`、`routes/sales_routes.py`、`scheduler.py` 仍超過 800 行,後續只能做 bugfix、安全修補或往外抽模組。
|
||
|
||
## 決策
|
||
|
||
執行 **Phase 3f 五階段收尾**,總工期估 12-15 小時(不含驗證),每階段獨立 commit、每階段 critic 審查、每階段先 SSH 驗證 production。
|
||
|
||
### Phase 3f-0:DB metadata 救急(30 分鐘,最高優先)
|
||
1. `database/manager.py` 補完 import:
|
||
- `permission_models`(Permission, UserPermission)
|
||
- `vendor_models` 補 VendorList / VendorEmail / EmailSendLog
|
||
- `ai_models` 顯式 import 4 個 AI history/template class
|
||
- `autoheal_models` 顯式 import 7 個 AIOps class,移除 `ai_models.py` re-export shim
|
||
2. 處置 `realtime_sales_monthly` 孤兒表:
|
||
- 選項 A:建 `database/realtime_sales_models.py`(推薦,補 ORM 一致性)
|
||
- 選項 B:移除 `app.py:693` 的 import,metrics 不依賴此 model
|
||
3. PostgreSQL 與 SQLite 初始化都執行全域 `Base.metadata.create_all()`;PostgreSQL 路徑以 process-local guard + advisory lock 保護,避免一般流量重複碰 DDL
|
||
4. `app.py` 啟動加 metadata self-check,缺表直接 `SystemExit`
|
||
5. `docker/postgres/init/01-init.sql` 的 `realtime_sales_monthly` 欄位同步 ORM,避免 fresh volume 先由 init.sql 建出窄表後 create_all 無法補欄位
|
||
|
||
**驗收**:Base.metadata 含全 34 個 table;create_all 在新環境零漏。
|
||
|
||
### Phase 3f-1:路由雙註冊徹底解(4-6 小時,P9 切分)
|
||
|
||
**策略**:保留 Blueprint,刪 app.py 對應 `@app.route`(28 條),13 條 app.py 獨有遷至 BP。實作順序要先處理 API shadow、次要頁面與 `/brand_assets`,首頁 `/` 最後動,因為多處 `url_for('index')` 仍依賴 app endpoint name。
|
||
|
||
子任務(按 BP 分組獨立 commit):
|
||
|
||
| Sprint | 範圍 | 動作 |
|
||
|--------|------|------|
|
||
| 3f-1-a | dashboard_bp | 刪 app.py:722 `/`;確認 BP 版可用 |
|
||
| 3f-1-b | edm_bp | 刪 app.py:1029, 1280 兩條 |
|
||
| 3f-1-c | export_bp | 刪 app.py:1431-1905 共 9 條;補 `/api/export/excel/seasonality_detail` 進 BP |
|
||
| 3f-1-d | api_bp | 刪 app.py:2219-2451 共 6 條 trigger/run |
|
||
| 3f-1-e | import_bp | 刪 app.py:2691, 2980 兩條 |
|
||
| 3f-1-f | monthly_bp | 刪 app.py:3076, 3083 兩條 |
|
||
| 3f-1-g | sales_bp | 刪 app.py:3592-5809 共 7 條;**同時刪 routes/sales_routes.py 6 處 wrapper(174-348)**,把實作搬進 BP |
|
||
| 3f-1-h | system routes 補強 | 現有 `system_bp` 有 `/api/system` prefix;公開 URL(`/health` `/metrics` `/logs` `/settings` 等)需新建無 prefix 的 `system_public_bp` 或拆成 public/internal 兩個 BP,不能直接塞進現有 `system_bp`。`/abc_analysis/detail` 歸 `sales_bp`。 |
|
||
| 3f-1-i | 死碼清除 | 刪 `routes/__init__.py:32-177` 整套(register_blueprints / MODULAR_ENDPOINTS / is_endpoint_modular / cleanup_duplicate_routes)+ `config.py:244-254` USE_MODULAR_ROUTES |
|
||
|
||
**驗收**:
|
||
- `app.url_map.iter_rules()` 任一 (rule, methods) 皆唯一
|
||
- 啟動加 self-check:duplicate detect 直接 raise SystemExit
|
||
- 全 endpoint smoke test(critic 監督)
|
||
|
||
### Phase 3f-2:Cache 統一(2-3 小時)
|
||
1. Dockerfile + docker-compose.yml gunicorn 加 `--preload`,只作為 COW 記憶體優化,不作為一致性保證
|
||
2. 新建 `services/cache_manager.py` 套用 ADR-016 fingerprint 模式
|
||
3. 移除三處重複 `_SALES_*_CACHE` 定義(app.py:82, routes/sales_routes.py:32, services/cache_service.py:16)→ 統一 import `services.cache_manager`
|
||
4. `clear_cache` endpoint 從「N-POST 廣播」改「DB fingerprint pull」(已在 daily_sales bp 完成,擴及 sales)
|
||
|
||
### Phase 3f-3:穩定性補強(1-2 小時)
|
||
1. 先在 `services/event_router.py` 補同步安全 facade(如 `notify_failure()` / `dispatch_sync()`),因現有 `EventRouter` 只有 async `dispatch()`,scheduler 直接呼叫會掉告警
|
||
2. scheduler.py 7 處裸 `except: pass`(line 243, 254, 582, 587, 653, 890, 1222)改為 `except Exception as e: logger.exception(...)`,P1/P2 經 EventRouter 同步 facade 強制告警
|
||
3. docker-compose.yml 刪 7 條死路徑 mount(vendor_routes.py / vendor_stockout_*.html)
|
||
4. routes/vendor_routes.py:28-30 template_folder 改用絕對路徑,移除靠 Flask fallback 的脆弱依賴
|
||
|
||
### Phase 3f-4:模板統一(2-3 小時)
|
||
1. 補 2 個 TemplateNotFound:`trends.html`(trend_routes.py:33)、`login_history.html`(user_routes.py:40)→ 找回或停用對應 endpoint
|
||
2. 刪 `web/templates/sales_analysis.html`(0-byte 空檔)
|
||
3. 刪 3 個雙寫死檔:`web/templates/brand_assets.html`、`web/templates/growth_analysis.html`、根目錄 `logs.html`
|
||
4. 根目錄 11 個 *.html 全搬 `templates/`,確認 docker-compose 舊 mount 已清理後,再移除 `app.py:185-188` ChoiceLoader 的 BASE_DIR fallback
|
||
5. `web/templates/` 僅留 `vendor_stockout/` 子目錄(vendor_bp 自帶 template_folder)
|
||
|
||
### Phase 3f-5:死碼清除(30 分鐘)
|
||
1. 確認並刪除 3 個孤兒 service:
|
||
- `services/elephant_alpha_decision_router.py`
|
||
- `services/telegram_ai_integration.py`
|
||
- `services/watcher_agent.py`
|
||
2. `.env.example` 補齊 15+ 個程式碼實際讀但 example 缺的變數(AIDER_*, ELEPHANT_ALPHA_*, HEAL_SSH_*, NVIDIA_API_KEY 等)
|
||
|
||
## 風險與回滾
|
||
|
||
每階段獨立 commit,CI/CD 失敗自動回滾(依賴 ADR-014 防線)。Phase 3f-1 為高風險區,必須:
|
||
1. 先在本機 `python app.py` 啟動驗證 url_map 無重複
|
||
2. critic 審 diff 過關
|
||
3. 部署後 SSH 健康檢查 `/health` + 抽測 5 條核心 endpoint
|
||
|
||
## 不做的事
|
||
|
||
- 不做模組化開關設計回填(USE_MODULAR_ROUTES 已死,直接刪)
|
||
- 不做 ORM 全面遷移(25 張無 SQL migration 軌跡的 table 留待 Phase 4)
|
||
- 不在 3f-0 修 `docker/postgres/init/01-init.sql` 與 ORM 的 `products` 中文欄位 schema drift;這是 Phase 4 migration 題
|
||
- 不做 openclaw_bot_routes.py(5,543 行)拆解(留 Phase 4)
|
||
- 不撤換 NGROK Token(統帥 2026-04-29 明示先忽略)
|
||
|
||
## 完成定義
|
||
|
||
- app.py < 5,000 行
|
||
- url_map 零重複
|
||
- create_all 新環境零漏表
|
||
- 模板僅存 `templates/` + `web/templates/vendor_stockout/`
|
||
- gunicorn `--preload` 啟用、cache 跨 worker 一致
|
||
- scheduler 7 處 except 全改、docker-compose mount 全清
|
||
|
||
## 2026-04-30 補充決策:模組化治理守門
|
||
|
||
Phase 3f 已陸續完成 DB metadata、路由註冊、cache、scheduler、模板與 orphan cleanup 的多個收斂項目,但 line-count 盤點仍顯示 15 個 Python 檔案超過 800 行。為避免後續功能開發再次把 route / service 寫成巨檔,本 ADR 補充以下守門規則:
|
||
|
||
1. `app.py` 只保留 Flask app bootstrap、Blueprint registration、啟動自檢、版本與全域設定;不得新增 route 或商業邏輯。
|
||
2. `routes/` 必須保持 thin controller,重複邏輯需抽到 `services/` 或 `utils/`。
|
||
3. 超過 800 行的 Python 檔案列入 `docs/memory/code_modularization_inventory_20260430.md`,只能做安全修補、bugfix、或往外抽模組;新增功能應先拆分。
|
||
4. 新增 Python 檔案超過 600 行需提出拆分理由;超過 800 行需同步更新 inventory 與測試。
|
||
5. `tests/test_modularization_governance.py` 是守門測試:新巨檔沒有被盤點會失敗。
|
||
|
||
詳細操作規則以 `docs/guides/modularization_governance.md` 為準。
|