14 KiB
14 KiB
マスタードキュメント - Windmill通知ワークフロー編
最終更新: 2026-02-21 対象システム: windmill.keinafarm.net(ワークスペース: admins) 目的: このドキュメントだけでWindmill通知ワークフローの全容を把握できること
目次
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 |
実行フロー(擬似コード)
# 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: <NOTIFICATION_API_KEY>
APIキーが不正な場合は 401 Unauthorized が返る。
クエリパラメータ
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
since |
ISO8601文字列 | 必須 | この日時以降の変更を取得する |
since の形式例:
2026-02-21T10:00:00+09:00(タイムゾーン付き、推奨)2026-02-21T10:00:00(ナイーブ、JSTとして扱われる)
レスポンス(200 OK)
{
"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": "記録ミスのため修正"
}
]
}
変更なし時のレスポンス
{
"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 <LINE_CHANNEL_ACCESS_TOKEN>
Content-Type: application/json
リクエストボディ
{
"to": "<LINE_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 に以下の環境変数を追加:
environment:
- NOTIFICATION_API_KEY=<NOTIFICATION_API_KEYと同一の値>
APIキー生成コマンド:
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. 設計判断と制約
絶対に変えてはいけない制約
-
SHIRAOU_LAST_CHECKED_ATにはchecked_atを保存すること(sinceを保存しない)checked_at: APIサーバーが「この時刻まで確認した」という保証付きの時刻- 同じ変更が2度通知されることを防ぐ
-
状態更新は正常完了後のみ行うこと
- API呼び出し失敗・LINE送信失敗時は
SHIRAOU_LAST_CHECKED_ATを更新しない - 次回実行で同じ範囲を再取得し、通知の取り漏れを防ぐ
- API呼び出し失敗・LINE送信失敗時は
-
wmill.get_state()は使用しないこと- Windmillのインラインフロースクリプトでは実行をまたいで保存されない
- 状態管理は必ず Windmill Variable を使うこと
設計判断
| 判断 | 理由 |
|---|---|
| 単一ステップフロー | 状態管理を1か所に集約するため。get_state()/set_state() のスコープ問題を回避 |
| SSL検証スキップ | shiraou.keinafarm.net が自己署名証明書の可能性があるため |
| タイムアウト 30秒 | 農業用途で多少の応答遅延を許容しつつ、無限待機を防ぐ |
| 5分ポーリング間隔 | 農業機械の予約用途では数分の遅延は許容範囲。リアルタイム不要 |
8. 運用手順
フローを手動実行
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呼び出し)
# 変更なし確認
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"
フローの更新デプロイ手順
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キーローテーション手順
- Djangoサーバー側で新しいキーを生成:
openssl rand -hex 32 docker-compose.ymlのNOTIFICATION_API_KEYを更新してデプロイ- Windmill UI で
u/admin/NOTIFICATION_API_KEYの値を同じ新しいキーに更新 - フローを手動実行して動作確認
過去のジョブ結果確認
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 | フロー本体。単一Pythonステップでポーリング・通知・状態更新を実行 |
フロー構造:
{
"path": "f/shiraou/shiraou_notification",
"value": {
"modules": [
{
"id": "a",
"value": {
"type": "rawscript",
"language": "python3",
"content": "..."
}
}
]
}
}
ヘルパースクリプト
| ファイル | 説明 |
|---|---|
| wm-api.sh | Windmill REST API操作ヘルパー。フロー作成・スケジュール管理に使用 |
主要コマンド:
create-flow <file> JSONファイルからフローを作成
create-schedule <file> JSONファイルからスケジュールを作成
flows フロー一覧取得
schedules スケジュール一覧取得
ドキュメント
| ファイル | 説明 |
|---|---|
| docs/shiraou/19_windmill_通知ワークフロー連携仕様.md | 仕様書。API仕様・メッセージフォーマットの原典 |
| docs/shiraou/20_マスタードキュメント_Windmill通知ワークフロー編.md | 本ドキュメント |
エージェントワークフロー
| ファイル | 説明 |
|---|---|
| .agent/workflows/windmill-push.md | Windmillへのpush手順。wmill CLIの制限とAPI代替の経緯を記録 |
| .agent/workflows/windmill-new-script.md | 新規スクリプト作成手順 |
更新履歴
| 日付 | 変更内容 |
|---|---|
| 2026-02-21 | 初版作成。フロー登録・スケジュール設定・状態管理バグ修正を含む |