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:
316
lib/ai-interpretation.ts
Normal file
316
lib/ai-interpretation.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { SajuData } from './saju-calculator';
|
||||
|
||||
/**
|
||||
* AI 기반 사주 해석
|
||||
* 사주 데이터를 분석하여 상세한 해석 제공
|
||||
*/
|
||||
|
||||
interface Interpretation {
|
||||
personality: string[];
|
||||
strengths: string[];
|
||||
weaknesses: string[];
|
||||
career: string[];
|
||||
relationships: string[];
|
||||
health: string[];
|
||||
wealth: string[];
|
||||
advice: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 오행 균형 분석
|
||||
*/
|
||||
function analyzeElementBalance(saju: SajuData): { [key: string]: number } {
|
||||
const elements = { 木: 0, 火: 0, 土: 0, 金: 0, 水: 0 };
|
||||
|
||||
// 사주팔자의 각 기둥에서 오행 카운트
|
||||
elements[saju.year.element]++;
|
||||
elements[saju.month.element]++;
|
||||
elements[saju.day.element]++;
|
||||
if (saju.hour) elements[saju.hour.element]++;
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 십성 분석
|
||||
*/
|
||||
function analyzeTenGods(saju: SajuData): { [key: string]: number } {
|
||||
const tenGods: { [key: string]: number } = {};
|
||||
|
||||
[saju.year.tenGod, saju.month.tenGod, saju.hour?.tenGod].forEach(god => {
|
||||
if (god && god !== '일간') {
|
||||
tenGods[god] = (tenGods[god] || 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
return tenGods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 일간 기반 성격 해석
|
||||
*/
|
||||
function interpretDayStem(stem: string, element: string): string[] {
|
||||
const interpretations: { [key: string]: string[] } = {
|
||||
'甲': [
|
||||
'큰 나무처럼 곧고 꿋꿋한 성격입니다.',
|
||||
'리더십이 강하고 개척 정신이 뛰어납니다.',
|
||||
'정의감이 강하고 원칙을 중요시합니다.',
|
||||
'때로는 융통성이 부족할 수 있습니다.'
|
||||
],
|
||||
'乙': [
|
||||
'부드러운 풀처럼 유연하고 적응력이 뛰어납니다.',
|
||||
'섬세하고 예술적 감각이 있습니다.',
|
||||
'주변 환경에 잘 적응하며 협력을 중시합니다.',
|
||||
'때로는 우유부단할 수 있습니다.'
|
||||
],
|
||||
'丙': [
|
||||
'태양처럼 밝고 활발한 성격입니다.',
|
||||
'사교성이 뛰어나고 열정적입니다.',
|
||||
'창의적이고 표현력이 풍부합니다.',
|
||||
'때로는 충동적일 수 있습니다.'
|
||||
],
|
||||
'丁': [
|
||||
'촛불처럼 따뜻하고 섬세한 성격입니다.',
|
||||
'예민하고 감수성이 풍부합니다.',
|
||||
'예의 바르고 배려심이 깊습니다.',
|
||||
'때로는 너무 예민할 수 있습니다.'
|
||||
],
|
||||
'戊': [
|
||||
'산처럼 묵직하고 안정적인 성격입니다.',
|
||||
'책임감이 강하고 신뢰할 수 있습니다.',
|
||||
'현실적이고 실용적입니다.',
|
||||
'때로는 고집이 셀 수 있습니다.'
|
||||
],
|
||||
'己': [
|
||||
'밭처럼 포용력 있고 온화한 성격입니다.',
|
||||
'배려심이 깊고 참을성이 강합니다.',
|
||||
'현실적이며 실속을 챙깁니다.',
|
||||
'때로는 소극적일 수 있습니다.'
|
||||
],
|
||||
'庚': [
|
||||
'금속처럼 단단하고 강인한 성격입니다.',
|
||||
'결단력이 있고 추진력이 강합니다.',
|
||||
'정직하고 의리를 중시합니다.',
|
||||
'때로는 융통성이 부족할 수 있습니다.'
|
||||
],
|
||||
'辛': [
|
||||
'보석처럼 고귀하고 섬세한 성격입니다.',
|
||||
'예리하고 통찰력이 뛰어납니다.',
|
||||
'품위 있고 우아함을 추구합니다.',
|
||||
'때로는 까다로울 수 있습니다.'
|
||||
],
|
||||
'壬': [
|
||||
'큰 바다처럼 넓고 깊은 성격입니다.',
|
||||
'지혜롭고 포용력이 있습니다.',
|
||||
'융통성이 있고 적응력이 뛰어납니다.',
|
||||
'때로는 변덕스러울 수 있습니다.'
|
||||
],
|
||||
'癸': [
|
||||
'이슬처럼 섬세하고 조용한 성격입니다.',
|
||||
'지적이고 사려 깊습니다.',
|
||||
'인내심이 강하고 끈기가 있습니다.',
|
||||
'때로는 소심할 수 있습니다.'
|
||||
]
|
||||
};
|
||||
|
||||
return interpretations[stem] || ['독특한 개성을 가진 사람입니다.'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 직업 운세 분석
|
||||
*/
|
||||
function interpretCareer(saju: SajuData, tenGods: { [key: string]: number }): string[] {
|
||||
const career: string[] = [];
|
||||
const element = saju.day.element;
|
||||
|
||||
// 오행 기반 직업 추천
|
||||
const careerByElement: { [key: string]: string[] } = {
|
||||
'木': ['교육', '출판', '디자인', '패션', '임업', '환경'],
|
||||
'火': ['예술', '광고', '방송', '요식업', 'IT', '전자'],
|
||||
'土': ['부동산', '건설', '농업', '유통', '중개', '컨설팅'],
|
||||
'金': ['금융', '법조', '의료', '기계', '자동차', '보석'],
|
||||
'水': ['무역', '물류', '여행', '수산', '음료', '화학']
|
||||
};
|
||||
|
||||
career.push(...careerByElement[element].slice(0, 3).map(c => `${c} 분야에 적성이 있습니다.`));
|
||||
|
||||
// 십성 기반 직업 성향
|
||||
if (tenGods['정관'] || tenGods['편관']) {
|
||||
career.push('조직 생활이나 공직에 적합합니다.');
|
||||
}
|
||||
if (tenGods['정재'] || tenGods['편재']) {
|
||||
career.push('재물 관리나 사업에 능력이 있습니다.');
|
||||
}
|
||||
if (tenGods['식신'] || tenGods['상관']) {
|
||||
career.push('창의적인 일이나 표현하는 직업이 좋습니다.');
|
||||
}
|
||||
if (tenGods['정인'] || tenGods['편인']) {
|
||||
career.push('학문, 연구, 교육 분야가 적합합니다.');
|
||||
}
|
||||
|
||||
return career;
|
||||
}
|
||||
|
||||
/**
|
||||
* 대인 관계 분석
|
||||
*/
|
||||
function interpretRelationships(saju: SajuData, tenGods: { [key: string]: number }): string[] {
|
||||
const relationships: string[] = [];
|
||||
|
||||
if (tenGods['비견'] || tenGods['겁재']) {
|
||||
relationships.push('친구나 동료와의 관계가 중요합니다.');
|
||||
relationships.push('경쟁심이 있지만 협력도 잘합니다.');
|
||||
}
|
||||
|
||||
if (tenGods['정관'] || tenGods['편관']) {
|
||||
relationships.push('윗사람의 인정을 받기 쉽습니다.');
|
||||
relationships.push('사회적 명예를 중시합니다.');
|
||||
}
|
||||
|
||||
if (tenGods['정재'] || tenGods['편재']) {
|
||||
if (saju.gender === 'male') {
|
||||
relationships.push('이성과의 인연이 좋습니다.');
|
||||
} else {
|
||||
relationships.push('재물 운이 좋습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
if (tenGods['정인'] || tenGods['편인']) {
|
||||
if (saju.gender === 'female') {
|
||||
relationships.push('가족과의 유대가 깊습니다.');
|
||||
} else {
|
||||
relationships.push('멘토를 만나기 쉽습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
return relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* 건강 운세 분석
|
||||
*/
|
||||
function interpretHealth(saju: SajuData, elements: { [key: string]: number }): string[] {
|
||||
const health: string[] = [];
|
||||
const element = saju.day.element;
|
||||
|
||||
// 오행별 건강 주의사항
|
||||
const healthByElement: { [key: string]: string } = {
|
||||
'木': '간, 담낭, 눈 건강에 주의하세요.',
|
||||
'火': '심장, 혈압, 소장 건강에 주의하세요.',
|
||||
'土': '위장, 소화기, 비장 건강에 주의하세요.',
|
||||
'金': '폐, 대장, 피부 건강에 주의하세요.',
|
||||
'水': '신장, 방광, 생식기 건강에 주의하세요.'
|
||||
};
|
||||
|
||||
health.push(healthByElement[element]);
|
||||
|
||||
// 오행 불균형 체크
|
||||
const maxElement = Object.keys(elements).reduce((a, b) =>
|
||||
elements[a] > elements[b] ? a : b
|
||||
);
|
||||
const minElement = Object.keys(elements).reduce((a, b) =>
|
||||
elements[a] < elements[b] ? a : b
|
||||
);
|
||||
|
||||
if (elements[maxElement] - elements[minElement] >= 2) {
|
||||
health.push('오행 균형을 맞추기 위한 식습관 관리가 필요합니다.');
|
||||
}
|
||||
|
||||
health.push('규칙적인 생활과 적절한 운동이 중요합니다.');
|
||||
|
||||
return health;
|
||||
}
|
||||
|
||||
/**
|
||||
* 재물 운세 분석
|
||||
*/
|
||||
function interpretWealth(saju: SajuData, tenGods: { [key: string]: number }): string[] {
|
||||
const wealth: string[] = [];
|
||||
|
||||
if (tenGods['정재']) {
|
||||
wealth.push('정직한 노력으로 재물을 모을 수 있습니다.');
|
||||
wealth.push('월급이나 안정적인 수입이 좋습니다.');
|
||||
}
|
||||
|
||||
if (tenGods['편재']) {
|
||||
wealth.push('사업이나 투자로 재물을 얻을 수 있습니다.');
|
||||
wealth.push('재테크에 관심을 가지면 좋습니다.');
|
||||
}
|
||||
|
||||
if (tenGods['식신'] || tenGods['상관']) {
|
||||
wealth.push('재능을 활용한 수입원이 있습니다.');
|
||||
wealth.push('창의적인 일로 돈을 벌 수 있습니다.');
|
||||
}
|
||||
|
||||
if (!tenGods['정재'] && !tenGods['편재']) {
|
||||
wealth.push('재물보다는 명예나 학문을 추구합니다.');
|
||||
wealth.push('꾸준한 저축이 중요합니다.');
|
||||
}
|
||||
|
||||
return wealth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 종합 조언
|
||||
*/
|
||||
function generateAdvice(saju: SajuData, elements: { [key: string]: number }): string[] {
|
||||
const advice: string[] = [];
|
||||
const element = saju.day.element;
|
||||
|
||||
// 오행별 조언
|
||||
const adviceByElement: { [key: string]: string[] } = {
|
||||
'木': ['아침 산책으로 하루를 시작하세요.', '녹색 식물을 가까이 하세요.', '독서로 마음을 충전하세요.'],
|
||||
'火': ['밝은 색상의 옷을 입으세요.', '사람들과 적극적으로 소통하세요.', '예술 활동을 즐기세요.'],
|
||||
'土': ['규칙적인 식사를 하세요.', '안정적인 계획을 세우세요.', '자연과 가까운 곳에 가세요.'],
|
||||
'金': ['명확한 목표를 설정하세요.', '금속 액세서리를 착용하세요.', '원칙을 지키되 융통성도 발휘하세요.'],
|
||||
'水': ['충분한 수분 섭취를 하세요.', '유연한 사고를 유지하세요.', '명상이나 요가로 마음을 다스리세요.']
|
||||
};
|
||||
|
||||
advice.push(...adviceByElement[element]);
|
||||
|
||||
// 일반적인 조언
|
||||
advice.push('자신의 장점을 살리고 단점을 보완하세요.');
|
||||
advice.push('긍정적인 마인드로 하루를 시작하세요.');
|
||||
|
||||
return advice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 사주 해석 생성
|
||||
*/
|
||||
export function generateInterpretation(saju: SajuData): Interpretation {
|
||||
const elements = analyzeElementBalance(saju);
|
||||
const tenGods = analyzeTenGods(saju);
|
||||
|
||||
const personality = interpretDayStem(saju.day.stem, saju.day.element);
|
||||
|
||||
// 장점과 단점 분리
|
||||
const strengths = personality.filter((_, i) => i < 3);
|
||||
const weaknesses = [personality[3] || '균형 잡힌 성격입니다.'];
|
||||
|
||||
return {
|
||||
personality,
|
||||
strengths,
|
||||
weaknesses,
|
||||
career: interpretCareer(saju, tenGods),
|
||||
relationships: interpretRelationships(saju, tenGods),
|
||||
health: interpretHealth(saju, elements),
|
||||
wealth: interpretWealth(saju, tenGods),
|
||||
advice: generateAdvice(saju, elements)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 오행 균형 점수 계산
|
||||
*/
|
||||
export function calculateElementScore(saju: SajuData): { [key: string]: number } {
|
||||
const elements = analyzeElementBalance(saju);
|
||||
const total = Object.values(elements).reduce((a, b) => a + b, 0);
|
||||
|
||||
const scores: { [key: string]: number } = {};
|
||||
for (const [element, count] of Object.entries(elements)) {
|
||||
scores[element] = Math.round((count / total) * 100);
|
||||
}
|
||||
|
||||
return scores;
|
||||
}
|
||||
Reference in New Issue
Block a user