# /opt/docker/dev/service_finder/backend/app/workers/researcher_v2_1.py import asyncio import logging import warnings import os from datetime import datetime, timezone from typing import Optional, List from sqlalchemy import select, update, and_, func, or_, case from app.db.session import AsyncSessionLocal from app.models.vehicle_definitions import VehicleModelDefinition # DuckDuckGo search API hiba-elnyomás és import warnings.filterwarnings("ignore", category=RuntimeWarning, module='duckduckgo_search') from duckduckgo_search import DDGS # Logolás beállítása logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s') logger = logging.getLogger("Robot-Researcher-v2.1") class ResearcherBot: """ Robot 2.1: Az internet porszívója. Technikai adatokat gyűjt (DuckDuckGo), hogy előkészítse az AI dúsítást. Kihasználja a motorkódot és a gyártási évet a pontosabb találatokért. """ def __init__(self): self.batch_size = 5 # Egyszerre 5 járművet vesz ki self.max_parallel_queries = 3 # Párhuzamos keresések száma async def fetch_source(self, label: str, query: str) -> str: """ Egyedi forrás lekérése szálbiztos módon. """ try: def search(): with DDGS() as ddgs: # Az első 3 találat body részét gyűjtjük be kontextusnak results = ddgs.text(query, max_results=3) return [f"[{r.get('title', 'No Title')}] {r.get('body', '')}" for r in results] if results else [] results = await asyncio.to_thread(search) if not results: return f"=== SOURCE: {label} | STATUS: EMPTY ===\n\n" content = f"=== SOURCE: {label} | QUERY: {query} ===\n" content += "\n---\n".join(results) content += "\n=== END SOURCE ===\n\n" return content except Exception as e: logger.error(f"❌ Keresési hiba ({label}): {str(e)}") return f"=== SOURCE: {label} | ERROR: {str(e)} ===\n\n" async def research_vehicle(self, vehicle_id: int): """ Egyetlen jármű teljes körű átvilágítása. """ async with AsyncSessionLocal() as db: res = await db.execute(select(VehicleModelDefinition).where(VehicleModelDefinition.id == vehicle_id)) v = res.scalar_one_or_none() if not v: return make = v.make model = v.marketing_name engine = v.engine_code or "" year = f"{v.year_from}" if v.year_from else "" # Státusz zárolása v.status = 'research_in_progress' await db.commit() logger.info(f"🔎 Kutatás indul: {make} {model} (Motor: {engine}, Év: {year})") # Célzott keresési kulcsszavak (Multi-Channel stratégia) queries = [ ("TECH_SPECS", f"{make} {model} {engine} {year} technical specifications engine power kw torque"), ("MAINTENANCE", f"{make} {model} {engine} oil capacity coolant transmission fluid type capacity"), ("TIRES_PROD", f"{make} {model} {year} tire size load index production years status") ] # Párhuzamos forrásgyűjtés tasks = [self.fetch_source(label, q) for label, q in queries] search_results = await asyncio.gather(*tasks) full_context = "".join(search_results) async with AsyncSessionLocal() as db: if len(full_context.strip()) > 200: # Ha van elegendő kontextus await db.execute( update(VehicleModelDefinition) .where(VehicleModelDefinition.id == vehicle_id) .values( raw_search_context=full_context, status='awaiting_ai_synthesis', # Átadás a Robot 2.2-nek last_research_at=func.now(), attempts=VehicleModelDefinition.attempts + 1 ) ) logger.info(f"✅ Kontextus rögzítve: {make} {model}") else: # Sikertelen keresés, visszatesszük később await db.execute( update(VehicleModelDefinition) .where(VehicleModelDefinition.id == vehicle_id) .values( status='unverified', attempts=VehicleModelDefinition.attempts + 1, last_research_at=func.now() ) ) logger.warning(f"⚠️ Kevés adat: {make} {model} - Újrapróbálkozás később") await db.commit() async def run(self): logger.info("🚀 Robot 2.1 (Researcher) ONLINE - Cél: 407 Toyota feldolgozása") while True: async with AsyncSessionLocal() as db: # Prioritás: unverified autók előre priorities = case( (VehicleModelDefinition.make == 'TOYOTA', 1), else_=2 ) stmt = select(VehicleModelDefinition.id).where( or_(VehicleModelDefinition.status == 'unverified', VehicleModelDefinition.status == 'awaiting_research') ).order_by(priorities, VehicleModelDefinition.attempts.asc()).limit(self.batch_size) res = await db.execute(stmt) ids = [r[0] for r in res.fetchall()] if not ids: await asyncio.sleep(30) continue # Szekvenciális feldolgozás a rate-limit miatt for rid in ids: await self.research_vehicle(rid) await asyncio.sleep(5) # 5 másodperc szünet a keresések között if __name__ == "__main__": asyncio.run(ResearcherBot().run())