artenschutz-router/tests/test_bnatschg_loader.py
m2 (AI Agent) 6da874b2e6 Initial commit — phase 1 + 2 of artenschutz-digest concept
Phase 1: router skeleton + Qdrant read path
- FastAPI app with /health, /search/generic, /ingest endpoints
- Qdrant reader (hybrid dense+sparse w/ RRF, plus payload scroll)
- BGE-M3 TEI client for query-side embedding
- memory-api client proxying writes to /memory/store (m2-memory untouched)
- Pydantic Fact + Exemplar payloads, discriminated by `kind`
- Dockerfile + docker-compose joining the coolify Docker network

Phase 2: authoritative-source ingest pipeline
- PDF text extraction + paragraph-aware (§ N) and size chunkers
- Loaders for bnatschg, mhbasp, biotopwertliste, state-<slug>
- CLI: artenschutz-ingest --source <name> [--states ...] [--dry-run]
- Helper script to fetch BNatSchG from gesetze-im-internet.de
- End-to-end smoke script (scripts/smoke.sh) for Coolify validation

22/22 unit tests pass. Real-world dry-runs verified against mhbasp Anhang 4,
biotopwertlisteNEU and Berlin Kartierstandards PDFs from GST-DATA.

See COOLIFY-DEPLOY.md for staging deploy + smoke procedure.
2026-05-14 16:36:00 +02:00

58 lines
2 KiB
Python

from __future__ import annotations
from datetime import date
from pathlib import Path
import pytest
from artenschutz_router.ingest.authoritative import bnatschg as loader
SAMPLE_BNATSCHG = """§ 44 Vorschriften für besonders geschützte und bestimmte andere Tier- und Pflanzenarten
(1) Es ist verboten,
1. wild lebenden Tieren der besonders geschützten Arten nachzustellen,
sie zu fangen, zu verletzen oder zu töten oder ihre Entwicklungsformen aus
der Natur zu entnehmen, zu beschädigen oder zu zerstören.
(5) Für nach § 15 Absatz 1 unvermeidbare Beeinträchtigungen …
§ 45b Schutz wild lebender Vögel
(1) Beim Betrieb von Windenergieanlagen sind die in Anlage 1 …
"""
def test_loads_records_from_synthetic_text(tmp_path: Path):
path = tmp_path / "bnatschg.txt"
path.write_text(SAMPLE_BNATSCHG, encoding="utf-8")
records = list(loader.load(path, valid_from=date(2024, 1, 1)))
assert len(records) == 2
anchors = [r.payload.citation_anchor for r in records]
assert any(a and a.startswith("§ 44") for a in anchors)
assert any(a and a.startswith("§ 45b") for a in anchors)
for r in records:
assert r.payload.fact_type == "legal"
assert r.payload.source.doc == "BNatSchG"
assert r.payload.source.type == "authoritative"
assert r.payload.scope.states == []
assert r.payload.valid_from == date(2024, 1, 1)
assert r.importance > 0
def test_missing_file_raises(tmp_path: Path):
path = tmp_path / "nope.txt"
with pytest.raises(FileNotFoundError):
list(loader.load(path))
def test_request_body_shape(tmp_path: Path):
path = tmp_path / "bnatschg.txt"
path.write_text(SAMPLE_BNATSCHG, encoding="utf-8")
record = next(loader.load(path))
body = record.to_request_body()
assert body["content"]
assert body["payload"]["kind"] == "fact"
assert body["payload"]["fact_type"] == "legal"
assert "agent_id" not in body # left to the router unless overridden