# backend/app/services/focus.py # 빈 블록 계산 → 집중 블록 제안 (free-slot finder). from __future__ import annotations from sqlmodel import Session, select from ..models import CalEvent, FocusBlock DAY_START_MIN = 9 * 60 # 09:00 (근무 시작) DAY_END_MIN = 18 * 60 # 18:00 DEEP_THRESHOLD = 60 # 60분 이상 빈 블록 → deep, 미만 → light def _to_min(t: str) -> int: h, m = map(int, t.split(":")) return h * 60 + m def _to_hhmm(mins: int) -> str: return f"{mins // 60:02d}:{mins % 60:02d}" def free_slots(s: Session, day: int) -> list[tuple[int, int]]: """그 날 09:00~18:00 중 이벤트가 없는 [start_min, end_min) 구간 목록.""" evs = s.exec(select(CalEvent).where(CalEvent.day == day)).all() busy = sorted([(_to_min(e.start), _to_min(e.end)) for e in evs]) slots: list[tuple[int, int]] = [] cur = DAY_START_MIN for bs, be in busy: if bs > cur: slots.append((cur, min(bs, DAY_END_MIN))) cur = max(cur, be) if cur >= DAY_END_MIN: break if cur < DAY_END_MIN: slots.append((cur, DAY_END_MIN)) return [(a, b) for a, b in slots if b > a] def suggest_focus(s: Session, day: int, min_minutes: int = 20) -> list[FocusBlock]: """빈 슬롯에 집중 블록 제안. 길이로 deep/light 결정(저장은 라우터 create 옵션).""" out: list[FocusBlock] = [] for i, (a, b) in enumerate(free_slots(s, day)): length = b - a if length < min_minutes: continue is_deep = length >= DEEP_THRESHOLD out.append( FocusBlock( id=f"fs{day}-{i}", day=day, start=_to_hhmm(a), end=_to_hhmm(min(a + (75 if is_deep else 20), b)), title="딥 워크 블록" if is_deep else "가벼운 일 처리", type="deep" if is_deep else "light", tag="딥 워크" if is_deep else "가벼운 일", auto=True, sort_order=i, ) ) return out