import os import json import logging import asyncio import re import base64 import httpx from typing import Dict, Any, Optional, List from sqlalchemy import select from app.db.session import SessionLocal from app.models import SystemParameter logger = logging.getLogger("AI-Service") class AIService: """ AI Service v1.3.5 - Private High-Performance Edition - Engine: Local Ollama (GPU Accelerated) - Features: DVLA Integration, 50-char Normalization, Private OCR """ # A Docker belső hálózatán a szerviznév 'ollama' OLLAMA_BASE_URL = "http://ollama:11434/api/generate" TEXT_MODEL = "vehicle-pro" VISION_MODEL = "llava:7b" DVLA_API_KEY = os.getenv("DVLA_API_KEY") @classmethod async def get_config_delay(cls) -> float: """Késleltetés lekérése az adatbázisból.""" try: async with SessionLocal() as db: stmt = select(SystemParameter).where(SystemParameter.key == "AI_REQUEST_DELAY") res = await db.execute(stmt) param = res.scalar_one_or_none() return float(param.value) if param else 0.1 except Exception: return 0.1 @classmethod async def get_clean_vehicle_data(cls, make: str, raw_model: str, v_type: str, sources: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Robot 2: Adat-összefésülés és normalizálás.""" # Várjunk egy kicsit a GPU kímélése érdekében await asyncio.sleep(await cls.get_config_delay()) prompt = f""" FELADAT: Normalizáld a jármű adatait több forrás alapján. GYÁRTÓ: {make} NYERS MODELLNÉV: {raw_model} FORRÁSOK NYERS ADATAI: {json.dumps(sources, ensure_ascii=False)} SZIGORÚ SZABÁLYOK: 1. 'marketing_name': MAXIMUM 50 KARAKTER! 2. 'synonyms': Gyűjtsd ide az összes többi névváltozatot. 3. 'technical_code': Keresd meg a gyári kódokat. VÁLASZ FORMÁTUM (Csak tiszta JSON): {{ "marketing_name": "string (max 50)", "synonyms": ["string"], "technical_code": "string", "ccm": int, "kw": int, "euro_class": int, "year_from": int, "year_to": int vagy null, "maintenance": {{ "oil_type": "string", "oil_qty": float, "spark_plug": "string" }}, "is_duplicate_potential": bool }} """ payload = { "model": cls.TEXT_MODEL, "prompt": prompt, "stream": False, "format": "json", "options": {"temperature": 0.1} } try: async with httpx.AsyncClient(timeout=90.0) as client: logger.info(f"📡 AI kérés küldése: {make} {raw_model}...") response = await client.post(cls.OLLAMA_BASE_URL, json=payload) response.raise_for_status() res_json = response.json() clean_data = json.loads(res_json.get("response", "{}")) if clean_data.get("marketing_name"): clean_data["marketing_name"] = clean_data["marketing_name"][:50].strip() return clean_data except Exception as e: logger.error(f"❌ AI hiba ({make} {raw_model}): {e}") return None @classmethod async def get_dvla_data(cls, vrm: str) -> Optional[Dict[str, Any]]: """Brit rendszám alapú adatok lekérése.""" if not cls.DVLA_API_KEY: return None url = "https://driver-vehicle-licensing.api.gov.uk/vehicle-enquiry/v1/vehicles" headers = {"x-api-key": cls.DVLA_API_KEY, "Content-Type": "application/json"} try: async with httpx.AsyncClient() as client: resp = await client.post(url, json={"registrationNumber": vrm}, headers=headers) return resp.json() if resp.status_code == 200 else None except Exception as e: logger.error(f"❌ DVLA API hiba: {e}") return None @classmethod async def analyze_document_image(cls, image_data: bytes, doc_type: str) -> Optional[Dict[str, Any]]: """Robot 3: Helyi OCR és dokumentum elemzés (Llava:7b).""" await asyncio.sleep(await cls.get_config_delay()) prompts = { "identity": "Extract ID card data (name, id_number, expiry) as JSON.", "vehicle_reg": "Extract vehicle registration (plate, VIN, power_kw, engine_ccm) as JSON.", "invoice": "Extract invoice details (vendor, total_amount, date) as JSON.", "odometer": "Identify the number on the odometer and return as JSON: {'value': int}." } img_b64 = base64.b64encode(image_data).decode('utf-8') payload = { "model": cls.VISION_MODEL, "prompt": prompts.get(doc_type, "Perform OCR and return JSON"), "images": [img_b64], "stream": False, "format": "json" } try: async with httpx.AsyncClient(timeout=120.0) as client: response = await client.post(cls.OLLAMA_BASE_URL, json=payload) res_data = response.json() clean_json = res_data.get("response", "{}") match = re.search(r'\{.*\}', clean_json, re.DOTALL) return json.loads(match.group()) if match else json.loads(clean_json) except Exception as e: logger.error(f"❌ Helyi OCR hiba: {e}") return None