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.
91 lines
2.4 KiB
Python
91 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from artenschutz_router.models import (
|
|
ExemplarPayload,
|
|
FactPayload,
|
|
FactScope,
|
|
IngestRequest,
|
|
SearchRequest,
|
|
SourceInfo,
|
|
)
|
|
|
|
|
|
def test_fact_payload_minimal():
|
|
p = FactPayload(
|
|
fact_type="legal",
|
|
source=SourceInfo(type="authoritative", doc="BNatSchG"),
|
|
)
|
|
assert p.kind == "fact"
|
|
assert p.scope.states == []
|
|
assert p.valid_until is None
|
|
|
|
|
|
def test_fact_payload_rich():
|
|
p = FactPayload(
|
|
fact_type="mitigation",
|
|
source=SourceInfo(type="extracted", doc="GS_GA_BER_Foo_FachGA", year=2024, tier="A"),
|
|
scope=FactScope(
|
|
states=["BE"],
|
|
anlagen_typ=["Neubau"],
|
|
species=["passer_domesticus"],
|
|
impact_types=["bauzeit"],
|
|
),
|
|
valid_from=date(2023, 4, 1),
|
|
accepted_by_behoerde=True,
|
|
citation_anchor="§44(1) Nr. 1",
|
|
)
|
|
dumped = p.model_dump(mode="json")
|
|
assert dumped["valid_from"] == "2023-04-01"
|
|
assert dumped["accepted_by_behoerde"] is True
|
|
|
|
|
|
def test_fact_payload_rejects_unknown_fact_type():
|
|
with pytest.raises(ValidationError):
|
|
FactPayload(fact_type="bogus", source=SourceInfo(type="authoritative", doc="x"))
|
|
|
|
|
|
def test_exemplar_payload_minimal():
|
|
p = ExemplarPayload(
|
|
project_slug="GS_GA_BER_Covivio_Hasenheide_94_FachGA",
|
|
report_type="FachGA",
|
|
tier="A",
|
|
)
|
|
assert p.kind == "exemplar"
|
|
assert p.is_draft is False
|
|
assert p.species_mentioned == []
|
|
|
|
|
|
def test_ingest_request_discriminates_payload():
|
|
fact_req = IngestRequest(
|
|
content="§44 (1) BNatSchG …",
|
|
payload={
|
|
"kind": "fact",
|
|
"fact_type": "legal",
|
|
"source": {"type": "authoritative", "doc": "BNatSchG"},
|
|
},
|
|
)
|
|
assert isinstance(fact_req.payload, FactPayload)
|
|
|
|
exemplar_req = IngestRequest(
|
|
content="Im Untersuchungsraum wurden …",
|
|
payload={
|
|
"kind": "exemplar",
|
|
"project_slug": "GS_GA_BER_Foo",
|
|
"report_type": "FachGA",
|
|
"tier": "A",
|
|
},
|
|
)
|
|
assert isinstance(exemplar_req.payload, ExemplarPayload)
|
|
|
|
|
|
def test_search_request_defaults():
|
|
s = SearchRequest()
|
|
assert s.query is None
|
|
assert s.limit == 10
|
|
assert s.prefetch_limit == 50
|
|
assert s.metadata_filter is None
|