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.
This commit is contained in:
+18
-5
@@ -58,11 +58,13 @@ zu planen).
|
|||||||
|
|
||||||
- **Push**: Remote localhost:3003 aus der Sandbox nicht erreichbar — User
|
- **Push**: Remote localhost:3003 aus der Sandbox nicht erreichbar — User
|
||||||
pusht vom Host.
|
pusht vom Host.
|
||||||
- **Fehlverweigerungen**: q-008 (Abfertigung/Verfügungsmöglichkeiten),
|
- **Fehlverweigerungen — Stand nach Prompt v2:** q-008 und q-031 sind
|
||||||
q-031 (Mindestlohngesetz) — breite Fragen, Retrieval erfolgreich, Modell
|
**behoben** (Section-Priorität + Regel 8). Verbleibend: q-029
|
||||||
verweigert. M3-Tuning: Regel-4-Formulierung lockern („behandle den
|
(„Welche Neuerungen behandelt WIKU Personal aktuell 2026?“) — breite
|
||||||
behandelten Teil“) oder Kontextblöcke erhöhen. Vorher Prompt einfrieren
|
Survey-Frage über 12 Hefte; Kontext enthält echte Zusammenfassungen,
|
||||||
für den Bake-off-Vergleich.
|
Modell verweigert trotzdem (sicherer Fehlermodus). Mögliche spätere
|
||||||
|
Hebel: Survey-Rule im Prompt, mehr Kontextblöcke oder Map-Reduce-artige
|
||||||
|
Zusammenfassung. Weitere Iterationen erst mit neuen Evaluationsdaten.
|
||||||
- **Latenz**: mean 32 s/ Antwort ist hoch (Dense 27B + große Prompts).
|
- **Latenz**: mean 32 s/ Antwort ist hoch (Dense 27B + große Prompts).
|
||||||
Hebel: weniger Kontextblöcke (aktuell 8+6), schnellere Kandidaten.
|
Hebel: weniger Kontextblöcke (aktuell 8+6), schnellere Kandidaten.
|
||||||
- **Cloud-Modelle** (`*:cloud` auf der Ollama-Instanz) sind für Antworten
|
- **Cloud-Modelle** (`*:cloud` auf der Ollama-Instanz) sind für Antworten
|
||||||
@@ -92,6 +94,17 @@ zu planen).
|
|||||||
(100 % Zitier-Präzision, 94,3 % Verweigerung korrekt). `gemma4:26b` als
|
(100 % Zitier-Präzision, 94,3 % Verweigerung korrekt). `gemma4:26b` als
|
||||||
dokumentierter Latenz-Kandidat; ein Modellwechsel läuft nur erneut über
|
dokumentierter Latenz-Kandidat; ein Modellwechsel läuft nur erneut über
|
||||||
das dokumentierte Protokoll (Skill).
|
das dokumentierte Protokoll (Skill).
|
||||||
|
- **D8 (Prompt-Tuning 2026-09-14):** Prompt v2 + Kontext-Section-
|
||||||
|
Priorität. (a) Regel 4 erlaubt Teilantworten bei unvollständiger Deckung;
|
||||||
|
Regel 8 verlangt Prämisse-Korrektur statt Verweigerung (Muster-Beispiel
|
||||||
|
„Mindestlohngesetz“). (b) `_representative_chunk` wählt pro Eintrag die
|
||||||
|
beste **Inhaltssektion** (Zusammenfassung > Kernwerte > Rechtsgrundlagen
|
||||||
|
> Payroll > sonstige; „Verweise“ nur als letzter Rückgriff — BM25
|
||||||
|
rangiert die dünnen Navigations-Chunks bevorzugt, q-008-Ursache).
|
||||||
|
Ergebnis v1→v2: Zitier-Präzision 100 % gehalten; q-008 + q-031 behoben;
|
||||||
|
erwartete Quelle 80,6 → 83,9 %; Verweigerung 94,3 % (Fehler getauscht:
|
||||||
|
neu q-029 — breite Survey-Frage über 12 Hefte, sicherer Fehlermodus).
|
||||||
|
Report `data/eval-qwen38-v2.json`.
|
||||||
- **Bake-off-Protokoll** (Skill): Modellwechsel nur über dokumentierten
|
- **Bake-off-Protokoll** (Skill): Modellwechsel nur über dokumentierten
|
||||||
Goldset-Vergleich; Kriterium: Zitier-Präzision > Verweigerungs-
|
Goldset-Vergleich; Kriterium: Zitier-Präzision > Verweigerungs-
|
||||||
korrektheit > Latenz.
|
korrektheit > Latenz.
|
||||||
|
|||||||
+9
-3
@@ -79,9 +79,15 @@ MRR 0,690 — M1-Ziel >0,9 erreicht (BM25-only war 0,855; die vier
|
|||||||
BM25-Fehltreffer behebt die Dense-Suche alle).
|
BM25-Fehltreffer behebt die Dense-Suche alle).
|
||||||
|
|
||||||
**Antworten** (Bake-off-Sieger qwen3.8:27b, Thinking aus, Temperatur 0,1):
|
**Antworten** (Bake-off-Sieger qwen3.8:27b, Thinking aus, Temperatur 0,1):
|
||||||
**Zitier-Präzision 100 %** (4 Zitierverletzungen wurden von der
|
**Zitier-Präzision 100 %** · Verweigerung korrekt 94,3 % · erwartete Quelle
|
||||||
Post-Validierung abgefangen und regeneriert) · Verweigerung korrekt
|
zitiert **83,9 %** · Latenz mean 34 s / p95 54 s.
|
||||||
94,3 % · erwartete Quelle zitiert 80,6 % · Latenz mean 32 s / p95 53 s.
|
|
||||||
|
**Prompt v2 + Kontext-Section-Priorität (2026-09-14):** Teilantworten bei
|
||||||
|
unvollständiger Deckung erlaubt (Regel 4), Prämisse-Korrektur statt
|
||||||
|
Verweigerung (Regel 8), pro Eintrag beste Inhaltssektion als Kontextblock
|
||||||
|
(Zusammenfassung > Kernwerte > … > Verweise zuletzt — Navigations-Chunks
|
||||||
|
lösen keine Fehlverweigerungen mehr aus). v1→v2: q-008 + q-031 behoben,
|
||||||
|
erwartete Quelle 80,6 % → 83,9 %, Zitier-Präzision unverändert 100 %.
|
||||||
|
|
||||||
**Modell-Bake-off (M3, 2026-09-14)** — Entscheidung: **qwen3.8:27b**
|
**Modell-Bake-off (M3, 2026-09-14)** — Entscheidung: **qwen3.8:27b**
|
||||||
(Protokoll: Zitier-Präzision → Verweigerungskorrektheit → Latenz):
|
(Protokoll: Zitier-Präzision → Verweigerungskorrektheit → Latenz):
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ questions:
|
|||||||
- id: q-031
|
- id: q-031
|
||||||
question: "Was regelt das Mindestlohngesetz und für wen gilt es?"
|
question: "Was regelt das Mindestlohngesetz und für wen gilt es?"
|
||||||
expected_ids: [lb-ent-09]
|
expected_ids: [lb-ent-09]
|
||||||
|
note: "Falsche Prämisse — ideale Antwort korrigiert: kein gesetzlich
|
||||||
|
betraglich festgelegtes Mindestentgelt; Untergrenze via KV/Satzung/
|
||||||
|
Mindestlohntarif (BEA) belegt aus lb-ent-09."
|
||||||
|
tags: [premise-correction]
|
||||||
|
|
||||||
# --- Verweigerungsfälle (Antwortmodus) ---
|
# --- Verweigerungsfälle (Antwortmodus) ---
|
||||||
- id: r-001
|
- id: r-001
|
||||||
|
|||||||
+10
-1
@@ -33,12 +33,21 @@ Verbindliche Regeln:
|
|||||||
IDs, die nur im Fließtext als Verweis genannt werden, sind Querverweise
|
IDs, die nur im Fließtext als Verweis genannt werden, sind Querverweise
|
||||||
und KEINE Belege.
|
und KEINE Belege.
|
||||||
3. Gib jeden Wert mit seinem Stand an, z. B. „28,5 % (Stand 2026-01)“.
|
3. Gib jeden Wert mit seinem Stand an, z. B. „28,5 % (Stand 2026-01)“.
|
||||||
4. Beantworten die Kontextblöcke die Frage nicht, antworte exakt:
|
4. Beantworte die Frage mit den fachlichen Aussagen der thematisch
|
||||||
|
relevanten Blöcke — auch wenn sie die Frage nur teilweise decken;
|
||||||
|
mache klar, welcher Aspekt belegt ist. Verweigere nur, wenn KEIN
|
||||||
|
Block thematisch zur Frage passt, mit exakt:
|
||||||
„Dazu enthält die Wissensbasis keine Aussage.“ — und schlage nichts vor.
|
„Dazu enthält die Wissensbasis keine Aussage.“ — und schlage nichts vor.
|
||||||
5. Widersprechen sich Kontextblöcke, nenne beide Werte mit ihren IDs und
|
5. Widersprechen sich Kontextblöcke, nenne beide Werte mit ihren IDs und
|
||||||
kennzeichne den Widerspruch mit ⚠. Löse Widersprüche niemals stillschweigend auf.
|
kennzeichne den Widerspruch mit ⚠. Löse Widersprüche niemals stillschweigend auf.
|
||||||
6. Nenne Paragraphen und Gesetze nur, wenn ein Kontextblock sie nennt.
|
6. Nenne Paragraphen und Gesetze nur, wenn ein Kontextblock sie nennt.
|
||||||
7. Antworte auf Deutsch und prägnant (Stichpunkte, wo sinnvoll).
|
7. Antworte auf Deutsch und prägnant (Stichpunkte, wo sinnvoll).
|
||||||
|
8. Baut die Frage auf einer falschen Annahme auf (z. B. ein nicht
|
||||||
|
existierendes Gesetz), korrigiere die Annahme anhand der Blöcke und
|
||||||
|
gib die zutreffende, belegte Aussage. Muster: Auf „Was regelt das
|
||||||
|
Mindestlohngesetz?“ antworte sinngemäß „Ein Mindestlohngesetz existiert
|
||||||
|
laut Kontext nicht; stattdätzlich gilt …“ — mit Beleg [ID].
|
||||||
|
Verweigere in diesem Fall nicht.
|
||||||
|
|
||||||
Verletze Regel 2 oder Regel 4 niemals — im Zweifel verweigere die Antwort."""
|
Verletze Regel 2 oder Regel 4 niemals — im Zweifel verweigere die Antwort."""
|
||||||
|
|
||||||
|
|||||||
+53
-5
@@ -34,6 +34,29 @@ class ChunkResult:
|
|||||||
source: str = "fused" # bm25 | dense | fused | cross_ref
|
source: str = "fused" # bm25 | dense | fused | cross_ref
|
||||||
|
|
||||||
|
|
||||||
|
def _section_priority(section: str) -> int:
|
||||||
|
"""Kontext-Sektionen priorisieren: Inhalt vor Navigation.
|
||||||
|
|
||||||
|
„Verweise“-Sektionen sind Navigationslisten (KB-IDs) — sie tragen
|
||||||
|
Retrieval-Signal (Stichworte), sind aber als Kontextblock wertlos und
|
||||||
|
provozieren Fehlverweigerungen. BM25-Längennormalisierung rangiert sie
|
||||||
|
bevorzugt, daher wird pro Eintrag bewusst die beste Inhaltssektion
|
||||||
|
gewählt (Fix 2026-09-14, q-008).
|
||||||
|
"""
|
||||||
|
s = (section or "").casefold()
|
||||||
|
if s.startswith("zusammenfassung"):
|
||||||
|
return 0
|
||||||
|
if s.startswith("kernwerte"):
|
||||||
|
return 1
|
||||||
|
if s.startswith("rechtsgrundlagen"):
|
||||||
|
return 2
|
||||||
|
if s.startswith("payroll"):
|
||||||
|
return 3
|
||||||
|
if s.startswith("verweise"):
|
||||||
|
return 5
|
||||||
|
return 4
|
||||||
|
|
||||||
|
|
||||||
class Retriever:
|
class Retriever:
|
||||||
def __init__(self, cfg: Config, db_path: str | None = None, client=None):
|
def __init__(self, cfg: Config, db_path: str | None = None, client=None):
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
@@ -182,6 +205,28 @@ class Retriever:
|
|||||||
source=source,
|
source=source,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _representative_chunk(
|
||||||
|
self, entry_id: str, ranked: list[ChunkResult]
|
||||||
|
) -> ChunkResult:
|
||||||
|
"""Beste Inhaltssektion des Eintrags als Kontextblock.
|
||||||
|
|
||||||
|
Bevorzugt die rangierte (gefundene) Sektion mit bester Priorität;
|
||||||
|
traf der Eintrag nur über „Verweise“, wird seine beste Inhalts-
|
||||||
|
sektion aus dem Index nachgeladen (source="section-swap").
|
||||||
|
"""
|
||||||
|
content = [c for c in ranked if _section_priority(c.section) < 5]
|
||||||
|
if content:
|
||||||
|
return min(content, key=lambda c: (_section_priority(c.section), -c.score))
|
||||||
|
rows = self._con.execute(
|
||||||
|
"SELECT * FROM chunks WHERE entry_id = ? AND section NOT LIKE 'Verweise%' "
|
||||||
|
"ORDER BY CASE WHEN section LIKE 'Zusammenfassung%' THEN 0 "
|
||||||
|
"WHEN section LIKE 'Kernwerte%' THEN 1 ELSE 2 END, chunk_id LIMIT 1",
|
||||||
|
(entry_id,),
|
||||||
|
).fetchall()
|
||||||
|
if rows:
|
||||||
|
return self._row_to_result(rows[0], 0.0, "section-swap")
|
||||||
|
return ranked[0] # Eintrag hat nur Verweise-Sektionen
|
||||||
|
|
||||||
def _best_chunk_of_entry(self, entry_id: str) -> ChunkResult | None:
|
def _best_chunk_of_entry(self, entry_id: str) -> ChunkResult | None:
|
||||||
rows = self._con.execute(
|
rows = self._con.execute(
|
||||||
"SELECT * FROM chunks WHERE entry_id = ? "
|
"SELECT * FROM chunks WHERE entry_id = ? "
|
||||||
@@ -224,16 +269,19 @@ class Retriever:
|
|||||||
results.append(self._row_to_result(rows[cid], score, source))
|
results.append(self._row_to_result(rows[cid], score, source))
|
||||||
results.sort(key=lambda r: -r.score)
|
results.sort(key=lambda r: -r.score)
|
||||||
|
|
||||||
# Bester Chunk je Eintrag -> Kontext (Entry-Level-Dedup)
|
# Bester Chunk je Eintrag -> Kontext (Entry-Level-Dedup).
|
||||||
|
# Der Vertreter-Chunk ist die beste Inhaltssektion des Eintrags,
|
||||||
|
# nicht die Rangfolge-Beste (vgl. _section_priority).
|
||||||
main: list[ChunkResult] = []
|
main: list[ChunkResult] = []
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
|
by_entry: dict[str, list[ChunkResult]] = {}
|
||||||
for r in results:
|
for r in results:
|
||||||
if r.entry_id in seen:
|
by_entry.setdefault(r.entry_id, []).append(r)
|
||||||
continue
|
for entry_id, chunks in by_entry.items():
|
||||||
seen.add(r.entry_id)
|
|
||||||
main.append(r)
|
|
||||||
if len(main) >= n:
|
if len(main) >= n:
|
||||||
break
|
break
|
||||||
|
seen.add(entry_id)
|
||||||
|
main.append(self._representative_chunk(entry_id, chunks))
|
||||||
|
|
||||||
# cross_ref-Erweiterung (kontrolliert, markiert, begrenzt)
|
# cross_ref-Erweiterung (kontrolliert, markiert, begrenzt)
|
||||||
extra: list[ChunkResult] = []
|
extra: list[ChunkResult] = []
|
||||||
|
|||||||
+10
@@ -271,6 +271,16 @@ tests/ # pytest: Ingest-, Retrieval-, Grounding-Unit-Tests
|
|||||||
vorhandener Zitate) und 37,5 s mean — Rang 5 von 6, schlägt qwen3.8
|
vorhandener Zitate) und 37,5 s mean — Rang 5 von 6, schlägt qwen3.8
|
||||||
in keiner Kennzahl. q-008 und q-031 verweigern alle Top-Kandidaten —
|
in keiner Kennzahl. q-008 und q-031 verweigern alle Top-Kandidaten —
|
||||||
Prompt-/Retrieval-Tuning-Thema, kein Modellthema.
|
Prompt-/Retrieval-Tuning-Thema, kein Modellthema.
|
||||||
|
|
||||||
|
**Prompt v2 + Kontext-Tuning (2026-09-14):** (a) Regel 4 erlaubt
|
||||||
|
Teilantworten, Regel 8 verlangt Prämisse-Korrektur mit Muster-Beispiel;
|
||||||
|
(b) Kontextblöcke pro Eintrag = beste Inhaltssektion statt bester
|
||||||
|
Rangfolge-Chunk („Verweise“-Sektionen zuletzt — BM25-Längennormalisierung
|
||||||
|
rangiert die dünnen Navigations-Chips bevorzugt, Ursache q-008).
|
||||||
|
Bestätigungslauf qwen3.8:27b (35 Fragen): Zitier-Präzision **100 %**,
|
||||||
|
Verweigerung korrekt 94,3 % (q-008/q-031 behoben; neuer bekannter Fall
|
||||||
|
q-029 — breite Survey-Frage, sicherer Fehlermodus), erwartete Quelle
|
||||||
|
**83,9 %** (v1: 80,6 %), mean 34 s. Report `data/eval-qwen38-v2.json`.
|
||||||
- **M4 offen:** Odoo-Integration (separater Plan nach Verifikation der
|
- **M4 offen:** Odoo-Integration (separater Plan nach Verifikation der
|
||||||
Odoo-19-LLM-Module).
|
Odoo-19-LLM-Module).
|
||||||
- Betrieb: `agent/README.md`.
|
- Betrieb: `agent/README.md`.
|
||||||
|
|||||||
+2
-1
@@ -3,4 +3,5 @@ uvicorn>=0.30
|
|||||||
httpx>=0.27
|
httpx>=0.27
|
||||||
PyYAML>=6.0
|
PyYAML>=6.0
|
||||||
numpy>=2.0
|
numpy>=2.0
|
||||||
pytest>=8.0
|
pytest>=8.0
|
||||||
|
beautifulsoup4>=4.12
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"""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}"
|
||||||
Reference in New Issue
Block a user