ac060c4977
- Korpus 601 -> 1274 Layer-2-Eintraege: kv-kvt-001...614 (ein Cluster kollektivvertraege, Branche als Tag) und ris-<cluster-prefix>-nn auf der bestehenden Cluster-Map + 7 neue Cluster (zvr/avr/agg/lst/abo/ nso/kvt); LAW_MAP dokumentiert die 59 Gesetz-Zuordnungen. - kv/ris-Eintraege sind quellentreu generiert (D9) - Gesetze sind amtliche Werke, KV-Lohntabellen zahlenexakt; Tool-Output in tools/ (ingest_sources.py, build_registry.py, kb_common.py), eingefrorene ID-Kataloge tools/catalogs/*.json (nur Metadaten). - kb.json/INDEX.md regeneriert (1274 Eintraege, 76 Cluster); agent/kb.py: ID-Raeume kv|ris, source akzeptiert html-only. - Tests 41 -> 49 (Konverter, LAW_MAP-Abdeckung, neue ID-Raeume, Korpus-Integrationszahl).
103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
"""Tests: Layer-2-Parsing, Sektionen, Cross-Ref-Integrität, kb.json-Gate."""
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from agent.kb import KbValidationError, load_entry, load_kb, parse_frontmatter, split_sections
|
|
from tests.conftest import DOC_ATZ, MIN_IDS, write_mini_kb
|
|
|
|
|
|
def test_parse_frontmatter_and_sections():
|
|
meta, body = parse_frontmatter(DOC_ATZ)
|
|
assert meta["id"] == "lb-min-01"
|
|
assert meta["stand"] == "2026-01"
|
|
sections = split_sections(body)
|
|
titles = [s.title for s in sections]
|
|
assert "Zusammenfassung" in titles
|
|
assert titles[1].startswith("Kernwerte & Fristen")
|
|
assert all(s.text for s in sections)
|
|
|
|
|
|
def test_split_sections_ignores_h1_and_intro():
|
|
sections = split_sections("# Titel\n\n*Quellzeile*\n\n## A\n\nText A\n\n## B\n\nText B")
|
|
assert [s.title for s in sections] == ["A", "B"]
|
|
assert sections[0].text == "Text A"
|
|
|
|
|
|
def test_load_entry_minimal_doc(tmp_path):
|
|
p = tmp_path / "doc.md"
|
|
p.write_text(DOC_ATZ, encoding="utf-8")
|
|
e = load_entry(p)
|
|
assert e.id == "lb-min-01"
|
|
assert e.tags == ["altersteilzeit", "lohnausgleich"]
|
|
assert e.cross_refs == ["lb-min-02"]
|
|
assert len(e.sections) >= 3
|
|
|
|
|
|
def test_gate_rejects_out_of_sync_registry(tmp_path):
|
|
root = write_mini_kb(tmp_path / "kb")
|
|
kb = json.loads((root / "kb.json").read_text(encoding="utf-8"))
|
|
kb["entries"].append({"id": "lb-min-99"})
|
|
(root / "kb.json").write_text(json.dumps(kb), encoding="utf-8")
|
|
with pytest.raises(KbValidationError, match="out of sync"):
|
|
load_kb(root)
|
|
|
|
|
|
def test_gate_rejects_dangling_cross_refs(tmp_path):
|
|
root = write_mini_kb(tmp_path / "kb")
|
|
doc = root / "dokumente" / "altersteilzeit_uberblick.md"
|
|
doc.write_text(
|
|
DOC_ATZ.replace('cross_refs: ["lb-min-02"]', 'cross_refs: ["lb-min-42"]'),
|
|
encoding="utf-8",
|
|
)
|
|
with pytest.raises(KbValidationError, match="dangling cross_refs"):
|
|
load_kb(root)
|
|
|
|
|
|
def test_gate_rejects_invalid_stand(tmp_path):
|
|
root = write_mini_kb(tmp_path / "kb")
|
|
doc = root / "dokumente" / "altersteilzeit_uberblick.md"
|
|
doc.write_text(DOC_ATZ.replace("stand: 2026-01", "stand: Jänner 2026"), encoding="utf-8")
|
|
with pytest.raises(KbValidationError, match="not YYYY-MM"):
|
|
load_kb(root)
|
|
|
|
|
|
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
|
|
assert "lb-atz-07" in ids and "wk-akt-01" in ids
|
|
assert "kv-kvt-001" in ids and "ris-url-01" 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 = 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"',
|
|
'source:\n html: ".firecrawl/wko-kv/docs/test.html"',
|
|
)
|
|
p = tmp_path / "kv_doc.md"
|
|
p.write_text(kv, encoding="utf-8")
|
|
assert load_entry(p).id == "kv-kvt-001"
|
|
|
|
ris = DOC_ATZ.replace("id: lb-min-01", "id: ris-url-01").replace(
|
|
'source:\n pdf: ".lexis360/Lexis360_test_atz.pdf"\n'
|
|
' text: ".lexis360/md/test_atz.md"',
|
|
'source:\n text: ".ris/Test.md"',
|
|
)
|
|
p2 = tmp_path / "ris_doc.md"
|
|
p2.write_text(ris, encoding="utf-8")
|
|
assert load_entry(p2).id == "ris-url-01"
|
|
|
|
bad = DOC_ATZ.replace("id: lb-min-01", "id: xx-min-01")
|
|
p3 = tmp_path / "bad.md"
|
|
p3.write_text(bad, encoding="utf-8")
|
|
with pytest.raises(KbValidationError, match="invalid id"):
|
|
load_entry(p3) |