aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-25 22:40:05 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-25 22:40:05 +0200
commitff8e9837f11a1488dbcdf95fa47457ef74f864bb (patch)
tree97a7bf72ac5f796b0d7b7cfa26be3a02b87769f0 /src/test/java
parentf3e9ae405ee64c98684d99966d75bb90367adc44 (diff)
Die weite Suche behält den Vorrang, die Mehrdeutigkeit entscheidet der Verbund
„Die bisherige Nummer 18 wird Nummer 29 und nach der Angabe „Absatz 1“ werden die Wörter „oder Absatz 4“ eingefügt.“ Der Bußgeldkatalog des § 108 führt zweiunddreißig Nummern, und beinahe jede nennt einen „Absatz 1“; norm-weit aufgelöst trifft der Anker einundzwanzig Vorkommen. Der Befehl blieb deshalb liegen — zu Recht, aber unnötig: Gemeint war ersichtlich die soeben umnummerierte Einheit. Für eine Satzzeichen-Operation galt diese Auslegung schon, für eine Wortoperation ausdrücklich nicht. Das war keine Nachlässigkeit, sondern eine bewusste Entscheidung: Nicht jede Begleitklausel meint die umnummerierte Einheit, und wer sie stets dorthin zwänge, verlöre die Fälle, in denen sie anderswo greift. Die Regel ist deshalb nicht umzukehren, sondern zu verfeinern. Die weite Suche behält den Vorrang; erst wenn sie mehrere Fundstellen findet, entscheidet der engere Skopus. Findet sie gar keine, so meint der Befehl etwas anderes und bleibt liegen — der Rückfall greift allein bei Mehrdeutigkeit. Bleibt der Anker auch in der umnummerierten Einheit mehrdeutig, so steht die Mehrdeutigkeit im Protokoll und nicht die Begründung des zweiten Versuchs. Die Regel gehört zur Anwendung und nicht zur Erkennung. Ob ein Anker mehrdeutig ist, steht erst nach den neununddreißig vorangegangenen Punkten fest; der Erkenner sieht nur das Ausgangsgesetz, in dem es die Nummer 29 noch gar nicht gibt. Der Schritt trägt darum eine Rückfallstelle, die das Auffalten des Verbunds mitgibt — vorwärts und nur innerhalb desselben Verbunds. Nennt die Klausel eine Einheit derselben Art, wie die Umnummerierung sie vergibt, so wird nicht geraten. Damit ist der ganze Artikel 1 der GEG-Novelle selbsttätig angewandt: 119 von 119 Befehlen, kein Rest. Der Wortlaut der Nummer 29 gleicht der amtlichen Fassung (BJNR172810020.xml). Geprüft: 381 Tests (zuvor 378), darunter die drei Grenzen der Regel — Rückfall bei Mehrdeutigkeit, kein Rückfall bei fehlendem Zieltext, und Mehrdeutigkeit bleibt Mehrdeutigkeit. Change-Id: Id0d7080ba93ebad73fbcad42fe41c434b9a67e05
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java21
-rw-r--r--src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java114
2 files changed, 128 insertions, 7 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 7a6dcee..41f30e5 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -293,8 +293,8 @@ class EndToEndTest {
.filter(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
.map(a -> a.befehl().provenienz().gliederungsPfad())
.toList();
- assertThat(manuellPfade).containsExactly("40. a) hh)");
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(118);
+ assertThat(manuellPfade).isEmpty();
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(119);
// Die Kaskade des § 108 Absatz 1 geht lückenlos auf: 32 Nummern, keine doppelt, keine fehlend.
var nummern =
@@ -312,6 +312,13 @@ class EndToEndTest {
assertThat(nummern).hasSize(32).endsWith("32");
var neu = anwendung.neu();
+ // Die Begleitklausel der Nummer 18/29 hat allein dort gegriffen — der Wortlaut gleicht der
+ // amtlichen Nachfassung (BJNR172810020.xml, § 108 Absatz 1 Nummer 29).
+ var paragraph108 = neu.norm("§ 108").orElseThrow().absaetze().get(0).text();
+ assertThat(paragraph108)
+ .contains("29. entgegen § 96 Absatz 1 oder Absatz 4 eine Bestätigung nicht")
+ .contains("30. entgegen § 96 Absatz 5 Satz 2 eine Abrechnung nicht");
+
// Die Überschrift der Nummer 1 der Anlage 8 ist geändert — und nur sie: Der Titel der Anlage
// trägt seinen eigenen Wortlaut unversehrt, den Punkt 43. a) ihm gegeben hat.
var anlage8 = neu.norm("Anlage 8").orElseThrow();
@@ -1722,14 +1729,14 @@ class EndToEndTest {
assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen");
var vollstaendig = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
- assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(118);
- assertThat(vollstaendig.anzahlManuell()).isEqualTo(1);
+ assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(119);
+ assertThat(vollstaendig.anzahlManuell()).isZero();
var anfang2024 =
Pipeline.erzeugeSynopse(
Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2024, 1, 1)));
- assertThat(anfang2024.anzahlAngewandt()).isEqualTo(117);
- assertThat(anfang2024.anzahlManuell()).isEqualTo(1);
+ assertThat(anfang2024.anzahlAngewandt()).isEqualTo(118);
+ assertThat(anfang2024.anzahlManuell()).isZero();
assertThat(anfang2024.html())
.contains("Am Stichtag noch nicht in Kraft")
.contains("Tritt erst am 1. Oktober 2024 in Kraft");
@@ -1737,7 +1744,7 @@ class EndToEndTest {
var oktober2024 =
Pipeline.erzeugeSynopse(
Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2024, 10, 1)));
- assertThat(oktober2024.anzahlAngewandt()).isEqualTo(118);
+ assertThat(oktober2024.anzahlAngewandt()).isEqualTo(119);
}
/**
diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
index 0e186ad..a1f5cff 100644
--- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
+++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
@@ -4,6 +4,7 @@ package eu.mulk.aendggner.anwendung;
import static org.assertj.core.api.Assertions.assertThat;
+import eu.mulk.aendggner.aenderung.Aenderungsbefehl;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Anfuegung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Aufhebung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.BereichsUmnummerierung;
@@ -1777,4 +1778,117 @@ class BefehlAnwenderTest {
assertThat(ergebnis.protokoll().get(0).grund()).isEqualTo(Grund.NICHT_UNTERSTUETZT);
assertThat(ergebnis.neu().norm("Anlage 8").orElseThrow().titel()).isEqualTo("Anforderungen");
}
+
+ private static Gesetz bussgeldkatalog() {
+ return new Gesetz(
+ "TestG",
+ "Testgesetz",
+ "TestG",
+ List.of(
+ new Norm(
+ "§ 108",
+ "Bußgeldvorschriften",
+ null,
+ List.of(
+ new Absatz(
+ "1",
+ "17. entgegen § 90 Absatz 1 etwas tut,\n"
+ + "18. entgegen § 96 Absatz 1 etwas unterlässt,\n"
+ + "19. entgegen § 97 Absatz 1 etwas anderes tut,")),
+ false)));
+ }
+
+ private static Sammelbefehl verbund(Aenderungsbefehl... teile) {
+ return new Sammelbefehl(List.of(teile));
+ }
+
+ /**
+ * „Die bisherige Nummer 18 wird Nummer 29 und nach der Angabe ‚Absatz 1‘ werden die Wörter ‚oder
+ * Absatz 4‘ eingefügt.“ — Der Anker steht in jeder Nummer; norm-weit ist er mehrdeutig. Erst dann
+ * entscheidet die soeben umnummerierte Einheit.
+ */
+ @Test
+ void mehrdeutigeBegleitklauselFaelltAufDieUmnummerierteEinheitZurueck() {
+ var rahmen = stelle(new Stelle.Paragraph("108"), new Stelle.AbsatzNr("1"));
+ var befehl =
+ verbund(
+ new Umnummerierung(
+ rahmen.plus(stelle(new Stelle.NummerNr("18"))),
+ stelle(new Stelle.NummerNr("29")),
+ PROV),
+ new WoerterEinfuegung(
+ rahmen, new WortAnker.NachWoertern("Absatz 1"), "oder Absatz 4", PROV));
+
+ var ergebnis = BefehlAnwender.anwenden(bussgeldkatalog(), List.of(befehl));
+ assertThat(ergebnis.protokoll()).allMatch(a -> a.status() == Status.ANGEWANDT);
+
+ var text = ergebnis.neu().norm("§ 108").orElseThrow().absaetze().get(0).text();
+ // Allein die umnummerierte Nummer hat die Wörter bekommen.
+ assertThat(text).contains("29. entgegen § 96 Absatz 1 oder Absatz 4 etwas unterlässt,");
+ assertThat(text).contains("17. entgegen § 90 Absatz 1 etwas tut,");
+ assertThat(text).contains("19. entgegen § 97 Absatz 1 etwas anderes tut,");
+ }
+
+ /**
+ * Der Rückfall greift nur bei Mehrdeutigkeit, nie bei fehlendem Zieltext: Wer seinen Anker
+ * nirgends findet, meint etwas anderes — ihn in die umnummerierte Einheit zu zwingen wäre eine
+ * Falschanwendung, keine Verfeinerung.
+ */
+ @Test
+ void fehlenderZieltextFaelltNichtZurueck() {
+ var rahmen = stelle(new Stelle.Paragraph("108"), new Stelle.AbsatzNr("1"));
+ var befehl =
+ verbund(
+ new Umnummerierung(
+ rahmen.plus(stelle(new Stelle.NummerNr("18"))),
+ stelle(new Stelle.NummerNr("29")),
+ PROV),
+ new WoerterEinfuegung(
+ rahmen, new WortAnker.NachWoertern("Paragraph 1"), "oder Absatz 4", PROV));
+
+ var ergebnis = BefehlAnwender.anwenden(bussgeldkatalog(), List.of(befehl));
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN);
+ assertThat(ergebnis.neu().norm("§ 108").orElseThrow().absaetze().get(0).text())
+ .doesNotContain("oder Absatz 4");
+ }
+
+ /**
+ * Bleibt der Anker auch in der umnummerierten Einheit mehrdeutig, so bleibt der Befehl liegen —
+ * und mit der Begründung des <em>ersten</em> Versuchs: Die Mehrdeutigkeit ist der wahre Befund.
+ */
+ @Test
+ void mehrdeutigBleibtMehrdeutig() {
+ var gesetz =
+ new Gesetz(
+ "TestG",
+ "Testgesetz",
+ "TestG",
+ List.of(
+ new Norm(
+ "§ 108",
+ null,
+ null,
+ List.of(
+ new Absatz(
+ "1",
+ "17. entgegen § 90 Absatz 1 etwas tut,\n"
+ + "18. entgegen § 96 Absatz 1 und § 97 Absatz 1 etwas unterlässt,")),
+ false)));
+ var rahmen = stelle(new Stelle.Paragraph("108"), new Stelle.AbsatzNr("1"));
+ var befehl =
+ verbund(
+ new Umnummerierung(
+ rahmen.plus(stelle(new Stelle.NummerNr("18"))),
+ stelle(new Stelle.NummerNr("29")),
+ PROV),
+ new WoerterEinfuegung(
+ rahmen, new WortAnker.NachWoertern("Absatz 1"), "oder Absatz 4", PROV));
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN);
+ assertThat(ergebnis.protokoll().get(0).grund()).isEqualTo(Grund.MEHRDEUTIG);
+ assertThat(ergebnis.protokoll().get(0).begruendung()).contains("mehrdeutig");
+ assertThat(ergebnis.neu().norm("§ 108").orElseThrow().absaetze().get(0).text())
+ .doesNotContain("oder Absatz 4");
+ }
}