dsh-metaphysics 0.38.2 → 0.50.1
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.
- package/CHANGELOG.md +315 -8
- package/README.md +353 -342
- package/client.js +2699 -177
- package/engine/almanac.js +7 -6
- package/engine/bazi-words.js +174 -0
- package/engine/bazi.js +62 -0
- package/engine/classics.js +175 -9
- package/engine/crosscheck.js +29 -3
- package/engine/fengshui.js +197 -11
- package/engine/followup.js +29 -5
- package/engine/geju.js +90 -5
- package/engine/index.js +119 -13
- package/engine/interpret-knowledge.js +48 -12
- package/engine/interpret-plain.js +380 -322
- package/engine/interpret.js +328 -35
- package/engine/liuyao.js +185 -0
- package/engine/name-data.js +229 -0
- package/engine/name.js +234 -9
- package/engine/ziwei-words.js +245 -0
- package/engine/ziwei.js +254 -26
- package/index.js +65 -9
- package/package.json +2 -2
- package//345/221/275/347/220/206/346/216/222/347/233/230.html +3419 -527
package/engine/fengshui.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// 参照公开通行表实现(与常见八宅/玄空入门资料一致),年紫白用纯公式,不依赖任何第三方历法库。
|
|
4
4
|
import { partsToJd, termInstantJd } from './calendar.js';
|
|
5
5
|
import { applyDst } from './dst.js';
|
|
6
|
+
import { fail } from './fail.js';
|
|
6
7
|
|
|
7
8
|
/** 九星名称表:数 → {name, color, element, star(北斗名), lucky}。 */
|
|
8
9
|
export const NINE_STARS = {
|
|
@@ -56,7 +57,7 @@ const MOUNTAIN_INDEX = new Map(MOUNTAINS_24.map((x, i) => [x.m, i]));
|
|
|
56
57
|
export function mountainInfo(name) {
|
|
57
58
|
const i = MOUNTAIN_INDEX.get(name);
|
|
58
59
|
if (i === undefined) {
|
|
59
|
-
throw
|
|
60
|
+
throw fail('E_INPUT', `坐向必须是 24 山之一(${MOUNTAINS_24.map((x) => x.m).join('')}),收到「${name}」——请按罗盘 24 山报坐或报向,不要自造方位词。`);
|
|
60
61
|
}
|
|
61
62
|
return { ...MOUNTAINS_24[i], index: i };
|
|
62
63
|
}
|
|
@@ -143,6 +144,127 @@ export function yearlyFlying(year) {
|
|
|
143
144
|
|
|
144
145
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
145
146
|
|
|
147
|
+
// ── 玄空飞星(下卦) ─────────────────────────────────────────────
|
|
148
|
+
// 口径:三元九运每运 20 年,1864 年起一运;运盘当运星入中顺飞。
|
|
149
|
+
// 山盘/向盘:取坐宫/向宫的运星入中,按「入中运星本宫同元龙」的阴阳定顺逆(五黄入中按本山阴阳)。
|
|
150
|
+
// 这一步是玄空挨星的关键:不是按坐山本身的阴阳,而是按入中运星原宫对应元龙的阴阳;
|
|
151
|
+
// 按本山阴阳会把旺山旺向算成每运固定的六局,与通行二十四山旺山旺向表对不上。
|
|
152
|
+
|
|
153
|
+
/** 三元九运:1864 起每 20 年一运,九运一轮(180 年)。 */
|
|
154
|
+
export function periodOfYear(year) {
|
|
155
|
+
const k = Math.floor((year - 1864) / 20);
|
|
156
|
+
const period = ((k % 9) + 9) % 9 + 1;
|
|
157
|
+
const start = 1864 + 20 * k;
|
|
158
|
+
return { period, start, end: start + 19, label: `${period}运(${start}–${start + 19})` };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 三元龙阴阳:天元/人元四隅为阳、四正为阴;地元四正为阳、四隅为阴。 */
|
|
162
|
+
const YANG_MOUNTAINS = new Set(['乾', '巽', '艮', '坤', '甲', '庚', '壬', '丙', '寅', '申', '巳', '亥']);
|
|
163
|
+
const YUAN_NAME = ['地元', '天元', '人元'];
|
|
164
|
+
|
|
165
|
+
/** 24 山 → 三元龙 + 阴阳(MOUNTAINS_24 每卦三山依次为 地元/天元/人元)。 */
|
|
166
|
+
export const MOUNTAIN_META = {};
|
|
167
|
+
MOUNTAINS_24.forEach((x, i) => {
|
|
168
|
+
MOUNTAIN_META[x.m] = { ...x, yuan: YUAN_NAME[i % 3], yinYang: YANG_MOUNTAINS.has(x.m) ? '阳' : '阴' };
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/** 飞星轨迹:中 → 乾 → 兑 → 艮 → 离 → 坎 → 坤 → 震 → 巽(即洛书 5→6→7→8→9→1→2→3→4)。 */
|
|
172
|
+
const FLY_SEQ = ['中', '乾', '兑', '艮', '离', '坎', '坤', '震', '巽'];
|
|
173
|
+
const NUM_TO_GUA = ['坎', '坤', '震', '巽', '中', '乾', '兑', '艮', '离']; // 洛书数 1–9 本位卦
|
|
174
|
+
const YUAN_INDEX = { 地元: 0, 天元: 1, 人元: 2 };
|
|
175
|
+
|
|
176
|
+
const mountainsOfGua = (gua) => MOUNTAINS_24.filter((x) => x.gua === gua).map((x) => MOUNTAIN_META[x.m]);
|
|
177
|
+
|
|
178
|
+
/** 入中星按轨迹飞布:forward=true 顺飞,false 逆飞。返回 宫 → 星数。 */
|
|
179
|
+
function fly(center, forward) {
|
|
180
|
+
const out = {};
|
|
181
|
+
for (let i = 0; i < 9; i++) {
|
|
182
|
+
const v = ((center - 1 + (forward ? i : -i)) % 9 + 9) % 9 + 1;
|
|
183
|
+
out[FLY_SEQ[i]] = v;
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 入中运星定顺逆:取本宫(运星本位卦)同元龙之山的阴阳;五黄入中按本山阴阳。 */
|
|
189
|
+
function flightOf(mount, center) {
|
|
190
|
+
if (center === 5) {
|
|
191
|
+
return { forward: mount.yinYang === '阳', basis: `五黄入中,按${mount.m}本山${mount.yuan}${mount.yinYang}论` };
|
|
192
|
+
}
|
|
193
|
+
const home = NUM_TO_GUA[center - 1];
|
|
194
|
+
const hm = mountainsOfGua(home)[YUAN_INDEX[mount.yuan]];
|
|
195
|
+
return { forward: hm.yinYang === '阳', basis: `${center}${home}宫取${mount.yuan}${hm.m}(${hm.yinYang})` };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 通行二十四山旺山旺向表(每项含正反两向;一运、九运无局)。 */
|
|
199
|
+
export const WANG_SHAN_WANG_XIANG = {
|
|
200
|
+
1: [],
|
|
201
|
+
2: [['乾', '巽'], ['亥', '巳'], ['丑', '未']],
|
|
202
|
+
3: [['卯', '酉'], ['乙', '辛'], ['辰', '戌']],
|
|
203
|
+
4: [['艮', '坤'], ['寅', '申'], ['甲', '庚']],
|
|
204
|
+
5: [['子', '午'], ['癸', '丁'], ['卯', '酉'], ['乙', '辛'], ['丑', '未'], ['辰', '戌']],
|
|
205
|
+
6: [['艮', '坤'], ['寅', '申'], ['甲', '庚']],
|
|
206
|
+
7: [['卯', '酉'], ['乙', '辛'], ['辰', '戌']],
|
|
207
|
+
8: [['乾', '巽'], ['亥', '巳'], ['丑', '未']],
|
|
208
|
+
9: [],
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const XUANKONG_SOURCES = [
|
|
212
|
+
'三元九运(1864 年起一运、每运 20 年,九运 180 年一轮)',
|
|
213
|
+
'玄空下卦挨星(运盘顺飞;山盘/向盘以入中运星本宫同元龙阴阳定顺逆,五黄入中按本山阴阳)',
|
|
214
|
+
'二十四山旺山旺向通行表(每项含正反两向;一运、九运无局)',
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
export const XUANKONG_DISCLAIMER = '玄空下卦为传统通行口径的本地排盘,属民俗参考,不构成现场勘舆结论,仅供娱乐;替卦/兼向、年月日时飞星与形峦不在本工具范围内。';
|
|
218
|
+
|
|
219
|
+
/** 玄空下卦排盘:运盘 + 山盘 + 向盘 + 当运星 + 合十 + 表法旺山旺向。 */
|
|
220
|
+
export function xuankongChart(period, sitting, facing) {
|
|
221
|
+
const sit = MOUNTAIN_META[sitting];
|
|
222
|
+
const face = MOUNTAIN_META[facing];
|
|
223
|
+
const yun = fly(period, true);
|
|
224
|
+
const sCenter = yun[sit.gua];
|
|
225
|
+
const fCenter = yun[face.gua];
|
|
226
|
+
const sFly = flightOf(sit, sCenter);
|
|
227
|
+
const fFly = flightOf(face, fCenter);
|
|
228
|
+
const shan = fly(sCenter, sFly.forward);
|
|
229
|
+
const xiang = fly(fCenter, fFly.forward);
|
|
230
|
+
const palaces = PALACE_ORDER.map((palace, i) => ({
|
|
231
|
+
palace,
|
|
232
|
+
direction: palace === '中' ? '中宫' : PALACE_DIRECTION[palace],
|
|
233
|
+
luoshu: i + 1,
|
|
234
|
+
yun: yun[palace],
|
|
235
|
+
shan: shan[palace],
|
|
236
|
+
xiang: xiang[palace],
|
|
237
|
+
}));
|
|
238
|
+
const wheres = (map) => PALACE_ORDER.filter((p) => map[p] === period);
|
|
239
|
+
const pairs = WANG_SHAN_WANG_XIANG[period];
|
|
240
|
+
const hit = pairs.find(([a, b]) => (sit.m === a && face.m === b) || (sit.m === b && face.m === a));
|
|
241
|
+
const byChart = shan[sit.gua] === period && xiang[face.gua] === period;
|
|
242
|
+
const note = byChart && hit
|
|
243
|
+
? '盘面与表法一致:本局到山到向。'
|
|
244
|
+
: byChart
|
|
245
|
+
? '盘面到山到向,但不在通行表法所列之局(表法与盘面口径不同,并列不合并)。'
|
|
246
|
+
: hit
|
|
247
|
+
? '通行表法列为旺山旺向,但本盘当运山星/向星未同时落坐向(表法与盘面口径不同,并列不合并)。'
|
|
248
|
+
: '本局既未到山到向,也不在表法旺山旺向之列。';
|
|
249
|
+
return {
|
|
250
|
+
palaces,
|
|
251
|
+
sitting: sit,
|
|
252
|
+
facing: face,
|
|
253
|
+
yun,
|
|
254
|
+
shan,
|
|
255
|
+
xiang,
|
|
256
|
+
yunCenter: period,
|
|
257
|
+
shanCenter: { star: sCenter, ...sFly },
|
|
258
|
+
xiangCenter: { star: fCenter, ...fFly },
|
|
259
|
+
dangYun: { shan: wheres(shan), xiang: wheres(xiang) },
|
|
260
|
+
heshi: {
|
|
261
|
+
shan: PALACE_ORDER.filter((p) => shan[p] + yun[p] === 10),
|
|
262
|
+
xiang: PALACE_ORDER.filter((p) => xiang[p] + yun[p] === 10),
|
|
263
|
+
},
|
|
264
|
+
wangShanWangXiang: { byChart, byTable: Boolean(hit), table: pairs.map(([a, b]) => `${a}${b}`), note },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
146
268
|
/** DST 校正出生日期(1986–1991),返回校正后的年月日。 */
|
|
147
269
|
function civilDate(input) {
|
|
148
270
|
const dst = input.dst === false
|
|
@@ -161,19 +283,21 @@ const DISCLAIMER = '八宅与飞星为传统通行口径的本地计算,属民
|
|
|
161
283
|
|
|
162
284
|
/**
|
|
163
285
|
* 风水主函数。
|
|
164
|
-
* @param {{action?:'natal'|'advise', year?:number, month?:number, day?:number, gender?:'male'|'female',
|
|
165
|
-
* fortuneYear?:number, sitting?:string, facing?:string, dst?:boolean}} input
|
|
286
|
+
* @param {{action?:'natal'|'advise'|'xuankong', year?:number, month?:number, day?:number, gender?:'male'|'female',
|
|
287
|
+
* fortuneYear?:number, sitting?:string, facing?:string, period?:number, dst?:boolean}} input
|
|
166
288
|
* natal(默认):要生日 + 性别 + 流年(缺流年则只算命卦与八星)。
|
|
167
289
|
* advise:要坐山或向山(24 山,给一即可);出生信息可选,给了才做命宅配。
|
|
290
|
+
* xuankong:要坐山或向山;运由 period(1–9)或 fortuneYear 定,缺省按九运并注明。
|
|
168
291
|
*/
|
|
169
292
|
export function buildFengshui(input) {
|
|
170
293
|
if (input.action === 'advise') return buildAdvise(input);
|
|
294
|
+
if (input.action === 'xuankong') return buildXuankong(input);
|
|
171
295
|
const gender = input.gender;
|
|
172
296
|
if (gender !== 'male' && gender !== 'female') {
|
|
173
|
-
throw
|
|
297
|
+
throw fail('E_INPUT', '风水命卦需要性别:男女命卦算法不同,不可缺省');
|
|
174
298
|
}
|
|
175
299
|
if (!Number.isInteger(input.year) || !Number.isInteger(input.month) || !Number.isInteger(input.day)) {
|
|
176
|
-
throw
|
|
300
|
+
throw fail('E_INPUT', '风水命卦需要完整出生年月日(公历)——只知道年份不够:命卦按立春界划分出生年');
|
|
177
301
|
}
|
|
178
302
|
const civil = civilDate(input);
|
|
179
303
|
const guaYear = guaYearOf(civil.year, civil.month, civil.day);
|
|
@@ -191,7 +315,7 @@ export function buildFengshui(input) {
|
|
|
191
315
|
fortuneYear,
|
|
192
316
|
...(civil.dst.applied ? { dst: civil.dst.note } : {}),
|
|
193
317
|
...(fortuneYear === null ? { note: '未给流年:未算年紫白飞星(需要时补 fortuneYear)。' } : {}),
|
|
194
|
-
scope: '八宅命卦 + 大游年八星 +
|
|
318
|
+
scope: '八宅命卦 + 大游年八星 + 年紫白;玄空飞星见 action=xuankong;不含形峦与现场勘舆。',
|
|
195
319
|
},
|
|
196
320
|
mingGua: {
|
|
197
321
|
num,
|
|
@@ -208,22 +332,28 @@ export function buildFengshui(input) {
|
|
|
208
332
|
};
|
|
209
333
|
}
|
|
210
334
|
|
|
211
|
-
/**
|
|
212
|
-
function
|
|
335
|
+
/** 坐向解析:至少给坐或向;两者都给则必须对宫。 */
|
|
336
|
+
function resolveSittingFacing(input) {
|
|
213
337
|
const hasSitting = typeof input.sitting === 'string' && input.sitting.trim() !== '';
|
|
214
338
|
const hasFacing = typeof input.facing === 'string' && input.facing.trim() !== '';
|
|
215
339
|
if (!hasSitting && !hasFacing) {
|
|
216
|
-
throw
|
|
340
|
+
throw fail('E_INPUT', '缺少坐向:sitting(坐山)或 facing(向山)至少给一个——按罗盘 24 山报坐或报向,例如「坐北朝南」对应坐子向午');
|
|
217
341
|
}
|
|
218
342
|
if (hasSitting && hasFacing) {
|
|
219
343
|
const s = mountainInfo(input.sitting.trim());
|
|
220
344
|
const f = mountainInfo(input.facing.trim());
|
|
221
345
|
if ((s.index + 12) % 24 !== f.index) {
|
|
222
|
-
throw
|
|
346
|
+
throw fail('E_INPUT', `坐向不自洽:坐${s.m}(${s.deg}°)与向${f.m}(${f.deg}°)不是对宫(相差应 180°,实测 ${((f.deg - s.deg) + 360) % 360}°)——请核对罗盘读数。`);
|
|
223
347
|
}
|
|
224
348
|
}
|
|
225
349
|
const sitting = hasSitting ? mountainInfo(input.sitting.trim()) : sittingFromFacing(input.facing.trim());
|
|
226
350
|
const facing = MOUNTAINS_24[(sitting.index + 12) % 24];
|
|
351
|
+
return { sitting, facing };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 宅卦坐向 + 门主灶厕布局倾向 + 命宅配。 */
|
|
355
|
+
function buildAdvise(input) {
|
|
356
|
+
const { sitting, facing } = resolveSittingFacing(input);
|
|
227
357
|
const houseGua = sitting.gua;
|
|
228
358
|
const stars = eightStars(houseGua);
|
|
229
359
|
const lucky = LUCKY_STARS.map((s) => stars[s]);
|
|
@@ -277,7 +407,7 @@ function buildAdvise(input) {
|
|
|
277
407
|
facing: `${facing.m}山(罗盘 ${facing.deg}°)`,
|
|
278
408
|
houseGua,
|
|
279
409
|
houseGroup: `${guaGroup(houseGua)}宅`,
|
|
280
|
-
scope: '
|
|
410
|
+
scope: '门主灶厕走向按八宅通行口径给倾向,玄空飞星见 action=xuankong;不含形峦、不是现场勘舆结论。',
|
|
281
411
|
},
|
|
282
412
|
houseStars: starChart,
|
|
283
413
|
layout,
|
|
@@ -286,3 +416,59 @@ function buildAdvise(input) {
|
|
|
286
416
|
disclaimer: DISCLAIMER,
|
|
287
417
|
};
|
|
288
418
|
}
|
|
419
|
+
|
|
420
|
+
/** 玄空下卦:坐向 + 运 → 九宫盘、当运星、合十、表法旺山旺向。 */
|
|
421
|
+
function buildXuankong(input) {
|
|
422
|
+
const { sitting, facing } = resolveSittingFacing(input);
|
|
423
|
+
let period;
|
|
424
|
+
let yearInfo = null;
|
|
425
|
+
let note;
|
|
426
|
+
if (input.period !== undefined && input.period !== null && !Number.isInteger(input.period)) {
|
|
427
|
+
throw fail('E_INPUT', `运必须是 1–9 的整数(三元九运),收到「${input.period}」——一运 1864 起、九运 2024–2043。`);
|
|
428
|
+
}
|
|
429
|
+
if (Number.isInteger(input.period)) {
|
|
430
|
+
if (input.period < 1 || input.period > 9) {
|
|
431
|
+
throw fail('E_INPUT', `运必须是 1–9 的整数(三元九运),收到「${input.period}」——一运 1864 起、九运 2024–2043。`);
|
|
432
|
+
}
|
|
433
|
+
period = input.period;
|
|
434
|
+
} else if (Number.isInteger(input.fortuneYear)) {
|
|
435
|
+
yearInfo = periodOfYear(input.fortuneYear);
|
|
436
|
+
period = yearInfo.period;
|
|
437
|
+
} else {
|
|
438
|
+
period = 9;
|
|
439
|
+
note = '未给运:默认按九运(2024–2043)排盘;可传 period(1–9)或 fortuneYear 指定。';
|
|
440
|
+
}
|
|
441
|
+
const chart = xuankongChart(period, sitting.m, facing.m);
|
|
442
|
+
const sitMeta = MOUNTAIN_META[sitting.m];
|
|
443
|
+
const faceMeta = MOUNTAIN_META[facing.m];
|
|
444
|
+
const center = (c) => ({ star: c.star, name: NINE_STARS[c.star].name, fly: c.forward ? '顺飞' : '逆飞', basis: c.basis });
|
|
445
|
+
const where = (list) => list.map((p) => `${p}宫${PALACE_DIRECTION[p]}`).join('、') || '无';
|
|
446
|
+
return {
|
|
447
|
+
system: 'fengshui',
|
|
448
|
+
meta: {
|
|
449
|
+
action: 'xuankong',
|
|
450
|
+
period,
|
|
451
|
+
periodLabel: yearInfo ? yearInfo.label : `${period}运`,
|
|
452
|
+
...(yearInfo ? { year: input.fortuneYear, periodStart: yearInfo.start, periodEnd: yearInfo.end } : {}),
|
|
453
|
+
sitting: `${sitting.m}山(罗盘 ${sitting.deg}°)`,
|
|
454
|
+
facing: `${facing.m}山(罗盘 ${facing.deg}°)`,
|
|
455
|
+
sittingGua: sitting.gua,
|
|
456
|
+
facingGua: facing.gua,
|
|
457
|
+
sittingYuan: `${sitMeta.yuan}${sitMeta.yinYang}`,
|
|
458
|
+
facingYuan: `${faceMeta.yuan}${faceMeta.yinYang}`,
|
|
459
|
+
shanCenter: center(chart.shanCenter),
|
|
460
|
+
xiangCenter: center(chart.xiangCenter),
|
|
461
|
+
...(note ? { note } : {}),
|
|
462
|
+
scope: '玄空下卦:运盘 + 山盘 + 向盘 + 当运星 + 合十 + 表法旺山旺向;不含替卦/兼向、不含年月日时飞星、不含形峦与现场勘舆。',
|
|
463
|
+
},
|
|
464
|
+
palaces: chart.palaces,
|
|
465
|
+
dangYun: {
|
|
466
|
+
shan: { palaces: chart.dangYun.shan, text: where(chart.dangYun.shan) },
|
|
467
|
+
xiang: { palaces: chart.dangYun.xiang, text: where(chart.dangYun.xiang) },
|
|
468
|
+
},
|
|
469
|
+
heshi: { shan: chart.heshi.shan, xiang: chart.heshi.xiang },
|
|
470
|
+
wangShanWangXiang: chart.wangShanWangXiang,
|
|
471
|
+
sources: XUANKONG_SOURCES,
|
|
472
|
+
disclaimer: XUANKONG_DISCLAIMER,
|
|
473
|
+
};
|
|
474
|
+
}
|
package/engine/followup.js
CHANGED
|
@@ -199,30 +199,54 @@ const SYSTEM_LABEL = {
|
|
|
199
199
|
* 客户端把这段文字写进对话输入框并提交,由对话里的模型基于盘面回答。
|
|
200
200
|
*
|
|
201
201
|
* @param {object} r compute 的结果(至少要带 system 与 summary)
|
|
202
|
-
* @param {{question?:string, maxChars?:number}} [opts]
|
|
202
|
+
* @param {{question?:string, maxChars?:number, interpret?:object}} [opts]
|
|
203
|
+
* interpret 传面板已算好的解析对象(it)时,把其中的白话结论一并交给模型,
|
|
204
|
+
* 让对话开场与面板里的「先答所问 / 说人话」是同一个口径;不传则不加这一段。
|
|
203
205
|
* @returns {{text:string, question:string, system:string, label:string, chars:number, truncated:boolean}}
|
|
204
206
|
*/
|
|
205
207
|
export function askPrompt(r, opts = {}) {
|
|
206
208
|
const label = SYSTEM_LABEL[r && r.system] || (r && r.system) || '未知体系';
|
|
207
209
|
const asked = String((opts && opts.question) || '').trim();
|
|
208
|
-
const question = asked || '
|
|
210
|
+
const question = asked || '请先用一句白话讲清这张盘的主线,再指出最值得注意的一两处,并说明每处的具体依据。';
|
|
209
211
|
const limit = Number.isFinite(opts && opts.maxChars) && opts.maxChars >= 400 ? opts.maxChars : 3000;
|
|
210
212
|
const raw = String((r && r.summary) || '').trim();
|
|
211
213
|
const truncated = raw.length > limit;
|
|
212
214
|
const summary = truncated ? `${raw.slice(0, limit)}…(摘要已截断)` : raw;
|
|
213
215
|
|
|
216
|
+
// 面板里已经算过一遍解析:直接取「直答 / 说人话」,避免模型开场另起一套说法
|
|
217
|
+
const it = (opts && typeof opts.interpret === 'object' && opts.interpret)
|
|
218
|
+
|| (r && typeof r.interpret === 'object' && r.interpret) || null;
|
|
219
|
+
const answer = String((it && it.answer) || '').trim();
|
|
220
|
+
const plain = String((it && it.plain) || '').trim();
|
|
221
|
+
const draft = [];
|
|
222
|
+
if (answer) draft.push(`直答:${answer}`);
|
|
223
|
+
if (plain) draft.push(`说人话:${plain}`);
|
|
224
|
+
|
|
214
225
|
const lines = [
|
|
215
226
|
'【命理追问】下面这张盘是命理插件刚排好的,请就着它回答我的问题;不用重新排盘,盘面字段已经够用。',
|
|
216
227
|
`体系:${label}`,
|
|
217
228
|
`所问:${question}`,
|
|
218
229
|
'',
|
|
230
|
+
...(draft.length ? [
|
|
231
|
+
'——开场结论(插件已按同一套规则算好,开场先沿用这句;措辞可改,口径别换)——',
|
|
232
|
+
...draft,
|
|
233
|
+
'',
|
|
234
|
+
] : []),
|
|
219
235
|
'——盘面摘要——',
|
|
220
236
|
summary || '(这张盘没有摘要字段,请先让我重新排一次。)',
|
|
221
237
|
'',
|
|
222
238
|
'——回答要求——',
|
|
223
|
-
'
|
|
224
|
-
|
|
225
|
-
|
|
239
|
+
'读者不懂命理。按下面的骨架答;任何一句他看不懂,就算没写完:',
|
|
240
|
+
draft.length
|
|
241
|
+
? '1) 开头一句结论:先沿用上面的「开场结论」,再用一句不用术语的话说清这张盘的主线——只读这一句也该有收获;'
|
|
242
|
+
: '1) 开头一句结论:不用术语,一句话说清这张盘的主线,只读这一句也该有收获;',
|
|
243
|
+
'2) 判断最多两条,按重要性排序,每条分四行写:',
|
|
244
|
+
' 盘面|只引盘面已有的字(具体的柱、宫或爻、字段名),不掺解释;',
|
|
245
|
+
' 白话|把上一行的术语当场翻成日常话——调候 / 化气 / 通关 / 空亡 / 争合 这类词必须落地,一个词只翻一次,不连续堆三个以上;',
|
|
246
|
+
' 倾向|说「更可能……」,讲清它在现实里大概意味着什么,用概率语气;',
|
|
247
|
+
' 边界|这条在什么情况下不成立;盘面没给的,直接写「这盘里看不出来」;',
|
|
248
|
+
'3) 收尾一句「如果只记一件事」:给一条能核对、能动手的观察点,并说明它出自上面哪条盘面依据;',
|
|
249
|
+
'4) 别把传统说法当成已证实的事实:不下绝对断言,也不用「肯定」「绝对」这类词,更不写「格局很好」「运程不顺」这种没有信息的空话。',
|
|
226
250
|
];
|
|
227
251
|
const text = lines.join('\n');
|
|
228
252
|
return { text, question, system: (r && r.system) || '', label, chars: text.length, truncated };
|
package/engine/geju.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { judgeClimate, arbitrate } from './tiaohou.js';
|
|
11
11
|
import { qtOf } from './qiongtong.js';
|
|
12
|
+
import { sanheGroupOf } from './sanhe.js';
|
|
12
13
|
|
|
13
14
|
const STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
|
14
15
|
const BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
|
@@ -87,6 +88,25 @@ export function elementRoles(dayStem) {
|
|
|
87
88
|
|
|
88
89
|
// ── 月令格 ───────────────────────────────────────────────────
|
|
89
90
|
|
|
91
|
+
// 四墓(杂气)月:辰戌丑未所藏多、用神不一,取用先看透干,再看会支。
|
|
92
|
+
// 会支只认三合局——子平真诠十六「逢申与子会局」的例子即三合,三会方与兼透兼用仍不纳入。
|
|
93
|
+
const ZA_MONTHS = ['辰', '未', '戌', '丑'];
|
|
94
|
+
|
|
95
|
+
function huiCandidate(pillars, monthBranch, cands) {
|
|
96
|
+
if (!ZA_MONTHS.includes(monthBranch)) return null;
|
|
97
|
+
const group = sanheGroupOf(monthBranch);
|
|
98
|
+
if (!group) return null;
|
|
99
|
+
const others = ['year', 'day', 'hour'];
|
|
100
|
+
const need = group.branches.filter((b) => b !== monthBranch);
|
|
101
|
+
if (!need.every((b) => others.some((k) => pillars[k].branch === b))) return null;
|
|
102
|
+
const cand = cands.find((c) => STEM_ELEMENT[c.stem] === group.element);
|
|
103
|
+
if (!cand) return null;
|
|
104
|
+
const where = others
|
|
105
|
+
.filter((k) => need.includes(pillars[k].branch))
|
|
106
|
+
.map((k) => `${({ year: '年', day: '日', hour: '时' })[k]}支${pillars[k].branch}`);
|
|
107
|
+
return { ...cand, group, where };
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
function monthPattern(pillars, dayStem, monthBranch) {
|
|
91
111
|
const hidden = HIDDEN[monthBranch] || [];
|
|
92
112
|
const cands = hidden.map((stem, i) => ({
|
|
@@ -98,10 +118,13 @@ function monthPattern(pillars, dayStem, monthBranch) {
|
|
|
98
118
|
const others = ['year', 'month', 'hour'];
|
|
99
119
|
const exposedKey = others.find((k) => cands.some((c) => c.stem === pillars[k].stem));
|
|
100
120
|
const base = cands[0];
|
|
101
|
-
const
|
|
121
|
+
const hui = exposedKey ? null : huiCandidate(pillars, monthBranch, cands);
|
|
122
|
+
const use = exposedKey ? cands.find((c) => c.stem === pillars[exposedKey].stem) : (hui || base);
|
|
123
|
+
const usedBy = exposedKey ? '透干' : (hui ? '会支' : '本气');
|
|
102
124
|
const exposedPos = exposedKey ? { year: '年', month: '月', hour: '时' }[exposedKey] : null;
|
|
103
125
|
|
|
104
|
-
|
|
126
|
+
// 建禄只认日干的禄位;戊土辰月、己土未月这类「本气比肩但不在禄位」的杂气月不再冒称建禄
|
|
127
|
+
if (base.god === '比肩' && LUSHEN_BRANCH[dayStem] === monthBranch) {
|
|
105
128
|
return {
|
|
106
129
|
name: '建禄格',
|
|
107
130
|
kind: '月令格',
|
|
@@ -118,10 +141,31 @@ function monthPattern(pillars, dayStem, monthBranch) {
|
|
|
118
141
|
}
|
|
119
142
|
return { name: '月劫格', kind: '月令格', evidence: `月令${monthBranch}本气${base.stem}为劫财` };
|
|
120
143
|
}
|
|
144
|
+
if (usedBy === '本气' && base.god === '比肩') {
|
|
145
|
+
return {
|
|
146
|
+
name: '比肩格',
|
|
147
|
+
kind: '月令格',
|
|
148
|
+
evidence: `月令${monthBranch}藏${hidden.join('')},本气${base.stem}为比肩,但${dayStem}禄在${LUSHEN_BRANCH[dayStem]}、不在${monthBranch},属杂气月比肩当权`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// 子平十六:「透乙则用月劫」——杂气月由透干或会支取到劫财时,仍作月劫论
|
|
152
|
+
if (usedBy !== '本气' && use.god === '劫财') {
|
|
153
|
+
const via = exposedKey ? `透于${exposedPos}干` : `${hui.where.join('、')}会成${hui.group.element}局`;
|
|
154
|
+
return {
|
|
155
|
+
name: '月劫格',
|
|
156
|
+
kind: '月令格',
|
|
157
|
+
evidence: `月令${monthBranch}藏${hidden.join('')},${use.pos}${use.stem}(劫财)${via},劫财为用,作月劫论`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
121
160
|
const name = `${use.god}格`;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
161
|
+
let ex;
|
|
162
|
+
if (exposedKey) {
|
|
163
|
+
ex = `月令${monthBranch}藏${hidden.join('')},${use.pos}${use.stem}(${use.god})透于${exposedPos}干`;
|
|
164
|
+
} else if (hui) {
|
|
165
|
+
ex = `月令${monthBranch}藏${hidden.join('')},本气${base.stem}(${base.god})未透干,而${hui.where.join('、')}会成${hui.group.element}局,取${use.pos}${use.stem}(${use.god})为用`;
|
|
166
|
+
} else {
|
|
167
|
+
ex = `月令${monthBranch}藏${hidden.join('')},本气${base.stem}(${base.god})未透干,以月令本气取格`;
|
|
168
|
+
}
|
|
125
169
|
return { name, kind: '月令格', evidence: ex };
|
|
126
170
|
}
|
|
127
171
|
|
|
@@ -198,6 +242,36 @@ function specialPatterns(pillars, dayStem, monthBranch, strength, wuxing, gods)
|
|
|
198
242
|
return out;
|
|
199
243
|
}
|
|
200
244
|
|
|
245
|
+
// ── 通关 ────────────────────────────────────────────────────
|
|
246
|
+
// 滴天髓「通关」只给取象(织女、牛郎),这里把结构说清:
|
|
247
|
+
// 用神与忌神两行在局中都真有分量、且相克时,五行里恰有一行能接上——克方所生、且能生被克方。
|
|
248
|
+
// 只报这一行在局中的分量,不判「关通了就吉」。
|
|
249
|
+
const TONGGUAN_MIN = 1.5;
|
|
250
|
+
export function tongguanOf(favor = [], avoid = [], wuxing = {}) {
|
|
251
|
+
const w = (el) => Math.round((Number(wuxing[el]) || 0) * 10) / 10;
|
|
252
|
+
for (const use of favor) {
|
|
253
|
+
if (w(use) < TONGGUAN_MIN) continue;
|
|
254
|
+
for (const foe of avoid) {
|
|
255
|
+
if (OVERCOMES[foe] !== use || w(foe) < TONGGUAN_MIN) continue;
|
|
256
|
+
const bridge = GENERATES[foe];
|
|
257
|
+
if (!bridge || GENERATES[bridge] !== use) continue;
|
|
258
|
+
const weight = w(bridge);
|
|
259
|
+
const bridgeIsAvoid = avoid.includes(bridge) && !favor.includes(bridge);
|
|
260
|
+
const level = bridgeIsAvoid ? '为忌' : weight >= TONGGUAN_MIN ? '可通' : weight > 0 ? '关弱' : '未通';
|
|
261
|
+
return {
|
|
262
|
+
use, foe, bridge,
|
|
263
|
+
weight,
|
|
264
|
+
present: weight > 0,
|
|
265
|
+
bridgeIsAvoid,
|
|
266
|
+
level,
|
|
267
|
+
usable: level === '可通',
|
|
268
|
+
note: '相战=用神与忌神两行各在局中 ≥1.5 分且相克;通关一行=克方所生、又能生用神的那一行(五行里唯一),只报局中分量,不据此断吉凶',
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
201
275
|
// ── 用神 / 喜忌 ──────────────────────────────────────────────
|
|
202
276
|
|
|
203
277
|
|
|
@@ -232,6 +306,7 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
|
|
|
232
306
|
};
|
|
233
307
|
const favorTenGods = Array.from(new Set(favor.flatMap((el) => expand[roleName(el)] || [])));
|
|
234
308
|
const avoidTenGods = Array.from(new Set(avoid.flatMap((el) => expand[roleName(el)] || [])));
|
|
309
|
+
const tongguan = tongguanOf(favor, avoid, wuxing);
|
|
235
310
|
|
|
236
311
|
const detail = [
|
|
237
312
|
`日主${dayStem}${me},${strength.level}(${strength.score} 分):`,
|
|
@@ -242,6 +317,15 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
|
|
|
242
317
|
: '身中和,先取调候与通关,喜忌随大运流年动态变化。',
|
|
243
318
|
`调候:${climate.label}(${(climate.evidence || []).join('、')})${climate.need ? `,取${climate.need.stem}${climate.need.element},忌${climate.avoidElement}` : ',气候平和'}。`,
|
|
244
319
|
qt ? `穷通宝鉴:${qt.month}${dayStem}${me},用神${qt.yong.join('、')}(首取${qt.primary})。` : '',
|
|
320
|
+
tongguan
|
|
321
|
+
? `通关:忌${tongguan.foe}克用${tongguan.use},五行以${tongguan.bridge}为关;局中${tongguan.level === '可通'
|
|
322
|
+
? `${tongguan.bridge} ${tongguan.weight} 分,关可通`
|
|
323
|
+
: tongguan.level === '关弱'
|
|
324
|
+
? `仅 ${tongguan.bridge} ${tongguan.weight} 分,关弱`
|
|
325
|
+
: tongguan.level === '为忌'
|
|
326
|
+
? `${tongguan.bridge} ${tongguan.weight} 分但此行为忌,通关力打折`
|
|
327
|
+
: `未见${tongguan.bridge},关未通`}(只作结构提示,不据此断吉凶)。`
|
|
328
|
+
: '',
|
|
245
329
|
arb.text,
|
|
246
330
|
arb.clash ? '取舍:本盘按「调候优先、扶抑次之」取用;若你更认同扶抑派,可把调候当环境偏好,强弱判断不受影响。' : '',
|
|
247
331
|
].filter(Boolean).join('');
|
|
@@ -258,6 +342,7 @@ function yongshenOf(dayStem, monthBranch, strength, wuxing, branches = []) {
|
|
|
258
342
|
tiaohou: { ...climate, ...arb, element: climate.need ? climate.need.element : null },
|
|
259
343
|
climate,
|
|
260
344
|
qt,
|
|
345
|
+
tongguan,
|
|
261
346
|
detail,
|
|
262
347
|
};
|
|
263
348
|
}
|