diff --git a/.roo/rules-architect/wiki-specialist.md b/.roo/rules-architect/wiki-specialist.md new file mode 100644 index 0000000..909c003 --- /dev/null +++ b/.roo/rules-architect/wiki-specialist.md @@ -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. \ No newline at end of file diff --git a/.roo/rules/04-debug-protocol.md b/.roo/rules/04-debug-protocol.md new file mode 100644 index 0000000..b8dc3d7 --- /dev/null +++ b/.roo/rules/04-debug-protocol.md @@ -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 `. + - 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 ` 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. \ No newline at end of file diff --git a/.roo/rules/05_Kanban_Workflow.md b/.roo/rules/05_Kanban_Workflow.md new file mode 100644 index 0000000..af71c4e --- /dev/null +++ b/.roo/rules/05_Kanban_Workflow.md @@ -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. \ No newline at end of file diff --git a/.roomodes b/.roomodes new file mode 100644 index 0000000..cc2e960 --- /dev/null +++ b/.roomodes @@ -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"] + } + ] +} \ No newline at end of file diff --git a/backend/app/test_outside/run_all_checks.sh b/backend/app/test_outside/run_all_checks.sh new file mode 100644 index 0000000..301f200 --- /dev/null +++ b/backend/app/test_outside/run_all_checks.sh @@ -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 \ No newline at end of file diff --git a/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro_1.0.1.py b/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro_1.0.1.py new file mode 100755 index 0000000..ab4df65 --- /dev/null +++ b/backend/app/workers/vehicle/vehicle_robot_3_alchemist_pro_1.0.1.py @@ -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()) \ No newline at end of file