aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-24 20:47:09 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-24 20:47:09 +0200
commita88d5707a4a58b1d37db97b211770d3509e95ee5 (patch)
treead557ef63a87dfc33396818f7095d9e1ffd5a8b0
parentb682c225ab39859b6488ad2c327c6a67bbaf5b50 (diff)
Drei benannte Reste, drei allgemeine Mängel
Sie standen als begründete Kommentare in den Prüfungen, jeder mit seinem Belegfall — und jeder war allgemein, nicht die Eigenheit seines Falls. Erstens räumt auch eine Aufhebung eine Bezeichnung. Die Schritt-Ordnung kannte bisher nur Umnummerierungen als Räumende. Im GEG macht ein Punkt der Kaskade des § 108 Absatz 1 aus den bisherigen Nummern 4 bis 6 die Nummern 8 bis 10, während ein anderer die bisherige Nummer 9 aufhebt; die Aufhebung suchte danach eine Nummer 9, die inzwischen eine andere Einheit war. Dass eine Aufhebung mitunter einen Platzhalter hinterlässt und dann nichts freigibt, steht erst am Bestand fest und schadet nicht: Vorrang erhält sie nur gegenüber Schritten, die dieselbe Bezeichnung neu vergeben, und eine zugleich weggefallene und neu vergebene Bezeichnung gibt es nicht. Zweitens ist ein Zwischentitel kein Teil des vorangehenden Absatzes. „Aus dem Bereich Verkehr:“ trägt keine Absatzbezeichnung und wurde deshalb angehängt — der Absatz endete dann nicht auf den Punkt, den „In Absatz 4a wird der Punkt am Ende durch ein Semikolon ersetzt“ meint. Er ist jetzt ein eigener, bezeichnungsloser Absatz, wie ihn der Vorspann einer Norm ohnehin bildet. Drittens erbt neu eingesetzter Wortlaut die amtliche Satzzählung. Führt das Stammgesetz sie, so trägt der aus dem Gesetzblatt zitierte Text sie gleichwohl nicht: Sie gehört zur Fassung, nicht zum Verkündungstext. Angefasst wird nur ein geänderter, einzeiliger, mehrsätziger Absatz ohne Zählung — die Mehrzeiligkeit hält Aufzählungen heraus. Geprüft: 348 Tests, REUSE 180/180. GEG 117/2 (war 116/3), dessen Beschlussempfehlung 70 (war 69), § 108 Absatz 1 mit lückenloser Nummernfolge 1 bis 32. Berlin ASOG 6/6 und alle 171 Normen gleich der amtlichen Nachfassung — die beiden benannten Abweichungen sind fort. Bayern, Niedersachsen und die übrigen Belegfälle unverändert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I47d3f9f150cb628ba5a298e95f2c647dac6ffa45
-rw-r--r--FASSUNGEN.txt39
-rw-r--r--README.md10
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java36
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java90
-rw-r--r--src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java43
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java87
6 files changed, 265 insertions, 40 deletions
diff --git a/FASSUNGEN.txt b/FASSUNGEN.txt
index b739390..393f6e9 100644
--- a/FASSUNGEN.txt
+++ b/FASSUNGEN.txt
@@ -154,6 +154,45 @@ Tage gegolten hat. Bislang tat das Erzeugnis eben dies, und zwar schweigend.
(8) Der Prüfbestand umfasst dreihundertachtundvierzig Prüfungen.
+Artikel 6
+Beseitigung dreier benannter Mängel
+
+Die nachstehenden drei Mängel waren in den Prüfungen einzeln begründet festge-
+halten. Sie sind sämtlich allgemeiner Natur und nicht Eigenheiten ihrer Belegfälle.
+
+(1) Auch eine Aufhebung räumt eine Bezeichnung. Die Ordnung der Anwendungs-
+ schritte kannte bislang allein Umnummerierungen als Räumende. Im Gebäude-
+ energiegesetz (§ 108 Absatz 1) macht ein Punkt der Kaskade aus den bisherigen
+ Nummern 4 bis 6 die Nummern 8 bis 10, während ein anderer die bisherige
+ Nummer 9 aufhebt; die Aufhebung suchte danach eine Nummer 9, die inzwischen
+ eine andere Einheit war. Sie läuft fortan vor der Umnummerierung. Dass eine
+ Aufhebung mitunter einen Platzhalter hinterlässt und dann nichts freigibt,
+ steht erst am Bestand fest und schadet nicht: Vorrang erhält sie nur gegenüber
+ Schritten, die dieselbe Bezeichnung neu vergeben.
+
+(2) Ein Zwischentitel ist kein Teil des vorangehenden Absatzes. Eine Zeile, die
+ auf einen Doppelpunkt endet, keine Absatzbezeichnung trägt und der ein
+ bezeichneter Absatz folgt, bildet fortan einen eigenen, bezeichnungslosen
+ Absatz (LandesRechtTextParser). Bislang wurde sie dem vorangehenden Absatz
+ zugeschlagen; dessen Text endete dann nicht dort, wo er endet, und die
+ Anweisung „In Absatz 4a wird der Punkt am Ende durch ein Semikolon ersetzt“
+ fand ihren Punkt nicht.
+
+(3) Neu eingesetzter Wortlaut erbt die amtliche Satzzählung (Satznummerierung).
+ Führt das Stammgesetz sie, so trägt der aus dem Gesetzblatt zitierte Text sie
+ gleichwohl nicht — sie gehört zur Fassung, nicht zum Verkündungstext.
+ Angefasst wird nur, was angefasst werden muss: ein geänderter Absatz, der
+ keine Zählung trägt, aus mehr als einem Satz besteht und auf einer Zeile
+ steht; die Mehrzeiligkeit schließt Aufzählungen aus.
+
+(4) Damit sind das Gebäudeenergiegesetz mit einhundertsiebzehn angewandten
+ Befehlen und zwei Resten (zuvor einhundertsechzehn und drei) und das Berliner
+ Allgemeine Sicherheits- und Ordnungsgesetz mit sämtlichen sechs Befehlen und
+ allen einhunderteinundsiebzig Normen gleich der amtlichen Nachfassung
+ ausgewiesen; die Beschlussempfehlung zum Gebäudeenergiegesetz steigt von
+ neunundsechzig auf siebzig angewandte Befehle.
+
+
════════════════════════════════════════════════════════════════════════════════
Fassung vom 23. August 2026,
zuletzt geändert durch die am 23. August 2026 vorgenommenen Änderungen
diff --git a/README.md b/README.md
index 1dcbb29..5a0e64c 100644
--- a/README.md
+++ b/README.md
@@ -275,9 +275,15 @@ sie neu besetzt.
(2) Aus Absatz 1 folgt
1. die absteigende Abarbeitung einer aufsteigenden Kaskade („Der bisherige
- Absatz 3 wird Absatz 4“, „Der bisherige Absatz 4 wird Absatz 5“, …) sowie
+ Absatz 3 wird Absatz 4“, „Der bisherige Absatz 4 wird Absatz 5“, …),
2. der Vorrang einer Umnummerierung vor derjenigen Einfügung, die deren
- Bezeichnung neu vergibt.
+ Bezeichnung neu vergibt, sowie
+3. der Vorrang einer Aufhebung vor demjenigen Schritt, der die aufgehobene
+ Bezeichnung neu vergibt. Auch die Aufhebung räumt nämlich eine Bezeichnung;
+ dass sie mitunter einen Platzhalter hinterlässt („9. (weggefallen)“) und dann
+ gerade nichts freigibt, steht erst am Bestand fest und schadet nicht: Vorrang
+ erhält sie nur gegenüber Schritten, die dieselbe Bezeichnung neu vergeben, und
+ eine zugleich weggefallene und neu vergebene Bezeichnung gibt es nicht.
Wer vorrückt, nimmt dabei seine eigenen Räumer mit: Auch eine über mehrere Punkte
verschränkte Kaskade („Die bisherige Nr. 15 wird Nr. 16“, „Die bisherigen Nrn. 13
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
index cccba63..15fe2ed 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
@@ -111,7 +111,13 @@ public final class BefehlAnwender {
}
protokoll.addAll(verdichte(befehle, schritte, ergebnisse));
- var neu = alt.mitNormen(normen).mitGliederungen(gliederungen);
+ // Führt das Gesetz amtliche Satznummern, so erbt sie auch der neu eingesetzte Wortlaut: Das
+ // Gesetzblatt zitiert ohne Zählung, die Fassung führt sie.
+ var fortgeschrieben =
+ Satznummerierung.fuehrtSatznummern(alt)
+ ? Satznummerierung.schreibeFort(alt, normen)
+ : normen;
+ var neu = alt.mitNormen(fortgeschrieben).mitGliederungen(gliederungen);
if (neuerLangtitel != null) {
neu = neu.mitLangue(neuerLangtitel);
}
@@ -359,15 +365,41 @@ public final class BefehlAnwender {
befehl, Status.MANUELL_PRUEFEN, String.join(" ", fehler), betroffene, ersterGrund);
}
- /** Bezeichnungen, die ein Befehl freigibt — die Ausgangsstellen seiner Umnummerierungen. */
+ /**
+ * Bezeichnungen, die ein Befehl freigibt — die Ausgangsstellen seiner Umnummerierungen und die
+ * Stellen seiner Aufhebungen.
+ *
+ * <p>Dass auch eine Aufhebung räumt, ist nicht selbstverständlich, denn sie hinterlässt vielfach
+ * einen Platzhalter („9. (weggefallen)“) und gibt die Bezeichnung dann gerade nicht frei. Ob das
+ * geschieht, steht erst am Bestand fest ({@code haeltPlatz}), also nach der Ordnung. Vorgezogen
+ * wird die Aufhebung gleichwohl: Sie rückt nur gegenüber Schritten vor, die dieselbe Bezeichnung
+ * neu vergeben — und wo eine Bezeichnung zugleich weggefallen und neu vergeben sein soll,
+ * widerspräche das Gesetz sich selbst. So ist es im GEG (§ 108 Absatz 1): Die Kaskade macht die
+ * bisherigen Nummern 4 bis 6 zu 8 bis 10, während die bisherige Nummer 9 aufgehoben wird. Läuft
+ * die Aufhebung nach der Umnummerierung, sucht sie eine Nummer 9, die inzwischen eine andere
+ * Einheit ist.
+ */
private static Set<String> geraeumteBezeichnungen(Aenderungsbefehl befehl) {
var raeumt = new LinkedHashSet<String>();
for (var u : umnummerierungen(befehl)) {
raeumt.add(u.stelle().anzeigeText());
}
+ for (var a : aufhebungen(befehl)) {
+ raeumt.add(a.stelle().anzeigeText());
+ }
return raeumt;
}
+ /** Die Aufhebungen eines Befehls — auch die in einem Verbund. */
+ private static List<Aufhebung> aufhebungen(Aenderungsbefehl befehl) {
+ return switch (befehl) {
+ case Aufhebung a -> List.of(a);
+ case Sammelbefehl s ->
+ s.teilbefehle().stream().flatMap(t -> aufhebungen(t).stream()).toList();
+ default -> List.of();
+ };
+ }
+
/** Bezeichnungen, die ein Befehl neu vergibt — Umnummerierungsziele und eingefügte Einheiten. */
private static Set<String> belegteBezeichnungen(Aenderungsbefehl befehl) {
var belegt = new LinkedHashSet<String>();
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java b/src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java
new file mode 100644
index 0000000..5165546
--- /dev/null
+++ b/src/main/java/eu/mulk/aendggner/anwendung/Satznummerierung.java
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: 2020 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+package eu.mulk.aendggner.anwendung;
+
+import eu.mulk.aendggner.gesetz.Absatz;
+import eu.mulk.aendggner.gesetz.Gesetz;
+import eu.mulk.aendggner.gesetz.Norm;
+import eu.mulk.aendggner.gesetz.Superskript;
+import java.util.ArrayList;
+import java.util.List;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Amtliche Satznummern für neu gesetzten Text.
+ *
+ * <p>Ein Teil der Länder zählt die Sätze eines Absatzes amtlich mit („(2) ¹Gegen einen … ²In diesem
+ * Fall …“). Das Gesetzblatt, aus dem der neue Wortlaut zitiert wird, setzt diese Zählung nicht —
+ * sie gehört zur Fassung, nicht zum Verkündungstext. Wer den Zitatwortlaut unverändert einsetzt,
+ * erhält deshalb einen Absatz, der als einziger seines Gesetzes ohne Satzzählung dasteht; die
+ * amtliche Nachfassung führt sie (Berlin, ASOG § 67 Absatz 2).
+ *
+ * <p>Angefasst wird nur, was angefasst werden muss: ein Absatz, der sich geändert hat, keine
+ * Zählung trägt, aus mehr als einem Satz besteht und auf einer Zeile steht. Die Mehrzeiligkeit
+ * schließt Aufzählungen aus — dort ist der Chapeau ein Satz und jedes Glied ein eigener Block, und
+ * eine fortlaufende Satzzählung wäre falsch.
+ */
+final class Satznummerierung {
+
+ private Satznummerierung() {}
+
+ /** Wahr, wenn das Gesetz die Sätze seiner Absätze amtlich zählt. */
+ static boolean fuehrtSatznummern(Gesetz gesetz) {
+ for (var norm : gesetz.normen()) {
+ for (var absatz : norm.absaetze()) {
+ if (Superskript.traegtSatznummern(absatz.text())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Schreibt die Satzzählung in den geänderten Absätzen fort. */
+ static List<Norm> schreibeFort(Gesetz alt, List<Norm> normen) {
+ var ergebnis = new ArrayList<Norm>(normen.size());
+ for (var norm : normen) {
+ var alteNorm = alt.norm(norm.enbez()).orElse(null);
+ var absaetze = new ArrayList<Absatz>(norm.absaetze().size());
+ boolean geaendert = false;
+ for (var absatz : norm.absaetze()) {
+ var neuerText = fortgeschrieben(absatz, alteNorm);
+ geaendert |= !neuerText.equals(absatz.text());
+ absaetze.add(neuerText.equals(absatz.text()) ? absatz : absatz.mitText(neuerText));
+ }
+ ergebnis.add(geaendert ? norm.mitAbsaetzen(absaetze) : norm);
+ }
+ return ergebnis;
+ }
+
+ private static String fortgeschrieben(Absatz absatz, @Nullable Norm alteNorm) {
+ var text = absatz.text();
+ if (text.contains("\n") || Superskript.traegtSatznummern(text)) {
+ return text;
+ }
+ if (alteNorm != null && trugDenselbenText(alteNorm, absatz)) {
+ return text;
+ }
+ var saetze = SatzTeiler.teileTexte(text);
+ if (saetze.size() < 2) {
+ return text;
+ }
+ var sb = new StringBuilder();
+ for (int i = 0; i < saetze.size(); i++) {
+ if (i > 0) {
+ sb.append(' ');
+ }
+ sb.append(Superskript.zuSuperskript(String.valueOf(i + 1))).append(saetze.get(i));
+ }
+ return sb.toString();
+ }
+
+ private static boolean trugDenselbenText(Norm alteNorm, Absatz absatz) {
+ for (var alterAbsatz : alteNorm.absaetze()) {
+ if (java.util.Objects.equals(alterAbsatz.nummer(), absatz.nummer())) {
+ return alterAbsatz.text().equals(absatz.text());
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
index ae1e861..d1d4002 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
@@ -411,6 +411,34 @@ final class LandesRechtTextParser {
return false;
}
+ /**
+ * Ein Zwischentitel gliedert einen Katalog, ohne selbst eine Vorschrift zu sein: „Aus dem Bereich
+ * Verkehr:“. Er steht für sich auf einer Zeile und endet auf einem Doppelpunkt.
+ */
+ private static boolean istZwischentitel(String zeile) {
+ return zeile.endsWith(":")
+ && !ABSATZ_MARKER.matcher(zeile).find()
+ && !AUFZAEHLUNGS_MARKE.matcher(zeile).find();
+ }
+
+ /**
+ * Wahr, wenn auf die Zeile {@code index} ein bezeichneter Absatz folgt. Nur dann ist ein
+ * Doppelpunkt am Zeilenende ein Zwischentitel und kein Chapeau, der seine Aufzählung einleitet.
+ */
+ private static boolean folgtAbsatz(List<String> zeilen, int index) {
+ for (int i = index + 1; i < zeilen.size(); i++) {
+ var naechste = zeilen.get(i).strip();
+ if (naechste.isEmpty()) {
+ continue;
+ }
+ return ABSATZ_MARKER.matcher(naechste).find();
+ }
+ return false;
+ }
+
+ private static final Pattern AUFZAEHLUNGS_MARKE =
+ Pattern.compile("^(?:\\d+[a-z]?\\.|[a-z]{1,2}\\))\\s");
+
private static Norm baueNorm(
String enbez, @Nullable String titel, @Nullable Gliederung gliederung, List<String> zeilen) {
boolean weggefallen = titel != null && WEGGEFALLEN_TITEL.matcher(titel).matches();
@@ -418,11 +446,24 @@ final class LandesRechtTextParser {
var absaetze = new ArrayList<Absatz>();
String absatzNummer = null;
var absatzZeilen = new ArrayList<String>();
- for (var zeile : zeilen) {
+ for (int i = 0; i < zeilen.size(); i++) {
+ var zeile = zeilen.get(i);
var gestutzt = zeile.strip();
if (gestutzt.isEmpty()) {
continue;
}
+ if (istZwischentitel(gestutzt) && !absatzZeilen.isEmpty() && folgtAbsatz(zeilen, i)) {
+ // Ein Zwischentitel trägt keine Absatzbezeichnung und gehört doch nicht zum
+ // vorangehenden Absatz: Dessen Text endete sonst nicht dort, wo er endet — und ein
+ // Befehl wie „In Absatz 4a wird der Punkt am Ende durch ein Semikolon ersetzt“ fände
+ // seinen Punkt nicht. Er wird deshalb ein eigener, bezeichnungsloser Absatz, wie ihn
+ // der Vorspann einer Norm ohnehin bildet.
+ absaetze.add(new Absatz(absatzNummer, String.join("\n", absatzZeilen)));
+ absatzZeilen.clear();
+ absatzNummer = null;
+ absaetze.add(new Absatz(null, gestutzt));
+ continue;
+ }
var fussnote = FUSSNOTE.matcher(gestutzt);
if (fussnote.matches()) {
// Fußnoten verbleiben superskript-normalisiert als eigene Zeile im tragenden Absatz.
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 10cfb8d..e1da584 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -251,16 +251,15 @@ class EndToEndTest {
*
* <p>Gegen die heutige konsolidierte Fassung ({@link #gegGrossesAenderungsgesetz}) blieben 51 von
* 119 Befehlen liegen, und der Test konnte deshalb keine Sollzahl pinnen. Gegen den Stand vom 24.
- * April 2023 sind es <b>116 angewandte und drei Reste</b> — und diese drei sind erstmals echte
- * Befunde, die hier einzeln festgehalten sind:
+ * April 2023 sind es <b>117 angewandte und zwei Reste</b>, und diese zwei sind echte Befunde:
+ *
+ * <p>Der dritte Rest ist keiner mehr: {@code 40. a) ff)} („Die bisherige Nummer 9 wird
+ * aufgehoben.“) scheiterte daran, dass Punkt bb) derselben Kaskade aus den bisherigen Nummern 4
+ * bis 6 die Nummern 8 bis 10 macht — vorübergehend trugen zwei Einheiten die Bezeichnung 9. Seit
+ * auch eine Aufhebung als Räumende zählt, läuft sie vor der Umnummerierung, und § 108 Absatz 1
+ * trägt danach die lückenlose Folge der Nummern 1 bis 32.
*
* <ol>
- * <li>{@code 40. a) ff)} „Die bisherige Nummer 9 wird aufgehoben.“ — In derselben Kaskade macht
- * Punkt bb) aus den bisherigen Nummern 4 bis 6 die Nummern 8 bis 10. Damit tragen
- * vorübergehend zwei Einheiten die Bezeichnung 9, und die Aufhebung findet keine
- * eindeutige. Die Schritt-Ordnung kennt bislang nur Umnummerierungen als Räumende; ein
- * Schritt, dessen <em>Ziel</em> eine Bezeichnung ist, die ein anderer neu vergibt, müsste
- * ebenso vorgezogen werden.
* <li>{@code 40. a) hh)} „Die bisherige Nummer 18 wird Nummer 29 und nach der Angabe ‚Absatz 1‘
* werden die Wörter ‚oder Absatz 4‘ eingefügt.“ — Die Begleitklausel löst norm-weit auf
* (bewusst so, siehe {@code BefehlErkenner}) und trifft dort 21 Vorkommen. Gemeint ist die
@@ -291,8 +290,23 @@ class EndToEndTest {
.filter(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
.map(a -> a.befehl().provenienz().gliederungsPfad())
.toList();
- assertThat(manuellPfade).containsExactly("40. a) ff)", "40. a) hh)", "43. b) aa)");
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(116);
+ assertThat(manuellPfade).containsExactly("40. a) hh)", "43. b) aa)");
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(117);
+
+ // Die Kaskade des § 108 Absatz 1 geht lückenlos auf: 32 Nummern, keine doppelt, keine fehlend.
+ var nummern =
+ anwendung
+ .neu()
+ .norm("§ 108")
+ .orElseThrow()
+ .absaetze()
+ .get(0)
+ .text()
+ .lines()
+ .map(z -> z.strip().split("\\.")[0])
+ .filter(z -> z.matches("\\d+"))
+ .toList();
+ assertThat(nummern).hasSize(32).endsWith("32");
var neu = anwendung.neu();
// Der neue § 9a steht im Gesetz und in der Inhaltsübersicht.
@@ -929,12 +943,11 @@ class EndToEndTest {
* Spaltenspikes fest — die Spaltenreihenfolge des Inhaltsstroms ist bereits die Lesereihenfolge,
* eine koordinatenbasierte Spaltenerkennung ist dafür nicht nötig.
*
- * <p>Artikel 2 (LAF-Errichtungsgesetz) ist ein voller Akzeptanzfall: beide Befehle angewandt,
- * alle fünf Normen gleich der amtlichen Nachfassung. Für Artikel 1 (ASOG) reicht die Prüfung bis
- * zur Befehlserkennung. Fünf seiner sechs Befehle zielen auf die <em>Anlage</em>, deren Einheiten
- * dort nicht als Aufzählungsmarken, sondern als Überschriften gesetzt sind („Nummer 6“, „Nummer
- * 23“); sie brauchen einen eigenen Rang zwischen Norm und Absatz, den das Datenmodell noch nicht
- * führt. Die Begründung im einzelnen steht in {@code Berlin/SOURCES}.
+ * <p>Beide Artikel sind volle Akzeptanzfälle: Artikel 2 (LAF-Errichtungsgesetz) mit beiden
+ * Befehlen und allen fünf Normen, Artikel 1 (ASOG) mit allen sechs Befehlen und allen 171 Normen
+ * gleich der amtlichen Nachfassung. Fünf der sechs Befehle zielen auf die <em>Anlage</em>, deren
+ * Einheiten dort nicht als Aufzählungsmarken, sondern als Überschriften gesetzt sind („Nummer 6“,
+ * „Nummer 23“) — sie werden als eigene Normen geführt.
*/
@Test
void asogLafAendGBerlin() throws Exception {
@@ -1019,20 +1032,23 @@ class EndToEndTest {
.filter(x -> x.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
.map(x -> x.befehl().provenienz().gliederungsPfad())
.toList();
- // Einziger Rest, und er benennt eine Grenze des Klartextformats: „In Absatz 4a wird der Punkt
- // am Ende durch ein Semikolon ersetzt.“ Auf den Absatz 4a folgt der Zwischentitel „Aus dem
- // Bereich Verkehr:“, der keine eigene Absatzbezeichnung trägt und deshalb dem vorangehenden
- // Absatz zugeschlagen wird. Dessen Text endet damit nicht auf den Punkt, den der Befehl meint.
- assertThat(manuellPfade).containsExactly("2. b) aa)");
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(5);
+ // Kein Rest mehr: Der Zwischentitel „Aus dem Bereich Verkehr:“ trägt keine Absatz-
+ // bezeichnung und wurde bislang dem vorangehenden Absatz zugeschlagen; dessen Text endete
+ // dann nicht auf den Punkt, den „In Absatz 4a wird der Punkt am Ende durch ein Semikolon
+ // ersetzt“ meint. Er ist jetzt ein eigener, bezeichnungsloser Absatz.
+ assertThat(manuellPfade).isEmpty();
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(6);
// Die Nummer 23 der Anlage trägt danach die Absatzfolge der amtlichen Nachfassung: Der neue
// Absatz 5 ist ein Absatz geworden (nicht eine Zeile im Absatz 4a), und die bisherigen
// Absätze 5 bis 9 sind zu 6 bis 10 aufgerückt.
var nummer23 = anwendung.neu().norm("Anlage Nummer 23").orElseThrow();
+ // Die bezeichnungslosen Einträge sind der Vorspann und die Zwischentitel des Katalogs („Aus
+ // dem Bereich Verkehr:“); sie tragen keine Absatzbezeichnung und stehen deshalb für sich.
assertThat(nummer23.absaetze())
.extracting(Absatz::nummer)
- .containsExactly(null, "1", "2", "3", "4", "4a", "5", "6", "7", "8", "9", "10");
+ .containsExactly(
+ null, null, "1", "2", "3", "4", "4a", "5", null, "6", "7", null, "8", "9", null, "10");
// Norm für Norm gegen die amtliche Nachfassung vom 12. Juni 2026. Es bleiben genau zwei
// benannte Abweichungen — keine davon in der Anwendung begründet:
@@ -1050,13 +1066,14 @@ class EndToEndTest {
abweichend.add(normSoll.enbez());
}
}
- // Zwei benannte Abweichungen, keine davon in der Anwendung begründet:
+ // Keine Abweichung mehr: Alle 171 Normen gleichen der amtlichen Nachfassung.
//
- // § 67 — Das Portal setzt in der neuen Fassung amtliche Satznummern („(2) ¹Gegen einen …“);
- // das Gesetzblatt, aus dem der Wortlaut stammt, setzt keine.
- // Anlage Nummer 23 — der oben benannte Rest: dort steht der Punkt, wo die Nachfassung ein
- // Semikolon führt.
- assertThat(abweichend).containsExactly("§ 67", "Anlage Nummer 23");
+ // § 67 trug die beiden zuvor benannten Abweichungen: Das Portal setzt in der neuen Fassung
+ // amtliche Satznummern („(2) ¹Gegen einen …“), das Gesetzblatt, aus dem der Wortlaut
+ // stammt, setzt keine — die Zählung wird jetzt beim Einsetzen fortgeschrieben. Und in der
+ // Anlage Nummer 23 steht das Semikolon, weil der Zwischentitel den Absatz nicht mehr
+ // verlängert.
+ assertThat(abweichend).isEmpty();
// Die Nummer 31 war die dritte: Ihr Zitat läuft über einen Seitenwechsel, und der
// ganzseitenbreite Titelblock des Änderungsgesetzes steht im Inhaltsstrom mitten darin
// („… zur Sicherung des Be-“ / Titelblock / „triebs von Unterkünften …“). Seit die
@@ -1596,7 +1613,7 @@ class EndToEndTest {
// 69 statt der früheren 68: Nennt der Rahmen dieselbe Gliederungseinheit wie der Befehl („…
// Teil 2 wird wie folgt geändert: … die Angabe zur Überschrift von Teil 2 Abschnitt 4 …“),
// so gilt sie jetzt einmal statt zweimal (InhaltsuebersichtAnwender.zielKette).
- assertThat(ausEmpfehlung.anzahlAngewandt()).isEqualTo(69);
+ assertThat(ausEmpfehlung.anzahlAngewandt()).isEqualTo(70);
assertThat(ausEmpfehlung.anzahlAngewandt() + ausEmpfehlung.anzahlManuell())
.as("die Ausschussfassung trägt mehr Befehle als der Entwurf")
.isGreaterThan(ausEntwurf.anzahlAngewandt() + ausEntwurf.anzahlManuell());
@@ -1704,20 +1721,20 @@ class EndToEndTest {
assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen");
var vollstaendig = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false);
- assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(116);
- assertThat(vollstaendig.anzahlManuell()).isEqualTo(3);
+ assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(117);
+ assertThat(vollstaendig.anzahlManuell()).isEqualTo(2);
var anfang2024 =
Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 1, 1));
- assertThat(anfang2024.anzahlAngewandt()).isEqualTo(115);
- assertThat(anfang2024.anzahlManuell()).isEqualTo(3);
+ assertThat(anfang2024.anzahlAngewandt()).isEqualTo(116);
+ assertThat(anfang2024.anzahlManuell()).isEqualTo(2);
assertThat(anfang2024.html())
.contains("Am Stichtag noch nicht in Kraft")
.contains("Tritt erst am 1. Oktober 2024 in Kraft");
var oktober2024 =
Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 10, 1));
- assertThat(oktober2024.anzahlAngewandt()).isEqualTo(116);
+ assertThat(oktober2024.anzahlAngewandt()).isEqualTo(117);
}
/**