This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.593 重整 PChome 比價覆核工作台 UX:覆核頁不再沿用首頁商品表格,也不再把 `matcher_rescore`、`stored_status`、`rescore_accepted_current`、`HITL`、`COMPLETE` 等內部診斷/狀態碼輸出到前台;改為「商品 / MOMO、PChome 候選、覆核判讀、下一步、紀錄」六欄工作流。同步修正 catalog review status 的前台語義、決策信封中文標籤、局部 1540px 橫向工作台與手機版欄位 label,並補 `test_frontend_v2_assets.py` guard 防止 raw diagnostics 回流。
|
||||
- V10.584 補 PChome Nick 去重與 stale recovery 單品窄門:`Nick` 先去 HTML / 行銷星號 / 重複品名,避免 `29g`、`100ml` 被同一商品副標重複計數成 `component_count_conflict`;同步新增 NIVEA 妮維雅霜 100ml、Schick 舒綺敏感肌除毛刀片 3 入、TS6 沁涼潔淨慕斯 100g 的具名 exact total-price alignment。IBL 沐浴精+洗髮精 vs 洗髮精仍保留 identity review,唇釉色號/目錄款與 Paula's Choice 效期/金蓋差異仍不自動寫正式價差。
|
||||
- V10.583 補 Paula's Choice 身體乳 PChome Nick 具名 alignment:`2%水楊酸身體乳210ml二入` 可和 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 對齊,進 `exact / total_price / price_alert_exact`;但 `118ml二入組(金蓋限定版)` 對上 PChome 效期品仍保留 `manual_review / identity_review`,不泛用放寬中文入數。
|
||||
- V10.582 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 價格決策信封現在保留 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,EventRouter / Telegram 模板會把 `match_type / price_basis / alert_tier` 翻成「直接價格威脅、單位價覆核、身份覆核、壓制告警」與操作邊界;PChome crawler 會保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍維持原品名,讓容量、入數、濃度資訊可參與比對。
|
||||
|
||||
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.591"
|
||||
SYSTEM_VERSION = "V10.593"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ def _build_pchome_match_status(attempt=None, ineligible=None):
|
||||
'tone': 'watch',
|
||||
'score': score,
|
||||
'summary': f'{score_text},最新版 matcher 已通過重算覆核門檻;仍需人工確認身份後才可寫入正式 PChome 價差',
|
||||
'detail': attempt.get('error_message')
|
||||
'detail': score_text,
|
||||
}
|
||||
if status == 'manual_accepted':
|
||||
score = _to_float(attempt.get('best_match_score'))
|
||||
@@ -264,6 +264,32 @@ def _build_pchome_match_status(attempt=None, ineligible=None):
|
||||
'summary': '候選同核心商品,但販售組合/買送不同;不可直接比總價,需用單位價或人工覆核',
|
||||
'detail': score_text,
|
||||
}
|
||||
if status in {'catalog_variant_review', 'catalog_unit_review', 'catalog_identity_review'}:
|
||||
score = _to_float(attempt.get('best_match_score'))
|
||||
score_text = f"候選信心 {round(score * 100)}%" if score is not None else "候選信心待補"
|
||||
candidate_count = int(attempt.get('candidate_count') or 0)
|
||||
catalog_labels = {
|
||||
'catalog_variant_review': (
|
||||
'選項 / 色號待核',
|
||||
'同品線候選已找到,但色號、香味、款式或任選組合仍需人工確認,避免錯配成正式價差',
|
||||
),
|
||||
'catalog_unit_review': (
|
||||
'單位價 / 入數待核',
|
||||
'同核心商品可比,但容量、入數或買送組合不同;需先換算單位價與檔期條件',
|
||||
),
|
||||
'catalog_identity_review': (
|
||||
'身份採用待核',
|
||||
'候選具備高信心身份證據,需人工最後確認後才寫入正式 PChome 價差',
|
||||
),
|
||||
}
|
||||
label, summary = catalog_labels[status]
|
||||
return {
|
||||
'label': label,
|
||||
'tone': 'watch',
|
||||
'blocks_price_gap': True,
|
||||
'summary': summary,
|
||||
'detail': f'{score_text} / {candidate_count} 筆候選' if candidate_count else score_text,
|
||||
}
|
||||
|
||||
if ineligible:
|
||||
reason = ineligible.get('reason') or 'not_eligible'
|
||||
@@ -351,13 +377,13 @@ def _build_pchome_match_status(attempt=None, ineligible=None):
|
||||
return {
|
||||
'label': '抓取異常',
|
||||
'tone': 'risk',
|
||||
'summary': 'PChome 比對流程發生錯誤,請查看嘗試紀錄',
|
||||
'detail': attempt.get('error_message'),
|
||||
'summary': 'PChome 比對流程發生異常,請補抓或查看後台嘗試紀錄',
|
||||
'detail': f'{candidate_count} 筆候選' if candidate_count else '流程異常',
|
||||
}
|
||||
return {
|
||||
'label': '狀態待釐清',
|
||||
'tone': 'neutral',
|
||||
'summary': '已有比對紀錄但尚未分類,需檢查 matcher diagnostics 或補抓紀錄',
|
||||
'summary': '已有比對紀錄但尚未分類,需檢查補抓紀錄或重新排入覆核',
|
||||
'detail': score_text,
|
||||
}
|
||||
|
||||
|
||||
@@ -242,18 +242,19 @@
|
||||
<span>候選 PChome ${{ item.candidate_pc_price | int | number_format }}</span>
|
||||
{% endif %}
|
||||
{% if item.best_match_score %}
|
||||
<span>match {{ (item.best_match_score * 100) | round(0) | int }}%</span>
|
||||
<span>信心 {{ (item.best_match_score * 100) | round(0) | int }}%</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="dashboard-focus-sub">{{ item.action_label }}</div>
|
||||
{% if item.decision_envelope %}
|
||||
{% set envelope = item.decision_envelope %}
|
||||
{% set guardrails = envelope.guardrails or {} %}
|
||||
{% set data_quality = guardrails.data_quality | default('partial', true) | lower %}
|
||||
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
|
||||
<span>{{ envelope.severity or 'P4' }}</span>
|
||||
<span>{{ guardrails.data_quality or 'partial' }}</span>
|
||||
<span>優先 {{ envelope.severity or 'P4' }}</span>
|
||||
<span>{{ {'complete': '證據完整', 'partial': '部分證據', 'missing': '證據不足'}.get(data_quality, '部分證據') }}</span>
|
||||
{% if guardrails.can_auto_execute == false %}
|
||||
<span>HITL</span>
|
||||
<span>需人工</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -452,33 +453,42 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="dashboard-table-wrap">
|
||||
<div class="dashboard-table-wrap {% if current_filter == 'pchome_review' %}is-review-wrap{% endif %}">
|
||||
<table class="dashboard-table {% if current_filter == 'ai_picks' %}is-ai-picks{% elif current_filter == 'pchome_review' %}is-review{% endif %}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>分類</th>
|
||||
<th>商品名稱</th>
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='price', order='asc' if current_sort == 'price' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">MOMO 價格</a>
|
||||
</th>
|
||||
<th class="text-end">PChome 價格</th>
|
||||
<th>競價判讀</th>
|
||||
{% if current_filter == 'ai_picks' %}
|
||||
<th>AI 建議</th>
|
||||
{% elif current_filter == 'pchome_review' %}
|
||||
<th>覆核動作</th>
|
||||
{% endif %}
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='yesterday_change', order='asc' if current_sort == 'yesterday_change' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">昨日漲跌</a>
|
||||
</th>
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='week_change', order='asc' if current_sort == 'week_change' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">週漲跌</a>
|
||||
</th>
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='timestamp', order='asc' if current_sort == 'timestamp' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">更新時間</a>
|
||||
</th>
|
||||
<th class="text-end">上架時間</th>
|
||||
</tr>
|
||||
{% if current_filter == 'pchome_review' %}
|
||||
<tr>
|
||||
<th>分類</th>
|
||||
<th>商品 / MOMO</th>
|
||||
<th>PChome 候選</th>
|
||||
<th>覆核判讀</th>
|
||||
<th>下一步</th>
|
||||
<th class="text-end">紀錄</th>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<th>分類</th>
|
||||
<th>商品名稱</th>
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='price', order='asc' if current_sort == 'price' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">MOMO 價格</a>
|
||||
</th>
|
||||
<th class="text-end">PChome 價格</th>
|
||||
<th>競價判讀</th>
|
||||
{% if current_filter == 'ai_picks' %}
|
||||
<th>AI 建議</th>
|
||||
{% endif %}
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='yesterday_change', order='asc' if current_sort == 'yesterday_change' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">昨日漲跌</a>
|
||||
</th>
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='week_change', order='asc' if current_sort == 'week_change' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">週漲跌</a>
|
||||
</th>
|
||||
<th class="text-end">
|
||||
<a href="{{ url_for('dashboard.index', page=1, sort_by='timestamp', order='asc' if current_sort == 'timestamp' and current_order == 'desc' else 'desc', category=current_category, filter=current_filter, q=search_query) }}">更新時間</a>
|
||||
</th>
|
||||
<th class="text-end">上架時間</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
@@ -487,6 +497,166 @@
|
||||
{% set decision = item.competitor_decision %}
|
||||
{% set match_status = item.pchome_match_status %}
|
||||
{% set image_url = product.image_url or ('https://m.momoshop.com.tw/moscdn/goods/' ~ product.i_code ~ '_m.webp') %}
|
||||
{% set safe_product_url = item.safe_momo_url or '#' %}
|
||||
{% if current_filter == 'pchome_review' %}
|
||||
{% set review = item.pchome_review %}
|
||||
{% set envelope = review.decision_envelope if review else {} %}
|
||||
{% set guardrails = envelope.guardrails if envelope else {} %}
|
||||
<tr class="dashboard-review-workbench-row" data-product-id="{{ product.id }}" data-product-name="{{ product.name|e }}">
|
||||
<td><span class="dashboard-category">{{ product.category or '未分類' }}</span></td>
|
||||
<td>
|
||||
<div class="dashboard-review-product-stack">
|
||||
<div class="dashboard-product-cell">
|
||||
<img class="dashboard-product-thumb" src="{{ image_url }}" alt="{{ product.name }}" loading="lazy" referrerpolicy="no-referrer">
|
||||
<div>
|
||||
<a class="dashboard-product-name momo-tracked-link" href="{{ safe_product_url or '#' }}" target="_blank" rel="noopener noreferrer"
|
||||
data-momo-original-url="{{ safe_product_url or '#' }}"
|
||||
data-track-platform="momo"
|
||||
data-track-source="dashboard-v2-pchome-review"
|
||||
data-track-product-id="{{ product.id }}"
|
||||
data-track-icode="{{ product.i_code }}"
|
||||
data-track-product-name="{{ product.name|e }}">{{ product.name }}</a>
|
||||
<div class="dashboard-review-product-meta">
|
||||
<span>MOMO {{ product.i_code }}</span>
|
||||
<span>${{ item.record.price | int | number_format }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dashboard-review-candidate">
|
||||
{% if review and review.candidate_pc_id %}
|
||||
<a class="dashboard-review-candidate-title" href="https://24h.pchome.com.tw/prod/{{ review.candidate_pc_id }}" target="_blank" rel="noopener noreferrer">
|
||||
{{ review.candidate_pc_name or ('PChome ' ~ review.candidate_pc_id) }}
|
||||
</a>
|
||||
<div class="dashboard-review-candidate-meta">
|
||||
{% if review.candidate_pc_price %}
|
||||
<span>${{ review.candidate_pc_price | int | number_format }}</span>
|
||||
{% endif %}
|
||||
<span>PChome {{ review.candidate_pc_id }}</span>
|
||||
{% if review.candidate_count %}
|
||||
<span>{{ review.candidate_count }} 筆候選</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif competitor and competitor.product_id %}
|
||||
<a class="dashboard-review-candidate-title" href="{{ competitor.product_url or ('https://24h.pchome.com.tw/prod/' ~ competitor.product_id) }}" target="_blank" rel="noopener noreferrer">
|
||||
{{ competitor.product_name or ('PChome ' ~ competitor.product_id) }}
|
||||
</a>
|
||||
<div class="dashboard-review-candidate-meta">
|
||||
{% if competitor.price %}
|
||||
<span>${{ competitor.price | int | number_format }}</span>
|
||||
{% endif %}
|
||||
<span>PChome {{ competitor.product_id }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="dashboard-match-state is-{{ match_status.tone | default('neutral') }}">{{ match_status.label | default('尚未搜尋') }}</span>
|
||||
<div class="dashboard-review-note">{{ match_status.summary | default('尚未進入 PChome 補抓佇列') }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dashboard-review-decision">
|
||||
<div class="dashboard-review-decision-head">
|
||||
<span class="dashboard-ai-pick-rank">{{ review.status_label if review else match_status.label }}</span>
|
||||
{% if review and review.best_match_score %}
|
||||
<span class="dashboard-ai-pick-confidence is-medium">信心 {{ (review.best_match_score * 100) | round(0) | int }}%</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="dashboard-ai-pick-reason">{{ review.action_label if review else match_status.summary }}</div>
|
||||
{% if review and review.identity_evidence_summary %}
|
||||
<div class="dashboard-review-note">證據:{{ review.identity_evidence_summary }}</div>
|
||||
{% endif %}
|
||||
{% if review and review.diagnostic_reasons %}
|
||||
<div class="dashboard-review-reasons" aria-label="比對差異與證據">
|
||||
{% for reason in review.diagnostic_reasons[:4] %}
|
||||
<span>{{ reason.label }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if review and review.unit_comparison and review.unit_comparison.summary %}
|
||||
<div class="dashboard-review-note momo-mono">{{ review.unit_comparison.summary }}</div>
|
||||
{% endif %}
|
||||
{% if review and review.unit_price_insight and review.unit_price_insight.summary %}
|
||||
<div class="dashboard-review-note">{{ review.unit_price_insight.summary }}</div>
|
||||
{% endif %}
|
||||
{% if envelope %}
|
||||
{% set data_quality = guardrails.data_quality | default('partial', true) | lower %}
|
||||
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
|
||||
<span>優先 {{ envelope.severity or 'P4' }}</span>
|
||||
<span>{{ {'complete': '證據完整', 'partial': '部分證據', 'missing': '證據不足'}.get(data_quality, '部分證據') }}</span>
|
||||
{% if guardrails.can_auto_execute == false %}
|
||||
<span>需人工</span>
|
||||
{% endif %}
|
||||
{% if envelope.decision_id %}
|
||||
<span title="{{ envelope.decision_id }}">追蹤</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dashboard-review-next-step">
|
||||
{% if review and review.catalog_review_guidance %}
|
||||
<div class="dashboard-review-next-title">{{ review.catalog_review_guidance.lane_label }}</div>
|
||||
<div class="dashboard-review-note">{{ review.catalog_review_guidance.action_hint }}</div>
|
||||
{% else %}
|
||||
<div class="dashboard-review-next-title">人工覆核</div>
|
||||
<div class="dashboard-review-note">{{ match_status.summary | default('確認候選是否同款,再決定是否採用。') }}</div>
|
||||
{% endif %}
|
||||
{% if review %}
|
||||
<div class="dashboard-review-actions" aria-label="人工覆核決策">
|
||||
{% if review.candidate_pc_id and review.candidate_pc_price %}
|
||||
<button class="dashboard-review-action is-accept" type="button"
|
||||
data-pchome-review-action
|
||||
data-review-action="accept_identity"
|
||||
data-review-sku="{{ review.sku }}"
|
||||
data-review-confirm="確認採用這筆 PChome 候選為正式同款配對?">
|
||||
採用同款
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="dashboard-review-action" type="button"
|
||||
data-pchome-review-action
|
||||
data-review-action="reject_identity"
|
||||
data-review-sku="{{ review.sku }}"
|
||||
data-review-confirm="確認否決這筆 PChome 候選?">
|
||||
否決候選
|
||||
</button>
|
||||
<button class="dashboard-review-action" type="button"
|
||||
data-pchome-review-action
|
||||
data-review-action="unit_price_required"
|
||||
data-review-sku="{{ review.sku }}"
|
||||
data-review-confirm="確認標記為需單位價比較?">
|
||||
標記單位價
|
||||
</button>
|
||||
<button class="dashboard-review-action is-research" type="button"
|
||||
data-pchome-review-action
|
||||
data-review-action="needs_research"
|
||||
data-review-sku="{{ review.sku }}"
|
||||
data-review-confirm="確認要求補搜尋詞或重新抓取?">
|
||||
補搜尋
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="dashboard-review-record text-end">
|
||||
{% if review and review.attempted_at %}
|
||||
<div class="momo-mono">{{ review.attempted_at }}</div>
|
||||
{% else %}
|
||||
<div class="momo-mono">{{ item.record.timestamp.strftime('%m-%d %H:%M') if item.record.timestamp else '--' }}</div>
|
||||
{% endif %}
|
||||
<div class="dashboard-review-record-tags">
|
||||
{% if review %}
|
||||
<span>覆核紀錄</span>
|
||||
{% endif %}
|
||||
{% if guardrails and guardrails.can_auto_execute == false %}
|
||||
<span>需人工</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="is-history-enabled" data-product-id="{{ product.id }}" data-product-name="{{ product.name|e }}" title="點擊查看歷史價格圖表">
|
||||
<td><span class="dashboard-category">{{ product.category or '未分類' }}</span></td>
|
||||
<td>
|
||||
@@ -565,7 +735,7 @@
|
||||
{% if competitor and competitor.price %}
|
||||
<div class="dashboard-pchome-price">${{ competitor.price | int | number_format }}</div>
|
||||
{% if competitor.match_score %}
|
||||
<div class="dashboard-price-sub">match {{ (competitor.match_score * 100) | round(0) | int }}%</div>
|
||||
<div class="dashboard-price-sub">信心 {{ (competitor.match_score * 100) | round(0) | int }}%</div>
|
||||
{% endif %}
|
||||
{% elif item.pchome_match_attempt and item.pchome_match_attempt.best_competitor_price and match_status.label == '需單位價比較' %}
|
||||
<div class="dashboard-pchome-price">${{ item.pchome_match_attempt.best_competitor_price | int | number_format }}</div>
|
||||
@@ -632,19 +802,20 @@
|
||||
<div class="dashboard-ai-pick-head">
|
||||
<span class="dashboard-ai-pick-rank">{{ review.status_label if review else match_status.label }}</span>
|
||||
{% if review and review.best_match_score %}
|
||||
<span class="dashboard-ai-pick-confidence is-medium">match {{ (review.best_match_score * 100) | round(0) | int }}%</span>
|
||||
<span class="dashboard-ai-pick-confidence is-medium">信心 {{ (review.best_match_score * 100) | round(0) | int }}%</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="dashboard-ai-pick-reason">{{ review.action_label if review else decision.summary }}</div>
|
||||
{% if envelope %}
|
||||
{% set data_quality = guardrails.data_quality | default('partial', true) | lower %}
|
||||
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
|
||||
<span>{{ envelope.severity or 'P4' }}</span>
|
||||
<span>{{ guardrails.data_quality or 'partial' }}</span>
|
||||
<span>優先 {{ envelope.severity or 'P4' }}</span>
|
||||
<span>{{ {'complete': '證據完整', 'partial': '部分證據', 'missing': '證據不足'}.get(data_quality, '部分證據') }}</span>
|
||||
{% if guardrails.can_auto_execute == false %}
|
||||
<span>HITL</span>
|
||||
<span>需人工</span>
|
||||
{% endif %}
|
||||
{% if envelope.decision_id %}
|
||||
<span title="{{ envelope.decision_id }}">trace</span>
|
||||
<span title="{{ envelope.decision_id }}">追蹤</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -750,9 +921,10 @@
|
||||
{{ item.safe_created_at.strftime('%m-%d %H:%M') if item.safe_created_at else '--' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="{{ 10 if current_filter in ['ai_picks', 'pchome_review'] else 9 }}">
|
||||
<td colspan="{{ 6 if current_filter == 'pchome_review' else (10 if current_filter == 'ai_picks' else 9) }}">
|
||||
<div class="dashboard-empty">
|
||||
{% if search_query %}
|
||||
找不到與「{{ search_query }}」相關的商品
|
||||
|
||||
@@ -228,7 +228,7 @@ def test_dashboard_v2_is_production_default_and_uses_real_dashboard_data():
|
||||
assert "人工閉環" in route_source
|
||||
assert "AI 挑品清單" in dashboard
|
||||
assert "比價覆核隊列" in dashboard
|
||||
assert "覆核動作" in dashboard
|
||||
assert "下一步" in dashboard
|
||||
assert "dashboard-ai-summary-grid" in dashboard
|
||||
assert "AI 建議" in dashboard
|
||||
assert "/api/export/excel/ai-picks" in dashboard
|
||||
@@ -265,6 +265,7 @@ def test_pchome_review_export_and_diagnostics_use_real_queue_data():
|
||||
repository_source = (ROOT / "services/competitor_intel_repository.py").read_text(encoding="utf-8")
|
||||
dashboard = (ROOT / "templates/dashboard_v2.html").read_text(encoding="utf-8")
|
||||
dashboard_css = (ROOT / "web/static/css/page-dashboard-v2.css").read_text(encoding="utf-8")
|
||||
page_guard_css = (ROOT / "web/static/css/ewoooc-v3-page-guard.css").read_text(encoding="utf-8")
|
||||
|
||||
assert "@export_bp.route('/api/export/excel/pchome-review')" in export_source
|
||||
assert "fetch_competitor_review_queue_page" in export_source
|
||||
@@ -284,13 +285,40 @@ def test_pchome_review_export_and_diagnostics_use_real_queue_data():
|
||||
assert "多款任選待確認" in repository_source
|
||||
assert "妝效質地不同" in route_source
|
||||
assert "_extract_match_diagnostic_reasons" in route_source
|
||||
assert "'detail': attempt.get('error_message')" not in route_source
|
||||
assert "需檢查 matcher diagnostics" not in route_source
|
||||
assert "匯出覆核" in dashboard
|
||||
assert "商品 / MOMO" in dashboard
|
||||
assert "PChome 候選" in dashboard
|
||||
assert "覆核判讀" in dashboard
|
||||
assert "dashboard-review-workbench-row" in dashboard
|
||||
assert "dashboard-review-candidate-title" in dashboard
|
||||
assert "dashboard-review-next-step" in dashboard
|
||||
assert "is-review-wrap" in dashboard
|
||||
assert "6 if current_filter == 'pchome_review'" in dashboard
|
||||
assert "review.review_bucket" not in dashboard
|
||||
assert "HITL" not in dashboard
|
||||
assert "stored_status" not in dashboard
|
||||
assert "stored_score" not in dashboard
|
||||
assert "matcher_rescore" not in dashboard
|
||||
assert "優先 {{ envelope.severity" in dashboard
|
||||
assert "證據完整" in dashboard
|
||||
assert "需人工" in dashboard
|
||||
assert "review.decision_envelope" in dashboard
|
||||
assert "dashboard-review-envelope" in dashboard
|
||||
assert "review.diagnostic_reasons" in dashboard
|
||||
assert "item.pchome_match_attempt.diagnostic_reasons" in dashboard
|
||||
assert "dashboard-review-reasons" in dashboard
|
||||
assert "dashboard-review-actions" in dashboard
|
||||
assert ".dashboard-review-workbench-row" in dashboard_css
|
||||
assert ".dashboard-review-candidate-title" in dashboard_css
|
||||
assert ".dashboard-review-next-step" in dashboard_css
|
||||
assert ".dashboard-table.is-review" in dashboard_css
|
||||
assert "min-width: 1540px" in dashboard_css
|
||||
assert ".dashboard-table-wrap.is-review-wrap" in page_guard_css
|
||||
assert "width: max(100%, 1540px) !important" in page_guard_css
|
||||
assert 'content: "商品/MOMO"' in page_guard_css
|
||||
assert 'content: "PChome候選"' in page_guard_css
|
||||
assert ".dashboard-review-reasons" in dashboard_css
|
||||
assert ".dashboard-review-envelope" in dashboard_css
|
||||
assert ".dashboard-review-actions" in dashboard_css
|
||||
|
||||
@@ -288,6 +288,17 @@
|
||||
width: 36%;
|
||||
}
|
||||
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table-wrap.is-review-wrap {
|
||||
overflow-x: auto !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review {
|
||||
width: max(100%, 1540px) !important;
|
||||
min-width: 1540px !important;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.momo-app:not(.momo-observability-mode) {
|
||||
--momo-page-title-size: 1.55rem;
|
||||
@@ -449,11 +460,11 @@
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-ai-picks td:nth-child(8)::before { content: "週"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-ai-picks td:nth-child(9)::before { content: "更新"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-ai-picks td:nth-child(10)::before { content: "上架"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(6)::before { content: "覆核"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(7)::before { content: "昨日"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(8)::before { content: "週"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(9)::before { content: "更新"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(10)::before { content: "上架"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(2)::before { content: "商品/MOMO"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(3)::before { content: "PChome候選"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(4)::before { content: "覆核判讀"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(5)::before { content: "下一步"; }
|
||||
.momo-app:not(.momo-observability-mode) .dashboard-table.is-review td:nth-child(6)::before { content: "紀錄"; }
|
||||
|
||||
.momo-app:not(.momo-observability-mode) .edm-page .campaign-table td:nth-child(1)::before { content: "分類"; }
|
||||
.momo-app:not(.momo-observability-mode) .edm-page .campaign-table td:nth-child(2)::before { content: "商品"; }
|
||||
|
||||
@@ -786,7 +786,32 @@
|
||||
}
|
||||
|
||||
.dashboard-table.is-review {
|
||||
min-width: 1460px;
|
||||
min-width: 1540px;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th:nth-child(1) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th:nth-child(2) {
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th:nth-child(3) {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th:nth-child(4) {
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th:nth-child(5) {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th:nth-child(6) {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.dashboard-table th {
|
||||
@@ -813,6 +838,17 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
box-shadow: 0 1px 0 var(--momo-border-light);
|
||||
}
|
||||
|
||||
.dashboard-table.is-review td {
|
||||
padding: 16px 14px;
|
||||
}
|
||||
|
||||
.dashboard-table tbody tr {
|
||||
transition: var(--momo-transition-base);
|
||||
}
|
||||
@@ -1115,6 +1151,111 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dashboard-review-workbench-row td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review .dashboard-product-cell {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review .dashboard-product-thumb {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.dashboard-table.is-review .dashboard-product-name {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
.dashboard-review-workbench-row:hover {
|
||||
background:
|
||||
radial-gradient(circle at 8px 8px, rgba(47, 43, 38, 0.06) 1px, transparent 1.4px),
|
||||
var(--momo-bg-paper);
|
||||
background-size: 16px 16px, auto;
|
||||
}
|
||||
|
||||
.dashboard-review-product-stack,
|
||||
.dashboard-review-candidate,
|
||||
.dashboard-review-decision,
|
||||
.dashboard-review-next-step,
|
||||
.dashboard-review-record {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-review-product-meta,
|
||||
.dashboard-review-candidate-meta,
|
||||
.dashboard-review-record-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
color: var(--momo-text-tertiary);
|
||||
font-family: var(--momo-font-family-mono);
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.dashboard-review-product-meta span,
|
||||
.dashboard-review-candidate-meta span,
|
||||
.dashboard-review-record-tags span {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
padding: 2px 7px;
|
||||
overflow: hidden;
|
||||
background: var(--momo-bg-paper);
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: var(--momo-radius-pill);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dashboard-review-candidate-title {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
text-decoration: none;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
.dashboard-review-candidate-title:hover {
|
||||
color: var(--momo-accent-strong);
|
||||
}
|
||||
|
||||
.dashboard-review-decision-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-review-next-title {
|
||||
color: var(--momo-text-primary);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.dashboard-review-record {
|
||||
justify-items: end;
|
||||
color: var(--momo-text-secondary);
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.dashboard-review-record-tags {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dashboard-review-reasons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -1506,7 +1647,7 @@
|
||||
}
|
||||
|
||||
.dashboard-table-wrap::before {
|
||||
content: '左右滑動查看完整商品列表';
|
||||
content: '左右滑動查看完整表格';
|
||||
position: sticky;
|
||||
left: 0;
|
||||
display: block;
|
||||
|
||||
Reference in New Issue
Block a user