Files
pv-agent/tools/kb_common.py
T
fegger eb1a768876 Prompt v2 + Kontext-Section-Prioritaet: Fehlverweigerungen behoben (D8)
Diagnose: q-008 bekam die 'Verweise'-Navigationssektion von lb-end-03 als
Kontext (BM25-Langennormalisierung bevorzugt duenne Chunks) -> inhaltlose
Bloecke -> korrekte Verweigerung nach Regel 4. q-031 enthaelt eine falsche
Praemisse (kein 'Mindestlohngesetz' in Oesterreich; Wissensbasis sagt:
kein gesetzlich betraglich festgelegtes Mindestentgelt).

Fixes: (a) retrieve.py _representative_chunk - pro Eintrag beste Inhalts-
sektion (Zusammenfassung > Kernwerte > Rechtsgrundlagen > Payroll >
sonstige > Verweise), Section-Swap aus dem Index falls nur 'Verweise'
rangiert; (b) generate.py Prompt v2 - Regel 4 erlaubt Teilantworten,
Regel 8 verlangt Prämisse-Korrektur mit Muster-Beispiel.

Bestaetigungslauf qwen3.8:27b (35 Fragen): Zitier-Praezision 100 % (8
Regenerierungen, alle geheilt), Verweigerung korrekt 94,3 %, erwartete
Quelle 83,9 % (v1 80,6 %), mean 34 s. q-008 + q-031 behoben; verbleibender
bekannter Fall q-029 (breite Survey-Frage, sicherer Fehlermodus).
Retrieval-Metriken unveraendert (Recall@8 0,952). 41 Tests gruen.
2026-09-15 00:22:41 +02:00

121 lines
5.9 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
# 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),
"GmbHH_PLACEHOLDER": ("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 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}"