ll7098ll commited on
Commit
5cdb81a
·
verified ·
1 Parent(s): e834c71

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -1061
app.py CHANGED
@@ -1,1100 +1,270 @@
1
- import os
2
  import streamlit as st
3
  import random
4
- import time
5
- import pandas as pd
6
- import plotly.express as px
7
  from openai import OpenAI
 
8
 
9
- # --- Streamlit 설정 ---
10
- st.set_page_config(
11
- page_title="⏳ 대한민국 경제 발전사 주식 투자 게임",
12
- page_icon="📈",
13
- layout="wide",
14
- initial_sidebar_state="expanded",
15
- )
16
-
17
- # --- Custom CSS (스타일링) ---
18
- st.markdown(
19
- """
20
- <style>
21
- /* 전체 폰트 (Nanum Gothic) */
22
- @import url('https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700&display=swap');
23
- body {
24
- font-family: 'Nanum Gothic', sans-serif !important;
25
- }
26
- /* 탭 메뉴 스타일 */
27
- .stTabs [data-baseweb="tab-list"] button[aria-selected="true"] {
28
- background-color: #007bff !important;
29
- color: white !important;
30
- font-weight: bold;
31
- }
32
- .stTabs [data-baseweb="tab-list"] button {
33
- background-color: #f0f2f6;
34
- color: #333;
35
- border-radius: 8px 8px 0 0;
36
- padding: 0.75em 1em;
37
- margin-bottom: -1px; /* border overlap */
38
- }
39
- /* 사이드바 스타일 */
40
- [data-testid="stSidebar"] {
41
- width: 350px !important;
42
- background-color: #f8f9fa; /* Light gray sidebar background */
43
- padding: 20px;
44
- }
45
- [data-testid="stSidebar"] h1, [data-testid="stSidebar"] h3 {
46
- color: #212529; /* Dark gray sidebar headings */
47
- }
48
- [data-testid="stSidebar"] hr {
49
- border-top: 1px solid #e0e0e0; /* Lighter sidebar hr */
50
- }
51
- /* Metric 스타일 */
52
- .streamlit-metric-label {
53
- font-size: 16px;
54
- color: #4a4a4a;
55
- }
56
- .streamlit-metric-value {
57
- font-size: 28px;
58
- font-weight: bold;
59
- }
60
- /* 버튼 스타일 */
61
- div.stButton > button {
62
- background-color: #007bff;
63
- color: white;
64
- padding: 12px 24px;
65
- font-size: 16px;
66
- border-radius: 8px;
67
- border: none;
68
- box-shadow: 2px 2px 5px rgba(0,0,0,0.1); /* Soft shadow */
69
- transition: background-color 0.3s ease;
70
- }
71
- div.stButton > button:hover {
72
- background-color: #0056b3;
73
- box-shadow: 2px 2px 7px rgba(0,0,0,0.15); /* Slightly stronger shadow on hover */
74
- }
75
- /* 보조 버튼 스타일 */
76
- div.stButton > button.secondary-button {
77
- background-color: #6c757d;
78
- color: white;
79
- padding: 10px 20px;
80
- font-size: 14px;
81
- border-radius: 6px;
82
- border: none;
83
- transition: background-color 0.3s ease;
84
- }
85
- div.stButton > button.secondary-button:hover {
86
- background-color: #5a6268;
87
- }
88
- /* Expander 스타일 */
89
- .streamlit-expanderHeader {
90
- font-weight: bold;
91
- color: #212529;
92
- border-bottom: 1px solid #e0e0e0;
93
- padding-bottom: 8px;
94
- margin-bottom: 15px;
95
- }
96
- /* Dataframe 스타일 */
97
- .dataframe {
98
- border: 1px solid #e0e0e0;
99
- border-radius: 8px;
100
- padding: 12px;
101
- box-shadow: 2px 2px 5px rgba(0,0,0,0.05); /* Very subtle shadow */
102
- }
103
- /* Info, Success, Error, Warning Box 스타일 */
104
- div.stInfo, div.stSuccess, div.stError, div.stWarning {
105
- border-radius: 8px;
106
- padding: 15px;
107
- margin-bottom: 15px;
108
- box-shadow: 2px 2px 5px rgba(0,0,0,0.05);
109
- }
110
- div.stInfo {
111
- background-color: #e7f3ff;
112
- border-left: 5px solid #007bff;
113
- }
114
- div.stSuccess {
115
- background-color: #e6f7ec;
116
- border-left: 5px solid #28a745;
117
- }
118
- div.stError {
119
- background-color: #fdeded;
120
- border-left: 5px solid #dc3545;
121
- }
122
- div.stWarning {
123
- background-color: #fffbe6;
124
- border-left: 5px solid #ffc107;
125
- }
126
- /* Toast message 스타일 */
127
- div.streamlit-toast-container {
128
- z-index: 10000; /* Toast를 항상 맨 위에 표시 */
129
- }
130
- div[data-testid="stToast"] {
131
- border-radius: 8px;
132
- padding: 15px;
133
- box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
134
- }
135
- </style>
136
- """,
137
- unsafe_allow_html=True,
138
- )
139
-
140
- # --- API 키 설정 ---
141
- if "OPENAI_API_KEY" not in os.environ:
142
- st.error(
143
- "OPENAI_API_KEY 환경 변수가 설정되지 않았습니다. Hugging Face Secrets 또는 환경 변수에 API 키를 설정해주세요."
144
- )
145
- st.stop()
146
-
147
- client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
148
 
149
  # --- 세션 상태 초기화 ---
150
- if "chat_session" not in st.session_state:
151
- st.session_state["chat_session"] = []
152
- if "portfolio" not in st.session_state:
153
- st.session_state["portfolio"] = {"cash": 10000000, "stocks": {}}
154
- if "stocks" not in st.session_state:
155
- st.session_state["stocks"] = {} # Decade별로 stocks 데이터를 초기화하므로, 여기서는 빈 딕셔너리로 시작
156
- if "news_analysis_results" not in st.session_state:
157
- st.session_state["news_analysis_results"] = {}
158
- if "messages" not in st.session_state:
159
- st.session_state["messages"] = []
160
- if "daily_news" not in st.session_state:
161
- st.session_state["daily_news"] = None
162
- if "previous_daily_news" not in st.session_state:
163
- st.session_state["previous_daily_news"] = None
164
- if "news_date" not in st.session_state:
165
- st.session_state["news_date"] = None
166
- if "news_meanings" not in st.session_state:
167
- st.session_state["news_meanings"] = {}
168
- if (
169
- "ai_news_analysis_output" not in st.session_state
170
- ):
171
- st.session_state["ai_news_analysis_output"] = {}
172
- if "day_count" not in st.session_state:
173
- st.session_state["day_count"] = 1
174
- if "decade_count" not in st.session_state: # Decade 카운트 추가
175
- st.session_state["decade_count"] = 1950 # 시작 년도 설정
176
- if "sector_news_impact" not in st.session_state:
177
- st.session_state["sector_news_impact"] = {}
178
- if 'buy_confirm' not in st.session_state:
179
- st.session_state['buy_confirm'] = False
180
- if 'sell_confirm' not in st.session_state:
181
- st.session_state['sell_confirm'] = False
182
- if 'difficulty_level' not in st.session_state:
183
- st.session_state['difficulty_level'] = "중학생" # 기본 난이도 중학생으로 변경
184
-
185
- # --- 주식 데이터 초기화 함수 (Decade별) ---
186
- def initialize_stocks_for_decade(decade):
187
- if decade == 1950:
188
- return {
189
- "재건": {
190
- "대한방직": {"current_price": random.randint(100, 200), "price_history": [], "description": "6.25 전쟁 이후 재건 시대, 섬유 산업의 부흥을 이끈 대표 기업."},
191
- "조선맥주": {"current_price": random.randint(50, 100), "price_history": [], "description": "국민들의 갈증을 해소하며 성장한 맥주 회사. 현재는 하이트진로."},
192
- "락희화학": {"current_price": random.randint(80, 150), "price_history": [], "description": "플라스틱 산업의 선구자, 럭키화학! 현재 LG화학의 모태."},
193
- },
194
- "농업": {
195
- "경성고무": {"current_price": random.randint(30, 60), "price_history": [], "description": "농업용 고무 제품 생산. 생활 필수품 수요 증가로 성장."},
196
- "고려제당": {"current_price": random.randint(40, 80), "price_history": [], "description": "설탕, 밀가루 등 기초 식량 산업. CJ제일제당의 전신."},
197
- }
198
- }
199
- elif decade == 1960:
200
- return {
201
- "경공업": {
202
- "삼성물산": {"current_price": random.randint(300, 600), "price_history": [], "description": "수출 주도 경제 성장 견인. 종합상사로 해외 시장 개척."},
203
- "선경직물": {"current_price": random.randint(250, 500), "price_history": [], "description": "Made in Korea 신화의 주역. 섬유 수출의 선두 주자, 현재 SK네트웍스."},
204
- "태평양화학": {"current_price": random.randint(200, 400), "price_history": [], "description": "화장품 산업의 태동. ABC 크림으로 화장품 대중화, 아모레퍼시픽."},
205
- },
206
- "식품": {
207
- "신동방": {"current_price": random.randint(150, 300), "price_history": [], "description": "라면, 스낵 등 인스턴트 식품 인기. 농심의 옛 이름."},
208
- "미원": {"current_price": random.randint(120, 250), "price_history": [], "description": "MSG 조미료 '미원'으로 식탁 변화. 대상그룹의 모태."},
209
- }
210
- }
211
- elif decade == 1970:
212
- return {
213
- "중화학공업": {
214
- "포항제철": {"current_price": random.randint(800, 1500), "price_history": [], "description": "산업화의 쌀, 철강 생산. 국가 경제 발전의 핵심 동력, 현재 POSCO홀딩스."},
215
- "현대조선": {"current_price": random.randint(700, 1300), "price_history": [], "description": "수출 효자 산업, 조선업. 세계적인 조선 강국으로 발돋움."},
216
- "기아산업": {"current_price": random.randint(600, 1200), "price_history": [], "description": "자동차 산업 육성 정책 수혜. 브리사, 승용차 생산 시작, 현재 기아."},
217
- "금성사": {"current_price": random.randint(500, 1000), "price_history": [], "description": "가전 제품 국산화. 흑백 TV, 냉장고 생산, 현재 LG전자."},
218
- },
219
- "건설": {
220
- "현대건설": {"current_price": random.randint(400, 800), "price_history": [], "description": "중동 건설 붐 주도. 해외 건설 시장 진출, 현재 현대건설."},
221
- "대림산업": {"current_price": random.randint(350, 700), "price_history": [], "description": "국내 건설 시장 성장. 아파트 건설 붐, 현재 DL이앤씨."},
222
- }
223
- }
224
- elif decade == 1980:
225
- return {
226
- "전자": {
227
- "삼성전자": {"current_price": random.randint(1500, 3000), "price_history": [], "description": "반도체 산업 투자 확대. D램 개발, 세계적인 IT 기업으로 성장."},
228
- "금성사": {"current_price": random.randint(1200, 2500), "price_history": [], "description": "컬러 TV, VCR 생산. 가전 제품 수출 증가, 현재 LG전자."},
229
- "대우전자": {"current_price": random.randint(1000, 2000), "price_history": [], "description": "전자 산업 경쟁 심화. '탱크주의'로 유명, 현재 위니아."},
230
- },
231
- "자동차": {
232
- "현대자동차": {"current_price": random.randint(1300, 2700), "price_history": [], "description": "포니, 엑셀 수출. 자동차 대중화 시대 개막, 현재 현대자동차."},
233
- "기아자동차": {"current_price": random.randint(1100, 2400), "price_history": [], "description": "프라이드 출시. 소형차 시장 확대, 현재 기아."},
234
- "대우자동차": {"current_price": random.randint(900, 1800), "price_history": [], "description": "르망 출시. 다양한 차종 개발 경쟁, 현재 한국GM."},
235
- }
236
- }
237
- elif decade == 1990:
238
- return {
239
- "정보통신": {
240
- "한국통신": {"current_price": random.randint(2500, 5000), "price_history": [], "description": "통신 시장 개방 및 민영화. 초고속 인터넷 서비스 시작, 현재 KT."},
241
- "SK텔레콤": {"current_price": random.randint(2300, 4500), "price_history": [], "description": "이동통신 서비스 확산. 디지털 이동통신 'CDMA' 상용화, 현재 SK텔레콤."},
242
- "LG텔레콤": {"current_price": random.randint(2000, 4000), "price_history": [], "description": "PCS 서비스 경쟁. 이동통신 시장 경쟁 심화, 현재 LG유플러스."},
243
- "데이콤": {"current_price": random.randint(1800, 3500), "price_history": [], "description": "국제전화, 시외전화 서비스. 통신 시장 다변화, 현재 LG유플러스."},
244
- },
245
- "금융": {
246
- "국민은행": {"current_price": random.randint(1600, 3200), "price_history": [], "description": "금융 시장 자유화. 은행 경쟁 심화, 현재 KB국민은행."},
247
- "신한은행": {"current_price": random.randint(1400, 2800), "price_history": [], "description": "IMF 외환 위기 속에서 성장. M&A 통해 몸집 불린 은행, 현재 신한은행."},
248
- "하나은행": {"current_price": random.randint(1300, 2600), "price_history": [], "description": "외환 은행 강자. 국제 금융 업무 특화, 현재 하나은행."},
249
- }
250
- }
251
- elif decade == 2000:
252
- return {
253
- "IT": {
254
- "네이버": {"current_price": random.randint(4000, 8000), "price_history": [], "description": "인터넷 시대 개막. 검색 포털 1위, IT 강국 코리아."},
255
- "다음": {"current_price": random.randint(3500, 7000), "price_history": [], "description": "Daum 카페, 이메일 서비스 인기. 네이버와 경쟁."},
256
- "NHN": {"current_price": random.randint(3000, 6000), "price_history": [], "description": "온라인 게임 시장 성장. '한게임' 포털 운영, 현재 NHN."},
257
- "싸이월드": {"current_price": random.randint(2500, 5000), "price_history": [], "description": "소셜 미디어 열풍. 미니홈피, 아바타 서비스 인기."},
258
- },
259
- "엔터테인먼트": {
260
- "SM엔터테인먼트": {"current_price": random.randint(2000, 4000), "price_history": [], "description": "한류 열풍 시작. BoA, 동방신기 등 아이돌 그룹 인기."},
261
- "YG엔터테인먼트": {"current_price": random.randint(1800, 3500), "price_history": [], "description": "힙합 음악 대중화. 빅뱅, 2NE1 등 개성 강한 그룹."},
262
- "JYP엔터테인먼트": {"current_price": random.randint(1600, 3200), "price_history": [], "description": "댄스 음악 강세. god, 원더걸스 등 국민 그룹 배출."},
263
- }
264
- }
265
- elif decade == 2010:
266
- return {
267
- "플랫폼": {
268
- "카카오": {"current_price": random.randint(6000, 12000), "price_history": [], "description": "모바일 메신저 시대. 카카오톡, 국민 앱으로 성장."},
269
- "네이버": {"current_price": random.randint(5500, 11000), "price_history": [], "description": "모바일 플랫폼 강화. 라인 메신저 해외 진출."},
270
- "쿠팡": {"current_price": random.randint(5000, 10000), "price_history": [], "description": "이커머스 시장 급성장. 로켓 배송으로 온라인 쇼핑 혁신."},
271
- "배달의민족": {"current_price": random.randint(4500, 9000), "price_history": [], "description": "O2O 서비스 확대. 배달 앱 시장 선점, 현재 우아한형제들."},
272
- },
273
- "바이오": {
274
- "셀트리온": {"current_price": random.randint(4000, 8000), "price_history": [], "description": "바이오시밀러 개발 성공. 바이오 의약품 수출, K-바이오."},
275
- "삼성바이오로직스": {"current_price": random.randint(3500, 7000), "price_history": [], "description": "바이오 의약품 생산 대행. CMO 사업 성장, 바이오 산업 육성."},
276
- "메디톡스": {"current_price": random.randint(3000, 6000), "price_history": [], "description": "보톡스 국산화 성공. 미용 의료 시장 확대."},
277
- }
278
- }
279
- elif decade == 2020:
280
- return {
281
- "미래산업": {
282
- "LG에너지솔루션": {"current_price": random.randint(8000, 16000), "price_history": [], "description": "전기차 배터리 시장 선도. 친환경 에너지 시대 개막."},
283
- "SK하이닉스": {"current_price": random.randint(7000, 14000), "price_history": [], "description": "반도체 슈퍼 호황. 메모리 반도체 수요 폭증, IT 강국 위상."},
284
- "현대자동차": {"current_price": random.randint(6500, 13000), "price_history": [], "description": "전기차, 수소차 개발 박차. 미래 모빌리티 전환."},
285
- "카카오뱅크": {"current_price": random.randint(6000, 12000), "price_history": [], "description": "디지털 금융 혁신. 인터넷 전문 은행 시대 개막."},
286
- "하이브": {"current_price": random.randint(5500, 11000), "price_history": [], "description": "K-팝 세계화. BTS, 블랙핑크 글로벌 팬덤 확보."},
287
- },
288
- "플랫폼": { # 2020년대 플랫폼 기업들은 미래산업 섹터에도 포함될 수 있지만, 분리하여 표현
289
- "네이버": {"current_price": random.randint(5000, 10000), "price_history": [], "description": "AI, 클라우드 등 신기술 투자 확대. 플랫폼 경쟁 심화."},
290
- "카카오": {"current_price": random.randint(4500, 9000), "price_history": [], "description": "ESG 경영 강화. 사회적 책임 강조, 플랫폼 규제."},
291
- }
292
- }
293
- else: # 2030년 이후를 위한 기본 데이터 (현재 2020년대 데이터와 유사하게 설정)
294
- return {
295
- "미래산업": {
296
- "LG에너지솔루션": {"current_price": random.randint(8000, 16000), "price_history": [], "description": "전기차 배터리 시장 선도. 친환경 에너지 시대 개막."},
297
- "SK하이닉스": {"current_price": random.randint(7000, 14000), "price_history": [], "description": "반도체 슈퍼 호황. 메모리 반도체 수요 폭증, IT 강국 위상."},
298
- "현대자동차": {"current_price": random.randint(6500, 13000), "price_history": [], "description": "전기차, 수소차 개발 박차. 미래 모빌리티 전환."},
299
- "카카오뱅크": {"current_price": random.randint(6000, 12000), "price_history": [], "description": "디지털 금융 혁신. 인터넷 전문 은행 시대 개막."},
300
- "하이브": {"current_price": random.randint(5500, 11000), "price_history": [], "description": "K-팝 세계화. BTS, 블랙핑크 글로벌 팬덤 확보."},
301
- },
302
- "플랫폼": { # 2020년대 플랫폼 기업들은 미래산업 섹터에도 포함될 수 있지만, 분리하여 표현
303
- "네이버": {"current_price": random.randint(5000, 10000), "price_history": [], "description": "AI, 클라우드 등 신기술 투자 확대. 플랫폼 경쟁 심화."},
304
- "카카오": {"current_price": random.randint(4500, 9000), "price_history": [], "description": "ESG 경영 강화. 사회적 책임 강조, 플랫폼 규제."},
305
- }
306
- }
307
-
308
- # --- 뉴스 생성 함수 ---
309
- def generate_news():
310
- decade_count = st.session_state["decade_count"]
311
- difficulty_level = st.session_state['difficulty_level']
312
-
313
- difficulty_prompt_map = {
314
- "초등학생": {
315
- "level_desc": "초등학생 5~6학년 수준",
316
- "sentence_count": "8~10",
317
- "vocabulary_level": "쉬운 어휘와 짧은 문장",
318
- "inference_level": "단순하고 명확한 정보",
319
- },
320
- "중학생": {
321
- "level_desc": "중학생 1~3학년 수준",
322
- "sentence_count": "10~12",
323
- "vocabulary_level": "일상적인 어휘와 약간의 전문 용어",
324
- "inference_level": "일반적인 경제 흐름과 관련된 추론",
325
- },
326
- "고등학생": {
327
- "level_desc": "고등학생 1~3학년 수준",
328
- "sentence_count": "12~15",
329
- "vocabulary_level": "다양한 어휘와 경제 전문 용어 포함",
330
- "inference_level": "심층적인 경제 분석 및 다각적인 추론",
331
- },
332
- }
333
-
334
- level_config = difficulty_prompt_map.get(difficulty_level, difficulty_prompt_map["중학생"])
335
-
336
- level_prompt = level_config["level_desc"]
337
- sentence_count = level_config["sentence_count"]
338
- vocabulary_level = level_config["vocabulary_level"]
339
- inference_level = level_config["inference_level"]
340
-
341
- decade_keyword_map = { # Decade별 경제 뉴스 키워드 설정
342
- 1950: "1950년대 한국전쟁 이후 재건, 원조 경제, 농업, 경공업",
343
- 1960: "1960년대 경제 개발 5개년 계획, 수출 주도 성장, 경공업 육성",
344
- 1970: "1970년대 중화학공업 육성, 오일 쇼크, 새마을 운동, 건설업",
345
- 1980: "1980년대 컬러 TV 보급, 자동차 산업 성장, 프로야구 출범, 민주화 운동",
346
- 1990: "1990년대 IMF 외환 위기, 정보통신 혁명, 인터넷, 벤처 기업",
347
- 2000: "2000년대 IT 버블 붕괴, 디지털 콘텐츠 산업, 한류, 온라인 게임",
348
- 2010: "2010년대 스마트폰 보급, 소셜 미디어, 핀테크, 공유 경제, 바이오시밀러",
349
- 2020: "2020년대 코로나19 팬데믹, 디지털 전환, 플랫폼 경제, ESG 경영, 미래 모빌리티, 인공지능",
350
  }
351
- decade_keywords = decade_keyword_map.get(decade_count, "2020년대 미래산업, 플랫폼 경제") # 기본 키워드 설정
352
 
 
 
353
  prompt = f"""
354
- 지시:
355
- {level_prompt}에 맞춰서, **{decade_count}년대 대한민국 경제**와 관련된 뉴스 기사 5개를 생성해주세요.
356
- **뉴스 주제**는 "{decade_keywords}" 키워드를 참고하여 시대적 특징을 반영해주세요.
357
- 기사는 {sentence_count}문장 정도로 작성하고, {vocabulary_level}를 사용하여 학생들이 이해하기 쉬워야 합니다.
358
- 학생들이 뉴스를 읽고 {inference_level} 수준에서 어떤 회사가 유망할지 또는 쇠락할지 스스로 추론할 수 있도록 {decade_count}년대 대한민국 경제 상황이나 산업 동향에 대한 뉴스를 만들어주세요.
359
- 특정 회사 이름이나 주식 종목을 직접적으로 언급하지 마세요.
360
- 긍정적 뉴스, 부정적 뉴스, 중립적 뉴스 다양하게 생성하세요.(긍정, 부정, 중립 이라는 말은 표시하지 마세요.)
361
- 뉴스에 따라 주식이 상승하기도 하고 하락하기도 할 수 있습니다.
362
- 각 뉴스 기사는 "## 뉴스 [번호]" 로 시작해주세요. (예: ## 뉴스 1, ## 뉴스 2 ...)
363
 
364
- **생성된 뉴스 기사:**
365
- """
366
- chat_session = st.session_state["chat_session"]
367
- messages = [{"role": "user", "content": prompt}]
368
-
369
- try: # OpenAI API 호출 예외 처리
370
- response = client.chat.completions.create(
371
- model="gpt-4o-mini",
372
- messages=messages,
373
- response_format={
374
- "type": "text"
375
- },
376
- temperature=0.7,
377
- max_completion_tokens=10000,
378
- top_p=0.95,
379
- frequency_penalty=0,
380
- presence_penalty=0
381
- )
382
- news_text = response.choices[0].message.content.strip()
383
-
384
- news_articles = []
385
- if news_text:
386
- news_articles = [
387
- article.strip() for article in news_text.split("## 뉴스 ") if article.strip()
388
- ]
389
-
390
- return news_articles[:5]
391
 
392
- except Exception as e: # API 호출 에러 발생 시 사용자에게 알림
393
- st.error(f"뉴스 생성 중 오류가 발생했습니다: {e}")
394
- return [] # 오류 발생 시 빈 리스트 반환
395
-
396
- # explain_daily_news_meanings 함수 (기존 코드와 동일)
397
- def explain_daily_news_meanings(daily_news):
398
- if daily_news is None:
399
- return {}
400
-
401
- difficulty_level = st.session_state['difficulty_level']
402
- difficulty_prompt_map = {
403
- "초등학생": "초등학생 5~6학년",
404
- "중학생": "중학생 1~3학년",
405
- "고등학생": "고등학생 1~3학년",
406
- }
407
- level_prompt = difficulty_prompt_map.get(difficulty_level, "중학생 1~3학년")
408
-
409
- meanings = {}
410
- for i, news_article in enumerate(daily_news):
411
- prompt = f"""
412
- **신문 기사:**
413
- {news_article}
414
-
415
- **지시:**
416
- 위 신문 기사의 핵심 의미를 {level_prompt}이 이해하기 쉽게 3문장 이내로 요약해서 "해설: " 다음에 설명해주세요.
417
- 그리고 이 뉴스와 관련된 주식 섹터 1~2개를 쉼표로 구분해서 "관련 섹터: " 다음에 알려주세요. 관련 섹터가 없다면 "관련 섹터: 없음" 이라고 해주세요.
418
-
419
- 뉴스 의미 해설:
420
  """
421
- chat_session = st.session_state["chat_session"]
422
- messages = [{"role": "user", "content": prompt}]
423
- try:
424
- response = client.chat.completions.create(
425
- model="gpt-4o-mini",
426
- messages=messages,
427
- response_format={
428
- "type": "text"
429
- },
430
- temperature=0.7,
431
- max_completion_tokens=10000,
432
- top_p=0.95,
433
- frequency_penalty=0,
434
- presence_penalty=0
435
- )
436
- meaning_text = response.choices[0].message.content.strip()
437
-
438
- explanation = ""
439
- related_sectors = []
440
-
441
- if "해설:" in meaning_text:
442
- explanation_start_index = meaning_text.find("해설:") + len("해설:")
443
- explanation_end_index = meaning_text.find("관련 섹터:")
444
- if explanation_end_index != -1:
445
- explanation = meaning_text[explanation_start_index:explanation_end_index].strip()
446
- else:
447
- explanation = meaning_text[explanation_start_index:].strip()
448
-
449
- if "관련 섹터:" in meaning_text:
450
- related_sectors_str = meaning_text.split("관련 섹터:")[1].strip()
451
- if related_sectors_str.lower() != "없음":
452
- related_sectors = [sector.strip() for sector in related_sectors_str.split(',')]
453
- else:
454
- related_sectors = [] # "없음" explicitly means empty list
455
-
456
- meanings[str(i + 1)] = {"explanation": explanation, "sectors": related_sectors}
457
-
458
- except Exception as e: # google.api_core.exceptions.ResourceExhausted -> Exception으로 변경
459
- st.error(
460
- f"API 할당량 초과 오류가 발생했습니다. 잠시 후 다시 시도해주세요. 오류 메시지: {e}"
461
- )
462
- return None
463
- time.sleep(1)
464
- return meanings
465
-
466
- # buy_stock, sell_stock 함수 (기존 코드와 동일)
467
- def buy_stock(stock_name, quantity, sector):
468
- if (
469
- sector not in st.session_state["stocks"]
470
- or stock_name not in st.session_state["stocks"][sector]
471
- ):
472
- st.session_state["messages"].append(
473
- {"type": "error", "text": "존재하지 않는 주식 종목입니다."}
474
- )
475
- return
476
-
477
- if quantity <= 0:
478
- st.session_state["messages"].append(
479
- {"type": "error", "text": "매수 수량은 1주 이상이어야 합니다."}
480
- )
481
- return
482
-
483
- stock_price = st.session_state["stocks"][sector][stock_name]["current_price"]
484
- max_quantity = st.session_state["portfolio"]["cash"] // stock_price
485
- if quantity > max_quantity:
486
- st.session_state["messages"].append(
487
- {
488
- "type": "error",
489
- "text": f"매수 가능 수량을 초과했습니다. (최대 {max_quantity}주까지 매수 가능)",
490
- }
491
- )
492
- st.toast(
493
- f"매수 가능 수량을 초과했습니다. (최대 {max_quantity}주까지 매수 가능)", icon="❌"
494
- )
495
- st.error(f"잔액이 부족합니다. (최대 {max_quantity}주까지 매수 가능)")
496
- return
497
-
498
- total_price = stock_price * quantity
499
-
500
- if st.session_state["portfolio"]["cash"] >= total_price:
501
- st.session_state["portfolio"]["cash"] -= total_price
502
- portfolio_stocks = st.session_state["portfolio"]["stocks"]
503
- if (
504
- stock_name in portfolio_stocks
505
- ):
506
- portfolio_stocks[stock_name]["quantity"] += quantity
507
- portfolio_stocks[stock_name]["purchase_price"] = (
508
- portfolio_stocks[stock_name]["purchase_price"]
509
- * (portfolio_stocks[stock_name]["quantity"] - quantity)
510
- + total_price
511
- ) / portfolio_stocks[stock_name]["quantity"]
512
- else:
513
- portfolio_stocks[stock_name] = {
514
- "quantity": quantity,
515
- "purchase_price": total_price / quantity,
516
- }
517
- st.session_state["messages"].append(
518
- {
519
- "type": "success",
520
- "text": f"{stock_name} {quantity}주 매수 완료. 총 {total_price:,.0f}원 소요.",
521
- }
522
- )
523
- st.success(f"{stock_name} {quantity}주 매수 완료. 총 {total_price:,.0f}원 소요.")
524
- st.toast(
525
- f"{stock_name} {quantity}주 매수 완료. 총 {total_price:,.0f}원 소요.", icon="✅"
526
- )
527
- st.session_state['buy_confirm'] = False
528
- else:
529
- st.session_state["messages"].append(
530
- {"type": "error", "text": "잔액이 부족합니다."}
531
- )
532
- st.toast("잔액이 부족합니다.", icon="❌")
533
- st.error(f"잔액이 부족합니다. (최대 {max_quantity}주까지 매수 가능)")
534
- st.session_state['buy_confirm'] = False
535
-
536
-
537
- def sell_stock(stock_name, quantity):
538
- if stock_name not in st.session_state["portfolio"]["stocks"]:
539
- st.session_state["messages"].append(
540
- {"type": "error", "text": "보유하고 있지 않은 주식입니다."}
541
- )
542
- st.error("보유하고 있지 않은 주식입니다.")
543
- st.toast("보유하고 있지 않은 주식입니다.", icon="❌")
544
- return
545
-
546
- owned_quantity = st.session_state["portfolio"]["stocks"][stock_name]["quantity"]
547
-
548
- if owned_quantity < quantity:
549
- st.session_state["messages"].append(
550
- {"type": "error", "text": "매도 수량이 보유 주식 수를 초과했습니다."}
551
- )
552
- st.error("매도 수량이 보유 주식 수를 초과했습니다.")
553
- st.toast("매도 수량이 보유 주식 수를 초과했습니다.", icon="❌")
554
- return
555
-
556
- if quantity <= 0:
557
- st.session_state["messages"].append(
558
- {"type": "error", "text": "잘못된 매도 수량입니다."}
559
- )
560
- st.error("잘못된 매도 수량입니다.")
561
- st.toast("잘못된 매도 수량입니다.", icon="❌")
562
- return
563
-
564
- stock_price = 0
565
- stock_sector = ""
566
- for sector, stocks in st.session_state["stocks"].items():
567
- if stock_name in stocks:
568
- stock_price = stocks[stock_name]["current_price"]
569
- stock_sector = sector
570
- break
571
-
572
- if stock_price == 0:
573
- st.session_state["messages"].append(
574
- {"type": "error", "text": "주식 정보를 찾을 수 없습니다."}
575
- )
576
- return
577
-
578
- sell_price = stock_price * quantity
579
- st.session_state["portfolio"]["cash"] += sell_price
580
- st.session_state["portfolio"]["stocks"][stock_name]["quantity"] -= quantity
581
- if st.session_state["portfolio"]["stocks"][stock_name]["quantity"] == 0:
582
- del st.session_state["portfolio"]["stocks"][stock_name]
583
-
584
- st.session_state["messages"].append(
585
- {
586
- "type": "success",
587
- "text": f"{stock_name} {quantity}주 매도 완료. 총 {sell_price:,.0f}원 획득.",
588
- }
589
- )
590
- st.toast(
591
- f"{stock_name} {quantity}주 매도 완료. 총 {sell_price:,.0f}원 획득.", icon="✅"
592
- )
593
- st.success(f"{stock_name} {quantity}주 매도 완료. 총 {sell_price:,.0f}원 획득.")
594
- st.session_state['sell_confirm'] = False
595
-
596
- # update_stock_prices 함수 (Decade 경제 상황 반영 추가)
597
- def update_stock_prices():
598
- if not st.session_state["daily_news"]:
599
- return
600
-
601
- sector_impacts = {sector: 0 for sector in st.session_state["stocks"]}
602
- decade_count = st.session_state["decade_count"]
603
- decade_economic_conditions = { # Decade별 경제 상황 반영 (수치 예시는 임의)
604
- 1950: {"growth_rate": 0.02, "volatility": 0.03}, # 재건 시대, 낮은 성장률, 변동성 중간
605
- 1960: {"growth_rate": 0.05, "volatility": 0.02}, # 경제 개발, 높은 성장률, 변동성 낮음
606
- 1970: {"growth_rate": 0.08, "volatility": 0.05}, # 중화학 공업, 고성장, 오일쇼크 변동성 증가
607
- 1980: {"growth_rate": 0.06, "volatility": 0.04}, # 안정 성장, 변동성 중간
608
- 1990: {"growth_rate": 0.03, "volatility": 0.1}, # IMF 외환위기, 낮은 성장률, 높은 변동성
609
- 2000: {"growth_rate": 0.04, "volatility": 0.07}, # IT 버블, 성장률 회복, 변동성 증가
610
- 2010: {"growth_rate": 0.03, "volatility": 0.05}, # 저성장 시대, 변동성 중간
611
- 2020: {"growth_rate": 0.02, "volatility": 0.08}, # 코로나19, 저성장, 높은 변동성
612
- }
613
- economic_condition = decade_economic_conditions.get(decade_count, {"growth_rate": 0.03, "volatility": 0.06}) # 기본값 설정
614
-
615
- for i, news_article in enumerate(st.session_state["daily_news"]):
616
- news_meaning = st.session_state["news_meanings"].get(str(i + 1))
617
- if news_meaning:
618
- related_sectors = news_meaning.get("sectors", [])
619
- news_explanation = news_meaning.get("explanation", "")
620
-
621
- news_sentiment = 0
622
- if "상승" in news_article or "성장" in news_article or "긍정적" in news_article or "유망" in news_article or "호황" in news_article:
623
- news_sentiment = 1
624
- elif "하락" in news_article or "감소" in news_article or "부정적" in news_article or "어려움" in news_article or "침체" in news_article or "위기" in news_article:
625
- news_sentiment = -1
626
- else:
627
- news_sentiment = 0
628
-
629
- for sector in related_sectors:
630
- if sector in sector_impacts:
631
- sector_impacts[sector] += news_sentiment * 0.03 # 뉴스 sentiment 영향도 감소
632
-
633
- for sector in st.session_state["stocks"]:
634
- sector_impact = sector_impacts[sector]
635
- # 경제 상황 반영: 성장률, 변동성
636
- base_change_rate = economic_condition["growth_rate"] # 기본 성장률 적용
637
- volatility = economic_condition["volatility"] # 변동성 적용
638
- change_rate = random.uniform(-volatility, volatility) + base_change_rate + sector_impact # 변동폭 증가, sector_impact 감소
639
-
640
- for stock_name in st.session_state["stocks"][sector]:
641
- change_rate = max(-0.2, min(0.2, change_rate)) # 변동폭 제한 (±20% 이내)
642
- st.session_state["stocks"][sector][stock_name]["current_price"] *= (
643
- 1 + change_rate
644
- )
645
- st.session_state["stocks"][sector][stock_name]["current_price"] = max(
646
- 1, int(st.session_state["stocks"][sector][stock_name]["current_price"])
647
- )
648
- st.session_state["stocks"][sector][stock_name]["price_history"].append(
649
- st.session_state["stocks"][sector][stock_name]["current_price"]
650
- )
651
- st.session_state["messages"].append({"type": "info", "text": "주가가 변동되었습니다."})
652
- st.toast("주가가 변동되었습니다.", icon="📈")
653
- st.info("주가가 변동되었습니다.")
654
- st.session_state["sector_news_impact"] = sector_impacts
655
-
656
- # display_portfolio, display_stock_prices, display_portfolio_table 함수 (기존 코드와 동일)
657
- def display_portfolio():
658
- portfolio = st.session_state["portfolio"]
659
- cash = portfolio["cash"]
660
- total_value = cash
661
- total_purchase_value = 0
662
- for stock_name, stock_info in portfolio["stocks"].items():
663
- quantity = stock_info["quantity"]
664
- purchase_price = stock_info["purchase_price"]
665
- current_price = 0
666
- stock_sector = ""
667
- for sector, stocks in st.session_state["stocks"].items():
668
- if stock_name in stocks:
669
- current_price = stocks[stock_name]["current_price"]
670
- stock_sector = sector
671
- break
672
- if current_price != 0:
673
- stock_value = current_price * quantity
674
- total_value += stock_value
675
- purchase_value = purchase_price * quantity
676
- total_purchase_value += purchase_value
677
-
678
- initial_cash = 10000000
679
- total_profit_loss = total_value - initial_cash
680
- total_profit_rate = (
681
- (total_profit_loss / initial_cash) * 100 if initial_cash != 0 else 0
682
- )
683
-
684
- return cash, total_value, total_profit_rate
685
-
686
- def display_stock_prices():
687
- stocks_data = []
688
- for sector, sector_stocks in st.session_state["stocks"].items():
689
- for stock_name, stock_info in sector_stocks.items():
690
- price_history = stock_info["price_history"]
691
- daily_change_rate_str = " - " # 기본값
692
- if len(price_history) >= 2:
693
- previous_day_price = price_history[-2]
694
- current_price = price_history[-1]
695
- daily_change_rate = (current_price - previous_day_price) / previous_day_price * 100
696
- daily_change_rate_str = f"{daily_change_rate:.2f}%"
697
-
698
- stocks_data.append(
699
- {
700
- "종목": stock_name,
701
- "섹터": sector,
702
- "현재 주가": f"{stock_info['current_price']:,} 원",
703
- "전일 대비": daily_change_rate_str, # 전일 대비 등락률 추가
704
- "price_history": stock_info["price_history"],
705
- "description": stock_info["description"],
706
- }
707
- )
708
- stocks_df = pd.DataFrame(stocks_data)
709
- st.dataframe(stocks_df[["섹터", "종목", "현재 주가", "전일 대비"]], hide_index=True) # "전일 대비" 컬럼 추가
710
-
711
- selected_stock_all_info = st.selectbox(
712
- "종목 선택 (기업 정보 및 주가 그래프)", stocks_df["종목"].tolist()
713
- )
714
- if selected_stock_all_info:
715
- selected_stock_sector = stocks_df[
716
- stocks_df["종목"] == selected_stock_all_info
717
- ]["섹터"].iloc[0]
718
- col1_info, col2_graph = st.columns([1, 2])
719
-
720
- with col1_info:
721
- st.subheader("기업 정보")
722
- st.info(
723
- f"**{selected_stock_all_info} ({selected_stock_sector})**\n\n{st.session_state['stocks'][selected_stock_sector][selected_stock_all_info]['description']}"
724
- )
725
-
726
- with col2_graph:
727
- st.subheader("주가 그래프")
728
- price_history_df = pd.DataFrame(
729
- {
730
- "날짜": range(
731
- 1,
732
- len(
733
- st.session_state["stocks"][selected_stock_sector][
734
- selected_stock_all_info
735
- ]["price_history"]
736
- )
737
- + 1,
738
- ),
739
- "주가": st.session_state["stocks"][selected_stock_sector][
740
- selected_stock_all_info
741
- ]["price_history"],
742
- }
743
- )
744
- fig = px.line(
745
- price_history_df,
746
- x="날짜",
747
- y="주가",
748
- title=f"{selected_stock_all_info} ({selected_stock_sector}) 주가 변동",
749
- )
750
- st.plotly_chart(fig)
751
- else:
752
- st.info("종목을 선택하여 기업 정보와 주가 그래프를 확인하세요.")
753
-
754
- def display_portfolio_table():
755
- portfolio = st.session_state["portfolio"]
756
- if portfolio["stocks"]:
757
- portfolio_data = []
758
- total_value = portfolio["cash"]
759
- total_purchase_value = 0
760
- total_profit_loss = 0
761
- total_profit_rate = 0.0
762
- for stock_name, stock_info in portfolio["stocks"].items():
763
- quantity = stock_info["quantity"]
764
- purchase_price = stock_info["purchase_price"]
765
- current_price = 0
766
- stock_sector = ""
767
- for sector, stocks in st.session_state["stocks"].items():
768
- if stock_name in stocks:
769
- current_price = stocks[stock_name]["current_price"]
770
- stock_sector = sector
771
- break
772
-
773
- if current_price == 0:
774
- continue
775
 
776
- stock_value = current_price * quantity
777
- purchase_value = purchase_price * quantity
778
- profit_loss = stock_value - purchase_value
779
- profit_rate = (
780
- (profit_loss / purchase_value) * 100 if purchase_value != 0 else 0
781
- )
782
- total_value += stock_value
783
- total_purchase_value += purchase_value
784
- total_profit_loss += profit_loss
785
 
786
- portfolio_data.append(
787
- {
788
- "종목": stock_name,
789
- "섹터": stock_sector,
790
- "보유 수량": quantity,
791
- "매수 단가": f"{purchase_price:,.0f} 원",
792
- "현재가": f"{current_price:,.0f} 원",
793
- "평가액": f"{stock_value:,.0f} 원",
794
- "손익": f"{profit_loss:,.0f} 원",
795
- "수익률": f"{profit_rate:.2f}%",
796
- }
797
- )
798
- portfolio_data.append(
799
- {
800
- "종목": "현금",
801
- "섹터": "-",
802
- "보유 수량": "-",
803
- "매수 단가": "-",
804
- "현재가": "-",
805
- "평가액": f"{portfolio['cash']:,} 원",
806
- "손익": "-",
807
- "수익률": "-",
808
- }
809
- )
810
- portfolio_df = pd.DataFrame(portfolio_data)
811
- st.dataframe(portfolio_df, hide_index=True, height=350)
812
- st.markdown(
813
- f"""**현금 잔고:** {portfolio['cash']:,} 원
814
- **📊 총 평가액:** {total_value:,.0f} 원
815
- **🛒 총 매수 금액:** {total_purchase_value:,.0f} 원
816
- **📈📉 총 손익:** {total_profit_loss:,.0f} 원 (🚀 수익률: {total_profit_rate:.2f}%)
817
  """
818
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
  else:
820
- st.info("보유 주식이 없습니다.")
821
-
822
- # display_stock_glossary 함수 (기존 코드와 동일)
823
- def display_stock_glossary():
824
- glossary = {
825
- "주식": "회사의 일부분을 나타내는 증서. 주식을 사면 회사의 주인이 되는 거예요.",
826
- "주가": "주식 1개당 가격. 사람들이 주식을 사고팔 때 가격이 변해요.",
827
- "매수": "주식을 사는 것.",
828
- "매도": "주식을 파는 것.",
829
- "포트폴리오": "내가 가진 주식과 현금 목록.",
830
- "수익률": "원래 돈에서 얼마나 이익을 보았는지 비율로 나타낸 것. (이익 / 원래 돈) X 100",
831
- "상승": "주가가 오르는 것.",
832
- "하락": "주가가 내리는 것.",
833
- "변동": "주가가 오르락내리락 움직이는 것.",
834
- "투자": "돈을 넣어 이익을 얻으려고 하는 모든 활동.",
835
- "섹터": "비슷한 사업을 하는 회사들을 묶어 놓은 그룹 (예: 기술 섹터, 자동차 섹터)",
836
- "전일 대비 등락률": "오늘 주가가 어제보다 얼마나 변했는지 퍼센트로 나타낸 것. +는 상승, -는 하락.", # 용어 추가
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837
  }
838
- with st.sidebar.expander("📚 주식 용어 사전", expanded=False):
839
  for term, definition in glossary.items():
840
  st.markdown(f"**{term}:** {definition}")
841
  st.markdown("---")
842
 
843
- # --- 메인 화면 ---
844
  def main():
845
- col_news, col_main_ui = st.columns([1, 2])
846
-
847
- with col_news:
848
- st.header(f"📰 {st.session_state['decade_count']}년대 뉴스") # Decade 표시
849
- if st.button("뉴스 생성", use_container_width=True, key="news_gen_button"):
850
- with st.spinner(f"{st.session_state['decade_count']}년대 뉴스 생성 중..."): # Decade 표시
851
- current_daily_news = generate_news()
852
- st.session_state["daily_news"] = current_daily_news
853
-
854
- if st.session_state["daily_news"]:
855
- st.subheader(f"{st.session_state['decade_count']}년대 뉴스") # Decade 표시
856
- for i, news in enumerate(st.session_state["daily_news"]):
857
- with st.expander(f"뉴스 {i+1}", expanded=False):
858
- st.write(news)
859
-
860
- if st.session_state["previous_daily_news"] and st.session_state[
861
- "news_meanings"
862
- ]:
863
- previous_decade = st.session_state['decade_count'] - 10 # 이전 Decade 계산
864
- st.subheader(f"{previous_decade}년대 뉴스 해설") # 이전 Decade 표시
865
- st.info("AI가 분석한 지난 뉴스 해설입니다.")
866
- with st.expander(f"{previous_decade}년대 뉴스 해설 보기", expanded=False): # 이전 Decade 표시
867
- if st.session_state["news_meanings"]:
868
- for i, meaning_data in st.session_state["news_meanings"].items():
869
- st.markdown(f"**뉴스 {i}**:")
870
- st.markdown(f"**AI 해설:** {meaning_data['explanation']}") # Markdown 으로 변경
871
- if meaning_data['sectors']:
872
- st.markdown(f"**관련 섹터:** {', '.join(meaning_data['sectors'])}") # Markdown 으로 변경
873
- else:
874
- st.markdown("**관련 섹터:** 없음") # Markdown 으로 변경
875
  else:
876
- st.info("지난 뉴스에 대한 해설이 없습니다.")
877
-
878
- elif not st.session_state["daily_news"]:
879
- st.info("뉴스 생성 버튼을 눌러 오늘의 뉴스를 받아보세요.")
880
-
881
- with col_main_ui:
882
- menu = st.tabs(
883
- ['현재 주가', '내 포트폴리오', '주식 매수', '주식 매도', '지난 뉴스 해설']
884
- )
885
-
886
- with menu[0]:
887
- st.subheader("📈 현재 주가 및 기업 정보")
888
- st.markdown("주식 시장의 현재 가격과 기업 정보를 확인하세요.")
889
- display_stock_prices()
890
-
891
- with menu[1]:
892
- st.subheader("📊 내 포트폴리오")
893
- st.markdown("현재 보유 중인 주식과 자산을 확인하세요.")
894
- display_portfolio_table()
895
-
896
- with menu[2]:
897
- st.subheader("💰 주식 매수")
898
- st.markdown("AI 예측과 뉴스 분석을 바탕으로 주식을 매수해보세요.")
899
- sector_names = list(st.session_state["stocks"].keys())
900
- selected_sector_buy = st.selectbox("매수 섹터 선택:", sector_names)
901
- stock_names_in_sector = list(
902
- st.session_state["stocks"][selected_sector_buy].keys()
903
- )
904
- selected_stock_buy = st.selectbox("매수 종목 선택:", stock_names_in_sector)
905
-
906
- stock_price_buy = st.session_state["stocks"][selected_sector_buy][
907
- selected_stock_buy
908
- ]["current_price"]
909
- st.info(f"**{selected_stock_buy}** 현재 주가: {stock_price_buy:,.0f}원")
910
- quantity_buy = st.number_input(
911
- "매수 수량 (주):", min_value=1, value=1, step=1
912
- )
913
-
914
- if not st.session_state['buy_confirm']:
915
- if st.button("주식 매수", use_container_width=True, key='buy_button_confirm'):
916
- st.session_state['buy_confirm'] = True
917
  else:
918
- st.warning("정말 매수하시겠습니까?")
919
- col_confirm, col_cancel = st.columns([1, 1])
920
- with col_confirm:
921
- if st.button("✅ 매수 확인", use_container_width=True, key='buy_confirm_button'):
922
- buy_stock(selected_stock_buy, quantity_buy, selected_sector_buy)
923
 
924
- with col_cancel:
925
- if st.button(" 매수 취소", use_container_width=True, key='buy_cancel_button', type='secondary'):
926
- st.session_state['buy_confirm'] = False
927
- st.info("매수를 취소했습니다.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928
 
929
- with menu[3]:
930
- st.subheader("📉 주식 매도")
931
- st.markdown("보유 중인 주식을 판매하고 수익을 실현해보세요.")
932
- if st.session_state["portfolio"]["stocks"]:
933
- stock_names_sell = list(st.session_state["portfolio"]["stocks"].keys())
934
- selected_stock_sell = st.selectbox("매도 종목 선택:", stock_names_sell)
935
- stock_price_sell = 0
936
- for sector, stocks in st.session_state["stocks"].items():
937
- if selected_stock_sell in stocks:
938
- stock_price_sell = stocks[selected_stock_sell]["current_price"]
939
- break
940
 
941
- st.info(f"**{selected_stock_sell}** 현재 주가: {stock_price_sell:,.0f}원")
942
- max_sell_quantity = st.session_state["portfolio"]["stocks"][
943
- selected_stock_sell
944
- ]["quantity"]
945
- quantity_sell = st.number_input(
946
- "매도 수량 (주):",
947
- min_value=1,
948
- max_value=max_sell_quantity,
949
- value=1,
950
- step=1,
951
- )
952
- if not st.session_state['sell_confirm']:
953
- if st.button("주식 매도", use_container_width=True, key='sell_button_confirm'):
954
- st.session_state['sell_confirm'] = True
955
- else:
956
- st.warning("정말 매도하시겠습니까?")
957
- col_confirm, col_cancel = st.columns([1, 1])
958
- with col_confirm:
959
- if st.button("✅ 매도 확인", use_container_width=True, key='sell_confirm_button'):
960
- sell_stock(selected_stock_sell, quantity_sell)
961
- with col_cancel:
962
- if st.button("❌ 매도 취소", use_container_width=True, key='sell_cancel_button', type='secondary'):
963
- st.session_state['sell_confirm'] = False
964
- st.info("매도를 취소했습니다.")
965
- else:
966
- st.info("보유 주식이 없습니다. 포트폴리오 탭에서 확인하세요.")
967
-
968
- with menu[4]:
969
- if st.session_state["previous_daily_news"] and st.session_state[
970
- "news_meanings"
971
- ]:
972
- previous_decade = st.session_state['decade_count'] - 10 # 이전 Decade 계산
973
- st.subheader(f"{previous_decade}년대 뉴스 해설") # 이전 Decade 표시
974
- st.info("AI가 분석한 지난 뉴스 해설입니다.")
975
- for i in range(len(st.session_state["previous_daily_news"])):
976
- with st.expander(f"뉴스 {i+1}", expanded=False):
977
- news_content = st.session_state["previous_daily_news"][i]
978
- st.markdown("**뉴스 원문:**")
979
- st.write(news_content)
980
- meaning_data = st.session_state["news_meanings"].get(str(i+1))
981
- if meaning_data:
982
- st.markdown("**AI 해설:**")
983
- st.info(meaning_data['explanation'])
984
- if meaning_data['sectors']:
985
- st.markdown("**관련 섹터:**")
986
- st.info(', '.join(meaning_data['sectors']))
987
- else:
988
- st.info("**관련 섹터:** 없음")
989
- else:
990
- st.warning("뉴스 해설을 생성하지 못했습니다.")
991
- else:
992
- st.info(
993
- "이전 뉴스 해설이 없습니다. 10년 후 버튼을 눌러 뉴스 해설을 받아보세요."
994
- )
995
 
 
996
  with st.sidebar:
997
- st.markdown(f"# 대한민국 경제 발전사 게임") # 앱 제목 변경
998
- st.markdown(f"### {st.session_state['decade_count']}년대") # Decade 표시
999
- st.markdown("---")
1000
-
1001
- # 난이도 선택 Selectbox (기존 코드와 동일)
1002
- difficulty_level = st.selectbox(
1003
- "📈 난이도 선택",
1004
- ["초등학생", "중학생", "고등학생"],
1005
- index=["초등학생", "중학생", "고등학생"].index(st.session_state['difficulty_level'])
1006
- )
1007
- st.session_state['difficulty_level'] = difficulty_level
1008
- st.markdown("---")
1009
-
1010
- decade_description_map = { # Decade별 게임 설명
1011
- 1950: "🇰🇷 **1950년대: 재건의 시대**\n\n 전쟁의 상처를 딛고 일어서는 시기입니다. 원조 경제와 농업을 기반으로 재건에 힘쓰세요.",
1012
- 1960: "🇰🇷 **1960년대: 경제 개발의 닻을 올리다**\n\n 경제 개발 5개년 계획이 시작됩니다. 수출과 경공업을 통해 경제 성장의 기반을 마련하세요.",
1013
- 1970: "🇰🇷 **1970년대: 중화학공업, 성장의 엔진**\n\n 중화학공업 육성 정책이 본격화됩니다. 건설 붐과 함께 중동 시장을 개척해보세요.",
1014
- 1980: "🇰🇷 **1980년대: 풍요 속의 변화**\n\n 컬러 TV와 자동차가 보급되고, 프로야구가 출범합니다. 민주화의 열기 속에서 경제의 다양성을 추구하세요.",
1015
- 1990: "🇰🇷 **1990년대: 시련과 도약**\n\n IMF 외환 위기를 극복하고 정보통신 혁명이 일어납니다. 벤처 기업에 투자하여 미래를 준비하세요.",
1016
- 2000: "🇰🇷 **2000년대: IT 강국, 문화 융성**\n\n IT 산업이 꽃피우고, 한류가 시작됩니다. 디지털 콘텐츠와 엔터테인먼트 산업에 주목하세요.",
1017
- 2010: "🇰🇷 **2010년대: 스마트 혁명, 플랫폼 경제**\n\n 스마트폰이 세상을 바꾸고, 플랫폼 기업이 성장합니다. 새로운 경제 질서에 올라타세요.",
1018
- 2020: "🇰🇷 **2020년대: 미래를 향한 도전**\n\n 팬데믹을 극복하고 미래 산업을 육성합니다. 친환경 에너지, 바이오, 플랫폼 기업의 미래를 예측해보세요.",
1019
- }
1020
- decade_description = decade_description_map.get(st.session_state['decade_count'], "🇰🇷 **미래 시대:**\n\n 미래 시대에 대비하여 새로운 산업과 기술에 투자해보세요.") # 기본 설명
1021
-
1022
- st.markdown(decade_description) # Decade별 설명
1023
-
1024
- cash, total_value, profit_rate = display_portfolio()
1025
- st.metric(label="💰 현금 잔고", value=f"{cash:,.0f} 원")
1026
- st.metric(label="📊 총 평가 금액", value=f"{total_value:,.0f} 원")
1027
- st.metric(label="🚀 총 수익률", value=f"{profit_rate:.2f}%")
1028
- st.markdown("---")
1029
-
1030
- if st.button("10년 후", use_container_width=True, key="decade_pass_button"): # 버튼 텍스트 변경
1031
- if st.session_state["daily_news"]:
1032
- with st.spinner(f"{st.session_state['decade_count']}년대 주가 변동 및 지난 뉴스 분석..."): # Decade 표시
1033
- st.session_state["previous_daily_news"] = st.session_state["daily_news"]
1034
- meanings = explain_daily_news_meanings(
1035
- st.session_state["previous_daily_news"]
1036
- )
1037
- if meanings:
1038
- st.session_state["news_meanings"] = meanings
1039
- update_stock_prices()
1040
- st.session_state["daily_news"] = generate_news()
1041
- st.session_state["decade_count"] += 10 # Decade + 10
1042
- st.session_state["stocks"] = initialize_stocks_for_decade(st.session_state["decade_count"]) # 다음 Decade 주식 데이터 초기화
1043
- st.rerun()
1044
- previous_decade = st.session_state['decade_count'] - 10 # 이전 Decade 계산
1045
- st.info(f"지난 {previous_decade}년대 뉴스 해설 탭에서 AI 분석을 확인해보세요.") # 이전 Decade 표시
1046
  else:
1047
- st.warning("오늘의 뉴스를 먼저 생성해주세요.")
1048
- st.markdown("***")
1049
-
1050
- display_stock_glossary()
1051
-
1052
- with st.expander("🚀 앱 사용 가이드", expanded=False): # 앱 가이드 수정
1053
- st.markdown(
1054
- """
1055
- **대한민국 경제 발전사 주식 투자 게임**에 오신 것을 환영��니다! ⏳
1056
-
1057
- **게임 목표:** 각 시대별 경제 뉴스에 기반하여 주식 투자를 하고, 대한민국 경제 발전사를 체험하며 투자 감각을 키우는 것입니다.
1058
-
1059
- **게임 방법:**
1060
-
1061
- **1단계: 뉴스 생성하기**
1062
- - 왼쪽 '오늘의 뉴스' 영역에서 '뉴스 생성' 버튼을 클릭하세요.
1063
- - AI가 **현재 {decade}년대**의 경제 뉴스를 5개 만들어줍니다.
1064
-
1065
- **2단계: 뉴스 읽고 예측하기**
1066
- - '오늘의 뉴스' 아래 뉴스들을 꼼꼼히 읽어보세요.
1067
- - 각 뉴스를 읽고 '🤔 어떤 회사가 이 뉴스 때문에 돈을 더 많이 벌 수 있을까?' 또는 '😥 손해를 볼 회사는 어디일까?' **{decade}년대 경제 상황**을 고려하여 예측해보세요.
1068
 
1069
- **3단계: 주가 및 기업 정보 확인하기**
1070
- - 메뉴에서 '📈 현재 주가' 탭을 선택하세요.
1071
- - **{decade}년대 주식 시장**의 현재 가격과 기업 정보를 확인하세요.
1072
 
1073
- **4단계: 주식 매수/매도하기**
1074
- - 메뉴에서 '💰 주식 매수' 또는 '📉 주식 매도' 탭을 선택하세요.
1075
- - 종목 선택, 수량 입력 매수/매도 버튼을 클릭하여 거래하세요.
1076
- - **팁:** 뉴스 예측과 **{decade}년대 경제 흐름**을 고려하여 투자 결정을 내리세요!
1077
 
1078
- **5단계: 포트폴리오 확인하기**
1079
- - 메뉴에서 '📊 포트폴리오' 탭을 선택하세요.
1080
- - 현재 자산 현황과 투자 수익률을 확인하세요.
1081
 
1082
- **6단계: 10년 후 & 이전 뉴스 해설 보기**
1083
- - 사이드바 메뉴의 '10년 ' 버튼을 클릭하세요.
1084
- - **다음 {next_decade}년대**로 넘어가고, 주가가 변동됩니다.
1085
- - '지난 뉴스 해설' 탭에서 이전 뉴스에 대한 AI 해설을 확인하고, 투자 전략을 복기해보세요.
 
 
1086
 
1087
- **주요 기능:**
1088
- - **시대별 뉴스:** **10년 단위 시대**에 맞는 경제 뉴스를 AI가 생성합니다.
1089
- - **AI 뉴스 해설:** 이전 뉴스에 대한 AI 해설을 통해 **경제 흐름**을 쉽게 이해할 수 있습니다.
1090
- - **시대별 주식 시장:** 시대별 유망 산업과 대표 기업, 경제 상황을 반영한 주식 시장을 제공합니다.
1091
- - **포트폴리오 관리:** 투자 자산과 수익률을 한눈에 보여줍니다.
1092
- - **난이도 조절:** 초등학생, 중학생, 고등학생 난이도를 선택하여 학습 효과를 높일 수 있습니다.
1093
 
1094
- **대한민국 경제 발전사를 따라가는 흥미진진한 주식 투자 게임! 지금 시작하세요!** 🚀
1095
- """.format(decade=st.session_state['decade_count'], next_decade=st.session_state['decade_count'] + 10)
1096
- )
1097
 
1098
  if __name__ == "__main__":
1099
- st.session_state["stocks"] = initialize_stocks_for_decade(st.session_state["decade_count"]) # stocks 초기화 (최초 실행 시 1950���대 데이터)
 
 
1100
  main()
 
 
1
  import streamlit as st
2
  import random
 
 
 
3
  from openai import OpenAI
4
+ import time
5
 
6
+ # --- OpenAI 클라이언트 설정 (기존 코드와 동일) ---
7
+ # client = OpenAI(...)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  # --- 세션 상태 초기화 ---
10
+ if 'game_year' not in st.session_state:
11
+ st.session_state['game_year'] = 2024
12
+ if 'national_status' not in st.session_state:
13
+ st.session_state['national_status'] = {'stability': 100, 'public_satisfaction': 70, 'influence_points': 5}
14
+ if 'government_branches' not in st.session_state:
15
+ # 초기 상태 설정 함수 호출 필요
16
+ st.session_state['government_branches'] = initialize_branches()
17
+ if 'current_issue' not in st.session_state:
18
+ st.session_state['current_issue'] = None
19
+ if 'issue_analysis' not in st.session_state:
20
+ st.session_state['issue_analysis'] = None
21
+ if 'event_log' not in st.session_state:
22
+ st.session_state['event_log'] = []
23
+
24
+ # --- 국가 기관 초기화 함수 ---
25
+ def initialize_branches():
26
+ # 기관의 초기 상태, 권한 설명 등 설정
27
+ return {
28
+ '행정부': {'name': '정부 (행정부)', 'role': '법률을 집행하고 국가 정책을 수립, 시행합니다.', 'status': '일상 업무 수행 중', 'actions': [], 'power_level': 75},
29
+ '국회': {'name': '국회 (입법부)', 'role': '법률을 만들거나 고치고, 국가 예산을 심의하며 행정부를 견제합니다.', 'status': '정기 국회 회기 중', 'actions': [], 'power_level': 80},
30
+ '법원': {'name': '법원/헌법재판소 (사법부)', 'role': '법률을 해석하고 적용하여 분쟁을 해결하며, 법률의 위헌 여부를 심판합니다.', 'status': '재판 진행 중', 'actions': [], 'power_level': 70}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
 
32
 
33
+ # --- 이슈 생성 함수 (generate_news 대체) ---
34
+ def generate_issue(game_year):
35
  prompt = f"""
36
+ {game_year}년 대한민국에서 발생할 만한 사회적, 정치적, 또는 경제적 이슈를 하나 생성해주세요.
37
+ 이슈는 행정부, 입법부, 사법부 하나 이상의 기관이 반응해야 할 성격을 가져야 합니다.
38
+ 이슈 내용은 3~4문장으로 간결하게 설명해주세요.
39
+ 이슈 제목은 "## 이슈: [이슈 제목]" 형식으로 시작해주세요.
 
 
 
 
 
40
 
41
+ 예시:
42
+ ## 이슈: 신종 감염병 국내 유입 확인
43
+ 최근 해외에서 유행하던 신종 감염병 X의 국내 첫 확진자가 발생했습니다. 빠른 확산 속도와 높은 치명률로 인해 국민들의 불안감이 커지고 있으며, 방역 당국의 초기 대응 능력에 대한 우려의 목소리가 높습니다. 효과적인 방역 대책 마련과 함께 관련 의료 시스템 정비가 시급한 상황입니다.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ 생성된 이슈:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  """
47
+ try:
48
+ # response = client.chat.completions.create(...) # GPT 호출
49
+ # issue_text = response.choices[0].message.content.strip()
50
+ # --- 임시 예시 ---
51
+ issues = [
52
+ "## 이슈: 최저임금 인상 관련 노사 갈등 심화\n내년도 최저임금 결정을 앞두고 노동계와 경영계의 입장이 첨예하게 대립하고 있습니다. 노동계는 생활 안정을 위해 큰 폭의 인상을 요구하는 반면, 경영계는 경제 상황 악화를 이유로 동결 또는 소폭 인상을 주장하며 갈등이 깊어지고 있습니다. 정부의 중재 역할과 국회의 관련 논의에 관심이 집중되고 있습니다.",
53
+ "## 이슈: 인공지능(AI) 창작물의 저작권 논란\n최근 AI 기술이 발전하면서 AI가 생성한 그림, 음악, 글 등의 저작권 귀속 문제가 사회적 논란으로 떠올랐습니다. 현행법으로는 AI 창작물의 권리 관계가 불분명하여 관련 분쟁이 증가하고 있으며, 이에 대한 명확한 법적 기준 마련이 시급하다는 지적이 제기되고 있습니다.",
54
+ "## 이슈: 기후 변화로 인한 농작물 피해 급증\n이상 기후 현상이 잦아지면서 특정 지역의 농작물 피해가 심각한 수준에 이르렀습니다. 농가 소득 감소와 식량 안보에 대한 우려가 커지면서, 정부의 실질적인 피해보상 대책과 기후 변화 적응을 위한 장기적인 농업 정책 마련을 요구하는 목소리가 높아지고 있습니다."
55
+ ]
56
+ issue_text = random.choice(issues)
57
+ # --- 임시 예시 끝 ---
58
+ st.session_state['event_log'].append(f"{game_year}년: {issue_text.splitlines()[0]}")
59
+ return issue_text
60
+ except Exception as e:
61
+ st.error(f"이슈 생성 중 오류: {e}")
62
+ return None
63
+
64
+ # --- 이슈 분석 함수 (explain_daily_news_meanings 대체) ---
65
+ def analyze_issue_impact(issue_text):
66
+ if not issue_text: return None
67
+ prompt = f"""
68
+ 다음 이슈에 대해 각 대한민국 국가기관(행정부, 입법부, 사법부) 어떤 역할과 권한에 근거하여 반응할 가능성이 높은지, 그리고 기관 간 어떤 상호작용(견제와 균형)이 예상되는지 간략하게 분석해주세요. 각 기관별 예상 반응과 상호작용 가능성을 명확히 구분하여 설명해주세요.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ 이슈 내용:
71
+ {issue_text}
 
 
 
 
 
 
 
72
 
73
+ 분석 결과:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  """
75
+ try:
76
+ # response = client.chat.completions.create(...) # GPT 호출
77
+ # analysis_text = response.choices[0].message.content.strip()
78
+ # --- 임시 예시 ---
79
+ analysis_text = f"""
80
+ **이슈 분석:** "{issue_text.splitlines()[0][len("## 이슈: "):]}"
81
+
82
+ **행정부 (정부):**
83
+ * **예상 반응:** {random.choice(['관련 부처 중심으로 TF팀 구성 및 현황 파악', '긴급 대책 발표 및 시행령 개정 검토', '피해 지원 예산 편성 및 집행 계획 수립'])} (법률 집행 및 정책 수립 권한)
84
+ * **상호작용:** 국회에 관련 법률안/예산안 제출 협의, 법원의 정책 위법성 판단에 대응.
85
+
86
+ **입법부 (국회):**
87
+ * **예상 반응:** {random.choice(['관련 상임위원회 개최 및 현안 보고 청취', '긴급 현안 질의 및 대정부 질문', '관련 법률 제정 또는 개정안 발의 및 심의'])} (입법권, 국정 통제권)
88
+ * **상호작용:** 행정부가 제출한 법률안/예산안 심의 및 수정, 행정부 정책 집행 과정 감독(국정감사 등), 사법부의 위헌법률심판 결과 주시.
89
+
90
+ **사법부 (법원/헌법재판소):**
91
+ * **예상 반응:** {random.choice(['이슈 관련 소송 제기 시 재판 진행', '정부 정책/명령의 위법성 심사 가능성', '국회 제정 법률의 위헌 여부 심판 가능성'])} (사법권, 위헌법률심판권)
92
+ * **상호작용:** 행정부의 처분/명령에 대한 행정소송 심리, 국회가 제정한 법률에 대한 위헌법률심판 또는 헌법소원 심리.
93
+ """
94
+ # --- 임시 예시 끝 ---
95
+ st.session_state['event_log'].append(f" - AI 분석 완료")
96
+ return analysis_text
97
+ except Exception as e:
98
+ st.error(f"이슈 분석 중 오류: {e}")
99
+ return None
100
+
101
+ # --- 플레이어 행동 처리 함수 (buy/sell 대체) ---
102
+ def take_player_action(action_type):
103
+ points_cost = 1 # 행동별 포인트 소모량 차등화 가능
104
+ if st.session_state['national_status']['influence_points'] >= points_cost:
105
+ st.session_state['national_status']['influence_points'] -= points_cost
106
+ st.session_state['event_log'].append(f" - 플레이어 행동: {action_type}")
107
+ st.success(f"'{action_type}' 행동을 수행했습니다. (영향력 포인트 {points_cost} 소모)")
108
+ # 실제 게임에서는 이 행동이 simulate_branch_interactions에 영향을 주도록 구현
109
+ return True
110
  else:
111
+ st.error("영향력 포인트가 부족합니다.")
112
+ return False
113
+
114
+ # --- 기관 상호작용 및 상태 업데이트 함수 (update_stock_prices 대체) ---
115
+ def simulate_branch_interactions():
116
+ # 현재 이슈, 기관 상태, 플레이어 행동 등을 바탕으로
117
+ # 기관 상호작용(협력, 갈등, 견제) 시뮬레이션
118
+ # 예: 국회 법안 통과 -> 행정부 검토 -> (확률적) 재��� 요구 -> 국회 재의결 시도 등
119
+ # 결과에 따라 national_status (안정도, 만족도 등) 업데이트
120
+ stability_change = random.randint(-5, 3) # 임시 변동치
121
+ satisfaction_change = random.randint(-4, 4) # 임시 변동치
122
+ st.session_state['national_status']['stability'] += stability_change
123
+ st.session_state['national_status']['public_satisfaction'] += satisfaction_change
124
+ # 기관 상태도 업데이트 (예: '법안 통과', '정책 시행 중' 등)
125
+ for branch in st.session_state['government_branches']:
126
+ st.session_state['government_branches'][branch]['status'] = random.choice(['다음 단계 진행 중', '이슈 관련 논의 중', '결정 대기 중'])
127
+
128
+ st.session_state['event_log'].append(f" - 기관 상호작용 결과: 안정도 {stability_change:+} / 만족도 {satisfaction_change:+}")
129
+ st.info(f"{st.session_state['game_year']}년 활동 결과가 국가 지표에 반영되었습니다.")
130
+ st.toast("국가 지표가 변동되었습니다.", icon="📊")
131
+
132
+
133
+ # --- UI 표시 함수들 ---
134
+ def display_national_dashboard():
135
+ st.metric("국가 안정도", f"{st.session_state['national_status']['stability']} / 100")
136
+ st.metric("국민 만족도", f"{st.session_state['national_status']['public_satisfaction']} / 100")
137
+ st.metric("나의 영향력", f"{st.session_state['national_status']['influence_points']} P")
138
+
139
+ def display_branch_status():
140
+ for branch_name, branch_info in st.session_state['government_branches'].items():
141
+ with st.expander(f"{branch_info['name']} (현재 상태: {branch_info['status']})"):
142
+ st.markdown(f"**주요 역할:** {branch_info['role']}")
143
+ # 여기에 더 상세한 활동 내역, 관련 법안/정책 등 표시 가능
144
+
145
+ def display_civics_glossary():
146
+ glossary = {
147
+ "삼권분립": "국가 권력을 입법권(국회), 행정권(정부), 사법권(법원)으로 나누어 서로 견제하고 균형을 이루도록 하는 원리.",
148
+ "입법권": "법률을 만들거나 고치는 권한. 국회가 가집니다.",
149
+ "행정권": "법률을 집행하고 국가 살림을 운영하는 권한. 정부(대통령과 각 부처)가 가집니다.",
150
+ "사법권": "법률을 해석하고 적용하여 재판하는 권한. 법원이 가집니다.",
151
+ "견제와 균형": "어느 한 국가기관이 권력을 독점하지 못하도록 다른 기관이 감시하고 제동을 거는 것.",
152
+ "법률안 거부권 (재의 요구권)": "국회가 만든 법률안에 대해 대통령이 동의하지 않고 다시 논의해달라고 국회에 돌려보내는 권한.",
153
+ "위헌법률심판": "국회가 만든 법률이 헌법에 어긋나는지(위헌인지) 헌법재판소가 심판하는 것.",
154
+ "국정감사": "국회가 정부가 국가 살림을 제대로 하고 있는지 감시하고 조사하는 활동.",
155
+ "탄핵 소추": "대통령 등 고위 공직자가 직무 수행 중 헌법이나 법률을 크게 위반했을 때 국회가 그 공직자를 파면해달라고 헌법재판소에 청구하는 것.",
156
+ # 필요에 따라 용어 추가
157
  }
158
+ with st.sidebar.expander("🏛️ 시민 용어 사전", expanded=False):
159
  for term, definition in glossary.items():
160
  st.markdown(f"**{term}:** {definition}")
161
  st.markdown("---")
162
 
163
+ # --- 메인 게임 루프 ---
164
  def main():
165
+ st.title("⚖️ 대한민국 균형 잡기: 삼권분립 시뮬레이션")
166
+
167
+ col_issue, col_main_ui = st.columns([2, 3])
168
+
169
+ with col_issue:
170
+ st.header(f"📰 {st.session_state['game_year']} 주요 이슈")
171
+ if st.button("새로운 이슈 발생시키기", key="issue_gen_button", use_container_width=True):
172
+ with st.spinner("새로운 국가 이슈 생성 중..."):
173
+ issue = generate_issue(st.session_state['game_year'])
174
+ if issue:
175
+ st.session_state['current_issue'] = issue
176
+ st.session_state['issue_analysis'] = analyze_issue_impact(issue)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  else:
178
+ st.error("이슈 생성에 실패했습니다.")
179
+
180
+ if st.session_state['current_issue']:
181
+ st.markdown(st.session_state['current_issue'])
182
+ st.divider()
183
+ if st.session_state['issue_analysis']:
184
+ st.subheader("🤖 AI 이슈 분석")
185
+ st.markdown(st.session_state['issue_analysis'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  else:
187
+ st.warning("AI 분석 정보를 불러오는 중이거나 실패했습니다.")
188
+ else:
189
+ st.info(" '새로운 이슈 발생시키기' 버튼을 눌러 게임을 시작하세요.")
 
 
190
 
191
+ with col_main_ui:
192
+ tabs = st.tabs(["🏛️ 국가 기관 현황", "🙋‍♀️ 나의 행동 선택", "📜 이벤트 로그"])
193
+
194
+ with tabs[0]:
195
+ st.subheader("국가 기관별 현재 활동")
196
+ display_branch_status()
197
+
198
+ with tabs[1]:
199
+ st.subheader("영향력 행사하기")
200
+ st.markdown(f"현재 보유 영향력: **{st.session_state['national_status']['influence_points']} P**")
201
+ st.warning("행동을 선택하면 영향력 포인트가 소모됩니다.")
202
+ action_buttons = {
203
+ "정책 연구/제안": "행정부나 국회에 특정 정책 방향을 제안합니다.",
204
+ "정보 공개 요청": "기관 활동의 투명성을 높이도록 요구합니다.",
205
+ "여론 조사/캠페인": "특정 이슈에 대한 국민적 공감대를 형성하려 시도합니다.",
206
+ "기관 간 협의 촉구": "갈등 상황에서 기관 간 대화를 유도합니다."
207
+ }
208
+ cols = st.columns(2)
209
+ i = 0
210
+ for action, desc in action_buttons.items():
211
+ with cols[i % 2]:
212
+ if st.button(action, help=desc, use_container_width=True, key=f"action_{action}"):
213
+ take_player_action(action)
214
+ i += 1
215
 
216
+ with tabs[2]:
217
+ st.subheader("주요 사건 기록")
218
+ log_text = "\n".join(st.session_state['event_log'][::-1]) # 최신 로그가 위로
219
+ st.text_area("로그", log_text, height=300, disabled=True)
 
 
 
 
 
 
 
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ # --- 사이드바 ---
223
  with st.sidebar:
224
+ st.header("📊 국가 현황판")
225
+ st.markdown(f"### 현재 연도: {st.session_state['game_year']}")
226
+ display_national_dashboard()
227
+ st.divider()
228
+
229
+ if st.button("➡️ 다음 해로 넘어가기", use_container_width=True, key="next_year_button"):
230
+ if st.session_state['current_issue']:
231
+ with st.spinner(f"{st.session_state['game_year']}년 활동 결과 처리 및 다음 해 준비 중..."):
232
+ simulate_branch_interactions() # 기관 상호작용 및 결과 반영
233
+ st.session_state['game_year'] += 1
234
+ st.session_state['national_status']['influence_points'] += 1 # 매년 영향력 포인트 회복
235
+ st.session_state['current_issue'] = None # 다음 턴 위해 이슈 초기화
236
+ st.session_state['issue_analysis'] = None
237
+ st.rerun() # 화면 새로고침하여 다음 시작
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  else:
239
+ st.warning("먼저 '새로운 이슈 발생시키기'를 눌러 현재 연도의 이슈를 확인하세요.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
+ st.divider()
242
+ display_civics_glossary() # 시민 용어 사전 표시
 
243
 
244
+ with st.expander("🎮 게임 가이드"):
245
+ st.markdown("""
246
+ **대한민국 균형 잡기: 삼권분립 시뮬레이션** 게임에 오신 것을 환영합니다!
 
247
 
248
+ **🎯 게임 목표:**
249
+ 다양한 국가적 이슈에 대해 행정부, 입법부, 사법부가 어떻게 상호작용하는지 관찰하고, 때로는 시민의 입장에서 영향력을 행사하여 국가 안정도와 국민 만족도를 높이는 것입니다.
 
250
 
251
+ **🕹️ 게임 방법:**
252
+ 1. **이슈 확인:** '새로운 이슈 발생시키기' 버튼을 눌러 해당 연도의 주요 이슈와 AI 분석을 확인합니다.
253
+ 2. **기관 현황 파악:** '국가 기관 현황' 탭에서 각 기관의 역할과 현재 활동을 살펴봅니다.
254
+ 3. **행동 선택:** '나의 행동 선택' 탭에서 보유한 '영향력 포인트'를 사용하여 원하는 행동을 선택합니다. (선택 사항)
255
+ 4. **다음 해 진행:** '다음 해로 넘어가기' 버튼을 누르면 기관들의 상호작용 결과가 반영되고, 다음 연도의 새로운 이슈가 발생합니다.
256
+ 5. **지표 관리:** 사이드바의 '국가 현황판'을 통해 안정도, 만족도, 영향력 포인트를 꾸준히 확인하며 게임을 진행하세요.
257
 
258
+ **💡 학습 포인트:**
259
+ * 기관이 어떤 **권한**을 가지고 **역할**을 수행하는지 주목하세요.
260
+ * 기관들이 서로 **견제**하거나 **협력**하는 모습을 관찰하세요. (예: 법률안 처리 과정, 정책 위법성 심사 등)
261
+ * '시민 용어 사전'을 통해 어려운 용어의 뜻을 확인하세요.
 
 
262
 
263
+ **대한민국의 균형을 잡아가는 여정을 시작해보세요!**
264
+ """)
 
265
 
266
  if __name__ == "__main__":
267
+ # 게임 시작 초기화 함수 호출 (필요시)
268
+ if 'government_branches' not in st.session_state:
269
+ st.session_state['government_branches'] = initialize_branches()
270
  main()