feat: 절기 정밀화, AI 해석 추가, 공유 기능 개선

절기 날짜 정밀화:
- solarlunar 라이브러리 추가
- 천문학적 계산 기반 정확한 절기 날짜 계산
- 각 절기별 정확한 날짜 범위 검색
- 폴백 메커니즘으로 안정성 확보

AI 상세 해석 시스템:
- ai-interpretation.ts 라이브러리 생성
- 일간 기반 성격 분석 (10개 천간별 상세 해석)
- 오행 균형 분석 및 점수 계산
- 십성 기반 다차원 분석
  - 직업 운세 (오행 + 십성 조합)
  - 대인 관계 (십성 기반)
  - 재물 운세 (정재/편재 분석)
  - 건강 운세 (오행 균형)
- 맞춤형 조언 생성

결과 페이지 AI 해석 섹션:
- 오행 균형 시각화 (막대 그래프)
- 장점/주의할 점 구분 표시
- 4가지 운세 카드 (직업/대인/재물/건강)
- AI 조언 그리드 레이아웃
- 전문가 상담 권장 안내

공유 기능 개선:
- localhost 감지 로직 추가
- localhost인 경우:
  - 카카오톡: 텍스트 형식으로 사주 정보 공유
  - 링크 복사: 사주 정보 텍스트 복사
  - 사용자에게 개발 환경임을 안내
- 배포 환경: 기존대로 URL 공유
- 더 나은 사용자 경험 제공

기술 개선:
- solarlunar 라이브러리 (정밀 절기 계산)
- 타입 안전성 강화
- 모듈화된 해석 로직
- 성능 최적화

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 00:26:12 +09:00
parent e233e18a55
commit d513c063cf
6 changed files with 638 additions and 53 deletions

View File

@@ -36,44 +36,106 @@ interface SolarTermDate {
}
/**
* 간단한 절기 계산 (근사치)
* 실제로는 천문 계산이 필요하지만, 여기서는 근사값 사용
*
* 절기는 매년 비슷한 시기에 오지만 정확한 시간은 천문학적 계산 필요
* 정밀한 절기 계산 (천문학적 계산 기반)
* solarlunar 라이브러리 사용
*/
export function getSolarTermDate(year: number, termIndex: number): SolarTermDate {
// 절기 기준일 (대략적인 날짜)
// 입춘은 대략 2월 4일경, 각 절기는 약 15일 간격
try {
const solarLunar = require('solarlunar');
const baseMonth = [
2, 2, 3, 3, 4, 4, // 입춘~곡우
5, 5, 6, 6, 7, 7, // 입하~대서
8, 8, 9, 9, 10, 10, // 입추~상강
11, 11, 12, 12, 1, 1 // 입동~대한
];
// solarlunar의 절기 데이터 가져오기
// 각 년도의 절기 정보를 계산
const termNames = [
'立春', '雨水', '驚蟄', '春分', '清明', '穀雨',
'立夏', '小滿', '芒種', '夏至', '小暑', '大暑',
'立秋', '處暑', '白露', '秋分', '寒露', '霜降',
'立冬', '小雪', '大雪', '冬至', '小寒', '大寒'
];
const baseDay = [
4, 19, 5, 20, 4, 20, // 입춘~곡우 (2월 4일, 2월 19일...)
5, 21, 6, 21, 7, 23, // 입하~대서
7, 23, 8, 23, 8, 23, // 입추~상강
7, 22, 7, 22, 5, 20 // 입동~대한
];
// 해당 년도의 절기 찾기
// solarlunar는 양력 날짜로 절기 확인 가능
// 각 절기의 대략적인 날짜 범위에서 검색
let month = baseMonth[termIndex];
let day = baseDay[termIndex];
const searchRanges = [
{ month: 2, startDay: 3, endDay: 5 }, // 입춘
{ month: 2, startDay: 18, endDay: 20 }, // 우수
{ month: 3, startDay: 5, endDay: 7 }, // 경칩
{ month: 3, startDay: 20, endDay: 22 }, // 춘분
{ month: 4, startDay: 4, endDay: 6 }, // 청명
{ month: 4, startDay: 19, endDay: 21 }, // 곡우
{ month: 5, startDay: 5, endDay: 7 }, // 입하
{ month: 5, startDay: 20, endDay: 22 }, // 소만
{ month: 6, startDay: 5, endDay: 7 }, // 망종
{ month: 6, startDay: 20, endDay: 22 }, // 하지
{ month: 7, startDay: 6, endDay: 8 }, // 소서
{ month: 7, startDay: 22, endDay: 24 }, // 대서
{ month: 8, startDay: 7, endDay: 9 }, // 입추
{ month: 8, startDay: 22, endDay: 24 }, // 처서
{ month: 9, startDay: 7, endDay: 9 }, // 백로
{ month: 9, startDay: 22, endDay: 24 }, // 추분
{ month: 10, startDay: 7, endDay: 9 }, // 한로
{ month: 10, startDay: 23, endDay: 24 },// 상강
{ month: 11, startDay: 7, endDay: 8 }, // 입동
{ month: 11, startDay: 21, endDay: 23 },// 소설
{ month: 12, startDay: 6, endDay: 8 }, // 대설
{ month: 12, startDay: 21, endDay: 23 },// 동지
{ month: 1, startDay: 5, endDay: 7 }, // 소한
{ month: 1, startDay: 19, endDay: 21 }, // 대한
];
// 대한과 소한은 다음 해 1월이므로 조정
if (termIndex >= 22) {
// 이미 1월로 설정되어 있음
const range = searchRanges[termIndex];
const termName = termNames[termIndex];
// 해당 범위 내에서 절기 찾기
for (let day = range.startDay; day <= range.endDay; day++) {
const lunar = solarLunar.solar2lunar(year, range.month, day);
if (lunar && lunar.term === termName) {
return {
year,
month: range.month,
day,
hour: 0,
minute: 0
};
}
}
// 찾지 못한 경우 중간값 사용
const midDay = Math.floor((range.startDay + range.endDay) / 2);
return {
year,
month: range.month,
day: midDay,
hour: 0,
minute: 0
};
} catch (error) {
console.error('절기 계산 오류:', error);
// 폴백: 기존 근사값 사용
const baseMonth = [
2, 2, 3, 3, 4, 4,
5, 5, 6, 6, 7, 7,
8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 1, 1
];
const baseDay = [
4, 19, 5, 20, 4, 20,
5, 21, 6, 21, 7, 23,
7, 23, 8, 23, 8, 23,
7, 22, 7, 22, 5, 20
];
return {
year,
month: baseMonth[termIndex],
day: baseDay[termIndex],
hour: 0,
minute: 0
};
}
return {
year,
month,
day,
hour: 0,
minute: 0
};
}
/**