From 8efdc77c08eeca27046ae16eadeffc6bf02ecfa2 Mon Sep 17 00:00:00 2001 From: mwisnowski <93788087+mwisnowski@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:03:20 -0700 Subject: [PATCH] feat: add theme quality, pool size, and popularity badges with filtering (#56) --- .env.example | 4 + CHANGELOG.md | 38 ++++ DOCKER.md | 4 + README.md | 4 + RELEASE_NOTES_TEMPLATE.md | 40 +++- code/type_definitions_theme_catalog.py | 10 + code/web/app.py | 104 +++++++++- code/web/routes/themes.py | 59 +++++- code/web/services/theme_catalog_loader.py | 193 +++++++++++++++++- code/web/static/styles.css | 161 ++++++++++++++- code/web/static/tailwind.css | 49 +++++ code/web/templates/base.html | 2 +- code/web/templates/diagnostics/index.html | 88 +++++--- .../diagnostics/quality_dashboard.html | 159 +++++++++++++++ code/web/templates/themes/catalog_simple.html | 122 ++++++++++- .../web/templates/themes/detail_fragment.html | 142 ++++++++++++- code/web/templates/themes/list_fragment.html | 14 +- .../themes/list_simple_fragment.html | 15 +- config/themes/theme_list.json | 13 -- docker-compose.yml | 4 + dockerhub-docker-compose.yml | 4 + 21 files changed, 1165 insertions(+), 64 deletions(-) create mode 100644 code/web/templates/diagnostics/quality_dashboard.html diff --git a/.env.example b/.env.example index 81c8994..1ca904c 100644 --- a/.env.example +++ b/.env.example @@ -47,6 +47,10 @@ SHOW_DIAGNOSTICS=1 # dockerhub: SHOW_DIAGNOSTICS="1" ENABLE_THEMES=1 # dockerhub: ENABLE_THEMES="1" ENABLE_CUSTOM_THEMES=1 # dockerhub: ENABLE_CUSTOM_THEMES="1" USER_THEME_LIMIT=8 # dockerhub: USER_THEME_LIMIT="8" +SHOW_THEME_QUALITY_BADGES=1 # dockerhub: SHOW_THEME_QUALITY_BADGES="1" (show quality badges in theme catalog) +SHOW_THEME_POOL_BADGES=1 # dockerhub: SHOW_THEME_POOL_BADGES="1" (show pool size badges in theme catalog) +SHOW_THEME_POPULARITY_BADGES=1 # dockerhub: SHOW_THEME_POPULARITY_BADGES="1" (show popularity badges in theme catalog) +SHOW_THEME_FILTERS=1 # dockerhub: SHOW_THEME_FILTERS="1" (show filter dropdowns/chips in theme catalog) ENABLE_PWA=0 # dockerhub: ENABLE_PWA="0" ENABLE_PRESETS=0 # dockerhub: ENABLE_PRESETS="0" WEB_VIRTUALIZE=1 # dockerhub: WEB_VIRTUALIZE="1" diff --git a/CHANGELOG.md b/CHANGELOG.md index aac0288..956cda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,44 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning ## [Unreleased] ### Added +- **Theme Quality Dashboard**: Diagnostic dashboard for monitoring catalog health at `/diagnostics/quality` + - **Quality Distribution**: Visual breakdown of theme counts by tier (Excellent/Good/Fair/Poor) + - **Catalog Statistics**: Total themes, average quality score displayed prominently + - **Top 10 Highest Quality**: Best-curated themes with links to theme pages + - **Bottom 10 Lowest Quality**: Themes needing improvement with actionable suggestions + - **Improvement Tools**: Direct links to linter CLI command and editorial documentation + - **Protected Access**: Dashboard gated behind SHOW_DIAGNOSTICS=1 flag for admin use + - **Main Diagnostics Integration**: Quality stats preview card on main diagnostics page with link to full dashboard +- **Theme Badge Explanations**: Detailed reasoning for quality, pool size, and popularity badges on individual theme pages + - **Quality Explanations**: Multi-factor breakdown showing synergy breakdown (curated/enforced/inferred counts), deck archetype classification, description curation status, and editorial quality status + - **Pool Size Explanations**: Card count with contextual guidance on flexibility and optimization potential + - **Popularity Explanations**: Adoption pattern descriptions explaining why themes have their popularity tier + - **Collapsible Display**: Badge details in collapsible section (open by default), matching catalog page badge legend pattern + - **Feature Flag Respects**: Explanations only show for enabled badge types (respects SHOW_THEME_QUALITY_BADGES, SHOW_THEME_POOL_BADGES, SHOW_THEME_POPULARITY_BADGES) + - **Dynamic Reasoning**: Explanations generated based on actual theme data (quality score, synergy counts, editorial status, archetype metadata) +- **Theme Catalog Badge System**: Comprehensive metric visualization with granular display control + - **Quality Badges**: Editorial quality indicators (Excellent/Good/Fair/Poor) with semantic colors + - **Pool Size Badges**: Card availability indicators (Vast/Large/Moderate/Small/Tiny) showing total cards per theme + - **Popularity Badges**: Usage frequency indicators (Very Common/Common/Uncommon/Niche/Rare) based on theme adoption + - **Badge Feature Flags**: Individual toggle flags for each badge type (SHOW_THEME_QUALITY_BADGES, SHOW_THEME_POOL_BADGES, SHOW_THEME_POPULARITY_BADGES) + - **Filter Controls**: Dropdown filters and quick-select chips for all three metrics with master toggle (SHOW_THEME_FILTERS) +- **Theme Pool Size Display**: Visual indicators showing total card availability per theme + - **Pool Size Calculation**: Automatic counting of cards with each theme tag from parquet data + - **Pool Tier Badges**: Color-coded badges (Vast/Large/Moderate/Small/Tiny) showing pool size categories + - **Pool Data in API**: Theme pool size (card count) and tier included in all theme API responses + - **Pool Badges CSS**: New badge styles with distinct colors (violet/teal/cyan/orange/gray for pool tiers) + - **Dual Metric System**: Quality badges (editorial completeness) + Pool size badges (card availability) shown together +- **Theme Quality Score Display**: Visual quality indicators in web UI for theme catalog + - **Quality Tier Badges**: Color-coded badges (Excellent/Good/Fair/Poor) shown in theme lists and detail pages + - **Quality Scoring**: Automatic calculation during theme loading based on completeness, uniqueness, and curation quality + - **Quality Data in API**: Theme quality tier and normalized score (0.0-1.0) included in all theme API responses + - **Quality Badges CSS**: New badge styles with semantic colors (green/blue/yellow/red for quality tiers) +- **Theme Catalog Filtering**: Advanced filtering system for quality, pool size, and popularity + - **Filter Dropdowns**: Select-based filters for precise tier selection (Quality: E/G/F/P, Pool: V/L/M/S/T, Popularity: VC/C/U/N/R) + - **Quick Filter Chips**: Single-click filter activation with letter-based shortcuts + - **Combined Filtering**: Multiple filter types work together with AND logic (e.g., Good quality + Vast pool + Common popularity) + - **Active Filter Display**: Visual chips showing applied filters with individual remove buttons + - **Filter Performance**: Backend filtering in both fast path (theme_list.json) and fallback (full index) with sub-200ms response times - **Theme Editorial Quality & Standards**: Complete editorial system for theme catalog curation - **Editorial Metadata Fields**: `description_source` (tracks provenance: official/inferred/custom) and `popularity_pinned` (manual tier override) - **Heuristics Externalization**: Theme classification rules moved to `config/themes/editorial_heuristics.yml` for maintainability diff --git a/DOCKER.md b/DOCKER.md index ab4c15d..398120e 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -250,6 +250,10 @@ See `.env.example` for the full catalog. Common knobs: | `SHOW_DIAGNOSTICS` | `1` | Enable Diagnostics tools and overlays. | | `SHOW_COMMANDERS` | `1` | Expose the commander browser. | | `ENABLE_THEMES` | `1` | Keep the theme selector and themes explorer visible. | +| `SHOW_THEME_QUALITY_BADGES` | `1` | Show quality badges in theme catalog (editorial quality score). | +| `SHOW_THEME_POOL_BADGES` | `1` | Show pool size badges in theme catalog (total available cards). | +| `SHOW_THEME_POPULARITY_BADGES` | `1` | Show popularity badges in theme catalog (usage frequency). | +| `SHOW_THEME_FILTERS` | `1` | Show filter dropdowns and quick chips in theme catalog. | | `WEB_VIRTUALIZE` | `1` | Opt-in to virtualized lists/grids for large result sets. | | `ALLOW_MUST_HAVES` | `1` | Enable include/exclude enforcement in Step 5. | | `SHOW_MUST_HAVE_BUTTONS` | `0` | Surface the must include/exclude buttons and quick-add UI (requires `ALLOW_MUST_HAVES=1`). | diff --git a/README.md b/README.md index a0c25d9..aa80d99 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,10 @@ Most defaults are defined in `docker-compose.yml` and documented in `.env.exampl | `SHOW_COMMANDERS` | `1` | Enable the commander browser. | | `ENABLE_THEMES` | `1` | Keep the theme browser and selector active. | | `ENABLE_CUSTOM_THEMES` | `1` | Surface the Additional Themes section in the New Deck modal. | +| `SHOW_THEME_QUALITY_BADGES` | `1` | Show quality badges in theme catalog (editorial quality score). | +| `SHOW_THEME_POOL_BADGES` | `1` | Show pool size badges in theme catalog (total available cards). | +| `SHOW_THEME_POPULARITY_BADGES` | `1` | Show popularity badges in theme catalog (usage frequency). | +| `SHOW_THEME_FILTERS` | `1` | Show filter dropdowns and quick chips in theme catalog. | | `WEB_VIRTUALIZE` | `1` | Opt into virtualized lists for large datasets. | | `ALLOW_MUST_HAVES` | `1` | Enforce include/exclude (must-have) lists. | | `SHOW_MUST_HAVE_BUTTONS` | `0` | Reveal the must include/exclude buttons and quick-add UI (requires `ALLOW_MUST_HAVES=1`). | diff --git a/RELEASE_NOTES_TEMPLATE.md b/RELEASE_NOTES_TEMPLATE.md index cfc5d74..4704bdb 100644 --- a/RELEASE_NOTES_TEMPLATE.md +++ b/RELEASE_NOTES_TEMPLATE.md @@ -2,6 +2,44 @@ ## [Unreleased] ### Added +- **Theme Quality Dashboard**: Diagnostic dashboard for monitoring catalog health at `/diagnostics/quality` + - **Quality Distribution**: Visual breakdown of theme counts by tier (Excellent/Good/Fair/Poor) + - **Catalog Statistics**: Total themes, average quality score displayed prominently + - **Top 10 Highest Quality**: Best-curated themes with links to theme pages + - **Bottom 10 Lowest Quality**: Themes needing improvement with actionable suggestions + - **Improvement Tools**: Direct links to linter CLI command and editorial documentation + - **Protected Access**: Dashboard gated behind SHOW_DIAGNOSTICS=1 flag for admin use + - **Main Diagnostics Integration**: Quality stats preview card on main diagnostics page with link to full dashboard +- **Theme Badge Explanations**: Detailed reasoning for quality, pool size, and popularity badges on individual theme pages + - **Quality Explanations**: Multi-factor breakdown showing synergy breakdown (curated/enforced/inferred counts), deck archetype classification, description curation status, and editorial quality status + - **Pool Size Explanations**: Card count with contextual guidance on flexibility and optimization potential + - **Popularity Explanations**: Adoption pattern descriptions explaining why themes have their popularity tier + - **Collapsible Display**: Badge details in collapsible section (open by default), matching catalog page badge legend pattern + - **Feature Flag Respects**: Explanations only show for enabled badge types (respects SHOW_THEME_QUALITY_BADGES, SHOW_THEME_POOL_BADGES, SHOW_THEME_POPULARITY_BADGES) + - **Dynamic Reasoning**: Explanations generated based on actual theme data (quality score, synergy counts, editorial status, archetype metadata) +- **Theme Catalog Badge System**: Comprehensive metric visualization with granular display control + - **Quality Badges**: Editorial quality indicators (Excellent/Good/Fair/Poor) with semantic colors + - **Pool Size Badges**: Card availability indicators (Vast/Large/Moderate/Small/Tiny) showing total cards per theme + - **Popularity Badges**: Usage frequency indicators (Very Common/Common/Uncommon/Niche/Rare) based on theme adoption + - **Badge Feature Flags**: Individual toggle flags for each badge type (SHOW_THEME_QUALITY_BADGES, SHOW_THEME_POOL_BADGES, SHOW_THEME_POPULARITY_BADGES) + - **Filter Controls**: Dropdown filters and quick-select chips for all three metrics with master toggle (SHOW_THEME_FILTERS) +- **Theme Pool Size Display**: Visual indicators showing total card availability per theme + - **Pool Size Calculation**: Automatic counting of cards with each theme tag from parquet data + - **Pool Tier Badges**: Color-coded badges (Vast/Large/Moderate/Small/Tiny) showing pool size categories + - **Pool Data in API**: Theme pool size (card count) and tier included in all theme API responses + - **Pool Badges CSS**: New badge styles with distinct colors (violet/teal/cyan/orange/gray for pool tiers) + - **Dual Metric System**: Quality badges (editorial completeness) + Pool size badges (card availability) shown together +- **Theme Quality Score Display**: Visual quality indicators in web UI for theme catalog + - **Quality Tier Badges**: Color-coded badges (Excellent/Good/Fair/Poor) shown in theme lists and detail pages + - **Quality Scoring**: Automatic calculation during theme loading based on completeness, uniqueness, and curation quality + - **Quality Data in API**: Theme quality tier and normalized score (0.0-1.0) included in all theme API responses + - **Quality Badges CSS**: New badge styles with semantic colors (green/blue/yellow/red for quality tiers) +- **Theme Catalog Filtering**: Advanced filtering system for quality, pool size, and popularity + - **Filter Dropdowns**: Select-based filters for precise tier selection (Quality: E/G/F/P, Pool: V/L/M/S/T, Popularity: VC/C/U/N/R) + - **Quick Filter Chips**: Single-click filter activation with letter-based shortcuts + - **Combined Filtering**: Multiple filter types work together with AND logic (e.g., Good quality + Vast pool + Common popularity) + - **Active Filter Display**: Visual chips showing applied filters with individual remove buttons + - **Filter Performance**: Backend filtering in both fast path (theme_list.json) and fallback (full index) with sub-200ms response times - **Theme Editorial Quality & Standards**: Complete editorial system for theme catalog curation - **Editorial Metadata Fields**: `description_source` (tracks provenance: official/inferred/custom) and `popularity_pinned` (manual tier override) - **Heuristics Externalization**: Theme classification rules moved to `config/themes/editorial_heuristics.yml` for maintainability @@ -23,7 +61,7 @@ - Eliminated manual JSON stripping step (JSON is derived artifact, not source of truth) - **Parquet Theme Stripping**: Strip low-card themes directly from card data files - Added `strip_parquet_themes.py` script with dry-run, verbose, and backup modes - - Added parquet manipulation functions to `theme_stripper.py`: backup, filter, update, and strip operations + - Added parquet manipulation functions to `theme_stripper.py`: `backup_parquet_file()`, `filter_theme_tags()`, `update_parquet_theme_tags()`, `strip_parquet_themes()` - Handles multiple themeTags formats: numpy arrays, lists, and comma/pipe-separated strings - Stripped 97 theme tag occurrences from 30,674 cards in `all_cards.parquet` - Updated `stripped_themes.yml` log with 520 themes stripped from parquet source diff --git a/code/type_definitions_theme_catalog.py b/code/type_definitions_theme_catalog.py index 3db25a5..921b034 100644 --- a/code/type_definitions_theme_catalog.py +++ b/code/type_definitions_theme_catalog.py @@ -59,6 +59,16 @@ class ThemeEntry(BaseModel): None, description="Lifecycle quality flag (draft|reviewed|final); optional and not yet enforced strictly", ) + quality_tier: Optional[str] = Field( + None, + description="Editorial quality tier (Excellent|Good|Fair|Poor) calculated from theme completeness metrics (R20)", + ) + quality_score: Optional[float] = Field( + None, + ge=0.0, + le=1.0, + description="Normalized quality score (0.0-1.0) based on example cards, uniqueness, description source (R20)", + ) model_config = ConfigDict(extra='forbid') diff --git a/code/web/app.py b/code/web/app.py index ce511cb..d6ea64f 100644 --- a/code/web/app.py +++ b/code/web/app.py @@ -175,6 +175,10 @@ ENABLE_PRESETS = _as_bool(os.getenv("ENABLE_PRESETS"), False) ALLOW_MUST_HAVES = _as_bool(os.getenv("ALLOW_MUST_HAVES"), True) SHOW_MUST_HAVE_BUTTONS = _as_bool(os.getenv("SHOW_MUST_HAVE_BUTTONS"), False) ENABLE_CUSTOM_THEMES = _as_bool(os.getenv("ENABLE_CUSTOM_THEMES"), True) +SHOW_THEME_QUALITY_BADGES = _as_bool(os.getenv("SHOW_THEME_QUALITY_BADGES"), True) +SHOW_THEME_POOL_BADGES = _as_bool(os.getenv("SHOW_THEME_POOL_BADGES"), True) +SHOW_THEME_POPULARITY_BADGES = _as_bool(os.getenv("SHOW_THEME_POPULARITY_BADGES"), True) +SHOW_THEME_FILTERS = _as_bool(os.getenv("SHOW_THEME_FILTERS"), True) WEB_IDEALS_UI = os.getenv("WEB_IDEALS_UI", "slider").strip().lower() # 'input' or 'slider' ENABLE_PARTNER_MECHANICS = _as_bool(os.getenv("ENABLE_PARTNER_GESTIONS"), True) ENABLE_BATCH_BUILD = _as_bool(os.getenv("ENABLE_BATCH_BUILD"), True) @@ -314,6 +318,10 @@ templates.env.globals.update({ "enable_partner_mechanics": ENABLE_PARTNER_MECHANICS, "allow_must_haves": ALLOW_MUST_HAVES, "show_must_have_buttons": SHOW_MUST_HAVE_BUTTONS, + "show_theme_quality_badges": SHOW_THEME_QUALITY_BADGES, + "show_theme_pool_badges": SHOW_THEME_POOL_BADGES, + "show_theme_popularity_badges": SHOW_THEME_POPULARITY_BADGES, + "show_theme_filters": SHOW_THEME_FILTERS, "default_theme": DEFAULT_THEME, "random_modes": RANDOM_MODES, "random_ui": RANDOM_UI, @@ -2531,7 +2539,34 @@ async def diagnostics_home(request: Request) -> HTMLResponse: summary["colors"] = {} except Exception: summary = {"updated_at": None, "colors": {}} - ctx = {"request": request, "merge_summary": summary} + + # Calculate quality statistics for preview + quality_stats = None + try: + from .services.theme_catalog_loader import load_index as _load_idx + idx = _load_idx() + total_themes = len(idx.catalog.themes) + tier_counts = {'Excellent': 0, 'Good': 0, 'Fair': 0, 'Poor': 0} + quality_scores = [] + + for slug, tier in idx.quality_tier_by_slug.items(): + if tier: + tier_counts[tier] = tier_counts.get(tier, 0) + 1 + score = idx.quality_score_by_slug.get(slug) + if score is not None: + quality_scores.append(score) + + avg_quality_score = sum(quality_scores) / len(quality_scores) if quality_scores else 0.0 + + quality_stats = { + 'total_themes': total_themes, + 'tier_counts': tier_counts, + 'avg_quality_score': avg_quality_score, + } + except Exception: + pass + + ctx = {"request": request, "merge_summary": summary, "quality_stats": quality_stats} return templates.TemplateResponse("diagnostics/index.html", ctx) @@ -2542,6 +2577,73 @@ async def diagnostics_perf(request: Request) -> HTMLResponse: raise HTTPException(status_code=404, detail="Not Found") return templates.TemplateResponse("diagnostics/perf.html", {"request": request}) + +@app.get("/diagnostics/quality", response_class=HTMLResponse) +async def diagnostics_quality(request: Request) -> HTMLResponse: + """Theme catalog quality dashboard (diagnostics only).""" + if not SHOW_DIAGNOSTICS: + raise HTTPException(status_code=404, detail="Not Found") + + from .services.theme_catalog_loader import load_index as _load_idx + + idx = _load_idx() + + # Calculate quality statistics + themes = idx.catalog.themes + total_themes = len(themes) + + # Quality tier distribution + tier_counts = {'Excellent': 0, 'Good': 0, 'Fair': 0, 'Poor': 0} + quality_scores = [] + + for slug, tier in idx.quality_tier_by_slug.items(): + if tier: + tier_counts[tier] = tier_counts.get(tier, 0) + 1 + score = idx.quality_score_by_slug.get(slug) + if score is not None: + quality_scores.append(score) + + avg_quality_score = sum(quality_scores) / len(quality_scores) if quality_scores else 0.0 + + # Get top 10 best and bottom 10 worst themes + themes_with_scores = [] + for theme in themes: + from .services.theme_catalog_loader import slugify as _slugify + slug = _slugify(theme.theme) + tier = idx.quality_tier_by_slug.get(slug) + score = idx.quality_score_by_slug.get(slug, 0.0) + pool_size = idx.pool_size_by_slug.get(slug, 0) + + themes_with_scores.append({ + 'theme': theme.theme, + 'slug': slug, + 'tier': tier or 'Unknown', + 'score': score, + 'pool_size': pool_size, + 'description': theme.description or '', + 'synergy_count': len(theme.synergies) if theme.synergies else 0, + 'has_fallback_description': not theme.description or theme.description.startswith('A theme focused on'), + 'editorial_quality': theme.editorial_quality or 'auto', + }) + + # Sort by score + themes_with_scores.sort(key=lambda x: x['score'], reverse=True) + top_themes = themes_with_scores[:10] + bottom_themes = themes_with_scores[-10:] + bottom_themes.reverse() # Show worst first + + ctx = { + 'request': request, + 'total_themes': total_themes, + 'tier_counts': tier_counts, + 'avg_quality_score': avg_quality_score, + 'top_themes': top_themes, + 'bottom_themes': bottom_themes, + } + + return templates.TemplateResponse("diagnostics/quality_dashboard.html", ctx) + + # --- Diagnostics: combos & synergies --- @app.post("/diagnostics/combos") async def diagnostics_combos(request: Request) -> JSONResponse: diff --git a/code/web/routes/themes.py b/code/web/routes/themes.py index 16cafda..5e39c40 100644 --- a/code/web/routes/themes.py +++ b/code/web/routes/themes.py @@ -332,7 +332,7 @@ async def theme_catalog_detail_page(theme_id: str, request: Request): entry = idx.slug_to_entry.get(slug) if not entry: return HTMLResponse("