from __future__ import annotations import enum import uuid from datetime import datetime, time from sqlalchemy import JSON, CheckConstraint, DateTime, Enum, ForeignKey, Integer, String, Text, Time, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from .database import Base class SourceType(str, enum.Enum): official_record = "official_record" user = "user" manual_import = "manual_import" class ModerationStatus(str, enum.Enum): pending = "pending" approved = "approved" rejected = "rejected" class BaitKind(str, enum.Enum): bait = "bait" lure = "lure" unknown = "unknown" class ImportStatus(str, enum.Enum): running = "running" success = "success" partial = "partial" failed = "failed" class Fish(Base): __tablename__ = "fish" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) slug: Mapped[str] = mapped_column(String(100), unique=True) name_ru: Mapped[str] = mapped_column(String(200), unique=True) trophy_weight_g: Mapped[int | None] class Waterbody(Base): __tablename__ = "waterbody" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) slug: Mapped[str] = mapped_column(String(100), unique=True) name_ru: Mapped[str] = mapped_column(String(200), unique=True) unlock_level: Mapped[int | None] class Bait(Base): __tablename__ = "bait" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) name: Mapped[str] = mapped_column(String(200)) normalized_name: Mapped[str] = mapped_column(String(200), unique=True) kind: Mapped[BaitKind] = mapped_column(Enum(BaitKind)) class Spot(Base): __tablename__ = "spot" __table_args__ = (UniqueConstraint("waterbody_id", "x", "y"),) id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) waterbody_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("waterbody.id")) x: Mapped[int] y: Mapped[int] description: Mapped[str | None] = mapped_column(Text) waterbody: Mapped[Waterbody] = relationship() class CatchReport(Base): __tablename__ = "catch_report" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) fish_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("fish.id")) spot_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("spot.id")) waterbody_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("waterbody.id")) bait_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("bait.id")) weight_g: Mapped[int] fishing_method: Mapped[str | None] = mapped_column(String(50)) rig_type: Mapped[str | None] = mapped_column(String(100)) retrieve_method: Mapped[str | None] = mapped_column(String(100)) retrieve_speed: Mapped[int | None] game_time: Mapped[time | None] = mapped_column(Time) caught_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) reported_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) player_name: Mapped[str | None] = mapped_column(String(100)) source_type: Mapped[SourceType] = mapped_column(Enum(SourceType)) source_url: Mapped[str | None] = mapped_column(Text) source_external_id: Mapped[str | None] = mapped_column(String(64), unique=True) source_confidence: Mapped[int] moderation_status: Mapped[ModerationStatus] = mapped_column(Enum(ModerationStatus)) screenshot_key: Mapped[str | None] = mapped_column(Text) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) raw_payload: Mapped[dict | None] = mapped_column(JSON) fish: Mapped[Fish] = relationship() spot: Mapped[Spot | None] = relationship() waterbody: Mapped[Waterbody] = relationship() bait: Mapped[Bait | None] = relationship() class OfficialRecordImport(Base): __tablename__ = "official_record_import" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) status: Mapped[ImportStatus] = mapped_column(Enum(ImportStatus)) source_url: Mapped[str] = mapped_column(Text) rows_seen: Mapped[int] = mapped_column(default=0) rows_created: Mapped[int] = mapped_column(default=0) rows_updated: Mapped[int] = mapped_column(default=0) error_summary: Mapped[str | None] = mapped_column(Text) response_status: Mapped[int | None] response_etag: Mapped[str | None] = mapped_column(String(500)) response_last_modified: Mapped[str | None] = mapped_column(String(500)) response_content_type: Mapped[str | None] = mapped_column(String(200)) response_bytes: Mapped[int | None] not_modified: Mapped[bool] = mapped_column(default=False) class ModerationEvent(Base): __tablename__ = "moderation_event" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) catch_report_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("catch_report.id")) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) previous_status: Mapped[ModerationStatus] = mapped_column(Enum(ModerationStatus, name="moderationstatus", create_type=False)) new_status: Mapped[ModerationStatus] = mapped_column(Enum(ModerationStatus, name="moderationstatus", create_type=False)) moderator: Mapped[str] = mapped_column(String(100)) reason: Mapped[str | None] = mapped_column(Text) catch_report: Mapped[CatchReport] = relationship() class SubmissionAttempt(Base): __tablename__ = "submission_attempt" id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) client_hash: Mapped[str] = mapped_column(String(64), index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True) class DataSource(Base): __tablename__ = "data_source" key: Mapped[str] = mapped_column(String(50), primary_key=True) name: Mapped[str] = mapped_column(String(100)) base_url: Mapped[str] = mapped_column(Text) default_confidence: Mapped[int] enabled: Mapped[bool] = mapped_column(default=False) class ExternalObservation(Base): __tablename__ = "external_observation" __table_args__ = (UniqueConstraint("source_system", "source_external_id"),) id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) source_system: Mapped[str] = mapped_column(ForeignKey("data_source.key"), index=True) source_external_id: Mapped[str] = mapped_column(String(200)) source_url: Mapped[str] = mapped_column(Text) fish_name: Mapped[str] = mapped_column(String(200)) fish_external_id: Mapped[str | None] = mapped_column(String(200)) waterbody_name: Mapped[str] = mapped_column(String(200)) waterbody_external_id: Mapped[str | None] = mapped_column(String(200)) x: Mapped[int | None] y: Mapped[int | None] weight_g: Mapped[int | None] published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True) status: Mapped[str] = mapped_column(String(30), default="staged", index=True) payload: Mapped[dict] = mapped_column(JSON) fish_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("fish.id")) waterbody_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("waterbody.id")) catch_report_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("catch_report.id"), unique=True) review_note: Mapped[str | None] = mapped_column(Text) reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) source: Mapped[DataSource] = relationship() fish: Mapped[Fish | None] = relationship() waterbody: Mapped[Waterbody | None] = relationship() catch_report: Mapped[CatchReport | None] = relationship() class ExternalEntityAlias(Base): __tablename__ = "external_entity_alias" __table_args__ = ( UniqueConstraint("source_system", "entity_type", "external_id"), CheckConstraint( "(entity_type = 'fish' AND fish_id IS NOT NULL AND waterbody_id IS NULL) OR " "(entity_type = 'waterbody' AND waterbody_id IS NOT NULL AND fish_id IS NULL)", name="ck_external_entity_alias_target", ), ) id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) source_system: Mapped[str] = mapped_column(ForeignKey("data_source.key"), index=True) entity_type: Mapped[str] = mapped_column(String(20)) external_id: Mapped[str] = mapped_column(String(200)) external_name: Mapped[str] = mapped_column(String(200)) fish_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("fish.id")) waterbody_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("waterbody.id")) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True)) fish: Mapped[Fish | None] = relationship() waterbody: Mapped[Waterbody | None] = relationship()