Files
windmill_workflow/docs/shiraou/20_マスタードキュメント_Windmill通知ワークフロー編.md
2026-02-21 16:14:07 +09:00

459 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# マスタードキュメント - 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 | 初版作成。フロー登録・スケジュール設定・状態管理バグ修正を含む |