wansu22 commited on
Commit
e78650e
ยท
verified ยท
1 Parent(s): 6b105d6

Create tool

Browse files
Files changed (1) hide show
  1. tool +320 -0
tool ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit.components.v1 as components
3
+ import time
4
+ import requests
5
+ import pandas as pd
6
+ import hmac
7
+ import hashlib
8
+ import base64
9
+ from datetime import datetime, timedelta
10
+ import random
11
+ import re
12
+
13
+ # [์ง€๋„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ]
14
+ import folium
15
+ from streamlit_folium import st_folium
16
+
17
+ # ==========================================
18
+ # 0. API ํ‚ค ๋ฐ ์„ค์ •
19
+ # ==========================================
20
+
21
+ # [1] ์นด์นด์˜ค API (ํ•„์ˆ˜)
22
+ KAKAO_REST_KEY = "968344aed4aff4d7aeb37eb199767d5a"
23
+
24
+ # [2] ๋„ค์ด๋ฒ„ ๊ด‘๊ณ  API
25
+ AD_API_KEY = "01000000002855c92d066a6e30d3eaeafbe6adebd688d73c3dd901f151b52c430ddcad5c88"
26
+ AD_SECRET_KEY = "AQAAAAAoVcktBmpuMNPq6vvmrevWXrbXSbEoh/+/3U3vTcTLyA=="
27
+ AD_CUSTOMER_ID = "4173931"
28
+
29
+ # [3] ๊ธฐํƒ€ ์„ค์ •
30
+ NAVER_SEARCH_ID = "dlOt9fIfGfpSj69uICWc"
31
+ NAVER_SEARCH_SECRET = "_rtIqpqYpd"
32
+ YOUTUBE_API_KEY = "AIzaSyBPgiYOvrPJ4cacWQ42UQb_KZobCcpOIH0"
33
+
34
+ EXCLUDED_KEYWORDS = ["์Šˆ๋งํฌ", "์จ๋งˆ์ง€", "์šธ์Ž„๋ผ", "์ธ๋ชจ๋“œ", "ํ‹ฐํƒ€๋Š„"]
35
+
36
+ # ==========================================
37
+ # 1. ํ•ต์‹ฌ ๊ธฐ๋Šฅ ํ•จ์ˆ˜
38
+ # ==========================================
39
+
40
+ def search_places_kakao(query):
41
+ """์žฅ์†Œ ๊ฒ€์ƒ‰"""
42
+ url = "https://dapi.kakao.com/v2/local/search/keyword.json"
43
+ headers = {"Authorization": f"KakaoAK {KAKAO_REST_KEY}"}
44
+ try:
45
+ res = requests.get(url, params={"query": query, "size": 15}, headers=headers)
46
+ return res.json()['documents'] if res.status_code == 200 else []
47
+ except: return []
48
+
49
+ def get_address_details_kakao(address_str):
50
+ """์ฃผ์†Œ -> ์ขŒํ‘œ + ๋ฒ•์ •๋™ ๋ถ„์„"""
51
+ url = "https://dapi.kakao.com/v2/local/search/address.json"
52
+ headers = {"Authorization": f"KakaoAK {KAKAO_REST_KEY}"}
53
+ try:
54
+ res = requests.get(url, params={"query": address_str}, headers=headers)
55
+ if res.status_code == 200:
56
+ docs = res.json()['documents']
57
+ if docs:
58
+ data = docs[0]
59
+ x, y = float(data['x']), float(data['y'])
60
+ addr = data.get('address', {})
61
+
62
+ region_1 = addr.get('region_1depth_name', '') # ์ถฉ๋ถ
63
+ region_2 = addr.get('region_2depth_name', '') # ์ฒญ์ฃผ์‹œ ์ƒ๋‹น๊ตฌ
64
+
65
+ city_name = ""
66
+ gu_name = ""
67
+ if region_2:
68
+ parts = region_2.split()
69
+ if len(parts) >= 2:
70
+ city_name = parts[0] # ์ฒญ์ฃผ์‹œ
71
+ gu_name = parts[1] # ์ƒ๋‹น๊ตฌ
72
+ else:
73
+ gu_name = parts[0] # ๊ฐ•๋‚จ๊ตฌ
74
+
75
+ b_dong = addr.get('region_3depth_name', '') # ๋ฒ•์ •๋™
76
+ return x, y, region_1, city_name, gu_name, b_dong
77
+ return 0.0, 0.0, "", "", "", ""
78
+ except: return 0.0, 0.0, "", "", "", ""
79
+
80
+ def get_admin_dong(x, y):
81
+ """ํ–‰์ •๋™ ์ถ”์ถœ (์ค‘์•™๋™ ๋“ฑ)"""
82
+ if x == 0.0 or y == 0.0: return ""
83
+ url = "https://dapi.kakao.com/v2/local/geo/coord2regioncode.json"
84
+ headers = {"Authorization": f"KakaoAK {KAKAO_REST_KEY}"}
85
+ try:
86
+ res = requests.get(url, params={"x": x, "y": y}, headers=headers)
87
+ if res.status_code == 200:
88
+ docs = res.json()['documents']
89
+ for doc in docs:
90
+ if doc['region_type'] == 'H': return doc['region_3depth_name']
91
+ return ""
92
+ except: return ""
93
+
94
+ def get_nearby_stations(x, y):
95
+ if x == 0.0 or y == 0.0: return []
96
+ url = "https://dapi.kakao.com/v2/local/search/category.json"
97
+ headers = {"Authorization": f"KakaoAK {KAKAO_REST_KEY}"}
98
+ params = {"category_group_code": "SW8", "x": x, "y": y, "radius": 1500, "sort": "distance"}
99
+ try:
100
+ res = requests.get(url, params=params, headers=headers)
101
+ if res.status_code == 200:
102
+ return [{"name": d['place_name'], "clean_name": d['place_name'].split()[0].replace("์—ญ",""), "x": float(d['x']), "y": float(d['y'])} for d in res.json()['documents']][:4]
103
+ return []
104
+ except: return []
105
+
106
+ def get_naver_expanded_rankings(seed_keywords, category_seed, filters, loc_info):
107
+ """
108
+ [ํ•ต์‹ฌ] ์œ„์น˜ ๊ธฐ๋ฐ˜ ํ•„ํ„ฐ๋ง (ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ๋ฐฉ์‹)
109
+ """
110
+ uri = '/keywordstool'
111
+ timestamp = str(int(time.time() * 1000))
112
+ msg = f"{timestamp}.GET.{uri}"
113
+ signature = base64.b64encode(hmac.new(bytes(AD_SECRET_KEY, 'UTF-8'), bytes(msg, 'UTF-8'), hashlib.sha256).digest())
114
+ headers = {'X-Timestamp': timestamp, 'X-API-KEY': AD_API_KEY, 'X-Customer': AD_CUSTOMER_ID, 'X-Signature': signature}
115
+
116
+ clean_seeds = []
117
+ seen = set()
118
+ for k in seed_keywords:
119
+ k_nospace = k.replace(" ", "")
120
+ if k_nospace not in seen:
121
+ clean_seeds.append(k_nospace)
122
+ seen.add(k_nospace)
123
+
124
+ # [ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ] ํ–‰์ •๊ตฌ์—ญ ๋ฐ ์—ญ ์ด๋ฆ„ ์ •์˜
125
+ valid_local_terms = []
126
+ if loc_info['si']: valid_local_terms.append(loc_info['si'].replace("์‹œ", ""))
127
+ if loc_info['gu']: valid_local_terms.append(loc_info['gu'])
128
+ if loc_info['b_dong']: valid_local_terms.append(re.sub(r'\d+๊ฐ€?', '', loc_info['b_dong']))
129
+ if loc_info['h_dong']: valid_local_terms.append(loc_info['h_dong'])
130
+ for s in loc_info['stations']: valid_local_terms.append(s['clean_name'])
131
+ valid_local_terms = list(set(valid_local_terms))
132
+
133
+ all_results = []
134
+ seen_kwd = set()
135
+
136
+ for i in range(0, len(clean_seeds), 5):
137
+ chunk = clean_seeds[i:i+5]
138
+ try:
139
+ res = requests.get("https://api.naver.com" + uri, params={'hintKeywords': ','.join(chunk), 'showDetail': '1'}, headers=headers)
140
+ if res.status_code == 200:
141
+ data = res.json()
142
+ for item in data.get('keywordList', []):
143
+ kwd = item['relKeyword'].replace(" ", "")
144
+ if kwd in seen_kwd: continue
145
+ if category_seed not in kwd: continue
146
+ if kwd == category_seed: continue
147
+ if any(bad in kwd for bad in EXCLUDED_KEYWORDS): continue
148
+
149
+ is_local_relevant = False
150
+ for term in valid_local_terms:
151
+ if term in kwd:
152
+ is_local_relevant = True
153
+ break
154
+ if not is_local_relevant: continue
155
+
156
+ if not filters['station']:
157
+ if "์—ญ" in kwd: continue
158
+ is_station_word = False
159
+ for s in loc_info['stations']:
160
+ if s['clean_name'] in kwd: is_station_word = True; break
161
+ if is_station_word: continue
162
+
163
+ priority = 0
164
+ if kwd in clean_seeds: priority = 100
165
+ for main_k in loc_info['main_keywords']:
166
+ if main_k in kwd: priority += 20
167
+
168
+ seen_kwd.add(kwd)
169
+ pc = item['monthlyPcQcCnt']
170
+ mo = item['monthlyMobileQcCnt']
171
+ if isinstance(pc, str): pc = 10
172
+ if isinstance(mo, str): mo = 10
173
+ all_results.append({'key': item['relKeyword'], 'total': pc + mo, 'priority': priority})
174
+ time.sleep(0.1)
175
+ except: pass
176
+ return sorted(all_results, key=lambda x: (x['priority'], x['total']), reverse=True)
177
+
178
+ def search_bloggers(keyword, display=30):
179
+ url = "https://openapi.naver.com/v1/search/blog.json"
180
+ headers = {"X-Naver-Client-Id": NAVER_SEARCH_ID, "X-Naver-Client-Secret": NAVER_SEARCH_SECRET}
181
+ params = {"query": keyword, "display": display, "sort": "sim"}
182
+ try:
183
+ res = requests.get(url, params=params, headers=headers)
184
+ if res.status_code == 200: return res.json()['items']
185
+ return None
186
+ except: return None
187
+
188
+ # ==========================================
189
+ # 2. ๋ฉ”์ธ UI
190
+ # ==========================================
191
+ st.set_page_config(page_title="๋ณ‘์› ๋งˆ์ผ€ํŒ… ๋งˆ์Šคํ„ฐ", layout="wide")
192
+
193
+ st.markdown("""
194
+ <style>
195
+ #myBtn { display: flex; justify-content: center; align-items: center; position: fixed; bottom: 30px; right: 30px; z-index: 9999;
196
+ font-size: 20px; border: none; outline: none; background-color: #E1306C; color: white; cursor: pointer; width: 50px; height: 50px;
197
+ padding: 0; border-radius: 50%; box-shadow: 0px 4px 6px rgba(0,0,0,0.2); transition: transform 0.2s; }
198
+ #myBtn:hover { transform: scale(1.1); }
199
+ </style>
200
+ <button onclick="window.parent.document.querySelector('.main').scrollTo({top:0, behavior:'smooth'})" id="myBtn">โ–ฒ</button>
201
+ """, unsafe_allow_html=True)
202
+
203
+ if 'target_location' not in st.session_state: st.session_state.target_location = None
204
+ if 'analysis_result' not in st.session_state: st.session_state.analysis_result = pd.DataFrame()
205
+ if 'search_results' not in st.session_state: st.session_state.search_results = []
206
+
207
+ st.title("๐Ÿฅ ๋ณ‘์› ๋งˆ์ผ€ํŒ… ์˜ฌ์ธ์› ํˆด")
208
+ tab1, tab2, tab3, tab4 = st.tabs(["๐Ÿ“Š ํ‚ค์›Œ๋“œ ๋ถ„์„ (Map)", "๐Ÿ“ ๋ธ”๋กœ๊ฑฐ ๋ฐœ๊ตด", "๐Ÿ“บ ์œ ํŠœ๋ฒ„ ๋ฐœ๊ตด", "๐Ÿ“ธ ์ธ์Šคํƒ€ ๋ฐœ๊ตด"])
209
+
210
+ with tab1:
211
+ st.header("1. ์ง€๋„ ๊ธฐ๋ฐ˜ ์ƒ๊ถŒ ๋ถ„์„ ๋ฐ ํ‚ค์›Œ๋“œ ์ถ”์ถœ")
212
+ with st.expander("๐Ÿ” ๋ณ‘์› ๊ฒ€์ƒ‰ ๋ฐ ์œ„์น˜ ์„ค์ •", expanded=True):
213
+ with st.form("search_form"):
214
+ col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
215
+ with col1: h_query = st.text_input("๋ณ‘์›๋ช… ์ž…๋ ฅ", placeholder="์˜ˆ: ๋””์•„ํŠธ์˜์› ์ฒญ์ฃผ")
216
+ with col2: search_btn = st.form_submit_button("๋ณ‘์› ์ฐพ๊ธฐ")
217
+ if search_btn and h_query:
218
+ places = search_places_kakao(h_query)
219
+ if places: st.session_state.search_results = places
220
+ else: st.warning("๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
221
+ if st.session_state.search_results:
222
+ st.divider()
223
+ options = {f"{p['place_name']} ({p['address_name']})": i for i, p in enumerate(st.session_state.search_results)}
224
+ selected_option = st.radio("๋ถ„์„ํ•  ๋ณ‘์›์„ ์„ ํƒํ•˜์„ธ์š”", list(options.keys()))
225
+ if st.button("โœ… ์„ ํƒํ•œ ๋ณ‘์›์œผ๋กœ ์„ค์ •"):
226
+ target = st.session_state.search_results[options[selected_option]]
227
+ x, y, region_1, city, gu, b_dong = get_address_details_kakao(target['address_name'])
228
+ st.session_state.target_location = {"name": target['place_name'], "x": x, "y": y, "do": region_1, "si": city, "gu": gu, "b_dong": b_dong, "h_dong": get_admin_dong(x, y)}
229
+ st.session_state.target_location['stations'] = get_nearby_stations(x, y)
230
+ st.rerun()
231
+
232
+ if st.session_state.target_location:
233
+ loc = st.session_state.target_location
234
+ col1, col2 = st.columns([1.2, 1])
235
+ with col2:
236
+ st.subheader("๐Ÿ—บ๏ธ ๋ถ„์„ ๊ตฌ์—ญ")
237
+ # [์ˆ˜์ •] ํ…์ŠคํŠธ ๋งํ’์„ (DivIcon) ๊ธฐ๋Šฅ์ด ์ œ๊ฑฐ๋œ ๊นจ๋—ํ•œ ์ง€๋„
238
+ m = folium.Map(location=[loc['y'], loc['x']], zoom_start=15)
239
+ folium.Marker([loc['y'], loc['x']], popup=f"<b>{loc['name']}</b>", tooltip=loc['name'], icon=folium.Icon(color="red", icon="star", prefix='fa')).add_to(m)
240
+ folium.Circle(location=[loc['y'], loc['x']], radius=1500, color='#E1306C', fill=True, fill_color='#E1306C', fill_opacity=0.1).add_to(m)
241
+ for s in loc['stations']:
242
+ folium.Marker([s['y'], s['x']], tooltip=f"{s['name']} (์—ญ์„ธ๊ถŒ)", popup=s['name'], icon=folium.Icon(color="green", icon="train", prefix="fa")).add_to(m)
243
+ st_folium(m, height=450, width="100%")
244
+
245
+ # [AI ์ƒ๊ถŒ ๋ถ„์„ ๋ฆฌํฌํŠธ]
246
+ st_names = [s['name'] for s in loc['stations']]
247
+ st_text = ", ".join(st_names) if st_names else "๋„๋ณด๊ถŒ ๋‚ด ์ง€ํ•˜์ฒ ์—ญ ์—†์Œ"
248
+ dong_name = loc['h_dong'] if loc['h_dong'] else loc['b_dong']
249
+
250
+ report_box = f"""
251
+ <div style="background-color:#f0f2f6; padding:15px; border-radius:10px; border-left: 5px solid #E1306C;">
252
+ <h4 style="margin:0 0 10px 0;">๐Ÿ“ข AI ๋งˆ์ผ€ํŒ… ์ƒ๊ถŒ ๋ถ„์„</h4>
253
+ <ul style="margin:0; padding-left:20px;">
254
+ <li><b>๐Ÿ“ ํ–‰์ •๊ตฌ์—ญ:</b> ํ˜„์žฌ <b>{loc['si']} {loc['gu']} {dong_name}</b> ์ง€์—ญ์„ ๋ถ„์„ ์ค‘์ž…๋‹ˆ๋‹ค.</li>
255
+ <li><b>๐Ÿš‡ ๊ตํ†ต/์ ‘๊ทผ์„ฑ:</b> ์ด ๋ณ‘์›์€ <b>{st_text}</b> ์ธ๊ทผ์— ์œ„์น˜ํ•ด ์žˆ์Šต๋‹ˆ๋‹ค.</li>
256
+ <li><b>๐Ÿ’ก ์ „๋žต:</b> ์ง€์—ญ๋ช…(<b>{loc['gu']}, {dong_name}</b>)์ด ํฌํ•จ๋œ ํ‚ค์›Œ๋“œ๋ฅผ ์ตœ์šฐ์„ ์œผ๋กœ ์„ ์ ํ•˜์„ธ์š”.</li>
257
+ </ul>
258
+ </div>
259
+ """
260
+ st.markdown(report_box, unsafe_allow_html=True)
261
+
262
+ with col1:
263
+ disp_addr = f"{loc['si']} {loc['gu']} {loc['b_dong']}"
264
+ if loc['h_dong'] and loc['h_dong'] != loc['b_dong']: disp_addr += f" ({loc['h_dong']})"
265
+ st.subheader(f"๐Ÿ“ {disp_addr}")
266
+ st.write("๐ŸŽฏ **ํ‚ค์›Œ๋“œ ์ถ”์ถœ ์˜ต์…˜**")
267
+ c1, c2 = st.columns(2)
268
+ use_region = c1.checkbox("๐Ÿ™๏ธ ์ง€์—ญ๋ช… (์‹œ/๊ตฌ/๋™)", True)
269
+ use_station = c2.checkbox("๐Ÿš‡ ์—ญ์„ธ๊ถŒ", True)
270
+ target_cat = st.selectbox("์—…์ข…", ["ํ”ผ๋ถ€๊ณผ", "์„ฑํ˜•์™ธ๊ณผ", "์น˜๊ณผ", "ํ•œ์˜์›", "์ •ํ˜•์™ธ๊ณผ", "์•ˆ๊ณผ", "๋น„๋‡จ๊ธฐ๊ณผ", "์‚ฐ๋ถ€์ธ๊ณผ"])
271
+
272
+ if st.button("๐Ÿš€ ํ‚ค์›Œ๋“œ ์ถ”์ถœ", type="primary", use_container_width=True):
273
+ with st.spinner("์ง€์—ญ ๊ธฐ๋ฐ˜ ์ •๋ฐ€ ๋ถ„์„ ์ค‘..."):
274
+ seeds = []
275
+ main_keywords = []
276
+ clean_si = loc['si'].replace("์‹œ", "") if loc['si'] else ""
277
+ if use_region:
278
+ if clean_si: seeds.extend([f"{clean_si}{target_cat}", f"{clean_si}{target_cat}์ถ”์ฒœ"])
279
+ if loc['gu']: seeds.extend([f"{loc['gu']}{target_cat}", f"{clean_si}{loc['gu']}{target_cat}"])
280
+ clean_b = re.sub(r'\d+๊ฐ€?', '', loc['b_dong'])
281
+ seeds.append(f"{clean_b}{target_cat}")
282
+ if loc['h_dong']: seeds.append(f"{loc['h_dong']}{target_cat}")
283
+ if use_station:
284
+ for s in loc['stations']: seeds.extend([f"{s['clean_name']}์—ญ{target_cat}", f"{s['clean_name']}{target_cat}"])
285
+ loc['main_keywords'] = [clean_si, loc['gu'], loc['h_dong']]
286
+ rankings = get_naver_expanded_rankings(seeds, target_cat, {'station': use_station}, loc)
287
+ if rankings: st.session_state.analysis_result = pd.DataFrame(rankings)
288
+
289
+ if not st.session_state.analysis_result.empty:
290
+ df = st.session_state.analysis_result
291
+ st.success(f"ํ‚ค์›Œ๋“œ {len(df)}๊ฐœ")
292
+ for _, row in df.head(30).iterrows():
293
+ icon = "๐Ÿ‘‘" if row['priority'] >= 100 else ("๐ŸŽฏ" if row['priority'] >= 60 else "โœ…")
294
+ st.markdown(f"""<div style="border:1px solid #eee; padding:10px; margin-bottom:5px; border-radius:5px; display:flex; justify-content:space-between; align-items:center; background-color:{'#fff0f6' if row['priority']>=80 else 'white'};">
295
+ <div><b>{row['key']}</b> <span style="font-size:0.8em; color:#E1306C;">{icon}</span></div>
296
+ <div style="text-align:right;"><span style="font-size:0.8em; color:#666;">์กฐํšŒ์ˆ˜</span><br><b>{row['total']:,}</b></div>
297
+ </div>""", unsafe_allow_html=True)
298
+ st.download_button("๐Ÿ“ฅ ๋‹ค์šด๋กœ๋“œ", df.to_csv(index=False).encode('utf-8-sig'), "keywords.csv", "text/csv", use_container_width=True)
299
+
300
+ with tab2:
301
+ st.header("2. ๋ธ”๋กœ๊ฑฐ ๋ฐœ๊ตด")
302
+ with st.form("blog_form"):
303
+ col_b1, col_b2 = st.columns([3, 1], vertical_alignment="bottom")
304
+ with col_b1: region_input = st.text_input("ํƒ€๊ฒŸ ์ง€์—ญ๋ช…", placeholder="์˜ˆ: ์ฒญ์ฃผ ์„ฑ์•ˆ๊ธธ")
305
+ with col_b2: submit_blog = st.form_submit_button("๐Ÿ•ต๏ธโ€โ™€๏ธ ๋ธ”๋กœ๊ฑฐ ์ฐพ๊ธฐ")
306
+ if submit_blog and region_input:
307
+ with st.spinner("๋ธ”๋กœ๊ฑฐ ๋ถ„์„ ์ค‘..."):
308
+ items = search_bloggers(f"{region_input} ํ”ผ๋ถ€๊ณผ ํ›„๊ธฐ", 30)
309
+ if items:
310
+ for i in items:
311
+ st.write(f"- [{i['bloggername']}] {i['title'].replace('<b>','').replace('</b>','')}")
312
+ st.caption(f"๐Ÿ”— {i['link']}")
313
+
314
+ with tab3:
315
+ st.header("3. ์œ ํŠœ๋ธŒ")
316
+ st.info("์ค€๋น„์ค‘")
317
+
318
+ with tab4:
319
+ st.header("4. ์ธ์Šคํƒ€")
320
+ st.info("์ค€๋น„์ค‘")