Spaces:
Runtime error
Runtime error
import gradio as gr | |
import datetime | |
from datetime import timedelta | |
import openai | |
import os | |
from typing import Tuple, Dict, List | |
import pandas as pd | |
from korean_lunar_calendar import KoreanLunarCalendar | |
# OpenAI API 설정 | |
openai.api_key = "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" | |
class SajuCalculator: | |
def __init__(self): | |
self.calendar = KoreanLunarCalendar() | |
# 천간 (10개) | |
self.cheongan = ['갑', '을', '병', '정', '무', '기', '경', '신', '임', '계'] | |
# 지지 (12개) | |
self.jiji = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해'] | |
# 월지지 매핑 (절기 기준) | |
self.month_jiji = { | |
1: '인', # 인월 (입춘~경칩) | |
2: '묘', # 묘월 (경칩~청명) | |
3: '진', # 진월 (청명~입하) | |
4: '사', # 사월 (입하~망종) | |
5: '오', # 오월 (망종~소서) | |
6: '미', # 미월 (소서~입추) | |
7: '신', # 신월 (입추~백로) | |
8: '유', # 유월 (백로~한로) | |
9: '술', # 술월 (한로~입동) | |
10: '해', # 해월 (입동~대설) | |
11: '자', # 자월 (대설~소한) | |
12: '축' # 축월 (소한~입춘) | |
} | |
# 24절기 날짜 (한국 기준) | |
self.solar_terms = { | |
'입춘': (2, 4), '우수': (2, 19), '경칩': (3, 6), '춘분': (3, 21), | |
'청명': (4, 5), '곡우': (4, 20), '입하': (5, 6), '소만': (5, 21), | |
'망종': (6, 6), '하지': (6, 21), '소서': (7, 7), '대서': (7, 23), | |
'입추': (8, 8), '처서': (8, 23), '백로': (9, 8), '추분': (9, 23), | |
'한로': (10, 8), '상강': (10, 23), '입동': (11, 7), '소설': (11, 22), | |
'대설': (12, 7), '동지': (12, 22), '소한': (1, 6), '대한': (1, 20) | |
} | |
# 시주 계산용 천간 순서 | |
self.time_cheongan_base = { | |
'갑': 0, '을': 0, '병': 2, '정': 2, '무': 4, | |
'기': 4, '경': 6, '신': 6, '임': 8, '계': 8 | |
} | |
# 시간대별 지지 (24시간제) | |
self.time_jiji_map = [ | |
('자', 23, 1), ('축', 1, 3), ('인', 3, 5), ('묘', 5, 7), | |
('진', 7, 9), ('사', 9, 11), ('오', 11, 13), ('미', 13, 15), | |
('신', 15, 17), ('유', 17, 19), ('술', 19, 21), ('해', 21, 23) | |
] | |
def get_month_from_solar_date(self, month: int, day: int) -> int: | |
"""절기 기준으로 명리학적 월 계산""" | |
# 절기 기준 월 구분 | |
if month == 1: | |
return 12 if day < 6 else 1 # 소한(1/6) 기준 | |
elif month == 2: | |
return 1 if day < 4 else 2 # 입춘(2/4) 기준 | |
elif month == 3: | |
return 2 if day < 6 else 3 # 경칩(3/6) 기준 | |
elif month == 4: | |
return 3 if day < 5 else 4 # 청명(4/5) 기준 | |
elif month == 5: | |
return 4 if day < 6 else 5 # 입하(5/6) 기준 | |
elif month == 6: | |
return 5 if day < 6 else 6 # 망종(6/6) 기준 | |
elif month == 7: | |
return 6 if day < 7 else 7 # 소서(7/7) 기준 | |
elif month == 8: | |
return 7 if day < 8 else 8 # 입추(8/8) 기준 | |
elif month == 9: | |
return 8 if day < 8 else 9 # 백로(9/8) 기준 | |
elif month == 10: | |
return 9 if day < 8 else 10 # 한로(10/8) 기준 | |
elif month == 11: | |
return 10 if day < 7 else 11 # 입동(11/7) 기준 | |
elif month == 12: | |
return 11 if day < 7 else 12 # 대설(12/7) 기준 | |
def get_year_ganjhi_from_korean_calendar(self, year: int, month: int, day: int) -> str: | |
"""한국 음력 달력 라이브러리를 이용한 연주 계산""" | |
try: | |
self.calendar.setSolarDate(year, month, day) | |
gapja_str = self.calendar.getGapJaString() | |
# "정유년 병오월 임오일" 형식에서 연주만 추출 | |
year_ganjhi = gapja_str.split('년')[0] + '년' | |
return year_ganjhi.replace('년', '') | |
except: | |
# 라이브러리 실패시 기존 방식 사용 | |
return self.get_year_ganjhi_fallback(year) | |
def get_year_ganjhi_fallback(self, year: int) -> str: | |
"""연주 계산 (fallback)""" | |
# 갑자년을 기준으로 계산 (1984년이 갑자년) | |
base_year = 1984 | |
year_diff = year - base_year | |
cheongan_index = year_diff % 10 | |
jiji_index = year_diff % 12 | |
if cheongan_index < 0: | |
cheongan_index += 10 | |
if jiji_index < 0: | |
jiji_index += 12 | |
return self.cheongan[cheongan_index] + self.jiji[jiji_index] | |
def get_month_ganjhi(self, year: int, saju_month: int) -> str: | |
"""월주 계산""" | |
year_cheongan = self.get_year_ganjhi_from_korean_calendar(year, 1, 15)[0] | |
# 년간에 따른 정월 천간 결정 | |
month_cheongan_start = { | |
'갑': 2, '을': 4, '병': 6, '정': 8, '무': 0, | |
'기': 2, '경': 4, '신': 6, '임': 8, '계': 0 | |
} | |
start_index = month_cheongan_start[year_cheongan] | |
month_cheongan_index = (start_index + saju_month - 1) % 10 | |
month_jiji = self.month_jiji[saju_month] | |
return self.cheongan[month_cheongan_index] + month_jiji | |
def get_day_ganjhi_from_korean_calendar(self, year: int, month: int, day: int) -> str: | |
"""한국 음력 달력 라이브러리를 이용한 일주 계산""" | |
try: | |
self.calendar.setSolarDate(year, month, day) | |
gapja_str = self.calendar.getGapJaString() | |
# "정유년 병오월 임오일" 형식에서 일주만 추출 | |
day_ganjhi = gapja_str.split(' ')[-1].replace('일', '') | |
return day_ganjhi | |
except: | |
# 라이브러리 실패시 기존 방식 사용 | |
return self.get_day_ganjhi_fallback(year, month, day) | |
def get_day_ganjhi_fallback(self, year: int, month: int, day: int) -> str: | |
"""일주 계산 (fallback)""" | |
# 기준일(1900.1.1 = 경자일) 부터의 일수 계산 | |
base_date = datetime.date(1900, 1, 1) | |
target_date = datetime.date(year, month, day) | |
days_diff = (target_date - base_date).days | |
# 경자일이 기준이므로 6(경)부터 시작 | |
cheongan_index = (6 + days_diff) % 10 | |
jiji_index = days_diff % 12 | |
return self.cheongan[cheongan_index] + self.jiji[jiji_index] | |
def get_time_jiji(self, hour: int) -> str: | |
"""시간대별 지지 결정""" | |
for jiji, start, end in self.time_jiji_map: | |
if start <= end: # 일반적인 경우 | |
if start <= hour < end: | |
return jiji | |
else: # 자시의 경우 (23시~1시) | |
if hour >= start or hour < end: | |
return jiji | |
return '자' # 기본값 | |
def get_time_ganjhi(self, day_cheongan: str, hour: int) -> str: | |
"""시주 계산""" | |
time_jiji = self.get_time_jiji(hour) | |
# 일간에 따른 시간 천간 계산 | |
base_index = self.time_cheongan_base[day_cheongan] | |
jiji_order = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해'] | |
jiji_index = jiji_order.index(time_jiji) | |
time_cheongan_index = (base_index + jiji_index) % 10 | |
return self.cheongan[time_cheongan_index] + time_jiji | |
def convert_lunar_to_solar(self, year: int, month: int, day: int, is_intercalation: bool = False) -> Tuple[int, int, int]: | |
"""음력을 양력으로 변환 (한국천문연구원 기준)""" | |
try: | |
success = self.calendar.setLunarDate(year, month, day, is_intercalation) | |
if success: | |
solar_iso = self.calendar.SolarIsoFormat() | |
# "2017-06-24" 형식을 파싱 | |
solar_year, solar_month, solar_day = map(int, solar_iso.split('-')) | |
return solar_year, solar_month, solar_day | |
else: | |
raise ValueError("잘못된 음력 날짜입니다.") | |
except Exception as e: | |
raise ValueError(f"음력 변환 실패: {str(e)}") | |
def convert_solar_to_lunar(self, year: int, month: int, day: int) -> Dict: | |
"""양력을 음력으로 변환 (한국천문연구원 기준)""" | |
try: | |
success = self.calendar.setSolarDate(year, month, day) | |
if success: | |
lunar_iso = self.calendar.LunarIsoFormat() | |
# "2017-05-01 Intercalation" 또는 "2017-05-01" 형식 파싱 | |
parts = lunar_iso.split() | |
date_part = parts[0] | |
is_intercalation = len(parts) > 1 and parts[1] == "Intercalation" | |
lunar_year, lunar_month, lunar_day = map(int, date_part.split('-')) | |
return { | |
'year': lunar_year, | |
'month': lunar_month, | |
'day': lunar_day, | |
'is_intercalation': is_intercalation, | |
'intercalation_text': ' (윤월)' if is_intercalation else '' | |
} | |
else: | |
raise ValueError("잘못된 양력 날짜입니다.") | |
except Exception as e: | |
raise ValueError(f"양력 변환 실패: {str(e)}") | |
def calculate_saju(self, name: str, birth_year: int, birth_month: int, | |
birth_day: int, birth_hour: int, birth_minute: int, | |
gender: str, location: str, is_lunar: bool, | |
is_intercalation: bool = False) -> Dict: | |
"""전체 사주 계산""" | |
try: | |
if is_lunar: | |
# 음력을 양력으로 변환 | |
solar_year, solar_month, solar_day = self.convert_lunar_to_solar( | |
birth_year, birth_month, birth_day, is_intercalation | |
) | |
lunar_info = { | |
'year': birth_year, | |
'month': birth_month, | |
'day': birth_day, | |
'is_intercalation': is_intercalation, | |
'intercalation_text': ' (윤월)' if is_intercalation else '' | |
} | |
else: | |
# 양력을 음력으로 변환 | |
solar_year, solar_month, solar_day = birth_year, birth_month, birth_day | |
lunar_info = self.convert_solar_to_lunar(birth_year, birth_month, birth_day) | |
# 명리학적 월 계산 (절기 기준) | |
saju_month = self.get_month_from_solar_date(solar_month, solar_day) | |
# 사주 계산 | |
year_ganjhi = self.get_year_ganjhi_from_korean_calendar(solar_year, solar_month, solar_day) | |
month_ganjhi = self.get_month_ganjhi(solar_year, saju_month) | |
day_ganjhi = self.get_day_ganjhi_from_korean_calendar(solar_year, solar_month, solar_day) | |
time_ganjhi = self.get_time_ganjhi(day_ganjhi[0], birth_hour) | |
result = { | |
'name': name, | |
'birth_info': { | |
'solar': f"{solar_year}년 {solar_month}월 {solar_day}일 {birth_hour:02d}시 {birth_minute:02d}분", | |
'lunar': f"{lunar_info['year']}년 {lunar_info['month']}월 {lunar_info['day']}일 {birth_hour:02d}시 {birth_minute:02d}분{lunar_info['intercalation_text']}", | |
'gender': gender, | |
'location': location, | |
'saju_month': saju_month, | |
'solar_term_info': self.get_solar_term_info(solar_month, solar_day) | |
}, | |
'saju': { | |
'year': year_ganjhi, | |
'month': month_ganjhi, | |
'day': day_ganjhi, | |
'time': time_ganjhi | |
}, | |
'elements': self.analyze_elements(year_ganjhi, month_ganjhi, day_ganjhi, time_ganjhi), | |
'detailed_analysis': self.get_detailed_saju_analysis(year_ganjhi, month_ganjhi, day_ganjhi, time_ganjhi) | |
} | |
return result | |
except Exception as e: | |
raise ValueError(f"사주 계산 중 오류 발생: {str(e)}") | |
def get_solar_term_info(self, month: int, day: int) -> str: | |
"""현재 절기 정보 반환""" | |
current_date = (month, day) | |
for term, term_date in self.solar_terms.items(): | |
if current_date >= term_date: | |
current_term = term | |
return current_term if 'current_term' in locals() else "절기 정보 없음" | |
def analyze_elements(self, year_ganjhi: str, month_ganjhi: str, | |
day_ganjhi: str, time_ganjhi: str) -> Dict: | |
"""오행 분석""" | |
element_map = { | |
'갑': '목', '을': '목', '병': '화', '정': '화', '무': '토', | |
'기': '토', '경': '금', '신': '금', '임': '수', '계': '수', | |
'인': '목', '묘': '목', '진': '토', '사': '화', '오': '화', | |
'미': '토', '신': '금', '유': '금', '술': '토', '해': '수', | |
'자': '수', '축': '토' | |
} | |
all_chars = year_ganjhi + month_ganjhi + day_ganjhi + time_ganjhi | |
elements = [element_map[char] for char in all_chars] | |
element_count = {} | |
for element in elements: | |
element_count[element] = element_count.get(element, 0) + 1 | |
# 오행 균형 분석 | |
total = sum(element_count.values()) | |
element_percentage = {k: round(v/total*100, 1) for k, v in element_count.items()} | |
return { | |
'count': element_count, | |
'percentage': element_percentage, | |
'dominant': max(element_count, key=element_count.get), | |
'weak': min(element_count, key=element_count.get) if element_count else None | |
} | |
def get_detailed_saju_analysis(self, year_ganjhi: str, month_ganjhi: str, | |
day_ganjhi: str, time_ganjhi: str) -> Dict: | |
"""상세 사주 분석""" | |
# 일간 (주인) 분석 | |
day_cheongan = day_ganjhi[0] | |
day_jiji = day_ganjhi[1] | |
# 십성 분석 (간략화된 버전) | |
sipseong_analysis = self.analyze_sipseong(day_cheongan, year_ganjhi, month_ganjhi, time_ganjhi) | |
return { | |
'day_master': day_cheongan, | |
'day_master_element': self.get_element(day_cheongan), | |
'month_season': self.get_season_from_month(month_ganjhi[1]), | |
'sipseong': sipseong_analysis, | |
'strength': self.analyze_day_master_strength(day_cheongan, month_ganjhi, day_jiji, time_ganjhi) | |
} | |
def get_element(self, char: str) -> str: | |
"""글자의 오행 반환""" | |
element_map = { | |
'갑': '목', '을': '목', '병': '화', '정': '화', '무': '토', | |
'기': '토', '경': '금', '신': '금', '임': '수', '계': '수', | |
'인': '목', '묘': '목', '진': '토', '사': '화', '오': '화', | |
'미': '토', '신': '금', '유': '금', '술': '토', '해': '수', | |
'자': '수', '축': '토' | |
} | |
return element_map.get(char, '미상') | |
def get_season_from_month(self, month_jiji: str) -> str: | |
"""월지지로부터 계절 판단""" | |
season_map = { | |
'인': '봄', '묘': '봄', '진': '봄', | |
'사': '여름', '오': '여름', '미': '여름', | |
'신': '가을', '유': '가을', '술': '가을', | |
'해': '겨울', '자': '겨울', '축': '겨울' | |
} | |
return season_map.get(month_jiji, '미상') | |
def analyze_sipseong(self, day_master: str, year_ganjhi: str, month_ganjhi: str, time_ganjhi: str) -> Dict: | |
"""십성 분석 (간략화)""" | |
# 실제로는 매우 복잡한 계산이 필요하지만, 여기서는 기본적인 분석만 | |
day_element = self.get_element(day_master) | |
sipseong_count = { | |
'비견': 0, '겁재': 0, '식신': 0, '상관': 0, '편재': 0, | |
'정재': 0, '편관': 0, '정관': 0, '편인': 0, '정인': 0 | |
} | |
# 간략화된 십성 계산 (실제로는 더 복잡) | |
for ganjhi in [year_ganjhi, month_ganjhi, time_ganjhi]: | |
for char in ganjhi: | |
char_element = self.get_element(char) | |
if char_element == day_element: | |
sipseong_count['비견'] += 1 | |
# 추가 십성 계산은 실제 구현에서 더 복잡하게 처리 | |
return sipseong_count | |
def analyze_day_master_strength(self, day_master: str, month_ganjhi: str, day_jiji: str, time_ganjhi: str) -> str: | |
"""일간의 강약 분석""" | |
day_element = self.get_element(day_master) | |
month_element = self.get_element(month_ganjhi[1]) | |
# 매우 간략화된 강약 판단 | |
if day_element == month_element: | |
return "강함" | |
else: | |
return "약함" | |
def get_ai_interpretation(saju_data: Dict) -> str: | |
"""OpenAI API를 사용한 사주 해석""" | |
try: | |
detailed = saju_data['detailed_analysis'] | |
elements = saju_data['elements'] | |
prompt = f""" | |
당신은 30년 경력의 전문 사주명리학자입니다. 다음 사주 정보를 바탕으로 상세하고 전문적인 해석을 제공해주세요. | |
📋 기본 정보: | |
- 이름: {saju_data['name']} | |
- 성별: {saju_data['birth_info']['gender']} | |
- 양력: {saju_data['birth_info']['solar']} | |
- 음력: {saju_data['birth_info']['lunar']} | |
- 출생지: {saju_data['birth_info']['location']} | |
🎯 사주명식: | |
- 년주: {saju_data['saju']['year']} | |
- 월주: {saju_data['saju']['month']} | |
- 일주: {saju_data['saju']['day']} (일간: {detailed['day_master']}) | |
- 시주: {saju_data['saju']['time']} | |
🌟 오행 분석: | |
- 오행 개수: {elements['count']} | |
- 우세 오행: {elements['dominant']} | |
- 약한 오행: {elements.get('weak', '없음')} | |
- 일간 오행: {detailed['day_master_element']} | |
- 월령 계절: {detailed['month_season']} | |
- 일간 강약: {detailed['strength']} | |
다음 항목들을 포함하여 한국어로 전문적이면서도 이해하기 쉽게 해석해주세요: | |
1. **성격 및 기질 분석** (일간과 오행 분석 기반) | |
2. **인생 운세의 전반적인 흐름** (사주 전체 구조 분석) | |
3. **직업 및 진로 방향** (십성과 오행 특성 반영) | |
4. **인간관계 및 결혼운** (사주의 인성, 관성 분석) | |
5. **건강 운세** (오행 균형과 계절 특성) | |
6. **재물운** (재성과 식상 분석) | |
7. **현재 시기의 운세 조언** (계절과 오행 조화) | |
각 항목마다 구체적인 근거를 제시하고, 실용적인 조언을 포함해주세요. | |
""" | |
response = openai.ChatCompletion.create( | |
model="gpt-4", | |
messages=[ | |
{"role": "system", "content": "당신은 한국 전통 사주명리학의 대가입니다."}, | |
{"role": "user", "content": prompt} | |
], | |
max_tokens=2500, | |
temperature=0.7 | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
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']}이 강하게 나타나는 사주입니다." | |
def create_saju_table(saju_data: Dict) -> str: | |
"""사주 정보를 표 형태로 생성""" | |
saju = saju_data['saju'] | |
birth_info = saju_data['birth_info'] | |
elements = saju_data['elements'] | |
detailed = saju_data['detailed_analysis'] | |
table_html = f""" | |
<div style='font-family: "Noto Sans KR", Arial, sans-serif; margin: 20px; line-height: 1.6;'> | |
<h2 style='color: #2c3e50; text-align: center; margin-bottom: 30px;'> | |
🔮 {saju_data['name']}님의 정밀 사주명리 분석서 | |
</h2> | |
<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);'> | |
<h3 style='margin-top: 0; text-align: center;'>📅 출생 정보</h3> | |
<div style='display: grid; grid-template-columns: 1fr 1fr; gap: 15px;'> | |
<div> | |
<p><strong>🌞 양력:</strong> {birth_info['solar']}</p> | |
<p><strong>🌙 음력:</strong> {birth_info['lunar']}</p> | |
</div> | |
<div> | |
<p><strong>👤 성별:</strong> {birth_info['gender']}</p> | |
<p><strong>📍 출생지:</strong> {birth_info['location']}</p> | |
</div> | |
</div> | |
<p style='text-align: center; margin-bottom: 0;'><strong>🌿 절기:</strong> {birth_info.get('solar_term_info', '정보없음')} 시기 출생</p> | |
</div> | |
<div style='background-color: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 2px solid #e9ecef;'> | |
<h3 style='color: #495057; text-align: center; margin-bottom: 20px;'>🎯 사주명식 (간지배치)</h3> | |
<table style='width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1);'> | |
<thead> | |
<tr style='background: linear-gradient(135deg, #74b9ff, #0984e3); color: white;'> | |
<th style='padding: 15px; border: 1px solid #ddd; font-size: 16px;'>년주 (祖上)</th> | |
<th style='padding: 15px; border: 1px solid #ddd; font-size: 16px;'>월주 (父母)</th> | |
<th style='padding: 15px; border: 1px solid #ddd; font-size: 16px;'>일주 (本人)</th> | |
<th style='padding: 15px; border: 1px solid #ddd; font-size: 16px;'>시주 (子女)</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr style='text-align: center; font-size: 20px; font-weight: bold;'> | |
<td style='padding: 20px; border: 1px solid #ddd; background: linear-gradient(135deg, #ffb3ba, #ff7675);'>{saju['year']}</td> | |
<td style='padding: 20px; border: 1px solid #ddd; background: linear-gradient(135deg, #bae1ff, #74b9ff);'>{saju['month']}</td> | |
<td style='padding: 20px; border: 1px solid #ddd; background: linear-gradient(135deg, #ffffba, #fdcb6e); border: 3px solid #f39c12;'>{saju['day']}</td> | |
<td style='padding: 20px; border: 1px solid #ddd; background: linear-gradient(135deg, #baffc9, #00b894);'>{saju['time']}</td> | |
</tr> | |
</tbody> | |
</table> | |
<p style='text-align: center; color: #7f8c8d; margin-bottom: 0;'> | |
<strong>일간:</strong> {detailed['day_master']} ({detailed['day_master_element']}) | | |
<strong>계절:</strong> {detailed['month_season']} | | |
<strong>강약:</strong> {detailed['strength']} | |
</p> | |
</div> | |
<div style='background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 25px;'> | |
<h3 style='margin-top: 0; text-align: center;'>🌟 오행 분석</h3> | |
<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 15px; margin-bottom: 15px;'>""" | |
for element, count in elements['count'].items(): | |
percentage = elements['percentage'].get(element, 0) | |
color_map = {'목': '#00b894', '화': '#e17055', '토': '#fdcb6e', '금': '#74b9ff', '수': '#6c5ce7'} | |
color = color_map.get(element, '#ddd') | |
table_html += f""" | |
<div style='background-color: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px; text-align: center; backdrop-filter: blur(10px);'> | |
<div style='color: {color}; font-size: 18px; font-weight: bold;'>{element}</div> | |
<div style='font-size: 16px; margin: 5px 0;'>{count}개</div> | |
<div style='font-size: 12px;'>{percentage}%</div> | |
</div>""" | |
table_html += f""" | |
</div> | |
<div style='text-align: center; background-color: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px;'> | |
<p style='margin: 5px 0;'><strong>🔥 우세 오행:</strong> {elements['dominant']}</p> | |
<p style='margin: 5px 0;'><strong>💧 부족 오행:</strong> {elements.get('weak', '균형적')}</p> | |
</div> | |
</div> | |
<div style='background-color: #fff; padding: 20px; border-radius: 12px; border: 2px solid #e74c3c; box-shadow: 0 4px 15px rgba(231,76,60,0.1);'> | |
<h3 style='color: #e74c3c; text-align: center; margin-bottom: 15px;'>⚡ 사주 구조 분석</h3> | |
<div style='display: grid; grid-template-columns: 1fr 1fr; gap: 20px;'> | |
<div style='background-color: #ffeaa7; padding: 15px; border-radius: 8px;'> | |
<h4 style='color: #2d3436; margin-top: 0;'>🎭 십성 구조</h4> | |
<p style='margin: 5px 0; color: #636e72;'>비견: {detailed['sipseong']['비견']}개</p> | |
<p style='margin: 5px 0; color: #636e72;'>식신: {detailed['sipseong']['식신']}개</p> | |
<p style='margin: 5px 0; color: #636e72;'>재성: {detailed['sipseong']['편재'] + detailed['sipseong']['정재']}개</p> | |
</div> | |
<div style='background-color: #81ecec; padding: 15px; border-radius: 8px;'> | |
<h4 style='color: #2d3436; margin-top: 0;'>🌊 오행 균형도</h4> | |
<p style='margin: 5px 0; color: #636e72;'>목: {elements['percentage'].get('목', 0)}%</p> | |
<p style='margin: 5px 0; color: #636e72;'>화: {elements['percentage'].get('화', 0)}%</p> | |
<p style='margin: 5px 0; color: #636e72;'>토: {elements['percentage'].get('토', 0)}%</p> | |
<p style='margin: 5px 0; color: #636e72;'>금: {elements['percentage'].get('금', 0)}%</p> | |
<p style='margin: 5px 0; color: #636e72;'>수: {elements['percentage'].get('수', 0)}%</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
""" | |
return table_html | |
def process_saju(name: str, birth_year: int, birth_month: int, birth_day: int, | |
birth_hour: int, birth_minute: int, gender: str, location: str, | |
calendar_type: str, is_intercalation: bool = False) -> Tuple[str, str]: | |
"""사주 처리 메인 함수""" | |
try: | |
if not name.strip(): | |
return "이름을 입력해주세요.", "" | |
if birth_year < 1000 or birth_year > 2050: | |
return "지원하는 연도 범위는 1000년~2050년입니다.", "" | |
if not (1 <= birth_month <= 12): | |
return "올바른 월을 입력해주세요 (1-12).", "" | |
if not (1 <= birth_day <= 31): | |
return "올바른 일을 입력해주세요 (1-31).", "" | |
if not (0 <= birth_hour <= 23): | |
return "올바른 시간을 입력해주세요 (0-23).", "" | |
calculator = SajuCalculator() | |
is_lunar = (calendar_type == "음력") | |
saju_data = calculator.calculate_saju( | |
name, birth_year, birth_month, birth_day, | |
birth_hour, birth_minute, gender, location, is_lunar, is_intercalation | |
) | |
# 사주 표 생성 | |
saju_table = create_saju_table(saju_data) | |
# AI 해석 생성 | |
ai_interpretation = get_ai_interpretation(saju_data) | |
return saju_table, ai_interpretation | |
except Exception as e: | |
error_msg = f"❌ 오류가 발생했습니다: {str(e)}" | |
return error_msg, error_msg | |
# Gradio 인터페이스 생성 | |
def create_interface(): | |
with gr.Blocks( | |
title="🔮 한국 전통 사주명리학 시스템", | |
theme=gr.themes.Soft(), | |
css=""" | |
.gradio-container { | |
font-family: "Noto Sans KR", Arial, sans-serif !important; | |
} | |
.main-header { | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: white; | |
padding: 30px; | |
border-radius: 15px; | |
text-align: center; | |
margin-bottom: 30px; | |
} | |
""" | |
) as interface: | |
gr.HTML(""" | |
<div class="main-header"> | |
<h1 style="margin: 0; font-size: 2.5em;">🔮 한국 전통 사주명리학 시스템</h1> | |
<p style="margin: 10px 0 0 0; font-size: 1.2em; opacity: 0.9;"> | |
한국천문연구원 기준 정밀 음양력 변환 | AI 기반 전문가 해석 | |
</p> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
with gr.Group(): | |
gr.Markdown("### 📝 개인정보") | |
name = gr.Textbox( | |
label="이름", | |
placeholder="홍길동", | |
info="정확한 이름을 입력해주세요" | |
) | |
with gr.Row(): | |
gender = gr.Radio( | |
choices=["남자", "여자"], | |
label="성별", | |
value="남자" | |
) | |
location = gr.Textbox( | |
label="출생지", | |
placeholder="서울특별시", | |
info="태어난 도시를 입력해주세요" | |
) | |
with gr.Group(): | |
gr.Markdown("### 📅 생년월일시") | |
calendar_type = gr.Radio( | |
choices=["양력", "음력"], | |
label="달력 구분", | |
value="양력", | |
info="양력/음력을 선택해주세요" | |
) | |
with gr.Row(): | |
birth_year = gr.Number( | |
label="년도", | |
value=1990, | |
precision=0, | |
info="1000-2050년 지원" | |
) | |
birth_month = gr.Number( | |
label="월", | |
value=1, | |
precision=0, | |
minimum=1, | |
maximum=12 | |
) | |
birth_day = gr.Number( | |
label="일", | |
value=1, | |
precision=0, | |
minimum=1, | |
maximum=31 | |
) | |
with gr.Row(): | |
birth_hour = gr.Number( | |
label="시 (24시간제)", | |
value=0, | |
precision=0, | |
minimum=0, | |
maximum=23, | |
info="정확한 출생시간이 중요합니다" | |
) | |
birth_minute = gr.Number( | |
label="분", | |
value=0, | |
precision=0, | |
minimum=0, | |
maximum=59 | |
) | |
is_intercalation = gr.Checkbox( | |
label="윤달 여부 (음력인 경우만)", | |
value=False, | |
info="음력인 경우 윤달인지 확인해주세요", | |
visible=False | |
) | |
submit_btn = gr.Button( | |
"🔮 정밀 사주 분석 시작", | |
variant="primary", | |
size="lg", | |
elem_classes="submit-button" | |
) | |
gr.Markdown(""" | |
--- | |
### 💡 사용 팁 | |
- **정확한 출생시간**: 2시간 차이로 시주가 바뀝니다 | |
- **음력 주의**: 윤달 여부를 정확히 확인해주세요 | |
- **절기 기준**: 명리학은 24절기로 월을 구분합니다 | |
""") | |
with gr.Column(scale=2): | |
with gr.Tab("📊 사주명식"): | |
saju_output = gr.HTML( | |
label="사주 분석 결과", | |
elem_classes="saju-result" | |
) | |
with gr.Tab("🎯 AI 전문가 해석"): | |
interpretation_output = gr.Textbox( | |
label="상세 운세 해석", | |
lines=25, | |
placeholder="사주 해석 결과가 여기에 표시됩니다...", | |
show_copy_button=True, | |
elem_classes="interpretation-result" | |
) | |
# 이벤트 처리 | |
def toggle_intercalation(calendar_type): | |
return gr.update(visible=(calendar_type == "음력")) | |
calendar_type.change( | |
fn=toggle_intercalation, | |
inputs=[calendar_type], | |
outputs=[is_intercalation] | |
) | |
submit_btn.click( | |
fn=process_saju, | |
inputs=[name, birth_year, birth_month, birth_day, birth_hour, | |
birth_minute, gender, location, calendar_type, is_intercalation], | |
outputs=[saju_output, interpretation_output] | |
) | |
gr.HTML(""" | |
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 10px; margin-top: 30px;"> | |
<h3 style="color: #495057; text-align: center;">📌 시스템 특징</h3> | |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px;"> | |
<div style="text-align: center; padding: 15px;"> | |
<div style="font-size: 24px; margin-bottom: 10px;">🎯</div> | |
<strong>한국천문연구원 기준</strong><br> | |
<small>정확한 음양력 변환</small> | |
</div> | |
<div style="text-align: center; padding: 15px;"> | |
<div style="font-size: 24px; margin-bottom: 10px;">🤖</div> | |
<strong>AI 전문가 해석</strong><br> | |
<small>GPT-4 기반 상세 분석</small> | |
</div> | |
<div style="text-align: center; padding: 15px;"> | |
<div style="font-size: 24px; margin-bottom: 10px;">🌿</div> | |
<strong>24절기 정확 반영</strong><br> | |
<small>전통 명리학 방식</small> | |
</div> | |
<div style="text-align: center; padding: 15px;"> | |
<div style="font-size: 24px; margin-bottom: 10px;">📊</div> | |
<strong>상세 오행 분석</strong><br> | |
<small>십성과 강약 판단</small> | |
</div> | |
</div> | |
<p style="text-align: center; margin-top: 20px; color: #6c757d; font-style: italic;"> | |
💡 사주는 참고용으로만 활용하시고, 중요한 결정은 신중히 하시기 바랍니다. | |
</p> | |
</div> | |
""") | |
return interface | |
# 메인 실행 | |
if __name__ == "__main__": | |
interface = create_interface() | |
interface.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=True, | |
show_error=True | |
) |