- source_meta.py kennt jetzt den echten Bestand: Kategorien telegram (107 Quellen) und x (40), Typen x_account und pdf_document. Die 10 nie belegten Lagen-Kategorien und der ungenutzte Typ excluded sind raus. - Pydantic-Pattern source_type um x_account ergaenzt, excluded raus. Behebt 422 beim Speichern von 40 X-Account-Quellen. - Modal-Selects (Typ, Kategorie, PDF-Kategorie) werden aus /api/sources/meta befuellt statt hartkodiert. Submit sendet category/source_type nur noch, wenn nicht leer. Behebt stilles Loeschen der Kategorie bei Telegram-Quellen. - source_suggester: Import auf shared.agents.claude_client angepasst (war im Portal seit jeher kaputt, Konvention wie source_classifier). - Toter Code raus: POST /health/run-stream (147 Z. SSE-Duplikat), runHealthCheck() im Frontend, excluded_counts-CTE + Sperren-Spalte (immer 0), Trend-Delta (braucht 2 Runs, es gibt 1), Alignment-Chips (source_alignments ist leer), doppeltes formatDateTime, redundanter setupHealthTab-Listener, toter healthContent-Zugriff. - Tests: Kategorien-/Typen-Sets neu gepinnt, Smoke-Karteileichen entfernt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 Zeilen
2.2 KiB
Python
68 Zeilen
2.2 KiB
Python
"""Single Source of Truth für Quellen-Kategorien und -Typen.
|
|
|
|
Wird vom Backend über GET /api/sources/meta exportiert.
|
|
Frontend (sources.js, source-health.js, dashboard.html) lädt diese
|
|
beim Init und befüllt damit Filter-Dropdowns und Label-Lookups.
|
|
"""
|
|
from typing import TypedDict
|
|
|
|
|
|
class CategoryEntry(TypedDict):
|
|
key: str
|
|
label: str
|
|
|
|
|
|
class TypeEntry(TypedDict):
|
|
key: str
|
|
label: str
|
|
|
|
|
|
SOURCE_CATEGORIES: list[CategoryEntry] = [
|
|
{"key": "nachrichtenagentur", "label": "Nachrichtenagentur"},
|
|
{"key": "oeffentlich-rechtlich", "label": "Öffentlich-Rechtlich"},
|
|
{"key": "qualitaetszeitung", "label": "Qualitätszeitung"},
|
|
{"key": "behoerde", "label": "Behörde"},
|
|
{"key": "fachmedien", "label": "Fachmedien"},
|
|
{"key": "think-tank", "label": "Think-Tank"},
|
|
{"key": "international", "label": "International"},
|
|
{"key": "regional", "label": "Regional"},
|
|
{"key": "boulevard", "label": "Boulevard"},
|
|
{"key": "telegram", "label": "Telegram"},
|
|
{"key": "x", "label": "X (Twitter)"},
|
|
{"key": "sonstige", "label": "Sonstige"},
|
|
]
|
|
|
|
|
|
SOURCE_TYPES: list[TypeEntry] = [
|
|
{"key": "rss_feed", "label": "RSS-Feed"},
|
|
{"key": "web_source", "label": "Webquelle"},
|
|
{"key": "telegram_channel", "label": "Telegram-Kanal"},
|
|
{"key": "podcast_feed", "label": "Podcast-Feed"},
|
|
{"key": "x_account", "label": "X-Konto"},
|
|
{"key": "pdf_document", "label": "PDF-Dokument"},
|
|
]
|
|
|
|
|
|
def get_meta() -> dict:
|
|
"""Vollständige Meta-Information für Frontend-Konsumenten."""
|
|
return {
|
|
"categories": SOURCE_CATEGORIES,
|
|
"types": SOURCE_TYPES,
|
|
}
|
|
|
|
|
|
def category_label(key: str) -> str:
|
|
"""Lookup: Kategorie-Key -> Label. Fallback: Key selbst."""
|
|
for c in SOURCE_CATEGORIES:
|
|
if c["key"] == key:
|
|
return c["label"]
|
|
return key
|
|
|
|
|
|
def type_label(key: str) -> str:
|
|
"""Lookup: Typ-Key -> Label. Fallback: Key selbst."""
|
|
for t in SOURCE_TYPES:
|
|
if t["key"] == key:
|
|
return t["label"]
|
|
return key
|