ChatGPTの言う通りに電車の運賃を計算するWebAPIを作ってみた

乗車券分割プログラムってあるじゃないですか。JRの運賃は特殊な形態をしているので、乗車券を途中で分割して購入したほうが安くなるという謎の仕組みがあり、どこで分割すれば安くなるかを調べてくれるサービスです。

私の知る限りだとbunkatsu.infoとbunkatsu.netがありますが、前者はよくアクセス制限になったり操作が難解(鉄道オタクにとってはそれが利点でしょうけど普段遣いには向いてない)だったりします。後者は大都市近郊区間しか対応していないのと、正確なルート指定ができないのが問題だったりします。

で、別にこの発想(分割乗車券を自動で提案してくれるサービス)自体は特許でもなんでもないと思うので、自作したいと思うのですが、それにあたって、まず運賃計算をできなきゃいけないので、それから始めようと思い、ChatGPTに質問しました。

>加算運賃が乗車キロに比例しない場合、キロ対運賃表があるとして、それをpandasで読み込んでそれをもとに運賃を算出するコード



もう私はトシなので、一からプログラミングするのは面倒なんですよね。なのでとりあえずChatGPTの言う通りに動かしてみて、ChatGPTが間違っているようなら適宜修正を加える、という形で楽ができないかと思いました。

しかし、ChatGPTもなかなか癖があり、質問の仕方が漠然だったり簡潔だったりすると以下のように答えを返してくれません。

人間と会話するときと違って、細かい条件の指定が必要です。逆に言えば、人間と違って、細かい注文をつけまくることによって意図に沿った回答を出してくれます。人間は一般的に細かい注文をつけられるのを嫌がると思うので、ChatGPTが人間でないことを理解することができます。

話がそれましたが、先述のプログラムをまず動かしてみたいと思います。
しかし、想定の運賃で動かすんだとつまらないので、試しに運賃形態が複雑そうではない京王電鉄の運賃表をもとに計算してみようと思います。
この部分は、ChatGPTでは答えてくれないと思うので自分でやります(スクレイピングしてくれる生成AIもあるという噂も聞きましたが、まあ別に運賃表コピペするだけなんでスクレイピングするほどでもないですし)

https://www.keio.co.jp/train/ticket/fare_chart/fare_chart_km.html
を開き、開発者ツールで運賃テーブルを選択します。

Copy elementでHTMLコードをコピーします。それを適当なファイルに保存して、Pandasで読み込みます。これは自作コードです。
ちなみに、この程度の行為ならスクレイピングとは言えないと思いますし、著作権侵害でもないし、常識の範疇だと思います。

ちなみに、pd.read_htmlという関数は、htmlのTable要素を読み込んでDataFrameにしてくれますが、Tableが1つしかない場合でも、DataFrameの配列として返してきますので、[0]で配列の先頭を指定することでdfを取得できます。
それを整形するコードをかきます。書いていて、いやこれもうちょっとスマートにできるだろと思いましたが、ChatGPTに質問する文章を考える面倒臭さと天秤にかけて、クソコードを書くことにしました。

fillnaはいらなかったかもしれない

で、fare_table.csvにデータを貼り付けて、先程のプログラムを実行します。

5キロメートルの運賃は136円です。

24キロメートルの運賃は283円です。




小数点以下がある場合切り上げになるようですね。そうなるようにプログラムを修正します。


…ChatGPTで

24.5キロメートルの運賃は325円です。

想定通りのプログラムができました。
じゃあこれを公開するにはどうすればいいかChatGPTにきいてみましょう。

GCFというのはGoogle Cloud Functionの略で、サーバーレス(厳密には裏でサーバーが動いているが、自分でサーバーの用意や設定をしなくていいもので…

わかったかな?

GCFって略称で理解してくれるって素晴らしいですね。プログラミングの文脈だからそう推測したんでしょうか。

言われたとおりにmain.pyとrequirements.txtを用意します。

gcloudコマンドの設定と認証は済ませてあるので、早速実行します。

実行するとログにURLっぽいものがあるので、ChatGPTの言う通りPostリクエストを送ります。

urlは例なので、コンソールに出てきたURLに置き換える

しかし、

calculate_fare() missing 1 required positional argument: 'fare_table_path'

と言われてしまいます。どうやらGCFは一番目の引数にすべてパラメータが吸収されてしまうようです。そのため、

def calculate_fare(distance, fare_table_path)

def calculate_fare(distance, fare_table_path="fare_table.csv")

のように応急処置的に関数の引数を修正し、gcloudコマンドを実行し…

unsupported operand type(s) for %: 'Request' and 'int'

ChatGPTは、ローカルの関数を呼び出すみたいにGCFの関数を呼び出せるみたいに言ってましたが、どうやら間違いのようです。Requestオブジェクトというのは多分Flaskのものです。
GCPの公式ドキュメントなどを見て、コードを修正します。

ローカルでも関数が試せるようになったんですね。便利ですね。

最終的に

import pandas as pd
import math
import functions_framework
import json

def round_up_if_not_integer(number):
    if number % 1 != 0:
        number = math.ceil(number)
    return int(number)

@functions_framework.http
def calculate_fare(request):
    fare = calculate_fare_(float(request.args.get('distance')))
    headers = {"Access-Control-Allow-Origin": "*"}
    return (json.dumps(dict(fare=int(fare))), 200, headers)

def calculate_fare_(distance, fare_table_path="fare_table.csv"):
    distance = round_up_if_not_integer(distance)
    fare_df = pd.read_csv(fare_table_path)
    max_distance = fare_df["キロ"].max()

    if distance <= max_distance:
        fare = fare_df[fare_df["キロ"] <= distance]["運賃"].iloc[-1]
    else:
        fare = fare_df["運賃"].iloc[-1]

    return fare
if __name__ == '__main__':
    # キロ対運賃表が記載されているファイルのパス
    fare_table_path = "fare_table.csv"

    # 例として5キロメートルの場合の運賃を計算
    distance = 24.5
    fare = calculate_fare_(distance, fare_table_path)
    print(f"{distance}キロメートルの運賃は{fare}円です。")

というようになりました
if name == '__main__':のところはデプロイしても動かないのであまり関係ないです。
で、

>> requests.get(url, params=dict(distance=24.5)).json()
{'fare': 325}

となりました。
ここまで思ったより長かった。

やっぱりChatGPTだけではどうにもならない部分はある。GCPの最新の情報なんか知りようがないんだから仕方ない
Google Bardとかいうのもあるようですけど、そっちならうまくいくんですかね
でも、コアとなる運賃算出のロジックはChatGPTの言う通りにほぼなっているし、もうちょっと良いコードはかけると思うけどそこまでのクオリティが要求されないときは圧倒的時短になるんじゃないでしょうか

この記事が気に入ったらサポートをしてみませんか?