diff --git a/calculator/src/evidence.ts b/calculator/src/evidence.ts new file mode 100644 index 0000000..74f5864 --- /dev/null +++ b/calculator/src/evidence.ts @@ -0,0 +1,765 @@ +/** + * 奇门遁甲证据链架构 + * 参考梅花易数 evidence 模式,为排盘结果增加结构化证据链 + * + * 核心原则: + * - 每个 fact 必须有具体的 limitation(不能是泛泛免责) + * - 证据链是增强层,不破坏现有排盘流程 + * - 支持/约束/反证三位一体 + */ + +import { + Stem, Branch, STEMS, BRANCHES, PALACES, Palace, + EIGHT_GATES, NINE_STARS, EIGHT_SPIRITS, + STEM_ELEMENT, BRANCH_ELEMENT, + WUXING_SHENG, WUXING_KE, getRelation, +} from './qimen-data.js'; +import type { QiMenResult } from './plate.js'; + +// ═══════════════════════════════════════════ +// 类型定义 +// ═══════════════════════════════════════════ + +/** QiMenFact 基础接口 — 所有 fact 的公共字段 */ +export interface QiMenFact { + key: string; + status: string; + promptText: string; + sources: string[]; + limitation: string; +} + +/** 起盘参数事实 */ +export interface CalculationFact extends QiMenFact { + type: 'calculation'; + yinYang: '阳' | '阴'; + juNumber: number; + dayStem: Stem; + dayBranch: Branch; + hourStem: Stem; + hourBranch: Branch; + month: number; + xunkong: Branch[]; + horse: Branch; +} + +/** 九宫逐宫事实 */ +export interface PalaceEvidence extends QiMenFact { + palaceNumber: number; + palaceName: string; + direction: string; + palaceElement: string; + earthStem: Stem; + earthElement: string; + heavenStem: Stem; + heavenElement: string; + gateName: string; + gateNature: string; + starName: string; + starNature: string; + spiritName: string; + spiritNature: string; + wuxingRelation: string; + support: string[]; + constraints: string[]; +} + +/** 用神焦点事实 */ +export interface YongShenFact extends QiMenFact { + questionType: string; + targetPalace: number; + palaceName: string; + reason: string; +} + +/** 格局事实 */ +export interface GeJuFact extends QiMenFact { + geJuType: string; + palaceNumber: number; + palaceName: string; + detail: string; + significance: string; +} + +/** 反证事实 */ +export interface CounterEvidenceFact extends QiMenFact { + type: string; + palaceNumber: number; + palaceName: string; + detail: string; + severity: '轻' | '中' | '重'; +} + +/** 应期事实 */ +export interface TimingFact extends QiMenFact { + type: '驿马' | '空亡填实' | '冲合' | '旺衰'; + detail: string; +} + +/** 完整的证据链分析结果 */ +export interface QiMenEvidenceAnalysis { + key: 'qimen:evidence'; + status: '已计算'; + // 起盘参数 + calculationFact: CalculationFact; + // 九宫逐宫 + palaceFacts: PalaceEvidence[]; + // 用神 + yongShenFact: YongShenFact | null; + // 格局 + geJuFacts: GeJuFact[]; + // 反证 + counterEvidenceFacts: CounterEvidenceFact[]; + counterSummary: { status: '有明确反证' | '未见明确反证'; detail: string }; + // 应期 + timingFacts: TimingFact[]; + // 汇总 + summary: { + factCount: number; + palaceCount: number; + geJuCount: number; + counterCount: number; + timingCount: number; + }; + // 全局限制 + limitations: string[]; + // 原始 prompt 文本 + promptText: string; +} + +// ═══════════════════════════════════════════ +// 限制声明常量 +// ═══════════════════════════════════════════ + +const CALCULATION_FACT_LIMITATION = + '起盘参数事实只记录四柱、阴阳遁、局数、旬空、驿马等排盘基础条件;不得由局数或阴阳遁直接推断吉凶、成败、概率或事件结果' as const; + +const PALACE_FACT_LIMITATION = + '九宫逐宫事实只记录地盘干、天盘干、八门、九星、八神及其五行关系;不得由单一宫位的门、星、神性质直接断言现实事件、人物意图、吉凶成败或固定应期' as const; + +const YONGSHEN_FACT_LIMITATION = + '用神焦点事实只根据问事类型选取关注宫位并说明选取依据;用神宫位的吉凶性质不等于事项的最终结果,仍须综合全局判断' as const; + +const GEJU_FACT_LIMITATION = + '格局事实只描述盘面中出现的特殊格局(三奇、六仪、庚格、空亡等);格局提示不等于必然发生,不得把格局名称直接等同于现实事件或结果' as const; + +const COUNTER_FACT_LIMITATION = + '反证事实只表示某一宫位存在凶门、凶神、空亡、克泄等限制;不得把单项反证直接写成现实失败、灾祸、伤病、损失或必然结果' as const; + +const COUNTER_SUMMARY_LIMITATION = + '反证汇总只说明当前盘面核验是否发现明确限制;未见明确反证不代表现实风险为零,也不得按反证数量换算吉凶总分或成功率' as const; + +const TIMING_FACT_LIMITATION = + '应期事实只提供驿马、空亡填实、冲合等相对触发条件;不得把地支、宫位数换算为绝对日期,也不证明事件必然发生' as const; + +const SUMMARY_FACT_LIMITATION = + '奇门证据汇总只统计起盘参数、九宫逐宫、格局、反证与应期事实的覆盖情况;不得按数量生成吉凶总分、成功率、人物意图或唯一日期' as const; + +// ═══════════════════════════════════════════ +// 辅助函数 +// ═══════════════════════════════════════════ + +function unique(values: string[]): string[] { + return Array.from(new Set(values.filter(Boolean))); +} + +/** 获取宫位五行 */ +function getPalaceElement(palaceNumber: number): string { + return PALACES[palaceNumber - 1]?.element ?? '未知'; +} + +/** 获取天干五行 */ +function stemElement(stem: Stem): string { + return STEM_ELEMENT[stem] ?? '未知'; +} + +/** 获取门的信息 */ +function getGateInfo(name: string): { element: string; nature: string; attribute: string } { + const gate = EIGHT_GATES.find(g => g.name === name); + return gate ?? { element: '未知', nature: '未知', attribute: '未知' }; +} + +/** 获取星的信息 */ +function getStarInfo(name: string): { element: string; nature: string; attribute: string } { + const star = NINE_STARS.find(s => s.name === name); + return star ?? { element: '未知', nature: '未知', attribute: '未知' }; +} + +/** 获取神的信息 */ +function getSpiritInfo(name: string): { nature: string; attribute: string } { + const spirit = EIGHT_SPIRITS.find(s => s.name === name); + return spirit ?? { nature: '未知', attribute: '未知' }; +} + +/** 判断天盘干与地盘干的五行关系 */ +function heavenEarthRelation(heaven: Stem, earth: Stem): string { + const hEl = stemElement(heaven); + const eEl = stemElement(earth); + return getRelation(hEl, eEl); +} + +/** 问事类型→用神宫位选取规则 */ +const QUESTION_TYPE_MAP: Record = { + '求财': { palace: 8, reason: '生门为财帛之门,取生门所在宫' }, + '事业': { palace: 6, reason: '开门为事业之门,取开门所在宫' }, + '疾病': { palace: 2, reason: '天芮主疾病,取天芮星所在宫' }, + '官司': { palace: 4, reason: '杜门主闭塞、诉讼,取杜门所在宫' }, + '出行': { palace: 9, reason: '景门主出行、道路,取景门所在宫' }, + '婚姻': { palace: 6, reason: '六合主婚姻合作,取六合神所在宫' }, + '考试': { palace: 9, reason: '景门主文书考试,取景门所在宫' }, + '失物': { palace: 1, reason: '天蓬主盗贼,取天蓬星所在宫以判断失物去向' }, + '工作': { palace: 6, reason: '开门主事业工作,取开门所在宫' }, + '投资': { palace: 8, reason: '生门主财富收益,取生门所在宫' }, +}; + +/** 检测特殊格局 */ +function detectGeJu(r: QiMenResult): GeJuFact[] { + const facts: GeJuFact[] = []; + + for (let i = 1; i <= 9; i++) { + const earth = r.earthPlate.get(i); + const heaven = r.heavenPlate.get(i); + const gate = r.gatePlate.get(i) || ''; + const star = r.starPlate.get(i) || ''; + const spirit = r.spiritPlate.get(i) || ''; + const palName = PALACES[i - 1].name; + + if (!earth || !heaven) continue; + + // 三奇:乙丙丁在天盘 + if (['乙', '丙', '丁'].includes(heaven)) { + const qiName = heaven === '乙' ? '日奇' : heaven === '丙' ? '月奇' : '星奇'; + facts.push({ + key: `qimen:geju:${i}:三奇-${heaven}`, + status: '已识别', + geJuType: '三奇', + palaceNumber: i, + palaceName: palName, + detail: `天盘${heaven}(${qiName})落${palName}`, + significance: `${qiName}临宫,主有奇助、贵人、意外之喜`, + promptText: `格局:${palName}天盘${heaven}(${qiName}),三奇之一,主有奇助贵人`, + sources: ['三奇六仪格局', '天盘干位置'], + limitation: GEJU_FACT_LIMITATION, + }); + } + + // 庚格:庚在天盘 + if (heaven === '庚') { + facts.push({ + key: `qimen:geju:${i}:庚格`, + status: '已识别', + geJuType: '庚格', + palaceNumber: i, + palaceName: palName, + detail: `天盘庚落${palName}`, + significance: '庚为阻隔之神,主有阻碍、对立、变革', + promptText: `格局:${palName}天盘庚,庚格主阻碍对立`, + sources: ['庚格格局', '天盘干位置'], + limitation: GEJU_FACT_LIMITATION, + }); + } + + // 丙+辛(悖格) + if (heaven === '丙' && earth === '辛') { + facts.push({ + key: `qimen:geju:${i}:悖格`, + status: '已识别', + geJuType: '悖格', + palaceNumber: i, + palaceName: palName, + detail: `天盘丙+地盘辛于${palName}`, + significance: '丙辛合化水,悖格主事有反复、矛盾', + promptText: `格局:${palName}天丙地辛,悖格主事有反复`, + sources: ['丙辛合格局'], + limitation: GEJU_FACT_LIMITATION, + }); + } + + // 乙+庚(合格) + if (heaven === '乙' && earth === '庚') { + facts.push({ + key: `qimen:geju:${i}:乙庚合`, + status: '已识别', + geJuType: '乙庚合', + palaceNumber: i, + palaceName: palName, + detail: `天盘乙+地盘庚于${palName}`, + significance: '乙庚合化金,主有合作、牵绊、因合而变', + promptText: `格局:${palName}天乙地庚,乙庚合金主合作牵绊`, + sources: ['乙庚合格局'], + limitation: GEJU_FACT_LIMITATION, + }); + } + + // 丁+壬(合格) + if (heaven === '丁' && earth === '壬') { + facts.push({ + key: `qimen:geju:${i}:丁壬合`, + status: '已识别', + geJuType: '丁壬合', + palaceNumber: i, + palaceName: palName, + detail: `天盘丁+地盘壬于${palName}`, + significance: '丁壬合化木,主有暗合、隐情、牵连', + promptText: `格局:${palName}天丁地壬,丁壬合木主暗合牵连`, + sources: ['丁壬合格局'], + limitation: GEJU_FACT_LIMITATION, + }); + } + + // 空亡宫 + for (const kong of r.xunkong) { + const kongBranch = BRANCH_ELEMENT[kong]; + const palaceBranch = PALACES[i - 1].earthBranch; + if (palaceBranch === kong) { + facts.push({ + key: `qimen:geju:${i}:空亡-${kong}`, + status: '已识别', + geJuType: '空亡', + palaceNumber: i, + palaceName: palName, + detail: `${palName}地支${palaceBranch}落旬空`, + significance: '空亡主虚象、不实、落空、等待', + promptText: `格局:${palName}地支${palaceBranch}旬空,主虚象不实`, + sources: ['旬空判断', '宫位地支'], + limitation: GEJU_FACT_LIMITATION, + }); + } + } + } + + return facts; +} + +/** 检测反证 */ +function detectCounterEvidence(r: QiMenResult): CounterEvidenceFact[] { + const counters: CounterEvidenceFact[] = []; + + for (let i = 1; i <= 9; i++) { + const gate = r.gatePlate.get(i) || ''; + const star = r.starPlate.get(i) || ''; + const spirit = r.spiritPlate.get(i) || ''; + const earth = r.earthPlate.get(i); + const heaven = r.heavenPlate.get(i); + const palName = PALACES[i - 1].name; + + // 死门 + if (gate === '死门') { + counters.push({ + key: `qimen:counter:${i}:死门`, + type: '凶门', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `死门落${palName},大凶之门`, + severity: '重', + promptText: `反证:${palName}落死门,大凶之门临宫,主终结、停滞`, + sources: ['八门性质', '死门落宫'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + + // 白虎 + if (spirit === '白虎') { + counters.push({ + key: `qimen:counter:${i}:白虎`, + type: '凶神', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `白虎临${palName},主凶险伤灾`, + severity: '重', + promptText: `反证:白虎临${palName},主凶险、伤灾、血光`, + sources: ['八神性质', '白虎临宫'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + + // 玄武 + if (spirit === '玄武') { + counters.push({ + key: `qimen:counter:${i}:玄武`, + type: '凶神', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `玄武临${palName},主暗昧盗贼`, + severity: '中', + promptText: `反证:玄武临${palName},主暗昧、欺骗、盗贼`, + sources: ['八神性质', '玄武临宫'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + + // 腾蛇 + if (spirit === '腾蛇') { + counters.push({ + key: `qimen:counter:${i}:腾蛇`, + type: '凶神', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `腾蛇临${palName},主惊恐怪异`, + severity: '轻', + promptText: `反证:腾蛇临${palName},主惊恐、怪异、虚惊`, + sources: ['八神性质', '腾蛇临宫'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + + // 天蓬(凶星) + if (star === '天蓬') { + counters.push({ + key: `qimen:counter:${i}:天蓬`, + type: '凶星', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `天蓬星临${palName},主盗贼淫邪`, + severity: '中', + promptText: `反证:天蓬星临${palName},主盗贼、淫邪、破财`, + sources: ['九星性质', '天蓬临宫'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + + // 天芮(凶星,主疾病) + if (star === '天芮') { + counters.push({ + key: `qimen:counter:${i}:天芮`, + type: '凶星', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `天芮星临${palName},主疾病阴人`, + severity: '中', + promptText: `反证:天芮星临${palName},主疾病、阴人、纠缠`, + sources: ['九星性质', '天芮临宫'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + + // 天盘克地盘(天盘五行克地盘五行) + if (heaven && earth) { + const hEl = stemElement(heaven); + const eEl = stemElement(earth); + if (WUXING_KE[hEl] === eEl) { + counters.push({ + key: `qimen:counter:${i}:天克地`, + type: '天盘克地盘', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `天盘${heaven}(${hEl})克地盘${earth}(${eEl})于${palName}`, + severity: '中', + promptText: `反证:${palName}天盘${heaven}${hEl}克地盘${earth}${eEl},天克地主外力压制`, + sources: ['五行克关系', '天地盘干五行'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + } + + // 空亡宫 + for (const kong of r.xunkong) { + if (PALACES[i - 1].earthBranch === kong) { + counters.push({ + key: `qimen:counter:${i}:空亡`, + type: '空亡', + status: '已触发', + palaceNumber: i, + palaceName: palName, + detail: `${palName}地支${kong}落旬空`, + severity: '中', + promptText: `反证:${palName}旬空,事有虚象、不实`, + sources: ['旬空判断'], + limitation: COUNTER_FACT_LIMITATION, + }); + } + } + } + + return counters; +} + +/** 生成应期事实 */ +function buildTimingFacts(r: QiMenResult): TimingFact[] { + const facts: TimingFact[] = []; + + // 驿马 + facts.push({ + key: 'qimen:timing:horse', + type: '驿马', + status: '已计算', + detail: `驿马在${r.horse},主快速变动`, + promptText: `应期参考:驿马${r.horse},主动、快、变;驿马冲动之日为应期参考`, + sources: ['驿马推算', '日支三合局'], + limitation: TIMING_FACT_LIMITATION, + }); + + // 空亡填实 + if (r.xunkong.length > 0) { + facts.push({ + key: 'qimen:timing:xunkong', + type: '空亡填实', + status: '已计算', + detail: `旬空${r.xunkong.join('、')},填实或冲实为应期`, + promptText: `应期参考:旬空${r.xunkong.join('、')},逢填实(遇相同地支)或冲实(遇对冲地支)时为应期参考`, + sources: ['旬空填实规则'], + limitation: TIMING_FACT_LIMITATION, + }); + } + + // 月令旺衰 + const monthElement = (() => { + // 按月份粗略判断月令五行 + const m = r.month; + if (m >= 1 && m <= 2) return '木'; + if (m >= 3 && m <= 4) return '火'; + if (m >= 5 && m <= 6) return '土'; + if (m >= 7 && m <= 8) return '金'; + if (m >= 9 && m <= 10) return '水'; + return '木'; // 11-12 冬至后 + })(); + + facts.push({ + key: 'qimen:timing:month', + type: '旺衰', + status: '已计算', + detail: `月令${r.month}月,当令${monthElement}`, + promptText: `应期参考:${r.month}月当令${monthElement},与当令五行同气或相生的宫位事速,受克者事缓`, + sources: ['月令五行旺衰'], + limitation: TIMING_FACT_LIMITATION, + }); + + // 冲合 + const horseOpposite: Record = { + '寅': '申', '申': '寅', '巳': '亥', '亥': '巳', + '子': '午', '午': '子', '丑': '未', '未': '丑', + '卯': '酉', '酉': '卯', '辰': '戌', '戌': '辰', + }; + const horseOpp = horseOpposite[r.horse]; + if (horseOpp) { + facts.push({ + key: 'qimen:timing:chong', + type: '冲合', + status: '已计算', + detail: `驿马${r.horse}冲${horseOpp},冲动时为应期参考`, + promptText: `应期参考:${r.horse}冲${horseOpp},逢${horseOpp}日/时为冲动应期参考`, + sources: ['地支六冲'], + limitation: TIMING_FACT_LIMITATION, + }); + } + + return facts; +} + +// ═══════════════════════════════════════════ +// 证据链生成主入口 +// ═══════════════════════════════════════════ + +/** + * 从排盘结果自动生成完整证据链 + * @param result calculateQiMen() 的返回值 + */ +export function buildEvidence(result: QiMenResult): QiMenEvidenceAnalysis { + // ── 1. 起盘参数事实 ── + const calculationFact: CalculationFact = { + key: 'qimen:calculation', + type: 'calculation', + status: '已计算', + yinYang: result.yinYang, + juNumber: result.juNumber, + dayStem: result.dayStem, + dayBranch: result.dayBranch, + hourStem: result.hourStem, + hourBranch: result.hourBranch, + month: result.month, + xunkong: result.xunkong, + horse: result.horse, + promptText: `起盘:${result.month}月${result.dayStem}${result.dayBranch}日${result.hourStem}${result.hourBranch}时,${result.yinYang}遁${result.juNumber}局,旬空${result.xunkong.join('、')},驿马${result.horse}`, + sources: ['四柱干支', '阴阳遁判断', '局数推算', '旬空推算', '驿马推算'], + limitation: CALCULATION_FACT_LIMITATION, + }; + + // ── 2. 九宫逐宫事实 ── + const palaceFacts: PalaceEvidence[] = []; + for (let i = 1; i <= 9; i++) { + const earth = result.earthPlate.get(i)!; + const heaven = result.heavenPlate.get(i)!; + const gateName = result.gatePlate.get(i) || '—'; + const starName = result.starPlate.get(i) || '—'; + const spiritName = result.spiritPlate.get(i) || '—'; + const pal = PALACES[i - 1]; + + const gateInfo = getGateInfo(gateName); + const starInfo = getStarInfo(starName); + const spiritInfo = getSpiritInfo(spiritName); + const relation = heavenEarthRelation(heaven, earth); + + // 支持与约束 + const support: string[] = []; + const constraints: string[] = []; + + // 门的吉凶 + if (gateInfo.nature === '吉' || gateInfo.nature === '大吉') { + support.push(`${gateName}为${gateInfo.nature}门,${gateInfo.attribute}`); + } else if (gateInfo.nature === '凶' || gateInfo.nature === '大凶') { + constraints.push(`${gateName}为${gateInfo.nature}门,${gateInfo.attribute}`); + } + + // 星的吉凶 + if (starInfo.nature === '吉' || starInfo.nature === '大吉') { + support.push(`${starName}为${starInfo.nature}星,${starInfo.attribute}`); + } else if (starInfo.nature === '凶') { + constraints.push(`${starName}为${starInfo.nature}星,${starInfo.attribute}`); + } + + // 神的吉凶 + if (spiritInfo.nature === '吉' || spiritInfo.nature === '大吉') { + support.push(`${spiritName}为${spiritInfo.nature}神,${spiritInfo.attribute}`); + } else if (spiritInfo.nature === '凶') { + constraints.push(`${spiritName}为${spiritInfo.nature}神,${spiritInfo.attribute}`); + } + + // 天地盘关系 + const hEl = stemElement(heaven); + const eEl = stemElement(earth); + if (WUXING_SHENG[hEl] === eEl) { + support.push(`天盘${heaven}${hEl}生地盘${earth}${eEl},天助地`); + } else if (WUXING_KE[eEl] === hEl) { + support.push(`地盘${earth}${eEl}克天盘${heaven}${hEl},地制天`); + } + + palaceFacts.push({ + key: `qimen:palace:${i}`, + status: '已记录', + palaceNumber: i, + palaceName: pal.name, + direction: pal.direction, + palaceElement: pal.element, + earthStem: earth, + earthElement: eEl, + heavenStem: heaven, + heavenElement: hEl, + gateName, + gateNature: gateInfo.nature, + starName, + starNature: starInfo.nature, + spiritName, + spiritNature: spiritInfo.nature, + wuxingRelation: relation, + support, + constraints, + promptText: `${pal.name}(${pal.direction}):地盘${earth}${eEl},天盘${heaven}${hEl},${relation};门${gateName}(${gateInfo.nature}),星${starName}(${starInfo.nature}),神${spiritName}(${spiritInfo.nature});支持:${support.join('、') || '无'};限制:${constraints.join('、') || '无'}`, + sources: ['地盘干排布', '天盘干排布', '八门排布', '九星排布', '八神排布', '五行生克关系'], + limitation: PALACE_FACT_LIMITATION, + }); + } + + // ── 3. 用神焦点 ── + let yongShenFact: YongShenFact | null = null; + if (result.question) { + // 简单关键词匹配问事类型 + const q = result.question; + let matched = false; + for (const [keyword, config] of Object.entries(QUESTION_TYPE_MAP)) { + if (q.includes(keyword)) { + const pal = PALACES[config.palace - 1]; + yongShenFact = { + key: `qimen:yongshen:${keyword}`, + status: '已选取', + questionType: keyword, + targetPalace: config.palace, + palaceName: pal.name, + reason: config.reason, + promptText: `用神:问「${keyword}」取${pal.name},依据${config.reason}`, + sources: ['问事类型→用神映射', '八门/九星/八神取用规则'], + limitation: YONGSHEN_FACT_LIMITATION, + }; + matched = true; + break; + } + } + if (!matched) { + // 默认取值符宫 + for (let i = 1; i <= 9; i++) { + if (result.earthPlate.get(i) === '戊') { + const pal = PALACES[i - 1]; + yongShenFact = { + key: 'qimen:yongshen:default', + status: '已选取', + questionType: '通用', + targetPalace: i, + palaceName: pal.name, + reason: '未匹配特定问事类型,默认取值符(戊)所在宫为用神', + promptText: `用神:问事类型未匹配,取值符宫${pal.name}为用神`, + sources: ['值符宫默认取用'], + limitation: YONGSHEN_FACT_LIMITATION, + }; + break; + } + } + } + } + + // ── 4. 格局事实 ── + const geJuFacts = detectGeJu(result); + + // ── 5. 反证 ── + const counterEvidenceFacts = detectCounterEvidence(result); + const counterSummary = { + status: counterEvidenceFacts.length > 0 ? '有明确反证' as const : '未见明确反证' as const, + detail: counterEvidenceFacts.length > 0 + ? `发现${counterEvidenceFacts.length}条反证:${counterEvidenceFacts.map(f => f.detail).join(';')}` + : '当前盘面未见明确反证', + }; + + // ── 6. 应期 ── + const timingFacts = buildTimingFacts(result); + + // ── 7. 汇总 ── + const factCount = 1 + palaceFacts.length + (yongShenFact ? 1 : 0) + + geJuFacts.length + counterEvidenceFacts.length + timingFacts.length; + const summary = { + factCount, + palaceCount: palaceFacts.length, + geJuCount: geJuFacts.length, + counterCount: counterEvidenceFacts.length, + timingCount: timingFacts.length, + }; + + // ── 全局限制 ── + const limitations = [ + '奇门遁甲证据链仅描述排盘参数、九宫五行关系与格局特征,不证明现实吉凶、成败、概率或固定应期', + '格局名称(三奇、庚格、悖格等)是盘面结构描述,不直接对应现实事件、人物意图或必然结果', + '反证与支持仅反映盘面门、星、神的五行性质,不代表现实中的必然结果或风险为零', + '应期分析只提供相对触发条件,不得换算为绝对日期或时间节点', + '用神选取基于传统规则,不同流派可能有差异,不代表唯一正确取法', + ]; + + // ── promptText ── + const promptText = [ + calculationFact.promptText, + ...palaceFacts.map(f => f.promptText), + yongShenFact?.promptText ?? '', + ...geJuFacts.map(f => f.promptText), + ...counterEvidenceFacts.map(f => f.promptText), + counterSummary.detail, + ...timingFacts.map(f => f.promptText), + ].filter(Boolean).join('\n'); + + return { + key: 'qimen:evidence', + status: '已计算', + calculationFact, + palaceFacts, + yongShenFact, + geJuFacts, + counterEvidenceFacts, + counterSummary, + timingFacts, + summary, + limitations, + promptText, + }; +} diff --git a/calculator/src/run-qimen.ts b/calculator/src/run-qimen.ts index ae30a52..f9cf379 100644 --- a/calculator/src/run-qimen.ts +++ b/calculator/src/run-qimen.ts @@ -1,5 +1,6 @@ import { Stem, Branch, STEMS, BRANCHES, PALACES, EIGHT_GATES, NINE_STARS, EIGHT_SPIRITS, getRelation } from './qimen-data.js'; import { calculateQiMen, QiMenInput } from './plate.js'; +import { buildEvidence } from './evidence.js'; const args = process.argv.slice(2); function getArg(name: string): string | undefined { @@ -18,6 +19,8 @@ const outputFormat = getArg('output') || 'text'; try { const result = calculateQiMen({ dayStem, dayBranch, hourStem, hourBranch, month, question }); + const evidence = buildEvidence(result); + if (outputFormat === 'json') { const obj: any = { yinYang: result.yinYang, juNumber: result.juNumber, @@ -39,16 +42,30 @@ try { spirit: result.spiritPlate.get(i) || '', }); } + // 证据链 + obj.evidence = { + key: evidence.key, + status: evidence.status, + calculation: evidence.calculationFact, + palaces: evidence.palaceFacts, + yongShen: evidence.yongShenFact, + geJu: evidence.geJuFacts, + counterEvidence: evidence.counterEvidenceFacts, + counterSummary: evidence.counterSummary, + timing: evidence.timingFacts, + summary: evidence.summary, + limitations: evidence.limitations, + }; console.log(JSON.stringify(obj, null, 2)); } else { - console.log(formatAsText(result)); + console.log(formatAsText(result, evidence)); } } catch (e: any) { console.error('排盘错误:', e.message); process.exit(1); } -function formatAsText(r: ReturnType): string { +function formatAsText(r: ReturnType, evidence: ReturnType): string { let text = `═══════════════════════════════════ 奇 门 遁 甲 排 盘 ═══════════════════════════════════ @@ -93,6 +110,49 @@ ${r.question ? `【问事】${r.question}` : ''} // 马星提示 text += `🐴 驿马:${r.horse},主动、快、变\n`; + // ═══ 证据链 ═══ + text += `\n═══ 证 据 链 ═══\n\n`; + + // 用神 + if (evidence.yongShenFact) { + const ys = evidence.yongShenFact; + text += `【用神】${ys.palaceName}(${ys.reason})\n\n`; + } + + // 格局 + if (evidence.geJuFacts.length > 0) { + text += `【格局】\n`; + for (const g of evidence.geJuFacts) { + text += ` · ${g.detail}——${g.significance}\n`; + } + text += `\n`; + } + + // 反证 + text += `【反证】${evidence.counterSummary.detail}\n`; + if (evidence.counterEvidenceFacts.length > 0) { + for (const c of evidence.counterEvidenceFacts) { + text += ` · ${c.detail}(${c.severity})\n`; + } + } + text += `\n`; + + // 应期 + if (evidence.timingFacts.length > 0) { + text += `【应期】\n`; + for (const t of evidence.timingFacts) { + text += ` · ${t.detail}\n`; + } + text += `\n`; + } + + // 汇总与限制 + text += `【汇总】共${evidence.summary.factCount}条事实:${evidence.summary.palaceCount}宫 + ${evidence.summary.geJuCount}格局 + ${evidence.summary.counterCount}反证 + ${evidence.summary.timingCount}应期\n`; + text += `\n【限制声明】\n`; + for (const lim of evidence.limitations) { + text += ` · ${lim}\n`; + } + return text; }