aliceblue11 commited on
Commit
9429a7b
·
verified ·
1 Parent(s): 8031785

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +480 -0
app.py CHANGED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import datetime
3
+ from datetime import timedelta
4
+ import openai
5
+ import os
6
+ from typing import Tuple, Dict, List
7
+ import pandas as pd
8
+ from korean_lunar_calendar import KoreanLunarCalendar
9
+
10
+ # OpenAI API 설정
11
+ openai.api_key = "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"
12
+
13
+ class SajuCalculator:
14
+ def __init__(self):
15
+ self.calendar = KoreanLunarCalendar()
16
+
17
+ # 천간 (10개)
18
+ self.cheongan = ['갑', '을', '병', '정', '무', '기', '경', '신', '임', '계']
19
+
20
+ # 지지 (12개)
21
+ self.jiji = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해']
22
+
23
+ # 월지지 매핑 (절기 기준)
24
+ self.month_jiji = {
25
+ 1: '인', # 인월 (입춘~경칩)
26
+ 2: '묘', # 묘월 (경칩~청명)
27
+ 3: '진', # 진월 (청명~입하)
28
+ 4: '사', # 사월 (입하~망종)
29
+ 5: '오', # 오월 (망종~소서)
30
+ 6: '미', # 미월 (소서~입추)
31
+ 7: '신', # 신월 (입추~백로)
32
+ 8: '유', # 유월 (백로~한로)
33
+ 9: '술', # 술월 (한로~입동)
34
+ 10: '해', # 해월 (입동~대설)
35
+ 11: '자', # 자월 (대설~소한)
36
+ 12: '축' # 축월 (소한~입춘)
37
+ }
38
+
39
+ # 24절기 날짜 (한국 기준)
40
+ self.solar_terms = {
41
+ '입춘': (2, 4), '우수': (2, 19), '경칩': (3, 6), '춘분': (3, 21),
42
+ '청명': (4, 5), '곡우': (4, 20), '입하': (5, 6), '소만': (5, 21),
43
+ '망종': (6, 6), '하지': (6, 21), '소서': (7, 7), '대서': (7, 23),
44
+ '입추': (8, 8), '처서': (8, 23), '백로': (9, 8), '추분': (9, 23),
45
+ '한로': (10, 8), '상강': (10, 23), '입동': (11, 7), '소설': (11, 22),
46
+ '대설': (12, 7), '동지': (12, 22), '소한': (1, 6), '대한': (1, 20)
47
+ }
48
+
49
+ # 시주 계산용 천간 순서
50
+ self.time_cheongan_base = {
51
+ '갑': 0, '을': 0, '병': 2, '정': 2, '무': 4,
52
+ '기': 4, '경': 6, '신': 6, '임': 8, '계': 8
53
+ }
54
+
55
+ # 시간대별 지지 (24시간제)
56
+ self.time_jiji_map = [
57
+ ('자', 23, 1), ('축', 1, 3), ('인', 3, 5), ('묘', 5, 7),
58
+ ('진', 7, 9), ('사', 9, 11), ('오', 11, 13), ('미', 13, 15),
59
+ ('신', 15, 17), ('유', 17, 19), ('술', 19, 21), ('해', 21, 23)
60
+ ]
61
+
62
+ def get_month_from_solar_date(self, month: int, day: int) -> int:
63
+ """절기 기준으로 명리학적 월 계산"""
64
+ # 절기 기준 월 구분
65
+ if month == 1:
66
+ return 12 if day < 6 else 1 # 소한(1/6) 기준
67
+ elif month == 2:
68
+ return 1 if day < 4 else 2 # 입춘(2/4) 기준
69
+ elif month == 3:
70
+ return 2 if day < 6 else 3 # 경칩(3/6) 기준
71
+ elif month == 4:
72
+ return 3 if day < 5 else 4 # 청명(4/5) 기준
73
+ elif month == 5:
74
+ return 4 if day < 6 else 5 # 입하(5/6) 기준
75
+ elif month == 6:
76
+ return 5 if day < 6 else 6 # 망종(6/6) 기준
77
+ elif month == 7:
78
+ return 6 if day < 7 else 7 # 소서(7/7) 기준
79
+ elif month == 8:
80
+ return 7 if day < 8 else 8 # 입추(8/8) 기준
81
+ elif month == 9:
82
+ return 8 if day < 8 else 9 # 백로(9/8) 기준
83
+ elif month == 10:
84
+ return 9 if day < 8 else 10 # 한로(10/8) 기준
85
+ elif month == 11:
86
+ return 10 if day < 7 else 11 # 입동(11/7) 기준
87
+ elif month == 12:
88
+ return 11 if day < 7 else 12 # 대설(12/7) 기준
89
+
90
+ def get_year_ganjhi_from_korean_calendar(self, year: int, month: int, day: int) -> str:
91
+ """한국 음력 달력 라이브러리를 이용한 연주 계산"""
92
+ try:
93
+ self.calendar.setSolarDate(year, month, day)
94
+ gapja_str = self.calendar.getGapJaString()
95
+ # "정유년 병오월 임오일" 형식에서 연주만 추출
96
+ year_ganjhi = gapja_str.split('년')[0] + '년'
97
+ return year_ganjhi.replace('년', '')
98
+ except:
99
+ # 라이브러리 실패시 기존 방식 사용
100
+ return self.get_year_ganjhi_fallback(year)
101
+
102
+ def get_year_ganjhi_fallback(self, year: int) -> str:
103
+ """연주 계산 (fallback)"""
104
+ # 갑자년을 기준으로 계산 (1984년이 갑자년)
105
+ base_year = 1984
106
+ year_diff = year - base_year
107
+
108
+ cheongan_index = year_diff % 10
109
+ jiji_index = year_diff % 12
110
+
111
+ if cheongan_index < 0:
112
+ cheongan_index += 10
113
+ if jiji_index < 0:
114
+ jiji_index += 12
115
+
116
+ return self.cheongan[cheongan_index] + self.jiji[jiji_index]
117
+
118
+ def get_month_ganjhi(self, year: int, saju_month: int) -> str:
119
+ """월주 계산"""
120
+ year_cheongan = self.get_year_ganjhi_from_korean_calendar(year, 1, 15)[0]
121
+
122
+ # 년간에 따른 정월 천간 결정
123
+ month_cheongan_start = {
124
+ '갑': 2, '을': 4, '병': 6, '정': 8, '무': 0,
125
+ '기': 2, '경': 4, '신': 6, '임': 8, '계': 0
126
+ }
127
+
128
+ start_index = month_cheongan_start[year_cheongan]
129
+ month_cheongan_index = (start_index + saju_month - 1) % 10
130
+ month_jiji = self.month_jiji[saju_month]
131
+
132
+ return self.cheongan[month_cheongan_index] + month_jiji
133
+
134
+ def get_day_ganjhi_from_korean_calendar(self, year: int, month: int, day: int) -> str:
135
+ """한국 음력 달력 라이브러리를 이용한 일주 계산"""
136
+ try:
137
+ self.calendar.setSolarDate(year, month, day)
138
+ gapja_str = self.calendar.getGapJaString()
139
+ # "정유년 병오월 임오일" 형식에서 일주만 추출
140
+ day_ganjhi = gapja_str.split(' ')[-1].replace('일', '')
141
+ return day_ganjhi
142
+ except:
143
+ # 라이브러리 실패시 기존 방식 사용
144
+ return self.get_day_ganjhi_fallback(year, month, day)
145
+
146
+ def get_day_ganjhi_fallback(self, year: int, month: int, day: int) -> str:
147
+ """일주 계산 (fallback)"""
148
+ # 기준일(1900.1.1 = 경자일) 부터의 일수 계산
149
+ base_date = datetime.date(1900, 1, 1)
150
+ target_date = datetime.date(year, month, day)
151
+ days_diff = (target_date - base_date).days
152
+
153
+ # 경자일이 기준이므로 6(경)부터 시작
154
+ cheongan_index = (6 + days_diff) % 10
155
+ jiji_index = days_diff % 12
156
+
157
+ return self.cheongan[cheongan_index] + self.jiji[jiji_index]
158
+
159
+ def get_time_jiji(self, hour: int) -> str:
160
+ """시간대별 지지 결정"""
161
+ for jiji, start, end in self.time_jiji_map:
162
+ if start <= end: # 일반적인 경우
163
+ if start <= hour < end:
164
+ return jiji
165
+ else: # 자시의 경우 (23시~1시)
166
+ if hour >= start or hour < end:
167
+ return jiji
168
+ return '자' # 기본값
169
+
170
+ def get_time_ganjhi(self, day_cheongan: str, hour: int) -> str:
171
+ """시주 계산"""
172
+ time_jiji = self.get_time_jiji(hour)
173
+
174
+ # 일간에 따른 시간 천간 계산
175
+ base_index = self.time_cheongan_base[day_cheongan]
176
+ jiji_order = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해']
177
+ jiji_index = jiji_order.index(time_jiji)
178
+ time_cheongan_index = (base_index + jiji_index) % 10
179
+
180
+ return self.cheongan[time_cheongan_index] + time_jiji
181
+
182
+ def convert_lunar_to_solar(self, year: int, month: int, day: int, is_intercalation: bool = False) -> Tuple[int, int, int]:
183
+ """음력을 양력으로 변환 (한국천문연구원 기준)"""
184
+ try:
185
+ success = self.calendar.setLunarDate(year, month, day, is_intercalation)
186
+ if success:
187
+ solar_iso = self.calendar.SolarIsoFormat()
188
+ # "2017-06-24" 형식을 파싱
189
+ solar_year, solar_month, solar_day = map(int, solar_iso.split('-'))
190
+ return solar_year, solar_month, solar_day
191
+ else:
192
+ raise ValueError("잘못된 음력 날짜입니다.")
193
+ except Exception as e:
194
+ raise ValueError(f"음력 변환 실패: {str(e)}")
195
+
196
+ def convert_solar_to_lunar(self, year: int, month: int, day: int) -> Dict:
197
+ """양력을 음력으로 변환 (한국천문연구원 기준)"""
198
+ try:
199
+ success = self.calendar.setSolarDate(year, month, day)
200
+ if success:
201
+ lunar_iso = self.calendar.LunarIsoFormat()
202
+ # "2017-05-01 Intercalation" 또는 "2017-05-01" 형식 파싱
203
+ parts = lunar_iso.split()
204
+ date_part = parts[0]
205
+ is_intercalation = len(parts) > 1 and parts[1] == "Intercalation"
206
+
207
+ lunar_year, lunar_month, lunar_day = map(int, date_part.split('-'))
208
+
209
+ return {
210
+ 'year': lunar_year,
211
+ 'month': lunar_month,
212
+ 'day': lunar_day,
213
+ 'is_intercalation': is_intercalation,
214
+ 'intercalation_text': ' (윤월)' if is_intercalation else ''
215
+ }
216
+ else:
217
+ raise ValueError("잘못된 양력 날짜입니다.")
218
+ except Exception as e:
219
+ raise ValueError(f"양력 변환 실패: {str(e)}")
220
+
221
+ def calculate_saju(self, name: str, birth_year: int, birth_month: int,
222
+ birth_day: int, birth_hour: int, birth_minute: int,
223
+ gender: str, location: str, is_lunar: bool,
224
+ is_intercalation: bool = False) -> Dict:
225
+ """전체 사주 계산"""
226
+
227
+ try:
228
+ if is_lunar:
229
+ # 음력을 양력으로 변환
230
+ solar_year, solar_month, solar_day = self.convert_lunar_to_solar(
231
+ birth_year, birth_month, birth_day, is_intercalation
232
+ )
233
+ lunar_info = {
234
+ 'year': birth_year,
235
+ 'month': birth_month,
236
+ 'day': birth_day,
237
+ 'is_intercalation': is_intercalation,
238
+ 'intercalation_text': ' (윤월)' if is_intercalation else ''
239
+ }
240
+ else:
241
+ # 양력을 음력으로 변환
242
+ solar_year, solar_month, solar_day = birth_year, birth_month, birth_day
243
+ lunar_info = self.convert_solar_to_lunar(birth_year, birth_month, birth_day)
244
+
245
+ # 명리학적 월 계산 (절기 기준)
246
+ saju_month = self.get_month_from_solar_date(solar_month, solar_day)
247
+
248
+ # 사주 계산
249
+ year_ganjhi = self.get_year_ganjhi_from_korean_calendar(solar_year, solar_month, solar_day)
250
+ month_ganjhi = self.get_month_ganjhi(solar_year, saju_month)
251
+ day_ganjhi = self.get_day_ganjhi_from_korean_calendar(solar_year, solar_month, solar_day)
252
+ time_ganjhi = self.get_time_ganjhi(day_ganjhi[0], birth_hour)
253
+
254
+ result = {
255
+ 'name': name,
256
+ 'birth_info': {
257
+ 'solar': f"{solar_year}년 {solar_month}월 {solar_day}일 {birth_hour:02d}시 {birth_minute:02d}분",
258
+ 'lunar': f"{lunar_info['year']}년 {lunar_info['month']}월 {lunar_info['day']}일 {birth_hour:02d}시 {birth_minute:02d}분{lunar_info['intercalation_text']}",
259
+ 'gender': gender,
260
+ 'location': location,
261
+ 'saju_month': saju_month,
262
+ 'solar_term_info': self.get_solar_term_info(solar_month, solar_day)
263
+ },
264
+ 'saju': {
265
+ 'year': year_ganjhi,
266
+ 'month': month_ganjhi,
267
+ 'day': day_ganjhi,
268
+ 'time': time_ganjhi
269
+ },
270
+ 'elements': self.analyze_elements(year_ganjhi, month_ganjhi, day_ganjhi, time_ganjhi),
271
+ 'detailed_analysis': self.get_detailed_saju_analysis(year_ganjhi, month_ganjhi, day_ganjhi, time_ganjhi)
272
+ }
273
+
274
+ return result
275
+
276
+ except Exception as e:
277
+ raise ValueError(f"사주 계산 중 오류 발생: {str(e)}")
278
+
279
+ def get_solar_term_info(self, month: int, day: int) -> str:
280
+ """현재 절기 정보 반환"""
281
+ current_date = (month, day)
282
+
283
+ for term, term_date in self.solar_terms.items():
284
+ if current_date >= term_date:
285
+ current_term = term
286
+
287
+ return current_term if 'current_term' in locals() else "절기 정보 없음"
288
+
289
+ def analyze_elements(self, year_ganjhi: str, month_ganjhi: str,
290
+ day_ganjhi: str, time_ganjhi: str) -> Dict:
291
+ """오행 분석"""
292
+ element_map = {
293
+ '갑': '목', '을': '목', '병': '화', '정': '화', '무': '토',
294
+ '기': '토', '경': '금', '신': '금', '임': '수', '계': '수',
295
+ '인': '목', '묘': '목', '진': '토', '사': '화', '오': '화',
296
+ '미': '토', '신': '금', '유': '금', '술': '토', '해': '수',
297
+ '자': '수', '축': '토'
298
+ }
299
+
300
+ all_chars = year_ganjhi + month_ganjhi + day_ganjhi + time_ganjhi
301
+ elements = [element_map[char] for char in all_chars]
302
+
303
+ element_count = {}
304
+ for element in elements:
305
+ element_count[element] = element_count.get(element, 0) + 1
306
+
307
+ # 오행 균형 분석
308
+ total = sum(element_count.values())
309
+ element_percentage = {k: round(v/total*100, 1) for k, v in element_count.items()}
310
+
311
+ return {
312
+ 'count': element_count,
313
+ 'percentage': element_percentage,
314
+ 'dominant': max(element_count, key=element_count.get),
315
+ 'weak': min(element_count, key=element_count.get) if element_count else None
316
+ }
317
+
318
+ def get_detailed_saju_analysis(self, year_ganjhi: str, month_ganjhi: str,
319
+ day_ganjhi: str, time_ganjhi: str) -> Dict:
320
+ """상세 사주 분석"""
321
+ # 일간 (주인) 분석
322
+ day_cheongan = day_ganjhi[0]
323
+ day_jiji = day_ganjhi[1]
324
+
325
+ # 십성 분석 (간략화된 버전)
326
+ sipseong_analysis = self.analyze_sipseong(day_cheongan, year_ganjhi, month_ganjhi, time_ganjhi)
327
+
328
+ return {
329
+ 'day_master': day_cheongan,
330
+ 'day_master_element': self.get_element(day_cheongan),
331
+ 'month_season': self.get_season_from_month(month_ganjhi[1]),
332
+ 'sipseong': sipseong_analysis,
333
+ 'strength': self.analyze_day_master_strength(day_cheongan, month_ganjhi, day_jiji, time_ganjhi)
334
+ }
335
+
336
+ def get_element(self, char: str) -> str:
337
+ """글자의 오행 반환"""
338
+ element_map = {
339
+ '갑': '목', '을': '목', '병': '화', '정': '화', '무': '토',
340
+ '기': '토', '경': '금', '신': '금', '임': '수', '계': '수',
341
+ '인': '목', '묘': '목', '진': '토', '사': '화', '오': '화',
342
+ '미': '토', '신': '금', '유': '금', '술': '토', '해': '수',
343
+ '자': '수', '축': '토'
344
+ }
345
+ return element_map.get(char, '미상')
346
+
347
+ def get_season_from_month(self, month_jiji: str) -> str:
348
+ """월지지로부터 계절 판단"""
349
+ season_map = {
350
+ '인': '봄', '묘': '봄', '진': '봄',
351
+ '사': '여름', '오': '여름', '미': '여름',
352
+ '신': '가을', '유': '가을', '술': '가을',
353
+ '해': '겨울', '자': '겨울', '축': '겨울'
354
+ }
355
+ return season_map.get(month_jiji, '미상')
356
+
357
+ def analyze_sipseong(self, day_master: str, year_ganjhi: str, month_ganjhi: str, time_ganjhi: str) -> Dict:
358
+ """십성 분석 (간략화)"""
359
+ # 실제로는 매우 복잡한 계산이 필요하지만, 여기서는 기본적인 분석만
360
+ day_element = self.get_element(day_master)
361
+
362
+ sipseong_count = {
363
+ '비견': 0, '겁재': 0, '식신': 0, '상관': 0, '편재': 0,
364
+ '정재': 0, '편관': 0, '정관': 0, '편인': 0, '정인': 0
365
+ }
366
+
367
+ # 간략화된 십성 계산 (실제로는 더 복잡)
368
+ for ganjhi in [year_ganjhi, month_ganjhi, time_ganjhi]:
369
+ for char in ganjhi:
370
+ char_element = self.get_element(char)
371
+ if char_element == day_element:
372
+ sipseong_count['비견'] += 1
373
+ # 추가 십성 계산은 실제 구현에서 더 복잡하게 처리
374
+
375
+ return sipseong_count
376
+
377
+ def analyze_day_master_strength(self, day_master: str, month_ganjhi: str, day_jiji: str, time_ganjhi: str) -> str:
378
+ """일간의 강약 분석"""
379
+ day_element = self.get_element(day_master)
380
+ month_element = self.get_element(month_ganjhi[1])
381
+
382
+ # 매우 간략화된 강약 판단
383
+ if day_element == month_element:
384
+ return "강함"
385
+ else:
386
+ return "약함"
387
+
388
+ def get_ai_interpretation(saju_data: Dict) -> str:
389
+ """OpenAI API를 사용한 사주 해석"""
390
+ try:
391
+ detailed = saju_data['detailed_analysis']
392
+ elements = saju_data['elements']
393
+
394
+ prompt = f"""
395
+ 당신은 30년 경력의 전문 사주명리학자입니다. 다음 사주 정보를 바탕으로 상세하고 전문적인 해석을 제공해주세요.
396
+
397
+ 📋 기본 정보:
398
+ - 이름: {saju_data['name']}
399
+ - 성별: {saju_data['birth_info']['gender']}
400
+ - 양력: {saju_data['birth_info']['solar']}
401
+ - 음력: {saju_data['birth_info']['lunar']}
402
+ - 출생지: {saju_data['birth_info']['location']}
403
+
404
+ 🎯 사주명식:
405
+ - 년주: {saju_data['saju']['year']}
406
+ - 월주: {saju_data['saju']['month']}
407
+ - 일주: {saju_data['saju']['day']} (일간: {detailed['day_master']})
408
+ - 시주: {saju_data['saju']['time']}
409
+
410
+ 🌟 오행 분석:
411
+ - 오행 개수: {elements['count']}
412
+ - 우세 오행: {elements['dominant']}
413
+ - 약한 오행: {elements.get('weak', '없음')}
414
+ - 일간 오행: {detailed['day_master_element']}
415
+ - 월령 계절: {detailed['month_season']}
416
+ - 일간 강약: {detailed['strength']}
417
+
418
+ 다음 항목들을 포함하여 한국어로 전문적이면서도 이해하기 쉽게 해석해주세요:
419
+
420
+ 1. **성격 및 기질 분석** (일간과 오행 분석 기반)
421
+ 2. **인생 운세의 전반적인 흐름** (사주 전체 구조 분석)
422
+ 3. **직업 및 진로 방향** (십성과 오행 특성 반영)
423
+ 4. **인간관계 및 결혼운** (사주의 인성, 관성 분석)
424
+ 5. **건강 운세** (오행 균형과 계절 특성)
425
+ 6. **재물운** (재성과 식상 분석)
426
+ 7. **현재 시기의 운세 조언** (계절과 오행 조화)
427
+
428
+ 각 항목마다 구체적인 근거를 제시하고, 실용적인 조언을 포함해주세요.
429
+ """
430
+
431
+ response = openai.ChatCompletion.create(
432
+ model="gpt-4",
433
+ messages=[
434
+ {"role": "system", "content": "당신은 한국 전통 사주명리학의 대가입니다."},
435
+ {"role": "user", "content": prompt}
436
+ ],
437
+ max_tokens=2500,
438
+ temperature=0.7
439
+ )
440
+
441
+ return response.choices[0].message.content
442
+
443
+ except Exception as e:
444
+ return f"AI 해석 생성 중 오류가 발생했습니다: {str(e)}\n\n기본 해석을 제공합니다:\n\n일간이 {saju_data['detailed_analysis']['day_master']}({saju_data['detailed_analysis']['day_master_element']})이고, {saju_data['detailed_analysis']['month_season']} 계절에 태어나신 분입니다. 오행 중 {saju_data['elements']['dominant']}이 강하게 나타나는 사주입니다."
445
+
446
+ def create_saju_table(saju_data: Dict) -> str:
447
+ """사주 정보를 표 형태로 생성"""
448
+ saju = saju_data['saju']
449
+ birth_info = saju_data['birth_info']
450
+ elements = saju_data['elements']
451
+ detailed = saju_data['detailed_analysis']
452
+
453
+ table_html = f"""
454
+ <div style='font-family: "Noto Sans KR", Arial, sans-serif; margin: 20px; line-height: 1.6;'>
455
+ <h2 style='color: #2c3e50; text-align: center; margin-bottom: 30px;'>
456
+ 🔮 {saju_data['name']}님의 정밀 사주명리 분석서
457
+ </h2>
458
+
459
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);'>
460
+ <h3 style='margin-top: 0; text-align: center;'>📅 출생 정보</h3>
461
+ <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 15px;'>
462
+ <div>
463
+ <p><strong>🌞 양력:</strong> {birth_info['solar']}</p>
464
+ <p><strong>🌙 음력:</strong> {birth_info['lunar']}</p>
465
+ </div>
466
+ <div>
467
+ <p><strong>👤 성별:</strong> {birth_info['gender']}</p>
468
+ <p><strong>📍 출생지:</strong> {birth_info['location']}</p>
469
+ </div>
470
+ </div>
471
+ <p style='text-align: center; margin-bottom: 0;'><strong>🌿 절기:</strong> {birth_info.get('solar_term_info', '정보없음')} 시기 출생</p>
472
+ </div>
473
+
474
+ <div style='background-color: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 2px solid #e9ecef;'>
475
+ <h3 style='color: #495057; text-align: center; margin-bottom: 20px;'>🎯 사주명식 (간지배치)</h3>
476
+ <table style='width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1);'>
477
+ <thead>
478
+ <tr style='background: linear-gradient(135deg, #74b9ff, #0984e3); color: white;'>
479
+ <th style='padding: 15px; border: 1px solid #ddd; font-size: 16px;'>년주 (祖上)</th>
480
+ <th style='padding: 15px; border: 1px solid #ddd; font-size: 16px;'>월주 (父母)</th>