aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/Stelle.java30
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java14
-rw-r--r--src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java35
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java32
-rw-r--r--src/main/java/eu/mulk/aendggner/anwendung/StellenAufloeser.java10
-rw-r--r--src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextAusgeber.java4
-rw-r--r--src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java15
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java34
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java20
-rw-r--r--src/test/resources/sampledata/MecklenburgVorpommern/APOAmtsTA-MV-alt.txt51
-rw-r--r--src/test/resources/sampledata/MecklenburgVorpommern/SOURCES45
-rw-r--r--src/test/resources/sampledata/korpus-grundlinie.tsv4
12 files changed, 268 insertions, 26 deletions
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/Stelle.java b/src/main/java/eu/mulk/aendggner/aenderung/Stelle.java
index 12a152e..fe29437 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/Stelle.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/Stelle.java
@@ -144,7 +144,35 @@ public record Stelle(List<Komponente> komponenten) {
return komponenten.stream()
.filter(Gliederungseinheit.class::isInstance)
.map(Gliederungseinheit.class::cast)
- .anyMatch(g -> !istAnlagenArt(g.art()));
+ .anyMatch(g -> !istAnlagenArt(g.art()) && !istAnlagenEinheitsArt(g.art()));
+ }
+
+ /**
+ * Die benannte Einheit einer Anlage („Ausbildungsabschnitt 1“), falls vorhanden.
+ *
+ * <p>Sie gliedert nicht das Gesetz, sondern die Anlage, und wird — wie deren Nummern — als eigene
+ * Norm geführt, deren Bezeichnung die Anlage voranstellt („Anlage 1 Ausbildungsabschnitt 1“).
+ */
+ public Optional<Gliederungseinheit> anlagenEinheit() {
+ return komponenten.stream()
+ .filter(Gliederungseinheit.class::isInstance)
+ .map(Gliederungseinheit.class::cast)
+ .filter(g -> istAnlagenEinheitsArt(g.art()))
+ .findFirst();
+ }
+
+ /**
+ * Trägt die Art den Bau einer benannten Anlagen-Einheit? Buchstaben <em>vor</em> dem Wortstamm
+ * sind verlangt: „Ausbildungsabschnitt“ trifft, „Abschnitt“ nicht — der gliedert das Gesetz.
+ */
+ private static boolean istAnlagenEinheitsArt(String art) {
+ // „Unterabschnitt“ endet zwar auf den Wortstamm, gliedert aber das Gesetz und ist keine
+ // Einheit einer Anlage; ebenso der schlichte „Abschnitt“.
+ if (art.equalsIgnoreCase("Abschnitt") || art.equalsIgnoreCase("Unterabschnitt")) {
+ return false;
+ }
+ int stelle = art.length() - "abschnitt".length();
+ return stelle > 0 && art.regionMatches(true, stelle, "abschnitt", 0, "abschnitt".length());
}
/** Die enbez der adressierten Anhang-/Anlagen-Norm („Anhang“, „Anlage 2“), falls vorhanden. */
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
index dc12398..1d0685b 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/BefehlErkenner.java
@@ -484,7 +484,7 @@ final class BefehlErkenner {
Pattern.compile(
"^(?:Der bisherige |Die bisherige |Das bisherige )?(.+?) wird (?:zu )?(?:der |die |das )?"
+ "(§|Art\\.|Absatz|Abs\\.|Satz|Nummer|Nr\\.|Buchstabe|Buchst\\."
- + "|Teil|Abschnitt|Unterabschnitt|Buch|Kapitel|Anlage) "
+ + "|Teil|Abschnitt|Unterabschnitt|Buch|Kapitel|Anlage|\\p{L}+abschnitt) "
+ "(\\d+[a-z]?)\\.$");
// „Die bisherigen Absätze 2 bis 4 werden zu den Absätzen 3 bis 5.“ bzw. „Die bisherigen Nummern 4
@@ -722,7 +722,7 @@ final class BefehlErkenner {
Pattern.compile(
"^(?:Der bisherige |Die bisherige |Das bisherige )?(.+?) wird (?:zu )?"
+ "(§|Art\\.|Absatz|Abs\\.|Satz|Nummer|Nr\\.|Buchstabe|Buchst\\."
- + "|Teil|Abschnitt|Unterabschnitt|Buch|Kapitel|Anlage)"
+ + "|Teil|Abschnitt|Unterabschnitt|Buch|Kapitel|Anlage|\\p{L}+abschnitt)"
// Der Wächter „(?!wird |werden )“ ist nötig: ohne ihn verschlänge die Untereinheit
// in der schlichten Form („Abs. 3 wird Abs. 2 und wird wie folgt geändert:“) das
// Verb und wäre als Stelle unparsbar.
@@ -2194,7 +2194,15 @@ final class BefehlErkenner {
case "Buchstabe", "Buchst." -> new Stelle.BuchstabeNr(nummer);
case "Teil", "Abschnitt", "Unterabschnitt", "Buch", "Kapitel", "Anlage" ->
new Stelle.Gliederungseinheit(ebene, nummer);
- default -> throw new IllegalArgumentException("Unbekannte Ebene: " + ebene);
+ // Die benannte Einheit einer Anlage („Ausbildungsabschnitt 5“) — sie gliedert die Anlage,
+ // nicht das Gesetz, und wird wie deren Nummern als eigene Norm geführt.
+ default -> {
+ if (!ebene.regionMatches(true, Math.max(0, ebene.length() - 9), "abschnitt", 0, 9)
+ || ebene.length() <= 9) {
+ throw new IllegalArgumentException("Unbekannte Ebene: " + ebene);
+ }
+ yield new Stelle.Gliederungseinheit(ebene, nummer);
+ }
};
}
diff --git a/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java b/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java
index f6e7360..e772846 100644
--- a/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java
+++ b/src/main/java/eu/mulk/aendggner/aenderung/parse/StellenParser.java
@@ -230,6 +230,18 @@ public final class StellenParser {
i++;
}
default -> {
+ // Die benannte Einheit einer Anlage: „Im Ausbildungsabschnitt 1 …“. Sie steht nicht in
+ // der Liste der Gliederungsarten, weil ihr Name der Anlage gehört und nicht dem Gesetz;
+ // erkannt wird sie an ihrem Bau (siehe istAnlagenEinheit).
+ if (istAnlagenEinheit(wort)) {
+ var wert = naechstesWort(woerter, i);
+ if (wert != null && NUMMER_WERT.matcher(wert).matches()) {
+ komponenten.add(new Stelle.Gliederungseinheit(anlagenEinheitsArt(wort), wert));
+ i++;
+ continue;
+ }
+ return Optional.empty();
+ }
// Ordinal vor der Gliederungsart: „zum zweiten Abschnitt“, „des 2. Abschnitts“.
var ordinal = ordinalZahl(wort);
var art = ordinal != null ? naechstesWort(woerter, i) : null;
@@ -573,6 +585,29 @@ public final class StellenParser {
};
}
+ /**
+ * Die benannte Einheit einer Anlage („Ausbildungsabschnitt 1“ des Ausbildungsrahmenplans).
+ *
+ * <p>Erkannt wird sie an ihrem Bau und nicht an einer Wortliste: Jede Anlage darf ihre Einheiten
+ * nennen, wie sie will, und eine Liste wäre am nächsten Land wieder unvollständig. Verlangt sind
+ * Buchstaben <em>vor</em> dem Wortstamm — „Ausbildungsabschnitt“ trifft, „Abschnitt“ nicht, denn
+ * der gliedert das Gesetz und ist anderswo geregelt.
+ */
+ private static final Pattern ANLAGEN_EINHEIT =
+ Pattern.compile("^(\\p{L}+abschnitt)(?:s|e|es|en)?$", Pattern.CASE_INSENSITIVE);
+
+ private static boolean istAnlagenEinheit(String wort) {
+ // „Unterabschnitt“ endet auf den Wortstamm, gliedert aber das Gesetz; er ist oben abgehandelt
+ // und darf hier nicht noch einmal zugreifen.
+ return !istGliederungsArt(wort) && ANLAGEN_EINHEIT.matcher(wort).matches();
+ }
+
+ /** Der Nominativ Singular der benannten Einheit („Ausbildungsabschnitts“ → „…abschnitt“). */
+ private static String anlagenEinheitsArt(String wort) {
+ var treffer = ANLAGEN_EINHEIT.matcher(wort);
+ return treffer.matches() ? treffer.group(1) : wort;
+ }
+
/** Normalisiert Genitiv-/Pluralformen der Gliederungsart auf den Nominativ Singular. */
private static String gliederungsArt(String wort) {
return switch (wort) {
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
index eb69423..a73ab77 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/BefehlAnwender.java
@@ -2217,6 +2217,38 @@ public final class BefehlAnwender {
return angewandt(befehl, enbezNeu);
}
+ // „Der bisherige Ausbildungsabschnitt 5 wird zu Ausbildungsabschnitt 6.“ — die benannte
+ // Einheit einer Anlage ist eine eigene Norm; umnummeriert wird deren Bezeichnung. Der Befehl
+ // nennt nur die Einheit, die Norm heißt aber „Anlage 1 Ausbildungsabschnitt 6“: Die Anlage
+ // steht im Bestand und wird von dort übernommen, nicht aus dem Befehl erraten.
+ var altEinheit = befehl.stelle().anlagenEinheit();
+ var neuEinheit = befehl.neu().anlagenEinheit();
+ if (altEinheit.isPresent() && neuEinheit.isPresent()) {
+ var ergebnis = StellenAufloeser.aufloese(gesetzAus(normen), befehl.stelle());
+ if (ergebnis instanceof StellenAufloeser.Ergebnis.NichtGefunden nicht) {
+ return manuell(befehl, nicht.grund(), nicht.begruendung());
+ }
+ var idx = ((StellenAufloeser.Ergebnis.Gefunden) ergebnis).fundstelle().normIndex();
+ var enbezAlt = normen.get(idx).enbez();
+ var alteBezeichnung = altEinheit.get().bezeichnung();
+ if (!enbezAlt.endsWith(alteBezeichnung)) {
+ return manuell(
+ befehl,
+ Grund.BESTAND_WIDERSPRICHT,
+ "„" + enbezAlt + "“ trägt nicht die Bezeichnung „" + alteBezeichnung + "“.");
+ }
+ var enbezNeu =
+ enbezAlt.substring(0, enbezAlt.length() - alteBezeichnung.length())
+ + neuEinheit.get().bezeichnung();
+ if (!enbezNeu.equals(enbezAlt)
+ && StellenAufloeser.normIndex(gesetzAus(normen), enbezNeu) >= 0) {
+ return manuell(
+ befehl, Grund.BESTAND_WIDERSPRICHT, enbezNeu + " existiert bereits im Gesetz.");
+ }
+ normen.set(idx, normen.get(idx).mitEnbez(enbezNeu));
+ return angewandt(befehl, enbezNeu);
+ }
+
var altAbsatz = befehl.stelle().absatz();
var neuAbsatz = befehl.neu().absatz();
// Nur wenn der Absatz selbst das Umnummerierungsziel ist — bei „Satz 5 wird Satz 4“ stammt
diff --git a/src/main/java/eu/mulk/aendggner/anwendung/StellenAufloeser.java b/src/main/java/eu/mulk/aendggner/anwendung/StellenAufloeser.java
index 32d9c8b..f9d4ff2 100644
--- a/src/main/java/eu/mulk/aendggner/anwendung/StellenAufloeser.java
+++ b/src/main/java/eu/mulk/aendggner/anwendung/StellenAufloeser.java
@@ -64,6 +64,16 @@ final class StellenAufloeser {
stelle = stelle.ohne(nummer.get());
}
}
+ // Ebenso die benannte Einheit einer Anlage („Anlage 1 Ausbildungsabschnitt 1“): Sie gliedert
+ // nicht das Gesetz, sondern die Anlage, und der Ausbildungsrahmenplan führt sie so.
+ var einheit = stelle.anlagenEinheit();
+ if (einheit.isPresent()) {
+ var alsNorm = enbez + " " + einheit.get().bezeichnung();
+ if (normIndex(gesetz, alsNorm) >= 0) {
+ enbez = alsNorm;
+ stelle = stelle.ohne(einheit.get());
+ }
+ }
} else {
return new Ergebnis.NichtGefunden(
"Stelle nennt keinen Paragraphen: " + stelle.anzeigeText(),
diff --git a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextAusgeber.java b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextAusgeber.java
index 3a82945..74d6e11 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextAusgeber.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextAusgeber.java
@@ -40,7 +40,9 @@ public final class LandesRechtTextAusgeber {
* Anlagen-Norm stellt den Bezug schon her.
*/
private static final Pattern ANLAGEN_NUMMER =
- Pattern.compile("^(?:Anlage|Anhang)(?:\\s+\\d+[a-z]?)?\\s+((?:Nummer|Nr\\.)\\s+\\d+[a-z]?)$");
+ Pattern.compile(
+ "^(?:Anlage|Anhang)(?:\\s+\\d+[a-z]?)?"
+ + "\\s+((?:Nummer|Nr\\.|\\p{L}+abschnitt)\\s+\\d+[a-z]?)$");
private LandesRechtTextAusgeber() {}
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 7f7e2f4..b93ef5d 100644
--- a/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
+++ b/src/main/java/eu/mulk/aendggner/gesetz/land/LandesRechtTextParser.java
@@ -149,6 +149,16 @@ final class LandesRechtTextParser {
"^((?:\\(X+\\) )?(?:§§|Artt\\.) \\S.*?) {2}(\\((?:weggefallen|aufgehoben)\\))$");
/**
+ * Eine benannte Einheit innerhalb einer Anlage: die Nummer eines Zuständigkeitskatalogs („Nummer
+ * 6“) ebenso wie der Abschnitt eines Ausbildungsrahmenplans („Ausbildungsabschnitt 1“).
+ *
+ * <p>Die zweite Form ist nicht als „Abschnitt“ zu lesen: Ein Abschnitt gliedert das Gesetz,
+ * innerhalb einer Anlage gliedert nichts mehr. Verlangt sind deshalb Buchstaben <em>vor</em> dem
+ * Wortstamm — „Ausbildungsabschnitt“ trifft, „Abschnitt“ nicht.
+ */
+ private static final String ANLAGEN_EINHEIT = "(?:Nummer|Nr\\.|\\p{L}+abschnitt)\\s+\\d+[a-z]?";
+
+ /**
* Der Kopf einer Nummer innerhalb einer Anlage („Nummer 6“). Die Nummern eines
* Zuständigkeitskatalogs sind keine Aufzählungsglieder, sondern eigene Einheiten mit eigener
* Absatzzählung — das Landesrechtsportal führt jede als eigene Einzelnorm, und die
@@ -157,7 +167,7 @@ final class LandesRechtTextParser {
* voranstellt: „Anlage Nummer 6“.
*/
private static final Pattern ANLAGEN_NUMMER_KOPF =
- Pattern.compile("^(?:Nummer|Nr\\.)\\s+(\\d+[a-z]?)\\s*$");
+ Pattern.compile("^(" + ANLAGEN_EINHEIT + ")\\s*$");
private static final Pattern WEGGEFALLEN_TITEL =
Pattern.compile("^\\((?:aufgehoben|weggefallen)\\)$");
@@ -331,8 +341,7 @@ final class LandesRechtTextParser {
} else if (neueAnlagenNummer) {
// „Anlage Nummer 6“ — die Anlage selbst bleibt als Norm vor ihnen stehen; trägt sie
// keinen eigenen Wortlaut, so ist sie eben leer.
- normEnbez =
- (anlagenEnbez != null ? anlagenEnbez + " " : "") + "Nummer " + anlagenNummer.group(1);
+ normEnbez = (anlagenEnbez != null ? anlagenEnbez + " " : "") + anlagenNummer.group(1);
normTitel = null;
} else if (neueNorm) {
normEnbez = normKopf.group(1) + " " + normKopf.group(2);
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 34bc8e6..ea014b0 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -1931,8 +1931,12 @@ class EndToEndTest {
var gesetz = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(alt);
assertThat(gesetz.jurabk()).isEqualTo("APOAmtsTA M-V");
- // Die Inhaltsübersicht ist eine Norm gleichen Namens; dazu die 28 Paragraphen.
- assertThat(gesetz.normen()).hasSize(29);
+ // Die Inhaltsübersicht ist eine Norm gleichen Namens; dazu die 28 Paragraphen, die Anlage 1
+ // (Ausbildungsrahmenplan) und deren fünf Ausbildungsabschnitte, die das Portal wie die Nummern
+ // eines Zuständigkeitskatalogs als eigene Einheiten führt.
+ assertThat(gesetz.normen()).hasSize(35);
+ assertThat(gesetz.norm("Anlage 1 Ausbildungsabschnitt 1")).isPresent();
+ assertThat(gesetz.norm("Anlage 1 Ausbildungsabschnitt 5")).isPresent();
var text = TextBereiniger.bereinige(new PatchTextExtraktor().extrahiere(pdf));
// Kolumnentitel und Seitenfuß stehen im Inhaltsstrom, und zwar mitten im Befehlstext.
@@ -1945,7 +1949,7 @@ class EndToEndTest {
assertThat(parseErgebnis.befehle()).hasSize(24);
var anwendung = BefehlAnwender.anwenden(gesetz, parseErgebnis.befehle());
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(20);
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(22);
// Die Nummern 2 und 3 entfallen, die Nummern 4 bis 6 rücken auf 2 bis 4 — ohne Platzhalter,
// denn sie stehen am Ende ihres Blockes nicht.
@@ -1965,15 +1969,31 @@ class EndToEndTest {
assertThat(anwendung.neu().norm("Inhaltsübersicht").orElseThrow().absaetze().get(0).text())
.contains("§ 10 | Ausbildungsnachweise");
- // Übrig bleiben allein die vier Befehle, die die Anlagen betreffen: Die Stammfassung führt sie
- // nicht mit (Tabellen und Vordrucke), und der elfte Befehl verweist auf einen Anhang des
- // Änderungsgesetzes.
+ // Die benannte Einheit der Anlage trägt die Wortersetzung …
+ assertThat(
+ anwendung
+ .neu()
+ .norm("Anlage 1 Ausbildungsabschnitt 1")
+ .orElseThrow()
+ .absaetze()
+ .get(0)
+ .text())
+ .startsWith("Das für das Veterinärwesen zuständige Ministerium")
+ .doesNotContain("Ministerium für Landwirtschaft, Umwelt und Verbraucherschutz");
+ // … und die Umnummerierung trifft ihre Bezeichnung, nicht ihren Wortlaut.
+ assertThat(anwendung.neu().norm("Anlage 1 Ausbildungsabschnitt 6")).isPresent();
+ assertThat(anwendung.neu().norm("Anlage 1 Ausbildungsabschnitt 5")).isEmpty();
+
+ // Übrig bleiben zwei Befehle, beide mit benanntem Grund: Nummer 10 Buchstabe b fügt einen
+ // ganzen Ausbildungsabschnitt ein — eine neue Norm innerhalb einer Anlage, die das Erzeugnis
+ // nicht setzt —, und Nummer 11 verweist auf einen Anhang des ändernden Gesetzes, der nicht
+ // vorliegt. Was nicht vorliegt, lässt sich nicht anwenden.
var offen =
anwendung.protokoll().stream()
.filter(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
.map(a -> a.befehl().provenienz().gliederungsPfad())
.toList();
- assertThat(offen).containsExactly("10. a)", "10. b)", "10. c)", "11.");
+ assertThat(offen).containsExactly("10. b)", "11.");
}
private static Aenderungsbefehl befehlZu(
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java
index 13e4442..6c34b8c 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/StellenParserTest.java
@@ -152,4 +152,24 @@ class StellenParserTest {
// Ein Wort, das bloß so anfängt, ist keiner.
assertThat(StellenParser.istNurChapeau("In der Einleitungsformel")).isFalse();
}
+
+ @Test
+ void benannteEinheitEinerAnlage() {
+ // „Im Ausbildungsabschnitt 1 …“ — der Ausbildungsrahmenplan gliedert sich in benannte
+ // Einheiten, die das Portal wie eigene Normen führt.
+ var stelle = StellenParser.parse("Ausbildungsabschnitt 1").orElseThrow();
+ assertThat(stelle.anzeigeText()).isEqualTo("Ausbildungsabschnitt 1");
+ assertThat(stelle.anlagenEinheit()).isPresent();
+ assertThat(stelle.betrifftEchteGliederung()).isFalse();
+ }
+
+ @Test
+ void unterabschnittBleibtGliederungDesGesetzes() {
+ // Er endet auf denselben Wortstamm, gliedert aber das Gesetz und ist keine Einheit einer
+ // Anlage. Ohne diese Grenze fiele „Die Überschrift zu Unterabschnitt 4 wird gestrichen“ aus
+ // dem Gliederungszweig heraus.
+ var stelle = StellenParser.parse("Unterabschnitt 4").orElseThrow();
+ assertThat(stelle.anlagenEinheit()).isEmpty();
+ assertThat(stelle.betrifftEchteGliederung()).isTrue();
+ }
}
diff --git a/src/test/resources/sampledata/MecklenburgVorpommern/APOAmtsTA-MV-alt.txt b/src/test/resources/sampledata/MecklenburgVorpommern/APOAmtsTA-MV-alt.txt
index 32a5bd0..c8749d7 100644
--- a/src/test/resources/sampledata/MecklenburgVorpommern/APOAmtsTA-MV-alt.txt
+++ b/src/test/resources/sampledata/MecklenburgVorpommern/APOAmtsTA-MV-alt.txt
@@ -216,3 +216,54 @@ Für Veterinärreferendarinnen und Veterinärreferendare mit Behinderungen sind
Abschnitt 4 Schlussvorschriften
§ 28 Inkrafttreten, Außerkrafttreten
Diese Verordnung tritt am 1. Oktober 2016 in Kraft. Gleichzeitig tritt die Ausbildungs- und Prüfungsordnung höherer Veterinärdienst vom 29. Dezember 1997 (GVOBl. M-V 1998 S. 85), die zuletzt durch Artikel 3 Absatz 8 des Gesetzes vom 1. Dezember 2008 (GVOBl. M-V S. 461, 464) geändert worden ist, außer Kraft.
+Anlage 1
+Ausbildungsrahmenplan
+(zu § 7 Absatz 3, § 12 Absatz 3)
+Die Lernziele und Lerninhalte richten sich nach dem Modulsystem der Länderarbeitsgemeinschaft Verbraucherschutz.
+Ausbildungsabschnitt 1
+Ministerium für Landwirtschaft, Umwelt und Verbraucherschutz
+Dauer: 7 Monate
+Ausbildungsinhalte:
+ - Aufbau und Funktion der Verwaltung, insbesondere der Veterinärverwaltung,
+ - Aufgaben der Dienst- und Fachaufsicht, Einblick in die laufenden Geschäftsvorgänge, Verwaltungs- und Haushaltsrecht, Bearbeitung von Vorgängen, Fertigung von Vermerken, Vorlagen, Schreiben, Berichten und Halten von Vorträgen,
+ - Erstellung von Entwürfen für Verordnungen, Verfügungen, Genehmigungen, Zulassungen, Obergutachten; konkrete Durchführung der Fachaufsicht bezüglich des Vollzuges des Tierseuchen-, Lebensmittel-, Tierschutz-, Arzneimittel-, Betäubungsmittel-, Heilmittelwerbe-, Futtermittel-, Tierische Nebenprodukte-Beseitigungs- und Abfallrechtes,
+ - Teilnahme an dem Lehrgang für technische Referendare, der einen Gesamtüberblick über die wesentlichen Aufgaben der allgemeinen und der landwirtschaftlichen Verwaltung gibt, Schulung im Vortrag während dieses Lehrganges.
+Ausbildungsabschnitt 2
+Veterinär- und Lebensmittelüberwachungsamt
+Dauer: 6 Monate
+Ausbildungsinhalte:
+ - allgemeine und spezielle Tierseuchenbekämpfungsmaßnahmen bei anzeigepflichtigen Tierseuchen und meldepflichtigen Tierkrankheiten, Tiergesundheit, Tierseuchenprophylaxe, Beseitigung tierischer Nebenprodukte,
+ - Überwachung der Erzeugung, Herstellung, Be- und Verarbeitung und des Inverkehrbringens von Lebensmitteln, Bedarfsgegenständen und Kosmetika,
+ - Beteiligung an der Fortbildung von Hilfskräften,
+ - Vorbereitung, Durchführung und Nachbereitung tierschutzrechtlicher Kontrollen, erforderlichenfalls Anordnung und Durchführung von Maßnahmen zur Behebung vorliegender und zur Vermeidung zukünftiger Verstöße,
+ - Berechnung und Erhebung von Veterinärverwaltungskosten,
+ - Aufgaben im Strahlenschutz und Katastrophenschutz,
+ - Einweisung in die Aufgaben als Sachverständiger vor Gericht,
+ - Koordination der behördlichen Zusammenarbeit, insbesondere mit der Ordnungsbehörde und der Staatsanwaltschaft sowie mit praktizierenden Tierärzten, Fachorganisationen und -verbänden.
+Ausbildungsabschnitt 3
+Landesamt für Landwirtschaft, Lebensmittelsicherheit und Fischerei
+Dauer: 4 Monate
+Ausbildungsinhalte:
+ - Vertiefung der Kenntnisse bezüglich Untersuchungsverfahren zur Diagnostik von Tierseuchen, Tierkrankheiten und Zoonosen,
+ - Grundsätze der Probenplanung unter Berücksichtigung des Risikokonzeptes und Überwachungsprogrammen, Datenmeldung,
+ - risikoorientierte Untersuchung und Beurteilung nach dem Lebensmittel-, Bedarfsgegenstände- und Kosmetikrecht,
+ - Untersuchungen und Beurteilungen nach dem Arzneimittel-, Betäubungsmittel- und Futtermittelrecht,
+ - Überwachung der tierärztlichen Hausapotheken, der Herstellung und des Einsatzes von Fütterungsarzneimitteln,
+ - Probeentnahmen im Rahmen der Tierarzneimittelüberwachung,
+ - Aufgaben der veterinärmedizinischen Bauhygiene,
+ - Erstellung von Stellungnahmen und Gutachten,
+ - Bearbeitung von Zulassungs- und Genehmigungsverfahren,
+ - Grundsätze des Tierschutzrechts und des Tierversuchswesens,
+ - Inhalt und praktische Anwendung des Tierische Nebenprodukte-Beseitigungsrechts.
+Ausbildungsabschnitt 4
+Veterinär- und Lebensmittelüberwachungsamt, Sachgebiet Schlachttier- und Fleischuntersuchung und Hygieneüberwachung
+Dauer: 2 Monate
+Ausbildungsinhalte:
+ - amtliche Überwachung von Frischfleisch, Überprüfungs- und Inspektionsaufgaben, Genusstauglichkeitskennzeichnung und Maßnahmen im Anschluss an die Kontrollen,
+ - Überwachung von Schlachttiertransporten, Kontrolle der Anlieferung der Tiere, gegebenenfalls tierschutzgerechte Tötung, Unterbringung der Tiere und Zutrieb zur Betäubung, Betäubungs- und Tötungsverfahren,
+ - Beseitigung von tierischen Nebenprodukten.
+Ausbildungsabschnitt 5
+Fachseminar
+Dauer: 3 Monate
+Ausbildungsinhalte:
+ - Vertiefung der aufgabenbezogenen wissenschaftlichen Kenntnisse und der Kenntnisse im allgemeinen Verwaltungsrecht und in Teilen des besonderen Verwaltungsrechtes
diff --git a/src/test/resources/sampledata/MecklenburgVorpommern/SOURCES b/src/test/resources/sampledata/MecklenburgVorpommern/SOURCES
index f821362..af7a157 100644
--- a/src/test/resources/sampledata/MecklenburgVorpommern/SOURCES
+++ b/src/test/resources/sampledata/MecklenburgVorpommern/SOURCES
@@ -77,12 +77,39 @@ Beispieldaten Mecklenburg-Vorpommern — Herkunft und Aufbereitung
- Hochgestellte Fußnotenziffern an den Artikel-Überschriften des Mantelgesetzes
(„Änderung der … amtstierärztlicher Dienst⁴⁴“).
-5. Benannte Grenzen
- Vier der 24 Befehle bleiben liegen, sämtlich die *Anlagen* betreffend:
- Nummer 10 Buchstaben a bis c ändern die Anlage 1 (Ausbildungsrahmenplan) über
- deren „Ausbildungsabschnitte“, Nummer 11 ersetzt die Anlage 2 durch eine
- Fassung, die im *Anhang des Änderungsgesetzes* steht. Die Anlagen sind
- Tabellen und Vordrucke; der kanonische Klartext hat für sie keine Form, und
- sie sind in der Stammfassung deshalb nicht mitgeführt. Der Verweis auf einen
- Anhang des ändernden Gesetzes ist überdies eine Form, die das Erzeugnis nicht
- auflöst.
+5. Die Anlage 1 (nachgetragen am 28.08.2026)
+ Die Stammfassung führt seither den Ausbildungsrahmenplan (Anlage 1) mit. Er
+ steht im Ursprungsheft GVOBl. M-V 2016 Nr. 12 auf den Seiten 498 bis 500 und
+ ist über das Internet-Archiv beschafft worden, weil der Wirt regierung-mv.de
+ einer Skriptabfrage nicht antwortet:
+ https://web.archive.org/web/20220302185826/https://www.regierung-mv.de/static/
+ Regierungsportal/Justizministerium/Inhalte/Rechtliches/GVOBI.M-V/Dateien/
+ GVOBl_1245663.pdf (abgerufen am 28.08.2026).
+
+ Der Befund dabei ist wichtiger als der Nachtrag selbst: **Die Anlage 1 ist
+ keine Tabelle.** Sie ist gegliederter Fließtext — je Ausbildungsabschnitt eine
+ Kopfzeile, die Ausbildungsstelle, die Dauer und eine Spiegelstrichliste der
+ Inhalte. Der kanonische Klartext trägt sie unverändert; die frühere Annahme,
+ die Anlagen ließen sich nur als Tabellen führen, trifft allein auf die Anlage 2
+ zu (Vordruck des Befähigungsberichts mit den Spalten Wertung, Wertigkeit,
+ Einzelergebnis).
+
+ Aufbereitet mit `--extract-only`: Kopfzeilen ohne Doppelpunkt, Spiegelstriche
+ als „ - “ eingerückt, der Kolumnentitel („Nr. 12 Tag der Ausgabe …“) entfernt.
+ Die fünf Ausbildungsabschnitte werden — wie die Nummern des Berliner
+ Zuständigkeitskatalogs — als eigene Normen geführt: „Anlage 1
+ Ausbildungsabschnitt 1“ bis „… 5“.
+
+6. Benannte Grenzen
+ Zwei der 24 Befehle bleiben liegen:
+ - Nummer 10 Buchstabe b fügt einen ganzen Ausbildungsabschnitt ein. Eine neue
+ Norm *innerhalb* einer Anlage setzt das Erzeugnis nicht; die Einfügung ganzer
+ Einheiten ist an den §-Kopf gebunden.
+ - Nummer 11 ersetzt die Anlage 2 durch eine Fassung, die im *Anhang des
+ Änderungsgesetzes* steht. Der Verweis auf einen Anhang des ändernden Gesetzes
+ wird nicht aufgelöst, und die neue Fassung liegt damit nicht vor. Was nicht
+ vorliegt, lässt sich nicht anwenden.
+
+ Die Anlage 2 selbst ist aus demselben Grund nicht nachgetragen: Sie ist ein
+ Vordruck, ihr einziger Befehl ersetzt sie ohnehin ganz, und ihre Form wäre die
+ einzige im ganzen Beispielkorpus, für die der kanonische Klartext keine hat.
diff --git a/src/test/resources/sampledata/korpus-grundlinie.tsv b/src/test/resources/sampledata/korpus-grundlinie.tsv
index 60da4f6..b5b360c 100644
--- a/src/test/resources/sampledata/korpus-grundlinie.tsv
+++ b/src/test/resources/sampledata/korpus-grundlinie.tsv
@@ -1,4 +1,4 @@
-# Grundlinie des Korpuslaufs (§ 6d des Handbuchs), Stand 28. August 2026.
+# Grundlinie des Korpuslaufs (§ 6d des Handbuchs), Stand 28. August 2026 (zweite Fassung des Tages).
#
# Jede Zeile hält fest, was ein Auftrag der Liste „korpus.tsv“ zu diesem Zeitpunkt hergegeben hat.
# Der Lauf wird dagegen gehalten (KorpusberichtTest, „--grundlinie“): Gerügt wird allein, was
@@ -34,7 +34,7 @@ TH-KiGaFinVO 10 10 0 0 - - - - - -
BB-FraktG 4 4 0 0 24 24 0 0 0 -
HH-GutachterausschussVO 21 17 4 0 10 14 0 0 4 Befehl nicht erkannt:2; Stelle nicht auffindbar:1; Zieltext nicht vorhanden:1
HB-BerufsfachschulVO 41 37 4 0 26 29 1 0 2 Befehl nicht erkannt:3; Fundstelle mehrdeutig:1
-MV-APOAmtsTA 24 20 4 0 - - - - - Befehl nicht erkannt:4
+MV-APOAmtsTA 24 22 2 0 - - - - - Befehl nicht erkannt:2
RP-UKAPOGS 23 9 14 0 - - - - - Stelle nicht auffindbar:2; Zieltext nicht vorhanden:10; Bestand widerspricht dem Befehl:2
GEG-GModG-RegE 97 87 10 0 - - - - - Befehl nicht erkannt:6; Stelle nicht auffindbar:2; Zieltext nicht vorhanden:2
GEG-Beschlussempfehlung 119 70 49 0 - - - - - Befehl nicht erkannt:1; Stelle nicht auffindbar:3; Zieltext nicht vorhanden:40; Fundstelle mehrdeutig:1; Bestand widerspricht dem Befehl:4