habit_tracker / src /utils /services.py
GitHub Action Sync
Automated sync from GitHub
dd482d8
Raw
History Blame Contribute Delete
6.28 kB
from __future__ import annotations
import json
from datetime import datetime, time, timedelta
from typing import Any, Optional
from pydantic import ValidationError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select
from src.utils.sleep import calculate_sleep_duration
from ..database.models import ActivityEntry, DayLog, Goal, User
from ..schemas.activity import activity_create
from ..schemas.daylog import daylog_create
from ..schemas.goal import goal_create
from .exceptions import CommandError
from .command_validation import parse_command_text, build_typed_payload, validate_required_fields
async def create_goal_service(payload: goal_create, current_user: User, db: AsyncSession) -> dict[str, Any]:
statement = select(Goal).where((Goal.user_id == current_user.id) & (Goal.title == payload.title))
result = await db.execute(statement)
existing_goal = result.scalar_one_or_none()
if existing_goal:
raise CommandError(400, "business_rule_violation", "A goal with the same name already exists.")
new_goal = Goal(
user_id=current_user.id,
title=payload.title,
description=payload.description,
total_productivity_hours=payload.total_productivity_hours,
active_status=payload.active_status,
date=payload.date
)
db.add(new_goal)
await db.commit()
await db.refresh(new_goal)
return {"message": "Goal created successfully", "goal": new_goal.model_dump()}
async def create_daylog_service(payload: daylog_create, current_user: User, db: AsyncSession) -> dict[str, Any]:
statement = select(DayLog).where((DayLog.user_id == current_user.id) & (DayLog.date == payload.date))
result = await db.execute(statement)
existing_log = result.scalar_one_or_none()
if existing_log:
raise CommandError(400, "business_rule_violation", "A day log for this date already exists.")
if payload.wake_time:
# 1. Fetch Yesterday's Log
yesterday_date = payload.date - timedelta(days=1)
stmt = select(DayLog).where(
DayLog.date == yesterday_date,
DayLog.user_id == current_user.id
)
result = await db.execute(stmt)
yesterday_log = result.scalar_one_or_none()
if yesterday_log.sleep_time:
payload.sleep_duration_hours = calculate_sleep_duration(
yesterday_date=yesterday_log.date,
yesterday_sleep=yesterday_log.sleep_time,
today_date=payload.date,
today_wake=payload.wake_time
)
# 3. Proceed with standard creation...
db_daylog = DayLog(**payload.model_dump(), user_id=current_user.id)
db.add(db_daylog)
await db.commit()
await db.refresh(db_daylog)
return {"message": "Day log created successfully",
"daylog_id": db_daylog.id,
"daylog_data": db_daylog}
def build_help_text(intent: Optional[str] = None) -> dict[str, Any]:
guides = {
None: (
"Use /goal, /daylog, /activity, or /help. Values with spaces must be quoted. "
"Examples : "
"/goal --name --description --status or -n -desc -s"
"/daylog --date --bed --wake --sleep --sleephr --totalcal --totalprod --note or -d -b -w -s -sh -tc -tp -n"
"/activity --goal --date --name --type --status --time --min --place --quan --what --result --note or -g -d -n -t -s -ti -m -p -q -w -r -no -de"
),
"goal": (
"/goal --name --description --status"
"OR -n -desc -s"
),
"daylog": (
"/daylog --date YYYY-MM-DD --bed HH:MM:SS --wake HH:MM:SS --note --sleep --sleephr --totalcal --totalprod"
"OR -d, -b, -w, -s, -sh, -tc, -tp, -n"
),
"activity": (
"/activity --goal --date YYYY-MM-DD --name --type --status --time HH:MM:SS --min --place --quan --what --result --note details"
"OR -g, -d, -n, -t, -s, -ti, -m, -p, -q, -w, -r, -no, -de"
),
}
if intent not in guides:
raise CommandError(400, "unknown_intent", f"Unknown help topic '{intent}'.")
return {"message": "Help guide", "guide": guides[intent]}
async def dispatch_command(command_text: str, current_user: User, db: AsyncSession) -> dict[str, Any]:
intent, help_topic, raw_values = parse_command_text(command_text)
if intent == "help":
guide = build_help_text(help_topic)
return {"success": True, "intent": "help", "data": guide}
typed_values = build_typed_payload(intent, raw_values)
validate_required_fields(intent, typed_values)
try:
if intent == "goal":
payload = goal_create.model_validate(typed_values)
result = await create_goal_service(payload, current_user, db)
return {"success": True, "intent": "goal", "data": result}
if intent == "daylog":
payload = daylog_create.model_validate(typed_values)
result = await create_daylog_service(payload, current_user, db)
return {"success": True, "intent": "daylog", "data": result}
if intent == "activity":
payload = activity_create.model_validate(typed_values)
result = await create_activity_service(payload, current_user, db)
return {"success": True, "intent": "activity", "data": result}
raise CommandError(400, "unknown_intent", f"Unknown intent '{intent}'.")
except ValidationError as exc:
raise CommandError(400, "invalid_field_type", "Command data does not match the expected schema.", exc.errors()) from exc
def command_error_payload(exc: CommandError) -> dict[str, Any]:
return {
"success": False,
"error_code": exc.error_code,
"message": exc.message,
"details": exc.details,
}
async def list_user_activities(current_user: User, db: AsyncSession) -> dict[str, Any]:
statement = select(ActivityEntry).where(ActivityEntry.user_id == current_user.id)
result = await db.execute(statement)
activities = result.scalars().all()
return {
"success": True,
"intent": "activity",
"data": {"activities": [activity.model_dump() for activity in activities]},
}