Commit graph

23 Commits

Autor SHA1 Nachricht Datum
Claude Code
9c50439785 feat(x): X (Twitter) als Bezugsquelle pro Lage
X-Accounts werden analog zu Telegram als Quelle (source_type=x_account)
konfiguriert und pro Lage ueber include_x zugeschaltet. Der Scraper
(feeds/x_parser.py, twscrape) liest Account-Timelines, optional ueber
einen HTTP-Proxy mit Fallback auf direkten Abruf ueber die Server-IP.

- DB-Migration include_x, Pydantic-Modelle, incidents-Router
- Orchestrator-X-Pipeline plus Haiku-Account-Vorselektion
- sources-Router /x/validate, x_account-Typ in Stats und Frontend
- Lage-Einstellungen: X-Toggle neben international und Telegram
- twscrape als Abhaengigkeit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 06:52:19 +00:00
86b12a156e feat(recency): Frische-Suchfeed (when:14d) + Aktualitaets-Score
Damit die Pipeline das aktuelle Bild einfaengt, nicht nur das relevanteste
(oft Monate alt). Bei der Test-Lage Qilin war der neueste Artikel 7 Wochen
alt, die Masse 6-7 Monate — weil Google-News-Volltextsuche nach Relevanz
rankt, nicht nach Datum.

- build_news_search_feeds: neuer Parameter recency_days. Wenn gesetzt, wird
  der Google-News-Operator "when:Nd" an die Query gehaengt — der Feed liefert
  nur Artikel der letzten N Tage. Eigene Domain-Gruppe '...-recent'.
- orchestrator._rss_pipeline: baut jetzt ZWEI Suchfeed-Saetze — einen
  Kontext-Feed (alle Zeiten) und einen Frische-Feed (when:14d). Beide laufen
  durch dieselbe Pipeline, Dedup entfernt Ueberschneidungen.
- rss_parser._fetch_feed: relevance_score bekommt einen Aktualitaets-Bonus
  (<=3d +0.35, <=14d +0.20, <=60d +0.05) bzw. -Malus (>180d -0.15, >365d
  -0.30). Damit ueberleben frische Artikel den Domain-Cap statt von alten
  verdraengt zu werden.

Nur adhoc-Pfad betroffen — research-Lagen ueberspringen die RSS-Pipeline
ohnehin und behalten ihre volle historische Tiefe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:32:55 +02:00
4e9d9f92f1 fix(rss): echten Publisher aus Google-News <source>-Tag uebernehmen
Google-News-Feeds (Site-Search wie auch der neue Volltext-Suchfeed) buendeln
Artikel vieler echter Publisher unter einer Feed-URL. Bisher bekamen alle
Artikel den generischen Feed-Namen als 'source' — der Faktencheck zaehlte
damit 25 Artikel verschiedener Zeitungen als EINE Quelle, und die
Quellenuebersicht war unbrauchbar.

Fix: Bei news.google.com-Feeds wird der echte Publisher aus dem <source>-Tag
des Feed-Items uebernommen (feedparser: entry.source.title). Fallback: der
Publisher-Teil hinter dem letzten ' - ' im Google-News-Titel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:18:50 +02:00
0e4c78d50a feat(recall): dynamische Google-News-Volltext-Suchfeeds pro Lage
Recall-Problem: Die Pipeline durchsuchte nur ~28 feste site:-RSS-Feeds plus
Claude-WebSearch. Japanische Security-Vendor-Blogs, Fachportale und
Regionalmedien (Cybertrust, ITmedia, INTERNET Watch, Reuters Japan ...)
tauchten in keinem festen Feed auf. Bei der Test-Lage "Qilin Ransomware
Japan" fand die Pipeline 20 Kandidaten — eine generische Google-News-JP-
Suche zum selben Thema liefert 49.

Fix: researcher.build_news_search_feeds baut pro Refresh einen Google-News-
Volltext-Suchfeed je Sprache (news.google.com/rss/search?q=keywords&hl=..&gl=..).
Query = Top-4-Keywords der jeweiligen Sprache aus der Keyword-Extraktion.
Der Orchestrator haengt diese Feeds an die selektierten site:-Feeds an; sie
laufen durch dieselbe Pipeline (Keyword-Match, Pre-Topic-Translate,
Topic-Filter). Precision bleibt, Recall steigt.

- researcher.py: build_news_search_feeds + _GNEWS_LOCALE-Tabelle.
- orchestrator._rss_pipeline: Suchfeeds aus source_language_whitelist
  (jp_demo: ['ja']) bzw. output+research_language (normale Orgs) gebaut
  und an selected_feeds angehaengt.
- rss_parser._apply_domain_cap: Suchfeeds (domain 'google-news-search-<lang>')
  bekommen Cap 25 statt 10 — sie sind der Recall-Treiber, Topic-Filter
  uebernimmt die Precision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 01:02:47 +02:00
379d14518c 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>
2026-05-22 00:12:56 +02:00
claude-dev
a41c8ae529 feat(articles): headline_en persistieren + Sprache aus Quell-Konfig übernehmen
Zwei Lücken beim Befund Lage 96 (Verfassungsänderung Japan): die japanische
Asahi-Shimbun-Quelle wurde durch das Sprach-aware Keyword-Matching (#27) und
Pre-Topic-Translate (#28) erstmals durchgereicht, landete aber mit
language='en' und ohne englische Headline in der DB. Damit ist sie im
Frontend nur als Kanji-Headline zu lesen und das Summary-LLM kann den
Treffer nicht aussagekräftig referenzieren.

1. INSERT INTO articles erweitert um headline_en und content_en. Werte
   stammen primär vom Translator (headline_en, falls TRANSLATOR_ENABLED den
   Pfad einmal in Englisch befüllt), Fallback auf die für den Topic-Filter
   angefertigte Mini-Übersetzung (headline_en_for_topic /
   content_en_for_topic). So liegt die englische Variante dauerhaft in der
   DB statt nur während des Refresh-Laufs im Speicher.

2. RSS- und Telegram-Parser setzen 'language' nun primär aus der Quell-/
   Kanal-Konfiguration (primary_language). Vorher war es hart 'de' wenn die
   Headline deutsch wirkte, sonst 'en' - mit dem Resultat, dass ein
   Kanji-Titel als language='en' landete. Mit dem Fix bekommen Asahi & Co.
   korrekt language='ja', russische Telegram-Kanäle 'ru' etc.

- src/agents/orchestrator.py: INSERT erweitert, Kommentar zur Fallback-Logik
- src/feeds/rss_parser.py: language aus feed_config.primary_language
- src/feeds/telegram_parser.py: channel_lang durch _fetch_channel reichen

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:05:47 +00:00
4b193d5784 fix(rss): Domain-Cap respektiert Quell-Domain statt URL-Domain
Bisher gruppierte der Domain-Cap nach der URL-Domain. Bei den 14 japanischen
Quellen, die wir über Google-News-Site-Search-RSS einspielen (MOFA, METI, MOD,
PSIA, Kyodo, Nikkei, Sankei, Tokyo-Shimbun, Chunichi, Ryukyu-Shimpo, Yahoo
Japan, NISC und der Hilfs-Bridge-Endpoint), zeigen alle Artikel-Links auf
news.google.com/articles/... — der Cap warf sie alle in einen Topf und
schnitt 10 davon weg.

Lösung: _fetch_feed gibt jetzt feed_config["domain"] (aus sources.domain,
also "mod.go.jp", "kyodo.com", ...) als source_domain mit ins Artikel-Dict.
_apply_domain_cap nutzt diese bevorzugt vor der URL-Domain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 01:53:50 +02:00
3345743aa5 feat(rss/telegram): sprach-aware Keyword-Matching für nicht-lateinische Quellen
Bisher generierte Haiku Keywords nur in DE/EN/Romaji. Japanische RSS-Feeds
(z.B. MOD-GNews mit "防衛省・自衛隊の宇宙政策") matchten daher nie, weil
"jieitai" ≠ "自衛隊". Arabische/persische Telegram-Channels matchten nur
durch Zufall (lateinische Eigennamen in Hashtags/URLs).

Drei zusammenhängende Änderungen:

1. get_feeds_with_metadata liefert primary_language pro Feed mit.
2. FEED_SELECTION_PROMPT_TEMPLATE und KEYWORD_EXTRACTION_PROMPT verlangen
   sprach-gruppierte Keywords ({de:[...], en:[...], ja:[...], ru:[...], ...}).
   "en" enthält lateinische Eigennamen (universell). Andere Sprachen werden
   nur gegen Feeds derselben Sprache gematcht.
3. RSS- und Telegram-Parser kombinieren pro Feed/Channel die "en"-Universalbegriffe
   mit den Keywords der Quellsprache. Die Spezifik-Schwelle (1-Treffer-Match)
   greift jetzt auch ab 3 Zeichen bei Non-ASCII (CJK, Arabisch, Kyrillisch).

Backward-kompatibel: flache Keyword-Listen werden weiter akzeptiert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 00:29:49 +02:00
Claude Code
9754dcb4ef feat(sources): primary_language Spalte + ISO-Backfill + org-relativer Feed-Bucket
- Neue Spalte sources.primary_language (ISO-2-Code) mit Backfill aus dem
  Freitext-Feld language (Erste Sprache vor /-Trennung). Edge-Cases wie
  Iran Military Magazine (English) [Farsi/Arabisch] landen als fa und
  koennen ueber das Verwaltungsportal manuell justiert werden.
- get_source_rules(tenant_id) bestimmt die Org-Sprache und bucketed Feeds
  nach primary (=Org-Sprache) / international (=alle anderen) / behoerden
  (Kategorie behoerde). Bei tenant_id=None oder Helper-Fehler default de.
- rss_parser.search_feeds unveraendert in Logik (international=False
  laesst weiterhin alle ausser dem international-Bucket durch), Kommentare
  generischer formuliert.

Phase 3 von 8 (eng_demo / Org-Sprache).
2026-05-13 20:57:51 +00:00
Claude Code
98c9da64b0 Umlaut-Normalisierung an drei Stellen + auch articles im QC
Fix fuer ASCII-Umlaute in Headlines/Inhalten (Gespraeche statt Gespraeche).
Zwei Quellen des Problems:
1. Quellen wie dpa-AFX, Telegram TASS/RIA liefern Headlines schon ASCII-fiziert
2. LLM-Uebersetzungen drift en gelegentlich zu ae/oe/ue trotz Prompt

Aenderungen:
- rss_parser.py: nach html_to_text auch normalize_german_umlauts auf
  title und summary anwenden (sicher, hunspell-Dict ignoriert englische
  Woerter wie Boeing/Business)
- orchestrator.py:1418 Translation-INSERT: headline_de und content_de
  durch normalize_german_umlauts schicken (LLM-Drift abfangen)
- post_refresh_qc.py: neue Funktion normalize_umlaut_articles als Sicher-
  heitsnetz analog zu normalize_umlaut_fields. Behandelt headline_de und
  content_de aller Artikel des Incidents; bei language=de zusaetzlich
  headline und content_original. Wird in run_post_refresh_qc nach
  normalize_umlaut_fields aufgerufen.

Backfill: migrations/migrate_umlauts_2026-05-03.py (im Verwaltungs-Repo)
2026-05-02 23:26:19 +00:00
Claude Code
307f0a1868 RSS-Parser: HTML aus summary strippen vor Speicherung
Ursache des Bugs: feedparser.entry.summary liefert bei vielen Quellen
(Guardian, AP, Sueddeutsche, Golem, Bellingcat, ...) HTML-kodierten Text
(<p>, <a>, <ul>, ...). Der Parser hat diesen 1:1 in articles.content_original
und content_de gespeichert. Folge:
- UI rendert HTML-Tags als Text in Timeline-Karten
- KI-Agenten (analyzer, entity_extractor, factchecker) bekommen HTML-Muell
  als Analyse-Input -> schwaechere Ergebnisse
- _is_german-Sprachheuristik wird durch Tags verzerrt
- 1000-Zeichen-Cap wird durch Tags + Tracking-URLs verbraucht

Fix: html_to_text aus feeds/transcript_extractors/_common.py wiederverwenden,
strippt Tags + decodiert HTML-Entities (inkl. dt. Umlaute) + normalisiert
Whitespace. Wird auf summary direkt nach entry.get angewandt -> betrifft
sowohl Match-Logik (text-Variable) als auch INSERT (content_original/de).

Backfill-Migration: migrations/migrate_html_strip_2026-05-03.py im
Verwaltungs-Repo, behandelt bestehende DB-Eintraege rueckwirkend.
2026-05-02 23:13:32 +00:00
a08df3d121 RSS-Parser: Match-Schwelle adaptiv (Bug 1 aus Buckelwal-Diagnose)
Bisher musste eine Headline mindestens 2 der dynamisch generierten
Suchworte enthalten, um den Match-Filter zu passieren. Bei thematisch
engen Lagen (Bsp. "Buckelwal timmy") fielen damit echte Treffer wie
"Transport mit Buckelwal erreicht dänische Gewässer..." durch, weil
nur 1 Keyword (buckelwal) gematcht hat.

Neue Heuristik: enthält der Text mindestens ein spezifisches Keyword
(>=7 Zeichen, also keine kurzen Akteursnamen wie "iran" oder "trump"),
reicht 1 Treffer. Bei nur kurzen, generischen Keywords gilt weiter die
alte Schwelle (halb der Wörter, max. 2). Topic-Filter danach (Haiku)
fängt False Positives.

Damit kommen ZDF/tagesschau/n-tv-Headlines mit nur einem starken
Begriff durch — der Hauptgrund, warum Lage 8 Buckelwal mit ZDF-Quelle
am ersten Refresh 0 Artikel hatte.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:55:05 +02:00
claude-dev
ed2ab1f3fc YouTube-Fallback aus Podcast-Kaskade entfernt
Der geplante YouTube-Captions-Fallback (Phase 2 via yt-dlp) wird nicht
umgesetzt. Begruendung: strategische Entscheidung, keinen YouTube-Scrape
als Quelle zu nutzen.

Geaendert:
- src/feeds/transcript_extractors/__init__.py:
  - try/except-Import fuer youtube-Modul entfernt (nie existiert)
  - Modul-Docstring aktualisiert (Stufen 1+2, kein 3)
  - source-Enum-Kommentar: nur noch rss_native / website_scrape

Konsequenz: Episoden, die weder Podcasting-2.0-Tag noch Sender-Manuskript
haben (z. B. Paywall-Inhalte bei FAZ/Handelsblatt), werden dauerhaft
verworfen. Fuer deutsche Qualitaetsmedien-Podcasts (Dlf, NDR, SZ, Spiegel,
ZEIT wo frei) reichen die zwei aktiven Stufen.
2026-04-18 12:30:28 +00:00
claude-dev
5127e0a42d Podcast-Integration Phase 1: Feed-Tag + Senderseiten
Podcasts werden wie normale RSS-Quellen behandelt (source_type=podcast_feed).
Kein externer bezahlter Dienst, keine lokale Transkription — Monitor nutzt
ausschliesslich vorhandene Transkripte.

Kaskade fuer Transkript-Bezug:
 1. Podcasting-2.0-Tag <podcast:transcript> im Feed (SRT/VTT/HTML/JSON)
 2. Redaktionelles Manuskript auf der Episodenseite
    (Adapter: Dlf, SZ, Spiegel, NDR)
 3. YouTube-Captions — Phase 2, optional per yt-dlp

Kein Stufen-Treffer -> Episode verworfen (graceful, kein Error).

Neu:
- src/feeds/podcast_parser.py (eigener Parser, RSS-Heisspfad unveraendert)
- src/feeds/transcript_extractors/ (Plugin-Muster):
    __init__.py        Dispatcher, Cache-Lookup gegen podcast_transcripts
    _common.py         HTML-Extraktion, Domain-Matching, httpx-Helper
    rss_native.py      Stufe 1: Feed-Tag-Parser (SRT/VTT/JSON/HTML)
    website_dlf.py     Stufe 2: deutschlandfunk.de + Schwester-Domains
    website_sz.py      Stufe 2: sz.de / sueddeutsche.de
    website_spiegel.py Stufe 2: spiegel.de / manager-magazin.de
    website_ndr.py     Stufe 2: ndr.de

Geaendert:
- src/database.py: idempotente Migration, Tabelle podcast_transcripts als
  URL-Cache gegen Mehrfach-Scrape zwischen Lagen
- src/models.py: Pydantic-Pattern von source_type um podcast_feed erweitert
- src/source_rules.py: get_feeds_with_metadata() nimmt source_type-Parameter,
  Default rss_feed (RSS-Pfad unveraendert)
- src/agents/orchestrator.py: neue _podcast_pipeline() parallel zu RSS,
  WebSearch und Telegram; nur fuer adhoc-Lagen; ohne Podcast-Quellen dormant

Verifikation:
- Migration auf Live-DB erfolgreich (Log: Tabelle podcast_transcripts angelegt)
- Import-/Instanziierungs-Test aller Module bestanden
- can_handle-Tests pro Sender-Adapter positiv + negativ OK
- Live-Scrape gegen Dlf: 22710 Zeichen, gegen SZ: 24918 Zeichen
- Dormant-Test: 0 Podcast-Quellen -> keine neue Codezeile im Refresh

Verwerfbarkeit: rein additiv, RSS-Pfad unberuehrt, Rollback in drei
Schritten (Quellen disablen, git revert, DROP TABLE podcast_transcripts).
2026-04-18 12:06:54 +00:00
Claude Dev
0019d74aea feat: Intelligente Telegram-Kanal-Selektion und verbesserte Quellenzuordnung
- Researcher: Claude-basierte Vorauswahl relevanter Telegram-Kanäle per Haiku
- FactChecker: Verbesserte Quellen-Zuordnung mit Relevanz-Scoring (Top 5)
- FactChecker: URLs werden nicht mehr doppelt zugeordnet, sources_count wird aktualisiert
- TelegramParser: Kanal-Filterung per channel_ids statt categories
- TelegramParser: Lockereres Keyword-Matching (1 Match reicht, da vorselektiert)
- Models: telegram_categories Feld entfernt (durch KI-Selektion ersetzt)
- Main: Chat-Router eingebunden unter /api/chat

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:01:32 +01:00
Claude Dev
2792e916c2 Telegram-Kategorie-Checkboxen: Nutzer koennen bei Lage-Erstellung einzelne Telegram-Quellkategorien auswaehlen 2026-03-13 19:08:36 +01:00
Claude Dev
01cad9dac5 Telegram-Kanaele als Quelle: Parser, Pipeline, UI-Checkbox, Validate-Endpoint
- Neuer source_type telegram_channel in models.py (Source + Incident)
- DB-Migration: include_telegram Spalte fuer incidents
- feeds/telegram_parser.py: Telethon-basierter Parser (analog RSS)
- Orchestrator: Telegram-Pipeline parallel zu RSS + WebSearch
- sources.py: POST /api/sources/telegram/validate Endpoint
- incidents.py: include_telegram in Create/Update/Response
- dashboard.html: Telegram-Checkbox + Filter-Option
- app.js: FormData, EditModal, SourceStats, TypeLabels
- config.py: TELEGRAM_API_ID, API_HASH, SESSION_PATH
- requirements.txt: telethon hinzugefuegt
2026-03-13 13:10:24 +01:00
claude-dev
5e19736a25 Per-User Domain-Ausschlüsse + Grundquellen-Schutz
- Neue Tabelle user_excluded_domains für benutzerspezifische Ausschlüsse
- Domain-Ausschlüsse wirken nur für den jeweiligen User, nicht org-weit
- user_id wird durch die gesamte Pipeline geschleust (Orchestrator → Researcher → RSS-Parser)
- Grundquellen (is_global) können nicht mehr bearbeitet/gelöscht werden im Frontend
- Grundquelle-Badge bei globalen Quellen statt Edit/Delete-Buttons
- Filter Von mir ausgeschlossen im Quellen-Modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:30:21 +01:00
claude-dev
7734eefd35 Dynamische Keyword-Extraktion fuer RSS-Filterung + min_matches-Fix
- researcher.py: Neuer dedizierter Haiku-Call extract_dynamic_keywords()
  analysiert die letzten 30 Headlines und generiert 5 DE+EN Begriffspaare
- orchestrator.py: Dynamische Keywords vor Feed-Selektion aus DB-Headlines
- rss_parser.py: min_matches auf max 2 gedeckelt (vorher n/2, bei 10 Keywords = 5)
- analyzer.py: Fettdruck-Anweisungen entfernt

Vorher: 0 RSS-Treffer (min_matches=5 unerreichbar)
Nachher: 22 RSS-Treffer (Tagesschau 11, Al Jazeera 5, BBC 4, NYT 2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:12:17 +01:00
claude-dev
536e7f585b Feat: Claude-Keywords für RSS-Suche, Jahreszahlen-Filter, strikteres Matching
- rss_researcher liefert jetzt Keywords zurück, die direkt für RSS-Suche genutzt werden
- Neue _clean_search_words() filtert rein-numerische Begriffe (Jahreszahlen etc.)
- Matching-Schwelle aufgerundet: bei 3 Keywords müssen mindestens 2 matchen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:36:08 +01:00
claude-dev
ff4c54d9a8 Quellenvielfalt sicherstellen: Domain-Cap + Balance + Discovery-Verbesserungen
- config.py: MAX_FEEDS_PER_DOMAIN=3, MAX_ARTICLES_PER_DOMAIN_RSS=10
- rss_parser.py: _apply_domain_cap() begrenzt Artikel pro Domain nach RSS-Fetch
- orchestrator.py: Domain-Balance vor Feed-Selektion (max 3 Feeds/Domain),
  Domain-Cap in Background-Discovery
- source_rules.py: article_count in get_feeds_with_metadata(), Content-Hash
  in _validate_feed() für Duplikat-Erkennung bei Discovery
- researcher.py: QUELLENVIELFALT-Regel im Haiku Feed-Selektions-Prompt
- DB: 52 WordPress-Redirect-Duplikate deaktiviert (netzpolitik.org, bashinho.de)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:25:04 +01:00
claude-dev
3d9a827bc8 Inkrementelle Analyse + Token-Optimierung + Relevanz-Scoring
TOKEN-OPTIMIERUNG:
- Inkrementelle Analyse: Folge-Refreshes senden nur noch das bisherige
  Lagebild + neue Artikel an Claude (statt alle Artikel erneut).
  Spart ~60-70% Tokens bei Lagen mit vielen Artikeln.
- Inkrementeller Faktencheck: Bestehende Fakten als Zusammenfassung,
  nur neue Artikel werden vollstaendig geprueft.
- Modell-Steuerung: Feed-Selektion nutzt jetzt Haiku (CLAUDE_MODEL_FAST)
  statt Opus. Spart ~50-70% bei Feed-Auswahl.
- Set-basierte DB-Deduplizierung: Bestehende URLs/Headlines einmal
  in Sets geladen statt N*M einzelne DB-Queries pro Artikel.

INHALTLICHE VERBESSERUNGEN:
- Relevanz-Scoring: Artikel nach Keyword-Dichte (40%),
  Quellen-Reputation (30%), Inhaltstiefe (20%), RSS-Score (10%).
- Flexibles RSS-Matching: min. Haelfte der Keywords statt alle.
  RSS-Artikel bekommen einen relevance_score.
- Fuzzy Claim-Matching: SequenceMatcher (0.7) statt exakter
  String-Vergleich. Verhindert Duplikat-Akkumulation.
- Translation-Fix: Nur gueltige DB-IDs (isinstance int).
- Researcher: WebFetch fuer Top-Artikel, erweiterte Zusammenfassungen.

DATEIEN:
- config.py: CLAUDE_MODEL_FAST
- claude_client.py: model-Parameter
- researcher.py: Haiku Feed-Selektion, erweiterte Prompts
- analyzer.py: Inkrementelle Analyse + analyze_incremental()
- factchecker.py: Inkrementeller Check + Fuzzy-Matching
- orchestrator.py: Set-Dedup, Relevanz-Scoring, inkrementeller Flow
- rss_parser.py: Flexibles Keyword-Matching + relevance_score
2026-03-04 20:22:47 +01:00
claude-dev
8312d24912 Initial commit: AegisSight-Monitor (OSINT-Monitoringsystem) 2026-03-04 17:53:18 +01:00