Files
pv-agent/tests/conftest.py
T
fegger 062e010d7b Stufe 1: komplexe Fragen via Query-Planer, Per-Query-Slots, Scope-Filter (D12, M6)
- Query-Planer (agent/query_planner.py): Heuristik-Gate (Jahr, Vergleichs-/
  Aggregationsmarker, Laenge) entscheidet ueber einen kleinen LLM-Call,
  der komplexe Fragen in 1-3 Sub-Queries zerlegt (JSON, temp 0; Fehler ->
  Original als Einzel-Query). Je Sub-Query optional stand_year und scope.
- Multi-Query-Retrieval: je Sub-Query BM25+Dense mit RRF-Summe;
  Per-Query-Slots (2 je Sub-Query) sichern jeden Frageaspekt im Kontext
  (sonst dominieren Eintraege, die in mehreren Sub-Queries mittelgut
  matchen — q-113-Befund). Scope-Filter: 'gesetz' (nur Lexis/WIKU/RIS)
  und 'kv' (nur Branchen-KV) mit Fallback auf unscoped bei leerem
  Ergebnis. Temporal-Intent: stand_year + temporal_boost (default 0,
  FTS-jahr-Tag-Signal reichte).
- Grounding unveraendert: eine Retrieved-Menge (Union), eine Antwort,
  Post-Validierung ueber die Union, Verweigerungspflicht unveraendert.
- Goldset 42 -> 46: q-110 (Temporal 2023; 2024er-Lohnordnung existiert
  im Korpus nicht - Mantelvertrag ohne Lohntabelle, korrekt verweigert),
  q-111 (2025), q-112 (Abfertigung-Vergleich), q-113 (Gesetz+KV).
- Eval (46 Fragen): Zitier-Praezision 97,8 %, Verweigerung 97,8 %
  (Gate >94,3 % erfuellt), erwartete Quelle 90,2 %, Latenz mean 33,5 s.
- Tests 50 -> 57. Reports lokal: data/eval-qwen38-stage1*.json.
  API-first festgehalten (D13-Vorbereitung): Odoo bleibt duenner Client;
  Lohndaten-Zugriff in M4 erfordert Privacy-Neubewertung.
2026-09-15 09:58:25 +02:00

202 lines
4.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.
"""Fixtures: Mini-Wissensbasis (3 Einträge), Index, Fake-Ollama-Client.
Alle Tests laufen offline — kein Ollama, kein Netz. Die Mini-KB folgt dem
verbindlichen Layer-2-Schema (Frontmatter + H2-Sektionen + kb.json-Gate).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from agent.config import Config
DOC_ATZ = """---
id: lb-min-01
batch: 1
title: "Altersteilzeit - Überblick"
work: "Lexis Briefings Personalrecht"
chapter: "Beschäftigungsverhältnisse"
topic: altersteilzeit
author: "Marek"
stand: 2026-01
source:
pdf: ".lexis360/Lexis360_test_atz.pdf"
text: ".lexis360/md/test_atz.md"
legal_bases: ["AlVG", "AZG § 19e"]
tags: [altersteilzeit, lohnausgleich]
cross_refs: ["lb-min-02"]
---
# Altersteilzeit Überblick
*Lexis Briefings Personalrecht, Marek, Stand Jänner 2026 (lb-min-01).*
## Zusammenfassung
Altersteilzeit ist eine Teilzeit, bei der der Arbeitnehmer zusätzlich zum
Teilzeitentgelt einen Lohnausgleich erhält; das AMS ersetzt dem Arbeitgeber
einen Teil der Zusatzkosten (Altersteilzeitgeld).
## Kernwerte & Fristen (Stand 2026-01)
| Wert / Regel | Detail |
|---|---|
| Ersatzquote | 28,5 % der ersetzbaren Kosten (Stand 2026-01) |
## Rechtsgrundlagen
- AZG § 19e (Kündigungszuschlag)
## Verweise
- lb-min-02
"""
DOC_URL = """---
id: lb-min-02
batch: 1
title: "Urlaubsanspruch und Verbrauch"
work: "Lexis Briefings Personalrecht"
chapter: "Urlaub & Karenzierung"
topic: urlaub
author: "Marek"
stand: 2026-07
source:
pdf: ".lexis360/Lexis360_test_urlaub.pdf"
text: ".lexis360/md/test_urlaub.md"
legal_bases: ["UrlG"]
tags: [urlaub, urlaubsentgelt]
cross_refs: ["lb-min-01"]
---
# Urlaubsanspruch und Verbrauch
*Lexis Briefings Personalrecht, Marek, Stand Juli 2026 (lb-min-02).*
## Zusammenfassung
Der gesetzliche Urlaubsanspruch beträgt fünf Werktage Wochenurlaub je
Dienstjahr; das Urlaubsentgelt ist wie Entgeltfortzahlung zu berechnen.
## Kernwerte & Fristen (Stand 2026-07)
| Wert / Regel | Detail |
|---|---|
| Mindesturlaub | 5 Wochen je Dienstjahr (Stand 2026-07) |
## Verweise
- lb-min-01
"""
DOC_WIKU = """---
id: wk-min-01
batch: 1
title: "WIKU Praxisfall: Lohnverrechnung"
work: "WIKU Fachbroschüre"
chapter: "WIKU Fachbroschüre"
topic: lohnverrechnung
author: "Wilhelm Kurzböck"
stand: 2026-03
source:
pdf: ".wiku/test_lohnverrechnung.pdf"
text: ".wiku/md/test_lohnverrechnung.md"
legal_bases: ["EStG § 25"]
tags: [lohnverrechnung, praxisfall]
cross_refs: ["lb-min-01"]
---
# WIKU Praxisfall: Lohnverrechnung
*WIKU Fachbroschüre, Wilhelm Kurzböck, Stand März 2026 (wk-min-01).*
## Zusammenfassung
Praxisfall zur laufenden Lohnverrechnung: Abrechnungsperiode und
Beitragsgrundlagen sind monatlich festzulegen.
## Verweise
- lb-min-01
"""
MIN_DOCS = {
"altersteilzeit_uberblick.md": DOC_ATZ,
"urlaubsanspruch.md": DOC_URL,
"wiku_lohnverrechnung.md": DOC_WIKU,
}
MIN_IDS = ["lb-min-01", "lb-min-02", "wk-min-01"]
def write_mini_kb(root: Path) -> Path:
doc = root / "dokumente"
doc.mkdir(parents=True, exist_ok=True)
for name, content in MIN_DOCS.items():
(doc / name).write_text(content, encoding="utf-8")
kb = {
"n_entries": len(MIN_IDS),
"entries": [{"id": i, "title": i} for i in MIN_IDS],
}
(root / "kb.json").write_text(
json.dumps(kb, ensure_ascii=False, indent=1), encoding="utf-8"
)
return root
class FakeOllama:
"""Skriptbarer Chat-Client; embed() schlägt fehl (Dense bleibt ungetestet)."""
def __init__(self, answers: list[str] | None = None):
self.answers = list(answers or [])
self.calls = 0
self.last_messages: list | None = None
def chat(self, model, messages, **kwargs):
self.calls += 1
self.last_messages = messages
if not self.answers:
raise AssertionError("FakeOllama: keine skriptierte Antwort mehr")
return self.answers.pop(0)
def embed(self, model, texts):
raise RuntimeError("embed nicht verfügbar (offline Test)")
def is_up(self):
return False
def close(self):
pass
@pytest.fixture
def mini_kb(tmp_path):
return write_mini_kb(tmp_path / "kb")
@pytest.fixture
def mini_cfg(tmp_path, mini_kb) -> Config:
return Config(
kb_dir=str(mini_kb),
db_path=str(tmp_path / "index.db"),
embed_off=True,
planner_enabled=False, # Tests skripten Chat-Antworten; Planer
# separat testbar (siehe test_generate/test_query_planner)
)
@pytest.fixture
def mini_index(mini_cfg) -> Config:
from agent.ingest import build_index
stats = build_index(mini_cfg)
assert stats.n_entries == 3
assert stats.n_chunks >= 7
assert stats.embed_error is None
return mini_cfg
@pytest.fixture
def fake_ollama():
return FakeOllama