203 lines
9.1 KiB
Python
203 lines
9.1 KiB
Python
# /opt/docker/dev/service_finder/backend/app/models/vehicle/vehicle.py
|
|
"""
|
|
TCO (Total Cost of Ownership) alapmodelljei a 'vehicle' sémában.
|
|
- CostCategory: Standardizált költségkategóriák hierarchiája
|
|
- VehicleCost: Járműhöz kapcsolódó tényleges költségnapló
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
import uuid
|
|
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Text, Numeric, UniqueConstraint, Float, CheckConstraint
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy.sql import func
|
|
from app.database import Base
|
|
|
|
|
|
class CostCategory(Base):
|
|
"""
|
|
Standardizált költségkategóriák hierarchikus fája.
|
|
Rendszerkategóriák (is_system=True) nem törölhetők, csak felhasználói kategóriák.
|
|
"""
|
|
__tablename__ = "cost_categories"
|
|
__table_args__ = {"schema": "vehicle"}
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
parent_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer,
|
|
ForeignKey("vehicle.cost_categories.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
index=True
|
|
)
|
|
code: Mapped[str] = mapped_column(String(50), unique=True, index=True, nullable=False)
|
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
is_system: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
|
|
|
|
# Hierarchikus kapcsolatok
|
|
parent: Mapped[Optional["CostCategory"]] = relationship(
|
|
"CostCategory",
|
|
remote_side=[id],
|
|
back_populates="children",
|
|
foreign_keys=[parent_id]
|
|
)
|
|
children: Mapped[list["CostCategory"]] = relationship(
|
|
"CostCategory",
|
|
back_populates="parent",
|
|
foreign_keys=[parent_id]
|
|
)
|
|
|
|
# Kapcsolódó költségek
|
|
costs: Mapped[list["VehicleCost"]] = relationship("VehicleCost", back_populates="category")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"CostCategory(id={self.id}, code='{self.code}', name='{self.name}')"
|
|
|
|
|
|
class VehicleCost(Base):
|
|
"""
|
|
Járműhöz kapcsolódó tényleges költségnapló.
|
|
Minden költséghez kötelező az odometer állás (km) és a dátum.
|
|
Az organization_id az Univerzális Flotta hivatkozás (fleet.organizations).
|
|
"""
|
|
__tablename__ = "costs"
|
|
__table_args__ = (
|
|
UniqueConstraint("vehicle_id", "category_id", "date", "odometer", name="uq_cost_unique_entry"),
|
|
{"schema": "vehicle"}
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
vehicle_id: Mapped[int] = mapped_column(
|
|
Integer,
|
|
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True
|
|
)
|
|
organization_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer,
|
|
ForeignKey("fleet.organizations.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
index=True
|
|
)
|
|
category_id: Mapped[int] = mapped_column(
|
|
Integer,
|
|
ForeignKey("vehicle.cost_categories.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
index=True
|
|
)
|
|
amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False) # Összeg
|
|
currency: Mapped[str] = mapped_column(String(3), default="HUF", server_default="'HUF'") # ISO valutakód
|
|
odometer: Mapped[int] = mapped_column(Integer, nullable=False) # Kilométeróra állás (km)
|
|
date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True)
|
|
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
|
|
|
|
# Kapcsolatok
|
|
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="costs")
|
|
organization: Mapped[Optional["Organization"]] = relationship("Organization", back_populates="vehicle_costs")
|
|
category: Mapped["CostCategory"] = relationship("CostCategory", back_populates="costs")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"VehicleCost(id={self.id}, vehicle_id={self.vehicle_id}, amount={self.amount} {self.currency})"
|
|
|
|
|
|
class VehicleOdometerState(Base):
|
|
"""
|
|
Jármű kilométeróra állapotának és becslésének tárolása.
|
|
Adminisztrátor által paraméterezhető algoritmusokkal működik.
|
|
"""
|
|
__tablename__ = "vehicle_odometer_states"
|
|
__table_args__ = {"schema": "vehicle"}
|
|
|
|
vehicle_id: Mapped[int] = mapped_column(
|
|
Integer,
|
|
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
nullable=False
|
|
)
|
|
last_recorded_odometer: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
last_recorded_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
daily_avg_distance: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
|
|
estimated_current_odometer: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
|
|
confidence_score: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
|
manual_override_avg: Mapped[Optional[float]] = mapped_column(Numeric(10, 2), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
|
|
|
|
# Kapcsolat a jármű definícióval
|
|
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="odometer_state")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"VehicleOdometerState(vehicle_id={self.vehicle_id}, estimated={self.estimated_current_odometer}, confidence={self.confidence_score})"
|
|
|
|
|
|
class VehicleUserRating(Base):
|
|
"""
|
|
Jármű értékelési rendszer - User -> Vehicle kapcsolat.
|
|
Egy felhasználó csak egyszer értékelhet egy adott járművet.
|
|
Értékelés 4 dimenzióban 1-10 skálán.
|
|
"""
|
|
__tablename__ = "vehicle_user_ratings"
|
|
__table_args__ = (
|
|
UniqueConstraint("vehicle_id", "user_id", name="uq_vehicle_user_rating_unique"),
|
|
CheckConstraint("driving_experience BETWEEN 1 AND 10", name="ck_driving_experience_range"),
|
|
CheckConstraint("reliability BETWEEN 1 AND 10", name="ck_reliability_range"),
|
|
CheckConstraint("comfort BETWEEN 1 AND 10", name="ck_comfort_range"),
|
|
CheckConstraint("consumption_satisfaction BETWEEN 1 AND 10", name="ck_consumption_satisfaction_range"),
|
|
{"schema": "vehicle"}
|
|
)
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
primary_key=True,
|
|
default=uuid.uuid4,
|
|
server_default=func.gen_random_uuid()
|
|
)
|
|
vehicle_id: Mapped[int] = mapped_column(
|
|
Integer,
|
|
ForeignKey("vehicle.vehicle_model_definitions.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True
|
|
)
|
|
user_id: Mapped[int] = mapped_column(
|
|
Integer,
|
|
ForeignKey("identity.users.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True
|
|
)
|
|
driving_experience: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
reliability: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
comfort: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
consumption_satisfaction: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
comment: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
|
|
|
|
# Kapcsolatok
|
|
vehicle: Mapped["VehicleModelDefinition"] = relationship("VehicleModelDefinition", back_populates="ratings")
|
|
user: Mapped["User"] = relationship("User", back_populates="vehicle_ratings")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"VehicleUserRating(id={self.id}, vehicle_id={self.vehicle_id}, user_id={self.user_id})"
|
|
|
|
@property
|
|
def average_score(self) -> float:
|
|
"""Számított átlagpontszám a 4 dimenzióból."""
|
|
scores = [self.driving_experience, self.reliability, self.comfort, self.consumption_satisfaction]
|
|
return sum(scores) / 4.0
|
|
|
|
|
|
class GbCatalogDiscovery(Base):
|
|
__tablename__ = "gb_catalog_discovery"
|
|
__table_args__ = {"schema": "vehicle"}
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
vrm: Mapped[str] = mapped_column(String(20), unique=True)
|
|
make: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
|
model: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(20), default='pending')
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) |