fix: Quellenlinks mit Buchstaben-Suffix ([389a] etc.) korrekt verlinken

Probleme:
- Frontend-Regex matchte nur reine Zahlen, nicht [389a]-Style Refs
- 17 alphanumerische Quellen im Irankonflikt blieben unverlinkt
- Orchestrator-Validierung erkannte diese Refs nicht als fehlend

Fixes:
- Frontend: Regex erweitert auf [\d+a-z?], Vergleich mit String und Number
- Orchestrator: Validierung erkennt jetzt auch alphanumerische Refs
- Analyzer-Prompts: Explizite Anweisung, nur ganze Zahlen als Nr zu verwenden
- 822a und 859a in Irankonflikt sources_json nachgetragen
- Cache-Buster aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-16 11:34:55 +01:00
Ursprung 599102740a
Commit bbd4821011
4 geänderte Dateien mit 23 neuen und 12 gelöschten Zeilen

Datei anzeigen

@@ -38,7 +38,7 @@ REGELN:
- Wenn eine Quelle eine erkennbare Ausrichtung hat (z.B. pro-russisch, pro-iranisch, staatsnah, rechtsextrem), muss dies im Fliesstext erwaehnt werden, damit der Leser die Information einordnen kann. Beispiel: "Laut dem pro-russischen Telegram-Kanal Rybar..." oder "Die iranische Nachrichtenagentur Fars meldete..." oder "Der rechtsextreme Kanal Compact behauptete..." - Wenn eine Quelle eine erkennbare Ausrichtung hat (z.B. pro-russisch, pro-iranisch, staatsnah, rechtsextrem), muss dies im Fliesstext erwaehnt werden, damit der Leser die Information einordnen kann. Beispiel: "Laut dem pro-russischen Telegram-Kanal Rybar..." oder "Die iranische Nachrichtenagentur Fars meldete..." oder "Der rechtsextreme Kanal Compact behauptete..."
- Quellen immer mit [Nr] referenzieren - Quellen immer mit [Nr] referenzieren
- Jede verwendete Quelle MUSS im sources-Array aufgelistet sein - Jede verwendete Quelle MUSS im sources-Array aufgelistet sein
- Nummeriere die Quellen fortlaufend ab [1] - Nummeriere die Quellen fortlaufend ab [1]. Verwende NUR ganze Zahlen als Quellennummern (z.B. [389], [390]), KEINE Buchstaben-Suffixe wie [389a]
- Ältere Quellen zeitlich einordnen (z.B. "laut einem Bericht vom Januar", "Anfang Februar berichtete...") - Ältere Quellen zeitlich einordnen (z.B. "laut einem Bericht vom Januar", "Anfang Februar berichtete...")
Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern: Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern:
@@ -91,7 +91,7 @@ REGELN:
- Wenn eine Quelle eine erkennbare Ausrichtung hat (z.B. pro-russisch, pro-iranisch, staatsnah, rechtsextrem), muss dies im Fliesstext erwaehnt werden, damit der Leser die Information einordnen kann. Beispiel: "Laut dem pro-russischen Telegram-Kanal Rybar..." oder "Die iranische Nachrichtenagentur Fars meldete..." oder "Der rechtsextreme Kanal Compact behauptete..." - Wenn eine Quelle eine erkennbare Ausrichtung hat (z.B. pro-russisch, pro-iranisch, staatsnah, rechtsextrem), muss dies im Fliesstext erwaehnt werden, damit der Leser die Information einordnen kann. Beispiel: "Laut dem pro-russischen Telegram-Kanal Rybar..." oder "Die iranische Nachrichtenagentur Fars meldete..." oder "Der rechtsextreme Kanal Compact behauptete..."
- Quellen immer mit [Nr] referenzieren - Quellen immer mit [Nr] referenzieren
- Jede verwendete Quelle MUSS im sources-Array aufgelistet sein - Jede verwendete Quelle MUSS im sources-Array aufgelistet sein
- Nummeriere die Quellen fortlaufend ab [1] - Nummeriere die Quellen fortlaufend ab [1]. Verwende NUR ganze Zahlen als Quellennummern (z.B. [389], [390]), KEINE Buchstaben-Suffixe wie [389a]
- Ältere Quellen zeitlich einordnen (z.B. "laut einem Bericht vom Januar", "Anfang Februar berichtete...") - Ältere Quellen zeitlich einordnen (z.B. "laut einem Bericht vom Januar", "Anfang Februar berichtete...")
- Markdown-Überschriften (##) für die Abschnitte verwenden - Markdown-Überschriften (##) für die Abschnitte verwenden
- KEIN Fettdruck (**) verwenden - KEIN Fettdruck (**) verwenden
@@ -142,7 +142,7 @@ REGELN:
Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern: Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern:
- "summary": Aktualisierte Zusammenfassung mit Quellenverweisen [1], [2] etc. - "summary": Aktualisierte Zusammenfassung mit Quellenverweisen [1], [2] etc.
- "sources": Array mit NUR den NEUEN Quellen aus den neuen Meldungen, je: {{"nr": <fortlaufend>, "name": "Quellenname", "url": "https://..."}}. Alte Quellen werden automatisch gemerged. - "sources": Array mit NUR den NEUEN Quellen aus den neuen Meldungen, je: {{"nr": <fortlaufende ganze Zahl, KEINE Buchstaben-Suffixe>, "name": "Quellenname", "url": "https://..."}}. Alte Quellen werden automatisch gemerged.
- "key_facts": Array aller aktuellen Kernfakten (in Ausgabesprache) - "key_facts": Array aller aktuellen Kernfakten (in Ausgabesprache)
- "translations": Array von Objekten mit "article_id", "headline_de", "content_de" (nur für neue fremdsprachige Artikel) - "translations": Array von Objekten mit "article_id", "headline_de", "content_de" (nur für neue fremdsprachige Artikel)
@@ -187,7 +187,7 @@ REGELN:
Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern: Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern:
- "summary": Das aktualisierte Briefing als Markdown-Text mit Quellenverweisen - "summary": Das aktualisierte Briefing als Markdown-Text mit Quellenverweisen
- "sources": Array mit NUR den NEUEN Quellen aus den neuen Meldungen, je: {{"nr": <fortlaufend>, "name": "Quellenname", "url": "https://..."}}. Alte Quellen werden automatisch gemerged. - "sources": Array mit NUR den NEUEN Quellen aus den neuen Meldungen, je: {{"nr": <fortlaufende ganze Zahl, KEINE Buchstaben-Suffixe>, "name": "Quellenname", "url": "https://..."}}. Alte Quellen werden automatisch gemerged.
- "key_facts": Array aller gesicherten Kernfakten (in Ausgabesprache) - "key_facts": Array aller gesicherten Kernfakten (in Ausgabesprache)
- "translations": Array von Objekten mit "article_id", "headline_de", "content_de" (nur für neue fremdsprachige Artikel) - "translations": Array von Objekten mit "article_id", "headline_de", "content_de" (nur für neue fremdsprachige Artikel)

Datei anzeigen

@@ -980,13 +980,24 @@ class AgentOrchestrator:
# Validierung: Fehlende Quellennummern im Summary erkennen und reparieren # Validierung: Fehlende Quellennummern im Summary erkennen und reparieren
if sources and new_summary: if sources and new_summary:
import re as _re import re as _re
referenced_nrs = set(int(m) for m in _re.findall(r'\[(\d+)\]', new_summary)) # Auch alphanumerische Refs wie [389a] erkennen
referenced_raw = set(_re.findall(r'\[(\d+[a-z]?)\]', new_summary))
referenced_nrs = set()
for r in referenced_raw:
try:
referenced_nrs.add(int(r))
except ValueError:
referenced_nrs.add(r) # Keep alphanumeric as string
defined_nrs = set() defined_nrs = set()
for s in sources: for s in sources:
nr = s.get("nr", 0)
if isinstance(nr, int):
defined_nrs.add(nr)
elif isinstance(nr, str):
try: try:
defined_nrs.add(int(s.get("nr", 0))) defined_nrs.add(int(nr))
except (ValueError, TypeError): except ValueError:
pass defined_nrs.add(nr) # Keep alphanumeric like '389a'
missing_nrs = sorted(referenced_nrs - defined_nrs) missing_nrs = sorted(referenced_nrs - defined_nrs)
if missing_nrs: if missing_nrs:
logger.warning( logger.warning(

Datei anzeigen

@@ -751,7 +751,7 @@
<script src="/static/vendor/leaflet.markercluster.js"></script> <script src="/static/vendor/leaflet.markercluster.js"></script>
<script src="/static/js/api.js?v=20260316b"></script> <script src="/static/js/api.js?v=20260316b"></script>
<script src="/static/js/ws.js?v=20260316b"></script> <script src="/static/js/ws.js?v=20260316b"></script>
<script src="/static/js/components.js?v=20260316c"></script> <script src="/static/js/components.js?v=20260316d"></script>
<script src="/static/js/layout.js?v=20260316b"></script> <script src="/static/js/layout.js?v=20260316b"></script>
<script src="/static/js/app.js?v=20260316b"></script> <script src="/static/js/app.js?v=20260316b"></script>
<script src="/static/js/api_network.js?v=20260316a"></script> <script src="/static/js/api_network.js?v=20260316a"></script>

Datei anzeigen

@@ -446,8 +446,8 @@ const UI = {
// Inline-Zitate [1], [2] etc. als klickbare Links rendern // Inline-Zitate [1], [2] etc. als klickbare Links rendern
if (sources.length > 0) { if (sources.length > 0) {
html = html.replace(/\[(\d+)\]/g, (match, num) => { html = html.replace(/\[(\d+[a-z]?)\]/g, (match, num) => {
const src = sources.find(s => Number(s.nr) === Number(num)); const src = sources.find(s => String(s.nr) === num || Number(s.nr) === Number(num));
if (src && src.url) { if (src && src.url) {
return `<a href="${this.escape(src.url)}" target="_blank" rel="noopener" class="citation" title="${this.escape(src.name)}">[${num}]</a>`; return `<a href="${this.escape(src.url)}" target="_blank" rel="noopener" class="citation" title="${this.escape(src.name)}">[${num}]</a>`;
} }