Spaces:
Sleeping
Sleeping
| import { DynamicCore, FlyingFact } from './dynamic-core'; | |
| import { AstrolabeData, DynamicChain, DynamicReport, HoroscopeScopeData, MutagenKey, PalaceData, RuleEvidence, TimeLayerData, TimeLayerScope } from './types'; | |
| export class DynamicAnalyzer { | |
| private timeLayerData: TimeLayerData; | |
| private astrolabe: AstrolabeData; | |
| private core: DynamicCore; | |
| private engineType: 'qintian' | 'zhuang'; | |
| private version = 'dynamic-v2.1.0'; | |
| constructor(timeLayerData: TimeLayerData, engineType: 'qintian' | 'zhuang' = 'qintian') { | |
| this.timeLayerData = timeLayerData; | |
| this.astrolabe = timeLayerData.original; | |
| this.core = new DynamicCore(timeLayerData); | |
| this.engineType = engineType; | |
| } | |
| public analyze(): DynamicReport { | |
| const chains = this.getDynamicChains(); | |
| return { | |
| laiYinGong: this.getLaiYinGong(), | |
| timeContext: this.getTimeContext(), | |
| chains, | |
| timingHighlights: this.getTimingHighlights(chains), | |
| sihua: { | |
| shengNian: this.getSihuaMapFromFacts(this.core.getScopeMutagenFacts('original')) || {}, | |
| daXian: this.getSihuaMapFromFacts(this.core.getScopeMutagenFacts('daXian')), | |
| liuNian: this.getSihuaMapFromFacts(this.core.getScopeMutagenFacts('liuNian')), | |
| liuYue: this.getSihuaMapFromFacts(this.core.getScopeMutagenFacts('liuYue')), | |
| liuRi: this.getSihuaMapFromFacts(this.core.getScopeMutagenFacts('liuRi')), | |
| liuShi: this.getSihuaMapFromFacts(this.core.getScopeMutagenFacts('liuShi')), | |
| }, | |
| ziHua: this.getZiHua(), | |
| interactions: [ | |
| ...this.getTimeLayerInteractions(), | |
| ...this.getMutagenChainInteractions(chains), | |
| ...this.getCrossScopeClashes(), | |
| ...this.getJiChongInteractions(), | |
| ], | |
| }; | |
| } | |
| private getLaiYinGong(): string { | |
| const shengNianGan = this.astrolabe.chineseDate.split(' ')[0][0]; | |
| const laiYinPalace = this.astrolabe.palaces.find((palace) => palace.heavenlyStem === shengNianGan); | |
| return laiYinPalace ? this.cleanPalaceName(laiYinPalace.name) : '未知'; | |
| } | |
| private getTimeContext(): DynamicReport['timeContext'] { | |
| return { | |
| targetDate: this.timeLayerData.horoscopeMeta?.targetDate, | |
| daXian: this.timeLayerData.daXian | |
| ? { | |
| name: this.timeLayerData.daXian.name, | |
| heavenlyStem: this.timeLayerData.daXian.heavenlyStem, | |
| earthlyBranch: this.timeLayerData.daXian.earthlyBranch, | |
| targetPalaceName: this.timeLayerData.daXian.targetPalaceName, | |
| } | |
| : undefined, | |
| liuNian: this.timeLayerData.liuNian | |
| ? { | |
| name: this.timeLayerData.liuNian.name, | |
| heavenlyStem: this.timeLayerData.liuNian.heavenlyStem, | |
| earthlyBranch: this.timeLayerData.liuNian.earthlyBranch, | |
| targetPalaceName: this.timeLayerData.liuNian.targetPalaceName, | |
| } | |
| : undefined, | |
| liuYue: this.timeLayerData.liuYue | |
| ? { | |
| name: this.timeLayerData.liuYue.name, | |
| heavenlyStem: this.timeLayerData.liuYue.heavenlyStem, | |
| earthlyBranch: this.timeLayerData.liuYue.earthlyBranch, | |
| targetPalaceName: this.timeLayerData.liuYue.targetPalaceName, | |
| } | |
| : undefined, | |
| liuRi: this.timeLayerData.liuRi | |
| ? { | |
| name: this.timeLayerData.liuRi.name, | |
| heavenlyStem: this.timeLayerData.liuRi.heavenlyStem, | |
| earthlyBranch: this.timeLayerData.liuRi.earthlyBranch, | |
| targetPalaceName: this.timeLayerData.liuRi.targetPalaceName, | |
| } | |
| : undefined, | |
| liuShi: this.timeLayerData.liuShi | |
| ? { | |
| name: this.timeLayerData.liuShi.name, | |
| heavenlyStem: this.timeLayerData.liuShi.heavenlyStem, | |
| earthlyBranch: this.timeLayerData.liuShi.earthlyBranch, | |
| targetPalaceName: this.timeLayerData.liuShi.targetPalaceName, | |
| } | |
| : undefined, | |
| }; | |
| } | |
| private getSihuaMapFromFacts(facts: FlyingFact[]): Record<string, string> | undefined { | |
| if (facts.length === 0) { | |
| return undefined; | |
| } | |
| return facts.reduce((result, fact) => { | |
| result[fact.mutagen] = fact.toPalaceName; | |
| return result; | |
| }, {} as Record<string, string>); | |
| } | |
| private getTimeLayerInteractions(): RuleEvidence[] { | |
| const evidences: RuleEvidence[] = []; | |
| if (this.timeLayerData.daXian) { | |
| evidences.push(this.buildTimeLayerEvidence(this.timeLayerData.daXian, '大限')); | |
| } | |
| if (this.timeLayerData.liuNian) { | |
| evidences.push(this.buildTimeLayerEvidence(this.timeLayerData.liuNian, '流年')); | |
| } | |
| if (this.timeLayerData.liuYue) { | |
| evidences.push(this.buildTimeLayerEvidence(this.timeLayerData.liuYue, '流月')); | |
| } | |
| if (this.timeLayerData.liuRi) { | |
| evidences.push(this.buildTimeLayerEvidence(this.timeLayerData.liuRi, '流日')); | |
| } | |
| if (this.timeLayerData.liuShi) { | |
| evidences.push(this.buildTimeLayerEvidence(this.timeLayerData.liuShi, '流时')); | |
| } | |
| return evidences; | |
| } | |
| private buildTimeLayerEvidence(scopeData: HoroscopeScopeData, scopeLabel: string): RuleEvidence { | |
| const mutagenSummary = Object.entries(scopeData.mutagenPalaces) | |
| .map(([mutagen, palaceName]) => `${mutagen}入${palaceName}`) | |
| .join('、'); | |
| return { | |
| ruleId: `ZW_DYNAMIC_${scopeData.scope.toUpperCase()}_POSITION_v1`, | |
| title: `当前${scopeLabel}【${scopeData.name}】落本命${scopeData.targetPalaceName}`, | |
| type: 'dynamic', | |
| description: `${scopeLabel}命宫当前落在本命${scopeData.targetPalaceName},可作为后续应期与飞化链推演的时间层锚点。`, | |
| boardEvidence: [ | |
| `${scopeLabel}天干地支为${scopeData.heavenlyStem}${scopeData.earthlyBranch}`, | |
| `${scopeLabel}索引为${scopeData.index},映射本命宫位为${scopeData.targetPalaceName}`, | |
| mutagenSummary ? `${scopeLabel}四化分布:${mutagenSummary}` : `${scopeLabel}四化分布:未取到有效落宫`, | |
| ], | |
| scoreImpact: 0, | |
| tags: ['动态', '时间层'], | |
| version: this.version, | |
| }; | |
| } | |
| private getZiHua(): RuleEvidence[] { | |
| return this.core.getZiHuaFacts().map((fact) => { | |
| const scoreMap: Record<MutagenKey, number> = { 禄: -5, 权: -5, 科: 5, 忌: -10 }; | |
| const descMap: Record<MutagenKey, string> = { | |
| 禄: '自化禄,主该宫位的人事物看似美好,但容易流失不长久。', | |
| 权: '自化权,主该宫位易起争执,多变动与摩擦。', | |
| 科: '自化科,主表面斯文,逢凶化吉。', | |
| 忌: '自化忌,主该宫位的人事物易流失或不长久,有消耗之象。', | |
| }; | |
| return { | |
| ruleId: `ZW_DYNAMIC_ZIHUA_${fact.mutagen}_v2`, | |
| title: `【${fact.fromPalaceName}】自化${fact.mutagen}`, | |
| type: 'dynamic', | |
| description: descMap[fact.mutagen], | |
| boardEvidence: [`${fact.fromPalaceName}宫干为${fact.sourceStem},使同宫内的${fact.star}化${fact.mutagen}`], | |
| scoreImpact: scoreMap[fact.mutagen], | |
| tags: ['动态', '自化'], | |
| version: this.version, | |
| }; | |
| }); | |
| } | |
| private getDynamicChains(): DynamicChain[] { | |
| const chainConfigs: Array<{ scope: TimeLayerScope; label: string }> = [ | |
| { scope: 'original', label: '本命' }, | |
| { scope: 'daXian', label: '大限' }, | |
| { scope: 'liuNian', label: '流年' }, | |
| { scope: 'liuYue', label: '流月' }, | |
| { scope: 'liuRi', label: '流日' }, | |
| { scope: 'liuShi', label: '流时' }, | |
| ]; | |
| const chains = chainConfigs | |
| .map<DynamicChain | undefined>(({ scope, label }) => { | |
| const steps = this.core.traceMutagenChain(scope, '忌', 3); | |
| if (steps.length === 0) { | |
| return undefined; | |
| } | |
| const impactPalaceName = steps[steps.length - 1].toPalaceName; | |
| const route = steps.map((step) => `${step.fromPalaceName}→${step.toPalaceName}`).join(' → '); | |
| return { | |
| scope, | |
| label, | |
| mutagen: '忌' as MutagenKey, | |
| impactPalaceName, | |
| summary: `${label}化忌链由${route},最终落点在【${impactPalaceName}】。`, | |
| steps, | |
| }; | |
| }); | |
| return chains.filter((chain): chain is DynamicChain => !!chain); | |
| } | |
| private getMutagenChainInteractions(chains: DynamicChain[]): RuleEvidence[] { | |
| return chains.map((chain) => { | |
| const impactIsSelfPalace = this.isWoGong(chain.impactPalaceName); | |
| return { | |
| ruleId: `ZW_DYNAMIC_${chain.scope.toUpperCase()}_TRANSFER_JI_v1`, | |
| title: `${chain.label}转忌链落【${chain.impactPalaceName}】`, | |
| type: 'dynamic', | |
| description: `${chain.summary}${impactIsSelfPalace ? ' 该落点属于我宫,应事更直接。' : ' 该落点偏他宫,多体现为外部人事与环境牵动。'}`, | |
| boardEvidence: chain.steps.map((step, index) => `第${index + 1}跳:${step.fromPalaceName}宫干${step.sourceStem}使${step.star}化${step.mutagen}入${step.toPalaceName}`), | |
| dynamicEvidence: [`应期层级:${chain.label}`, `最终落点:${chain.impactPalaceName}`], | |
| scoreImpact: impactIsSelfPalace ? -18 : -8, | |
| tags: ['动态', '风险', '转忌链', '时间层'], | |
| version: this.version, | |
| }; | |
| }); | |
| } | |
| private getCrossScopeClashes(): RuleEvidence[] { | |
| const scopes: Array<{ scope: TimeLayerScope; label: string }> = [ | |
| { scope: 'original', label: '本命' }, | |
| { scope: 'daXian', label: '大限' }, | |
| { scope: 'liuNian', label: '流年' }, | |
| { scope: 'liuYue', label: '流月' }, | |
| { scope: 'liuRi', label: '流日' }, | |
| { scope: 'liuShi', label: '流时' }, | |
| ]; | |
| const scopeFacts = scopes.map((item) => ({ ...item, facts: this.core.getScopeMutagenFacts(item.scope) })).filter((item) => item.facts.length > 0); | |
| const evidences: RuleEvidence[] = []; | |
| const seen = new Set<string>(); | |
| for (let i = 0; i < scopeFacts.length; i += 1) { | |
| for (let j = i + 1; j < scopeFacts.length; j += 1) { | |
| const left = scopeFacts[i]; | |
| const right = scopeFacts[j]; | |
| const rightJi = right.facts.find((fact) => fact.mutagen === '忌'); | |
| const leftJi = left.facts.find((fact) => fact.mutagen === '忌'); | |
| left.facts | |
| .filter((fact) => fact.mutagen !== '忌' && rightJi && fact.toPalaceName === rightJi.toPalaceName) | |
| .forEach((fact) => { | |
| const key = `${left.scope}-${fact.mutagen}-${right.scope}-忌-${fact.toPalaceName}`; | |
| if (seen.has(key) || !rightJi) { | |
| return; | |
| } | |
| seen.add(key); | |
| evidences.push(this.buildClashEvidence(left.label, fact, right.label, rightJi)); | |
| }); | |
| right.facts | |
| .filter((fact) => fact.mutagen !== '忌' && leftJi && fact.toPalaceName === leftJi.toPalaceName) | |
| .forEach((fact) => { | |
| const key = `${right.scope}-${fact.mutagen}-${left.scope}-忌-${fact.toPalaceName}`; | |
| if (seen.has(key) || !leftJi) { | |
| return; | |
| } | |
| seen.add(key); | |
| evidences.push(this.buildClashEvidence(right.label, fact, left.label, leftJi)); | |
| }); | |
| } | |
| } | |
| return evidences; | |
| } | |
| private buildClashEvidence(leftLabel: string, leftFact: FlyingFact, rightLabel: string, rightJi: FlyingFact): RuleEvidence { | |
| const isHeavyImpact = this.isWoGong(leftFact.toPalaceName); | |
| return { | |
| ruleId: `ZW_DYNAMIC_CLASH_${leftLabel}_${rightLabel}_${leftFact.toPalaceName}_v1`, | |
| title: `${leftLabel}${leftFact.mutagen}与${rightLabel}忌在【${leftFact.toPalaceName}】交战`, | |
| type: 'dynamic', | |
| description: `${leftLabel}${leftFact.mutagen}与${rightLabel}忌同落【${leftFact.toPalaceName}】,形成跨层交战链,主吉象受阻、推进反复。`, | |
| boardEvidence: [ | |
| `${leftLabel}${leftFact.mutagen}由${leftFact.fromPalaceName}飞入${leftFact.toPalaceName}`, | |
| `${rightLabel}忌由${rightJi.fromPalaceName}飞入${rightJi.toPalaceName}`, | |
| ], | |
| conflicts: [`交战宫位:${leftFact.toPalaceName}`, isHeavyImpact ? '该宫为我宫,冲击更直接。' : '该宫偏他宫,更多体现为环境阻滞。'], | |
| scoreImpact: isHeavyImpact ? -22 : -12, | |
| tags: ['动态', '风险', '交战链'], | |
| version: this.version, | |
| }; | |
| } | |
| private getJiChongInteractions(): RuleEvidence[] { | |
| const scopes: Array<{ scope: TimeLayerScope; label: string }> = [ | |
| { scope: 'original', label: '本命' }, | |
| { scope: 'daXian', label: '大限' }, | |
| { scope: 'liuNian', label: '流年' }, | |
| { scope: 'liuYue', label: '流月' }, | |
| { scope: 'liuRi', label: '流日' }, | |
| { scope: 'liuShi', label: '流时' }, | |
| ]; | |
| const evidences = scopes.map<RuleEvidence | undefined>(({ scope, label }) => { | |
| const jiFact = this.core.getScopeMutagenFacts(scope).find((fact) => fact.mutagen === '忌'); | |
| if (!jiFact) { | |
| return undefined; | |
| } | |
| const jiPalace = this.core.findPalaceByName(jiFact.toPalaceName); | |
| if (!jiPalace) { | |
| return undefined; | |
| } | |
| const oppIndex = (jiPalace.index + 6) % 12; | |
| const oppPalace = this.astrolabe.palaces.find((palace) => palace.index === oppIndex); | |
| if (!oppPalace) { | |
| return undefined; | |
| } | |
| const cleanOppPalaceName = this.cleanPalaceName(oppPalace.name); | |
| const isWoGong = this.isWoGong(cleanOppPalaceName); | |
| let desc = `${label}忌在【${jiFact.toPalaceName}】,冲【${cleanOppPalaceName}】。`; | |
| if (this.engineType === 'qintian') { | |
| desc += isWoGong | |
| ? `[钦天门] 冲宫为我宫,破败力更强,应事会直接落在本人核心领域。` | |
| : `[钦天门] 冲宫为他宫,偏向外缘关系与环境消耗。`; | |
| } else { | |
| desc += `[庄氏] 需观察体用关系,当前表现为冲${isWoGong ? '我宫(偏重)' : '他宫(偏外应)'}。`; | |
| } | |
| return { | |
| ruleId: `ZW_DYNAMIC_${scope.toUpperCase()}_JICHONG_v2`, | |
| title: `${label}忌冲【${cleanOppPalaceName}】`, | |
| type: 'dynamic' as const, | |
| description: desc, | |
| boardEvidence: [`${label}忌由${jiFact.fromPalaceName}飞入${jiFact.toPalaceName}`, `对宫为${cleanOppPalaceName}`], | |
| dynamicEvidence: [`应期层级:${label}`], | |
| scoreImpact: isWoGong ? -20 : -10, | |
| tags: ['动态', '风险', '忌冲'], | |
| version: this.version, | |
| }; | |
| }); | |
| return evidences.filter((item): item is RuleEvidence => !!item); | |
| } | |
| private getTimingHighlights(chains: DynamicChain[]): string[] { | |
| const highlights: string[] = []; | |
| const liuShiChain = chains.find((chain) => chain.scope === 'liuShi'); | |
| const liuRiChain = chains.find((chain) => chain.scope === 'liuRi'); | |
| const liuYueChain = chains.find((chain) => chain.scope === 'liuYue'); | |
| const liuNianChain = chains.find((chain) => chain.scope === 'liuNian'); | |
| const daXianChain = chains.find((chain) => chain.scope === 'daXian'); | |
| const originalChain = chains.find((chain) => chain.scope === 'original'); | |
| if (this.timeLayerData.horoscopeMeta?.targetDate) { | |
| highlights.push(`解析日期锁定为 ${this.timeLayerData.horoscopeMeta.targetDate}`); | |
| } | |
| if (liuShiChain) { | |
| highlights.push(`流时应期优先看【${liuShiChain.impactPalaceName}】`); | |
| } | |
| if (liuRiChain) { | |
| highlights.push(`流日应期优先看【${liuRiChain.impactPalaceName}】`); | |
| } | |
| if (liuYueChain) { | |
| highlights.push(`流月应期优先看【${liuYueChain.impactPalaceName}】`); | |
| } | |
| if (liuNianChain) { | |
| highlights.push(`流年应期优先看【${liuNianChain.impactPalaceName}】`); | |
| } | |
| if (daXianChain) { | |
| highlights.push(`大限背景主轴落在【${daXianChain.impactPalaceName}】`); | |
| } | |
| if (originalChain) { | |
| highlights.push(`本命长期底层风险位在【${originalChain.impactPalaceName}】`); | |
| } | |
| return highlights; | |
| } | |
| private isWoGong(palaceName: string) { | |
| return ['命宫', '财帛', '官禄', '田宅', '疾厄', '福德'].includes(this.cleanPalaceName(palaceName)); | |
| } | |
| private cleanPalaceName(name: string) { | |
| return name.split('(')[0]; | |
| } | |
| } | |