Files
ewoooc/docs/AI_INTELLIGENCE_MODULE_SOT.md
OoO b2604a576d
All checks were successful
CD Pipeline / deploy (push) Successful in 1m23s
V10.575 拆分型錄可比覆核 lane
2026-06-04 11:13:35 +08:00

686 lines
68 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MOMO PRO — AI 競價情報模組 Single Source of Truth
> **最後更新**: 2026-05-25 (台北時間)
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地LLM 路由紅線升級為 Ollama-first 三主機級聯Gemini 備援預設關閉
> **適用版本**: V10.473
---
## 零、LLM 路由紅線2026-05-12
- 所有 AI Agent、LLM 推理與 embedding 預設必須走 Ollama 三主機級聯GCP-A `34.143.170.20:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434`
- `services/ollama_service.resolve_ollama_host()` 是主機解析契約;`OLLAMA_HOST``HERMES_URL``EMBEDDING_HOST``OLLAMA_API_BASE` 只接受 GCP-A / GCP-B / 111 或 110 的核准轉發端口。
- `config.OLLAMA_HOST``config.HERMES_URL``config.EMBEDDING_HOST` 只保留為舊 caller 相容常數import-time 不得 probe network也不得因 GCP-A/GCP-B 短暫不可用而 freeze 到 111。需要即時路由時一律呼叫 `get_ollama_host()``get_hermes_url()``get_embedding_host()``OllamaService`
- Gemini 只能作為 Ollama 主路徑失敗後的備援MCP Grounding、PPT/vision、週/月報、Code Review、EA HITL、複雜 SKU 升級等舊鎖定場景也必須先走 GCP-A → GCP-B → 111。
- 188 `192.168.0.188` 僅是 App / DB / scheduler / Telegram bot 容器宿主與 AutoHeal target不可作為 Ollama 節點。
- 通用 AI 文案、關鍵字、商品洞察與 Telegram Q&A 第一響應不得 Gemini-first。
- Hermes intent / analyst 路徑不得手刻 `/api/generate` 或只 resolve 單次 host必須走 `OllamaService`。預設 `HERMES_ALLOW_111_FALLBACK=false`,同一請求只跑 GCP-A → GCP-B兩台都失敗時回規則引擎或 DB 證據 fallback不把批量價格分析轉嫁到 111。救急時才可顯式設 true 允許 111 接手。
- NemoTron qwen3 dispatch 的 `/api/chat` tool-calling 路徑也必須同一請求最多嘗試三台 Ollama第一台失敗要 `mark_unhealthy()` 後再試下一台,最後才 fallback NIM。
- PPT vision、PPT 文案 final fallback、MCP 離線 final fallback 等特殊 Ollama 路徑也不得只打單一 host如需 `/api/generate`,一律透過 `OllamaService.generate()`
- Code Review pipeline 也必須 Ollama-firstHermes scan 與 OpenClaw assessment 都走 `OllamaService` 三主機 retryGemini telemetry 只能以 `code_review_openclaw_gemini` 出現,表示 Ollama/可選 Claude 備援都失敗後才啟用。
- Code Review Hermes scan 預設不呼叫 LLM改用 deterministic fast static scan避免部署後先卡三段 Ollama timeout需要 LLM 掃描時才以 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 啟用本地矩陣。
- Code Review Hermes LLM scan 啟用時才使用本地模型矩陣,且預設只跑 GCP-A `qwen2.5-coder:7b` → GCP-B `gemma3:4b``CODE_REVIEW_ALLOW_111_FALLBACK=true` 時才允許落到 111並由 `OllamaService` 降級到 `llama3.2:latest`。不啟用 Gemini 備援,本地掃描失敗時只回空 findings 並交由 OpenClaw 本地矩陣續跑。
- Code Review OpenClaw assessment 預設只跑 GCP-A → GCP-BGCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`primary timeout 預設 `15s`、secondary timeout 預設 `60s`,讓 A 掛時快速讓位給 B且 B 有足夠時間完成審查 prompt。111 是最後救急節點,但部署後重分析預設不打 111只有 `CODE_REVIEW_ALLOW_111_FALLBACK=true` 才允許 111 接手,並降級到 `llama3.2:latest`。Code Review 的 Ollama `keep_alive` 預設為 `5m`,不得再用 `24h` 長駐 runner 壓住 GCP-B/111。GCP-A/GCP-B 都失敗且 Claude/Gemini 未顯式開啟時,必須回 deterministic 本地降級摘要,不呼叫 Gemini、不落 111、不走其他雲端模型。
- Embedding / semantic RAG 背景任務預設只跑 GCP-A → GCP-B`OpenClawLearningService` embedding worker 與 `RAGService` 查詢 embedding 呼叫 `OllamaService.generate_embedding(..., allow_111_fallback=False)`111 只可作人工明確指定的救急路徑,不承接 `bge-m3` 背景批次。`OLLAMA_EMBED_TIMEOUT` / `OLLAMA_EMBED_MAX_TIMEOUT` 預設 `30s``OLLAMA_EMBED_KEEP_ALIVE=1m``OLLAMA_EMBED_MAX_CHARS=4000`;此上限依 GCP-B `bge-m3` 實測 623s 波動調整,避免慢但成功的 embedding 被 15s cap 誤殺。
- `allow_111_fallback=False` 時,若 resolver 因 unhealthy cache 回傳 111不得直接結束 embedding必須強制改試尚未嘗試的 GCP-A / GCP-B避免正式 log 出現 `tried=[]` 或只試單台 GCP-B。
- `allow_111_fallback=False` 且 GCP-A / GCP-B 皆失敗時,背景 embedding 會開啟短暫 GCP failure circuit預設 60 秒),期間不重複打兩台 GCP、不落 111避免 worker 與 log 被連續失敗拖慢GCP 恢復後會自然再試。
- 背景 embedding 的 GCP-only 熔斷屬於可降級背景能力,應記錄為明確 WARNING 與 circuit 狀態,不應每次污染 ERROR 通道;真正允許三主機 fallback 的同步 embedding 全失敗仍保留 ERROR。
- Scheduler host health probe 不只看 `/api/tags`GCP-A / GCP-B 節點必須再通過 `bge-m3` `/api/embed` 實作探針,才算 healthy。探針 timeout 預設 30s111 預設不納入這個背景 embedding 探針,避免監測任務把 fallback Mac 載入 `bge-m3`
- 背景 embedding 會讀取最近 `host_health_probes` runtime 結果;若 GCP-A / GCP-B 在 `OLLAMA_EMBED_HOST_HEALTH_SKIP_WINDOW_MINUTES=20` 視窗內已被標為 unhealthy`OllamaService.generate_embedding(..., allow_111_fallback=False)` 會先跳過該節點並開啟短暫 GCP circuit不再等待 30 秒 timeout也仍不落 111。此功能由 `OLLAMA_EMBED_HOST_HEALTH_SKIP_ENABLED=true` 控制DB 讀取失敗時 fail-open 回到原本網路 retry。
- BGE-M3 一致性檢查是監測任務,不是 fallback 壓測;預設只比對 GCP-A / GCP-B。111 Mac fallback 只有 `EMBED_CONSISTENCY_INCLUDE_111=true` 時才納入,避免每週背景檢查把 `bge-m3` 載入 111。
- OpenClaw Telegram Q&A 主路徑也不得綁單一 host`_call_qwen3_qa()` 必須透過 `OllamaService` 跑 GCP-A → GCP-B → 111並把實際落點寫入 `ai_calls.provider`
- OpenClaw Telegram 圖片商品辨識也必須 Ollama-first`_identify_product_name_with_ollama_vision()` 透過 `OllamaService` 嘗試 GCP-A → GCP-B → 111Gemini 只允許以 `openclaw_bot_image_gemini` caller 作為失敗後備援。
- OpenClaw 週報、月報、Meta analysis、日報洞察、Telegram PPT 分析與 MCP fallback 也必須 Ollama-firstGemini caller 只能帶 `_gemini_fallback` 或明確 fallback caller 語意,且不得先於 Ollama/NIM 被呼叫。OpenClaw strategy 的 Ollama `keep_alive` 預設為 `5m`,避免報告型任務把 GCP-B/111 runner 長駐 24h。
- OpenClaw 週報、月報、Meta analysis、日報洞察與每日報告的 Gemini/NIM 備援 caller 必須登錄在 caller registry、AI 觀測台 agent group 與 Telegram 狀態統計,避免 fallback 用量被歸類為未知或漏算。
- `ai_calls.provider='ollama_other'` 只允許作為 unresolved/unknown Ollama telemetry bucket例如全 host 失敗、尚未選定實際 GCP-A/GCP-B/111 host 或舊 caller 未帶 host不得把 `ollama_other` 當成實際路由目標或新增非核准 Ollama host。
- GCP-B 若缺 caller 指定的 coder/large 模型,`OllamaService` 必須先在 GCP-B 改用 `OLLAMA_SECONDARY_MODEL_FALLBACK`(預設 `gemma3:4b`),不可因 model 404 把整台 GCP-B 標成 unhealthy 後直接推到 111真正 timeout / HTTP 5xx 才標 host unhealthy。
- Gemini API 出站有第二道 kill switch`GEMINI_FALLBACK_ENABLED` 預設為 `false`。即使 `GEMINI_API_KEY` 存在,通用 AI fallback、OpenClaw 報告/QA/PPT/圖片、MCP Grounding 與 Code Review L3 都不得呼叫 Gemini只有操作員明確設為 `true`Gemini 才能作緊急備援。
- `docker-compose.yml``momo-app``scheduler``telegram-bot` 必須明確設定 `GEMINI_API_HARD_DISABLED=${GEMINI_API_HARD_DISABLED:-true}``GEMINI_FALLBACK_ENABLED=${GEMINI_FALLBACK_ENABLED:-false}``.env` 可保留 `GEMINI_API_KEY`,但不得因 key 存在就讓核心容器產生 Gemini 付費出站。
- Gemini 不可被任何狀態面板或 router 推薦為主提供者:`AIProviderService._get_recommended_provider()` 不得回傳 `gemini`,只能顯示為 fallback 狀態;`llm_model_router``ea_engine` 若收到 `gemini-*` default 必須改回 `hermes3:latest`,需要深推理時才升本地 `deepseek-r1:14b`
- ElephantAlpha prompt / agent registry 不得再把 OpenClaw 描述為 Gemini 主模型OpenClaw 是 `qwen2.5-coder:7b` / `qwen3:14b` Ollama-first 策略師Gemini 僅能在 guard 顯式解鎖後作 emergency fallback。
- 111 `192.168.0.111` 只是最後一道 Mac fallback不承接 7B+、vision、long-context 模型長駐;`OllamaService.generate()` 落到 111 時會將 `qwen3``deepseek-r1``hermes3``qwen2.5*``gemma3``llava``minicpm-v` 與 7B+ 模型依 `OLLAMA_111_MODEL_DOWNGRADE_PATTERNS` 降級到 `OLLAMA_111_MODEL_FALLBACK=llama3.2:latest`,並以 `OLLAMA_111_KEEP_ALIVE=5m``OLLAMA_111_MAX_TIMEOUT=20``OLLAMA_111_NUM_CTX=4096``OLLAMA_111_NUM_PREDICT=512` 封頂。OpenClaw 報告型路徑的業務 keep-alive 預設 `5m`Code Review 以 `CODE_REVIEW_ALLOW_111_FALLBACK=false`、Hermes 以 `HERMES_ALLOW_111_FALLBACK=false` 預設跳過 111避免 16GB RAM 主機與 GCP-B 被長駐 runner、長輸出與 24h keep-alive 壓到高 load。
- Scheduler 每 15 分鐘執行 `run_ollama_111_usage_guard_check()`,只讀 `ai_calls` 統計最近視窗的 GCP-A / GCP-B / 111 呼叫量;預設 60 分鐘內 Ollama 呼叫至少 20 次、111 至少 3 次且占比 >= 5% 才推 Telegram。這是觀測護欄不改路由、不寫 DB、不自動重啟服務。
- `OllamaService` 對 111 final fallback 有 circuit breaker預設最近 60 分鐘 Ollama 呼叫至少 20 次、111 至少 5 次且占比 >= 5% 時,短暫跳過 111`OLLAMA_111_CIRCUIT_CACHE_SEC=60`),避免 111 在已偏高時繼續承接長任務DB 觀測失敗時 fail-open不讓主要 GCP-A/GCP-B 路由被觀測層中斷。
- 111 的 LAN 入口必須經 `scripts/ops/ollama111_allow_proxy.py` allowlist proxy真實 Ollama 綁 `127.0.0.1:11434`proxy 綁 `192.168.0.111:11434`,預設只允許 111 本機與 188 生產宿主110 / 121 / 其他 LAN client 不能直接打 111避免跨專案 CI 或 VM 繞過 momo-pro router 載入 7B+ runner。111 上以 `scripts/ops/install_ollama111_allow_proxy.sh` 安裝 user LaunchAgent安裝器會把 proxy script 複製到 `~/.local/share/momo-pro-system/ollama111_allow_proxy.py`,讓 LaunchAgent 不依賴 iCloud repo 掛載路徑,並讓 proxy 與 `OLLAMA_HOST=127.0.0.1:11434` 在登入/重啟後自動恢復。拒絕日誌以 `OLLAMA111_PROXY_REJECT_LOG_DEDUP_SEC=60` 去重,避免 121 這類旁路探測刷爆 111 磁碟日誌。
- ElephantAlpha 的 `price_drop_alert` / `market_opportunity` Telegram HITL 告警必須把同款證據獨立呈現,至少包含 `match_type``price_basis``alert_tier``match_score`;沒有高信心同款與總價可比證據時,不得把 PChome/MOMO 價差寫成可直接跟價建議。
## 零之一、12 Agent 決策信封2026-05-24
- 12 角色分工不作為 12 個常駐模型;在產品層統一收斂成 `decision_envelope`,由 Hermes / NemoTron / OpenClaw / ElephantAlpha 與人工審核、PPT QA、競品 review queue 共用。
- `decision_envelope` 必須至少能表達:`decision_type``severity``evidence[]``recommended_action``expected_impact``confidence``guardrails``trace`
- `guardrails.can_auto_execute=false` 是預設價格調整、正式比價覆寫、PPT 發送與修復執行都必須遵守 HITL 或既有 service guard不得因 Agent 信心高就繞過 matcher / feeder / review service。
- 證據不足時不得輸出空泛效益預測;必須標記 `data_quality=missing|partial|stale`,並把建議行動降級成 `human_review``needs_research``silence_alert`
- Telegram `triaged_alert()` 已支援渲染 `decision_envelope`,讓告警固定呈現嚴重度、證據、建議行動、預期影響、信心度與追蹤 ID後續觀測台與 PPT 也應共用同一份欄位語意。
- NemoTron `price_alert` / `human_review` 派發會把同款證據、價差、七日銷量變化、營收流失、HITL 邊界與資料品質寫入同一份 `decision_envelope`,並同步放入 EventRouter event 與 KM metadata12 Agent 後續只能沿用此信封補充分析,不得繞過 matcher / feeder / review service 直接改價或覆寫比價資料。
- EventRouter / Telegram 的 HITL callback 必須優先使用 `decision_envelope.decision_id` 作為事件追蹤 ID若上游未帶 `event.id``triaged_alert()` 仍會用 `decision_id` 產生 `momo:eig:*` callback避免價格決策審核落成 `unknown`。所有 `momo:eig:*` callback 必須以 UTF-8 byte-safe 截斷,確保 `callback_data` 不超過 Telegram 64-byte 限制。
- 競品比價相關的 Agent 建議只能讀 `competitor_match_attempts` / review queue / `competitor_prices` 的既有證據;不得直接寫 `competitor_prices` 或覆蓋 `_should_upsert_competitor_price()` 的保護規則。
- 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 HITL 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。
- PChome 覆核隊列本身也必須輸出 `decision_envelope``fetch_competitor_review_queue()``fetch_competitor_review_queue_page()``/api/pchome-review/queue` 的每筆候選需帶相同的 `subject``evidence``recommended_action``expected_impact``guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 HITL guardrails。
- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、HITL、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。
- OpenClaw 週報/日報/月報與 competitor PPT 不得再各自重算或翻譯 PChome 覆核狀態;必須透過 `competitor_intel_repository.summarize_review_decision_envelopes()` 讀取同一份 `decision_envelope` 摘要,並在 prompt / data_summary / KPI slide 保留 HITL 與 `can_auto_execute=false` 邊界。
- Webcrumbs / Shared UI host data 也必須透過 `summarize_review_decision_envelopes()` 輸出 `reviewDecisionBrief`,並在 metadata 保留 review queue、HITL、auto-execute-blocked、`decision_support_rate``catalog_comparable_count` 與 catalog review lane counts不得另寫一套 PChome 覆核摘要或在前端 runtime 重新推論價格行動。
- ElephantAlpha 的 `resource_optimization` 與低信心 `ea_escalation` 也必須輸出 `decision_envelope`:資源壓力信封只能使用 `action_plans`、CPU 實測、hygiene 結果與 insight/action trace不得加入 LLM 預測效益;`triaged_alert()``ea_escalation` 亦需渲染信封並以 `decision_id` 作為 callback 追蹤 ID。
## 一、四 AI Agent 路由架構
```
SQL漏斗(~300筆)
[Hermes 3 8B] — 分析師 (Ollama 三主機級聯, 零成本)
模型: hermes3:latest @ GCP-A → GCP-B → 111
任務: 競價威脅分類 → TOP 20 HIGH/MED/LOW
[NemoTron / qwen3] — 派發器
主路徑: qwen3:14b @ GCP-A/GCP-B落到 111 時自動降級 llama3.2
備援: NVIDIA NIM meta/llama-3.1-8b-instruct
任務: Tool Calling → Telegram 告警 / DB 寫入
[OpenClaw] — 策略師 (Ollama-firstGemini 僅備援 / 鎖定場景)
任務: 週策略報告、洞察報告、L3 HITL 建議
[ElephantAlpha] — 編排者 (L3 Orchestrator)
任務: 跨 Agent orchestration、HITL、AutoHeal bridge、受控 log scan
```
### 1.1 PChome 挑品 Agent2026-05-01
`services/ai_product_pick_agent.py` 新增 PChome 銷售用挑品 Agent
- 只讀真實資料表:`products``price_records``competitor_prices``competitor_price_history`,若 `daily_sales_snapshot` 可用則納入近 7 天銷售額、數量、毛利或成本推算毛利率。
- 將 PChome 比 MOMO 有價格優勢、比對信心足夠、且有歷史快照或銷售動能的品項寫入 `ai_price_recommendations`。信心度不以固定倍率灌高,而是由商機分數與證據完整度共同決定,證據包含 PChome match score、歷史快照、銷售/毛利、PChome 商品 ID/名稱、抓取時間與促銷/評價/庫存標籤。每次重算只保留最新 50 品為 `status='pending'`,未進榜舊品標為 `superseded`,避免統計與清單超量。
- 寫入策略使用 `strategy='product_pick'`,保留在既有 AI 決策表,不新增假頁面或暫存 JSON。
- 後台入口:`POST /api/ai/product-picks/generate``/ai_intelligence` 可手動產生清單。
- 配對來源仍以 PChome crawler 真實搜尋結果為準;無競品資料時不生成挑品。
- 比對覆蓋率補強入口:`POST /api/ai/pchome-match/backfill`,優先補抓仍無有效 PChome 配對的高價 ACTIVE 商品,完成後自動重算 AI 挑品清單。
- 過期價格刷新入口:`POST /api/ai/pchome-match/refresh-stale`,只針對已建立 `identity_v2``expires_at` 過期的 PChome product_id 執行 `run_expired_identity_refresh()`;不得跑 fresh search recovery不得呼叫 LLM完成後重算 AI 挑品並清除 Dashboard / competitor intel cache。
- 過期 identity 搜尋救援入口:`POST /api/ai/pchome-match/recover-stale` 預設必須關閉主操作入口,僅保留只讀 preview正式 smoke 顯示小批次成功率不足且耗時偏高時,不得在 Dashboard 顯示日常操作按鈕。若需操作員手動執行,必須先明確設定 `PCHOME_STALE_RECOVERY_ENABLED=true`,再對已過期 `identity_v2` 先走既有 PChome product_id refresh只有舊 ID 查無商品或重評低於門檻時,才允許受控 fresh search recovery。救援隊列必須先排除 variant、catalog、commercial condition、count、bundle、unit-price 與任選 / 多款 / 香味 / 色號 / 即期 / 融燭燈 / 香氛蠟燭 / `+` / `xN` / `*N` / 具名香味或膚感版本等高風險名稱訊號。這條路徑可抓 PChome但不得呼叫 LLM正式寫入仍必須通過 matcher、hard veto、auto price write safety 與 overwrite protection。
- 補抓狀態入口:`GET /api/ai/pchome-match/backfill/status` 除背景任務狀態外,必須回傳 read-only coverage snapshot`active_with_price` / `valid_matches` / `match_rate` / `fresh_matches` / `fresh_match_rate` / `decision_ready_matches` / `decision_ready_rate` / `stale_matches` / `pending` / `actionable_review_count`,供 Dashboard 顯示目前該刷新過期價格或補抓未搜尋商品;此端點不寫 DB、不呼叫 LLM、不抓外站。`match_rate` 是身份覆蓋率,`fresh_match_rate` 是已配對 identity 內的新鮮比例,`decision_ready_rate` 才是可直接進入決策、圖表與簡報的 ACTIVE 商品比價覆蓋率。
- 排程閉環:`run_pchome_match_backfill_task` 每日 10:30 執行,補抓 PChome 待比對商品、寫入歷史價格,再重算 `strategy='product_pick'` 清單。
- PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 30 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis``/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;`run_competitor_price_feeder_task` 與 PChome backfill 完成後會主動清除快取。快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。
- 商品看板第一屏:`/` 的 V2 看板直接以 `products``price_records``competitor_prices``competitor_match_attempts``competitor_match_reviews``ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需人工處理的比價覆核 SKU並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與人工閉環,讓人工可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、人工動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷讓人工覆核、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review/<sku>/decision` 是人工閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices``competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2``reject_identity``unit_price_required``needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。人工 `reject_identity``unit_price_required``needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected``manual_unit_price_required``manual_needs_research` 顯示為明確人工閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出人工採用、人工否決、人工單位價與採用率daily/growth/PPT 共用 payload 必須顯示人工閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。
- PChome re-score 回收線:`rescore_accepted_current` 只能表示最新版 matcher 判定「值得人工覆核身份」,不可直接寫入正式 `competitor_prices``no_match``price_basis=none``alert_tier=suppress``variant_selection_review` 不得進入此隊列。`fetch_competitor_coverage()` 必須輸出 `rescore_accepted_count`Dashboard、daily/growth 與 OpenClaw 競品摘要都要把「重算待人工覆核」獨立呈現,避免和一般低信心/單位價覆核混在一起。
- PChome 低信心操作分流Dashboard 與 read-only `/api/pchome-review/queue` 必須把近門檻可救、證據不足、低信心舊候選拆成 `recoverable_low_score``true_low_confidence``legacy_low_score` 三個可篩選桶;廣義 `low_score` 僅作 repository/export 相容查詢,不可在 UI 中冒充單一操作分流。
- PChome coverage 的 `attempt_status` / `rescore_accepted_count` / `actionable_review_count` 口徑必須與 review queue 對齊:統計「沒有新鮮有效 identity」的商品而不是只統計「完全沒有 identity」的商品已過期但可重算採用的 stale identity 仍應出現在待審數字中,避免 API 與 Dashboard 漏報。
- `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score``true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開人工審核池。`rescore_accepted_current` 只允許命中具名 focused exact 品線、舊分數 >= 0.76、且仍無 hard veto / 阻擋理由時進窄門回刷;最後仍由最新版 matcher 判定是否可寫正式價差,像不同指甲油型號 / 色號必須 hard veto。
- 高分 `true_low_confidence` 的自動救回只能用具名 focused exact 線逐批擴充;同品牌、同品線、同規格/同組合的花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏與理膚寶水全面修復潤唇膏可走 total-price色號、香味、款式、即期品與 catalog selection 仍維持 review / veto。
- `true_low_confidence` focused exact 線必須同步接入 `run_retryable_candidate_revalidation()` 的 SQL 窄門,讓舊候選可被批次回收;該窄門只允許具名品線豁免 `variant_selection_review`,其他 hard veto / 型別、款式、香味、件數、組合、refill、commercial condition 阻擋仍不得回刷。
- 任選 catalog focused exact 只允許雙方都明確是同品線任選賣場且規格一致的窄範圍案例,例如 FLORTTE 眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g若有 `commercial_condition_gap`即期品、短效、航空版等狀態差異focused bypass 不得移除 `variant_selection_review`,不得自動寫正式價差。
- O.P.I 指彩救回只允許同品牌、同 `類光繚` / `如膠似漆` 指甲油或指彩線,且共享 `ISL...` 精準型號 token 的案例自動走 total-price不同型號/色號仍維持人工或 veto。此規則可接入 `true_low_confidence` revalidation 窄門,但不得變成「同品線即通過」。
- 其他正式覆核池 focused exact 線只能針對「已在正式頁面反覆出現且有硬規格」的窄範圍族群,例如 The Ordinary 咖啡因 EGCG、Natures Care 綿羊油同入數、TOMOON 指甲剪同尺寸、HH 雙 200ml 組、SEBAMED 200ml x2、YES 9cm 剪刀;同尺寸、同入數、同組合或單側漏規格必須可由 matcher 明確判斷,不能只因同品牌同品線通過。
- `/api/ai/pchome-match/backfill/status` 必須把近門檻重評池與過期 identity 救援池以只讀 `revalidation_preview` / `stale_recovery_preview` 曝光給操作員;預覽只復用正式候選 SQL 並受 limit / 60 秒快取限制,不啟動 PChome 搜尋、不呼叫 LLM、不寫 `competitor_match_attempts` / `competitor_prices`。重評 preview 必須先從最新 `competitor_match_attempts` 縮小候選,再用 `JOIN LATERAL` 取單一最新 MOMO 價;救援 preview 必須從過期 `competitor_prices` 小集合出發並用 `JOIN LATERAL` 取最新 MOMO 價,兩者都不得掃全量 `price_records`Dashboard 只能顯示「可救援」觀測值,不得在未開啟 `PCHOME_STALE_RECOVERY_ENABLED` 時提供 recover-stale 執行按鈕;其中 `review_gated_count` 僅代表窄門 `true_low_confidence` exact 候選,不得被解讀為全量人工池可自動回刷。
- PChome re-score audit 預設必須先取每個 SKU 的最新 `competitor_match_attempts` 狀態,再套用 status / reason 篩選;舊低信心歷史候選只能透過 `--include-historical-candidates` 明確進入考古掃描,避免已入隊、已否決或已修正 SKU 被舊紀錄重新推回報表。
- production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給人工覆核;執行後需清除 Dashboard / competitor intel cache且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。
- production re-score 若曾把 `variant_selection_review` 追加成 `rescore_accepted_current`,必須用 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted` 追加最新 `true_low_confidence` 退回列;此路徑只寫 `competitor_match_attempts`,不得刪歷史紀錄,也不得寫 `competitor_prices` / `competitor_price_history`
- PChome matcher replay 必須先守住假陽性:`EX8` 等型號不可被誤解析成 `x8` 入數;香氛固體凝膠 / 空氣芳香劑若一側為泛稱、一側含明確香味或 No. 款式,必須走 `aroma_scent_variant_conflict` veto不得因同品牌同重量直接寫正式價差。
- PChome matcher 對「同規格同數量」的多件組可以安全回收,但必須同時滿足:商品型別完全對齊、品牌同線、規格與數量對齊、沒有 variant / count / bundle / commercial / unit-price / price-ratio 阻擋理由,才可打 `safe_multi_component_exact_total_price` 並進 `exact / total_price / price_alert_exact`混合組、香味款、色號款、catalog 任選仍需留在 `identity_review` 或 veto。護唇品 focused total-price 僅允許已明確建規則的 DHC 純欖 1.5g、FRUDIA 蜂蜜藍莓 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml,不得把所有 lip/cosmetic catalog 一次放行。
- PChome feeder 正式寫入必須再套一層價格資料閘門:只有 `match_type='exact'``price_basis='total_price'``alert_tier='price_alert_exact'` 且無 `variant_selection_review` 的結果可以自動寫入 `competitor_prices``manual_review` / `identity_review` 只能留在覆核隊列或人工採用流程,不得由 retryable replay 或 known identity refresh 自動升成正式價差。Rescore audit 若遇到 `variant_selection_review`,也不得產生 `accepted_current`
| 角色 | 模型 | 主機 | 成本 | 每日限額 |
|------|------|------|------|---------|
| Hermes 分析師 | hermes3:latest / bge-m3 | GCP-A → GCP-B → 111 Ollama | 零 | 無限 |
| NemoTron 派發器 | qwen3:14b111 fallback 降級 llama3.2NIM fallback | GCP-A → GCP-B → 111NVIDIA NIM 備援 | Ollama 零NIM 配額內免費 | NIM 80 |
| OpenClaw 策略師 | qwen2.5-coder:7b / qwen3:14b111 fallback 降級 llama3.2 | Ollama-firstGemini emergency fallback only | Ollama 零Gemini 預設封鎖 | — |
| ElephantAlpha 編排者 | ElephantAlpha | 依部署環境 | 受控 | HITL / 任務制 |
---
## 一之一、AI 自動化閉環實況2026-04-29
```text
事件 / 排程失敗 / code review finding
→ EventRouter 分流、去重、降級
→ Hermes L1 摘要或 NemoTron L2 tool calling
→ L2 SAFE_ACTIONS / AutoHeal / OpenClaw memory
→ Telegram 通知,失敗則 file queue成功後 replay
→ ai_insights + embedding_retry_queue
→ OpenClaw / ElephantAlpha 後續策略與 HITL
```
硬性邊界:
- EventRouter 是告警與 L2 safe action 的入口。
- AutoHeal 是自癒副作用入口。
- `momo-db` / `momo-postgres` 不可被 AI 自動 restart / stop / recreate。
- raw `ai_insights` insert 必須接 `enqueue_insight_embedding()` 或可被 backfill。
- ElephantAlpha 只做編排與 bridge不可繞過 ADR-011 / ADR-012 / ADR-013。
- ElephantAlpha / NemoTron 不可直接執行商品價格調整;`execute_price_adjustment``adjust_price` 等動作必須攔截並寫入 `human_review`,等待人工核准。
可觀測性:
- `/metrics` 匯出 `momo_ai_event_router_dispatch_total`
- `/metrics` 匯出 `momo_ai_event_router_latency_ms_count/sum/max`
- `/metrics` 匯出 `momo_ai_event_router_safe_action_total`
- `/metrics` 匯出 `momo_ai_event_router_replay_total`
- `/metrics` 匯出 `momo_ai_autoheal_action_total``momo_ai_autoheal_duration_ms_count/sum/max`
- `/metrics` 在尚無事件時仍輸出 `momo_ai_*` zero-baseline series讓 Prometheus/Grafana 重啟後可立即看到 metric names。
- `/ai_automation_smoke` 提供登入後 smoke dashboard。
- `/api/ai-automation/smoke` 提供 read-only JSON 狀態,不做外部網路呼叫。
- Smoke API 會將最近快檢結果保存到 JSONLdashboard 顯示最近狀態趨勢。
- Smoke history 支援 JSONL 匯出、清理與每日 OK / Warning / Critical 摘要。
- Smoke 每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`
- Grafana provisioning 新增 `docker/grafana/provisioning/dashboards/json/ai-automation-overview.json`,觀測 EventRouter dispatch/latency、safe action、Telegram replay 與 AutoHeal action/duration。
- Active monitoring stack 使用 `monitoring/prometheus.yml``momo-app` job scrape `momo-pro-system:80/metrics`Prometheus container 需加入 `momo-network`
- Active Blackbox HTTP targets 必須探測 `/health`188 stack 目前 `https://mo.wooo.work/health``http://momo-pro-system:80/health`110 gateway stack 目前 `https://mo.wooo.work/health`),不可探測 Dashboard 首頁 `/`,避免監控流量觸發重型 DB 查詢。
- `/metrics``realtime_sales_monthly` 只用 raw `SELECT COUNT(*)` 取得總筆數,避免 ORM schema drift 讓 Prometheus scrape 產生 warning。
- `momo-app` 必須 bind mount `./gunicorn.conf.py:/app/gunicorn.conf.py:ro`,讓 CD sync/rebuild 後的 Gunicorn runtime 設定與 repo 保持一致。
- Gunicorn runtime 預設 `worker_class = gthread``GUNICORN_THREADS=4``preload_app = False`;此組合讓 HUP 熱重載可用,也避免 Dashboard 長查詢完全阻塞 `/health`
- CD rebuild 模式必須先 build image 成功,再短暫 stop/rm/recreate 三應用容器,避免 no-cache build 造成長時間 502。
- ElephantAlpha 使用 NVIDIA NIM hosted APIproduction 預設模型為 `nvidia/llama-3.3-nemotron-super-49b-v1.5``ELEPHANT_ALPHA_FALLBACK_MODELS` 需保留至少一個可呼叫備援403/404、408/409/425/429、5xx、timeout 與 connection error 必須嘗試下一個模型。
- ElephantAlpha L3 HITL 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown不寫 pending `human_review`,不發 Telegram 空告警。
- ElephantAlpha 價格類 trigger 的 HITL / 決策 prefetch 必須先使用觸發 SQL 與 `competitor_prices` / `price_records` 的 DB 實證生成 SKU、MOMO / PChome 價差與建議 action lines完整 Hermes LLM prefetch 預設關閉(`ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false`),避免 5s timeout 後落入無實證摘要或雲端備援。若無 DB 實證,只記錄 suppressed telemetry / cooldown不發 Telegram 空告警。
- ElephantAlpha `price_drop_alert` / `market_opportunity` trigger 不得對整張 `price_records` 做全表最新價聚合;必須先篩最近有效 `identity_v2` PChome 候選,再用 per-SKU `JOIN LATERAL` 讀最新 MOMO 價格,並把 `match_score``tags``match_diagnostic_json` 帶入 evidence。
- ElephantAlpha 協調器收到非純 JSON、fenced JSON 或混文字 JSON 時,必須先做容錯抽取;仍無法解析時,只能使用 DB/Hermes 實證生成保守 HITL fallback。fallback 不得放入 OpenClaw `generate_*` 類舊策略步驟,也不得暗示已自動調價。
- ElephantAlpha 執行器若遇到舊版 OpenClaw strategy 類步驟(含 `generate_market_strategy` / `generate_dynamic_pricing_strategy` / `generate_resource_optimization_strategy`),只能記錄為 advisory skipped不得觸發 circuit breaker也不得轉成實際排程、外部呼叫或價格行動。
- `resource_optimization` 不再交給 LLM 生成「預期效益 / 已執行」敘事,顯示名稱統一為「資源壓力治理」。此 trigger 必須先由程式量測 `action_plans` backlog、P1/P2 數、pending_review、逾時項目與 CPU load只有 CPU 達門檻、P1/P2 積壓或逾時積壓才發 Telegram「資源壓力告警」。單純 queue 大但 CPU 正常只記錄 telemetry不派發 Hermes/NemoTron、不宣稱 48 小時效益Telegram 段落使用「系統處置紀錄」而非泛稱「已執行」,避免暗示 AI 已完成未經驗證的外部動作。
- `resource_optimization` 的 Telegram 必須包含 `decision_envelope` 區塊,標明 `source_agent=elephant_alpha`、資料品質、量測證據、`can_auto_execute=false` 與 deterministic trace此路徑不呼叫 Gemini、不呼叫 Hermes/NemoTron也不得把 queue backlog 翻譯成主機資源耗盡。
- `resource_optimization` 會先執行 `ActionPlanHygieneService` 清理過期噪音:只關閉超過 72 小時的 `code_review_fix` / `openclaw_recommendation` 類 advisory action_plans以及 NemoTron `direct_response/reply_simple` 舊聊天回覆計畫;將狀態改為 `auto_disabled``rejected` 並寫入 `metadata_json.hygiene_history`。不刪資料,也不碰 NemoTron human_review / pricing / tool action 類業務行動。
- `momo-scheduler` 每 6 小時固定執行 `run_action_plan_hygiene_task()`,讓過期 advisory action_plans 的關閉不再依賴 `resource_optimization` 告警觸發;排程失敗會經 EventRouter 發送 `action_plan_hygiene_failure`
- `action_plans` 產生端必須防重Code Review 同一檔案已有 active `code_review_fix` 時不重建OpenClaw recommendation 會寫入文字 fingerprint 並跳過同一建議AIOrchestrator 不再把 NemoTron `direct_response/reply_simple` 聊天回覆存成 action plan真正需工具、審核或執行的 NemoTron action 才能進 queue。
- OpenClaw/Hermes embedding 優先呼叫 Ollama `/api/embed`,只在舊節點不支援時 fallback `/api/embeddings`timeout 由 `EMBEDDING_TIMEOUT` / `OLLAMA_EMBED_TIMEOUT` 控制,並受 `OLLAMA_EMBED_MAX_TIMEOUT` 封頂。背景 worker / RAG 查詢不得落 111除非 caller 顯式允許 `allow_111_fallback=True`
- PPT 自動產線由 `momo-scheduler` 依節奏執行 `run_ppt_auto_generation_task(schedule_kind)`:每日 20:30 產日報、週一 20:40 產週報/市場情報、每月 1 日 20:50 產月報與管理型簡報、季初 21:00 產季報、半年初 21:10 產半年報、年初 21:20 產年報,再交給 22:00 `ppt_vision_audit` 做視覺審核;每次嘗試會寫入 `ppt_generation_runs``/observability/ppt_audit_history` 以精準參數檢查目標版本是否已產生,並可用 `/observability/ppt_audit/generate_missing` 手動補齊缺漏,總開關為 `PPT_AUTO_GENERATION_ENABLED`。PPT vision 需 `PPT_VISION_ENABLED=true` 與容器內 LibreOffice`/observability/ppt_audit_file/<filename>` 會把 PPTX 轉成 PDF 快取供站內線上預覽,原始 PPTX 仍保留下載。QA 失敗項目的「重跑」必須從檔名推回原 report_type並只失效相同 `report_type + parameters` 的 active `ppt_reports` cache避免拿到舊 PPT 或誤重跑 daily。
---
## 二、真實資料庫 Schema已校對確認
### 2.1 `products` 表SQLAlchemy ORMSQLite/PostgreSQL 通用)
| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | Integer PK | 主鍵 |
| `i_code` | String(50) UNIQUE | **MOMO 商品代碼**(爬蟲來源,即商品 SKU |
| `name` | String(255) | 商品名稱 |
| `url` | String(500) | MOMO 商品頁 URL |
| `image_url` | Text | 商品圖片 URL |
| `category` | String(100) | 分類名稱(直接欄位) |
| `status` | String(20) | 預設 `'ACTIVE'` |
| `created_at` | DateTime | 建立時間 |
| `updated_at` | DateTime | 更新時間 |
| `category_id` | Integer FK → categories.id | 分類關聯(可選) |
> **重要**: `i_code` = MOMO 網站上的商品 ID例如 `I132467614`
### 2.2 `price_records` 表
| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | Integer PK | 主鍵 |
| `product_id` | Integer FK → products.id | 商品關聯 |
| `price` | Float | **MOMO 自家售價**(爬蟲抓取) |
| `timestamp` | DateTime indexed | 抓取時間戳 |
> ⚠️ **架構限制**: `price_records` **只存 MOMO 自家售價**,無 `source` 欄位無競品PChome價格。
> PChome 比價資料必須由外部爬蟲即時抓取,以 `pchome_prices: dict` 形式注入 `HermesAnalystService.run()`。
### 2.3 `daily_sales_snapshot` 表(動態表,從 Excel 匯入)
> **重要**: 此表由 `import_service.py` 使用 `df.to_sql()` 動態建立。
> 欄位名稱**完全繼承自匯入的 MOMO Excel 報表原始欄位**,加上程式碼追加的 `snapshot_date`。
#### 已確認的關鍵欄位(實際 MOMO 報表欄位名稱)
| 欄位 | 型別 | 說明 | 備注 |
|------|------|------|------|
| `snapshot_date` | Date | 資料所屬日期(程式追加) | 由 `import_service.py` 從「日期」欄位解析 |
| `商品ID` | VARCHAR | **商品識別碼**= `products.i_code` | ⚠️ 非 `商品編號`|
| `商品名稱` | TEXT | 商品名稱 | |
| `銷售金額` | NUMERIC | 銷售業績金額 | 系統以 find_col 模糊比對,優先 `銷售金額` |
| `數量` | NUMERIC | 銷售數量 | |
| `總成本` | NUMERIC | 成本 | |
| `廠商名稱` | VARCHAR | 廠商名稱 | |
| `商品館` / `館別` | VARCHAR | 分類 | |
#### 欄位自動偵測邏輯(`find_col`
系統使用 keyword 模糊比對,**不要求欄位名完全固定**
```python
SKU/商品ID = find_col(['商品ID', 'Product ID', 'ID', 'i_code', 'Item Code'])
商品名稱 = find_col(['商品名稱', '品名', 'Name', 'Product'])
銷售金額 = find_col(['銷售金額', '業績', '金額', 'Amount', 'Sales', 'Total'])
成本 = find_col(['成本', 'Cost', '進價', '總成本'])
數量 = find_col(['銷售數量', '銷量', '數量', 'Qty', 'Quantity'])
日期 = find_col(['日期', '訂單日期', '交易日期', 'Date'])
分類 = find_col(['商品館', '館別', '分類', 'Category'])
```
### 2.4 `competitor_prices` 表Migration 004 — 已建立)
競品價格快取表,由 `competitor_price_feeder.py` Worker 寫入AI Pipeline LEFT JOIN 消費。
| 欄位 | 型別 | 說明 |
|------|------|------|
| `id` | SERIAL PK | 主鍵 |
| `sku` | VARCHAR(50) | MOMO 商品代碼(= products.i_code |
| `source` | VARCHAR(30) | 競品來源:`'pchome'`(預留 shopee 等) |
| `price` | NUMERIC(10,2) | 競品售價 |
| `original_price` | NUMERIC(10,2) | 競品原價 |
| `discount_pct` | INTEGER | 折扣 %NULL=未折扣) |
| `competitor_product_id` | VARCHAR(100) | PChome 商品 ID |
| `competitor_product_name` | TEXT | PChome 商品名稱(核對用) |
| `match_score` | NUMERIC(4,3) | 商品身份比對分數0~1< 0.76 不寫入正式快取 |
| `tags` | JSONB | 語意標籤,如 `["on_sale","discount_20pct"]` |
| `crawled_at` | TIMESTAMP | 爬取時間 |
| `expires_at` | TIMESTAMP | TTL = crawled_at + 48h可由 `PCHOME_FEEDER_TTL_HOURS` 調整),過期後 Hermes 忽略UI 身份覆蓋率不因價格 TTL 過期歸零 |
**UNIQUE**: `(sku, source)` — 同一 SKU+來源只有一筆ON CONFLICT UPDATE
**語意標籤字典**
| 標籤 | 觸發條件 |
|------|---------|
| `on_sale` | PChome `is_on_sale = True` |
| `discount_10pct` | 折扣 10-19% |
| `discount_20pct` | 折扣 20-29% |
| `discount_30pct` | 折扣 ≥ 30% |
| `low_stock` | 庫存 < 10 |
| `high_rating` | 評分 ≥ 4.5 |
### 2.5 `ai_price_recommendations` 表Migration 003 — 已建立)
此表需執行 `migrations/003_ai_price_recommendations.sql` 才能完整寫入 DB
```sql
CREATE TABLE IF NOT EXISTS ai_price_recommendations (
id SERIAL PRIMARY KEY,
sku VARCHAR(50) UNIQUE NOT NULL,
name TEXT NOT NULL,
reason TEXT,
status VARCHAR(20) DEFAULT 'pending', -- pending / approved / rejected
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
> **現狀**: `nemoton_dispatcher_service.py` 的 `_exec_add_to_recommendation()` 在 engine 注入且表存在時才寫入,否則只發 Telegram 通知,不會 crash。
---
## 三、SQL 漏斗設計(已修正欄位名稱)
`hermes_analyst_service.py``fetch_candidates()` 的核心 SQL
```sql
WITH latest_momo_price AS (
-- 從爬蟲商品庫取最新 MOMO 售價
SELECT
p.i_code AS sku, -- MOMO 商品代碼
p.name,
p.category,
pr.price AS momo_price,
ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY pr.timestamp DESC) AS rn
FROM products p
JOIN price_records pr ON pr.product_id = p.id
WHERE p.status = 'ACTIVE'
),
recent_sales AS (
-- 從每日業績快照計算近7天 vs 前7天銷售額
SELECT
"商品ID" AS sku, -- ⚠️ 實際欄位名為「商品ID」(非「商品編號」)
SUM(CASE WHEN snapshot_date >= CURRENT_DATE - 7
THEN COALESCE("銷售金額"::numeric, 0) ELSE 0 END) AS sales_7d_curr,
SUM(CASE WHEN snapshot_date >= CURRENT_DATE - 14
AND snapshot_date < CURRENT_DATE - 7
THEN COALESCE("銷售金額"::numeric, 0) ELSE 0 END) AS sales_7d_prev
FROM daily_sales_snapshot
GROUP BY "商品ID"
)
SELECT lmp.sku, lmp.name, lmp.category, lmp.momo_price,
rs.sales_7d_curr, rs.sales_7d_prev
FROM latest_momo_price lmp
JOIN recent_sales rs ON rs.sku = lmp.sku
WHERE lmp.rn = 1
AND rs.sales_7d_prev > 0
AND (rs.sales_7d_curr - rs.sales_7d_prev) / rs.sales_7d_prev < -0.10
ORDER BY (rs.sales_7d_curr - rs.sales_7d_prev) / rs.sales_7d_prev ASC
LIMIT 300
```
**漏斗效果**: 226萬筆 price_records → ~300 筆近7天銷量跌幅 > 10% 的活躍商品)
**JOIN 邏輯**:
- `products.i_code``daily_sales_snapshot."商品ID"` — 均為 MOMO 商品代碼,格式相同
---
## 四、競品價格補給線架構(已實裝)
### 生產者-消費者解耦設計
```
[competitor_price_feeder.py Worker] ←← 每 4 小時獨立運行
↓ 搜尋 PChomesearch_products
↓ 商品身份比對marketplace_product_matcher.py
↓ 提取語意標籤
↓ UPSERT competitor_prices預設 TTL 48h
[HermesAnalystService.fetch_candidates()] ←← AI Pipeline 消費端
↓ LEFT JOIN competitor_prices零網路等待
↓ 有效期內expires_at > NOW()+ match_score ≥ 0.76 + tags 含 identity_v2 才 JOIN
↓ pchome_price + competitor_tags 一起傳給 Hermes
```
### 關鍵設計決策
| 決策 | 選擇 | 原因 |
|------|------|------|
| 解耦方式 | DB 表快取(非 Redis | PostgreSQL 已是核心,無需額外依賴;支援 JOIN |
| TTL | 6 小時 | 與 AI Pipeline 排程週期對齊 |
| 比對算法 | 品牌 + 核心 token + 容量/重量/包數 + 品類 + 價格 sanity check | 由 `marketplace_product_matcher.py` 統一供 feeder、legacy crawler、AI/PPT 鏈路使用 |
| 最低比對門檻 | 0.76 | 核心比價寧可待審,不允許低信心錯配影響 AI 決策 |
| 已有不同 PChome 商品覆蓋門檻 | 0.84 | 新候選與既有正式配對不同時,除非超高信心,否則寫入 `needs_review` attempt 不覆蓋 |
| 單位價可比模式 | `unit_comparable` | 同核心商品但買送/套組/件數不同時,不寫正式總價差;只寫入 attempt並以單位價證據供 Dashboard / PPT / AI 報表與人工覆核 |
| Browse.sh 診斷 | optional wrapper | 只用於 selector / XHR / network trace 探勘;不得取代正式 crawler也不得直接把輸出寫成正式競品價格 |
| 語意標籤 | JSONB 陣列 | 傳給 Hermes 提升情境感知品質 |
### 競品比對邏輯(`competitor_price_feeder.py`
```
MOMO 商品名稱
→ marketplace_product_matcher.build_search_terms()
→ PChomeCrawler.search_products(keyword, limit=12)
→ marketplace_product_matcher.score_marketplace_match()
→ 品牌衝突 / 容量衝突 / 包數衝突 hard veto
→ 同核心但買送/套組/件數不同標記 unit_comparable不進正式總價差
→ 同款高信心 score ≥ 0.76 才進 competitor_prices
→ 低信心、規格衝突、既有配對衝突寫入 competitor_match_attempts
```
### `fetch_candidates()` v2 漏斗(已更新)
```sql
LEFT JOIN competitor_prices cp
ON cp.sku = lmp.sku
AND cp.source = 'pchome'
AND cp.expires_at > NOW()
AND cp.match_score >= 0.76
AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2'
```
→ 無競品資料的商品仍回傳,`pchome_price=NULL``_batch_analyze` 自動跳過
### 下游消費規範2026-05-19 更新)
- Dashboard、AI pick、Hermes、Excel export、daily/growth 圖表與 competitor PPT 必須以 `competitor_prices + competitor_price_history + competitor_match_attempts` 為短期唯一生產真相源,且只消費 `identity_v2` matcher 驗證過的配對;舊版僅靠 `match_score` 的快取不可直接進入決策或簡報。
- `pchome_matches` 與 live `pchome_batch()` 僅保留 legacy compatibility不得作為新簡報或 AI 決策主來源。
- `services/competitor_intel_repository.py` 是下游頁面、圖表、簡報的共用查詢出口;新增消費端不得各自硬寫不同 match threshold。所有競品報表的價差方向統一為 `MOMO - PChome`:正值代表 MOMO 較貴 / PChome 低價壓力,負值代表 MOMO 價格優勢daily、growth、OpenClaw、PPT 不得使用反向定義。
- competitor PPT 不可只輸出 matched rows 造成覆蓋率假象;`fetch_competitor_comparison_results()` 必須用 `LEFT JOIN valid_competitor` 保留高營收/高價但尚未有效配對的 MOMO 商品,並帶出 `match_status``candidate_count``best_match_score``match_diagnostic`,讓簡報與 AI 文案明確區分「高信心比對」與「待補身份/價格」。
- `services/competitor_identity_revalidator.py` 可對既有 `competitor_prices` legacy row 離線重跑 `identity_v2`:只有新版 matcher 分數 `>= 0.76` 且無 hard veto 才補 `identity_v2` / `legacy_revalidated` tags預設不刷新 `expires_at`,避免過期價格進入決策。
- `CompetitorPriceFeeder.run_expired_identity_refresh()` 會優先刷新已通過 `identity_v2` 但 TTL 過期的 PChome row直接用既有 `competitor_product_id` 批次呼叫 PChome 商品 API再用新版 matcher 重新驗證名稱/規格/價格 sanity通過後寫回 `competitor_prices``competitor_price_history`。這條路徑提升新鮮價格覆蓋率,但不降低 match threshold也不讓過期價格直接進入決策佇列排序必須先處理既有 `price_basis_total_price` / `alert_tier_price_alert_exact` 或 diagnostic 等價欄位的安全價差 row再處理需要 review 的舊 row。若既有 `competitor_product_id` 已查不到或回傳候選低於門檻expired refresh 只寫 `refresh_no_result` / 低信心 attempt 並標記 `fresh_search_recovery_deferred`,不得在同一條價格刷新路徑 fresh search 替換正式 identity。fresh search recovery 只保留給 retryable candidate revalidation / unmatched priority 等補抓路徑。
- 過期 identity refresh 排序必須優先 `price_basis_total_price` / `alert_tier_price_alert_exact``match_diagnostic_json.price_basis='total_price'` / `alert_tier='price_alert_exact'` 的正式價差配對,再依 `expires_at` 與 MOMO 價格排序,避免高風險可決策價差長期排在低價或非告警型 stale row 後面。
- `marketplace_product_matcher.py` 的擴充只能走「正向證據 + 反向 veto」品牌一致、商品線/型號訊號強、價格合理且無 hard veto 時才允許 `strong_product_line_match` 加分;補充瓶/補充包/refill 與一般正裝不互相配對,分享組/加量組/明星組等組合包不得誤配單品。
- 近門檻規則必須成對補「召回 + 防錯配」測試:可召回者需有品牌、商品線、規格或具名 identity anchor例如 MUJI 精油芬香護手霜、Mustela 慕之幼爽身潤膚乳、Herbacin 小甘菊護手霜;防錯配者需成為 hard veto例如 M·A·C Macximal 柔霧/緞光唇膏質地、ERBE 指甲清垢棒/指甲緣刨刀功能、Schick 舒芙/舒綺女用除毛刀品線。不得用單一同規格或同品牌放寬全域門檻。
- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時matcher 必須回傳 `comparison_mode='unit_comparable'``unit_comparable` reasonFeeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'``refresh_unit_comparable`,不得寫入 `competitor_prices`。Dashboard 與 `competitor_intel_repository` 必須用 `build_unit_price_comparison()` 產生每 ml / 每 g / 每入單位價證據,讓 PPT / AI 報表可說明「需單位價比較」而不是把總價當同款價差。商品看板在正式配對尚未成立時,仍必須顯示最佳候選 PChome 商品名稱、候選價與「候選價需單位換算」說明讓人工覆核可直接看見下一步daily/growth、PPT 與 OpenClaw 摘要不得自建查詢,需消費 `fetch_competitor_review_queue()` 與 coverage 的 `unit_comparable_count`。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`
- PChome feeder 的外部 request timeout 由 `PCHOME_FEEDER_TIMEOUT` 控制,預設 12 秒;排程不得因單一 PChome 搜尋 API timeout 被拖到數分鐘。
- 品牌 alias 屬於正向身份證據,不是門檻放寬;`DR.WU / DR WU / DRWU / 達爾膚` 這類同品牌中英混寫必須正規化後再進 matcher避免同規格真同款被誤降成 brandless identity review。
- 近門檻 rescore pilot 必須支援明確 SKU 篩選;`audit_competitor_match_attempt_rescore.py --sku <sku>` 可只重算指定 SKU避免為了小批次驗證而掃整批 `true_low_confidence`
- 商品看板的 PChome 狀態必須把 matcher 診斷原因翻成可行動語意:品牌不符已排除、規格不符已排除、補充包不相容、組合規格不相容、系列不符已排除、需單位價比較、低信心待補強等,不可只顯示籠統「待比對」或「身份否決」。
- PChome 補抓產線與 priority list 若尚未進入搜尋/補抓必須顯示「PChome 補抓產線」、「尚未搜尋」與「尚未進入 PChome 補抓」,不得使用「待比對」這類會被誤解成已有候選待人工審核的字眼。
- 商品看板、PChome review queue 與 `/api/export/excel/pchome-review` 必須優先讀取 `match_diagnostic_json.reasons` 並轉成操作員可讀標籤;文字版 `error_message` 只作 legacy fallback。商品列的 PChome 狀態摘要也必須使用同一套專業標籤,避免 overview 顯示「妝效質地不同」但列表仍顯示籠統身份不符。新增 matcher reason 時需同步更新 `MATCH_DIAGNOSTIC_REASON_LABELS` 與 dashboard 狀態翻譯,避免 UI 顯示 `makeup_finish_conflict` 這類 machine code。PChome 標題缺品牌但有窄範圍 exact identity anchor 的商品,只能透過具名 brandless recovery 進 manual-review identity多色任選 / 單一色號 gap 必須標記 `variant_selection_review`,並從 `recoverable_low_score` 降回 `true_low_confidence`,不得自動批次寫正式價差。
- Dashboard 必須把「待比對」拆成可診斷狀態:`價格過期待刷新``舊版配對待重驗``低分配對待補強``已排除``需單位價比較``找不到同款``抓取異常``尚未搜尋`。硬性不相容候選應顯示為已排除/不相容,不得讓使用者誤以為每筆都需要人工待審。
### 執行方式
```bash
# 手動觸發一輪抓取
python3 services/competitor_price_feeder.py
# 預覽 legacy PChome 快取 identity_v2 重驗證(不寫入)
python3 -m services.competitor_identity_revalidator --limit 500
# 寫入安全通過的 identity_v2 tag不刷新過期價格
python3 -m services.competitor_identity_revalidator --limit 500 --apply
# 未來整合為 K3s CronJob每 4 小時)
# k8s/jobs/competitor-price-feeder-cronjob.yaml
```
---
## 五、Telegram 語意化訊息規範ChatOps 標準)
### 5.1 核心原則
1. **語意化排版 (Semantic Formatting)** — Emoji 作為視覺標籤0.5 秒判斷嚴重性
2. **倒金字塔結構** — 結論先行 → 核心數據 → AI 洞察 → 建議行動 → 運算足跡
3. **收斂行動呼籲 (Call to Action)** — 每則訊息只有一個明確的 👉 建議行動
4. **底部運算足跡** — FinOps + Observability用分隔線隔開主訊息
5. **EA HITL 專業 brief**`ea_escalation` 必須分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格類行動不得用長 bullet 串接,必須拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID。
6. **價格類決策信封專業 brief**`price_alert``pchome_match_review``competitor_price_review` 等含 PChome / 價格證據的 `decision_envelope`EventRouter 必須直送 evidence template不得進 L1/L2 重摘要Telegram 內容必須拆成「標的、價格證據、比對證據、人工下一步」,並從信封讀取 `momo_price``competitor_price``candidate_gap_pct``match_score``unit_price_insight``existing_match_conflict`
7. **Shared UI 信封摘要共用** — Webcrumbs host data 與其他共用 UI runtime 必須讀 `reviewDecisionBrief` / `decision_envelope` 的結構化證據,不在瀏覽器端重組比價判斷;這些 payload 只能讀 DB 既有覆核資料,不呼叫 LLM、不抓外站、不寫資料。
### 5.2 語意化 Emoji 字典
| 類別 | Emoji | 語意 |
|------|-------|------|
| 身份識別 | `⚡ NemoTron 派發器` | Dispatcher 身份 |
| 身份識別 | `🔍 Hermes 3 8B` | Analyst 身份(僅出現在足跡) |
| 風險級別 | `🚨` | 高危險,立即行動 |
| 風險級別 | `⚠️` | 中風險,人工覆核 |
| 風險級別 | `💡` | 低風險,策略建議 |
| 例行報告 | `📊` | 核心數據區塊標頭 |
| 業務屬性 | `💰` | 價格/毛利 |
| 業務屬性 | `📦` | 庫存/銷量 |
| 業務屬性 | `🏆` | 競品情報 |
| AI 洞察 | `🧠` | AI 分析結果 |
| 運算足跡 | `⚙️` | FinOps 底部區塊 |
### 5.3 三大類訊息模板(標準格式)
#### 類別一:緊急告警(`trigger_price_alert` 觸發)
```
🚨 [⚡ NemoTron 派發器] 競價高危險預警
⚠️ 核心問題:[A003 舒特膚 AD 乳液] 價格大幅落後競品,訂單流失中!
📊 關鍵數據:
• 我方價格:$1,200
• 競品價格:$980 (價差 22.4%)
• 銷量變化:近七天銷量 -35.0%
🧠 AI 洞察 (信心度 85%)
價差已突破 20% 警戒線,且伴隨實質銷量下滑,高度判定為競品大力促銷攔截。
👉 建議行動:建議立即降價至 $1,000 迎戰,或發放 $200 專屬折價券
─────────────────────
⚙️ 運算足跡:
• 🔍 分析: Hermes 3 8B (GCP-A/GCP-B/111 Ollama) | 耗時: 34.2s | Tokens: 512 | $0 成本
• ⚡ 決策: NemoTron NIM | 185 Tokens | $0 (配額內 2/80)
```
#### 類別二:人工覆核(`flag_for_human_review` 觸發)
```
⚠️ [⚡ NemoTron 派發器] 異常波動需人工覆核
🔍 待查商品:[A001 玻尿酸面膜10片裝]
📊 矛盾數據:
• 價格狀態:無明顯價差 (與競品齊平)
• 異常現象:過去 3 天銷量突然掛零 (平日日均 15 件)
• 庫存狀態:目前庫存充足 (500+ 件)
🧠 AI 洞察 (信心度 45%)
數據出現矛盾訊號AI 信心不足以自主決策,需人工走查確認。
👉 建議行動:請營運人員立即進行人工走查。
─────────────────────
⚙️ 運算足跡:
• 🔍 分析: Hermes 3 8B (GCP-A/GCP-B/111 Ollama) | 耗時: 34.2s | Tokens: 512 | $0 成本
• ⚡ 決策: NemoTron NIM | 185 Tokens | $0 (配額內 2/80)
```
#### 類別三:策略執行通知(`add_to_recommendation` 觸發)
```
💡 [⚡ NemoTron 派發器] 潛力商品自動佈署
🏆 推薦品項:[A009 美白化妝水150ml] 已自動加入「首頁推薦區塊」
📊 決策依據:
我方價格低於市場 20%近7天銷量回升具備流量轉換潛力
🧠 AI 洞察 (信心度 82%)
具備價格競爭優勢NemoTron 主動提升曝光量以最大化業績。
👉 執行狀態:✅ 系統已自動寫入 ai_price_recommendations 推薦表
─────────────────────
⚙️ 運算足跡:
• 🔍 分析: Hermes 3 8B (GCP-A/GCP-B/111 Ollama) | 耗時: 34.2s | Tokens: 512 | $0 成本
• ⚡ 決策: NemoTron NIM | 185 Tokens | $0 (配額內 2/80)
```
#### 類別四Gemini 備援 / 鎖定場景推理週報
```
... (前文省略) ...
─────────────────────
⚙️ 運算足跡:
• 🔍 彙整: Hermes 3 8B (GCP-A/GCP-B/111 Ollama) | 耗時: 12s | $0 成本
• 🧠 備援/鎖定場景: Gemini 2.5 Flash | 8,420 Tokens | 費用: 約 $0.003 USD
```
### 5.4 運算足跡資料來源
| 模型 | API Response 欄位 | 說明 |
|------|-----------------|------|
| Hermes (Ollama) | `eval_count`, `total_duration` | 生成 tokens 數 + 推理耗時 (ns→s) |
| NemoTron (NIM) | `usage.total_tokens` (OpenAI 格式) | prompt + completion tokens 合計 |
| Gemini | `usageMetadata.totalTokenCount` | 乘費率算 USD |
> **程式碼位置**: `nemoton_dispatcher_service.py` → `_build_footprint_block(hermes_stats, nim_stats)`
### 5.5 Inline Keyboard 按鈕Level 2 互動,待實裝)
當收到類別一緊急告警時訊息底部附帶互動按鈕Telegram Bot API inline_keyboard
```
[ ✅ 批准降價 ] [ ❌ 拒絕並忽略 ] [ 🔗 查看報表 ]
```
- `✅ 批准降價` → 呼叫 MOMO PRO 後台 API 改價 + 決策寫入知識庫
- `❌ 拒絕並忽略` → 決策寫入知識庫(訓練未來在此品類保守點)
- `🔗 查看報表` → 跳轉至 MOMO PRO 該商品數據分析頁
> **現狀**: 尚未實裝Inline Keyboard 需搭配 Telegram Webhook + callback_query handler
---
## 六、Telegram 告警架構
### 告警群組
- 群組: **小龍蝦** (業務情報專用,非 SRE 維運)
- Chat ID: `-1003940688311`
- Bot: `8610496165:AAFOlcWV4oRUSC2TI-fYux7JV97fjNzsYR8`
### 單 Bot 多身份策略One Bot, Multiple Headers
| 模組 | Telegram 標頭 |
|------|--------------|
| Hermes 分析師 | `[Hermes 分析師]` |
| NemoTron 派發器 | `[NemoTron 派發器]` |
| Gemini 備援 | `[Gemini 備援]`(僅 Ollama 失敗或 ADR-028 鎖定場景) |
### 三種告警類型
| Tool | 觸發條件 | Telegram 格式 |
|------|---------|--------------|
| `trigger_price_alert` | HIGH 風險 (gap>15% + 銷量跌>20%) | 🔴/🟡 競價威脅告警 |
| `add_to_recommendation` | 我方價格低於競品且銷量正成長 | ⭐ 推薦商品候選 |
| `flag_for_human_review` | 信心 < 0.6 或情況複雜 | ⚠️ 需要人工審核 |
---
## 六、已驗證的服務參數
### Hermes 分析師
| 參數 | 值 |
|------|---|
| 模型 | `hermes3:latest` |
| Ollama URL | GCP-A `http://34.143.170.20:11434` → GCP-B `http://34.21.145.224:11434` → 111 `http://192.168.0.111:11434` |
| Timeout | 120s |
| Temperature | 0.1 |
| 實測推理時間 | **19.3s3筆實彈 2026-04-17** |
| TOP_N | 20每次最多輸出威脅數 |
### NemoTron 派發器
| 參數 | 值 |
|------|---|
| 模型 | `meta/llama-3.1-8b-instruct` |
| NIM API URL | `https://integrate.api.nvidia.com/v1` |
| Timeout | 60s |
| 每日配額上限 | 80留 20 給 AWOOOI |
| 配額耗盡 fallback | 直接派發 HIGH 風險告警,跳過 NIM |
| 實測 token 消耗 | **1206 tokens/輪(實彈 2026-04-17** |
---
## 七、已知技術債與待辦
| 優先 | 項目 | 說明 |
|------|------|------|
| ✅ | Migration 003 + 004 | `competitor_prices` + `ai_price_recommendations` 已在 188 `momo_analytics` 執行 |
| ✅ | 188 生產容器實彈驗證 | Hermes + NIM + PostgreSQL + Telegram 全通 (2026-04-17) |
| ✅ | momo-app port 5001→5002 | docker-registry 佔用 5001docker-compose.yml 改為 `127.0.0.1:5002:80`,已 Up healthy |
| ✅ | momo-app 雙網路連線 | 同時連 `momo-network` + `momo-pro_default`(後者含 `momo-db` alias `momo-postgres`|
| P1 | PChome Feeder CronJob | `competitor_price_feeder.py` 每 4 小時排程 (Scheduler 整合) |
| P1 | 告警去重 TTL | 同一 SKU 短期內重複告警未防範 |
| P1 | `daily_sales_snapshot` 欄位防禦 | 若 Excel 欄位名變更JOIN 條件會靜默失效 |
| P2 | Scheduler 整合 | 每6小時自動觸發 Hermes→NIM→Telegram 管線 |
| P2 | Gemini 備援治理 | 僅保留 ADR-028 鎖定場景與 Ollama 失敗備援,新增 caller 必須走 ADR |
---
## 八、部署拓撲2026-04-17 確認)
### 實體機器對應
| 服務 | 主機 | 容器名 | 說明 |
|------|------|--------|------|
| PostgreSQL | 192.168.0.188 | `momo-db` | pgvector/pgvector:pg14含所有 AI 相關表 |
| momo-app | 192.168.0.188 | `momo-pro-system` | **Up healthyport 5002:80**5001 被 docker-registry 佔用,已改 5002 |
| momo-scheduler | 192.168.0.188 | `momo-scheduler` | 常駐排程容器 |
| Ollama Primary | 34.143.170.20 | Ollama 原生 | GCP-AAI/LLM/embedding 主路徑 |
| Ollama Secondary | 34.21.145.224 | Ollama 原生 | GCP-B同等備援 |
| Ollama Fallback | 192.168.0.111 | Ollama 原生 | 最後一道本地防線 |
| E2E 驗證容器 | 192.168.0.188 | `momo-e2e-test` | 臨時容器,含新服務模組 |
### 188 `/home/ollama/momo-pro/.env` 正確設定
```bash
TELEGRAM_BOT_TOKEN=8610496165:AAFOlcWV4oRUSC2TI-fYux7JV97fjNzsYR8 # ← 唯一正確 token
TELEGRAM_CHAT_IDS=["-1003940688311"] # 小龍蝦群組
NVIDIA_API_KEY=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
USE_POSTGRESQL=true
POSTGRES_HOST=momo-db
# POSTGRES_DB / USER / PASSWORD 使用 docker-compose.yml 預設值
```
> ⚠️ **Split-brain 陷阱**188 容器環境曾存在舊 bot token `8569720657`(不同 bot不在小龍蝦群組
> 已於 2026-04-17 深夜修正為正確 token `8610496165`。
---
## 九、校對歷程
| 日期 | 問題 | 修正 |
|------|------|------|
| 2026-04-17 | `fetch_candidates()` SQL 使用 `"商品編號"` | 修正為 `"商品ID"`(與 MOMO Excel 實際欄位名一致) |
| 2026-04-17 | Hermes gap_pct 由 LLM 計算 → 誤差大 | 改為 Python 預算 `(momo-pchome)/pchome*100` |
| 2026-04-17 | 推理時間 52s | 預算 gap_pct 後降至 19.3s (3筆) |
| 2026-04-17 | `model_footprint` DB 欄位寫入 `{}` | 分離 `footprint_text`Telegram 顯示)與 `footprint_data`DB JSON|
| 2026-04-17 | SQLite 語法 `NOW()` / `datetime('now')` / `::jsonb` | 全部改為 `CURRENT_TIMESTAMP` / Python 計算(跨 DB 相容)|
| 2026-04-17 | 本機 SQLite 測試通過但 188 未同步任何檔案 | rsync 推送 6 核心檔案 + 全站 dry-run 對帳 + migrations 跑通 |
| 2026-04-17 | 188 容器無 volume mount`docker cp` 臨時解 | 重建 image`COPY . .` bake 進新代碼port 5001 衝突記錄為技術債 |
| 2026-04-17 | 188 .env Telegram token 不正確split-brain| 修正為 `8610496165`188→Telegram message_id=282 確認 |
| 2026-04-17 | NIM Tool Calling E2E | 真實 NVIDIA_API_KEY 驗證dispatched=3, errors=[] |
| 2026-05-20 | PChome 商品身份比對仍可能因單一規格重疊誤放行 | V10.312 起 matcher 解析 mg/mcg 劑量、件組套組、多規格集合與同數字不同單位;劑量/容量/重量/件數/品類衝突會硬否決或導向單位價覆核,避免錯配污染 Dashboard、daily/growth、PPT 與 AI 競價分析 |
| 2026-05-20 | 正確 PChome 候選常因只掃第一頁或搜尋詞丟失品牌/規格而未進入 matcher | V10.314 起搜尋 API 依 limit 掃多頁、對暫時性錯誤有限重試feeder 預設 5 組搜尋詞、20 候選、2 頁,並保留括號/方括號內品牌與規格,提升覆核隊列與正式比價的候選品質 |
| 2026-05-20 | 指定日期競品簡報可能混用目前 `competitor_prices` 快取價 | V10.315 起 `fetch_competitor_comparison_results()` 有 start/end date 時改用 `competitor_price_history` 期間快照MOMO 價格取報表結束日前最新價;即時報表才使用目前有效 `competitor_prices` |
| 2026-05-20 | PChome 覆蓋率分子可能被非活躍或無 MOMO 現價 SKU 膨脹 | V10.317 起 `fetch_competitor_coverage()``valid_matches` 改為 active MOMO latest price 與有效 PChome `identity_v2` 價格交集,確保 daily/growth/PPT/AI 看到的比價資料品質不被舊快取列高估 |
| 2026-05-20 | EA HITL 告警可能把非 SKU 診斷誤排成待審 SKU或在缺少 DB/Hermes 實證時打擾人工 | V10.318 起 `ea_escalation` 僅對含 SKU/價格比較的 actions 使用競價卡片;非 SKU 診斷改為「待確認事項」。價格類低信心事件若無 DB/Hermes 實證,測試鎖定只 suppress、不寫 human_review、不發 Telegram |
| 2026-05-21 | ElephantAlpha NIM/LLM 回應偶爾不是純 JSON會觸發 `json.loads()` 失敗並落入舊式空泛策略 fallback | V10.383 起協調器容忍 fenced/混文字 JSON無法解析時改用 DB/Hermes 實證 fallback且 fallback 不再包含 OpenClaw `generate_*` 舊步驟或自動調價暗示 |
| 2026-05-20 | Telegram HTML parse mode 不支援 `<br>`,可能導致告警或報告送出 400 | V10.321 起 Telegram template 發送前會把 `<br>` / `<br/>` / `<BR />` 轉為換行;保留其他 HTML 標籤,非 HTML parse mode 不改寫 |
| 2026-05-20 | 部分舊 Telegram 入口繞過中央 sanitizer且 RAG awaiting review 使用錯誤 `chat_id=` 參數會讓人工審核推播失敗 | V10.322 起 Bot API price decision 走 `send_telegram_with_result()``price_decision()``report_url` 相容並 escape 動態欄位RAG awaiting review 改用 `chat_ids=[...]` 呼叫 `_send_telegram_raw()` |