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 | undefined { if (facts.length === 0) { return undefined; } return facts.reduce((result, fact) => { result[fact.mutagen] = fact.toPalaceName; return result; }, {} as Record); } 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 = { 禄: -5, 权: -5, 科: 5, 忌: -10 }; const descMap: Record = { 禄: '自化禄,主该宫位的人事物看似美好,但容易流失不长久。', 权: '自化权,主该宫位易起争执,多变动与摩擦。', 科: '自化科,主表面斯文,逢凶化吉。', 忌: '自化忌,主该宫位的人事物易流失或不长久,有消耗之象。', }; 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(({ 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(); 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(({ 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]; } }