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.
306 lines
6.9 KiB
Python
306 lines
6.9 KiB
Python
# backend/app/schemas.py
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from .models import ClsType, InboxKind, InboxStatus, Prio, Sphere, TaskStatus
|
|
|
|
|
|
# ---------- people ----------
|
|
class PersonOut(BaseModel):
|
|
id: str
|
|
name: str
|
|
initial: str
|
|
color: str
|
|
is_me: bool
|
|
|
|
|
|
# ---------- tree ----------
|
|
class ProjectNode(BaseModel):
|
|
id: str
|
|
folder_id: str
|
|
parent_id: Optional[str] = None
|
|
name: str
|
|
tone: str
|
|
sort_order: int
|
|
pinned: bool
|
|
task_count: int = 0 # 자신+하위 프로젝트의 작업 수 합
|
|
children: list[ProjectNode] = []
|
|
|
|
|
|
class FolderOut(BaseModel):
|
|
id: str
|
|
name: str
|
|
tone: str
|
|
icon: str
|
|
sort_order: int
|
|
is_system: bool
|
|
projects: list[ProjectNode] = [] # 최상위 프로젝트(중첩 children 포함)
|
|
|
|
|
|
class FolderCreate(BaseModel):
|
|
name: str
|
|
tone: Optional[str] = "ink"
|
|
icon: Optional[str] = "folder"
|
|
|
|
|
|
class FolderPatch(BaseModel):
|
|
name: Optional[str] = None
|
|
tone: Optional[str] = None
|
|
icon: Optional[str] = None
|
|
sort_order: Optional[int] = None
|
|
|
|
|
|
class ProjectCreate(BaseModel):
|
|
folder_id: str
|
|
parent_id: Optional[str] = None
|
|
name: str
|
|
tone: Optional[str] = "ink"
|
|
|
|
|
|
class ProjectPatch(BaseModel):
|
|
name: Optional[str] = None
|
|
tone: Optional[str] = None
|
|
parent_id: Optional[str] = None
|
|
sort_order: Optional[int] = None
|
|
|
|
|
|
# ---------- tasks ----------
|
|
class CommentOut(BaseModel):
|
|
id: str
|
|
task_id: str
|
|
person_id: str
|
|
text: str
|
|
created_at: datetime
|
|
|
|
|
|
class TaskNode(BaseModel):
|
|
id: str
|
|
project_id: str
|
|
parent_id: Optional[str] = None
|
|
title: str
|
|
status: TaskStatus
|
|
assignee_id: Optional[str] = None
|
|
due: Optional[date] = None
|
|
prio: Prio
|
|
notes: str
|
|
est: str
|
|
delegated: bool
|
|
sort_order: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
comments: list[CommentOut] = []
|
|
children: list[TaskNode] = []
|
|
|
|
|
|
class TaskCreate(BaseModel):
|
|
title: str
|
|
project_id: str
|
|
parent_id: Optional[str] = None
|
|
status: Optional[TaskStatus] = TaskStatus.todo
|
|
assignee_id: Optional[str] = None
|
|
due: Optional[date] = None
|
|
prio: Optional[Prio] = Prio.normal
|
|
notes: Optional[str] = ""
|
|
est: Optional[str] = ""
|
|
|
|
|
|
class TaskPatch(BaseModel):
|
|
title: Optional[str] = None
|
|
project_id: Optional[str] = None
|
|
parent_id: Optional[str] = None
|
|
status: Optional[TaskStatus] = None
|
|
assignee_id: Optional[str] = None
|
|
due: Optional[date] = None
|
|
prio: Optional[Prio] = None
|
|
notes: Optional[str] = None
|
|
est: Optional[str] = None
|
|
delegated: Optional[bool] = None
|
|
sort_order: Optional[int] = None
|
|
|
|
|
|
class CommentCreate(BaseModel):
|
|
person_id: str
|
|
text: str
|
|
|
|
|
|
# ---------- scaffold ----------
|
|
class ScaffoldItem(BaseModel):
|
|
title: str
|
|
est: str = ""
|
|
|
|
|
|
class ScaffoldOut(BaseModel):
|
|
kind: str # "연구 · 논문 작성" 등
|
|
icon: str # "brain"|"branch"|"file"|"list"
|
|
items: list[ScaffoldItem]
|
|
created: bool = False # True면 실제 생성됨, False면 미리보기
|
|
created_task_ids: list[str] = []
|
|
|
|
|
|
class ScaffoldRequest(BaseModel):
|
|
create: bool = False # True면 하위작업 실제 생성, False면 미리보기
|
|
use_llm: bool = False # LLM 보강 사용 여부
|
|
|
|
|
|
# ---------- risks ----------
|
|
class RiskOut(BaseModel):
|
|
kind: str # "지연 위험" | "업무 쏠림" | "의존성"
|
|
icon: str # "clock" | "scale" | "link"
|
|
tone: str # "coral" | "amber" | "violet"
|
|
task_id: Optional[str] = None # 관련 task id (cta 대상)
|
|
text: str # 한국어. 강조어를 ** ** 마크다운으로 감싼다
|
|
cta: Optional[str] = None # "작업 열기" | "후속 작업 보기"
|
|
|
|
|
|
# ---------- inbox ----------
|
|
class ClassificationOut(BaseModel):
|
|
id: Optional[str] = None
|
|
inbox_item_id: str
|
|
type: ClsType
|
|
sphere: Sphere
|
|
project_id: Optional[str] = None
|
|
proj_label: str
|
|
tone: str
|
|
due_text: str
|
|
when_text: str
|
|
extra: str
|
|
reason: str
|
|
confidence: float
|
|
model: str
|
|
created_at: Optional[datetime] = None
|
|
|
|
|
|
class InboxItemOut(BaseModel):
|
|
id: str
|
|
kind: InboxKind
|
|
raw: str
|
|
status: InboxStatus
|
|
created_at: datetime
|
|
materialized_task_id: Optional[str] = None
|
|
classification: Optional[ClassificationOut] = None # 최신 1건
|
|
|
|
|
|
class CaptureRequest(BaseModel):
|
|
kind: InboxKind = InboxKind.text
|
|
raw: str
|
|
|
|
|
|
class CaptureResponse(BaseModel):
|
|
item: InboxItemOut
|
|
classification: ClassificationOut
|
|
|
|
|
|
class ReclassifyRequest(BaseModel):
|
|
type: Optional[ClsType] = None # 사용자가 타입 강제 가능
|
|
|
|
|
|
class ConfirmResponse(BaseModel):
|
|
item: InboxItemOut
|
|
task: Optional[TaskNode] = None # type=task 면 생성된 작업
|
|
|
|
|
|
# ---------- llm ----------
|
|
class LLMHealth(BaseModel):
|
|
reachable: bool
|
|
provider: str # "ollama" | "heuristic"
|
|
model: str
|
|
host: Optional[str] = None
|
|
detail: str = ""
|
|
|
|
|
|
# ---------- dashboard ----------
|
|
class UserOut(BaseModel):
|
|
name: str # 예: "지우"
|
|
initial: str # 예: "지"
|
|
|
|
|
|
class WeatherOut(BaseModel):
|
|
temp: int # 현재 기온 (예: 24)
|
|
cond: str # "맑음 · 한낮 28°"
|
|
icon: str # 'cloudSun' 저장 → 표시용 'sun' 으로 매핑
|
|
|
|
|
|
class BriefingOut(BaseModel):
|
|
today: str # 히어로 날짜 라벨 (예: "6월 7일 일요일")
|
|
weather: WeatherOut
|
|
commute: str
|
|
sleep: str
|
|
note: str # HTML 허용
|
|
|
|
|
|
class EventOut(BaseModel):
|
|
id: str
|
|
time: str
|
|
title: str
|
|
tag: str
|
|
dur: str
|
|
tone: str
|
|
soon: bool
|
|
|
|
|
|
class ApprovalSummaryOut(BaseModel):
|
|
id: str
|
|
icon: str
|
|
tone: str
|
|
title: str
|
|
time: str
|
|
# risk=="high" 만 포함, 최대 3건. detail/cta/alt/undo_label 미포함.
|
|
|
|
|
|
class GoalOut(BaseModel):
|
|
id: str
|
|
title: str
|
|
pct: int
|
|
sub: str
|
|
tone: str # tone=키('blue' 등)
|
|
|
|
|
|
class InboxRecentOut(BaseModel):
|
|
id: str
|
|
kind: str
|
|
raw: str
|
|
type: str
|
|
proj_label: str
|
|
tone: str
|
|
# 평탄화 형태: 최신 classification 에서 type/proj_label/tone 을 끌어와 채운다.
|
|
|
|
|
|
class TaskSummaryItem(BaseModel):
|
|
id: str
|
|
title: str
|
|
prio: str
|
|
project: str
|
|
|
|
|
|
class TaskSummaryOut(BaseModel):
|
|
open_count: int # 미완료 작업 수
|
|
items: list[TaskSummaryItem]
|
|
|
|
|
|
class BadgesOut(BaseModel):
|
|
appr: int # 결재함 대기(=high risk 건수)
|
|
task: int # 미완료 작업 수(상단 작업 배지)
|
|
noti: int # 알림 배지 (시드 상수 6)
|
|
|
|
|
|
class DashboardOut(BaseModel):
|
|
user: UserOut
|
|
briefing: BriefingOut
|
|
saved_today: str # "47분"
|
|
today_routed: int # 7
|
|
schedule: list[EventOut]
|
|
task_summary: TaskSummaryOut
|
|
goals: list[GoalOut]
|
|
approvals_summary: list[ApprovalSummaryOut] # high-risk 만, 최대 3건
|
|
inbox_recent: list[InboxRecentOut] # 최근 3 (평탄화 형태)
|
|
badges: BadgesOut
|
|
|
|
|
|
# 재귀 모델 전방참조 해소
|
|
ProjectNode.model_rebuild()
|
|
TaskNode.model_rebuild()
|