feat: 奇门遁甲证据链架构

This commit is contained in:
小钰 committed 2026-08-03 10:24:00 +08:00
1 parent 380a516520
commit 49e834abde
2 files changed
+827 -2

No files matched your search

+765
View File
@@ -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<string, { palace: number; reason: string }> = {
'求财': { 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<string, string> = {
'寅': '申', '申': '寅', '巳': '亥', '亥': '巳',
'子': '午', '午': '子', '丑': '未', '未': '丑',
'卯': '酉', '酉': '卯', '辰': '戌', '戌': '辰',
};
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,
};
}
+62 -2
View File
@@ -1,5 +1,6 @@
import { Stem, Branch, STEMS, BRANCHES, PALACES, EIGHT_GATES, NINE_STARS, EIGHT_SPIRITS, getRelation } from './qimen-data.js'; import { Stem, Branch, STEMS, BRANCHES, PALACES, EIGHT_GATES, NINE_STARS, EIGHT_SPIRITS, getRelation } from './qimen-data.js';
import { calculateQiMen, QiMenInput } from './plate.js'; import { calculateQiMen, QiMenInput } from './plate.js';
import { buildEvidence } from './evidence.js';
const args = process.argv.slice(2); const args = process.argv.slice(2);
function getArg(name: string): string | undefined { function getArg(name: string): string | undefined {
@@ -18,6 +19,8 @@ const outputFormat = getArg('output') || 'text';
try { try {
const result = calculateQiMen({ dayStem, dayBranch, hourStem, hourBranch, month, question }); const result = calculateQiMen({ dayStem, dayBranch, hourStem, hourBranch, month, question });
const evidence = buildEvidence(result);
if (outputFormat === 'json') { if (outputFormat === 'json') {
const obj: any = { const obj: any = {
yinYang: result.yinYang, juNumber: result.juNumber, yinYang: result.yinYang, juNumber: result.juNumber,
@@ -39,16 +42,30 @@ try {
spirit: result.spiritPlate.get(i) || '', 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)); console.log(JSON.stringify(obj, null, 2));
} else { } else {
console.log(formatAsText(result)); console.log(formatAsText(result, evidence));
} }
} catch (e: any) { } catch (e: any) {
console.error('排盘错误:', e.message); console.error('排盘错误:', e.message);
process.exit(1); process.exit(1);
} }
function formatAsText(r: ReturnType<typeof calculateQiMen>): string { function formatAsText(r: ReturnType<typeof calculateQiMen>, evidence: ReturnType<typeof buildEvidence>): string {
let text = `═══════════════════════════════════ let text = `═══════════════════════════════════
奇 门 遁 甲 排 盘 奇 门 遁 甲 排 盘
═══════════════════════════════════ ═══════════════════════════════════
@@ -93,6 +110,49 @@ ${r.question ? `【问事】${r.question}` : ''}
// 马星提示 // 马星提示
text += `🐴 驿马:${r.horse},主动、快、变\n`; 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; return text;
} }