mitai-jinkendo/backend/routers/goal_progress.py

156 lines
5.2 KiB
Python
Raw Normal View History

"""
Goal Progress Router - Progress Tracking for Goals
Endpoints for logging and managing goal progress:
- Get progress history
- Create manual progress entries
- Delete progress entries
Part of v9h Goal System.
"""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import Optional
from datetime import date
from db import get_db, get_cursor, r2d
from auth import require_auth
router = APIRouter(prefix="/api/goals", tags=["goal-progress"])
# ============================================================================
# Pydantic Models
# ============================================================================
class GoalProgressCreate(BaseModel):
"""Log progress for a goal"""
date: date
value: float
note: Optional[str] = None
class GoalProgressUpdate(BaseModel):
"""Update progress entry"""
value: Optional[float] = None
note: Optional[str] = None
# ============================================================================
# Endpoints
# ============================================================================
@router.get("/{goal_id}/progress")
def get_goal_progress(goal_id: str, session: dict = Depends(require_auth)):
"""Get progress history for a goal"""
pid = session['profile_id']
with get_db() as conn:
cur = get_cursor(conn)
# Verify ownership
cur.execute(
"SELECT id FROM goals WHERE id = %s AND profile_id = %s",
(goal_id, pid)
)
if not cur.fetchone():
raise HTTPException(status_code=404, detail="Ziel nicht gefunden")
# Get progress entries
cur.execute("""
SELECT id, date, value, note, source, created_at
FROM goal_progress_log
WHERE goal_id = %s
ORDER BY date DESC
""", (goal_id,))
entries = cur.fetchall()
return [r2d(e) for e in entries]
@router.post("/{goal_id}/progress")
def create_goal_progress(goal_id: str, data: GoalProgressCreate, session: dict = Depends(require_auth)):
"""Log new progress for a goal"""
pid = session['profile_id']
with get_db() as conn:
cur = get_cursor(conn)
# Verify ownership and check if manual entry is allowed
cur.execute("""
SELECT g.id, g.unit, gt.source_table
FROM goals g
LEFT JOIN goal_type_definitions gt ON g.goal_type = gt.type_key
WHERE g.id = %s AND g.profile_id = %s
""", (goal_id, pid))
goal = cur.fetchone()
if not goal:
raise HTTPException(status_code=404, detail="Ziel nicht gefunden")
# Prevent manual entries for goals with automatic data sources
if goal['source_table']:
raise HTTPException(
status_code=400,
detail=f"Manuelle Einträge nicht erlaubt für automatisch erfasste Ziele. "
f"Bitte nutze die entsprechende Erfassungsseite (z.B. Gewicht, Aktivität)."
)
# Insert progress entry
try:
cur.execute("""
INSERT INTO goal_progress_log (goal_id, profile_id, date, value, note, source)
VALUES (%s, %s, %s, %s, %s, 'manual')
RETURNING id
""", (goal_id, pid, data.date, data.value, data.note))
progress_id = cur.fetchone()['id']
# Trigger will auto-update goals.current_value
return {
"id": progress_id,
"message": f"Fortschritt erfasst: {data.value} {goal['unit']}"
}
except Exception as e:
if "unique_progress_per_day" in str(e):
raise HTTPException(
status_code=400,
detail=f"Für {data.date} existiert bereits ein Eintrag. Bitte bearbeite den existierenden Eintrag."
)
raise HTTPException(status_code=500, detail=f"Fehler beim Speichern: {str(e)}")
@router.delete("/{goal_id}/progress/{progress_id}")
def delete_goal_progress(goal_id: str, progress_id: str, session: dict = Depends(require_auth)):
"""Delete progress entry"""
pid = session['profile_id']
with get_db() as conn:
cur = get_cursor(conn)
# Verify ownership
cur.execute(
"SELECT id FROM goals WHERE id = %s AND profile_id = %s",
(goal_id, pid)
)
if not cur.fetchone():
raise HTTPException(status_code=404, detail="Ziel nicht gefunden")
# Delete progress entry
cur.execute(
"DELETE FROM goal_progress_log WHERE id = %s AND goal_id = %s AND profile_id = %s",
(progress_id, goal_id, pid)
)
if cur.rowcount == 0:
raise HTTPException(status_code=404, detail="Progress-Eintrag nicht gefunden")
# After deletion, recalculate current_value from remaining entries
cur.execute("""
UPDATE goals
SET current_value = (
SELECT value FROM goal_progress_log
WHERE goal_id = %s
ORDER BY date DESC
LIMIT 1
)
WHERE id = %s
""", (goal_id, goal_id))
return {"message": "Progress-Eintrag gelöscht"}