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 { 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<typeof calculateQiMen>): string {
|
||||
function formatAsText(r: ReturnType<typeof calculateQiMen>, evidence: ReturnType<typeof buildEvidence>): 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user