feat(multitenancy): Sprach-Whitelist + Translator-Override + Forum-Quellenklasse
Vorbereitung fuer jp_demo-Organisation: drei separate Sprach-Settings statt einer einzigen output_language. org_settings.py: - get_source_language_whitelist: Liste erlaubter Quellsprachen als JSON-Array (z.B. ["ja"] beschraenkt RSS/Telegram auf japanische Quellen). - get_research_language: Sprache fuer WebSearch-Prompts (Default: output_language). - get_translator_enabled: Pro-Org-Override des globalen TRANSLATOR_ENABLED-Flags. - LANGUAGE_DISPLAY_NAMES um ja/zh/ko/ru/ar/fa/he/fr/es erweitert. source_rules.py: - get_feeds_with_metadata filtert nach source_language_whitelist, wenn gesetzt. - Feeds ohne primary_language fallen bei aktiver Whitelist raus (gewollt). - SELECT um media_type erweitert, damit es im Feed-Dict ankommt. orchestrator.py: - Laedt research_language, source_language_whitelist, translator_enabled aus den Org-Settings. - Wenn Whitelist gesetzt: international_sources-Flag wird ignoriert. - research_language_iso wird an researcher.search() weitergegeben. - translate_articles bekommt enabled-Parameter aus Org-Setting. - Geoparsing ueberspringt media_type='forum' Artikel. - SELECT * FROM articles wird zu JOIN sources, damit media_type beim Reload am Article-Dict haengt. researcher.py: - search() akzeptiert research_language_iso. Asymmetrische Sprach-Auswahl (Recherche != Output) erzeugt eigene Prompt-Anweisung "primaer in Quell- sprache, englische Region-Outlets erlaubt". translator.py: - translate_articles akzeptiert enabled-Parameter. Ueberschreibt die globale TRANSLATOR_ENABLED-Konstante pro Aufruf. factchecker.py: - _format_articles_text filtert Artikel mit media_type='forum' aus. Anonyme Foren-Posts gelten nicht als Faktenbeleg. rss_parser.py: - _fetch_feed traegt media_type aus feed_config ins Article-Dict ein, damit downstream Pipeline-Schritte Foren-Quellen erkennen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -642,14 +642,20 @@ async def get_feeds_with_metadata(tenant_id: int = None, source_type: str = "rss
|
||||
|
||||
source_type: "rss_feed" (Default) oder "podcast_feed" — trennt RSS- und Podcast-Quellen
|
||||
in getrennten Pipelines, damit der RSS-Heisspfad unveraendert bleibt.
|
||||
|
||||
Wenn die Org eine source_language_whitelist gesetzt hat (z.B. jp_demo: ['ja']),
|
||||
werden nur Feeds geliefert, deren primary_language darauf passt. Feeds ohne
|
||||
gesetztes primary_language fallen in dem Fall raus — das ist gewollt, weil
|
||||
eine Whitelist gerade die strenge Beschraenkung ist.
|
||||
"""
|
||||
from database import get_db
|
||||
from services.org_settings import get_source_language_whitelist
|
||||
|
||||
db = await get_db()
|
||||
try:
|
||||
if tenant_id:
|
||||
cursor = await db.execute(
|
||||
"SELECT name, url, domain, category, notes, primary_language, "
|
||||
"SELECT name, url, domain, category, notes, primary_language, media_type, "
|
||||
"COALESCE(article_count, 0) AS article_count FROM sources "
|
||||
"WHERE source_type = ? AND status = 'active' "
|
||||
"AND (tenant_id IS NULL OR tenant_id = ?)",
|
||||
@@ -657,12 +663,25 @@ async def get_feeds_with_metadata(tenant_id: int = None, source_type: str = "rss
|
||||
)
|
||||
else:
|
||||
cursor = await db.execute(
|
||||
"SELECT name, url, domain, category, notes, primary_language, "
|
||||
"SELECT name, url, domain, category, notes, primary_language, media_type, "
|
||||
"COALESCE(article_count, 0) AS article_count FROM sources "
|
||||
"WHERE source_type = ? AND status = 'active'",
|
||||
(source_type,),
|
||||
)
|
||||
return [dict(row) for row in await cursor.fetchall()]
|
||||
feeds = [dict(row) for row in await cursor.fetchall()]
|
||||
|
||||
# Whitelist-Filter (nur wenn die Org eine gesetzt hat)
|
||||
if tenant_id:
|
||||
whitelist = await get_source_language_whitelist(db, tenant_id)
|
||||
if whitelist:
|
||||
before = len(feeds)
|
||||
feeds = [f for f in feeds if (f.get("primary_language") or "").lower() in whitelist]
|
||||
logger.info(
|
||||
"source_language_whitelist=%s fuer Org %s: %d/%d Feeds passieren",
|
||||
whitelist, tenant_id, len(feeds), before,
|
||||
)
|
||||
|
||||
return feeds
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden der Feed-Metadaten ({source_type}): {e}")
|
||||
return []
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren