feat: add fullstack deployment and oracle app
This commit is contained in:
708
web/src/lib/divination.ts
Normal file
708
web/src/lib/divination.ts
Normal file
@@ -0,0 +1,708 @@
|
||||
import { Solar } from 'lunar-typescript'
|
||||
|
||||
const PALACE_ORDER = ['大安', '留连', '速喜', '赤口', '小吉', '空亡'] as const
|
||||
const BRANCH_ORDER = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'] as const
|
||||
const LUNAR_MONTH_BRANCHES = ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑'] as const
|
||||
const SLOT_ORDER = ['year', 'month', 'day', 'time'] as const
|
||||
const COMPASS_SLOT_VALUES = ['first', 'second', 'third'] as const
|
||||
|
||||
const BRANCH_VALUES: Record<BranchName, number> = {
|
||||
子: 1,
|
||||
丑: 2,
|
||||
寅: 3,
|
||||
卯: 4,
|
||||
辰: 5,
|
||||
巳: 6,
|
||||
午: 7,
|
||||
未: 8,
|
||||
申: 9,
|
||||
酉: 10,
|
||||
戌: 11,
|
||||
亥: 12,
|
||||
}
|
||||
|
||||
const PALACE_META = {
|
||||
大安: {
|
||||
element: '木',
|
||||
quality: '吉',
|
||||
theme: '稳住格局,利于正面推进。',
|
||||
detail: '适合谈条件、做决策、走正式流程。',
|
||||
weight: 2,
|
||||
},
|
||||
留连: {
|
||||
element: '土',
|
||||
quality: '中',
|
||||
theme: '反复拖延,过程比结果更磨人。',
|
||||
detail: '适合补材料、等批复、拉齐分歧,不宜急催。',
|
||||
weight: 0,
|
||||
},
|
||||
速喜: {
|
||||
element: '火',
|
||||
quality: '吉',
|
||||
theme: '消息来得快,适合主动推进。',
|
||||
detail: '利沟通、利反馈、利敲定窗口期。',
|
||||
weight: 3,
|
||||
},
|
||||
赤口: {
|
||||
element: '金',
|
||||
quality: '凶',
|
||||
theme: '口舌争执,信息容易带刺。',
|
||||
detail: '要管住措辞,避免硬碰硬。',
|
||||
weight: -2,
|
||||
},
|
||||
小吉: {
|
||||
element: '水',
|
||||
quality: '吉',
|
||||
theme: '缓步有利,贵在借力。',
|
||||
detail: '利协商、利求人、利柔性处理。',
|
||||
weight: 2,
|
||||
},
|
||||
空亡: {
|
||||
element: '土',
|
||||
quality: '凶',
|
||||
theme: '预期容易落空,先核实条件。',
|
||||
detail: '适合先确认资源和边界,再决定要不要投入。',
|
||||
weight: -3,
|
||||
},
|
||||
} as const
|
||||
|
||||
const ELEMENT_GENERATES: Record<ElementName, ElementName> = {
|
||||
木: '火',
|
||||
火: '土',
|
||||
土: '金',
|
||||
金: '水',
|
||||
水: '木',
|
||||
}
|
||||
|
||||
const ELEMENT_CONTROLS: Record<ElementName, ElementName> = {
|
||||
木: '土',
|
||||
火: '金',
|
||||
土: '水',
|
||||
金: '木',
|
||||
水: '火',
|
||||
}
|
||||
|
||||
const SLOT_LABELS: Record<BranchSlot, string> = {
|
||||
year: '年宫',
|
||||
month: '月宫',
|
||||
day: '日宫',
|
||||
time: '时宫',
|
||||
}
|
||||
|
||||
const SLOT_SOURCES: Record<BranchSlot, string> = {
|
||||
year: '农历年支',
|
||||
month: '农历月建',
|
||||
day: '万年历日支',
|
||||
time: '时辰地支',
|
||||
}
|
||||
|
||||
const SLOT_EXPLANATIONS: Record<BranchSlot, string> = {
|
||||
year: '对应大环境、外部周期、行业与权力方态度。',
|
||||
month: '对应当月氛围、资源松紧、团队与预算状态。',
|
||||
day: '对应当天执行面、你的状态、沟通与临场表现。',
|
||||
time: '对应结果落点,成败判断以此为主。',
|
||||
}
|
||||
|
||||
const DOMAIN_KEYWORDS = {
|
||||
career: ['工作', '加薪', '升职', 'offer', '面试', '跳槽', '裁员', '项目', '合作', '老板'],
|
||||
wealth: ['财运', '投资', '赚钱', '回款', '签单', '收入', '生意', '客户'],
|
||||
relation: ['感情', '恋爱', '复合', '结婚', '相亲', '关系', '伴侣'],
|
||||
study: ['考试', '学习', '申请', '留学', '证书', '答辩'],
|
||||
} as const
|
||||
|
||||
export type PalaceName = (typeof PALACE_ORDER)[number]
|
||||
export type BranchName = (typeof BRANCH_ORDER)[number]
|
||||
export type BranchSlot = (typeof SLOT_ORDER)[number]
|
||||
export type ElementName = '木' | '火' | '土' | '金' | '水'
|
||||
export type DivinationMode = 'time' | 'compass'
|
||||
|
||||
export interface CompassNumbers {
|
||||
first: number
|
||||
second: number
|
||||
third: number
|
||||
}
|
||||
|
||||
export interface ManualBranchOverrides {
|
||||
year?: BranchName
|
||||
month?: BranchName
|
||||
day?: BranchName
|
||||
time?: BranchName
|
||||
}
|
||||
|
||||
export interface BranchInfo {
|
||||
slot: BranchSlot
|
||||
label: string
|
||||
branch: string
|
||||
value: number
|
||||
source: string
|
||||
sourceNote: string
|
||||
ganZhi: string
|
||||
}
|
||||
|
||||
export interface PalaceResult {
|
||||
slot: BranchSlot
|
||||
label: string
|
||||
branch: string
|
||||
branchValue: number
|
||||
palace: PalaceName
|
||||
element: ElementName
|
||||
quality: '吉' | '中' | '凶'
|
||||
theme: string
|
||||
detail: string
|
||||
slotExplanation: string
|
||||
}
|
||||
|
||||
export interface PalaceRelation {
|
||||
from: BranchSlot
|
||||
to: BranchSlot
|
||||
kind: '前生后' | '前克后' | '后生前' | '后克前' | '比和'
|
||||
score: number
|
||||
summary: string
|
||||
}
|
||||
|
||||
export interface LocalInterpretation {
|
||||
verdict: string
|
||||
score: number
|
||||
summary: string
|
||||
domainLabel: string
|
||||
chainSummary: string
|
||||
actionAdvice: string[]
|
||||
riskAdvice: string[]
|
||||
}
|
||||
|
||||
export interface DivinationResult {
|
||||
mode: DivinationMode
|
||||
modeLabel: string
|
||||
question: string
|
||||
datetime: string
|
||||
solarLabel: string
|
||||
lunarLabel: string
|
||||
methodNote: string
|
||||
branches: Record<BranchSlot, BranchInfo>
|
||||
palaces: PalaceResult[]
|
||||
relations: PalaceRelation[]
|
||||
finalPalace: PalaceResult
|
||||
localInterpretation: LocalInterpretation
|
||||
}
|
||||
|
||||
interface DerivedContext {
|
||||
mode: DivinationMode
|
||||
modeLabel: string
|
||||
solarLabel: string
|
||||
lunarLabel: string
|
||||
methodNote: string
|
||||
branches: Record<BranchSlot, BranchInfo>
|
||||
}
|
||||
|
||||
export const branchOptions = [...BRANCH_ORDER]
|
||||
export const compassNumberOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9] as const
|
||||
export const defaultCompassNumbers: CompassNumbers = {
|
||||
first: 3,
|
||||
second: 6,
|
||||
third: 9,
|
||||
}
|
||||
|
||||
export function buildDivination(input: {
|
||||
datetime: string
|
||||
question: string
|
||||
mode?: DivinationMode
|
||||
overrides?: ManualBranchOverrides
|
||||
compassNumbers?: CompassNumbers
|
||||
}): DivinationResult {
|
||||
const trimmedQuestion = input.question.trim() || '这件事现在推进,结果会怎样?'
|
||||
const context = deriveContext({
|
||||
mode: input.mode ?? 'time',
|
||||
datetime: input.datetime,
|
||||
overrides: input.overrides,
|
||||
compassNumbers: input.compassNumbers,
|
||||
})
|
||||
const palaces = buildPalaces(context.branches)
|
||||
const relations = buildRelations(palaces)
|
||||
const finalPalace = palaces[palaces.length - 1]
|
||||
const localInterpretation = buildLocalInterpretation(trimmedQuestion, palaces, relations)
|
||||
|
||||
return {
|
||||
mode: context.mode,
|
||||
modeLabel: context.modeLabel,
|
||||
question: trimmedQuestion,
|
||||
datetime: input.datetime,
|
||||
solarLabel: context.solarLabel,
|
||||
lunarLabel: context.lunarLabel,
|
||||
methodNote: context.methodNote,
|
||||
branches: context.branches,
|
||||
palaces,
|
||||
relations,
|
||||
finalPalace,
|
||||
localInterpretation,
|
||||
}
|
||||
}
|
||||
|
||||
export function deriveBranchDefaults(datetime: string): Record<BranchSlot, BranchName> {
|
||||
const branches = deriveTimeContext(datetime).branches
|
||||
return {
|
||||
year: branches.year.branch as BranchName,
|
||||
month: branches.month.branch as BranchName,
|
||||
day: branches.day.branch as BranchName,
|
||||
time: branches.time.branch as BranchName,
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultDatetimeValue(date = new Date()): string {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hour = String(date.getHours()).padStart(2, '0')
|
||||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}T${hour}:${minute}`
|
||||
}
|
||||
|
||||
function deriveContext(input: {
|
||||
mode: DivinationMode
|
||||
datetime: string
|
||||
overrides?: ManualBranchOverrides
|
||||
compassNumbers?: CompassNumbers
|
||||
}): DerivedContext {
|
||||
if (input.mode === 'compass') {
|
||||
return deriveCompassContext(input.datetime, input.compassNumbers)
|
||||
}
|
||||
|
||||
return deriveTimeContext(input.datetime, input.overrides)
|
||||
}
|
||||
|
||||
function deriveTimeContext(datetime: string, overrides?: ManualBranchOverrides): DerivedContext {
|
||||
const date = parseLocalDatetime(datetime)
|
||||
const solar = Solar.fromDate(date)
|
||||
const lunar = solar.getLunar()
|
||||
const lunarMonth = Math.abs(lunar.getMonth())
|
||||
const yearGanZhi = lunar.getYearInGanZhi()
|
||||
const monthGanZhi = lunar.getMonthInGanZhi()
|
||||
const dayGanZhi = lunar.getDayInGanZhi()
|
||||
const timeGanZhi = lunar.getTimeInGanZhi()
|
||||
const autoBranches: Record<BranchSlot, BranchName> = {
|
||||
year: lunar.getYearZhi() as BranchName,
|
||||
month: LUNAR_MONTH_BRANCHES[(Math.max(1, Math.min(12, lunarMonth)) - 1) as number],
|
||||
day: lunar.getDayZhi() as BranchName,
|
||||
time: getTimeBranch(date.getHours()),
|
||||
}
|
||||
const resolvedBranches: Record<BranchSlot, BranchName> = {
|
||||
year: overrides?.year ?? autoBranches.year,
|
||||
month: overrides?.month ?? autoBranches.month,
|
||||
day: overrides?.day ?? autoBranches.day,
|
||||
time: overrides?.time ?? autoBranches.time,
|
||||
}
|
||||
|
||||
return {
|
||||
mode: 'time',
|
||||
modeLabel: '时刻起课',
|
||||
solarLabel: formatSolarLabel(date),
|
||||
lunarLabel: `农历${lunar.getYearInChinese()}年${lunar.getMonthInChinese()}月${lunar.getDayInChinese()}`,
|
||||
methodNote:
|
||||
'默认按农历年支、农历月建、万年历日支、时辰地支起四宫;若涉及晚子时、闰月或流派差异,可手动覆写。',
|
||||
branches: {
|
||||
year: {
|
||||
slot: 'year',
|
||||
label: SLOT_LABELS.year,
|
||||
branch: resolvedBranches.year,
|
||||
value: BRANCH_VALUES[resolvedBranches.year],
|
||||
source: SLOT_SOURCES.year,
|
||||
sourceNote: `自动换算为 ${yearGanZhi},本法取年支 ${resolvedBranches.year}。`,
|
||||
ganZhi: yearGanZhi,
|
||||
},
|
||||
month: {
|
||||
slot: 'month',
|
||||
label: SLOT_LABELS.month,
|
||||
branch: resolvedBranches.month,
|
||||
value: BRANCH_VALUES[resolvedBranches.month],
|
||||
source: SLOT_SOURCES.month,
|
||||
sourceNote: `当前为农历${lunar.getMonthInChinese()}月,月建取支 ${resolvedBranches.month};干支参考 ${monthGanZhi}。`,
|
||||
ganZhi: monthGanZhi,
|
||||
},
|
||||
day: {
|
||||
slot: 'day',
|
||||
label: SLOT_LABELS.day,
|
||||
branch: resolvedBranches.day,
|
||||
value: BRANCH_VALUES[resolvedBranches.day],
|
||||
source: SLOT_SOURCES.day,
|
||||
sourceNote: `万年历日柱参考 ${dayGanZhi},本法取日支 ${resolvedBranches.day}。`,
|
||||
ganZhi: dayGanZhi,
|
||||
},
|
||||
time: {
|
||||
slot: 'time',
|
||||
label: SLOT_LABELS.time,
|
||||
branch: resolvedBranches.time,
|
||||
value: BRANCH_VALUES[resolvedBranches.time],
|
||||
source: SLOT_SOURCES.time,
|
||||
sourceNote: `当前时柱参考 ${timeGanZhi},本法取时支 ${resolvedBranches.time}。`,
|
||||
ganZhi: timeGanZhi,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function deriveCompassContext(datetime: string, numbers?: CompassNumbers): DerivedContext {
|
||||
const date = parseLocalDatetime(datetime)
|
||||
const solar = Solar.fromDate(date)
|
||||
const lunar = solar.getLunar()
|
||||
const resolvedNumbers = normalizeCompassNumbers(numbers)
|
||||
const total = resolvedNumbers.first + resolvedNumbers.second + resolvedNumbers.third
|
||||
|
||||
return {
|
||||
mode: 'compass',
|
||||
modeLabel: '罗盘三数起课',
|
||||
solarLabel: formatSolarLabel(date),
|
||||
lunarLabel: `农历${lunar.getYearInChinese()}年${lunar.getMonthInChinese()}月${lunar.getDayInChinese()}`,
|
||||
methodNote:
|
||||
'按罗盘三数法起课:第一数定外势,第二数看过程,第三数落执行,三数合参定终局。三数都取 1 到 9,结果宫按合参之数顺推。',
|
||||
branches: {
|
||||
year: {
|
||||
slot: 'year',
|
||||
label: SLOT_LABELS.year,
|
||||
branch: `数${resolvedNumbers.first}`,
|
||||
value: resolvedNumbers.first,
|
||||
source: '罗盘第一数',
|
||||
sourceNote: `第一数取 ${resolvedNumbers.first},从大安起数,先定年宫。`,
|
||||
ganZhi: '-',
|
||||
},
|
||||
month: {
|
||||
slot: 'month',
|
||||
label: SLOT_LABELS.month,
|
||||
branch: `数${resolvedNumbers.second}`,
|
||||
value: resolvedNumbers.second,
|
||||
source: '罗盘第二数',
|
||||
sourceNote: `第二数取 ${resolvedNumbers.second},从年宫顺推,落月宫。`,
|
||||
ganZhi: '-',
|
||||
},
|
||||
day: {
|
||||
slot: 'day',
|
||||
label: SLOT_LABELS.day,
|
||||
branch: `数${resolvedNumbers.third}`,
|
||||
value: resolvedNumbers.third,
|
||||
source: '罗盘第三数',
|
||||
sourceNote: `第三数取 ${resolvedNumbers.third},从月宫顺推,落日宫。`,
|
||||
ganZhi: '-',
|
||||
},
|
||||
time: {
|
||||
slot: 'time',
|
||||
label: SLOT_LABELS.time,
|
||||
branch: `合${total}`,
|
||||
value: total,
|
||||
source: '三数合参',
|
||||
sourceNote: `三数相加为 ${total},从日宫顺推,定时宫终局。`,
|
||||
ganZhi: '-',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildPalaces(branches: Record<BranchSlot, BranchInfo>): PalaceResult[] {
|
||||
let currentIndex = 0
|
||||
|
||||
return SLOT_ORDER.map((slot) => {
|
||||
const branch = branches[slot]
|
||||
currentIndex = walkPalace(currentIndex, branch.value)
|
||||
const palace = PALACE_ORDER[currentIndex]
|
||||
const meta = PALACE_META[palace]
|
||||
|
||||
return {
|
||||
slot,
|
||||
label: SLOT_LABELS[slot],
|
||||
branch: branch.branch,
|
||||
branchValue: branch.value,
|
||||
palace,
|
||||
element: meta.element,
|
||||
quality: meta.quality,
|
||||
theme: meta.theme,
|
||||
detail: meta.detail,
|
||||
slotExplanation: SLOT_EXPLANATIONS[slot],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function buildRelations(palaces: PalaceResult[]): PalaceRelation[] {
|
||||
const relations: PalaceRelation[] = []
|
||||
|
||||
for (let index = 0; index < palaces.length - 1; index += 1) {
|
||||
const current = palaces[index]
|
||||
const next = palaces[index + 1]
|
||||
const relation = classifyRelation(current.element, next.element)
|
||||
|
||||
relations.push({
|
||||
from: current.slot,
|
||||
to: next.slot,
|
||||
kind: relation.kind,
|
||||
score: relation.score,
|
||||
summary: `${current.label}${current.palace}(${current.element})${relation.text}${next.label}${next.palace}(${next.element})`,
|
||||
})
|
||||
}
|
||||
|
||||
return relations
|
||||
}
|
||||
|
||||
function buildLocalInterpretation(
|
||||
question: string,
|
||||
palaces: PalaceResult[],
|
||||
relations: PalaceRelation[],
|
||||
): LocalInterpretation {
|
||||
const domainLabel = detectDomain(question)
|
||||
const palaceScore = palaces.reduce((sum, item, index) => {
|
||||
const weight = PALACE_META[item.palace].weight
|
||||
return sum + (index === palaces.length - 1 ? weight * 2 : weight)
|
||||
}, 0)
|
||||
const relationScore = relations.reduce((sum, relation) => sum + relation.score, 0)
|
||||
const score = palaceScore + relationScore
|
||||
const finalPalace = palaces[palaces.length - 1]
|
||||
const verdict = resolveVerdict(score, finalPalace.palace)
|
||||
const chainSummary = relations.map((item) => item.summary).join(';')
|
||||
|
||||
const summary = [
|
||||
`终宫落在${finalPalace.palace},主调是“${PALACE_META[finalPalace.palace].theme}”`,
|
||||
`${finalPalace.label}主结果,说明这件事的落点偏向${describeOutcome(finalPalace.palace)}。`,
|
||||
chainSummary ? `四宫链路里,${chainSummary}。` : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('')
|
||||
|
||||
return {
|
||||
verdict,
|
||||
score,
|
||||
summary,
|
||||
domainLabel,
|
||||
chainSummary,
|
||||
actionAdvice: buildActionAdvice(domainLabel, finalPalace, relations),
|
||||
riskAdvice: buildRiskAdvice(finalPalace, relations),
|
||||
}
|
||||
}
|
||||
|
||||
function buildActionAdvice(
|
||||
domainLabel: string,
|
||||
finalPalace: PalaceResult,
|
||||
relations: PalaceRelation[],
|
||||
): string[] {
|
||||
const advice = [
|
||||
'先把所问之事收束成一个判断点,例如现在推进、暂缓观望,还是先补条件。',
|
||||
'先看年宫与月宫给不给势,再决定当下动作,不要只盯终宫一句吉凶。',
|
||||
]
|
||||
|
||||
if (finalPalace.palace === '速喜') {
|
||||
advice.unshift('眼下宜先动一步,把时间、对象和诉求一次定清,不要把顺势拖成失势。')
|
||||
} else if (finalPalace.palace === '大安') {
|
||||
advice.unshift('此局宜走正路,把条件、边界和预期回报摆清,再稳稳推进。')
|
||||
} else if (finalPalace.palace === '小吉') {
|
||||
advice.unshift('此时宜先借力,让熟人、旧成果或中间人替你开路,不必硬闯。')
|
||||
} else if (finalPalace.palace === '留连') {
|
||||
advice.unshift('此局贵在补缺,把顾虑逐条拆开,比一味催结果更有效。')
|
||||
} else if (finalPalace.palace === '赤口') {
|
||||
advice.unshift('此时先收锋芒再出手,话要留余地,事要留凭据。')
|
||||
} else {
|
||||
advice.unshift('此时先验信息真假与承诺落点,未坐实之前,不宜下重注。')
|
||||
}
|
||||
|
||||
if (domainLabel === '事业/合作') {
|
||||
advice.push('涉及事业或合作时,诉求要落成岗位、预算、周期或资源清单,别只谈感受。')
|
||||
} else if (domainLabel === '财务/生意') {
|
||||
advice.push('涉及财务或生意时,所有判断都要落到现金流、付款条件和违约边界。')
|
||||
} else if (domainLabel === '感情/关系') {
|
||||
advice.push('涉及关系时,先辨对方真实态度,再决定进退,不要拿想象替代反馈。')
|
||||
} else if (domainLabel === '考试/申请') {
|
||||
advice.push('涉及考试或申请时,先守时间规划与材料完整,再谈额外发挥。')
|
||||
}
|
||||
|
||||
if (relations.some((item) => item.kind === '前生后')) {
|
||||
advice.push('四宫若见顺生,说明次第不可乱,按“先看环境,再落动作,后取结果”会更顺。')
|
||||
}
|
||||
|
||||
return advice.slice(0, 4)
|
||||
}
|
||||
|
||||
function buildRiskAdvice(finalPalace: PalaceResult, relations: PalaceRelation[]): string[] {
|
||||
const advice: string[] = []
|
||||
|
||||
if (relations.some((item) => item.kind === '前克后' || item.kind === '后克前')) {
|
||||
advice.push('四宫存在克制,推进中容易出现抵触、卡口或资源冲突。')
|
||||
}
|
||||
|
||||
if (finalPalace.palace === '赤口') {
|
||||
advice.push('结果宫带赤口,最大的风险不是没有机会,而是把机会说坏。')
|
||||
}
|
||||
|
||||
if (finalPalace.palace === '空亡') {
|
||||
advice.push('结果宫带空亡,最大的风险是信息不实、承诺虚高或条件临时失效。')
|
||||
}
|
||||
|
||||
if (finalPalace.palace === '留连') {
|
||||
advice.push('结果宫带留连,最大的风险是一直等、一直猜,迟迟不做下一步动作。')
|
||||
}
|
||||
|
||||
if (advice.length === 0) {
|
||||
advice.push('整体链路不算凶,但仍要把时间点、承诺边界和证据留存好。')
|
||||
}
|
||||
|
||||
return advice.slice(0, 3)
|
||||
}
|
||||
|
||||
function walkPalace(startIndex: number, steps: number): number {
|
||||
return (startIndex + steps - 1) % PALACE_ORDER.length
|
||||
}
|
||||
|
||||
function classifyRelation(from: ElementName, to: ElementName): {
|
||||
kind: PalaceRelation['kind']
|
||||
score: number
|
||||
text: string
|
||||
} {
|
||||
if (from === to) {
|
||||
return { kind: '比和', score: 0, text: '与' }
|
||||
}
|
||||
|
||||
if (ELEMENT_GENERATES[from] === to) {
|
||||
return { kind: '前生后', score: 1, text: '生' }
|
||||
}
|
||||
|
||||
if (ELEMENT_CONTROLS[from] === to) {
|
||||
return { kind: '前克后', score: -1, text: '克' }
|
||||
}
|
||||
|
||||
if (ELEMENT_GENERATES[to] === from) {
|
||||
return { kind: '后生前', score: 0, text: '受' }
|
||||
}
|
||||
|
||||
return { kind: '后克前', score: -1, text: '受' }
|
||||
}
|
||||
|
||||
function resolveVerdict(score: number, finalPalace: PalaceName): string {
|
||||
if (score >= 7) {
|
||||
return '上吉,可主动推进。'
|
||||
}
|
||||
|
||||
if (score >= 3) {
|
||||
return '偏吉,成事面大于阻力。'
|
||||
}
|
||||
|
||||
if (score >= 0) {
|
||||
return '中平,能不能成取决于执行方式。'
|
||||
}
|
||||
|
||||
if (finalPalace === '空亡' || finalPalace === '赤口') {
|
||||
return '偏凶,先控风险再行动。'
|
||||
}
|
||||
|
||||
return '有阻,宜放慢节奏并补条件。'
|
||||
}
|
||||
|
||||
function describeOutcome(palace: PalaceName): string {
|
||||
switch (palace) {
|
||||
case '大安':
|
||||
return '稳中见成,适合按规矩落地'
|
||||
case '留连':
|
||||
return '拖中见变,需要耐心和补件'
|
||||
case '速喜':
|
||||
return '快中见成,重在主动出击'
|
||||
case '赤口':
|
||||
return '先有口舌,再看能否化解'
|
||||
case '小吉':
|
||||
return '缓中有利,重在人和与借力'
|
||||
case '空亡':
|
||||
return '容易落空,必须先验真伪'
|
||||
}
|
||||
}
|
||||
|
||||
function detectDomain(question: string): string {
|
||||
for (const [key, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
|
||||
if (keywords.some((word) => question.includes(word))) {
|
||||
switch (key) {
|
||||
case 'career':
|
||||
return '事业/合作'
|
||||
case 'wealth':
|
||||
return '财务/生意'
|
||||
case 'relation':
|
||||
return '感情/关系'
|
||||
case 'study':
|
||||
return '考试/申请'
|
||||
default:
|
||||
return '综合事项'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '综合事项'
|
||||
}
|
||||
|
||||
function normalizeCompassNumbers(numbers?: CompassNumbers): CompassNumbers {
|
||||
const fallback = defaultCompassNumbers
|
||||
const values = {
|
||||
first: numbers?.first ?? fallback.first,
|
||||
second: numbers?.second ?? fallback.second,
|
||||
third: numbers?.third ?? fallback.third,
|
||||
}
|
||||
|
||||
for (const key of COMPASS_SLOT_VALUES) {
|
||||
values[key] = clampCompassNumber(values[key])
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
function clampCompassNumber(value: number): number {
|
||||
if (!Number.isFinite(value)) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return Math.max(1, Math.min(9, Math.round(value)))
|
||||
}
|
||||
|
||||
function parseLocalDatetime(value: string): Date {
|
||||
const [datePart, timePart = '00:00'] = value.split('T')
|
||||
const [year, month, day] = datePart.split('-').map((item) => Number(item))
|
||||
const [hour, minute] = timePart.split(':').map((item) => Number(item))
|
||||
|
||||
if (![year, month, day, hour, minute].every(Number.isFinite)) {
|
||||
return new Date()
|
||||
}
|
||||
|
||||
return new Date(year, month - 1, day, hour, minute, 0, 0)
|
||||
}
|
||||
|
||||
function formatSolarLabel(date: Date): string {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hour = String(date.getHours()).padStart(2, '0')
|
||||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}年${month}月${day}日 ${hour}:${minute}`
|
||||
}
|
||||
|
||||
function getTimeBranch(hour: number): BranchName {
|
||||
if (hour >= 23 || hour < 1) {
|
||||
return '子'
|
||||
}
|
||||
if (hour < 3) {
|
||||
return '丑'
|
||||
}
|
||||
if (hour < 5) {
|
||||
return '寅'
|
||||
}
|
||||
if (hour < 7) {
|
||||
return '卯'
|
||||
}
|
||||
if (hour < 9) {
|
||||
return '辰'
|
||||
}
|
||||
if (hour < 11) {
|
||||
return '巳'
|
||||
}
|
||||
if (hour < 13) {
|
||||
return '午'
|
||||
}
|
||||
if (hour < 15) {
|
||||
return '未'
|
||||
}
|
||||
if (hour < 17) {
|
||||
return '申'
|
||||
}
|
||||
if (hour < 19) {
|
||||
return '酉'
|
||||
}
|
||||
if (hour < 21) {
|
||||
return '戌'
|
||||
}
|
||||
return '亥'
|
||||
}
|
||||
Reference in New Issue
Block a user