# 気象データ機能 マスタードキュメント

> **最終更新**: 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-Key（MAIL_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 で変換
