|
|
# backend/app/services/journey.py — 여정 집계
|
|
|
from sqlmodel import Session, select
|
|
|
|
|
|
from ..models import Event, JourneyLink, Person, Task, TaskStatus
|
|
|
from ..schemas import (
|
|
|
GaugeOut,
|
|
|
JourneyCardsOut,
|
|
|
JourneyHeaderOut,
|
|
|
JourneyLinkOut,
|
|
|
JourneyMetaOut,
|
|
|
JourneyOut,
|
|
|
JourneyPersonOut,
|
|
|
JourneyRowOut,
|
|
|
JourneyStageOut,
|
|
|
JourneyTableOut,
|
|
|
MeetCardOut,
|
|
|
RoutineCardOut,
|
|
|
TaskCardOut,
|
|
|
TileCardOut,
|
|
|
)
|
|
|
|
|
|
STAGES = [
|
|
|
("s1", "아침 준비", "06:40 – 09:00", "blue", "2"),
|
|
|
("s2", "오전 집중", "09:00 – 13:00", "coral", "4"),
|
|
|
("s3", "오후 협업", "13:00 – 17:00", "violet", "4"),
|
|
|
("s4", "마무리 & 내일", "17:00 – 23:00", "ink", "4"),
|
|
|
]
|
|
|
ROUTINE_CARDS = [
|
|
|
dict(
|
|
|
node="m-sleep",
|
|
|
icon="moon",
|
|
|
title="수면 7시간 12분",
|
|
|
meta="평소만큼 푹 잔 밤",
|
|
|
tag="컨디션 좋음",
|
|
|
tool="tick",
|
|
|
toolTone="ok",
|
|
|
),
|
|
|
dict(
|
|
|
node="m-brief",
|
|
|
icon="sun",
|
|
|
title="아침 브리핑 확인",
|
|
|
meta="맑음 24° · 출근 23분",
|
|
|
tag="4분 빠름",
|
|
|
tool="cal",
|
|
|
toolTone="",
|
|
|
),
|
|
|
]
|
|
|
TASK_NODES = [
|
|
|
("t-report", "k1", "경영 전략 · 우선"),
|
|
|
("t-wire", "k2", "온보딩 · 우선"),
|
|
|
("t-interview", "k5", "온보딩 · 리서치"),
|
|
|
("t-welcome", "k3", "팀 운영"),
|
|
|
]
|
|
|
MEET_NODES = [
|
|
|
("k-stand", "e1", "minseo", False),
|
|
|
("k-design", "e2", "hyunwoo", False),
|
|
|
("k-strat", "e3", "jaeho", True),
|
|
|
("k-11", "e4", "minseo", False),
|
|
|
]
|
|
|
TILE_CARDS = [
|
|
|
dict(node="w-focus", hi=True, icon="target", tt="지금 집중", ts="분기 리포트 마무리"),
|
|
|
dict(node="w-retro", hi=False, icon="send", tt="회고 배포", ts="팀 채널 공유"),
|
|
|
dict(node="w-health", hi=False, icon="heart", tt="운동 30분", ts="주 3/4회"),
|
|
|
dict(node="w-next", hi=False, icon="cal", tt="내일 준비", ts="일정 4건"),
|
|
|
]
|
|
|
PEOPLE_TASKCOUNT = {"jiwoo": 4, "hyunwoo": 2, "minseo": 2, "jaeho": 2, "sua": 3}
|
|
|
PEOPLE_ROLE = {
|
|
|
"jiwoo": "나 · PM",
|
|
|
"hyunwoo": "디자인",
|
|
|
"minseo": "팀 운영",
|
|
|
"jaeho": "데이터",
|
|
|
"sua": "리서치",
|
|
|
}
|
|
|
TABLE_ROWS = [
|
|
|
("t-report", "k1", True, "09:00"),
|
|
|
("t-wire", "k2", False, "11:00"),
|
|
|
("t-interview", "k5", False, "13:30"),
|
|
|
("t-welcome", "k3", True, "08:30"),
|
|
|
("t-card", "k4", False, "18:00"),
|
|
|
]
|
|
|
STATUS_CHIP = {
|
|
|
"doing": ("prog", "진행중"),
|
|
|
"review": ("review", "검토"),
|
|
|
"done": ("done", "완료"),
|
|
|
"todo": ("sched", "예정"),
|
|
|
"waiting": ("prog", "진행중"),
|
|
|
}
|
|
|
|
|
|
|
|
|
def build_journey(s: Session) -> JourneyOut:
|
|
|
people = s.exec(select(Person)).all()
|
|
|
tasks = {t.id: t for t in s.exec(select(Task)).all()}
|
|
|
events = {e.id: e for e in s.exec(select(Event)).all()}
|
|
|
|
|
|
# 여정 협업 인물은 핵심 5인만(메일 발신자 제외)
|
|
|
core = {"jiwoo", "hyunwoo", "minseo", "jaeho", "sua"}
|
|
|
person_out = [
|
|
|
JourneyPersonOut(
|
|
|
id=p.id,
|
|
|
name=p.name,
|
|
|
initial=p.initial,
|
|
|
color=p.color,
|
|
|
me=p.is_me,
|
|
|
tasks=PEOPLE_TASKCOUNT.get(p.id, 0),
|
|
|
role=PEOPLE_ROLE.get(p.id, ""),
|
|
|
)
|
|
|
for p in people
|
|
|
if p.id in core
|
|
|
]
|
|
|
|
|
|
stages = [
|
|
|
JourneyStageOut(id=i, title=t, time=tm, tone=tone, n=n) for (i, t, tm, tone, n) in STAGES
|
|
|
]
|
|
|
s1 = [RoutineCardOut(**c) for c in ROUTINE_CARDS]
|
|
|
s2 = []
|
|
|
for node, tid, meta in TASK_NODES:
|
|
|
t = tasks.get(tid)
|
|
|
s2.append(
|
|
|
TaskCardOut(
|
|
|
node=node,
|
|
|
title=t.title if t else node,
|
|
|
meta=meta,
|
|
|
who=t.assignee_id if t else "jiwoo",
|
|
|
done=(t.status == TaskStatus.done) if t else False,
|
|
|
)
|
|
|
)
|
|
|
s3 = []
|
|
|
for node, eid, who, soon in MEET_NODES:
|
|
|
e = events.get(eid)
|
|
|
meta = f"{e.tag} · {e.dur}" if e else ""
|
|
|
s3.append(
|
|
|
MeetCardOut(
|
|
|
node=node,
|
|
|
time=e.time if e else "",
|
|
|
title=e.title if e else node,
|
|
|
meta=meta,
|
|
|
who=who,
|
|
|
soon=soon,
|
|
|
)
|
|
|
)
|
|
|
s4 = [TileCardOut(**c) for c in TILE_CARDS]
|
|
|
|
|
|
links = [
|
|
|
JourneyLinkOut(
|
|
|
from_node=link.from_node, to_node=link.to_node, tone=link.tone, dash=link.dash
|
|
|
)
|
|
|
for link in s.exec(select(JourneyLink).order_by(JourneyLink.sort_order)).all()
|
|
|
]
|
|
|
|
|
|
rows = []
|
|
|
for node, tid, star, start in TABLE_ROWS:
|
|
|
t = tasks.get(tid)
|
|
|
chip, label = STATUS_CHIP.get(t.status.value if t else "todo", ("sched", "예정"))
|
|
|
rows.append(
|
|
|
JourneyRowOut(
|
|
|
node=node,
|
|
|
title=(t.title if t else node),
|
|
|
star=star,
|
|
|
statusKey=chip,
|
|
|
statusLabel=label,
|
|
|
startTime=start,
|
|
|
who=(t.assignee_id if t else "jiwoo"),
|
|
|
)
|
|
|
)
|
|
|
|
|
|
counts = {"prog": 0, "review": 0, "done": 0, "sched": 0}
|
|
|
for r in rows:
|
|
|
counts[r.statusKey] = counts.get(r.statusKey, 0) + 1
|
|
|
subtitle = (
|
|
|
f"{len(rows)}건 · 진행중 {counts['prog']} · 검토 {counts['review']} "
|
|
|
f"· 완료 {counts['done']} · 예정 {counts['sched']}"
|
|
|
)
|
|
|
|
|
|
gauges = [
|
|
|
GaugeOut(pct=87, tone="blue", val="5.2", unit="시간", label="딥 워크", sub="목표 6시간"),
|
|
|
GaugeOut(pct=72, tone="coral", val="72", unit="%", label="활동 링", sub="7.2천 걸음"),
|
|
|
]
|
|
|
return JourneyOut(
|
|
|
header=JourneyHeaderOut(dateLabel="6월 10일 화요일", weather="맑음 · 한낮 28°"),
|
|
|
meta=JourneyMetaOut(stageCount=4, itemCount=12, collabCount=5, peopleMore="+3"),
|
|
|
people=person_out,
|
|
|
stages=stages,
|
|
|
cards=JourneyCardsOut(s1=s1, s2=s2, s3=s3, s4=s4),
|
|
|
links=links,
|
|
|
rows=rows,
|
|
|
table=JourneyTableOut(subtitle=subtitle),
|
|
|
gauges=gauges,
|
|
|
gaugeFoot="오후 3시예요 — 물 한 잔, 5분 스트레칭 어때요?",
|
|
|
)
|