Im Anlege- und Bearbeiten-Dialog einer Lage laesst sich jetzt waehlen, ob sie
ueber den heutigen Weg (Anthropic) oder den EU-Weg (Bedrock Frankfurt + staan)
laeuft; Standard bleibt die Vorgabe der Organisation. Dazu ai_backend in den
Schemas und den Lagen-Endpunkten (Create, Update, Ausgabe), Leerstring setzt
bewusst auf Standard zurueck. In der Lagen-Kopfzeile zeigt ein Kennzeichen
'EU' an, dass die Verarbeitung europaeisch laeuft.
UI-Sync: bewusst NUR online. Der Lokal-Fork hat kein Bedrock-Backend, dort
waere das Feld funktionslos (wie X-Zugaenge und Studio-Gating).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
- POST /api/sources/upload-pdf: tenant-scoped Upload, gleiche Speicher-
Konvention wie der Verwaltungs-Endpoint (<dirname(DB)>/pdfs/{sha}.pdf).
Duplikat-Check beruecksichtigt globale Quellen.
- dashboard.html: +PDF-Button in der Quellenverwaltungs-Toolbar +
eigenes Modal modal-pdf-upload (closeModal-Quotes via ').
- app.js: App.openPdfUpload + _bindPdfUploadFormOnce (Submit nur einmal
binden).
- api.js: API.upload(path, formData) Helper analog Verwaltung.
STAGING_MODE deaktivierte bisher den Org-Switcher im Frontend, weil keine
Demo-Besucher zwischen Mandanten hoppen sollten. Mit eng_demo brauchen
wir aber bewussten Zugriff auf alle Sprach-Mandanten via Switcher. Der
Token-Budget-Schutz (license_service._staging_mode) bleibt unveraendert.
Backend:
- UserMeResponse um output_language (de | en) erweitert.
- /auth/me liefert die Org-Sprache aus organization_settings.
Frontend:
- Neu: static/js/i18n.js mit T(key)-Helper, I18N.load(lang) und
applyDom() ueber data-i18n + data-i18n-attr.
- Neu: static/i18n/de.json + en.json (sichtbare Bereiche: Sidebar,
Header, Modal-Titel, Faktencheck-Status, Refresh-Hinweise).
- dashboard.html: i18n.js Script-Tag vor api.js, data-i18n auf den
prominenten Strings (Abmelden, + Neuer Fall, Alle/Eigene, Sidebar-
Sektionen, Bericht exportieren, Faktencheck-Tab, Lage anlegen).
Tutorial.init() entfernt aus DOMContentLoaded.
- components.js: factCheckLabels/Tooltips/ChipLabels als Getter ueber
T() mit DE-Fallbacks.
- app.js: vor Setup wird I18N.load(user.output_language) aufgerufen und
applyDom() ausgefuehrt. Tutorial.init() laeuft nur bei lang === de.
Phase 6 von 8 (eng_demo / Org-Sprache).
- email_utils/templates.magic_link_login_email + incident_notification_email
nehmen jetzt lang Parameter (de | en).
- routers/auth.request_magic_link zieht Sprache aus der Org des Users und
uebergibt sie ans Template.
- agents/orchestrator._send_email_notifications_for_incident lokalisiert
ebenfalls und gibt lang an incident_notification_email durch.
- DB-Notification-Texte (refresh_summary, new_articles) sind in der
Pipeline org-sprach-relativ (englische Variante: "3 new articles", etc.).
Status-Change-Notifications: Codes (confirmed/contradicted) bleiben, FE
uebersetzt sie in Phase 6.
Phase 5 von 8 (eng_demo / Org-Sprache).
Endpoints unter /api/sources/classification/* weg, Service-Module (source_classifier, external_reputation) gelöscht. Quellen-Modal verliert Tab Klassifikations-Review, Klassifikations-Section in der Edit-Form, alle Bulk-Buttons (Sync, Klassifikation starten, Bulk-Approve). API-Methoden in api.js entfernt, alignment-Helper raus, saveSource entschlackt.
Read-Only bleibt: Filter-Dropdowns über der Quellenliste (Politik, Medientyp, Reliability, Externe Reputation, Alignment) und Inline-Badges (_renderClassificationBadges + Label-Maps in components.js). Kunde sieht nur freigegebene Werte.
GET /api/sources liefert weiter Klassifikations-Felder + alignments für die Anzeige; SourceCreate/SourceUpdate akzeptieren keine Klassifikations-Felder mehr.
Bulk-Klassifikations-Skripte entfernt — Pflege läuft über Verwaltungs-UI.
- src/services/source_classifier.py: classify_source(db, id) ruft Haiku mit
strukturiertem Prompt (4 Achsen + state_affiliated + country + Konfidenz)
und schreibt Vorschlaege in proposed_*-Spalten. bulk_classify(db, limit)
iteriert sequenziell ueber unklassifizierte Quellen.
- API-Endpoints (alle hinter Auth, globale Quellen nur fuer org_admin):
- GET /api/sources/classification/stats
- GET /api/sources/classification/queue
- POST /api/sources/{id}/classification/approve (proposed_* -> echte Felder)
- POST /api/sources/{id}/classification/reject (proposed_* loeschen)
- POST /api/sources/{id}/classification/reclassify (sofort, ~3-5s)
- POST /api/sources/classification/bulk-classify (BackgroundTask)
- scripts/migrate_sources_classification.py: CLI-Wrapper fuer Bulk-Migration
zur einmaligen Erstbestueckung aller Bestandsquellen.
Sample-Test auf Staging steht aus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wenn STAGING_MODE=1 (oder true/yes) in der .env gesetzt ist:
- check_license() liefert immer unlimited_budget=True -> kein Token-Budget-Hard-Stop,
egal was in der DB steht.
- /api/auth/me liefert is_global_admin=False -> Frontend ruft _initOrgSwitcher
nicht auf, Org-Switcher-Section bleibt versteckt.
Nur in ~/AegisSight-Monitor-staging/.env gesetzt; Live-.env hat das Flag
nicht, daher dort unverändertes Produktiv-Verhalten.
- check_license() liefert jetzt unlimited_budget, credits_total, credits_used,
read_only_reason. Bei nicht-unlimited UND credits_used >= credits_total wird
status=budget_exceeded, read_only=True gesetzt.
- require_writable_license blockiert mit 403 + X-License-Status-Header je nach Reason.
- /api/auth/me liefert read_only_reason und unlimited_budget; credits_percent_used
wird nicht mehr auf 100 gekappt (echte Prozente).
- Frontend: Banner-Text dynamisch je nach reason (budget_exceeded/expired/...).
Refresh-Button bei read_only deaktiviert + Tooltip. Globaler 403-Handler in
api.js: bei X-License-Status -> Banner + Toast aktualisieren.
1. Faktencheck immer vollständig
PDF-Export hatte im scope=report einen [:20]-Cap, der vollständige
Faktencheck wurde nur bei scope=full gerendert. Jetzt ungekürzt
überall, sortiert chronologisch absteigend (DB-Sortierung).
2. Status-Labels aus Frontend übernommen
FC_STATUS_LABELS hatte nur 4 Werte; in der DB existieren aber 7+
(confirmed/unconfirmed/contradicted/developing/established/
unverified/disputed). Folge: "contradicted" und drei weitere
wurden auf englisch ausgegeben. Jetzt 1:1 vom Monitor-UI:
contradicted → "Widerlegt"
developing → "Unklar"
established → "Gesichert"
unverified → "Ungeprüft"
3. Adhoc-Export: Neueste Entwicklungen statt Executive Summary
Bei Live-Monitoring-Lagen ist die generische Executive Summary
weniger aussagekräftig als die kompakten "Neueste Entwicklungen"-
Bullets. Endpoint nutzt jetzt:
- adhoc + latest_developments vorhanden → latest_developments
(Markdown -> HTML konvertiert)
- adhoc + leer → cached/generierte Executive Summary (Fallback)
- research → unverändert Executive Summary
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Em-dashes und Umlaut-Umschreibungen aus den Pipeline-Aenderungen
entfernt: Tooltip-Texte, HTML-Empty-State, JS-Kommentare,
Count-Status-Platzhalter, Orchestrator-Kommentare und CSS-Kommentare.
Anstelle von typografischen Gedankenstrichen werden jetzt Kommas oder
Punkte gesetzt, "uebersprungen" -> "uebersprungen" mit echtem Umlaut,
"laeuft" usw. analog. UI-Text "— Refresh starten" wird zu zwei
Saetzen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Tab "Analysepipeline" zwischen Faktencheck und Quellenuebersicht.
Zeigt 9 Verarbeitungsschritte als n8n-artige Blockkette: Quellen sichten,
Nachrichten sammeln, Doppeltes filtern, Relevanz bewerten, Orte erkennen,
Lagebild verfassen, Fakten pruefen, Qualitaetscheck, Benachrichtigen.
- Backend: refresh_pipeline_steps-Tabelle persistiert pro Refresh+Pass die
Status- und Zahlen-Werte. pipeline_tracker.py kapselt Start/Done/Skip/Error
inkl. WebSocket-Broadcast (Event-Typ pipeline_step). 9 Hooks im Orchestrator
speisen die Anzeige.
- API: GET /api/incidents/{id}/pipeline liefert Definition + letzten Stand
(Zahlen aus letztem Refresh, Multi-Pass-Konsolidierung).
- Frontend: pipeline.js rendert Vollbild-Blockkette mit pulsierendem Glow am
aktiven Block, animierten Pfeilen bei Datenfluss, Haekchen am fertigen Block.
Hover-Tooltip mit Erklaerung in Nutzersprache, Klick oeffnet Detail-Popup.
Bei Research-Lagen leuchtet ein Schleifen-Pfeil pro Mehrfach-Durchlauf auf.
Mini-Variante (nur Icons) im Refresh-Progress-Popup.
- CSS: Light/Dark-Theme-fest, dezenter Circuit-Hintergrund (5% Opacity),
Mobile-vertikale Stapelung unter 900px, prefers-reduced-motion respektiert.
- Uebersprungene Schritte (z.B. Geoparsing ohne neue Artikel) werden
ausgeblendet, brandneue Lagen ohne Refresh zeigen Hinweis.
Tooltips bewusst in normaler Sprache ohne Internas (keine Modellnamen,
keine Toolnamen, keine Phasen-Labels).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ENHANCE_PROMPT_ADHOC und ENHANCE_PROMPT_RESEARCH: Umschreibungen durch
echte Umlaute ersetzt (fuer -> fuer, praezises -> praezises, ...). Behebt
den Widerspruch, dass der Prompt "echte Umlaute verwenden" forderte,
die Anweisung selbst aber ae/oe/ue/ss nutzte.
- call_claude() bekommt neuen timeout-Parameter. None = Fallback auf
CLAUDE_TIMEOUT (1800s), sonst Override in Sekunden. asyncio.wait_for
und die cancel-aware Variante nutzen durchgaengig den effective_timeout.
- Enhance-Endpoint ruft call_claude mit timeout=60 auf (Haiku-Single-Shot,
vorher global 1800s).
- chat.py _call_claude_chat: Timeout von 60s auf 120s erhoeht (Chat-Antworten
koennen etwas laenger dauern, haben aber keinen Anspruch auf 30 Min).
- Neue Exception-Klasse ClaudeCliError(error_type, message) in claude_client.py
mit Kategorien rate_limit / auth_error / timeout / cli_error.
- _classify_cli_error() als geteilter Klassifikator (Keywords fuer Rate-Limit
und Auth-Fehler wie "does not have access", "login again").
- call_claude() erkennt jetzt auch is_error=true im JSON bei returncode=0
(Hauptursache des Ausfalls vom 22.04.: CLI liefert "Your organization
does not have access" mit is_error=true statt Exit-Code).
- Orchestrator: ClaudeCliError mit rate_limit/timeout als transient behandelt
(3 Retries mit Backoff 0s/120s/300s). auth_error/cli_error brechen sofort
ab ohne Retry. Behebt den bestehenden Bug, dass Rate-Limit-Fehler gar nicht
retried wurden.
- routers/incidents.py Enhance-Endpoint: ClaudeCliError wird auf
503 (auth_error) / 429 (rate_limit) gemappt, TimeoutError auf 504.
- routers/chat.py _call_claude_chat(): wirft jetzt ClaudeCliError statt
generischem RuntimeError. Chat-Endpoint mappt auth_error auf 503.
- Frontend: neue ApiError-Klasse in api.js mit status+detail.
generateDescription() in app.js zeigt differenzierte Toasts nach
HTTP-Status (503/429/504/403).
- dashboard.html: Cache-Bust api.js + app.js auf v=20260423a
- Neuer Helper charge_usage_to_tenant() in services/license_service.py:
UPSERT in token_usage_monthly und Credits-Abzug aus licenses.credits_used.
Wiederverwendbar fuer alle Claude-Call-Verursacher.
- Orchestrator: Inline-Buchungslogik (35 Zeilen) durch Helper-Aufruf ersetzt.
- routers/incidents.py POST /enhance-description: require_writable_license
statt get_current_user, db_dependency hinzugefuegt, Credits-Buchung mit
source="enhance" nach jedem Claude-Call.
- routers/chat.py POST /: analog require_writable_license + Credits-Buchung
mit source="chat". _call_claude_chat() gibt jetzt zusaetzlich ClaudeUsage
zurueck.
Abgelaufene/gesperrte Lizenzen koennen damit keine Haiku-Calls mehr ausloesen,
und alle Kosten werden konsistent auf Tenant-Ebene verbucht.
Bei Research-Multi-Pass (3 Durchlaeufe) und bei Retry-Versuchen wird
pro Pass/Retry ein neuer refresh_log-Eintrag mit frischem started_at
angelegt. /incidents/refreshing gab dadurch beim Reload den spaeteren
started_at zurueck statt des urspruenglichen Session-Starts — der
Frontend-Timer sprang auf 0:00 zurueck.
Orchestrator traegt jetzt _current_task_started_at in-memory, gesetzt
beim Queue-Pickup und geraeumt im finally. /incidents/refreshing liefert
diesen Session-Start fuer den aktuell laufenden Task (Fallback: letzter
refresh_log-Eintrag, falls der Server zwischenzeitlich neu gestartet
wurde).
Ersetzt den rohen JOIN ueber article_locations x articles (bei Iran
21.814 Zeilen, 11 MB Payload) durch drei kleine aggregierte Queries:
1. Orte per GROUP BY (name, lat, lon) — direkt die Ergebnismenge.
2. Kategorien pro Ort per GROUP BY fuer die dominante Kategorie.
3. Sample-Artikel (max. 10 pro Ort) via ROW_NUMBER() OVER PARTITION BY.
Response-Shape unveraendert ({category_labels, locations: [...]}), keine
Frontend-Aenderung noetig. Priorisierung primary > secondary > tertiary >
mentioned bleibt erhalten.
Erwarteter Effekt: Iran-Locations 11 MB -> <500 KB; Query-Zeit sinkt
zusaetzlich, da kein 21k-Zeilen-JOIN mehr materialisiert werden muss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
- GET /{id}/articles paginiert jetzt per limit/offset (Default 500,
Max 1000) und unterstuetzt optionalen search-Parameter (LIKE ueber
headline/source/content). Response-Shape: {total, articles}.
- Neuer Endpunkt GET /{id}/articles/sources-summary liefert pro Quelle
{source, article_count, languages} sowie language_counts gesamt —
serverseitige Aggregation, unabhaengig von Artikel-Paginierung.
- Neuer Endpunkt GET /{id}/articles/timeline-buckets?granularity=hour|day|week|month
aggregiert Artikel + Snapshot-Counts pro Zeitbucket (fuer spaetere
Timeline-Zaehler ueber die volle Historie).
- database.py: Index idx_articles_incident_collected auf
(incident_id, collected_at DESC) fuer schnelleres ORDER BY + Pagination.
Frontend:
- api.js: getArticles({limit, offset, search}),
getArticlesSourcesSummary(), getArticlesTimelineBuckets().
- app.js: loadIncidentDetail laedt erste Seite (500 Artikel), startet
_loadSourcesSummary parallel und zieht restliche Artikel
batchweise (500er Bloecke) im Hintergrund nach, bis _currentArticlesTotal
erreicht ist. rerenderTimeline nach jedem Batch.
- components.js: renderSourceOverviewFromSummary(data) rendert aus
Aggregat-Daten (ersetzt clientseitige Zaehlung ueber geladene Artikel).
Hintergrund: /articles lieferte bei der Iran-Lage 22 MB (17.286 Artikel
mit SELECT *). Die Erstantwort sinkt auf ~650 KB (500 Artikel), weitere
werden progressiv im Hintergrund nachgeladen. Quellenuebersicht zeigt
dank Aggregat-Endpunkt sofort alle Quellen + Sprachen komplett.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
- GET /{id}/snapshots liefert nur noch schlanke Shape (Metadaten +
SUBSTR(summary,1,300) AS summary_preview), kein Volltext, kein sources_json.
- Neuer Endpunkt GET /{id}/snapshots/{snapshot_id} fuer Volltext-Lazy-Load.
- Neuer Endpunkt GET /{id}/snapshots/search?q=... fuer serverseitige
Volltextsuche ueber alle Snapshots einer Lage.
Frontend:
- api.js: getSnapshot() und searchSnapshots() ergaenzt.
- app.js: _snapshotFullCache, Volltext wird beim Aufklappen eines
Snapshot-Eintrags per lazyLoadSnapshotDetail() nachgeladen und gecacht.
- Suche ueber Snapshots filtert weiterhin clientseitig ueber summary_preview.
Hintergrund: Bei grossen Lagen (Iran-Lage: 347 Snapshots) fiel die
Snapshots-Listenantwort mit Volltext-Summaries auf ~54 MB. Die Liste
faellt damit auf ~150 KB; Volltexte werden nur on-demand geladen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die oeffentliche API (/api/public/lagebild) liefert jetzt latest_developments
als Feld im Incident-Objekt. Damit kann der Website-Sync das Feld in
current.json und summary.json uebertragen, und die Lagebild-Seite kann einen
Tab Neueste Entwicklungen rendern.
- Alle sichtbaren "Executive Summary"-Bezeichnungen durch "Zusammenfassung" ersetzt
(PDF/DOCX-Überschrift, Dateiname, Fallback-Texte)
- Deckblatt-Farben von #888/#aaa auf Navy #0a1832 geändert für
bessere Lesbarkeit beim Druck (PDF-Template + DOCX)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Beide Prompts (Research + Adhoc) definieren jetzt explizit:
- Modell ist Recherche-Planer, nicht Faktenbehaupter
- Thema muss nicht bekannt oder verifiziert werden
- Briefing IMMER erstellen, keine Rueckfragen/Disclaimer
- Recherche-Schwerpunkte praxisnaeher formuliert
Behebt sporadische Verweigerungen bei unbekannten Faellen.
_build_lagebild_response() liefert jetzt Top-3-Artikel (neueste)
pro Location für Karten-Popups mit klickbaren Quellen-Links.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared-Logik extrahiert (_build_lagebild_response, _get_snapshot_response).
Neue Endpunkte:
- GET /api/public/lagebild/{incident_id} für beliebige öffentliche Lagen
- GET /api/public/lagebild/{incident_id}/snapshot/{snapshot_id}
Bestehende Iran-Endpunkte bleiben abwärtskompatibel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API /incidents/refreshing gibt jetzt auch queued IDs mit Position
und den aktuell laufenden Task zurueck.
Frontend nutzt started_at aus der API fuer Timer-Wiederherstellung.
Queued Lagen werden mit korrekter Position wiederhergestellt.
Aktiv laufender Task wird als researching angezeigt statt queued.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ermoeglicht dem Global Admin (is_global_admin Flag) zwischen
Organisationen zu wechseln. Neue Endpoints: GET /api/auth/organizations,
POST /api/auth/switch-org. Org-Dropdown im Header-Menue, nur fuer
Global Admin sichtbar. Komplett herausnehmbar (Flag + Code-Bloecke).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Neuer raw_text Parameter in call_claude() umgeht den JSON-System-Prompt.
Haiku gibt direkt lesbaren Text zurück statt JSON-Objekte.
Gesamtes JSON-Parsing (_json_to_text, Markdown-Strip) entfernt.
Claude CLI gibt bei tools=None oft Antworten in Markdown-Code-Blocks
zurueck (dreifache Backticks json...Backticks). Diese werden jetzt vor
dem JSON-Parse per Regex entfernt.
Haiku gibt oft tief verschachtelte JSON-Objekte zurück statt reinem
Text. Neue _json_to_text() Funktion konvertiert beliebige JSON-Strukturen
rekursiv in lesbaren Fliesstext mit Aufzaehlungen.
call_claude erzwingt bei tools=None JSON-Output per System-Prompt.
Haiku wrapped den generierten Text dann in ein JSON-Objekt.
Fix: JSON parsen und erstes String-Feld extrahieren.
KI-gestütztes Prompt Enhancement: Button generiert per Haiku aus dem
Titel eine strukturierte Beschreibung. Unterscheidet zwischen
Live-Monitoring (kompakte Vorfallsbeschreibung) und Recherche
(strukturiertes Briefing mit Schwerpunkten und Suchbegriffen).
- Neuer Endpoint POST /api/incidents/enhance-description
- Button erscheint für beide Lage-Typen, aktiv ab 3 Zeichen Titel
- Info-Hinweis wechselt je nach Typ mit Beispiel
- Spinner-Animation während der Generierung