Files
ewoooc/templates/dashboard_v2.html
OoO b2604a576d
All checks were successful
CD Pipeline / deploy (push) Successful in 1m23s
V10.575 拆分型錄可比覆核 lane
2026-06-04 11:13:35 +08:00

782 lines
59 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'ewoooc_base.html' %}
{% block title %}EwoooC 商品看板{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-dashboard-v2.css') }}">
{% endblock %}
{% block ewooo_content %}
<div class="dashboard-v2-stack">
{% set overview = competitor_overview | default({}) %}
<section>
<div class="dashboard-section-label">
<span class="num momo-mono">01</span>
<span class="title">比價監控總覽</span>
<span class="meta momo-mono">KPI · 最新有效價格 {{ overview.last_pchome_crawled or '待刷新' }}</span>
</div>
<div class="dashboard-kpi-grid">
<div class="dashboard-kpi">
<div class="dashboard-kpi-label momo-mono">決策支援覆蓋率</div>
<div class="dashboard-kpi-value momo-mono">{{ overview.decision_support_rate | default(overview.decision_ready_rate | default(0)) }}%</div>
<div class="dashboard-kpi-sub momo-mono">
{{ overview.decision_support_count | default(overview.decision_ready_count | default(0)) | number_format }} / {{ overview.total_active | default(total_products) | number_format }} ACTIVE
· 精準可用 {{ overview.decision_ready_rate | default(0) }}%
· 型錄可比 {{ overview.catalog_comparable_count | default(0) | number_format }}
· 單位價 {{ overview.unit_comparable_count | default(0) | number_format }}
· 身份 {{ overview.identity_coverage_rate | default(overview.match_rate | default(0)) }}%
· 過期 {{ overview.stale_match_count | default(0) | number_format }}
· 未設到期 {{ overview.unknown_freshness_count | default(0) | number_format }}
</div>
</div>
<div class="dashboard-kpi is-accent">
<div class="dashboard-kpi-label momo-mono">PChome 價格壓力</div>
<div class="dashboard-kpi-value momo-mono">{{ overview.pchome_advantage_count | default(0) | number_format }}</div>
<div class="dashboard-kpi-sub momo-mono">PChome 較低 · 平均價差 +{{ overview.avg_advantage_gap | default(0) }}%</div>
</div>
<div class="dashboard-kpi">
<div class="dashboard-kpi-label momo-mono">MOMO 價格優勢</div>
<div class="dashboard-kpi-value momo-mono is-success">{{ overview.momo_threat_count | default(0) | number_format }}</div>
<div class="dashboard-kpi-sub momo-mono">MOMO 價格低於 PChome</div>
</div>
<div class="dashboard-kpi">
<div class="dashboard-kpi-label momo-mono">AI 挑品</div>
<div class="dashboard-kpi-value momo-mono is-success">{{ overview.ai_pick_count | default(0) | number_format }}</div>
<div class="dashboard-kpi-sub momo-mono">
<a class="dashboard-kpi-sub-link" href="{{ url_for('dashboard.index', filter='ai_picks', category=current_category, q=search_query, sort_by='timestamp', order='desc') }}">查看 {{ ai_pick_list_limit }} 品清單</a>
</div>
</div>
<div class="dashboard-kpi">
<div class="dashboard-kpi-label momo-mono">比價覆核</div>
<div class="dashboard-kpi-value momo-mono is-warning">{{ overview.review_queue_count | default(0) | number_format }}</div>
<div class="dashboard-kpi-sub momo-mono">
<a class="dashboard-kpi-sub-link" href="{{ url_for('dashboard.index', filter='pchome_review', category=current_category, q=search_query, review_status='rescore_accepted', sort_by='pchome_review', order='desc') }}">重算待覆核 {{ overview.rescore_accepted_count | default(0) | number_format }}</a>
· <a class="dashboard-kpi-sub-link" href="{{ url_for('dashboard.index', filter='pchome_review', category=current_category, q=search_query, review_status='unit_comparable', sort_by='pchome_review', order='desc') }}">需單位價 {{ overview.unit_comparable_count | default(0) | number_format }}</a>
· 人工閉環 {{ overview.manual_closed_count | default(0) | number_format }}
· 待補抓 {{ overview.pending_match_count | default(0) | number_format }}
</div>
</div>
<div class="dashboard-kpi">
<div class="dashboard-kpi-label momo-mono">最新有效價格抓取</div>
<div class="dashboard-kpi-value momo-mono is-small">{{ overview.last_pchome_crawled or '待刷新' }}</div>
<div class="dashboard-kpi-sub momo-mono">
新鮮率 {{ overview.fresh_match_rate | default(0) }}%
· 待刷新 {{ overview.stale_match_count | default(0) | number_format }}
· 未設到期 {{ overview.unknown_freshness_count | default(0) | number_format }}
</div>
</div>
</div>
<div class="dashboard-backfill-card"
data-pchome-backfill-card
data-backfill-endpoint="/api/ai/pchome-match/backfill"
data-refresh-stale-endpoint="/api/ai/pchome-match/refresh-stale"
data-status-endpoint="/api/ai/pchome-match/backfill/status"
data-pchome-backfill-action="backfillPchomeMatches">
<div class="dashboard-backfill-main">
<div class="dashboard-backfill-label momo-mono">PCHOME MATCH BACKFILL</div>
<div class="dashboard-backfill-title">PChome 比價補強產線</div>
<div class="dashboard-backfill-meta momo-mono">
待刷新 {{ overview.stale_match_count | default(0) | number_format }} · 未設到期 {{ overview.unknown_freshness_count | default(0) | number_format }} · 待補抓 {{ overview.pending_match_count | default(0) | number_format }} · 待處理覆核 {{ overview.review_queue_count | default(0) | number_format }} · 人工閉環 {{ overview.manual_closed_count | default(0) | number_format }} · 單位價 {{ overview.unit_comparable_count | default(0) | number_format }}
</div>
</div>
<div class="dashboard-backfill-progress" aria-hidden="true">
<span data-pchome-backfill-progress></span>
</div>
<div class="dashboard-backfill-status momo-mono">
<span data-pchome-backfill-status>讀取狀態中</span>
<span data-pchome-backfill-result>--</span>
</div>
<div class="dashboard-backfill-actions">
<button class="dashboard-action-button"
type="button"
data-pchome-refresh-stale-trigger
data-limit="120">
<i class="fas fa-rotate"></i> 刷新過期 120 筆
</button>
<button class="dashboard-action-button is-primary"
type="button"
data-pchome-backfill-trigger
data-limit="60">
<i class="fas fa-magnifying-glass-chart"></i> 補強 60 筆
</button>
</div>
</div>
</section>
<section>
<div class="dashboard-section-label">
<span class="num momo-mono">02</span>
<span class="title">比價決策焦點</span>
<span class="meta momo-mono">{{ today_date }}</span>
</div>
<div class="dashboard-focus-grid">
<div class="dashboard-focus-card">
<div class="dashboard-focus-label momo-mono">今日優先銷售</div>
{% if overview.top_picks %}
<div class="dashboard-focus-list">
{% for pick in overview.top_picks %}
<div class="dashboard-focus-row">
<a class="dashboard-focus-row-title momo-tracked-link" href="{{ pick.momo_url or '#' }}" data-momo-original-url="{{ pick.momo_url or '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-top-picks"
data-track-product-id="{{ pick.sku }}"
data-track-icode="{{ pick.sku }}"
data-track-product-name="{{ pick.name|e }}">{{ pick.name }}</a>
<div class="dashboard-focus-row-meta momo-mono">
<span class="dashboard-focus-chip is-win">AI {{ (pick.confidence * 100) | round(0) | int if pick.confidence else 0 }}%</span>
<span>證據 {{ pick.evidence_quality | round(0) | int }}%</span>
<span>機會 {{ pick.opportunity_score | round(0) | int }}</span>
<span>MOMO ${{ pick.momo_price | int | number_format }}</span>
<span>PChome ${{ pick.pchome_price | int | number_format }}</span>
<span>+{{ pick.gap_pct | round(1) }}%</span>
</div>
{% if pick.missing_evidence %}
<div class="dashboard-ai-evidence-line">
{% for evidence in pick.missing_evidence[:2] %}
<span class="dashboard-ai-evidence-chip">{{ evidence }}</span>
{% endfor %}
</div>
{% endif %}
<div class="dashboard-focus-row-links">
<a class="dashboard-platform-link is-momo momo-tracked-link" href="{{ pick.momo_url or '#' }}" data-momo-original-url="{{ pick.momo_url or '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-top-picks"
data-track-product-id="{{ pick.sku }}"
data-track-icode="{{ pick.sku }}"
data-track-product-name="{{ pick.name|e }}">MOMO {{ pick.sku }}</a>
{% if pick.pchome_url %}
<a class="dashboard-platform-link is-pchome" href="{{ pick.pchome_url }}" target="_blank" rel="noopener noreferrer">PChome {{ pick.pchome_id }}</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="dashboard-focus-title">尚無 AI 挑品</div>
<div class="dashboard-focus-sub momo-mono">請先讓 PChome 比對與挑品 Agent 累積資料</div>
{% endif %}
</div>
<div class="dashboard-focus-card">
<div class="dashboard-focus-label momo-mono">價格威脅</div>
{% if overview.top_momo_threats %}
<div class="dashboard-focus-list">
{% for item in overview.top_momo_threats %}
<div class="dashboard-focus-row">
<a class="dashboard-focus-row-title momo-tracked-link" href="{{ item.momo_url or '#' }}" data-momo-original-url="{{ item.momo_url or '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-top-momo-threats"
data-track-product-id="{{ item.sku }}"
data-track-icode="{{ item.sku }}"
data-track-product-name="{{ item.name|e }}">{{ item.name }}</a>
<div class="dashboard-focus-row-meta momo-mono">
<span class="dashboard-focus-chip is-risk">{{ item.gap_pct | round(1) }}%</span>
<span>MOMO ${{ item.momo_price | int | number_format }}</span>
<span>PChome ${{ item.pchome_price | int | number_format }}</span>
</div>
<div class="dashboard-focus-row-links">
<a class="dashboard-platform-link is-momo momo-tracked-link" href="{{ item.momo_url or '#' }}" data-momo-original-url="{{ item.momo_url or '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-top-momo-threats"
data-track-product-id="{{ item.sku }}"
data-track-icode="{{ item.sku }}"
data-track-product-name="{{ item.name|e }}">MOMO {{ item.sku }}</a>
{% if item.pchome_url %}
<a class="dashboard-platform-link is-pchome" href="{{ item.pchome_url }}" target="_blank" rel="noopener noreferrer">PChome {{ item.pchome_id }}</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="dashboard-focus-title">尚無明顯威脅</div>
<div class="dashboard-focus-sub momo-mono">目前沒有 MOMO 低於 PChome 5% 以上的配對商品</div>
{% endif %}
</div>
<div class="dashboard-focus-card">
<div class="dashboard-focus-label momo-mono">覆核與補資料</div>
{% if overview.review_queue %}
<div class="dashboard-focus-list">
{% for item in overview.review_queue %}
<div class="dashboard-focus-row">
<a class="dashboard-focus-row-title" href="{{ url_for('dashboard.index', filter='pchome_review', q=item.sku, sort_by='pchome_review', order='desc') }}">{{ item.name }}</a>
<div class="dashboard-focus-row-meta momo-mono">
<span class="dashboard-focus-chip is-review">{{ item.status_label }}</span>
<span>MOMO ${{ item.momo_price | int | number_format }}</span>
{% if item.candidate_pc_price %}
<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>
{% 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 {} %}
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
<span>{{ envelope.severity or 'P4' }}</span>
<span>{{ guardrails.data_quality or 'partial' }}</span>
{% if guardrails.can_auto_execute == false %}
<span>HITL</span>
{% endif %}
</div>
{% endif %}
{% if item.diagnostic_reasons %}
<div class="dashboard-review-reasons" aria-label="比對診斷原因">
{% for reason in item.diagnostic_reasons[:4] %}
<span>{{ reason.label }}</span>
{% endfor %}
</div>
{% endif %}
{% if item.unit_comparison and item.unit_comparison.summary %}
<div class="dashboard-review-note momo-mono">{{ item.unit_comparison.summary }}</div>
{% endif %}
{% if item.unit_price_insight and item.unit_price_insight.summary %}
<div class="dashboard-review-note">{{ item.unit_price_insight.summary }}</div>
{% endif %}
<div class="dashboard-focus-row-links">
<a class="dashboard-platform-link is-momo momo-tracked-link" href="{{ build_momo_product_url(item.sku) if build_momo_product_url is defined else '#' }}" data-momo-original-url="{{ build_momo_product_url(item.sku) if build_momo_product_url is defined else '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-review-queue"
data-track-product-id="{{ item.sku }}"
data-track-icode="{{ item.sku }}"
data-track-product-name="{{ item.name|e }}">MOMO {{ item.sku }}</a>
{% if item.candidate_pc_id %}
<a class="dashboard-platform-link is-pchome" href="https://24h.pchome.com.tw/prod/{{ item.candidate_pc_id }}" target="_blank" rel="noopener noreferrer">PChome {{ item.candidate_pc_id }}</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<a class="dashboard-focus-action" href="{{ url_for('dashboard.index', filter='pchome_review', category=current_category, q=search_query, sort_by='pchome_review', order='desc') }}">查看高優先覆核隊列</a>
{% elif overview.pending_priority %}
<div class="dashboard-focus-list">
{% for item in overview.pending_priority %}
<div class="dashboard-focus-row">
<a class="dashboard-focus-row-title momo-tracked-link" href="{{ item.momo_url or '#' }}" data-momo-original-url="{{ item.momo_url or '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-pending-priority"
data-track-product-id="{{ item.sku }}"
data-track-icode="{{ item.sku }}"
data-track-product-name="{{ item.name|e }}">{{ item.name }}</a>
<div class="dashboard-focus-row-meta momo-mono">
<span class="dashboard-focus-chip is-neutral">尚未搜尋</span>
<span>MOMO ${{ item.momo_price | int | number_format }}</span>
<span>{{ item.category or '未分類' }}</span>
</div>
<div class="dashboard-focus-row-links">
<a class="dashboard-platform-link is-momo momo-tracked-link" href="{{ item.momo_url or '#' }}" data-momo-original-url="{{ item.momo_url or '#' }}" target="_blank" rel="noopener noreferrer"
data-track-platform="momo"
data-track-source="dashboard-v2-overview-pending-priority"
data-track-product-id="{{ item.sku }}"
data-track-icode="{{ item.sku }}"
data-track-product-name="{{ item.name|e }}">MOMO {{ item.sku }}</a>
<span class="dashboard-platform-muted">尚未進入 PChome 補抓</span>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="dashboard-focus-title">覆核隊列已清空</div>
<div class="dashboard-focus-sub momo-mono">目前 ACTIVE 商品沒有高優先 PChome 覆核項目</div>
{% endif %}
</div>
</div>
</section>
<section>
<div class="dashboard-section-label">
<span class="num momo-mono">03</span>
<span class="title">篩選</span>
</div>
<div class="dashboard-filter-card">
<form class="dashboard-filter-form" method="GET" action="/">
<input class="dashboard-search" type="text" name="q" value="{{ search_query }}" placeholder="搜尋商品名稱或品號...">
<select class="dashboard-select" name="category" data-dashboard-auto-submit>
<option value="all">所有分類</option>
{% for cat in categories %}
<option value="{{ cat }}" {% if current_category == cat %}selected{% endif %}>{{ cat }}</option>
{% endfor %}
</select>
<input type="hidden" name="filter" value="{{ current_filter }}">
<input type="hidden" name="review_status" value="{{ current_review_status }}">
<input type="hidden" name="sort_by" value="{{ current_sort }}">
<input type="hidden" name="order" value="{{ current_order }}">
<button class="dashboard-action-button" type="submit">
<i class="fas fa-search"></i> 搜尋
</button>
<div class="dashboard-segmented">
<a class="{% if current_filter == 'all' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='all', category=current_category, q=search_query, sort_by=current_sort, order=current_order) }}">全部</a>
<a class="{% if current_filter == 'ai_picks' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='ai_picks', category=current_category, q=search_query, sort_by='timestamp', order='desc') }}">AI挑品</a>
<a class="{% if current_filter == 'pchome_review' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='pchome_review', review_status='all', category=current_category, q=search_query, sort_by='pchome_review', order='desc') }}">比價覆核</a>
<a class="{% if current_filter == 'new' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='new', category=current_category, q=search_query, sort_by=current_sort, order=current_order) }}">新上架</a>
<a class="{% if current_filter == 'increase' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='increase', category=current_category, q=search_query, sort_by=current_sort, order=current_order) }}">漲價</a>
<a class="{% if current_filter == 'decrease' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='decrease', category=current_category, q=search_query, sort_by=current_sort, order=current_order) }}">降價</a>
<a class="{% if current_filter == 'delisted' %}is-active{% endif %}" href="{{ url_for('dashboard.index', filter='delisted', category=current_category, q=search_query, sort_by=current_sort, order=current_order) }}">下架</a>
</div>
<button class="dashboard-action-button" type="button" data-dashboard-task="crawler">
<i class="fas fa-rotate"></i> 更新
</button>
<button class="dashboard-action-button is-primary" type="button" data-dashboard-task="notification">
<i class="fas fa-bell"></i> 發送通知
</button>
</form>
</div>
</section>
<section>
<div class="dashboard-table-card">
<div class="dashboard-table-head">
<span class="dashboard-section-index momo-mono">04</span>
<span class="dashboard-table-title">{{ 'AI 挑品清單' if current_filter == 'ai_picks' else ('比價覆核隊列' if current_filter == 'pchome_review' else '商品列表') }}</span>
<span class="dashboard-table-meta momo-mono">
{% if current_filter == 'ai_picks' %}
{{ total_items | number_format }} / {{ ai_pick_list_limit }} 品
{% elif current_filter == 'pchome_review' %}
{% if review_total_is_estimated %}約 {% endif %}{{ total_items | number_format }} / {{ overview.review_queue_count | default(0) | number_format }} 待處理
{% else %}
{{ total_items | number_format }} 筆
{% endif %}
</span>
<div class="momo-topbar-spacer"></div>
<a class="dashboard-action-link" href="/api/export/excel/all">
<i class="fas fa-download"></i> 匯出全部
</a>
<a class="dashboard-action-link" href="/api/export/excel/changes">
<i class="fas fa-arrow-trend-up"></i> 匯出漲跌
</a>
{% if current_filter == 'ai_picks' %}
<a class="dashboard-action-link" href="/api/export/excel/ai-picks">
<i class="fas fa-file-excel"></i> 匯出 AI 挑品
</a>
{% elif current_filter == 'pchome_review' %}
<a class="dashboard-action-link" href="{{ url_for('export.export_excel_pchome_review', review_status=current_review_status, category=current_category, q=search_query) }}">
<i class="fas fa-file-excel"></i> 匯出覆核
</a>
{% endif %}
</div>
{% if current_filter == 'ai_picks' and ai_pick_summary %}
<div class="dashboard-ai-summary-grid">
<div class="dashboard-ai-summary-item">
<div class="dashboard-ai-summary-label">PICK COUNT</div>
<div class="dashboard-ai-summary-value momo-mono">{{ ai_pick_summary.count | number_format }}</div>
<div class="dashboard-ai-summary-sub">目前清單上限 {{ ai_pick_list_limit }} 品</div>
</div>
<div class="dashboard-ai-summary-item">
<div class="dashboard-ai-summary-label">AVG CONFIDENCE</div>
<div class="dashboard-ai-summary-value momo-mono">{{ (ai_pick_summary.avg_confidence * 100) | round(0) | int }}%</div>
<div class="dashboard-ai-summary-sub">高信心 {{ ai_pick_summary.high_confidence_count | number_format }} 品</div>
</div>
<div class="dashboard-ai-summary-item">
<div class="dashboard-ai-summary-label">EVIDENCE</div>
<div class="dashboard-ai-summary-value momo-mono">{{ ai_pick_summary.avg_evidence_quality | round(0) | int }}%</div>
<div class="dashboard-ai-summary-sub">需補證據 {{ ai_pick_summary.needs_evidence_count | number_format }} 品</div>
</div>
<div class="dashboard-ai-summary-item">
<div class="dashboard-ai-summary-label">AVG GAP</div>
<div class="dashboard-ai-summary-value momo-mono">+{{ ai_pick_summary.avg_gap_pct | round(1) }}%</div>
<div class="dashboard-ai-summary-sub">PChome 相對 MOMO 價差</div>
</div>
<div class="dashboard-ai-summary-item">
<div class="dashboard-ai-summary-label">BEST GAP</div>
<div class="dashboard-ai-summary-value momo-mono">+{{ ai_pick_summary.max_gap_pct | round(1) }}%</div>
<div class="dashboard-ai-summary-sub">清單內最大價格優勢</div>
</div>
<div class="dashboard-ai-summary-item">
<div class="dashboard-ai-summary-label">EVIDENCE GAP</div>
<div class="dashboard-ai-summary-value momo-mono">
{% if ai_pick_summary.top_missing_evidence %}
{{ ai_pick_summary.top_missing_evidence[0].count | number_format }}
{% else %}
0
{% endif %}
</div>
<div class="dashboard-ai-summary-sub">
{% if ai_pick_summary.top_missing_evidence %}
{{ ai_pick_summary.top_missing_evidence[0].label }}
{% else %}
暫無待補證據
{% endif %}
</div>
</div>
</div>
{% endif %}
{% if current_filter == 'pchome_review' %}
<div class="dashboard-review-segments">
{% for option in review_status_options %}
<a class="{% if current_review_status == option.key %}is-active{% endif %}" href="{{ url_for('dashboard.index', page=1, filter='pchome_review', review_status=option.key, category=current_category, q=search_query, sort_by='pchome_review', order='desc') }}">
<span>{{ option.label }}</span>
<span class="momo-mono">{{ option.count | number_format }}</span>
</a>
{% endfor %}
</div>
{% endif %}
<div class="dashboard-table-wrap">
<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>
</thead>
<tbody>
{% for item in items %}
{% set product = item.record.product %}
{% set competitor = item.pchome_competitor %}
{% 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') %}
<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>
<div class="dashboard-product-cell">
<img class="dashboard-product-thumb" src="{{ image_url }}" alt="{{ product.name }}" loading="lazy" referrerpolicy="no-referrer">
{% set safe_product_url = item.safe_momo_url or '#' %}
<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-table-main"
data-track-product-id="{{ product.id }}"
data-track-icode="{{ product.i_code }}"
data-track-product-name="{{ product.name|e }}">{{ product.name }}</a>
<div>
<div class="dashboard-platform-links">
<a class="dashboard-platform-link is-momo 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-table-main"
data-track-product-id="{{ product.id }}"
data-track-icode="{{ product.i_code }}"
data-track-product-name="{{ product.name|e }}">
MOMO {{ product.i_code }}
</a>
{% if competitor and competitor.product_url %}
<a class="dashboard-platform-link is-pchome" href="{{ competitor.product_url }}" target="_blank" rel="noopener noreferrer">
PChome {{ competitor.product_id }}
</a>
{% elif competitor and competitor.product_id %}
<span class="dashboard-platform-muted">PChome {{ competitor.product_id }}</span>
{% else %}
<span class="dashboard-platform-status is-{{ match_status.tone | default('neutral') }}" title="{{ match_status.summary | default('尚未搜尋 PChome 同款') }}">
PChome {{ match_status.label | default('尚未搜尋') }}
</span>
{% endif %}
</div>
{% if competitor and competitor.product_name %}
<div class="dashboard-product-id momo-mono" title="{{ competitor.product_name }}">PChome{{ competitor.product_name }}</div>
{% elif item.pchome_match_attempt and item.pchome_match_attempt.best_competitor_product_name %}
<div class="dashboard-product-id momo-mono" title="{{ item.pchome_match_attempt.best_competitor_product_name }}">候選:{{ item.pchome_match_attempt.best_competitor_product_name }}</div>
{% if item.pchome_match_attempt.competitor_product_url %}
<a class="dashboard-product-id dashboard-candidate-link momo-mono" href="{{ item.pchome_match_attempt.competitor_product_url }}" target="_blank" rel="noopener noreferrer">
PChome 候選 {{ item.pchome_match_attempt.best_competitor_product_id }}
</a>
{% endif %}
{% if item.pchome_match_attempt.diagnostic_reasons %}
<div class="dashboard-review-reasons" aria-label="比對診斷原因">
{% for reason in item.pchome_match_attempt.diagnostic_reasons[:3] %}
<span>{{ reason.label }}</span>
{% endfor %}
</div>
{% endif %}
{% endif %}
{% if item.ai_pick %}
<div class="dashboard-product-id momo-mono" title="{{ item.ai_pick.reason }}">
AI挑品 #{{ item.ai_pick.rank }} · 信心 {{ (item.ai_pick.confidence * 100) | round(0) | int }}% · 證據 {{ item.ai_pick.evidence_quality | round(0) | int }}% · 價差 {{ item.ai_pick.gap_pct | round(1) }}%
</div>
{% endif %}
</div>
</div>
</td>
<td class="text-end">
<button
class="dashboard-history-button"
type="button"
data-history-trigger
data-product-id="{{ product.id }}"
data-product-name="{{ product.name|e }}"
aria-label="查看歷史價格圖表"
>
<span class="dashboard-price momo-mono">${{ item.record.price | int | number_format }}</span>
<i class="fas fa-chart-line" aria-hidden="true"></i>
</button>
</td>
<td class="text-end momo-mono">
{% 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>
{% 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>
<div class="dashboard-price-sub">候選價,需單位換算</div>
{% else %}
<span class="dashboard-match-state is-{{ match_status.tone | default('neutral') }}" title="{{ match_status.summary | default('尚未搜尋 PChome 同款') }}">
{{ match_status.label | default('尚未搜尋') }}
</span>
{% if match_status.detail %}
<div class="dashboard-price-sub">{{ match_status.detail }}</div>
{% endif %}
{% if match_status.summary %}
<div class="dashboard-price-sub">{{ match_status.summary }}</div>
{% endif %}
{% endif %}
</td>
<td>
<div class="dashboard-competition-card">
<span class="dashboard-competition-badge is-{{ decision.tone }}">{{ decision.label }}</span>
{% if decision.gap_pct is not none %}
<span class="dashboard-competition-meta momo-mono">
MOMO - PChome
{% if decision.gap_amount > 0 %}+{% endif %}${{ decision.gap_amount | round(0) | int | number_format }}
/ {% if decision.gap_pct > 0 %}+{% endif %}{{ decision.gap_pct | round(1) }}%
</span>
{% endif %}
<span class="dashboard-competition-meta">{{ decision.summary }}</span>
</div>
</td>
{% if current_filter == 'ai_picks' %}
<td>
{% if item.ai_pick %}
<div class="dashboard-ai-pick-card">
<div class="dashboard-ai-pick-head">
<span class="dashboard-ai-pick-rank">#{{ item.ai_pick.rank }}</span>
<span class="dashboard-ai-pick-confidence is-{{ item.ai_pick.confidence_band | replace('_', '-') }}">信心 {{ (item.ai_pick.confidence * 100) | round(0) | int }}%</span>
</div>
<div class="dashboard-ai-evidence-line">
<span>機會 {{ item.ai_pick.opportunity_score | round(0) | int }}</span>
<span>證據 {{ item.ai_pick.evidence_quality | round(0) | int }}%</span>
{% if item.ai_pick.margin_rate is not none %}
<span>毛利 {{ item.ai_pick.margin_rate | round(1) }}%</span>
{% endif %}
</div>
<div class="dashboard-ai-pick-reason">{{ item.ai_pick.reason }}</div>
{% if item.ai_pick.missing_evidence %}
<div class="dashboard-ai-evidence-line" title="{{ item.ai_pick.missing_evidence_text }}">
{% for evidence in item.ai_pick.missing_evidence[:3] %}
<span class="dashboard-ai-evidence-chip">{{ evidence }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% else %}
<span class="dashboard-muted">尚無建議理由</span>
{% endif %}
</td>
{% elif current_filter == 'pchome_review' %}
<td>
{% set review = item.pchome_review %}
{% set envelope = review.decision_envelope if review else {} %}
{% set guardrails = envelope.guardrails if envelope else {} %}
<div class="dashboard-review-card">
<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>
{% endif %}
</div>
<div class="dashboard-ai-pick-reason">{{ review.action_label if review else decision.summary }}</div>
{% if envelope %}
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
<span>{{ envelope.severity or 'P4' }}</span>
<span>{{ guardrails.data_quality or 'partial' }}</span>
{% if guardrails.can_auto_execute == false %}
<span>HITL</span>
{% endif %}
{% if envelope.decision_id %}
<span title="{{ envelope.decision_id }}">trace</span>
{% endif %}
</div>
{% endif %}
{% if review %}
{% if review.candidate_pc_name or review.candidate_pc_price %}
<div class="dashboard-review-note">
{% if review.candidate_pc_name %}
{{ review.candidate_pc_name[:42] }}
{% endif %}
{% if review.candidate_pc_price %}
<span class="momo-mono">候選 ${{ review.candidate_pc_price | number_format }}</span>
{% endif %}
</div>
{% endif %}
{% if 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.catalog_review_guidance %}
<div class="dashboard-review-note">
覆核建議:{{ review.catalog_review_guidance.lane_label }} · {{ review.catalog_review_guidance.action_hint }}
</div>
{% endif %}
<div class="dashboard-ai-evidence-line">
{% if review.candidate_count %}
<span>{{ review.candidate_count }} 筆候選</span>
{% endif %}
{% if review.candidate_pc_id %}
<span>PChome {{ review.candidate_pc_id }}</span>
{% endif %}
{% if review.attempted_at %}
<span>{{ review.attempted_at }}</span>
{% endif %}
</div>
{% if review.unit_comparison and review.unit_comparison.summary %}
<div class="dashboard-review-note momo-mono">{{ review.unit_comparison.summary }}</div>
{% endif %}
{% if review.unit_price_insight and review.unit_price_insight.summary %}
<div class="dashboard-review-note">{{ review.unit_price_insight.summary }}</div>
{% endif %}
<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>
{% endif %}
<td class="text-end momo-mono">
{% if item.yesterday_diff > 0 %}
<span class="dashboard-change-up">▲ +{{ item.yesterday_diff | abs | int | number_format }}</span>
{% elif item.yesterday_diff < 0 %}
<span class="dashboard-change-down">▼ -{{ item.yesterday_diff | abs | int | number_format }}</span>
{% else %}
<span class="dashboard-muted">--</span>
{% endif %}
</td>
<td class="text-end momo-mono">
{% set week_diff = item.stats.get('7d_diff', 0) %}
{% if week_diff > 0 %}
<span class="dashboard-change-up">+{{ week_diff | int | number_format }}</span>
{% elif week_diff < 0 %}
<span class="dashboard-change-down">-{{ week_diff | abs | int | number_format }}</span>
{% else %}
<span class="dashboard-muted">--</span>
{% endif %}
</td>
<td class="dashboard-table-time text-end momo-mono">
{{ item.record.timestamp.strftime('%m-%d %H:%M') if item.record.timestamp else '--' }}
</td>
<td class="dashboard-table-time text-end momo-mono">
{{ item.safe_created_at.strftime('%m-%d %H:%M') if item.safe_created_at else '--' }}
</td>
</tr>
{% else %}
<tr>
<td colspan="{{ 10 if current_filter in ['ai_picks', 'pchome_review'] else 9 }}">
<div class="dashboard-empty">
{% if search_query %}
找不到與「{{ search_query }}」相關的商品
{% else %}
目前沒有符合條件的商品
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if total_pages > 1 %}
<div class="dashboard-pagination">
{% if current_page > 1 %}
<a class="dashboard-action-link" href="{{ url_for('dashboard.index', page=current_page - 1, category=current_category, filter=current_filter, review_status=current_review_status, q=search_query, sort_by=current_sort, order=current_order) }}">上一頁</a>
{% endif %}
<span class="dashboard-table-meta momo-mono">第 {{ current_page }} / {{ total_pages }} 頁</span>
{% if current_page < total_pages %}
<a class="dashboard-action-link" href="{{ url_for('dashboard.index', page=current_page + 1, category=current_category, filter=current_filter, review_status=current_review_status, q=search_query, sort_by=current_sort, order=current_order) }}">下一頁</a>
{% endif %}
</div>
{% endif %}
</div>
</section>
</div>
<div class="modal fade dashboard-history-modal" id="historyModal" tabindex="-1" aria-labelledby="historyModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<div>
<h5 class="modal-title" id="historyModalLabel">歷史價格走勢</h5>
<div class="dashboard-history-subtitle momo-mono" id="historyModalSubtitle">真實價格紀錄</div>
<div class="dashboard-history-range" aria-label="價格歷史區間">
<button type="button" data-history-range="week"></button>
<button class="is-active" type="button" data-history-range="month"></button>
<button type="button" data-history-range="quarter"></button>
<button type="button" data-history-range="year"></button>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="關閉"></button>
</div>
<div class="modal-body">
<div class="dashboard-chart-shell">
<div class="dashboard-chart-state" id="historyChartState">載入價格歷史中...</div>
<canvas class="dashboard-chart-canvas is-hidden" id="priceChart"></canvas>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
<script src="{{ url_for('static', filename='js/page-dashboard-v2.js') }}"></script>
{% endblock %}