IBM Cloud Functionsで、天気照会機能を実装する

# はじめに
元々やりたかったことはWatson Conversationの外部機能呼び出しです。
昨年末、ベータレベルでIBM Cloud Functions呼出しをサポートしたので、この機能を試そうと思いました。
その[チュートリアル](https://console.bluemix.net/docs/services/conversation/dialog-actions.html#create-action)でIBM Cloud Functionsの例として使われていたのが、[yahoo APIの天気照会機能](https://console.bluemix.net/docs/openwhisk/openwhisk_actions.html#openwhisk_apicall_action)だったのですが、元のAPIが英語のみサポートなので、日本人的に使い勝手が悪いなと思い、WatsonのLanguage Translaorと組み合わせれば日本語化できそうなので、この記事で紹介するサンプルを作ってみました。
「晴れ」とか「曇り」などの天気情報は、辞書により日本語化しています。
コード定義が十分でないので、日本語メッセージがでない場合があります。
[コード表](https://gist.github.com/bzerangue/805520)
ライトアカウントの範囲で試せることは確認していますので、「IBM Cloud Functionsでこんなことができるのか」ということを試していただけるといいのではないかと思います。

(備考) 天気APIをWatsonのWeather APIにすることも検討したのですが、残念ながらライトアカウントで使えないようなので、あきらめました。。

* 2018-02-20 入力都市名も戻すよう、コードを修正
* 2018-05-24 最新版に合わせて画面コピー、手順など見直し

# IBM Cloud Functionsとは
**IBM Cloud Functions**とは、少し前まで**IBM OpenWhisk**と呼ばれていたサービスです。
**FaaS**とか**サーバーレス**とか言われるサービスのIBM Cloud版で、裏ではDockerが動いているのですが、リクエストが出た段階でDockerを立ち上げ、リクエストがなくなり不要になると止めるということにより、CPU / Memoryなどの計算機資源を最小限に抑えるような仕組みとなっています。
このため、課金体系も何秒間サービスが上がっていたかで計算されるようになっています。

# 当サンプルアプリの機能
都市名(city)を入力パラメータとして受け取り、いったんこれをWatson APIの1つであるLanguage Translatorで英語にした後、Yahoo APIでその都市の天気と気温を取得します。
気温は、英語版で華氏で返ってくるので、日本人にわかるよう、摂氏に変換しました。
天気名については、かなり特殊な言葉が多いので、コード値に対して辞書登録して日本語名称を返すようにしています。
どの程度の都市に対応できているのかはわかりませんが、下記の都市であれば結果が返ってくることは確認済みです。

札幌、稚内、東京、京都、那覇、ニューヨーク、ロンドン

# 事前準備
Watson サービスの一つ、Langugage Translatorを使うので、このサービスを作成し、認証情報を取得してテキストエディタなどに保存して下さい。
やりかたがわからない場合は[IBM Cloudの基本 -サービスの作成から資格情報の取得まで-](https://qiita.com/makaishi2/items/43622db769d6a7e9b560)を参照していただければと思います。


# Cloud Functionsサービスの作成
非常にわかりにくいのですが、下記のダッシュボードのメニューの中「機能」がCloud Functionsの管理メニューとなっています。

![function-1.png](https://qiita-image-store.s3.amazonaws.com/0/206130/a0a004a9-cfdb-752d-fd28-ad28a75f3f7e.png)

このメニューを選ぶと次のような画面が現れますので、左の「作成の開始」を選びます。

![function-2.png](https://qiita-image-store.s3.amazonaws.com/0/206130/a3ea5ae6-2cec-fad4-0924-ac313a7ea082.png)


次の「作成」の画面になったら、左から2つめの「アクションの作成」を選択します。

![cloud-function1.png](https://qiita-image-store.s3.amazonaws.com/0/206130/183afcf2-7c9b-0229-3141-52bc15475ea4.png)

下記の「アクションの作成」画面が出たら、

**アクション名:** weather-jp
**ランタイム:** Python3

として、「作成」ボタンをクリックします。
※　[オリジナルのチュートリアルのコード](https://console.bluemix.net/docs/openwhisk/openwhisk_actions.html#openwhisk_apicall_action)はNode.jsなのですが、ここではPython(v3)を選んでいます。なぜ、こういう選択にしたかは当記事の一番最後に説明しましたので、参考とされて下さい。

![function-5.png](https://qiita-image-store.s3.amazonaws.com/0/206130/2f387491-b08a-ac5f-14b7-7abcf13e7568.png)

下のように雛形コードの入ったコード開発画面が現れます。ここで、パラメータの設定を行うため、画面左の「パラメータ」をクリックします。

![function-10.png](https://qiita-image-store.s3.amazonaws.com/0/206130/d78ee4cc-246d-9761-1d49-dc07898a1d98.png)

「パラメータの追加」をクリックして、下の図のように
**パラメータ名**: city
**パラメータ値**: "札幌"
を入力し、「保存」ボタンをクリックします。
(2018-05-24 追記 パラメータ値は以前は単に「札幌」でよかったのですが、最新版では「"札幌"」とダブルクオートで囲む必要がありました。)
保存が終わったら、画面左のメニューで再び「コード」をクリックします。

![cloud-function2.png](https://qiita-image-store.s3.amazonaws.com/0/206130/5e2acaa2-20e9-1f6c-e0de-8a20372f5a8a.png)

コード部分を下記のものに置き換えて下さい。
usernameとpasswordは事前準備であらかじめ取得したものに置き換えます。

```py3:weather-jp.py
#
#
# main() このアクションの実行時に呼び出されます
#
# @param Cloud Functions アクションは 1 つのパラメーターを受け入れます。このパラメーターは JSON オブジェクトでなければなりません。
#
# @return このアクションの出力。この出力は、JSON オブジェクトでなければなりません。
#
#
import sys
import json
import requests
from watson_developer_cloud import LanguageTranslatorV2 as LanguageTranslator

username = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
password = 'xxxxxxxxxxxx'

language_translator = LanguageTranslator(
    username=username,
    password=password
)

weather_code = {
    '11': 'にわか雨',
    '12': 'にわか雨',
    '16': '雪',
    '26': '曇り',
    '27': 'ほぼ曇り(夜間)',
    '28': 'ほぼ曇り(昼間)',
    '29': 'ところにより曇り(夜間)',
    '30': 'ところにより曇り(昼間)',
    '31': '晴れ(夜間)',
    '32': '晴れ(昼間)',
}

def main(dict):
    # 漢字都市名取得
    city = dict['city']
    #print(city)

    # 英語に翻訳
    translation = language_translator.translate(
        text=city,
        model_id='ja-en')
    #print(json.dumps(translation, indent=2, ensure_ascii=False))
    result = translation["translations"][0]['translation']
    #print(result)

    # 天気API用URL組立て
    location = "'" + result + "'"
    print(location)
    query = 'select item.condition from weather.forecast where ' +\
      'woeid in (select woeid from geo.places(1) where text=' +\
      location + ")&format=json"
    baseurl = 'https://query.yahooapis.com/v1/public/yql'
    url = baseurl + '?q=' + query
    print(url)

    # 天気API呼出し
    res = requests.get(url)
    result2 = json.loads(res.text)
    result3 = result2["query"]["results"]["channel"]["item"]["condition"]
    print(result3)

    # 華氏摂氏変換、天気の日本語化
    f_temp = float(result3['temp'])
    c_temp = round((f_temp - 32) / 1.8, 1)
    en_text = result3['text']
    code = result3['code']
    jp_text = weather_code[code]
    print(c_temp, jp_text)

    # 結果を返す
    return { 'city_jp': city, 'city_en': result, 'code': code, 'cond_en': en_text, 'cond_jp': jp_text, 'c_temp': c_temp}
```

コードの置き換えが終わったら、画面左上の「保存」ボタンをクリックします。

※　このコードを実装する上で一番苦労したのは、Yahoo APIのquery文組立てのところです。
ちゃんと動くチュートリアルオリジナルの書き換えを行う過程でゴミが入ってしまったらしく、なかなかシンタックスエラーがなくならなくて。。
その時、たまたま見つけたサイトで正しいquery文を作ることができました。このサイトについても最後に紹介します。

![function-12.png](https://qiita-image-store.s3.amazonaws.com/0/206130/0e87514c-a9fc-8f0c-78d7-5f6dc3d1ede7.png)

# テスト
## 開発ツールからのテスト
修正したコードに文法エラーがなければ、コードが保存され、先ほどまで「保存」だったボタンが「起動」に変わるはずです。この時、裏では新しいDockerイメージがデプロイされています。
この「起動」ボタンを押すことで、簡単にテストを実施することが可能です。

![スクリーンショット 2018-05-24 19.05.29.png](https://qiita-image-store.s3.amazonaws.com/0/206130/5d242248-ad66-48e6-80dc-36b5f996d4e1.png)

うまくいった場合には、下記のような画面となります。

![スクリーンショット 2018-05-24 19.10.10.png](https://qiita-image-store.s3.amazonaws.com/0/206130/fd6b01a5-20d3-20c9-2a95-f41fdbe93a88.png)


## コマンドラインからのテスト
今度は、今作ったサービスをコマンドラインから起動してみましょう。
そのためには、Cloud Functionsの初期画面に戻り、右側の「CLIのダウンロード」ボタンをクリックします。

![function-15.png](https://qiita-image-store.s3.amazonaws.com/0/206130/41a923b5-c2ef-d7d9-b857-90e6c132c2fc.png)

次のような画面が出てくるので、Step 4までは指示通りに実施して下さい。

![function-16.png](https://qiita-image-store.s3.amazonaws.com/0/206130/5d8e6cc5-74dd-8191-0b52-47767808a6a0.png)

Step 4のテストも通ったら、次のコマンドで先ほど作成したActionを確認します。

```sh:
$ bx wsk action list
```

次のような結果が返ってくるはずです。
**\<org_name\>**と**\<space_name\>**は自分のIBM Cloud環境の組織名とスペース名が入ります。

```
actions
/<org_name>_<space_name>/weather-jp                                 private python-jessie:3
```

後は次のコマンドで最後のパラメータを変えて、いろいろな都市の天気を調べてみましょう。

```sh
$ bx wsk action invoke --result weather-jp --param city 那覇
{
    "c_temp": 20.6,
    "city_en": "NAHA -",
    "city_jp": "那覇",
    "code": "28",
    "cond_en": "Mostly Cloudy",
    "cond_jp": "ほぼ曇り(昼間)"
}
$ bx wsk action invoke --result weather-jp --param city 福岡
{
    "c_temp": 7.8,
    "city_en": "Fukuoka",
    "city_jp": "福岡",
    "code": "30",
    "cond_en": "Partly Cloudy",
    "cond_jp": "ところにより曇り(昼間)"
}

$ bx wsk action invoke --result weather-jp --param city 富山
{
    "c_temp": 3.9,
    "city_en": "Toyama",
    "city_jp": "富山",
    "code": "28",
    "cond_en": "Mostly Cloudy",
    "cond_jp": "ほぼ曇り(昼間)"
}
```

こんな風に外国の都市名もいけますね。(もともと英語のサービスだったので当たり前??)

```sh
$ bx wsk action invoke --result weather-jp --param city ニューヨーク
{
    "c_temp": 8.9,
    "city_en": "New York",
    "city_jp": "ニューヨーク",
    "code": "26",
    "cond_en": "Cloudy",
    "cond_jp": "曇り"
}

$ bx wsk action invoke --result weather-jp --param city ロンドン
{
    "c_temp": 9.4,
    "city_en": "London",
    "city_jp": "ロンドン",
    "code": "26",
    "cond_en": "Cloudy",
    "cond_jp": "曇り"
}
```

# この後は
これで、十分に機能する天気照会サービスを作れたので、近々、このサービスをWatson Conversatoinから起動する事例を紹介する予定です。
乞うご期待!
-> [Watson ConversationからIBM Cloud Functions(旧OpenWhisk)を呼び出す](https://qiita.com/makaishi2/items/349d366e550b37cf4521)として記事をアップしました!

## 備考1 なぜ言語をPython3にしたのか
Node.jsでリモートAPI呼出しをすると、必然的にコールバック関数の処理が必須となります。
呼び出す関数が1つだけならまだやりかたがあるのですが、複数回の呼出しが必要だといわゆるコールバック地獄状態になり、相当実装が難しいです。
(async関数使って頑張ってこれをやり抜いた例は[こちら(記事の一番最後のapp.js)](https://qiita.com/makaishi2/items/dd9eafcbe824ef9482fd)。)
今回はこういうことを避けるため、同期処理でリモートAPI呼出しのできるPython(v3)を選択しました。

## 備考2 Yahoo Weather APIのquery文組立て
本文でも触れたように、このアプリ開発で一番苦労したのは、この部分です。
いつものように、困った時のGoogle頼みで、Googole先生に教えを請うたところ、提示してもらったのが下記サイトでした。
[Yahoo Weather API for your apps](https://developer.yahoo.com/weather/)
![スクリーンショット 2018-02-19 9.45.44.png](https://qiita-image-store.s3.amazonaws.com/0/206130/e3c23e97-ddf3-85d9-a42d-27064c66a41c.png)


まさに、今回のような人を助けるためのサイトかと。同じことで困っている方は是非ご活用下さい。