Delete app-backup.py
Browse files- app-backup.py +0 -947
app-backup.py
DELETED
|
@@ -1,947 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
AI ๊ธฐ๋ฐ ์๊ถ ๋ถ์ ์์คํ
- ์คํธ๋ฆฌ๋ฐ ๊ฐํ ๋ฒ์
|
| 3 |
-
Dataset: https://huggingface.co/datasets/ginipick/market
|
| 4 |
-
"""
|
| 5 |
-
import gradio as gr
|
| 6 |
-
import pandas as pd
|
| 7 |
-
import numpy as np
|
| 8 |
-
from typing import Dict, List, Tuple
|
| 9 |
-
import json
|
| 10 |
-
from datasets import load_dataset
|
| 11 |
-
import plotly.express as px
|
| 12 |
-
import plotly.graph_objects as go
|
| 13 |
-
from plotly.subplots import make_subplots
|
| 14 |
-
import folium
|
| 15 |
-
from folium.plugins import HeatMap, MarkerCluster
|
| 16 |
-
import requests
|
| 17 |
-
from collections import Counter
|
| 18 |
-
import re
|
| 19 |
-
import os
|
| 20 |
-
import time
|
| 21 |
-
|
| 22 |
-
# ============================================================================
|
| 23 |
-
# ๋ฐ์ดํฐ ๋ก๋ ํด๋์ค
|
| 24 |
-
# ============================================================================
|
| 25 |
-
|
| 26 |
-
class MarketDataLoader:
|
| 27 |
-
"""ํ๊น
ํ์ด์ค ์๊ถ ๋ฐ์ดํฐ ๋ก๋"""
|
| 28 |
-
|
| 29 |
-
REGIONS = {
|
| 30 |
-
'์์ธ': '์์ธ_202506', '๊ฒฝ๊ธฐ': '๊ฒฝ๊ธฐ_202506', '๋ถ์ฐ': '๋ถ์ฐ_202506',
|
| 31 |
-
'๋๊ตฌ': '๋๊ตฌ_202506', '์ธ์ฒ': '์ธ์ฒ_202506', '๊ด์ฃผ': '๊ด์ฃผ_202506',
|
| 32 |
-
'๋์ ': '๋์ _202506', '์ธ์ฐ': '์ธ์ฐ_202506', '์ธ์ข
': '์ธ์ข
_202506',
|
| 33 |
-
'๊ฒฝ๋จ': '๊ฒฝ๋จ_202506', '๊ฒฝ๋ถ': '๊ฒฝ๋ถ_202506', '์ ๋จ': '์ ๋จ_202506',
|
| 34 |
-
'์ ๋ถ': '์ ๋ถ_202506', '์ถฉ๋จ': '์ถฉ๋จ_202506', '์ถฉ๋ถ': '์ถฉ๋ถ_202506',
|
| 35 |
-
'๊ฐ์': '๊ฐ์_202506', '์ ์ฃผ': '์ ์ฃผ_202506'
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
# ์
์ข
๋ถ๋ฅ ๋งคํ
|
| 39 |
-
CATEGORY_MAPPING = {
|
| 40 |
-
'G2': '์๋งค์
',
|
| 41 |
-
'I1': '์๋ฐ์
',
|
| 42 |
-
'I2': '์์์ ์
',
|
| 43 |
-
'L1': '๋ถ๋์ฐ์
',
|
| 44 |
-
'M1': '์ ๋ฌธ/๊ณผํ/๊ธฐ์ ',
|
| 45 |
-
'N1': '์ฌ์
์ง์/์๋',
|
| 46 |
-
'P1': '๊ต์ก์๋น์ค',
|
| 47 |
-
'Q1': '๋ณด๊ฑด์๋ฃ',
|
| 48 |
-
'R1': '์์ /์คํฌ์ธ /์ฌ๊ฐ',
|
| 49 |
-
'S2': '์๋ฆฌ/๊ฐ์ธ์๋น์ค'
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
@staticmethod
|
| 53 |
-
def load_region_data(region: str, sample_size: int = 30000) -> pd.DataFrame:
|
| 54 |
-
"""์ง์ญ๋ณ ๋ฐ์ดํฐ ๋ก๋"""
|
| 55 |
-
try:
|
| 56 |
-
file_name = f"์์๊ณต์ธ์์ฅ์งํฅ๊ณต๋จ_์๊ฐ(์๊ถ)์ ๋ณด_{MarketDataLoader.REGIONS[region]}.csv"
|
| 57 |
-
dataset = load_dataset("ginipick/market", data_files=file_name, split="train")
|
| 58 |
-
df = dataset.to_pandas()
|
| 59 |
-
|
| 60 |
-
if len(df) > sample_size:
|
| 61 |
-
df = df.sample(n=sample_size, random_state=42)
|
| 62 |
-
|
| 63 |
-
return df
|
| 64 |
-
except Exception as e:
|
| 65 |
-
print(f"๋ฐ์ดํฐ ๋ก๋ ์คํจ: {str(e)}")
|
| 66 |
-
return pd.DataFrame()
|
| 67 |
-
|
| 68 |
-
@staticmethod
|
| 69 |
-
def load_multiple_regions(regions: List[str], sample_per_region: int = 30000) -> pd.DataFrame:
|
| 70 |
-
"""์ฌ๋ฌ ์ง์ญ ๋ฐ์ดํฐ ๋ก๋"""
|
| 71 |
-
dfs = []
|
| 72 |
-
for region in regions:
|
| 73 |
-
df = MarketDataLoader.load_region_data(region, sample_per_region)
|
| 74 |
-
if not df.empty:
|
| 75 |
-
dfs.append(df)
|
| 76 |
-
|
| 77 |
-
if dfs:
|
| 78 |
-
return pd.concat(dfs, ignore_index=True)
|
| 79 |
-
return pd.DataFrame()
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
# ============================================================================
|
| 83 |
-
# ์๊ถ ๋ถ์ ํด๋์ค
|
| 84 |
-
# ============================================================================
|
| 85 |
-
|
| 86 |
-
class MarketAnalyzer:
|
| 87 |
-
"""์๊ถ ๋ฐ์ดํฐ ๋ถ์ ์์ง"""
|
| 88 |
-
|
| 89 |
-
def __init__(self, df: pd.DataFrame):
|
| 90 |
-
self.df = df
|
| 91 |
-
self.prepare_data()
|
| 92 |
-
|
| 93 |
-
def prepare_data(self):
|
| 94 |
-
"""๋ฐ์ดํฐ ์ ์ฒ๋ฆฌ"""
|
| 95 |
-
if '๊ฒฝ๋' in self.df.columns:
|
| 96 |
-
self.df['๊ฒฝ๋'] = pd.to_numeric(self.df['๊ฒฝ๋'], errors='coerce')
|
| 97 |
-
if '์๋' in self.df.columns:
|
| 98 |
-
self.df['์๋'] = pd.to_numeric(self.df['์๋'], errors='coerce')
|
| 99 |
-
self.df = self.df.dropna(subset=['๊ฒฝ๋', '์๋'])
|
| 100 |
-
|
| 101 |
-
# ์ธต ์ ๋ณด ์ ์
|
| 102 |
-
if '์ธต์ ๋ณด' in self.df.columns:
|
| 103 |
-
self.df['์ธต์ ๋ณด_์ซ์'] = self.df['์ธต์ ๋ณด'].apply(self._parse_floor)
|
| 104 |
-
|
| 105 |
-
def _parse_floor(self, floor_str):
|
| 106 |
-
"""์ธต ์ ๋ณด๋ฅผ ์ซ์๋ก ๋ณํ"""
|
| 107 |
-
if pd.isna(floor_str):
|
| 108 |
-
return None
|
| 109 |
-
floor_str = str(floor_str)
|
| 110 |
-
if '์งํ' in floor_str or 'B' in floor_str:
|
| 111 |
-
match = re.search(r'\d+', floor_str)
|
| 112 |
-
return -int(match.group()) if match else -1
|
| 113 |
-
elif '1์ธต' in floor_str or floor_str == '1':
|
| 114 |
-
return 1
|
| 115 |
-
else:
|
| 116 |
-
match = re.search(r'\d+', floor_str)
|
| 117 |
-
return int(match.group()) if match else None
|
| 118 |
-
|
| 119 |
-
def get_comprehensive_insights(self) -> List[Dict]:
|
| 120 |
-
"""ํฌ๊ด์ ์ธ ์ธ์ฌ์ดํธ ์์ฑ"""
|
| 121 |
-
insights = []
|
| 122 |
-
|
| 123 |
-
# 1. ์
์ข
๋ณ ์ ํฌ ์ (์๊ถ์
์ข
์ค๋ถ๋ฅ)
|
| 124 |
-
insights.append(self._create_top_categories_chart())
|
| 125 |
-
|
| 126 |
-
# 2. ๋๋ถ๋ฅ๋ณ ๋ถํฌ (ํ์ด ์ฐจํธ)
|
| 127 |
-
insights.append(self._create_major_category_pie())
|
| 128 |
-
|
| 129 |
-
# 3. ์ธต๋ณ ๋ถํฌ ์์ธ ๋ถ์
|
| 130 |
-
insights.append(self._create_floor_analysis())
|
| 131 |
-
|
| 132 |
-
# 4. ์ง์ญ๋ณ ์
์ข
๋ค์์ฑ ์ง์
|
| 133 |
-
insights.append(self._create_diversity_index())
|
| 134 |
-
|
| 135 |
-
# 5. ํ๋์ฐจ์ด์ฆ vs ๊ฐ์ธ์ฌ์
์ ๋ถ์
|
| 136 |
-
insights.append(self._create_franchise_analysis())
|
| 137 |
-
|
| 138 |
-
# 6. ์
์ข
๋ณ ์ธต ์ ํธ๋
|
| 139 |
-
insights.append(self._create_floor_preference())
|
| 140 |
-
|
| 141 |
-
# 7. ์๊ตฐ๊ตฌ๋ณ ์๊ถ ๋ฐ์ง๋ TOP 20
|
| 142 |
-
insights.append(self._create_district_density())
|
| 143 |
-
|
| 144 |
-
# 8. ์
์ข
์๊ด๊ด๊ณ (๊ฐ์ ์ง์ญ์ ์์ฃผ ๋ํ๋๋ ์
์ข
)
|
| 145 |
-
insights.append(self._create_category_correlation())
|
| 146 |
-
|
| 147 |
-
# 9. ์๋ถ๋ฅ ํธ๋ ๋ (์์ 20๊ฐ)
|
| 148 |
-
insights.append(self._create_subcategory_trends())
|
| 149 |
-
|
| 150 |
-
# 10. ์ง์ญ๋ณ ํนํ ์
์ข
|
| 151 |
-
insights.append(self._create_regional_specialization())
|
| 152 |
-
|
| 153 |
-
return insights
|
| 154 |
-
|
| 155 |
-
def _create_top_categories_chart(self) -> Dict:
|
| 156 |
-
"""์
์ข
๋ณ ์ ํฌ ์ ์ฐจํธ"""
|
| 157 |
-
if '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' not in self.df.columns:
|
| 158 |
-
return None
|
| 159 |
-
|
| 160 |
-
top_categories = self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].value_counts().head(15)
|
| 161 |
-
fig = px.bar(
|
| 162 |
-
x=top_categories.values,
|
| 163 |
-
y=top_categories.index,
|
| 164 |
-
orientation='h',
|
| 165 |
-
labels={'x': '์ ํฌ ์', 'y': '์
์ข
'},
|
| 166 |
-
title='๐ ์์ ์
์ข
TOP 15',
|
| 167 |
-
color=top_categories.values,
|
| 168 |
-
color_continuous_scale='blues'
|
| 169 |
-
)
|
| 170 |
-
fig.update_layout(showlegend=False, height=500)
|
| 171 |
-
return {'type': 'plot', 'data': fig, 'title': '์
์ข
๋ณ ์ ํฌ ์ ๋ถ์'}
|
| 172 |
-
|
| 173 |
-
def _create_major_category_pie(self) -> Dict:
|
| 174 |
-
"""๋๋ถ๋ฅ๋ณ ๋ถํฌ"""
|
| 175 |
-
if '์๊ถ์
์ข
๋๋ถ๋ฅ์ฝ๋' not in self.df.columns:
|
| 176 |
-
return None
|
| 177 |
-
|
| 178 |
-
major_counts = self.df['์๊ถ์
์ข
๋๋ถ๋ฅ์ฝ๋'].value_counts()
|
| 179 |
-
labels = [MarketDataLoader.CATEGORY_MAPPING.get(code, code) for code in major_counts.index]
|
| 180 |
-
|
| 181 |
-
fig = px.pie(
|
| 182 |
-
values=major_counts.values,
|
| 183 |
-
names=labels,
|
| 184 |
-
title='๐ ์
์ข
๋๋ถ๋ฅ ๋ถํฌ',
|
| 185 |
-
hole=0.4,
|
| 186 |
-
color_discrete_sequence=px.colors.qualitative.Set3
|
| 187 |
-
)
|
| 188 |
-
fig.update_traces(textposition='inside', textinfo='percent+label')
|
| 189 |
-
return {'type': 'plot', 'data': fig, 'title': '๋๋ถ๋ฅ๋ณ ์๊ถ ๊ตฌ์ฑ'}
|
| 190 |
-
|
| 191 |
-
def _create_floor_analysis(self) -> Dict:
|
| 192 |
-
"""์ธต๋ณ ๋ถํฌ ์์ธ ๋ถ์"""
|
| 193 |
-
if '์ธต์ ๋ณด_์ซ์' not in self.df.columns:
|
| 194 |
-
return None
|
| 195 |
-
|
| 196 |
-
floor_data = self.df['์ธต์ ๋ณด_์ซ์'].dropna()
|
| 197 |
-
floor_counts = floor_data.value_counts().sort_index()
|
| 198 |
-
|
| 199 |
-
# ์งํ, 1์ธต, 2์ธต ์ด์์ผ๋ก ๊ทธ๋ฃนํ
|
| 200 |
-
underground = floor_counts[floor_counts.index < 0].sum()
|
| 201 |
-
first_floor = floor_counts.get(1, 0)
|
| 202 |
-
upper_floors = floor_counts[floor_counts.index > 1].sum()
|
| 203 |
-
|
| 204 |
-
fig = go.Figure(data=[
|
| 205 |
-
go.Bar(
|
| 206 |
-
x=['์งํ', '1์ธต', '2์ธต ์ด์'],
|
| 207 |
-
y=[underground, first_floor, upper_floors],
|
| 208 |
-
text=[f'{underground:,}<br>({underground/len(floor_data)*100:.1f}%)',
|
| 209 |
-
f'{first_floor:,}<br>({first_floor/len(floor_data)*100:.1f}%)',
|
| 210 |
-
f'{upper_floors:,}<br>({upper_floors/len(floor_data)*100:.1f}%)'],
|
| 211 |
-
textposition='auto',
|
| 212 |
-
marker_color=['#e74c3c', '#3498db', '#95a5a6']
|
| 213 |
-
)
|
| 214 |
-
])
|
| 215 |
-
fig.update_layout(
|
| 216 |
-
title='๐ข ์ธต๋ณ ์ ํฌ ๋ถํฌ (์งํ vs 1์ธต vs ์์ธต)',
|
| 217 |
-
xaxis_title='์ธต ๊ตฌ๋ถ',
|
| 218 |
-
yaxis_title='์ ํฌ ์',
|
| 219 |
-
height=400
|
| 220 |
-
)
|
| 221 |
-
return {'type': 'plot', 'data': fig, 'title': '์ธต๋ณ ์
์ง ๋ถ์'}
|
| 222 |
-
|
| 223 |
-
def _create_diversity_index(self) -> Dict:
|
| 224 |
-
"""์ง์ญ๋ณ ์
์ข
๋ค์์ฑ ์ง์"""
|
| 225 |
-
if '์๊ตฐ๊ตฌ๋ช
' not in self.df.columns or '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' not in self.df.columns:
|
| 226 |
-
return None
|
| 227 |
-
|
| 228 |
-
# ๊ฐ ์๊ตฐ๊ตฌ๋ณ ์
์ข
๋ค์์ฑ ๊ณ์ฐ (์
์ข
์ / ์ ์ฒด ์ ํฌ ์)
|
| 229 |
-
diversity_data = []
|
| 230 |
-
for district in self.df['์๊ตฐ๊ตฌ๋ช
'].unique()[:20]: # ์์ 20๊ฐ
|
| 231 |
-
district_df = self.df[self.df['์๊ตฐ๊ตฌ๋ช
'] == district]
|
| 232 |
-
num_categories = district_df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].nunique()
|
| 233 |
-
total_stores = len(district_df)
|
| 234 |
-
diversity_score = (num_categories / total_stores) * 100
|
| 235 |
-
diversity_data.append({
|
| 236 |
-
'์๊ตฐ๊ตฌ': district,
|
| 237 |
-
'๋ค์์ฑ ์ง์': diversity_score,
|
| 238 |
-
'์
์ข
์': num_categories,
|
| 239 |
-
'์ด ์ ํฌ': total_stores
|
| 240 |
-
})
|
| 241 |
-
|
| 242 |
-
diversity_df = pd.DataFrame(diversity_data).sort_values('๋ค์์ฑ ์ง์', ascending=False).head(15)
|
| 243 |
-
|
| 244 |
-
fig = px.bar(
|
| 245 |
-
diversity_df,
|
| 246 |
-
x='๋ค์์ฑ ์ง์',
|
| 247 |
-
y='์๊ตฐ๊ตฌ',
|
| 248 |
-
orientation='h',
|
| 249 |
-
title='๐ ์ง์ญ๋ณ ์
์ข
๋ค์์ฑ ์ง์ (์์ 15๊ฐ)',
|
| 250 |
-
labels={'๋ค์์ฑ ์ง์': '๋ค์์ฑ ์ง์ (%)', '์๊ตฐ๊ตฌ': '์ง์ญ'},
|
| 251 |
-
color='๋ค์์ฑ ์ง์',
|
| 252 |
-
color_continuous_scale='viridis',
|
| 253 |
-
hover_data=['์
์ข
์', '์ด ์ ํฌ']
|
| 254 |
-
)
|
| 255 |
-
fig.update_layout(height=500)
|
| 256 |
-
return {'type': 'plot', 'data': fig, 'title': '์
์ข
๋ค์์ฑ ๋ถ์'}
|
| 257 |
-
|
| 258 |
-
def _create_franchise_analysis(self) -> Dict:
|
| 259 |
-
"""ํ๋์ฐจ์ด์ฆ vs ๊ฐ์ธ์ฌ์
์ ๋ถ์"""
|
| 260 |
-
if '์ํธ๋ช
' not in self.df.columns:
|
| 261 |
-
return None
|
| 262 |
-
|
| 263 |
-
# ์ฃผ์ ํ๋์ฐจ์ด์ฆ ํค์๋
|
| 264 |
-
franchise_keywords = [
|
| 265 |
-
'CU', 'GS25', '์ธ๋ธ์ผ๋ ๋ธ', '์ด๋งํธ24', '๋ฏธ๋์คํฑ',
|
| 266 |
-
'์คํ๋ฒ
์ค', 'ํฌ์ธํ๋ ์ด์ค', '์ด๋์ผ', 'ํ์คํ์ค', '์ปคํผ๋น',
|
| 267 |
-
'๋งฅ๋๋ ๋', '๋ฒ๊ฑฐํน', '๋กฏ๋ฐ๋ฆฌ์', 'KFC', '๋ง์คํฐ์น',
|
| 268 |
-
'BBQ', '๊ต์ด', '๊ตฝ๋ค', 'bhc', '๋ค๋ค์นํจ'
|
| 269 |
-
]
|
| 270 |
-
|
| 271 |
-
franchise_counts = {}
|
| 272 |
-
for keyword in franchise_keywords:
|
| 273 |
-
count = self.df['์ํธ๋ช
'].str.contains(keyword, case=False, na=False).sum()
|
| 274 |
-
if count > 0:
|
| 275 |
-
franchise_counts[keyword] = count
|
| 276 |
-
|
| 277 |
-
total_franchise = sum(franchise_counts.values())
|
| 278 |
-
total_stores = len(self.df)
|
| 279 |
-
individual_stores = total_stores - total_franchise
|
| 280 |
-
|
| 281 |
-
# ํ์ด ์ฐจํธ
|
| 282 |
-
fig = make_subplots(
|
| 283 |
-
rows=1, cols=2,
|
| 284 |
-
specs=[[{'type': 'pie'}, {'type': 'bar'}]],
|
| 285 |
-
subplot_titles=('์ ์ฒด ๋น์จ', 'ํ๋์ฐจ์ด์ฆ๋ณ ์ ํฌ ์')
|
| 286 |
-
)
|
| 287 |
-
|
| 288 |
-
# ์ ์ฒด ๋น์จ
|
| 289 |
-
fig.add_trace(
|
| 290 |
-
go.Pie(
|
| 291 |
-
labels=['๊ฐ์ธ์ฌ์
์', 'ํ๋์ฐจ์ด์ฆ'],
|
| 292 |
-
values=[individual_stores, total_franchise],
|
| 293 |
-
hole=0.3,
|
| 294 |
-
marker_colors=['#3498db', '#e74c3c']
|
| 295 |
-
),
|
| 296 |
-
row=1, col=1
|
| 297 |
-
)
|
| 298 |
-
|
| 299 |
-
# ํ๋์ฐจ์ด์ฆ๋ณ
|
| 300 |
-
top_franchises = dict(sorted(franchise_counts.items(), key=lambda x: x[1], reverse=True)[:10])
|
| 301 |
-
fig.add_trace(
|
| 302 |
-
go.Bar(
|
| 303 |
-
x=list(top_franchises.keys()),
|
| 304 |
-
y=list(top_franchises.values()),
|
| 305 |
-
marker_color='#e74c3c'
|
| 306 |
-
),
|
| 307 |
-
row=1, col=2
|
| 308 |
-
)
|
| 309 |
-
|
| 310 |
-
fig.update_layout(
|
| 311 |
-
title_text='๐ช ํ๋์ฐจ์ด์ฆ vs ๊ฐ์ธ์ฌ์
์ ๋ถ์',
|
| 312 |
-
showlegend=True,
|
| 313 |
-
height=400
|
| 314 |
-
)
|
| 315 |
-
return {'type': 'plot', 'data': fig, 'title': 'ํ๋์ฐจ์ด์ฆ ์ ์ ์จ'}
|
| 316 |
-
|
| 317 |
-
def _create_floor_preference(self) -> Dict:
|
| 318 |
-
"""์
์ข
๋ณ ์ธต ์ ํธ๋"""
|
| 319 |
-
if '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' not in self.df.columns or '์ธต์ ๋ณด_์ซ์' not in self.df.columns:
|
| 320 |
-
return None
|
| 321 |
-
|
| 322 |
-
# ์์ 10๊ฐ ์
์ข
์ ์ธต๋ณ ๋ถํฌ
|
| 323 |
-
top_categories = self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].value_counts().head(10).index
|
| 324 |
-
|
| 325 |
-
floor_pref_data = []
|
| 326 |
-
for category in top_categories:
|
| 327 |
-
cat_df = self.df[self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'] == category]
|
| 328 |
-
underground = (cat_df['์ธต์ ๋ณด_์ซ์'] < 0).sum()
|
| 329 |
-
first_floor = (cat_df['์ธต์ ๋ณด_์ซ์'] == 1).sum()
|
| 330 |
-
upper_floors = (cat_df['์ธต์ ๋ณด_์ซ์'] > 1).sum()
|
| 331 |
-
|
| 332 |
-
total = underground + first_floor + upper_floors
|
| 333 |
-
if total > 0:
|
| 334 |
-
floor_pref_data.append({
|
| 335 |
-
'์
์ข
': category,
|
| 336 |
-
'์งํ': (underground / total) * 100,
|
| 337 |
-
'1์ธต': (first_floor / total) * 100,
|
| 338 |
-
'2์ธต ์ด์': (upper_floors / total) * 100
|
| 339 |
-
})
|
| 340 |
-
|
| 341 |
-
pref_df = pd.DataFrame(floor_pref_data)
|
| 342 |
-
|
| 343 |
-
fig = go.Figure(data=[
|
| 344 |
-
go.Bar(name='์งํ', x=pref_df['์
์ข
'], y=pref_df['์งํ'], marker_color='#e74c3c'),
|
| 345 |
-
go.Bar(name='1์ธต', x=pref_df['์
์ข
'], y=pref_df['1์ธต'], marker_color='#3498db'),
|
| 346 |
-
go.Bar(name='2์ธต ์ด์', x=pref_df['์
์ข
'], y=pref_df['2์ธต ์ด์'], marker_color='#95a5a6')
|
| 347 |
-
])
|
| 348 |
-
|
| 349 |
-
fig.update_layout(
|
| 350 |
-
title='๐ฏ ์
์ข
๋ณ ์ธต ์ ํธ๋ (์์ 10๊ฐ)',
|
| 351 |
-
xaxis_title='์
์ข
',
|
| 352 |
-
yaxis_title='๋น์จ (%)',
|
| 353 |
-
barmode='stack',
|
| 354 |
-
height=500,
|
| 355 |
-
xaxis_tickangle=-45
|
| 356 |
-
)
|
| 357 |
-
return {'type': 'plot', 'data': fig, 'title': '์ธต๋ณ ์
์ง ์ ํธ๋'}
|
| 358 |
-
|
| 359 |
-
def _create_district_density(self) -> Dict:
|
| 360 |
-
"""์๊ตฐ๊ตฌ๋ณ ์๊ถ ๋ฐ์ง๋"""
|
| 361 |
-
if '์๊ตฐ๊ตฌ๋ช
' not in self.df.columns:
|
| 362 |
-
return None
|
| 363 |
-
|
| 364 |
-
district_counts = self.df['์๊ตฐ๊ตฌ๋ช
'].value_counts().head(20)
|
| 365 |
-
|
| 366 |
-
fig = px.bar(
|
| 367 |
-
x=district_counts.values,
|
| 368 |
-
y=district_counts.index,
|
| 369 |
-
orientation='h',
|
| 370 |
-
title='๐ ์๊ตฐ๊ตฌ๋ณ ์๊ถ ๋ฐ์ง๋ TOP 20',
|
| 371 |
-
labels={'x': '์ ํฌ ์', 'y': '์๊ตฐ๊ตฌ'},
|
| 372 |
-
color=district_counts.values,
|
| 373 |
-
color_continuous_scale='reds'
|
| 374 |
-
)
|
| 375 |
-
fig.update_layout(showlegend=False, height=600)
|
| 376 |
-
return {'type': 'plot', 'data': fig, 'title': '์ง์ญ๋ณ ๋ฐ์ง๋'}
|
| 377 |
-
|
| 378 |
-
def _create_category_correlation(self) -> Dict:
|
| 379 |
-
"""์
์ข
์๊ด๊ด๊ณ (๊ฐ์ ๋์ ์์ฃผ ๋ํ๋๋ ์
์ข
)"""
|
| 380 |
-
if 'ํ์ ๋๋ช
' not in self.df.columns or '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' not in self.df.columns:
|
| 381 |
-
return None
|
| 382 |
-
|
| 383 |
-
# ์์ 10๊ฐ ์
์ข
๋ง ๋ถ์
|
| 384 |
-
top_categories = self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].value_counts().head(10).index.tolist()
|
| 385 |
-
|
| 386 |
-
# ๊ฐ ๋๋ณ๋ก ์
์ข
์นด์ดํธ
|
| 387 |
-
correlation_matrix = []
|
| 388 |
-
for cat1 in top_categories:
|
| 389 |
-
row = []
|
| 390 |
-
for cat2 in top_categories:
|
| 391 |
-
# ๋ ์
์ข
์ด ๊ฐ์ ๋์ ์๋ ๊ฒฝ์ฐ์ ์
|
| 392 |
-
cat1_dongs = set(self.df[self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'] == cat1]['ํ์ ๋๋ช
'].unique())
|
| 393 |
-
cat2_dongs = set(self.df[self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'] == cat2]['ํ์ ๋๋ช
'].unique())
|
| 394 |
-
intersection = len(cat1_dongs & cat2_dongs)
|
| 395 |
-
union = len(cat1_dongs | cat2_dongs)
|
| 396 |
-
similarity = (intersection / union * 100) if union > 0 else 0
|
| 397 |
-
row.append(similarity)
|
| 398 |
-
correlation_matrix.append(row)
|
| 399 |
-
|
| 400 |
-
fig = go.Figure(data=go.Heatmap(
|
| 401 |
-
z=correlation_matrix,
|
| 402 |
-
x=top_categories,
|
| 403 |
-
y=top_categories,
|
| 404 |
-
colorscale='Blues',
|
| 405 |
-
text=np.round(correlation_matrix, 1),
|
| 406 |
-
texttemplate='%{text}',
|
| 407 |
-
textfont={"size": 10}
|
| 408 |
-
))
|
| 409 |
-
|
| 410 |
-
fig.update_layout(
|
| 411 |
-
title='๐ ์
์ข
์๊ด๊ด๊ณ ๋งคํธ๋ฆญ์ค (๊ฐ์ ์ง์ญ ๋์ ์ถํ์จ)',
|
| 412 |
-
xaxis_title='์
์ข
',
|
| 413 |
-
yaxis_title='์
์ข
',
|
| 414 |
-
height=600,
|
| 415 |
-
xaxis_tickangle=-45
|
| 416 |
-
)
|
| 417 |
-
return {'type': 'plot', 'data': fig, 'title': '์
์ข
๊ณต์กด ๋ถ์'}
|
| 418 |
-
|
| 419 |
-
def _create_subcategory_trends(self) -> Dict:
|
| 420 |
-
"""์๋ถ๋ฅ ํธ๋ ๋"""
|
| 421 |
-
if '์๊ถ์
์ข
์๋ถ๋ฅ๋ช
' not in self.df.columns:
|
| 422 |
-
return None
|
| 423 |
-
|
| 424 |
-
subcat_counts = self.df['์๊ถ์
์ข
์๋ถ๋ฅ๋ช
'].value_counts().head(20)
|
| 425 |
-
|
| 426 |
-
fig = px.treemap(
|
| 427 |
-
names=subcat_counts.index,
|
| 428 |
-
parents=[''] * len(subcat_counts),
|
| 429 |
-
values=subcat_counts.values,
|
| 430 |
-
title='๐ ์๋ถ๋ฅ ์
์ข
ํธ๋ ๋ TOP 20',
|
| 431 |
-
color=subcat_counts.values,
|
| 432 |
-
color_continuous_scale='greens'
|
| 433 |
-
)
|
| 434 |
-
fig.update_layout(height=600)
|
| 435 |
-
return {'type': 'plot', 'data': fig, 'title': '์ธ๋ถ ์
์ข
๋ถ์'}
|
| 436 |
-
|
| 437 |
-
def _create_regional_specialization(self) -> Dict:
|
| 438 |
-
"""์ง์ญ๋ณ ํนํ ์
์ข
"""
|
| 439 |
-
if '์๋๋ช
' not in self.df.columns or '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' not in self.df.columns:
|
| 440 |
-
return None
|
| 441 |
-
|
| 442 |
-
# ๊ฐ ์๋๋ณ ์์ 3๊ฐ ์
์ข
|
| 443 |
-
specialization_data = []
|
| 444 |
-
for region in self.df['์๋๋ช
'].unique():
|
| 445 |
-
region_df = self.df[self.df['์๋๋ช
'] == region]
|
| 446 |
-
top_categories = region_df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].value_counts().head(3)
|
| 447 |
-
for category, count in top_categories.items():
|
| 448 |
-
specialization_data.append({
|
| 449 |
-
'์ง์ญ': region,
|
| 450 |
-
'ํนํ์
์ข
': category,
|
| 451 |
-
'์ ํฌ์': count
|
| 452 |
-
})
|
| 453 |
-
|
| 454 |
-
spec_df = pd.DataFrame(specialization_data)
|
| 455 |
-
|
| 456 |
-
fig = px.sunburst(
|
| 457 |
-
spec_df,
|
| 458 |
-
path=['์ง์ญ', 'ํนํ์
์ข
'],
|
| 459 |
-
values='์ ํฌ์',
|
| 460 |
-
title='๐ฏ ์ง์ญ๋ณ ํนํ ์
์ข
(๊ฐ ์ง์ญ TOP 3)',
|
| 461 |
-
color='์ ํฌ์',
|
| 462 |
-
color_continuous_scale='oranges'
|
| 463 |
-
)
|
| 464 |
-
fig.update_layout(height=700)
|
| 465 |
-
return {'type': 'plot', 'data': fig, 'title': '์ง์ญ ํนํ ๋ถ์'}
|
| 466 |
-
|
| 467 |
-
def create_density_map(self, sample_size: int = 1000) -> str:
|
| 468 |
-
"""์ ํฌ ๋ฐ์ง๋ ์ง๋ ์์ฑ"""
|
| 469 |
-
df_sample = self.df.sample(n=min(sample_size, len(self.df)), random_state=42)
|
| 470 |
-
|
| 471 |
-
center_lat = df_sample['์๋'].mean()
|
| 472 |
-
center_lon = df_sample['๊ฒฝ๋'].mean()
|
| 473 |
-
|
| 474 |
-
m = folium.Map(location=[center_lat, center_lon], zoom_start=11, tiles='OpenStreetMap')
|
| 475 |
-
|
| 476 |
-
# ํํธ๋งต
|
| 477 |
-
heat_data = [[row['์๋'], row['๊ฒฝ๋']] for _, row in df_sample.iterrows()]
|
| 478 |
-
HeatMap(heat_data, radius=15, blur=25, max_zoom=13).add_to(m)
|
| 479 |
-
|
| 480 |
-
return m._repr_html_()
|
| 481 |
-
|
| 482 |
-
def analyze_for_llm(self) -> Dict:
|
| 483 |
-
"""LLM ์ปจํ
์คํธ์ฉ ๋ถ์ ๋ฐ์ดํฐ"""
|
| 484 |
-
context = {
|
| 485 |
-
'์ด_์ ํฌ_์': len(self.df),
|
| 486 |
-
'์ง์ญ_์': self.df['์๋๋ช
'].nunique() if '์๋๋ช
' in self.df.columns else 0,
|
| 487 |
-
'์
์ข
_์': self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].nunique() if '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' in self.df.columns else 0,
|
| 488 |
-
}
|
| 489 |
-
|
| 490 |
-
if '์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
' in self.df.columns:
|
| 491 |
-
context['์์_์
์ข
_5'] = self.df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].value_counts().head(5).to_dict()
|
| 492 |
-
|
| 493 |
-
if '์ธต์ ๋ณด_์ซ์' in self.df.columns:
|
| 494 |
-
first_floor_ratio = (self.df['์ธต์ ๋ณด_์ซ์'] == 1).sum() / len(self.df) * 100
|
| 495 |
-
context['1์ธต_๋น์จ'] = f"{first_floor_ratio:.1f}%"
|
| 496 |
-
|
| 497 |
-
return context
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
# ============================================================================
|
| 501 |
-
# LLM ์ฟผ๋ฆฌ ํ๋ก์ธ์ (์คํธ๋ฆฌ๋ฐ ์ง์)
|
| 502 |
-
# ============================================================================
|
| 503 |
-
|
| 504 |
-
class LLMQueryProcessor:
|
| 505 |
-
"""Fireworks AI ๊ธฐ๋ฐ ์์ฐ์ด ์ฒ๋ฆฌ (์คํธ๋ฆฌ๋ฐ ์ง์)"""
|
| 506 |
-
|
| 507 |
-
def __init__(self, api_key: str = None):
|
| 508 |
-
# ํ๊ฒฝ๋ณ์์์ API ํค ๊ฐ์ ธ์ค๊ธฐ
|
| 509 |
-
self.api_key = api_key or os.getenv("FIREWORKS_API_KEY")
|
| 510 |
-
self.base_url = "https://api.fireworks.ai/inference/v1/chat/completions"
|
| 511 |
-
|
| 512 |
-
if not self.api_key:
|
| 513 |
-
raise ValueError("โ FIREWORKS_API_KEY ํ๊ฒฝ๋ณ์๋ฅผ ์ค์ ํ๊ฑฐ๋ API ํค๋ฅผ ์
๋ ฅํด์ฃผ์ธ์!")
|
| 514 |
-
|
| 515 |
-
def process_query_stream(self, query: str, data_context: Dict, chat_history: List = None):
|
| 516 |
-
"""์์ฐ์ด ์ฟผ๋ฆฌ ์ฒ๋ฆฌ (์คํธ๋ฆฌ๋ฐ ๋ชจ๋)"""
|
| 517 |
-
system_prompt = f"""๋น์ ์ ํ๊ตญ ์๊ถ ๋ฐ์ดํฐ ๋ถ์ ์ ๋ฌธ๊ฐ์
๋๋ค.
|
| 518 |
-
|
| 519 |
-
๐ **ํ์ฌ ๋ถ์ ๋ฐ์ดํฐ**
|
| 520 |
-
{json.dumps(data_context, ensure_ascii=False, indent=2)}
|
| 521 |
-
|
| 522 |
-
๊ตฌ์ฒด์ ์ธ ์ซ์์ ๋น์จ๋ก ์ ๋์ ๋ถ์์ ์ ๊ณตํ์ธ์.
|
| 523 |
-
์ฐฝ์
, ํฌ์, ๊ฒฝ์ ๋ถ์ ๊ด์ ์์ ์ค์ฉ์ ์ธ์ฌ์ดํธ๋ฅผ ์ ๊ณตํ์ธ์."""
|
| 524 |
-
|
| 525 |
-
messages = [{"role": "system", "content": system_prompt}]
|
| 526 |
-
if chat_history:
|
| 527 |
-
messages.extend(chat_history[-6:])
|
| 528 |
-
messages.append({"role": "user", "content": query})
|
| 529 |
-
|
| 530 |
-
payload = {
|
| 531 |
-
"model": "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507",
|
| 532 |
-
"max_tokens": 4800,
|
| 533 |
-
"temperature": 0.7,
|
| 534 |
-
"messages": messages,
|
| 535 |
-
"stream": True # ๐ฅ ์คํธ๋ฆฌ๋ฐ ํ์ฑํ!
|
| 536 |
-
}
|
| 537 |
-
|
| 538 |
-
headers = {
|
| 539 |
-
"Authorization": f"Bearer {self.api_key}",
|
| 540 |
-
"Content-Type": "application/json"
|
| 541 |
-
}
|
| 542 |
-
|
| 543 |
-
try:
|
| 544 |
-
response = requests.post(
|
| 545 |
-
self.base_url,
|
| 546 |
-
headers=headers,
|
| 547 |
-
json=payload,
|
| 548 |
-
timeout=60,
|
| 549 |
-
stream=True # ์คํธ๋ฆฌ๋ฐ ๋ชจ๋
|
| 550 |
-
)
|
| 551 |
-
|
| 552 |
-
if response.status_code == 200:
|
| 553 |
-
# ์คํธ๋ฆฌ๋ฐ ์๋ต ์ฒ๋ฆฌ
|
| 554 |
-
for line in response.iter_lines():
|
| 555 |
-
if line:
|
| 556 |
-
line_text = line.decode('utf-8')
|
| 557 |
-
if line_text.startswith('data: '):
|
| 558 |
-
data_str = line_text[6:] # 'data: ' ์ ๊ฑฐ
|
| 559 |
-
if data_str.strip() == '[DONE]':
|
| 560 |
-
break
|
| 561 |
-
try:
|
| 562 |
-
data = json.loads(data_str)
|
| 563 |
-
if 'choices' in data and len(data['choices']) > 0:
|
| 564 |
-
delta = data['choices'][0].get('delta', {})
|
| 565 |
-
content = delta.get('content', '')
|
| 566 |
-
if content:
|
| 567 |
-
yield content
|
| 568 |
-
except json.JSONDecodeError:
|
| 569 |
-
continue
|
| 570 |
-
else:
|
| 571 |
-
yield f"โ ๏ธ API ์ค๋ฅ: {response.status_code}"
|
| 572 |
-
|
| 573 |
-
except requests.exceptions.Timeout:
|
| 574 |
-
yield "โ ๏ธ API ์๋ต ์๊ฐ ์ด๊ณผ. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์."
|
| 575 |
-
except requests.exceptions.ConnectionError:
|
| 576 |
-
yield "โ ๏ธ ๋คํธ์ํฌ ์ฐ๊ฒฐ ์ค๋ฅ. ์ธํฐ๋ท ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์."
|
| 577 |
-
except Exception as e:
|
| 578 |
-
yield f"โ ์ค๋ฅ: {str(e)}"
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
# ============================================================================
|
| 582 |
-
# ์ ์ญ ์ํ
|
| 583 |
-
# ============================================================================
|
| 584 |
-
|
| 585 |
-
class AppState:
|
| 586 |
-
def __init__(self):
|
| 587 |
-
self.analyzer = None
|
| 588 |
-
self.llm_processor = None
|
| 589 |
-
self.chat_history = []
|
| 590 |
-
|
| 591 |
-
app_state = AppState()
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
# ============================================================================
|
| 595 |
-
# Gradio ์ธํฐํ์ด์ค ํจ์
|
| 596 |
-
# ============================================================================
|
| 597 |
-
|
| 598 |
-
def load_data(regions):
|
| 599 |
-
"""๋ฐ์ดํฐ ๋ก๋"""
|
| 600 |
-
if not regions:
|
| 601 |
-
return "โ ์ต์ 1๊ฐ ์ง์ญ์ ์ ํํด์ฃผ์ธ์!", None, None, None
|
| 602 |
-
|
| 603 |
-
try:
|
| 604 |
-
df = MarketDataLoader.load_multiple_regions(regions, sample_per_region=30000)
|
| 605 |
-
if df.empty:
|
| 606 |
-
return "โ ๋ฐ์ดํฐ ๋ก๋ ์คํจ!", None, None, None
|
| 607 |
-
|
| 608 |
-
app_state.analyzer = MarketAnalyzer(df)
|
| 609 |
-
|
| 610 |
-
# ๊ธฐ๋ณธ ํต๊ณ
|
| 611 |
-
stats = f"""
|
| 612 |
-
โ
**๋ฐ์ดํฐ ๋ก๋ ์๋ฃ!**
|
| 613 |
-
|
| 614 |
-
๐ **ํต๊ณ**
|
| 615 |
-
- ์ด ์ ํฌ: {len(df):,}๊ฐ
|
| 616 |
-
- ๋ถ์ ์ง์ญ: {', '.join(regions)}
|
| 617 |
-
- ์
์ข
์: {df['์๊ถ์
์ข
์ค๋ถ๋ฅ๋ช
'].nunique()}๊ฐ
|
| 618 |
-
- ๋๋ถ๋ฅ: {df['์๊ถ์
์ข
๋๋ถ๋ฅ๋ช
'].nunique()}๊ฐ
|
| 619 |
-
"""
|
| 620 |
-
|
| 621 |
-
return stats, gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)
|
| 622 |
-
except Exception as e:
|
| 623 |
-
return f"โ ์ค๋ฅ: {str(e)}", None, None, None
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
def generate_insights():
|
| 627 |
-
"""์ธ์ฌ์ดํธ ์์ฑ"""
|
| 628 |
-
if app_state.analyzer is None:
|
| 629 |
-
return [None] * 11
|
| 630 |
-
|
| 631 |
-
insights = app_state.analyzer.get_comprehensive_insights()
|
| 632 |
-
map_html = app_state.analyzer.create_density_map(sample_size=2000)
|
| 633 |
-
|
| 634 |
-
result = [map_html]
|
| 635 |
-
for insight in insights:
|
| 636 |
-
if insight and insight['type'] == 'plot':
|
| 637 |
-
result.append(insight['data'])
|
| 638 |
-
else:
|
| 639 |
-
result.append(None)
|
| 640 |
-
|
| 641 |
-
# ๋ถ์กฑํ ์ฐจํธ๋ None์ผ๋ก ์ฑ์ฐ๊ธฐ
|
| 642 |
-
while len(result) < 11:
|
| 643 |
-
result.append(None)
|
| 644 |
-
|
| 645 |
-
return result[:11]
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
def chat_respond(message, history):
|
| 649 |
-
"""์ฑ๋ด ์๋ต (์คํธ๋ฆฌ๋ฐ ๋ชจ๋) - Generator ํจ์"""
|
| 650 |
-
if app_state.analyzer is None:
|
| 651 |
-
yield history + [[message, "โ ๋จผ์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํด์ฃผ์ธ์!"]]
|
| 652 |
-
return
|
| 653 |
-
|
| 654 |
-
data_context = app_state.analyzer.analyze_for_llm()
|
| 655 |
-
|
| 656 |
-
# LLM ํ๋ก์ธ์ ์ด๊ธฐํ (ํ๊ฒฝ๋ณ์์์ API ํค ์๋ ๋ก๋)
|
| 657 |
-
try:
|
| 658 |
-
if app_state.llm_processor is None:
|
| 659 |
-
app_state.llm_processor = LLMQueryProcessor()
|
| 660 |
-
|
| 661 |
-
# ๋ํ ํ์คํ ๋ฆฌ ๊ตฌ์ฑ
|
| 662 |
-
chat_hist = []
|
| 663 |
-
for user_msg, bot_msg in history:
|
| 664 |
-
chat_hist.append({"role": "user", "content": user_msg})
|
| 665 |
-
chat_hist.append({"role": "assistant", "content": bot_msg})
|
| 666 |
-
|
| 667 |
-
# ์ ๋ฉ์์ง ์ถ๊ฐ
|
| 668 |
-
history = history + [[message, ""]]
|
| 669 |
-
|
| 670 |
-
# ์คํธ๋ฆฌ๋ฐ ์๋ต
|
| 671 |
-
full_response = ""
|
| 672 |
-
for chunk in app_state.llm_processor.process_query_stream(message, data_context, chat_hist):
|
| 673 |
-
full_response += chunk
|
| 674 |
-
history[-1][1] = full_response
|
| 675 |
-
yield history
|
| 676 |
-
|
| 677 |
-
except ValueError as e:
|
| 678 |
-
# API ํค๊ฐ ์๋ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ํต๊ณ ์ ๊ณต
|
| 679 |
-
response = f"""๐ **๊ธฐ๋ณธ ๋ฐ์ดํฐ ๋ถ์ ๊ฒฐ๊ณผ**
|
| 680 |
-
|
| 681 |
-
**์ ์ฒด ํํฉ**
|
| 682 |
-
- ์ด ์ ํฌ ์: {data_context['์ด_์ ํฌ_์']:,}๊ฐ
|
| 683 |
-
- ์
์ข
์ข
๋ฅ: {data_context['์
์ข
_์']}๊ฐ
|
| 684 |
-
- 1์ธต ๋น์จ: {data_context.get('1์ธต_๋น์จ', 'N/A')}
|
| 685 |
-
|
| 686 |
-
โ ๏ธ **AI ๋ถ์ ์ฌ์ฉ ๋ฐฉ๋ฒ**
|
| 687 |
-
ํ๊ฒฝ๋ณ์๋ฅผ ์ค์ ํ์ธ์:
|
| 688 |
-
```bash
|
| 689 |
-
export FIREWORKS_API_KEY="your_api_key_here"
|
| 690 |
-
```
|
| 691 |
-
|
| 692 |
-
๋๋ Hugging Face Space์์๋ Settings > Variables ์์ ์ค์ ํ์ธ์."""
|
| 693 |
-
|
| 694 |
-
history = history + [[message, response]]
|
| 695 |
-
yield history
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
# ============================================================================
|
| 699 |
-
# Gradio UI
|
| 700 |
-
# ============================================================================
|
| 701 |
-
|
| 702 |
-
with gr.Blocks(title="AI ์๊ถ ๋ถ์ ์์คํ
Pro", theme=gr.themes.Soft()) as demo:
|
| 703 |
-
gr.Markdown("""
|
| 704 |
-
# ๐ช AI ์๊ถ ๋ถ์ ์์คํ
Pro (์คํธ๋ฆฌ๋ฐ ์ง์ โก)
|
| 705 |
-
*์ ๊ตญ ์๊ฐ(์๊ถ) ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์ค์๊ฐ ๋ถ์ | Powered by Fireworks AI*
|
| 706 |
-
|
| 707 |
-
**โจ 10๊ฐ์ง ์ฌ์ธต ๋ถ์ ์ ๊ณต**: ์
์ข
ํธ๋ ๋, ๊ฒฝ์ ๊ฐ๋, ์
์ง ๋ถ์, ํ๋์ฐจ์ด์ฆ ๋น์จ, ์ง์ญ ํนํ, ์ธต๋ณ ์ ํธ๋ ๋ฑ
|
| 708 |
-
""")
|
| 709 |
-
|
| 710 |
-
# ์น ๋ฐฐ์ง ์ถ๊ฐ
|
| 711 |
-
gr.HTML("""
|
| 712 |
-
<style>
|
| 713 |
-
.badges-container {
|
| 714 |
-
display: flex;
|
| 715 |
-
justify-content: center;
|
| 716 |
-
align-items: center;
|
| 717 |
-
gap: 15px;
|
| 718 |
-
flex-wrap: wrap;
|
| 719 |
-
margin: 20px 0;
|
| 720 |
-
}
|
| 721 |
-
|
| 722 |
-
.badge {
|
| 723 |
-
display: inline-flex;
|
| 724 |
-
align-items: center;
|
| 725 |
-
gap: 8px;
|
| 726 |
-
padding: 10px 20px;
|
| 727 |
-
border-radius: 25px;
|
| 728 |
-
text-decoration: none;
|
| 729 |
-
font-weight: 600;
|
| 730 |
-
font-size: 0.95em;
|
| 731 |
-
transition: all 0.3s ease;
|
| 732 |
-
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
| 733 |
-
position: relative;
|
| 734 |
-
overflow: hidden;
|
| 735 |
-
}
|
| 736 |
-
|
| 737 |
-
.badge::before {
|
| 738 |
-
content: '';
|
| 739 |
-
position: absolute;
|
| 740 |
-
top: 0;
|
| 741 |
-
left: -100%;
|
| 742 |
-
width: 100%;
|
| 743 |
-
height: 100%;
|
| 744 |
-
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
|
| 745 |
-
transition: left 0.5s;
|
| 746 |
-
}
|
| 747 |
-
|
| 748 |
-
.badge:hover::before {
|
| 749 |
-
left: 100%;
|
| 750 |
-
}
|
| 751 |
-
|
| 752 |
-
.badge:hover {
|
| 753 |
-
transform: translateY(-3px);
|
| 754 |
-
box-shadow: 0 6px 25px rgba(0,0,0,0.3);
|
| 755 |
-
}
|
| 756 |
-
|
| 757 |
-
.badge-kakao {
|
| 758 |
-
background: linear-gradient(135deg, #FEE500 0%, #FFEB3B 100%);
|
| 759 |
-
color: #3C1E1E;
|
| 760 |
-
}
|
| 761 |
-
|
| 762 |
-
.badge-kakao:hover {
|
| 763 |
-
background: linear-gradient(135deg, #FFD700 0%, #FFC107 100%);
|
| 764 |
-
}
|
| 765 |
-
|
| 766 |
-
.badge-ginigen {
|
| 767 |
-
background: linear-gradient(135deg, #00D9FF 0%, #0099FF 100%);
|
| 768 |
-
color: white;
|
| 769 |
-
}
|
| 770 |
-
|
| 771 |
-
.badge-ginigen:hover {
|
| 772 |
-
background: linear-gradient(135deg, #00C4E6 0%, #0080E6 100%);
|
| 773 |
-
}
|
| 774 |
-
|
| 775 |
-
.badge-icon {
|
| 776 |
-
font-size: 1.2em;
|
| 777 |
-
}
|
| 778 |
-
</style>
|
| 779 |
-
|
| 780 |
-
<div class="badges-container">
|
| 781 |
-
<a href="https://open.kakao.com/o/peIe8KWh" target="_blank" class="badge badge-kakao">
|
| 782 |
-
<span class="badge-icon">๐ฌ</span>
|
| 783 |
-
<span>์คํ์ฑํ
๋ฐ๋ก๊ฐ๊ธฐ</span>
|
| 784 |
-
</a>
|
| 785 |
-
<a href="https://ginigen.ai" target="_blank" class="badge badge-ginigen">
|
| 786 |
-
<span class="badge-icon">๐</span>
|
| 787 |
-
<span>๋๋
ธ ๋ฐ๋๋ ์ ๋์จ ๋ฌด๋ฃ ์๋น์ค</span>
|
| 788 |
-
</a>
|
| 789 |
-
</div>
|
| 790 |
-
""")
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
with gr.Row():
|
| 794 |
-
with gr.Column(scale=1):
|
| 795 |
-
gr.Markdown("### โ๏ธ ์ค์ ")
|
| 796 |
-
|
| 797 |
-
# ํ๊ฒฝ๋ณ์ ์ํ ํ์
|
| 798 |
-
api_status = "โ
API ํค ์ค์ ๋จ" if os.getenv("FIREWORKS_API_KEY") else "โ ๏ธ API ํค ๋ฏธ์ค์ (๊ธฐ๋ณธ ํต๊ณ๋ง ์ ๊ณต)"
|
| 799 |
-
gr.Markdown(f"**๐ API ์ํ**: {api_status}")
|
| 800 |
-
|
| 801 |
-
region_select = gr.CheckboxGroup(
|
| 802 |
-
choices=list(MarketDataLoader.REGIONS.keys()),
|
| 803 |
-
value=['์์ธ'],
|
| 804 |
-
label="๐ ๋ถ์ ์ง์ญ ์ ํ (์ต๋ 5๊ฐ ๊ถ์ฅ)"
|
| 805 |
-
)
|
| 806 |
-
|
| 807 |
-
load_btn = gr.Button("๐ ๋ฐ์ดํฐ ๋ก๋", variant="primary", size="lg")
|
| 808 |
-
|
| 809 |
-
status_box = gr.Markdown("๐ ์ง์ญ์ ์ ํํ๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ์ธ์!")
|
| 810 |
-
|
| 811 |
-
with gr.Column(scale=3):
|
| 812 |
-
with gr.Tabs() as tabs:
|
| 813 |
-
with gr.Tab("๐ ์ธ์ฌ์ดํธ ๋์๋ณด๋", id=0) as tab1:
|
| 814 |
-
insights_content = gr.Column(visible=False)
|
| 815 |
-
|
| 816 |
-
with insights_content:
|
| 817 |
-
gr.Markdown("### ๐บ๏ธ ์ ํฌ ๋ฐ์ง๋ ํํธ๋งต")
|
| 818 |
-
map_output = gr.HTML()
|
| 819 |
-
|
| 820 |
-
gr.Markdown("---")
|
| 821 |
-
gr.Markdown("### ๐ 10๊ฐ์ง ์ฌ์ธต ์๊ถ ์ธ์ฌ์ดํธ")
|
| 822 |
-
|
| 823 |
-
with gr.Row():
|
| 824 |
-
chart1 = gr.Plot(label="์
์ข
๋ณ ์ ํฌ ์")
|
| 825 |
-
chart2 = gr.Plot(label="๋๋ถ๋ฅ ๋ถํฌ")
|
| 826 |
-
|
| 827 |
-
with gr.Row():
|
| 828 |
-
chart3 = gr.Plot(label="์ธต๋ณ ๋ถํฌ")
|
| 829 |
-
chart4 = gr.Plot(label="์
์ข
๋ค์์ฑ")
|
| 830 |
-
|
| 831 |
-
with gr.Row():
|
| 832 |
-
chart5 = gr.Plot(label="ํ๋์ฐจ์ด์ฆ ๋ถ์")
|
| 833 |
-
chart6 = gr.Plot(label="์ธต ์ ํธ๋")
|
| 834 |
-
|
| 835 |
-
with gr.Row():
|
| 836 |
-
chart7 = gr.Plot(label="์ง์ญ ๋ฐ์ง๋")
|
| 837 |
-
chart8 = gr.Plot(label="์
์ข
์๊ด๊ด๊ณ")
|
| 838 |
-
|
| 839 |
-
with gr.Row():
|
| 840 |
-
chart9 = gr.Plot(label="์๋ถ๋ฅ ํธ๋ ๋")
|
| 841 |
-
chart10 = gr.Plot(label="์ง์ญ ํนํ")
|
| 842 |
-
|
| 843 |
-
with gr.Tab("๐ค AI ๋ถ์ ์ฑ๋ด (์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ โก)", id=1) as tab2:
|
| 844 |
-
chat_content = gr.Column(visible=False)
|
| 845 |
-
|
| 846 |
-
with chat_content:
|
| 847 |
-
gr.Markdown("""
|
| 848 |
-
### ๐ก ์ํ ์ง๋ฌธ
|
| 849 |
-
๊ฐ๋จ์์ ์นดํ ์ฐฝ์
? | ์นํจ์ง ํฌํ ์ง์ญ? | 1์ธต์ด ์ ๋ฆฌํ ์
์ข
? | ํ๋์ฐจ์ด์ฆ ์ ์ ์จ?
|
| 850 |
-
|
| 851 |
-
โก **์คํธ๋ฆฌ๋ฐ ๋ชจ๋**: AI ์๋ต์ด ์ค์๊ฐ์ผ๋ก ํ์๋ฉ๋๋ค!
|
| 852 |
-
""")
|
| 853 |
-
|
| 854 |
-
chatbot = gr.Chatbot(height=400, label="AI ์๊ถ ๋ถ์ ์ด์์คํดํธ")
|
| 855 |
-
|
| 856 |
-
with gr.Row():
|
| 857 |
-
msg_input = gr.Textbox(
|
| 858 |
-
placeholder="๋ฌด์์ด๋ ๋ฌผ์ด๋ณด์ธ์! (์: ๊ฐ๋จ์์ ์นดํ ์ฐฝ์
ํ๋ ค๋ฉด?)",
|
| 859 |
-
show_label=False,
|
| 860 |
-
scale=4
|
| 861 |
-
)
|
| 862 |
-
submit_btn = gr.Button("์ ์ก", variant="primary", scale=1)
|
| 863 |
-
|
| 864 |
-
# ์ํ ๋ฒํผ๋ค
|
| 865 |
-
with gr.Row():
|
| 866 |
-
sample_btn1 = gr.Button("๊ฐ๋จ์์ ์นดํ ์ฐฝ์
?", size="sm")
|
| 867 |
-
sample_btn2 = gr.Button("์นํจ์ง ํฌํ ์ง์ญ?", size="sm")
|
| 868 |
-
sample_btn3 = gr.Button("1์ธต์ด ์ ๋ฆฌํ ์
์ข
?", size="sm")
|
| 869 |
-
sample_btn4 = gr.Button("ํ๋์ฐจ์ด์ฆ ์ ์ ์จ?", size="sm")
|
| 870 |
-
|
| 871 |
-
# ์ด๋ฒคํธ ํธ๋ค๋ฌ
|
| 872 |
-
load_btn.click(
|
| 873 |
-
fn=load_data,
|
| 874 |
-
inputs=[region_select],
|
| 875 |
-
outputs=[status_box, insights_content, chat_content, tab1]
|
| 876 |
-
).then(
|
| 877 |
-
fn=generate_insights,
|
| 878 |
-
outputs=[map_output, chart1, chart2, chart3, chart4, chart5, chart6, chart7, chart8, chart9, chart10]
|
| 879 |
-
)
|
| 880 |
-
|
| 881 |
-
# ์ฑ๋ด ์ด๋ฒคํธ (์คํธ๋ฆฌ๋ฐ ๋ชจ๋)
|
| 882 |
-
submit_btn.click(
|
| 883 |
-
fn=chat_respond,
|
| 884 |
-
inputs=[msg_input, chatbot],
|
| 885 |
-
outputs=[chatbot]
|
| 886 |
-
).then(
|
| 887 |
-
fn=lambda: "",
|
| 888 |
-
outputs=[msg_input]
|
| 889 |
-
)
|
| 890 |
-
|
| 891 |
-
msg_input.submit(
|
| 892 |
-
fn=chat_respond,
|
| 893 |
-
inputs=[msg_input, chatbot],
|
| 894 |
-
outputs=[chatbot]
|
| 895 |
-
).then(
|
| 896 |
-
fn=lambda: "",
|
| 897 |
-
outputs=[msg_input]
|
| 898 |
-
)
|
| 899 |
-
|
| 900 |
-
# ์ํ ๋ฒํผ ์ด๋ฒคํธ
|
| 901 |
-
def create_sample_click(text):
|
| 902 |
-
def handler(history):
|
| 903 |
-
for result in chat_respond(text, history or []):
|
| 904 |
-
yield result
|
| 905 |
-
return handler
|
| 906 |
-
|
| 907 |
-
sample_btn1.click(fn=create_sample_click("๊ฐ๋จ์์ ์นดํ ์ฐฝ์
?"), inputs=[chatbot], outputs=[chatbot])
|
| 908 |
-
sample_btn2.click(fn=create_sample_click("์นํจ์ง ํฌํ ์ง์ญ?"), inputs=[chatbot], outputs=[chatbot])
|
| 909 |
-
sample_btn3.click(fn=create_sample_click("1์ธต์ด ์ ๋ฆฌํ ์
์ข
?"), inputs=[chatbot], outputs=[chatbot])
|
| 910 |
-
sample_btn4.click(fn=create_sample_click("ํ๋์ฐจ์ด์ฆ ์ ์ ์จ?"), inputs=[chatbot], outputs=[chatbot])
|
| 911 |
-
|
| 912 |
-
gr.Markdown("""
|
| 913 |
-
---
|
| 914 |
-
### ๐ ์ฌ์ฉ ๊ฐ์ด๋
|
| 915 |
-
1. ์ง์ญ ์ ํ โ 2. ๋ฐ์ดํฐ ๋ก๋ โ 3. 10๊ฐ์ง ์ธ์ฌ์ดํธ ํ์ธ ๋๋ AI์๊ฒ ์ง๋ฌธ
|
| 916 |
-
|
| 917 |
-
### ๐ AI ์ฑ๋ด ํ์ฑํ ๋ฐฉ๋ฒ
|
| 918 |
-
ํ๊ฒฝ๋ณ์ ์ค์ :
|
| 919 |
-
```bash
|
| 920 |
-
export FIREWORKS_API_KEY="your_api_key_here"
|
| 921 |
-
```
|
| 922 |
-
|
| 923 |
-
Hugging Face Space์์๋:
|
| 924 |
-
1. Settings ๋ฉ๋ด ํด๋ฆญ
|
| 925 |
-
2. Variables ํญ ์ ํ
|
| 926 |
-
3. New variable ์ถ๊ฐ: `FIREWORKS_API_KEY`
|
| 927 |
-
|
| 928 |
-
### ๐ ์ ๊ณต๋๋ 10๊ฐ์ง ๋ถ์
|
| 929 |
-
1. **์
์ข
๋ณ ์ ํฌ ์**: ๊ฐ์ฅ ๋ง์ ์
์ข
TOP 15
|
| 930 |
-
2. **๋๋ถ๋ฅ ๋ถํฌ**: ์๋งค/์์/์๋น์ค ๋ฑ ๋๋ถ๋ฅ ๋น์จ
|
| 931 |
-
3. **์ธต๋ณ ๋ถํฌ**: ์งํ/1์ธต/์์ธต ์
์ง ๋ถ์
|
| 932 |
-
4. **์
์ข
๋ค์์ฑ**: ์ง์ญ๋ณ ์
์ข
๋ค์์ฑ ์ง์
|
| 933 |
-
5. **ํ๋์ฐจ์ด์ฆ ๋ถ์**: ๊ฐ์ธ vs ํ๋์ฐจ์ด์ฆ ๋น์จ
|
| 934 |
-
6. **์ธต ์ ํธ๋**: ์
์ข
๋ณ ์ ํธ ์ธต์
|
| 935 |
-
7. **์ง์ญ ๋ฐ์ง๋**: ์ ํฌ ์ ์์ ์ง์ญ
|
| 936 |
-
8. **์
์ข
์๊ด๊ด๊ณ**: ๊ฐ์ด ๋ํ๋๋ ์
์ข
ํจํด
|
| 937 |
-
9. **์๋ถ๋ฅ ํธ๋ ๋**: ์ธ๋ถ ์
์ข
๋ถํฌ
|
| 938 |
-
10. **์ง์ญ ํนํ**: ๊ฐ ์ง์ญ์ ํนํ ์
์ข
|
| 939 |
-
|
| 940 |
-
๐ก **Tip**: API ํค ์์ด๋ 10๊ฐ์ง ์๊ฐํ ๋ถ์๊ณผ ๊ธฐ๋ณธ ํต๊ณ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค!
|
| 941 |
-
|
| 942 |
-
โก **NEW!** ์ฑ๋ด์ด ์ด์ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ์ผ๋ก ์๋ตํฉ๋๋ค!
|
| 943 |
-
""")
|
| 944 |
-
|
| 945 |
-
# ์คํ
|
| 946 |
-
if __name__ == "__main__":
|
| 947 |
-
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|