129 lines
5.1 KiB
Python
Executable File
129 lines
5.1 KiB
Python
Executable File
# app/services/ai_ocr_service.py
|
|
import json
|
|
import httpx
|
|
import base64
|
|
import logging
|
|
from typing import Dict, Any, Optional
|
|
from app.schemas.evidence import RegistrationDocumentExtracted
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class AiOcrService:
|
|
OLLAMA_URL = "http://sf_ollama:11434/api/generate"
|
|
MODEL_NAME = "llama3.2-vision"
|
|
DEFAULT_TIMEOUT = 90.0
|
|
|
|
@classmethod
|
|
async def analyze_image(cls, image_bytes: bytes, prompt: str) -> Dict[str, Any]:
|
|
"""
|
|
Általános képfeldolgozás Ollama Vision modellel.
|
|
|
|
Args:
|
|
image_bytes: A kép bájtjai
|
|
prompt: A prompt szöveg, amit a modelnek küldünk
|
|
|
|
Returns:
|
|
Dict a válasz adataival (a 'response' mezőből parse-olt JSON)
|
|
|
|
Raises:
|
|
httpx.RequestError: Ha a hálózati kérés sikertelen
|
|
json.JSONDecodeError: Ha a válasz nem érvényes JSON
|
|
ValueError: Ha más hiba történik
|
|
"""
|
|
base64_image = base64.b64encode(image_bytes).decode('utf-8')
|
|
|
|
payload = {
|
|
"model": cls.MODEL_NAME,
|
|
"prompt": prompt,
|
|
"images": [base64_image],
|
|
"stream": False,
|
|
"format": "json"
|
|
}
|
|
|
|
async with httpx.AsyncClient(timeout=cls.DEFAULT_TIMEOUT) as client:
|
|
try:
|
|
logger.info(f"Ollama API hívás: {cls.OLLAMA_URL}, model: {cls.MODEL_NAME}")
|
|
response = await client.post(cls.OLLAMA_URL, json=payload)
|
|
response.raise_for_status()
|
|
|
|
result = response.json()
|
|
ai_response_text = result.get("response", "{}")
|
|
|
|
# Próbáljuk JSON-ként értelmezni a választ
|
|
try:
|
|
parsed = json.loads(ai_response_text)
|
|
except json.JSONDecodeError:
|
|
# Ha nem JSON, visszaadjuk szövegként
|
|
parsed = {"raw_response": ai_response_text}
|
|
|
|
logger.info(f"Ollama válasz sikeresen feldolgozva")
|
|
return parsed
|
|
|
|
except httpx.TimeoutException:
|
|
logger.error("Ollama API timeout")
|
|
raise ValueError("Ollama API időtúllépés")
|
|
except httpx.HTTPStatusError as e:
|
|
logger.error(f"Ollama HTTP hiba: {e.response.status_code} - {e.response.text}")
|
|
raise ValueError(f"Ollama HTTP hiba: {e.response.status_code}")
|
|
except Exception as e:
|
|
logger.error(f"Ollama API hiba: {e}")
|
|
raise ValueError(f"AI hiba a képfeldolgozás során: {str(e)}")
|
|
|
|
@classmethod
|
|
async def extract_registration_data(cls, clean_image_bytes: bytes) -> RegistrationDocumentExtracted:
|
|
"""
|
|
Speciális metódus magyar forgalmi engedély adatainak kinyerésére.
|
|
A régi kompatibilitás miatt megtartva.
|
|
"""
|
|
base64_image = base64.b64encode(clean_image_bytes).decode('utf-8')
|
|
|
|
prompt = """
|
|
Te egy magyar hatósági okmány-szakértő AI vagy. A feladatod a mellékelt magyar forgalmi engedély (kép) összes adatának kinyerése.
|
|
|
|
Keresd meg és olvasd le az adatokat az alábbi hatósági kódok alapján:
|
|
- A: Rendszám (kötőjellel, pl: ABC-123 vagy AA-BB-123)
|
|
- B: Első nyilvántartásba vétel dátuma (YYYY.MM.DD)
|
|
- C.1.1: Családi név vagy cégnév
|
|
- C.1.2: Utónév
|
|
- C.1.3: Teljes lakcím (Irsz, Város, Utca, Házszám)
|
|
- C.4: Jogosultság (a = tulajdonos, b = üzembentartó)
|
|
- D.1: Gyártmány (pl. TOYOTA, VOLKSWAGEN)
|
|
- D.2: Jármű típusa
|
|
- D.3: Kereskedelmi leírás (pl. COROLLA, GOLF)
|
|
- E: Alvázszám (pontosan 17 karakter)
|
|
- G: Saját tömeg (kg)
|
|
- F.1: Együttes tömeg (kg)
|
|
- P.1: Hengerűrtartalom (cm3)
|
|
- P.2: Teljesítmény (kW)
|
|
- P.3: Hajtóanyag (pl. Benzin, Gázolaj, Elektromos)
|
|
- P.5: Motorkód
|
|
- V.9: Környezetvédelmi osztály kódja
|
|
- R: Szín
|
|
- S.1: Ülések száma
|
|
- H: Műszaki érvényesség vége (YYYY.MM.DD)
|
|
- Sebességváltó: Keresd a 0, 1, 2, 3 kódokat (0=mechanikus, 2=automata).
|
|
|
|
VÁLASZ FORMÁTUMA: Kizárólag érvényes JSON. Ha egy adat nem olvasható, az értéke null legyen.
|
|
"""
|
|
|
|
payload = {
|
|
"model": cls.MODEL_NAME,
|
|
"prompt": prompt,
|
|
"images": [base64_image],
|
|
"stream": False,
|
|
"format": "json"
|
|
}
|
|
|
|
async with httpx.AsyncClient(timeout=cls.DEFAULT_TIMEOUT) as client:
|
|
try:
|
|
response = await client.post(cls.OLLAMA_URL, json=payload)
|
|
response.raise_for_status()
|
|
|
|
ai_response_text = response.json().get("response", "{}")
|
|
data_dict = json.loads(ai_response_text)
|
|
|
|
return RegistrationDocumentExtracted(**data_dict)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Robot 3 AI Hiba: {e}")
|
|
raise ValueError(f"AI hiba az adatkivonás során: {str(e)}") |