saju01 / app.py
aliceblue11's picture
Update app.py
ff7bc08 verified
raw
history blame
20.5 kB
import gradio as gr
import datetime
import re
class SajuCalculator:
def __init__(self):
# 천간 (하늘줄기)
self.heavenly_stems = ['갑', '을', '병', '정', '무', '기', '경', '신', '임', '계']
# 지지 (땅가지)
self.earthly_branches = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해']
# 오행
self.five_elements = {
'갑': '목', '을': '목', '병': '화', '정': '화', '무': '토',
'기': '토', '경': '금', '신': '금', '임': '수', '계': '수',
'자': '수', '축': '토', '인': '목', '묘': '목', '진': '토',
'사': '화', '오': '화', '미': '토', '신': '금', '유': '금',
'술': '토', '해': '수'
}
# 십신
self.ten_gods = {
'목': {'목': '비견/겁재', '화': '식신/상관', '토': '편재/정재', '금': '편관/정관', '수': '편인/정인'},
'화': {'목': '편인/정인', '화': '비견/겁재', '토': '식신/상관', '금': '편재/정재', '수': '편관/정관'},
'토': {'목': '편관/정관', '화': '편인/정인', '토': '비견/겁재', '금': '식신/상관', '수': '편재/정재'},
'금': {'목': '편재/정재', '화': '편관/정관', '토': '편인/정인', '금': '비견/겁재', '수': '식신/상관'},
'수': {'목': '식신/상관', '화': '편재/정재', '토': '편관/정관', '금': '편인/정인', '수': '비견/겁재'}
}
# 지역별 시간 보정 (서울 기준 분 단위)
self.location_offsets = {
'서울': 0, '서울특별시': 0, '경기': 0, '경기도': 0, '인천': 0, '인천광역시': 0,
'강원': +12, '강원도': +12, '춘천': +12, '원주': +8, '강릉': +20,
'충북': -8, '충청북도': -8, '청주': -8, '충주': 0,
'충남': -16, '충청남도': -16, '대전': -12, '대전광역시': -12, '천안': -12,
'전북': -20, '전라북도': -20, '전주': -20, '군산': -24,
'전남': -24, '전라남도': -24, '광주': -20, '광주광역시': -20, '목포': -32, '여수': -16,
'경북': +8, '경상북도': +8, '대구': +4, '대구광역시': +4, '포항': +20, '경주': +16,
'경남': -4, '경상남도': -4, '부산': +12, '부산광역시': +12, '울산': +16, '울산광역시': +16, '창원': +4, '마산': +4, '진주': -8,
'제주': -20, '제주도': -20, '제주시': -20, '서귀포': -20
}
def get_location_offset(self, location):
"""출생지에 따른 시간 보정값 반환 (분 단위)"""
if not location:
return 0
location = location.strip()
# 정확한 매칭 시도
if location in self.location_offsets:
return self.location_offsets[location]
# 부분 매칭 시도
for key, offset in self.location_offsets.items():
if key in location or location in key:
return offset
return 0 # 기본값 (서울 기준)
def parse_date(self, date_str):
"""날짜 파싱"""
numbers = re.findall(r'\d+', str(date_str))
if not numbers:
raise ValueError("날짜에서 숫자를 찾을 수 없습니다")
if len(numbers) == 1:
num_str = numbers[0]
if len(num_str) == 8: # YYYYMMDD
year = int(num_str[:4])
month = int(num_str[4:6])
day = int(num_str[6:8])
elif len(num_str) == 6: # YYMMDD
year_part = int(num_str[:2])
year = 1900 + year_part if year_part > 30 else 2000 + year_part
month = int(num_str[2:4])
day = int(num_str[4:6])
else:
raise ValueError(f"날짜 형식을 인식할 수 없습니다: {num_str}")
elif len(numbers) >= 3:
year = int(numbers[0])
month = int(numbers[1])
day = int(numbers[2])
if year < 100:
year = 1900 + year if year > 30 else 2000 + year
else:
raise ValueError("날짜 정보가 부족합니다")
return year, month, day
def parse_time(self, time_str):
"""시간 파싱"""
if not time_str:
return 12, 0
numbers = re.findall(r'\d+', str(time_str))
if not numbers:
return 12, 0
if len(numbers) == 1:
time_num = numbers[0]
if len(time_num) == 4: # HHMM
hour = int(time_num[:2])
minute = int(time_num[2:4])
elif len(time_num) == 3: # HMM
hour = int(time_num[0])
minute = int(time_num[1:3])
else: # H or HH
hour = int(time_num)
minute = 0
else:
hour = int(numbers[0])
minute = int(numbers[1]) if len(numbers) > 1 else 0
hour = hour % 24
return hour, minute
def get_ganzhi(self, year, month, day, hour):
"""간지 계산"""
base_date = datetime.datetime(1900, 1, 1)
target_date = datetime.datetime(year, month, day)
days_diff = (target_date - base_date).days
# 연주 계산
year_stem_index = (year - 1900 + 6) % 10
year_branch_index = (year - 1900 + 0) % 12
# 월주 계산
month_stem_index = (year_stem_index * 2 + month + 1) % 10
month_branch_index = (month + 1) % 12
# 일주 계산
day_stem_index = (days_diff + 10) % 10
day_branch_index = (days_diff + 2) % 12
# 시주 계산
hour_branch_index = ((hour + 1) // 2) % 12
hour_stem_index = (day_stem_index * 2 + hour_branch_index) % 10
return {
'year': (self.heavenly_stems[year_stem_index], self.earthly_branches[year_branch_index]),
'month': (self.heavenly_stems[month_stem_index], self.earthly_branches[month_branch_index]),
'day': (self.heavenly_stems[day_stem_index], self.earthly_branches[day_branch_index]),
'hour': (self.heavenly_stems[hour_stem_index], self.earthly_branches[hour_branch_index])
}
def analyze_elements(self, ganzhi):
"""오행 분석"""
elements = []
for pillar in ganzhi.values():
stem_element = self.five_elements[pillar[0]]
branch_element = self.five_elements[pillar[1]]
elements.extend([stem_element, branch_element])
element_count = {}
for element in ['목', '화', '토', '금', '수']:
element_count[element] = elements.count(element)
return element_count
def get_ten_gods_analysis(self, ganzhi):
"""십신 분석"""
day_stem = ganzhi['day'][0]
day_element = self.five_elements[day_stem]
analysis = {}
for pillar_name, pillar in ganzhi.items():
stem_element = self.five_elements[pillar[0]]
branch_element = self.five_elements[pillar[1]]
stem_relation = self.ten_gods[day_element][stem_element]
branch_relation = self.ten_gods[day_element][branch_element]
analysis[pillar_name] = {
'stem_relation': stem_relation,
'branch_relation': branch_relation
}
return analysis
def get_element_personality(element):
"""오행별 성격 특성"""
personalities = {
'목': "창의적이고 성장 지향적이며, 유연하고 협력적인 성격을 가지고 있습니다.",
'화': "열정적이고 활동적이며, 밝고 사교적인 성격을 가지고 있습니다.",
'토': "안정적이고 신중하며, 포용력이 있고 책임감이 강한 성격을 가지고 있습니다.",
'금': "원칙적이고 정의로우며, 결단력이 있고 리더십이 강한 성격을 가지고 있습니다.",
'수': "지혜롭고 적응력이 있으며, 깊이 있고 신중한 성격을 가지고 있습니다."
}
return personalities.get(element, "균형 잡힌 성격을 가지고 있습니다.")
def get_element_balance_advice(elements):
"""오행 균형에 따른 조언"""
max_element = max(elements, key=elements.get)
min_element = min(elements, key=elements.get)
advice = f"현재 {max_element}가 가장 강하고 {min_element}가 가장 약합니다. "
if elements[max_element] - elements[min_element] > 2:
advice += f"{min_element}를 보강하고 {max_element}의 기운을 조절하는 것이 좋겠습니다."
else:
advice += "전체적으로 균형이 잘 잡혀 있는 편입니다."
return advice
def format_saju_result(calculator, ganzhi, elements, ten_gods, birth_info):
"""사주 결과 포맷팅 - 표 형태로 출력"""
# 오행별 색상 설정
element_colors = {
'목': '#28a745', # 녹색
'화': '#dc3545', # 빨강
'토': '#ffc107', # 노랑
'금': '#6c757d', # 회색
'수': '#007bff' # 파랑
}
def get_colored_element(char, element):
color = element_colors.get(element, '#000000')
return f'<span style="color: {color}; font-weight: bold;">{char}</span>'
def get_colored_ganzhi(stem, branch):
stem_element = calculator.five_elements[stem]
branch_element = calculator.five_elements[branch]
colored_stem = get_colored_element(stem, stem_element)
colored_branch = get_colored_element(branch, branch_element)
return f'{colored_stem}{colored_branch}'
# 시간 보정 정보
time_correction_info = ""
if birth_info['location_offset'] != 0:
sign = "+" if birth_info['location_offset'] > 0 else ""
time_correction_info = f"""
### ⏰ 출생지 시간 보정
- **입력 시간**: {birth_info['original_time']}
- **보정 시간**: {birth_info['corrected_time']} ({birth_info['birth_place']} 기준 {sign}{birth_info['location_offset']}분 보정)
"""
result = f"""
# 🔮 사주명리 만세력 분석결과
## 📋 기본정보
- **생년월일**: {birth_info['birth_datetime'].strftime('%Y년 %m월 %d일')}
- **출생시간**: {birth_info['corrected_time']} ({birth_info['birth_place']})
- **성별**: {birth_info['gender']}
{time_correction_info}
## 🏛️ 사주(四柱) 만세력표
<div style="text-align: center;">
| 구분 | **연주(年柱)** | **월주(月柱)** | **일주(日柱)** | **시주(時柱)** |
|:---:|:---:|:---:|:---:|:---:|
| **천간** | {get_colored_element(ganzhi['year'][0], calculator.five_elements[ganzhi['year'][0]])} | {get_colored_element(ganzhi['month'][0], calculator.five_elements[ganzhi['month'][0]])} | {get_colored_element(ganzhi['day'][0], calculator.five_elements[ganzhi['day'][0]])} | {get_colored_element(ganzhi['hour'][0], calculator.five_elements[ganzhi['hour'][0]])} |
| **지지** | {get_colored_element(ganzhi['year'][1], calculator.five_elements[ganzhi['year'][1]])} | {get_colored_element(ganzhi['month'][1], calculator.five_elements[ganzhi['month'][1]])} | {get_colored_element(ganzhi['day'][1], calculator.five_elements[ganzhi['day'][1]])} | {get_colored_element(ganzhi['hour'][1], calculator.five_elements[ganzhi['hour'][1]])} |
| **간지** | **{get_colored_ganzhi(ganzhi['year'][0], ganzhi['year'][1])}** | **{get_colored_ganzhi(ganzhi['month'][0], ganzhi['month'][1])}** | **{get_colored_ganzhi(ganzhi['day'][0], ganzhi['day'][1])}** | **{get_colored_ganzhi(ganzhi['hour'][0], ganzhi['hour'][1])}** |
| **의미** | 조상, 뿌리 | 부모, 성장기 | 본인, 배우자 | 자식, 말년 |
</div>
## 🌟 오행(五行) 분석
<div style="text-align: center;">
| 오행 | 개수 | 비율 | 상태 |
|:---:|:---:|:---:|:---:|
| {get_colored_element('목', '목')} | {elements['목']}개 | {(elements['목']/8*100):.1f}% | {'강함' if elements['목'] >= 3 else '보통' if elements['목'] >= 2 else '약함'} |
| {get_colored_element('화', '화')} | {elements['화']}개 | {(elements['화']/8*100):.1f}% | {'강함' if elements['화'] >= 3 else '보통' if elements['화'] >= 2 else '약함'} |
| {get_colored_element('토', '토')} | {elements['토']}개 | {(elements['토']/8*100):.1f}% | {'강함' if elements['토'] >= 3 else '보통' if elements['토'] >= 2 else '약함'} |
| {get_colored_element('금', '금')} | {elements['금']}개 | {(elements['금']/8*100):.1f}% | {'강함' if elements['금'] >= 3 else '보통' if elements['금'] >= 2 else '약함'} |
| {get_colored_element('수', '수')} | {elements['수']}개 | {(elements['수']/8*100):.1f}% | {'강함' if elements['수'] >= 3 else '보통' if elements['수'] >= 2 else '약함'} |
</div>
### 오행 균형 분석
"""
max_element = max(elements, key=elements.get)
min_element = min(elements, key=elements.get)
result += f"- **가장 강한 오행**: {get_colored_element(max_element, max_element)} ({elements[max_element]}개)\n"
result += f"- **가장 약한 오행**: {get_colored_element(min_element, min_element)} ({elements[min_element]}개)\n"
result += """
## 🎭 십신(十神) 분석표
<div style="text-align: center;">
| 구분 | **연주** | **월주** | **일주** | **시주** |
|:---:|:---:|:---:|:---:|:---:|
| **천간 십신** | """ + ten_gods['year']['stem_relation'] + """ | """ + ten_gods['month']['stem_relation'] + """ | """ + ten_gods['day']['stem_relation'] + """ | """ + ten_gods['hour']['stem_relation'] + """ |
| **지지 십신** | """ + ten_gods['year']['branch_relation'] + """ | """ + ten_gods['month']['branch_relation'] + """ | """ + ten_gods['day']['branch_relation'] + """ | """ + ten_gods['hour']['branch_relation'] + """ |
</div>
## 💡 기본 해석
### 일간 분석
- **일간**: {get_colored_element(ganzhi['day'][0], calculator.five_elements[ganzhi['day'][0]])} ({calculator.five_elements[ganzhi['day'][0]]})
- **성격**: {get_element_personality(calculator.five_elements[ganzhi['day'][0]])}
### 오행 조화
{get_element_balance_advice(elements)}
---
### 📌 주의사항
- 본 분석은 기본적인 사주명리학 원리에 따른 것입니다
- 정확한 해석을 위해서는 전문가 상담을 권장합니다
- 절기, 음력 변환 등 세부 요소는 고려되지 않았습니다
*분석 일시: {datetime.datetime.now().strftime('%Y년 %m월 %d일 %H시 %M분')}*
"""
return result
def calculate_saju(birth_date, birth_time, gender, birth_place):
"""사주 계산 메인 함수"""
# SajuCalculator 인스턴스 생성
calculator = SajuCalculator()
try:
# 입력 검증
if not birth_date:
return "❌ 생년월일을 입력해주세요."
# 날짜 파싱
year, month, day = calculator.parse_date(birth_date)
# 시간 파싱
hour, minute = calculator.parse_time(birth_time)
# 출생지에 따른 시간 보정
location_offset = calculator.get_location_offset(birth_place)
# 보정된 시간 계산
corrected_minute = minute + location_offset
corrected_hour = hour
# 분이 60을 넘거나 0 미만인 경우 시간 조정
if corrected_minute >= 60:
corrected_hour += corrected_minute // 60
corrected_minute = corrected_minute % 60
elif corrected_minute < 0:
corrected_hour -= (-corrected_minute - 1) // 60 + 1
corrected_minute = 60 + (corrected_minute % 60)
# 시간이 24를 넘거나 0 미만인 경우 조정
corrected_hour = corrected_hour % 24
# 날짜 유효성 검사
if year < 1900 or year > 2100:
return f"❌ 연도는 1900~2100 사이여야 합니다. 입력된 연도: {year}"
if month < 1 or month > 12:
return f"❌ 월은 1~12 사이여야 합니다. 입력된 월: {month}"
if day < 1 or day > 31:
return f"❌ 일은 1~31 사이여야 합니다. 입력된 일: {day}"
# datetime 객체 생성 (보정된 시간 사용)
birth_datetime = datetime.datetime(year, month, day, corrected_hour, corrected_minute)
original_time = f"{hour:02d}:{minute:02d}"
corrected_time = f"{corrected_hour:02d}:{corrected_minute:02d}"
# 출생 정보 딕셔너리
birth_info = {
'birth_datetime': birth_datetime,
'gender': gender,
'birth_place': birth_place,
'original_time': original_time,
'corrected_time': corrected_time,
'location_offset': location_offset
}
# 간지 계산
ganzhi = calculator.get_ganzhi(year, month, day, corrected_hour)
# 오행 분석
elements = calculator.analyze_elements(ganzhi)
# 십신 분석
ten_gods = calculator.get_ten_gods_analysis(ganzhi)
# 결과 포맷팅
result = format_saju_result(calculator, ganzhi, elements, ten_gods, birth_info)
return result
except ValueError as ve:
return f"❌ 입력 오류: {str(ve)}\n\n💡 입력 예시:\n- 생년월일: 19851015, 1985-10-15\n- 시간: 1430, 14:30"
except Exception as e:
return f"❌ 계산 중 오류가 발생했습니다: {str(e)}"
def create_interface():
"""Gradio 인터페이스 생성"""
with gr.Blocks(title="🔮 사주명리 만세력 시스템") as demo:
gr.HTML("""
<div style="text-align: center; padding: 20px;">
<h1>🔮 사주명리학 만세력 분석 시스템</h1>
<p>생년월일시와 출생지 정보를 입력하시면 상세한 만세력을 분석해드립니다.</p>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.HTML("<h3>📝 정보 입력</h3>")
birth_date = gr.Textbox(
label="생년월일",
placeholder="19851015 또는 1985-10-15",
info="8자리 숫자 또는 구분자 포함하여 입력"
)
birth_time = gr.Textbox(
label="태어난 시간 (선택사항)",
placeholder="1430 또는 14:30",
info="4자리 숫자 또는 구분자 포함하여 입력. 비우면 정오로 설정"
)
gender = gr.Radio(
choices=["남", "여"],
label="성별",
value="남"
)
birth_place = gr.Textbox(
label="출생지",
placeholder="서울특별시",
info="시/도 단위로 입력해주세요"
)
calculate_btn = gr.Button(
"🔮 만세력 분석하기",
variant="primary"
)
with gr.Row():
with gr.Column():
result_output = gr.Markdown(
label="분석 결과",
value="👆 위의 정보를 입력하고 '만세력 분석하기' 버튼을 클릭하세요."
)
# 이벤트 연결
calculate_btn.click(
fn=calculate_saju,
inputs=[birth_date, birth_time, gender, birth_place],
outputs=result_output
)
gr.HTML("""
<div style="text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #eee;">
<p><small>※ 본 시스템은 전통 사주명리학을 기반으로 하며, 참고용으로만 활용해주시기 바랍니다.</small></p>
<p><small>※ 정확한 해석을 위해서는 전문가의 상담을 받으시기 바랍니다.</small></p>
</div>
""")
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True
)