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

123
backend/app/schemas/admin.py Executable file
View File

@@ -0,0 +1,123 @@
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/admin.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, text, delete
from typing import List, Any, Dict, Optional
from datetime import datetime, timedelta
from app.api import deps
from app.models.identity import User, UserRole
from app.models.system import SystemParameter
from app.models.audit import SecurityAuditLog, OperationalLog
from app.models.security import PendingAction, ActionStatus
from app.services.security_service import security_service
from app.services.translation_service import TranslationService
from app.schemas.admin import PointRuleResponse, LevelConfigResponse, ConfigUpdate
from app.schemas.admin_security import PendingActionResponse, SecurityStatusResponse
router = APIRouter()
# --- 🛡️ ADMIN JOGOSULTSÁG ELLENŐRZŐ ---
async def check_admin_access(current_user: User = Depends(deps.get_current_active_user)):
""" Csak Admin vagy Superadmin léphet be a Sentinel központba. """
if current_user.role not in [UserRole.admin, UserRole.superadmin]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Sentinel jogosultság szükséges a művelethez!"
)
return current_user
# --- 🛰️ 1. SENTINEL: RENDSZERÁLLAPOT ÉS MONITORING ---
@router.get("/health-monitor", response_model=Dict[str, Any], tags=["Sentinel Monitoring"])
async def get_system_health(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Részletes rendszerstatisztikák (Felhasználók, Eszközök, Biztonság). """
stats = {}
# Felhasználói eloszlás (Nyers SQL a sebességért)
user_res = await db.execute(text("SELECT subscription_plan, count(*) FROM data.users GROUP BY subscription_plan"))
stats["user_distribution"] = {row[0]: row[1] for row in user_res}
# Eszköz és Szervezet számlálók
stats["total_assets"] = (await db.execute(text("SELECT count(*) FROM data.assets"))).scalar()
stats["total_organizations"] = (await db.execute(text("SELECT count(*) FROM data.organizations"))).scalar()
# Biztonsági riasztások (Kritikus logok az elmúlt 24 órában)
day_ago = datetime.now() - timedelta(days=1)
crit_logs = await db.execute(
select(func.count(SecurityAuditLog.id))
.where(SecurityAuditLog.is_critical == True, SecurityAuditLog.created_at >= day_ago)
)
stats["critical_alerts_24h"] = crit_logs.scalar() or 0
return stats
# --- ⚖️ 2. SENTINEL: NÉGY SZEM ELV (Approval System) ---
@router.get("/pending-actions", response_model=List[PendingActionResponse], tags=["Sentinel Security"])
async def list_pending_actions(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Jóváhagyásra váró kritikus műveletek listázása. """
stmt = select(PendingAction).where(PendingAction.status == ActionStatus.pending)
result = await db.execute(stmt)
return result.scalars().all()
@router.post("/approve/{action_id}", tags=["Sentinel Security"])
async def approve_action(
action_id: int,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Művelet véglegesítése egy második admin által. """
try:
await security_service.approve_action(db, admin.id, action_id)
return {"status": "success", "message": "Művelet végrehajtva."}
except Exception as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# --- ⚙️ 3. DINAMIKUS KONFIGURÁCIÓ (System Parameters) ---
@router.get("/parameters", tags=["Dynamic Configuration"])
async def list_all_parameters(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Globális és lokális paraméterek (Limitek, XP szorzók) lekérése. """
result = await db.execute(select(SystemParameter))
return result.scalars().all()
@router.post("/parameters", tags=["Dynamic Configuration"])
async def set_parameter(
config: ConfigUpdate,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
""" Paraméter beállítása vagy frissítése hierarchikus scope-al. """
query = text("""
INSERT INTO data.system_parameters (key, value, scope_level, scope_id, category, last_modified_by)
VALUES (:key, :val, :sl, :sid, :cat, :user)
ON CONFLICT (key, scope_level, scope_id)
DO UPDATE SET
value = EXCLUDED.value,
category = EXCLUDED.category,
last_modified_by = EXCLUDED.last_modified_by,
updated_at = now()
""")
await db.execute(query, {
"key": config.key, "val": config.value, "sl": config.scope_level,
"sid": config.scope_id, "cat": config.category, "user": admin.email
})
await db.commit()
return {"status": "success", "message": f"'{config.key}' frissítve."}
@router.post("/translations/sync", tags=["System Utilities"])
async def sync_translations(db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
""" DB fordítások exportálása JSON fájlokba a frontendnek. """
await TranslationService.export_to_json(db)
return {"message": "Nyelvi fájlok frissítve."}

View File

@@ -0,0 +1,23 @@
# /opt/docker/dev/service_finder/backend/app/schemas/admin_security.py
from pydantic import BaseModel, ConfigDict
from datetime import datetime
from typing import Optional, Any, Dict
from app.models.security import ActionStatus
class PendingActionResponse(BaseModel):
id: int
requester_id: int
action_type: str
payload: Dict[str, Any]
reason: str
status: ActionStatus
created_at: datetime
expires_at: datetime
model_config = ConfigDict(from_attributes=True)
class SecurityStatusResponse(BaseModel):
total_pending: int
critical_logs_last_24h: int
emergency_locks_active: int

56
backend/app/schemas/asset.py Executable file
View File

@@ -0,0 +1,56 @@
# /opt/docker/dev/service_finder/backend/app/schemas/asset.py
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional, Dict, Any, List
from uuid import UUID
from datetime import datetime
class AssetCatalogResponse(BaseModel):
""" A technikai katalógus (Master Data) teljes adattartalma. """
id: int
make: str
model: str
generation: Optional[str] = None
engine_variant: Optional[str] = None
year_from: Optional[int] = None
year_to: Optional[int] = None
vehicle_class: Optional[str] = None
fuel_type: Optional[str] = None
# Technikai paraméterek az automatizáláshoz
power_kw: Optional[int] = None
engine_capacity: Optional[int] = None
max_weight_kg: Optional[int] = None
axle_count: Optional[int] = None
euro_class: Optional[str] = None
body_type: Optional[str] = None
engine_code: Optional[str] = None
factory_data: Dict[str, Any] = Field(default_factory=dict)
model_config = ConfigDict(from_attributes=True)
class AssetResponse(BaseModel):
""" A konkrét járműpéldány (Asset) teljes válaszmodellje. """
id: UUID
vin: str = Field(..., min_length=17, max_length=17)
license_plate: Optional[str] = None
name: Optional[str] = None
year_of_manufacture: Optional[int] = None
# Státusz és ellenőrzés
status: str
is_verified: bool
verification_method: Optional[str] = None
catalog_match_score: Optional[float] = None
# Kapcsolt adatok
catalog_id: Optional[int] = None
catalog: Optional[AssetCatalogResponse] = None # Itt jön a dúsítás!
owner_organization_id: Optional[int] = None
operator_person_id: Optional[int] = None
created_at: datetime
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,35 @@
# /opt/docker/dev/service_finder/backend/app/schemas/asset_cost.py
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional, Dict, Any
from datetime import datetime
from decimal import Decimal
from uuid import UUID
class AssetCostBase(BaseModel):
cost_type: str # fuel, service, tax, insurance
amount_local: Decimal
currency_local: str = "HUF"
net_amount_local: Optional[Decimal] = None
vat_rate: Optional[Decimal] = Field(default=27.0)
date: datetime = Field(default_factory=datetime.now)
mileage_at_cost: Optional[int] = None
description: Optional[str] = None
data: Dict[str, Any] = Field(default_factory=dict) # nyugta adatai, GPS koordináták
class AssetCostCreate(AssetCostBase):
asset_id: UUID
organization_id: int
class AssetCostResponse(AssetCostBase):
id: UUID
asset_id: UUID
organization_id: int
driver_id: Optional[int] = None
# Pénzügyi dúsítás (Backend számolja)
amount_eur: Optional[Decimal] = None
exchange_rate_used: Optional[Decimal] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)

54
backend/app/schemas/auth.py Executable file
View File

@@ -0,0 +1,54 @@
# /opt/docker/dev/service_finder/backend/app/schemas/auth.py
from pydantic import BaseModel, EmailStr, Field, ConfigDict
from typing import Optional, Dict, List
from datetime import date, datetime
class DocumentDetail(BaseModel):
number: str
expiry_date: date
class ICEContact(BaseModel):
name: str
phone: str
relationship: str
class UserLiteRegister(BaseModel):
""" Step 1: Gyors regisztráció (Alap azonosítás). """
email: EmailStr
password: str = Field(..., min_length=8, description="Minimum 8 karakter hosszú jelszó")
first_name: str
last_name: str
model_config = ConfigDict(from_attributes=True)
class UserKYCComplete(BaseModel):
""" Step 2: Teljes körű személyazonosítás és címadatok. """
phone_number: str = Field(..., pattern=r"^\+?[0-9]{7,15}$")
birth_place: str
birth_date: date
mothers_last_name: str
mothers_first_name: str
# Atomizált címadatok a pontos GPS-hez és Robot-munkához
address_zip: str
address_city: str
address_street_name: str
address_street_type: str # utca, út, tér...
address_house_number: str
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
address_hrsz: Optional[str] = None # Külterület/Helyrajzi szám
# Okmányok és Vészhelyzet
identity_docs: Dict[str, DocumentDetail] # pl: {"ID_CARD": {...}, "LICENSE": {...}}
ice_contact: ICEContact
preferred_language: str = "hu"
preferred_currency: str = "HUF"
class Token(BaseModel):
access_token: str
refresh_token: Optional[str] = None
token_type: str = "bearer"
is_active: bool

47
backend/app/schemas/evidence.py Executable file
View File

@@ -0,0 +1,47 @@
# app/schemas/evidence.py
from pydantic import BaseModel, Field
from typing import Optional
class RegistrationDocumentExtracted(BaseModel):
"""A magyar forgalmi engedély teljes adattartalma."""
# A - Okmány adatok
license_plate: Optional[str] = Field(None, alias="A", description="Rendszám")
first_registration_date: Optional[str] = Field(None, alias="B", description="Első nyilvántartásba vétel")
doc_serial_number: Optional[str] = Field(None, description="Okmány sorszáma (jobb felső sarok)")
# C - Tulajdonos/Üzembentartó adatok
owner_last_name: Optional[str] = Field(None, alias="C.1.1", description="Családi név vagy cégnév")
owner_first_name: Optional[str] = Field(None, alias="C.1.2", description="Utónév")
owner_address: Optional[str] = Field(None, alias="C.1.3", description="Lakcím/Székhely")
owner_status: Optional[str] = Field(None, alias="C.4", description="Jogosultság státusza (a=tulaj, b=nem tulaj)")
# D - Jármű technikai adatai
make: Optional[str] = Field(None, alias="D.1", description="Gyártmány")
vehicle_type: Optional[str] = Field(None, alias="D.2", description="Típus")
commercial_description: Optional[str] = Field(None, alias="D.3", description="Kereskedelmi leírás")
vin: Optional[str] = Field(None, alias="E", description="Alvázszám (17 karakter)")
# G, F - Tömeg adatok
weight_kg: Optional[int] = Field(None, alias="G", description="Saját tömeg")
max_weight_kg: Optional[int] = Field(None, alias="F.1", description="Együttes tömeg")
# P, V - Motor és Környezetvédelem
engine_capacity: Optional[int] = Field(None, alias="P.1", description="Hengerűrtartalom (cm3)")
engine_power: Optional[float] = Field(None, alias="P.2", description="Teljesítmény (kW)")
fuel_type: Optional[str] = Field(None, alias="P.3", description="Hajtóanyag")
engine_code: Optional[str] = Field(None, alias="P.5", description="Motorkód")
env_category: Optional[str] = Field(None, alias="V.9", description="Környezetvédelmi osztály")
# R, S, H - Egyéb
color: Optional[str] = Field(None, alias="R", description="Szín")
seats: Optional[int] = Field(None, alias="S.1", description="Ülések száma")
expiry_date: Optional[str] = Field(None, alias="H", description="Műszaki érvényesség")
transmission_type: Optional[str] = Field(None, description="Sebességváltó fajtája")
class Config:
populate_by_name = True
class OcrResponse(BaseModel):
success: bool
message: str
data: Optional[RegistrationDocumentExtracted] = None

20
backend/app/schemas/fleet.py Executable file
View File

@@ -0,0 +1,20 @@
# /opt/docker/dev/service_finder/backend/app/schemas/fleet.py
from pydantic import BaseModel, ConfigDict
from typing import Optional, List
from datetime import date
from uuid import UUID
class EventCreate(BaseModel):
asset_id: UUID
event_type: str # 'SERVICE', 'FUEL', 'MOT'
date: date
odometer_value: int
cost_amount: float
description: Optional[str] = None
provider_id: Optional[int] = None
class TCOStats(BaseModel):
asset_id: UUID
total_cost_huf: float
cost_per_km: float
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,38 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
class ContactCreate(BaseModel):
full_name: str
email: str
phone: Optional[str] = None
contact_type: str = "primary"
class CorpOnboardIn(BaseModel):
""" Teljes onboarding adatcsomag atomizált címekkel. """
full_name: str = Field(..., description="Hivatalos cégnév")
name: str = Field(..., description="Rövid név")
display_name: str
tax_number: str
reg_number: Optional[str] = None
country_code: str = "HU"
language: str = "hu"
default_currency: str = "HUF"
# --- ATOMIZÁLT CÍM (Modell szinkron) ---
address_zip: str
address_city: str
address_street_name: str
address_street_type: str
address_house_number: str
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
address_hrsz: Optional[str] = None
contacts: List[ContactCreate] = []
class CorpOnboardResponse(BaseModel):
organization_id: int
status: str
model_config = ConfigDict(from_attributes=True)

38
backend/app/schemas/service.py Executable file
View File

@@ -0,0 +1,38 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, List
class ContactCreate(BaseModel):
full_name: str
email: str
phone: Optional[str] = None
contact_type: str = "primary"
class CorpOnboardIn(BaseModel):
""" Teljes onboarding adatcsomag atomizált címekkel. """
full_name: str = Field(..., description="Hivatalos cégnév")
name: str = Field(..., description="Rövid név")
display_name: str
tax_number: str
reg_number: Optional[str] = None
country_code: str = "HU"
language: str = "hu"
default_currency: str = "HUF"
# --- ATOMIZÁLT CÍM (Modell szinkron) ---
address_zip: str
address_city: str
address_street_name: str
address_street_type: str
address_house_number: str
address_stairwell: Optional[str] = None
address_floor: Optional[str] = None
address_door: Optional[str] = None
address_hrsz: Optional[str] = None
contacts: List[ContactCreate] = []
class CorpOnboardResponse(BaseModel):
organization_id: int
status: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,9 @@
# /opt/docker/dev/service_finder/backend/app/schemas/service_hunt.py
class ServiceHuntRequest(BaseModel):
name: str
category_id: int
address: str
latitude: float
longitude: float
user_latitude: float
user_longitude: float

58
backend/app/schemas/social.py Executable file
View File

@@ -0,0 +1,58 @@
# /opt/docker/dev/service_finder/backend/app/schemas/social.py
from pydantic import BaseModel, ConfigDict
from typing import Optional, List
from datetime import datetime
from app.models.social import ModerationStatus, SourceType
# --- Alap Sémák (Szolgáltatók) ---
class ServiceProviderBase(BaseModel):
name: str
address: Optional[str] = None
category: Optional[str] = None
source: SourceType = SourceType.manual
class ServiceProviderCreate(BaseModel):
name: str
address: str
category: Optional[str] = None
class ServiceProviderResponse(ServiceProviderBase):
id: int
status: ModerationStatus
validation_score: int
evidence_image_path: Optional[str] = None
added_by_user_id: Optional[int] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
# --- Gamifikáció és Szavazás (Voting & Gamification) ---
class VoteCreate(BaseModel):
vote_value: int
class LeaderboardEntry(BaseModel):
username: str
points: int
rank: int
model_config = ConfigDict(from_attributes=True)
class BadgeSchema(BaseModel):
id: int
name: str
description: str
icon_url: Optional[str] = None # JAVÍTVA: icon_url a modell szerint
model_config = ConfigDict(from_attributes=True) # Pydantic V2 kompatibilis
class UserStatSchema(BaseModel):
user_id: int
total_xp: int # JAVÍTVA: total_xp a modell szerint
current_level: int
penalty_points: int # JAVÍTVA: új mező
rank_title: Optional[str] = None
badges: List[BadgeSchema] = []
model_config = ConfigDict(from_attributes=True)

10
backend/app/schemas/token.py Executable file
View File

@@ -0,0 +1,10 @@
from pydantic import BaseModel
from typing import Optional
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
role: Optional[str] = None

25
backend/app/schemas/user.py Executable file
View File

@@ -0,0 +1,25 @@
# /opt/docker/dev/service_finder/backend/app/schemas/user.py
from pydantic import BaseModel, EmailStr, field_validator, ConfigDict
from typing import Optional
from datetime import date
class UserBase(BaseModel):
email: EmailStr
first_name: Optional[str] = None
last_name: Optional[str] = None
is_active: bool = True
region_code: str = "HU"
class UserResponse(UserBase):
id: int
person_id: Optional[int] = None
role: str
subscription_plan: str
scope_level: str
scope_id: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class UserUpdate(BaseModel):
first_name: Optional[str] = None
last_name: Optional[str] = None
preferred_language: Optional[str] = None

View File

@@ -0,0 +1,30 @@
from pydantic import BaseModel, Field, validator
from typing import Optional, List, Any
from uuid import UUID
from datetime import datetime
class EngineSpecBase(BaseModel):
engine_code: str
fuel_type: str
power_kw: int
default_service_interval_km: int = 15000
class VehicleBase(BaseModel):
brand_id: int
model_name: str
identification_number: str
license_plate: Optional[str] = None
tracking_mode: str = "km"
class VehicleCreate(VehicleBase):
current_company_id: int
engine_spec_id: int
class VehicleRead(VehicleBase):
id: UUID
current_rating_pct: int
total_real_usage: float
created_at: datetime
class Config:
from_attributes = True

View File

@@ -0,0 +1,20 @@
# app/core/schemas/vehicle_categories.py
VEHICLE_SCHEMAS = {
"motorcycle": {
"features": ["ABS", "Markolatfűtés", "Szélvédő", "Bukócső/gomba", "Automata váltó", "Gyári dobozok", "Zárható doboz", "Veterán"],
"service_items": ["motorolaj", "olajszűrő", "levegőszűrő", "lánc_szett", "fékfolyadék", "gyújtógyertya", "szelephézag_ellenőrzés"]
},
"car": {
"features": ["Automata", "Tempomat", "Összkerékhajtás", "Alufelni", "Elektromos ablak", "Vonóhorog", "ISOFIX rendszer", "ESP", "Szervizkönyv", "Veterán"],
"service_items": ["motorolaj", "olajszűrő", "levegőszűrő", "pollenszűrő", "vezérlés_szett", "hosszbordásszíj", "váltóolaj", "fagyálló"]
},
"truck": {
"features": ["Légrugó", "Hálófülke", "Retarder/Intarder", "Emelőhátfal", "Tengelysúly-mérő", "AdBlue", "Állóhelyzeti klíma"],
"service_items": ["motorolaj", "légfék_szárító_patron", "üzemanyagszűrő", "érintésvédelmi_vizsga", "tengely_zsírozás"]
},
"boat": {
"features": ["Utánfutó", "Takaróponyva", "Orrsugárkormány", "Halradar", "Kormányállás", "Üzemanyagtartály", "Sólyakocsi", "Zárható tároló", "Elektromos horgonycsörlő"],
"service_items": ["motorolaj", "hajómotor_anód", "vízpumpa_lapát", "téliesítés", "algagátlózás"]
}
}