You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
62 lines
2.0 KiB
Python
62 lines
2.0 KiB
Python
# 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
|