# 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"
회의 ‘{ev_title}’의 액션 아이템에서 아리가 만든 작업이에요.
" 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 ], )