from datetime import date as Date, datetime from decimal import Decimal from typing import Optional from pydantic import BaseModel, Field # ── Auth ────────────────────────────────────────────────────────────────────── class LoginRequest(BaseModel): username: str password: str class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" class UserPayload(BaseModel): sub: str username: str is_admin: bool timezone: str exp: int admin_id: Optional[int] = None class AuthResponse(BaseModel): user: UserPayload class ChangePasswordRequest(BaseModel): current_password: str new_password: str = Field(min_length=10) class ResetPasswordRequest(BaseModel): new_password: str = Field(min_length=10) class TimezoneUpdate(BaseModel): timezone: str = Field(min_length=1, max_length=64) # ── Users ───────────────────────────────────────────────────────────────────── class UserCreate(BaseModel): username: str = Field(min_length=2, max_length=64) password: str = Field(min_length=10) class UserOut(BaseModel): id: int username: str is_admin: bool is_disabled: bool timezone: str created_at: datetime model_config = {"from_attributes": True} # ── Egg Collections ─────────────────────────────────────────────────────────── class EggCollectionCreate(BaseModel): date: Date eggs: int = Field(ge=0) notes: Optional[str] = None class EggCollectionUpdate(BaseModel): date: Optional[Date] = None eggs: Optional[int] = Field(default=None, ge=0) notes: Optional[str] = None class EggCollectionOut(BaseModel): id: int date: Date eggs: int notes: Optional[str] created_at: datetime model_config = {"from_attributes": True} # ── Flock History ───────────────────────────────────────────────────────────── class FlockHistoryCreate(BaseModel): date: Date chicken_count: int = Field(ge=0) notes: Optional[str] = None class FlockHistoryUpdate(BaseModel): date: Optional[Date] = None chicken_count: Optional[int] = Field(default=None, ge=0) notes: Optional[str] = None class FlockHistoryOut(BaseModel): id: int date: Date chicken_count: int notes: Optional[str] created_at: datetime model_config = {"from_attributes": True} # ── Feed Purchases ──────────────────────────────────────────────────────────── class FeedPurchaseCreate(BaseModel): date: Date bags: Decimal = Field(gt=0, decimal_places=2) price_per_bag: Decimal = Field(gt=0, decimal_places=2) notes: Optional[str] = None class FeedPurchaseUpdate(BaseModel): date: Optional[Date] = None bags: Optional[Decimal] = Field(default=None, gt=0, decimal_places=2) price_per_bag: Optional[Decimal] = Field(default=None, gt=0, decimal_places=2) notes: Optional[str] = None class FeedPurchaseOut(BaseModel): id: int date: Date bags: Decimal price_per_bag: Decimal notes: Optional[str] created_at: datetime model_config = {"from_attributes": True} # ── Other Purchases ─────────────────────────────────────────────────────────── class OtherPurchaseCreate(BaseModel): date: Date total: Decimal = Field(gt=0, decimal_places=2) notes: Optional[str] = None class OtherPurchaseUpdate(BaseModel): date: Optional[Date] = None total: Optional[Decimal] = Field(default=None, gt=0, decimal_places=2) notes: Optional[str] = None class OtherPurchaseOut(BaseModel): id: int date: Date total: Decimal notes: Optional[str] created_at: datetime model_config = {"from_attributes": True} # ── Stats ───────────────────────────────────────────────────────────────────── class MonthlySummary(BaseModel): year: int month: int month_label: str total_eggs: int days_logged: int avg_eggs_per_day: Optional[float] flock_at_month_end: Optional[int] avg_eggs_per_hen_per_day: Optional[float] feed_cost: Optional[Decimal] other_cost: Optional[Decimal] cost_per_egg: Optional[Decimal] cost_per_dozen: Optional[Decimal] class DashboardStats(BaseModel): current_flock: Optional[int] total_eggs_alltime: int total_eggs_30d: int total_eggs_7d: int avg_eggs_per_day_30d: Optional[float] avg_eggs_per_hen_day_30d: Optional[float] days_tracked: int class BudgetStats(BaseModel): total_feed_cost: Optional[Decimal] total_feed_cost_30d: Optional[Decimal] total_other_cost: Optional[Decimal] total_other_cost_30d: Optional[Decimal] total_eggs_alltime: int total_eggs_30d: int cost_per_egg: Optional[Decimal] cost_per_dozen: Optional[Decimal] cost_per_egg_30d: Optional[Decimal] cost_per_dozen_30d: Optional[Decimal]