この記事は「オフィス上空の天気予報を通知してみた。」シリーズの第2回です。
「オフィス上空の天気予報を通知してみた。」シリーズ(全4回)
- 【まとめ】 オフィス上空の天気予報を通知してみた。
- 【第1回】 オフィスを出る前に外が雨かを知れるシステムを作ってみた。
- 【第2回】 降雨予測の的中確率を通知してみた。(←現在、閲覧中。)
- 【第3回】 OpenWeatherMapで天気予報を取得。
- 【第4回】 slackで1日の天気予報を通知してみた。
はじめに
前回、「オフィスを出る前に外が雨かを知れるシステムを作ってみた。」で降雨情報を通知してくれるシステムをつくりました。
このシステムは現在の天気と1時間後までの降雨予測情報を通知してくれます。
しかし、予測を通知してもそれが当たらなければこのシステムを信用しなくなり、使わなくなってしまいます。
そこで、今回はそのシステムを改良して、降雨情報のログを取り、降雨予測を通知する際に的中確率も通知してくれるようにしていきたいと思います。
構成
YOLP(Yahoo! Open Local Platform)の気象情報APIからTOWNのオフィス上空の降雨情報を取得
S3からログファイルを読み込む
通知メッセージの作成とログファイルのアップデート
S3にログファイルを保存
slackに現在の天気と降雨予報を送信
仕様
- 天候の変化予測を記録しておき、それをもとに的中確率を求め、記録をしておく。
- 天候の変化予報を通知するときに的中確率も通知する。
例
- 「現在、晴れ。30分後、雨」と予測
- 予測を記録
- 30分後の雨量の実測値と記録した予測を比較
- 比較した結果と的中確率を記録
- 再び、「晴れ→30分後に雨」と予測が出たら「〇〇%の確率で雨が降り出すでしょう。」と通知する。
準備するもの
- AWSのアクセスキー、シークレットアクセスキー
- Amazon S3のバケット(バケットの作り方は以下の記事を参考にしてください)
- 天候の変化予測を記録するファイル:”weather-log.json”
- 的中確率を記録するファイル:”analyse.json”
ログファイル
今回はログファイルを以下のようにjson形式で記述しました。
weather-log.json
{ "10": [ #10分前の予測 { "Now": 0.55 #その時の降雨量 }, { #予測降雨情報 "Type": "forecast", "Date": "201902191320", #時刻 "Rainfall": 1.45 #降雨量 } ], "20": [ { "Now": 0.0 }, { "Type": "forecast", "Date": "201902191310", "Rainfall": 1.15 } ], "30": [ { "Now": 0.0 }, { "Type": "forecast", "Date": "201902191340", "Rainfall": 0.75 } ], "40": [ { "Now": 0.55 }, { "Type": "forecast", "Date": "201902191250", "Rainfall": 0.0 } ], "50": [ { "Now": null }, { "Type": null, "Date": null, "Rainfall": null } ], "60": [ { "Now": null }, { "Type": null, "Date": null, "Rainfall": null } ] }
analyse.json
{ "Minutes": { #時間 "10": { #10分後 "Now": { #予測したときの天候 "Sun": { "Forecast": { #予報した天気 "Light_Rain": { "Right": 2, #予報が当たった回数 "Wrong": 0, #予報が外れた回数 "Rate": 1.0 #的中確率("Rate"×100[%])(→10分後、晴れから小雨になるという予報が的中する確率は100%) }, "Rain": { "Right": 0, "Wrong": 1, "Rate": 0.0 } } }, "Light_Rain": { "Forecast": { "Rain": { "Right": 0, "Wrong": 1, "Rate": 0.0 }, "Sun": { "Right": 5, "Wrong": 3, "Rate": 0.625 } } }, "Rain": { "Forecast": { "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } } } }, "20": { "Now": { "Sun": { "Forecast": { "Light_Rain": { "Right": 1, "Wrong": 1, "Rate": 0.5 }, "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Light_Rain": { "Forecast": { "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Sun": { "Right": 1, "Wrong": 0, "Rate": 1.0 } } }, "Rain": { "Forecast": { "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } } } }, "30": { "Now": { "Sun": { "Forecast": { "Light_Rain": { "Right": 0, "Wrong": 1, "Rate": 0.0 }, "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Light_Rain": { "Forecast": { "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Sun": { "Right": 0, "Wrong": 1, "Rate": 0.0 } } }, "Rain": { "Forecast": { "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } } } }, "40": { "Now": { "Sun": { "Forecast": { "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Light_Rain": { "Forecast": { "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Sun": { "Right": 1, "Wrong": 0, "Rate": 1.0 } } }, "Rain": { "Forecast": { "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } } } }, "50": { "Now": { "Sun": { "Forecast": { "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Light_Rain": { "Forecast": { "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Rain": { "Forecast": { "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } } } }, "60": { "Now": { "Sun": { "Forecast": { "Light_Rain": { "Right": 1, "Wrong": 0, "Rate": 1.0 }, "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Light_Rain": { "Forecast": { "Rain": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 } } }, "Rain": { "Forecast": { "Sun": { "Right": 0, "Wrong": 0, "Rate": 0 }, "Light_Rain": { "Right": 0, "Wrong": 0, "Rate": 0 } } } } } } }
コード
- test_rain
import json import requests from slackclient import SlackClient import boto3 ### YOLP ### APP_ID = "xxxxxxxxxxxxxxx" BASE_URL = "http://weather.olp.yahooapis.jp/v1/place" COORDINATES = "xxxxxxxxxxxxxxx" OUTPUT="json" YOLP_URL = BASE_URL + "?appid=%s&coordinates=%s&output=%s" % (APP_ID,COORDINATES,OUTPUT) Weather_PARAMS = { "appid" : APP_ID, "coordinates" : COORDINATES, "output" : OUTPUT, "past" : 1 } ### SLACK ### slack_token = "xxxxxxxxxxxxxxx" slack_client = SlackClient(slack_token) ### channel_id for 降雨情報 ### channel_id="xxxxxxxxxxxxxxx" ts_num="xxxxxxxxxxxxxxx" message = "" ### AWS ### accesskey = "xxxxxxxxxxxxxxx" secretkey = "xxxxxxxxxxxxxxx" region = "ap-northeast-1" s3_client = boto3.client('s3',region_name=region) file_log = "weather_log.json" file_analyse = "analyse.json" bucket_name = "weather-notification-log" def lambda_handler(event, context): sun = 5 rain = 5 light_rain = 5 change_data = None ### 降雨情報取得 ### weather = requests.get(YOLP_URL,params=Weather_PARAMS).json()['Feature'][0]['Property']['WeatherList']['Weather'] ### 降水量ログ読み込み ### log_data = json.loads(s3_client.get_object(Bucket=bucket_name,Key=file_log)["Body"].read()) ### 降水量予測誤差記録読み込み ### log_analyse = json.loads(s3_client.get_object(Bucket=bucket_name,Key=file_analyse)["Body"].read()) ### 通知メッセージ作成 ### for var in range(5,13): date = weather[var]['Date'] rainfall = weather[var]['Rainfall'] if(var == 6): before_words = "現在、" print(sun) print(var) if(rainfall == 0.0): if(var == rain): message = before_words + "雨が止みました。" + ":sunny:" sun += 2 elif(var == light_rain): message = before_words + "小雨が止みました。" + ":sunny:" sun += 2 else: message = before_words + "雨は降っていません。" + ":sunny:" sun += 1 elif(rainfall >= 1.0): if(var == sun): message = before_words + "雨が降り始めました。" + " :umbrella_with_rain_drops:" rain += 2 elif(var == light_rain): message = before_words + "小雨から雨になりました。" + ":umbrella_with_rain_drops:" rain += 2 else: message = before_words + "雨が降っています。" + ":umbrella_with_rain_drops:" rain += 1 else: if(var == sun): message = before_words + "小雨が降り始めました。" + ":closed_umbrella:" light_rain += 2 elif(var == rain): message = before_words + "雨から小雨になりました。" + ":closed_umbrella:" light_rain += 2 else: message = before_words + "小雨が降っています。" + ":closed_umbrella:" light_rain += 1 ### 降水量予測誤差更新 ### body_analyse = json.dumps(update_analyse(rainfall,log_analyse,log_data,date),indent=4) elif(var > 6): before_words = "分後、" time = (var-6) * 10 if(rainfall == 0.0): if(var == rain): rate = log_analyse["Minutes"][str(time)]["Now"]["Rain"]["Forecast"]["Sun"]["Rate"] * 100 message += "\n" + str(time) + before_words + str(int(rate)) + "%の確率で雨が止むでしょう。" change_data = weather[var] elif(var == light_rain): rate = log_analyse["Minutes"][str(time)]["Now"]["Light_Rain"]["Forecast"]["Sun"]["Rate"] * 100 message += "\n" + str(time) + before_words + str(int(rate)) + "%の確率で小雨が止むでしょう。" change_data = weather[var] else: sun += 1 elif(rainfall >= 1.0): if(var == sun): rate = log_analyse["Minutes"][str(time)]["Now"]["Sun"]["Forecast"]["Rain"]["Rate"] * 100 message += "\n" + str(time) + before_words + str(int(rate)) + "%の確率で雨が降り出すでしょう。" change_data = weather[var] print(json.dumps(weather[var],indent=4)) print(change_data) elif(var == light_rain): rate = log_analyse["Minutes"][str(time)]["Now"]["Light_Rain"]["Forecast"]["Rain"]["Rate"] * 100 message += "\n" + str(time) + before_words + str(int(rate)) + "%の確率で小雨から雨に変わるでしょう。" change_data = weather[var] else: rain += 1 else: if(var == sun): rate = log_analyse["Minutes"][str(time)]["Now"]["Sun"]["Forecast"]["Light_Rain"]["Rate"] * 100 message += "\n" + str(time) + before_words + str(int(rate)) + "%の確率で小雨が降り出すでしょう。" change_data = weather[var] elif(var ==rain): rate = log_analyse["Minutes"][str(time)]["Now"]["Rain"]["Forecast"]["Light_Rain"]["Rate"] * 100 message += "\n" + str(time) + before_words + str(int(rate)) + "%の確率で雨から小雨に変わるでしょう。" change_data = weather[var] #else: light_rain += 1 elif(var == 5): if(rainfall == 0.0): sun += 1 elif(rainfall >= 1.0): rain += 1 else: light_rain += 1 ### 降水量ログ更新 ### body_log = json.dumps(update_log(log_data,weather,change_data),indent=4) ### 降雨情報以外のメッセージを削除 ### for history in get_history(slack_client,channel_id)["messages"]: ts=history["ts"] if ts != ts_num: response=delete_message(slack_client,channel_id,str(ts)) ### Slackにメッセージを送信 ### slack_response = update_message(slack_client, channel_id, message, ts_num) ### S3にファイルをアップロード ### s3_response_analyse = send_s3(file_analyse,body_analyse) s3_response_log = send_s3(file_log,body_log) return slack_response def channel_list(client): response = client.api_call("channels.list") if channels['ok']: return channels['channels'] else: return response def send_message(client, channel_id, message): response = client.api_call( "chat.postMessage", channel=channel_id, username="Weather", text=message, icon_emoji=":rainbow:" ) return response def update_message(client, channel_id, message,ts): response = client.api_call( "chat.update", channel=channel_id, text=message, ts=ts ) return response def delete_message(client,channel_id,ts): response = client.api_call( "chat.delete", channel=channel_id, ts=ts ) return response def get_history(client,channel_id): response = client.api_call( "channels.history", channel=channel_id, ) return response def send_s3(filename,body): response = s3_client.put_object( Bucket=bucket_name, Key=filename, Body=body ) return response def update_log(log_data,weather,change_data): print(json.dumps(log_data,indent=4)) for x in range(6,1,-1): log_data[str((x)*10)][0]["Now"] = log_data[str((x-1)*10)][0]["Now"] log_data[str((x)*10)][1]["Type"] = log_data[str((x-1)*10)][1]["Type"] log_data[str((x)*10)][1]["Date"] = log_data[str((x-1)*10)][1]["Date"] log_data[str((x)*10)][1]["Rainfall"] = log_data[str((x-1)*10)][1]["Rainfall"] if(change_data): log_data["10"][0]["Now"] = weather[6]["Rainfall"] log_data["10"][1] = change_data else: log_data["10"][0]["Now"] = None log_data["10"][1]["Type"] = None log_data["10"][1]["Date"] = None log_data["10"][1]["Rainfall"] = None print(json.dumps(log_data,indent=4)) print("log update complete") return log_data def update_analyse(rainfall,log_analyse,log_data,date): for x in range(1,7): value = log_data[str((x)*10)][1]["Rainfall"] log_date = log_data[str((x)*10)][1]["Date"] now = log_data[str((x)*10)][0]["Now"] if(now != None and date == log_date): print("ok") if(now == 0.0): if(0.0 < value < 1.0): right = log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Light_Rain"]["Right"] wrong = log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Light_Rain"]["Wrong"] if(0.0 < rainfall < 1.0): right += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Light_Rain"]["Right"] = right else: wrong += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Light_Rain"]["Wrong"] = wrong log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Light_Rain"]["Rate"] = right / (right + wrong) elif(value >= 1.0): right = log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Rain"]["Right"] wrong = log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Rain"]["Wrong"] if(rainfall >= 1.0): right += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Rain"]["Right"] = right else: wrong += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Rain"]["Wrong"] = wrong log_analyse["Minutes"][str((x)*10)]["Now"]["Sun"]["Forecast"]["Rain"]["Rate"] = right / (right + wrong) elif(0.0 < now < 1.0): if(value >= 1.0): right = log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Rain"]["Right"] wrong = log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Rain"]["Wrong"] if(rainfall >= 1.0): right += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Rain"]["Right"] = right else: wrong += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Rain"]["Wrong"] = wrong log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Rain"]["Rate"] = right / (right + wrong) elif(value == 0.0): right = log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Sun"]["Right"] wrong = log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Sun"]["Wrong"] if(rainfall == 0.0): right += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Sun"]["Right"] = right else: wrong += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Sun"]["Wrong"] = wrong log_analyse["Minutes"][str((x)*10)]["Now"]["Light_Rain"]["Forecast"]["Sun"]["Rate"] = right / (right + wrong) elif(now >= 1.0): if(value == 0.0): right = log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Sun"]["Right"] wrong = log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Sun"]["Wrong"] if(rainfall == 0.0): right += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Sun"]["Right"] = right else: wrong += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Sun"]["Wrong"] = wrong log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Sun"]["Rate"] = right / (right + wrong) elif(0.0 < value < 1.0): right = log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Light_Rain"]["Right"] wrong = log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Light_Rain"]["Wrong"] if(0.0 < rainfall < 1.0): right += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Light_Rain"]["Right"] = right else: wrong += 1 log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Light_Rain"]["Wrong"] = wrong log_analyse["Minutes"][str((x)*10)]["Now"]["Rain"]["Forecast"]["Light_Rain"]["Rate"] = right / (right + wrong) return log_analyse
動作確認
「小雨から晴れになる」と予報がでたので、降雨予報の的中確率を記録したファイル”analyse.json”を確認してみました。
Slack
analyse.json
的中確率の記録とメッセージへの反映が正しくできていることが確認できました。
さいごに
まだ、ログを取り始めたばかりなのでデータが少なく、的中確率の信頼度が低いですが日数を重ねれば的中確率の信頼度も高くなると思います。
今後はオフィスのエレベータを待っている間に降雨状況を確認できるようにさらなる改良を重ねていきたいと思います。
追記
現在の降雨情報と1時間後までの予報だけでなく、1日の天気予報も通知できるように改良しました。詳細は次の記事で紹介していきたいと思います。
「オフィス上空の天気予報を通知してみた。」シリーズ(全4回)
- 【まとめ】 オフィス上空の天気予報を通知してみた。
- 【第1回】 オフィスを出る前に外が雨かを知れるシステムを作ってみた。
- 【第2回】 降雨予測の的中確率を通知してみた。(←現在、閲覧中。)
- 【第3回】 OpenWeatherMapで天気予報を取得。
- 【第4回】 slackで1日の天気予報を通知してみた。