はじめに
前回は作業登録時に気象庁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と似た機能で、必要な時だけ実行されるため、コストが抑えられます。
実装手順:
- VS Codeなどでファイルを作成
プロジェクトのsupabase/functions/fetch-weather-forecast/フォルダにindex.tsファイルを作成します。 - TypeScriptでコードを書く
気象庁APIから天気を取得して、データベースに保存する処理を実装します。 - ターミナルでSupabaseにデプロイ
- 以下のコマンドを実行します:
# 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時」「毎週月曜日」といった設定ができます。
設定手順:
- Supabase DashboardのSQL Editorを開く
Supabaseの管理画面から「SQL Editor」を選択します。 - 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' ) ); $$ ); - 設定を確認
以下の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. 作業登録時のデータフロー変更
天気の取得方法を大きく変更しました。
変更前:
- 作業登録ボタンを押す
- 気象庁APIを呼び出す
- 取得した天気をデータベースに保存
変更後:
- 作業登録ボタンを押す
- データベースから天気を参照
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参照だけ
結果
- より実用的な仕様になりました
- ユーザーの手間が減りました
- システムもシンプルになりました
今後の改善点
現在は福岡県の天気のみ対応していますが、今後は他の地域でも対応できるよう検討しています。
