토론

회의 안건 시간 제한 스킬

Wikiprompt, 무료 프롬프트 백과사전에서

Fatih Kadir Akın

2026년 10월 10일

회의 안건 시간 제한 스킬 회의 주제의 대략적인 목록을 시간 제한이 있는 안건으로 변환하는 완전한 스킬/에이전트로, 담당자, 결과물, 버퍼 점검, 안건 이상 징후 감지를 포함합니다. 일정 수립 및 맞춤 조정을 위한 Python 스크립트와 참고 자료, 실제 사례가 포함되어 있습니다.

프롬프트 내용저장

🌐
--- name: meeting-agenda-timeboxer description: 회의 주제 목록을 시작 및 종료 시간, 담당자, 각 항목의 명확한 결과(정보 공유, 논의, 결정, 브레인스토밍, 검토)가 포함된 시간 배정 안건으로 전환하고, 버퍼를 고려해 시간 내에 들어맞는지 확인하며, 늦은 결정이나 서두른 결정, 너무 많은 상태 업데이트, 과도하게 큰 항목, 휴식 누락과 같은 안건 문제를 지적하고, 바로 보낼 수 있는 안건과 사전 자료를 작성합니다. 사용자가 "이 회의 계획을 도와줘", "이 안건이 한 시간 안에 들어갈까?", "이 안건을 더 빡빡하게 만들어줘"라고 묻거나 팀 회의, 워크숍, 오프사이트, 검토를 위한 주제 목록을 공유할 때 사용합니다. --- # 회의 안건 시간 배정 도우미 여러분이 제시간에 끝나고 결정을 만들어내는 회의를 운영하도록 돕습니다. 주제 목록을 현실적인 일정으로 바꾸고, 각 항목에 담당자와 목적을 부여하며, 상태 업데이트를 회의실 밖으로 옮기고, 시간 계산을 투명하게 보여줘서 55분이 지나서야 핵심 결정을 논의할 시간이 없다는 것을 발견하는 일이 없도록 합니다. ## 이 스킬의 파일 - `scripts/timebox_agenda.py` - 모든 항목을 일정에 배정하고, 슬롯 및 버퍼와의 적합성을 확인하며, 안건 문제를 지적하고, 5분 단위로 항목을 슬롯에 맞출 수 있습니다 (Python 3 표준 라이브러리만 사용) - `references/timeboxing-principles.md` - 실제로 일이 얼마나 걸리는지, 항목 결과, 순서, 버퍼, 휴식 - `references/facilitation-moves.md` - 각 유형의 항목을 시간 배정 안에 유지하는 기법과, 시간이 초과되었을 때 해야 할 일 - `templates/agenda.md` - 안건 입력 형식 및 보낼 안건 - `examples/example-roadmap-meeting.md` - 과부하된 1시간 기획 회의를 수정한 실제 사례 ## 작업 흐름 ### 1. 사실 수집 질문하거나, 가정하고 그렇게 말합니다: - 회의 목적을 한 문장으로: 회의가 끝났을 때 반드시 사실이어야 하는 것은 무엇인가요? - 시작 시간 및 길이(또는 종료 시간), 참석자 수, 대면 또는 원격. - 각각 담당자와 그룹이 해야 할 일(정보 공유, 업데이트, 논의, 브레인스토밍, 검토, 결정)이 있는 주제. - 하드 제약 조건: 늦게 참여하거나 일찍 떠나는 사람, 고정된 휴식 시간. ### 2. 초안 안건 작성 `templates/agenda.md`의 입력 형식을 사용합니다 (항목당 한 줄: `분 | 주제 | 담당자 | 결과`). 아무도 모르는 시간은 `?`를 사용합니다. 시간은 `references/timeboxing-principles.md`로 추정합니다. ### 3. 시간 배정 도구 실행 ```bash python3 scripts/timebox_agenda.py agenda.txt python3 scripts/timebox_agenda.py agenda.txt --fit # 5분 단위로 슬롯에 맞게 조정 python3 scripts/timebox_agenda.py agenda.json --json ``` 시계 시간, 버퍼, 그리고 결과(안건이 맞지 않을 때 HIGH, 문제점에 대해 WARN, 계획되지 않은 시간 및 회의 비용(인-시간)에 대해 INFO)가 포함된 일정을 출력합니다. `--fit`은 먼저 상태 항목을 줄인 다음 다른 모든 것을 조정합니다. 항목 순서는 변경하지 않으므로, 이후에 직접 순서를 확인하세요. 스크립트를 실행할 수 없다면, 항목별로 직접 시계 계산을 하고 그렇게 말하세요. ### 4. 숫자만 고치는 것이 아니라 안건을 수정 다음 순서로 결과를 검토합니다: 1. 상태 업데이트 및 공지를 제거하거나 축소: 서면 사전 자료로 옮깁니다. 2. 결정은 초반(전반부)에 배치하고, 각 결정에는 최소 10분과 사전에 발송된 서면 제안서를 제공합니다. 3. 모든 항목에 담당자와 결과를 부여합니다. "기타 안건"은 삭제하거나 5분짜리 파킹 롯 검토로 전환합니다. 4. 45분이 넘는 항목은 분할하고, 60~90분마다 휴식을 추가합니다. 5. 그래도 맞지 않으면, 무엇을 삭제하거나 다른 회의 또는 비동기로 옮길지 제안하고, 무엇이 손실되는지 말합니다. HIGH 결과가 없고 모든 WARN이 수정되거나 설명될 때까지 스크립트를 다시 실행합니다. ### 5. 전달 `templates/agenda.md`에서 보낼 안건을 작성합니다: 목적, 원하는 결과, 시간이 표시된 일정, 담당자와 마감일이 있는 사전 자료, 파킹 롯 규칙. `references/facilitation-moves.md`에서 위험한 항목별로 진행 팁을 하나 또는 두 개 추가합니다. `examples/example-roadmap-meeting.md`의 스타일을 따릅니다. ## 규칙 - 필요한 것보다 결정 시간을 줄여서 계산을 맞추지 마십시오. 다른 것을 삭제하거나 옮기거나, 더 긴 슬롯을 권장하십시오. - 버퍼 유지: 시간당 약 5분, 워크숍의 경우 10%. - 회의가 열리면 안 되는 경우(결정 없음, 논의할 내용 없음)를 명확히 말하고 비동기 대안을 제안하십시오. - 사용자의 문화와 제약을 존중하고, 설교하지 말고 제안하십시오. - 시간은 회의 현지 시간 기준입니다. 다른 시간대의 원격 참석자의 경우 언급하되 요청하지 않는 한 변환하지 마십시오. ## 시간 배정 원칙 ### 모든 항목에는 결과가 필요합니다 | 결과 | 그룹이 하는 일 | 일반적인 시간 | 참고 | |---|---|---|---| | 정보 공유 | 무언가를 듣고, 명확한 질문을 함 | 2~5분 | 서면 사전 자료로 하는 것이 더 나음. | | 업데이트 | 개인 또는 팀의 상태 | 1인당 1~2분 | 일반적으로 비동기; 차단 요소만 실시간으로 유지. | | 논의 | 주제 탐색, 의견 공유, 아직 결정 없음 | 10~20분 | 논의가 무엇으로 이어지는지 말하세요 (다음 주 결정, 문서). | | 브레인스토밍 | 옵션 생성 | 15~30분 | 프레임된 질문과 방법(먼저 조용히 쓰기)이 필요함. | | 검토 | 초안, 디자인, 계획에 대한 피드백 제공 | 15~40분 | 자료를 사전에 보내고, 어떤 종류의 피드백을 원하는지 합의. | | 결정 | 옵션 중에서 선택하고 약속 | 10~30분 | 서면 제안서, 옵션, 결정권자, 결정 규칙이 필요함. | | 휴식 | 휴식 | 5~15분 | 60~90분마다. | 항목에 명확한 결과가 없으면 "이 항목 후에 무엇이 달라질까요?"라고 물어보세요. 대답이 "아무것도"라면 삭제하세요. ### 실제로 일이 얼마나 걸리는지 - 소개: 새로운 그룹은 1인당 약 1분, 정규 팀은 0분. - 주제에 대해 각자 한 번씩 말하기: 1인당 약 1.5~2분. 8명이 한 번씩 돌아가면 15분. - 화면 공유 및 시작: 2~3분, 새로운 도구를 사용하는 원격 회의는 더 오래 걸림. - 실제 의견 충돌이 있는 결정: 추정치를 두 배로 하거나, 지금 "논의"하고 다음에 "결정"으로 나누세요. - 안건 후반의 항목은 더 빠르지만 더 나쁘게 진행됩니다: 사람들이 서두르거나 떠납니다. ### 순서 1. 짧은 프레이밍으로 시작: 목적 및 원하는 결과 (2~5분). 2. 에너지와 참석률이 높은 전반부에 결정과 가장 어려운 주제를 배치. 3. 중반에 논의 및 검토. 4. 정보 및 업데이트는 마지막에 하거나 회의에서 완전히 제외. 5. 다음 단계, 담당자, 날짜로 끝내고 (5분), 절대 "기타 안건"으로 끝내지 마십시오. 같은 사람들이 필요한 항목을 그룹화하여 다른 사람들은 일찍 떠날 수 있게 하세요. ### 버퍼 및 휴식 - 버퍼: 시간당 약 5분, 워크숍의 경우 10%. 버퍼는 슬롯의 일부이지 추가 시간이 아닙니다. - 휴식: 60~90분마다 5~10분; 더 긴 워크숍은 15분. 원격 회의는 더 일찍 휴식이 필요합니다. - 연속 일정: 5분 일찍 끝내기 (25분 또는 50분 회의). ### 적합성 확인 - 시간당 약 8개 이상의 항목은 각각 7분 미만을 의미합니다. 병합하거나 삭제하세요. - 상태 및 공지가 시간의 약 1/3 이상: 사전 자료로 옮기세요. - 45분이 넘는 항목: 자체 결과가 있는 단계로 분할하세요 (예: "브레인스토밍 25" 후 "클러스터링 및 선택 20"). ### 회의 비용 인-시간 = 참석자 수 x 길이(시간). 9명이 참석하는 1시간 회의는 9시간의 업무 시간이 소요됩니다. 모든 사람이 모든 항목에 참석해야 하는지 물어보세요. ## 시간 배정을 유지하는 진행 기법 ### 회의 전 - 안건과 사전 자료를 24~48시간 전에 각 항목의 담당자와 함께 보내세요. - 모든 결정 항목에 대해 한 페이지 분량의 제안서를 보내세요: 질문, 옵션, 권장 사항, 결정권자. - 사람들이 사전 자료에 서면으로 질문을 추가하도록 요청하여, 회의가 프레젠테이션이 아닌 질문에서 시작되도록 하세요. ### 시작 시 - 목적과 끝까지 필요한 결과를 명시하세요. - 시간 관리자를 지정하고 신호(채팅 메시지, 2분 남았을 때 손 들기)에 동의하세요. - 오늘 결정이 어떻게 이루어지는지 말하세요: 담당자가 의견 수렴 후 결정, 다수결, 또는 동의("강한 반대 있나요?"). ### 항목 유형별 - 정보 공유: "2분, 그런 다음 각자 명확한 질문 하나." 자세한 질문은 파킹 롯 또는 후속 채팅으로. - 업데이트: 차단 요소와 요청만 실시간으로; 나머지는 사전 자료에. - 논의: 먼저 질문을 프레임하세요 ("옵션 B에 대해 우리가 걱정하는 것은 무엇인가요?"). 빠른 라운드를 사용하여 누구나 두 번 말하기 전에 한 번씩 말하게 하세요. - 브레인스토밍: 먼저 3~5분간 조용히 쓰고, 공유하고, 클러스터링. 판단보다 양. - 검토: 피드백 유형(차단 문제, 질문, 다듬기)에 동의하세요. 먼저 차단 문제를 수집하세요. - 결정: 제안을 다시 말하고, 우려 사항을 나열하고, 반대를 확인한 다음 결정하고 큰 소리로 말하세요: "우리는 X로 결정했습니다. 담당자 Y, 날짜 Z까지." ### 항목이 시간을 초과했을 때 다음 항목을 조용히 잠식하는 대신 선택을 명확히 하세요: 1. "이 항목에 2분 남았습니다. 5분 연장하고 다른 것을 줄일까요, 아니면 보류할까요?" 2. 보류: 담당자와 날짜를 적어 파킹 롯에 기록. 3. 다음 항목을 줄이되, 결정 항목은 절대 줄이지 마세요. 4. 회의 목표가 위험하면, 필요한 사람만 참석하는 짧은 후속 회의를 계획하세요. ### 원격 및 하이브리드 - 모든 사람이 자신의 기기로 참여하여 원격 참석자가 동등하게 되도록 하세요. - 발언 대기열과 빠른 설문에 채팅을 사용하세요. - 현재 항목이 강조된 안건을 화면에 공유하세요. ### 마무리 - 결정, 담당자, 날짜를 다시 읽어주세요 (5분). - 파킹 롯 검토: 각 항목을 배정하거나 삭제. - 24시간 이내에 메모를 보내세요. ## 안건 입력 (scripts/timebox_agenda.py용) ``` Meeting: <이름> Start: <HH:MM> Length: <분> # 또는 End: <HH:MM> Buffer: <분 또는 퍼센트, 예: 5 또는 10%> Attendees: <숫자> <분> | <주제> | <담당자> | <inform | update | discuss | brainstorm | review | decide | break> ? | <시간을 모르는 주제> | <담당자> | <결과> ``` Min, Topic, Owner, Outcome 열이 있는 Markdown 테이블도 작동하며, JSON도 작동합니다: `{"meeting": "...", "start": "14:00", "length": 60, "buffer": 5, "attendees": 6, "items": [{"minutes": 10, "topic": "...", "owner": "...", "outcome": "decide"}]}` --- # 보낼 안건 **<회의 이름>** <요일, 날짜>, <시작>-<종료> (<길이> 분), <장소 또는 화상 링크> 참석자: <이름 또는 팀> | 진행자: <이름> | 시간 관리자: <이름> | 메모: <이름> **목적:** <한 문장: 우리가 끝냈을 때 반드시 사실이어야 하는 것> **필요한 결과** 1. <결정 또는 결과> 2. <결정 또는 결과> **사전 자료 (by <요일, 시간>)** - <문서> - <담당자> - <무엇을 볼지, 읽는 데 걸리는 시간> **일정** | 시간 | 분 | 항목 | 담당자 | 결과 | |---|---|---|---|---| | <HH:MM-HH:MM> | <n> | <주제> | <담당자> | <결과> | | <HH:MM-HH:MM> | <n> | 버퍼 및 마무리: 결정, 담당자, 날짜 | <진행자> | | **오늘 결정 방식:** <담당자가 의견 수렴 후 결정 | 동의 | 투표> **파킹 롯:** 주제에서 벗어난 논점은 파킹 롯으로 이동하며, 마지막에 담당자가 배정됩니다. **이 회의에서 제외된 항목** - <항목> -> <비동기 업데이트, 다른 회의, 또는 문서>, 담당자 <이름> ## 예시: 과부하된 1시간 기획 회의 수정 ### 요청 "목요일 14:00에 9명과 1시간 동안 Q4를 계획하려고 합니다. 제 안건입니다. 들어갈까요? 예산 분할은 이 회의에서 승인되어야 하고, 상위 3개 이니셔티브도 마찬가지입니다." 초안 안건 (`agenda.txt`): ``` Meeting: Q4 로드맵 계획 Start: 14:00 Length: 60 Buffer: 5 Attendees: 9 10 | 환영 및 목표 | Dana | inform 15 | 팀 상태 라운드 (각 팀 리더) | 리더 | update 10 | 고객 피드백 하이라이트 | Priya | inform 20 | Q4 우선순위: 상위 3개 이니셔티브 선택 | Lee | discuss 10 | Q4 채용 계획 | | discuss 5 | Q4 예산 분할 승인 | Dana | decide ? | 기타 안건 | | ``` ### 첫 번째 실행 ``` $ python3 scripts/timebox_agenda.py agenda.txt Q4 로드맵 계획: 14:00-15:00 (60분, 버퍼 5분, 항목용 55분) 계획: 70분 14:00-14:10 10분 환영 및 목표 (Dana) [inform] 14:10-14:25 15분 팀 상태 라운드 (각 팀 리더) (리더) [update] 14:25-14:35 10분 고객 피드백 하이라이트 (Priya) [inform] 14:35-14:55 20분 Q4 우선순위: 상위 3개 이니셔티브 선택 (Lee) [discuss] 14:55-15:05 10분 Q4 채용 계획 [discuss] 15:05-15:10 5분 Q4 예산 분할 승인 (Dana) [decide] 15:10-15:10 ?분 기타 안건 [?] 14:55-15:00 5분 버퍼 / 마무리 결과: - [HIGH] 안건에 70분이 필요하지만 5분 버퍼 후 사용 가능한 시간은 55분뿐입니다: 15분 초과 (14:55 대신 15:10에 종료). 항목을 삭제, 축소, 이동하거나 --fit으로 실행하세요. - [WARN] 시간이 없는 항목 1개: 기타 안건. 남은 시간: 0분. - [WARN] "Q4 채용 계획"에 담당자가 없습니다. 준비하고 이끌 사람을 지정하세요. - [WARN] "Q4 예산 분할 승인"은 결정 항목인데 5분뿐입니다. 최소 10분을 계획하거나, 사전에 서면 제안서로 결정하세요. - [WARN] "Q4 예산 분할 승인"은 15:05에 시작하는 결정 항목으로, 회의의 마지막 1/4 지점에 있습니다. 에너지와 참석률이 높은 초반으로 옮기세요. - [WARN] "기타 안건"에 담당자가 없습니다. 준비하고 이끌 사람을 지정하세요. - [WARN] "기타 안건"에 결과가 없습니다. 정보 공유, 논의, 결정, 브레인스토밍 중 무엇인지 말하세요. - [WARN] 상태 업데이트 및 공지가 시간의 50%를 차지합니다. 사전 자료로 보내고 회의는 논의와 결정에 사용하세요. - [INFO] 회의 비용: 9명 x 60분 = 9인-시간. ``` 종료 코드 1: 안건이 맞지 않습니다. ### 숫자가 말하는 것 - 55분 창에 70분의 항목(기타 안건 제외): 기타 안건이 시작되기도 전에 15분 초과. - 시간의 절반이 업데이트와 공지인 반면, 결정해야 할 두 가지는 5분(예산)과 "논의" 라벨(우선순위)만 받았고, 예산 결정은 회의가 끝나야 할 시간 이후인 15:05에 시작될 예정. - `--fit`만 실행하면 모든 것을 55분에 맞추겠지만 예산 결정은 마지막에 5분으로 남게 됩니다. 숫자를 맞추는 것만으로는 부족합니다. 항목의 순서와 목적이 바뀌어야 합니다. ### 변경 사항 1. 팀 상태 라운드 (15분) 및 고객 피드백 하이라이트 (10분)는 수요일 12:00까지 제출되는 서면 사전 자료가 됩니다. 질문은 댓글로. 2. 예산 분할은 월요일에 제안서와 함께 시작 부분으로 이동하여 10분이 주어지며, 회의에서는 확인하거나 조정만 합니다. 3. "Q4 우선순위"는 25분의 결정 항목이 됩니다: 결과는 순위가 매겨진 상위 3개. 4. 채용 계획에는 담당자(Priya)와 더 좁은 질문이 생깁니다: 어떤 직무를 열지. 5. "기타 안건"은 5분짜리 "다음 단계, 담당자, 파킹 롯" 마무리로 대체됩니다. 6. 상태 라운드에만 필요했던 3명은 회의를 건너뛸 수 있습니다: 9명 대신 6명 참석. 수정된 안건: ``` Meeting: Q4 로드맵 계획 Start: 14:00 Length: 60 Buffer: 5 Attendees: 6 5 | 오늘의 환영 및 목표 | Dana | inform 10 | Q4 예산 분할 승인 (월요일에 제안서 발송) | Dana | decide 25 | Q4 우선순위: 상위 3개 이니셔티브 선택 | Lee | decide 10 | Q4 채용 계획: 열 직무 합의 | Priya | discuss 5 | 다음 단계, 담당자, 파킹 롯 | Dana | inform ``` ### 두 번째 실행 ``` $ python3 scripts/timebox_agenda.py agenda-revised.txt Q4 로드맵 계획: 14:00-15:00 (60분, 버퍼 5분, 항목용 55분) 계획: 55분 14:00-14:05 5분 오늘의 환영 및 목표 (Dana) [inform] 14:05-14:15 10분 Q4 예산 분할 승인 (월요일에 제안서 발송) (Dana) [decide] 14:15-14:40 25분 Q4 우선순위: 상위 3개 이니셔티브 선택 (Lee) [decide] 14:40-14:50 10분 Q4 채용 계획: 열 직무 합의 (Priya) [discuss] 14:50-14:55 5분 다음 단계, 담당자, 파킹 롯 (Dana) [inform] 14:55-15:00 5분 버퍼 / 마무리 결과: - [INFO] 회의 비용: 6명 x 60분 = 6인-시간. ``` 종료 코드 0, 경고 없음, 회의 비용은 9인-시간 대신 6인-시간입니다. ### 보낼 안건 (템플릿 작성, 축약) **Q4 로드맵 계획** 목요일, 14:00-15:00 (60분), 3호실 및 화상 링크 진행자: Dana | 시간 관리자: Lee | 메모: Priya **목적:** 승인된 Q4 예산 분할과 순위가 매겨진 상위 3개 이니셔티브를 가지고 퇴장. **사전 자료 (수요일 12:00까지)** - 팀 상태 한 페이지 요약 - 각 팀 리더 - 읽는 데 5분 - 고객 피드백 하이라이트 - Priya - 5분 - 예산 분할 제안서 - Dana - 10분 **일정** | 시간 | 분 | 항목 | 담당자 | 결과 | |---|---|---|---|---| | 14:00-14:05 | 5 | 오늘의 환영 및 목표 | Dana | inform | | 14:05-14:15 | 10 | Q4 예산 분할 승인 (월요일에 제안서 발송) | Dana | decide | | 14:15-14:40 | 25 | Q4 우선순위: 상위 3개 이니셔티브 선택 | Lee | decide | | 14:40-14:50 | 10 | Q4 채용 계획: 열 직무 합의 | Priya | discuss | | 14:50-14:55 | 5 | 다음 단계, 담당자, 파킹 롯 | Dana | inform | | 14:55-15:00 | 5 | 버퍼 | | | **오늘 결정 방식:** Dana가 의견 수렴 후 예산을 결정합니다. 상위 3개는 동의로 선택됩니다 (강한 반대 있나요?). **진행 팁** - 우선순위: 후보 목록에 대해 3분간 조용히 점수 투표를 한 다음, 2~5위 항목만 논의하세요. - 예산 항목이 10분에 도달하면, 우선순위 시간을 잠식하는 대신 미해결 지점을 담당자와 24시간 마감으로 파킹하세요. **이 회의에서 제외된 항목:** 팀 상태 라운드 및 고객 피드백 (사전 자료). ### 이것이 좋은 답변인 이유 - 분만이 아니라 목적과 순서를 수정합니다. - 필수 결정 두 가지가 이제 준비된 제안서와 함께 처음 40분 안에 이루어집니다. - 스크립트가 적합성을 확인하고, 업데이트만을 위해 참석했던 사람들은 시간을 돌려받습니다. ## Python 스크립트: timebox_agenda.py ```python #!/usr/bin/env python3 """회의 안건 시간 배정: 모든 항목을 일정에 배정하고, 적합성을 확인하고, 안건 문제를 지적합니다. 사용법: python3 timebox_agenda.py agenda.txt python3 timebox_agenda.py agenda.txt --fit # 슬롯에 맞게 항목 조정 (5분 단위) python3 timebox_agenda.py agenda.json --json cat agenda.txt | python3 timebox_agenda.py - 텍스트 입력: 선택적 "Key: value" 헤더 줄, 그 다음 항목당 한 줄: 분 | 주제 | 담당자 | 결과 Markdown 테이블 행 (| 10 | 주제 | 담당자 | decide |)도 작동합니다. 헤더 및 구분자 행은 건너뜁니다. 알 수 없는 시간은 ?를 사용하세요. 헤더 키: Meeting, Start (HH:MM), Length (분) 또는 End (HH:MM), Buffer (분 또는 퍼센트, 예: 5 또는 10%), Attendees (숫자). 결과: inform, update, discuss, brainstorm, review, decide, break. JSON 입력: {"meeting": "...", "start": "14:00", "length": 60, "buffer": 5, "attendees": 8, "items": [{"minutes": 10, "topic": "...", "owner": "...", "outcome": "decide"}]} --fit은 먼저 상태 항목(및 결과가 없는 항목)을 줄인 다음 모든 것을 조정합니다. 남는 시간은 decide 및 discuss 항목에 사용됩니다. 확인: 초과 (HIGH), 누락된 시간, 담당자, 결과, 늦거나 서두른 결정, 과도하게 큰 항목, 휴식 없는 긴 시간, 너무 많은 상태 업데이트, 시간당 너무 많은 항목, 회의 비용(인-시간). 종료 코드: 0 HIGH 결과 없음, 1 HIGH 발견, 2 사용법 또는 입력 오류. 표준 라이브러리만 사용. """ import argparse import json import math import re import sys OUTCOMES = {"inform", "update", "discuss", "brainstorm", "review", "decide", "break"} ALIASES = {"decision": "decide", "info": "inform", "fyi": "inform", "status": "update", "updates": "update", "discussion": "discuss", "ideas": "brainstorm", "ideation": "brainstorm", "feedback": "review", "approve": "decide", "approval": "decide", "pause": "break", "coffee": "break"} class InputError(Exception): pass def hhmm(text): m = re.fullmatch(r"\s*(\d{1,2})[:.](\d{2})\s*", str(text)) if not m or int(m.group(1)) > 23 or int(m.group(2)) > 59: raise InputError(f"시간은 HH:MM 형식이어야 합니다. 입력값: {text!r}") return int(m.group(1)) * 60 + int(m.group(2)) def fmt(minutes): minutes = int(round(minutes)) % (24 * 60) return f"{minutes // 60:02d}:{minutes % 60:02d}" def parse_minutes(text): t = str(text).strip().lower() if t in ("", "?", "tbd", "-"): return None m = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(m|min|mins|minutes?)?", t) if m: return float(m.group(1)) m = re.fullmatch(r"(\d+(?:\.\d+)?)\s*h(?:ours?)?(?:\s*(\d+)\s*m(?:in)?)?", t) if m: return float(m.group(1)) * 60 + float(m.group(2) or 0) raise InputError(f"시간을 읽을 수 없습니다: {text!r}") def norm_outcome(text): t = (text or "").strip().lower() return ALIASES.get(t, t) def parse_text(text): meta, items, errors = {}, [], [] for no, raw in enumerate(text.replace("\r\n", "\n").split("\n"), 1): line = raw.strip() if not line or line.startswith("#"): continue hm = re.fullmatch(r"([A-Za-z ]{3,20}):\s*(.+)", line) if hm and "|" not in line: meta[hm.group(1).strip().lower()] = hm.group(2).strip() continue if "|" not in line: errors.append(f"줄 {no}: '분 | 주제 | 담당자 | 결과' 형식이어야 합니다. 입력값: {line[:60]!r}") continue cells = [c.strip() for c in line.strip("|").split("|")] if all(re.fullmatch(r":?-{2,}:?", c) for c in cells if c): continue if cells and cells[0].lower() in ("min", "mins", "minutes", "time", "duration"): continue cells += [""] * (4 - len(cells)) try: minutes = parse_minutes(cells[0]) except InputError as e: errors.append(f"줄 {no}: {e}") continue if not cells[1]: errors.append(f"줄 {no}: 항목에 주제가 없습니다") continue items.append({"line": no, "minutes": minutes, "topic": cells[1], "owner": cells[2], "outcome": norm_outcome(cells[3])}) return meta, items, errors def parse_json(text): data = json.loads(text) if not isinstance(data, dict) or not isinstance(data.get("items"), list): raise InputError("JSON 입력은 'items' 목록이 있는 객체여야 합니다") meta = {k.lower(): str(v) for k, v in data.items() if k != "items" and v is not None} items, errors = [], [] for i, it in enumerate(data["items"], 1): try: minutes = parse_minutes(it.get("minutes", "")) except InputError as e: errors.append(f"항목 {i}: {e}") continue if not str(it.get("topic", "")).strip(): errors.append(f"항목 {i}: 항목에 주제가 없습니다") continue items.append({"line": i, "minutes": minutes, "topic": str(it["topic"]).strip(), "owner": str(it.get("owner", "") or "").strip(), "outcome": norm_outcome(it.get("outcome", ""))}) return meta, items, errors def settings(meta): start = hhmm(meta.get("start", "09:00")) if "length" in meta: length = parse_minutes(meta["length"]) if not length or length <= 0: raise InputError("Length는 양수 분이어야 합니다") elif "end" in meta: length = (hhmm(meta["end"]) - start) % (24 * 60) if length == 0: raise InputError("End는 Start 이후여야 합니다") else: raise InputError("헤더에 'Length: <분>' 또는 'End: HH:MM'이 필요합니다") b = meta.get("buffer", "0").strip() if b.endswith("%"): buffer = 5 * round(length * float(b[:-1]) / 100 / 5) # 퍼센트 버퍼는 5분 단위로 반올림 else: buffer = parse_minutes(b) or 0 if buffer >= length: raise InputError("Buffer는 회의 시간보다 짧아야 합니다") attendees = int(meta["attendees"]) if str(meta.get("attendees", "")).strip().isdigit() else None return {"meeting": meta.get("meeting", meta.get("title", "Meeting")), "start": start, "length": length, "buffer": buffer, "available": length - buffer, "attendees": attendees} def fit(items, available): """항목을 `available` 분 안에 5분 단위로 맞춥니다 (누락된 시간은 10분으로 계산, 휴식 유지). 안건이 너무 길면 상태 항목(inform, update, 또는 결과 없음)이 먼저 계획 시간의 절반까지 줄어들고, 그런 다음 모든 항목이 비례적으로 조정됩니다. 너무 짧으면 남는 시간은 먼저 decide 및 discuss 항목에 사용됩니다. """ fixed = sum(it["minutes"] or 0 for it in items if it["outcome"] == "break") flex = [it for it in items if it["outcome"] != "break"] room = available - fixed if not flex or room < 5 * len(flex): raise InputError(f"{len(flex)}개 항목을 각각 최소 5분씩 {room:g}분에 맞출 수 없습니다") want = [max(5, 5 * round((it["minutes"] or 10) / 5)) for it in flex] cur = list(want) status = [i for i, it in enumerate(flex) if it["outcome"] in ("inform", "update", "")] while sum(cur) > room: cands = [i for i in status if cur[i] - 5 >= max(5, want[i] / 2)] if not cands: break cur[max(cands, key=lambda k: cur[k])] -= 5 if sum(cur) > room: scale = room / sum(cur) ref = [c * scale for c in cur] cur = [max(5, 5 * round(r / 5)) for r in ref] while sum(cur) > room: cands = [k for k in range(len(cur)) if cur[k] > 5] cur[max(cands, key=lambda k: (cur[k] - ref[k], cur[k]))] -= 5 while sum(cur) + 5 <= room: cur[min(range(len(cur)), key=lambda k: cur[k] - ref[k])] += 5 else: prio = [i for i, it in enumerate(flex) if it["outcome"] in ("decide", "discuss")] or list(range(len(flex))) while sum(cur) + 5 <= room: cur[min(prio, key=lambda k: cur[k] / want[k])] += 5 out, it_new = [], iter(cur) for it in items: copy = dict(it) copy["original"] = it["minutes"] if it["outcome"] != "break": copy["minutes"] = float(next(it_new)) out.append(copy) return out def analyze(items, cfg): findings = [] def add(sev, msg): findings.append({"severity": sev, "message": msg}) t = cfg["start"] for it in items: it["start"] = t t += it["minutes"] or 0 it["end"] = t planned = sum(it["minutes"] or 0 for it in items) avail, end_slot = cfg["available"], cfg["start"] + cfg["length"] unknown = [it for it in items if it["minutes"] is None] if planned > avail: add("HIGH", f"안건에 {planned:g}분이 필요하지만 사용 가능한 시간은 {avail:g}분뿐입니다" f"{' (5분 버퍼 후)' if cfg['buffer'] else ''}: {planned - avail:g}분 초과" f" ({fmt(cfg['start'] + planned)}에 종료, {fmt(end_slot - cfg['buffer'])} 대신). 항목을 삭제, 축소, 이동하거나 --fit으로 실행하세요.") elif avail - planned > 0.15 * avail and not unknown: add("INFO", f"{avail - planned:g}분 계획되지 않음. 일찍 끝내거나 주요 결정에 더 많은 시간을 주세요.") if unknown: add("WARN", f"시간이 없는 항목 {len(unknown)}개: " + "; ".join(it["topic"][:40] for it in unknown) + f". 남은 시간: {max(avail - planned, 0):g}분.") for it in items: name = f"\"{it['topic'][:50]}\"" if it["outcome"] != "break" and not it["owner"]: add("WARN", f"{name}에 담당자가 없습니다. 준비하고 이끌 사람을 지정하세요.") if not it["outcome"]: add("WARN", f"{name}에 결과가 없습니다. 정보 공유, 논의, 결정, 브레인스토밍 중 무엇인지 말하세요.") elif it["outcome"] not in OUTCOMES: add("WARN", f"{name}에 알 수 없는 결과 '{it['outcome']}' (사용 가능: {', '.join(sorted(OUTCOMES))}).") m = it["minutes"] or 0 if it["outcome"] == "decide": if it["minutes"] is not None and m < 10: add("WARN", f"{name}은(는) 결정 항목인데 {m:g}분뿐입니다. 최소 10분을 계획하거나, 사전에 서면 제안서로 결정하세요.") if it["start"] - cfg["start"] >= 0.75 * cfg["length"]: add("WARN", f"{name}은(는) {fmt(it['start'])}에 시작하는 결정 항목으로, 회의의 마지막 1/4 지점에 있습니다. 에너지와 참석률이 높은 초반으로 옮기세요.") if it["outcome"] in ("discuss", "brainstorm", "review") and it["minutes"] is not None and m < 5: add("WARN", f"{name}은(는) {it['outcome']} 항목인데 {m:g}분뿐입니다. 그것은 {it['outcome']}이 아니라 언급에 불과합니다.") if m > 45 and it["outcome"] != "break": add("WARN", f"{name}은(는) {m:g}분입니다. 자체 결과가 있는 부분으로 분할하세요.") stretch, stretch_start = 0, cfg["start"] for it in items: if it["outcome"] == "break": stretch, stretch_start = 0, it["end"] continue stretch += it["minutes"] or 0 if stretch > 90: add("WARN", f"{fmt(stretch_start)}부터 {stretch:g}분 동안 휴식이 없습니다. 60~90분마다 5~10분 휴식을 추가하세요.") stretch = -10**9 status = sum(it["minutes"] or 0 for it in items if it["outcome"] in ("inform", "update")) if planned and status / planned > 0.35: add("WARN", f"상태 업데이트 및 공지가 시간의 {round(100 * status / planned)}%를 차지합니다. 사전 자료로 보내고 회의는 논의와 결정에 사용하세요.") work_items = [it for it in items if it["outcome"] != "break"] hours = cfg["length"] / 60 if hours and len(work_items) / hours > 8: add("WARN", f"{cfg['length']:g}분에 {len(work_items)}개 항목은 시간당 8개가 넘습니다. 항목을 병합하거나 삭제하세요.") if not any(it["outcome"] == "decide" for it in items): add("INFO", "결정 항목이 없습니다. 이 회의가 실시간으로 열릴 필요가 있는지 확인하세요.") cost = None if cfg["attendees"]: cost = round(cfg["attendees"] * cfg["length"] / 60, 1) add("INFO", f"회의 비용: {cfg['attendees']}명 x {cfg['length']:g}분 = {cost:g}인-시간.") order = {"HIGH": 0, "WARN": 1, "INFO": 2} findings.sort(key=lambda f: order[f["severity"]]) return {"meeting": cfg["meeting"], "start": fmt(cfg["start"]), "end": fmt(end_slot), "length": cfg["length"], "buffer": cfg["buffer"], "available": avail, "planned": planned, "person_hours": cost, "items": [{k: (fmt(v) if k in ("start", "end") else v) for k, v in it.items()} for it in items], "findings": findings} def main(): ap = argparse.ArgumentParser(description="회의 안건 시간 배정") ap.add_argument("path", help="안건 텍스트/Markdown/JSON 파일, 또는 - (stdin)") ap.add_argument("--fit", action="store_true", help="사용 가능한 시간에 맞게 항목 시간 조정") ap.add_argument("--json", action="store_true") args = ap.parse_args() try: text = sys.stdin.read() if args.path == "-" else open(args.path, encoding="utf-8").read() if not text.strip(): raise InputError("입력이 비어 있습니다") meta, items, errors = parse_json(text) if text.lstrip().startswith("{") else parse_text(text) if errors: raise InputError("안건을 읽을 수 없습니다:\n " + "\n ".join(errors)) if not items: raise InputError("안건 항목을 찾을 수 없습니다 ('10 | 주제 | 담당자 | decide' 형식의 줄이 필요합니다)") cfg = settings(meta) before = sum(it["minutes"] or 0 for it in items) n_unknown = sum(1 for it in items if it["minutes"] is None) if args.fit: items = fit(items, cfg["available"]) except FileNotFoundError: print(f"오류: 파일을 찾을 수 없습니다: {args.path}", file=sys.stderr) return 2 except (InputError, json.JSONDecodeError, ValueError) as e: print(f"오류: {e}", file=sys.stderr) return 2 rep = analyze(items, cfg) rep["fitted_from"] = before if args.fit else None if args.json: print(json.dumps(rep, indent=2)) else: print(f"{rep['meeting']}: {rep['start']}-{rep['end']} ({rep['length']:g}분, 버퍼 {rep['buffer']:g}분, 항목용 {rep['available']:g}분)") if args.fit: extra = f" + 시간이 없는 {n_unknown}개 (각각 10분으로 계산)" if n_unknown else "" print(f"조정됨: {before:g}분의 항목{extra}을(를) {rep['planned']:g}분에 맞춤 (5분 단위, 휴식 유지).") print(f"계획: {rep['planned']:g}분\n") for it in rep["items"]: mins = "?" if it["minutes"] is None else f"{it['minutes']:g}" was = f" (이전: {it['original']:g})" if args.fit and it.get("original") not in (None, it["minutes"]) else "" who = f" ({it['owner']})" if it["owner"] else "" print(f" {it['start']}-{it['end']} {mins:>3}분{was:<10} {it['topic']}{who} [{it['outcome'] or '?'}]") if rep["buffer"]: print(f" {fmt(hhmm(rep['end']) - rep['buffer'])}-{rep['end']} {rep['buffer']:>3g}분 버퍼 / 마무리") print() if rep["findings"]: print("결과:") for f in rep["findings"]: print(f"- [{f['severity']}] {f['message']}") else: print("결과: 없음") return 1 if any(f["severity"] == "HIGH" for f in rep["findings"]) else 0 if __name__ == "__main__": sys.exit(main()) ```

전체 프롬프트를 보려면 로그인하세요

Continue with:

By logging in, you agree to our Terms of Use and Privacy Policy

사용법

이 프롬프트는 productivity와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.

최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.

참고 자료

분류:productivity| prompts.chat| meeting-agenda| timeboxing

토론