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.

156 lines
4.6 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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
],
)