Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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>
|