Files
keinasystem/document/12_マスタードキュメント_気象データ編.md
Akira 6e99164e3f Triliumに記録しました。
ドキュメント整備が全て完了しました。今回のセッションで行った作業のまとめ:

実装した機能

Windmill フロー f/weather/weather_sync(毎朝6時 Asia/Tokyo)の作成・本番稼働
/weather フロントエンドページ(年別集計 / 期間指定の2モード)
Recharts 3.7.0 による月別・日次グラフ(気温折れ線、降水量棒+日照折れ線 2軸)
整備したドキュメント

ドキュメント	内容
CLAUDE.md	weather 画面追加・Windmill フロー・マスタードキュメントリンク
document/12_マスタードキュメント_気象データ編.md	気象機能の完全リファレンス(新規作成)
MEMORY.md	Windmill API パターン・Recharts TypeScript パターン
Trilium マスタードキュメント	フロントエンド2モード・Recharts・document/12_ 参照追加
2026-02-28 14:05:32 +09:00

242 lines
7.6 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.
# 気象データ機能 マスタードキュメント
> **最終更新**: 2026-02-28
> **状態**: 本番稼働中
---
## 概要
Open-Meteo archive API から窪川の気象データを日次取得し、PostgreSQL に蓄積する。
農業における積算温度計算・類似年分析・作期の気象振り返りを目的とする。
- **観測地点**: 窪川 (lat=33.213, lon=133.133)
- **データソース**: [Open-Meteo Archive API](https://archive-api.open-meteo.com/v1/archive)(無料)
- **蓄積期間**: 2016-01-01 〜 前日(毎朝自動更新)
- **Django アプリ**: `backend/apps/weather/`
---
## データモデル
### WeatherRecord (`apps/weather/models.py`)
| フィールド | 型 | 説明 |
|---|---|---|
| date | DateField (unique) | 日付 |
| temp_mean | FloatField nullable | 平均気温 (℃) |
| temp_max | FloatField nullable | 最高気温 (℃) |
| temp_min | FloatField nullable | 最低気温 (℃) |
| sunshine_h | FloatField nullable | 日照時間 (h) |
| precip_mm | FloatField nullable | 降水量 (mm) |
| wind_max | FloatField nullable | 最大風速 (m/s) |
| pressure_min | FloatField nullable | 最低気圧 (hPa) |
**Crop.base_temp** (`apps/plans/models.py` に追加):
- FloatField, default=0.0
- GDD有効積算温度計算の基準温度
---
## API エンドポイント (`/api/weather/`)
### POST `/api/weather/sync/`
- **認証**: X-API-KeyMAIL_API_KEY 設定値、Windmill と共用)
- **用途**: Windmill から日次データを受け取り upsert
- **リクエスト**: 単一オブジェクトまたはリスト
- **レスポンス**: `{"saved": N}` or `{"saved": N, "errors": [...]}`
```json
[
{
"date": "2026-02-27",
"temp_mean": 8.5, "temp_max": 14.2, "temp_min": 3.1,
"sunshine_h": 6.3, "precip_mm": 0.0,
"wind_max": 4.2, "pressure_min": 1008.0
}
]
```
---
### GET `/api/weather/records/`
- **認証**: JWT
- **クエリパラメータ**:
- `?year=2025` — 年指定
- `?start=2025-05-01&end=2025-09-30` — 日付範囲指定
- **レスポンス**: WeatherRecord の配列date 昇順)
---
### GET `/api/weather/summary/?year=2025`
- **認証**: JWT
- **レスポンス**:
```json
{
"year": 2025,
"monthly": [
{
"month": 1,
"temp_mean_avg": 5.2, "temp_max_avg": 10.1, "temp_min_avg": 0.8,
"precip_total": 45.0, "sunshine_total": 98.3, "wind_max": 9.5,
"hot_days": 0, "cold_days": 8, "rainy_days": 12
},
...
],
"annual": {
"temp_mean_avg": 16.1, "precip_total": 2310.0, "sunshine_total": 1850.5,
"hot_days": 12, "cold_days": 25
}
}
```
- **hot_days**: 最高気温 ≥ 35℃ の日数
- **cold_days**: 最低気温 < 0℃ の日数
- **rainy_days**: 降水量 ≥ 1.0mm の日数
---
### GET `/api/weather/gdd/`
- **認証**: JWT
- **用途**: 播種日〜現在の有効積算温度Growing Degree Daysを計算
- **クエリパラメータ**:
- `?start_date=2025-05-15` (必須) — 起算日
- `?base_temp=10` (省略時=0) — 基準温度 ℃
- `?end_date=2025-09-30` (省略時=昨日)
- **レスポンス**:
```json
{
"start_date": "2025-05-15",
"end_date": "2025-09-30",
"base_temp": 10.0,
"total_gdd": 1342.5,
"records": [
{"date": "2025-05-15", "temp_mean": 18.2, "daily_gdd": 8.2, "cumulative_gdd": 8.2},
...
]
}
```
- **日積算温度** = max(0, 平均気温 - 基準温度)
---
### GET `/api/weather/similarity/?year=2026`
- **認証**: JWT
- **用途**: 今年 1/1〜昨日 の気象パターンと過去年を比較し、類似年 Top3 を返す
- **アルゴリズム**: (平均気温, 総降水量, 総日照時間) の正規化ユークリッド距離
- **レスポンス**:
```json
{
"target_year": 2026,
"comparison_period": "1/1〜2/27",
"target_features": {"mean_temp": 7.3, "total_precip": 185.0, "total_sunshine": 240.5},
"similar_years": [
{
"year": 2020, "distance": 0.312,
"features": {...},
"monthly": [...]
}
]
}
```
---
## 管理コマンド
```bash
# 全期間取得(初回のみ)
docker compose exec backend python manage.py fetch_weather --full
# 差分取得(最終レコード翌日〜昨日)
docker compose exec backend python manage.py fetch_weather
# 任意期間
docker compose exec backend python manage.py fetch_weather --start-date 2025-01-01 --end-date 2025-12-31
```
**仕様**:
- 年単位で Open-Meteo API を呼び出しAPI 制限回避のため分割)
- upsert: 既存データを上書き更新
- `--full`: 2016-01-01 から昨日まで(初回投入用)
---
## Windmill フロー
| 項目 | 値 |
|---|---|
| パス | `f/weather/weather_sync` |
| スケジュール | `0 0 6 * * *`(毎朝 6:00 Asia/Tokyo |
| スクリプト | `windmill/u/admin/weather_sync.flow/a.inline_script.py` |
| 状態 | ✅ 本番稼働中windmill.keinafarm.net |
**使用 Windmill Variables**:
| 変数名 | 内容 |
|---|---|
| `u/admin/KEINASYSTEM_API_KEY` | API キー(メール機能と共用) |
| `u/admin/KEINASYSTEM_API_URL` | `https://keinafarm.net` |
---
## フロントエンド画面 (`/weather`)
### 年別集計モード(デフォルト)
- 年セレクタ (2016〜現在)
- **年間サマリーカード**: 平均気温 / 年間降水量 / 年間日照時間 / 猛暑日数・冬日数
- **グラフタブ**: 月別気温折れ線(最高・平均・最低)、月別降水量棒 + 日照時間折れ線2軸
- **月別サマリータブ**: 12ヶ月のテーブル
- **直近14日タブ**: 日次データテーブルWindmill 同期確認用)
### 期間指定モード
- 開始日・終了日の date input + 「表示」ボタン
- **期間集計カード**: 期間の平均気温 / 総降水量 / 総日照時間 / 猛暑日・冬日
- **グラフタブ**: 日次気温折れ線 + 日次降水量棒+日照折れ線
- X軸ラベル自動間引き30日以内→3日おき、3ヶ月→週1、半年→2週、1年超→月1
- 60日以内はドット表示あり
- **一覧タブ**: 日次データテーブル(スクロール対応)
**使用ライブラリ**: Recharts 3.7.x`frontend/package.json` に登録済み)
---
## ファイル索引
| ファイル | 役割 |
|---|---|
| `backend/apps/weather/models.py` | WeatherRecord モデル |
| `backend/apps/weather/views.py` | 5つのAPIビュー |
| `backend/apps/weather/urls.py` | URL設定 |
| `backend/apps/weather/serializers.py` | Serializer |
| `backend/apps/weather/admin.py` | 管理画面登録 |
| `backend/apps/weather/migrations/0001_initial.py` | 初回マイグレーション |
| `backend/apps/weather/management/commands/fetch_weather.py` | 管理コマンド |
| `backend/apps/plans/migrations/0004_crop_base_temp.py` | Crop.base_temp 追加 |
| `frontend/src/app/weather/page.tsx` | 気象画面400行 |
| `windmill/u/admin/weather_sync.flow/a.inline_script.py` | Windmill Python スクリプト |
| `windmill/u/admin/weather_sync.flow/flow.yaml` | Windmill フロー定義 |
---
## 将来計画Phase 2 以降)
1. **GDD 到達日予測**: `Crop.base_temp` を使い、播種日から目標GDDに達する日を予測
2. **類似年ベースの収穫予測**: 類似年の収穫時期を参考に今年の予測を表示
3. **作付け計画との連携**: 作期ごとの気象サマリーを圃場詳細に表示
4. **気象アラート**: 猛暑・長雨・強風などの異常気象を検知して通知
---
## 注意事項
- Open-Meteo archive API は**前日まで**のデータしか取得できない(リアルタイム不可)
- `pressure_min``surface_pressure_min`(地表気圧)。海面更正気圧とは異なる
- Open-Meteo の `sunshine_duration` は秒単位 → `sunshine_h` = 秒 ÷ 3600 で変換