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,7 @@
# Models package
from app.models.user import User
from app.models.chore import Chore
from app.models.chore_assignment import ChoreAssignment
from app.models.chore_completion_log import ChoreCompletionLog
__all__ = ["User", "Chore", "ChoreAssignment", "ChoreCompletionLog"]

View File

@@ -0,0 +1,49 @@
"""Chore model."""
from sqlalchemy import Boolean, Column, Integer, String, DateTime, ForeignKey, Enum as SQLEnum
from sqlalchemy.orm import relationship
from datetime import datetime
from app.core.database import Base
import enum
class ChoreFrequency(str, enum.Enum):
"""Chore frequency options."""
ON_TRIGGER = "on_trigger"
DAILY = "daily"
WEEKLY = "weekly"
FORTNIGHTLY = "fortnightly"
MONTHLY = "monthly"
class ChoreStatus(str, enum.Enum):
"""Chore status options."""
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
SKIPPED = "skipped"
class ChoreAssignmentType(str, enum.Enum):
"""Chore assignment type - how the chore should be completed."""
ANY_ONE = "any_one" # Only one assigned person needs to complete
ALL_ASSIGNED = "all_assigned" # All assigned people must complete
class Chore(Base):
"""Chore model."""
__tablename__ = "chores"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False)
description = Column(String(500))
room = Column(String(50)) # bedroom1, bedroom2, kitchen, bathroom1, etc.
frequency = Column(SQLEnum(ChoreFrequency, values_callable=lambda x: [e.value for e in x]), nullable=False)
points = Column(Integer, default=0) # Points awarded for completing the chore
image_url = Column(String(500)) # URL to chore image
assignment_type = Column(SQLEnum(ChoreAssignmentType, values_callable=lambda x: [e.value for e in x]), default=ChoreAssignmentType.ANY_ONE) # How chore should be completed
status = Column(SQLEnum(ChoreStatus, values_callable=lambda x: [e.value for e in x]), default=ChoreStatus.PENDING)
assigned_user_id = Column(Integer, ForeignKey("users.id")) # Deprecated - use assignments instead
due_date = Column(DateTime)
completed_at = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
assigned_user = relationship("User", back_populates="chores") # Deprecated - use assignments
assignments = relationship("ChoreAssignment", back_populates="chore", cascade="all, delete-orphan")

View File

@@ -0,0 +1,19 @@
"""Chore Assignment model for many-to-many user-chore relationship."""
from sqlalchemy import Column, Integer, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
from app.core.database import Base
class ChoreAssignment(Base):
"""Junction table for assigning multiple users to chores."""
__tablename__ = "chore_assignments"
id = Column(Integer, primary_key=True, index=True)
chore_id = Column(Integer, ForeignKey("chores.id", ondelete="CASCADE"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
completed_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
chore = relationship("Chore", back_populates="assignments")
user = relationship("User", back_populates="chore_assignments")

View File

@@ -0,0 +1,31 @@
"""Chore Completion Log model for tracking historical chore completions."""
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text
from sqlalchemy.orm import relationship
from datetime import datetime
from app.core.database import Base
class ChoreCompletionLog(Base):
"""
Comprehensive log of chore completions.
This model tracks every completion instance, allowing for:
- Historical completion data
- Multiple completions of the same chore
- Optional notes and verification
- Weekly/monthly reporting
"""
__tablename__ = "chore_completion_logs"
id = Column(Integer, primary_key=True, index=True)
chore_id = Column(Integer, ForeignKey("chores.id", ondelete="CASCADE"), nullable=False, index=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
completed_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
notes = Column(Text, nullable=True) # Optional notes about the completion
verified_by_user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True) # Optional verification
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
chore = relationship("Chore", foreign_keys=[chore_id])
user = relationship("User", foreign_keys=[user_id], back_populates="chore_completion_logs")
verified_by = relationship("User", foreign_keys=[verified_by_user_id])

View File

@@ -0,0 +1,18 @@
"""Meal model."""
from sqlalchemy import Column, Integer, String, DateTime, Text
from datetime import datetime
from app.core.database import Base
class Meal(Base):
"""Meal model for menu planning."""
__tablename__ = "meals"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False)
description = Column(Text)
meal_type = Column(String(20)) # breakfast, lunch, dinner, snack
scheduled_date = Column(DateTime)
mealie_recipe_id = Column(String(100)) # Link to Mealie recipe
notes = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

View File

@@ -0,0 +1,28 @@
"""User model."""
from sqlalchemy import Boolean, Column, Integer, String, DateTime, Date
from sqlalchemy.orm import relationship
from datetime import datetime
from app.core.database import Base
class User(Base):
"""User model."""
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, index=True, nullable=False)
email = Column(String(100), unique=True, index=True, nullable=False)
full_name = Column(String(100))
hashed_password = Column(String(200), nullable=False)
discord_id = Column(String(100)) # For Discord integration
profile_picture = Column(String(500)) # URL to profile picture
avatar_url = Column(String(500)) # URL to uploaded avatar
birthday = Column(Date, nullable=True) # Birthday for chore logic
is_active = Column(Boolean, default=True)
is_admin = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships (lazy loaded to avoid circular imports)
chores = relationship("Chore", back_populates="assigned_user", lazy="select")
chore_assignments = relationship("ChoreAssignment", back_populates="user", lazy="select")
chore_completion_logs = relationship("ChoreCompletionLog", foreign_keys="[ChoreCompletionLog.user_id]", back_populates="user", lazy="select")