Promote develop → main (2026-05-09 10:57 UTC) #21

Zusammengeführt
IntelSight_Admin hat 15 Commits von develop nach main 2026-05-09 12:57:14 +02:00 zusammengeführt
Mitwirkender

Automatischer Promote-Trigger via AegisSight Promote-UI. Commits: 15

Automatischer Promote-Trigger via AegisSight Promote-UI. Commits: 15
IntelSight_Admin hat 15 Commits 2026-05-09 12:57:13 +02:00 hinzugefügt
Bislang liefen factcheck + analyze parallel via asyncio.gather. Folge:
Lagebild konnte Aussagen treffen, die der Faktencheck im selben Refresh als
contradicted markiert. Inkonsistenz zwischen Lagebild-Tab und Faktencheck-
Tab; im PDF/DOCX-Export schon kritisch.

Variante 1 aus der Diskussion: strikt sequenziell, mit Fallback bei
Faktencheck-Fail (Refresh bricht NICHT ab, Lagebild laeuft dann ohne
Faktenkontext wie bisher, ein Logeintrag dokumentiert den Fallback).

Aenderungen:
- analyzer.build_fact_context_block(): neuer Helper, baut den
  GEPRUEFTE-FAKTEN-Block aus existing_facts + neuen/aktualisierten
  Fakten. Status-Domaenen adhoc/research vereinheitlicht zu Bestaetigt /
  Umstritten / Unbestaetigt / Entwicklung. Max 20 Fakten, sortiert nach
  Status-Prioritaet desc und sources_count desc. Bei leerer Eingabe
  leerer String -> Fallback-Pfad.
- analyzer.analyze() / analyze_incremental(): neuer Optional-Parameter
  fact_context_block (default leer, Backward-Compat). 4 Prompt-Templates
  bekommen {fact_context_block}-Platzhalter sowie eine AUSSAGE-DISZIPLIN-
  Sektion: bestaetigte Fakten als Geruest, Umstrittenes explizit machen,
  Unbestaetigtes klar einordnen, kein Spekulieren ueber ungedecktes.
- orchestrator: asyncio.gather durch sequenzielle Logik ersetzt.
  Faktencheck zuerst, Pipeline-Step 6 done direkt nach dem Aufruf
  (count_value ist Schaetzung; finale DB-Zahlen stehen spaeter). Lagebild
  danach (Step 7) mit fact_context_block. _do_analysis-Closure um den
  Parameter erweitert, kein toter Inline-Block.
- spaeteres _pipe_done(factcheck) entfernt -- der Step wird jetzt frueher
  geschlossen, der spaetere Persistierungsblock laesst ihn unberuehrt.

UI-Pipeline zeigt automatisch sequenzielle Aktivitaet statt beide Steps
gleichzeitig -- keine Frontend-Aenderung noetig.

Latenz pro Refresh steigt um die factcheck-Dauer. Bewusst akzeptiert:
Konsistenz vor Geschwindigkeit.
- Neue sources-Spalten: political_orientation (7+2 Stufen), media_type (20),
  reliability (5+1), state_affiliated, country_code, classification_source,
  classified_at sowie proposed_*-Spalten fuer LLM-Vorschlaege.
- Neue source_alignments-Tabelle fuer Mehrfach-Tagging geopolitischer Naehe
  (prorussisch, proiranisch, prowestlich, ...).
- API-Filter: ?political_orientation, ?media_type, ?reliability,
  ?state_affiliated, ?alignment.
- create/update_source nehmen alignments[] entgegen und setzen
  classification_source automatisch auf 'manual' bei Klassifikations-Edits.

Backwards-kompatibel: bestehendes bias/language/category bleibt unveraendert,
Default fuer Bestandsquellen ist classification_source = 'legacy'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Quellen-Modal: 4 neue Filter (Politik, Medientyp, Reliability, Alignment).
- Edit-Form: Selects fuer political_orientation/media_type/reliability,
  Multi-Select-Chips fuer alignments, Toggle state_affiliated, Country-Code-Input.
- renderSourceGroup: Politik-Badge mit DACH-Farbskala (rot=L, blau=R),
  Reliability-Punkt (gruen→rot), Alignment-Tags, state-affiliated-Indikator.
  Tooltip um alle 4 Achsen erweitert.
- CSS-Block fuer alle neuen Badge-/Chip-Styles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
- Tab-Schalter im Quellen-Modal: "Quellenliste" vs. "Klassifikations-Review"
  (Review-Tab nur fuer org_admin sichtbar, mit Pending-Counter-Badge).
- Review-Karten zeigen Diff aktueller Wert -> LLM-Vorschlag pro Achse,
  Konfidenz-Indikator (gruen/gelb/rot), LLM-Begruendung, Buttons fuer
  Uebernehmen / Verwerfen / Neu klassifizieren.
- Toolbar: Konfidenz-Filter, "Klassifikation starten" (Bulk im Hintergrund),
  "Alle >= 0.85 genehmigen" (Bulk-Approve).
- API-Wrapper in api.js fuer alle 6 neuen Endpoints + erweiterte listSources-Filter.
- Backend-Endpoint POST /api/sources/classification/bulk-approve (Admin-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Externe Datenquellen (kostenlos, Open Data) ergaenzen die LLM-geschaetzte
Reliability-Achse mit objektiven Signalen:

- IFCN-Signatories (raw.githubusercontent.com/IFCN/verified-signatories):
  Plain-Text-Liste anerkannter Faktencheck-Organisationen.
- EUvsDisinfo (Zenodo CSV): Pro-Kreml-Desinformations-Datenbank.

Schema-Erweiterung:
- ifcn_signatory, eu_disinfo_listed, eu_disinfo_case_count,
  eu_disinfo_last_seen, external_data_synced_at.

Service src/services/external_reputation.py:
- sync_ifcn_signatories(), sync_eu_disinfo(), apply_reputation_overrides(),
  sync_all() mit Domain-Normalisierung (lowercase, ohne www., ohne Schema).

Reliability-Override-Regeln (laufen nach Approve und manuellem Sync):
- ifcn_signatory=1 -> reliability=sehr_hoch
- eu_disinfo_case_count >= 5 -> reliability=sehr_niedrig
- eu_disinfo_case_count >= 1 -> Reliability eine Stufe runter (max niedrig)

API: POST /api/sources/external-reputation/sync (Admin, BackgroundTask).
Filter: ?ifcn_signatory=true, ?eu_disinfo_listed=true.

UI:
- Filter-Dropdown "Externe Reputation" im Quellen-Modal.
- Badges: gruenes "IFCN" und rotes "EU-Desinfo (n)".
- Tooltip macht Reliability-Quelle transparent: "(IFCN-Faktenchecker)",
  "(EU-Desinfo, n Faelle)" oder "(LLM-Schaetzung)".
- "Externe Daten syncen"-Button im Review-Toolbar (Admin-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
False positive bei sync_eu_disinfo: t.me wurde als Quelle markiert, weil
EUvsDisinfo anonyme Telegram-Posts unter der Plattform-Domain aggregiert.
Eine Allowlist von Plattform-Domains schliesst diese Falle aus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beide Files hatten Doppel-Encoded UTF-8 in Docstrings, Kommentaren und
Prompt-Strings (z.B. "prüft" statt "prüft", "Vorschläge" statt
"Vorschläge"). ftfy hat das automatisch repariert.

Hauptauswirkungen:
- Logs sind jetzt mit echten Umlauten lesbar
- Claude/Haiku-Prompts in source_suggester.py (Quellen-Vorschlaege via KI)
  bekommen jetzt korrekte deutsche Umlaute - sollte bessere Antworten geben

Daneben hat ftfy line-endings normalisiert, daher der grosse Diff in
source_health.py - inhaltlich nur Mojibake-Reparatur.

Verifiziert mit:
  grep -cE "ä|ö|ü|ß|Ä|Ö|Ü" src/services/*.py
  -> 0 Treffer
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.
Beim Anlegen einer neuen Lage ist der Schalter "Internationale Quellen einbeziehen"
ab jetzt standardmaessig DEAKTIVIERT.

Hintergrund: Bei lokalen DACH-Ereignissen (Tier-/Personenstoryen wie
"Buckelwal timmy") hat der "international=True"-Default zu schlechteren
Treffern gefuehrt, weil Claude in Deutsch UND Englisch suchte und die
englische Berichterstattung haeufig fehlt. Excluded-Sources- und
Boulevard-Filter haben das Problem zusaetzlich verschaerft.

Aenderungen:
- src/models.py IncidentCreate.international_sources: bool=True -> False
  (nur das Pydantic-Default beim Create-Endpoint - IncidentResponse/Incident
  bleiben True, weil das die DB-Werte bestehender Lagen reflektiert)
- src/static/dashboard.html: <input id="inc-international" checked> -> ohne checked
  (UI-Default ist jetzt unchecked, User muss bewusst aktivieren fuer
  internationale Lagen)
- Tooltip-Text ergaenzt: "Deaktiviert (Standard): ... empfohlen fuer DACH-Lagen."

Bestandslagen sind nicht betroffen - DB-Schema-Default INTEGER DEFAULT 1
bleibt unveraendert, fuer alle existierenden Lagen behaelt international
seinen aktuellen Wert.

Damit ist die Buckelwal-Diagnose komplett geloest:
- Bug 1 (rss_parser min_matches adaptiv) seit a08df3d auf main
- Bug 2 (Eigennamen-Pflicht-Keywords) seit e83f80d auf main
- Bug 3 (international-Default) jetzt auf develop, gleich Cherry-pick auf main
Phase 2 hatte die Verbesserungen nur in der Verwaltung
(src/shared/services/source_health.py). Der Daily-Health-Check laeuft aber
im Monitor-Backend (Cron 04:00 UTC) und nutzte deshalb weiter den alten
Code - Folge:

- Tenant-Quellen wurden NIE gecheckt (0 Eintraege in source_health_checks
  fuer tenant_id IS NOT NULL).
- source_health_history blieb leer.

Diese Aenderung holt die Phase-2-Logik in den Monitor:
- services/source_health.py: Verwaltung-Version 1:1 uebernommen
  (tenant_id-Filter weg + History-Save vor DELETE + UA/Timeout aus config).
- config.py: HEALTH_CHECK_USER_AGENT + HEALTH_CHECK_TIMEOUT_S ergaenzt.

Manueller Test auf Staging-Monitor:
  283 Quellen geprueft, 253 Issues, 61 davon Tenant-Quellen.
  History 0 -> 458 Eintraege.

Damit ist die shared/-LOCKED-FILES-Markierung in der Verwaltung obsolet -
beide Repos haben jetzt den gleichen Code.
Telegram-Quellen mit url=t.me/kanal (ohne https:// Prefix) liessen httpx
mit "ValueError: unknown url type" crashen. Fix: vor dem Request
https:// vorne anhaengen wenn kein Schema vorhanden ist.

Beobachtet auf Live: 110 Health-Errors, davon einige Telegram-Kanaele
mit "ValueError: unknown url type:" als Fehlermeldung.
Pro Quelle ein Feld sources.fetch_strategy (default | googlebot | paywall | skip):
- default: normaler UA, Retry mit Googlebot bei 403/406/429.
- googlebot: direkt mit Googlebot-UA (fuer SEO-freundliche Sites).
- paywall: Anfrage via removepaywalls.com (fuer Spiegel+/SZ+/FT etc.).
- skip: Health-Check ueberspringen (bekannte unerreichbare Quellen wie Login-only).

Pre-Flagging in der Migration: FT/WSJ/NZZ/Handelsblatt/WiWo -> paywall,
Rheinische Post/Verfassungsschutz -> googlebot.

(Test mit den vier prominent fehlerhaften Quellen zeigt: FT/RP/Verfassungsschutz
sind besonders streng, gehen auch nicht ueber Googlebot/removepaywalls durch.
Fuer milder restriktive Quellen wirkt der Retry-Mechanismus.)
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.
removepaywall.com liefert HTML (Article-Renderer), nicht XML - der
Feed-Validity-Check schlug daher fehl mit "Kein gueltiger RSS/Atom-Feed".

Korrektur:
- paywall: Feed-URL direkt mit Browser-UA laden (kein URL-Rewrite).
- Bei paywall + 4xx: status=warning (erwartbar), Feed-Validity skippen.
- removepaywall.com bleibt im Researcher-Prompt fuer Article-Inhalte
  (das ist der korrekte Use-Case).
IntelSight_Admin hat Commit ed057fa6f5 in main 2026-05-09 12:57:14 +02:00 gemerged
Anmelden, um an der Diskussion teilzunehmen.
Keine Reviewer
Kein Label
1 Beteiligte
Nachrichten
Fällig am
Kein Fälligkeitsdatum gesetzt.
Abhängigkeiten

Keine Abhängigkeiten gesetzt.

Referenz: AegisSight/AegisSight-Monitor#21