from __future__ import annotations from datetime import datetime from uuid import UUID from pydantic import BaseModel, ConfigDict, Field, field_validator class FishOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: UUID slug: str name_ru: str trophy_weight_g: int | None class WaterbodyOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: UUID slug: str name_ru: str unlock_level: int | None class BaitOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: UUID name: str normalized_name: str kind: str class ActivityOut(BaseModel): spot_id: UUID waterbody_slug: str waterbody: str fish_slug: str fish: str x: int y: int best_bait: str | None catches: int unique_players: int average_weight_g: int max_weight_g: int last_confirmed_at: datetime activity_score: int confidence_score: int explanation: str class CatchOut(BaseModel): id: UUID fish: str weight_g: int bait: str | None player_name: str | None caught_at: datetime | None reported_at: datetime retrieve_method: str | None retrieve_speed: int | None class SpotOut(BaseModel): id: UUID waterbody_slug: str waterbody: str x: int y: int description: str | None catches_24h: int catches_3d: int catches_7d: int top_baits: list[str] class OfficialRecordOut(BaseModel): id: UUID fish: str weight_g: int waterbody: str bait: str | None player_name: str | None record_date: datetime | None category: str | None region: str | None source_url: str | None class ImportRunOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: UUID started_at: datetime finished_at: datetime | None status: str source_url: str rows_seen: int rows_created: int rows_updated: int error_summary: str | None response_status: int | None response_etag: str | None response_last_modified: str | None response_content_type: str | None response_bytes: int | None not_modified: bool class CatchReportCreate(BaseModel): fish_slug: str waterbody_slug: str x: int = Field(ge=-10_000, le=10_000) y: int = Field(ge=-10_000, le=10_000) weight_g: int = Field(gt=0, le=3_000_000) bait_name: str | None = Field(default=None, max_length=200) fishing_method: str | None = Field(default=None, max_length=50) rig_type: str | None = Field(default=None, max_length=100) retrieve_method: str | None = Field(default=None, max_length=100) retrieve_speed: int | None = Field(default=None, ge=0, le=100) caught_at: datetime | None = None player_name: str | None = Field(default=None, max_length=100) source_url: str | None = Field(default=None, max_length=1000) comment: str | None = Field(default=None, max_length=1000) website: str = Field(default="", max_length=200) @field_validator("fish_slug", "waterbody_slug") @classmethod def nonempty_slug(cls, value: str) -> str: value = value.strip() if not value: raise ValueError("must not be empty") return value class CatchReportCreated(BaseModel): id: UUID moderation_status: str class AdminCatchReportOut(BaseModel): id: UUID fish: str waterbody: str coordinates: str weight_g: int bait: str | None player_name: str | None reported_at: datetime moderation_status: str comment: str | None screenshot_url: str | None class ModerationUpdate(BaseModel): status: str reason: str | None = Field(default=None, max_length=1000) @field_validator("status") @classmethod def known_status(cls, value: str) -> str: if value not in {"approved", "rejected", "pending"}: raise ValueError("unknown moderation status") return value class ExternalObservationOut(BaseModel): id: UUID source_system: str source_external_id: str source_url: str fish_name: str fish_external_id: str | None waterbody_name: str waterbody_external_id: str | None x: int | None y: int | None weight_g: int | None published_at: datetime | None last_seen_at: datetime status: str fish_slug: str | None waterbody_slug: str | None catch_report_id: UUID | None review_note: str | None class ExternalObservationMapping(BaseModel): fish_slug: str waterbody_slug: str note: str | None = Field(default=None, max_length=1000) class ExternalObservationDecision(BaseModel): reason: str = Field(min_length=1, max_length=1000) class ExternalObservationPublished(BaseModel): observation_id: UUID catch_report_id: UUID status: str