|
|
# backend/app/services/meetings.py
|
|
|
# 회의 액션 → 작업 실체화(federation) + 회의 데이터 조립.
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import json
|
|
|
import uuid
|
|
|
|
|
|
from sqlmodel import Session, select
|
|
|
|
|
|
from ..automation.event_bus import bus
|
|
|
from ..models import (
|
|
|
CalEvent,
|
|
|
Meeting,
|
|
|
MeetingAction,
|
|
|
Prio,
|
|
|
Project,
|
|
|
Task,
|
|
|
TaskStatus,
|
|
|
)
|
|
|
|
|
|
# cal-data.js who(표시명) → person id
|
|
|
WHO_TO_PERSON = {
|
|
|
"나": "jiwoo",
|
|
|
"현우": "hyunwoo",
|
|
|
"민서": "minseo",
|
|
|
"재호": "jaeho",
|
|
|
"수아": "sua",
|
|
|
}
|
|
|
DEFAULT_PROJECT = "me" # 회의 액션 기본 프로젝트(개인 일상)
|
|
|
|
|
|
|
|
|
def _person_id(who: str) -> str | None:
|
|
|
return WHO_TO_PERSON.get(who.strip())
|
|
|
|
|
|
|
|
|
def _tid() -> str:
|
|
|
return "mt-" + uuid.uuid4().hex[:8]
|
|
|
|
|
|
|
|
|
def materialize_action(s: Session, meeting_id: str, idx: int) -> tuple[MeetingAction, Task]:
|
|
|
"""회의 액션 1건 → task 생성 + materialized_task_id 연결."""
|
|
|
action = s.exec(
|
|
|
select(MeetingAction).where(
|
|
|
MeetingAction.meeting_id == meeting_id, MeetingAction.idx == idx
|
|
|
)
|
|
|
).first()
|
|
|
if action is None:
|
|
|
raise ValueError("action not found")
|
|
|
if action.added and action.materialized_task_id:
|
|
|
return action, s.get(Task, action.materialized_task_id) # 멱등
|
|
|
|
|
|
meeting = s.get(Meeting, meeting_id)
|
|
|
ev = s.get(CalEvent, meeting.event_id) if meeting else None
|
|
|
project_id = DEFAULT_PROJECT
|
|
|
if ev and ev.cal in ("work", "meeting", "team"):
|
|
|
project_id = "biz" # 업무성 회의 → 경영 전략 루트(데모 결정성)
|
|
|
if not s.get(Project, project_id):
|
|
|
project_id = DEFAULT_PROJECT
|
|
|
|
|
|
siblings = s.exec(
|
|
|
select(Task).where(Task.project_id == project_id, Task.parent_id == None) # noqa: E711
|
|
|
).all()
|
|
|
order = max([t.sort_order for t in siblings] + [-1]) + 1
|
|
|
ev_title = ev.title if ev else ""
|
|
|
notes = f"<p>회의 <b>‘{ev_title}’</b>의 액션 아이템에서 아리가 만든 작업이에요.</p>"
|
|
|
task = Task(
|
|
|
id=_tid(),
|
|
|
project_id=project_id,
|
|
|
parent_id=None,
|
|
|
title=action.text,
|
|
|
status=TaskStatus.todo,
|
|
|
assignee_id=_person_id(action.who),
|
|
|
due=None,
|
|
|
prio=Prio.normal,
|
|
|
notes=notes,
|
|
|
est="",
|
|
|
sort_order=order,
|
|
|
)
|
|
|
s.add(task)
|
|
|
action.added = True
|
|
|
action.materialized_task_id = task.id
|
|
|
s.add(action)
|
|
|
s.commit()
|
|
|
s.refresh(task)
|
|
|
s.refresh(action)
|
|
|
# task.created 발행 (phase-3 작업 보드/대시보드 갱신)
|
|
|
bus.publish(
|
|
|
"task.created",
|
|
|
{"task_id": task.id, "source": "meeting", "meeting_id": meeting_id},
|
|
|
)
|
|
|
return action, task
|
|
|
|
|
|
|
|
|
def materialize_all(s: Session, meeting_id: str) -> list[tuple[MeetingAction, Task]]:
|
|
|
"""남은(added=False) 액션 전부 실체화 → '모두 작업으로'."""
|
|
|
pending = s.exec(
|
|
|
select(MeetingAction).where(
|
|
|
MeetingAction.meeting_id == meeting_id, MeetingAction.added == False # noqa: E712
|
|
|
)
|
|
|
).all()
|
|
|
pairs = [materialize_action(s, meeting_id, a.idx) for a in pending]
|
|
|
if pairs:
|
|
|
bus.publish(
|
|
|
"meeting.ended",
|
|
|
{
|
|
|
"meeting_id": meeting_id,
|
|
|
"action_ids": [a.id for a, _ in pairs],
|
|
|
"task_ids": [t.id for _, t in pairs],
|
|
|
},
|
|
|
)
|
|
|
return pairs
|
|
|
|
|
|
|
|
|
def assemble_meeting(s: Session, meeting_id: str) -> dict | None:
|
|
|
"""Meeting + actions → MeetingOut dict (JSON 펼침)."""
|
|
|
m = s.get(Meeting, meeting_id)
|
|
|
if not m:
|
|
|
return None
|
|
|
actions = s.exec(
|
|
|
select(MeetingAction)
|
|
|
.where(MeetingAction.meeting_id == meeting_id)
|
|
|
.order_by(MeetingAction.idx)
|
|
|
).all()
|
|
|
j = json.loads
|
|
|
return dict(
|
|
|
id=m.id,
|
|
|
event_id=m.event_id,
|
|
|
phase=m.phase.value if hasattr(m.phase, "value") else m.phase,
|
|
|
one_on_one=m.one_on_one,
|
|
|
meta=m.meta,
|
|
|
starts_in=m.starts_in,
|
|
|
summary=j(m.summary_json),
|
|
|
decisions=j(m.decisions_json),
|
|
|
agenda=j(m.agenda_json),
|
|
|
last_meeting=j(m.last_meeting_json),
|
|
|
last_actions=j(m.last_actions_json),
|
|
|
insights=j(m.insights_json),
|
|
|
docs=j(m.docs_json),
|
|
|
person=j(m.person_json),
|
|
|
promises=j(m.promises_json),
|
|
|
signals=j(m.signals_json),
|
|
|
talking_points=j(m.talking_points_json),
|
|
|
actions=[
|
|
|
dict(
|
|
|
id=a.id,
|
|
|
idx=a.idx,
|
|
|
text=a.text,
|
|
|
who=a.who,
|
|
|
when=a.when_text,
|
|
|
added=a.added,
|
|
|
materialized_task_id=a.materialized_task_id,
|
|
|
)
|
|
|
for a in actions
|
|
|
],
|
|
|
)
|