File size: 14,278 Bytes
2e1954f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e9d74a
8c8de09
2e1954f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e5f425
2e1954f
6e5f425
 
 
 
 
 
 
 
 
2e1954f
 
6e5f425
 
 
 
2e1954f
 
 
 
 
 
 
8c8de09
6e9d74a
2e1954f
6e9d74a
2e1954f
6e5f425
 
 
 
2e1954f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e9d74a
6e5f425
 
 
 
 
 
2e1954f
 
 
 
 
1c741df
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
import codecs
import io
import random
import requests
import time
from datetime import date, timedelta, datetime
from tqdm import tqdm
from typing import Generator, Tuple

import numpy as np
import pandas as pd
import plotly.express as px

import gradio as gr


# ラップする関数
def date_range(
    start: date, stop: date, step: timedelta = timedelta(1)
) -> Generator[date, None, None]:
    """startからendまで日付をstep日ずつループさせるジェネレータ"""
    current = start
    while current < stop:
        yield current
        current += step


def get_url(download_date: date) -> Tuple[str, str]:
    """ダウンロードするURLと日付の文字列を返す"""
    month = download_date.strftime("%Y%m")
    day = download_date.strftime("%Y%m%d")
    return (
        f"https://www.shijou-nippo.metro.tokyo.lg.jp/SN/{month}/{day}/Sui/Sui_K1.csv",
        day,
    )


def content_wrap(content):
    """1行目にヘッダ行が来るまでスキップする"""
    buffer = ""
    first = True
    for line in io.BytesIO(content):
        line_str = codecs.decode(line, "shift-jis")
        if first:
            if "品名" in line_str:
                first = False
                buffer = line_str
            else:
                continue
        else:
            buffer += line_str
    return io.StringIO(buffer)


def insert_data(data, day, low_price, center_price, high_price, quantity):
    """ "データをリストに追加する"""
    data["date"].append(day)
    data["low_price"].append(low_price)
    data["center_price"].append(center_price)
    data["high_price"].append(high_price)
    data["quantity"].append(quantity)


def to_numeric(x):
    """文字列を数値に変換する"""
    if isinstance(x, str):
        return float(x)
    else:
        return x


def get_fish_data(year, month, day, drop):
    """
    東京卸売市場からデータを引っ張ってくる
    :param start_date: 開始日
    :param end_date: 終了日
    :return: さばの値段を結合したデータ
    """

    year = str(year)
    if len(str(month)) == 1:
        month = '0' + str(month)
    else:
        month = str(month)

    if len(str(day)) == 1:
        day = '0' + str(day)
    else:
        day = str(day)

    date_str = year +'-' + month + '-' + day
    date_title = year + '年' + month + '月' + day + '日'
    date = datetime.strptime(date_str, '%Y-%m-%d')
    yesterday = date - timedelta(days=2)
    tommorow = date + timedelta(days=3)
    
    data = {
        "date": [],
        "low_price": [],
        "center_price": [],
        "high_price": [],
        "quantity": [],
    }

    iterator = tqdm(
        date_range(yesterday, tommorow), total=(tommorow - yesterday).days
    )

    for download_date in iterator:
        url, day = get_url(download_date)
        iterator.set_description(day)
        response = requests.get(url)

        # URLが存在しないとき
        if response.status_code == 404:
            insert_data(data, day, np.nan, np.nan, np.nan, 0)
            continue
        assert (
            response.status_code == 200
        ), f"Unexpected HTTP response. Please check the website {url}."

        df = pd.read_csv(content_wrap(response.content))

        # 欠損値補完
        price_cols = ["安値(円)", "中値(円)", "高値(円)"]
        for c in price_cols:
            df[c].mask(df[c] == "-", np.nan, inplace=True)
            df[c].mask(df[c] == "−", np.nan, inplace=True)
        df["卸売数量"].mask(df["卸売数量"] == "-", np.nan, inplace=True)
        df["卸売数量"].mask(df["卸売数量"] == "−", np.nan, inplace=True)

        # 長崎で獲れたあじの中値と卸売数量
        # 品目 == さば の行だけ抽出
        df_aji = df.loc[df["品名"] == "さば", ["卸売数量"] + price_cols]

        # さばの販売がなかったら欠損扱いに
        if len(df_aji) == 0:
            insert_data(data, day, np.nan, np.nan, np.nan, 0)
            continue

        isnan = lambda x: isinstance(x, float) and np.isnan(x)
        # 産地ごと(?)のさばの販売実績を調べる
        low_prices = []
        center_prices = []
        high_prices = []
        quantities = []
        for i, row in enumerate(df_aji.iloc):
            lp, cp, hp, q = row[price_cols + ["卸売数量"]]
            lp, cp, hp, q = (
                to_numeric(lp),
                to_numeric(cp),
                to_numeric(hp),
                to_numeric(q),
            )

            # 中値だけが記録されている -> 価格帯が1個だけなので高値、安値も中値と同じにしておく
            if isnan(lp) and isnan(hp) and (not isnan(cp)):
                low_prices.append(cp)
                center_prices.append(cp)
                high_prices.append(cp)

            # 高値・安値があり中値がない -> 価格帯2個、とりあえず両者の平均を中値とする
            elif (not isnan(lp)) and (not isnan(hp)) and isnan(cp):
                low_prices.append(lp)
                center_prices.append((lp + hp) / 2)
                high_prices.append(hp)
            else:
                low_prices.append(lp)
                center_prices.append(cp)
                high_prices.append(hp)

            if isnan(row["卸売数量"]):
                quantities.append(0)
            else:
                quantities.append(q)

        low_price = int(min(low_prices))
        center_price = int(sum(center_prices) / len(center_prices))
        high_price = int(max(high_prices))
        quantity = int(float(sum(quantities)))

        # 保存
        insert_data(data, day, low_price, center_price, high_price, quantity)
        # 短期間にアクセスが集中しないようにクールタイムを設定
        time.sleep(max(0.5 + random.normalvariate(0, 0.3), 0.1))
    # DataFrameを作成
    df = pd.DataFrame(data)
    df['date'] = pd.to_datetime(df['date'])
    df['日付']  = df['date'].dt.strftime('%m月%d日')
    df = df.rename(columns={'low_price':"低値(円/kg)", 'center_price':"中値(円/kg)",'high_price':"高値(円/kg)",'quantity': "卸売数量(kg)"})
    df = df.fillna(0)

    quantity = df["卸売数量(kg)"].iloc[-3]
    high_price = df["高値(円/kg)"].iloc[-3]
    center_price = df["中値(円/kg)"].iloc[-3]
    low_price = df["低値(円/kg)"].iloc[-3]

    fig = px.line(df, x='日付', y=df[drop])
    fig.update_layout(
        title=dict(text=date_title + 'と前後5日間の' + drop, 
                    font=dict(family='Times New Roman', size=15)),
        xaxis_title="日付",
        yaxis_title=drop,
    )

    if drop == "卸売数量(kg)":
        return quantity, fig
    elif drop == "高値(円/kg)":
        return high_price, fig
    elif drop == "中値(円/kg)":
        return center_price, fig
    else:
        return low_price, fig

def get_tide(year, month, day):
    tide_url = 'https://api.tide736.net/get_tide.php'
    
    year = str(year)
    if len(str(month)) == 1:
        month = '0' + str(month)
    else:
        month = str(month)

    if len(str(day)) == 1:
        day = '0' + str(day)
    else:
        day = str(day)

    date_str = year +'-' + month + '-' + day
    params = {
        # 鯖の漁獲量全国1位である茨城県の大津市に設定
            'pc': 8,
            'hc': 1,
            'yr': year,
            'mn': month,
            'dy': day,
            'rg': 'day'
        }

    res = requests.get(tide_url, params)
    info = res.json()
    tide_chart = info['tide']['chart'][date_str]
    tide = tide_chart['moon']['title']

    if len(tide_chart['flood']) == 1:
            high_tide1 = tide_chart['flood'][0]['time']
            high_tide_height1 = tide_chart['flood'][0]['cm']
            high_tide2 = np.nan
            high_tide_height2 = np.nan 
    else:
        high_tide1 = tide_chart['flood'][0]['time']
        high_tide_height1 = tide_chart['flood'][0]['cm']
        high_tide2 = tide_chart['flood'][1]['time']
        high_tide_height2 = tide_chart['flood'][1]['cm']

    if len(tide_chart['edd']) == 1:
        low_tide1 = tide_chart['edd'][0]['time']
        low_tide_height1 = tide_chart['edd'][0]['cm']
        low_tide2 = np.nan
        low_tide_height2 = np.nan
    else:
        low_tide1 = tide_chart['edd'][0]['time']
        low_tide_height1 = tide_chart['edd'][0]['cm']
        low_tide2 = tide_chart['edd'][1]['time']
        low_tide_height2 = tide_chart['edd'][1]['cm']
        
    hinode = tide_chart['sun']['rise']
    hinoiri = tide_chart['sun']['set']

    data ={'date':date_str, 
        'tide':tide,
        'high_tide1':high_tide1,
        'high_tide_height1':high_tide_height1,
        'high_tide2':high_tide2,
        'high_tide_height2':high_tide_height2,
        'low_tide1':low_tide1,
        'low_tide_height1':low_tide_height1,
        'low_tide2':low_tide2,
        'low_tide_height2':low_tide_height2,
        'hinode':hinode,
        'hinoiri':hinoiri}

    df_otu = pd.DataFrame(data = [data])

    tide= df_otu['tide'].iloc[-1]
    high_tide1= df_otu['high_tide1'].iloc[-1]
    high_tide_height1= df_otu['high_tide_height1'].iloc[-1]
    high_tide2= df_otu['high_tide2'].iloc[-1]
    high_tide_height2= df_otu['high_tide_height2'].iloc[-1]
    low_tide1= df_otu['low_tide1'].iloc[-1]
    low_tide_height1= df_otu['low_tide_height1'].iloc[-1]
    low_tide2= df_otu['low_tide2'].iloc[-1]
    low_tide_height2= df_otu['low_tide_height2'].iloc[-1]
    hinode= df_otu['hinode'].iloc[-1]
    hinoiri= df_otu['hinoiri'].iloc[-1]

    return tide, hinode, hinoiri, high_tide1, high_tide_height1, high_tide2, high_tide_height2, low_tide1, low_tide_height1, low_tide2, low_tide_height2, hinode, hinoiri


with gr.Blocks() as demo:
    gr.Markdown(
    """
    # 鯖の市場データ&潮見表
    #### 鯖の卸売数量と市場での価格、また鯖がよく獲れる茨城県の潮見表を表示できます。 
    """)

    with gr.Accordion("項目の説明", open = False):
         gr.Markdown("""
        **卸売数量(kg)** : 市場で取引された数量  
        **高値(円/kg)** : 1日の中で最も高い卸売価格  
        **中値(円/kg)** : 最も総卸売数量の多い卸売価格  
        **低値(円/kg)** : 中値未満の卸売価格のうち、総卸売数量が最も多い卸売価格
        """)
         
    with gr.Tabs():
        with gr.TabItem("鯖の市場データ"):
            gr.Markdown(
            """
            指定した日時と項目の結果を表示します。
            """)
            with gr.Row():
                with gr.Column():
                    year_input_fish = gr.Slider(2010, 2023, step = 1, interactive = True, label="年", info="年を選択して下さい")
                    month_input_fish = gr.Slider(1, 12, step = 1, interactive = True, label="月", info="月を選択して下さい")
                    day_input_fish = gr.Slider(1, 31, step = 1, interactive = True, label="日", info="日を選択して下さい")
                    drop_input_data = gr.Radio(["卸売数量(kg)", "高値(円/kg)", "中値(円/kg)", "低値(円/kg)"], label="卸売数量 or 市場での価格", info="項目をひとつ選択して下さい")
                with gr.Column():
                    drop_output_data = gr.Textbox(label = "結果", info="取引がない日は0になります")
                    line_fig = gr.Plot(label="結果")
            fish_data_button = gr.Button("表示")
            gr.Examples(examples=[[2015, 12, 18, "卸売数量(kg)"], [2023, 3, 7, "高値(円/kg)"]], label="入力例", inputs=[year_input_fish, month_input_fish, day_input_fish, drop_input_data])
        with gr.TabItem("潮見表"):
            gr.Markdown(
            """
            指定した日時の潮見表を表示します。
            """)
            with gr.Row():
                with gr.Column():
                    year_input_tide = gr.Slider(2010, 2023, step = 1, interactive = True, label="年", info="年を選択して下さい")
                    month_input_tide = gr.Slider(1, 12, step = 1, interactive = True, label="月", info="月を選択して下さい")
                    day_input_tide = gr.Slider(1, 31, step = 1, interactive = True, label="日", info="日を選択して下さい")
                    tide_button = gr.Button("表示")
                with gr.Row():
                    output_tide = gr.Textbox(label = "潮名")
                    output_hinode = gr.Textbox(label = "日の出")
                    output_hinoiri = gr.Textbox(label = "日の入り")
                with gr.Row():
                    output_high_tide1 = gr.Textbox(label = "満潮時刻1")
                    output_high_tide_height1 = gr.Textbox(label = "満潮水位1(cm)")
                    output_high_tide2 = gr.Textbox(label = "満潮時刻2")
                    output_high_tide_height2 = gr.Textbox(label = "満潮水位2(cm)")
                with gr.Row():
                    output_low_tide1 = gr.Textbox(label = "干潮時刻1")
                    output_low_tide_height1 = gr.Textbox(label = "干潮水位1(cm)")
                    output_low_tide2 = gr.Textbox(label = "干潮時刻2")
                    output_low_tide_height2 = gr.Textbox(label = "干潮水位2(cm)")
            gr.Examples(examples=[[2015, 12, 18], [2023, 3, 7]], label="入力例", inputs=[year_input_tide, month_input_tide, day_input_tide])
   
    with gr.Accordion("使用データ", open = False):
         gr.Markdown("""
    #####  
    東京卸売市場が発表している上記項目、また海上保安庁から潮汐データを使用している。 
    """)        
    
    fish_data_button.click(get_fish_data, inputs=[year_input_fish, month_input_fish, day_input_fish, drop_input_data], outputs=[drop_output_data, line_fig])
    tide_button.click(get_tide, inputs=[year_input_tide, month_input_tide, day_input_tide], outputs=[output_tide, output_hinode, output_hinoiri, output_high_tide1, output_high_tide_height1, output_high_tide2, output_high_tide_height2, output_low_tide1, output_low_tide_height1, output_low_tide2, output_low_tide_height2])
    
# 起動
demo.launch()