Live-Monitoring-Lagen (adhoc) zeigten im Export die Entwicklungen als
Bullet-Liste mit "[DD.MM. HH:MM] Text" und Quellen-Links. Jetzt:
- Abschnitt heisst "Neueste Entwicklungen" statt "Zusammenfassung"
(Ueberschrift, Inhaltsverzeichnis). Title folgt dem Inhalt.
- Pro Eintrag eigene Datum/Uhrzeit-Zeile (TT.MM.JJ, HH:MM Uhr),
darunter als eigener Absatz der Meldungstext.
- Keine Links: Quellen-Klammern {Name|URL} und [N]-Zitate werden entfernt.
- Gilt fuer PDF und DOCX. Research-Lagen und KI-Executive-Summary
unveraendert.
Nebenbei: DOCX-Export crashte bei Beschreibungen ueber 255 Zeichen
(python-docx Core-Property "subject" Limit). Subject wird jetzt gekappt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mindest-Aktualisierungsintervall im Lage-Modal: 30 Minuten Basis, 45 bei X oder Telegram, 60 bei X und Telegram zugleich (internationale Quellen ohne Einfluss). Minutenwerte darunter sind im UI nicht mehr einstellbar (min-Attribut, Clamp am Feld und beim Speichern). Beim Umstellen von Stunden auf Minuten wird das Minimum gesetzt und als Hinweis angezeigt. Gilt für Anlegen und Bearbeiten.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Die Typ-Auswahl (Live-Monitoring/Recherche) steht jetzt als erstes Feld vor Titel und Beschreibung, beim Anlegen und beim Bearbeiten (gemeinsames Modal modal-new). Auf ausdrücklichen Wunsch direkt auf main aufgespielt, Staging-Zyklus umgangen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folgeregel zum Neu-am-Fix: verbietet Telegramm-Verkuerzungen
(Stichwort + [Nr]) und Auffangbloecke ohne Aussage (Fruehere Belege
[...]). Jede Quellennummer muss an einem vollstaendigen Satz haengen,
am Abschnitts- oder Lagebild-Ende keine reine Quellennummern-Liste.
Der inkrementelle Analyse-Prompt liess das LLM neue Erkenntnisse als
datierte Changelog-Bloecke (Neu am DD.MM.) anhaengen, die nie eingefaltet
wurden. Beim Iran-Lagebild summierten sich so 151 solcher Bloecke. Punkt 3
fordert jetzt das Einarbeiten in den thematischen Abschnitt; zusaetzliche
STRUKTUR-Regel loest bestehende Neu-am-Bloecke auf. Die chronologische Sicht
bleibt der separaten Kachel Neueste Entwicklungen vorbehalten.
Folgefix zu 952df87. Der Translator-Block laeuft post-summary bei jp_demo
40+ Min und war bisher fuer das Frontend unsichtbar und fuer den Watchdog
ein blinder Fleck (kein Pipeline-Step-Eintrag).
Aenderungen:
- pipeline_tracker.py: neuer Step 'translate' zwischen 'summary' und 'qc'
(DE+EN Label/Tooltip). Bewusst conditional sichtbar: erscheint nur, wenn
fremdsprachige Artikel ohne DE-Uebersetzung vorliegen UND
translator_enabled fuer die Org an ist.
- orchestrator.py: Translator-Block umrandet mit _pipe_start('translate')
und _pipe_done('translate', count_value=uebersetzt, count_secondary=
pending). Translator-Fehler schliesst Step trotzdem sauber ab.
Bedingung 'pending_translations and translator_enabled' ersetzt das
alte 'pending_translations' - skipped den Block sauber wenn Org-Override
deaktiviert (war vorher redundant in translate_articles selbst).
- main.py: ORPHAN_IDLE_LIMIT 30->60 Min, ORPHAN_HARD_LIMIT 90->120 Min.
Deckt jp_demo Translator-Phase (beobachtet bis 41 Min) mit Puffer ab,
ohne echte Haenger durchzulassen.
Resultierend: Frontend zeigt den Uebersetzungs-Schritt mit Fortschritt
(uebersetzt/gesamt). Watchdog killt nicht mehr vorzeitig.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der bisherige Watchdog markierte jeden running-Refresh nach 15 Min als
verwaist. Bei jp_demo-Lagen laeuft nach summary aber noch der Translator
(synchron, ~20 Min bei 200+ Artikeln), der den Refresh legitim ueber das
Limit traegt - er wurde dann faelschlich abgebrochen und der Orchestrator
hing in-memory weiter mit incident in _current_task.
Neuer Watchdog:
- ORPHAN_IDLE_LIMIT (30 Min): wird der Refresh nur als verwaist markiert,
wenn seit dieser Zeit kein refresh_pipeline_steps-Eintrag Fortschritt
zeigte (started_at oder completed_at)
- ORPHAN_HARD_LIMIT (90 Min): absolute Obergrenze gegen echte Haenger
- Wenn ueberhaupt keine Pipeline-Steps existieren -> als verwaist markieren
Folge: Long-Running-Refreshes (Translator-Block) laufen sauber durch,
nur echte Haenger werden bereinigt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Export-Dialog hat ein neues optionales Feld "Ersteller". Ist es
gefuellt, wird dieser Name im Bericht als Ersteller verwendet; bleibt es
leer, gilt wie bisher die E-Mail des Lage-Erstellers.
- export_incident: optionaler Query-Parameter creator, hat Vorrang vor
der E-Mail-Ableitung
- exportReport (api.js) haengt creator an die Export-URL
- submitExport (app.js) liest das neue Feld aus
- Eingabefeld im Export-Modal (dashboard.html)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die Branding-Auswahl im Export blieb wirkungslos, weil der
Browser die alten gecachten app.js/api.js weiterverwendete.
Versions-Query der beiden Skripte angehoben.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beim Bericht-Export lässt sich im Modal nun zwischen "Mit
AegisSight-Branding" und "Ohne Firmen-Branding" wählen. Im
neutralen Modus entfallen Logo, AegisSight-Zeile auf dem
Deckblatt und Branding-Footer; die Datei-Metadaten werden
neutralisiert. Das Deckblatt mit Titel, Stand und Ersteller
bleibt erhalten. Betrifft PDF und DOCX.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SOURCE_TYPE_PATTERN kannte kein x_account und SOURCE_CATEGORY_PATTERN
kein x. Dadurch schlug das Speichern einer X-Quelle ueber die Monitor-
Oberflaeche mit HTTP 422 fehl: bei neuen X-Quellen am source_type, beim
Bearbeiten bestehender X-Quellen an der Kategorie x. Beide Patterns
ergaenzt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die Filter-Chips wurden nur eingeblendet, wenn ein Fall Telegram- oder
X-Quellen hatte. Bei reinen Web-Faellen (z.B. in der Org jp_demo) fehlte
die Filterleiste damit komplett. Sie wird jetzt immer angezeigt, sobald
Quellen vorhanden sind, und zeigt zugleich, welche Quellentypen der Fall
enthaelt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die Quellenuebersicht innerhalb einer Lage zeigt jetzt Filter-Chips
(Alle / Web / Telegram / X) und blendet die Quellen-Boxen nach
Quellentyp ein und aus. Die Chips erscheinen nur, wenn neben Web auch
Telegram- oder X-Quellen vorkommen.
- sources-summary-Endpoint liefert pro Quelle einen source_type,
abgeleitet aus dem source-Praefix (X: / Telegram: / sonst Web)
- Filter-Chips und data-type in renderSourceOverviewFromSummary
- App.filterSourceOverview blendet die Boxen nach Typ
- Chip-Styles in style.css
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Typ-Filter im Quellen-Modal kennt jetzt auch podcast_feed, damit
alle Quellentypen (RSS, Web, Telegram, X, Podcast) filterbar sind.
Zusaetzlich zeigt jede Quelle ein korrektes Typ-Badge -- vorher zeigten
Telegram, X und Podcast faelschlich "Web".
- podcast_feed im sources-filter-type-Dropdown
- _sourceTypeLabel-Helfer, korrekte Typ-Badges im Gruppen-Header und in
den Feed-Zeilen, x_account im Info-Tooltip-typeMap
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
twscrape 0.17.0 von PyPI scheitert am x-client-transaction-id-Generator
(IndexError, twscrape-Issue #248). Der main-Branch enthaelt den Fix.
Pin auf einen festen Commit fuer Reproduzierbarkeit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
Vor der Stimmungs-Zusammenfassung laeuft ein separater Haiku-Call, der pro
Forum-Beitrag entscheidet:
- publishable: unveraendert uebernehmen
- redact: thematisch wertvoll, aber PII/Beleidigungen — Haiku liefert eine
bereinigte Kurzfassung
- discard: Hassrede gegen Gruppen, NSFW, glaubhafte Drohungen, reines
Trolling — entfernen
Damit liefert die jp_demo-Org keine ungefilterten 5ch/Hatena/Note-Posts
in die Lagen-Anzeige. Fail-open: Bei API-/Parse-Fehler wird die Original-
liste durchgereicht (Pipeline bricht nicht ab).
- analyzer.moderate_forum_articles: Batch (max 25/Call), JSON-Output, Logging
pro Entscheidungs-Klasse.
- orchestrator: Moderation laeuft vor generate_public_mood, gefilterte Liste
geht in die Stimmungs-Zusammenfassung.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eigene Pipeline-Stufe nach factcheck, vor summary, die Foren-Artikel
(media_type='forum') zu einer Themen-Zusammenfassung verarbeitet. Wird als
separate Dashboard-Kachel "Öffentliche Stimmung" angezeigt — getrennt von
Lagebild und Faktencheck, damit anonyme Forenposts nicht mit belegter
Faktenlage verwechselt werden.
- DB-Migration: incidents.public_mood (TEXT) + public_mood_updated_at (TS).
- pipeline_tracker: neuer Pipeline-Step "public_mood" (DE/EN-Labels).
- analyzer.generate_public_mood: Haiku-Call der Foren-Beitraege pro Quelle
gruppiert und 3-6 thematische Bullets erzeugt, mit expliziter Quellen-
Herkunft pro Bullet. Bei zu duennem Material gibt's keinen Output.
- orchestrator: neuer Schritt zwischen Factcheck und Summary. Laedt alle
Foren-Artikel der Lage (via JOIN auf sources), uebergibt sie an den
Stimmungs-Agent, speichert den Markdown-Text in incidents.public_mood.
- Topic-Filter (analyzer.filter_relevant_articles) markiert Foren-Quellen
mit [FORUM]-Tag und bekommt im Prompt die Regel, Foren-Artikel weicher
zu bewerten (Lage-Keyword im Titel reicht). Sie sollen in der Stimmungs-
Kachel landen, nicht voreilig verworfen werden.
- IncidentResponse-Modell: public_mood/public_mood_updated_at ergaenzt.
- Frontend: neuer Tab "Öffentliche Stimmung" (nur sichtbar wenn Inhalt da),
eigene Kachel mit Warn-Hinweis "keine Faktenlage". UI.renderPublicMood
als einfacher Bullet-Renderer.
- dashboard.html Cache-Buster fuer components.js + app.js gebumpt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Beim Aktualisieren von Lage 96 (Verfassungsänderung Japan) ist der Topic-Filter
in den letzten Refreshes auf 2/15, 4/26 bzw. 7/23 zurückgefallen. Die jp-RSS-
Treffer aus Asahi-Politik, NHK-Politik und Mainichi werden offenbar verworfen,
aber ohne Detail-Log lässt sich nicht beurteilen, ob das gerechtfertigt ist.
- analyzer.filter_relevant_articles: pro verworfenem Artikel eine INFO-Zeile
mit laufendem Index, Quelle, Original-Headline und (falls vorhanden) der
englischen Pre-Topic-Übersetzung. Ohne zusätzlichen Claude-Call, nur Logging
des bereits vorhandenen Materials.
- translator._TOPIC_TRANSLATE_CONTENT_MAX von 240 auf 500 erhöht. Bei dichten
Kanji- oder kyrillischen Headlines reichten 240 Zeichen oft nicht aus, um
dem nachgelagerten Topic-Filter den thematischen Kontext zu vermitteln.
Mehrkosten pro Refresh: vernachlässigbar (Haiku, einmal pro Refresh).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>