146 lines
4.3 KiB
Python
146 lines
4.3 KiB
Python
|
|
"""Field mapping utilities for mobile.de listing format conversion."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import date
|
||
|
|
from decimal import Decimal
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from app.models.vehicle import Vehicle
|
||
|
|
|
||
|
|
|
||
|
|
# mobile.de category mapping based on vehicle_type
|
||
|
|
_CATEGORY_MAP: dict[str, str] = {
|
||
|
|
"lkw": "Truck",
|
||
|
|
"pkw": "Car",
|
||
|
|
"baumaschine": "ConstructionMachine",
|
||
|
|
"stapler": "ForkliftTruck",
|
||
|
|
"transporter": "Van",
|
||
|
|
}
|
||
|
|
|
||
|
|
# LKW sub-type mapping for mobile.de category refinement
|
||
|
|
_LKW_TYPE_MAP: dict[str, str] = {
|
||
|
|
"sattelzugmaschine": "SemiTractor",
|
||
|
|
"sattelauflieger": "SemiTrailer",
|
||
|
|
"kipper": "Tipper",
|
||
|
|
"kuehlmobil": "RefrigeratedVehicle",
|
||
|
|
"tieflader": "LowLoader",
|
||
|
|
"silofahrzeug": "SiloVehicle",
|
||
|
|
"tankwagen": "TankVehicle",
|
||
|
|
"kranwagen": "CraneVehicle",
|
||
|
|
"muldenkipper": "DumperTruck",
|
||
|
|
"sattelkipper": "SemiTipper",
|
||
|
|
"schwertransporter": "HeavyTransporter",
|
||
|
|
"sonderkonstruktion": "SpecialConstruction",
|
||
|
|
"pritsche": "Flatbed",
|
||
|
|
"planenlkw": "TarpaulinTruck",
|
||
|
|
"boxenlkw": "BoxBodyTruck",
|
||
|
|
"iso_lkw": "IsoTruck",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _format_first_registration(reg_date: date | None) -> str | None:
|
||
|
|
"""Convert date to YYYY-MM format for mobile.de."""
|
||
|
|
if reg_date is None:
|
||
|
|
return None
|
||
|
|
return reg_date.strftime("%Y-%m")
|
||
|
|
|
||
|
|
|
||
|
|
def _format_mileage(vehicle: Vehicle) -> dict[str, Any] | None:
|
||
|
|
"""Map mileage or operating hours to mobile.de mileage field."""
|
||
|
|
if vehicle.mileage_km is not None:
|
||
|
|
return {"value": vehicle.mileage_km, "unit": "km"}
|
||
|
|
if vehicle.operating_hours is not None:
|
||
|
|
unit = vehicle.operating_hours_unit or "h"
|
||
|
|
return {"value": float(vehicle.operating_hours), "unit": unit}
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def _format_price(price: Decimal) -> dict[str, Any]:
|
||
|
|
"""Map price to mobile.de price format."""
|
||
|
|
return {
|
||
|
|
"amount": float(price),
|
||
|
|
"currency": "EUR",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _format_power(power_kw: int | None, power_hp: int | None) -> dict[str, Any] | None:
|
||
|
|
"""Map power to mobile.de power format."""
|
||
|
|
if power_kw is None and power_hp is None:
|
||
|
|
return None
|
||
|
|
return {
|
||
|
|
"powerKw": power_kw,
|
||
|
|
"powerHp": power_hp,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _format_category(vehicle: Vehicle) -> str:
|
||
|
|
"""Determine mobile.de category from vehicle type and lkw_type."""
|
||
|
|
base = _CATEGORY_MAP.get(vehicle.vehicle_type, "Other")
|
||
|
|
if vehicle.vehicle_type == "lkw" and vehicle.lkw_type:
|
||
|
|
# Try direct match, then strip lkw_ prefix for lookup
|
||
|
|
lkw_key = vehicle.lkw_type
|
||
|
|
if lkw_key in _LKW_TYPE_MAP:
|
||
|
|
return _LKW_TYPE_MAP[lkw_key]
|
||
|
|
stripped = lkw_key.removeprefix("lkw_")
|
||
|
|
if stripped in _LKW_TYPE_MAP:
|
||
|
|
return _LKW_TYPE_MAP[stripped]
|
||
|
|
return base
|
||
|
|
return base
|
||
|
|
|
||
|
|
|
||
|
|
def map_fields(vehicle: Vehicle) -> dict[str, Any]:
|
||
|
|
"""Map a Vehicle model to the mobile.de listing Ad format.
|
||
|
|
|
||
|
|
Converts internal vehicle fields to the mobile.de REST API listing format.
|
||
|
|
See: https://developer.mobile.de/api/seller-listings
|
||
|
|
"""
|
||
|
|
mileage = _format_mileage(vehicle)
|
||
|
|
power = _format_power(vehicle.power_kw, vehicle.power_hp)
|
||
|
|
|
||
|
|
ad: dict[str, Any] = {
|
||
|
|
"vin": vehicle.fin,
|
||
|
|
"make": vehicle.make,
|
||
|
|
"model": vehicle.model,
|
||
|
|
"category": _format_category(vehicle),
|
||
|
|
"price": _format_price(vehicle.price),
|
||
|
|
"availabilityStatus": vehicle.availability,
|
||
|
|
"condition": vehicle.condition,
|
||
|
|
}
|
||
|
|
|
||
|
|
if vehicle.first_registration is not None:
|
||
|
|
ad["firstRegistration"] = _format_first_registration(
|
||
|
|
vehicle.first_registration
|
||
|
|
)
|
||
|
|
|
||
|
|
if mileage is not None:
|
||
|
|
ad["mileage"] = mileage
|
||
|
|
|
||
|
|
if power is not None:
|
||
|
|
ad["power"] = power
|
||
|
|
|
||
|
|
if vehicle.fuel_type is not None:
|
||
|
|
ad["fuelType"] = vehicle.fuel_type
|
||
|
|
|
||
|
|
if vehicle.transmission is not None:
|
||
|
|
ad["transmission"] = vehicle.transmission
|
||
|
|
|
||
|
|
if vehicle.color is not None:
|
||
|
|
ad["color"] = vehicle.color
|
||
|
|
|
||
|
|
if vehicle.year is not None:
|
||
|
|
ad["year"] = vehicle.year
|
||
|
|
|
||
|
|
if vehicle.location is not None:
|
||
|
|
ad["sellerLocation"] = vehicle.location
|
||
|
|
|
||
|
|
if vehicle.machine_type is not None:
|
||
|
|
ad["bodyType"] = vehicle.machine_type
|
||
|
|
elif vehicle.body_type is not None:
|
||
|
|
ad["bodyType"] = vehicle.body_type
|
||
|
|
|
||
|
|
if vehicle.description is not None:
|
||
|
|
ad["description"] = vehicle.description
|
||
|
|
|
||
|
|
return ad
|