mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2026-03-24 22:16:31 +01:00
feat: add theme quality, pool size, and popularity badges with filtering (#56)
This commit is contained in:
parent
03e2846882
commit
8efdc77c08
21 changed files with 1165 additions and 64 deletions
|
|
@ -3,9 +3,9 @@
|
|||
<section>
|
||||
<h2>Diagnostics</h2>
|
||||
<p class="muted">Use these tools to verify error handling surfaces.</p>
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">System summary</h3>
|
||||
<div id="sysSummary" class="muted">Loading…</div>
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">System summary</summary>
|
||||
<div id="sysSummary" class="muted" style="margin-top:.5rem">Loading…</div>
|
||||
<div id="envFlags" style="margin-top:.5rem"></div>
|
||||
<div id="themeSuppMetrics" class="muted" style="margin-top:.5rem">Loading theme metrics…</div>
|
||||
<div id="themeSummary" style="margin-top:.5rem"></div>
|
||||
|
|
@ -13,9 +13,49 @@
|
|||
<div style="margin-top:.35rem">
|
||||
<button class="btn" id="diag-theme-reset">Reset theme preference</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Multi-face merge snapshot</h3>
|
||||
</details>
|
||||
|
||||
{# Theme Quality Overview #}
|
||||
{% if quality_stats %}
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">Theme Catalog Quality</summary>
|
||||
<div class="muted" style="margin-bottom:.5rem; margin-top:.5rem">Quick overview of theme quality metrics</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-bottom: 1rem;">
|
||||
<div>
|
||||
<div class="muted" style="font-size: 11px;">Total Themes</div>
|
||||
<div style="font-size: 20px; font-weight: 600;">{{ quality_stats.total_themes }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size: 11px;">Average Quality</div>
|
||||
<div style="font-size: 20px; font-weight: 600;">{{ (quality_stats.avg_quality_score * 100)|round|int }}%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size: 11px; color: #10b981;">Excellent</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: #10b981;">{{ quality_stats.tier_counts.Excellent }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size: 11px; color: #3b82f6;">Good</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: #3b82f6;">{{ quality_stats.tier_counts.Good }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size: 11px; color: #f59e0b;">Fair</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: #f59e0b;">{{ quality_stats.tier_counts.Fair }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size: 11px; color: #ef4444;">Poor</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: #ef4444;">{{ quality_stats.tier_counts.Poor }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: .75rem;">
|
||||
<a href="/diagnostics/quality" class="btn" style="text-decoration: none;">View Full Quality Dashboard →</a>
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">Multi-face merge snapshot</summary>
|
||||
<div class="muted" style="margin-bottom:.35rem">Pulls from <code>logs/dfc_merge_summary.json</code> to verify merge coverage.</div>
|
||||
{% set colors = (merge_summary.colors if merge_summary else {}) | default({}) %}
|
||||
{% if colors %}
|
||||
|
|
@ -70,25 +110,25 @@
|
|||
<div class="muted">No merge summary has been recorded. Run the tagger with multi-face merging enabled.</div>
|
||||
{% endif %}
|
||||
<div id="dfcMetrics" class="muted" style="margin-top:.5rem;">Loading MDFC metrics…</div>
|
||||
</div>
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Dual-Commander diagnostics</h3>
|
||||
<div class="muted" style="margin-bottom:.35rem;">Latest partner, partner-with, doctor, and background pairings with color sources.</div>
|
||||
</details>
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">Dual-Commander diagnostics</summary>
|
||||
<div class="muted" style="margin-bottom:.35rem; margin-top:.5rem;">Latest partner, partner-with, doctor, and background pairings with color sources.</div>
|
||||
<div id="partnerMetricsSummary" class="muted">Loading partner metrics…</div>
|
||||
<div id="partnerMetricsModes" class="muted" style="margin-top:.5rem;"></div>
|
||||
<div id="partnerColorSources" style="margin-top:.5rem;"></div>
|
||||
</div>
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Performance (local)</h3>
|
||||
</details>
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">Performance (local)</summary>
|
||||
<div class="muted" style="margin-bottom:.35rem">Scroll the Step 5 list; this panel shows a rough FPS estimate and virtualization renders.</div>
|
||||
<div style="display:flex; gap:1rem; flex-wrap:wrap">
|
||||
<div><strong>Scroll FPS:</strong> <span id="perf-fps">–</span></div>
|
||||
<div><strong>Visible tiles:</strong> <span id="perf-visible">–</span></div>
|
||||
<div><strong>Render count:</strong> <span id="perf-renders">0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Combos & Synergies (ad-hoc)</h3>
|
||||
</details>
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">Combos & Synergies (ad-hoc)</summary>
|
||||
<div class="muted" style="margin-bottom:.35rem">Paste card names (one per line) and detect two-card combos and synergies using current lists.</div>
|
||||
<textarea id="diag-combos-input" rows="6" style="width:100%; resize:vertical; font-family: var(--mono);"></textarea>
|
||||
<div style="margin-top:.5rem; display:flex; gap:.5rem; align-items:center">
|
||||
|
|
@ -96,21 +136,21 @@
|
|||
<small class="muted">Runs in diagnostics mode only.</small>
|
||||
</div>
|
||||
<pre id="diag-combos-out" style="margin-top:.5rem; white-space:pre-wrap"></pre>
|
||||
</div>
|
||||
</details>
|
||||
{% if enable_pwa %}
|
||||
<div class="card" style="background:#0f1115; border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">PWA status</h3>
|
||||
<div id="pwaStatus" class="muted">Checking…</div>
|
||||
</div>
|
||||
<details class="card" style="background:#0f1115; border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">PWA status</summary>
|
||||
<div id="pwaStatus" class="muted" style="margin-top:.5rem">Checking…</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem;">
|
||||
<h3 style="margin-top:0">Error triggers</h3>
|
||||
<details class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem;">
|
||||
<summary style="cursor:pointer; user-select:none; margin-top:0; font-size:1.17em; font-weight:bold;">Error triggers</summary>
|
||||
<div class="row" style="display:flex; gap:.5rem; align-items:center">
|
||||
<button class="btn" hx-get="/diagnostics/trigger-error" hx-trigger="click" hx-target="this" hx-swap="none">Trigger HTTP error (418)</button>
|
||||
<button class="btn" hx-get="/diagnostics/trigger-error?kind=unhandled" hx-trigger="click" hx-target="this" hx-swap="none">Trigger unhandled error (500)</button>
|
||||
<small class="muted">You should see a toast and an inline banner with Request-ID.</small>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{% if show_logs %}
|
||||
<p style="margin-top:.75rem"><a class="btn" href="/logs">Open Logs</a></p>
|
||||
{% endif %}
|
||||
|
|
|
|||
159
code/web/templates/diagnostics/quality_dashboard.html
Normal file
159
code/web/templates/diagnostics/quality_dashboard.html
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section>
|
||||
<h2>Theme Quality Dashboard</h2>
|
||||
<p class="muted">Monitor theme catalog health and quality metrics</p>
|
||||
|
||||
{# Summary Statistics #}
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Catalog Statistics</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1rem;">
|
||||
<div>
|
||||
<div class="muted" style="font-size: 12px;">Total Themes</div>
|
||||
<div style="font-size: 24px; font-weight: 600;">{{ total_themes }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size: 12px;">Average Quality Score</div>
|
||||
<div style="font-size: 24px; font-weight: 600;">{{ (avg_quality_score * 100)|round|int }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Quality Distribution #}
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Quality Tier Distribution</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
|
||||
<div>
|
||||
<div style="font-size: 12px; color: #10b981; font-weight: 600;">Excellent (≥75%)</div>
|
||||
<div style="font-size: 32px; font-weight: 700; color: #10b981;">{{ tier_counts.Excellent }}</div>
|
||||
<div class="muted" style="font-size: 11px;">{{ ((tier_counts.Excellent / total_themes) * 100)|round(1) }}% of catalog</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: #3b82f6; font-weight: 600;">Good (60-74%)</div>
|
||||
<div style="font-size: 32px; font-weight: 700; color: #3b82f6;">{{ tier_counts.Good }}</div>
|
||||
<div class="muted" style="font-size: 11px;">{{ ((tier_counts.Good / total_themes) * 100)|round(1) }}% of catalog</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: #f59e0b; font-weight: 600;">Fair (40-59%)</div>
|
||||
<div style="font-size: 32px; font-weight: 700; color: #f59e0b;">{{ tier_counts.Fair }}</div>
|
||||
<div class="muted" style="font-size: 11px;">{{ ((tier_counts.Fair / total_themes) * 100)|round(1) }}% of catalog</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: #ef4444; font-weight: 600;">Poor (<40%)</div>
|
||||
<div style="font-size: 32px; font-weight: 700; color: #ef4444;">{{ tier_counts.Poor }}</div>
|
||||
<div class="muted" style="font-size: 11px;">{{ ((tier_counts.Poor / total_themes) * 100)|round(1) }}% of catalog</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Top 10 Highest Quality Themes #}
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Top 10 Highest Quality Themes</h3>
|
||||
<div class="muted" style="margin-bottom:.5rem">Well-curated themes with high scores</div>
|
||||
<div style="overflow-x:auto">
|
||||
<table style="width:100%; border-collapse:collapse; font-size:13px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid var(--border); text-align:left;">
|
||||
<th style="padding:.35rem .5rem;">Rank</th>
|
||||
<th style="padding:.35rem .5rem;">Theme</th>
|
||||
<th style="padding:.35rem .5rem;">Tier</th>
|
||||
<th style="padding:.35rem .5rem;">Score</th>
|
||||
<th style="padding:.35rem .5rem;">Pool Size</th>
|
||||
<th style="padding:.35rem .5rem;">Synergies</th>
|
||||
<th style="padding:.35rem .5rem;">Editorial</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for theme in top_themes %}
|
||||
<tr style="border-bottom:1px solid rgba(148,163,184,0.2);">
|
||||
<td style="padding:.35rem .5rem; font-weight:600;">{{ loop.index }}</td>
|
||||
<td style="padding:.35rem .5rem;">
|
||||
<a href="/themes/{{ theme.slug }}" style="text-decoration: none; color: var(--link-color);">{{ theme.theme }}</a>
|
||||
</td>
|
||||
<td style="padding:.35rem .5rem;">
|
||||
<span class="theme-badge badge-quality-{{ theme.tier|lower }}">{{ theme.tier }}</span>
|
||||
</td>
|
||||
<td style="padding:.35rem .5rem; font-weight:600;">{{ (theme.score * 100)|round|int }}%</td>
|
||||
<td style="padding:.35rem .5rem;">{{ theme.pool_size }}</td>
|
||||
<td style="padding:.35rem .5rem;">{{ theme.synergy_count }}</td>
|
||||
<td style="padding:.35rem .5rem; text-transform: capitalize;">{{ theme.editorial_quality }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Bottom 10 Lowest Quality Themes #}
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Bottom 10 Lowest Quality Themes</h3>
|
||||
<div class="muted" style="margin-bottom:.5rem">Themes that need improvement</div>
|
||||
<div style="overflow-x:auto">
|
||||
<table style="width:100%; border-collapse:collapse; font-size:13px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid var(--border); text-align:left;">
|
||||
<th style="padding:.35rem .5rem;">Theme</th>
|
||||
<th style="padding:.35rem .5rem;">Tier</th>
|
||||
<th style="padding:.35rem .5rem;">Score</th>
|
||||
<th style="padding:.35rem .5rem;">Pool Size</th>
|
||||
<th style="padding:.35rem .5rem;">Synergies</th>
|
||||
<th style="padding:.35rem .5rem;">Issues</th>
|
||||
<th style="padding:.35rem .5rem;">Suggestions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for theme in bottom_themes %}
|
||||
<tr style="border-bottom:1px solid rgba(148,163,184,0.2);">
|
||||
<td style="padding:.35rem .5rem;">
|
||||
<a href="/themes/{{ theme.slug }}" style="text-decoration: none; color: var(--link-color);">{{ theme.theme }}</a>
|
||||
</td>
|
||||
<td style="padding:.35rem .5rem;">
|
||||
<span class="theme-badge badge-quality-{{ theme.tier|lower }}">{{ theme.tier }}</span>
|
||||
</td>
|
||||
<td style="padding:.35rem .5rem; font-weight:600;">{{ (theme.score * 100)|round|int }}%</td>
|
||||
<td style="padding:.35rem .5rem;">{{ theme.pool_size }}</td>
|
||||
<td style="padding:.35rem .5rem;">{{ theme.synergy_count }}</td>
|
||||
<td style="padding:.35rem .5rem; font-size: 11px;">
|
||||
{% set issues = [] %}
|
||||
{% if theme.pool_size < 15 %}{% set _ = issues.append('Low card count') %}{% endif %}
|
||||
{% if theme.synergy_count < 3 %}{% set _ = issues.append('Few synergies') %}{% endif %}
|
||||
{% if theme.has_fallback_description %}{% set _ = issues.append('Auto-generated desc') %}{% endif %}
|
||||
{% if theme.editorial_quality == 'auto' %}{% set _ = issues.append('Not reviewed') %}{% endif %}
|
||||
{{ issues|join(', ') or 'None identified' }}
|
||||
</td>
|
||||
<td style="padding:.35rem .5rem; font-size: 11px;">
|
||||
{% set suggestions = [] %}
|
||||
{% if theme.pool_size < 15 %}{% set _ = suggestions.append('Add example cards') %}{% endif %}
|
||||
{% if theme.synergy_count < 3 %}{% set _ = suggestions.append('Define synergies') %}{% endif %}
|
||||
{% if theme.has_fallback_description %}{% set _ = suggestions.append('Write custom description') %}{% endif %}
|
||||
{% if theme.editorial_quality == 'auto' %}{% set _ = suggestions.append('Review & curate') %}{% endif %}
|
||||
{{ suggestions|join('; ') or 'N/A' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Tools and Links #}
|
||||
<div class="card" style="background: var(--panel); border:1px solid var(--border); border-radius:10px; padding:.75rem; margin-bottom:.75rem">
|
||||
<h3 style="margin-top:0">Quality Improvement Tools</h3>
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<h4 style="margin: 0 0 .5rem 0; font-size: 14px;">Run Linter</h4>
|
||||
<p class="muted" style="margin: 0 0 .5rem 0; font-size: 12px;">Analyze theme catalog for quality issues and get actionable suggestions</p>
|
||||
<code style="display: block; background: rgba(0,0,0,0.1); padding: .5rem; border-radius: 6px; font-size: 12px; overflow-x: auto;">
|
||||
python code/scripts/validate_theme_catalog.py --lint
|
||||
</code>
|
||||
</div>
|
||||
<div>
|
||||
<h4 style="margin: 1rem 0 .5rem 0; font-size: 14px;">Documentation</h4>
|
||||
<ul style="margin: 0; padding-left: 1.25rem; font-size: 13px;">
|
||||
<li><a href="https://github.com/mwisnowski/mtg_python_deckbuilder/blob/main/docs/theme_editorial_guide.md" target="_blank" rel="noopener noreferrer" style="color: var(--link-color);">Theme Editorial Guide</a> - Quality scoring methodology and best practices</li>
|
||||
<li><a href="/themes" style="color: var(--link-color);">Browse Themes</a> - View all themes with quality badges</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue