diff --git "a/src/norm_anchors.py" "b/src/norm_anchors.py" new file mode 100644--- /dev/null +++ "b/src/norm_anchors.py" @@ -0,0 +1,2273 @@ +"""Welche Norm eine Frage entscheidet — kuratiert statt erraten. + +Dieselbe Bauweise wie die vier Listenmodule (`amrl_substitution`, +`amrl_austauschbarkeit`, `amrl_biosimilars`, `amrl_otc`), aber eine Ebene höher. +Die Listen sichern die **Stoffebene**: steht ein Wirkstoff auf einer Anlage. +Dieses Modul sichert die **Normebene**: welche Vorschrift eine Frage überhaupt +beantwortet. + +Der Anlass war eine Livemessung mit fünf normbezogenen Fragen — keine nannte +einen Wirkstoff, alle vier Lookups meldeten deshalb planmäßig `nicht_pruefbar`, +und zwei Antworten nannten die falsche maßgebliche Norm: + + OTC zu Lasten der GKV beantwortet mit § 31 Abs. 1 SGB V. Diese Norm + gewährt den Anspruch aber nur, „soweit die + Arzneimittel nicht nach § 34 … ausgeschlossen + sind“ — sie verweist auf die Antwort, sie ist sie + nicht. Entschieden wird die Frage von § 34 Abs. 1. + + Zuweisungsverbot beantwortet mit § 360 Abs. 2 SGB V, einer + Ausnahmeklausel zur E-Rezept-Pflicht. Das Ergebnis + stimmte zufällig, die Herleitung war verkehrt herum. + Das Verbot steht in § 31 Abs. 1 Satz 6. + +Beide Male standen die richtigen Normen im Index. Das Retrieval hat sie nicht +gefunden, weil Ähnlichkeit zur Frage etwas anderes ist als Einschlägigkeit — und +kein Ähnlichkeitsmaß kann den Unterschied kennen. Deshalb dieselbe Antwort wie +bei den Listen: eine kuratierte Tabelle und eine Substring-Prüfung, kein Modell. + +Das Register tut zwei Dinge: + +1. **Pflichtabruf.** `pflichtabruf()` benennt Korpus, Container, § und Absatz + der tragenden Normen. Der `FederatedRetriever` holt sie unabhängig vom + Ähnlichkeitswettbewerb — die entscheidende Norm steht damit im Kontext, auch + wenn sie sich schlecht einbettet. + +2. **Abgleich und Richtigstellung.** `pruefe()` liest den Abschnitt + „Maßgebliche Norm“ der fertigen Antwort. Fehlt ein Glied der Kette, wird es + nachgetragen. Steht dort eine Vorschrift, die die Frage nicht entscheidet, + setzt `in_antwort_einsetzen()` die Abschnitte „Kurzantwort“ und „Maßgebliche + Norm“ neu. + +**Warum die Antwort neu gesetzt und nicht nur ergänzt wird.** Die erste Fassung +hängte den richtigen Befund unter die falsche Antwort. Damit standen beide +nebeneinander und nichts im Text sagte, welcher gilt — dieselbe Lage, die +`amrl_substitution.antwort_mit_befunden` für die Listen und +`corpus_amendments.reconcile_negative_answer` für spätere Änderungen auflöst, +und sie wird hier genauso aufgelöst. + +**Eine Richtigstellung, die den Widerspruch stehen lässt, ist keine.** Der +Ausdruck vom 20.08.2026 zeigte die zweite Fassung von außen: die Antwort zur +Austauschbarkeit von Darreichungsformen begann mit der Meldung, das Modell habe +danebengelegen, setzte Kurzantwort und maßgebliche Norm neu — und ließ darunter +den vollständigen Wortlaut des verworfenen § 40c AM-RL stehen, mitsamt seinem +Quellenmarker und als erste Quelle der Liste. Angekündigt wurde das im Text +sogar („können sich noch auf die verworfene Vorschrift beziehen“); aufgelöst +wurde es nicht. Drei Dinge folgen daraus, und alle drei stehen unten im Code: + +* Der Hinweis steht **hinter** der Antwort, nicht davor (`in_antwort_einsetzen`). +* Absätze, die die verworfene Vorschrift **zitieren**, fallen weg; ihre + Erwähnung im Wortlaut einer anderen Norm bleibt (`_ohne_verworfene_bloecke`). +* Der neu gesetzte Normabschnitt übernimmt die Quellenmarker, die das Modell + denselben Vorschriften gegeben hat (`_marker_je_norm`) — sonst verlöre gerade + die richtiggestellte Norm ihren Beleg. + +**Die `kernaussage` ist die Grenze dieses Moduls.** Sie gibt wieder, was die +tragende Norm sagt — sie subsumiert nicht. „Vertragsärzte dürfen nicht zuweisen“ +steht im Gesetz; ob eine konkrete Praxis zugewiesen hat, entscheidet dieses +Modul nicht und darf es nicht entscheiden. Alles Fallbezogene bleibt beim +Modell, dessen übrige Abschnitte unangetastet stehen bleiben. + +**Bedingte Einträge gegen Übergenauigkeit.** Eine Norm, ein Hinweis oder ein +Fehlgriff kann an Signale gebunden werden (`nur_bei`, `nicht_bei`). Ohne das +trug die OTC-Antwort für eine erwachsene Versicherte den Hinweis auf Kinder bis +12, und die allgemeine Frage nach Darreichungsformen bekam § 40c AM-RL +aufgeladen, der nur biotechnologisch hergestellte Arzneimittel betrifft. Ein +Register, das zu jeder Frage alles nennt, verwischt den Hauptfall. + +**Die Grenze des Registers ist Absicht.** Es enthält nur Fälle, die in der +Auswertung tatsächlich falsch beantwortet wurden. Jeder Eintrag ist eine +Rechtsbehauptung, die gepflegt werden muss; ein Register, das +„vorsichtshalber“ jede denkbare Frage abdeckt, ist bei der nächsten Novelle +stiller Ballast. Trifft kein Anker, ist der Befund `nicht_pruefbar` und die +Pipeline verhält sich wie zuvor. + +**Der Lifestyle-Anker ist derselbe Fehler an einer zweiten Stelle.** Auch die +Frage nach der Erstattungsfähigkeit von Sildenafil, Wegovy oder Champix wurde +auf § 31 SGB V gestützt statt auf § 34 Abs. 1 Satz 7 SGB V. Er steht neben dem +OTC-Anker, weil beide aus § 34 Abs. 1 kommen und trotzdem verschiedene +Ausschlüsse sind: Satz 1 schließt nach der Verschreibungspflicht aus, Satz 7 nach +der Zweckbestimmung. Zwei Dinge trägt nur dieser Anker. Erstens hat der +Ausschluss genau eine Ausnahme — die Tabakentwöhnung nach § 34 Abs. 2 SGB V, +§ 14a AM-RL, Anlage IIa —, und sie hängt an `nur_bei=_TABAK`, weil sie in einer +Frage nach Sildenafil nichts zu suchen hat. Zweitens führt der Weg über den +medizinisch begründeten Einzelfall hier *nicht* hinaus: § 16 Abs. 5 AM-RL öffnet +sich wörtlich nur für die nach § 16 Abs. 1 und 2 ausgeschlossenen Arzneimittel. +Das steht in der `kernaussage`, aber bewusst **nicht** als `Fehlgriff` — der +Listenblock von `amrl_lifestyle` schreibt denselben Satz in seiner Verneinung +(„… greift hier nicht“), und ein Muster auf `§ 16` schlüge an einer Antwort an, +die gerade das Richtige sagt. + +**Die Fundstellen sind gegen den Index geprüft**, nicht aus dem Gedächtnis +notiert: `container_id`, `section_id` und `subsection` stammen aus der +Chunk-Metadata der laufenden Collections. `tests/test_norm_anchors.py` hält das +nach — ein Anker, der auf nichts zeigt, ist schlimmer als kein Anker. +""" + +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Tuple + +# --------------------------------------------------------------------------- +# Datenmodell +# --------------------------------------------------------------------------- + +# Eine UND-Verknüpfung von ODER-Gruppen: jede Gruppe muss treffen, innerhalb +# einer Gruppe genügt ein Treffer. Ein Anker kann mehrere davon führen; er +# greift, sobald einer vollständig trifft. +Signalsatz = Tuple[Tuple[str, ...], ...] + + +@dataclass(frozen=True) +class Bedingung: + """Wann ein Eintrag zur Frage gehört — und wann er nur Ballast wäre.""" + + nur_bei: Tuple[str, ...] = () + nicht_bei: Tuple[str, ...] = () + + def gilt(self, haystack: str) -> bool: + if self.nur_bei and not any(_trifft(s, haystack) for s in self.nur_bei): + return False + if self.nicht_bei and any(_trifft(s, haystack) for s in self.nicht_bei): + return False + return True + + +@dataclass(frozen=True) +class Norm: + """Eine Vorschrift, so wie sie zu zitieren und so wie sie zu finden ist. + + `zitat` ist die Form, in der die Antwort sie nennen soll. Die übrigen Felder + sind die Adresse im Index; sie sind leer, wenn die Norm nicht indiziert ist + (ApoG, Technische Anlagen, AM-RL-Anlagen). Eine nicht indizierte Norm wird + benannt, aber nicht abgerufen — sonst behauptete der Pflichtabruf einen + Bestand, den es nicht gibt. + """ + + zitat: str + corpus_id: str = "" + container_id: str = "" + section_id: str = "" + # Der Absatz grenzt den Abruf ein. Ohne ihn holt „§ 129 SGB V“ achtzig + # Chunks; mit ihm sind es zwei, und beide tragen. + absatz: str = "" + tragend: bool = True + warum: str = "" + wenn: Bedingung = Bedingung() + # Wie viele Chunks diese Norm braucht. 0 heißt: was der Aufrufer vorgibt. + # + # Der Grund ist ein Budget, keine Feinjustierung. Der Kontext fasst zehn + # Treffer; ein Anker mit vier tragenden Normen belegte bei zwei Chunks je + # Norm acht davon und verdrängte damit genau die Suche, die er ergänzen + # soll. Gemessen am 19.08.2026 an der Verweisauflösung, die an derselben + # Grenze scheiterte. Wo der Absatz eng gefasst ist, genügt ein Chunk: der + # Parent trägt den ganzen Absatz, das Kind wiederholt Teile davon. + chunks: int = 0 + + @property + def abrufbar(self) -> bool: + return bool(self.corpus_id and self.section_id) + + def als_ziel(self, max_chunks: int = 2) -> Dict[str, Any]: + """Die Adresse als schlichtes dict — der Retriever kennt dieses Modul nicht.""" + return { + "corpus_id": self.corpus_id, + "container_id": self.container_id, + "section_id": self.section_id, + "subsection": self.absatz, + "max_chunks": self.chunks or max_chunks, + "zitat": self.zitat, + } + + +@dataclass(frozen=True) +class Fehlgriff: + """Eine Vorschrift, die zu dieser Frage regelmäßig fälschlich genannt wird. + + Zwei Sorten, und die Unterscheidung entscheidet, wann angeschlagen wird: + + `nur_ohne_tragende=True` — die Vorschrift *gehört* in die Antwort, nur nicht + als die entscheidende. § 31 SGB V ist in einer OTC-Antwort die Anspruchs- + norm; ein Fehler ist sie erst, wenn sie **statt** § 34 dasteht. + + `nur_ohne_tragende=False` — die Vorschrift ist zu eng oder zu speziell und + stört auch neben der richtigen Norm. § 40c AM-RL betrifft ausschließlich + biotechnologisch hergestellte Arzneimittel; in einer allgemeinen Frage nach + Darreichungsformen verengt er die Antwort auf einen Sonderfall. + """ + + muster: str + warum: str + nur_ohne_tragende: bool = True + wenn: Bedingung = Bedingung() + + +@dataclass(frozen=True) +class Hinweis: + """Was eine richtige Antwort nicht verschweigen darf — soweit es der Fall aufwirft.""" + + text: str + wenn: Bedingung = Bedingung() + + +@dataclass(frozen=True) +class Kernaussage: + """Eine Kurzantwort für einen Teilfall, die die allgemeine verdrängt. + + Der Anlass ist gemessen: die Frage nach einem **zehnjährigen** Kind mit einer + OTC-Verordnung bekam am 21.08.2026 die allgemeine Kurzantwort — „zu Lasten + der GKV verordnungsfähig nur, soweit … Anlage I …". Für ein Kind bis zum + vollendeten 12. Lebensjahr ist das falsch: der Ausschluss greift dort gar + nicht, und die Liste entscheidet nichts. + + Ein Hinweis am Ende reichte dafür nicht. Er stünde unter einer Kurzantwort, + die das Gegenteil sagt — dieselbe Lage, die `in_antwort_einsetzen` für die + Richtigstellung auflöst, eine Ebene tiefer. Deshalb verdrängt die bedingte + Kernaussage die allgemeine, statt neben ihr zu stehen. + """ + + text: str + wenn: Bedingung = Bedingung() + + +@dataclass(frozen=True) +class Anker: + """Ein Fragetyp und die Normenkette, die ihn entscheidet.""" + + key: str + thema: str + signale: Tuple[Signalsatz, ...] + normen: Tuple[Norm, ...] + # Was die tragende Norm sagt — als Satz, ohne Subsumtion. Ersetzt die + # Kurzantwort, wenn die Antwort auf der falschen Vorschrift stand. + kernaussage: str = "" + # Teilfälle, die eine eigene Kurzantwort brauchen. Der erste zutreffende + # gewinnt; trifft keiner, gilt `kernaussage`. + kernaussagen: Tuple[Kernaussage, ...] = () + fehlgriffe: Tuple[Fehlgriff, ...] = () + hinweise: Tuple[Hinweis, ...] = () + + def trifft(self, haystack: str) -> bool: + return any( + all(any(_trifft(s, haystack) for s in gruppe) for gruppe in satz) + for satz in self.signale + ) + + def geltende_normen(self, haystack: str) -> Tuple[Norm, ...]: + return tuple(n for n in self.normen if n.wenn.gilt(haystack)) + + def tragende(self, haystack: str) -> Tuple[Norm, ...]: + return tuple(n for n in self.geltende_normen(haystack) if n.tragend) + + def geltende_hinweise(self, haystack: str) -> Tuple[str, ...]: + return tuple(h.text for h in self.hinweise if h.wenn.gilt(haystack)) + + def geltende_kernaussage(self, haystack: str) -> str: + for eintrag in self.kernaussagen: + if eintrag.wenn.gilt(haystack): + return eintrag.text + return self.kernaussage + + +# --------------------------------------------------------------------------- +# Das Register +# --------------------------------------------------------------------------- + +# Fundstellen im Index (Stand der geprüften Collections): +# sgb5 container_id = "Drittes/Viertes/Zehntes Kapitel", section_id = "§ 34" +# rv129 container_id = "Vertrag" — § 10 gibt es auch in Anlage 11! +# amrl container_id = "Richtlinie" +# amabrv container_id = "Vertrag" — § 2 gibt es auch in Anlage 1, 2 und 4! +# +# Der Container ist deshalb Pflicht, wo ein § mehrfach vorkommt. Beim SGB V ist +# er es nicht (§§ sind über die Kapitel hinweg eindeutig), steht aber trotzdem +# da: er ist die billigste Kontrolle dafür, dass der Anker noch stimmt. + + +# --------------------------------------------------------------------------- +# Das Alter als eigenes Merkmal +# +# § 34 Abs. 1 SGB V staffelt nach Alter — Satz 5 Nr. 1 nimmt Kinder bis zum +# vollendeten 12. Lebensjahr vom Ausschluss aus, Nr. 2 Jugendliche mit +# Entwicklungsstörungen bis 18. Ein Register, das nur nach Wörtern sucht, kann +# das nicht: „jährige" steht in „45-jährige Versicherte" **und** in +# „zehnjähriges Kind". Genau daran ist der Kinderhinweis am 21.08.2026 +# gescheitert, und zwar in der Frage, für die er gebaut ist. +# +# Gelesen wird deshalb die Zahl, und das Ergebnis hängt als Marke am Haystack. +# Die Marken sind absichtlich unaussprechlich: sie sollen nie zufällig in einer +# Frage stehen. +# --------------------------------------------------------------------------- + +_MARKE_ERWACHSEN = "__alter_ab_18__" +_MARKE_KIND = "__alter_bis_11__" +_MARKE_MINDERJAEHRIG = "__alter_bis_17__" + +# Zahlwörter bis zwanzig — darüber hinaus schreibt niemand ein Alter aus, und +# jede weitere Zeile wäre eine Zeile, die gepflegt werden muss. +_ZAHLWORT: Dict[str, int] = { + "ein": 1, "eine": 1, "einem": 1, "einjährig": 1, + "zwei": 2, "drei": 3, "vier": 4, "fünf": 5, "fuenf": 5, "sechs": 6, + "sieben": 7, "acht": 8, "neun": 9, "zehn": 10, "elf": 11, "zwölf": 12, + "zwoelf": 12, "dreizehn": 13, "vierzehn": 14, "fünfzehn": 15, + "fuenfzehn": 15, "sechzehn": 16, "siebzehn": 17, "achtzehn": 18, + "neunzehn": 19, "zwanzig": 20, +} + +# „10-jährig", „10 jährige", ���zehnjähriges", „10 Jahre alt", „zehn Jahre alt". +# Bewusst **nicht** „vollendetes 12. Lebensjahr": das ist die Regel, nicht der +# Patient — eine Frage, die den Gesetzeswortlaut zitiert, hat damit noch kein +# Alter genannt. +_ALTER_RX = ( + re.compile(r"(\d{1,3})\s*-?\s*j[äa]hrig"), + re.compile(r"(\d{1,3})\s+jahre?\s+alt"), + re.compile(r"([a-zäöüß]+)j[äa]hrig"), + re.compile(r"([a-zäöüß]+)\s+jahre?\s+alt"), +) + + +def alter_in_jahren(question: str) -> Optional[int]: + """Das Alter, das die Frage nennt — oder None. + + Gefunden wird das **erste** Alter. Eine Frage mit zwei Altersangaben ist + ein Sachverhalt, den das Register nicht entscheidet; dann trägt es lieber + das erste und der Fachanwender sieht die Marke im Befund. + """ + text = " ".join(str(question or "").lower().split()) + for erkenner in _ALTER_RX: + treffer = erkenner.search(text) + if not treffer: + continue + roh = treffer.group(1) + if roh.isdigit(): + jahre = int(roh) + else: + jahre = _ZAHLWORT.get(roh, _ZAHLWORT.get(roh.rstrip("e"), -1)) + if 0 <= jahre <= 120: + return jahre + return None + + +# Eine Verneinung, die ein Signal enthält, ist kein Signal. „… und **nicht** zur +# Herstellung einer parenteralen Zubereitung verwendet" beschreibt gerade den +# Fall, den `_PARENTERAL` ausschließen soll — für eine Substring-Prüfung steht +# dort aber „parenteral". Gemessen am 21.08.2026: der § 40b-Fehlgriff schwieg an +# der Frage, für die er gebaut ist. +# +# Entfernt wird deshalb die verneinte Wendung, bevor die Signale greifen. Eng +# gefasst: nur die Verneinung unmittelbar vor dem Signalwort. +_VERNEINT = re.compile( + r"\b(?:nicht|kein\w*)\s+(?:zur\s+herstellung\s+(?:einer\s+)?)?" + r"(?:parenteral\w*)(?:\s+zubereitung\w*)?" +) + + +def _alterssignale(question: str) -> str: + jahre = alter_in_jahren(question) + if jahre is None: + return "" + marken = [] + if jahre >= 18: + marken.append(_MARKE_ERWACHSEN) + else: + marken.append(_MARKE_MINDERJAEHRIG) + if jahre < 12: + marken.append(_MARKE_KIND) + return " " + " ".join(marken) + +# Signale, die einen Fall als Erwachsenenfall ausweisen. Sie unterdrücken den +# Kinderhinweis bei § 34 — gemessen an einer Frage, die ausdrücklich von einer +# erwachsenen Versicherten handelte und trotzdem die Ausnahme für Kinder bis 12 +# vorgesetzt bekam. +# `"jährige"` stand hier bis zum 21.08.2026 und war der Fehler, den Fall +# `kette_kind_otc_anlage_i` sichtbar gemacht hat: „ein **zehnjähriges** Kind" +# enthält das Signal und galt damit als Erwachsenenfall — der Kinderhinweis +# wurde unterdrückt, und zwar in genau der Frage, für die er gebaut ist. Ein +# Altersmerkmal als blanker Substring ist altersblind. +# +# Jetzt wird das Alter gelesen (`alter_in_jahren`) und als Marke in den +# Haystack gehängt. Die Wortsignale bleiben für die Fälle ohne Zahl. +_ERWACHSEN = ("erwachsen", "volljährig", "volljaehrig", "rentner", _MARKE_ERWACHSEN) + +# Dass nach der Verordnung zu Lasten der GKV gefragt ist. Beide § 34-Anker +# brauchen dieses Signal: ohne es spränge der Ausschluss auf jede Frage an, in +# der ein ausgeschlossener Stoff bloß vorkommt. +_ZU_LASTEN = ( + "zu lasten", + "zulasten", + "kasse", + "gkv", + "erstattung", + "erstattungsfähig", + "erstattungsfaehig", + "verordnungsfähig", + "verordnungsfaehig", + "kassenrezept", + "verordnet werden", + "verordnen", +) + +# Dieselbe Frage in den Wendungen der Offizin. Beim Lifestyle-Ausschluss wird +# sie regelmäßig von der anderen Seite gestellt — nicht „zahlt die Kasse", sondern +# „muss der Patient das selbst zahlen". `amrl_lifestyle.VERORDNUNGS_SIGNALE` +# kennt dieselben Wendungen auf der Stoffebene. +_SELBST_ZAHLEN = ( + "privatrezept", + "grünes rezept", + "gruenes rezept", + "grüne rezept", + "selbstzahler", + "selbst zahlen", + "selbst bezahlen", + "ausgeschlossen", +) + +# Signale für ein biologisches/biotechnologisch hergestelltes Arzneimittel. +_BIOLOGISCH = ("biolog", "biosimilar", "biotechnolog", "referenzarzneimittel", "§ 40c", "40c") + +# Die parenterale Zubereitung — die Konstellation, die § 40b von § 40c trennt. +# „zubereitung“ steht bewusst nicht allein darin: das Wort kommt in +# Abgabefragen auch ohne Bezug zur Herstellung vor. +_PARENTERAL = ( + "parenteral", + "zytostatik", + "antineoplast", + "applikationsfertig", + "unmittelbaren ärztlichen anwendung", + "unmittelbaren aerztlichen anwendung", + "zubereitung aus fertigarzneimitteln", +) + +# Die Anwendungsgebiete, die § 34 Abs. 1 Satz 8 SGB V selbst aufzählt, in den +# Wendungen, in denen danach gefragt wird. Signale der Normebene: sie stehen im +# Gesetz. Welcher Wirkstoff unter sie fällt, entscheidet Anlage II — dieses +# Register entscheidet das nicht und behauptet es auch nicht. +_LIFESTYLE_GEBIETE = ( + "erektil", + "dysfunktion", + "sexuelle potenz", + "sexuellen potenz", + "sexuellen verlangen", + "sexuellen verlangens", + "potenzmittel", + "libido", + "raucherentwöhn", + "raucherentwoehn", + "tabakentwöhn", + "tabakentwoehn", + "tabakabhängig", + "tabakabhaengig", + "nikotinabhängig", + "nikotinabhaengig", + "rauchstopp", + "abmagerung", + "appetitzügl", + "appetitzuegl", + "zügelung des appetits", + "zuegelung des appetits", + "gewichtsreduktion", + "gewichtsabnahme", + "gewichtsregulierung", + "regulierung des körpergewichts", + "regulierung des koerpergewichts", + "abnehmspritze", + "adipositas", + "übergewicht", + "uebergewicht", + "haarwuchs", + "haarausfall", + "verbesserung des aussehens", + "kosmetisch", + "schlaf-wach-rhythmus", +) + +# Die Namen, unter denen die Frage in der Offizin gestellt wird. Das ist **nicht** +# Anlage II — die steht in `data/amrl_anlage_ii.json` und wird von +# `amrl_lifestyle` geführt, mit Stand, Fußnoten und den Maßgaben, an denen der +# Ausschluss wirklich hängt. Hier stehen nur so viele Namen, dass eine Frage nach +# ihnen den Weg zur richtigen Norm findet: gemessen wurde „Ist Sildenafil zu +# Lasten der GKV erstattungsfähig?" — beantwortet aus § 31 SGB V. +# +# Diese Doppelung ist ungefährlich, weil sie nichts behauptet. Fällt ein Name +# aus Anlage II heraus, bleibt § 34 Abs. 1 Satz 7 SGB V trotzdem die Vorschrift, +# an der sich die Frage nach ihm entscheidet; die Auskunft, ob er noch gelistet +# ist, gibt allein der Lookup. +_LIFESTYLE_LEITFAELLE = ( + "sildenafil", + "viagra", + "tadalafil", + "cialis", + "vardenafil", + "levitra", + "avanafil", + "alprostadil", + "dapoxetin", + "semaglutid", + "wegovy", + "liraglutid", + "saxenda", + "tirzepatid", + "mounjaro", + "orlistat", + "xenical", + "mysimba", + "finasterid", + "propecia", + "minoxidil", + "regaine", + "vareniclin", + "champix", + "zyban", + "cytisin", +) + +# Wann die eine Ausnahme überhaupt im Raum steht. Nur dann gehören § 34 Abs. 2 +# SGB V, § 14a AM-RL und Anlage IIa in die Kette — bei einer Frage nach Sildenafil +# wäre die Tabakentwöhnung Ballast. +_TABAK = ( + "raucherentwöhn", + "raucherentwoehn", + "tabakentwöhn", + "tabakentwoehn", + "tabakabhängig", + "tabakabhaengig", + "nikotinabhängig", + "nikotinabhaengig", + "rauchstopp", + "raucher", + "rauchen", + "tabak", + "nikotin", + "nicotin", + "vareniclin", + "champix", + "zyban", + "cytisin", +) + +# Wer steuern könnte. Nur diese Akteure machen aus einer Empfehlung eine +# Zuweisung im Sinne des § 31 Abs. 1 Satz 6 SGB V. +_AKTEURE = ( + "arzt", + "ärzt", + "aerzt", + "praxis", + "krankenkasse", + "kasse", + "verordner", + "vertragsarzt", +) + + +ANKER: Tuple[Anker, ...] = ( + Anker( + key="otc_zu_lasten_gkv", + thema="Nicht verschreibungspflichtiges Arzneimittel zu Lasten der GKV", + signale=( + ( + ( + "nicht verschreibungspflichtig", + "nicht-verschreibungspflichtig", + "nicht rezeptpflichtig", + "rezeptfrei", + "freiverkäuflich", + "freiverkaeuflich", + "otc", + ), + _ZU_LASTEN, + ), + ), + kernaussage=( + "Nicht verschreibungspflichtige Arzneimittel sind nach § 34 Abs. 1 Satz 1 SGB V " + "von der Versorgung ausgeschlossen. Zu Lasten der GKV verordnungsfähig sind sie " + "nur, soweit der Gemeinsame Bundesausschuss sie in § 12 und Anlage I der AM-RL " + "als Therapiestandard bei der Behandlung einer schwerwiegenden Erkrankung " + "ausweist und der Vertragsarzt sie für diese Erkrankung mit Begründung verordnet." + ), + kernaussagen=( + Kernaussage( + # Gemessen am 21.08.2026: die Frage nach einem zehnjährigen Kind + # bekam die allgemeine Kurzantwort und damit die Anlage I als + # Voraussetzung vorgesetzt — bei einem Kind bis zum vollendeten + # 12. Lebensjahr entscheidet sie nichts, weil der Ausschluss + # selbst nicht greift. + text=( + "Der Ausschluss des § 34 Abs. 1 Satz 1 SGB V gilt hier nicht: nach " + "§ 34 Abs. 1 Satz 5 Nr. 1 SGB V sind versicherte Kinder bis zum " + "vollendeten 12. Lebensjahr von ihm ausgenommen, § 12 Abs. 12 AM-RL " + "wiederholt das für die Richtlinie. Ein Eintrag in Anlage I ist " + "deshalb keine Voraussetzung der Verordnungsfähigkeit — die Anlage " + "führt die Ausnahmen **vom** Ausschluss, und der greift in diesem " + "Fall nicht. Zu prüfen bleibt die Verordnung im Übrigen, nicht die " + "Liste." + ), + wenn=Bedingung(nur_bei=(_MARKE_KIND,)), + ), + ), + normen=( + Norm( + zitat="§ 34 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 34", + absatz="1", + warum=( + "Satz 1 schließt nicht verschreibungspflichtige Arzneimittel von der " + "Versorgung aus, Satz 2 ermächtigt den G-BA zu den Ausnahmen." + ), + ), + Norm( + zitat="§ 12 AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 12", + warum=( + "Setzt die Ermächtigung des § 34 Abs. 1 Satz 2 um und regelt nach " + "Absatz 10 abschließend, wann ausnahmsweise verordnet werden darf." + ), + ), + Norm( + zitat="AM-RL Anlage I", + warum=( + "Führt die ausnahmsweise verordnungsfähigen Stoffe samt Indikation. " + "Die konkrete Verordnungsfähigkeit ist an ihr zu prüfen." + ), + ), + Norm( + zitat="§ 31 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 31", + absatz="1", + tragend=False, + warum=( + "Anspruchsnorm — gewährt die Versorgung ausdrücklich nur, soweit sie " + "nicht nach § 34 ausgeschlossen ist. Gehört in die Antwort, " + "entscheidet sie aber nicht." + ), + ), + ), + fehlgriffe=( + Fehlgriff( + muster=r"§\s*31\b", + warum=( + "§ 31 Abs. 1 Satz 1 SGB V gewährt den Anspruch nur, „soweit die " + "Arzneimittel nicht nach § 34 oder durch Richtlinien nach § 92 " + "Absatz 1 Satz 2 Nummer 6 ausgeschlossen sind“. Die Norm verweist " + "auf die Antwort; die Ausschlussentscheidung trifft § 34." + ), + ), + ), + hinweise=( + Hinweis( + text=( + "Neben Anlage I bleibt verordnungsfähig, was § 12 AM-RL gesondert regelt: " + "Begleitmedikation, die die Fachinformation des Hauptarzneimittels " + "voraussetzt (Absatz 7), die Behandlung schwerwiegender unerwünschter " + "Arzneimittelwirkungen (Absatz 8) und Arzneimittel der Anthroposophie und " + "Homöopathie für die dort gelisteten Indikationsgebiete (Absatz 6)." + ), + ), + Hinweis( + # Für einen ausdrücklichen Erwachsenenfall ist das nur Rauschen. + # Die Nummern stehen hier, seit die Messung vom 21.08.2026 zeigte, + # dass die Antwort den Inhalt traf und die Fundstelle nicht: „Satz 5" + # allein sagt nicht, welche der beiden Gruppen gemeint ist, und bei + # einem zehnjährigen Kind ist das Nr. 1 und nicht Nr. 2. + text=( + "Für versicherte Kinder bis zum vollendeten 12. Lebensjahr " + "(§ 34 Abs. 1 Satz 5 Nr. 1 SGB V) und Jugendliche mit " + "Entwicklungsstörungen bis zum vollendeten 18. Lebensjahr " + "(§ 34 Abs. 1 Satz 5 Nr. 2 SGB V) gilt der Ausschluss überhaupt " + "nicht; § 12 Abs. 12 AM-RL wiederholt das für die Richtlinie." + ), + wenn=Bedingung(nicht_bei=_ERWACHSEN), + ), + ), + ), + Anker( + key="lifestyle_arzneimittel", + thema="Arzneimittel zur Erhöhung der Lebensqualität (Lifestyle-Arzneimittel)", + # Drei Zugänge, weil dieselbe Frage auf drei Höhen gestellt wird: über den + # Namen der Sache, über das Anwendungsgebiet und über den Wirkstoff. Die + # letzten beiden verlangen zusätzlich, dass nach der Verordnung zu Lasten + # der GKV gefragt ist — „Wie wirkt Sildenafil?" ist keine Normfrage. + signale=( + (("lifestyle", "life-style", "erhöhung der lebensqualität", "erhoehung der lebensqualitaet"),), + (_LIFESTYLE_GEBIETE, (*_ZU_LASTEN, *_SELBST_ZAHLEN)), + (_LIFESTYLE_LEITFAELLE, (*_ZU_LASTEN, *_SELBST_ZAHLEN)), + ), + kernaussage=( + "Arzneimittel, bei deren Anwendung eine Erhöhung der Lebensqualität im Vordergrund " + "steht, sind nach § 34 Abs. 1 Satz 7 SGB V von der Versorgung ausgeschlossen; Satz 8 " + "nennt die Anwendungsgebiete — erektile Dysfunktion, Anreizung sowie Steigerung der " + "sexuellen Potenz, Raucherentwöhnung, Abmagerung, Zügelung des Appetits, Regulierung " + "des Körpergewichts und Verbesserung des Haarwuchses. Das Nähere regelt § 14 AM-RL, " + "dessen Absatz 2 das erste Gebiet zu den sexuellen Dysfunktionen weitet; welche " + "Fertigarzneimittel und Anwendungsgebiete darunterfallen, stellt Anlage II der AM-RL " + "zusammen. Der Ausschluss beruht damit auf dem Gesetz selbst: der medizinisch " + "begründete Einzelfall nach § 16 Abs. 5 AM-RL öffnet sich wörtlich nur für die nach " + "§ 16 Abs. 1 und 2 ausgeschlossenen Arzneimittel und trägt hier nicht. Die einzige " + "Ausnahme ist die Tabakentwöhnung: bei festgestellter schwerer Tabakabhängigkeit " + "besteht nach § 34 Abs. 2 SGB V, § 14a AM-RL Anspruch auf eine einmalige Versorgung " + "mit den in Anlage IIa geführten Arzneimitteln im Rahmen evidenzbasierter Programme." + ), + normen=( + Norm( + zitat="§ 34 Abs. 1 Satz 7 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 34", + absatz="1", + warum=( + "Satz 7 schließt Arzneimittel aus, bei deren Anwendung eine Erhöhung der " + "Lebensqualität im Vordergrund steht; Satz 8 zählt die Anwendungsgebiete " + "auf, Satz 9 verweist auf die Richtlinien nach § 92 Abs. 1 Satz 2 Nr. 6. " + "Das ist ein anderer Ausschluss als der des Satzes 1 und hängt nicht an der " + "Verschreibungspflicht." + ), + ), + Norm( + zitat="§ 14 AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 14", + warum=( + "Führt Satz 7 aus: Absatz 1 umschreibt die ausgeschlossenen Arzneimittel " + "nach ihrer Zweckbestimmung, Absatz 2 wiederholt die Anwendungsgebiete, " + "Absatz 3 stellt sie in Anlage II zusammen." + ), + ), + Norm( + zitat="AM-RL Anlage II", + warum=( + "Die Übersicht der ausgeschlossenen Fertigarzneimittel und Anwendungsgebiete " + "— hier entscheidet sich die Frage nach dem einzelnen Arzneimittel. Nicht " + "indiziert, aber als deterministischer Lookup vorhanden (amrl_lifestyle)." + ), + ), + Norm( + zitat="§ 34 Abs. 2 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 34", + absatz="2", + warum=( + "Der einzige Weg an dem Ausschluss vorbei: Versicherte mit festgestellter " + "schwerer Tabakabhängigkeit haben Anspruch auf eine einmalige Versorgung mit " + "Arzneimitteln zur Tabakentwöhnung im Rahmen evidenzbasierter Programme; " + "erneut frühestens drei Jahre nach Abschluss der Behandlung." + ), + wenn=Bedingung(nur_bei=_TABAK), + ), + Norm( + zitat="§ 14a AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 14a", + warum=( + "Setzt § 34 Abs. 2 um: Absatz 1 hält den Ausschluss nach § 34 Abs. 1 Satz 7 " + "ff. SGB V ausdrücklich fest, Absatz 2 gewährt die Ausnahme, Absatz 3 nennt " + "Anlage IIa und die Voraussetzungen, unter denen eine schwere " + "Tabakabhängigkeit festgestellt ist." + ), + wenn=Bedingung(nur_bei=_TABAK), + ), + Norm( + zitat="AM-RL Anlage IIa", + warum=( + "Führt die zur Tabakentwöhnung ausnahmsweise verordnungsfähigen Arzneimittel " + "— hier entscheidet sich, ob die Ausnahme den Wirkstoff erfasst. Nicht " + "indiziert, aber als deterministischer Lookup vorhanden " + "(amrl_tabakentwoehnung)." + ), + wenn=Bedingung(nur_bei=_TABAK), + ), + Norm( + zitat="§ 31 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 31", + absatz="1", + tragend=False, + warum=( + "Anspruchsnorm — gewährt die Versorgung ausdrücklich nur, soweit sie nicht " + "nach § 34 ausgeschlossen ist. Gehört in die Antwort, entscheidet sie aber " + "nicht." + ), + ), + ), + fehlgriffe=( + Fehlgriff( + # Der gemessene Fehler: die Erstattungsfähigkeit eines + # Lifestyle-Arzneimittels aus der Anspruchsnorm hergeleitet. + muster=r"§\s*31\b", + warum=( + "§ 31 Abs. 1 Satz 1 SGB V gewährt den Anspruch nur, „soweit die Arzneimittel " + "nicht nach § 34 oder durch Richtlinien nach § 92 Absatz 1 Satz 2 Nummer 6 " + "ausgeschlossen sind“. Für Arzneimittel zur Erhöhung der Lebensqualität " + "trifft diese Ausschlussentscheidung § 34 Abs. 1 Satz 7." + ), + ), + ), + hinweise=( + Hinweis( + # Die Verwechslung, die in einer § 34-Antwort naheliegt: Satz 1 + # schließt nach der Verschreibungspflicht aus, Satz 7 nach der + # Zweckbestimmung. Viagra, Wegovy und Champix sind + # verschreibungspflichtig und trotzdem ausgeschlossen. + text=( + "Der Ausschluss nach § 34 Abs. 1 Satz 7 SGB V trifft auch " + "verschreibungspflichtige Arzneimittel: er hängt nicht an der " + "Verschreibungspflicht wie der des Satzes 1, sondern an der " + "Zweckbestimmung (§ 14 Abs. 1 AM-RL)." + ), + ), + Hinweis( + # Ohne Fallbezug ist die Tabakentwöhnung ein Sonderfall und + # gehört nicht in die Antwort. + text=( + "Zur Tabakentwöhnung besteht die eine gesetzliche Ausnahme: Versicherte mit " + "festgestellter schwerer Tabakabhängigkeit haben nach § 34 Abs. 2 SGB V, " + "§ 14a AM-RL Anspruch auf eine einmalige Versorgung im Rahmen " + "evidenzbasierter Programme; die dafür verordnungsfähigen Arzneimittel führt " + "Anlage IIa. Ob die Voraussetzungen vorliegen, ist eine ärztliche " + "Feststellung nach § 14a Abs. 3 AM-RL und keine Auskunft dieses Registers." + ), + wenn=Bedingung(nur_bei=_TABAK), + ), + ), + ), + Anker( + key="apothekenwahl_zuweisung", + thema="Freie Apothekenwahl und Zuweisungsverbot", + # Drei Signalsätze, weil dieselbe Frage in drei Registern gestellt wird. + # Die erste Fassung hatte nur den ersten und traf die gemessene Frage + # deshalb nicht: sie sprach von „vorgeben, in welcher Apotheke", nicht + # von „zuweisen". + # + # Satz 2 und 3 verlangen zusätzlich einen Akteur (Arzt, Praxis, Kasse). + # Ohne diese Schranke spränge der Anker auf jede Frage an, in der eine + # Apotheke irgendetwas empfiehlt. + # + # Warum Satz 3 eine Richtungsangabe nimmt und kein Verb: „weiterleiten + # an" traf zusammen mit „Apotheke" und „Kasse" die Frage „Muss die + # Apotheke die Verordnung an die Krankenkasse weiterleiten?" — eine + # echte Abrechnungsfrage, die mit dem Zuweisungsverbot nichts zu tun + # hat. Entscheidend ist nicht das Verb, sondern dass die Apotheke das + # **Ziel** ist. Im Deutschen steht das Verb am Satzende, weshalb ein + # Bigramm wie „übermitteln an" ohnehin an der Wortstellung scheitert. + signale=( + ( + ( + "zuweis", + "zuführ", + "zufuehr", + "zuleiten", + "makeln", + "makelverbot", + "apothekenwahl", + "freie wahl", + "freier wahl", + "frei wähl", + "frei waehl", + "bestimmte apotheke", + "bestimmten apotheke", + "bestimmter apotheke", + "bestimmte apotheken", + "welcher apotheke", + "welche apotheke", + ), + ("apotheke", "apotheken", "leistungserbringer"), + ), + ( + ( + "vorgeb", + "vorschreib", + "beeinfluss", + "steuer", + "lenken", + "nahelegen", + "nahe legen", + "empfehl", + "verweis", + "aussuchen", + "auswählen", + "auswaehlen", + ), + ("apotheke", "apotheken"), + _AKTEURE, + ), + ( + ( + "an eine apotheke", + "an die apotheke", + "an eine bestimmte apotheke", + "an bestimmte apotheken", + "zu einer apotheke", + "zu einer bestimmten apotheke", + "in eine bestimmte apotheke", + ), + _AKTEURE, + ), + ), + kernaussage=( + "Nein. Versicherte wählen die Apotheke frei (§ 31 Abs. 1 Satz 5 SGB V). " + "Vertragsärzte und Krankenkassen dürfen sie weder dahin beeinflussen, " + "Verordnungen bei einer bestimmten Apotheke einzulösen, noch Verordnungen " + "unmittelbar oder mittelbar bestimmten Apotheken zuweisen (Satz 6); für " + "elektronische Verordnungen gilt nichts anderes (Satz 7). Ausgenommen ist nur, " + "was gesetzlich anders bestimmt ist oder was aus medizinischen Gründen im " + "Einzelfall als Empfehlung geboten ist." + ), + normen=( + Norm( + zitat="§ 31 Abs. 1 Satz 5 bis 7 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 31", + absatz="1", + warum=( + "Satz 5 gibt den Versicherten die freie Apothekenwahl, Satz 6 verbietet " + "Vertragsärzten und Krankenkassen Beeinflussung und Zuweisung, Satz 7 " + "erstreckt beides ausdrücklich auf elektronische Verordnungen." + ), + ), + Norm( + zitat="§ 11 ApoG", + tragend=False, + warum=( + "Spiegelt das Verbot auf der Seite der Apotheke (Absprachen mit " + "Ärzten). Nicht indiziert — die Antwort kann es benennen, nicht " + "zitieren." + ), + ), + ), + fehlgriffe=( + Fehlgriff( + muster=r"§\s*360\b", + warum=( + "§ 360 SGB V regelt die Pflicht zur elektronischen Verordnung. Aus der " + "technischen Ausgestaltung des E-Rezepts folgt kein Zuweisungsverbot — " + "es steht in § 31 Abs. 1 Satz 6 und gilt nach Satz 7 auch für " + "elektronische Verordnungen." + ), + ), + ), + hinweise=( + Hinweis( + text=( + "Das Verbot ist ein Verbot, nicht bloß eine fehlende Erlaubnis: § 31 " + "Abs. 1 Satz 6 SGB V untersagt Beeinflussung und Zuweisung ausdrücklich. " + "Eine Antwort, die nur feststellt, es gebe keine Berechtigung, verfehlt " + "die Rechtslage." + ), + ), + ), + ), + Anker( + key="sonderkennzeichen", + thema="PZN oder Sonderkennzeichen in der Abrechnung", + signale=( + ( + ( + "sonderkennzeichen", + "sonder-pzn", + "sonderpzn", + "pseudo-pzn", + "pseudopzn", + "sonder-kennzeichen", + ), + ), + ), + kernaussage=( + "Zu übermitteln ist die Pharmazentralnummer des tatsächlich abgegebenen " + "Fertigarzneimittels (§ 300 Abs. 1 Nr. 1 SGB V, § 2 Abs. 1 " + "Arzneimittelabrechnungsvereinbarung). Ein Sonderkennzeichen ist zu verwenden, " + "soweit die Technische Anlage 1 zur Vereinbarung für den konkreten Abgabe- oder " + "Abrechnungssachverhalt eines vorsieht. Ob es die Produkt-PZN ersetzt oder einen " + "zusätzlichen Sachverhalt abbildet, richtet sich nach der dortigen Regelung." + ), + normen=( + Norm( + zitat="§ 2 Abs. 1 Arzneimittelabrechnungsvereinbarung", + corpus_id="amabrv", + container_id="Vertrag", + section_id="§ 2", + absatz="1", + warum=( + "Bestimmt das bundeseinheitliche Kennzeichen nach § 300 Abs. 3 Satz 1 " + "Nr. 1 SGB V und rechnet die Sonderkennzeichen der Technischen Anlage 1 " + "ausdrücklich dazu." + ), + ), + Norm( + zitat="§ 300 Abs. 1 und 3 SGB V", + corpus_id="sgb5", + container_id="Zehntes Kapitel", + section_id="§ 300", + absatz="1", + warum=( + "Verpflichtet die Apotheke, das Kennzeichen maschinenlesbar zu " + "übertragen, und ist die Ermächtigungsgrundlage der Vereinbarung." + ), + ), + Norm( + zitat="Technische Anlage 1 zur Arzneimittelabrechnungsvereinbarung", + tragend=False, + warum=( + "Führt die einzelnen Sonderkennzeichen und bestimmt, für welchen " + "Sachverhalt welches gilt. Nicht indiziert — die Vereinbarung verweist " + "darauf, das Verzeichnis selbst liegt nicht vor." + ), + ), + ), + hinweise=( + Hinweis( + text=( + "Maßgeblich ist das **abgegebene**, nicht das verordnete Fertigarzneimittel. " + "Bei einer Ersetzung fallen beide auseinander." + ), + ), + Hinweis( + text=( + "Welches Sonderkennzeichen im Einzelfall gilt, steht in der Technischen " + "Anlage 1 und nicht in der Vereinbarung selbst. Sie ist nicht Teil des " + "indizierten Bestandes; die Antwort kann die Regel nennen, nicht das " + "einzelne Kennzeichen." + ), + ), + ), + ), + Anker( + key="rabatt_vor_import", + thema="Abgaberangfolge: Rabattarzneimittel und preisgünstiger Import", + signale=( + ( + ("import", "reimport", "re-import", "parallelimport", "parallelvertrieb"), + ( + "rabatt", + "130a", + "rabattvertrag", + "rabattarzneimittel", + "rabattbegünstigt", + "rabattbeguenstigt", + ), + ), + ), + kernaussage=( + "Vorrang hat das rabattbegünstigte Fertigarzneimittel; die Abgabe eines " + "preisgünstigen Imports tritt dahinter zurück (§ 11 Abs. 1 Rahmenvertrag, " + "§ 129 Abs. 1 Satz 8 SGB V). Von der Abgaberangfolge darf nur unter den " + "Voraussetzungen der §§ 10 bis 14 Rahmenvertrag abgewichen werden, insbesondere " + "bei Nichtverfügbarkeit oder bei ordnungsgemäß geltend gemachten pharmazeutischen " + "Bedenken. Welches Arzneimittel dann abzugeben ist, richtet sich nach dem " + "Auswahlbereich des § 9 und der weiteren Rangfolge." + ), + normen=( + Norm( + zitat="§ 10 Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 10", + warum=( + "Die Abgaberangfolge selbst: die Auswahl ist nach Maßgabe der §§ 11 bis " + "14 zu treffen. Ohne diese Norm wird eine Rangfolge behauptet, ohne die " + "Vorschrift zu nennen, die sie anordnet." + ), + ), + Norm( + zitat="§ 11 Abs. 1 Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 11", + absatz="1", + warum=( + "Vorrang der Rabattverträge: vorrangig ist ein Fertigarzneimittel " + "abzugeben, für das ein Rabattvertrag nach § 130a Abs. 8 SGB V besteht." + ), + ), + Norm( + zitat="§ 129 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Viertes Kapitel", + section_id="§ 129", + absatz="1", + warum=( + "Satz 1 Nr. 2 begründet die Importabgabepflicht samt Preisabstand; " + "Satz 8 ordnet an, dass die Abgabe eines Rabattarzneimittels ihr " + "vorgeht. Das ist die gesetzliche Grundlage der Rangfolge." + ), + ), + Norm( + zitat="§ 13 Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 13", + tragend=False, + warum="Führt die Abgabe preisgünstiger Importe nach § 9 im Einzelnen aus.", + ), + Norm( + zitat="§ 14 Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 14", + tragend=False, + warum=( + "Regelt abschließend, wann von der Abgaberangfolge abgewichen werden " + "darf. Eine Antwort, die nur sagt, die vorrangige Abgabe sei „nicht " + "möglich“, bleibt ohne diese Tatbestände unbestimmt." + ), + ), + ), + hinweise=( + Hinweis( + # Ohne Fallbezug ist das ein Sonderfall und gehört nicht in die Kette. + text=( + "§ 129 Abs. 1 Satz 9 SGB V nimmt biotechnologisch hergestellte und " + "antineoplastische Arzneimittel zur parenteralen Anwendung von der " + "Importabgabepflicht aus." + ), + wenn=Bedingung( + nur_bei=( + "parenteral", + "zytostatik", + "antineoplast", + "biotechnolog", + "biolog", + "zubereitung", + ) + ), + ), + ), + ), + Anker( + key="darreichungsform_austausch", + thema="Austauschbarkeit von Darreichungsformen", + signale=( + ( + ("darreichungsform",), + ( + "austausch", + "austauschbar", + "ersetz", + "aut idem", + "aut-idem", + "substitu", + "abweichen", + "abgeben statt", + "anstelle", + "andere", + "anderer", + "unterschiedlich", + ), + ), + ), + kernaussage=( + "Eine andere Darreichungsform darf abgegeben werden, wenn beide Formen nach " + "§ 9 Abs. 3 Buchst. d Rahmenvertrag in Verbindung mit § 40 und Anlage VII Teil A " + "der AM-RL austauschbar sind. Die Austauschbarkeit folgt aus der Zuordnung in " + "Anlage VII Teil A, nicht aus einer Ähnlichkeit der Formen; Einzeldosis- und " + "Mehrdosenbehältnis sind untereinander nicht austauschbar. Daneben müssen die " + "übrigen Kriterien des § 9 Abs. 3 erfüllt sein: gleicher Wirkstoff, identische " + "Wirkstärke, identische Packungsgröße und Zulassung für ein gleiches " + "Anwendungsgebiet." + ), + normen=( + Norm( + zitat="§ 9 Abs. 3 Buchst. d Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 9", + absatz="3", + warum=( + "Nennt die Kriterien: identische Bezeichnung heißt gleich; austauschbar " + "ist, was Anlage VII Teil A der AM-RL benennt; Einzeldosis- und " + "Mehrdosenbehältnis sind nicht austauschbar; bei Importen zählt die Form " + "des Referenzarzneimittels. Buchstaben a bis c und e bis f nennen die " + "übrigen Voraussetzungen des Austauschs." + ), + ), + Norm( + zitat="§ 129 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Viertes Kapitel", + section_id="§ 129", + absatz="1", + warum=( + "Satz 2 verlangt neben der gleichen oder austauschbaren Darreichungsform " + "Identität in Wirkstärke und Packungsgröße sowie die Zulassung für ein " + "gleiches Anwendungsgebiet. Die Austauschbarkeit der Form ist also nur " + "eine von mehreren Voraussetzungen." + ), + ), + Norm( + zitat="§ 40 AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 40", + warum=( + "Die Hinweise des G-BA nach § 129 Abs. 1a Satz 1 SGB V selbst, mit " + "Anlage VII Teil A als Fundstelle." + ), + ), + Norm( + zitat="AM-RL Anlage VII Teil A", + warum=( + "Die Liste der austauschbaren Darreichungsformen — hier entscheidet sich " + "die Frage. Nicht indiziert, aber als deterministischer Lookup vorhanden " + "(amrl_austauschbarkeit)." + ), + ), + Norm( + zitat="§ 40c Abs. 1 AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 40c", + tragend=False, + warum=( + "Zusätzliche Regelungen zu den Darreichungsformen — aber nur für " + "biotechnologisch hergestellte biologische Arzneimittel." + ), + wenn=Bedingung(nur_bei=_BIOLOGISCH), + ), + ), + fehlgriffe=( + Fehlgriff( + # Steht auch neben der richtigen Norm falsch: § 40c verengt die + # allgemeine Frage auf einen Sonderfall, den sie nicht aufwirft. + muster=r"§\s*40c\b", + warum=( + "§ 40c AM-RL betrifft ausschließlich biotechnologisch hergestellte " + "biologische Arzneimittel. Für die allgemeine Austauschbarkeit von " + "Darreichungsformen ist § 40 in Verbindung mit Anlage VII Teil A " + "maßgeblich; § 40c tritt nur hinzu, wenn ein biologisches Arzneimittel " + "betroffen ist." + ), + nur_ohne_tragende=False, + wenn=Bedingung(nicht_bei=_BIOLOGISCH), + ), + ), + hinweise=( + Hinweis( + text=( + "Ohne beide konkret benannten Darreichungsformen lässt sich die " + "Zugehörigkeit zu einer Gruppe der Anlage VII Teil A nicht feststellen — " + "und damit die Frage nicht abschließend beantworten." + ), + ), + ), + ), + Anker( + # Der einzige Anker, dessen Beleg keine falsche Antwort ist, sondern + # eine Messung: die Kettenabdeckung des Falls + # `kette_verordnungsausschluss_abgabe` lag am 19.08.2026 bei 1 von 4. + # § 34 SGB V, § 16 AM-RL und § 6 Rahmenvertrag erreichten den Kontext + # nicht, und keiner der gefundenen Texte nennt sie, sodass auch die + # Verweisauflösung sie nicht hätte holen können. Das ist dieselbe Lage + # wie bei den fünf übrigen Ankern — die Norm steht im Index und die + # Ähnlichkeit findet sie nicht —, nur diesmal vor der falschen Antwort + # bemerkt statt danach. + # + # Kein `Fehlgriff`-Eintrag: das Register nimmt Muster nur auf, wenn + # eine Auswertung sie als tatsächlich falsch genannte Norm gezeigt hat. + # Eine Kettenlücke ist kein solcher Beleg. + key="abgabe_nicht_verordnungsfaehig", + thema="Abgabe eines nicht verordnungsfähigen Arzneimittels zu Lasten der GKV", + signale=( + ( + ( + "nicht verordnungsfähig", + "nicht verordnungsfaehig", + "nicht verordnungsfähiges", + "nicht verordnungsfaehiges", + "verordnungsausschluss", + "verordnungseinschränkung", + "verordnungseinschraenkung", + "von der versorgung ausgeschlossen", + "ausgeschlossenes arzneimittel", + "anlage iii", + ), + ( + "abgeben", + "abgabe", + "beliefer", + "einlösen", + "einloesen", + "zu lasten", + "zulasten", + "abrechn", + "retax", + "vergütungsanspruch", + "verguetungsanspruch", + ), + ), + ), + kernaussage=( + "Der Anspruch der Versicherten besteht nach § 31 Abs. 1 Satz 1 SGB V nur, " + "soweit das Arzneimittel nicht nach § 34 SGB V oder durch die Richtlinien " + "nach § 92 Abs. 1 Satz 2 Nr. 6 SGB V ausgeschlossen ist. Die arzneimittel" + "rechtliche Zulassung und die Verschreibungspflicht sagen darüber nichts: " + "sie betreffen die Verkehrsfähigkeit, nicht die sozialrechtliche " + "Verordnungsfähigkeit zu Lasten der GKV. Ausgeschlossen oder eingeschränkt " + "wird auf Grundlage des § 92 Abs. 1 Satz 1 Halbsatz 3 SGB V in Verbindung " + "mit § 16 Abs. 1 und 2 AM-RL; die so erfassten Arzneimittel sind in " + "Anlage III der AM-RL zusammengestellt. Für die durch Richtlinie " + "ausgeschlossenen Arzneimittel eröffnet § 31 Abs. 1 Satz 4 SGB V die " + "Verordnung im medizinisch begründeten Einzelfall mit Begründung; adressiert " + "ist dort der Vertragsarzt. Der Vergütungsanspruch der Apotheke entsteht " + "nach § 6 Abs. 1 Satz 1 Rahmenvertrag mit der Belieferung einer gültigen " + "ordnungsgemäßen vertragsärztlichen Verordnung." + ), + normen=( + Norm( + zitat="§ 31 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 31", + absatz="1", + chunks=1, + warum=( + "Satz 1 trägt den Anspruch samt Ausschlussvorbehalt — „soweit die " + "Arzneimittel nicht nach § 34 oder durch Richtlinien nach § 92 Absatz 1 " + "Satz 2 Nummer 6 ausgeschlossen sind“. Satz 4 eröffnet für die durch " + "Richtlinie ausgeschlossenen Arzneimittel die Verordnung im medizinisch " + "begründeten Einzelfall." + ), + ), + Norm( + zitat="§ 34 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 34", + absatz="1", + chunks=1, + warum=( + "Der zweite der beiden Ausschlusswege, die § 31 Abs. 1 Satz 1 nennt. " + "Wer nur den Richtlinienweg nennt, gibt die Grundregel unvollständig " + "wieder." + ), + ), + # Die Ermächtigung selbst — ohne sie steht die Anlage III für sich, + # und genau das ist der Mangel, den dieser Fall messen soll: eine + # gefundene Liste muss auf ihre Rechtsgrundlage zurückgeführt werden. + # Maßgeblich ist Satz 1 Halbsatz 3 (Einschränkung der Verordnung von + # Arzneimitteln), nicht Satz 2 Nr. 6 — der ermächtigt nur, überhaupt + # eine Arzneimittel-Richtlinie zu erlassen. § 31 Abs. 1 Satz 1 nennt + # deshalb Satz 2 Nr. 6, die Vorbemerkung der Anlage III den Satz 1 + # Halbsatz 3; beide Zitate sind an ihrer Stelle richtig. + Norm( + zitat="§ 92 Abs. 1 Satz 1 Halbsatz 3 SGB V", + corpus_id="sgb5", + container_id="Viertes Kapitel", + section_id="§ 92", + absatz="1", + chunks=1, + warum=( + "Die gesetzliche Ermächtigung, die Verordnung von Arzneimitteln " + "einzuschränken oder auszuschließen, „wenn die Unzweckmäßigkeit " + "erwiesen oder eine andere, wirtschaftlichere Behandlungsmöglichkeit " + "mit vergleichbarem diagnostischen oder therapeutischen Nutzen " + "verfügbar ist“. Die Vorbemerkung der Anlage III nimmt genau diesen " + "Halbsatz in Verbindung mit § 16 Abs. 1 und 2 AM-RL in Bezug." + ), + ), + # Am 21.08.2026 vom Fachanwender auf die Absätze geschärft. Die + # Vorbemerkung der Anlage III nimmt ausdrücklich „§ 92 Abs. 1 Satz 1 + # Halbsatz 3 SGB V in Verbindung mit § 16 Abs. 1 und 2 AM-RL" in + # Bezug — und die beiden Absätze tragen Verschiedenes: Absatz 1 + # nennt die drei Gründe, Absatz 2 die Regelbeispiele. Der Anker holte + # vorher nur „§ 16" und damit den Abschnittskopf; die Messung + # `kette_verordnungseinschraenkung_abgabe` zeigte Abs. 2 als fehlend. + Norm( + zitat="§ 16 Abs. 1 AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 16", + absatz="1", + chunks=1, + warum=( + "„Verordnungseinschränkungen und -ausschlüsse von Arzneimitteln nach " + "§ 92“ — die Konkretisierung des Richtlinienwegs. Absatz 1 nennt die " + "drei Gründe: fehlender Nutzen, fehlende medizinische Notwendigkeit, " + "fehlende Wirtschaftlichkeit." + ), + ), + Norm( + zitat="§ 16 Abs. 2 AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 16", + absatz="2", + chunks=1, + warum=( + "Die Regelbeispiele, wann diese Voraussetzungen zutreffen — " + "Unzweckmäßigkeit, wirtschaftlichere Behandlungsmöglichkeit, fehlende " + "medizinische Notwendigkeit, nichtmedikamentöse Alternative, fixe " + "Kombination statt Monopräparat. Anlage III ist die Fundstelle, in der " + "die so ausgeschlossenen Arzneimittel zusammengestellt sind." + ), + ), + Norm( + zitat="§ 6 Abs. 1 Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 6", + absatz="1", + chunks=1, + warum=( + "Die Abgabeebene, nach der gefragt ist: Satz 1 lässt den " + "Vergütungsanspruch der Apotheke „mit Belieferung einer gültigen " + "ordnungsgemäßen vertragsärztlichen Verordnung“ entstehen. Ohne diese " + "Norm beantwortet die Antwort die Verordnungsfrage und nicht die " + "gestellte." + ), + ), + Norm( + zitat="AM-RL Anlage III", + tragend=False, + warum=( + "Führt die eingeschränkten und ausgeschlossenen Arzneimittel. Nicht " + "indiziert, aber als deterministischer Lookup vorhanden " + "(amrl_verordnungsausschluss)." + ), + ), + ), + hinweise=( + # Die beiden folgenden Hinweise stehen hier und nicht nur in der + # `kernaussage`, weil die Kernaussage die Antwort **nur bei einer + # Abweichung** ersetzt. Am 21.08.2026 nannte die Antwort § 31 und + # § 16 richtig — also kein Fehlgriff, also keine Ersetzung — und + # zitierte die Ermächtigung trotzdem als § 92 Abs. 1 Satz 2 Nr. 6, + # weil § 31 Abs. 1 Satz 1 sie so nennt. Der mittelbare Pfad blieb + # damit unaufgelöst. Ein Hinweis wird immer angehängt. + Hinweis( + text=( + "Die Ermächtigung für Anlage III ist § 92 Abs. 1 Satz 1 Halbsatz 3 " + "SGB V in Verbindung mit § 16 Abs. 1 und 2 AM-RL — nicht § 92 Abs. 1 " + "Satz 2 Nr. 6 SGB V. Satz 2 Nr. 6 ermächtigt dazu, überhaupt eine " + "Arzneimittel-Richtlinie zu erlassen; Satz 1 Halbsatz 3 dazu, die " + "Verordnung von Arzneimitteln einzuschränken oder auszuschließen. " + "§ 31 Abs. 1 Satz 1 SGB V verweist auf die Richtlinie, die " + "Vorbemerkung der Anlage III auf die Einschränkungsermächtigung." + ), + ), + Hinweis( + text=( + "Die arzneimittelrechtliche Zulassung und die Verschreibungspflicht " + "entscheiden die Frage nicht: sie betreffen die Verkehrsfähigkeit " + "nach dem Arzneimittelrecht. Die sozialrechtliche " + "Verordnungsfähigkeit zu Lasten der GKV ist davon getrennt zu " + "prüfen." + ), + ), + Hinweis( + text=( + "Ob das konkrete Arzneimittel betroffen ist, entscheidet Anlage III der " + "AM-RL. Anders als bei der Substitutions-Ausschlussliste trägt dort die " + "Negativauskunft nicht: die Anlage ist eine Übersicht und nennt sich " + "nirgends abschließend." + ), + ), + Hinweis( + text=( + "Die Verordnung im medizinisch begründeten Einzelfall steht nach der " + "Vorbemerkung der Anlage III nur bei den Rechtsfolgen 3 bis 6 offen — " + "nicht bei einem gesetzlichen Ausschluss." + ), + ), + ), + ), + Anker( + # Das Spiegelbild des § 40c-Fehlgriffs im Anker + # `darreichungsform_austausch`: dort ist § 40c zu speziell, hier ist er + # genau richtig — und § 40b zu speziell. Beide Anker können zugleich + # greifen; der § 40c-Fehlgriff hängt an `nicht_bei=_BIOLOGISCH` und + # schweigt dann, der § 40b-Fehlgriff hier an den Signalen der + # parenteralen Zubereitung. + # + # Der Beleg ist eine gemessene falsche Antwort vom 21.08.2026: auf eine + # Frage, die die Abgabekonstellation ausdrücklich als unveränderte + # Fertigspritze beschrieb, führte der Normabschnitt „§ 40b Abs. 1, + # Abs. 3" an **erster** Stelle und zitierte im Wortlaut den Satz, der + # die Pflicht auf parenterale Zubereitungen beschränkt. In zwei + # weiteren Läufen schwankte die Antwort — mal mit Anlage VIIa, mal + # ohne, mal mit Abgrenzung, mal ohne. Genau diese Schwankung nimmt ein + # Anker heraus. + key="biosimilar_austausch", + thema="Austausch eines biologischen Referenzarzneimittels durch ein Biosimilar", + signale=( + ( + ("biosimilar", "biotechnolog", "biologische", "biologisches", "referenzarzneimittel"), + ( + "austausch", + "austauschbar", + "ersetz", + "substitu", + "abgeben", + "abgabe", + "aut idem", + "aut-idem", + ), + ), + ), + kernaussage=( + "Vor dem Austausch sind zwei Ebenen zu prüfen. Allgemein gelten die " + "Voraussetzungen des § 129 Abs. 1 Satz 2 SGB V und des § 9 Abs. 3 " + "Rahmenvertrag: gleicher Wirkstoff, identische Wirkstärke, identische " + "Packungsgröße, Zulassung für ein gleiches Anwendungsgebiet und gleiche oder " + "austauschbare Darreichungsform. Besonders gilt für biotechnologisch " + "hergestellte biologische Fertigarzneimittel § 40c AM-RL: das abzugebende " + "Arzneimittel muss nach Anlage VIIa dem verordneten Referenzarzneimittel " + "zugeordnet sein, bei gleicher gemeldeter Darreichungsform muss zusätzlich " + "das in der Fachinformation angegebene Behältnis übereinstimmen, und die " + "Ersetzung darf vom verordnenden Arzt nicht ausgeschlossen sein. Für ein " + "unverändert abgegebenes Fertigarzneimittel gilt § 40c, nicht § 40b — der " + "betrifft die parenterale Zubereitung zur unmittelbaren ärztlichen Anwendung." + ), + normen=( + # Welche der beiden Parallelvorschriften trägt, entscheidet die + # Abgabekonstellation — deshalb zwei Einträge mit gegenläufiger + # Bedingung statt eines festen `tragend`. + Norm( + zitat="§ 40c AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 40c", + chunks=2, + warum=( + "Die Spezialnorm für den Austausch biotechnologisch hergestellter " + "biologischer **Fertigarzneimittel** durch die Apotheke. Absatz 1 " + "nennt die Abgabekriterien samt Behältnisgleichheit, Absatz 3 die " + "Zuordnung von Referenzarzneimittel und Biosimilar über Anlage VIIa." + ), + wenn=Bedingung(nicht_bei=_PARENTERAL), + ), + Norm( + zitat="§ 129 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Viertes Kapitel", + section_id="§ 129", + absatz="1", + chunks=1, + warum=( + "Die allgemeinen Voraussetzungen jeder Ersetzung: Satz 2 verlangt " + "Identität in Wirkstärke und Packungsgröße, eine gleiche oder " + "austauschbare Darreichungsform und die Zulassung für ein gleiches " + "Anwendungsgebiet." + ), + ), + Norm( + zitat="§ 9 Abs. 3 Rahmenvertrag", + corpus_id="rv129", + container_id="Vertrag", + section_id="§ 9", + absatz="3", + chunks=1, + warum=( + "Der Auswahlbereich, in dem die allgemeinen Kriterien für die Abgabe " + "ausgeführt sind. Buchstabe a stellt Biologika und Bezug nehmend " + "zugelassene Arzneimittel ausdrücklich wirkstoffgleich und verweist " + "für die Auswahl auf § 40c AM-RL." + ), + ), + Norm( + zitat="AM-RL Anlage VIIa", + warum=( + "Die Zuordnung von Referenzarzneimittel und Biosimilar — hier " + "entscheidet sich, ob überhaupt ausgetauscht werden darf. Nicht " + "indiziert, aber als deterministischer Lookup vorhanden " + "(amrl_biosimilars)." + ), + ), + Norm( + zitat="§ 40b AM-RL", + corpus_id="amrl", + container_id="Richtlinie", + section_id="§ 40b", + chunks=1, + warum=( + "Die Parallelvorschrift für parenterale Zubereitungen aus " + "Fertigarzneimitteln zur unmittelbaren ärztlichen Anwendung — in " + "einem solchen Fall die tragende Norm, sonst gar keine." + ), + wenn=Bedingung(nur_bei=_PARENTERAL), + ), + ), + fehlgriffe=( + Fehlgriff( + # Wie bei § 40c in der allgemeinen Darreichungsformfrage: die + # Vorschrift stört auch neben der richtigen, weil sie den Fall + # auf eine Konstellation verengt, die er nicht aufwirft. + muster=r"§\s*40b\b", + warum=( + "§ 40b AM-RL verpflichtet zur Ersetzung nur, „wenn es sich um eine " + "parenterale Zubereitung aus Fertigarzneimitteln zur unmittelbaren " + "ärztlichen Anwendung handelt“. Wird das Fertigarzneimittel " + "unverändert an die versicherte Person abgegeben, ist § 40c AM-RL " + "die maßgebliche Vorschrift; § 40b bleibt als Abgrenzung richtig, " + "als tragende Norm dieses Falls nicht." + ), + nur_ohne_tragende=False, + wenn=Bedingung(nicht_bei=_PARENTERAL), + ), + ), + hinweise=( + Hinweis( + text=( + "Die Abgrenzung entscheidet die Abgabekonstellation, nicht das " + "Arzneimittel: § 40c AM-RL gilt für das unverändert abgegebene " + "Fertigarzneimittel, § 40b AM-RL für das Fertigarzneimittel, das zu " + "einer parenteralen Zubereitung zur unmittelbaren ärztlichen " + "Anwendung verarbeitet wird." + ), + ), + Hinweis( + text=( + "Ohne das konkrete Referenzarzneimittel lässt sich die Zuordnung in " + "Anlage VIIa nicht feststellen — und ohne sie ist die Frage nicht " + "abschließend zu beantworten." + ), + ), + ), + ), + Anker( + # Der zweite Anker aus einer Messung statt aus einer falschen Antwort, + # und die Lücke sitzt hier zwischen zwei Kapiteln: die Frage nach der + # Aktualisierung des Medikationsplans erreichte am 21.08.2026 § 31a + # SGB V im Dritten Kapitel auf Rang 1 — und § 334 SGB V im Elften + # Kapitel überhaupt nicht (Kettenabdeckung 1 von 2). An seiner Stelle + # kam § 342 Abs. 2a, thematisch benachbart und nicht die Norm, auf die + # § 31a verweist. + # + # Anders als bei den übrigen Ankern nennt der gefundene Text die + # fehlende Norm ausdrücklich („elektronischen Medikationsplan nach + # § 334 Absatz 1 Satz 2 Nummer 4"). Die Verweisauflösung hätte sie also + # holen können — sie ist seit dem 21.08.2026 abgeschaltet, weil sie im + # festen Budget mehr verdrängt als sie beibringt. Ein Anker holt genau + # diese eine Norm, statt drei beliebige. + # + # Kein `Fehlgriff`: eine Kettenlücke ist kein Beleg für eine falsch + # genannte Norm. + key="medikationsplan_aktualisierung", + thema="Aktualisierung des Medikationsplans durch die Apotheke", + signale=( + ( + ("medikationsplan",), + ( + "aktualisier", + "fortschreib", + "ergänz", + "ergaenz", + "ändern", + "aendern", + "pflegen", + "eintrag", + "elektronisch", + "muss", + "darf", + ), + ), + ), + kernaussage=( + "Die Apotheke hat den Medikationsplan nach § 31a Abs. 3 Satz 2 SGB V bei der " + "Abgabe eines Arzneimittels auf Wunsch des Versicherten zu aktualisieren, " + "soweit die Abgabe eine Aktualisierung erforderlich macht. Für den " + "elektronischen Medikationsplan nach § 334 Abs. 1 Satz 2 Nr. 4 SGB V besteht " + "der Anspruch nach § 31a Abs. 3 Satz 3 SGB V, wenn der Versicherte der " + "abgebenden Apotheke den Zugriff erlaubt; gespeichert wird die Aktualisierung " + "nach Satz 5 nur, sofern der Versicherte das wünscht. Seit die elektronische " + "Patientenakte nach § 342 Abs. 1 Satz 2 SGB V zur Verfügung steht, ist die " + "Aktualisierung und Speicherung nach § 31a Abs. 3a SGB V eine Pflicht, soweit " + "der Versicherte dem Zugriff nicht widersprochen hat." + ), + normen=( + Norm( + zitat="§ 31a SGB V", + corpus_id="sgb5", + container_id="Drittes Kapitel", + section_id="§ 31a", + chunks=2, + warum=( + "Absatz 3 Satz 2 begründet die Aktualisierung durch die Apotheke auf " + "Wunsch des Versicherten, Satz 3 den darüber hinausgehenden Anspruch " + "beim elektronischen Medikationsplan, Satz 5 die Speicherung. Absatz 3a " + "macht aus dem Anspruch eine Pflicht, sobald die elektronische " + "Patientenakte zur Verfügung steht. Ohne Absatz gefasst, weil die " + "Antwort auf Absatz 1, 3 und 3a zugleich beruht." + ), + ), + Norm( + zitat="§ 334 Abs. 1 SGB V", + corpus_id="sgb5", + container_id="Elftes Kapitel", + section_id="§ 334", + absatz="1", + chunks=1, + warum=( + "Satz 2 Nr. 4 ist der elektronische Medikationsplan — die Norm, auf die " + "§ 31a für die zweite Hälfte der Frage verweist. Sie steht im Elften " + "Kapitel und wird von einer Frage nach der Apotheke nicht gefunden." + ), + ), + ), + hinweise=( + Hinweis( + # Ohne Fallbezug ist die Anspruchsvoraussetzung ein Sonderfall + # und gehört nicht in jede Antwort. + text=( + "Der Anspruch auf den Medikationsplan setzt nach § 31a Abs. 1 Satz 1 " + "SGB V voraus, dass der Versicherte gleichzeitig mindestens drei " + "verordnete Arzneimittel anwendet; erstellt wird er vom Vertragsarzt, " + "die Apotheke aktualisiert ihn." + ), + ), + ), + ), +) + + +# --------------------------------------------------------------------------- +# Erkennung in der Frage +# --------------------------------------------------------------------------- + +def _norm(text: str) -> str: + return " ".join(str(text or "").lower().split()) + + +def _haystack(question: str) -> str: + """Die Frage, wie das Register sie liest — samt der Marken aus dem Alter. + + Getrennt von `_norm`, weil `_trifft` auch die **Signale** normalisiert: ein + Signal hat kein Alter, und eine Anreicherung dort wäre nur Rauschen. + """ + text = _norm(question) + if not text: + return text + text = _VERNEINT.sub(" ", text) + return f"{text}{_alterssignale(text)}" + + +# „otc“ als blanker Substring träfe irgendwann in einem längeren Wort. Kurze +# Signale bekommen deshalb eine Wortgrenze; lange bleiben Substrings, weil +# „zuweis“ gerade „zuweisen/Zuweisung/zugewiesen“ mitnehmen soll. +_KURZ = 4 + + +def _trifft(signal: str, haystack: str) -> bool: + signal = _norm(signal) + if not signal: + return False + if len(signal) <= _KURZ and signal.isalnum(): + return re.search(rf"(? List[Anker]: + """Alle Anker, bei denen mindestens ein Signalsatz vollständig trifft.""" + haystack = _haystack(question) + if not haystack: + return [] + return [anker for anker in ANKER if anker.trifft(haystack)] + + +def pflichtabruf(question: str, *, max_chunks: int = 2) -> List[Dict[str, Any]]: + """Adressen der tragenden Normen, die unabhängig vom Ranking zu holen sind. + + Bewusst schlichte dicts: der `FederatedRetriever` soll dieses Modul nicht + importieren müssen. Er bekommt eine Liste von Zielen und weiß nicht, dass + dahinter eine Rechtsbehauptung steht. + """ + haystack = _haystack(question) + ziele: List[Dict[str, Any]] = [] + gesehen = set() + for anker in passende_anker(question): + for norm in anker.tragende(haystack): + if not norm.abrufbar: + continue + schluessel = (norm.corpus_id, norm.container_id, norm.section_id, norm.absatz) + if schluessel in gesehen: + continue + gesehen.add(schluessel) + ziele.append(norm.als_ziel(max_chunks=max_chunks)) + return ziele + + +def pflicht_korpora(question: str) -> List[str]: + """Korpora, die eine Frage erreichen muss, auch ohne im Text genannt zu sein. + + Das schließt die Lücke, die „Sonderkennzeichen“ aufgedeckt hat: das Wort + nennt kein Korpus, die Regelung steht aber in der Abrechnungsvereinbarung — + und deren 106 Chunks verlieren im Ähnlichkeitswettbewerb gegen 6.670 des + SGB V. Ein zweites Alias-Register braucht es dafür nicht: die Anker nennen + ihr Korpus bereits. + """ + out: List[str] = [] + for ziel in pflichtabruf(question): + if ziel["corpus_id"] not in out: + out.append(ziel["corpus_id"]) + return out + + +def anlagen_geprueft(question: str) -> bool: + """Nennt ein Anker eine AM-RL-Anlage als Fundstelle? + + Für den Reichweiten-Hinweis: er meldete die AM-RL-Anlagen als ungeprüft, + während die Antwort sich zwei Absätze darüber auf Anlage VII Teil A berief. + """ + haystack = _haystack(question) + return any( + "anlage" in norm.zitat.lower() and "am-rl" in norm.zitat.lower() + for anker in passende_anker(question) + for norm in anker.geltende_normen(haystack) + ) + + +# --------------------------------------------------------------------------- +# Abgleich mit der Antwort +# --------------------------------------------------------------------------- + +@dataclass +class Befund: + status: str + anker: List[Anker] = field(default_factory=list) + fehlend: List[Norm] = field(default_factory=list) + genannt: List[Norm] = field(default_factory=list) + erwartet: List[Norm] = field(default_factory=list) + fehlgriffe: List[Fehlgriff] = field(default_factory=list) + hinweise: List[str] = field(default_factory=list) + kernaussage: str = "" + grund: str = "" + + @property + def ist_belastbar(self) -> bool: + return self.status in {"abweichung", "unvollstaendig", "bestaetigt"} + + @property + def korrigiert(self) -> bool: + """Der Fall, in dem die Hauptantwort neu gesetzt wird.""" + return self.status == "abweichung" + + def to_dict(self) -> Dict[str, Any]: + return { + "status": self.status, + "anker": [a.key for a in self.anker], + "erwartete_normen": [n.zitat for n in self.erwartet], + "genannt": [n.zitat for n in self.genannt], + "fehlend": [n.zitat for n in self.fehlend], + "fehlgriffe": [f.muster for f in self.fehlgriffe], + "grund": self.grund, + } + + +# Der Abschnitt, in dem die Antwort ihre maßgebliche Norm nennt. Beide Schreib- +# weisen kommen vor; `answer_schema.SECTION_HEADINGS` kennt sie ebenfalls, aber +# dieses Modul kommt bewusst ohne Import aus — es soll auch auf einem rohen +# Antworttext arbeiten können. +_NORM_UEBERSCHRIFT = "Maßgebliche Norm" +_KURZ_UEBERSCHRIFT = "Kurzantwort" + +_UEBERSCHRIFT_RE = re.compile( + r"^\s*#*\s*(Kurzantwort|Maßgebliche\s+Norm(?:\(en\))?|Wortlaut(?:\s*/\s*Kriterien)?|" + r"Einordnung|Ausnahmen|Ergebnis|Heilungen|Retaxationsgrenzen)\s*:?\s*$", + re.I, +) + + +def _abschnitte(answer: str) -> List[Tuple[Optional[str], List[str]]]: + """(Überschrift | None, Zeilen) in Reihenfolge. Der erste Block trägt None.""" + blocks: List[Tuple[Optional[str], List[str]]] = [] + heading: Optional[str] = None + body: List[str] = [] + for line in (answer or "").split("\n"): + treffer = _UEBERSCHRIFT_RE.match(line) + if treffer is None: + body.append(line) + continue + blocks.append((heading, body)) + heading, body = " ".join(treffer.group(1).split()), [] + blocks.append((heading, body)) + return blocks + + +def _ist(heading: Optional[str], name: str) -> bool: + return bool(heading) and str(heading).lower().startswith(name.lower()) + + +def normabschnitt(answer: str) -> str: + """Der Text unter „Maßgebliche Norm“, oder die ganze Antwort ohne den Abschnitt. + + Der Rückfall auf die ganze Antwort ist kein Notbehelf: nennt das Modell die + Norm nur in der Kurzantwort, ist sie genannt. Geprüft wird, ob die tragende + Norm überhaupt vorkommt — nicht, ob sie unter der richtigen Überschrift steht. + """ + for heading, body in _abschnitte(answer): + if _ist(heading, _NORM_UEBERSCHRIFT): + return "\n".join(body) + return answer or "" + + +# „§ 34 Abs. 1 SGB V“ soll auch auf „§ 34 SGB V“ und „§ 34 Absatz 1“ passen: das +# Modell schreibt die Norm selten genau so, wie das Register sie führt. Geprüft +# wird deshalb nur der Paragraf — der Absatz ist eine Genauigkeit, die eine +# Substring-Prüfung nicht belastbar feststellen kann. +_PARAGRAF_RE = re.compile(r"§+\s*(\d+[a-z]?)") +# Anlagen haben keine §-Nummer und werden über ihren Namen erkannt. +_ANLAGE_RE = re.compile(r"anlage\s+([ivx]+[a-z]?|\d+[a-z]?)(?:\s+teil\s+([ab]))?", re.I) + + +def _paragrafen(text: str) -> set: + return {m.group(1).lower() for m in _PARAGRAF_RE.finditer(text or "")} + + +def _anlagen(text: str) -> set: + return { + (m.group(1).lower(), (m.group(2) or "").lower()) for m in _ANLAGE_RE.finditer(text or "") + } + + +def _adresse(zitat: str) -> Optional[tuple]: + """Die vergleichbare Kennung eines Zitats: ('§', '34') oder ('anlage', 'vii', 'a').""" + anlage = _ANLAGE_RE.search(zitat or "") + if anlage and "§" not in (zitat or ""): + return ("anlage", anlage.group(1).lower(), (anlage.group(2) or "").lower()) + paragraf = _PARAGRAF_RE.search(zitat or "") + return ("§", paragraf.group(1).lower()) if paragraf else None + + +def _genannt_in(zitat: str, text: str) -> bool: + adresse = _adresse(zitat) + if adresse is None: + return False + if adresse[0] == "anlage": + return (adresse[1], adresse[2]) in _anlagen(text) + return adresse[1] in _paragrafen(text) + + +def pruefe(question: str = "", answer: str = "") -> Befund: + """Nennt die Antwort die Norm, die das Register für maßgeblich hält?""" + anker = passende_anker(question) + if not anker: + return Befund(status="nicht_pruefbar", grund="kein_anker") + + haystack = _haystack(question) + tragende = [n for a in anker for n in a.tragende(haystack)] + hinweise = [h for a in anker for h in a.geltende_hinweise(haystack)] + kernaussage = "\n\n".join( + text for text in (a.geltende_kernaussage(haystack) for a in anker) if text + ) + + if not tragende: + return Befund(status="nicht_pruefbar", anker=anker, grund="anker_ohne_tragende_norm") + if not (answer or "").strip(): + return Befund(status="nicht_pruefbar", anker=anker, grund="keine_antwort") + + abschnitt = normabschnitt(answer) + genannt = [n for n in tragende if _genannt_in(n.zitat, abschnitt) or _genannt_in(n.zitat, answer)] + fehlend = [n for n in tragende if n not in genannt] + + gemeinsam = dict( + anker=anker, erwartet=tragende, genannt=genannt, fehlend=fehlend, + hinweise=hinweise, kernaussage=kernaussage, + ) + + # Ein Fehlgriff der Sorte „gehört dazu, nur nicht als die entscheidende Norm" + # zählt nur, wenn keine tragende Norm neben ihm steht. § 31 SGB V gehört in + # eine OTC-Antwort — nur eben nicht als die Norm, die den Ausschluss trifft. + tragend_im_abschnitt = any(_genannt_in(n.zitat, abschnitt) for n in tragende) + fehlgriffe = [ + f + for a in anker + for f in a.fehlgriffe + if f.wenn.gilt(haystack) + and re.search(f.muster, abschnitt) + and not (f.nur_ohne_tragende and tragend_im_abschnitt) + ] + + if fehlgriffe or not genannt: + return Befund( + status="abweichung", + grund="falsche_norm" if fehlgriffe else "tragende_norm_fehlt", + fehlgriffe=fehlgriffe, + **gemeinsam, + ) + if fehlend: + return Befund(status="unvollstaendig", grund="normenkette_unvollstaendig", **gemeinsam) + return Befund(status="bestaetigt", **gemeinsam) + + +# --------------------------------------------------------------------------- +# Darstellung +# --------------------------------------------------------------------------- + +_EINLEITUNG = "Maßgebliche Norm — Abgleich mit dem kuratierten Normregister" + +# Die Richtigstellung bekommt eine eigene Überschrift, weil sie an einer anderen +# Stelle steht als der Nachtrag: der Nachtrag *ergänzt* die maßgebliche Norm, die +# Richtigstellung *erklärt* die oben bereits gesetzte. Sie reiht sich damit in +# die übrigen Schlusshinweise ein („Hinweis zur Reichweite", „Hinweis zum +# Stand") statt eine zweite Überschrift „Maßgebliche Norm" zu eröffnen. +_KORREKTUR_EINLEITUNG = "Hinweis zum Normabgleich" + +# Nur noch, wenn es stimmt: siehe `_ohne_verworfene_bloecke`. Vorher stand der +# Satz unter jeder Richtigstellung — auch unter denen, bei denen gar keine +# Vorschrift verworfen worden war (`tragende_norm_fehlt`), und über einem Text, +# den niemand mehr auf die verworfene Norm hin gelesen hatte. +_RESTVERWEIS = ( + "Die übrigen Abschnitte stammen unverändert vom Modell und nehmen diese Vorschrift " + "stellenweise noch in Bezug." +) + +# Ein Quellenmarker, wie ihn die Antwort führt. Zu diesem Zeitpunkt sind es +# bereits die Anzeigenummern (`app._renumber_sources_for_display` läuft im +# Compose-Schritt davor), sie werden hier nur verschoben, nie neu vergeben. +_MARKER_RE = re.compile(r"\[Quellen?\s+\d+(?:\s*,\s*\d+)*\]") + + +def _normzeilen(normen: List[Norm], *, einzug: str = " ", marker: Optional[Dict[tuple, str]] = None) -> List[str]: + zeilen: List[str] = [] + for norm in normen: + adresse = _adresse(norm.zitat) + beleg = (marker or {}).get(adresse) if adresse else None + zeilen.append(f"{einzug}{norm.zitat} {beleg}" if beleg else f"{einzug}{norm.zitat}") + if norm.warum: + zeilen.append(f"{einzug} {norm.warum}") + return zeilen + + +def _marker_je_norm(abschnitt: str) -> Dict[tuple, str]: + """Welchen Quellenmarker das Modell neben welche Vorschrift gesetzt hat. + + Die Richtigstellung ersetzt den Normabschnitt durch die Zitate des Registers + — und das Register führt keine Belege. Im Ausdruck vom 20.08.2026 stand die + richtiggestellte maßgebliche Norm deshalb ganz ohne Quellenverweis, während + die verworfene Vorschrift ihren Marker behielt und die Quellenliste anführte. + Wo das Modell dieselbe Vorschrift belegt hat, wandert der Marker mit. + + Übernommen wird nur aus Segmenten mit genau einer Fundstelle und genau einem + Marker. „§ 300 Abs. 3 SGB V in Verbindung mit § 2 der Abrechnungs- + vereinbarung — [Quelle 1]" nennt zwei Vorschriften und einen Beleg; welcher + zu welcher gehört, steht nicht im Text, und ein geratener Beleg wäre + schlimmer als keiner. + """ + out: Dict[tuple, str] = {} + for segment in re.split(r"[;\n]", abschnitt or ""): + marker = _MARKER_RE.findall(segment) + if len(marker) != 1: + continue + ohne_marker = _MARKER_RE.sub(" ", segment) + adressen = {("§", p) for p in _paragrafen(ohne_marker)} + adressen |= {("anlage", a, b) for a, b in _anlagen(ohne_marker)} + if len(adressen) != 1: + continue + out.setdefault(next(iter(adressen)), marker[0]) + return out + + +def _ohne_verworfene_bloecke(text: str, fehlgriffe: List[Fehlgriff]) -> str: + """Die Zitatblöcke der verworfenen Vorschrift aus dem Modelltext nehmen. + + Der Anlass steht im Ausdruck vom 20.08.2026: die Richtigstellung setzte + Kurzantwort und maßgebliche Norm neu, ließ darunter aber den vollständigen + Wortlaut des verworfenen § 40c AM-RL stehen — mitsamt seinem Quellenmarker — + und kündigte den Widerspruch im Text auch noch an. Wer die Antwort las, + musste selbst entscheiden, welche der beiden Vorschriften gilt. + + Entfernt wird ein Absatz nur, wenn seine **erste** Zeile die verworfene + Vorschrift nennt: dann ist der Absatz ein Zitat von ihr. Steht sie mitten in + einem Absatz, gehört sie zum Wortlaut einer anderen Norm — § 9 Abs. 3 + Buchst. d Rahmenvertrag verweist in seinem letzten Spiegelstrich selbst auf + § 40c —, und dieser Wortlaut ist richtig zitiert und bleibt stehen. + + Eine Abschnittsüberschrift schützt ihren Absatz: Text unter einer eigenen + Überschrift zu löschen hieße, den Abschnitt zu entscheiden, und dafür ist + das Register nicht zuständig. + """ + if not fehlgriffe or not (text or "").strip(): + return text + + muster = [re.compile(f.muster) for f in fehlgriffe] + behalten: List[str] = [] + for absatz in re.split(r"\n[ \t]*\n", text): + erste = next((zeile for zeile in absatz.split("\n") if zeile.strip()), "") + if not erste or _UEBERSCHRIFT_RE.match(erste): + behalten.append(absatz) + continue + if any(m.search(erste) for m in muster): + continue + behalten.append(absatz) + return "\n\n".join(a for a in behalten if a.strip()) + + +def _nimmt_noch_bezug(text: str, fehlgriffe: List[Fehlgriff]) -> bool: + """Argumentiert der verbliebene Modelltext noch aus der verworfenen Vorschrift? + + Gefragt ist nur nach dem Text, der **stehen bleibt**. „Kurzantwort" und + „Maßgebliche Norm" werden ohnehin neu gesetzt; was dort noch steht, ist + gleich weg und kein Restbezug. + + Der Abschnitt „Wortlaut / Kriterien" zählt ebenfalls nicht mit. Er gibt + Normtext wieder, und dass eine Vorschrift auf eine andere verweist, ist dann + keine Aussage des Modells, sondern die des Gesetzgebers: § 9 Abs. 3 + Buchst. d Rahmenvertrag nennt § 40c in seinem letzten Spiegelstrich selbst. + Ein Hinweis auf einen „Restbezug" wäre dort eine Warnung vor dem richtigen + Zitat. + """ + if not fehlgriffe: + return False + ersetzt = (_KURZ_UEBERSCHRIFT, _NORM_UEBERSCHRIFT, "Wortlaut") + rest = "\n".join( + "\n".join(body) + for heading, body in _abschnitte(text) + if not any(_ist(heading, name) for name in ersetzt) + ) + return any(re.search(f.muster, rest) for f in fehlgriffe) + + +def befund_block(befund: Befund, *, restverweis: bool = False) -> str: + """Der Textblock, den die Antwort bekommt. Leer, wenn nichts zu sagen ist. + + **Ein `Hinweis` hängt nicht daran, dass die Antwort falsch war.** Bis zum + 21.08.2026 gab dieser Block nur bei `abweichung` und `unvollstaendig` etwas + zurück; eine im Übrigen richtige Antwort bekam die Hinweise nicht. Das + widerspricht dem, wofür sie da sind — „was eine richtige Antwort nicht + verschweigen darf". Gemessen an zwei Läufen derselben Frage: im ersten + fehlte ein Kettenglied und der Hinweis auf die Altersausnahme kam mit, im + zweiten war die Kette vollständig und derselbe Hinweis blieb weg. + """ + if befund.status == "bestaetigt": + hinweise = list(befund.hinweise) + if not hinweise: + return "" + themen = ", ".join(a.thema for a in befund.anker) + return "\n".join([f"{_KORREKTUR_EINLEITUNG} ({themen}):", *hinweise]) + + if befund.status not in {"abweichung", "unvollstaendig"}: + return "" + + themen = ", ".join(a.thema for a in befund.anker) + zeilen: List[str] = [] + + if befund.status == "abweichung": + zeilen.append(f"{_KORREKTUR_EINLEITUNG} ({themen}):") + if befund.fehlgriffe: + zeilen.append( + "Kurzantwort und maßgebliche Norm oben stammen aus dem kuratierten " + "Normregister; die zunächst herangezogene Vorschrift entscheidet diese " + "Frage nicht." + ) + for fehlgriff in befund.fehlgriffe: + zeilen.append(f" {fehlgriff.warum}") + else: + zeilen.append( + "Kurzantwort und maßgebliche Norm oben stammen aus dem kuratierten " + "Normregister; die ursprüngliche Fassung nannte die tragende Vorschrift " + "nicht." + ) + if restverweis: + zeilen.append(_RESTVERWEIS) + else: + zeilen.append(f"{_EINLEITUNG} ({themen}):") + zeilen.append("Zur Normenkette gehört außerdem:") + zeilen.extend(_normzeilen(befund.fehlend)) + + for hinweis in befund.hinweise: + zeilen.append(hinweis) + + return "\n".join(zeilen) + + +def _normabschnitt_text(befund: Befund, marker: Optional[Dict[tuple, str]] = None) -> str: + return "\n".join(_normzeilen(befund.erwartet, einzug="", marker=marker)) + + +def in_antwort_einsetzen(answer: str, befund: Befund) -> str: + """Befund und Antwort zusammenführen. + + Zwei Wege, und der Unterschied ist der Punkt: + + * `unvollstaendig` — die Antwort ist im Übrigen richtig, ihr fehlt ein Glied + der Kette. Der Nachtrag hängt unten an und lässt sie stehen. + * `abweichung` — die Antwort steht auf der falschen Vorschrift. Dann genügt + ein Nachtrag nicht: er stünde neben der falschen Antwort, und nichts im + Text sagte, welcher gilt. „Kurzantwort" und „Maßgebliche Norm" werden + deshalb neu gesetzt. + + **Die Antwort führt in beiden Fällen, nicht der Werkstattbericht.** Bis zum + 20.08.2026 stand der Richtigstellungsblock *über* der Antwort: der Leser + bekam zuerst zu lesen, dass das Modell danebengelegen habe, und erst danach, + was gilt. Der Block wandert deshalb ans Ende, zu den übrigen Hinweisen — + die Kurzantwort steht wieder oben, wo sie hingehört. + + Die übrigen Abschnitte bleiben Modelltext. Sie umzuschreiben hieße, den Fall + zu entscheiden, und das ist nicht Aufgabe eines Normregisters. Entfernt + werden nur die Absätze, die die verworfene Vorschrift zitieren — siehe + `_ohne_verworfene_bloecke`. + """ + if befund.status not in {"abweichung", "unvollstaendig", "bestaetigt"}: + return answer + + text = (answer or "").strip() + + if befund.status != "abweichung": + # `bestaetigt` läuft hier mit: die Antwort bleibt unangetastet, ihre + # Hinweise hängen unten an. Siehe `befund_block`. + block = befund_block(befund) + if not block: + return answer + return f"{text}\n\n{block}" if text else block + + # Erst den Modelltext von den Zitaten der verworfenen Vorschrift befreien, + # dann fragen, ob sie darin überhaupt noch vorkommt: der Hinweis auf den + # Restbezug soll nur stehen, wenn er zutrifft. + marker = _marker_je_norm(normabschnitt(text)) + text = _ohne_verworfene_bloecke(text, befund.fehlgriffe) + block = befund_block(befund, restverweis=_nimmt_noch_bezug(text, befund.fehlgriffe)) + + blocks = _abschnitte(text) + hat_schema = any( + _ist(h, _KURZ_UEBERSCHRIFT) or _ist(h, _NORM_UEBERSCHRIFT) for h, _ in blocks + ) + if not hat_schema: + # Kein erkennbares Schema — dann gibt es keinen Abschnitt zum Ersetzen. + # Die Richtigstellung wird trotzdem gesetzt, sonst bestünde sie aus einer + # Beanstandung ohne Auskunft: Kurzantwort und maßgebliche Norm gehen dem + # Modelltext voran, der Hinweis folgt am Ende. + # + # `text` kann hier leer sein — dann bestand die ganze Antwort aus einem + # Zitat der verworfenen Vorschrift und ist restlos entfallen. Auch dieser + # Fall braucht eine Auskunft, keinen alleinstehenden Hinweis. + kopf: List[str] = [] + if befund.kernaussage: + kopf.append(f"{_KURZ_UEBERSCHRIFT}:\n{befund.kernaussage}") + kopf.append(f"{_NORM_UEBERSCHRIFT}:\n{_normabschnitt_text(befund, marker)}") + return "\n\n".join([t for t in (*kopf, text, block) if t.strip()]) + + teile: List[str] = [] + norm_gesetzt = False + for heading, body_lines in blocks: + body = "\n".join(body_lines).strip() + if heading is None: + if body: + teile.append(body) + elif _ist(heading, _KURZ_UEBERSCHRIFT) and befund.kernaussage: + teile.append(f"{heading}:\n{befund.kernaussage}") + elif _ist(heading, _NORM_UEBERSCHRIFT): + teile.append(f"{heading}:\n{_normabschnitt_text(befund, marker)}") + norm_gesetzt = True + elif body: + teile.append(f"{heading}:\n{body}") + # Eine Überschrift ohne Text fällt weg. Sie kann hier neu entstehen: hat + # ein Abschnitt nur die verworfene Vorschrift zitiert, ist er nach der + # Bereinigung leer, und eine nackte Überschrift sähe nach verlorenem + # Inhalt aus. + + if not norm_gesetzt: + # Die Antwort hatte keinen Normabschnitt. Er wird hinter der Kurzantwort + # eingesetzt, damit die Reihenfolge des Schemas erhalten bleibt. + einfuegen = 1 if teile and teile[0].startswith(_KURZ_UEBERSCHRIFT) else 0 + teile.insert(einfuegen, f"{_NORM_UEBERSCHRIFT}:\n{_normabschnitt_text(befund, marker)}") + + return "\n\n".join(t for t in teile if t.strip()) + f"\n\n{block}"