Compare commits

..

2 Commits

Author SHA1 Message Date
Roo
6c359040e2 vehicle robot 3 update - Fixes #1 2026-03-06 18:48:22 +01:00
Roo
75975b2741 Architect és robot szabályok frissítése - Fixes #1 2026-03-06 14:43:46 +00:00
9 changed files with 368 additions and 43 deletions

View File

@@ -15,6 +15,9 @@ Te vagy a rendszer őre. Feladatod a forráskód (Primary Truth) és a MasterBoo
3. **Kanban Menedzsment:** 3A szintű granulártság. Minden technikai részfeladatot (pl. "Alembic migration for vehicle_types") rögzíts a Focalboardon. 3. **Kanban Menedzsment:** 3A szintű granulártság. Minden technikai részfeladatot (pl. "Alembic migration for vehicle_types") rögzíts a Focalboardon.
4. **Jóváhagyási Pont:** A tervezés végén ÁLLJ MEG. Várj a felhasználó kifejezett jóváhagyására a `logic_spec` kapcsán. 4. **Jóváhagyási Pont:** A tervezés végén ÁLLJ MEG. Várj a felhasználó kifejezett jóváhagyására a `logic_spec` kapcsán.
5. **Focalboard Automatizálás:** Ha a logokban azt látod, hogy egy robot (pl. Alchemist) `manual_review_needed` státuszba tesz egy rekordot, kötelességed erről egy feladatkártyát nyitni a "Manual Review" oszlopban a pontos ID-val.
6. **Környezeti Audit:** Kódmódosítás előtt mindig ellenőrizd a `docker-compose.yml` fájlban a `command` sort, hogy pontosan lásd, melyik fájlt futtatja a konténer. Így elkerülhető a rossz fájl szerkesztése.
## ⚠️ Korlátozások ## ⚠️ Korlátozások
- Meglévő, hiba nélkül futó kódhoz TILOS hozzányúlni jóváhagyás nélkül. - Meglévő, hiba nélkül futó kódhoz TILOS hozzányúlni jóváhagyás nélkül.
- Tervmódosítás esetén add vissza az irányítást a felhasználónak egyeztetésre. - Tervmódosítás esetén add vissza az irányítást a felhasználónak egyeztetésre.

View File

@@ -0,0 +1,13 @@
# 📝 Role Definition: Service Finder Wiki Specialist & Konzulens
## 🎯 Alapvető Küldetés
Te vagy a "Business Logic" és a dokumentáció őre. A te feladatod biztosítani a "2A Elv" (A kód a mérvadó, a Wiki követi) érvényesülését, és hidat képezni a nyers kód és a felhasználók (flottavezetők) között.
## 📋 Főbb Felelősségek
1. **2A Validátor (Kód-Wiki Szinkron):** - Rendszeresen összeveted a Wiki.js (Postgres) tartalmát a legfrissebb SQLAlchemy modellekkel (`/backend/app/models/`).
- Ha a kód megváltozott (pl. új mező került be), te frissíted a Wiki dokumentációt.
2. **Koncepciók Karbantartása:**
- Te felelsz a "Dual Entity" modell és a "Triple Wallet" gazdasági motor pontos, naprakész és érthető dokumentálásáért.
3. **User Manual Generátor:**
- A bonyolult technikai kódokból (pl. Alchemist dúsítási logika) közérthető, magyar nyelvű leírást készítesz az adminisztrátorok számára.
- Formátum: Átlátható Markdown, gyakorlati példákkal.

View File

@@ -4,4 +4,9 @@ AI & OCR: Hibrid AI Gateway (Helyi Ollama: 14B Qwen szövegre, Llama Vision kép
Identity & Auth: "Dual Entity" modell (Person = hús-vér ember, User = technikai fiók). Triple Wallet gazdasági motor. Identity & Auth: "Dual Entity" modell (Person = hús-vér ember, User = technikai fiók). Triple Wallet gazdasági motor.
Deduplikáció (MDM): Csak akkor van merge, ha a make, a technical_code és a hengerűrtartalom egyezik. N/A és UNKNOWN fallback kódok generálása az SQL kényszerek miatt. Deduplikáció (MDM): Csak akkor van merge, ha a make, a technical_code és a hengerűrtartalom egyezik. N/A és UNKNOWN fallback kódok generálása az SQL kényszerek miatt.
## 5. SQL és Adatbázis Hibakezelés (Error Handling)
- **Unique Constraint hibák:** Ha a PostgreSQL `InvalidColumnReferenceError` vagy `UniqueViolation` hibát dob az `ON CONFLICT` miatt, TILOS találgatni a mezőket!
- **A kötelező megoldás:** Használd az `ON CONFLICT ON CONSTRAINT [korlát_neve] DO NOTHING` vagy `DO UPDATE` szintaxist.
- A pontos korlát (constraint) nevét mindig a pgAdmin-ból vagy a `\d+ táblanév` lekérdezéssel kell kideríteni módosítás előtt.

View File

@@ -0,0 +1,13 @@
# 🔍 Service Finder Debug & Hibavadász Protokoll
## 🎯 Alapvető Küldetés
Soha ne találgass! A hibakeresés nálunk tényalapú és szisztematikus. Ha valami nem működik, tilos azonnal átírni a kódot. Előbb diagnosztizálj!
## 🕵️‍♂️ A Hibakeresés Kötelező Lépései:
1. **Log-First Megközelítés:** - Első lépés mindig a konténer logjainak lekérése: `docker logs --tail 100 -f <konténer_neve>`.
- Ha teljesítményprobléma gyanús, ellenőrizd a `docker stats` kimenetét.
2. **Környezeti Audit (Sync Check):**
- Ha a logok szerint a módosított kód nem frissült, AZONNAL ellenőrizd a `docker-compose.yml` volume beállításait.
- Ha a kód "be van sütve" (COPY), használd a `docker compose up -d --build <szolgáltatás>` parancsot a frissítéshez.
3. **SQL Trace & Adatbázis Audit:**
- Adatbázis hiba (pl. SQLAlchemy Exception) esetén az első lépés a táblaséma lekérdezése (Constraints, Indexes) a PostgreSQL konténerből, nem pedig a Python kód átírása.

View File

@@ -0,0 +1,19 @@
# Gitea & Kanban Workflow Szabályok
Te egy Senior Developer vagy, aki a `/opt/docker/dev/service_finder` mappában dolgozik. A projektmenedzsment a helyi Gitea szerveren folyik.
## 🛠 Rendelkezésre álló eszközök:
1. **Git:** Használhatod a terminált (`execute_command`) git parancsokhoz (status, add, commit, push).
2. **Fájlrendszer:** Olvashatsz és írhatsz fájlokat a projektmappában.
3. **Gitea Automatizáció:** A Gitea figyeli a commit üzeneteket.
## 🔄 Kötelező Munkafolyamat:
1. **Feladat azonosítása:** Mindig kérdezd meg vagy keresd meg az aktuális Issue (hibajegy) számát (pl. #1).
2. **Végrehajtás:** Ne kérdezz feleslegesen! Ha megvan a feladat, hajtsd végre a kódmódosítást.
3. **Dokumentálás:** A munka végén a commit üzenetbe KÖTELEZŐ beleírnod a "Fixes #X" kifejezést (ahol X a feladat száma).
- Példa: `git commit -m "README frissítése - Fixes #1"`
4. **Lezárás:** A commit után azonnal futtasd a `git push` parancsot.
## 🚫 Tiltások:
- NE kérj engedélyt olyan fájlok módosításához, amik a feladathoz tartoznak.
- NE keress külső API-kat a kártyák mozgatásához; a "Fixes #X" kulcsszó megoldja az automatikus mozgatást a Kanban táblán.

28
.roomodes Normal file
View File

@@ -0,0 +1,28 @@
{
"customModes": [
{
"slug": "architect",
"name": "Architect",
"roleDefinition": "Te vagy a Rendszer-Architect. Tervezel, felügyeled a Kanban táblát (Focalboard), és elemzed a rendszert. Szabályaid: .roo/rules-architect/architect.md",
"groups": ["read", "command", "mcp"]
},
{
"slug": "fast-coder",
"name": "Fast Coder",
"roleDefinition": "Te vagy a Core Developer. Kódot írsz, tesztelsz, és betartod a Clean Code elveket. Szabályaid: .roo/rules-code/fast-coder.md",
"groups": ["read", "edit", "command"]
},
{
"slug": "debugger",
"name": "Debugger",
"roleDefinition": "Te vagy a Hibavadász. Tilos találgatnod, mindent logok és tények alapján vizsgálsz. Szabályaid: .roo/rules/04-debug-protocol.md",
"groups": ["read", "command"]
},
{
"slug": "wiki-specialist",
"name": "Wiki Specialist",
"roleDefinition": "Te vagy a Dokumentátor és Konzulens. Felelsz a kód és a Wiki.js szinkronjáért. Szabályaid: .roo/rules-architect/wiki-specialist.md",
"groups": ["read", "edit", "mcp"]
}
]
}

View File

@@ -0,0 +1,5 @@
docker exec sf_api python /app/app/test_outside/robot_dashboard.py
docker exec sf_api python /app/app/test_outside/rontgen_felkesz_adatok.py
docker exec sf_api python /app/app/test_outside/rontgen_skript.py
docker exec sf_api python /app/app/test_outside/rdw_api_test.py
docker exec sf_api python /app/app/test_outside/rdw_zt646p_test.py

View File

@@ -4,6 +4,7 @@ import datetime
import random import random
import sys import sys
import json import json
import os
from sqlalchemy import text, func, update, case from sqlalchemy import text, func, update, case
from app.database import AsyncSessionLocal from app.database import AsyncSessionLocal
from app.models.vehicle_definitions import VehicleModelDefinition from app.models.vehicle_definitions import VehicleModelDefinition
@@ -15,9 +16,9 @@ logger = logging.getLogger("Vehicle-Robot-3-Alchemist-Pro")
class TechEnricher: class TechEnricher:
""" """
Vehicle Robot 3: Alchemist Pro (Atomi Zárolás Patch) Vehicle Robot 3: Alchemist Pro (Atomi Zárolás + Kézi Moderáció Patch)
Tiszta GPU fókusz: Csak az AI elemzésre és adategyesítésre koncentrál. Tiszta GPU fókusz: Csak az AI elemzésre és adategyesítésre koncentrál.
Nincs felesleges webkeresés. Szigorú Sane-Check. Nincs felesleges webkeresés. Szigorú, de intelligens Sane-Check.
""" """
def __init__(self): def __init__(self):
self.max_attempts = 5 self.max_attempts = 5
@@ -31,34 +32,38 @@ class TechEnricher:
self.last_reset_date = datetime.date.today() self.last_reset_date = datetime.date.today()
return self.ai_calls_today < self.daily_ai_limit return self.ai_calls_today < self.daily_ai_limit
def is_data_sane(self, data: dict, base_info: dict) -> bool: def validate_merged_data(self, merged_kw: int, merged_ccm: int, v_class: str, fuel: str, current_attempts: int) -> tuple[bool, str]:
""" Szigorított AI Hallucináció szűrő """ """ Intelligens validáció a MERGE után. Visszaadja a státuszt és a hiba okát. """
if not data: return False if merged_ccm > 18000:
return False, f"Irreális CCM érték ({merged_ccm})"
try: if merged_kw > 1500 and v_class != "truck":
ccm = int(data.get("ccm", 0) or 0) return False, f"Irreális KW érték ({merged_kw})"
kw = int(data.get("kw", 0) or 0)
v_class = base_info.get("v_type", "car")
# 1. Alapvető fizikai korlátok
if ccm > 18000 or (kw > 1500 and v_class != "truck"):
return False
# 2. Üres adatok kizárása (Kivéve elektromos autók, ahol ccm = 0)
fuel = data.get("fuel_type", base_info.get("rdw_fuel", "")).lower()
if kw == 0:
return False
if ccm == 0 and "electric" not in fuel and "elektric" not in fuel and v_class != "trailer":
return False
return True # Ha hiányzik a KW
except Exception as e: if merged_kw == 0:
logger.debug(f"Sane check hiba: {e}") if current_attempts < 3:
return False return False, "Hiányzó KW adat. Újrakutatás javasolt."
else:
logger.warning("Sane-check: Többszöri próbálkozás után sincs KW, de átengedjük részlegesként.")
# Ha hiányzik a CCM (és belsőégésű)
if merged_ccm == 0 and "electric" not in fuel and "elektric" not in fuel and v_class != "trailer":
if current_attempts < 3:
return False, "Hiányzó CCM (belsőégésű motornál). Újrakutatás javasolt."
else:
logger.warning("Sane-check: Többszöri próbálkozás után sincs CCM, átengedjük részlegesként.")
return True, "OK"
async def process_single_record(self, db, record_id: int, base_info: dict, current_attempts: int): async def process_single_record(self, db, record_id: int, base_info: dict, current_attempts: int):
# Pontos azonosító a logokhoz (Márka, Modell, ID, RDW adatok)
v_ident = f"{base_info['make'].upper()} {base_info['m_name']} (ID: {record_id}, RDW: {base_info['rdw_ccm']}ccm, KW: {base_info['rdw_kw']})"
attempt_str = f"[Próba: {current_attempts + 1}/{self.max_attempts}]"
ai_data = {} # Üres dict, ha az AI hívás elszállna
try: try:
logger.info(f"🧠 AI dúsítás indul: {base_info['make']} {base_info['m_name']}") logger.info(f"🧠 AI dúsítás indul: {v_ident} {attempt_str}")
# 1. LÉPÉS: AI Hívás (Rábízzuk az adatokat a modellre) # 1. LÉPÉS: AI Hívás (Rábízzuk az adatokat a modellre)
ai_data = await AIService.get_clean_vehicle_data( ai_data = await AIService.get_clean_vehicle_data(
@@ -66,14 +71,14 @@ class TechEnricher:
base_info['m_name'], base_info['m_name'],
base_info base_info
) )
if not ai_data:
raise ValueError("Teljesen üres AI válasz (API hiba vagy extrém hallucináció).")
# 2. LÉPÉS: Validáció (Ha az AI rossz adatot ad, NEM megyünk ki a webre, hanem dobjuk az aktát!) # 2. LÉPÉS: HIBRID MERGE (Még a validáció előtt!)
if not ai_data or not self.is_data_sane(ai_data, base_info): # Az RDW adatok felülbírálják az AI-t a hatósági paramétereknél
raise ValueError("Az AI hiányos adatot adott vissza vagy hallucinált.") final_kw = base_info['rdw_kw'] if base_info['rdw_kw'] > 0 else int(ai_data.get("kw", 0) or 0)
final_ccm = base_info['rdw_ccm'] if base_info['rdw_ccm'] > 0 else int(ai_data.get("ccm", 0) or 0)
# 3. LÉPÉS: HIBRID MERGE (Az RDW adatok felülbírálják az AI-t a hatósági paramétereknél)
final_kw = base_info['rdw_kw'] if base_info['rdw_kw'] > 0 else (ai_data.get("kw") or 0)
final_ccm = base_info['rdw_ccm'] if base_info['rdw_ccm'] > 0 else (ai_data.get("ccm") or 0)
# Üzemanyag tisztítása # Üzemanyag tisztítása
fuel_rdw = base_info.get('rdw_fuel', '') fuel_rdw = base_info.get('rdw_fuel', '')
@@ -83,6 +88,11 @@ class TechEnricher:
final_euro = base_info['rdw_euro'] or ai_data.get("euro_classification") final_euro = base_info['rdw_euro'] or ai_data.get("euro_classification")
final_cylinders = base_info['rdw_cylinders'] or ai_data.get("cylinders") final_cylinders = base_info['rdw_cylinders'] or ai_data.get("cylinders")
# 3. LÉPÉS: Intelligens Validáció
is_valid, error_msg = self.validate_merged_data(final_kw, final_ccm, base_info['v_type'], final_fuel.lower(), current_attempts)
if not is_valid:
raise ValueError(f"Validációs hiba: {error_msg}")
# 4. LÉPÉS: Mentés az Arany Katalógusba # 4. LÉPÉS: Mentés az Arany Katalógusba
clean_model = str(ai_data.get("marketing_name", base_info['m_name']))[:50].upper() clean_model = str(ai_data.get("marketing_name", base_info['m_name']))[:50].upper()
@@ -90,7 +100,7 @@ class TechEnricher:
INSERT INTO data.vehicle_catalog INSERT INTO data.vehicle_catalog
(master_definition_id, make, model, power_kw, engine_capacity, fuel_type, factory_data) (master_definition_id, make, model, power_kw, engine_capacity, fuel_type, factory_data)
VALUES (:m_id, :make, :model, :kw, :ccm, :fuel, :factory) VALUES (:m_id, :make, :model, :kw, :ccm, :fuel, :factory)
ON CONFLICT (make, model, year_from, fuel_type) DO NOTHING ON CONFLICT ON CONSTRAINT uix_vehicle_catalog_full DO NOTHING
RETURNING id; RETURNING id;
""") """)
@@ -121,15 +131,18 @@ class TechEnricher:
) )
) )
await db.commit() await db.commit()
logger.info(f"✨ ARANY REKORD KÉSZ: {base_info['make'].upper()} {clean_model}") logger.info(f"✨ ARANY REKORD KÉSZ: {v_ident}")
self.ai_calls_today += 1 self.ai_calls_today += 1
except Exception as e: except Exception as e:
await db.rollback() await db.rollback()
logger.warning(f"⚠️ Alkimista hiba ({base_info['make']} {base_info['m_name']}): {e}") logger.warning(f"⚠️ Alkimista hiba - {v_ident}: {e}")
# Visszaküldés a sorba vagy felfüggesztés # Ha elértük a limitet, KÉZI MODERÁCIÓRA küldjük, egyébként vissza a Kutatónak
new_status = 'suspended' if current_attempts + 1 >= self.max_attempts else 'unverified' new_status = 'manual_review_needed' if current_attempts + 1 >= self.max_attempts else 'unverified'
# Elmentjük az AI részleges válaszát (vagy a hibát), hogy az admin lássa, mit rontott el a gép
review_data = ai_data if ai_data else {"error": "Nincs értékelhető JSON adat az AI-tól", "raw_context": base_info['web_context']}
await db.execute( await db.execute(
update(VehicleModelDefinition) update(VehicleModelDefinition)
@@ -138,15 +151,19 @@ class TechEnricher:
attempts=current_attempts + 1, attempts=current_attempts + 1,
last_error=str(e)[:200], last_error=str(e)[:200],
status=new_status, status=new_status,
specifications=review_data, # Kézi ellenőrzéshez beírjuk a törött adatot!
updated_at=func.now() updated_at=func.now()
) )
) )
await db.commit() await db.commit()
if new_status == 'unverified': if new_status == 'unverified':
logger.info("♻️ Akta visszaküldve a Robot-2-nek (Kutató).") logger.info(f"♻️ Akta visszaküldve a Robot-2-nek (Kutató). {attempt_str}")
else:
logger.error(f"🛑 Max próbálkozás elérve! Kézi moderációra küldve: {v_ident}")
async def run(self): async def run(self):
logger.info(f"🚀 Alchemist Pro HIBRID ONLINE (Atomi Zárolás Patch)") logger.info(f"🚀 Alchemist Pro HIBRID ONLINE (Atomi Zárolás + Moderáció Patch)")
while True: while True:
if not self.check_budget(): if not self.check_budget():
logger.warning("💸 Napi AI limit kimerítve! Pihenés...") logger.warning("💸 Napi AI limit kimerítve! Pihenés...")
@@ -155,7 +172,6 @@ class TechEnricher:
try: try:
async with AsyncSessionLocal() as db: async with AsyncSessionLocal() as db:
# ATOMI ZÁROLÁS (A "Szent Grál" a race condition ellen) # ATOMI ZÁROLÁS (A "Szent Grál" a race condition ellen)
# A Robot-1 (ACTIVE) és a Robot-2 (awaiting_ai_synthesis) aktáit is felveszi!
query = text(""" query = text("""
UPDATE data.vehicle_model_definitions UPDATE data.vehicle_model_definitions
SET status = 'ai_synthesis_in_progress' SET status = 'ai_synthesis_in_progress'
@@ -204,5 +220,4 @@ class TechEnricher:
await asyncio.sleep(10) await asyncio.sleep(10)
if __name__ == "__main__": if __name__ == "__main__":
import os # Import az AI limit környezeti változóhoz
asyncio.run(TechEnricher().run()) asyncio.run(TechEnricher().run())

View File

@@ -0,0 +1,224 @@
import asyncio
import logging
import datetime
import random
import sys
import json
from sqlalchemy import text, func, update, case
from app.database import AsyncSessionLocal
from app.models.vehicle_definitions import VehicleModelDefinition
from app.models.asset import AssetCatalog
from app.services.ai_service import AIService
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Vehicle-Alchemist-Pro: %(message)s', stream=sys.stdout)
logger = logging.getLogger("Vehicle-Robot-3-Alchemist-Pro")
class TechEnricher:
"""
Vehicle Robot 3: Alchemist Pro (Atomi Zárolás Patch)
Tiszta GPU fókusz: Csak az AI elemzésre és adategyesítésre koncentrál.
Nincs felesleges webkeresés. Szigorú Sane-Check.
"""
def __init__(self):
self.max_attempts = 5
self.daily_ai_limit = int(os.getenv("AI_DAILY_LIMIT", "10000"))
self.ai_calls_today = 0
self.last_reset_date = datetime.date.today()
def check_budget(self) -> bool:
if datetime.date.today() > self.last_reset_date:
self.ai_calls_today = 0
self.last_reset_date = datetime.date.today()
return self.ai_calls_today < self.daily_ai_limit
ddef is_data_sane(self, data: dict, base_info: dict) -> bool:
""" Szigorított, de intelligens AI Hallucináció szűrő """
if not data:
logger.warning("Sane-check: Teljesen üres AI válasz.")
return False
try:
# 1. Alapvető fizikai korlátok vizsgálata (csak az AI adatokon)
ai_ccm = int(data.get("ccm", 0) or 0)
ai_kw = int(data.get("kw", 0) or 0)
v_class = base_info.get("v_type", "car")
if ai_ccm > 18000:
logger.warning(f"Sane-check bukás: Irreális CCM érték ({ai_ccm})")
return False
if ai_kw > 1500 and v_class != "truck":
logger.warning(f"Sane-check bukás: Irreális KW érték ({ai_kw})")
return False
# 2. KOMBINÁLT Adat teljesség vizsgálata (RDW + AI)
# Ha az RDW tudja, akkor nem baj, ha az AI nem találta meg!
merged_kw = base_info.get('rdw_kw') or ai_kw
merged_ccm = base_info.get('rdw_ccm') or ai_ccm
fuel = data.get("fuel_type", base_info.get("rdw_fuel", "")).lower()
# Ha még kombinálva sincs meg a KW
if merged_kw == 0:
logger.warning("Sane-check figyelmeztetés: Hiányzó KW (se RDW, se AI). Engedélyezve részleges adatként.")
# Nem térünk vissza False-al, inkább mentsük el, amit eddig tudunk!
# Ha még kombinálva sincs meg a CCM (és nem elektromos)
if merged_ccm == 0 and "electric" not in fuel and "elektric" not in fuel and v_class != "trailer":
logger.warning("Sane-check figyelmeztetés: Hiányzó CCM egy belsőégésű motornál. Engedélyezve részleges adatként.")
# Ezt is átengedjük, hogy kitörjünk a végtelen hurokból.
return True
except Exception as e:
logger.error(f"Sane check hiba: {e}")
return False
async def process_single_record(self, db, record_id: int, base_info: dict, current_attempts: int):
try:
logger.info(f"🧠 AI dúsítás indul: {base_info['make']} {base_info['m_name']}")
# 1. LÉPÉS: AI Hívás (Rábízzuk az adatokat a modellre)
ai_data = await AIService.get_clean_vehicle_data(
base_info['make'],
base_info['m_name'],
base_info
)
# 2. LÉPÉS: Validáció (Ha az AI rossz adatot ad, NEM megyünk ki a webre, hanem dobjuk az aktát!)
if not ai_data or not self.is_data_sane(ai_data, base_info):
raise ValueError("Az AI hiányos adatot adott vissza vagy hallucinált.")
# 3. LÉPÉS: HIBRID MERGE (Az RDW adatok felülbírálják az AI-t a hatósági paramétereknél)
final_kw = base_info['rdw_kw'] if base_info['rdw_kw'] > 0 else (ai_data.get("kw") or 0)
final_ccm = base_info['rdw_ccm'] if base_info['rdw_ccm'] > 0 else (ai_data.get("ccm") or 0)
# Üzemanyag tisztítása
fuel_rdw = base_info.get('rdw_fuel', '')
final_fuel = fuel_rdw if fuel_rdw and fuel_rdw != "Unknown" else ai_data.get("fuel_type", "petrol")
final_engine = base_info['rdw_engine'] if base_info['rdw_engine'] else ai_data.get("engine_code", "Unknown")
final_euro = base_info['rdw_euro'] or ai_data.get("euro_classification")
final_cylinders = base_info['rdw_cylinders'] or ai_data.get("cylinders")
# 4. LÉPÉS: Mentés az Arany Katalógusba
clean_model = str(ai_data.get("marketing_name", base_info['m_name']))[:50].upper()
cat_stmt = text("""
INSERT INTO data.vehicle_catalog
(master_definition_id, make, model, power_kw, engine_capacity, fuel_type, factory_data)
VALUES (:m_id, :make, :model, :kw, :ccm, :fuel, :factory)
RETURNING id;
""")
await db.execute(cat_stmt, {
"m_id": record_id,
"make": base_info['make'].upper(),
"model": clean_model,
"kw": final_kw,
"ccm": final_ccm,
"fuel": final_fuel,
"factory": json.dumps(ai_data)
})
# 5. LÉPÉS: Staging tábla (VMD) lezárása
await db.execute(
update(VehicleModelDefinition)
.where(VehicleModelDefinition.id == record_id)
.values(
status="gold_enriched",
engine_capacity=final_ccm,
power_kw=final_kw,
fuel_type=final_fuel,
engine_code=final_engine,
euro_classification=final_euro,
cylinders=final_cylinders,
specifications=ai_data, # Elmentjük az AI teljes outputját a mestertáblába is
updated_at=func.now()
)
)
await db.commit()
logger.info(f"✨ ARANY REKORD KÉSZ: {base_info['make'].upper()} {clean_model}")
self.ai_calls_today += 1
except Exception as e:
await db.rollback()
logger.warning(f"⚠️ Alkimista hiba ({base_info['make']} {base_info['m_name']}): {e}")
# Visszaküldés a sorba vagy felfüggesztés
new_status = 'suspended' if current_attempts + 1 >= self.max_attempts else 'unverified'
await db.execute(
update(VehicleModelDefinition)
.where(VehicleModelDefinition.id == record_id)
.values(
attempts=current_attempts + 1,
last_error=str(e)[:200],
status=new_status,
updated_at=func.now()
)
)
await db.commit()
if new_status == 'unverified':
logger.info("♻️ Akta visszaküldve a Robot-2-nek (Kutató).")
async def run(self):
logger.info(f"🚀 Alchemist Pro HIBRID ONLINE (Atomi Zárolás Patch)")
while True:
if not self.check_budget():
logger.warning("💸 Napi AI limit kimerítve! Pihenés...")
await asyncio.sleep(3600); continue
try:
async with AsyncSessionLocal() as db:
# ATOMI ZÁROLÁS (A "Szent Grál" a race condition ellen)
# A Robot-1 (ACTIVE) és a Robot-2 (awaiting_ai_synthesis) aktáit is felveszi!
query = text("""
UPDATE data.vehicle_model_definitions
SET status = 'ai_synthesis_in_progress'
WHERE id = (
SELECT id FROM data.vehicle_model_definitions
WHERE status IN ('awaiting_ai_synthesis', 'ACTIVE')
AND attempts < :max_attempts
ORDER BY
CASE WHEN status = 'awaiting_ai_synthesis' THEN 1 ELSE 2 END,
priority_score DESC
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING id, make, marketing_name, vehicle_class, power_kw, engine_capacity,
fuel_type, engine_code, euro_classification, cylinders, raw_search_context, attempts;
""")
result = await db.execute(query, {"max_attempts": self.max_attempts})
task = result.fetchone()
await db.commit()
if task:
# Szétbontjuk a lekérdezett rekordot a base_info dict-be
r_id = task[0]
base_info = {
"make": task[1], "m_name": task[2], "v_type": task[3] or "car",
"rdw_kw": task[4] or 0, "rdw_ccm": task[5] or 0,
"rdw_fuel": task[6] or "petrol", "rdw_engine": task[7] or "",
"rdw_euro": task[8], "rdw_cylinders": task[9],
"web_context": task[10] or ""
}
attempts = task[11]
# Külön adatbázis kapcsolat a feldolgozáshoz (hosszú AI hívás miatt)
async with AsyncSessionLocal() as process_db:
await self.process_single_record(process_db, r_id, base_info, attempts)
# GPU hűtés / Ollama rate limit
await asyncio.sleep(random.uniform(1.5, 3.5))
else:
logger.info("😴 Nincs feldolgozandó akta, az Alkimista pihen...")
await asyncio.sleep(15)
except Exception as e:
logger.error(f"💀 Kritikus hiba a főciklusban: {e}")
await asyncio.sleep(10)
if __name__ == "__main__":
import os # Import az AI limit környezeti változóhoz
asyncio.run(TechEnricher().run())