refaktorálás javításai

This commit is contained in:
Roo
2026-03-13 10:22:41 +00:00
parent 2d8d23f469
commit f53e0b53df
140 changed files with 7316 additions and 4579 deletions

View File

@@ -0,0 +1,213 @@
"""
Smart Odometer Service - Adminisztrátor által paraméterezhető kilométeróra becslés.
A szolgáltatás a járművek kilométeróra állását becsüli a költségbejegyzések alapján,
figyelembe véve a rendszerparamétereket (ODOMETER_MIN_DAYS_FOR_AVG, ODOMETER_CONFIDENCE_THRESHOLD).
Ha az admin beállított manuális átlagot (manual_override_avg), akkor azt használja.
"""
from datetime import datetime, timedelta
from typing import Optional, Tuple
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_
from sqlalchemy.orm import selectinload
from app.models.vehicle import VehicleOdometerState, VehicleCost
from app.models.system import SystemParameter
from app.models.vehicle_definitions import VehicleModelDefinition
class OdometerService:
"""Kilométeróra becslési szolgáltatás adminisztrációs kontrollal."""
@staticmethod
async def get_system_param(db: AsyncSession, key: str, default_value):
"""Rendszerparaméter lekérése a system.system_parameters táblából."""
stmt = select(SystemParameter).where(
SystemParameter.key == key,
SystemParameter.scope_level == 'global',
SystemParameter.is_active == True
)
result = await db.execute(stmt)
param = result.scalars().first()
if param and 'value' in param.value:
return param.value['value']
return default_value
@staticmethod
async def update_vehicle_stats(db: AsyncSession, vehicle_id: int) -> Optional[VehicleOdometerState]:
"""
Frissíti a jármű kilométeróra statisztikáit.
Algoritmus:
1. Ha van manual_override_avg, használja azt.
2. Különben számol átlagot a vehicle.costs bejegyzésekből.
3. Figyelembe veszi az ODOMETER_MIN_DAYS_FOR_AVG paramétert.
4. Kiszámolja a confidence_score-t a minták száma alapján.
5. Frissíti vagy létrehozza a VehicleOdometerState rekordot.
"""
# Rendszerparaméterek lekérése
min_days = await OdometerService.get_system_param(db, 'ODOMETER_MIN_DAYS_FOR_AVG', 7)
confidence_threshold = await OdometerService.get_system_param(db, 'ODOMETER_CONFIDENCE_THRESHOLD', 0.5)
# Meglévő állapot lekérése
stmt = select(VehicleOdometerState).where(VehicleOdometerState.vehicle_id == vehicle_id)
result = await db.execute(stmt)
odometer_state = result.scalars().first()
# Költségbejegyzések lekérése dátum és odometer szerint rendezve
cost_stmt = select(VehicleCost).where(
VehicleCost.vehicle_id == vehicle_id,
VehicleCost.odometer.isnot(None)
).order_by(VehicleCost.date.asc())
cost_result = await db.execute(cost_stmt)
costs = cost_result.scalars().all()
if not costs:
# Nincs adat, alapértelmezett értékek
if odometer_state:
odometer_state.daily_avg_distance = 0
odometer_state.confidence_score = 0
odometer_state.estimated_current_odometer = odometer_state.last_recorded_odometer
else:
# Jármű alapadatok lekérése
vehicle_stmt = select(VehicleModelDefinition).where(VehicleModelDefinition.id == vehicle_id)
vehicle_result = await db.execute(vehicle_stmt)
vehicle = vehicle_result.scalars().first()
if not vehicle:
return None
odometer_state = VehicleOdometerState(
vehicle_id=vehicle_id,
last_recorded_odometer=0,
last_recorded_date=datetime.now(),
daily_avg_distance=0,
estimated_current_odometer=0,
confidence_score=0,
manual_override_avg=None
)
db.add(odometer_state)
await db.commit()
await db.refresh(odometer_state)
return odometer_state
# Utolsó rögzített adatok
last_cost = costs[-1]
last_recorded_odometer = last_cost.odometer
last_recorded_date = last_cost.date
# Manuális átlag ellenőrzése
if odometer_state and odometer_state.manual_override_avg is not None:
daily_avg = float(odometer_state.manual_override_avg)
confidence = 1.0 # Manuális beállítás esetén teljes bizalom
else:
# Átlag számítása a költségbejegyzésekből
valid_pairs = []
for i in range(1, len(costs)):
prev = costs[i-1]
curr = costs[i]
days_diff = (curr.date - prev.date).days
km_diff = curr.odometer - prev.odometer
if days_diff >= min_days and km_diff > 0:
daily_avg = km_diff / days_diff
valid_pairs.append((daily_avg, days_diff))
if valid_pairs:
# Súlyozott átlag (hosszabb időszakok nagyobb súllyal)
total_weighted = sum(avg * weight for avg, weight in valid_pairs)
total_days = sum(weight for _, weight in valid_pairs)
daily_avg = total_weighted / total_days if total_days > 0 else 0
# Confidence score: érvényes párok száma / összes lehetséges párok
confidence = min(len(valid_pairs) / max(len(costs) - 1, 1), 1.0)
else:
daily_avg = 0
confidence = 0
# Becsült jelenlegi kilométer
days_since_last = (datetime.now(last_recorded_date.tzinfo) - last_recorded_date).days
estimated_odometer = last_recorded_odometer + (daily_avg * max(days_since_last, 0))
# Állapot frissítése vagy létrehozása
if odometer_state:
odometer_state.last_recorded_odometer = last_recorded_odometer
odometer_state.last_recorded_date = last_recorded_date
odometer_state.daily_avg_distance = daily_avg
odometer_state.estimated_current_odometer = estimated_odometer
odometer_state.confidence_score = confidence
else:
odometer_state = VehicleOdometerState(
vehicle_id=vehicle_id,
last_recorded_odometer=last_recorded_odometer,
last_recorded_date=last_recorded_date,
daily_avg_distance=daily_avg,
estimated_current_odometer=estimated_odometer,
confidence_score=confidence,
manual_override_avg=None
)
db.add(odometer_state)
await db.commit()
await db.refresh(odometer_state)
return odometer_state
@staticmethod
async def get_estimated_odometer(db: AsyncSession, vehicle_id: int) -> Tuple[Optional[float], float]:
"""
Visszaadja a jármű becsült jelenlegi kilométeróra állását és a bizalom pontszámot.
Returns:
Tuple[estimated_odometer, confidence_score]
"""
stmt = select(VehicleOdometerState).where(VehicleOdometerState.vehicle_id == vehicle_id)
result = await db.execute(stmt)
odometer_state = result.scalars().first()
if not odometer_state:
# Ha nincs állapot, frissítsük
odometer_state = await OdometerService.update_vehicle_stats(db, vehicle_id)
if not odometer_state:
return None, 0.0
return odometer_state.estimated_current_odometer, odometer_state.confidence_score
@staticmethod
async def set_manual_override(db: AsyncSession, vehicle_id: int, daily_avg: Optional[float]) -> Optional[VehicleOdometerState]:
"""
Adminisztrátori manuális átlag beállítása.
Args:
daily_avg: Napi átlagos kilométer (km/nap). Ha None, törli a manuális beállítást.
"""
stmt = select(VehicleOdometerState).where(VehicleOdometerState.vehicle_id == vehicle_id)
result = await db.execute(stmt)
odometer_state = result.scalars().first()
if not odometer_state:
# Ha nincs állapot, hozzuk létre
odometer_state = VehicleOdometerState(
vehicle_id=vehicle_id,
last_recorded_odometer=0,
last_recorded_date=datetime.now(),
daily_avg_distance=0,
estimated_current_odometer=0,
confidence_score=0,
manual_override_avg=daily_avg
)
db.add(odometer_state)
else:
odometer_state.manual_override_avg = daily_avg
# Frissítsük a becslést a manuális átlaggal
if daily_avg is not None:
days_since_last = (datetime.now(odometer_state.last_recorded_date.tzinfo) - odometer_state.last_recorded_date).days
odometer_state.estimated_current_odometer = odometer_state.last_recorded_odometer + (daily_avg * max(days_since_last, 0))
odometer_state.confidence_score = 1.0
await db.commit()
await db.refresh(odometer_state)
return odometer_state