landvex: IOL v3 + Identify API + Neo4j-graf + byggpipeline

- FastAPI Identify API (port 8081)
- Neo4j propertygraf (64 noder, 80 kanter)
- Ingest-pipeline (Python)
- Byggscript + integrationstester
- Docker Compose setup
This commit is contained in:
Bernt
2026-07-05 05:55:48 +00:00
parent 8dcca96185
commit 7d40cf95bf
61 changed files with 22330 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Exportera master-data till CSV för Neo4j-import.
"""
import json
import csv
from pathlib import Path
DATA = Path(__file__).parent.parent / "10-data"
DB = Path(__file__).parent.parent / "db"
def main():
poster = {}
for fn in ("lvx-klassposter-master-v3.json", "lvx-produktmodeller-vag2.json",
"lvx-produktmodeller-vag3.json", "lvx-produktmodeller-auto.json"):
fp = DATA / fn
if not fp.exists(): continue
with open(fp, encoding="utf-8") as f:
for p in json.load(f).get("poster", []):
poster[p["lvx_id"]] = p
# Noder
with open(DB / "noder.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["lvx_id","posttyp","namn_sv","namn_en","doman","tier","verifieringsniva","konfidens"])
w.writeheader()
for p in poster.values():
prov = p.get("proveniens", {})
w.writerow({
"lvx_id": p["lvx_id"],
"posttyp": p.get("posttyp", ""),
"namn_sv": p.get("namn", {}).get("sv", ""),
"namn_en": p.get("namn", {}).get("en", ""),
"doman": p.get("doman", ""),
"tier": p.get("tier", ""),
"verifieringsniva": prov.get("verifieringsniva", "obekraftad"),
"konfidens": prov.get("konfidens", 0),
})
# Kanter
with open(DATA / "lvx-graf-kanter-v3.json", encoding="utf-8") as f:
kanter = json.load(f)["kanter"]
with open(DB / "kanter.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["subjekt","predikat","objekt","niva","beskrivning"])
w.writeheader()
for k in kanter:
if k["objekt_typ"] != "lvx":
continue # hoppa över textnoder i grafen
w.writerow({
"subjekt": k["subjekt"],
"predikat": k["predikat"],
"objekt": k["objekt"],
"niva": k.get("niva", ""),
"beskrivning": k.get("beskrivning", ""),
})
print(f"Exporterat: {len(poster)} noder, {len(kanter)} kanter")
print(f"{DB}/noder.csv")
print(f"{DB}/kanter.csv")
if __name__ == "__main__":
main()
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Integrationstester för Landvex Identify API.
"""
import requests
import sys
BASE = "http://localhost:8081"
def test_health():
r = requests.get(f"{BASE}/health")
assert r.status_code == 200
d = r.json()
assert d["status"] == "ok"
assert d["poster"] > 0
print(f"✅ Health: {d['poster']} poster, {d['kanter']} kanter")
def test_search():
r = requests.get(f"{BASE}/v0/search", params={"q": "belysning"})
assert r.status_code == 200
d = r.json()
assert d["antal"] > 0
print(f"✅ Search: {d['antal']} träffar för 'belysning'")
def test_get_object():
r = requests.get(f"{BASE}/v0/objects/LVX-TRP-0102")
assert r.status_code == 200
d = r.json()
assert d["lvx_id"] == "LVX-TRP-0102"
assert d["posttyp"] == "objektklass"
assert "kallor" in d
print(f"✅ Object: {d['namn']['sv']} ({len(d['kallor'])} källor)")
def test_succession():
r = requests.get(f"{BASE}/v0/objects/LVX-TRP-0102/succession")
assert r.status_code == 200
print("✅ Succession: OK")
def test_standard():
r = requests.get(f"{BASE}/v0/standards/EN%2012439-5/objects")
assert r.status_code == 200
d = r.json()
print(f"✅ Standard: {d['antal']} objekt kopplade till EN 12439-5")
def test_feedback():
r = requests.post(f"{BASE}/v0/feedback", json={
"kommentar": "Testbounty från integrationstest"
})
assert r.status_code == 200
d = r.json()
assert d["status"] == "skapad"
print(f"✅ Feedback: bounty {d['bounty_id']} skapad")
def test_404():
r = requests.get(f"{BASE}/v0/objects/LVX-XXX-9999")
assert r.status_code == 404
print("✅ 404: OK")
def main():
print("Landvex API Integrationstester")
print("=" * 40)
try:
test_health()
test_search()
test_get_object()
test_succession()
test_standard()
test_feedback()
test_404()
print("=" * 40)
print("✅ Alla tester passerade!")
return 0
except Exception as e:
print(f"❌ FAIL: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())