feat(kb): ingest rechtsprechung corpus

This commit is contained in:
2026-09-15 15:27:28 +02:00
parent 1594aa9cbd
commit 5209539ba3
365 changed files with 33112 additions and 51 deletions
+5
View File
@@ -8,6 +8,7 @@ import pytest
from agent.retrieve import ChunkResult
from agent.generate import (
REFUSAL_MESSAGE,
SYSTEM_PROMPT,
UNCERTAIN_MESSAGE,
answer_question,
build_user_content,
@@ -37,6 +38,10 @@ class TestValidateAnswer:
assert validate_answer("Siehe lb-min-02 für Details.", ["lb-min-02"]) == []
def test_system_prompt_requires_nonofficial_source_disclosure():
assert "nicht amtlich" in SYSTEM_PROMPT
class TestRefusalDetection:
def test_refusal_phrase(self):
assert looks_like_refusal("Dazu enthält die Wissensbasis keine Aussage.")
+106 -1
View File
@@ -134,4 +134,109 @@ def test_ris_entry(tmp_path, monkeypatch):
assert 'legal_bases: ["TestG"]' in md
assert "## § 1. Geltungsbereich" in md
assert "(1) Dieses Gesetz gilt für alle." in md
assert "ris-nso-06" in md # ID-Zeile im Body
assert "ris-nso-06" in md # ID-Zeile im Body
def _rj_catalog():
return {"source": "rj", "updated": None, "next_seq": 1, "entries": {}}
def _rj_stats():
return {"rj_written": 0, "rj_unparsed": [], "rj_empty": []}
def test_rj_rechtssatz_preserves_court_date_and_norm(tmp_path, monkeypatch):
root = tmp_path / ".rechtsprechung"
source = root / "originale" / "JJR_test.md"
source.parent.mkdir(parents=True)
source.write_text(
"# Rechtssatz\n\n"
"**Gericht:** OGH\n"
"**Entscheidungsdatum:** 15. 7. 1954\n"
"**Geschäftszahl:** 4 Ob 100/54\n"
"**Norm:** AngG § 26 Z 4\n"
"**Rechtssatz:** Ein Austrittsgrund ist unverzüglich geltend zu machen.\n",
encoding="utf-8",
)
monkeypatch.setattr(ing, "RJ_DIR", root)
slug, md = ing.rj_entry(source, _rj_catalog(), _rj_stats())
assert slug == "rj_jjr-test"
assert "id: rj-rjs-001" in md
assert 'chapter: "OGH-Rechtssatz"' in md
assert "stand: 1954-07" in md
assert 'legal_bases: ["AngG § 26 Z 4"]' in md
assert "## Rechtssatz" in md
def test_rj_volltext_splits_long_reasoning_at_paragraphs(tmp_path, monkeypatch):
root = tmp_path / ".rechtsprechung"
source = root / "originale" / "JJT_test.md"
source.parent.mkdir(parents=True)
paragraphs = [f"Absatz {i}: " + "x" * 900 for i in range(5)]
source.write_text(
"# Entscheidung\n\n"
"**Gericht:** OGH\n"
"**Entscheidungsdatum:** 1. 2. 2020\n"
"**Geschäftszahl:** 1 Ob 2/20a\n\n---\n\n"
+ "\n\n".join(paragraphs),
encoding="utf-8",
)
monkeypatch.setattr(ing, "RJ_DIR", root)
_, md = ing.rj_entry(source, _rj_catalog(), _rj_stats())
assert "## Begründung (1)" in md
assert "## Begründung (2)" in md
def test_rj_norm_supports_h2_and_flat_wording(tmp_path, monkeypatch):
root = tmp_path / ".rechtsprechung"
h2_source = root / "gesetze" / "AngG_§15.md"
flat_source = root / "gesetze" / "ABGB_§1435.md"
h2_source.parent.mkdir(parents=True)
h2_source.write_text(
"# Angestelltengesetz\n\n**Kurztitel:** Angestelltengesetz\n"
"**Abkürzung:** AngG\n**§/Artikel/Anlage:** § 15\n\n---\n\n"
"## § 15\n\nWortlaut.\n\n## Zuletzt aktualisiert am\n\n1. 7. 2023\n",
encoding="utf-8",
)
flat_source.write_text(
"# ABGB\n\n**Kurztitel:** ABGB\n**Abkürzung:** ABGB\n"
"**§/Artikel/Anlage:** § 1435\n\n---\n\nDer Wortlaut ohne H2.\n",
encoding="utf-8",
)
monkeypatch.setattr(ing, "RJ_DIR", root)
catalog = _rj_catalog()
_, h2_md = ing.rj_entry(h2_source, catalog, _rj_stats())
_, flat_md = ing.rj_entry(flat_source, catalog, _rj_stats())
assert "stand: 2023-07" in h2_md
assert 'legal_bases: ["AngG § 15"]' in h2_md
assert "## § 15 Wortlaut" in h2_md
assert "stand: 2026-09" in flat_md
assert "stand-abruf" in flat_md
assert "## Normtext" in flat_md
def test_rj_eugh_marks_lexetius_copy_as_nonofficial(tmp_path, monkeypatch):
root = tmp_path / ".rechtsprechung"
source = root / "originale" / "EUGH_C-29-1991.md"
source.parent.mkdir(parents=True)
source.write_text(
"# EuGH C-29/91\n\n"
"**URL:** https://www.lexetius.com/1992,1\n"
"**Offizielle Fassung:** https://eur-lex.europa.eu/example\n\n---\n\n"
"Urteil vom 19. 5. 1992\n\nEntscheidungsgründe.",
encoding="utf-8",
)
monkeypatch.setattr(ing, "RJ_DIR", root)
_, md = ing.rj_entry(source, _rj_catalog(), _rj_stats())
assert 'work: "EuGH Rechtsprechung (Textwiedergabe lexetius, nicht amtlich)"' in md
assert "stand: 1992-05" in md
assert "Textwiedergabe (nicht amtlich): https://www.lexetius.com/1992,1" in md
assert "amtliche Fassung: https://eur-lex.europa.eu/example" in md
+13 -4
View File
@@ -67,17 +67,17 @@ def test_real_corpus_loads_and_matches_registry():
"""Integrationstest gegen die echte Wissensbasis (Gate inklusive)."""
entries = load_kb("wissensbasis", verify_registry=True)
ids = {e.id for e in entries}
# 601 kuratierte (lb/wk) + 614 WKO-KV + 59 RIS-Gesetze
assert len(entries) == 1274
# 601 kuratierte (lb/wk) + 614 WKO-KV + 59 RIS-Gesetze + 348 Rechtsprechungsquellen
assert len(entries) == 1622
assert "lb-atz-07" in ids and "wk-akt-01" in ids
assert "kv-kvt-001" in ids and "ris-url-01" in ids
assert "kv-kvt-001" in ids and "ris-url-01" in ids and "rj-rjs-001" in ids
atz = [e for e in entries if e.id == "lb-atz-07"][0]
assert atz.topic == "altersteilzeit"
assert any(s.title.startswith("Kernwerte") for s in atz.sections)
def test_new_id_spaces_and_html_source(tmp_path):
"""kv-/ris-IDs und html-only-Quellenangabe sind gültig; Unbekanntes nicht."""
"""kv-/ris-/rj-IDs und html-/text-Quellenangaben sind gültig; Unbekanntes nicht."""
kv = DOC_ATZ.replace("id: lb-min-01", "id: kv-kvt-001").replace(
'source:\n pdf: ".lexis360/Lexis360_test_atz.pdf"\n'
' text: ".lexis360/md/test_atz.md"',
@@ -96,6 +96,15 @@ def test_new_id_spaces_and_html_source(tmp_path):
p2.write_text(ris, encoding="utf-8")
assert load_entry(p2).id == "ris-url-01"
rj = DOC_ATZ.replace("id: lb-min-01", "id: rj-rjs-001").replace(
'source:\n pdf: ".lexis360/Lexis360_test_atz.pdf"\n'
' text: ".lexis360/md/test_atz.md"',
'source:\n text: ".rechtsprechung/originale/Test.md"',
)
p3 = tmp_path / "rj_doc.md"
p3.write_text(rj, encoding="utf-8")
assert load_entry(p3).id == "rj-rjs-001"
bad = DOC_ATZ.replace("id: lb-min-01", "id: xx-min-01")
p3 = tmp_path / "bad.md"
p3.write_text(bad, encoding="utf-8")