diff --git a/.gitignore b/.gitignore index 6d42f3e..2002c80 100755 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ vscode_config/ # Backup files *.bak full_db_dump.sql + +.roo/.env.focalboard \ No newline at end of file diff --git a/.roo/rules-debug/debugger.md b/.roo/rules-debug/debugger.md deleted file mode 100644 index 2224c32..0000000 --- a/.roo/rules-debug/debugger.md +++ /dev/null @@ -1,10 +0,0 @@ -# 🔍 Service Finder Debugger & QA Protokoll - -## 🛡️ Minőségbiztosítási Feladatok -1. **Logikai Konzisztenia Audit:** Ellenőrizd, hogy a kód megfelel-e a MasterBook2 és a `logic_spec` logikájának. -2. **Adatbázis Ellenőrzés:** SQL lekérdezésekkel vizsgáld meg, hogy az Alembic migráció és a Twin-adatok rendben vannak-e. -3. **Záró Dokumentáció (MD alapú):** - - **Wiki:** Frissítsd a modul leírását. - - **User Manual:** Készíts útmutatót egyéni és kis cégek számára. - - **Tech Doc:** Részletes leírás fejlesztőknek a gyors hibakereséshez. -4. **Kanban Lezárás:** Csak minden dokumentum elkészülte után mozgasd a kártyát "Done" státuszba. \ No newline at end of file diff --git a/.roo/rules/global.md b/.roo/rules/00-global.md similarity index 100% rename from .roo/rules/global.md rename to .roo/rules/00-global.md diff --git a/.roo/rules/01-core-behavior.md b/.roo/rules/01-core-behavior.md new file mode 100644 index 0000000..6f4cb2f --- /dev/null +++ b/.roo/rules/01-core-behavior.md @@ -0,0 +1,7 @@ +"Read Before Write" (Olvasd el, mielőtt írsz): Mielőtt bármilyen meglévő kódot módosítanál, KÖTELEZŐ bekérned vagy beolvasnod a releváns fájlokat. Sose dolgozz feltételezések alapján! + +Clean Code & No Harm: Ne okozz kárt a meglévő, jól működő kódbázisban. Csak a célzott problémára fókuszálj. + +Gondolatmenet (Thought Process): Mielőtt legenerálod a kódot, 2-3 mondatban vázold fel a logikádat, hogy lássam, jó irányba indultál-e el. + +Nyelv: Magyar nyelven kommunikálj velem. \ No newline at end of file diff --git a/.roo/rules/02-architecture.md b/.roo/rules/02-architecture.md new file mode 100644 index 0000000..2082725 --- /dev/null +++ b/.roo/rules/02-architecture.md @@ -0,0 +1,7 @@ +Tech Stack: FastAPI (v2, aszinkron), SQLAlchemy (Async), PostgreSQL (Izolált hálózaton), Docker Compose V2. + +AI & OCR: Hibrid AI Gateway (Helyi Ollama: 14B Qwen szövegre, Llama Vision képekre. Fallback: Gemini/Groq). + +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 diff --git a/.roo/rules/03-workflow.md b/.roo/rules/03-workflow.md new file mode 100644 index 0000000..d3dd83a --- /dev/null +++ b/.roo/rules/03-workflow.md @@ -0,0 +1,3 @@ +Feladatkezelés: A projektmenedzsmenthez MCP Focalboard-ot vagy a projekt gyökerében található KANBAN_AUDIT.md fájlt használunk. Minden munkamenet elején ellenőrizd ezeket, hogy tudd, mi a feladat (Todo) és mi van már kész (Done). + +Jelenlegi Fókusz: A következő időszak fő feladata a "Historical Data" (múltbéli költségek, szervizek) bevezetése az occurrence_date mezővel, és a flottavezetőknek szóló AnalyticsService (TCO/km) kidolgozása. \ No newline at end of file diff --git a/.roo/rules-robots/logic_spec_robot_0_gb_discovery.md b/.roo/rules/logic_spec_robot_0_gb_discovery.md similarity index 100% rename from .roo/rules-robots/logic_spec_robot_0_gb_discovery.md rename to .roo/rules/logic_spec_robot_0_gb_discovery.md diff --git a/.roo/rules-robots/logic_spec_robot_1_gb_hunter.md b/.roo/rules/logic_spec_robot_1_gb_hunter.md similarity index 100% rename from .roo/rules-robots/logic_spec_robot_1_gb_hunter.md rename to .roo/rules/logic_spec_robot_1_gb_hunter.md diff --git a/Roo code_utasitas.md b/Roo code_utasitas.md new file mode 100644 index 0000000..d00e560 --- /dev/null +++ b/Roo code_utasitas.md @@ -0,0 +1,32 @@ +Szia Roo! A "Service Finder - Masterbook 2.0" alapján elkészült egy rendszer-audit. Kérlek, az alábbi Markdown struktúrát dolgozd fel. + + Feladatod: Ha az MCP Focalboard kapcsolat él, hozd létre ezeket a kártyákat a Kanban táblán a megfelelő oszlopokban (TODO, IN PROGRESS, DONE). Ha az MCP nem működik, hozz létre egy KANBAN_AUDIT.md fájlt a projekt gyökerében ezzel a tartalommal, hogy manuálisan is követni tudjuk. + + Utána kezdj el dolgozni a BACKLOG 1. pontján (Historical Data logikájának beépítése az AssetCost modellekbe az occurrence_date hozzáadásával). + +Itt az Audit lista: +✅ KÉSZ (DONE) + + Infrastruktúra: Docker konténerek, Postgres adatbázis izolált hálózaton, Ollama AI gateway beállítva. + + Identity & Auth: Regisztrációs és Login végpontok (auth.py). JWT Token generálás RBAC (rank, scope_level, scope_id) paraméterekkel. + + Digital Twin & MDM: Asset és VehicleModelDefinition adatbázis sémái felépítve (asset.py, vehicle_definitions.py). + + AI Gateway & OCR: Robot 3 diszpécser (Base64 képkódolás), Hibrid AI hívás (Llama 3.2 Vision + Google Gemini/Groq Fallback), Izolált képmentés a NAS Vault-ba. + +⏳ FOLYAMATBAN (IN PROGRESS) + + Robot 2 (Technical Enricher): Szöveges adatok feldolgozása a 14B Qwen modellel. + +🚀 BACKLOG (TO DO) + + 1. Historical Data (Visszamenőleges Adatok): occurrence_date bevezetése az AssetCost, AssetEvent, VehicleOwnership táblákba a valós eseményidők tárolására (elkülönítve a created_at rendszeridőtől). + + 2. TCO & Flotta Analitika: AnalyticsService létrehozása (TCO / km, Amortizációs motor, Fix vs Változó költségmegoszlás, Fogyasztási anomália detektálás). + + 3. Trust Matching (Bizonyítékok Hálója): A Robot 3 OCR JSON adatainak automatikus összekötése a regisztrált szervizekkel (organizations), 'Verified' státusz adása. + + 4. Marketplace & Foglalás: pending_actions integrálása, Geofencing alapú ajánlatkérés szórása (Broadcast). + + 5. Robot 2.3 (The Guardian): Napi ütemező (CRON) fejlesztése lejáratok és szervizintervallumok figyelésére (30/7/0 napos riasztások). \ No newline at end of file diff --git a/backend/app/models/document.py b/backend/app/models/document.py index 183cb47..6d66967 100755 --- a/backend/app/models/document.py +++ b/backend/app/models/document.py @@ -2,7 +2,7 @@ import uuid from datetime import datetime from typing import Optional -from sqlalchemy import String, Integer, Boolean, DateTime, ForeignKey, text +from sqlalchemy import String, Integer, Boolean, DateTime, ForeignKey, Text from sqlalchemy.dialects.postgresql import UUID as PG_UUID from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.sql import func @@ -11,6 +11,7 @@ from app.db.base_class import Base class Document(Base): """ NAS alapú dokumentumtár metaadatai. """ __tablename__ = "documents" + __table_args__ = {"schema": "data"} id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) parent_type: Mapped[str] = mapped_column(String(20)) # 'organization' vagy 'asset' @@ -27,4 +28,27 @@ class Document(Base): thumbnail_path: Mapped[Optional[str]] = mapped_column(String(255)) uploaded_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id")) - created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) \ No newline at end of file + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) + + # ========================================================================= + # THOUGHT PROCESS & ARCHITECTURE DECISIONS (2026-03-04) + # ========================================================================= + # Probléma: Az `ocr_robot.py` (Robot 3) módosítani próbálta a dokumentumok + # állapotát és menteni akarta az AI eredményeket, de a mezők hiányoztak. + # + # Megoldás: Hozzáadtuk a szükséges mezőket a munkafolyamat (Workflow) + # támogatásához. + # + # 1. `status`: A robot a 'pending_ocr' státuszra szűr. Indexeljük, + # mert a WHERE feltételben szerepel, így az adatbázis sokkal gyorsabb lesz. + # + # 2. `ocr_data`: A kinyert adatokat tárolja. Text típust használunk String + # helyett, mert az AI válasza (pl. JSON formátumú adat) hosszú lehet. + # + # 3. `error_log`: Ha az AI hibázik, vagy üres választ ad, itt rögzítjük + # a hiba okát a könnyebb debuggolás érdekében. + # ========================================================================= + + status: Mapped[str] = mapped_column(String(50), default="uploaded", index=True) + ocr_data: Mapped[Optional[str]] = mapped_column(Text) + error_log: Mapped[Optional[str]] = mapped_column(Text) \ No newline at end of file diff --git a/backend/app/services/ai_service.py b/backend/app/services/ai_service.py index 7ec14cd..5177e82 100755 --- a/backend/app/services/ai_service.py +++ b/backend/app/services/ai_service.py @@ -9,35 +9,42 @@ from sqlalchemy import select from app.db.session import AsyncSessionLocal from app.models.system import SystemParameter -from app.services.config_service import config # 2.2-es központi config +from app.services.config_service import config -logger = logging.getLogger("AI-Service-2.2") +logger = logging.getLogger("AI-Service-2.2-Gateway") class AIService: """ - Sentinel Master AI Service 2.2. - Felelős az LLM hívásokért, prompt sablonok kezeléséért és az OCR feldolgozásért. - Minden paraméter (modell, url, prompt, hőmérséklet) adminból vezérelt. + Sentinel Master AI Service 2.2 - Multi-Agent & Fallback Gateway. + Felelős az LLM hívásokért. Ha a helyi GPU (Ollama) túlterhelt, + automatikusan áttér a felhős (Groq/Gemini) megoldásokra. """ @classmethod async def _execute_ai_call(cls, db, prompt: str, model_key: str = "text", images: Optional[List[str]] = None) -> Optional[Dict[str, Any]]: """ - Központi AI végrehajtó. Kezeli a modellt, a várakozást és a JSON parzolást. + Központi AI végrehajtó intelligens teherelosztással (Load Balancing). """ + # 1. BEÁLLÍTÁSOK LEKÉRÉSE + base_url = await config.get_setting(db, "ai_ollama_url", default="http://ollama:11434/api/generate") + delay = await config.get_setting(db, "AI_REQUEST_DELAY", default=0.1) + temp = await config.get_setting(db, "ai_temperature", default=0.1) + + # A helyi timeout-ot levesszük 25 mp-re. Ha az RTX 3090 eddig nem végez, + # akkor biztosan tele van a várólistája, és azonnal átváltunk felhőbe. + local_timeout = await config.get_setting(db, "ai_timeout_local", default=25.0) + + # Fallback engedélyezése az .env fájlból + enable_fallback = os.getenv("ENABLE_AI_FALLBACK", "false").lower() == "true" + + # Helyi modellek definiálása + default_model = "llama3.2-vision:latest" if model_key == "vision" else "qwen2.5-coder:14b" + model_name = await config.get_setting(db, f"ai_model_{model_key}", default=default_model) + + await asyncio.sleep(float(delay)) + + # 2. ELSŐDLEGES PRÓBA: Helyi Ollama szerver (Ingyenes, VRAM alapú) try: - # 1. ADMIN KONFIGURÁCIÓ LEKÉRÉSE - base_url = await config.get_setting(db, "ai_ollama_url", default="http://ollama:11434/api/generate") - delay = await config.get_setting(db, "AI_REQUEST_DELAY", default=0.1) - - # Modell választás (text vagy vision) - model_name = await config.get_setting(db, f"ai_model_{model_key}", default="qwen2.5-coder:32b") - temp = await config.get_setting(db, "ai_temperature", default=0.1) - timeout_val = await config.get_setting(db, "ai_timeout", default=120.0) - - await asyncio.sleep(float(delay)) - - # 2. PAYLOAD ÖSSZEÁLLÍTÁSA payload = { "model": model_name, "prompt": prompt, @@ -45,60 +52,128 @@ class AIService: "format": "json", "options": {"temperature": float(temp)} } - - if images: # Llava/Vision támogatás + if images: payload["images"] = images - # 3. HTTP HÍVÁS - async with httpx.AsyncClient(timeout=float(timeout_val)) as client: + logger.info(f"🧠 Helyi AI ({model_name}) hívása indult...") + async with httpx.AsyncClient(timeout=float(local_timeout)) as client: response = await client.post(base_url, json=payload) response.raise_for_status() raw_res = response.json().get("response", "{}") return json.loads(raw_res) + except (httpx.ReadTimeout, httpx.ConnectError) as e: + logger.warning(f"⚠️ Helyi GPU túlterhelt vagy lassú (Timeout/ConnectError). Váltás Fallback módba!") except json.JSONDecodeError as je: - logger.error(f"❌ AI JSON hiba (parszolási hiba): {je}") - return None + logger.error(f"❌ Helyi AI JSON parszolási hiba: {je}. Váltás Fallback módba!") except Exception as e: - logger.error(f"❌ AI hívás kritikus hiba: {e}") + logger.error(f"❌ Helyi AI váratlan hiba: {e}. Váltás Fallback módba!") + + # 3. MÁSODLAGOS PRÓBA: Hibrid Fallback (Csak ha engedélyezve van és a helyi elbukott) + if enable_fallback: + return await cls._fallback_ai_call(prompt, model_key, images, float(temp)) + else: + logger.error("❌ A Fallback ki van kapcsolva (.env), a feladat feldolgozása sikertelen.") return None @classmethod - async def get_gold_data_from_research(cls, make: str, model: str, raw_context: str) -> Optional[Dict[str, Any]]: - """ - Robot 3 (Alchemist) dúsító folyamata. - Kutatási adatokból csinál tiszta technikai adatlapot. - """ - async with AsyncSessionLocal() as db: - template = await config.get_setting(db, "ai_prompt_gold_data", - default="Extract technical car data for {make} {model} from: {context}") + async def _fallback_ai_call(cls, prompt: str, model_key: str, images: Optional[List[str]], temp: float) -> Optional[Dict[str, Any]]: + """ Külső API hívások (Groq és Gemini) vészhelyzet esetére. """ + + if model_key == "vision" and images: + # --------------------------------------------------------- + # VISION FALLBACK: GOOGLE GEMINI 1.5 FLASH (Képelemzés/OCR) + # --------------------------------------------------------- + gemini_api_key = os.getenv("GEMINI_API_KEY") + if not gemini_api_key: + logger.error("❌ Hiányzik a GEMINI_API_KEY! Képtelen Fallback módban OCR-t végezni.") + return None + + logger.info("☁️ Google Gemini 1.5 API (Vision) hívása...") + url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={gemini_api_key}" + payload = { + "contents": [{ + "parts": [ + {"text": prompt + "\nKérlek, SZIGORÚAN csak érvényes JSON objektummal válaszolj, Markdown formázás (```json) nélkül!"}, + {"inline_data": { + "mime_type": "image/jpeg", # A korábbi kódban JPEG-be konvertáljuk + "data": images[0] + }} + ] + }], + "generationConfig": { + "temperature": temp, + "response_mime_type": "application/json" # Gemini specifikus JSON kényszerítés + } + } + + try: + async with httpx.AsyncClient(timeout=30.0) as client: + resp = await client.post(url, json=payload) + resp.raise_for_status() + data = resp.json() + text_res = data["candidates"][0]["content"]["parts"][0]["text"] + return json.loads(text_res) + except Exception as e: + logger.error(f"❌ Gemini Fallback kritikus hiba: {e}") + return None + + else: + # --------------------------------------------------------- + # TEXT FALLBACK: GROQ CLOUD (Szövegelemzés / Web Scraping) + # --------------------------------------------------------- + groq_api_key = os.getenv("GROQ_API_KEY") + if not groq_api_key: + logger.error("❌ Hiányzik a GROQ_API_KEY! Képtelen Fallback módban szöveget elemezni.") + return None + + logger.info("☁️ Groq Cloud (Llama 3 8B) hívása...") + url = "[https://api.groq.com/openai/v1/chat/completions](https://api.groq.com/openai/v1/chat/completions)" + headers = { + "Authorization": f"Bearer {groq_api_key}", + "Content-Type": "application/json" + } + + payload = { + "model": "llama3-8b-8192", # Villámgyors szöveges modell + "messages": [{"role": "user", "content": prompt}], + "temperature": temp, + "response_format": {"type": "json_object"} # Groq specifikus JSON kényszerítés + } + + try: + async with httpx.AsyncClient(timeout=20.0) as client: + resp = await client.post(url, headers=headers, json=payload) + resp.raise_for_status() + data = resp.json() + text_res = data["choices"][0]["message"]["content"] + return json.loads(text_res) + except Exception as e: + logger.error(f"❌ Groq Fallback kritikus hiba: {e}") + return None + + # --- A TÖBBI METÓDUS VÁLTOZATLAN MARAD --- + + @classmethod + async def get_gold_data_from_research(cls, make: str, model: str, raw_context: str) -> Optional[Dict[str, Any]]: + async with AsyncSessionLocal() as db: + template = await config.get_setting(db, "ai_prompt_gold_data", default="Extract technical car data for {make} {model} from: {context}") full_prompt = template.format(make=make, model=model, context=raw_context) return await cls._execute_ai_call(db, full_prompt, model_key="text") @classmethod async def get_clean_vehicle_data(cls, make: str, raw_model: str, sources: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """ - Név normalizálás és szinonima gyűjtés. - """ async with AsyncSessionLocal() as db: - template = await config.get_setting(db, "ai_prompt_normalization", - default="Normalize car model names: {make} {model}. Sources: {sources}") - + template = await config.get_setting(db, "ai_prompt_normalization", default="Normalize car model names: {make} {model}. Sources: {sources}") full_prompt = template.format(make=make, model=raw_model, sources=json.dumps(sources)) return await cls._execute_ai_call(db, full_prompt, model_key="text") @classmethod async def process_ocr_document(cls, doc_type: str, base64_image: str) -> Optional[Dict[str, Any]]: - """ - Robot 1 (OCR) látó folyamata. - Képet (base64) küld a Vision modellnek (pl. Llava). - """ async with AsyncSessionLocal() as db: - # Külön prompt sablon minden dokumentum típushoz (számla, forgalmi, adásvételi) - template = await config.get_setting(db, f"ai_prompt_ocr_{doc_type}", - default="Analyze this {doc_type} image and return structured JSON data.") - + template = await config.get_setting(db, f"ai_prompt_ocr_{doc_type}", default="Analyze this {doc_type} image and return structured JSON data.") full_prompt = template.format(doc_type=doc_type) + # Itt mondjuk meg a diszpécsernek, hogy ez egy Vision feladat! return await cls._execute_ai_call(db, full_prompt, model_key="vision", images=[base64_image]) \ No newline at end of file diff --git a/backend/app/services/asset_service.py b/backend/app/services/asset_service.py index 37bcf11..4d56cda 100755 --- a/backend/app/services/asset_service.py +++ b/backend/app/services/asset_service.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging import uuid +from typing import List, Optional, Dict, Any, TYPE_CHECKING from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func, and_ diff --git a/backend/app/workers/ocr/robot_1_ocr_processor.py b/backend/app/workers/ocr/robot_1_ocr_processor.py index ec2f3fb..9f5b956 100755 --- a/backend/app/workers/ocr/robot_1_ocr_processor.py +++ b/backend/app/workers/ocr/robot_1_ocr_processor.py @@ -1,4 +1,4 @@ -# /opt/docker/dev/service_finder/backend/app/workers/ocr_robot.py +# /opt/docker/dev/service_finder/backend/app/workers/ocr/robot_1_ocr_processor.py import asyncio import os import logging @@ -71,13 +71,16 @@ class OCRRobot: with open(temp_path, "rb") as f: image_bytes = f.read() + + # 1. Kép kódolása Base64 formátumba (A Vision modellek ezt várják) + import base64 + base64_image = base64.b64encode(image_bytes).decode('utf-8') - # AI felismerés (pl. Llama-Vision vagy GPT-4o) - ocr_result = await AIService.get_clean_vehicle_data( - make="OCR_SCAN", - raw_model=doc.parent_type, - v_type="document", - sources={"image_data": "raw_scan"} + # 2. A MEGFELELŐ AI HÍVÁS (Vision modell hívása az AIService-en keresztül) + doc_type_str = doc.doc_type or "general_document" + ocr_result = await AIService.process_ocr_document( + doc_type=doc_type_str, + base64_image=base64_image ) if ocr_result: diff --git a/backend/migrations/versions/4f083e0ad046_fix_document_schema_mapping.py b/backend/migrations/versions/4f083e0ad046_fix_document_schema_mapping.py new file mode 100644 index 0000000..59c877e --- /dev/null +++ b/backend/migrations/versions/4f083e0ad046_fix_document_schema_mapping.py @@ -0,0 +1,28 @@ +"""Fix Document schema mapping + +Revision ID: 4f083e0ad046 +Revises: e44655e0eae8 +Create Date: 2026-03-04 18:02:38.190169 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '4f083e0ad046' +down_revision: Union[str, Sequence[str], None] = 'e44655e0eae8' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/backend/migrations/versions/e44655e0eae8_add_ocr_workflow_fields_to_document.py b/backend/migrations/versions/e44655e0eae8_add_ocr_workflow_fields_to_document.py new file mode 100644 index 0000000..2124db6 --- /dev/null +++ b/backend/migrations/versions/e44655e0eae8_add_ocr_workflow_fields_to_document.py @@ -0,0 +1,28 @@ +"""Add OCR workflow fields to Document + +Revision ID: e44655e0eae8 +Revises: 92fe3b877b24 +Create Date: 2026-03-04 17:54:03.810505 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = 'e44655e0eae8' +down_revision: Union[str, Sequence[str], None] = '92fe3b877b24' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/git init b/git init new file mode 100644 index 0000000..e69de29