JAMESPARK3 commited on
Commit
8427833
ยท
verified ยท
1 Parent(s): 78d918a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +663 -0
app.py ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import xmltodict
4
+ import pandas as pd
5
+ from datetime import datetime, timedelta
6
+ import streamlit.components.v1 as components
7
+ import plotly.express as px
8
+ import time
9
+ import plotly.io as pio
10
+ from openai import OpenAI
11
+ from elevenlabs import ElevenLabs
12
+
13
+ # plotly์˜ JSON ์ง๋ ฌํ™” ์—”์ง„์„ ๊ธฐ๋ณธ json์œผ๋กœ ์„ค์ •
14
+ pio.json.config.default_engine = 'json'
15
+
16
+ # ํŽ˜์ด์ง€ ์„ค์ •
17
+ st.set_page_config(
18
+ page_title="์šฐ๋ฆฌ์ง‘ ๋‚ ์”จ ์ •๋ณด",
19
+ page_icon="๐ŸŒค๏ธ",
20
+ layout="wide",
21
+ menu_items={
22
+ 'Get Help': None,
23
+ 'Report a bug': None,
24
+ 'About': None
25
+ }
26
+ )
27
+
28
+ # CSS ์Šคํƒ€์ผ
29
+ st.markdown("""
30
+ <style>
31
+ section[data-testid="stSidebar"] {
32
+ display: none;
33
+ }
34
+ #MainMenu {
35
+ display: none;
36
+ }
37
+ header {
38
+ display: none;
39
+ }
40
+ .block-container {
41
+ padding: 0 !important;
42
+ max-width: 100% !important;
43
+ }
44
+ .element-container {
45
+ margin: 0 !important;
46
+ }
47
+ .stApp > header {
48
+ display: none;
49
+ }
50
+ #other-info {
51
+ display: none;
52
+ }
53
+ .stPlotlyChart {
54
+ width: 100%;
55
+ margin: 0 !important;
56
+ padding: 0 !important;
57
+ }
58
+ [data-testid="stMetricValue"] {
59
+ font-size: 3rem;
60
+ }
61
+ .time-container {
62
+ width: 100%;
63
+ text-align: center;
64
+ margin: 0 auto;
65
+ padding: 15px 0;
66
+ }
67
+ .date-text {
68
+ font-size: 6em !important;
69
+ font-weight: bold !important;
70
+ color: rgb(0, 0, 0) !important;
71
+ font-family: Arial, sans-serif !important;
72
+ text-shadow: none !important;
73
+ background: transparent !important;
74
+ display: block !important;
75
+ line-height: 1.2 !important;
76
+ margin-bottom: 0.5px !important;
77
+ }
78
+ h1, h2, h3, h4, h5, h6, p, .stMetric > div > div {
79
+ color: black !important;
80
+ }
81
+ .plotly-graph-div {
82
+ overflow-x: scroll !important;
83
+ min-width: 100% !important;
84
+ }
85
+ div[data-testid="stVerticalBlock"] > div {
86
+ padding: 0 !important;
87
+ }
88
+ .main {
89
+ padding: 0 !important;
90
+ }
91
+ .stApp {
92
+ margin: 0 !important;
93
+ }
94
+ [data-testid="stHeader"] {
95
+ display: none;
96
+ }
97
+ .section-container {
98
+ position: absolute;
99
+ top: 0;
100
+ left: 0;
101
+ width: 100%;
102
+ height: 100vh;
103
+ padding: 1rem;
104
+ box-sizing: border-box;
105
+ background-color: inherit;
106
+ }
107
+ .graph-container {
108
+ width: 100%;
109
+ height: calc(100vh - 100px);
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: center;
113
+ }
114
+ iframe {
115
+ margin: 0 !important;
116
+ padding: 0 !important;
117
+ }
118
+ [data-testid="column"] {
119
+ padding: 0 !important;
120
+ }
121
+ [data-testid="stVerticalBlock"] {
122
+ padding: 0 !important;
123
+ gap: 0 !important;
124
+ }
125
+ .dust-status {
126
+ font-size: 2em;
127
+ font-weight: bold;
128
+ color: black;
129
+ padding: 0.3rem 1rem;
130
+ border-radius: 1rem;
131
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
132
+ display: inline-block;
133
+ }
134
+ @keyframes scroll-text {
135
+ from {
136
+ transform: translateX(100%);
137
+ }
138
+ to {
139
+ transform: translateX(-100%);
140
+ }
141
+ }
142
+ .scroll-container {
143
+ position: fixed;
144
+ bottom: 20px;
145
+ left: 0;
146
+ width: 100%;
147
+ overflow: hidden;
148
+ background-color: rgba(255, 255, 255, 0.9);
149
+ padding: 10px 0;
150
+ z-index: 1000;
151
+ }
152
+ .scroll-text {
153
+ display: inline-block;
154
+ white-space: nowrap;
155
+ animation: scrolling 60s linear infinite;
156
+ font-size: 1.5em;
157
+ font-weight: bold;
158
+ color: #333;
159
+ }
160
+ @keyframes scrolling {
161
+ 0% {transform: translateX(100%);}
162
+ 100% {transform: translateX(-100%);}
163
+ }
164
+ </style>
165
+ """, unsafe_allow_html=True)
166
+
167
+
168
+ def get_korean_weekday(date):
169
+ weekday = date.strftime('%a')
170
+ weekday_dict = {
171
+ 'Mon': '์›”',
172
+ 'Tue': 'ํ™”',
173
+ 'Wed': '์ˆ˜',
174
+ 'Thu': '๋ชฉ',
175
+ 'Fri': '๊ธˆ',
176
+ 'Sat': 'ํ† ',
177
+ 'Sun': '์ผ'
178
+ }
179
+ return weekday_dict[weekday]
180
+
181
+ @st.cache_data(ttl=300) # 5๋ถ„๋งˆ๋‹ค ์บ์‹œ ๊ฐฑ์‹ 
182
+ def get_weather_data():
183
+ url = "http://openapi.seoul.go.kr:8088/77544e69764a414d363647424a655a/xml/citydata/1/5/์‹ ๋ฆผ์—ญ"
184
+ try:
185
+ response = requests.get(url)
186
+ response.raise_for_status() # HTTPError์— ๋Œ€ํ•ด ์˜ˆ์™ธ ๋ฐœ์ƒ
187
+ if not response.text.strip(): # ๋นˆ ์‘๋‹ต ์ฒ˜๋ฆฌ
188
+ raise ValueError("์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
189
+ data = xmltodict.parse(response.text)
190
+ return data['SeoulRtd.citydata']['CITYDATA']['WEATHER_STTS']['WEATHER_STTS']
191
+ except requests.exceptions.RequestException as e:
192
+ st.error(f"API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
193
+ except Exception as e:
194
+ st.error(f"๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
195
+ return None
196
+
197
+ def get_background_color(pm10_value):
198
+ try:
199
+ pm10 = float(pm10_value)
200
+ if pm10 <= 30:
201
+ return "#87CEEB" # ํŒŒ๋ž‘ (์ข‹์Œ)
202
+ elif pm10 <= 80:
203
+ return "#90EE90" # ์ดˆ๋ก (๋ณดํ†ต)
204
+ elif pm10 <= 150:
205
+ return "#FFD700" # ๋…ธ๋ž‘ (๋‚˜์จ)
206
+ else:
207
+ return "#FF6B6B" # ๋นจ๊ฐ• (๋งค์šฐ ๋‚˜์จ)
208
+ except:
209
+ return "#FFFFFF" # ๊ธฐ๋ณธ ํฐ์ƒ‰
210
+
211
+
212
+ def get_current_sky_status(data):
213
+ current_time = datetime.utcnow() + timedelta(hours=9)
214
+ current_hour = current_time.hour
215
+
216
+ forecast_data = data['FCST24HOURS']['FCST24HOURS']
217
+ if not isinstance(forecast_data, list):
218
+ forecast_data = [forecast_data]
219
+
220
+ closest_forecast = None
221
+ min_time_diff = float('inf')
222
+
223
+ for forecast in forecast_data:
224
+ forecast_hour = int(forecast['FCST_DT'][8:10])
225
+ time_diff = abs(forecast_hour - current_hour)
226
+ if time_diff < min_time_diff:
227
+ min_time_diff = time_diff
228
+ closest_forecast = forecast
229
+
230
+ return closest_forecast['SKY_STTS'] if closest_forecast else "์ •๋ณด์—†์Œ"
231
+
232
+ def format_news_message(news_list):
233
+ if not isinstance(news_list, list):
234
+ news_list = [news_list]
235
+
236
+ current_warnings = []
237
+ for news in news_list:
238
+ if not isinstance(news, dict):
239
+ continue
240
+
241
+ warn_val = news.get('WARN_VAL', '')
242
+ warn_stress = news.get('WARN_STRESS', '')
243
+ command = news.get('COMMAND', '')
244
+ warn_msg = news.get('WARN_MSG', '')
245
+ announce_time = news.get('ANNOUNCE_TIME', '')
246
+
247
+ if announce_time and len(announce_time) == 12:
248
+ year = announce_time[0:4]
249
+ month = announce_time[4:6]
250
+ day = announce_time[6:8]
251
+ hour = announce_time[8:10]
252
+ minute = announce_time[10:12]
253
+ formatted_time = f"({year}๋…„{month}์›”{day}์ผ{hour}์‹œ{minute}๋ถ„)"
254
+ else:
255
+ formatted_time = ""
256
+
257
+ if command == 'ํ•ด์ œ':
258
+ warning_text = f"โœ… {warn_val}{warn_stress} ํ•ด์ œ {formatted_time} {warn_msg}"
259
+ else:
260
+ warning_text = f"โš ๏ธ {warn_val}{warn_stress} ๋ฐœ๋ น {formatted_time} {warn_msg}"
261
+ current_warnings.append(warning_text)
262
+
263
+ return ' | '.join(current_warnings)
264
+
265
+ def show_weather_info(data):
266
+ st.markdown('<div class="section-container">', unsafe_allow_html=True)
267
+
268
+ # Add update time display using the last API call timestamp (already in KST)
269
+ refresh_time = datetime.fromtimestamp(st.session_state.last_api_call) if st.session_state.last_api_call else (datetime.utcnow() + timedelta(hours=9))
270
+ st.markdown(f'''
271
+ <div style="text-align: center; font-size: 0.8em; color: gray;">
272
+ Data refreshed at: {refresh_time.strftime('%Y-%m-%d %H:%M:%S')}
273
+ </div>
274
+ ''', unsafe_allow_html=True)
275
+
276
+ # Add this code to define formatted_date
277
+ current_time = datetime.utcnow() + timedelta(hours=9)
278
+ weekday = get_korean_weekday(current_time)
279
+ formatted_date = f"{current_time.strftime('%Y-%m-%d')}({weekday})"
280
+
281
+ pm10 = float(data['PM10'])
282
+ if pm10 <= 30:
283
+ dust_status = "์ข‹์Œ"
284
+ dust_color = "#87CEEB" # Blue
285
+ elif pm10 <= 80:
286
+ dust_status = "๋ณดํ†ต"
287
+ dust_color = "#90EE90" # Green
288
+ elif pm10 <= 150:
289
+ dust_status = "๋‚˜์จ"
290
+ dust_color = "#FFD700" # Yellow
291
+ else:
292
+ dust_status = "๋งค์šฐ๋‚˜์จ"
293
+ dust_color = "#FF6B6B" # Red
294
+
295
+ temp = data.get('TEMP', "์ •๋ณด์—†์Œ")
296
+ precip_type = data.get('PRECPT_TYPE', "์ •๋ณด์—†์Œ")
297
+
298
+ try:
299
+ temp = f"{float(temp):.1f}ยฐC"
300
+ except:
301
+ temp = "์ •๋ณด์—†์Œ"
302
+
303
+ # ๋‚ด์ผ ์•„์นจ 6์‹œ ์˜ˆ๋ณด ์ฐพ๊ธฐ
304
+ tomorrow_morning_weather = "์—†์Œ"
305
+ forecast_data = data['FCST24HOURS']['FCST24HOURS']
306
+ if not isinstance(forecast_data, list):
307
+ forecast_data = [forecast_data]
308
+
309
+ # ํ˜„์žฌ ์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ๋‚ด์ผ ๋‚ ์งœ ๊ณ„์‚ฐ
310
+ current_time = datetime.utcnow() + timedelta(hours=9)
311
+ tomorrow = current_time + timedelta(days=1)
312
+ tomorrow_date = tomorrow.strftime('%Y%m%d')
313
+
314
+ for forecast in forecast_data:
315
+ # ๋‚ ์งœ์™€ ์‹œ๊ฐ„์ด ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
316
+ if forecast['FCST_DT'][:8] == tomorrow_date and forecast['FCST_DT'][8:10] == '06':
317
+ tomorrow_temp = forecast['TEMP']
318
+ precip_type = forecast['PRECPT_TYPE']
319
+
320
+ # ๊ฐ•์ˆ˜ ํƒ€์ž…์— ๋”ฐ๋ฅธ ํ‘œ์‹œ
321
+ precip_mark = ""
322
+ if precip_type == "๋น„":
323
+ precip_mark = "(๋น„)"
324
+ elif precip_type == "๋ˆˆ":
325
+ precip_mark = "(๋ˆˆ)"
326
+ elif precip_type == "๋น„/๋ˆˆ":
327
+ precip_mark = "(๋น„/๋ˆˆ)"
328
+
329
+ tomorrow_morning_weather = f"{tomorrow_temp}ยฐC{precip_mark}"
330
+ break
331
+
332
+ # ํ™”๋ฉด์— ํ‘œ์‹œ
333
+ st.markdown(f'''
334
+ <div class="time-container">
335
+ <div style="text-align: center; margin-bottom: 0.5rem; font-size: 10em; font-weight: bold; color: black;">
336
+ {temp} &nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp; {tomorrow_morning_weather}
337
+ </div>
338
+ <span class="date-text">{formatted_date}</span>
339
+ </div>
340
+ ''', unsafe_allow_html=True)
341
+
342
+
343
+ clock_html = """
344
+ <div style="width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 20px;">
345
+ <div style="text-align: center; height: 300px; display: flex; align-items: center; justify-content: center;">
346
+ <span id="clock" style="font-size: 15em; font-weight: bold; color: black; line-height: 1.2; white-space: nowrap;"></span>
347
+ </div>
348
+ </div>
349
+ <script>
350
+ function updateClock() {
351
+ const now = new Date();
352
+ const options = {
353
+ timeZone: 'Asia/Seoul',
354
+ hour12: true,
355
+ hour: 'numeric',
356
+ minute: '2-digit'
357
+ };
358
+ document.getElementById('clock').textContent = now.toLocaleTimeString('ko-KR', options);
359
+ }
360
+ setInterval(updateClock, 1000);
361
+ updateClock();
362
+ </script>
363
+ """
364
+ components.html(clock_html, height=300)
365
+
366
+ st.button("์‹œ๊ฐ„๋Œ€๋ณ„ ์˜จ๋„ ๋ณด๊ธฐ", on_click=lambda: st.session_state.update({'current_section': 'temperature'}))
367
+
368
+ st.markdown('</div>', unsafe_allow_html=True)
369
+
370
+
371
+ def show_temperature_graph(data):
372
+ st.markdown('<div class="section-container">', unsafe_allow_html=True)
373
+ st.markdown('<h1 style="text-align: center; margin-bottom: 1rem;">์‹œ๊ฐ„๋Œ€๋ณ„ ์˜จ๋„</h1>', unsafe_allow_html=True)
374
+
375
+ forecast_data = data['FCST24HOURS']['FCST24HOURS']
376
+ if not isinstance(forecast_data, list):
377
+ forecast_data = [forecast_data]
378
+
379
+ # Sort forecast data by FCST_DT to ensure correct time ordering
380
+ forecast_data = sorted(forecast_data, key=lambda x: x['FCST_DT'])
381
+
382
+ # ํ˜„์žฌ ์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ์œ ํšจํ•œ ์˜ˆ๋ณด ๋ฐ์ดํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง
383
+ current_time = datetime.utcnow() + timedelta(hours=9)
384
+ current_time_str = current_time.strftime('%Y%m%d%H%M')
385
+
386
+ # ํ˜„์žฌ ์‹œ๊ฐ„ ์ดํ›„์˜ ์˜ˆ๋ณด ๋ฐ์ดํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง
387
+ valid_forecast_data = [fcst for fcst in forecast_data if fcst['FCST_DT'] >= current_time_str]
388
+
389
+ # ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ „์ฒด ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
390
+ if not valid_forecast_data:
391
+ valid_forecast_data = forecast_data
392
+
393
+ times = []
394
+ temps = []
395
+ weather_icons = []
396
+ weather_descriptions = []
397
+ date_changes = []
398
+
399
+ # Find the index closest to current time
400
+ time_differences = []
401
+ for fcst in valid_forecast_data:
402
+ forecast_time = datetime.strptime(fcst['FCST_DT'], '%Y%m%d%H%M')
403
+ time_diff = abs((forecast_time - current_time).total_seconds())
404
+ time_differences.append(time_diff)
405
+
406
+ current_index = time_differences.index(min(time_differences))
407
+
408
+ # Reorder forecast data to start from current time
409
+ valid_forecast_data = valid_forecast_data[current_index:] + valid_forecast_data[:current_index]
410
+
411
+ for i, forecast in enumerate(valid_forecast_data):
412
+ time_str = forecast['FCST_DT']
413
+ date = time_str[6:8]
414
+ hour = time_str[8:10]
415
+
416
+ if i > 0 and valid_forecast_data[i-1]['FCST_DT'][6:8] != date:
417
+ date_changes.append(i)
418
+ times.append(f"{hour}์‹œ")
419
+ temps.append(float(forecast['TEMP']))
420
+
421
+ sky_status = forecast['SKY_STTS']
422
+ precip_type = forecast['PRECPT_TYPE']
423
+
424
+ if precip_type == "๋น„":
425
+ icon = "๐ŸŒง๏ธ"
426
+ description = "๋น„"
427
+ elif precip_type == "๋ˆˆ":
428
+ icon = "๐ŸŒจ๏ธ"
429
+ description = "๋ˆˆ"
430
+ elif precip_type == "๋น„/๋ˆˆ":
431
+ icon = "๐ŸŒจ๏ธ๐ŸŒง๏ธ"
432
+ description = "๋น„/๋ˆˆ"
433
+ elif sky_status == "๋ง‘์Œ":
434
+ icon = "โ˜€๏ธ"
435
+ description = "๋ง‘์Œ"
436
+ elif sky_status == "๊ตฌ๋ฆ„๋งŽ์Œ":
437
+ icon = "โ›…"
438
+ description = "๊ตฌ๋ฆ„<br>๋งŽ์Œ"
439
+ elif sky_status == "ํ๋ฆผ":
440
+ icon = "โ˜๏ธ"
441
+ description = "ํ๋ฆผ"
442
+ else:
443
+ icon = "โ˜€๏ธ"
444
+ description = "์ •๋ณด์—†์Œ"
445
+
446
+ weather_icons.append(icon)
447
+ weather_descriptions.append(description)
448
+
449
+ df = pd.DataFrame({
450
+ '์‹œ๊ฐ„': times,
451
+ '๊ธฐ์˜จ': temps,
452
+ '๋‚ ์”จ': weather_icons,
453
+ '์„ค๋ช…': weather_descriptions,
454
+ 'FCST_DT': [f['FCST_DT'] for f in valid_forecast_data]
455
+ })
456
+
457
+ fig = px.line(df, x='์‹œ๊ฐ„', y='๊ธฐ์˜จ', markers=True)
458
+
459
+ # Add nighttime overlay (18:00-06:00)
460
+ for i in range(len(times)):
461
+ hour = int(times[i].replace('์‹œ', ''))
462
+ if hour >= 18 or hour < 6:
463
+ fig.add_vrect(
464
+ x0=times[i],
465
+ x1=times[i+1] if i < len(times)-1 else times[-1],
466
+ fillcolor='rgba(0, 0, 0, 0.1)',
467
+ layer='below',
468
+ line_width=0,
469
+ annotation_text="",
470
+ annotation_position="top left"
471
+ )
472
+
473
+ # ํ˜„์žฌ ์‹œ๊ฐ๊ณผ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์˜ˆ๋ณด ์‹œ๊ฐ„ ์ฐพ๊ธฐ
474
+ current_time = datetime.utcnow() + timedelta(hours=9)
475
+
476
+ # ๋…น์ƒ‰ ์„ธ๋กœ์„  ์ถ”๊ฐ€ ๋ฐ "ํ˜„์žฌ" ํ…์ŠคํŠธ ํ‘œ์‹œ - ์ด์ œ ํ•ญ์ƒ ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ์— ํ‘œ์‹œ
477
+ fig.add_vline(x=times[0], line_width=2, line_dash="dash", line_color="green")
478
+ fig.add_annotation(
479
+ x=times[0],
480
+ y=max(temps) + 4,
481
+ text="<b>ํ˜„์žฌ</b>",
482
+ showarrow=True,
483
+ arrowhead=2,
484
+ )
485
+
486
+ bold_times = ["00์‹œ", "06์‹œ", "12์‹œ", "18์‹œ", "24์‹œ"]
487
+ for time in bold_times:
488
+ if time in times:
489
+ index = times.index(time)
490
+ fig.add_annotation(
491
+ x=time,
492
+ y=min(temps),
493
+ text=time,
494
+ showarrow=False,
495
+ font=dict(size=30, color="black", family="Arial")
496
+ )
497
+
498
+ fig.add_vline(x='12์‹œ', line_width=2, line_dash="dash", line_color="rgba(0,0,0,0.5)")
499
+
500
+ # ์˜ค๋Š˜๊ณผ ๋‚ด์ผ, ์˜ค์ „๊ณผ ์˜คํ›„ ํ…์ŠคํŠธ๋Š” ํ•ด๋‹น ์‹œ๊ฐ„๋Œ€์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ํ‘œ์‹œ
501
+ time_set = set(times)
502
+
503
+ if '11์‹œ' in time_set:
504
+ fig.add_annotation(x='11์‹œ', y=max(temps) + 4, text="์˜ค์ „", showarrow=False, font=dict(size=24))
505
+
506
+ if '13์‹œ' in time_set:
507
+ fig.add_annotation(x='13์‹œ', y=max(temps) + 4, text="์˜คํ›„", showarrow=False, font=dict(size=24))
508
+
509
+ if '23์‹œ' in time_set:
510
+ fig.add_annotation(x='23์‹œ', y=max(temps) + 4, text="์˜ค๋Š˜", showarrow=False, font=dict(size=24))
511
+
512
+ if '01์‹œ' in time_set:
513
+ fig.add_annotation(x='01์‹œ', y=max(temps) + 4, text="๋‚ด์ผ", showarrow=False, font=dict(size=24))
514
+
515
+ fig.update_traces(
516
+ line_color='#FF6B6B',
517
+ marker=dict(size=10, color='#FF6B6B'),
518
+ textposition="top center",
519
+ mode='lines+markers+text',
520
+ text=[f"<b>{int(round(temp))}ยฐ</b>" for temp in df['๊ธฐ์˜จ']],
521
+ textfont=dict(size=24)
522
+ )
523
+
524
+ for i, (icon, description) in enumerate(zip(weather_icons, weather_descriptions)):
525
+ fig.add_annotation(
526
+ x=times[i],
527
+ y=max(temps) + 3,
528
+ text=f"{icon}",
529
+ showarrow=False,
530
+ font=dict(size=30)
531
+ )
532
+ fig.add_annotation(
533
+ x=times[i],
534
+ y=max(temps) + 2,
535
+ text=f"{description}",
536
+ showarrow=False,
537
+ font=dict(size=16),
538
+ textangle=0
539
+ )
540
+
541
+ for date_change in date_changes:
542
+ fig.add_vline(
543
+ x=times[date_change],
544
+ line_width=2,
545
+ line_dash="dash",
546
+ line_color="rgba(255, 0, 0, 0.7)"
547
+ )
548
+
549
+ fig.update_layout(
550
+ title=None,
551
+ xaxis_title='',
552
+ yaxis_title='๊ธฐ์˜จ (ยฐC)',
553
+ height=600,
554
+ width=7200,
555
+ showlegend=False,
556
+ plot_bgcolor='rgba(255,255,255,0.9)',
557
+ paper_bgcolor='rgba(0,0,0,0)',
558
+ margin=dict(l=50, r=50, t=0, b=0),
559
+ xaxis=dict(
560
+ tickangle=0,
561
+ tickfont=dict(size=14),
562
+ gridcolor='rgba(0,0,0,0.1)',
563
+ dtick=1,
564
+ tickmode='array',
565
+ ticktext=[f"{i:02d}์‹œ" for i in range(24)],
566
+ tickvals=[f"{i:02d}์‹œ" for i in range(24)]
567
+ ),
568
+ yaxis=dict(
569
+ tickfont=dict(size=14),
570
+ gridcolor='rgba(0,0,0,0.1)'
571
+ )
572
+ )
573
+
574
+ st.plotly_chart(fig, use_container_width=True)
575
+
576
+ # ๋‚ ์”จ ์˜ˆ๋ณด ์ƒ์„ฑ ๋ฐ ํ‘œ์‹œ ๋ถ€๋ถ„์„ ์„ธ์…˜ ์ƒํƒœ๋กœ ๊ด€๋ฆฌ
577
+ if 'weather_forecast' not in st.session_state:
578
+ client = OpenAI(
579
+ api_key="glhf_9ea0e0babe1e45353dd03b44cb979e22",
580
+ base_url="https://glhf.chat/api/openai/v1"
581
+ )
582
+
583
+ forecast_data_str = "\n".join([f"{time}: {temp}๋„, {description}" for time, temp, description in zip(times, temps, weather_descriptions)])
584
+
585
+ response = client.chat.completions.create(
586
+ model="hf:Nexusflow/Athene-V2-Chat",
587
+ messages=[
588
+ {"role": "system", "content": "๋‹น์‹ ์€ ๋‚ ์”จ ์˜ˆ๋ณด๊ด€์ž…๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์‹œ๊ฐ„๋Œ€๋ณ„ ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ •ํ™•ํ•œ ๋‚ ์”จ ์˜ˆ๋ณด๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”."},
589
+ {"role": "user", "content": f"๋‹ค์Œ ์‹œ๊ฐ„๋Œ€๋ณ„ ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ณ  ์‹ค์ œ ๋‚ ์”จ ์ƒํ™ฉ์— ๋งž๋Š” ์ •ํ™•ํ•œ ๋‚ ์”จ ์˜ˆ๋ณด๋ฅผ 200์ž๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”. ๋น„๋‚˜ ๋ˆˆ ์˜ˆ๋ณด๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์šฐ์‚ฐ์„ ์ค€๋น„ํ•˜๋„๋ก ์•ˆ๋‚ดํ•ด์ฃผ์„ธ์š”. ์˜ท์ฐจ๋ฆผ์€ ๋‹ค์Œ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. \n27ยฐC์ด์ƒ:๋ฐ˜ํŒ”ํ‹ฐ, ๋ฐ˜๋ฐ”์ง€, ๋ฏผ์†Œ๋งค.\n23ยฐC~26ยฐC: ์–‡์€ ์…”์ธ , ๋ฐ˜ํŒ”ํ‹ฐ, ๋ฐ˜๋ฐ”์ง€, ๋ฉด๋ฐ”์ง€.\n20ยฐC~22ยฐC: ์–‡์€ ๊ฐ€๋””๊ฑด, ๊ธดํŒ”ํ‹ฐ, ๊ธด๋ฐ”์ง€.\n17ยฐC~19ยฐC: ์–‡์€ ๋‹ˆํŠธ, ๊ฐ€๋””๊ฑด, ๋งจํˆฌ๋งจ, ์–‡์€ ์ž์ผ“, ๊ธด๋ฐ”์ง€.\n12ยฐC~16ยฐC: ์ž์ผ“, ๊ฐ€๋””๊ฑด, ์•ผ์ƒ, ๋งจํˆฌ๋งจ, ๋‹ˆํŠธ, ์Šคํƒ€ํ‚น, ๊ธด๋ฐ”์ง€\n9ยฐC~11ยฐC: ํŠธ๋ Œ์น˜์ฝ”ํŠธ, ์•ผ์ƒ, ๊ฐ€์ฃฝ ์ž์ผ“, ์Šคํƒ€ํ‚น, ๊ธด๋ฐ”์ง€\n5ยฐC~8ยฐC: ์ฝ”ํŠธ, ํžˆํŠธํ…, ๋‹ˆํŠธ, ๊ธด๋ฐ”์ง€\n4ยฐC์ดํ•˜: ํŒจ๋”ฉ, ๋‘๊บผ์šด ์ฝ”ํŠธ, ๋ชฉ๋„๋ฆฌ, ๊ธฐ๋ชจ์ œํ’ˆ: {forecast_data_str}"}
590
+ ]
591
+ )
592
+ st.session_state.weather_forecast = response.choices[0].message.content
593
+
594
+ # ์ €์žฅ๋œ ๋‚ ์”จ ์˜ˆ๋ณด ํ‘œ์‹œ
595
+ st.markdown(f'''
596
+ <div class="scroll-container">
597
+ <div class="scroll-text">{st.session_state.weather_forecast}</div>
598
+ </div>
599
+ ''', unsafe_allow_html=True)
600
+
601
+
602
+ # ์šฐ๋ฆฌ์ง‘ ๋‚ ์”จ ์ •๋ณด๋กœ ๋Œ์•„๊ฐ€๊ธฐ ๋ฒ„ํŠผ ์ถ”๊ฐ€
603
+ st.button("์šฐ๋ฆฌ์ง‘ ๋‚ ์”จ ์ •๋ณด๋กœ ๋Œ์•„๊ฐ€๊ธฐ", on_click=lambda: st.session_state.update({'current_section': 'weather'}))
604
+
605
+ st.markdown('</div>', unsafe_allow_html=True)
606
+
607
+ def main():
608
+ if 'current_section' not in st.session_state:
609
+ st.session_state.current_section = 'weather'
610
+ st.session_state.last_api_call = 0
611
+ st.session_state.weather_data = None
612
+
613
+ # ํ˜„์žฌ ์‹œ๊ฐ„์„ ์„œ์šธ ์‹œ๊ฐ„์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
614
+ current_time = datetime.utcnow() + timedelta(hours=9)
615
+ current_timestamp = current_time.timestamp()
616
+
617
+ # ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ ์ฒดํฌ
618
+ if 'last_api_call' not in st.session_state:
619
+ st.session_state.last_api_call = 0
620
+
621
+ time_since_last_call = current_timestamp - st.session_state.last_api_call
622
+
623
+ # ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ์„ ์œ„ํ•œ placeholder
624
+ refresh_placeholder = st.empty()
625
+
626
+ # ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
627
+ if not st.session_state.weather_data or time_since_last_call >= 300:
628
+ try:
629
+ new_data = get_weather_data()
630
+ if new_data: # ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ›์•„์™”์„ ๋•Œ๋งŒ ์—…๋ฐ์ดํŠธ
631
+ st.session_state.weather_data = new_data
632
+ st.session_state.last_api_call = current_timestamp
633
+ st.rerun() # ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
634
+ except Exception as e:
635
+ st.error(f"Failed to refresh data: {str(e)}")
636
+
637
+ data = st.session_state.weather_data
638
+ if data:
639
+ pm10_value = data['PM10']
640
+ background_color = get_background_color(pm10_value)
641
+
642
+ st.markdown(f"""
643
+ <style>
644
+ .stApp {{
645
+ background-color: {background_color};
646
+ }}
647
+ </style>
648
+ """, unsafe_allow_html=True)
649
+
650
+ if st.session_state.current_section == 'weather':
651
+ show_weather_info(data)
652
+ else:
653
+ show_temperature_graph(data)
654
+
655
+ # ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ์„ ์œ„ํ•œ ํƒ€์ด๋จธ
656
+ with refresh_placeholder:
657
+ if time_since_last_call < 300:
658
+ remaining_time = 300 - time_since_last_call
659
+ time.sleep(min(remaining_time, 1)) # ์ตœ๋Œ€ 1์ดˆ์”ฉ ๋Œ€๊ธฐ
660
+ st.rerun()
661
+
662
+ if __name__ == "__main__":
663
+ main()