aboutsummaryrefslogtreecommitdiff
path: root/src/test
Commit message (Collapse)AuthorAgeFilesLines
* Add Schleswig-Holstein extraction case and three cleanup fixesMatthias Andreas Benkard2026-07-252-0/+88
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The GVOBl. Schl.-H. amendment act (Kommunalrecht, 2026/27) brings three conventions the tool had not seen: * a running two-line page header that sits between articles and glued itself onto the following article heading, * superscript footnote markers on article headings ("Artikel 1 ¹)"), which defeated the article split, and * ragged-right typesetting, where the geometric margin detection finds no alignment cluster and reports full lines as hard line ends, so a trailing hyphen was never joined. All three are fixed in TextBereiniger; the hyphen rule now trusts the extractor's trailing-space signal over the geometric class. A full acceptance test is not possible: the Schleswig-Holstein law portal serves document bodies only through an authenticated API, and the last freely archived consolidated Gemeindeordnung (14 Nov 2022) predates the amended § 34a. The new test therefore covers everything up to and including command recognition; the blocker is recorded in Landesrecht-Beispiele.adoc. 219 tests green; Bund/Bayern/Sachsen/Niedersachsen figures unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I9b3ab8a756372504b51c4d410b491fded255da96
* Add Lower Saxony NEFG acceptance case and two parser robustness fixesMatthias Andreas Benkard2026-07-241-0/+61
| | | | | | | | | | | | | | | | | | | | | | | | | | | Second §-structured Landesrecht case for wave six: the Lower Saxony ELER Support Act (NEFG, unchanged since 2022, derived from the two-column Nds. GVBl 2022 No. 33) plus the amending act of 28 Jan 2026 (Nds. GVBl 2026 No. 10). Five of seven commands apply — both "erhält folgende Fassung" re-enactments (§ 1(1), § 2), the citation replacement in § 1(5), the words insertion in § 6(1), and the § 13 → § 14 renumbering. - LandesRechtTextParser: a norm head no longer accepts a title ending in a full stop, so a cross-reference sentence at line start ("§ 7 GAPInVeKoSG findet entsprechend Anwendung.") is not misread as a norm head "§ 7" (which had made the monotonicity check swallow the norms in between). - TextBereiniger: strip the Lower Saxony GVBl running footer and publisher line. The footer sat between two enumerated items and otherwise attached to the preceding re-enactment quote, defeating its end-of-sentence anchor. - Add sampledata/Niedersachsen/NEFG-alt.txt and EndToEndTest.nefgAcceptance. Two insertion forms remain documented residues (manuell prüfen): inserting a §-block with a space-separated sub-number ("§ 2 a") and a chapter-scoped §-block insertion ("In Kapitel 4 wird nach § 12 … angefügt"). 221 tests green; federal, Bavarian and Saxon pinned numbers unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: Ie23eee1f596ec8eecc6a73f6ce585a22e73ec6c9
* Add Saxony SächsBeamtVG acceptance case (first non-Bavarian Landesrecht)Matthias Andreas Benkard2026-07-241-0/+57
| | | | | | | | | | | | | | | | | | | | | | Complete the first §-structured Landesrecht acceptance case for wave six: the Saxon Civil Servants' Pension Act (SächsBeamtVG) in its 1 Feb 2025 version plus the amending act of 13 April 2026 (SächsGVBl. S. 134). All five commands are recognised and applied without residue — the amount replacements in § 47 and the re-enactment of § 80(1) sentence 2. - LandesRechtTextParser: recognise arabic, keyword-first structural headings ("Abschnitt 1", "Unterabschnitt 2", "Teil 3") via GLIEDERUNG_ARABISCH, in addition to Bavaria's roman "I. Abschnitt" form. Bavaria behaviour unchanged. - TextBereiniger: strip the footer of revosax full-text output (REVOSAX_FUSS, "https://…revosax… Fassung vom … Seite N von M"). - Add the consolidated pre-amendment stem as canonical plaintext (sampledata/Sachsen/SaechsBeamtVG-alt.txt, derived from the archived revosax full text) and EndToEndTest.saechsBeamtVgAcceptance (5 applied, 0 manual). 220 tests green; federal and Bavarian pinned numbers unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: I8e878bc9506f24028cf643a873de000f3d066d64
* Generalize Bavarian loader to a shared Landesrecht loaderMatthias Andreas Benkard2026-07-193-12/+71
| | | | | | | | | | | | | | | | | | | | | | | | | | | Prepare wave six (Landesrecht of the remaining Länder) by lifting the Bavaria-specific stem-law loader to a shared one. Unlike Bavaria (Art./§ inversion), the other Länder structure their stem laws in §, so the loader must handle both sigils. - Move gesetz/bayern/{BayRechtLoader,BayRechtTextParser} to gesetz/land/{LandesRechtLoader,LandesRechtTextParser}; the norm head now matches "§ N" as well as "Art. N" and derives the sigil per norm from the match. Cross-reference keywords in the norm head are excluded only as whole words (so a title "Satzungen" no longer trips on "Satz"), and the juris abbreviation may be multi-token ("(GO NRW)"). - Derive the superscript mode data-drivenly from the loaded stem law (Superskript.traegtSatznummern) instead of from "is it gii-XML": Bavaria and Lower Saxony keep their amtliche Satznummern, the Bund and Länder without official sentence numbering drop them. - Recognize the neufassung idiom "erhält/erhalten folgende Fassung" (Schleswig- Holstein, Niedersachsen) via a NEUFASSUNG_VERB building block, additive to "wird/werden wie folgt gefasst". Federal and Bavarian behaviour is unchanged (219 tests green, pinned acceptance numbers UWG 19/0, GEG 66/53, GEG-GModG 90/9, IfSG 42/24, BayJG 149/154 hold). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: I55a70a7932bf657a2346ca70f3fa05e173bf80ad
* Support Bavarian state law (BayJG) with Art./§ inversion and superscriptsMatthias Andreas Benkard2026-07-198-0/+689
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Extend ÄndGgner from federal-only to Bavarian state law. Bavarian base laws are structured in Artikel while their amending acts are structured in Paragraphen (inverted from the Bund); official sentence numbers and footnote markers are carried as Unicode superscripts (¹²³, ⁶)). - Generalise Stelle.Paragraph to carry a sigil ("§" or "Art.") and route it through recogniser, applier and resolver (bit-identical Bund output). - Superscript pipeline: geometric detection in FontgroessenFilter (SuperskriptModus BEHALTEN), Superskript util, exact sentence splitting in SatzTeiler, label-based sentence resolution. - BayRechtLoader/BayRechtTextParser for gesetze-bayern.de PDF/plaintext. - §-structured amending acts, GVBl/Landtag column titles, non-breaking spaces, the GVBl continuation quote; Bavarian command forms (footnote aufhebung, Satznummerierung streichung, Wortlaut forms, Halbsatz, gapping chains). Acceptance (EndToEndTest.bayJgGvblAcceptance): the pre-2026 BayJG fassung (BayJG-alt.txt, reconstructed from Wayback single-article snapshots) with GVBl 6/2026 §§ 1-2 applied — 154 commands, 0 unknown, 149 applied automatically, 5 pinned residuals (follow-up edits inside two multi-step renumbering sequences in Art. 29a and Art. 56). Application-side fixes surfaced by the acceptance run (all Bund-safe): sentence-start superscript before §; sentence boundaries = {0} ∪ {each number ≥ 2}; gapping scope inheritance for bare word operations; footnote definition lines hidden from word operations; absatz aufhebung marks "(weggefallen)" keeping its number, and absatz renumbering overwrites an empty placeholder (weggefallen/gegenstandslos) target. 211 tests green (mvnw verify); federal reference numbers unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: Iebae4c17ca90755c5fd36251362042f3d5796fd0
* Classify line ends by alignment clusters and canonicalize definition listsMatthias Andreas Benkard2026-07-194-1/+101
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The percentile-based right-margin estimate from the previous commit misclassified two real-world layouts: at the column switch of the two-column old-BGBl format a window contains both column margins, so the 90th percentile returned the right column's margin and marked every left-column soft wrap as deliberate (vetoing hyphen mending, IfSG 2020: "Impf- surveillance"); and series of equally wide centered "Artikel N" headings in draft bills formed the top of their window's distribution, so they counted as full-width and were reflowed into the following line, breaking teileInArtikel's line anchor (ProdHaftG RegE). Classify against alignment clusters instead: a line end is soft when at least five window lines end within 4 pt of it (the justified block's own margin), hard when such a cluster runs at least 10 pt above it, unclassified otherwise. Structure anchors ("Artikel 2", "§ 19", bare Gliederung labels) are always exempt from reflow since equal-width heading series still form sham clusters. Also flush the pending line end-X at page ends — without it, the first line of the next page inherited the previous page's footer geometry and was misclassified (UWG: "Artikel 2" swallowed the closing provisions). Markerless joins additionally never cross into a following enumeration marker line ("...vorgesehen und" + "d) die Überwachung" no longer glues to "undd)"), a text-level veto that also works without geometry. On the base-law side, ContentFlattener now separates sibling <LA> elements within a <DD> — the short-label/definition pairs of the UWG Anhang and § 2 IfSG were previously glued without any separator ("Irreführung über Unternehmereigenschaftdie unwahre Angabe...") in both synopsis columns. The continuation line is indented two spaces deeper than its enumeration line so StellenAufloeser.zeilenBlock keeps it inside the unit's block ("Anhang Nummer 31 Buchstabe b"). BefehlAnwender writes the same canonical shape when inserting or recasting quoted units (rueckeZitatEin), so XML-derived and PDF-derived items agree: " 2a. Stichwort" + " Definitionstext". Verified by mvnw verify (179 tests, 7 new) and a before/after sweep of all 21 sample law/amendment combinations: applied/manual counts are unchanged throughout (UWG 19/0, GEG-BGBl 66/53, GModG-RegE 77/20, IfSG-0645 42/24, ...), no marker characters leak into the HTML output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Change-Id: Ib7faae1bc5c59bda83f648a579af16a4c77e4285
* Fix markerless line-break join gluing whole words without a spaceMatthias Andreas Benkard2026-07-172-0/+26
| | | | | | | | | | | | | | | | | | | | | | | | TextBereiniger.verbindeUmbrueche assumed any letter-ending, no-trailing-space line followed by a lowercase continuation was a hyphen-less mid-word split (PDF extraction artifact) and joined it with zero separator. That assumption breaks for deliberate word-boundary breaks, e.g. the short-label/hanging- indent definition format in the UWG Anhang ("...Nachhaltigkeitssiegels" + "das Anbringen..."), producing glued words in the rendered synopsis. Gate the markerless join on the candidate line reaching a locally-typical "full column width" (90th percentile in a ±20-line window), since automatic wraps always land near the column edge while deliberate breaks don't. The window is local rather than document-wide because some source PDFs mix column widths within one document (narrower Regelungstext vs. wider Begründung), which a global statistic would otherwise penalize. Also route the single-unit Neufassung fallback through the existing normalisiereZitatText normalization, matching its sibling code paths, so that internal line breaks now more often preserved by the fix above don't leak into the HTML output as spurious line breaks instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Change-Id: I0a79f22f9286cd4a90eea32bad9b54cb4a082cf4
* Support Anhang/Anlage targets, Inhaltsübersicht Angabe commands, and ↵Matthias Andreas Benkard2026-07-163-5/+616
| | | | | | | | | | | | | | Gliederungs-Überschriften insertion/replacement Fourth enablement wave: Anhänge/Anlagen resolve as ordinary norm targets with nested Nummer/Buchstabe block resolution, InhaltsuebersichtAnwender applies Angabe commands automatically, GliederungsUeberschriften handles inserting and replacing structural headings, the law's own heading can be recast, and FontgroessenFilter now determines body text per page instead of document-wide. Sample data (UWG/AGG/ProdHaftG) now applies with 0 manual cases. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Change-Id: I1a76750afbecfa1aae734bcdabc91470a28420ca
* Support range structure replacement, struct renumbering, and multi-paragraph ↵Matthias Andreas Benkard2026-07-153-11/+345
| | | | | | | | | | | | blocks Adds bisStelle to StrukturErsetzung for coordinated target ranges, recognizes deletion/renumbering of whole structural units (paragraphs and Gliederung entries), and handles insertion/replacement of multi-paragraph blocks split on §-headings. Updates FASSUNGEN.txt accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Change-Id: I637cc5effbe32c094e62c070b63c470149a77c85
* Model and apply structural (Teil/Abschnitt) and TOC commandsMatthias Andreas Benkard2026-07-134-0/+94
| | | | | | | | | | | | | | | | | | | | | | | | Introduce the Gliederungsbaum into the model and support the structural command families that were the bulk of the remaining unknowns: - Gliederung gains a kennzahl; Gesetz carries the ordered list of Gliederungseinheiten, which the loader now collects. - New Stelle components Gliederungseinheit (Teil/Abschnitt/Unterabschnitt/ Anlage/…) and Absatzbezeichnung; StellenParser parses these plus "Überschrift von <Gliederung>" and drops "Satzteil/Angabe vor Nummer N" chapeau qualifiers. - Gliederungs-Überschrift Neufassung/Streichung apply to the tree and render as a "Geänderte Gliederungs-Überschriften" diff section; Absatzbezeichnung-Streichung removes an Absatz number; Inhaltsübersicht "Angabe(n) zu …" commands are recognized (applied via the existing TOC path). Reuses Neufassung/Aufhebung with structural Stellen rather than adding new command types. Final UnbekannterBefehl counts: GEG 50->5, IfSG 11->3, AGG 2->0 (§ 1 Alters->Lebensalters now applies), UWG 2->0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: I0cc91bfe65798140dd982b19f1884dfe60be87c4
* Support range, renumbering, and punctuation commandsMatthias Andreas Benkard2026-07-132-0/+106
| | | | | | | | | | | | | | | | | | | | | | | | | Extend the parser and applier for several command families that were falling back to UnbekannterBefehl: - Range/coordinated Aufhebung ("Die Nummern 1 bis 3 werden aufgehoben.", "Die Absätze 4 und 5 werden aufgehoben.") via bis-range expansion in StellenParser.parseMehrfach and ausStellen. - Range renumbering without "zu den" and for Nummern/Buchstaben ("Die bisherigen Nummern 4 bis 6 werden die Nummern 8 bis 10."); single renumbering now also covers Nummer/Buchstabe and "Die bisherige". - §-range Neufassung ("Die §§ 52 bis 56 werden wie folgt gefasst: …"), splitting the quoted block at "§ N" boundaries. - "Der Wortlaut wird Absatz N." — a new WortlautZuAbsatz command that numbers the previously unnumbered body. - Word-to-punctuation replacement ("… das Wort „oder" am Ende durch ein Komma ersetzt") and comma+words insert/replace variants. StellenParser gains plural component words (Absätze/Sätze/Nummern/ Buchstaben) and bis-range expansion. Cuts UnbekannterBefehl further: GEG 36->21, IfSG 7->3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: I4972d4eb1d6062521a55710c1f23822e8e22b0c4
* Recognize compound and multi-pair amendment commandsMatthias Andreas Benkard2026-07-132-17/+55
| | | | | | | | | | | | | | | | | | | | | | Split BefehlErkenner into a single-command pass and a fallback that composes Sammelbefehle from commands chained with "und"/", wird": - Multi-pair replacement ("… A durch B und C durch D ersetzt") emits one Ersetzung per pair, crossed with the (possibly coordinated) Stelle. - Verbund splitter probes each "und"/", wird" boundary; when both halves parse — trying the right clause as-is, capitalized, or with the left clause's locative prefix — it folds them into one Sammelbefehl. The single pass no longer short-circuits when a pattern matches but its Stelle is unparseable, so the fallback still gets a chance. Add "ein Komma eingefügt" and anchor-first insertion patterns the splitter needs, and let StellenParser.parseMehrfach inherit the component type for bare-number continuations ("Absatz 1 und 5"). Cuts UnbekannterBefehl counts: GEG 50->36, IfSG 11->7, AGG/UWG 2->1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: I8f76edd0c9ed6cf68c10c50f535a8182e13e378e
* Support multi-target "jeweils" commands and range renumberingMatthias Andreas Benkard2026-07-134-1/+200
| | | | | | | | | | | | Add a Sammelbefehl command that applies one operation to several "und"/"sowie"/comma-coordinated Stellen sharing a common prefix, and resolve "Die bisherigen Absätze X bis Y werden zu den Absätzen X' bis Y'" into descending single renumberings. Parsing gains StellenParser.parseMehrfach; the applier folds sub-commands into one log entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Change-Id: Iaa2c7161066d40d0605c00d3b6ca9a08696070e7
* Support the digital BGBl format and bill drafts as inputs.Matthias Andreas Benkard2026-07-134-4/+307
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The Bundesgesetzblatt has been published digitally via recht.bund.de since 2023 in a new single-column layout, and pending amendment acts are only available as Referenten-/Regierungsentwuerfe or Bundestag printed papers. Both now work as patch inputs: * PDF extraction filters out small print by font size (two-pass PDFTextStripper): in the new BGBl format, footnote blocks and superscript footnote markers would otherwise land in the middle of the statutory text, even inside quoted passages. * TextBereiniger recognizes the new page headers (BGBl "Seite N von M", draft page markers " - N - ", Bundestag printed-paper headers) and joins markerless end-of-line hyphenation (BT-Drs PDFs break words without a hyphen character; regular wraps carry a trailing space, so its absence is the signal -- gated on the source using the trailing-space convention at all, protecting hand-written plain-text inputs). BMJV draft templates draw the hanging opening quote after the paragraph marker ("(1) „" / "§ 19„"); this inversion is repaired. * Drafts embed the statutory text between a cover sheet and a Begruendung section; article scanning now stops at the Begruendung heading. Articles without numbered items (single-command articles like ProdHaftG-RegE Artikel 2) are parsed from the preamble rest. * Target-law matching is declension-tolerant ("Das Allgemeine Gleichbehandlungsgesetz" matches "Allgemeines Gleichbehandlungsgesetz") via rough word-stem comparison. * New command form StrukturErsetzung ("§ 2 Absatz 2 wird durch die folgenden Absätze 2 und 3 ersetzt"); "durch die folgende Überschrift/den folgenden § N ersetzt" map to Neufassung; plural insertions ("die folgenden Absätze 6 und 7"), triple-letter outline markers (aaa), the compound punctuation replacement ("durch ein Komma und die Wörter ... ersetzt"), and Inhaltsuebersicht-Angaben inside a context frame are recognized. End-to-end smoke tests cover the four new datasets: UWG (new BGBl format, footnote-filter assertion), GEG 2023 ("Heizungsgesetz", 121 commands), AGG (BT-Drs draft against an unconsolidated base -- the first dataset with real diffs, spot-checked against the official BMJV synopsis), and ProdHaftG (draft whose Artikel 1 is a replacement law and only Artikel 2 amends the base). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Change-Id: Id1b12bc0bee4178bd1a5c55b3a83f6e13944af69
* Add tests, usage documentation, and .claude/ to .gitignore.Matthias Andreas Benkard2026-07-139-0/+1145
Unit tests for the gii-norm loader, text cleanup, quote extraction, command recognition (literal sentences from the sample amendment act), sentence splitting, and command application on a synthetic mini law; an HTML renderer test including escaping; and an end-to-end smoke test on the IfSG sample data that skips itself when the (untracked) sample files are absent. The README gains a German usage section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Change-Id: I7f02ff349f9c3bd34fa90a2a84678b9e6fad697d