refaktorálás javításai
This commit is contained in:
213
backend/app/services/odometer_service.py
Normal file
213
backend/app/services/odometer_service.py
Normal 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
|
||||
Reference in New Issue
Block a user