Cleanup: MB 2.0 Gap Analysis előtti állapot (adatok kizárva)

This commit is contained in:
2026-02-23 09:44:02 +01:00
parent 5757754aae
commit 893f39fa15
74 changed files with 34239 additions and 2834 deletions

View File

@@ -3,24 +3,20 @@ import json
import logging
import asyncio
import re
from typing import Dict, Any, Optional
from google import genai
from google.genai import types
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
from app.models.system import SystemParameter
logger = logging.getLogger("AI-Service")
class AIService:
"""
AI Service v1.2.5 - Final Integrated Edition
- Robot 2: Technikai dúsítás (Search + Regex JSON parsing)
- Robot 3: OCR (Controlled JSON generation)
"""
api_key = os.getenv("GEMINI_API_KEY")
client = genai.Client(api_key=api_key) if api_key else None
PRIMARY_MODEL = "gemini-2.0-flash"
OLLAMA_BASE_URL = "http://ollama:11434/api/generate"
TEXT_MODEL = "qwen2.5-coder:32b"
VISION_MODEL = "llava:7b"
DVLA_API_KEY = os.getenv("DVLA_API_KEY")
@classmethod
async def get_config_delay(cls) -> float:
@@ -29,83 +25,71 @@ class AIService:
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 1.0
except Exception: return 1.0
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) -> Optional[Dict[str, Any]]:
"""Robot 2: Adatbányászat Google Search segítségével."""
if not cls.client: return None
async def get_gold_data_from_research(cls, make: str, model: str, raw_context: str) -> Optional[Dict[str, Any]]:
await asyncio.sleep(await cls.get_config_delay())
search_tool = types.Tool(google_search=types.GoogleSearch())
prompt = f"""
KERESS RÁ az interneten: {make} {raw_model} ({v_type}) pontos gyári modellkódja és technikai adatai.
Adj választ szigorúan csak egy JSON blokkban:
FELADAT: A mellékelt kutatási adatokból állíts össze egy hiteles technikai adatlapot.
JÁRMŰ: {make} {model}
KUTATÁSI ADATOK (Szemetesláda tartalom):
{raw_context}
SZIGORÚ SZABÁLYOK:
1. Csak a megerősített adatokat töltsd ki.
2. Ha lóerőt (hp/bhp) találsz, váltsd át kW-ra (hp * 0.745).
3. A 'marketing_name' maradjon 50 karakter alatt.
VÁLASZ FORMÁTUM (Tiszta JSON):
{{
"marketing_name": "tiszta név",
"synonyms": ["név1", "név2"],
"technical_code": "gyári kód",
"year_from": int,
"year_to": int_vagy_null,
"marketing_name": "string",
"technical_code": "string",
"ccm": int,
"kw": int,
"maintenance": {{ "oil_type": "string", "oil_qty": float, "spark_plug": "string", "coolant": "string" }}
"maintenance": {{
"oil_type": "string",
"oil_qty_liters": float,
"spark_plug": "string",
"final_drive": "string"
}},
"tires": {{
"front": "string",
"rear": "string"
}},
"is_duplicate_potential": bool
}}
FONTOS: A 'technical_code' NEM lehet üres. Ha nem találod, adj 'N/A' értéket!
"""
# Search tool használata esetén a response_mime_type tilos!
config = types.GenerateContentConfig(
system_instruction="Profi járműtechnikai adatbányász vagy. Csak tiszta JSON-t válaszolsz markdown kódblokk nélkül.",
tools=[search_tool],
temperature=0.1
)
return await cls._execute_ai_call(prompt, make, model)
@classmethod
async def _execute_ai_call(cls, prompt: str, make: str, model: str) -> Optional[Dict[str, Any]]:
payload = {
"model": cls.TEXT_MODEL,
"prompt": prompt,
"stream": False,
"format": "json",
"options": {"temperature": 0.1}
}
try:
response = cls.client.models.generate_content(model=cls.PRIMARY_MODEL, contents=prompt, config=config)
text = response.text
# Tisztítás: ha az AI mégis tenne bele markdown jeleket
clean_json = re.sub(r'```json\s*|```', '', text).strip()
res_json = json.loads(clean_json)
if isinstance(res_json, list) and len(res_json) > 0: res_json = res_json[0]
return res_json if isinstance(res_json, dict) else None
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.post(cls.OLLAMA_BASE_URL, json=payload)
response.raise_for_status()
res_json = response.json()
return json.loads(res_json.get("response", "{}"))
except Exception as e:
logger.error(f"❌ AI hiba ({make} {raw_model}): {e}")
logger.error(f"❌ AI hiba ({make} {model}): {e}")
return None
@classmethod
async def analyze_document_image(cls, image_data: bytes, doc_type: str) -> Optional[Dict[str, Any]]:
"""Robot 3: OCR funkció - Forgalmi, Személyi, Számla, Odometer."""
if not cls.client: return None
async def get_clean_vehicle_data(cls, make: str, raw_model: str, v_type: str, sources: Dict[str, Any]) -> Optional[Dict[str, Any]]:
await asyncio.sleep(await cls.get_config_delay())
prompts = {
"identity": "Személyes okmány adatok (név, szám, lejárat).",
"vehicle_reg": "Forgalmi adatok (rendszám, alvázszám, kW, ccm).",
"invoice": "Számla adatok (partner, végösszeg, dátum).",
"odometer": "Csak a kilométeróra állása számként."
}
# Itt maradhat a response_mime_type, mert nem használunk Search-öt
config = types.GenerateContentConfig(
system_instruction="Profi OCR dokumentum-elemző vagy. Csak tiszta JSON-t válaszolsz.",
response_mime_type="application/json"
)
try:
response = cls.client.models.generate_content(
model=cls.PRIMARY_MODEL,
contents=[
f"Elemezd ezt a képet ({doc_type}): {prompts.get(doc_type, 'OCR')}",
types.Part.from_bytes(data=image_data, mime_type="image/jpeg")
],
config=config
)
res_json = json.loads(response.text)
if isinstance(res_json, list) and len(res_json) > 0: res_json = res_json[0]
return res_json if isinstance(res_json, dict) else None
except Exception as e:
logger.error(f"❌ OCR hiba: {e}")
return None
prompt = f"""
FELADAT: Normalizáld a jármű adatait.
GYÁRTÓ: {make} | MODELL: {raw_model}
ADATOK: {json.dumps(sources)}
(JSON válasz marketing_name, synonyms, technical_code, ccm, kw, year_from, year_to)
"""
return await cls._execute_ai_call(prompt, make, raw_model)