Phase 3.1: Enhanced Chore Logging and Reporting System

This commit is contained in:
2026-02-05 12:33:51 +11:00
commit e3cae7bfbb
178 changed files with 30105 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# Schemas package
from app.schemas import auth, chore, user, chore_completion_log
__all__ = ["auth", "chore", "user", "chore_completion_log"]

View File

@@ -0,0 +1,11 @@
"""Authentication schemas."""
from pydantic import BaseModel
class Token(BaseModel):
"""Token response schema."""
access_token: str
token_type: str
class TokenData(BaseModel):
"""Token data schema."""
username: str | None = None

View File

@@ -0,0 +1,99 @@
"""Chore schemas."""
from pydantic import BaseModel, ConfigDict, field_validator
from typing import Optional, Union, List
from datetime import datetime, date
from app.models.chore import ChoreFrequency, ChoreStatus, ChoreAssignmentType
class ChoreBase(BaseModel):
"""Base chore schema."""
title: str
description: Optional[str] = None
room: str
frequency: ChoreFrequency
points: Optional[int] = 0
image_url: Optional[str] = None
assignment_type: Optional[ChoreAssignmentType] = ChoreAssignmentType.ANY_ONE
due_date: Optional[Union[datetime, date, str]] = None
@field_validator('due_date', mode='before')
@classmethod
def parse_due_date(cls, v):
"""Parse due_date to handle various formats."""
if v is None or v == '' or isinstance(v, (datetime, date)):
return None if v == '' else v
if isinstance(v, str):
# Try parsing as datetime first
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d']:
try:
return datetime.strptime(v, fmt)
except ValueError:
continue
# If no format matches, return None instead of the invalid string
return None
return None
class ChoreCreate(ChoreBase):
"""Schema for creating a chore."""
assigned_user_ids: Optional[List[int]] = [] # Multiple users can be assigned
class ChoreUpdate(BaseModel):
"""Schema for updating a chore."""
title: Optional[str] = None
description: Optional[str] = None
room: Optional[str] = None
frequency: Optional[ChoreFrequency] = None
points: Optional[int] = None
status: Optional[ChoreStatus] = None
assignment_type: Optional[ChoreAssignmentType] = None
assigned_user_ids: Optional[List[int]] = None # Multiple users
due_date: Optional[Union[datetime, date, str]] = None
@field_validator('due_date', mode='before')
@classmethod
def parse_due_date(cls, v):
"""Parse due_date to handle various formats."""
if v is None or v == '' or isinstance(v, (datetime, date)):
return None if v == '' else v
if isinstance(v, str):
# Try parsing as datetime first
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%d']:
try:
return datetime.strptime(v, fmt)
except ValueError:
continue
# If no format matches, return None instead of the invalid string
return None
return None
class AssignedUserDetail(BaseModel):
"""User info for chore assignment."""
model_config = ConfigDict(from_attributes=True)
id: int
username: str
full_name: str
avatar_url: Optional[str] = None
birthday: Optional[date] = None
completed_at: Optional[datetime] = None # When this user completed the chore
class Chore(ChoreBase):
"""Schema for a chore response."""
model_config = ConfigDict(from_attributes=True)
id: int
status: ChoreStatus
points: int
assignment_type: ChoreAssignmentType
assigned_users: List[AssignedUserDetail] = [] # Multiple users with completion status
completed_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
# Legacy field for backward compatibility
assigned_user_id: Optional[int] = None

View File

@@ -0,0 +1,66 @@
"""Schemas for Chore Completion Logs."""
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional
class ChoreCompletionLogBase(BaseModel):
"""Base schema for chore completion log."""
notes: Optional[str] = Field(None, description="Optional notes about the completion")
class ChoreCompletionLogCreate(ChoreCompletionLogBase):
"""Schema for creating a chore completion log."""
chore_id: int = Field(..., description="ID of the chore being completed")
user_id: int = Field(..., description="ID of the user completing the chore")
completed_at: Optional[datetime] = Field(None, description="When the chore was completed (defaults to now)")
class ChoreCompletionLogUpdate(BaseModel):
"""Schema for updating a chore completion log."""
notes: Optional[str] = Field(None, description="Update notes about the completion")
verified_by_user_id: Optional[int] = Field(None, description="ID of user verifying the completion")
class ChoreCompletionLog(ChoreCompletionLogBase):
"""Schema for chore completion log response."""
id: int
chore_id: int
user_id: int
completed_at: datetime
verified_by_user_id: Optional[int] = None
created_at: datetime
# Nested objects for expanded responses
chore_title: Optional[str] = None
user_name: Optional[str] = None
user_avatar: Optional[str] = None
verified_by_name: Optional[str] = None
class Config:
from_attributes = True
class WeeklyChoreReport(BaseModel):
"""Schema for weekly chore completion report."""
start_date: datetime
end_date: datetime
total_completions: int
completions_by_user: dict[str, int] # {username: count}
completions_by_chore: dict[str, int] # {chore_title: count}
completions_by_day: dict[str, int] # {day: count}
top_performers: list[dict] # [{username, count, avatar_url}]
recent_completions: list[ChoreCompletionLog]
class UserChoreStats(BaseModel):
"""Schema for user-specific chore statistics."""
user_id: int
username: str
full_name: Optional[str] = None
avatar_url: Optional[str] = None
total_completions: int
completions_this_week: int
completions_this_month: int
favorite_chore: Optional[str] = None
recent_completions: list[ChoreCompletionLog]

View File

@@ -0,0 +1,47 @@
"""User schemas."""
from pydantic import BaseModel, Field
from datetime import datetime, date
from typing import Optional
class UserBase(BaseModel):
"""Base user schema."""
username: str
email: str # Changed from EmailStr to allow .local domains for home networks
full_name: Optional[str] = None
discord_id: Optional[str] = None
profile_picture: Optional[str] = None
avatar_url: Optional[str] = None
birthday: Optional[date] = None
class UserCreate(UserBase):
"""Schema for creating a user."""
password: str
class UserUpdate(BaseModel):
"""Schema for updating a user."""
email: Optional[str] = None
full_name: Optional[str] = None
discord_id: Optional[str] = None
profile_picture: Optional[str] = None
birthday: Optional[date] = None
password: Optional[str] = None
is_active: Optional[bool] = None
class UserAdminUpdate(UserUpdate):
"""Schema for admin updating a user (includes admin-only fields)."""
is_admin: Optional[bool] = None
class UserResponse(UserBase):
"""Schema for user response."""
id: int
is_active: bool
is_admin: bool
created_at: datetime
class Config:
from_attributes = True
class UserLogin(BaseModel):
"""Schema for user login."""
username: str
password: str