From b68125ca263769ecbcbabe593f81c5279a802309 Mon Sep 17 00:00:00 2001 From: OoO Date: Wed, 20 May 2026 13:32:57 +0800 Subject: [PATCH] =?UTF-8?q?[V10.329]=20=E5=BB=B6=E9=95=B7=E6=88=90?= =?UTF-8?q?=E9=95=B7=E5=88=86=E6=9E=90=E7=A9=A9=E5=AE=9A=E5=BF=AB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO_NEXT_STEPS.txt | 1 + config.py | 2 +- docs/memory/history_logs.md | 1 + services/cache_service.py | 7 +++++-- tests/test_cache_manager.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index d1e3bd4..675647b 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.329 優化 `/growth_analysis` 冷快取策略:source fingerprint 未變時,成長分析共享快取由 30 分鐘延長為 6 小時有效;每日匯入仍會主動清除快取,避免資料更新後沿用舊圖表,同時降低正式端重啟或冷 worker 重新掃 `realtime_sales_monthly` 的 14 秒級等待。 - V10.328 強化 MOMO/PChome 核心比價準確性第一波:補高頻品牌 alias、中文商品線 bigram 訊號、保健/包裝同義單位與買送件數解析,搜尋詞改為品牌/核心/主規格三層;PChome 比對嘗試與正式快照補存 URL、圖片、庫存與結構化 diagnostics,商品列表用 tone 分流顯示尚未搜尋、低信心、身份否決、單位價與過期狀態,不再把不同問題全部壓成灰色待比對;同步持久化首頁 / PChome coverage 熱路徑索引,避免重開機後慢查詢回歸。 - V10.327 補 OpenClaw fallback 可觀測性:週報、月報、Meta、日報洞察、每日報告的 Gemini/NIM 備援 caller 納入 caller registry、AI 觀測台 agent group 與 Telegram 狀態統計,並補 MCP collector Ollama-first regression test,避免 fallback 真實使用量在觀測層被歸類成未知或漏算。 - V10.326 補市場情報 candidate queue review AI summary Telegram dispatch report run readiness:新增 read-only report run readiness builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report run package 後整理 report generation readiness manifest、manual report command boundary、artifact path gate 與後續 report run receipt gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。 diff --git a/config.py b/config.py index c6d73af..e72b71c 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.328" +SYSTEM_VERSION = "V10.329" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index d0aaff5..1548d29 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -14,6 +14,7 @@ ### 2026-05-20:重開機後首頁熱路徑索引持久化 - **Dashboard / PChome 慢查詢修復**: 主機重開機後 `https://mo.wooo.work/` 首頁可用但多次逾時,實際瓶頸集中在首頁與 PChome coverage 查詢掃描 `products`、`price_records`、`competitor_match_attempts`。線上先補三個索引讓首頁恢復 200,並新增 `migrations/040_dashboard_hot_path_indexes.sql` 將修復持久化到 fresh restore / DB rebuild 流程。 +- **Growth Analysis 冷快取修復**: `/growth_analysis` 在 `monthly_summary_analysis` 落後時會改掃 `realtime_sales_monthly` 聚合,冷計算約 14 秒;修正為 source fingerprint 未變時延長共享快取有效期,匯入流程仍主動清除快取,避免資料未變卻反覆掃大表。 ### 2026-04-29:ADR-017 Phase 3f 模組化收尾啟動 - **DB metadata 救急**: `database/manager.py` 改為顯式載入 permission / AI / autoheal / import / vendor / realtime_sales ORM,PostgreSQL 初始化透過 process-local guard + advisory lock 執行 `Base.metadata.create_all()`,避免新環境漏表與一般流量重複碰 DDL。 diff --git a/services/cache_service.py b/services/cache_service.py index f1fe02c..b0bedfe 100644 --- a/services/cache_service.py +++ b/services/cache_service.py @@ -41,6 +41,7 @@ _GROWTH_ANALYSIS_CACHE = { 'source_fingerprint': None, } _GROWTH_CACHE_TTL = 1800 # 成長分析快取: 30 分鐘 +_GROWTH_STABLE_SOURCE_CACHE_TTL = 21600 # source fingerprint 未變時延長到 6 小時,避免冷 worker 反覆掃明細表 _BASE_DIR = Path(__file__).resolve().parents[1] _GROWTH_SHARED_CACHE_FILE = _BASE_DIR / "data" / "growth_analysis_cache.pkl" _GROWTH_SOURCE_FINGERPRINT_CACHE = {} @@ -166,10 +167,12 @@ def _is_growth_payload_valid(payload, source_fingerprint=None): return False if not payload.get('chart_data') or not payload.get('kpi'): return False + if source_fingerprint is not None: + if payload.get('source_fingerprint') != source_fingerprint: + return False + return is_cache_valid(payload.get('timestamp'), _GROWTH_STABLE_SOURCE_CACHE_TTL) if not is_cache_valid(payload.get('timestamp'), _GROWTH_CACHE_TTL): return False - if source_fingerprint is not None: - return payload.get('source_fingerprint') == source_fingerprint return True diff --git a/tests/test_cache_manager.py b/tests/test_cache_manager.py index 23d1505..c725c82 100644 --- a/tests/test_cache_manager.py +++ b/tests/test_cache_manager.py @@ -147,6 +147,34 @@ def test_growth_cache_shared_file_roundtrip(tmp_path, monkeypatch): assert shared_cache.exists() +def test_growth_cache_reuses_expired_ttl_when_source_fingerprint_matches(tmp_path, monkeypatch): + import pickle + from datetime import timedelta + from services import cache_service + + shared_cache = tmp_path / "growth_analysis_cache.pkl" + monkeypatch.setattr(cache_service, "_GROWTH_SHARED_CACHE_FILE", shared_cache) + cache_service.clear_growth_cache() + cache_service.set_growth_cache( + {"labels": ["2026-05"], "revenue": [1000]}, + {"ytd_revenue": 1000}, + source_fingerprint=("2026-05-17", 10), + ) + + stale_timestamp = ( + cache_service.datetime.now(cache_service.TAIPEI_TZ) + - timedelta(seconds=cache_service._GROWTH_CACHE_TTL + 60) + ) + cache_service._GROWTH_ANALYSIS_CACHE["timestamp"] = stale_timestamp + payload = pickle.loads(shared_cache.read_bytes()) + payload["timestamp"] = stale_timestamp + shared_cache.write_bytes(pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)) + + assert cache_service.is_growth_cache_valid(("2026-05-17", 10)) + assert not cache_service.is_growth_cache_valid(("2026-05-18", 10)) + assert not cache_service.is_growth_cache_valid() + + def test_clear_growth_cache_removes_shared_file(tmp_path, monkeypatch): from services import cache_service