dsh-metaphysics 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ // 能力边界:这个插件不回答什么。
2
+ //
3
+ // 写这份清单的理由很具体:命理产品最容易犯的错不是算错,是让用户以为它能回答所有问题。
4
+ // 所以每条都要对应一处**真实的实现状况**,不是免责话术——
5
+ // 「我们可能不准」这种句子没有信息量,用户看完还是不知道该信哪部分。
6
+ // 有用的写法是:这条为什么做不到、做到哪一步就停了、停在哪里的依据是什么。
7
+
8
+ /**
9
+ * 通用边界:所有体系都适用。放在清单最前面,因为它比任何单条技法边界都重要。
10
+ */
11
+ export const UNIVERSAL_LIMITS = [
12
+ {
13
+ id: 'validity',
14
+ title: '任何技法的预测效度都未经检验',
15
+ text: '能被复核的只有形式层——纳甲、干支关系、十神、宫位这类可由程序重算的部分,错了能查。解释层是各派通行说法,本插件不声称它比别的说法更准,也没有做过能被别人重复的验证。',
16
+ },
17
+ {
18
+ id: 'same-chart',
19
+ title: '同盘不同命,本插件不解决',
20
+ text: '同一个四柱、同一张命盘对应无数人,从盘面推不出具体某一个人。这是模型的硬缺陷,不是样本不够——换个更大的样本也补不上。所以本插件给的是「这一类盘常见的结构」,不是「你会怎样」。',
21
+ },
22
+ {
23
+ id: 'advice',
24
+ title: '不给医疗、法律、投资建议',
25
+ text: '不做疾病诊断与用药建议,不判诉讼胜负,不做买卖与借贷决策。三类问题都有各自的专业判断标准,盘面推不出它们。',
26
+ },
27
+ ];
28
+
29
+ /**
30
+ * 按体系的能力边界。system 为 null 时返回通用三条。
31
+ * 每条都应能追到 engine 里的实际实现,改实现时同步改这里。
32
+ */
33
+ export const SYSTEM_LIMITS = {
34
+ liuyao: [
35
+ {
36
+ id: 'liuyao-no-date',
37
+ title: '应期不给公历日期',
38
+ text: '只给地支与快慢档(偏速/偏缓/待解/未定),不折算成「某月某日」。原因不是保守:应期可取逢值、逢冲、逢合、出空、填实多种,任一种都能事后对得上,折算成日期只是把不确定性藏进一个具体数字里。回测 15 条规则命中 51.7%,平均要给 3.55 个候选支(随机基线 29.6%)——高于随机,但撑不起一个具体日期。',
39
+ },
40
+ {
41
+ id: 'liuyao-yongshen',
42
+ title: '用神多现时,不替你选',
43
+ text: '卦中用神两现时,插件把候选都列出来,不代选。取哪一个直接决定断语方向,各家取舍标准不一,替选等于把一派之见说成唯一答案。',
44
+ },
45
+ {
46
+ id: 'liuyao-zhi-date',
47
+ title: '「某支应事」不等于「那天会发生」',
48
+ text: '地支只标方向与快慢,不承诺落点。同一支在一天里对应两个时辰、在一个月里对应多个日子,具体哪一天,本插件不判。',
49
+ },
50
+ {
51
+ id: 'liuyao-liuyue',
52
+ title: '不推演流月推进后的二次变化',
53
+ text: '应期按「当下一个月建 + 日辰」静态算,不模拟下一个月到来后「原来的合被冲开、原本被制住的爻复起」这类连锁。实占里有反例:用神伏藏、本来可靠日辰冲开,但进入下个月建后一冲解掉了原月建对制它之爻的合,制者复起,出伏失败,事情没成。跨月推演还没做,所以应期只能作「该留意哪几支」,不能承诺某个月份。',
54
+ },
55
+ ],
56
+ bazi: [
57
+ {
58
+ id: 'bazi-no-event',
59
+ title: '不预测具体年份的事件',
60
+ text: '不判「哪年结婚」「哪年升职」「哪年发财」。岁运并临、运年反吟、伏吟这类只作变动提示、不断吉凶——这些格局在十年里会反复出现,按它们断事,等于每年都能找到一次「应验」。',
61
+ },
62
+ {
63
+ id: 'bazi-strength',
64
+ title: '旺衰是分档,不是评分',
65
+ text: '旺衰按五行分量与月令得令情况分档,给出的百分比是相对权重,不是命理学的定量指标。各家对「几分算旺」没有共识,本插件不把档位差当成精确结论。',
66
+ },
67
+ {
68
+ id: 'bazi-huagi',
69
+ title: '化气格的真化,仍缺对拍',
70
+ text: '化气格成立条件各家宽严不一(是否要求月令为化神、日主是否允许有根),本插件按从严一路判,并把条件与原文并列摆出;真化/假化的判定还没有实盘对拍集。',
71
+ },
72
+ ],
73
+ ziwei: [
74
+ {
75
+ id: 'ziwei-feixing',
76
+ title: '飞星层只报路径,不判吉凶',
77
+ text: '飞星能做到单一支化逐宫追到成环、三方四正按落宫归堆,但串连与复合飞化叠上大限流年之后的力重还没定,所以只报走到哪、不判好与不好。',
78
+ },
79
+ {
80
+ id: 'ziwei-poge',
81
+ title: '格局只判成立,破格条件未做',
82
+ text: '34 条格局按独立谓词逐条复算过成立条件,但「什么情况下这条格局不算」各家破法不一,需先定底本,所以现在只判成不成立。',
83
+ },
84
+ ],
85
+ meihua: [
86
+ {
87
+ id: 'meihua-waiying',
88
+ title: '不取外应',
89
+ text: '只按时间/数字起卦,用体用生克与互变卦推演。外应(当时的声音、来人衣着、路上所见)不纳入——那部分的解释空间几乎没有边界,纳入后任何结论都能凑出来。',
90
+ },
91
+ ],
92
+ daliuren: [
93
+ {
94
+ id: 'daliuren-yingqi',
95
+ title: '三传只给结构,应期未做',
96
+ text: '四课三传按通行起法给出,天将、六亲、旺衰并列;具体应期推演尚未实现,不据此判时间。',
97
+ },
98
+ ],
99
+ qimen: [
100
+ {
101
+ id: 'qimen-bamen',
102
+ title: '八门九星入墓未做',
103
+ text: '十干克应与六仪入墓已按通行口径上,八门、九星入墓各家说法不同、需逐条核,暂不实现;用神落宫与击刑入墓的联动只并列条件、不推结论。',
104
+ },
105
+ ],
106
+ tarot: [
107
+ {
108
+ id: 'tarot-claim',
109
+ title: '牌义取通行义,不做个人化解读',
110
+ text: '按牌阵位置与正逆位给通行牌义,不做联想式延伸。牌阵与问题的对应关系由你确定,插件不替你判断「该抽几张」「该问什么」。',
111
+ },
112
+ ],
113
+ };
114
+
115
+ /** 取某体系的边界清单:通用三条 + 该体系条目 */
116
+ export function limitsFor(system) {
117
+ return [...UNIVERSAL_LIMITS, ...(SYSTEM_LIMITS[system] || [])];
118
+ }
package/engine/liuyao.js CHANGED
@@ -67,6 +67,10 @@ const ZHI_CHONG_PAIRS = [['子', '午'], ['丑', '未'], ['寅', '申'], ['卯',
67
67
  const ZHI_HE_PAIRS = [['子', '丑'], ['寅', '亥'], ['卯', '戌'], ['辰', '酉'], ['巳', '申'], ['午', '未']];
68
68
  const CHONG_OF = Object.fromEntries(ZHI_CHONG_PAIRS.flatMap(([a, b]) => [[a, b], [b, a]]));
69
69
  const HE_OF = Object.fromEntries(ZHI_HE_PAIRS.flatMap(([a, b]) => [[a, b], [b, a]]));
70
+
71
+ // 应期规则要算「冲开/合住」,但表只有这一份:导出成函数,别让第二个模块再抄一张表
72
+ export const chongOf = (branch) => CHONG_OF[branch] || null;
73
+ export const heOf = (branch) => HE_OF[branch] || null;
70
74
  const TRIGRAM_OPPOSITE = { 乾: '巽', 巽: '乾', 坎: '离', 离: '坎', 艮: '坤', 坤: '艮', 震: '兑', 兑: '震' };
71
75
 
72
76
  // ── 化进神 / 化退神 ────────────────────────────────────────────────
@@ -0,0 +1,303 @@
1
+ // 奇门问事解读提示:把「先问清事 → 再取用神 → 看落宫与组合 → 回到现实核对」固化成一段提示。
2
+ //
3
+ // 为什么要有这个文件:奇门的盘面符号极多(九宫、八门、九星、八神、天地盘干、十干应克),
4
+ // 模型拿到一屏数据后最省力的写法就是「开门临生门,吉」——这不是解读,是把符号念一遍。
5
+ // 所以提示里把「取哪个符号、为什么取它、它落在哪、和谁什么关系、回到现实查什么」写成硬步骤,
6
+ // 并要求先给人话结论,再给依据。文案与取法分开存,取法可测,文案可改。
7
+ export const QIMEN_PROMPT_VERSION = 'qimen-1';
8
+
9
+ /** 九宫序号 → 方位与领域(读「事情落在哪个领域」时用,不直接当吉凶) */
10
+ export const PALACE_FIELD = {
11
+ 1: { dir: '正北', field: '坎宫:向内、暗中、流动、基础' },
12
+ 2: { dir: '西南', field: '坤宫:承载、家庭、土地、执行方' },
13
+ 3: { dir: '正东', field: '震宫:启动、动作、速度、竞争' },
14
+ 4: { dir: '东南', field: '巽宫:沟通、文书、渐进、反复' },
15
+ 5: { dir: '正中', field: '中宫:枢纽;无门无神,按寄宫(多寄坤二)取用,读时要说清是寄宫' },
16
+ 6: { dir: '西北', field: '乾宫:上级、规则、外部权威、主导方' },
17
+ 7: { dir: '正西', field: '兑宫:口舌、谈判、交易、愉悦' },
18
+ 8: { dir: '东北', field: '艮宫:停滞、门槛、转向、积累' },
19
+ 9: { dir: '正南', field: '离宫:显露、名声、扩散、明亮' },
20
+ };
21
+
22
+ /** 八门的现实含义。门名只是「这件事的形态」,不是吉凶判决 */
23
+ export const GATE_MEANING = {
24
+ 开门: '启动、公开、对外推进、正式场合',
25
+ 休门: '休息、等待、缓和、暂缓',
26
+ 生门: '生长、收益、资源、可持续',
27
+ 伤门: '损耗、冲突、风险、代价',
28
+ 杜门: '阻碍、隐藏、不宜张扬、需要绕过',
29
+ 景门: '信息、名声、曝光、文书往来',
30
+ 死门: '停止、收尾、不可再推、需要换路',
31
+ 惊门: '变动、惊吓、口舌、突发',
32
+ };
33
+
34
+ /**
35
+ * 问事类型 → 用神清单。subjects 的顺序就是「先讲清对方最关心哪个对象」的顺序:
36
+ * 第一个是本方(我),第二个通常是对方/事体,后面才是环境与条件。
37
+ * how 是取法(可复算),why 是取这个符号的理由(三追问第一问要能答出来)。
38
+ */
39
+ export const ASK_KINDS = {
40
+ career: {
41
+ label: '工作 / 事业',
42
+ gate: '开门',
43
+ subjects: [
44
+ { key: 'self', name: '我', how: 'dayStem', why: '日干代表提问者本人;先定位「我现在在哪一宫」,才谈得上后面谁克谁。' },
45
+ { key: 'job', name: '这份工作本身', how: 'gate:开门', why: '开门的本义是「开启、公开、正式」,对应岗位与正式机会,所以用它代表工作这个对象,而不是代表结果好坏。' },
46
+ { key: 'boss', name: '上司 / 决策人', how: 'zhiFu', why: '值符是全局主导之神,代表有拍板权的一方;谈成不成,最终看这一宫的状态与它和日干宫的关系。' },
47
+ { key: 'peer', name: '同事 / 竞争者', how: 'monthStem', why: '月干为同辈;同岗竞争、内部评价这类事落在这里。' },
48
+ ],
49
+ },
50
+ love: {
51
+ label: '感情 / 关系',
52
+ gate: '休门',
53
+ subjects: [
54
+ { key: 'self', name: '我', how: 'dayStem', why: '日干为我。' },
55
+ { key: 'other', name: '对方', how: 'hourStem', why: '时干代表所问之事与对方;感情里问「他怎么样」,时干就是那个人此刻的状态。' },
56
+ { key: 'bond', name: '你俩的连接', how: 'god:六合', why: '六合主结合、撮合、连结;看连接本身有没有被冲开或合住,比看单方态度更接近真相。' },
57
+ ],
58
+ },
59
+ business: {
60
+ label: '生意 / 合作',
61
+ gate: '生门',
62
+ subjects: [
63
+ { key: 'self', name: '我方', how: 'dayStem', why: '日干为我方。' },
64
+ { key: 'partner', name: '对方 / 合作方', how: 'hourStem', why: '时干代表对方与所谈之事。' },
65
+ { key: 'chance', name: '项目机会', how: 'gate:生门', why: '生门主生长与收益,用它代表「这个机会能不能长出东西」。' },
66
+ { key: 'resource', name: '资源与条件', how: 'gate:开门', why: '开门代表公开的资源、渠道、正式手续;有没有真资源,看这一宫有没有被制。' },
67
+ { key: 'deal', name: '契约 / 撮合', how: 'god:六合', why: '六合主合约与中间人;合作最怕的是「六合落空或受冲」,即说了不算。' },
68
+ ],
69
+ },
70
+ action: {
71
+ label: '某件事能不能推进',
72
+ gate: '开门',
73
+ subjects: [
74
+ { key: 'self', name: '我', how: 'dayStem', why: '日干为我。' },
75
+ { key: 'act', name: '这件事本身', how: 'hourStem', why: '时干为事体:你要问的那件事,当下的状态在时干这一宫。' },
76
+ { key: 'env', name: '外部条件', how: 'zhiShi', why: '值使门是「当下正在值班的门」,代表这件事所处的执行环境与节奏。' },
77
+ ],
78
+ },
79
+ day: {
80
+ label: '挑日子 / 看时机',
81
+ gate: '值使',
82
+ subjects: [
83
+ { key: 'act', name: '要办的事', how: 'hourStem', why: '时干为事体,先看它落在哪一宫、受不受制。' },
84
+ { key: 'env', name: '当天的执行环境', how: 'zhiShi', why: '值使门代表当值之门,是这一天的主导形态,不把它当吉凶。' },
85
+ { key: 'self', name: '办事的人', how: 'dayStem', why: '日干为办事人;人有空亡、受制,日子再好也执行不了。' },
86
+ ],
87
+ },
88
+ other: {
89
+ label: '其他具体事',
90
+ gate: '开门',
91
+ subjects: [
92
+ { key: 'self', name: '我', how: 'dayStem', why: '日干为我。' },
93
+ { key: 'act', name: '所问之事', how: 'hourStem', why: '时干为事体。' },
94
+ { key: 'env', name: '当下环境', how: 'zhiShi', why: '值使门代表当下的执行节奏。' },
95
+ ],
96
+ },
97
+ };
98
+
99
+ /** 回到现实要核对什么:按类型给具体清单,不写「请结合现实」这种空话 */
100
+ export const REALITY_CHECK = {
101
+ career: ['有没有书面邀约或正式通知?(口头承诺不算)', '岗位职责、汇报对象、试用期写清楚了吗?', '薪酬范围、发放方式、社保口径有没有落到纸面?', '最坏情况是什么:试用不过、岗位撤销,你损失多少?'],
102
+ love: ['对方有没有具体行动:约见、介绍给朋友、共同计划?', '这种投入持续了多久,还是一阵热?', '有没有第三方牵涉(前任、家人、异地)?', '你自己愿意为这段关系让渡什么,底线在哪?'],
103
+ business: ['资源清单能落到具体人、具体钱、具体渠道吗?', '合同条款:出资、分账、违约、退出,哪条没写?', '谁执行?执行人是全职还是顺手帮忙?', '如果对方撤了,你一个人能不能收场?'],
104
+ action: ['这件事有没有截止日期?谁定的?', '实际成本:钱、时间、人情,各多少?', '做错了能不能退回去?可逆性有多高?', '成功的样子长什么样,能不能用一句话说清?'],
105
+ day: ['当天必须到场的人确认了吗?', '场地、材料、审批这些硬约束排除了吗?', '有备选日期吗?排到第几位?', '这一天之前的准备来得及吗?'],
106
+ other: ['这件事成功的样子,能用一件可观察的事描述吗?', '有没有一个明确的时间点可以验证?', '如果没成,你会先怀疑哪一环?', '现在最该做的一件小事是什么?'],
107
+ };
108
+
109
+ /** 少用术语、面向提问者的表达要求 */
110
+ export const STYLE_RULES = [
111
+ '开头先用三到五句话回答他问的那件事,全用日常说法:不出现「用神、落宫、值符、生克」这些词。',
112
+ '每个术语第一次出现时,必须紧跟一句人话解释(例:「开门,就是这件事能不能摆到台面上正式推进」)。',
113
+ '不写「吉/凶」二字作结论,改写成人能执行的话:能推进 / 卡在谁那里 / 还缺什么条件 / 大概什么时候会有信号。',
114
+ '盘面给不出的东西(具体金额、对方心里怎么想、某天几点),直接说给不出,不要补。',
115
+ ];
116
+
117
+ const HARD_RULES = [
118
+ '只使用下面「盘面事实」里出现的符号。没给的宫、门、星、神,一律不许自己补。',
119
+ '不许说「这是生门所以好」「这是白虎所以凶」。门名与神名只说明形态,吉凶要看它和日干宫、时干宫的关系,以及有没有空亡、受制、入墓。',
120
+ '不给医疗、法律、投资的具体结论;这些只能建议去找对应专业人士。',
121
+ '不给公历日期。奇门这里只谈「什么条件下会有信号」,不谈「几月几号」。',
122
+ '不确定就写不确定,并说明缺哪条信息才能确定。',
123
+ ];
124
+
125
+ const stemOf = (siZhu, key) => {
126
+ const v = siZhu && siZhu[key];
127
+ return v ? String(v).charAt(0) : '';
128
+ };
129
+
130
+ /** 天盘某干落在哪一宫(日干/时干/月干定位都走这里) */
131
+ function palaceOfStem(q, stem) {
132
+ if (!stem) return null;
133
+ const hit = (q.palaces || []).find((p) => p.tianPan === stem);
134
+ if (hit) return hit;
135
+ // 中五宫不在 palaces 里(它没有门、不参与门宫关系),天盘干可能只在这里。
136
+ // 中宫按「寄坤二宫」取,但要把「这是中宫」说出来,不能让读者以为是坤二本宫。
137
+ const c = q.centerPalace;
138
+ return c && c.tianPan === stem ? c : null;
139
+ }
140
+
141
+ function palaceOfGate(q, gate) {
142
+ if (!gate) return null;
143
+ return (q.palaces || []).find((p) => p.gate === gate) || null;
144
+ }
145
+
146
+ function palaceOfGod(q, god) {
147
+ if (!god) return null;
148
+ return (q.palaces || []).find((p) => p.god === god) || null;
149
+ }
150
+
151
+ /** 把 subjects 里的取法解析成 { palace, symbol };解析不到就明确返回缺失原因 */
152
+ export function resolveSubject(q, sub) {
153
+ const si = q.siZhu || {};
154
+ const how = String(sub.how || '');
155
+ if (how === 'dayStem') {
156
+ const s = stemOf(si, 'day');
157
+ return { symbol: `日干 ${s}`, palace: palaceOfStem(q, s), missing: s ? '' : '四柱里没有日干' };
158
+ }
159
+ if (how === 'hourStem') {
160
+ const s = stemOf(si, 'time');
161
+ return { symbol: `时干 ${s}`, palace: palaceOfStem(q, s), missing: s ? '' : '四柱里没有时干' };
162
+ }
163
+ if (how === 'monthStem') {
164
+ const s = stemOf(si, 'month');
165
+ return { symbol: `月干 ${s}`, palace: palaceOfStem(q, s), missing: s ? '' : '四柱里没有月干' };
166
+ }
167
+ if (how === 'zhiFu') {
168
+ const g = q.zhiFu && q.zhiFu.gong;
169
+ return { symbol: `值符(${(q.zhiFu && q.zhiFu.star) || ''})`, palace: (q.palaces || []).find((p) => p.palace === String(g)) || null, missing: g ? '' : '没有值符落宫' };
170
+ }
171
+ if (how === 'zhiShi') {
172
+ const g = q.zhiShi && q.zhiShi.gong;
173
+ return { symbol: `值使(${(q.zhiShi && q.zhiShi.gate) || ''})`, palace: (q.palaces || []).find((p) => p.palace === String(g)) || null, missing: g ? '' : '没有值使落宫' };
174
+ }
175
+ if (how.startsWith('gate:')) {
176
+ const gate = how.slice(5);
177
+ const p = palaceOfGate(q, gate);
178
+ return { symbol: `${gate}`, palace: p, missing: p ? '' : `盘上没有${gate}` };
179
+ }
180
+ if (how.startsWith('god:')) {
181
+ const god = how.slice(4);
182
+ const p = palaceOfGod(q, god);
183
+ return { symbol: `${god}`, palace: p, missing: p ? '' : `盘上没有${god}` };
184
+ }
185
+ return { symbol: how, palace: null, missing: `未知取法 ${how}` };
186
+ }
187
+
188
+ /** 一宫的事实摘要:只写盘面上有的,不做判断 */
189
+ function palaceFacts(p) {
190
+ if (!p) return '(这一项在盘上定位不到)';
191
+ const f = PALACE_FIELD[p.palace] || { dir: '', field: '' };
192
+ const bits = [
193
+ `${p.palace} 宫(${f.dir})`,
194
+ p.center ? '中宫(无门无神,按寄宫取用)' : `门:${p.gate || '—'}${p.gateRelation && p.gateRelation.type ? `(${p.gateRelation.type}:${p.gateRelation.note || ''})` : ''}`,
195
+ `星:${p.star || '—'}`,
196
+ p.center ? '' : `神:${p.god || '—'}`,
197
+ `天盘 ${p.tianPan || '—'} / 地盘 ${p.diPan || '—'}${p.stemRelation && p.stemRelation.type ? `(${p.stemRelation.type})` : ''}`,
198
+ ];
199
+ if (p.kong) bits.push('空亡');
200
+ if (p.ma) bits.push('临马星');
201
+ if (p.ganYing && p.ganYing.name) bits.push(`十干应克:${p.ganYing.name}`);
202
+ return bits.join(' ');
203
+ }
204
+
205
+ const CHECK_QUESTIONS = [
206
+ '你说「我」是哪个符号?为什么是它,不是别的?',
207
+ '机会在哪一宫、阻碍在哪一宫、对方态度在哪一宫?各自依据哪一条关系(生、克、合、冲、空、墓)?',
208
+ '盘上有没有相反提示?你为什么采信这一边,而不是那一边?',
209
+ ];
210
+
211
+ /**
212
+ * 生成一段可直接交给模型的提示。
213
+ * @param {object} q 奇门盘(compute 结果里的 qimen 部分,或整个 r)
214
+ * @param {{question?:string, kind?:string, timeKnown?:boolean, askedAt?:string}} opts
215
+ * @returns {{version:string, kind:string, blocked:boolean, text:string, subjects:Array}}
216
+ */
217
+ export function buildQimenPrompt(q, opts = {}) {
218
+ const chart = q && q.palaces ? q : (q && q.qimen);
219
+ const kindKey = ASK_KINDS[opts.kind] ? opts.kind : 'other';
220
+ const kind = ASK_KINDS[kindKey];
221
+ const question = String(opts.question || '').replace(/\s+/g, ' ').trim().slice(0, 200);
222
+
223
+ // 第一步永远是时间:奇门以「起局那一刻」定盘,时刻不明就没有盘,不能默认拿现在起。
224
+ if (opts.timeKnown === false || !chart) {
225
+ return {
226
+ version: QIMEN_PROMPT_VERSION,
227
+ kind: kindKey,
228
+ blocked: true,
229
+ subjects: [],
230
+ text: [
231
+ '先别起局——时间还不确定。',
232
+ '',
233
+ '奇门是按「起局那一刻」排盘的,时刻差一个时辰,九宫里的门、星、神全换一遍。所以先确认:',
234
+ '1)这盘是按现在这一刻起,还是按你打算行动的那一刻起?',
235
+ '2)你说的日期时间,是钟表时间还是农历?在哪个城市(真太阳时会差几分钟到十几分钟)?',
236
+ '',
237
+ '确认之后,再用同一句话把要问的事说清楚:主语 + 时间 + 可验证的结果,例如「下周三之前,这份工作的书面 offer 能不能拿到」。',
238
+ '在那之前,我不排盘,也不猜。',
239
+ ].join('\n'),
240
+ };
241
+ }
242
+
243
+ const subjects = kind.subjects.map((s) => {
244
+ const got = resolveSubject(chart, s);
245
+ return { ...s, symbol: got.symbol, palace: got.palace, missing: got.missing };
246
+ });
247
+
248
+ const si = chart.siZhu || {};
249
+ const head = [
250
+ `# 奇门问事解读(提示版本 ${QIMEN_PROMPT_VERSION})`,
251
+ '',
252
+ '## 你要做的',
253
+ '先把这张盘翻译成「他能拿去用的话」,再给依据。顺序不能反:先结论(人话)→ 再盘面依据 → 再现实核对 → 最后说哪里不确定。',
254
+ '',
255
+ '## 一、先确认问的是哪一件事',
256
+ question
257
+ ? `他的问题是:「${question}」`
258
+ : '他还没说清要问什么。第一句先问清:要问的是哪一件具体的事(哪份工作、哪次沟通、哪笔合作),以及他希望看到什么结果。不要笼统答「运势如何」。',
259
+ `已按「${kind.label}」取用神。若他实际问的是别的事,先纠正类型再解读,不要硬套。`,
260
+ '',
261
+ '## 二、盘面事实(只能用这些,不许自行补算)',
262
+ `四柱:${si.year || '—'}年 ${si.month || '—'}月 ${si.day || '—'}日 ${si.time || '—'}时;局:${chart.dun || ''}${chart.juNumber || ''}局(${chart.yuan || ''});值符 ${(chart.zhiFu && chart.zhiFu.star) || '—'} 落 ${(chart.zhiFu && chart.zhiFu.gong) || '—'} 宫;值使 ${(chart.zhiShi && chart.zhiShi.gate) || '—'} 落 ${(chart.zhiShi && chart.zhiShi.gong) || '—'} 宫。`,
263
+ (chart.kongWang && chart.kongWang.length) ? `空亡:${chart.kongWang.join('、')}。` : '',
264
+ '',
265
+ '## 三、取哪个符号代表什么(每一项都要能说出「为什么是它」)',
266
+ ...subjects.map((s, i) => `${i + 1}. ${s.name} → ${s.symbol}${s.missing ? `(注意:${s.missing})` : ''}\n - 取法理由:${s.why}\n - 盘面:${palaceFacts(s.palace)}`),
267
+ '',
268
+ '## 四、怎么读状态(这是最容易偷懒的地方)',
269
+ '1. 落宫先说「这件事落在哪个领域」:',
270
+ ...subjects.filter((s) => s.palace).map((s) => ` - ${s.name}:${(PALACE_FIELD[s.palace.palace] || {}).field || ''}`),
271
+ '2. 所遇之门说明「这件事现在是什么形态」,不是吉凶:',
272
+ ...Object.entries(GATE_MEANING).map(([g, mean]) => ` - ${g}:${mean}`),
273
+ '3. 关系只认四种,且都要指到具体两宫:',
274
+ ' - 生:得助(谁生谁,说清方向)',
275
+ ' - 克:受制(谁克谁,说清方向)',
276
+ ' - 合:连接、聚合(合住也可能意味着被拴住)',
277
+ ' - 冲:变动、对立、打破(冲开也可能意味着解脱)',
278
+ '4. 空亡、入墓、门迫、击刑这些「减分项」,要么明确说它压低了什么,要么别提——不要念一遍名字就过去。',
279
+ '',
280
+ '## 五、开口之前先自检这三问',
281
+ ...CHECK_QUESTIONS.map((q2, i) => `${i + 1}. ${q2}`),
282
+ '三问答不全,就还没到能下结论的程度,先把答不出的那条写出来。',
283
+ '',
284
+ '## 六、最后必须回到现实核对',
285
+ ...REALITY_CHECK[kindKey].map((x, i) => `${i + 1}. ${x}`),
286
+ '盘面只能给方向;上面这些是「他明天可以去查、去问、去要」的东西。核对完再回来谈结论。',
287
+ '',
288
+ '## 七、表达要求',
289
+ ...STYLE_RULES.map((x) => `- ${x}`),
290
+ '',
291
+ '## 八、禁止',
292
+ ...HARD_RULES.map((x) => `- ${x}`),
293
+ '',
294
+ '## 九、输出格式',
295
+ '1. **先给你要的答案**:三到五句人话,直接回答他问的那件事。',
296
+ '2. **盘上依据**:按「我 / 对方或事体 / 环境」三块列,每块一句符号 + 一句人话解释。',
297
+ '3. **现实里该去核对什么**:抄上面第六节里适用的项,不要全抄。',
298
+ '4. **相反提示与不确定处**:盘上哪些地方指向另一个方向,你为什么这样权衡。',
299
+ '5. **一句提醒**:这是参考,不替代你自己去核实和决定。',
300
+ ].filter((x) => x !== '').join('\n');
301
+
302
+ return { version: QIMEN_PROMPT_VERSION, kind: kindKey, blocked: false, subjects, text: head };
303
+ }
@@ -0,0 +1,208 @@
1
+ // 六爻应期:把《增删卜易》各门类应期总注的条文逐条落成规则。
2
+ //
3
+ // 条文出处:各門類應期總注章第又二十六(本地底本见 test/fixtures/citation-sources.json,
4
+ // 键 https://zh.wikisource.org/wiki/增刪卜易/26又3)。原文 19 条,这里只实现能落到「地支」上的那些;
5
+ // 落不到的(如「独发独静」「卦之不明再占」)明确列在 NOT_IMPLEMENTED,不假装做了。
6
+ //
7
+ // 设计原则(和 yingqiWindow 一致):只给「该留意哪一支」,不换算公历日期。
8
+ // 一条卦例往往同时满足两条规则,所以输出「适用规则列表」+「触发支并集」,
9
+ // 并标出优先级最高的一条作主规则——对拍时分会算「并集命中」与「主规则命中」两个数,
10
+ // 不拿并集去冒充精度。
11
+ import { chongOf, heOf, jinTuiOf, zhiElementOf } from './liuyao.js';
12
+
13
+ const ZHI = '子丑寅卯辰巳午未申酉戌亥'.split('');
14
+
15
+ // 五行墓库:水/土墓辰、木墓未、火墓戌、金墓丑
16
+ const MU_OF = { 水: '辰', 土: '辰', 木: '未', 火: '戌', 金: '丑' };
17
+ // 五行生我
18
+ const SHENG_ME = { 木: '水', 火: '木', 土: '火', 金: '土', 水: '金' };
19
+ const SHENG_BY_ME = { 木: '火', 火: '土', 土: '金', 金: '水', 水: '木' };
20
+ const KE_ME = { 木: '金', 火: '水', 土: '木', 金: '火', 水: '土' };
21
+
22
+ /** 某五行对应的全部地支(金→申酉) */
23
+ const BRANCHES_OF = (() => {
24
+ const out = {};
25
+ for (const b of ZHI) {
26
+ const e = zhiElementOf(b);
27
+ (out[e] = out[e] || []).push(b);
28
+ }
29
+ return out;
30
+ })();
31
+
32
+ export const YINGQI_RULE_IDS = [
33
+ 'jing-zhi-chong', 'dong-he-zhi', 'taiwang-mu-chong', 'shuaijue-sheng-wang',
34
+ 'rumu-chong-kai', 'liuhe-xiang-ji', 'yuepo-xi-tian', 'xunkong-tian-chong',
35
+ 'jixiang-keshen-shouke', 'xiong-keshen-fengsheng', 'huajin-zhi-he',
36
+ 'huatui-ji-zhi-chong', 'bianyao-dongyao', 'shikong-yuanshen-zhi', 'shishuai-yuanshen-chong',
37
+ ];
38
+
39
+ /**
40
+ * 规则的排列顺序 = 原文《各門類應期總注章第又二十六》19 条的罗列次序。
41
+ *
42
+ * 这一条要写清楚,因为 0.59.0 之前这里是错的:
43
+ * 旧注释称「状态互斥优先级照应期总注的排列:空 → 破 → 动 → 墓 → 太旺 → 衰绝 → 静」,
44
+ * 但把原文逐条对下来,真实次序是——
45
+ * 静 → 动 → 太旺 → 衰绝 → 入墓 → 六合 → 月破 → 旬空 → 大象吉受克 → 大象凶受克
46
+ * → 元神总纲 → 化进 → 化退 → 独发独静 → 变爻动爻 → 远近当分 → 再占 → 世空元动 → 世衰元静
47
+ * 旧代码几乎是把这串倒过来排的,而「空 → 破 → … → 静」这个说法原文里没有,是编的。
48
+ *
49
+ * 更要紧的一点:**原文是分类罗列,不是效力排序**。前八条是「常、常、病×6」,
50
+ * 后面又混进化进/化退与变爻动爻,章内找不到任何「哪条先判」的表述。
51
+ * 所以 primary 只表示「按原文顺序第一条生效的规则」,不表示「最可能应验的那条」——
52
+ * 对拍(tools/backtest-yingqi.mjs)也显示按这个顺序取第一条,命中率与随机基线无显著差异。
53
+ */
54
+ export const RULE_ORDER = [
55
+ 'jing-zhi-chong',
56
+ 'dong-he-zhi',
57
+ 'taiwang-mu-chong',
58
+ 'shuaijue-sheng-wang',
59
+ 'rumu-chong-kai',
60
+ 'liuhe-xiang-ji',
61
+ 'yuepo-xi-tian',
62
+ 'xunkong-tian-chong',
63
+ 'jixiang-keshen-shouke',
64
+ 'xiong-keshen-fengsheng',
65
+ 'huajin-zhi-he',
66
+ 'huatui-ji-zhi-chong',
67
+ 'bianyao-dongyao',
68
+ 'shikong-yuanshen-zhi',
69
+ 'shishuai-yuanshen-chong',
70
+ ];
71
+
72
+ // 条文里给出、但落不到「某支」上的条目:列出来,避免被误以为已经做了
73
+ export const NOT_IMPLEMENTED = [
74
+ '問有應於獨發獨靜:詳獨發章(要判「獨發/獨靜」整卦只有一爻動,属整卦层,未并入单爻规则)',
75
+ '勿謂爻之不驗,遠近當分:遠事定之以年月,近事應之於日時(要按事情远近换尺,本模块不猜远近)',
76
+ '倘遇卦之不明,再占是一法(操作建议,不是应期取法)',
77
+ '元神來助來扶,貴看用神衰旺,忌神來沖克,須觀元气興衰(总纲,无独立取支)',
78
+ ];
79
+
80
+ const uniq = (xs) => [...new Set(xs.filter(Boolean))];
81
+
82
+ /**
83
+ * 判用神当前状态。
84
+ *
85
+ * 各项**独立计算、不互斥**——同一爻可以既旬空又月破、又入墓,各条规则各自判断要不要给支。
86
+ * 0.59.0 之前的注释写成「状态互斥优先级:空 → 破 → 动 → 墓 → 太旺 → 衰绝 → 静」,
87
+ * 原文里没有这层互斥关系,那段话是编的;这里没有选择逻辑,所以改注释不改行为。
88
+ * @param {object} line 用神爻(buildLiuyao 的 ben.lines 之一)
89
+ * @param {object} bianLine 同位变爻(不动则为 null)
90
+ * @param {{monthBranch?:string, dayBranch?:string, kong?:string[], sameElement?:number}} ctx
91
+ */
92
+ export function yingqiState(line, bianLine, ctx = {}) {
93
+ const branch = line.branch;
94
+ const element = line.element || zhiElementOf(branch);
95
+ const month = ctx.monthBranch || null;
96
+ const day = ctx.dayBranch || null;
97
+ const kong = ctx.kong || [];
98
+ const kongHit = kong.includes(branch);
99
+ const monthPo = !!(month && chongOf(branch) === month);
100
+ const moving = Boolean(line.moving);
101
+ const bianBranch = bianLine ? bianLine.branch : null;
102
+ const jt = bianBranch ? jinTuiOf(branch, bianBranch) : null;
103
+ const muBranch = MU_OF[element] || null;
104
+ const ruMu = !!(muBranch && (day === muBranch || month === muBranch));
105
+ // 六合:与月建或日辰作合,或动而化合(变爻与本爻六合)
106
+ const heWith = [];
107
+ if (month && heOf(branch) === month) heWith.push({ with: '月建', branch: month });
108
+ if (day && heOf(branch) === day) heWith.push({ with: '日辰', branch: day });
109
+ if (moving && bianBranch && heOf(branch) === bianBranch) heWith.push({ with: '变爻', branch: bianBranch });
110
+ // 太旺:月建同气(旺/相)且日辰生扶或比和
111
+ const monthTone = month ? (BRANCHES_OF[zhiElementOf(month)] || []) : [];
112
+ const monthSame = !!(month && zhiElementOf(month) === element);
113
+ const monthSheng = !!(month && zhiElementOf(month) === SHENG_ME[element]);
114
+ const daySame = !!(day && zhiElementOf(day) === element);
115
+ const daySheng = !!(day && zhiElementOf(day) === SHENG_ME[element]);
116
+ const taiWang = (monthSame || monthSheng) && (daySame || daySheng);
117
+ // 衰绝:被月建所克,且日辰不生不扶
118
+ const monthKe = !!(month && zhiElementOf(month) === KE_ME[element]);
119
+ const shuaiJue = monthKe && !(daySame || daySheng);
120
+
121
+ return {
122
+ branch, element, moving, bianBranch, jinTui: jt, kongHit, monthPo, ruMu,
123
+ heWith, taiWang, shuaiJue, monthSame, monthSheng, daySame, daySheng, monthKe,
124
+ muBranch, monthSameBranches: monthTone,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * 按应期总注给出触发支集合。
130
+ * @returns {{rules:Array<{id:string,label:string,quote:string,branches:string[]}>, primary:object|null, branches:string[], state:object}}
131
+ */
132
+ export function yingqiPredict(result, useGod, opts = {}) {
133
+ const st = {
134
+ monthBranch: opts.monthBranch || null,
135
+ dayBranch: opts.dayBranch || (result && result.dayGanZhi ? result.dayGanZhi.branch : null),
136
+ kong: Array.isArray(result && result.xunKong) ? result.xunKong : [],
137
+ };
138
+ if (!useGod || !useGod.branch) return { rules: [], primary: null, branches: [], state: null };
139
+
140
+ const bianLine = useGod.position && result && result.bian
141
+ ? (result.bian.lines || []).find((l) => l.position === useGod.position) || null
142
+ : null;
143
+ const s = yingqiState({ branch: useGod.branch, element: useGod.element, moving: useGod.moving }, bianLine, st);
144
+ const b = s.branch;
145
+ const rules = [];
146
+ const add = (id, label, quote, branches) => {
147
+ const bs = uniq(branches);
148
+ if (bs.length) rules.push({ id, label, quote, branches: bs });
149
+ };
150
+
151
+ if (s.kongHit) add('xunkong-tian-chong', '旬空填实/冲空', '旬空最愛塡沖', [b, chongOf(b)]);
152
+ if (s.monthPo) add('yuepo-xi-tian', '月破填实/逢合', '月破喜塡…又有逢午月應之,塡實之月則不破矣', [b, heOf(b)]);
153
+ if (s.moving && s.jinTui === '进') add('huajin-zhi-he', '化进神逢值逢合', '化進神、逢值逢合:如申動酉…有應申月日者,有應巳月日者', [b, heOf(b)]);
154
+ if (s.moving && s.jinTui === '退') add('huatui-ji-zhi-chong', '化退神忌值忌冲', '化退神、忌值忌沖:如酉化申…有應申月日者,有應寅日月者', [s.bianBranch, chongOf(s.bianBranch)]);
155
+ if (s.moving && !s.jinTui) add('bianyao-dongyao', '动爻/变爻之支', '問有應於變爻動爻:如爻臨戌土,變出酉金,有戌日應者,有酉日應者', [b, s.bianBranch]);
156
+ if (s.moving) add('dong-he-zhi', '动而逢合逢值', '動而逢合逢值:如主事爻臨子水發動,後遇丑日子日而應之', [heOf(b), b]);
157
+ if (s.ruMu) add('rumu-chong-kai', '入墓喜冲开', '入三墓俱喜沖開:如主事爻臨午火,假使火墓於戌,後逢辰日則應之', [chongOf(s.muBranch)]);
158
+ if (s.heWith.length) add('liuhe-xiang-ji', '六合宜相击(冲开)', '遇六合亦宜相擊…卽如主象臨子,與丑作合,後逢午未日應之', s.heWith.flatMap((h) => [chongOf(b), chongOf(h.branch)]));
159
+ if (s.taiWang) add('taiwang-mu-chong', '太旺逢墓逢冲', '太旺者逢墓逢沖:如主事爻臨午火…後逢亥子日應之,又有戌日應之乃火入墓也', [s.muBranch, chongOf(b)]);
160
+ if (s.shuaiJue) add('shuaijue-sheng-wang', '衰绝遇生遇旺', '衰絕者,遇生遇旺:如主事爻屬金,占卦於巳午月日…後逢土月日或至秋令當時則旺矣', [...(BRANCHES_OF[SHENG_ME[s.element]] || []), ...(BRANCHES_OF[s.element] || [])]);
161
+ if (!s.moving && !s.kongHit && !s.monthPo && !s.ruMu && !s.heWith.length) {
162
+ add('jing-zhi-chong', '静而逢值逢冲', '靜而逢值逢沖:如主事爻臨子水不動,後逢子日午日而應之', [b, chongOf(b)]);
163
+ }
164
+
165
+ // 元神/忌神两条要整卦信息:用神受克 → 待克神受制;用神衰而元静 → 待元神逢冲
166
+ const lines = (result && result.ben && result.ben.lines) || [];
167
+ const keBranch = (() => {
168
+ const keEl = KE_ME[s.element];
169
+ const hit = lines.filter((l) => (l.element || zhiElementOf(l.branch)) === keEl && l.moving);
170
+ return hit.length ? hit[0].branch : null;
171
+ })();
172
+ const yuanBranch = (() => {
173
+ const yuanEl = SHENG_ME[s.element];
174
+ const hit = lines.filter((l) => ((l.element || zhiElementOf(l.branch)) === yuanEl));
175
+ return hit.length ? hit[0].branch : null;
176
+ })();
177
+ if (keBranch && (s.shuaiJue || s.kongHit || s.monthPo)) {
178
+ add('jixiang-keshen-shouke', '待克神受克', '大象吉而受克,須待克神受克…後逢申酉日沖克克神則吉', [chongOf(keBranch)]);
179
+ }
180
+ if (yuanBranch && s.shuaiJue && !s.moving) {
181
+ add('shishuai-yuanshen-chong', '元神逢冲', '世衰元靜,必然是元气逢沖…後逢巳日成事', [chongOf(yuanBranch)]);
182
+ }
183
+ if (yuanBranch && s.kongHit) {
184
+ add('shikong-yuanshen-zhi', '待元神逢值', '世空元動,須待元神逢值…主亥日得財', [yuanBranch]);
185
+ }
186
+
187
+ // 按原文罗列次序排(见 RULE_ORDER),primary 只是「第一条生效的」,不是「最可能应验的」
188
+ const rankOf = (id) => {
189
+ const i = RULE_ORDER.indexOf(id);
190
+ return i < 0 ? RULE_ORDER.length : i;
191
+ };
192
+ rules.sort((a, b) => rankOf(a.id) - rankOf(b.id));
193
+
194
+ const primary = rules[0] || null;
195
+ return {
196
+ rules,
197
+ primary,
198
+ branches: uniq(rules.flatMap((r) => r.branches)),
199
+ state: s,
200
+ };
201
+ }
202
+
203
+ /** 把预测结果压成一句可读的话(报告与面板共用措辞) */
204
+ export function yingqiSay(pred) {
205
+ if (!pred || !pred.branches.length) return '应期:无可用触发支(缺用神或月建)';
206
+ const main = pred.primary ? pred.primary.label : '无';
207
+ return `应期:主看「${main}」,留意 ${pred.branches.join('、')} 支(共 ${pred.branches.length} 支)`;
208
+ }