from __future__ import annotations from datetime import datetime from sqlalchemy import ( BigInteger, Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, func, ) from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass CAR_TABLE_NAME = "iaai_cars" IMAGE_TABLE_NAME = "iaai_images" SYNC_RUN_TABLE_NAME = "iaai_sync_runs" CURRENCY_ENUM_VALUES = ("JPY", "USD", "EUR", "RUB", "KRW", "AED", "GBP", "CAD") DRIVE_ENUM_VALUES = ("FWD", "RWD", "TWO_WD", "FOUR_WD", "2WD", "4WD", "NA") GEARBOX_ENUM_VALUES = ("AT", "CVT", "MT", "EV", "NA") STEERING_WHEEL_ENUM_VALUES = ("LEFT", "RIGHT", "left", "right", "NA") BODY_TYPE_ENUM_VALUES = ( "COUPE", "SUV", "HATCHBACK", "MINIVAN", "SEDAN", "NA", "Station Wagon", "Pickup", "Truck", "Open", "RV", "Other", "STATION_WAGON", "PICKUP", "TRUCK", "OPEN", "OTHER", ) COUNTRY_ENUM_VALUES = ("JP", "KR", "US", "CA", "NA") ORIGIN_ENUM_VALUES = ( "TAU", "CARSENSOR", "HANAMARU", "ENCAR", "KURUMA_TRADER", "carsensor", "encar", "kuruma_trader", "asnet", "kababa", "ACV", "COPART", "IAAI", "copart", "iaai", "NA", "ASNET", "KABABA", ) SELLING_TYPE_ENUM_VALUES = ("STOCK", "AUCTION", "TENDER", "stock", "auction", "tender", "NA") class Car(Base): __tablename__ = CAR_TABLE_NAME id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) parser_id: Mapped[str] = mapped_column(String(50), nullable=False, unique=True) brand: Mapped[str] = mapped_column(String(50), nullable=False) model: Mapped[str] = mapped_column(String(50), nullable=False) year: Mapped[int | None] = mapped_column(Integer, nullable=True) price: Mapped[int | None] = mapped_column(BigInteger, nullable=True) currency: Mapped[str] = mapped_column( Enum( *CURRENCY_ENUM_VALUES, name="currencyenum", native_enum=True, create_constraint=False, ), nullable=False, default="USD", ) mileage: Mapped[int] = mapped_column(Integer, nullable=False, default=0) country: Mapped[str] = mapped_column( Enum( *COUNTRY_ENUM_VALUES, name="countryenum", native_enum=True, create_constraint=False, ), nullable=False, default="NA", ) is_sold: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) color: Mapped[str] = mapped_column(String(), nullable=False, default="other") drive: Mapped[str | None] = mapped_column( Enum( *DRIVE_ENUM_VALUES, name="driveenum", native_enum=True, create_constraint=False, ), nullable=True, ) gearbox: Mapped[str | None] = mapped_column( Enum( *GEARBOX_ENUM_VALUES, name="gearboxenum", native_enum=True, create_constraint=False, ), nullable=True, ) steering_wheel: Mapped[str | None] = mapped_column( Enum( *STEERING_WHEEL_ENUM_VALUES, name="steeringwheelenum", native_enum=True, create_constraint=False, ), nullable=True, ) body_type: Mapped[str] = mapped_column( Enum( *BODY_TYPE_ENUM_VALUES, name="bodytypeenum", native_enum=True, create_constraint=False, ), nullable=False, default="OTHER", ) engine_volume: Mapped[int | None] = mapped_column(Integer, nullable=True) selling_type: Mapped[str] = mapped_column( Enum( *SELLING_TYPE_ENUM_VALUES, name="sellingtypeenum", native_enum=True, create_constraint=False, ), nullable=False, default="NA", ) one_owner: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) new_car: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_hidden: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) origin: Mapped[str] = mapped_column( Enum( *ORIGIN_ENUM_VALUES, name="originenum", native_enum=True, create_constraint=False, ), nullable=False, default="NA", ) origin_url: Mapped[str] = mapped_column(String(), nullable=False) origin_id: Mapped[str] = mapped_column(String(), nullable=False, unique=True) is_damaged: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) evaluation: Mapped[str | None] = mapped_column(String(), nullable=True) non_smoking: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) rental: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) repair_history: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) slug: Mapped[str] = mapped_column(String(), nullable=False) last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now()) images: Mapped[list["Image"]] = relationship( "Image", back_populates="car", cascade="all, delete-orphan", ) class Image(Base): __tablename__ = IMAGE_TABLE_NAME id: Mapped[int] = mapped_column( BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True, ) fullres_image: Mapped[str] = mapped_column(String(), nullable=False) preview_image: Mapped[str] = mapped_column(String(), nullable=False) order_index: Mapped[int] = mapped_column(Integer, nullable=False) car_id: Mapped[int] = mapped_column( Integer, ForeignKey(f"{CAR_TABLE_NAME}.id", ondelete="CASCADE"), nullable=False, ) car: Mapped[Car] = relationship("Car", back_populates="images") class SyncRun(Base): __tablename__ = SYNC_RUN_TABLE_NAME id: Mapped[int] = mapped_column( BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True, ) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, default=func.now()) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) status: Mapped[str] = mapped_column(Text, nullable=False) lane: Mapped[str] = mapped_column(Text, nullable=False) ids_fetched: Mapped[int] = mapped_column(Integer, nullable=False, default=0) cars_upserted: Mapped[int] = mapped_column(Integer, nullable=False, default=0) cars_failed: Mapped[int] = mapped_column(Integer, nullable=False, default=0) images_upserted: Mapped[int] = mapped_column(Integer, nullable=False, default=0) error_summary: Mapped[str | None] = mapped_column(Text, nullable=True)