diff --git a/src/agents/analyzer.py b/src/agents/analyzer.py
index 7223fa3..d91e052 100644
--- a/src/agents/analyzer.py
+++ b/src/agents/analyzer.py
@@ -242,6 +242,7 @@ class AnalyzerAgent:
result, usage = await call_claude(prompt)
analysis = self._parse_response(result)
if analysis:
+ analysis = self._sanitize_sources(analysis)
logger.info(f"Erstanalyse abgeschlossen: {len(analysis.get('sources', []))} Quellen referenziert")
return analysis, usage
except Exception as e:
@@ -303,6 +304,8 @@ class AnalyzerAgent:
try:
result, usage = await call_claude(prompt)
analysis = self._parse_response(result)
+ if analysis:
+ analysis = self._sanitize_sources(analysis)
if analysis and self._all_previous_sources:
# Merge: alte Quellen beibehalten, neue hinzufuegen
returned_sources = analysis.get("sources", [])
@@ -325,6 +328,51 @@ class AnalyzerAgent:
logger.error(f"Inkrementelle Analyse-Fehler: {e}")
return None, None
+ def _sanitize_sources(self, analysis: dict) -> dict:
+ """Entfernt Buchstaben-Suffixe aus Quellennummern (z.B. '1383a' -> 1383).
+
+ Das LLM erzeugt trotz Anweisung gelegentlich Suffix-Nummern.
+ Diese werden hier auf die Basisnummer normalisiert.
+ Duplikate werden entfernt, wobei Eintraege mit URL bevorzugt werden.
+ """
+ sources = analysis.get("sources", [])
+ if not sources:
+ return analysis
+
+ cleaned = {}
+ suffix_count = 0
+ for s in sources:
+ nr = s.get("nr", "")
+ nr_str = str(nr)
+ # Prüfe auf Buchstaben-Suffix (z.B. "1383a", "1383b")
+ m = re.match(r"^(\d+)[a-z]$", nr_str)
+ if m:
+ base_nr = int(m.group(1))
+ suffix_count += 1
+ # Nur übernehmen wenn Basisnummer noch nicht existiert oder
+ # dieser Eintrag eine URL hat und der bisherige nicht
+ if base_nr not in cleaned:
+ s_copy = dict(s)
+ s_copy["nr"] = base_nr
+ cleaned[base_nr] = s_copy
+ elif s.get("url") and not cleaned[base_nr].get("url"):
+ s_copy = dict(s)
+ s_copy["nr"] = base_nr
+ cleaned[base_nr] = s_copy
+ else:
+ nr_int = int(nr) if isinstance(nr, (int, float)) or (isinstance(nr, str) and nr.isdigit()) else nr
+ if nr_int not in cleaned:
+ cleaned[nr_int] = s
+ elif s.get("url") and not cleaned[nr_int].get("url"):
+ cleaned[nr_int] = s
+
+ if suffix_count > 0:
+ logger.info(f"Quellen-Sanitierung: {suffix_count} Buchstaben-Suffixe entfernt")
+ analysis["sources"] = sorted(cleaned.values(),
+ key=lambda s: s.get("nr", 0) if isinstance(s.get("nr"), int) else 9999)
+
+ return analysis
+
def _parse_response(self, response: str) -> dict | None:
"""Parst die Claude-Antwort als JSON-Objekt mit robustem Fallback."""
# Markdown-Code-Fences entfernen
diff --git a/src/static/js/components.js b/src/static/js/components.js
index 8ef0507..801f84b 100644
--- a/src/static/js/components.js
+++ b/src/static/js/components.js
@@ -444,10 +444,17 @@ const UI = {
html = html.replace(/<\/ul>(
)+/g, '');
html = html.replace(/(
){2,}/g, '
');
- // Inline-Zitate [1], [2] etc. als klickbare Links rendern
+ // Inline-Zitate [1], [2], [1383a] etc. als klickbare Links rendern
if (sources.length > 0) {
html = html.replace(/\[(\d+[a-z]?)\]/g, (match, num) => {
- const src = sources.find(s => String(s.nr) === num || Number(s.nr) === Number(num));
+ // Exakte Suche (auch mit Buchstaben-Suffix)
+ let src = sources.find(s => String(s.nr) === num || Number(s.nr) === Number(num));
+ // Fallback: Bei Suffix wie "1383a" auf Basisnummer 1383 zurueckfallen
+ if ((!src || !src.url) && /[a-z]$/.test(num)) {
+ const baseNum = num.replace(/[a-z]$/, '');
+ const baseSrc = sources.find(s => String(s.nr) === baseNum || Number(s.nr) === Number(baseNum));
+ if (baseSrc && baseSrc.url) src = baseSrc;
+ }
if (src && src.url) {
return `[${num}]`;
}