docs(modularization): 建立模組化治理守門
All checks were successful
CD Pipeline / deploy (push) Successful in 1m36s

This commit is contained in:
OoO
2026-04-30 14:07:10 +08:00
parent db21e7e8e8
commit c2e38be43d
13 changed files with 176 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
# EwoooC (MOMO Pro System) — Codex 專案工作規則
> 版本: V13.3
> 版本: V13.4
> 目標: 把專案知識整理成 Codex 可低成本讀取、可持續維護、可安全落地的單一工作入口。
## 1. 入口原則
@@ -131,12 +131,14 @@
- 部署 SOP: `docs/guides/deployment_sop.md`
- DevOps 手冊: `docs/guides/devops_handbook.md`
- 模組化治理: `docs/guides/modularization_governance.md`
- AI 自動化 Session SOP: `docs/guides/ai_automation_session_sop.md`
- AI 競價情報 SOT: `docs/AI_INTELLIGENCE_MODULE_SOT.md`
- Agent 角色矩陣: `docs/guides/codex_agent_roles.md`
- ADR 索引: `docs/adr/README.md`
- Memory 索引: `docs/memory/README.md`
- 歷史紀錄: `docs/memory/history_logs.md`
- 程式碼模組化盤點: `docs/memory/code_modularization_inventory_20260430.md`
- AI 自動化閉環記憶: `docs/memory/ai_automation_closure_20260429.md`
- 憑證手冊: `docs/memory/credentials_passbook.md`

View File

@@ -2,7 +2,7 @@
> 本文件定義專案開發的核心準則與不可違反的規範
> **建立日期**: 2026-01-12
> **當前版本**: V10.20 (ElephantAlpha transient fallback 版)
> **當前版本**: V10.21 (模組化治理守門版)
> **最後更新**: 2026-04-30
---
@@ -178,6 +178,18 @@
---
## 第六之一章:模組化治理規範
### 第 21.1 條:模組分層與大檔治理(強制要求)
- ✅ **正確**:新功能必須先放入對應層級:`routes/` 只處理 HTTP/Blueprint glue`services/` 放商業邏輯與外部 API client`database/` 放 ORM/DB access`utils/` 放無狀態共用工具,`templates/` 放 UI。
- ✅ **正確**`app.py` 只保留 Flask 初始化、Blueprint 註冊、啟動自檢、版本與全域設定;禁止新增 route、長函式或商業邏輯。
- ✅ **正確**:現有超過 800 行的 Python 檔案視為拆分技術債,只能做 bugfix / 安全修補 / 搭橋式抽離;新增功能前應優先抽 shared service 或子模組。
- ✅ **正確**:新增或修改導致 Python 檔案超過 600 行時,必須先評估拆模組;超過 800 行必須更新 `docs/memory/code_modularization_inventory_20260430.md` 與對應測試,並寫出拆分計畫。
- ❌ **禁止**:把可共用邏輯藏在 route、template helper 或單一巨檔內,造成其他模組只能複製貼上。
- **依據**ADR-017、`docs/guides/modularization_governance.md`
---
## 第七章:程式碼品質規範
### 第 22 條:命名規範(強制要求)

View File

@@ -29,8 +29,10 @@
- Scheduler 例外記錄強化:清除 `scheduler.py` 靜默 `except/pass`資源清理、EDM 可選欄位、備份 insight/通知失敗全改為可診斷 log。
- AI metrics baseline 觀測:`/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series避免重啟後 Grafana/Prometheus 看不到 metric names。
- ElephantAlpha transient fallbackNVIDIA NIM timeout、connection error、429 與 5xx 會嘗試下一個 fallback model400 等非暫時性請求錯誤不重試。
- 模組化治理守門:新增 `docs/guides/modularization_governance.md`、`docs/memory/code_modularization_inventory_20260430.md` 與 `tests/test_modularization_governance.py`,盤點並鎖住 15 個 >800 行 Python 大檔。
【下次待辦】
- 依 inventory 優先拆 `routes/openclaw_bot_routes.py`、`routes/sales_routes.py`、`scheduler.py`。
- 觀察 Prometheus scrape 後 `momo_ai_*` baseline 與非 baseline 事件序列是否持續穩定。
- Superset panel 設定與 Smoke 摘要成效觀察。

4
app.py
View File

@@ -95,8 +95,8 @@ except Exception as e:
sys_log.error(f"無法檢測磁碟空間: {e}")
# 🚩 系統版本定義 (備份與顯示用)
# 🚩 2026-04-30 V10.20: ElephantAlpha transient NIM fallback
SYSTEM_VERSION = "V10.20"
# 🚩 2026-04-30 V10.21: Modularization governance guardrail
SYSTEM_VERSION = "V10.21"
# ==========================================
# 🔒 SQL Injection 防護函數

View File

@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.20"
SYSTEM_VERSION = "V10.21"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -112,3 +112,15 @@ Phase 3e4/28-29完成 app.py 7,386→6,590 行(-10.8%),但**僅完
- 模板僅存 `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` 為準。

View File

@@ -0,0 +1,49 @@
# 模組化治理指南
> 目的:讓 EwoooC 後續功能不再回到「單一巨檔、分類不清、共用邏輯複製貼上」的狀態。
## 分層規則
| 層級 | 放什麼 | 不放什麼 |
|---|---|---|
| `app.py` | Flask 初始化、Blueprint 註冊、啟動自檢、版本 | route 實作、DB 查詢、商業邏輯、排程任務 |
| `routes/` | HTTP request/response、表單參數解析、權限 decorator、呼叫 service | 大量資料轉換、外部 API client、可重用計算 |
| `services/` | 商業邏輯、AI pipeline、外部 API client、報表生成、可重用流程 | Flask request 物件、template rendering、route-only glue |
| `database/` | ORM models、DB manager、schema helpers、migration baseline | HTTP/Telegram/AI side effect |
| `utils/` | 無狀態純工具、字串/時間/安全 helper | 需要 DB/session/env-heavy 的流程 |
| `templates/` | Jinja UI | Python business rule |
## 檔案尺寸規則
- 300 行以下:健康範圍。
- 300-600 行:可接受,但新增功能前要確認是否能抽 service/helper。
- 600-800 行警戒範圍PR/commit 需說明為何不拆。
- 800 行以上:技術債範圍;只能做 bugfix、安全修補、或「往外抽」的過渡改動。新增功能應先拆模組。
- 新增超過 800 行的 Python 檔案必須更新 `docs/memory/code_modularization_inventory_20260430.md`,否則 `tests/test_modularization_governance.py` 會失敗。
## 新功能落點決策
1. 先問:這是 HTTP endpoint、商業流程、DB access、外部 API、還是純工具
2. endpoint 只新增到對應 Blueprint不新增到 `app.py`
3. route 內超過 40 行的資料處理,優先抽到 `services/`
4. 兩個以上 route 需要同一段邏輯時,第一次就抽 shared service不複製貼上。
5. service 需要 DB 時,透過既有 `DatabaseManager` / repository-style helper不在 route 裡反覆建立 engine。
6. AI 自動化、告警、自癒相關變更需同步檢查 `docs/AI_INTELLIGENCE_MODULE_SOT.md`
## 拆大檔順序
優先處理「又大又承擔多種責任」的檔案:
1. `routes/openclaw_bot_routes.py`:拆成 route、bot command service、learning/report service、scheduler hook。
2. `routes/sales_routes.py`:拆 dashboard/page routes、API routes、chart/query service。
3. `scheduler.py`:拆 task registry、crawler jobs、report jobs、notification jobs。
4. `routes/ai_routes.py` / `routes/vendor_routes.py`:把資料處理與整合邏輯移到 services。
5. `services/ppt_generator.py`:拆 deck orchestration、chart builders、slide templates。
## Review 檢查表
- 是否新增 `@app.route`?若是,退回改 Blueprint。
- route 是否直接包含大量 SQL、DataFrame、AI prompt 組裝或外部 API 呼叫?若是,抽 service。
- 是否讓既有 >800 行檔案淨增加?若是,先抽模組或更新 inventory 與拆分計畫。
- 是否新增重複 cache / DB manager / Telegram / AI client 初始化?若是,改用既有 service。
- 是否需要更新 ADR、memory、SOT 或 guide若是本次 commit 一起更新。

View File

@@ -18,6 +18,7 @@
| `feedback_db_metadata_import.md` | SQLAlchemy metadata / `create_all()` 漏表鐵律 | 新增 model、修 schema、排查 fresh env 漏表時 |
| `db_connection_pool_singleton_20260430.md` | PostgreSQL `too many clients` 連線池放大事故與 DatabaseManager singleton 修正 | 排查 DB 連線數暴增、route 內反覆初始化 DatabaseManager、SQLAlchemy engine/pool 行為時 |
| `project_phase3f_cleanup_roadmap.md` | ADR-017 執行矩陣與階段紅線 | 正在做 3f 模組化收尾時 |
| `code_modularization_inventory_20260430.md` | Python 大檔盤點、分層規範與拆分工作項目 | 新增功能、拆大檔、審查是否違反模組化治理時 |
| `schema_inventory_baseline.md` | DB 表分類與 drift 基線 | 要收斂 migration / ORM / raw SQL 真相時 |
## 關聯 Guide

View File

@@ -0,0 +1,44 @@
# 程式碼模組化盤點2026-04-30
> 用途:接續 ADR-017 Phase 3f 時,快速知道哪些 Python 檔案仍是大檔技術債,以及新增功能應該放在哪個模組層。
## 盤點結論
- Python 總量:約 64,748 行。
- 最大壓力區:`routes/` 約 21,020 行、`services/` 約 24,533 行。
- `app.py` 已降到 1,206 行,功能定位應固定為 bootstrap / Blueprint registration / startup guard不再承接新 route。
- 目前仍有 15 個 Python 檔案超過 800 行;這些不是禁止修 bug而是禁止繼續塞新功能。
## 超過 800 行檔案清單
| 行數 | 檔案 | 分類 | 拆分方向 |
|---:|---|---|---|
| 5543 | `routes/openclaw_bot_routes.py` | P0 巨型 Blueprint | route / bot command service / report service / scheduler hook |
| 2653 | `routes/sales_routes.py` | P0 巨型 Blueprint | page routes / API routes / chart query service / calendar service |
| 2644 | `scheduler.py` | P0 排程總管 | task registry / crawler jobs / report jobs / notification jobs |
| 1662 | `routes/ai_routes.py` | P1 AI Blueprint | route glue / AI orchestration service / prompt builders |
| 1661 | `routes/vendor_routes.py` | P1 Vendor Blueprint | route glue / vendor query service / stockout service |
| 1345 | `services/ppt_generator.py` | P1 報表生成 service | deck orchestration / slide builders / chart builders |
| 1339 | `services/nemoton_dispatcher_service.py` | P1 NemoTron service | NIM client / tool-call parser / action dispatcher |
| 1300 | `services/openclaw_strategist_service.py` | P1 OpenClaw service | prompt builders / report composer / strategy rules |
| 1206 | `app.py` | P1 bootstrap | 保持只做 app setup繼續往 app_factory / extension setup 抽 |
| 1079 | `routes/cicd_routes.py` | P2 CI/CD Blueprint | route glue / CI query service / deployment action service |
| 986 | `services/telegram_bot_service.py` | P2 Telegram service | command handlers / message formatters / bot client |
| 966 | `services/trend_crawler.py` | P2 crawler service | source adapters / parser / persistence |
| 868 | `services/elephant_alpha_autonomous_engine.py` | P2 ElephantAlpha engine | HITL / executor / planning policy |
| 818 | `services/import_service.py` | P2 import service | validators / import writers / report builders |
| 805 | `routes/bot_api_routes.py` | P2 Bot API Blueprint | route glue / bot action service |
## 工作項目
1. P0`routes/openclaw_bot_routes.py`,先把非 HTTP 邏輯搬到 `services/openclaw_bot/` 子模組。
2. P0`routes/sales_routes.py`,先把 chart/query/calendar 計算搬到 `services/sales/`
3. P0`scheduler.py`,建立 `jobs/``services/scheduler/` task registry。
4. P1`routes/ai_routes.py``routes/vendor_routes.py` 的資料處理移出 route。
5. P1把 PPT / NemoTron / OpenClaw 大 service 拆成 client、parser、composer、policy。
6. P2對 800-1100 行檔案採「碰到就順手抽」策略,但不可讓淨行數繼續增加。
## 守門
- `tests/test_modularization_governance.py` 會掃描所有 Python 檔案;任何新的 >800 行檔案沒有列在本 inventory 就會失敗。
- 若檔案拆小後低於 800 行,可從本清單移除並同步更新測試。

View File

@@ -42,6 +42,7 @@
- **Scheduler 例外記錄強化**: 清除 `scheduler.py` 靜默 `except/pass`Chrome 清理、EDM optional 欄位、備份 insight/Telegram 失敗均保留 log。
- **AI metrics baseline 觀測**: `/metrics` 在尚無 AI 自動化事件時仍輸出 `momo_ai_*` zero-baseline series避免 app 重啟後 Grafana/Prometheus 看不到 metric names。
- **ElephantAlpha transient fallback**: NVIDIA NIM primary model timeout、connection error、429 與 5xx 會嘗試下一個 fallback model400 等非暫時性請求錯誤不重試。
- **模組化治理守門**: 盤點 15 個超過 800 行 Python 大檔,新增 `docs/guides/modularization_governance.md``tests/test_modularization_governance.py`,防止未分類巨檔再長出來。
### 2026-04-28~29Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除
- **app.py 縮減 -10.8%**: 7,386 → 6,590 行11 commits 全綠零 502。

View File

@@ -18,6 +18,7 @@
| 3f-3 | scheduler 裸 `except`、EventRouter 同步告警、compose mount、vendor template_folder | 中 | 1-2h |
| 3f-4 | 模板統一至 `templates/``web/templates/vendor_stockout/` | 低 | 2-3h |
| 3f-5 | 孤兒 service 與 `.env.example` 收尾 | 極低 | 30m |
| 3f-6 | 模組化治理守門:大檔 inventory、分層 guide、測試防新增巨檔 | 低 | 30m |
## 已校正事項
@@ -26,3 +27,4 @@
- 3f-2 的 `--preload` 只降低 copy-on-write 記憶體成本,一致性仍以 DB fingerprint 為準。
- 3f-3 必須先補 `EventRouter` 同步 facade再把 scheduler P1/P2 失敗導入告警。
- 3f-4 移除 ChoiceLoader fallback 前,需先清 docker-compose 舊 mount 與根目錄模板。
- 3f-6 已將 >800 行 Python 檔案列入 `docs/memory/code_modularization_inventory_20260430.md`;未來新增巨檔需更新 inventory否則 `tests/test_modularization_governance.py` 會失敗。

View File

@@ -24,3 +24,5 @@
| `import_routes.py` | 匯入功能 | `/api/import_excel`, `/api/import/monthly_summary` |
新增 route 時請優先放入對應 Blueprint並用本機 `app.url_map` duplicate check 驗證。
若 route 內開始出現大量資料處理、SQL、AI prompt 或外部 API 呼叫,請依
`docs/guides/modularization_governance.md` 抽到 `services/``utils/`,不要讓 Blueprint 變成新巨檔。

View File

@@ -0,0 +1,44 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
INVENTORY = ROOT / "docs/memory/code_modularization_inventory_20260430.md"
GUIDE = ROOT / "docs/guides/modularization_governance.md"
def _python_line_counts():
ignored_parts = {".git", "venv", "__pycache__", ".pytest_cache"}
for path in ROOT.rglob("*.py"):
if any(part in ignored_parts for part in path.relative_to(ROOT).parts):
continue
with path.open(encoding="utf-8", errors="ignore") as handle:
yield path.relative_to(ROOT).as_posix(), sum(1 for _ in handle)
def test_large_python_modules_are_tracked_in_modularization_inventory():
inventory = INVENTORY.read_text(encoding="utf-8")
large_modules = {
path: lines
for path, lines in _python_line_counts()
if lines >= 800
}
assert large_modules, "盤點應至少包含目前既有大檔"
missing = [
f"{path} ({lines} lines)"
for path, lines in sorted(large_modules.items())
if f"`{path}`" not in inventory
]
assert missing == []
def test_modularization_governance_is_indexed_for_codex_sessions():
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
constitution = (ROOT / "CONSTITUTION.md").read_text(encoding="utf-8")
memory_index = (ROOT / "docs/memory/README.md").read_text(encoding="utf-8")
assert "docs/guides/modularization_governance.md" in agents
assert "docs/guides/modularization_governance.md" in constitution
assert "code_modularization_inventory_20260430.md" in agents
assert "code_modularization_inventory_20260430.md" in memory_index
assert GUIDE.exists()