diff --git a/.roo/rules-architect/architect.md b/.roo/rules-architect/architect.md index e704a3d..59b1651 100644 --- a/.roo/rules-architect/architect.md +++ b/.roo/rules-architect/architect.md @@ -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. 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 - 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. diff --git a/.roo/rules/02-architecture.md b/.roo/rules/02-architecture.md index 2082725..b4a73f4 100644 --- a/.roo/rules/02-architecture.md +++ b/.roo/rules/02-architecture.md @@ -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. -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro.py b/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro.py old mode 100755 new mode 100644 index 740db90..379b493 --- a/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro.py +++ b/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro.py @@ -4,6 +4,7 @@ import datetime import random import sys import json +import os from sqlalchemy import text, func, update, case from app.database import AsyncSessionLocal from app.models.vehicle_definitions import VehicleModelDefinition @@ -15,9 +16,9 @@ logger = logging.getLogger("Vehicle-Robot-3-Alchemist-Pro") 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. - Nincs felesleges webkeresés. Szigorú Sane-Check. + Nincs felesleges webkeresés. Szigorú, de intelligens Sane-Check. """ def __init__(self): self.max_attempts = 5 @@ -31,34 +32,38 @@ class TechEnricher: self.last_reset_date = datetime.date.today() return self.ai_calls_today < self.daily_ai_limit - def is_data_sane(self, data: dict, base_info: dict) -> bool: - """ Szigorított AI Hallucináció szűrő """ - if not data: return False - - try: - ccm = int(data.get("ccm", 0) or 0) - 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 + def validate_merged_data(self, merged_kw: int, merged_ccm: int, v_class: str, fuel: str, current_attempts: int) -> tuple[bool, str]: + """ Intelligens validáció a MERGE után. Visszaadja a státuszt és a hiba okát. """ + if merged_ccm > 18000: + return False, f"Irreális CCM érték ({merged_ccm})" + if merged_kw > 1500 and v_class != "truck": + return False, f"Irreális KW érték ({merged_kw})" - return True - except Exception as e: - logger.debug(f"Sane check hiba: {e}") - return False + # Ha hiányzik a KW + if merged_kw == 0: + if current_attempts < 3: + 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): + # 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: - 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) ai_data = await AIService.get_clean_vehicle_data( @@ -66,14 +71,14 @@ class TechEnricher: base_info['m_name'], 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!) - 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) + # 2. LÉPÉS: HIBRID MERGE (Még a validáció előtt!) + # 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 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) # Üzemanyag tisztítása 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_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 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 (master_definition_id, make, model, power_kw, engine_capacity, fuel_type, factory_data) 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; """) @@ -121,15 +131,18 @@ class TechEnricher: ) ) 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 except Exception as e: 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 - new_status = 'suspended' if current_attempts + 1 >= self.max_attempts else 'unverified' + # Ha elértük a limitet, KÉZI MODERÁCIÓRA küldjük, egyébként vissza a Kutatónak + 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( update(VehicleModelDefinition) @@ -138,15 +151,19 @@ class TechEnricher: attempts=current_attempts + 1, last_error=str(e)[:200], status=new_status, + specifications=review_data, # Kézi ellenőrzéshez beírjuk a törött adatot! updated_at=func.now() ) ) await db.commit() + 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): - 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: if not self.check_budget(): logger.warning("💸 Napi AI limit kimerítve! Pihenés...") @@ -155,7 +172,6 @@ class TechEnricher: 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' @@ -204,5 +220,4 @@ class TechEnricher: await asyncio.sleep(10) if __name__ == "__main__": - import os # Import az AI limit környezeti változóhoz asyncio.run(TechEnricher().run()) \ No newline at end of file