栽培管理アプリを作ってます⑪:毎日自動で天気予報を取得する仕組みに変えた話

目次

はじめに

前回は作業登録時に気象庁APIから天気を自動取得する機能を実装しました。

しかし、実際に使ってみたんですが、まあ使いにくい。

薄々気づいていたものの、やっぱりダメだということで方針を転換して、「毎日自動で明日の天気を取得する」仕組みに作り直しました。

今回実装した内容:

  • Supabase Edge Functionで天気予報を自動取得
  • pg_cronで毎朝6時に自動実行
  • 作業登録時はデータベースから参照するだけ
  • 最低気温もしっかり記録できるように改善

前回の実装の問題点

前回実装した「作業登録時に天気を取得する機能」には、実用上の大きな問題がありました。

問題1:作業は「やった後」に登録することが多い

農作業の記録は、基本的に作業が終わった後に登録します。

具体例:

  • 1月10日 朝8時:防除作業を実施
  • 1月10日 夕方17時:アプリで作業を登録

この時、気象庁APIから「1月10日の天気予報」を取得すると、最高気温は取れますが最低気温が空欄になります。

理由:
最低気温は通常、深夜から明け方(午前2時〜6時頃)に記録されます。夕方17時の時点では、すでに最低気温の時間帯が過ぎているため、予報データに含まれていません。

結果として、晴れ 12°C / ?°C のように最低気温が空欄で表示されてしまいます。
まあ、これは前回の記事からわかっていたことなんですが、毎日となると嫌になっていく。

問題2:過去日付は取得できない

これが、「使えねえな」と思った一番の理由なんですが、
翌日に前日の作業を登録しようとすると、天気が取得できません。

具体例:

  • 1月10日:防除作業を実施(記録忘れ)
  • 1月11日:前日(1月10日)の作業を登録

気象庁APIは「今日・明日・明後日」の3日間の予報しか提供していないため、過去の日付では天気が取得できません。

結果として、手動で天気を入力する必要があり、わざわざ実装した意味がないという状況になってしまいました。

新しい仕様:毎日自動で明日の天気を取得

問題点を解決するため、天気取得の仕組みを根本から見直しました。

コンセプト

  • 毎朝6時に、明日の天気予報を自動取得
  • データベースに保存しておく
  • 作業登録時は、保存済みの天気を参照するだけ

メリット

1. 最低気温も取得できる
前日の夜に「明日の天気」を取得すれば、翌日の最低気温(明け方の気温)も予報データに含まれています。

2. 作業登録時にAPI呼び出し不要
すでにデータベースに保存されている天気を参照するだけなので、処理が高速です。

3. 過去の作業を登録しても天気が自動で入る
前日に取得済みの天気データがあるため、翌日に「昨日の作業」を登録しても天気が自動で表示されます。

実装内容

4-1. Supabase Edge Functionで天気取得

Supabase Edge Functionは、Denoで動く軽量なサーバーレス関数です。

Edge Functionとは:
サーバーを立てなくても、コードを実行できる仕組みです。AWSのLambdaと似た機能で、必要な時だけ実行されるため、コストが抑えられます。

実装手順:

  1. VS Codeなどでファイルを作成
    プロジェクトのsupabase/functions/fetch-weather-forecast/フォルダにindex.tsファイルを作成します。
  2. TypeScriptでコードを書く
    気象庁APIから天気を取得して、データベースに保存する処理を実装します。
  3. ターミナルでSupabaseにデプロイ
  4. 以下のコマンドを実行します:
    # Supabase CLIをインストールしている場合
    supabase functions deploy fetch-weather-forecast #
    インストールしていない場合
    npx supabase functions deploy fetch-weather-forecast

    これで、Supabaseのクラウド環境にEdge Functionがアップロードされます。

実装内容:

  • 気象庁APIから福岡県(地域コード:400000)の予報を取得
  • cult_weather_recordsテーブルに保存
  • 明日の天気のみを取得(当日はスキップ)

なぜ当日をスキップするのか:
当日の予報を取得すると、最低気温がnull(空欄)で上書きされてしまうためです。前日に取得した「明日の予報」(最低気温あり)を保護するため、当日の再取得は行いません。

4-2. Cronで毎日自動実行

PostgreSQLの拡張機能pg_cronを使って、Edge Functionを毎日自動実行します。

Cronとは:
決まった時間に自動でプログラムを実行する仕組みです。例えば、「毎日朝6時」「毎週月曜日」といった設定ができます。

設定手順:

  1. Supabase DashboardのSQL Editorを開く
    Supabaseの管理画面から「SQL Editor」を選択します。
  2. Cron設定のSQLを実行
    以下のSQLを実行して、毎日朝6時に自動実行する設定をします: SELECT cron.schedule( 'fetch-weather-forecast', '0 21 * * *', $$ SELECT net.http_post( url := 'https://your-project.supabase.co/functions/v1/fetch-weather-forecast', headers := jsonb_build_object( 'Authorization', 'Bearer your-anon-key' ) ); $$ );
  3. 設定を確認
    以下のSQLを実行して、Cronジョブが登録されているか確認します: SELECT * FROM cron.job WHERE jobname = 'fetch-weather-forecast';

実行スケジュール:

  • 毎日 UTC 21:00 = JST 6:00(翌朝)
  • Edge Functionを呼び出して天気予報を取得

なぜUTC 21時なのか:
Supabaseのサーバーは世界標準時(UTC)で動いています。日本時間(JST)はUTC+9時間なので、日本時間の朝6時は、UTC時間では前日の21時になります。

Cronジョブの確認方法:

  • SQL Editorで確認:SELECT * FROM cron.job;で全てのジョブを表示
  • ログで確認:Supabase Dashboard → Functions → fetch-weather-forecast → Logsタブで実行履歴を確認
  • データベースで確認:cult_weather_recordsテーブルに新しいデータが追加されているか確認

4-3. 作業登録時のデータフロー変更

天気の取得方法を大きく変更しました。

変更前:

  1. 作業登録ボタンを押す
  2. 気象庁APIを呼び出す
  3. 取得した天気をデータベースに保存

変更後:

  1. 作業登録ボタンを押す
  2. データベースから天気を参照
  3. cult_logs(作業記録)に天気をコピー

API呼び出しがなくなったため、処理が高速になりました。

4-4. 作業ログに天気をコピー

cult_logsテーブル(作業記録)に、以下のカラムを追加しました。

  • weather(天気)
  • temperature_max(最高気温)
  • temperature_min(最低気温)

なぜコピーするのか:
作業登録時にcult_weather_recordsから天気をコピーすることで、その作業を行った時点の天気が記録として残ります。後から天気を修正しても、他の作業には影響しません。

実装時の工夫

工夫1:当日の予報は取得しない

Edge Functionは「明日の天気」のみを取得し、当日の天気は更新しません。

理由:
当日の予報を再取得すると、最低気温がnullで上書きされてしまうためです。前日に取得した「明日の予報」には最低気温が含まれているので、それを保護します。

動作例:

  • 1月9日 朝6時:1月10日の予報を取得(最高12°C、最低5°C)
  • 1月10日 朝6時:1月11日の予報を取得(1月10日は更新しない)
  • 1月10日 夕方:1月10日の作業を登録(最高12°C、最低5°C が表示される)

工夫2:古いデータの自動削除

データベースの容量を節約するため、古い天気データは自動で削除します。

削除条件:

  • 8日以上前のデータ
  • かつ、作業が紐づいていないデータ

削除しないデータ:
作業登録済みの日付は、過去の記録として保護するため削除しません。

工夫3:タイムゾーン対応

Edge FunctionはUTC(協定世界時)で動作しますが、気象庁APIはJST(日本標準時)でデータを返します。

対応方法:
日本時間(JST = UTC+9時間)に変換してから日付を計算することで、正しい「明日」の日付を取得できるようにしました。

// 日本時間で今日の日付を取得
const now = new Date();
const jstOffset = 9 * 60; // JSTはUTC+9時間
const jstTime = new Date(now.getTime() + jstOffset * 60 * 1000);
const today = new Date(jstTime.toISOString().split('T')[0]);

データベース設計の変更

天気データの保存方法を変更しました。

変更前

  • cult_weather_records:ユーザーごとに天気を保存
  • owner_idが必須(ユーザーIDを保存)

変更後

  • cult_weather_records全ユーザー共通の予報データ(参照用)
  • owner_id = null(ユーザーIDは保存しない)
  • cult_logs:各作業に天気をコピー保存

なぜこの設計にしたか

理由1:天気予報は全ユーザー共通
福岡県の天気予報は、どのユーザーも同じデータです。ユーザーごとに保存する必要がありません。

理由2:作業時の天気は各ユーザーの記録
同じ日でも、ユーザーAさんとユーザーBさんが別々の時間帯に作業することがあります。その場合、それぞれが記録した天気(例:午前は晴れ、午後は雨)を個別に保存できます。

理由3:手動修正しても他のユーザーに影響しない
天気を手動で修正した場合、自分の作業記録だけが変わり、他のユーザーには影響しません。

実際の動作

ケース1:当日の作業を登録

1月10日 朝6時:Edge Functionが1月11日の予報を取得
1月10日 夕方:ユーザーが1月10日の作業を登録
→ 1月10日の天気(前日に取得済み)がcult_logsにコピーされる

前日に取得した天気データがあるため、最低気温も含めて正しく表示されます。

ケース2:翌日に前日の作業を登録

1月10日 朝6時:Edge Functionが1月11日の予報を取得
1月11日 朝:ユーザーが1月10日の作業を登録
→ 1月10日の天気(前日に取得済み)がcult_logsにコピーされる

過去の作業を登録しても、前日に取得した天気データがあるため、自動で天気が表示されます。手動入力の必要はありません。

まとめ

今回、天気取得の仕組みを「作業登録時にリアルタイム取得」から「毎日自動で明日の天気を取得」に変更しました。

変更前

  • 作業登録時にリアルタイムで天気取得
  • 当日の最低気温が取れない
  • 過去日付は手動入力が必要

変更後

  • 毎朝自動で明日の天気を取得
  • 最低気温も取得できる
  • 作業登録時はDB参照だけ

結果

  • より実用的な仕様になりました
  • ユーザーの手間が減りました
  • システムもシンプルになりました

今後の改善点

現在は福岡県の天気のみ対応していますが、今後は他の地域でも対応できるよう検討しています。

目次