# app/schemas/syndarix/sprint.py """ Pydantic schemas for Sprint entity. """ from datetime import date, datetime from uuid import UUID from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from .enums import SprintStatus class SprintBase(BaseModel): """Base sprint schema with common fields.""" name: str = Field(..., min_length=1, max_length=255) number: int = Field(..., ge=1) goal: str | None = None start_date: date end_date: date status: SprintStatus = SprintStatus.PLANNED planned_points: int | None = Field(None, ge=0) velocity: int | None = Field(None, ge=0) @field_validator("name") @classmethod def validate_name(cls, v: str) -> str: """Validate sprint name.""" if not v or v.strip() == "": raise ValueError("Sprint name cannot be empty") return v.strip() @model_validator(mode="after") def validate_dates(self) -> "SprintBase": """Validate that end_date is after start_date.""" if self.end_date < self.start_date: raise ValueError("End date must be after or equal to start date") return self class SprintCreate(SprintBase): """Schema for creating a new sprint.""" project_id: UUID class SprintUpdate(BaseModel): """Schema for updating a sprint.""" name: str | None = Field(None, min_length=1, max_length=255) goal: str | None = None start_date: date | None = None end_date: date | None = None status: SprintStatus | None = None planned_points: int | None = Field(None, ge=0) velocity: int | None = Field(None, ge=0) @field_validator("name") @classmethod def validate_name(cls, v: str | None) -> str | None: """Validate sprint name.""" if v is not None and (not v or v.strip() == ""): raise ValueError("Sprint name cannot be empty") return v.strip() if v else v class SprintStart(BaseModel): """Schema for starting a sprint.""" start_date: date | None = None # Optionally override start date class SprintComplete(BaseModel): """Schema for completing a sprint.""" velocity: int | None = Field(None, ge=0) notes: str | None = None class SprintInDB(SprintBase): """Schema for sprint in database.""" id: UUID project_id: UUID created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) class SprintResponse(SprintBase): """Schema for sprint API responses.""" id: UUID project_id: UUID created_at: datetime updated_at: datetime # Expanded fields from relationships project_name: str | None = None project_slug: str | None = None issue_count: int | None = 0 open_issues: int | None = 0 completed_issues: int | None = 0 model_config = ConfigDict(from_attributes=True) class SprintListResponse(BaseModel): """Schema for paginated sprint list responses.""" sprints: list[SprintResponse] total: int page: int page_size: int pages: int class SprintVelocity(BaseModel): """Schema for sprint velocity metrics.""" sprint_number: int sprint_name: str planned_points: int | None velocity: int | None # Sum of completed story points velocity_ratio: float | None # velocity/planned ratio class SprintBurndown(BaseModel): """Schema for sprint burndown data point.""" date: date remaining_points: int ideal_remaining: float