f4f853d94b
Datafabrik: - Skördare: crawler, källvitlista, upphandlingsskördare - Extraktor: LLM-baserad schemastyrd extraktion - Upplösare: Entitetsupplösning och deduplicering - Köer: Schemalagd / kunddriven / fält - Agentorkestrering: 20+ parallella agenter Vision: - Identify-modell: ResNet50 + kontrastivt lärande - Träningspipeline: NT-Xent loss - Vektordatabas: FAISS för snabb sökning - OCR-pipeline: Typskyltsläsning Infrastruktur: - Docker Compose production - Terraform för AWS ECS - Prometheus + Grafana monitorering - Neo4j + FAISS + MinIO + Redis
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Landvex Extraktor — LLM-baserad schemastyrd extraktion
|
|
Dokument in, strukturerade fakta ut.
|
|
"""
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import List, Dict, Optional
|
|
from datetime import datetime
|
|
|
|
from openai import AsyncOpenAI
|
|
|
|
# Schema för extraktion (från lvx-objekt-schema.json)
|
|
EXTRAKTIONS_SCHEMA = {
|
|
"objektklass": {
|
|
"required": ["lvx_id", "posttyp", "namn", "doman", "proveniens"],
|
|
"fields": [
|
|
"lvx_id", "posttyp", "slug", "namn", "doman", "parent", "tier",
|
|
"identitet", "teknik", "historik", "geografi", "standarder",
|
|
"ai", "problem", "dokumentation", "relationer", "proveniens"
|
|
]
|
|
},
|
|
"produktmodell": {
|
|
"required": ["lvx_id", "posttyp", "namn", "doman", "parent", "proveniens"],
|
|
"fields": [
|
|
"lvx_id", "posttyp", "slug", "namn", "doman", "parent", "tier",
|
|
"identitet", "teknik", "historik", "geografi", "standarder",
|
|
"ai", "problem", "dokumentation", "relationer", "proveniens"
|
|
]
|
|
}
|
|
}
|
|
|
|
EXTRAKTIONS_PROMPT = """Du är Landvex Extraktor — en noggrann forskarassistent specialiserad på infrastrukturobjekt.
|
|
|
|
DIN UPPGIFT: Analysera följande dokument och extrahera strukturerade fakta om infrastrukturobjekt.
|
|
|
|
REGLER:
|
|
1. Extrahera ENDAST fakta som finns i dokumentet — uppfinn inget
|
|
2. Varje faktum måste ha en källa (URL eller dokumentreferens)
|
|
3. Använd Landvex-schemat strikt
|
|
4. Om något är osäkert, markera med låg konfidens
|
|
5. Problem/Kända fel kräver alltid källa
|
|
|
|
OUTPUT: JSON enligt följande struktur:
|
|
{
|
|
"kandidater": [
|
|
{
|
|
"atgard": "ny" | "uppdatera",
|
|
"posttyp": "objektklass" | "produktmodell",
|
|
"post": { ... },
|
|
"patch": { ... },
|
|
"verifieringsniva_forslag": "obekraftad" | "kallbelagd" | "faltverifierad" | "tillverkarbekraftad",
|
|
"kallor": [
|
|
{"titel": "...", "url": "...", "hamtad": "2026-07-05"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
DOKUMENT:
|
|
{content}
|
|
"""
|
|
|
|
class LandvexExtraktor:
|
|
def __init__(self, api_key: Optional[str] = None):
|
|
self.client = AsyncOpenAI(api_key=api_key or os.getenv("OPENAI_API_KEY"))
|
|
self.model = "gpt-4o-mini" # Kostnadseffektiv för volym
|
|
|
|
async def extrahera(self, dokument: str, url: str, doman: str) -> dict:
|
|
"""Extrahera strukturerade fakta från ett dokument."""
|
|
|
|
prompt = EXTRAKTIONS_PROMPT.format(content=dokument[:15000]) # Begränsa längd
|
|
|
|
try:
|
|
response = await self.client.chat.completions.create(
|
|
model=self.model,
|
|
messages=[
|
|
{"role": "system", "content": "Du är Landvex Extraktor. Extrahera strukturerade fakta om infrastrukturobjekt."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
response_format={"type": "json_object"},
|
|
temperature=0.1, # Låg för determinism
|
|
)
|
|
|
|
resultat = json.loads(response.choices[0].message.content)
|
|
|
|
# Lägg till proveniens
|
|
for kand in resultat.get("kandidater", []):
|
|
kand.setdefault("kallor", []).append({
|
|
"titel": f"Extraherat från {url}",
|
|
"url": url,
|
|
"hamtad": datetime.utcnow().isoformat()
|
|
})
|
|
|
|
return resultat
|
|
|
|
except Exception as e:
|
|
print(f" ⚠️ Extraktionsfel: {e}")
|
|
return {"kandidater": [], "fel": str(e)}
|
|
|
|
async def extrahera_batch(self, dokument: List[dict]) -> List[dict]:
|
|
"""Extrahera från flera dokument parallellt."""
|
|
import asyncio
|
|
|
|
uppgifter = [
|
|
self.extrahera(d["html"], d["url"], d.get("doman", "TRP"))
|
|
for d in dokument
|
|
]
|
|
|
|
resultat = await asyncio.gather(*uppgifter, return_exceptions=True)
|
|
return [r for r in resultat if not isinstance(r, Exception)]
|
|
|
|
async def main():
|
|
"""Demo: extrahera från testdokument."""
|
|
extraktor = LandvexExtraktor()
|
|
|
|
test_doc = """
|
|
ABB Kabeldon CDC-LT är ett lågspänningskabelskåp för utomhusbruk.
|
|
Material: Varmförzinkad stålplåt, IP34D enligt SS-EN 60529.
|
|
Standard: SS-EN 61439-5 (provnings- och konstruktionskrav).
|
|
Tillverkare: ABB Kabeldon, Alingsås.
|
|
Lås: Trekantslås standard, cylinderlås som tillval.
|
|
"""
|
|
|
|
resultat = await extraktor.extrahera(test_doc, "https://example.com/test", "ELN")
|
|
print(json.dumps(resultat, ensure_ascii=False, indent=2))
|
|
|
|
if __name__ == "__main__":
|
|
import asyncio
|
|
asyncio.run(main())
|