seawolf2357 commited on
Commit
60c5c0e
ยท
verified ยท
1 Parent(s): cef3bdf

Delete app-backup.py

Browse files
Files changed (1) hide show
  1. 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)