Files
pv-agent/tools/kb_common.py
T
fegger ac060c4977 KV/RIS-Erweiterung: 614 WKO-KV-Dokumente + 59 RIS-Gesetze (D9)
- 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).
2026-09-15 07:44:56 +02:00

143 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Gemeinsame Konstanten für die Wissensbasis-Tools (Intake + Registry).
Quellen-ID-Räume: `lb` (Lexis Briefings), `wk` (WIKU Personal), `kv`
(WKO.at Kollektivvertrag-Seiten), `ris` (RIS-Gesetze). Die bestehende
Cluster-Map (topic -> ID-Präfix) der lb/wk-Einträge wird von
`build_registry.py` aus den Layer-2-Frontmatterdaten abgeleitet; nur die
Cluster der neuen Quellen sind hier deklariert.
LAW_MAP begründet je Gesetz topic/chapter aus dem tatsächlichen Inhalt
der .ris-Dateien (Stand 2026-09-15, vgl. §-Inventar der Quelldateien):
Gesetze ohne natürlichen Cluster bekommen einen eigenen Rechtsgebiet-
Cluster oder landen bewusst im Sammel-Cluster `normen-sonstige`.
"""
from __future__ import annotations
import re
from pathlib import Path
# Alle ID-Räume des Korpus (Schema-Beschreibung für kb.json).
ID_SPACES = {
"lb": "Lexis Briefings Personalrecht (Lexis 360-Export)",
"wk": "WIKU Personal (Fachbroschüren, Arbeitsunterlagen, Casebook, Personal aktuell)",
"kv": "WKO.at — Kollektivvertrag-Dokumente (Lohn-/Gehaltsordnungen, KV-Texte)",
"ris": "RIS — Rechtsinformationssystem des Bundes (Gesetzes-§-Auschnitte)",
}
# Neue Cluster der Quellen kv/ris: slug -> (ID-Präfix, Anzeigename).
NEW_CLUSTERS = {
"kollektivvertraege": ("kvt", "Kollektivverträge (WKO.at)"),
"zivilrecht-normen": ("zvr", "Zivilrechtliche Normen (ABGB, ZPO & Co.)"),
"arbeitsvertragsrecht": ("avr", "Arbeitsvertragsrecht (AVRAG)"),
"arbeitsgerichtsbarkeit": ("agg", "Arbeits- und Sozialgerichtsbarkeit (ASGG)"),
"lohnsteuer": ("lst", "Lohnsteuer & Einkommensteuer (EStG)"),
"abgabenverfahren": ("abo", "Abgabenverfahren (BAO)"),
"normen-sonstige": ("nso", "Weitere Gesetze und Verordnungen"),
}
# Werk-Namen der neuen Quellen (Frontmatter `work`).
WORK_KV = "WKO.at Kollektivvertrag"
WORK_RIS = "RIS Rechtsinformationssystem des Bundes"
# .ris/<Datei ohne .md> -> (topic, chapter, lfd. Nummer im Cluster).
# Nummern sind fix (dann eingefroren); neue Gesetze hängen in ihrem Cluster
# an der höchsten Nummer an.
LAW_MAP: dict[str, tuple[str, str, int]] = {
"ABGB": ("zivilrecht-normen", "Zivilrecht", 1),
"IPRG": ("zivilrecht-normen", "Zivilrecht", 2),
"KSchG": ("zivilrecht-normen", "Verbraucherschutz", 3),
"ZPO": ("zivilrecht-normen", "Zivilrecht", 4),
"APG": ("pension", "Pensionsrecht", 1),
"APSG": ("arbeitszeitmodelle", "Arbeitsrecht (Kurzarbeit)", 1),
"APflG": ("lehrlinge", "Arbeitsrecht (Lehrverhältnis)", 1),
"BAG": ("lehrlinge", "Arbeitsrecht (Lehrverhältnis)", 2),
"ARG": ("ruhezeiten", "Arbeitsrecht", 1),
"ASGG": ("arbeitsgerichtsbarkeit", "Arbeits- und Sozialgerichtsbarkeit", 1),
"ASVG": ("beitragsrecht-asvg", "Sozialversicherungsrecht", 1),
"ASchG": ("arbeitnehmerschutz", "Arbeitnehmerschutz", 1),
"BS-V": ("arbeitnehmerschutz", "Arbeitnehmerschutz", 2),
"BauV": ("arbeitnehmerschutz", "Arbeitnehmerschutz", 3),
"AVRAG": ("arbeitsvertragsrecht", "Arbeitsrecht", 1),
"AZG": ("arbeitszeitgrenzen", "Arbeitsrecht", 1),
"AktG": ("vorstand", "Gesellschaftsrecht", 1),
"AlVG": ("altersteilzeit", "Sozialversicherungsrecht", 1),
"AngG": ("beendigungsarten", "Arbeitsrecht", 1),
"LAG": ("beendigungsarten", "Arbeitsrecht", 2),
"ArbIG": ("normen-sonstige", "Arbeitsrecht", 1),
"RStDG": ("normen-sonstige", "Dienstrecht", 2),
"StGB": ("normen-sonstige", "Strafrecht", 3),
"StPO": ("normen-sonstige", "Strafrecht", 4),
"UGB": ("normen-sonstige", "Gesellschaftsrecht", 5),
"ArbVG": ("betriebsrat", "Arbeitsverfassungsrecht", 1),
"AÜG": ("arbeitskrafteuberlassung", "Arbeitsrecht", 1),
"BAO": ("abgabenverfahren", "Steuerrecht", 1),
"BBG": ("behinderte", "Sozialrecht", 1),
"BEinstG": ("behinderte", "Sozialrecht", 2),
"BMSVG": ("vorsorgeleistungen", "Betriebliche Vorsorge", 1),
"PKG": ("vorsorgeleistungen", "Betriebliche Vorsorge", 2),
"FlexKapGG": ("vorsorgeleistungen", "Gesellschaftsrecht", 3),
"BSVG": ("bauerliche-sozialversicherung", "Sozialversicherungsrecht", 1),
"BUAG": ("endabrechnung", "Arbeitsrecht", 1),
"DHG": ("dienstnehmerhaftung", "Arbeitsrecht", 1),
"EFZG": ("krankenstand", "Arbeitsrecht", 1),
"EO": ("lohnpfandung", "Exekutionsrecht", 1),
"EStG": ("lohnsteuer", "Steuerrecht", 1),
"FLAG": ("kinderbetreuungsgeld", "Familienleistungen", 1),
"KBGG": ("kinderbetreuungsgeld", "Familienleistungen", 2),
"FamZeitbG": ("familienzeit", "Familienleistungen", 1),
"GSVG": ("gsvg-fsvg", "Sozialversicherungsrecht", 1),
"GewO": ("gewerbe", "Gewerberecht", 1),
"GlBG": ("gleichbehandlung", "Arbeitsrecht", 1),
"GmbHG": ("geschaftsfuhrer", "Gesellschaftsrecht", 1),
"IESG": ("insolvenz-betriebsubergang", "Insolvenzrecht", 1),
"KJBG": ("jugendliche", "Jugendschutz", 1),
"LSD-BG": ("lohndumping", "Arbeitsrecht", 1),
"MSchG": ("schwangerschaft", "Mutterschutz", 1),
"MSchV": ("schwangerschaft", "Mutterschutz", 2),
"NSchG": ("nachtschwerarbeit", "Arbeitsrecht", 1),
"Sachbezugswerteverordnung": ("sachbezuge", "Steuerrecht", 1),
"Schwerarbeitsverordnung": ("schwerarbeit", "Arbeitsrecht", 1),
"UrlG": ("urlaub", "Arbeitsrecht", 1),
"TAG": ("urlaub", "Arbeitsrecht", 2),
"VBG": ("urlaub", "Dienstrecht", 3),
"VKG": ("karenz", "Familienrecht", 1),
"ZDG": ("prasenzdienst", "Zivildienst", 1),
}
DATE_RE = re.compile(r"(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})")
YEAR_RE = re.compile(r"(20\d\d)")
def load_topic_prefix_map(kb_json_path) -> dict[str, str]:
"""topic -> ID-Präfix über ALLE Cluster: aus den bestehenden Layer-2-
Einträgen (kb.json) abgeleitet, ergänzt um die neuen Cluster."""
import json
kb = json.loads(Path(kb_json_path).read_text(encoding="utf-8"))
mapping: dict[str, str] = {}
for e in kb["entries"]:
prefix = e["id"].split("-")[1]
known = mapping.get(e["topic"])
if known is not None and known != prefix:
raise SystemExit(
f"inkonsistente Cluster-Map: topic '{e['topic']}' hat Präfixe "
f"{known} und {prefix}"
)
mapping[e["topic"]] = prefix
for slug, (prefix, _name) in NEW_CLUSTERS.items():
mapping[slug] = prefix
return mapping
def ascii_slug(text: str) -> str:
"""Wissensbasis-Konvention: Umlaute auf Basisbuchstabe (ü→u, nicht ue),
ß→ss, Rest klein; alles außer a-z0-9 und Bindestrich fällt weg."""
repl = {"ä": "a", "ö": "o", "ü": "u", "Ä": "a", "Ö": "o", "Ü": "u", "ß": "ss"}
for k, v in repl.items():
text = text.replace(k, v)
return re.sub(r"[^a-z0-9-]+", "-", text.lower()).strip("-")
def stand_from_date(date_match: tuple[str, str, str]) -> str:
d, m, y = date_match
return f"{y}-{int(m):02d}"