Portierung aus dem Lokal-Fork (AegisSight-Monitor-Local, Commit d2b9168,
per Cherry-Pick übernommen und an den Server-Stand angepasst).
- Feste Sätze je Aktion (BILLING_MODE=flat) statt echter Kosten geteilt
durch cost_per_credit. Sätze auf die am 23.07.2026 beschlossenen
Verkaufswerte gesetzt. Live-Lauf 45, Recherche je Durchlauf 40,
Studio-Bausteine 12, Chat/Beschreibung/Globe 1. Per ENV überschreibbar,
der alte Modus bleibt als BILLING_MODE=actual erhalten.
- Abrechnungsperiode. licenses.credits_period trennt monthly von total,
träger Monatsreset bei der nächsten Lizenzprüfung, optionaler Übertrag
(credits_rollover, gedeckelt auf ein Monatskontingent). Bestandslizenzen
ohne Periodenmarke behalten ihren Verbrauch.
- Hard-Stop gegen das verfügbare Monatskontingent (Kontingent plus
Übertrag) statt gegen das Lebenszeit-Total.
- Warnschwelle budget_warning_percent (Default 80 Prozent) wird erstmals
ausgewertet, einmalige Meldung an alle aktiven Nutzer der Organisation.
- DB-Migration additiv und idempotent (credits_period, credits_period_start,
credits_rollover, credits_carried, budget_warning_sent, unlimited_budget).
- /api/auth/me liefert credits_available als Bezugsgröße plus
credits_period, das Credits-Widget zeigt "Credits diesen Monat".
- Wortlaut überall Credits (nicht Guthaben/Einheiten), docs/ABRECHNUNG.md
auf den Online-Stand gebracht.
Abweichungen zur Fork-Vorlage. Die Takt-Änderungen (Untergrenze 30 Min,
Kostenvorschau im Anlege-Dialog, Fork-Commit 5b0b578) sind bewusst NICHT
enthalten, die Sätze stehen auf 45/40 statt der Fork-Defaults 24/33.
Getestet gegen eine Kopie der Staging-DB, 20 Prüfungen bestanden.
Migration, Flat-Buchung adhoc/research/chat, Warnschwelle einmalig,
Hard-Stop, Monatsreset, Übertrag gedeckelt, Bestandslizenz ohne Marke.
Die vier Live-Lizenzen stehen auf unlimited_budget und sind unbeeinflusst.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neuer CLAUDE.md-Abschnitt legt fest, dass jede Änderung unter src/static
noch in derselben Sitzung ins jeweils andere Repo portiert wird.
Dokumentiert die gewollten Unterschiede, den einheitlichen
Credits-Wortlaut, den Cherry-Pick-Weg über Cross-Remotes und die
CRLF-Stolperfalle. Der Drift-Check liegt im Lokal-Fork unter
scripts/ui-drift-check.sh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Der Knopf für den Ansichtswechsel steht jetzt links neben "Abmelden" am
Ende der Kopfzeile statt an erster Stelle. Reihenfolge oben rechts damit
Barrierefreiheit, Theme, Konto, Klassische Ansicht, Abmelden.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Portierung aus dem Lokal-Fork (AegisSight-Monitor-Local, Commit 9613610).
Vier Angleichungen an das klassische Dashboard.
1. "Klassische Ansicht" ist jetzt derselbe Knopf wie "Studio-Ansicht" im
Dashboard, mit Symbol, an erster Stelle im rechten Kopfzeilen-Block.
2. Oben rechts fehlten Barrierefreiheit, Konto-Menü und Abmelden. Der
A11yManager liegt jetzt in js/a11y.js und wird von beiden Oberflächen
eingebunden, denn das Studio lädt app.js nicht. Konto-Menü (Organisation,
Lizenz, Credits, Über KI-Inhalte) und Abmelden nachgezogen, der
Theme-Schalter ist derselbe Schiebeschalter wie im Dashboard.
3. Die Fall-Liste hat jetzt die Umschaltung "Alle" / "Eigene" in der
Knopf-Optik der klassischen Seitenleiste, der Reiter-Zähler zieht mit.
4. Der Hinweis "Wähle links einen Fall aus" endet vor der Bausteine-Spalte
statt am Fensterrand, im gestapelten Layout nimmt er die volle Breite.
Das info@-Gating des Studios bleibt unverändert bestehen.
Versionsmarker der angefassten Dateien auf 20260725a angehoben.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fall-Chat und Ereignis-Timeline, alles ueber Claude, strikt fallorientiert.
incidents.py:
- GET /{id}/events: Aktivitaets-Timeline (article_ingest/refresh/analysis/chat_qa/source_change)
- POST /{id}/ask: Fall-Chat/RAG mit Guardrails:
* tools=None (kein Netz/kein Werkzeug) = zentrale Anti-Exfil-Abwehr (GitLost)
* nur Materialien DIESES Falls (scope=alle entfaellt), [n]-Zitate, strikt aus Materialien
* Claude-Vorauswahl der relevanten Artikel statt lokalem Embedding (kein lokales Modell)
* _escape_prompt_content + Ausgabe-Leak-Filter + EchoLeak-Haertung (externe Bilder/Links/URLs raus)
* Verbotsliste: kein Backend/App/Modell/Anbieter, kein Fremd-Fall
* chat_qa in incident_events protokolliert
- Recherche-Angebot: liefert die Materialien die Frage nicht, haengt der Analyst einen
needs_research-Block an (focus + description_addition, NUR aus der Frage abgeleitet).
- POST /{id}/clarify (nur auf Nutzer-Bestaetigung): Beschreibung ergaenzen + fokussierte
Folge-Recherche AUSSCHLIESSLICH zur Frage (Researcher title=focus/description=frage),
url-dedupliziert in den Fall. Laeuft als Hintergrund-Job (stage_runners.start_job).
stage_runners.py: generischer start_job() fuer Hintergrund-Jobs (run-status-sichtbar).
api.js: clarify(). studio.js + studio.css: Angebot rendern, bestaetigen, run-status pollen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend fuer die modularen Studio-Pipeline-Bausteine, alles ueber Claude.
DB (additiv, idempotent):
- Tabellen incident_events + fact_check_runs (+ Indizes)
- Funktion log_incident_event()
- Spalten incidents.summary_at, incidents.executive_summary, articles.geoparsed_at
agents/stage_runners.py (neu): isolierte Bausteine analyze + factcheck auf dem
vorhandenen Bestand, komplett neu, Historie bleibt (Snapshot/fact_check_runs).
orchestrator.py: collect_only in die Lane-Multi-Tenant-Queue eingebaut (enqueue_refresh
4-Tupel, _worker-Entpacken, Multi-Pass-Gate, _run_refresh-Signatur + Analyse/FC-Skip).
Zusaetzlich summary_at beim Schreiben des Lagebilds gestempelt (Studio-Freshness).
incidents.py: Endpunkte POST /{id}/run/{stage} (collect|analyze|factcheck),
GET /{id}/run-status, GET /{id}/freshness, GET /{id}/factcheck-runs[/{run_id}].
Busy-Check auf Online-Orchestrator (_current_tasks), Tenant-Zugriffspruefung.
Kein lokaler LLM-Unterbau, kein lokales Modell. /ask + /events folgen in Phase 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Das blockierende Fortschritts-Overlay (.progress-overlay.blocking) liegt
position:fixed über dem gesamten Viewport und schluckt damit auch die
Klicks auf den globalen Header. Beim ersten Durchlauf eines Falls waren so
Barrierefreiheit, Theme-Wechsel, Konto-Menü inkl. Org-Switcher und
Abmelden nicht erreichbar.
Neue Body-Klasse first-refresh-blocking hebt den Header per z-index über
das Overlay, solange dieses blockierend sichtbar ist. UI._syncHeaderAccess()
spiegelt den Overlay-Zustand an allen Stellen, die das Overlay ein- oder
ausblenden.
Die fallbezogenen Aktionen (Aktualisieren, Bearbeiten, ...) bleiben
unverändert gesperrt: dafür sorgen weiterhin #incident-view.refresh-blurred
und .incident-header-actions.first-refresh-locked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ersetzt die globale Warteschlange mit einem einzelnen Worker durch
Per-Tenant-Lanes: jede Organisation bekommt eine eigene Queue und einen
eigenen Worker-Task, die unabhaengig und parallel laufen. Lanes werden
lazy angelegt und bei Leerlauf (60s) wieder beendet.
- Skalarer Zustand (_current_task, _cancel_event) -> Maps pro incident_id
(_current_tasks, _cancel_events); _queued_ids/_cancel_requested bleiben
global (IDs sind lane-uebergreifend eindeutig).
- enqueue_refresh/cancel_refresh: Signatur unveraendert, routen intern per
tenant_id in die passende Lane. Oeffentliche Lagen (tenant_id NULL) in
fester PUBLIC_LANE.
- Optionales Ventil ORCHESTRATOR_MAX_PARALLEL (Default 0 = unbegrenzt)
begrenzt bei Bedarf gleichzeitige Recherchen ueber alle Lanes.
- /refreshing-Endpoint auf _current_tasks-Map umgestellt, Response-Format
unveraendert (pro Tenant laeuft weiterhin hoechstens eine Lage).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Die IONOS-Farm (smtp.ionos.de, 10 Backends im DNS-Round-Robin) hatte am
10.07. teils tote/lahme Backends - ein einzelner Sendeversuch schlug dann
fehl (Timed out waiting for server ready message). Jeder Retry oeffnet
eine neue Verbindung und trifft per Round-Robin ein anderes Backend.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>