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 | |
| ) |