docs: Windmill通知ワークフロー マスタードキュメント追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Akira
2026-02-21 16:14:07 +09:00
parent 5b806b4c32
commit 76db14cf10

View File

@@ -0,0 +1,458 @@
# マスタードキュメント - 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: <NOTIFICATION_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 <LINE_CHANNEL_ACCESS_TOKEN>
Content-Type: application/json
```
### リクエストボディ
```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 UIVariables ページ)で作成・管理する。
| 変数パス | 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=<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 <file> JSONファイルからフローを作成
create-schedule <file> 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 | 初版作成。フロー登録・スケジュール設定・状態管理バグ修正を含む |