[V10.329] 延長成長分析穩定快取
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s

This commit is contained in:
OoO
2026-05-20 13:32:57 +08:00
parent 2c47a79f05
commit b68125ca26
5 changed files with 36 additions and 3 deletions

View File

@@ -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 gateAPI/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。

View File

@@ -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 # 用於模板顯示

View File

@@ -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-29ADR-017 Phase 3f 模組化收尾啟動
- **DB metadata 救急**: `database/manager.py` 改為顯式載入 permission / AI / autoheal / import / vendor / realtime_sales ORMPostgreSQL 初始化透過 process-local guard + advisory lock 執行 `Base.metadata.create_all()`,避免新環境漏表與一般流量重複碰 DDL。

View File

@@ -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

View File

@@ -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