From 0709392d9d961d9331d4eac1f74ea026741a87aa Mon Sep 17 00:00:00 2001 From: OoO Date: Sun, 24 May 2026 19:00:13 +0800 Subject: [PATCH] Skip exact count for full PChome review page --- TODO_NEXT_STEPS.txt | 1 + config.py | 2 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 2 +- docs/memory/history_logs.md | 1 + routes/dashboard_routes.py | 19 ++++++++- services/competitor_intel_repository.py | 40 +++++++++++++------ ...t_competitor_match_attempts_persistence.py | 2 + tests/test_frontend_v2_assets.py | 2 + 8 files changed, 54 insertions(+), 15 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index fa23e23..d587514 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.448 讓 PChome 覆核「全部」頁跳過 exact count:`review_status=all` 使用 shared overview cache 的待處理總數作為分頁總數提示,只查當頁 50 筆;單一狀態分流仍保留 exact count,降低全量覆核頁互動成本。 - V10.447 反轉 PChome 覆核頁查詢方向:review queue page 先從最新 `competitor_match_attempts` 的可覆核狀態縮小候選,再 join ACTIVE 商品與最新價,並用 `NOT EXISTS` 排除已有有效 identity_v2 正式價;避免每次「全部覆核」先掃全站 ACTIVE 商品。 - V10.446 修正 PChome 覆核頁輕量路徑的 overview timeout:覆核頁總覽改讀已存在的 shared dashboard cache / stale cache,沒有快取時只用目前覆核頁資料補足狀態,不再現場跑 `_load_competitor_decision_overview(session)` 的重型後備 SQL。 - V10.445 補 PChome 覆核頁輕量渲染路徑:`filter=pchome_review` 不再先建完整 Dashboard `unique_items`,改為只查覆核當頁 50 筆商品、當前價、昨日價與週前價,再沿用同一張新版表格與人工覆核按鈕;降低核心比價覆核頁的全站資料負載。 diff --git a/config.py b/config.py index d646722..a3727db 100644 --- a/config.py +++ b/config.py @@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.447" +SYSTEM_VERSION = "V10.448" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index 3fe110a..6329c14 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-05-24 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 備援預設關閉 -> **適用版本**: V10.447 +> **適用版本**: V10.448 --- diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index 871f8a7..bb6eae0 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-05-24:PChome 近門檻身份回收第二輪 +- **V10.448 PChome 覆核全量頁跳過 exact count**: `review_status=all` 改用 shared overview cache 的待處理總數作為分頁總數提示,當頁只查 50 筆;單一狀態分流仍保留 exact count,避免每次操作全量覆核頁都為總筆數重掃整個 review queue。 - **V10.447 PChome 覆核頁查詢方向反轉**: review queue page 改由最新 `competitor_match_attempts` 的可覆核狀態先縮小候選,再 join ACTIVE 商品與最新價,並用 `NOT EXISTS` 排除已有有效 `identity_v2` 正式 PChome 價格;避免「全部覆核」每次先掃全站 ACTIVE 商品後才過濾,提高核心比價覆核頁可操作性。 - **V10.446 PChome 覆核頁 overview timeout 修正**: 覆核頁輕量 render path 的總覽改讀既有 shared dashboard cache / stale cache;若快取不存在,只用目前覆核頁資料補足 review count 與狀態分流,不再即時呼叫 `_load_competitor_decision_overview(session)` 的重型 SQL,避免正式資料量下 statement timeout。 - **V10.445 PChome 覆核頁輕量渲染路徑**: `filter=pchome_review` 直接走覆核隊列專用 render path,不再先建完整 Dashboard `unique_items`;當頁只查 50 筆覆核 SKU 的商品資料、最新價、昨日價與週前價,仍沿用同一張新版表格、狀態分流、PChome 候選說明與人工覆核按鈕,降低核心比價覆核頁的全站資料負載。 diff --git a/routes/dashboard_routes.py b/routes/dashboard_routes.py index 2fb9cd5..5f06e02 100644 --- a/routes/dashboard_routes.py +++ b/routes/dashboard_routes.py @@ -715,6 +715,7 @@ def _load_competitor_review_page( search_query='', category_filter='all', review_status='all', + count_total=True, ): try: from services.competitor_intel_repository import fetch_competitor_review_queue_page @@ -729,6 +730,7 @@ def _load_competitor_review_page( search_query=search_query, category=_normalize_dashboard_category_filter(category_filter), status_filter='' if review_status == 'all' else review_status, + count_total=count_total, ) except Exception as exc: sys_log.warning(f"[Dashboard] PChome 覆核隊列分頁讀取略過: {exc}") @@ -1703,6 +1705,13 @@ def _render_pchome_review_dashboard( now_taipei, today_start_db, ): + overview_hint = _load_cached_competitor_overview_for_review( + now_taipei, + [], + 0, + review_status, + ) + count_total = review_status != 'all' review_page = _load_competitor_review_page( session, page=page, @@ -1710,9 +1719,17 @@ def _render_pchome_review_dashboard( search_query=search_query, category_filter=category_filter, review_status=review_status, + count_total=count_total, ) review_queue = review_page.get('items') or [] - review_queue_total = int(review_page.get('total') or len(review_queue)) + review_queue_total = int(review_page.get('total') or 0) + if review_queue_total < 0: + review_queue_total = int( + (overview_hint.get('review_status_counts') or {}).get('all') + or overview_hint.get('review_queue_count') + or len(review_queue) + or 0 + ) review_queue_map = { str(row.get('sku') or ''): row for row in review_queue diff --git a/services/competitor_intel_repository.py b/services/competitor_intel_repository.py index 9235be2..e0d5fb3 100644 --- a/services/competitor_intel_repository.py +++ b/services/competitor_intel_repository.py @@ -796,6 +796,7 @@ def fetch_competitor_review_queue_page( search_query: str = "", category: str = "", status_filter: str = "", + count_total: bool = True, ) -> dict: """Paginated PChome review queue for operator-facing Dashboard pages.""" page = max(1, int(page or 1)) @@ -809,6 +810,7 @@ def fetch_competitor_review_queue_page( "review_queue_page:v2:" f"page={page}:per={per_page}:q={search_query.lower()}:cat={category}:" f"status={status_filter}:" + f"count={int(bool(count_total))}:" f"floor={PCHOME_MATCH_SCORE_FLOOR}" ) return _cached_payload( @@ -820,6 +822,7 @@ def fetch_competitor_review_queue_page( search_query=search_query, category=category, status_filter=status_filter, + count_total=count_total, ), ttl_seconds=min(COMPETITOR_INTEL_CACHE_TTL_SECONDS, 300), ) @@ -922,6 +925,7 @@ def _fetch_competitor_review_queue_page_uncached( search_query: str = "", category: str = "", status_filter: str = "", + count_total: bool = True, ) -> dict: inspector = inspect(engine) if not ( @@ -950,12 +954,28 @@ def _fetch_competitor_review_queue_page_uncached( "limit": per_page, "offset": (page - 1) * per_page, } - page_sql = text(cte + """ - , total_rows AS ( - SELECT COUNT(*) AS total_count - FROM review_rows - ), - paged_rows AS ( + if count_total: + page_sql = text(cte + """ + , total_rows AS ( + SELECT COUNT(*) AS total_count + FROM review_rows + ), + paged_rows AS ( + SELECT * + FROM review_rows + ORDER BY + priority_rank ASC, + momo_price DESC NULLS LAST, + best_match_score DESC NULLS LAST, + attempted_at DESC NULLS LAST + LIMIT :limit OFFSET :offset + ) + SELECT paged_rows.*, total_rows.total_count + FROM total_rows + LEFT JOIN paged_rows ON TRUE + """) + else: + page_sql = text(cte + """ SELECT * FROM review_rows ORDER BY @@ -964,15 +984,11 @@ def _fetch_competitor_review_queue_page_uncached( best_match_score DESC NULLS LAST, attempted_at DESC NULLS LAST LIMIT :limit OFFSET :offset - ) - SELECT paged_rows.*, total_rows.total_count - FROM total_rows - LEFT JOIN paged_rows ON TRUE - """) + """) with engine.connect() as conn: rows = conn.execute(page_sql, page_params).mappings().all() - total = int(rows[0].get("total_count") or 0) if rows else 0 + total = int(rows[0].get("total_count") or 0) if count_total and rows else -1 item_rows = [dict(row) for row in rows if row.get("sku")] return { diff --git a/tests/test_competitor_match_attempts_persistence.py b/tests/test_competitor_match_attempts_persistence.py index 7d2a564..00d0698 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -50,6 +50,8 @@ def test_competitor_review_queue_page_uses_single_paged_total_query(): assert "total_rows AS" in page_body assert "paged_rows AS" in page_body assert "LEFT JOIN paged_rows ON TRUE" in page_body + assert "if count_total:" in page_body + assert "total = int(rows[0].get(\"total_count\") or 0) if count_total and rows else -1" in page_body assert "SELECT COUNT(*) AS total FROM review_rows" not in page_body assert "rows[0].get(\"total_count\")" in page_body assert "if row.get(\"sku\")" in page_body diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index e8ee5d4..7010e36 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -138,6 +138,8 @@ def test_dashboard_v2_is_production_default_and_uses_real_dashboard_data(): assert "_build_review_dashboard_items(session, review_queue, today_start_db)" in route_source assert "_load_cached_competitor_overview_for_review(" in route_source assert "_load_competitor_decision_overview(session)" not in route_source + assert "count_total = review_status != 'all'" in route_source + assert "count_total=count_total" in route_source assert "只替 PChome 覆核當頁建立商品列" in route_source assert "_load_competitor_decision_overview(session, unique_items)" in route_source assert "item_map = {}" in route_source