chore: Archive legacy docs and backup files, prepare for codebase cleanup v2.0
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
from sendgrid import SendGridAPIClient
|
||||
from sendgrid.helpers.mail import Mail
|
||||
|
||||
def send_verification_email(to_email: str, token: str):
|
||||
message = Mail(
|
||||
from_email='noreply@servicefinder.pro', # Ezt majd igazítsd a SendGrid verified senderhez
|
||||
to_emails=to_email,
|
||||
subject='Service Finder - Regisztráció megerősítése',
|
||||
html_content=f'<h3>Üdvözöljük a Service Finderben!</h3><p>A regisztráció befejezéséhez kattintson az alábbi linkre:</p><p><a href="https://servicefinder.pro/verify?token={token}">Megerősítem a regisztrációmat</a></p>'
|
||||
)
|
||||
try:
|
||||
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
|
||||
response = sg.send(message)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Email hiba: {e}")
|
||||
return False
|
||||
@@ -1,24 +0,0 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/core/security.py
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional, Dict, Any
|
||||
import bcrypt
|
||||
from jose import jwt
|
||||
from app.core.config import settings
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
if not hashed_password:
|
||||
return False
|
||||
return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
salt = bcrypt.gensalt()
|
||||
return bcrypt.hashpw(password.encode("utf-8"), salt).decode("utf-8")
|
||||
|
||||
def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
@@ -1,18 +0,0 @@
|
||||
from sqlalchemy import Column, String, JSON, Boolean, DateTime, Integer, text
|
||||
from sqlalchemy.sql import func
|
||||
from app.db.base_class import Base
|
||||
|
||||
class SystemParameter(Base):
|
||||
"""
|
||||
Rendszerszintű dinamikus paraméterek tárolása.
|
||||
Szinkronban az admin.py és config.py elvárásaival.
|
||||
"""
|
||||
__tablename__ = "system_parameters"
|
||||
__table_args__ = {"schema": "data"}
|
||||
|
||||
# Az admin.py 'key' mezőt vár, nem 'key_name'-et!
|
||||
key = Column(String(50), primary_key=True, index=True)
|
||||
value = Column(JSON, server_default=text("'{}'::jsonb"), nullable=False)
|
||||
description = Column(String(255), nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
@@ -1,116 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Dict, Any, Optional
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
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.2.4 - Production Ready
|
||||
- Robot 2 (Technical Enrichment) & Robot 3 (OCR)
|
||||
- Fix: JSON response cleaning and array-to-dict transformation.
|
||||
"""
|
||||
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"
|
||||
|
||||
@classmethod
|
||||
async def get_config_delay(cls) -> float:
|
||||
"""Lekéri az adminisztrálható késleltetést 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 1.0
|
||||
except Exception:
|
||||
return 1.0
|
||||
|
||||
@classmethod
|
||||
async def get_clean_vehicle_data(cls, make: str, raw_model: str, v_type: str) -> Optional[Dict[str, Any]]:
|
||||
"""Robot 2: Gépjármű technikai adatok dúsítása."""
|
||||
if not cls.client:
|
||||
return None
|
||||
|
||||
await asyncio.sleep(await cls.get_config_delay())
|
||||
|
||||
prompt = f"""
|
||||
Jármű: {make} {raw_model} ({v_type}).
|
||||
Adj technikai adatokat JSON formátumban.
|
||||
FONTOS: A 'technical_code' mező NEM lehet üres. Ha nem tudod a gyári kódot, adj 'N/A' értéket!
|
||||
|
||||
Várt struktúra:
|
||||
{{
|
||||
"marketing_name": "tiszta marketing név",
|
||||
"technical_code": "gyári kód vagy N/A",
|
||||
"ccm": egész szám,
|
||||
"kw": egész szám,
|
||||
"maintenance": {{
|
||||
"oil_type": "viszkozitás",
|
||||
"oil_qty": tizedes tört literben,
|
||||
"spark_plug": "gyertya típus",
|
||||
"coolant": "hűtőfolyadék"
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
config = types.GenerateContentConfig(
|
||||
system_instruction="Profi gépjárműtechnikus vagy. Kizárólag tiszta JSON-t válaszolsz.",
|
||||
response_mime_type="application/json",
|
||||
temperature=0.1
|
||||
)
|
||||
|
||||
try:
|
||||
response = cls.client.models.generate_content(model=cls.PRIMARY_MODEL, contents=prompt, 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"❌ AI hiba ({make} {raw_model}): {e}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def analyze_document_image(cls, image_data: bytes, doc_type: str) -> Optional[Dict[str, Any]]:
|
||||
"""Robot 3: Multimodális OCR elemzés (Képbeolvasás)."""
|
||||
if not cls.client:
|
||||
return None
|
||||
|
||||
await asyncio.sleep(await cls.get_config_delay())
|
||||
|
||||
prompts = {
|
||||
"identity": "Személyes okmány adatok.",
|
||||
"vehicle_reg": "Rendszám, alvázszám, technikai adatok.",
|
||||
"invoice": "Számla adatok, összegek, dátumok.",
|
||||
"odometer": "Csak a kilométeróra állása számként."
|
||||
}
|
||||
|
||||
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, '')}",
|
||||
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"❌ AI OCR hiba ({doc_type}): {e}")
|
||||
return None
|
||||
@@ -1,102 +0,0 @@
|
||||
import asyncio
|
||||
import httpx
|
||||
import logging
|
||||
import os # <--- EZ HIÁNYZOTT!
|
||||
import datetime
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from app.db.session import SessionLocal
|
||||
from app.models.vehicle_definitions import VehicleModelDefinition
|
||||
from app.services.ai_service import AIService
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("Robot-v1.2.4-Fixed")
|
||||
|
||||
class TechEnricher:
|
||||
API_URL = "https://opendata.rdw.nl/resource/kyri-nuah.json"
|
||||
RDW_TOKEN = os.getenv("RDW_APP_TOKEN")
|
||||
HEADERS = {"X-App-Token": RDW_TOKEN} if RDW_TOKEN else {}
|
||||
|
||||
@classmethod
|
||||
async def fetch_rdw_tech_data(cls, make, model):
|
||||
"""Hibatűrő RDW lekérdezés tisztított paraméterekkel."""
|
||||
clean_model = str(model).strip().upper()
|
||||
params = {"merk": make.upper(), "handelsbenaming": clean_model, "$limit": 1}
|
||||
async with httpx.AsyncClient(headers=cls.HEADERS) as client:
|
||||
try:
|
||||
resp = await client.get(cls.API_URL, params=params, timeout=15)
|
||||
if resp.status_code == 200 and resp.json():
|
||||
return resp.json()[0]
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def run(cls):
|
||||
logger.info("🚀 Master Enricher INDUL (Atomi mentés üzemmód)...")
|
||||
|
||||
# 1. Csak az ID-kat kérjük le, hogy ne tartsuk nyitva a tranzakciót feleslegesen
|
||||
async with SessionLocal() as main_db:
|
||||
stmt = select(VehicleModelDefinition.id).where(
|
||||
VehicleModelDefinition.status == "unverified"
|
||||
).limit(50)
|
||||
res = await main_db.execute(stmt)
|
||||
ids = res.scalars().all()
|
||||
|
||||
if not ids:
|
||||
logger.info("😴 Nincs dúsítandó adat.")
|
||||
return
|
||||
|
||||
# 2. Egyesével dolgozzuk fel a rekordokat saját session-ben
|
||||
for m_id in ids:
|
||||
async with SessionLocal() as db:
|
||||
try:
|
||||
master = await db.get(VehicleModelDefinition, m_id)
|
||||
if not master:
|
||||
continue
|
||||
|
||||
logger.info(f"🧪 Feldolgozás: {master.make} {master.marketing_name} (ID: {m_id})")
|
||||
data_found = False
|
||||
|
||||
# A: RDW fázis
|
||||
rdw_data = await cls.fetch_rdw_tech_data(master.make, master.marketing_name)
|
||||
if rdw_data:
|
||||
master.engine_capacity = int(float(rdw_data.get("cilinderinhoud", 0))) or None
|
||||
master.power_kw = int(float(rdw_data.get("netto_maximum_vermogen_kw", 0))) or None
|
||||
data_found = True
|
||||
|
||||
# B: AI fázis (ha hiányzik adat vagy pontosítani kell)
|
||||
if not data_found or master.engine_capacity is None:
|
||||
ai_data = await AIService.get_clean_vehicle_data(
|
||||
master.make, master.marketing_name, master.vehicle_type
|
||||
)
|
||||
if ai_data:
|
||||
master.marketing_name = ai_data.get("marketing_name", master.marketing_name)
|
||||
master.technical_code = ai_data.get("technical_code") or master.technical_code or "N/A"
|
||||
master.engine_capacity = ai_data.get("ccm") or master.engine_capacity
|
||||
master.power_kw = ai_data.get("kw") or master.power_kw
|
||||
master.specifications = ai_data.get("maintenance", {})
|
||||
data_found = True
|
||||
|
||||
# C: Mentés és véglegesítés
|
||||
if data_found:
|
||||
master.status = "ai_enriched"
|
||||
master.updated_at = datetime.datetime.now()
|
||||
await db.commit() # AZONNALI COMMIT A LEMEZRE
|
||||
logger.info(f"✅ Sikeresen mentve: {master.marketing_name} (CCM: {master.engine_capacity})")
|
||||
else:
|
||||
logger.warning(f"⚠️ Nem találtam adatot az ID {m_id} esetében.")
|
||||
|
||||
except IntegrityError:
|
||||
await db.rollback()
|
||||
logger.warning(f"🚫 Duplikáció vagy Constraint hiba (ID: {m_id}). Kihagyva.")
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"❌ Váratlan hiba az ID {m_id} esetében: {e}")
|
||||
finally:
|
||||
await db.close()
|
||||
|
||||
logger.info("🏁 50-es batch feldolgozva.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(TechEnricher.run())
|
||||
Reference in New Issue
Block a user