fix: antwortendes Modell erfassen; 0.040, LaTeX-Dezimalkomma und Teilfragen-Überschriften

- CallRecord.responded_model / LLMUsage.responded_models: tatsächlich antwortendes
  Modell (beim Alias ~deepseek/deepseek-flash-latest) in Log, Report und Benchmark
- normalize_number: führende Null ist nie Tausender (0.040 wurde als 40 gelesen)
- numbers_in: LaTeX-Dezimalkomma 0{,}08 als eine Zahl
- question_ids_in: '### Teil N' als Teilfrage; ausdrückliche Marker haben Vorrang
  vor nummerierten Listen

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 19:30:19 +02:00
commit 8bd3c2e7e1

View file

@ -113,7 +113,7 @@ NUMBER_PATTERN = re.compile(r"(?<![A-Za-z0-9_])\d+(?:[.,]\d+)*(?![A-Za-z0-9_])")
# Teilfragen-Marker am Zeilenanfang: "a)", "- b)", "**Teil 2:**", "**Teil 2:** Text", "1." – bewusst eng, um
# Aufzählungen im Fließtext nicht als Frage zu zählen. Englische Marker ("Part 2", "Question 1", "Task 3")
# gehören dazu: sonst fehlt bei englischen Aufgaben das Nachfassen bei übersehenen Teilfragen.
QUESTION_PATTERN = re.compile(r"^[\s\-*>]*\**\s*(?:\(?([a-h])\)|(?:Teil|Aufgabe|Frage|Part|Question|Task)\s+(\d+)|([1-9])\.)\s*[:.)]?\**\s+\S",
QUESTION_PATTERN = re.compile(r"^[\s\-*>]*\**\s*(?:\(?([a-h])\)|(?:#{1,6}\s*)?(?:Teil|Aufgabe|Frage|Part|Question|Task)\s+(\d+)|([1-9])\.)\s*[:.)]?\**\s+\S",
re.MULTILINE)
@ -136,7 +136,8 @@ def normalize_number(roh: str) -> str:
roh = roh.replace(".", "").replace(",", ".")
elif "." in roh:
teile = roh.split(".")
if all(len(t) == 3 for t in teile[1:]) and len(teile[0]) <= 3:
# „0.040“ ist nie eine Tausenderschreibweise – vorher wurde es zu 40 (portfolio_qp, Rückrechnung 17.09.2026)
if all(len(t) == 3 for t in teile[1:]) and len(teile[0]) <= 3 and teile[0] != "0":
roh = "".join(teile) # 1.234 bzw. 12.345.678
wert = float(roh)
return str(int(wert)) if wert.is_integer() else f"{wert:g}" # große Zahlen exakt, nicht 1.23457e+06
@ -145,7 +146,9 @@ def normalize_number(roh: str) -> str:
def numbers_in(text: str) -> Set[str]:
"""Zahlen eines Textes in einheitlicher Schreibweise."""
gefunden = set()
for roh in NUMBER_PATTERN.findall(text or ""):
# LaTeX schreibt das Dezimalkomma als {,} (0{,}08) – sonst zerfiele die Zahl in 0 und 08
text = re.sub(r"(?<=\d)\{,\}(?=\d)", ",", text or "")
for roh in NUMBER_PATTERN.findall(text):
try:
gefunden.add(normalize_number(roh))
except ValueError:
@ -154,8 +157,15 @@ def numbers_in(text: str) -> Set[str]:
def question_ids_in(text: str) -> Set[str]:
"""Kennungen der Teilfragen, die im Aufgabentext erkennbar sind."""
return {(a or b or c).lower() for a, b, c in QUESTION_PATTERN.findall(text or "")}
"""Kennungen der Teilfragen, die im Aufgabentext erkennbar sind.
Nennt der Text Teilfragen ausdrücklich („### Teil 3“, „Aufgabe 2“), zählen bloße Nummerierungen („1.“) nicht –
beobachtet: eine nummerierte Liste von Restriktionen galt als acht Teilfragen, die als Überschriften gesetzten
eigentlichen Teilaufgaben wurden übersehen.
"""
treffer = QUESTION_PATTERN.findall(text or "")
ausdruecklich = any(b for _, b, _ in treffer)
return {(a or b or c).lower() for a, b, c in treffer if not (ausdruecklich and c and not (a or b))}
def _covers(genannt: str, erkannt: str) -> bool: