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.
58 lines
2 KiB
Python
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
|