Commit graph

27 Commits

Autor SHA1 Nachricht Datum
claude-dev
768150c302 fix(agents): nicht maskierte Anfuehrungszeichen in Modell-JSON reparieren
Beobachtet am 31.07.2026 auf Staging: Modelle schliessen deutsche Zitate
gelegentlich mit einem geraden Anfuehrungszeichen, das das JSON an dieser
Stelle bricht. Die Folgen waren still und teuer:
  - Lagebild von Lage 47 brach mitten im Satz ab (738 statt ~4000 Zeichen),
    weil der Regex-Fallback nur bis zum Stoerzeichen rettete
  - ein kompletter Recherche-Durchlauf von Lage 45 ging verloren
  - sechs weitere Durchlaeufe fielen auf die verlustbehaftete
    Einzelobjekt-Rettung zurueck
Betroffen war vor allem der EU-Weg (Opus 4.6), der CLI-Weg aber ebenfalls.

Neu: src/json_utils.py repariert solche Antworten, indem es beim Durchlauf
mitfuehrt, ob eine Zeichenkette offen ist, und ein Anfuehrungszeichen maskiert,
auf das kein gueltiger JSON-Fortsetzer folgt (Komma nur dann als Ende, wenn
danach wirklich ein Wert oder Schluessel beginnt). Roh eingebettete
Zeilenumbrueche werden ebenfalls maskiert. Repariert wird ausschliesslich als
Rueckfallebene, nachdem der normale Parser gescheitert ist, gueltiges JSON
bleibt unangetastet.

Eingebunden in analyzer, factchecker und researcher. Zusaetzlich meldet der
Analyzer jetzt als ERROR, wenn der letzte Fallback ein erkennbar
abgeschnittenes Lagebild liefert, statt das still hinzunehmen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 11:32:35 +00:00
claude-dev
a7f1aa29ab feat(eu-umbau): staan-Recherche-Schleife und Doppelspur (Phase 2)
Im EU-Modus laeuft die Recherche jetzt europaeisch: agents/eu_researcher.py
steuert eine Schleife aus Bedrock-Planung (Opus, EU-Profile) und staan-Suchen
(services/staan_client.py, Suche + Volltexte via full_content=markdown,
Pflicht-Domain-Ausschlussliste je Anfrage). Bildet die 4-Phasen-Tiefenrecherche
nach und liefert exakt das JSON der bisherigen CLI-WebSearch, Parsing und
Filter in researcher.search bleiben unveraendert. Anti-Halluzination: nur URLs
aus echten staan-Treffern werden akzeptiert; published_at nur wenn aus Inhalt
oder URL ableitbar (staan liefert keine Daten).

Doppelspur im Orchestrator: Lane-Schluessel ist jetzt (Organisation, Backend),
Aufloesung zentral in _resolve_ai_backend (Lage > Org-Setting > ENV). CLI- und
EU-Lauf derselben Organisation laufen gleichzeitig, gleiche Backends seriell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 20:12:31 +00:00
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
309c97f40a fix(geo+recall): Länder-Centroid statt Hauptstadt + Eigennamen in GNews-Query
Zwei Fixes aus der jp_demo-Verifikation:

1. Geoparsing — Länder mit Centroid statt Hauptstadt
   Bisher bekam ein Land die Koordinaten seiner Hauptstadt. Damit landeten
   alle "Japan"-Marker exakt auf Tokyo (35.69, 139.69) und die Karte
   suggerierte faelschlich ein Ereignis in der Hauptstadt. Neue Tabelle
   _COUNTRY_CENTROIDS (37 Laender) verortet ein Land in seiner geografischen
   Mitte (Japan: 36.20, 138.25). Laender ohne Centroid-Eintrag fallen auf die
   Hauptstadt zurueck.

2. Recall — Eigennamen in den Google-News-Suchfeed erzwingen
   Beim ersten Refresh fehlt die Headlines-Historie, daher kamen die GNews-
   Such-Keywords aus der Feed-Selektion. Haiku legt Eigennamen (z.B. "Qilin")
   in die en-Liste, die ja-Liste hatte nur Allgemeinbegriffe — die ja-Query
   suchte ohne "Qilin". build_news_search_feeds stellt nicht-englischen
   Sprach-Queries jetzt die 2 wichtigsten en-Keywords voran (Eigennamen
   kommen auch in fremdsprachigen Artikeln lateinisch vor). Damit ist schon
   der erste Refresh spezifisch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 02:13:30 +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
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
f68d25dbce feat(pipeline): output_language pro Org durch die Pipeline reichen
- OUTPUT_LANGUAGE Konstante aus config.py entfernt (jetzt pro Org in
  organization_settings).
- Orchestrator laedt output_language einmal pro Refresh aus der Org-Sprache.
- researcher.search(), analyzer.analyze/.analyze_incremental/.generate_latest_developments,
  factchecker.check/.check_incremental/.check_incremental_twophase bekommen
  output_language als Parameter (Default Deutsch).
- LANG_INTERNATIONAL / LANG_GERMAN_ONLY (+ Deep-Varianten) sind Funktionen,
  die je nach output_language die Sprachanweisung erzeugen (Deutsch | English
  | Fallback).
- Sprachfilter in researcher.search ist org-relativ: bei nicht-international
  werden Artikel mit Sprache != output_language_iso gefiltert.

Phase 2 von 8 (eng_demo / Org-Sprache). Bestandsorgs unveraendert, weil
Default-Setting weiterhin de (siehe Phase-1-Migration).
2026-05-13 20:54:28 +00:00
Claude Code
f22c8dbc61 fix: removepaywalls.com -> removepaywall.com (Singular ist die echte Domain)
User-Korrektur: die echte Service-Domain heisst removepaywall.com (Singular).
removepaywalls.com (Plural) liefert HTTP 403 - vermutlich nicht der gleiche
Service oder gar nicht mehr existent.

Betrifft:
- services/source_health.py: REMOVEPAYWALLS_PREFIX-Konstante (Phase 18)
- agents/researcher.py: Claude-Prompts fuer Paywall-Hinweise (zwei Stellen)

Verifiziert mit curl: removepaywall.com -> 200, removepaywalls.com -> 403.
2026-05-09 05:00:11 +00:00
Claude Code
5a123ef3b8 fix(researcher): Lagentitel-Eigennamen als Pflicht-Keywords (Bug 2 Buckelwal-Diagnose)
KEYWORD_EXTRACTION_PROMPT explizit erweitert:
- Eigennamen/Tiernamen/Personennamen aus dem THEMA als ZWINGEND markiert.
- Hinweis dass DE und EN identisch sein duerfen (Eigennamen).
- Klar gesagt: bei spezifischen Begriffen (>=7 Zeichen) reicht 1 Treffer in
  RSS-Headlines (passt zu rss_parser.py adaptive Schwelle aus a08df3d).

Code-Post-Processing (researcher.py _extract_keywords):
- Nach dem Parser werden Lagentitel-Woerter (>=4 Zeichen, nicht in Stopwords)
  ggf. in die Keyword-Liste injiziert, falls Haiku sie weggelassen hat.
- Verhindert konkret den "Buckelwal timmy"-Bug: "timmy" fehlte in Haikus
  Liste, damit fielen Headlines mit nur "Buckelwal" durch das min_matches.

Hintergrund: Memory-Eintrag rss_match_und_keyword_bug.md, Bug 2 von 3.
Bug 1 (rss_parser min_matches adaptiv) ist seit Commit a08df3d auf Live.
Bug 3 (international=True default) bleibt offen, ist primaer UX-Frage.
2026-05-09 03:52:36 +00:00
0a6208c289 WebSearch: eingetragene Web-Quellen via Haiku vorselektieren
Bisher hatten Quellen vom Typ web_source keine praktische Wirkung auf
die Recherche - sie lagen nur als Marker in der DB. Jetzt werden sie
aktiv in den Recherche-Prompt eingebunden.

Ablauf:
1. Vor dem Hauptaufruf an Opus prüft ein günstiger Haiku-Call alle
   aktiven Web-Quellen des Tenants (plus globale) und wählt die
   thematisch passenden aus. Leere Selektion ist ausdrücklich erlaubt.
2. Die ausgewählten Domains werden dem Recherche-Prompt als
   "EINGETRAGENE WEB-QUELLEN" Block beigegeben mit der Empfehlung,
   gezielt mit "site:domain query" zu suchen, falls thematisch passend.
3. site: ist Empfehlung, kein Zwang - Claude bleibt flexibel und
   ergänzt seine sonstige Recherche.

- source_rules.get_feeds_with_metadata: SELECT um notes-Feld erweitert,
  damit der Selektor besseren Kontext zur Quelle hat.
- ResearcherAgent.select_relevant_web_sources: neuer Helper analog zu
  select_relevant_feeds, mit Skip-Optimierung wenn ≤3 Quellen.
- WEB_SOURCE_SELECTION_PROMPT: explizite Regel "lieber leer als
  pauschal alle", verhindert Token-Verschwendung.
- ResearcherAgent.search: neuer Parameter preferred_sources, beide
  Templates (RESEARCH + DEEP_RESEARCH) bekommen optionalen
  preferred_sources_block.
- Orchestrator._web_search_pipeline: Vorselektion vor researcher.search,
  Token-Usage in usage_acc, Logging der gewählten Domains.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:45:17 +02:00
Claude Code
88b18d0775 fix(researcher): Robusteres JSON-Parsing der Claude-Antworten
Behebt das Symptom, dass Recherche-Lagen wie staging Lage 6 "Friedrich Merz"
trotz erfolgreichem Refresh leer blieben. Claude lieferte nicht-leere Antworten
(1226-2125 Zeichen), die der bisherige Regex-Parser nicht extrahieren konnte —
die Recherche meldete "0 Artikel" und der Refresh wurde stumm als Erfolg
verbucht.

Aenderungen:
- _parse_response, select_relevant_feeds, extract_dynamic_keywords und
  select_relevant_telegram_channels nutzen jetzt json.JSONDecoder.raw_decode
  ueber Modul-Helper _extract_json_array/_extract_json_object. Damit werden
  auch JSON-Bloecke mit Vor-/Nachtext, Markdown-Fences oder verschachtelten
  Objekten zuverlaessig erkannt.
- Bei Parse-Fehlschlag wird jetzt ein gekuerztes Sample der Claude-Antwort
  geloggt, damit kuenftige Faelle direkt debuggbar sind.
- Neue ResearcherParseError-Exception unterscheidet "echt 0 Treffer" von
  "Antwort kaputt". search() gibt zusaetzlich ein parse_failed-Flag zurueck.
- Orchestrator-Multi-Pass: wenn alle 3 research-Durchlaeufe 0 neue Artikel
  ergeben UND mindestens einer am Parser scheiterte, wird der Refresh als
  Fehler markiert (statt als stiller Erfolg). Der WebSocket-refresh_error
  loest dann die sichtbare UI-Meldung aus.

Adhoc-Lagen sind unveraendert: dort fangen RSS und Telegram die kaputte
Claude-Antwort auf, dafuer ist nur die Diagnose im Log neu.
2026-04-30 20:45:41 +00:00
Claude Dev
a2aaa061d4 fix: Keine Gedankenstriche (mdash/endash) in LLM-generierten Inhalten
- Keine-Gedankenstriche-Regel in factchecker.py und researcher.py Prompts
- _sanitize_mdash() in claude_client.py als Sicherheitsnetz: ersetzt
  alle mdash/endash im Output durch Kommas
- analyzer.py hatte die Prompt-Regel bereits

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:37:01 +01:00
Claude Dev
474e2beca9 fix: URL-Verifizierung fuer WebSearch-Ergebnisse
- Prompt-Verbesserung: Claude muss exakte URLs aus WebSearch kopieren, keine konstruierten URLs
- Neue _verify_article_urls() Funktion im Orchestrator
- HEAD-Request auf jede WebSearch-URL, GET-Fallback bei 405
- Bei 404/unerreichbar: Ersetzung durch Google-Suchlink (site:domain headline)
- Nur WebSearch-URLs werden geprueft, RSS-URLs sind bereits verifiziert
2026-03-17 10:22:01 +01:00
Claude Dev
6a2bd9e9c9 feat: Adhoc-Recherche bekommt bestehende Artikel als Kontext
- RESEARCH_PROMPT_TEMPLATE: {existing_context} Platzhalter eingefügt
- search(): Baut bei Adhoc-Folge-Refreshes Kontextblock mit bis zu 30 bekannten Headlines auf
- orchestrator: Übergibt bestehende Artikel jetzt für ALLE Incident-Typen, nicht nur Research

Effekt: Bei Adhoc-Auto-Refreshes findet Claude WebSearch gezielt neue Quellen statt immer dieselben Mainstream-Treffer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 20:07:13 +01:00
Claude Dev
e0f8124e10 feat: Mehrstufige Deep-Research-Pipeline mit Quellenkontext
- DEEP_RESEARCH_PROMPT: 4-Phasen-Strategie (Breite Erfassung → Lückenanalyse → Gezielte Tiefenrecherche → Verifikation)
- Ziel 15-25 Quellen aus 5+ Quellentypen statt 8-15 aus Mainstream
- researcher.search(): Neuer Parameter existing_articles — bereits bekannte Quellen werden als Kontext übergeben, damit Claude gezielt neue Perspektiven findet
- orchestrator: DB-Abfrage vor Pipeline verschoben, bestehende Artikel als Kontext an Researcher übergeben (nur Research-Typ)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:33:56 +01: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
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
f7809ccc77 Fix: TimeoutError wird nicht mehr verschluckt - Retry greift jetzt
- researcher.py/factchecker.py: TimeoutError wird nach oben durchgereicht
  statt vom breiten except Exception geschluckt zu werden
- orchestrator.py: Built-in TimeoutError zu TRANSIENT_ERRORS hinzugefuegt
  (war nur asyncio.TimeoutError, aber claude_client wirft TimeoutError)
- config.py: CLAUDE_TIMEOUT von 300s auf 420s erhoeht

Vorher: Timeout fuehrte zu "0 Artikel" ohne Retry (8 Timeouts seit 28.02.)
Nachher: Timeout loest bis zu 3 Retries aus (sofort, +2min, +5min)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:48:48 +01:00
claude-dev
d274a9c9b6 Fix: Echte UTF-8-Umlaute in KI-generierten Inhalten erzwingen
- Claude CLI Umgebung: LANG=C.UTF-8, LC_ALL=C.UTF-8 setzen
- Alle 10 Agent-Prompts: Explizite Anweisung für echte Umlaute (ä,ö,ü,ß)
  statt Umschreibungen (ae,oe,ue,ss)
- Betrifft: Researcher, Analyzer, Factchecker (jeweils initial + inkrementell)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:28:36 +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
23ac6d6fd7 Fix: researcher.py korrupte Datei repariert (base64-Transfer) 2026-03-04 20:32:31 +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
71296edb97 Großes Cleanup: Bugs fixen, Features fertigstellen, toten Code entfernen
Bugs behoben:
- handleEdit() async keyword hinzugefügt (E-Mail-Checkboxen funktionieren jetzt)
- parseUTC() Funktion definiert (Fortschritts-Timer nutzt Server-Startzeit)
- Status cancelling wird im Frontend korrekt angezeigt

Features fertiggestellt:
- Sidebar: Lagen nach Typ getrennt (adhoc/research) mit Zählern
- Quellen-Bearbeiten: Edit-Button pro Quelle, Formular vorausfüllen
- Lizenz-Info: Org-Name und Lizenzstatus im Header angezeigt

Toter Code entfernt:
- 5 verwaiste Dateien gelöscht (alte rss_parser, style.css, components.js, layout.js, setup_users)
- 6 ungenutzte Pydantic Models entfernt
- Ungenutzte Funktionen/Imports in auth.py, routers, agents, config
- Tote API-Methoden, Legacy-UI-Methoden, verwaiste WS-Handler
- Abgeschlossene DB-Migrationen aufgeräumt

Sonstiges:
- requirements.txt: passlib[bcrypt] durch bcrypt ersetzt
- Umlaute korrigiert (index.html)
- CSS: incident-type-label → incident-type-badge, .login-success hinzugefügt
- Schließen statt Schliessen im Feedback-Modal
2026-03-04 18:45:38 +01:00
claude-dev
8312d24912 Initial commit: AegisSight-Monitor (OSINT-Monitoringsystem) 2026-03-04 17:53:18 +01:00