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.
146 lines
4.5 KiB
Python
146 lines
4.5 KiB
Python
# backend/app/routers/worker.py — 능동 워커 트리거 + 능동 카드 + 주간 리뷰 API (phase-14)
|
|
import json
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlmodel import Session, select
|
|
|
|
from ..db import get_session
|
|
from ..models import (
|
|
AutomationRule,
|
|
FocusBlock,
|
|
ProactiveCard,
|
|
Project,
|
|
Task,
|
|
TaskStatus,
|
|
)
|
|
from ..schemas import (
|
|
ProactiveAcceptOut,
|
|
ProactiveCardOut,
|
|
WeeklyReviewOut,
|
|
WorkerRunOut,
|
|
)
|
|
from ..worker.jobs.weekly_review import run_weekly_review
|
|
from ..worker.triggers import JOBS
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _pc_out(c: ProactiveCard) -> ProactiveCardOut:
|
|
return ProactiveCardOut(
|
|
id=c.id,
|
|
kind=c.kind,
|
|
icon=c.icon,
|
|
tone=c.tone,
|
|
title=c.title,
|
|
why=c.why,
|
|
cta=c.cta,
|
|
action_kind=c.action_kind,
|
|
detected_at=c.detected_at,
|
|
status=c.status,
|
|
)
|
|
|
|
|
|
@router.post("/worker/run/{job}", response_model=WorkerRunOut)
|
|
def run_job(job: str, payload: dict | None = None, s: Session = Depends(get_session)):
|
|
if job not in JOBS:
|
|
raise HTTPException(404, f"unknown job: {job}")
|
|
result = JOBS[job](s, payload or {})
|
|
return WorkerRunOut(job=job, ok=True, detail=json.dumps(result, ensure_ascii=False))
|
|
|
|
|
|
@router.get("/proactive", response_model=list[ProactiveCardOut])
|
|
def list_proactive(s: Session = Depends(get_session)):
|
|
rows = s.exec(select(ProactiveCard).where(ProactiveCard.status == "active")).all()
|
|
return [_pc_out(c) for c in rows]
|
|
|
|
|
|
@router.post("/proactive/{pid}/accept", response_model=ProactiveAcceptOut)
|
|
def accept_proactive(pid: str, s: Session = Depends(get_session)):
|
|
c = s.get(ProactiveCard, pid)
|
|
if not c:
|
|
raise HTTPException(404, "proactive card not found")
|
|
created_id, message = _materialize(s, c)
|
|
c.status = "accepted"
|
|
s.add(c)
|
|
s.commit()
|
|
return ProactiveAcceptOut(
|
|
id=c.id, action_kind=c.action_kind or "none", created_id=created_id, message=message
|
|
)
|
|
|
|
|
|
@router.post("/proactive/{pid}/dismiss")
|
|
def dismiss_proactive(pid: str, s: Session = Depends(get_session)):
|
|
c = s.get(ProactiveCard, pid)
|
|
if not c:
|
|
raise HTTPException(404, "proactive card not found")
|
|
c.status = "dismissed"
|
|
s.add(c)
|
|
s.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/weekly-review", response_model=WeeklyReviewOut)
|
|
def weekly_review(week: str = "6/7~6/13", s: Session = Depends(get_session)):
|
|
wr = run_weekly_review(s, week_label=week)
|
|
return WeeklyReviewOut(
|
|
id=wr.id,
|
|
week_label=wr.week_label,
|
|
deep_work_h=wr.deep_work_h,
|
|
meeting_h=wr.meeting_h,
|
|
done_count=wr.done_count,
|
|
auto_count=wr.auto_count,
|
|
saved_minutes=wr.saved_minutes,
|
|
patterns=json.loads(wr.patterns_json or "[]"),
|
|
note=wr.note,
|
|
)
|
|
|
|
|
|
def _materialize(s: Session, c: ProactiveCard) -> tuple[str | None, str]:
|
|
"""action_kind → focus_block/task/automation 실체화. (id, 메시지) 반환."""
|
|
payload = json.loads(c.action_payload) if c.action_payload else {}
|
|
kind = c.action_kind
|
|
if kind == "focus_block":
|
|
fb = FocusBlock(
|
|
id="fb-" + uuid.uuid4().hex[:8],
|
|
day=int(payload.get("day", 0)),
|
|
start=payload.get("start", ""),
|
|
end=payload.get("end", ""),
|
|
title=payload.get("title", "보호 블록"),
|
|
type=payload.get("type", "light"),
|
|
auto=True,
|
|
)
|
|
s.add(fb)
|
|
s.commit()
|
|
return fb.id, "집중 블록을 잡아뒀어요"
|
|
if kind == "task":
|
|
pid = payload.get("project_id", "me")
|
|
if not s.get(Project, pid):
|
|
first = s.exec(select(Project)).first()
|
|
pid = first.id if first else pid
|
|
t = Task(
|
|
id="kx-" + uuid.uuid4().hex[:8],
|
|
project_id=pid,
|
|
title=payload.get("title", "확인할 일"),
|
|
status=TaskStatus.todo,
|
|
)
|
|
s.add(t)
|
|
s.commit()
|
|
return t.id, "작업으로 추가했어요"
|
|
if kind == "automation":
|
|
r = AutomationRule(
|
|
id="rule-" + uuid.uuid4().hex[:8],
|
|
name=payload.get("name", "새 규칙"),
|
|
cat=payload.get("cat", "focus"),
|
|
trigger=payload.get("trigger", ""),
|
|
cond=payload.get("cond"),
|
|
action=payload.get("action", ""),
|
|
source="suggestion",
|
|
fresh=True,
|
|
last="방금 만듦",
|
|
)
|
|
s.add(r)
|
|
s.commit()
|
|
return r.id, "자동화 규칙을 만들었어요"
|
|
return None, "확인했어요"
|