90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
import { Solar } from 'lunar-typescript'
|
|||
|
|
import { ElementScore } from '../services/request'
|
||
|
|
|
||
|
|
type ElementName = '木' | '火' | '土' | '金' | '水'
|
||
|
|
|
||
|
|
export type FiveElementReport = {
|
||
|
|
scores: ElementScore[]
|
||
|
|
favorable: string
|
||
|
|
unfavorable: string
|
||
|
|
dayMaster: string
|
||
|
|
}
|
||
|
|
|
||
|
|
const stemElements: Record<string, ElementName> = {
|
||
|
|
甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土', 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水'
|
||
|
|
}
|
||
|
|
|
||
|
|
const branchElements: Record<string, ElementName> = {
|
||
|
|
子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火', 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水'
|
||
|
|
}
|
||
|
|
|
||
|
|
const elementMeta: Record<ElementName, { color: string }> = {
|
||
|
|
木: { color: '#36b85b' },
|
||
|
|
火: { color: '#ef5a58' },
|
||
|
|
土: { color: '#b46a18' },
|
||
|
|
金: { color: '#d59a05' },
|
||
|
|
水: { color: '#35bfe8' }
|
||
|
|
}
|
||
|
|
|
||
|
|
const scoreOrder: ElementName[] = ['土', '金', '水', '木', '火']
|
||
|
|
const generationOrder: ElementName[] = ['木', '火', '土', '金', '水']
|
||
|
|
|
||
|
|
const hourFromBirthHour = (birthHour: string) => {
|
||
|
|
const numericHour = Number.parseInt(birthHour, 10)
|
||
|
|
if (Number.isFinite(numericHour)) return Math.max(0, Math.min(23, numericHour))
|
||
|
|
|
||
|
|
const branchHours: Record<string, number> = {
|
||
|
|
子: 23, 丑: 1, 寅: 3, 卯: 5, 辰: 7, 巳: 9,
|
||
|
|
午: 11, 未: 13, 申: 15, 酉: 17, 戌: 19, 亥: 21
|
||
|
|
}
|
||
|
|
return branchHours[birthHour.slice(0, 1)] ?? 12
|
||
|
|
}
|
||
|
|
|
||
|
|
export function calculateFiveElements(birthday: string, birthHour: string): FiveElementReport {
|
||
|
|
const date = new Date(`${birthday}T00:00:00`)
|
||
|
|
if (Number.isNaN(date.getTime())) {
|
||
|
|
return {
|
||
|
|
scores: scoreOrder.map((name) => ({ name, color: elementMeta[name].color, value: 20 })),
|
||
|
|
favorable: '金水',
|
||
|
|
unfavorable: '木火土',
|
||
|
|
dayMaster: '壬'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const solar = Solar.fromYmdHms(
|
||
|
|
date.getFullYear(),
|
||
|
|
date.getMonth() + 1,
|
||
|
|
date.getDate(),
|
||
|
|
hourFromBirthHour(birthHour),
|
||
|
|
0,
|
||
|
|
0
|
||
|
|
)
|
||
|
|
const eightChar = solar.getLunar().getEightChar()
|
||
|
|
const pillars = [eightChar.getYear(), eightChar.getMonth(), eightChar.getDay(), eightChar.getTime()]
|
||
|
|
const weights: Record<ElementName, number> = { 木: 0, 火: 0, 土: 0, 金: 0, 水: 0 }
|
||
|
|
|
||
|
|
pillars.forEach((pillar) => {
|
||
|
|
const [stem, branch] = pillar
|
||
|
|
if (stemElements[stem]) weights[stemElements[stem]] += 2
|
||
|
|
if (branchElements[branch]) weights[branchElements[branch]] += 1
|
||
|
|
})
|
||
|
|
|
||
|
|
const total = Object.values(weights).reduce((sum, value) => sum + value, 0)
|
||
|
|
const scores = scoreOrder.map((name) => ({
|
||
|
|
name,
|
||
|
|
color: elementMeta[name].color,
|
||
|
|
value: Math.round((weights[name] / total) * 100)
|
||
|
|
}))
|
||
|
|
const dayMaster = eightChar.getDay().slice(0, 1)
|
||
|
|
const masterElement = stemElements[dayMaster] || '水'
|
||
|
|
const masterIndex = generationOrder.indexOf(masterElement)
|
||
|
|
const resourceElement = generationOrder[(masterIndex + generationOrder.length - 1) % generationOrder.length]
|
||
|
|
const unfavorable = generationOrder.filter((element) => element !== resourceElement && element !== masterElement).join('')
|
||
|
|
|
||
|
|
return {
|
||
|
|
scores,
|
||
|
|
favorable: `${resourceElement}${masterElement}`,
|
||
|
|
unfavorable,
|
||
|
|
dayMaster
|
||
|
|
}
|
||
|
|
}
|