Skip exact count for full PChome review page

This commit is contained in:
OoO
2026-05-24 19:00:13 +08:00
parent 82b5616921
commit 0709392d9d
8 changed files with 54 additions and 15 deletions

View File

@@ -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 筆商品、當前價、昨日價與週前價,再沿用同一張新版表格與人工覆核按鈕;降低核心比價覆核頁的全站資料負載。

View File

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

View File

@@ -2,7 +2,7 @@
> **最後更新**: 2026-05-24 (台北時間)
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地LLM 路由紅線升級為 Ollama-first 三主機級聯Gemini 備援預設關閉
> **適用版本**: V10.447
> **適用版本**: V10.448
---

View File

@@ -13,6 +13,7 @@
## 📅 詳細更新日誌 (考古存檔)
### 2026-05-24PChome 近門檻身份回收第二輪
- **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 候選說明與人工覆核按鈕,降低核心比價覆核頁的全站資料負載。

View File

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

View File

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

View File

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

View File

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