# マスタードキュメント - Windmill通知ワークフロー編 > **最終更新**: 2026-02-21 > **対象システム**: windmill.keinafarm.net(ワークスペース: admins) > **目的**: このドキュメントだけでWindmill通知ワークフローの全容を把握できること --- ## 目次 1. [機能概要](#1-機能概要) 2. [フロー設計](#2-フロー設計) 3. [変更履歴取得API仕様](#3-変更履歴取得api仕様) 4. [LINE通知仕様](#4-line通知仕様) 5. [Windmill設定仕様](#5-windmill設定仕様) 6. [状態管理仕様](#6-状態管理仕様) 7. [設計判断と制約](#7-設計判断と制約) 8. [運用手順](#8-運用手順) 9. [ソースファイル索引](#9-ソースファイル索引) 10. [更新履歴](#更新履歴) --- ## 1. 機能概要 ### 目的 `shiraou.keinafarm.net`(白皇集落営農組合 統合システム)で発生した予約・実績の変更を、LINE Messaging API 経由で管理者に通知する。 ### ユーザーフロー ``` 統合システム上で予約・実績の変更が発生 └→ Windmill が5分毎にポーリング └→ 変更があればLINEにプッシュ通知 └→ 管理者がLINEで変更内容を確認 ``` ### 通知される操作一覧 | 区分 | 操作 | 説明 | |------|------|------| | 予約 | `create` | 予約が作成された | | 予約 | `update` | 予約の日時・機械が変更された | | 予約 | `cancel` | 予約がキャンセルされた | | 実績 | `create` | 実績が登録された | | 実績 | `update` | 実績が修正された | | 実績 | `delete` | 実績が削除された | --- ## 2. フロー設計 ### Windmillフロー情報 | 項目 | 値 | |------|-----| | パス | `f/shiraou/shiraou_notification` | | 概要 | 白皇集落営農 変更通知 | | ステップ数 | 1(単一Pythonスクリプト) | | スケジュール | `0 */5 * * * *`(5分毎、JST) | | スケジュールパス | `f/shiraou/shiraou_notification_every_5min` | ### 実行フロー(擬似コード) ```python # Step 1: シークレット・前回実行時刻を取得 api_key = get_variable("u/admin/NOTIFICATION_API_KEY") line_token = get_variable("u/admin/LINE_CHANNEL_ACCESS_TOKEN") line_to = get_variable("u/admin/LINE_TO") last_checked = get_variable("u/admin/SHIRAOU_LAST_CHECKED_AT") # 空なら初回 since = last_checked or (now() - 10分) # Step 2: 変更履歴を取得 response = GET "https://shiraou.keinafarm.net/reservations/api/changes/?since={since}" headers: { "X-API-Key": api_key } # Step 3: 変更があればLINE通知 if response.reservations or response.usages: message = format_message(response) POST "https://api.line.me/v2/bot/message/push" body: { "to": line_to, "messages": [{"type": "text", "text": message}] } # Step 4: 前回実行時刻を更新(正常完了時のみ) set_variable("u/admin/SHIRAOU_LAST_CHECKED_AT", response.checked_at) ``` ### エラー時の挙動 - API呼び出し失敗、LINE送信失敗のいずれでも例外が発生 - 例外が発生した場合、`SHIRAOU_LAST_CHECKED_AT` は更新されない - 次回実行時に同じ `since` で再試行される(変更の取り漏れ防止) --- ## 3. 変更履歴取得API仕様 ### エンドポイント ``` GET https://shiraou.keinafarm.net/reservations/api/changes/ ``` ### 認証 ``` X-API-Key: ``` APIキーが不正な場合は `401 Unauthorized` が返る。 ### クエリパラメータ | パラメータ | 型 | 必須 | 説明 | |-----------|-----|------|------| | `since` | ISO8601文字列 | 必須 | この日時以降の変更を取得する | **`since` の形式例**: - `2026-02-21T10:00:00+09:00`(タイムゾーン付き、推奨) - `2026-02-21T10:00:00`(ナイーブ、JSTとして扱われる) ### レスポンス(200 OK) ```json { "checked_at": "2026-02-21T12:00:00+09:00", "since": "2026-02-21T10:00:00+09:00", "reservations": [ { "operation": "create", "reservation_id": 123, "user_name": "田中太郎", "machine_name": "トラクター", "start_at": "2026-02-25T09:00:00+09:00", "end_at": "2026-02-25T12:00:00+09:00", "operated_at": "2026-02-21T11:30:00+09:00", "operator_name": "田中太郎", "reason": "" } ], "usages": [ { "operation": "update", "usage_id": 456, "user_name": "山田次郎", "machine_name": "コンバイン", "amount": 4.0, "unit": "時間", "start_at": "2026-02-20T08:00:00+09:00", "end_at": "2026-02-20T12:00:00+09:00", "operated_at": "2026-02-21T11:55:00+09:00", "operator_name": "管理者A", "reason": "記録ミスのため修正" } ] } ``` ### 変更なし時のレスポンス ```json { "checked_at": "2026-02-21T12:05:00+09:00", "since": "2026-02-21T12:00:00+09:00", "reservations": [], "usages": [] } ``` ### エラーレスポンス | ステータス | 原因 | |-----------|------| | `401 Unauthorized` | APIキーが不正または未設定 | | `400 Bad Request` | `since` パラメータが欠落または不正な日時形式 | --- ## 4. LINE通知仕様 ### 使用API LINE Messaging API - Push Message ``` POST https://api.line.me/v2/bot/message/push Authorization: Bearer Content-Type: application/json ``` ### リクエストボディ ```json { "to": "", "messages": [ { "type": "text", "text": "<フォーマット済みメッセージ>" } ] } ``` ### メッセージフォーマット ``` 📋 営農システム 変更通知 🟢 予約作成 機械: トラクター 利用者: 田中太郎 日時: 2026-02-25 09:00 〜 2026-02-25 12:00 🔴 予約キャンセル 機械: 田植機 利用者: 佐藤花子 日時: 2026-02-22 08:00 〜 2026-02-22 17:00 🔵 実績修正 機械: コンバイン 利用者: 山田次郎 利用量: 4.0時間 日: 2026-02-20 理由: 記録ミスのため修正 ``` ### アイコン規則 | アイコン | 意味 | |---------|------| | 🟢 | 作成(create / 予約作成 / 実績登録) | | 🔵 | 変更(update / 予約変更 / 実績修正) | | 🔴 | 削除・キャンセル(cancel / delete) | ### 通知先の種別 `LINE_TO` にはユーザーIDまたはグループIDを設定する。 | 種別 | ID形式 | |------|-------| | ユーザー | `U` で始まる文字列 | | グループ | `C` で始まる文字列 | --- ## 5. Windmill設定仕様 ### Windmill Variables(シークレット) 以下の変数を Windmill UI(Variables ページ)で作成・管理する。 | 変数パス | Secret | 説明 | 取得元 | |---------|--------|------|-------| | `u/admin/NOTIFICATION_API_KEY` | ✅ | shiraou.keinafarm.net のAPIキー | Djangoサーバー側 `NOTIFICATION_API_KEY` 環境変数と同一値 | | `u/admin/LINE_CHANNEL_ACCESS_TOKEN` | ✅ | LINE Messaging API チャネルアクセストークン | LINE Developers Console | | `u/admin/LINE_TO` | ✅ | 通知先のLINEユーザーIDまたはグループID | LINE webhook / profile API | | `u/admin/SHIRAOU_LAST_CHECKED_AT` | ❌ | 前回確認時刻(ワークフローが自動更新) | ワークフローが自動管理(初期値: 空文字) | ### Django側の設定(shiraou.keinafarm.net) `docker-compose.yml` に以下の環境変数を追加: ```yaml environment: - NOTIFICATION_API_KEY= ``` APIキー生成コマンド: ```bash openssl rand -hex 32 ``` --- ## 6. 状態管理仕様 ### 状態変数: `SHIRAOU_LAST_CHECKED_AT` | 項目 | 内容 | |------|------| | 格納場所 | Windmill Variable `u/admin/SHIRAOU_LAST_CHECKED_AT` | | 型 | ISO8601文字列(例: `2026-02-21T15:30:00+09:00`) | | 初期値 | 空文字(初回実行時は `現在時刻 - 10分` を使用) | | 更新タイミング | フロー正常完了時のみ、APIレスポンスの `checked_at` を保存 | | 参照タイミング | フロー実行開始時、`since` パラメータとして使用 | ### 重複通知防止の仕組み ``` 実行1: since=T0, checked_at=T1 → LAST_CHECKED_AT = T1 実行2: since=T1, checked_at=T2 → T1以降の変更のみ取得 ``` - `since` に `checked_at`(APIが確認した時刻)を使うことで、変更の取りこぼしが発生しない - `since`(リクエストに渡した時刻)ではなく `checked_at`(サーバーが確認した時刻)を保存するのがポイント ### 旧実装との違い(トラブルシュート記録) | | 旧実装 | 現実装 | |---|--------|--------| | 状態保存方法 | `wmill.get_state()` / `set_state()` | `wmill.get_variable()` / `set_variable()` | | 問題 | フローのインラインスクリプトでは実行をまたいで保存されない | - | | 症状 | 毎回 `since = 現在 - 10分` になり、毎回通知が飛ぶ | 正常動作 | --- ## 7. 設計判断と制約 ### 絶対に変えてはいけない制約 1. **`SHIRAOU_LAST_CHECKED_AT` には `checked_at` を保存すること**(`since` を保存しない) - `checked_at`: APIサーバーが「この時刻まで確認した」という保証付きの時刻 - 同じ変更が2度通知されることを防ぐ 2. **状態更新は正常完了後のみ行うこと** - API呼び出し失敗・LINE送信失敗時は `SHIRAOU_LAST_CHECKED_AT` を更新しない - 次回実行で同じ範囲を再取得し、通知の取り漏れを防ぐ 3. **`wmill.get_state()` は使用しないこと** - Windmillのインラインフロースクリプトでは実行をまたいで保存されない - 状態管理は必ず Windmill Variable を使うこと ### 設計判断 | 判断 | 理由 | |------|------| | 単一ステップフロー | 状態管理を1か所に集約するため。`get_state()`/`set_state()` のスコープ問題を回避 | | SSL検証スキップ | shiraou.keinafarm.net が自己署名証明書の可能性があるため | | タイムアウト 30秒 | 農業用途で多少の応答遅延を許容しつつ、無限待機を防ぐ | | 5分ポーリング間隔 | 農業機械の予約用途では数分の遅延は許容範囲。リアルタイム不要 | --- ## 8. 運用手順 ### フローを手動実行 ```bash curl -sk -X POST \ -H "Authorization: Bearer qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh" \ -H "Content-Type: application/json" \ -d '{}' \ "https://windmill.keinafarm.net/api/w/admins/jobs/run/f/f/shiraou/shiraou_notification" ``` ### 動作確認(curlで直接API呼び出し) ```bash # 変更なし確認 curl -H "X-API-Key: <キー>" \ "https://shiraou.keinafarm.net/reservations/api/changes/?since=2026-02-21T11:59:00%2B09:00" # 広い範囲で変更取得(初期確認用) curl -H "X-API-Key: <キー>" \ "https://shiraou.keinafarm.net/reservations/api/changes/?since=2026-01-01T00:00:00" ``` ### フローの更新デプロイ手順 ```bash cd /path/to/windmill_workflow # 1. flows/shiraou_notification.flow.json を編集 # 2. 既存フローを削除して再作成(PUTは405のため) curl -sk -X DELETE \ -H "Authorization: Bearer qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh" \ "https://windmill.keinafarm.net/api/w/admins/flows/delete/f/shiraou/shiraou_notification" curl -sk -X POST \ -H "Authorization: Bearer qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh" \ -H "Content-Type: application/json" \ -d @flows/shiraou_notification.flow.json \ "https://windmill.keinafarm.net/api/w/admins/flows/create" # 3. スケジュールは再作成不要(フローの削除・再作成でも維持される) ``` ### APIキーローテーション手順 1. Djangoサーバー側で新しいキーを生成: `openssl rand -hex 32` 2. `docker-compose.yml` の `NOTIFICATION_API_KEY` を更新してデプロイ 3. Windmill UI で `u/admin/NOTIFICATION_API_KEY` の値を同じ新しいキーに更新 4. フローを手動実行して動作確認 ### 過去のジョブ結果確認 ```bash curl -sk -H "Authorization: Bearer qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh" \ "https://windmill.keinafarm.net/api/w/admins/jobs/list?per_page=10&script_path_exact=f/shiraou/shiraou_notification&is_flow=true" ``` --- ## 9. ソースファイル索引 ### フロー定義 | ファイル | 説明 | |---------|------| | [flows/shiraou_notification.flow.json](../../flows/shiraou_notification.flow.json) | フロー本体。単一Pythonステップでポーリング・通知・状態更新を実行 | **フロー構造**: ```json { "path": "f/shiraou/shiraou_notification", "value": { "modules": [ { "id": "a", "value": { "type": "rawscript", "language": "python3", "content": "..." } } ] } } ``` ### ヘルパースクリプト | ファイル | 説明 | |---------|------| | [wm-api.sh](../../wm-api.sh) | Windmill REST API操作ヘルパー。フロー作成・スケジュール管理に使用 | **主要コマンド**: ``` create-flow JSONファイルからフローを作成 create-schedule JSONファイルからスケジュールを作成 flows フロー一覧取得 schedules スケジュール一覧取得 ``` ### ドキュメント | ファイル | 説明 | |---------|------| | [docs/shiraou/19_windmill_通知ワークフロー連携仕様.md](19_windmill_通知ワークフロー連携仕様.md) | 仕様書。API仕様・メッセージフォーマットの原典 | | [docs/shiraou/20_マスタードキュメント_Windmill通知ワークフロー編.md](20_マスタードキュメント_Windmill通知ワークフロー編.md) | 本ドキュメント | ### エージェントワークフロー | ファイル | 説明 | |---------|------| | [.agent/workflows/windmill-push.md](../../.agent/workflows/windmill-push.md) | Windmillへのpush手順。wmill CLIの制限とAPI代替の経緯を記録 | | [.agent/workflows/windmill-new-script.md](../../.agent/workflows/windmill-new-script.md) | 新規スクリプト作成手順 | --- ## 更新履歴 | 日付 | 変更内容 | |------|---------| | 2026-02-21 | 初版作成。フロー登録・スケジュール設定・状態管理バグ修正を含む |