Architect és robot szabályok frissítése - Fixes #1

This commit is contained in:
Roo
2026-03-06 14:43:46 +00:00
parent 696db55fd8
commit 75975b2741
3 changed files with 66 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

@@ -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

@@ -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())