降雨予測の的中確率を通知してみた。

この記事は「オフィス上空の天気予報を通知してみた。」シリーズの第2回です。

「オフィス上空の天気予報を通知してみた。」シリーズ(全4回)

はじめに

前回、「オフィスを出る前に外が雨かを知れるシステムを作ってみた。」で降雨情報を通知してくれるシステムをつくりました。

このシステムは現在の天気と1時間後までの降雨予測情報を通知してくれます。

しかし、予測を通知してもそれが当たらなければこのシステムを信用しなくなり、使わなくなってしまいます。

そこで、今回はそのシステムを改良して、降雨情報のログを取り、降雨予測を通知する際に的中確率も通知してくれるようにしていきたいと思います。

構成

  1. YOLP(Yahoo! Open Local Platform)の気象情報APIからTOWNのオフィス上空の降雨情報を取得

  2. S3からログファイルを読み込む

  3. 通知メッセージの作成とログファイルのアップデート

  4. S3にログファイルを保存

  5. slackに現在の天気と降雨予報を送信

仕様

  • 天候の変化予測を記録しておき、それをもとに的中確率を求め、記録をしておく。
  • 天候の変化予報を通知するときに的中確率も通知する。

  1. 「現在、晴れ。30分後、雨」と予測
  2. 予測を記録
  3. 30分後の雨量の実測値と記録した予測を比較
  4. 比較した結果と的中確率を記録
  5. 再び、「晴れ→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回)