WeatherAI / app.py
JAMESPARK3's picture
Update app.py
f880c9e verified
raw
history blame
27.4 kB
import streamlit as st
import requests
import xmltodict
import pandas as pd
from datetime import datetime, timedelta
import streamlit.components.v1 as components
import plotly.express as px
import time
import plotly.io as pio
import httpx
from openai import OpenAI
# plotly์˜ JSON ์ง๋ ฌํ™” ์—”์ง„์„ ๊ธฐ๋ณธ json์œผ๋กœ ์„ค์ •
pio.json.config.default_engine = 'json'
# ํŽ˜์ด์ง€ ์„ค์ •
st.set_page_config(
page_title="์šฐ๋ฆฌ์ง‘ ๋‚ ์”จ ์ •๋ณด",
page_icon="๐ŸŒค๏ธ",
layout="wide",
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
# CSS ์Šคํƒ€์ผ
st.markdown("""
<style>
section[data-testid="stSidebar"] {
display: none;
}
#MainMenu {
display: none;
}
header {
display: none;
}
.block-container {
padding: 0 !important;
max-width: 100% !important;
}
.element-container {
margin: 0 !important;
}
.stApp > header {
display: none;
}
#other-info {
display: none;
}
.stPlotlyChart {
width: 100%;
margin: 0 !important;
padding: 0 !important;
}
[data-testid="stMetricValue"] {
font-size: 3rem;
}
.time-container {
width: 100%;
text-align: center;
margin: 0 auto;
padding: 15px 0;
}
.date-text {
font-size: 8em !important;
font-weight: bold !important;
color: rgb(0, 0, 0) !important;
font-family: Arial, sans-serif !important;
text-shadow: none !important;
background: transparent !important;
display: block !important;
line-height: 1.2 !important;
margin-bottom: 0.5px !important;
}
h1, h2, h3, h4, h5, h6, p, .stMetric > div > div {
color: black !important;
}
.plotly-graph-div {
overflow-x: scroll !important;
min-width: 100% !important;
}
div[data-testid="stVerticalBlock"] > div {
padding: 0 !important;
}
.main {
padding: 0 !important;
}
.stApp {
margin: 0 !important;
}
[data-testid="stHeader"] {
display: none;
}
.section-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
padding: 1rem;
box-sizing: border-box;
background-color: inherit;
}
.graph-container {
width: 100%;
height: calc(100vh - 100px);
display: flex;
flex-direction: column;
align-items: center;
}
iframe {
margin: 0 !important;
padding: 0 !important;
}
[data-testid="column"] {
padding: 0 !important;
}
[data-testid="stVerticalBlock"] {
padding: 0 !important;
gap: 0 !important;
}
.dust-status {
font-size: 2em;
font-weight: bold;
color: black;
padding: 0.3rem 1rem;
border-radius: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: inline-block;
}
@keyframes scroll-text {
from {
transform: translateX(100%);
}
to {
transform: translateX(-100%);
}
}
.scroll-container {
position: fixed;
bottom: 20px;
left: 0;
width: 100%;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.9);
padding: 10px 0;
z-index: 1000;
}
.scroll-text {
display: inline-block;
white-space: nowrap;
animation: scrolling 60s linear infinite;
font-size: 1.5em;
font-weight: bold;
color: #333;
}
@keyframes scrolling {
0% {transform: translateX(100%);}
100% {transform: translateX(-100%);}
}
</style>
""", unsafe_allow_html=True)
def get_korean_weekday(date):
weekday = date.strftime('%a')
weekday_dict = {
'Mon': '์›”',
'Tue': 'ํ™”',
'Wed': '์ˆ˜',
'Thu': '๋ชฉ',
'Fri': '๊ธˆ',
'Sat': 'ํ† ',
'Sun': '์ผ'
}
return weekday_dict[weekday]
@st.cache_data(ttl=300) # 5๋ถ„๋งˆ๋‹ค ์บ์‹œ ๊ฐฑ์‹ 
def get_weather_data():
url = "http://openapi.seoul.go.kr:8088/77544e69764a414d363647424a655a/xml/citydata/1/5/์‹ ๋ฆผ์—ญ"
try:
response = requests.get(url)
response.raise_for_status() # HTTPError์— ๋Œ€ํ•ด ์˜ˆ์™ธ ๋ฐœ์ƒ
if not response.text.strip(): # ๋นˆ ์‘๋‹ต ์ฒ˜๋ฆฌ
raise ValueError("์‘๋‹ต์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
data = xmltodict.parse(response.text)
return data['SeoulRtd.citydata']['CITYDATA']['WEATHER_STTS']['WEATHER_STTS']
except requests.exceptions.RequestException as e:
st.error(f"API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
except Exception as e:
st.error(f"๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
return None
def get_background_color(pm10_value):
try:
pm10 = float(pm10_value)
if pm10 <= 30:
return "#87CEEB" # ํŒŒ๋ž‘ (์ข‹์Œ)
elif pm10 <= 80:
return "#90EE90" # ์ดˆ๋ก (๋ณดํ†ต)
elif pm10 <= 150:
return "#FFD700" # ๋…ธ๋ž‘ (๋‚˜์จ)
else:
return "#FF6B6B" # ๋นจ๊ฐ• (๋งค์šฐ ๋‚˜์จ)
except:
return "#FFFFFF" # ๊ธฐ๋ณธ ํฐ์ƒ‰
def get_current_sky_status(data):
current_time = datetime.utcnow() + timedelta(hours=9)
current_hour = current_time.hour
forecast_data = data['FCST24HOURS']['FCST24HOURS']
if not isinstance(forecast_data, list):
forecast_data = [forecast_data]
closest_forecast = None
min_time_diff = float('inf')
for forecast in forecast_data:
forecast_hour = int(forecast['FCST_DT'][8:10])
time_diff = abs(forecast_hour - current_hour)
if time_diff < min_time_diff:
min_time_diff = time_diff
closest_forecast = forecast
return closest_forecast['SKY_STTS'] if closest_forecast else "์ •๋ณด์—†์Œ"
def format_news_message(news_list):
if not isinstance(news_list, list):
news_list = [news_list]
current_warnings = []
for news in news_list:
if not isinstance(news, dict):
continue
warn_val = news.get('WARN_VAL', '')
warn_stress = news.get('WARN_STRESS', '')
command = news.get('COMMAND', '')
warn_msg = news.get('WARN_MSG', '')
announce_time = news.get('ANNOUNCE_TIME', '')
if announce_time and len(announce_time) == 12:
year = announce_time[0:4]
month = announce_time[4:6]
day = announce_time[6:8]
hour = announce_time[8:10]
minute = announce_time[10:12]
formatted_time = f"({year}๋…„{month}์›”{day}์ผ{hour}์‹œ{minute}๋ถ„)"
else:
formatted_time = ""
if command == 'ํ•ด์ œ':
warning_text = f"โœ… {warn_val}{warn_stress} ํ•ด์ œ {formatted_time} {warn_msg}"
else:
warning_text = f"โš ๏ธ {warn_val}{warn_stress} ๋ฐœ๋ น {formatted_time} {warn_msg}"
current_warnings.append(warning_text)
return ' | '.join(current_warnings)
def show_weather_info(data):
st.markdown('<div class="section-container">', unsafe_allow_html=True)
# Add update time display using the last API call timestamp (already in KST)
refresh_time = datetime.fromtimestamp(st.session_state.last_api_call) if st.session_state.last_api_call else (datetime.utcnow() + timedelta(hours=9))
st.markdown(f'''
<div style="text-align: center; font-size: 0.8em; color: gray;">
Data refreshed at: {refresh_time.strftime('%Y-%m-%d %H:%M:%S')}
</div>
''', unsafe_allow_html=True)
# Add this code to define formatted_date
current_time = datetime.utcnow() + timedelta(hours=9)
weekday = get_korean_weekday(current_time)
formatted_date = f"{current_time.strftime('%Y-%m-%d')}({weekday})"
pm10 = float(data['PM10'])
if pm10 <= 30:
dust_status = "์ข‹์Œ"
dust_color = "#87CEEB" # Blue
elif pm10 <= 80:
dust_status = "๋ณดํ†ต"
dust_color = "#90EE90" # Green
elif pm10 <= 150:
dust_status = "๋‚˜์จ"
dust_color = "#FFD700" # Yellow
else:
dust_status = "๋งค์šฐ๋‚˜์จ"
dust_color = "#FF6B6B" # Red
temp = data.get('TEMP', "์ •๋ณด์—†์Œ")
precip_type = data.get('PRECPT_TYPE', "์ •๋ณด์—†์Œ")
try:
temp = f"{float(temp):.1f}ยฐC"
except:
temp = "์ •๋ณด์—†์Œ"
# ํ˜„์žฌ ์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด 06์‹œ ๋ฐ์ดํ„ฐ ์ฐพ๊ธฐ
morning_six_data = None
current_time = datetime.utcnow() + timedelta(hours=9) # KST
forecast_data = data['FCST24HOURS']['FCST24HOURS']
if not isinstance(forecast_data, list):
forecast_data = [forecast_data]
for fcst in forecast_data:
fcst_hour = int(fcst['FCST_DT'][8:10]) # HH
if fcst_hour == 6:
fcst_datetime = datetime.strptime(fcst['FCST_DT'], '%Y%m%d%H%M')
if fcst_datetime > current_time:
morning_six_data = fcst
break
# 06์‹œ ๋‚ ์”จ ์ •๋ณด ์ค€๋น„
tomorrow_morning_weather = "์—†์Œ"
if morning_six_data:
tomorrow_temp = morning_six_data['TEMP']
weather_icon = ""
# PRECPT_TYPE ๋จผ์ € ํ™•์ธ
precip_type = morning_six_data['PRECPT_TYPE']
if precip_type == "๋น„" or precip_type == "๋น„/๋ˆˆ":
weather_icon = "โ˜”"
elif precip_type == "๋ˆˆ":
weather_icon = "โ„"
# PRECPT_TYPE์ด '์—†์Œ'์ด๋ฉด SKY_STTS ๊ธฐ๋ฐ˜์œผ๋กœ ์•„์ด์ฝ˜ ์„ค์ •
else:
if morning_six_data['SKY_STTS'] == "๋ง‘์Œ":
weather_icon = "๐ŸŒž"
elif morning_six_data['SKY_STTS'] in ["๊ตฌ๋ฆ„", "๊ตฌ๋ฆ„๋งŽ์Œ"]:
weather_icon = "โ›…"
elif morning_six_data['SKY_STTS'] == "ํ๋ฆผ":
weather_icon = "โ˜๏ธ"
tomorrow_morning_weather = f"{tomorrow_temp}ยฐC {weather_icon}"
# ํ™”๋ฉด์— ํ‘œ์‹œ
weather_icon = ""
current_time_str = current_time.strftime('%Y%m%d%H')
# Check current precipitation type first
if data['PRECPT_TYPE'] in ["๋น„", "๋ˆˆ", "๋น„/๋ˆˆ", "๋น—๋ฐฉ์šธ"]:
if data['PRECPT_TYPE'] in ["๋น„", "๋น—๋ฐฉ์šธ"]:
weather_icon = "โ˜”"
elif data['PRECPT_TYPE'] == "๋ˆˆ":
weather_icon = "โ„"
elif data['PRECPT_TYPE'] == "๋น„/๋ˆˆ":
weather_icon = "โ˜”โ„"
else:
# Find nearest forecast time when no current precipitation
nearest_forecast = None
min_time_diff = float('inf')
for forecast in forecast_data:
forecast_time = datetime.strptime(forecast['FCST_DT'], '%Y%m%d%H%M')
time_diff = abs((forecast_time - current_time).total_seconds())
if time_diff < min_time_diff:
min_time_diff = time_diff
nearest_forecast = forecast
if nearest_forecast:
if nearest_forecast['PRECPT_TYPE'] in ["๋น„", "๋ˆˆ", "๋น„/๋ˆˆ", "๋น—๋ฐฉ์šธ"]:
if nearest_forecast['PRECPT_TYPE'] in ["๋น„", "๋น—๋ฐฉ์šธ"]:
weather_icon = "โ˜”"
elif nearest_forecast['PRECPT_TYPE'] == "๋ˆˆ":
weather_icon = "โ„"
elif nearest_forecast['PRECPT_TYPE'] == "๋น„/๋ˆˆ":
weather_icon = "โ˜”โ„"
else:
# Use SKY_STTS when no precipitation
sky_status = nearest_forecast['SKY_STTS']
if sky_status == "๋ง‘์Œ":
weather_icon = "๐ŸŒž"
elif sky_status in ["๊ตฌ๋ฆ„", "๊ตฌ๋ฆ„๋งŽ์Œ"]:
weather_icon = "โ›…"
elif sky_status == "ํ๋ฆผ":
weather_icon = "โ˜"
precip_mark = weather_icon
st.markdown(f'''
<div class="time-container">
<div style="text-align: center; margin-bottom: 0.5rem; font-size: 6em; font-weight: bold; color: black;">
{temp}{precip_mark} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {tomorrow_morning_weather}
</div>
<span class="date-text">{formatted_date}</span>
</div>
''', unsafe_allow_html=True)
clock_html = """
<div style="width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 20px;">
<div style="text-align: center; height: 300px; display: flex; align-items: center; justify-content: center;">
<span id="clock" style="font-size: 15em; font-weight: bold; color: black; line-height: 1.2; white-space: nowrap;"></span>
</div>
</div>
<script>
function updateClock() {
const now = new Date();
const options = {
timeZone: 'Asia/Seoul',
hour12: true,
hour: 'numeric',
minute: '2-digit'
};
document.getElementById('clock').textContent = now.toLocaleTimeString('ko-KR', options);
}
setInterval(updateClock, 1000);
updateClock();
</script>
"""
components.html(clock_html, height=300)
st.button("์‹œ๊ฐ„๋Œ€๋ณ„ ์˜จ๋„ ๋ณด๊ธฐ", on_click=lambda: st.session_state.update({'current_section': 'temperature'}))
st.markdown('</div>', unsafe_allow_html=True)
def show_temperature_graph(data):
st.markdown('<div class="section-container">', unsafe_allow_html=True)
st.markdown('<h1 style="text-align: center; margin-bottom: 1rem;">์‹œ๊ฐ„๋Œ€๋ณ„ ์˜จ๋„</h1>', unsafe_allow_html=True)
forecast_data = data['FCST24HOURS']['FCST24HOURS']
if not isinstance(forecast_data, list):
forecast_data = [forecast_data]
# Sort forecast data by FCST_DT to ensure correct time ordering
forecast_data = sorted(forecast_data, key=lambda x: x['FCST_DT'])
# ํ˜„์žฌ ์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ์œ ํšจํ•œ ์˜ˆ๋ณด ๋ฐ์ดํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง
current_time = datetime.utcnow() + timedelta(hours=9) # KST
current_date = current_time.strftime('%Y%m%d')
next_date = (current_time + timedelta(days=1)).strftime('%Y%m%d')
# ํ˜„์žฌ ์‹œ๊ฐ„ ์ดํ›„์˜ ์˜ˆ๋ณด ๋ฐ์ดํ„ฐ์™€ ๋‹ค์Œ ๋‚ ์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋‘ ํฌํ•จ
valid_forecast_data = []
for fcst in forecast_data:
fcst_date = fcst['FCST_DT'][:8] # YYYYMMDD
fcst_hour = int(fcst['FCST_DT'][8:10]) # HH
current_hour = current_time.hour
# ํ˜„์žฌ ๋‚ ์งœ์˜ ํ˜„์žฌ ์‹œ๊ฐ„ ์ดํ›„ ๋ฐ์ดํ„ฐ ๋˜๋Š” ๋‹ค์Œ ๋‚ ์˜ ๋ฐ์ดํ„ฐ
if (fcst_date == current_date and fcst_hour >= current_hour) or fcst_date == next_date:
valid_forecast_data.append(fcst)
# ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ „์ฒด ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
if not valid_forecast_data:
valid_forecast_data = forecast_data
# ํ˜„์žฌ ์‹œ๊ฐ๊ณผ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์˜ˆ๋ณด ์‹œ๊ฐ„ ์ฐพ๊ธฐ
current_time = datetime.utcnow() + timedelta(hours=9)
# ๋…น์ƒ‰ ์„ธ๋กœ์„  ์ถ”๊ฐ€ ๋ฐ "ํ˜„์žฌ" ํ…์ŠคํŠธ ํ‘œ์‹œ - ์ด์ œ ํ•ญ์ƒ ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ์— ํ‘œ์‹œ
time_differences = []
for fcst in valid_forecast_data:
forecast_time = datetime.strptime(fcst['FCST_DT'], '%Y%m%d%H%M')
time_diff = abs((forecast_time - current_time).total_seconds())
time_differences.append(time_diff)
current_index = time_differences.index(min(time_differences))
# Reorder forecast data to start from current time
valid_forecast_data = valid_forecast_data[current_index:] + valid_forecast_data[:current_index]
times = []
temps = []
weather_icons = []
weather_descriptions = []
date_changes = []
for i, forecast in enumerate(valid_forecast_data):
time_str = forecast['FCST_DT']
date = time_str[6:8]
hour = time_str[8:10]
if i > 0 and valid_forecast_data[i-1]['FCST_DT'][6:8] != date:
date_changes.append(i)
times.append(f"{hour}์‹œ")
temps.append(float(forecast['TEMP']))
sky_status = forecast['SKY_STTS']
precip_type = forecast['PRECPT_TYPE']
if precip_type == "๋น„":
icon = "โ˜”"
description = "๋น„"
elif precip_type == "๋ˆˆ":
icon = "โ„"
description = "๋ˆˆ"
elif precip_type == "๋น„/๋ˆˆ":
icon = "โ˜”โ„"
description = "๋น„/๋ˆˆ"
elif sky_status == "๋ง‘์Œ":
icon = "๐ŸŒž"
description = "๋ง‘์Œ"
elif sky_status in ["๊ตฌ๋ฆ„", "๊ตฌ๋ฆ„๋งŽ์Œ"]:
icon = "โ›…"
description = "๊ตฌ๋ฆ„" if sky_status == "๊ตฌ๋ฆ„" else "๊ตฌ๋ฆ„<br>๋งŽ์Œ"
elif sky_status == "ํ๋ฆผ":
icon = "โ˜๏ธ"
description = "ํ๋ฆผ"
else:
icon = "โ˜€๏ธ"
description = "์ •๋ณด์—†์Œ"
weather_icons.append(icon)
weather_descriptions.append(description)
df = pd.DataFrame({
'์‹œ๊ฐ„': times,
'๊ธฐ์˜จ': temps,
'๋‚ ์”จ': weather_icons,
'์„ค๋ช…': weather_descriptions,
'FCST_DT': [f['FCST_DT'] for f in valid_forecast_data]
})
fig = px.line(df, x='์‹œ๊ฐ„', y='๊ธฐ์˜จ', markers=True)
# Add nighttime overlay (18:00-06:00)
for i in range(len(times)):
hour = int(times[i].replace('์‹œ', ''))
if hour >= 18 or hour < 6:
fig.add_vrect(
x0=times[i],
x1=times[i+1] if i < len(times)-1 else times[-1],
fillcolor='rgba(0, 0, 0, 0.1)',
layer='below',
line_width=0,
annotation_text="",
annotation_position="top left"
)
# ๋…น์ƒ‰ ์„ธ๋กœ์„  ์ถ”๊ฐ€ ๋ฐ "ํ˜„์žฌ" ํ…์ŠคํŠธ ํ‘œ์‹œ
fig.add_vline(x=times[0], line_width=2, line_dash="dash", line_color="green")
fig.add_annotation(
x=times[0],
y=max(temps) + 4,
text="<b>ํ˜„์žฌ</b>",
showarrow=True,
arrowhead=2,
)
bold_times = ["00์‹œ", "06์‹œ", "12์‹œ", "18์‹œ", "24์‹œ"]
for time in bold_times:
if time in times:
index = times.index(time)
fig.add_annotation(
x=time,
y=min(temps) - 3,
text=time,
showarrow=False,
font=dict(size=30, color="black", family="Arial")
)
fig.add_vline(x='12์‹œ', line_width=2, line_dash="dash", line_color="rgba(0,0,0,0.5)")
# ์˜ค๋Š˜๊ณผ ๋‚ด์ผ, ์˜ค์ „๊ณผ ์˜คํ›„ ํ…์ŠคํŠธ๋Š” ํ•ด๋‹น ์‹œ๊ฐ„๋Œ€์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ํ‘œ์‹œ
time_set = set(times)
current_date = datetime.utcnow() + timedelta(hours=9) # KST
current_hour = current_date.hour
if '11์‹œ' in time_set:
fig.add_annotation(x='11์‹œ', y=max(temps) + 4, text="์˜ค์ „", showarrow=False, font=dict(size=24))
if '13์‹œ' in time_set:
fig.add_annotation(x='13์‹œ', y=max(temps) + 4, text="์˜คํ›„", showarrow=False, font=dict(size=24))
# ์‹œ๊ฐ„ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ๋ผ๊ณ  ๊ฐ€์ •
for i, time in enumerate(times):
hour = int(time.replace('์‹œ', ''))
# ํ˜„์žฌ ์‹œ๊ฐ์ด 23์‹œ์ด๊ณ , times[0]์ด 00์‹œ๋ผ๋ฉด ์ฒซ ๋ฒˆ์งธ 23์‹œ๊ฐ€ ์˜ค๋Š˜ 23์‹œ
if hour == 23 and times[0] == '00์‹œ':
if i == 0: # ์ฒซ ๋ฒˆ์งธ 23์‹œ (์˜ค๋Š˜ 23์‹œ)
fig.add_annotation(x=time, y=max(temps) + 4, text="์˜ค๋Š˜", showarrow=False, font=dict(size=24))
# 01์‹œ๋Š” ๋‹ค์Œ ๋‚ ์ด๋ฏ€๋กœ "๋‚ด์ผ" ํ‘œ์‹œ (00์‹œ ๋‹ค์Œ์— ์˜ค๋Š” 01์‹œ)
if hour == 1 and i > 0 and times[i-1] == '00์‹œ':
fig.add_annotation(x=time, y=max(temps) + 4, text="๋‚ด์ผ", showarrow=False, font=dict(size=24))
fig.update_traces(
line_color='#FF6B6B',
marker=dict(size=10, color='#FF6B6B'),
textposition="top center",
mode='lines+markers+text',
text=[f"<b>{int(round(temp))}ยฐ</b>" for temp in df['๊ธฐ์˜จ']],
textfont=dict(size=24)
)
for i, (icon, description) in enumerate(zip(weather_icons, weather_descriptions)):
fig.add_annotation(
x=times[i],
y=max(temps) + 3,
text=f"{icon}",
showarrow=False,
font=dict(size=30)
)
fig.add_annotation(
x=times[i],
y=max(temps) + 2,
text=f"{description}",
showarrow=False,
font=dict(size=16),
textangle=0
)
for date_change in date_changes:
fig.add_vline(
x=times[date_change],
line_width=2,
line_dash="dash",
line_color="rgba(255, 0, 0, 0.7)"
)
fig.update_layout(
title=None,
xaxis_title='',
yaxis_title=None, #'๊ธฐ์˜จ (ยฐC)',
height=600,
width=7200,
showlegend=False,
plot_bgcolor='rgba(255,255,255,0.9)',
paper_bgcolor='rgba(0,0,0,0)',
margin=dict(l=50, r=50, t=0, b=0),
xaxis=dict(
tickangle=0,
tickfont=dict(size=14),
gridcolor='rgba(0,0,0,0.1)',
dtick=1,
tickmode='array',
ticktext=[f"{i:02d}์‹œ" for i in range(24)],
tickvals=[f"{i:02d}์‹œ" for i in range(24)]
),
yaxis=dict(
tickfont=dict(size=14),
gridcolor='rgba(0,0,0,0.1)',
showticklabels=True,
tickformat='d',
ticksuffix='ยฐC',
automargin=True,
rangemode='tozero'
)
)
st.plotly_chart(fig, use_container_width=True)
# ๋‚ ์”จ ์˜ˆ๋ณด ์ƒ์„ฑ ๋ฐ ํ‘œ์‹œ ๋ถ€๋ถ„์„ ์„ธ์…˜ ์ƒํƒœ๋กœ ๊ด€๋ฆฌ
if 'weather_forecast' not in st.session_state:
client = OpenAI(
api_key="glhf_9ea0e0babe1e45353dd03b44cb979e22",
base_url="https://glhf.chat/api/openai/v1",
http_client=httpx.Client(
follow_redirects=True,
timeout=30.0
)
)
# ํ˜„์žฌ ์‹œ๊ฐ„๊ณผ ์˜ˆ๋ณด ์‹œ๊ฐ„ ์ •๋ณด ์ถ”๊ฐ€
current_time = datetime.utcnow() + timedelta(hours=9)
current_date_str = current_time.strftime('%Y๋…„ %m์›” %d์ผ')
tomorrow_date_str = (current_time + timedelta(days=1)).strftime('%Y๋…„ %m์›” %d์ผ')
forecast_data_str = "\n".join([
f"[{f['FCST_DT'][:4]}๋…„ {f['FCST_DT'][4:6]}์›” {f['FCST_DT'][6:8]}์ผ {f['FCST_DT'][8:10]}์‹œ] {temp}๋„, {description}"
for f, time, temp, description in zip(valid_forecast_data, times, temps, weather_descriptions)
])
response = client.chat.completions.create(
model="hf:Nexusflow/Athene-V2-Chat",
messages=[
{"role": "system", "content": "๋‹น์‹ ์€ ๋‚ ์”จ ์˜ˆ๋ณด๊ด€์ž…๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์‹œ๊ฐ„๋Œ€๋ณ„ ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ •ํ™•ํ•œ ๋‚ ์”จ ์˜ˆ๋ณด๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”."},
{"role": "user", "content": f"""ํ˜„์žฌ ์‹œ๊ฐ์€ {current_time.strftime('%H์‹œ %M๋ถ„')}์ž…๋‹ˆ๋‹ค.
๋‹ค์Œ FCST_DT์˜ ์‹œ๊ฐ„๋Œ€๋ณ„ ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ณ  ์‹ค์ œ ๋‚ ์”จ ์ƒํ™ฉ์— ๋งž๋Š” ์ •ํ™•ํ•œ ๋‚ ์”จ ์˜ˆ๋ณด๋ฅผ 200์ž์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฌธ์žฅ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”. ๋น„๋‚˜ ๋ˆˆ ์˜ˆ๋ณด๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์šฐ์‚ฐ์„ ์ค€๋น„ํ•˜๋„๋ก ์•ˆ๋‚ดํ•ด์ฃผ์„ธ์š”. ์˜ท์ฐจ๋ฆผ์€ ๋‹ค์Œ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.
27ยฐC์ด์ƒ: ๋ฐ˜ํŒ”ํ‹ฐ, ๋ฐ˜๋ฐ”์ง€, ๋ฏผ์†Œ๋งค
23ยฐC~26ยฐC: ์–‡์€ ์…”์ธ , ๋ฐ˜ํŒ”ํ‹ฐ, ๋ฐ˜๋ฐ”์ง€, ๋ฉด๋ฐ”์ง€
20ยฐC~22ยฐC: ์–‡์€ ๊ฐ€๋””๊ฑด, ๊ธดํŒ”ํ‹ฐ, ๊ธด๋ฐ”์ง€
17ยฐC~19ยฐC: ์–‡์€ ๋‹ˆํŠธ, ๊ฐ€๋””๊ฑด, ๋งจํˆฌ๋งจ, ์–‡์€ ์ž์ผ“, ๊ธด๋ฐ”์ง€
12ยฐC~16ยฐC: ์ž์ผ“, ๊ฐ€๋””๊ฑด, ์•ผ์ƒ, ๋งจํˆฌ๋งจ, ๋‹ˆํŠธ, ์Šคํƒ€ํ‚น, ๊ธด๋ฐ”์ง€
9ยฐC~11ยฐC: ํŠธ๋ Œ์น˜์ฝ”ํŠธ, ์•ผ์ƒ, ๊ฐ€์ฃฝ ์ž์ผ“, ์Šคํƒ€ํ‚น, ๊ธด๋ฐ”์ง€
5ยฐC~8ยฐC: ์ฝ”ํŠธ, ํžˆํŠธํ…, ๋‹ˆํŠธ, ๊ธด๋ฐ”์ง€
4ยฐC์ดํ•˜: ํŒจ๋”ฉ, ๋‘๊บผ์šด ์ฝ”ํŠธ, ๋ชฉ๋„๋ฆฌ, ๊ธฐ๋ชจ์ œํ’ˆ
์‹œ๊ฐ„๋Œ€๋ณ„ ๋‚ ์”จ ๋ฐ์ดํ„ฐ:
{forecast_data_str}"""}
]
)
st.session_state.weather_forecast = response.choices[0].message.content
# ์ €์žฅ๋œ ๋‚ ์”จ ์˜ˆ๋ณด ํ‘œ์‹œ
st.markdown(f'''
<div class="scroll-container">
<div class="scroll-text">{st.session_state.weather_forecast}</div>
</div>
''', unsafe_allow_html=True)
# ์Šคํฌ๋กค ํ…์ŠคํŠธ ์œ„์— ๋ฒ„ํŠผ์ด ์˜ค๋„๋ก ๋งˆ์ง„ ์ถ”๊ฐ€
st.markdown('''
<div style="margin-bottom: 10px;">
''', unsafe_allow_html=True)
# ์šฐ๋ฆฌ์ง‘ ๋‚ ์”จ ์ •๋ณด๋กœ ๋Œ์•„๊ฐ€๊ธฐ ๋ฒ„ํŠผ ์ถ”๊ฐ€
st.button("์šฐ๋ฆฌ์ง‘ ๋‚ ์”จ ์ •๋ณด๋กœ ๋Œ์•„๊ฐ€๊ธฐ", on_click=lambda: st.session_state.update({'current_section': 'weather'}))
st.markdown('</div>', unsafe_allow_html=True)
def main():
if 'current_section' not in st.session_state:
st.session_state.current_section = 'weather'
st.session_state.last_api_call = 0
st.session_state.weather_data = None
# ํ˜„์žฌ ์‹œ๊ฐ„์„ ์„œ์šธ ์‹œ๊ฐ„์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
current_time = datetime.utcnow() + timedelta(hours=9)
current_timestamp = current_time.timestamp()
# ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ ์ฒดํฌ
if 'last_api_call' not in st.session_state:
st.session_state.last_api_call = 0
time_since_last_call = current_timestamp - st.session_state.last_api_call
# ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ์„ ์œ„ํ•œ placeholder
refresh_placeholder = st.empty()
# ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
if not st.session_state.weather_data or time_since_last_call >= 300:
try:
new_data = get_weather_data()
if new_data: # ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ›์•„์™”์„ ๋•Œ๋งŒ ์—…๋ฐ์ดํŠธ
st.session_state.weather_data = new_data
st.session_state.last_api_call = current_timestamp
st.rerun() # ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
except Exception as e:
st.error(f"Failed to refresh data: {str(e)}")
data = st.session_state.weather_data
if data:
pm10_value = data['PM10']
background_color = get_background_color(pm10_value)
st.markdown(f"""
<style>
.stApp {{
background-color: {background_color};
}}
</style>
""", unsafe_allow_html=True)
if st.session_state.current_section == 'weather':
show_weather_info(data)
else:
show_temperature_graph(data)
# ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ์„ ์œ„ํ•œ ํƒ€์ด๋จธ
with refresh_placeholder:
if time_since_last_call < 300:
remaining_time = 300 - time_since_last_call
time.sleep(min(remaining_time, 300)) # ๊ทธ๋ž˜ํ”„ 5๋ถ„๋งˆ๋‹ค ๋ Œ๋”๋ง
st.rerun()
if __name__ == "__main__":
main()