Files
fast-next-template/backend/app/schemas/syndarix/sprint.py
Felipe Cardoso 520a4d60fb feat(backend): Add Syndarix domain models with CRUD operations
- Add Project model with slug, description, autonomy level, and settings
- Add AgentType model for agent templates with model config and failover
- Add AgentInstance model for running agents with status and memory
- Add Issue model with external tracker sync (Gitea/GitHub/GitLab)
- Add Sprint model with velocity tracking and lifecycle management
- Add comprehensive Pydantic schemas with validation
- Add full CRUD operations for all models with filtering/sorting
- Add 280+ tests for models, schemas, and CRUD operations

Implements #23, #24, #25, #26, #27

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 02:07:27 +01:00

136 lines
3.4 KiB
Python

# 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)
completed_points: 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)
completed_points: 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."""
completed_points: 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
completed_points: int | None
velocity: float | None # completed/planned ratio
class SprintBurndown(BaseModel):
"""Schema for sprint burndown data point."""
date: date
remaining_points: int
ideal_remaining: float