feat: 奇门遁甲证据链架构
This commit is contained in:
1 parent
380a516520
commit
49e834abde
2 files changed
+827
-2
No files matched your search
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user