Initial commit: Robot ökoszisztéma v2.0 - Stabilizált jármű és szerviz robotok

This commit is contained in:
Kincses
2026-03-04 02:03:03 +01:00
commit 250f4f4b8f
7942 changed files with 449625 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
# 🛠️ Internal Diagnostic Tools
Ez a mappa a rendszer stabilitását ellenőrző szkripteket tartalmazza.
Futtatásuk a konténeren belül javasolt.
### 1. Schema Sync (`compare_schema.py`)
**Mikor használd:** Ha új oszlopot adtál a modellhez, vagy nem indul el a rendszer DB hiba miatt.
**Futtatás:** `docker compose exec api python -m app.tests_internal.compare_schema`
docker compose exec api python -m app.tests_internal.diagnostics.compare_schema
### 2. API Health (`check_api.py`)
**Mikor használd:** Refaktorálás után. Ellenőrzi, hogy az összes API "kapu" nyitva van-e.
**Futtatás:** `docker compose exec api python -m app.tests_internal.check_api`
docker compose exec api python -m app.tests_internal.diagnostics.check_api
### 3. Geo Search Test (`test_postgis.py`)
**Mikor használd:** Ha a szervizkereső nem ad vissza eredményt, vagy SQL hibát dob.
**Futtatás:** `docker compose exec api python -m app.tests_internal.test_postgis`
### 3. Rendszerdiagnosztika (`diagnose_system.py`)
- **Cél:** Mély ellenőrzés: DB kapcsolat, i18n szótárak, Master Data mezők és konfigurációk.
- **Mikor használd:** Telepítés után, vagy ha a rendszer "furcsán" viselkedik (pl. angolul beszél magyar helyett).
- **Indítás:**
docker compose exec api python -m app.tests_internal.diagnostics.diagnose_system

View File

View File

@@ -0,0 +1,53 @@
# /app/tests_internal/diagnostics/check_api.py
import requests
import json
# THOUGHT PROCESS:
# 1. Az API konténeren belül futva a localhost:8000 a célpont.
# 2. Csak olyan végpontokat tesztelünk, amik szerepelnek a v1/api.py-ben.
# 3. A 401/405 kódokat elfogadjuk 'OK'-nak, mert azt jelentik, hogy a szerver
# látja az útvonalat, csak hitelesítést vagy más HTTP metódust vár.
# 4. A 404 azt jelenti, hogy a router nincs jól regisztrálva.
# 5. A 500 azt jelenti, hogy a kód elszállt (pl. AttributeError a main.py-ban).
base_url = 'http://localhost:8000'
# A valós, api.py-ben definiált útvonalak:
tests = [
('Health Check', '/health', 'GET'),
('Auth Login (Entry)', '/api/v1/auth/login', 'POST'), # Létezik
('Catalog Makes', '/api/v1/catalog/makes', 'GET'), # Létezik
('Service Hunt', '/api/v1/services/hunt', 'POST'), # Létezik
('Admin Health', '/api/v1/admin/health-monitor', 'GET'), # Létezik
('My Organizations', '/api/v1/organizations/my', 'GET') # Létezik
]
print('\n--- 🧪 API VÉGPONT DIAGNOSZTIKA ---')
print(f"{'Végpont neve':20} | {'Útvonal':25} | {'Állapot'}")
print("-" * 65)
for name, endpoint, method in tests:
try:
url = f"{base_url}{endpoint}"
# A POST hívásokhoz üres adatot küldünk, hogy ne 422-t kapjunk a hiányzó body miatt
resp = requests.request(method, url, timeout=5, json={})
# Logika:
# 200: Tökéletes
# 401: Él, de login kell (JÓ)
# 405: Él, de pl. GET helyett POST kell (JÓ - az útvonal létezik)
# 422: Él, de hiányoznak a küldött adatok (JÓ - a validáció működik)
if resp.status_code in [200, 401, 405, 422]:
status_msg = f"✅ OK ({resp.status_code})"
elif resp.status_code == 404:
status_msg = f"❌ HIÁNYZIK (404)"
else:
status_msg = f"🔥 SZERVER HIBA ({resp.status_code})"
print(f"{name:20} | {endpoint:25} | {status_msg}")
except Exception as e:
print(f"{name:20} | {endpoint:25} | 🔌 ELÉRHETETLEN")
print("\n💡 Megjegyzés: Ha a Health Check továbbra is 500, az a main.py-ban lévő elírás miatt van.")

View File

@@ -0,0 +1,66 @@
# /opt/docker/dev/service_finder/backend/app/tests_internal/diagnostics/compare_schema.py
import asyncio
import sys
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import inspect
from app.database import Base
from app.core.config import settings
try:
import app.models
except ImportError as e:
print(f"❌ KRITIKUS IMPORT HIBA: {e}")
sys.exit(1)
async def compare():
""" Diagnosztika minden sémára: identity, data, system. """
print(f"🔗 Kapcsolódás az adatbázishoz...")
engine = create_async_engine(str(settings.SQLALCHEMY_DATABASE_URI))
def get_diff(connection):
inspector = inspect(connection)
# Ezeket a sémákat ellenőrizzük
schemas = ["identity", "data", "system"]
all_db_schemas = inspector.get_schema_names()
mismatches = 0
for sc in schemas:
if sc not in all_db_schemas:
print(f"❌ HIBA: A(z) '{sc}' séma nem létezik!")
continue
db_tables = inspector.get_table_names(schema=sc)
# Begyűjtjük a modelleket, amik ehhez a sémához tartoznak
model_tables = [t.name for t in Base.metadata.sorted_tables if t.schema == sc]
print(f"\n--- 🔍 DIAGNOSZTIKA: '{sc}' séma ({len(db_tables)} tábla a DB-ben) ---")
for mt in model_tables:
if mt not in db_tables:
print(f"❌ HIÁNYZÓ TÁBLA: {sc}.{mt}")
mismatches += 1
else:
db_cols = {c['name']: c for c in inspector.get_columns(mt, schema=sc)}
# Kikeressük a modellt a metadata-ból
table_key = f"{sc}.{mt}"
model_cols = Base.metadata.tables[table_key].columns
missing_cols = [m.name for m in model_cols if m.name not in db_cols]
if missing_cols:
print(f"⚠️ {mt:25} | HIÁNYZÓ OSZLOPOK: {missing_cols}")
mismatches += 1
else:
print(f"{mt:25} | Rendben.")
return mismatches
try:
async with engine.connect() as conn:
err_count = await conn.run_sync(get_diff)
print(f"\n--- Összegzés: {err_count} eltérés található. ---\n")
except Exception as e:
print(f"❌ HIBA: {e}")
finally:
await engine.dispose()
if __name__ == "__main__":
asyncio.run(compare())

View File

@@ -0,0 +1,157 @@
# /opt/docker/dev/service_finder/backend/app/tests_internal/diagnostics/diagnose_system.py
"""
🛰️ SENTINEL SYSTEM DIAGNOSTICS - MB2.0 (2026)
---------------------------------------------
CÉL: A rendszer mélyszintű integritásának és működőképességének auditálása.
FŐBB TESZTEK:
1. Adatbázis Kapcsolat: PostgreSQL aszinkron elérés ellenőrzése.
2. Séma Integritás: Kritikus Master Data táblák és mezők meglétének vizsgálata.
3. Rendszer Paraméterek: A Sentinel központi konfigurációs táblájának ellenőrzése.
4. i18n Motor: Nyelvi gyorsítótár (Cache) és fordítási mechanizmus tesztje.
5. Robot Pipeline: Staging (Hunter) és Gold (Catalog) rekordok számlálása.
FUTTATÁS:
docker compose exec api python -m app.tests_internal.diagnostics.diagnose_system
"""
import asyncio
import sys
import logging
from datetime import datetime
from sqlalchemy import text, select, func
# 🛠️ KRITIKUS IMPORT KÖRNYEZET ELLENŐRZÉSE
try:
from app.core.config import settings
# Megjegyzés: A projekt struktúrájától függően AsyncSessionLocal az app.database-ben van
from app.database import AsyncSessionLocal
from app.services.translation_service import translation_service
from app.models.system import SystemParameter
from app.models.identity import User
from app.models.organization import Organization
from app.models.asset import AssetCatalog
from app.models.vehicle_definitions import VehicleModelDefinition
except ImportError as e:
print(f"\n❌ [KRITIKUS HIBA] Az importálás nem sikerült: {e}")
print("💡 Javaslat: Ellenőrizd a PYTHONPATH-t és a __init__.py fájlok meglétét!")
sys.exit(1)
# SQL logolás némítása a tiszta kimenet érdekében
logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)
async def diagnose():
start_time = datetime.now()
print("\n" + ""*70)
print(f"🛰️ SENTINEL SYSTEM DIAGNOSTICS - MB2.0 | {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(""*70 + "\n")
async with AsyncSessionLocal() as session:
# --- 1. CSATLAKOZÁS ÉS ADATBÁZIS PING ---
print("1⃣ Kapcsolódási teszt...")
try:
await session.execute(text("SELECT 1"))
print(" [✅ OK] PostgreSQL aszinkron kapcsolat aktív.")
except Exception as e:
print(f" [❌ HIBA] Nem sikerült kapcsolódni az adatbázishoz: {e}")
return
# --- 2. SÉMA INTEGRITÁS (Master Data Audit) ---
print("\n2⃣ Séma integritás ellenőrzése (Kritikus mezők)...")
# Tábla neve (sémával) | Elvárt oszlopok listája
tables_to_check = [
("identity.users", ["preferred_language", "scope_id", "is_active"]),
("data.organizations", ["org_type", "folder_slug", "is_active"]),
("data.assets", ["owner_org_id", "catalog_id", "vin"]),
# "asset_catalog" helyett "vehicle_catalog"
("data.vehicle_catalog", ["make", "model", "factory_data"]),
("data.vehicle_model_definitions", ["status", "raw_search_context"])
]
for table, columns in tables_to_check:
try:
schema_name, table_name = table.split('.')
query = text(f"""
SELECT column_name FROM information_schema.columns
WHERE table_schema = '{schema_name}' AND table_name = '{table_name}';
""")
res = await session.execute(query)
existing_cols = [row[0] for row in res.fetchall()]
if not existing_cols:
print(f" [❌ HIBA] A tábla NEM létezik: {table}")
continue
missing = [c for c in columns if c not in existing_cols]
if not missing:
print(f" [✅ OK] {table:35} (Minden mező rendben)")
else:
print(f" [⚠️ HIÁNY] {table:35} | Hiányzó mezők: {', '.join(missing)}")
except Exception as e:
print(f" [❌ HIBA] Hiba a(z) {table} ellenőrzésekor: {e}")
# --- 3. RENDSZER PARAMÉTEREK (Sentinel Config) ---
print("\n3⃣ System Parameters (Sentinel Config) ellenőrzése...")
try:
res = await session.execute(select(SystemParameter))
params = res.scalars().all()
if params:
print(f" [✅ OK] Talált paraméterek: {len(params)} db")
# Kritikus kulcsok, amiknek illik lenniük
critical_keys = ["SECURITY_MAX_RECORDS_PER_HOUR", "VEHICLE_LIMIT"]
existing_keys = [p.key for p in params]
for ck in critical_keys:
status = "✔️" if ck in existing_keys else "❌ (Hiányzik)"
print(f" {status} {ck}")
else:
print(" [⚠️ FIGYELEM] A system_parameters tábla üres! Futtasd a seedert.")
except Exception as e:
print(f" [❌ HIBA] SystemParameter lekérdezési hiba: {e}")
# --- 4. i18n ÉS CACHE MOTOR ---
print("\n4⃣ Nyelvi motor és i18n Cache ellenőrzése...")
try:
# Gyorsítótár frissítése az adatbázisból
await translation_service.load_cache(session)
test_key = "COMMON.SAVE"
test_val = translation_service.get_text(test_key, "hu")
# Ha a visszakapott érték nem egyezik a kulccsal, akkor van fordítás
if test_val and test_val != f"[{test_key}]":
print(f" [✅ OK] Fordítás sikeres (HU): '{test_key}' -> '{test_val}'")
else:
print(f" [❌ HIBA] Fordítás nem működik. Nincs betöltött adat vagy hibás a cache.")
except Exception as e:
print(f" [❌ HIBA] Nyelvi motor hiba: {e}")
# --- 5. ROBOT ELŐKÉSZÜLETEK (Pipeline MDM) ---
print("\n5⃣ Robot Pipeline (MDM Staging) állapot...")
try:
# Hunter robot eredményei (nyers adatok)
res_hunter = await session.execute(
select(func.count(VehicleModelDefinition.id)).where(VehicleModelDefinition.status == 'unverified')
)
unverified_count = res_hunter.scalar() or 0
# Catalog robot eredményei (tisztított adatok)
res_gold = await session.execute(select(func.count(AssetCatalog.id)))
gold_count = res_gold.scalar() or 0
print(f" [📊 STAT] Feldolgozatlan rekordok (Staging): {unverified_count} db")
print(f" [📊 STAT] Validált arany rekordok (Catalog): {gold_count} db")
except Exception as e:
print(f" [❌ HIBA] Robot-statisztika hiba: {e}")
duration = (datetime.now() - start_time).total_seconds()
print("\n" + ""*70)
print(f"🏁 DIAGNOSZTIKA BEFEJEZŐDÖTT | Időtartam: {duration:.2f} mp")
print(""*70 + "\n")
if __name__ == "__main__":
try:
asyncio.run(diagnose())
except KeyboardInterrupt:
print("\n🛑 Diagnosztika megszakítva a felhasználó által.")
sys.exit(0)

View File

View File

@@ -0,0 +1,82 @@
# /opt/docker/dev/service_finder/backend/app/final_admin_fix.py
import asyncio
import uuid
from sqlalchemy import text, select
from app.database import AsyncSessionLocal
from app.models.identity import User, Person, UserRole
from app.core.security import get_password_hash
async def run_fix():
print("\n" + ""*50)
print("🛠️ ADMIN RENDSZERJAVÍTÁS ÉS INICIALIZÁLÁS (MB2.0)")
print(""*50)
async with AsyncSessionLocal() as db:
# 1. LOGIKA: Séma ellenőrzése az 'identity' névtérben
# Az MB2.0-ban a felhasználók már nem a 'data', hanem az 'identity' sémában vannak.
check_query = text("""
SELECT column_name FROM information_schema.columns
WHERE table_schema = 'identity' AND table_name = 'users'
""")
res = await db.execute(check_query)
cols = [r[0] for r in res.fetchall()]
if not cols:
print("❌ HIBA: Az 'identity.users' tábla nem található. Futtasd az Alembic migrációt!")
return
if "hashed_password" not in cols:
print("❌ HIBA: A 'hashed_password' oszlop hiányzik. Az adatbázis sémája elavult.")
return
# 2. LOGIKA: Admin keresése
admin_email = "admin@profibot.hu"
stmt = select(User).where(User.email == admin_email)
existing_res = await db.execute(stmt)
existing_admin = existing_res.scalar_one_or_none()
if existing_admin:
print(f"⚠️ Információ: A(z) {admin_email} felhasználó már létezik.")
# Opcionális: Jelszó kényszerített frissítése, ha elfelejtetted
# existing_admin.hashed_password = get_password_hash("Admin123!")
# await db.commit()
else:
try:
# 3. LOGIKA: Person és User létrehozása (MB2.0 Standard)
# Előbb létrehozzuk a fizikai személyt
new_person = Person(
id_uuid=uuid.uuid4(),
first_name="Rendszer",
last_name="Adminisztrátor",
is_active=True
)
db.add(new_person)
await db.flush() # ID lekérése a mentés előtt
# Létrehozzuk a felhasználói fiókot az Admin role-al
new_admin = User(
email=admin_email,
hashed_password=get_password_hash("Admin123!"),
person_id=new_person.id,
role=UserRole.superadmin, # MB2.0 enum érték
is_active=True,
is_deleted=False,
preferred_language="hu"
)
db.add(new_admin)
await db.commit()
print(f"✅ SIKER: Superadmin létrehozva!")
print(f" 📧 Email: {admin_email}")
print(f" 🔑 Jelszó: Admin123!")
except Exception as e:
print(f"❌ HIBA a mentés során: {e}")
await db.rollback()
print("\n" + ""*50)
print("🏁 JAVÍTÁSI FOLYAMAT BEFEJEZŐDÖTT")
print(""*50 + "\n")
if __name__ == "__main__":
asyncio.run(run_fix())

View File

View File

@@ -0,0 +1,98 @@
# /opt/docker/dev/service_finder/backend/app/tests_internal/seeds/seed_catalog.py
"""
🌱 MB2.0 KATALÓGUS ÉS ROBOT SEEDER
----------------------------------
CÉL: A járműkatalógus alapadatainak és a robotok munkalistájának feltöltése.
FUNKCIÓK:
1. DiscoveryParameter: Azon városok rögzítése, ahol a Scout robot keresni fog.
2. CatalogDiscovery: Azon márkák rögzítése, amiket a Robot 0/1 fel fog dolgozni.
3. AssetCatalog: 'Arany' (már validált) technikai rekordok beszúrása.
JAVÍTÁSOK:
- 'last_error' oszlop eltávolítva (mivel az adatbázisban nem létezik).
- 'attempts' mező kényszerített 0 értékkel (NOT NULL kényszer miatt).
"""
import asyncio
import logging
from app.database import AsyncSessionLocal
from app.models.asset import AssetCatalog, CatalogDiscovery
from app.models.staged_data import DiscoveryParameter
# Logolás beállítása
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Sentinel-Seed: %(message)s')
logger = logging.getLogger("Seed-Catalog")
async def quick_seed():
""" Katalógus és Discovery adatok inicializálása. """
async with AsyncSessionLocal() as db:
logger.info("🚀 Katalógus alapozás indítása...")
try:
# 1. Felderítendő Városok (DiscoveryParameter)
# A Scout robot ezekben a városokban kezdi meg a szervizek kutatását.
cities = [
("BUDAPEST", "HU"),
("DEBRECEN", "HU"),
("GYŐR", "HU"),
("SZEGED", "HU")
]
for city_name, country in cities:
db.add(DiscoveryParameter(
city=city_name,
country_code=country,
is_active=True
))
# 2. Felderítendő Márkák és Típusok (CatalogDiscovery)
# Ezeket a rekordokat fogja a Robot 0 és Robot 1 feldolgozni a háttérben.
discovery_queue = [
("SUZUKI", "ALL"),
("TOYOTA", "ALL"),
("HONDA", "ALL"),
("SKODA", "ALL"),
("YAMAHA", "ALL")
]
for m, mod in discovery_queue:
# Az attempts=0 kötelező a DB kényszer miatt
db.add(CatalogDiscovery(
make=m,
model=mod,
status="pending",
attempts=0
))
# 3. Arany rekordok (AssetCatalog / vehicle_catalog tábla)
# Példa adatok, amik már átmentek a validációs folyamaton.
gold_data = [
AssetCatalog(
make="SUZUKI",
model="VITARA",
generation="LY (2015-)",
fuel_type="petrol",
factory_data={"segment": "SUV", "origin": "Hungary"}
),
AssetCatalog(
make="SKODA",
model="OCTAVIA",
generation="IV (2020-)",
fuel_type="diesel",
factory_data={"segment": "Sedan/Combi"}
)
]
db.add_all(gold_data)
# Mentés végrehajtása
await db.commit()
logger.info("✨ Katalógus és Discovery paraméterek sikeresen rögzítve!")
except Exception as e:
await db.rollback()
logger.error(f"❌ HIBA a katalógus feltöltésekor: {e}")
raise e
if __name__ == "__main__":
asyncio.run(quick_seed())

View File

@@ -0,0 +1,106 @@
# /opt/docker/dev/service_finder/backend/app/seed_data.py
import asyncio
import uuid
from datetime import datetime, timedelta, timezone
from sqlalchemy import text, select
from app.database import AsyncSessionLocal
from app.models.identity import User, Person, UserRole
from app.models.social import ServiceProvider, Vote, ModerationStatus, Competition
from app.services.social_service import SocialService
from app.core.security import get_password_hash
async def run_simulation():
async with AsyncSessionLocal() as db:
print("--- 1. TAKARÍTÁS (MB2.0 Séma-tisztítás) ---")
# Szigorú sorrend a kényszerek miatt (Cascade)
await db.execute(text("TRUNCATE identity.users, identity.persons, data.service_providers, data.votes, data.competitions RESTART IDENTITY CASCADE"))
await db.commit()
print("\n--- 2. SZEREPLŐK LÉTREHOZÁSA (Person + User) ---")
users_to_create = [
("admin@test.com", "Adminisztrátor", UserRole.superadmin),
("good@test.com", "Rendes Srác", UserRole.user),
("bad@test.com", "Spammer Aladár", UserRole.user),
("voter@test.com", "Szavazó Gép", UserRole.user)
]
created_users = {}
for email, name, role in users_to_create:
p = Person(id_uuid=uuid.uuid4(), first_name=name.split()[0], last_name=name.split()[1], is_active=True)
db.add(p)
await db.flush()
u = User(
email=email,
hashed_password=get_password_hash("test1234"),
person_id=p.id,
role=role,
is_active=True,
reputation_score=5 if "good" in email else (-8 if "bad" in email else 0)
)
db.add(u)
await db.flush()
created_users[email] = u
await db.commit()
print("\n--- 3. VERSENY INDÍTÁSA ---")
race = Competition(
name="Téli Szervizvadászat",
start_date=datetime.now(timezone.utc) - timedelta(days=1),
end_date=datetime.now(timezone.utc) + timedelta(days=30),
is_active=True
)
db.add(race)
await db.commit()
# Szereplők kiemelése a szimulációhoz
good_user = created_users["good@test.com"]
bad_user = created_users["bad@test.com"]
voter = created_users["voter@test.com"]
print("\n--- 4. SZCENÁRIÓ A: POZITÍV VALIDÁCIÓ ---")
# Rendes srác beküld egy szervizt
shop = ServiceProvider(
name="Profi Gumis",
address="Budapest, Váci út 10.",
added_by_user_id=good_user.id,
status=ModerationStatus.pending
)
db.add(shop)
await db.flush()
# Szavazatok szimulálása (SocialService használatával a pontszámítás miatt)
print(f"Szavazás a '{shop.name}'-re...")
# Szimulálunk 5 pozitív szavazatot különböző "virtuális" szavazóktól
for _ in range(5):
await SocialService.vote_for_provider(db, voter.id, shop.id, 1)
await db.refresh(good_user)
print(f"Jó felhasználó hírneve: {good_user.reputation_score}")
print("\n--- 5. SZCENÁRIÓ B: AUTO-BAN (SPAM SZŰRÉS) ---")
fake_shop = ServiceProvider(
name="KAMU SZERVIZ",
address="Nincs ilyen utca 0.",
added_by_user_id=bad_user.id,
status=ModerationStatus.pending
)
db.add(fake_shop)
await db.flush()
# Leszavazás (Kell -3 a bukáshoz)
print("Spam jelentése...")
await SocialService.vote_for_provider(db, voter.id, fake_shop.id, -1)
await SocialService.vote_for_provider(db, voter.id, fake_shop.id, -1)
await SocialService.vote_for_provider(db, voter.id, fake_shop.id, -1)
await db.refresh(bad_user)
print(f"Rossz felhasználó hírneve: {bad_user.reputation_score}")
print(f"Fiók státusza: {'KITILTVA' if not bad_user.is_active else 'AKTÍV'}")
if not bad_user.is_active:
print("✅ SIKER: A Sentinel automatikusan leállította a spammert!")
if __name__ == "__main__":
asyncio.run(run_simulation())

View File

@@ -0,0 +1,65 @@
import asyncio
from app.database import AsyncSessionLocal
from app.models.service import ExpertiseTag
from sqlalchemy import text
async def seed_expertises():
tags = [
# --- ALAPSZOLGÁLTATÁSOK (MECHANICS) ---
('OIL_SERVICE', 'Időszakos szerviz / Olajcsere', 'MECHANICS'),
('BRAKE_REPAIR', 'Fékrendszer javítás', 'MECHANICS'),
('SUSPENSION', 'Futómű javítás és beállítás', 'MECHANICS'),
('EXHAUST', 'Kipufogó szerviz', 'MECHANICS'),
('CLUTCH', 'Kuplung és kettőstömegű csere', 'MECHANICS'),
# --- MOTOR ÉS VÁLTÓ (ENGINE_DRIVETRAIN) ---
('ENGINE_REBUILD', 'Motorfelújítás', 'ENGINE_DRIVETRAIN'),
('TIMING_BELT', 'Vezérlés csere', 'ENGINE_DRIVETRAIN'),
('AUTO_GEARBOX', 'Automata váltó javítás/olajcsere', 'ENGINE_DRIVETRAIN'),
('TURBO_REPAIR', 'Turbófeltöltő felújítás', 'ENGINE_DRIVETRAIN'),
('INJECTOR', 'Dízel injektor / Adagoló javítás', 'ENGINE_DRIVETRAIN'),
('DPF_CLEAN', 'DPF / Részecskeszűrő tisztítás', 'ENGINE_DRIVETRAIN'),
# --- ELEKTRONIKA (ELECTRICAL) ---
('DIAGNOSTICS', 'Számítógépes diagnosztika', 'ELECTRICAL'),
('AC_REPAIR', 'Klíma javítás és töltés', 'ELECTRICAL'),
('BATTERY', 'Akkumulátor szerviz', 'ELECTRICAL'),
('HYBRID_EV', 'Hibrid és Elektromos autó szerviz', 'ELECTRICAL'),
('CHIP_TUNING', 'Szoftveres optimalizálás / Tuning', 'ELECTRICAL'),
('ADAS', 'Vezetéstámogató rendszerek kalibrálása', 'ELECTRICAL'),
# --- GUMI ÉS KERÉK (TYRES) ---
('TYRE_CHANGE', 'Gumiszerelés és centírozás', 'TYRES'),
('WHEEL_REPAIR', 'Alufelni javítás / Görgőzés', 'TYRES'),
# --- KAROSSZÉRIA (BODY) ---
('BODY_REPAIR', 'Karosszéria lakatolás', 'BODY'),
('PAINTING', 'Autófényezés', 'BODY'),
('GLASS_REPAIR', 'Szélvédő javítás és csere', 'BODY'),
('PDR', 'Jégkár és horpadásjavítás (PDR)', 'BODY'),
# --- SEGÉLY ÉS SZÁLLÍTÁS (EMERGENCY) ---
('TOWING', 'Autómentés / Vontatás', 'EMERGENCY'),
('ROADSIDE_ASSIST', 'Segélyszolgálat / Helyszíni javítás', 'EMERGENCY'),
('LOCKSMITH', 'Autózár szerviz / Kulcsmásolás', 'EMERGENCY'),
# --- EGYÉB JÁRMŰVEK (VEHICLE_TYPES) ---
('MOTO_SERVICE', 'Motorkerékpár szerviz', 'VEHICLE_TYPES'),
('TRUCK_SERVICE', 'Tehergépjármű szerviz', 'VEHICLE_TYPES'),
('AGRI_SERVICE', 'Mezőgazdasági gép szerviz', 'VEHICLE_TYPES'),
]
async with AsyncSessionLocal() as db:
print("🌱 Szakmai címkék feltöltése...")
for key, name, cat in tags:
stmt = text("""
INSERT INTO data.expertise_tags (key, name_hu, category, is_official)
VALUES (:k, :n, :c, true)
ON CONFLICT (key) DO UPDATE SET name_hu = EXCLUDED.name_hu, category = EXCLUDED.category
""")
await db.execute(stmt, {"k": key, "n": name, "c": cat})
await db.commit()
print(f"{len(tags)} címke rögzítve.")
if __name__ == "__main__":
asyncio.run(seed_expertises())

View File

@@ -0,0 +1,74 @@
# /opt/docker/dev/service_finder/backend/app/seed_honda.py
import asyncio
import logging
from sqlalchemy import select
from app.database import AsyncSessionLocal
from app.models.asset import AssetCatalog
from app.models.staged_data import DiscoveryParameter
# Logolás beállítása
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Sentinel-Seed: %(message)s')
logger = logging.getLogger("Honda-Seeder")
async def seed_honda():
"""
Honda specifikus alapozás az MB2.0 MDM (Master Data Management) szerint.
Létrehozza a katalógus-vázat és a robot-feladatokat.
"""
async with AsyncSessionLocal() as db:
logger.info("🚀 Honda márka-ökoszisztéma inicializálása...")
# 1. LOGIKA: Robot Discovery feladatok rögzítése
# Ezzel mondjuk meg a Hunter robotnak, hogy keressen rá minden Honda variánsra
discovery_tasks = [
DiscoveryParameter(make="HONDA", vehicle_class="car", city="BUDAPEST", keyword="repair", is_active=True),
DiscoveryParameter(make="HONDA", vehicle_class="motorcycle", city="BUDAPEST", keyword="service", is_active=True)
]
for task in discovery_tasks:
# Megnézzük, van-e már ilyen feladat
stmt = select(DiscoveryParameter).where(
DiscoveryParameter.make == task.make,
DiscoveryParameter.vehicle_class == task.vehicle_class
)
exists = (await db.execute(stmt)).scalar_one_or_none()
if not exists:
db.add(task)
# 2. LOGIKA: Népszerű modellek (Arany Rekordok) betöltése
# Ezek a "Starter" adatok, amik azonnal elérhetők a felhasználóknak
honda_models = [
# Személyautók
{"model": "CIVIC", "gen": "X (2015-2021)", "class": "car"},
{"model": "ACCORD", "gen": "X (2017-)", "class": "car"},
{"model": "CR-V", "gen": "V (2016-)", "class": "car"},
{"model": "JAZZ", "gen": "IV (2020-)", "class": "car"},
# Motorkerékpárok
{"model": "CB500X", "gen": "PC64 (2019-)", "class": "motorcycle"},
{"model": "AFRICA TWIN", "gen": "CRF1100L", "class": "motorcycle"},
{"model": "NC750X", "gen": "RH09 (2021-)", "class": "motorcycle"}
]
for m in honda_models:
# Ellenőrizzük az AssetCatalog-ban (MDM tábla)
stmt = select(AssetCatalog).where(
AssetCatalog.make == "HONDA",
AssetCatalog.model == m["model"],
AssetCatalog.generation == m["gen"]
)
exists = (await db.execute(stmt)).scalar_one_or_none()
if not exists:
db.add(AssetCatalog(
make="HONDA",
model=m["model"],
generation=m["gen"],
vehicle_class=m["class"],
factory_data={"source": "manual_priority_seed"} # MDM metaadat
))
await db.commit()
logger.info("✅ Honda (Autó & Motor) katalógus váz sikeresen felépítve!")
if __name__ == "__main__":
asyncio.run(seed_honda())

View File

@@ -0,0 +1,79 @@
# /app/tests_internal/seeds/seed_system.py
import asyncio
import logging
import uuid
from sqlalchemy import select
from app.database import AsyncSessionLocal
from app.models.identity import User, Person, UserRole
from app.models.system import SystemParameter
# JAVÍTOTT IMPORTOK: A grep alapján szétválasztva
from app.models.gamification import PointRule, LevelConfig, UserStats
from app.models.core_logic import SubscriptionTier
from app.core.security import get_password_hash
from app.core.config import settings
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Sentinel-Seed: %(message)s')
logger = logging.getLogger("System-Seeder")
async def seed_data():
""" Rendszer alapadatok inicializálása a megfelelő modellekből. """
async with AsyncSessionLocal() as db:
logger.info("🚀 Rendszer-alapozás indítása (MB2.0 Standard)...")
admin_email = settings.INITIAL_ADMIN_EMAIL
admin_password = settings.INITIAL_ADMIN_PASSWORD
if not admin_email or not admin_password:
logger.error("❌ HIBA: Admin hitelesítési adatok hiányoznak!")
return
# 1. Superadmin és Person kapcsolat
stmt = select(User).where(User.email == admin_email)
admin_exists = (await db.execute(stmt)).scalar_one_or_none()
if not admin_exists:
new_person = Person(
first_name="Rendszer",
last_name="Adminisztrátor",
id_uuid=uuid.uuid4(),
is_active=True
)
db.add(new_person)
await db.flush()
new_admin = User(
email=admin_email,
hashed_password=get_password_hash(admin_password),
role=UserRole.superadmin,
is_active=True,
person_id=new_person.id
)
db.add(new_admin)
await db.flush()
# Statisztikai rekord (Gamification)
db.add(UserStats(user_id=new_admin.id, total_xp=0, current_level=1))
logger.info(f"✅ Superadmin létrehozva: {admin_email}")
# 2. Rendszerparaméterek (JSONB értékekkel)
params = [
("SECURITY_MAX_RECORDS_PER_HOUR", {"limit": 50}, "Biztonsági limit"),
("VEHICLE_LIMIT", {"default": 5}, "Alapértelmezett jármű limit")
]
for key, val, desc in params:
stmt_p = select(SystemParameter).where(SystemParameter.key == key)
if not (await db.execute(stmt_p)).scalar_one_or_none():
db.add(SystemParameter(key=key, value=val, description=desc))
# 3. Gamification Szabályok (gamification.py-ból)
rules = [("ASSET_REGISTER", 100), ("ASSET_REVIEW", 75)]
for key, pts in rules:
stmt_r = select(PointRule).where(PointRule.action_key == key)
if not (await db.execute(stmt_r)).scalar_one_or_none():
db.add(PointRule(action_key=key, points=pts))
await db.commit()
logger.info("✨ Rendszer alapadatok rögzítve.")
if __name__ == "__main__":
asyncio.run(seed_data())

View File

@@ -0,0 +1,120 @@
# /opt/docker/dev/service_finder/backend/app/seed_test_scenario.py
import asyncio
import uuid
import logging
from datetime import datetime, timedelta, timezone
from sqlalchemy import select
from app.database import AsyncSessionLocal
from app.models.identity import User
from app.models.organization import Organization, OrganizationMember, OrgType
from app.models.asset import (
Asset, AssetCatalog, AssetTelemetry,
AssetFinancials, AssetCost
)
# Sentinel naplózás
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Sentinel-Scenario: %(message)s')
logger = logging.getLogger("Test-Scenario")
async def seed_test_scenario():
async with AsyncSessionLocal() as db:
logger.info("🚀 MB2.0 Teszt ökoszisztéma felépítése indul...")
# 1. LOGIKA: Admin (Superuser) lekérése az identity sémából
res = await db.execute(select(User).where(User.is_active == True))
admin = res.scalars().first()
if not admin:
logger.error("❌ Hiba: Nincs aktív felhasználó a rendszerben. Futtasd a seed_system.py-t!")
return
# 2. LOGIKA: Szervezeti struktúra felállítása
# Privát garázs
private_org = Organization(
name="Kincses Privát",
full_name="Kincses Magánflotta és Garázs",
org_type=OrgType.individual,
owner_id=admin.id,
folder_slug="kincses-privat-vault"
)
# Üzleti flotta
company_org = Organization(
name="ProfiBot Fleet",
full_name="ProfiBot Software Solutions Kft.",
org_type=OrgType.business,
owner_id=admin.id,
folder_slug="profibot-fleet-vault"
)
# Szolgáltatók (Szerviz és Üzemanyag)
service_org = Organization(
name="Mester Szerviz",
org_type=OrgType.service,
owner_id=admin.id,
is_active=True
)
db.add_all([private_org, company_org, service_org])
await db.flush()
# Tagsági viszonyok rögzítése
db.add(OrganizationMember(user_id=admin.id, organization_id=company_org.id, role="owner"))
# 3. LOGIKA: Tesla Model 3 - Digitális Iker (Digital Twin)
# Előbb a katalógus (Gold Data)
catalog = AssetCatalog(
make="TESLA", model="MODEL 3", generation="Long Range (2021-)",
fuel_type="electric",
factory_data={
"battery": "75 kWh", "power_kw": 366,
"tire_size": "235/45 R18", "ac_charge": "11kW"
}
)
db.add(catalog)
await db.flush()
# Majd a konkrét jármű (Asset)
vehicle = Asset(
vin=f"5YJ3E1EB8LF{uuid.uuid4().hex[:6].upper()}",
license_plate="TES-777-EV",
name="Céges Tesla",
year_of_manufacture=2021,
catalog_id=catalog.id,
owner_org_id=company_org.id,
status="active"
)
db.add(vehicle)
await db.flush()
# Telemetria és Pénzügyi alapok
db.add(AssetTelemetry(asset_id=vehicle.id, current_mileage=45200, vqi_score=100.0))
db.add(AssetFinancials(asset_id=vehicle.id, acquisition_price=18500000, currency="HUF"))
# 4. LOGIKA: A 9 költségtípus szimulálása
costs_data = [
("FUEL", 12500, "Supercharger töltés"),
("MAINTENANCE", 85000, "Pollenszűrő és átvizsgálás"),
("TIRES", 280000, "Téli gumi szett"),
("INSURANCE", 32000, "Havi CASCO"),
("TAX", 15000, "Cégautóadó (szimulált)"),
("TOLL", 6500, "Éves matrica"),
("CLEANING", 4500, "Külső-belső takarítás"),
("PARKING", 1200, "Belvárosi zóna"),
("OTHER", 2500, "Szélvédőmosó folyadék")
]
for c_type, amount, desc in costs_data:
db.add(AssetCost(
asset_id=vehicle.id,
organization_id=company_org.id,
cost_type=c_type,
amount=amount,
currency="HUF",
date=datetime.now(timezone.utc) - timedelta(days=2),
specifications={"description": desc}
))
await db.commit()
logger.info("✅ Siker! A teljes flotta-ökoszisztéma üzemkész.")
if __name__ == "__main__":
asyncio.run(seed_test_scenario())

View File

@@ -0,0 +1,31 @@
# /app/tests_internal/test_functional.py
"""
CÉL: Éles funkcionális teszt a bejelentkezési folyamathoz.
"""
import asyncio
from sqlalchemy import select
from app.database import AsyncSessionLocal
from app.models.identity import User
from app.services.auth_service import AuthService
async def test_login_flow():
print("\n--- 🔑 FUNKCIONÁLIS LOGIN TESZT ---")
async with AsyncSessionLocal() as db:
# 1. Keressünk egy teszt felhasználót
result = await db.execute(select(User).limit(1))
user = result.scalar_one_or_none()
if not user:
print("❌ HIBA: Nincs felhasználó az adatbázisban. Futtass egy seeder-t!")
return
print(f"👤 Tesztelés felhasználóval: {user.email}")
# 2. Próbáljunk meg egy 'hitelesítést' (jelszó ellenőrzés nélkül a DB szinten)
if user.is_active:
print(f"✅ SIKER: A(z) {user.email} fiók aktív és elérhető.")
else:
print(f"⚠️ FIGYELEM: A felhasználó létezik, de inaktív.")
if __name__ == "__main__":
asyncio.run(test_login_flow())

View File

@@ -0,0 +1,91 @@
# /opt/docker/dev/service_finder/backend/app/test_gamification_flow.py
import asyncio
import os
import sys
import logging
from sqlalchemy import select
from dotenv import load_dotenv
# Környezeti változók betöltése
load_dotenv()
# MB2.0 Importok
from app.database import AsyncSessionLocal
from app.models.identity import User
from app.models.system import UserStats, PointsLedger
from app.services.social_service import SocialService
from app.schemas.social import ServiceProviderCreate
# Naplózás beállítása
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] Sentinel-Test: %(message)s')
logger = logging.getLogger("Gamification-Test")
async def run_test():
logger.info("🚀 Gamifikációs integrációs folyamat tesztelése...")
async with AsyncSessionLocal() as db:
try:
# 1. LOGIKA: Teszt felhasználó lekérése az identity sémából
result = await db.execute(select(User).limit(1))
user = result.scalars().first()
if not user:
logger.error("❌ Hiba: Nincs felhasználó az adatbázisban. Futtasd a seed_system.py-t!")
return
logger.info(f"👤 Aktív teszt alany: {user.email}")
# 2. LOGIKA: Új szolgáltató rögzítése (Trigger az XP szerzéshez)
# A SocialService.create_service_provider automatikusan hívja a GamificationService-t
unique_id = os.urandom(2).hex()
test_provider = ServiceProviderCreate(
name=f"Robot Szerviz {unique_id}",
address="Alchemist utca 12.",
category="service"
)
logger.info(f"🛠️ Esemény kiváltása: '{test_provider.name}' rögzítése...")
new_provider = await SocialService.create_service_provider(db, test_provider, user.id)
# Commit kényszerítése, hogy a háttérfolyamatok rögzüljenek
await db.commit()
logger.info(f"✅ Szolgáltató elfogadva (ID: {new_provider.id})")
# 3. LOGIKA: Eredmények ellenőrzése a Ledgerben (Főkönyv)
# Újra lekérjük a statisztikákat a commit után
stats_res = await db.execute(select(UserStats).where(UserStats.user_id == user.id))
stats = stats_res.scalar_one_or_none()
ledger_res = await db.execute(
select(PointsLedger)
.where(PointsLedger.user_id == user.id)
.order_by(PointsLedger.created_at.desc())
.limit(1)
)
last_entry = ledger_res.scalars().first()
print("\n" + ""*40)
print("📊 INTEGRÁCIÓS JELENTÉS:")
if stats:
print(f"🏆 Aktuális XP: {stats.total_xp}")
print(f"📈 Szint: {stats.current_level}")
else:
print("⚠️ UserStats rekord nem található!")
if last_entry:
print(f"📝 Tranzakció oka: {last_entry.reason}")
print(f"💰 XP változás: +{last_entry.points_change}")
print(""*40 + "\n")
if stats and stats.total_xp > 0:
logger.info("✅ SIKER: A gamifikációs lánc éles és működik!")
else:
logger.warning("❌ HIBA: A pontszámítás nem történt meg.")
except Exception as e:
logger.error(f"💥 Kritikus hiba a teszt közben: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(run_test())

View File

@@ -0,0 +1,33 @@
# app/tests_internal/test_postgis.py
import asyncio
from sqlalchemy import text
from app.db.session import AsyncSessionLocal
async def test_geo_logic():
"""
THOUGHT PROCESS:
Ellenőrizni kell, hogy a PostgreSQL-ben a 'data.branches' tábla 'location' oszlopa
valóban GEOGRAPHY típusú-e, és az ST_Distance függvény működik-e.
Ha ez elbukik, a 'search.py' nem fog eredményt adni.
"""
print("🌍 PostGIS távolságszámítás tesztelése...")
async with AsyncSessionLocal() as session:
try:
# Egy teszt pont (Budapest központ) és egy körzet lekérdezése
query = text("""
SELECT id, name,
ST_Distance(location, ST_SetSRID(ST_MakePoint(19.0402, 47.4979), 4326)::geography) / 1000 as distance_km
FROM data.branches
LIMIT 1
""")
result = await db.execute(query)
row = result.fetchone()
if row:
print(f"✅ SIKER: Találtunk egy ágat ({row.name}) {row.distance_km:.2f} km távolságra.")
else:
print("⚠️ FIGYELEM: A lekérdezés lefutott, de nincsenek adatok a data.branches táblában.")
except Exception as e:
print(f"❌ HIBA: A PostGIS lekérdezés elbukott. Oka: {str(e)}")
if __name__ == "__main__":
asyncio.run(test_geo_logic())