from datetime import date from app.models.habit import ALL_WEEKDAYS_MASK, HABIT_DIFFICULTY_TARGET_DAYS, Habit, HabitDifficulty from app.services.log_service import MILESTONE_STREAKS _DAY_LABELS = ["월", "화", "수", "목", "금", "토", "일"] _DIFFICULTY_LABELS: dict[HabitDifficulty, str] = { HabitDifficulty.EASY: "하 (21일)", HabitDifficulty.MEDIUM: "중 (66일)", HabitDifficulty.HARD: "상 (254일)", HabitDifficulty.UNLIMITED: "기간 무제한", } def is_milestone_streak(streak: int) -> bool: return streak in MILESTONE_STREAKS def weekday_label(mask: int) -> str: if mask == ALL_WEEKDAYS_MASK: return "매일" days = [label for i, label in enumerate(_DAY_LABELS) if mask & (1 << i)] return ", ".join(days) if days else "선택된 요일 없음" def difficulty_label(difficulty: HabitDifficulty) -> str: return _DIFFICULTY_LABELS[difficulty] def goal_progress(habit: Habit) -> dict | None: """습관의 목표 기간 진행 상황. 기간 무제한(UNLIMITED)이면 None.""" target_days = HABIT_DIFFICULTY_TARGET_DAYS[habit.difficulty] if target_days is None: return None elapsed = (date.today() - habit.created_at.date()).days + 1 # 시작일을 1일차로 계산 elapsed = max(elapsed, 1) return { "elapsed": min(elapsed, target_days), "target": target_days, "remaining": max(target_days - elapsed, 0), "reached": elapsed >= target_days, } def heatmap_opacity(checked_count: int, scheduled_count: int) -> float: """월별 캘린더 히트맵 셀의 배경 투명도(0~0.9)를 계산한다.""" if not scheduled_count: return 0.0 ratio = checked_count / scheduled_count if ratio <= 0: return 0.0 return round(0.12 + ratio * 0.78, 2)