chore: Roo Code szabályok, módok és MCP szerverek konfigurálása a Masterbook alapján

This commit is contained in:
Kincses
2026-03-05 00:13:40 +01:00
parent 250f4f4b8f
commit 696db55fd8
16 changed files with 266 additions and 66 deletions

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@ vscode_config/
# Backup files # Backup files
*.bak *.bak
full_db_dump.sql full_db_dump.sql
.roo/.env.focalboard

View File

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

View File

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

View File

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

View File

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

32
Roo code_utasitas.md Normal file
View File

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

View File

@@ -2,7 +2,7 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import Optional 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.dialects.postgresql import UUID as PG_UUID
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func from sqlalchemy.sql import func
@@ -11,6 +11,7 @@ from app.db.base_class import Base
class Document(Base): class Document(Base):
""" NAS alapú dokumentumtár metaadatai. """ """ NAS alapú dokumentumtár metaadatai. """
__tablename__ = "documents" __tablename__ = "documents"
__table_args__ = {"schema": "data"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) 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' 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)) thumbnail_path: Mapped[Optional[str]] = mapped_column(String(255))
uploaded_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id")) 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()) 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)

View File

@@ -9,35 +9,42 @@ from sqlalchemy import select
from app.db.session import AsyncSessionLocal from app.db.session import AsyncSessionLocal
from app.models.system import SystemParameter 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: class AIService:
""" """
Sentinel Master AI Service 2.2. Sentinel Master AI Service 2.2 - Multi-Agent & Fallback Gateway.
Felelős az LLM hívásokért, prompt sablonok kezeléséért és az OCR feldolgozásért. Felelős az LLM hívásokért. Ha a helyi GPU (Ollama) túlterhelt,
Minden paraméter (modell, url, prompt, hőmérséklet) adminból vezérelt. automatikusan áttér a felhős (Groq/Gemini) megoldásokra.
""" """
@classmethod @classmethod
async def _execute_ai_call(cls, db, prompt: str, model_key: str = "text", images: Optional[List[str]] = None) -> Optional[Dict[str, Any]]: 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: 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 = { payload = {
"model": model_name, "model": model_name,
"prompt": prompt, "prompt": prompt,
@@ -45,60 +52,128 @@ class AIService:
"format": "json", "format": "json",
"options": {"temperature": float(temp)} "options": {"temperature": float(temp)}
} }
if images:
if images: # Llava/Vision támogatás
payload["images"] = images payload["images"] = images
# 3. HTTP HÍVÁS logger.info(f"🧠 Helyi AI ({model_name}) hívása indult...")
async with httpx.AsyncClient(timeout=float(timeout_val)) as client: async with httpx.AsyncClient(timeout=float(local_timeout)) as client:
response = await client.post(base_url, json=payload) response = await client.post(base_url, json=payload)
response.raise_for_status() response.raise_for_status()
raw_res = response.json().get("response", "{}") raw_res = response.json().get("response", "{}")
return json.loads(raw_res) 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: except json.JSONDecodeError as je:
logger.error(f"❌ AI JSON hiba (parszolási hiba): {je}") logger.error(f" Helyi AI JSON parszolási hiba: {je}. Váltás Fallback módba!")
return None
except Exception as e: 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 return None
@classmethod @classmethod
async def get_gold_data_from_research(cls, make: str, model: str, raw_context: str) -> Optional[Dict[str, Any]]: 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. """
Robot 3 (Alchemist) dúsító folyamata.
Kutatási adatokból csinál tiszta technikai adatlapot. if model_key == "vision" and images:
""" # ---------------------------------------------------------
async with AsyncSessionLocal() as db: # VISION FALLBACK: GOOGLE GEMINI 1.5 FLASH (Képelemzés/OCR)
template = await config.get_setting(db, "ai_prompt_gold_data", # ---------------------------------------------------------
default="Extract technical car data for {make} {model} from: {context}") 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) full_prompt = template.format(make=make, model=model, context=raw_context)
return await cls._execute_ai_call(db, full_prompt, model_key="text") return await cls._execute_ai_call(db, full_prompt, model_key="text")
@classmethod @classmethod
async def get_clean_vehicle_data(cls, make: str, raw_model: str, sources: Dict[str, Any]) -> Optional[Dict[str, Any]]: 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: async with AsyncSessionLocal() as db:
template = await config.get_setting(db, "ai_prompt_normalization", template = await config.get_setting(db, "ai_prompt_normalization", default="Normalize car model names: {make} {model}. Sources: {sources}")
default="Normalize car model names: {make} {model}. Sources: {sources}")
full_prompt = template.format(make=make, model=raw_model, sources=json.dumps(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") return await cls._execute_ai_call(db, full_prompt, model_key="text")
@classmethod @classmethod
async def process_ocr_document(cls, doc_type: str, base64_image: str) -> Optional[Dict[str, Any]]: 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: 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) 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]) return await cls._execute_ai_call(db, full_prompt, model_key="vision", images=[base64_image])

View File

@@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import uuid import uuid
from typing import List, Optional, Dict, Any, TYPE_CHECKING
from datetime import datetime from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_ from sqlalchemy import select, func, and_

View File

@@ -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 asyncio
import os import os
import logging import logging
@@ -71,13 +71,16 @@ class OCRRobot:
with open(temp_path, "rb") as f: with open(temp_path, "rb") as f:
image_bytes = f.read() 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) # 2. A MEGFELELŐ AI HÍVÁS (Vision modell hívása az AIService-en keresztül)
ocr_result = await AIService.get_clean_vehicle_data( doc_type_str = doc.doc_type or "general_document"
make="OCR_SCAN", ocr_result = await AIService.process_ocr_document(
raw_model=doc.parent_type, doc_type=doc_type_str,
v_type="document", base64_image=base64_image
sources={"image_data": "raw_scan"}
) )
if ocr_result: if ocr_result:

View File

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

View File

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

0
git init Normal file
View File