会議アジェンダタイムボクサースキル
フリーのプロンプト百科事典 Wikiprompt より
会議アジェンダタイムボクサースキル 会議トピックの大まかなリストを、時間枠付きのアジェンダに変換する完全なスキル/エージェント。担当者、成果、バッファチェック、アジェンダの不自然さ検出を含む。スケジューリングとフィッティング用のPythonスクリプト、参考資料、実例を含む。
プロンプト内容保存
🌐
---
name: meeting-agenda-timeboxer
description: 会議のトピックの大まかなリストを、各項目に開始・終了時刻、担当者、明確な成果(情報共有、議論、決定、ブレインストーミング、レビュー)を備えたタイムボックス化されたアジェンダに変換し、バッファを考慮してスロットに収まるかチェックし、決定が遅すぎる・駆け足になる、ステータス更新が多すぎる、項目が大きすぎる、休憩がないなどのアジェンダの問題点を指摘し、送信可能なアジェンダと事前資料を作成します。ユーザーが「この会議の計画を手伝って」「このアジェンダは1時間に収まる?」「このアジェンダをもっとタイトにして」と尋ねたり、チーム会議、ワークショップ、オフサイト、レビュー用のトピックのリストを共有したときに使用します。
---
# 会議アジェンダ・タイムボクサー
あなたが、時間通りに終わり、決定を生み出す会議を運営できるよう支援します。トピックのリストを現実的なスケジュールに変換し、各項目に担当者と目的を与え、ステータス更新を会議から追い出し、時間の計算を可視化して、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` の入力形式を使用します(各項目1行:`分数 | トピック | 担当者 | 成果`)。誰も知らない期間には `?` を使用します。期間の見積もりには `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` から1つか2つのファシリテーションのヒントを追加します。`examples/example-roadmap-meeting.md` のスタイルに従います。
## ルール
- 決定事項を必要な時間より短くして計算を成立させないでください。他の何かを削除または移動するか、より長いスロットを推奨します。
- バッファを維持します:1時間あたり約5分、ワークショップでは10パーセント。
- 会議を開催すべきでない場合(決定事項がない、議論することがない)は、はっきりと伝え、非同期の代替案を提案します。
- ユーザーの文化と制約を尊重します。説教せず、提案します。
- 時刻は会議の現地時刻です。他のタイムゾーンのリモート参加者のために言及しますが、求められない限り変換しません。
## タイムボクシングの原則
### すべての項目には成果が必要です
| 成果 | グループが行うこと | 標準的な時間 | メモ |
|---|---|---|---|
| 情報共有 | 何かを聞き、明確化の質問をする | 2〜5分 | 書面による事前資料の方が良い。 |
| 更新 | 人やチームからの状況報告 | 1人あたり1〜2分 | 通常は非同期。ブロッカーのみライブで。 |
| 議論 | トピックを探求し、見解を共有、まだ決定しない | 10〜20分 | 議論が何に繋がるかを伝える(来週の決定、ドキュメント)。 |
| ブレインストーミング | 選択肢を生成する | 15〜30分 | 枠組みのある質問と方法が必要(最初に黙って書く)。 |
| レビュー | ドラフト、デザイン、または計画にフィードバックを与える | 15〜40分 | 資料を事前に送付。どのようなフィードバックが求められているか合意する。 |
| 決定 | 選択肢から選び、コミットする | 10〜30分 | 書面による提案、選択肢、決定者、決定ルールが必要。 |
| 休憩 | 休息 | 5〜15分 | 60〜90分ごと。 |
項目に明確な成果がない場合、「この項目の後で何が変わる?」と尋ねます。答えが「何もない」なら、削除します。
### 物事は実際にどれだけ時間がかかるか
- 自己紹介:新しいグループでは1人あたり約1分、定期的なチームでは0分。
- 各人がトピックについて1回発言する:1人あたり約1.5〜2分。8人で1ラウンドは15分。
- 画面共有と開始:2〜3分。新しいツールを使うリモート会議ではさらに長い。
- 本当の意見の相違がある決定:見積もりを2倍にするか、「議論」と「決定」に分けて次回にします。
- アジェンダの後半の項目は速く進むが質が悪い:人々は急いだり、去ったりします。
### 順序
1. 短いフレーミングから始める:目的と期待される成果(2〜5分)。
2. 決定事項と最も難しいトピックを前半に。エネルギーと出席率が高いうちに。
3. 議論とレビューを中間に。
4. 情報と更新は最後に、または会議から完全に外す。
5. 次のステップ、担当者、日付で終了(5分)。「その他」では決して終わらない。
同じ人々を必要とする項目をグループ化し、他の人が早く退出できるようにします。
### バッファと休憩
- バッファ:1時間あたり約5分、ワークショップでは10パーセント。バッファはスロットの一部であり、追加時間ではありません。
- 休憩:60〜90分ごとに5〜10分。長いワークショップでは15分。リモート会議ではより早く休憩が必要です。
- 連続したカレンダー:5分早く終了します(25分または50分の会議)。
### 適合チェック
- 1時間あたり約8項目を超えると、各項目は7分未満になります。統合または削除します。
- ステータスとアナウンスが時間の約3分の1を超える:事前資料に移します。
- 45分を超える項目:独自の成果を持つステップに分割します(例:「ブレインストーミング25分」→「クラスタリングと選択20分」)。
### 会議のコスト
人時 = 参加者数 x 時間(時間単位)。9人での1時間の会議は9労働時間かかります。全員がすべての項目に参加する必要があるか尋ねます。
## タイムボックスを守るファシリテーションの動き
### 会議の前
- アジェンダと事前資料を24〜48時間前に、各項目の担当者とともに送信します。
- 決定項目ごとに、1ページの提案を送信します:質問、選択肢、推奨事項、誰が決定するか。
- 参加者に事前資料への質問を書面で追加するよう依頼します。プレゼンテーションではなく、質問から会議が始まるように。
### 開始時
- 目的と、終了時までに必要な成果を述べます。
- タイムキーパーを指名し、合図に同意します(チャットでのメッセージ、残り2分での手の挙上)。
- 今日の決定方法を伝えます:担当者がインプット後に決定、多数決、またはコンセント(「強い反対はありますか?」)。
### 項目タイプ別
- 情報共有:「2分、その後各1つの明確化質問。」詳細な質問はパーキングロットまたはフォローアップチャットへ。
- 更新:ブロッカーと依頼のみライブ。他はすべて事前資料に。
- 議論:最初に質問を組み立てます(「オプションBについて何が心配ですか?」)。誰もが2回発言する前に1回発言するように、クイックラウンドを使用します。
- ブレインストーミング:最初に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. <決定または結果>
**事前資料(<曜日、時刻>まで)**
- <ドキュメント> - <担当者> - <何を見るか、読むのにかかる時間>
**スケジュール**
| 時刻 | 分 | 項目 | 担当者 | 成果 |
|---|---|---|---|---|
| <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 | 歓迎と目標 | ダナ | inform
15 | チームステータスラウンド(各チームリード) | リード | update
10 | カスタマーフィードバックのハイライト | プリヤ | inform
20 | Q4の優先事項:トップ3のイニシアチブを選ぶ | リー | discuss
10 | Q4の採用計画 | | discuss
5 | Q4予算配分を承認 | ダナ | decide
? | その他 | |
```
### 最初の実行
```
$ python3 scripts/timebox_agenda.py agenda.txt
Q4ロードマップ計画: 14:00-15:00 (60分、バッファ5分、項目用55分)
計画: 70分
14:00-14:10 10分 歓迎と目標 (ダナ) [inform]
14:10-14:25 15分 チームステータスラウンド(各チームリード) (リード) [update]
14:25-14:35 10分 カスタマーフィードバックのハイライト (プリヤ) [inform]
14:35-14:55 20分 Q4の優先事項:トップ3のイニシアチブを選ぶ (リー) [discuss]
14:55-15:05 10分 Q4の採用計画 [discuss]
15:05-15:10 5分 Q4予算配分を承認 (ダナ) [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開始の決定事項で、会議の最後の4分の1にあります。エネルギーと出席率が高いうちに、より早い時間に移動してください。
- [WARN] 「その他」に担当者がいません。準備と進行を担当する人を指名してください。
- [WARN] 「その他」に成果がありません。情報共有、議論、決定、ブレインストーミングのいずれかを指定してください。
- [WARN] ステータス更新とアナウンスが時間の50%を占めています。事前資料として送信し、会議は議論と決定のために使用してください。
- [INFO] 会議コスト: 9人 x 60分 = 9人時。
```
終了コード 1:アジェンダが収まりません。
### 数字が示すもの
- 55分の枠に70分の項目(その他は除く):その他が始まる前から15分超過。
- 時間の半分は更新とアナウンス。一方、決定が必要な2つのことには5分(予算)と「議論」ラベル(優先事項)しかなく、予算決定は会議が終了するはずの15:05に開始されます。
- `--fit` を単独で実行すると、すべてを55分に圧縮できますが、予算決定は最後の5分のままです。数字を合わせるだけでは不十分です。項目の順序と目的を変更する必要があります。
### 変更点
1. チームステータスラウンド(15分)とカスタマーフィードバックのハイライト(10分)は、水曜日12:00までに書面による事前資料になります。質問はコメントに。
2. 予算配分は10分で開始時に移動し、月曜日に提案を送信します。会議では確認または調整のみ行います。
3. 「Q4の優先事項」は25分の決定事項になります。成果はランク付けされたトップ3です。
4. 採用計画には担当者(プリヤ)と、より狭い質問が与えられます:どの役割を募集するか。
5. 「その他」は、5分の「次のステップ、担当者、パーキングロット」クローズに置き換えられます。
6. ステータスラウンドにのみ必要な3人は会議をスキップできます:参加者は9人ではなく6人。
修正されたアジェンダ:
```
Meeting: Q4ロードマップ計画
Start: 14:00
Length: 60
Buffer: 5
Attendees: 6
5 | 本日の歓迎と目標 | ダナ | inform
10 | Q4予算配分を承認(提案は月曜日に送信済み) | ダナ | decide
25 | Q4の優先事項:トップ3のイニシアチブを選ぶ | リー | decide
10 | Q4の採用計画:募集する役割に同意 | プリヤ | discuss
5 | 次のステップ、担当者、パーキングロット | ダナ | inform
```
### 2回目の実行
```
$ python3 scripts/timebox_agenda.py agenda-revised.txt
Q4ロードマップ計画: 14:00-15:00 (60分、バッファ5分、項目用55分)
計画: 55分
14:00-14:05 5分 本日の歓迎と目標 (ダナ) [inform]
14:05-14:15 10分 Q4予算配分を承認(提案は月曜日に送信済み) (ダナ) [decide]
14:15-14:40 25分 Q4の優先事項:トップ3のイニシアチブを選ぶ (リー) [decide]
14:40-14:50 10分 Q4の採用計画:募集する役割に同意 (プリヤ) [discuss]
14:50-14:55 5分 次のステップ、担当者、パーキングロット (ダナ) [inform]
14:55-15:00 5分 バッファ / まとめ
調査結果:
- [INFO] 会議コスト: 6人 x 60分 = 6人時。
```
終了コード 0、警告なし。会議コストは9人時ではなく6人時です。
### 送信用アジェンダ(記入済みテンプレート、短縮版)
**Q4ロードマップ計画**
木曜日、14:00-15:00(60分)、3号室とビデオリンク
ファシリテーター: ダナ | タイムキーパー: リー | ノート: プリヤ
**目的:** 承認されたQ4予算配分と、ランク付けされたトップ3のイニシアチブを持って退出する。
**事前資料(水曜日12:00まで)**
- チームステータス1ページ - 各チームリード - 読むのに5分
- カスタマーフィードバックのハイライト - プリヤ - 5分
- 予算配分提案 - ダナ - 10分
**スケジュール**
| 時刻 | 分 | 項目 | 担当者 | 成果 |
|---|---|---|---|---|
| 14:00-14:05 | 5 | 本日の歓迎と目標 | ダナ | inform |
| 14:05-14:15 | 10 | Q4予算配分を承認(提案は月曜日に送信済み) | ダナ | decide |
| 14:15-14:40 | 25 | Q4の優先事項:トップ3のイニシアチブを選ぶ | リー | decide |
| 14:40-14:50 | 10 | Q4の採用計画:募集する役割に同意 | プリヤ | discuss |
| 14:50-14:55 | 5 | 次のステップ、担当者、パーキングロット | ダナ | inform |
| 14:55-15:00 | 5 | バッファ | | |
**本日の決定方法:** ダナがインプット後に予算を決定。トップ3はコンセント(強い反対はありますか?)で選択。
**ファシリテーションのヒント**
- 優先事項:候補リストへの3分間の黙ってのドット投票。その後、ランク2〜5の項目のみ議論。
- 予算項目が10分に達した場合、優先事項を削る代わりに、未解決の論点を担当者と24時間の期限付きで保留にします。
**この会議から移動したもの:** チームステータスラウンドとカスタマーフィードバック(事前資料)。
### これが良い回答である理由
- 分数だけでなく、目的と順序を修正しています。
- 必要な2つの決定は、準備された提案とともに、最初の40分で行われます。
- スクリプトが適合を確認し、更新のためだけに参加していた人々は1時間を取り戻します。
## 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 -
テキスト入力: オプションの "キー: 値" ヘッダー行、その後1行に1項目を
分数 | トピック | 担当者 | 成果
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)、期間・担当者・成果の欠落、遅すぎるまたは駆け足の決定、過大な項目、休憩なしの長時間、ステータス更新の多さ、1時間あたりの項目数の多さ、会議の人時コスト。
終了コード: 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"{'(' + format(cfg['buffer'], 'g') + '分のバッファ後)' 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'])} 開始の決定事項で、会議の最後の4分の1にあります。エネルギーと出席率が高いうちに、より早い時間に移動してください。")
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)} 個の項目は、1時間あたり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ファイル、または標準入力の場合は -")
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())
```
ログインして完全なプロンプトを表示
次で続行:
ログインすると、次に同意したことになります: 利用規約 と プライバシーポリシー
使い方
このプロンプトは productivity 向けに設計されています。上の内容をコピーして、お好みの AI ツールに貼り付けてください。
最良の結果を得るには、プレースホルダー(角括弧や大文字で示された部分)を具体的な要件に置き換えてください。
ノート
0 件のコメント