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("
Not found.
", status_code=404)
- detail = project_detail(slug, entry, idx.slug_to_yaml, uncapped=False)
+ detail = project_detail(slug, entry, idx.slug_to_yaml, idx, uncapped=False)
# Strip diagnostics-only fields for public page
detail.pop('has_fallback_description', None)
detail.pop('editorial_quality', None)
@@ -428,12 +428,16 @@ async def theme_list_fragment(
async def theme_list_simple_fragment(
request: Request,
q: str | None = None,
+ quality_tier: str | None = None,
+ pool_tier: str | None = None,
+ bucket: str | None = None,
limit: int | None = Query(100, ge=1, le=300),
offset: int | None = Query(0, ge=0),
):
"""Lightweight list: only id, theme, short_description (for speed).
Attempts fast path using precomputed theme_list.json; falls back to full index.
+ Supports filtering by quality_tier, pool_tier, and bucket (popularity).
"""
import time as _t
t0 = _t.time()
@@ -445,17 +449,46 @@ async def theme_list_simple_fragment(
total = 0
if fast_items is not None:
fast_used = True
+ # Load index to get quality and pool data
+ try:
+ idx = load_index()
+ except FileNotFoundError:
+ return HTMLResponse("
Catalog unavailable.
", status_code=503)
# Filter (substring on theme only) if q provided
if q:
ql = q.lower()
fast_items = [e for e in fast_items if isinstance(e.get("theme"), str) and ql in e["theme"].lower()]
+
+ # Apply quality and pool tier filters
+ if quality_tier or pool_tier or bucket:
+ filtered_fast_items = []
+ for e in fast_items:
+ theme_id = e.get("id")
+ summary = idx.summary_by_slug.get(theme_id, {})
+ if quality_tier and summary.get("quality_tier") != quality_tier:
+ continue
+ if pool_tier and summary.get("pool_tier") != pool_tier:
+ continue
+ if bucket and summary.get("popularity_bucket") != bucket:
+ continue
+ filtered_fast_items.append(e)
+ fast_items = filtered_fast_items
+
total = len(fast_items)
slice_items = fast_items[off: off + lim]
for e in slice_items:
+ theme_id = e.get("id")
+ # Get quality and pool data from index
+ summary = idx.summary_by_slug.get(theme_id, {})
items.append({
"id": e.get("id"),
"theme": e.get("theme"),
"short_description": e.get("short_description"),
+ "quality_tier": summary.get("quality_tier"),
+ "quality_score": summary.get("quality_score"),
+ "pool_size": summary.get("pool_size"),
+ "pool_tier": summary.get("pool_tier"),
+ "popularity_bucket": summary.get("popularity_bucket"),
})
else:
# Fallback: load full index
@@ -463,7 +496,20 @@ async def theme_list_simple_fragment(
idx = load_index()
except FileNotFoundError:
return HTMLResponse("