dsh-metaphysics 0.13.5 → 0.14.2
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 +57 -0
- package/README.md +24 -5
- package/client.js +3209 -2101
- package/engine/bazi.js +52 -13
- package/engine/calendar.js +88 -16
- package/engine/daliuren.js +36 -1
- package/engine/geo.js +28 -11
- package/engine/hepan.js +144 -0
- package/engine/index.js +262 -39
- package/engine/interpret.js +108 -6
- package/engine/lunar.js +46 -12
- package/engine/meihua.js +2 -2
- package/engine/qimen-interpret.js +176 -0
- package/engine/qimen.js +440 -0
- package/engine/random.js +33 -0
- package/engine/tarot.js +5 -4
- package/engine/vsop-earth.js +40 -0
- package/engine/ziwei-hepan.js +120 -0
- package/engine/ziwei.js +2 -2
- package/index.js +109 -25
- package/locale/en.json +1 -1
- package/locale/zh.json +2 -2
- package/package.json +13 -9
package/engine/index.js
CHANGED
|
@@ -1,30 +1,77 @@
|
|
|
1
1
|
// 统一入口:解析参数、分派到各体系、生成可读摘要
|
|
2
2
|
import { buildBazi } from './bazi.js';
|
|
3
3
|
import { buildLiuyao, tossLines } from './liuyao.js';
|
|
4
|
-
import { buildZiwei, BRIGHTNESS, flowOf } from './ziwei.js';
|
|
4
|
+
import { buildZiwei, BRIGHTNESS, flowOf, asOfParts } from './ziwei.js';
|
|
5
5
|
import { drawTarot } from './tarot.js';
|
|
6
6
|
import { buildMeihua } from './meihua.js';
|
|
7
7
|
import { buildDaliuren } from './daliuren.js';
|
|
8
|
+
import { buildHepan } from './hepan.js';
|
|
9
|
+
import { buildZiweiHepan } from './ziwei-hepan.js';
|
|
10
|
+
import { buildQimen } from './qimen.js';
|
|
8
11
|
import { geocodeAddress, resolveAddress } from './geo.js';
|
|
9
12
|
import { toJDN, ganzhiName, dayGanzhiIndex } from './calendar.js';
|
|
13
|
+
import { randomOf, isSeed } from './random.js';
|
|
10
14
|
import { interpret, DIRECTIONS, DIRECTION_DEFAULT } from './interpret.js';
|
|
11
15
|
|
|
12
16
|
export { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf };
|
|
13
17
|
|
|
14
18
|
const pad = (n) => String(n).padStart(2, '0');
|
|
15
19
|
|
|
20
|
+
/** 带错误码的参数错误:工具层据此给出可执行提示,而不是把内部异常直出给模型 */
|
|
21
|
+
const fail = (code, message) => {
|
|
22
|
+
const e = new Error(message);
|
|
23
|
+
e.code = code;
|
|
24
|
+
return e;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const GENDERS = ['male', 'female'];
|
|
28
|
+
|
|
29
|
+
function daysInMonth(year, month) {
|
|
30
|
+
if (month === 2) {
|
|
31
|
+
const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
32
|
+
return leap ? 29 : 28;
|
|
33
|
+
}
|
|
34
|
+
return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 性别是必填项:它决定大运/大限的顺逆,缺省按某一方排等于给出一张错盘。
|
|
39
|
+
* 之前用 `=== 'female' ? 'female' : 'male'` 兜底,静默把未填性别的人按男命排。
|
|
40
|
+
*/
|
|
41
|
+
function requireGender(value, who = '') {
|
|
42
|
+
if (GENDERS.includes(value)) return value;
|
|
43
|
+
const got = value === undefined || value === null || value === '' ? '(未提供)' : `「${value}」`;
|
|
44
|
+
throw fail('E_INPUT', `${who ? who + '的' : ''}gender 必须是 male 或 female${got};性别决定大运/大限顺逆,不能靠默认值`);
|
|
45
|
+
}
|
|
46
|
+
|
|
16
47
|
/** 解析 'YYYY-MM-DDTHH:mm' / 'YYYY-MM-DD HH:mm' / 'YYYY-MM-DD' */
|
|
17
48
|
export function parseDateTime(text) {
|
|
18
|
-
if (typeof text !== 'string' || !text.trim())
|
|
19
|
-
|
|
20
|
-
|
|
49
|
+
if (typeof text !== 'string' || !text.trim()) {
|
|
50
|
+
throw fail('E_INPUT', '缺少 datetime(公历日期时间),例如 1990-05-12T14:30');
|
|
51
|
+
}
|
|
52
|
+
const m = text.trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?$/);
|
|
53
|
+
if (!m) throw fail('E_INPUT', `无法解析 datetime:「${text}」,请用 1990-05-12T14:30 这种格式`);
|
|
21
54
|
const out = {
|
|
22
55
|
year: Number(m[1]), month: Number(m[2]), day: Number(m[3]),
|
|
23
56
|
hour: Number(m[4] ?? 0), minute: Number(m[5] ?? 0),
|
|
24
57
|
};
|
|
25
|
-
if (out.month < 1 || out.month > 12) throw
|
|
26
|
-
|
|
27
|
-
|
|
58
|
+
if (out.month < 1 || out.month > 12) throw fail('E_INPUT', `月份超出范围:${out.month}`);
|
|
59
|
+
// 只校验 1..31 会让 1990-02-30、2026-04-31 这类日期静默滚到下个月,整盘错位且无人察觉
|
|
60
|
+
const maxDay = daysInMonth(out.year, out.month);
|
|
61
|
+
if (out.day < 1 || out.day > maxDay) {
|
|
62
|
+
throw fail('E_INPUT', `日期不存在:${out.year}-${out.month}-${out.day}(${out.year} 年 ${out.month} 月只有 ${maxDay} 天)`);
|
|
63
|
+
}
|
|
64
|
+
if (out.hour > 23 || out.minute > 59) throw fail('E_INPUT', `时间超出范围:${out.hour}:${out.minute}`);
|
|
65
|
+
// ΔT(地球自转改正)表只覆盖 1900–2100,超出后按端点值外推,精度不可保证
|
|
66
|
+
if (out.year < 1600 || out.year > 2400) {
|
|
67
|
+
throw fail('E_RANGE', `年份 ${out.year} 超出可计算范围(1600–2400)`);
|
|
68
|
+
}
|
|
69
|
+
if (out.year < 1900 || out.year > 2100) {
|
|
70
|
+
out.precision = {
|
|
71
|
+
level: 'approx',
|
|
72
|
+
note: `ΔT 模型仅覆盖 1900–2100,${out.year} 年按端点值外推,节气与朔望时刻可能有分钟级偏差,月柱在交节前后需复核`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
28
75
|
return out;
|
|
29
76
|
}
|
|
30
77
|
|
|
@@ -35,10 +82,14 @@ function baziText(r) {
|
|
|
35
82
|
+ ` 地势 ${p.dishi} 自坐 ${p.zizuo} 空亡 ${p.kongwang.join('')}`
|
|
36
83
|
+ (p.shensha && p.shensha.length ? ` 神煞 ${p.shensha.join('、')}` : '');
|
|
37
84
|
const wuxing = Object.entries(r.wuxing).map(([k, v]) => `${k}${v}`).join(' ');
|
|
85
|
+
const palaces = r.palaces
|
|
86
|
+
? `【胎元】${r.palaces.taiyuan.ganzhi} 命宫 ${r.palaces.minggong.ganzhi} 身宫 ${r.palaces.shengong.ganzhi}`
|
|
87
|
+
: '';
|
|
38
88
|
const dayun = r.dayun.list.slice(0, 8)
|
|
39
89
|
.map((d) => `${d.startAge}岁 ${d.ganzhi}(${d.stemTenGod})${d.luck ? `·${d.luck.tier}` : ''}${d.relations && d.relations.length ? `[${d.relations.join('')}]` : ''}`).join(' ');
|
|
40
|
-
|
|
41
|
-
|
|
90
|
+
// 阈值 20 分钟:与 NAOJ 历要项对拍实测,本引擎节气最多偏早 13.5 分钟(Meeus 低精度上限约 14 分钟)
|
|
91
|
+
const near = r.jieqi.nearBoundaryMinutes < 20
|
|
92
|
+
? `\n⚠ 出生时刻距「节」仅 ${r.jieqi.nearBoundaryMinutes} 分钟,而节气时刻本身有最多约 14 分钟的偏差,月柱与年柱建议人工复核。`
|
|
42
93
|
: '';
|
|
43
94
|
const SHORT = { year: '年', month: '月', day: '日', hour: '时' };
|
|
44
95
|
const shenshaLine = r.shensha && r.shensha.groups && r.shensha.groups.length
|
|
@@ -57,12 +108,13 @@ function baziText(r) {
|
|
|
57
108
|
`日主:${r.dayMaster.stem}${r.dayMaster.element}(${r.dayMaster.yinYang}) 空亡:${r.kongwang.join('')}`,
|
|
58
109
|
line('年柱', P.year), line('月柱', P.month), line('日柱', P.day), line('时柱', P.hour),
|
|
59
110
|
`五行:${wuxing}`,
|
|
111
|
+
...(palaces ? [palaces] : []),
|
|
60
112
|
`日主强弱:${r.strength.level}(${r.strength.score}) ${r.strength.detail}`,
|
|
61
113
|
...gejuLine,
|
|
62
114
|
`天干关系:${(r.relations && r.relations.stems.length) ? r.relations.stems.join(' ') : '—'}`,
|
|
63
115
|
`地支关系:${(r.relations && r.relations.branches.length) ? r.relations.branches.join(' ') : '—'}`,
|
|
64
116
|
shenshaLine,
|
|
65
|
-
`真太阳时:${r.solarTime.trueSolarText}(${r.input.geo ? r.input.geo.note + ';' : ''}${r.solarTime.note}
|
|
117
|
+
`真太阳时:${r.solarTime.trueSolarText}(${r.input.geo ? r.input.geo.note + ';' : ''}${r.solarTime.note}) 晚子时:${r.input.lateZi ? '23:00 后算次日' : '23:00 后仍算当日'}`,
|
|
66
118
|
`节气:上一节 ${r.jieqi.previous.name} ${r.jieqi.previous.time} 下一节 ${r.jieqi.next.name} ${r.jieqi.next.time}`,
|
|
67
119
|
`大运:${r.dayun.rule}(${r.dayun.direction}),${r.dayun.startAge.years}岁${r.dayun.startAge.months}个月起运`,
|
|
68
120
|
` ${dayun}`,
|
|
@@ -113,15 +165,57 @@ function ziweiText(r) {
|
|
|
113
165
|
].filter(Boolean).join('\n');
|
|
114
166
|
}
|
|
115
167
|
|
|
168
|
+
function qimenText(r) {
|
|
169
|
+
const P = r.palaces;
|
|
170
|
+
const cell = (x) => `${x.palace}宫 ${x.star || '—'}/${x.gate || '—'}/${x.god || '—'}${x.kong ? ' 空' : ''}${x.ma ? ' 马' : ''}`;
|
|
171
|
+
return [
|
|
172
|
+
`【四柱】${r.siZhu.year} ${r.siZhu.month} ${r.siZhu.day} ${r.siZhu.time}`,
|
|
173
|
+
`【局数】${r.ju} 节气 ${r.jieqi}(${r.jieqiTime})`,
|
|
174
|
+
`【值符】${r.zhiFu.star}(${r.zhiFu.gong}宫) 【值使】${r.zhiShi.gate}(${r.zhiShi.gong}宫) 旬首 ${r.xunshou}`,
|
|
175
|
+
`【空亡】${r.kongWang.join('') || '—'} 驿马 ${r.ma.zhi || '—'}(${r.ma.gong || '—'}宫)`,
|
|
176
|
+
`【九宫】${P.map(cell).join(' ')}`,
|
|
177
|
+
`※ 晚子时:${r.lateZiHourMode === 'next-day' ? '23:00 后归次日' : '23:00 后仍归当日'}(可用 lateZiHourMode / lateZi 指定)`,
|
|
178
|
+
`※ ${r.rules[r.rules.length - 1]}`,
|
|
179
|
+
].join('\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function hepanText(r) {
|
|
183
|
+
if (r.chart === 'ziwei') {
|
|
184
|
+
const d = (x) => `${x.label}:${x.score >= 0 ? '+' : ''}${x.score} ${x.text}`;
|
|
185
|
+
return [
|
|
186
|
+
`【紫微合盘】${r.score.level}(${r.score.total}/100)`,
|
|
187
|
+
`你 命宫 ${r.a.soul.branch}(${r.a.soulStars.join('、') || '无主星'}) 夫妻 ${r.a.spouseStars.join('、') || '无主星'} ${r.a.bureau}`,
|
|
188
|
+
`对方 命宫 ${r.b.soul.branch}(${r.b.soulStars.join('、') || '无主星'}) 夫妻 ${r.b.spouseStars.join('、') || '无主星'} ${r.b.bureau}`,
|
|
189
|
+
...r.dimensions.map(d),
|
|
190
|
+
...r.interactions.map((h) => ` · ${h.text}`),
|
|
191
|
+
...r.notes.map((n) => `※ ${n}`),
|
|
192
|
+
].join('\n');
|
|
193
|
+
}
|
|
194
|
+
const d = (x) => `${x.label}:${x.score >= 0 ? '+' : ''}${x.score} ${x.text}`;
|
|
195
|
+
const solarLine = (side) => {
|
|
196
|
+
if (!side || !side.solarNote) return '—';
|
|
197
|
+
return `${side.longitude == null ? '未含经度修正' : `${side.longitude}°E`}(${side.solarNote})`;
|
|
198
|
+
};
|
|
199
|
+
return [
|
|
200
|
+
`【合盘】${r.score.level}(${r.score.total}/100)`,
|
|
201
|
+
`你 ${r.a.pillars.join(' ')} 日主 ${r.a.dayMaster} 日支 ${r.a.dayBranch} ${r.a.strength || ''}`,
|
|
202
|
+
`对方 ${r.b.pillars.join(' ')} 日主 ${r.b.dayMaster} 日支 ${r.b.dayBranch} ${r.b.strength || ''}`,
|
|
203
|
+
`真太阳时:你 ${solarLine(r.a)};对方 ${solarLine(r.b)}`,
|
|
204
|
+
...r.dimensions.map(d),
|
|
205
|
+
...r.notes.map((n) => `※ ${n}`),
|
|
206
|
+
].join('\n');
|
|
207
|
+
}
|
|
208
|
+
|
|
116
209
|
function daliurenText(r) {
|
|
117
210
|
const sc = r.sanChuan;
|
|
118
211
|
const one = (c) => `${c.zhi}(${c.jiang}${c.liuQin}${c.xunKong ? '空' : ''})`;
|
|
119
212
|
return [
|
|
120
|
-
`【日时】${r.dayGanZhi}日 ${r.hourZhi}时(${r.dayNight}贵) 月将 ${r.yueJiang.name}${r.yueJiang.zhi} 节气 ${r.jieqi.name}`,
|
|
213
|
+
`【日时】${r.dayGanZhi}日 ${r.hourZhi}时(${r.dayNight}贵) 月将 ${r.yueJiang.name}${r.yueJiang.zhi} 节气 ${r.jieqi.name} ${r.lateZi === false ? '晚子时不换日' : '晚子时换日'}`,
|
|
121
214
|
`【三传】初 ${one(sc.chu)} → 中 ${one(sc.zhong)} → 末 ${one(sc.mo)}`,
|
|
122
215
|
`【四课】${r.siKe.map((k) => `${k.position}课 ${k.shang}上${k.xia}下(${k.relation}·${k.jiang})`).join(' ')}`,
|
|
123
216
|
`【天地盘】${r.pan.map((p) => `${p.di}:${p.tian}(${p.jiang})`).join(' ')}`,
|
|
124
217
|
`【格局】${r.geJu.name}·${r.geJu.detail}`,
|
|
218
|
+
...((r.shensha && r.shensha.length) ? [`【神煞】${r.shensha.map((x) => `${x.name}${x.branch}`).join(' ')}`] : []),
|
|
125
219
|
].join('\n');
|
|
126
220
|
}
|
|
127
221
|
|
|
@@ -186,6 +280,14 @@ function jsonSafe(value) {
|
|
|
186
280
|
*/
|
|
187
281
|
export function compute(args = {}) {
|
|
188
282
|
const r = jsonSafe(computeRaw(args));
|
|
283
|
+
// 超出 ΔT 覆盖范围时把精度提示顶到摘要最前,别让远期盘悄悄出错
|
|
284
|
+
if (r && r.precision && r.precision.level === 'approx' && r.summary) {
|
|
285
|
+
r.summary = `⚠ ${r.precision.note}\n${r.summary}`;
|
|
286
|
+
}
|
|
287
|
+
// 没有经度就没有真太阳时:时柱可能整柱偏(海外出生尤其常见),必须显式告警而不是藏在括号里
|
|
288
|
+
if (r && (r.system === 'bazi' || r.system === 'ziwei') && r.input && r.input.longitude == null && r.summary) {
|
|
289
|
+
r.summary = `⚠ 未提供出生地或经度:按东八区平太阳时排盘,时柱可能与本命盘相差一个时辰;海外出生请先把当地时间换算成东八区标准时。\n${r.summary}`;
|
|
290
|
+
}
|
|
189
291
|
// 显式要求时才附加解析模块,避免默认输出过长;「所问之事」与「询问方向」一并传进去
|
|
190
292
|
if (args.withInterpret) {
|
|
191
293
|
r.interpret = interpret(r, {
|
|
@@ -201,78 +303,190 @@ export function interpretText(i) {
|
|
|
201
303
|
if (!i) return '';
|
|
202
304
|
const out = [`【${i.title || '解析与详解'}】${i.ask && i.ask.echo ? i.ask.echo : ''}`];
|
|
203
305
|
if (i.lead) out.push(i.lead);
|
|
306
|
+
// 解析生成失败时不能只返回一个空壳:盘面数据仍然可用,但要让调用方知道"这段没生成出来"
|
|
307
|
+
if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
|
|
204
308
|
for (const d of i.dimensions || []) {
|
|
205
|
-
|
|
309
|
+
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}` : '';
|
|
310
|
+
out.push(`${d.label}:${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}`);
|
|
206
311
|
}
|
|
207
312
|
for (const s of i.sections || []) out.push(`${s.label}:${s.text}`);
|
|
208
313
|
if (i.disclaimer) out.push(i.disclaimer);
|
|
209
314
|
return out.join('\n');
|
|
210
315
|
}
|
|
211
316
|
|
|
212
|
-
|
|
317
|
+
const JU_METHODS = ['chaibu', 'chaoshen', 'zhirun'];
|
|
318
|
+
const TAROT_COUNTS = [1, 3, 5];
|
|
319
|
+
const LATE_ZI_MODES = ['same-day', 'next-day'];
|
|
320
|
+
|
|
321
|
+
/** asOf 只接受能解析成真实日期的字符串;非法值以前会静默回退到「此刻」 */
|
|
322
|
+
function checkAsOf(asOf) {
|
|
323
|
+
if (asOf === undefined || asOf === null || asOf === '') return undefined;
|
|
324
|
+
const parts = asOfParts(asOf);
|
|
325
|
+
if (!parts) throw fail('E_ASOF', `无法解析 asOf:「${asOf}」,请用 2026-10-22T16:00 这种格式`);
|
|
326
|
+
const { year, month, day } = parts;
|
|
327
|
+
if (!(month >= 1 && month <= 12) || !(day >= 1 && day <= daysInMonth(year, month))) {
|
|
328
|
+
throw fail('E_ASOF', `asOf 指向的日期不存在:${asOf}`);
|
|
329
|
+
}
|
|
330
|
+
return parts;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function computeRaw(args = {}) {
|
|
213
334
|
const system = args.system;
|
|
214
335
|
if (system === 'bazi') {
|
|
215
336
|
const dt = parseDateTime(args.datetime);
|
|
337
|
+
const asOf = checkAsOf(args.asOf);
|
|
216
338
|
const result = buildBazi({
|
|
217
339
|
...dt,
|
|
218
|
-
gender: args.gender
|
|
340
|
+
gender: requireGender(args.gender),
|
|
219
341
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
220
342
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
221
343
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
222
344
|
lateZi: args.lateZi !== false,
|
|
223
345
|
trueSolar: args.trueSolar !== false,
|
|
346
|
+
// 流年表默认从「今年」起排;要复现历史结论就把基准年钉死
|
|
347
|
+
nowYear: asOf ? asOf.year : undefined,
|
|
224
348
|
});
|
|
225
|
-
return { ...result, summary: baziText(result) };
|
|
349
|
+
return { ...result, ...(asOf ? { asOf } : {}), precision: dt.precision, summary: baziText(result) };
|
|
226
350
|
}
|
|
227
351
|
if (system === 'ziwei') {
|
|
228
352
|
const dt = parseDateTime(args.datetime);
|
|
353
|
+
const asOf = checkAsOf(args.asOf);
|
|
229
354
|
const result = buildZiwei({
|
|
230
355
|
...dt,
|
|
231
|
-
gender: args.gender
|
|
356
|
+
gender: requireGender(args.gender),
|
|
232
357
|
leapAsNext: args.leapAsNext === true,
|
|
233
358
|
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
234
359
|
address: typeof args.address === 'string' ? args.address : undefined,
|
|
235
360
|
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
236
361
|
trueSolar: args.trueSolar,
|
|
237
362
|
// 流月/流日/流时的基准时刻:默认本机此刻,可用 asOf 指定以便复现
|
|
238
|
-
asOf
|
|
363
|
+
asOf,
|
|
239
364
|
});
|
|
240
|
-
return { ...result, summary: ziweiText(result) };
|
|
365
|
+
return { ...result, precision: dt.precision, summary: ziweiText(result) };
|
|
241
366
|
}
|
|
242
367
|
if (system === 'tarot') {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
368
|
+
const count = args.count === undefined || args.count === null || args.count === '' ? 3 : Number(args.count);
|
|
369
|
+
if (!TAROT_COUNTS.includes(count)) {
|
|
370
|
+
throw fail('E_COUNT', `塔罗张数只能是 1 / 3 / 5,收到「${args.count}」`);
|
|
371
|
+
}
|
|
372
|
+
if (!isSeed(args.seed)) throw fail('E_INPUT', `seed 必须是数字,收到「${args.seed}」`);
|
|
373
|
+
const asOf = checkAsOf(args.asOf);
|
|
374
|
+
// 抽牌不看时间:castAt 只记录此刻(或 asOf),避免误用出生时间;给了 seed 就能复现同一次抽牌
|
|
375
|
+
const result = drawTarot({ count, question: args.question, at: args.asOf, random: randomOf(args.seed) });
|
|
376
|
+
if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
|
|
377
|
+
return { ...result, ...(asOf ? { asOf } : {}), summary: tarotText(result) };
|
|
246
378
|
}
|
|
247
379
|
if (system === 'daliuren') {
|
|
248
380
|
const dt = parseDateTime(args.datetime);
|
|
249
|
-
const
|
|
250
|
-
|
|
381
|
+
const lateZi = args.lateZi !== false;
|
|
382
|
+
const result = buildDaliuren({ ...dt, lateZi });
|
|
383
|
+
result.lateZi = lateZi;
|
|
384
|
+
return { ...result, precision: dt.precision, summary: daliurenText(result) };
|
|
385
|
+
}
|
|
386
|
+
if (system === 'qimen') {
|
|
387
|
+
const dt = parseDateTime(args.datetime);
|
|
388
|
+
if (args.juMethod !== undefined && !JU_METHODS.includes(args.juMethod)) {
|
|
389
|
+
throw fail('E_INPUT', `juMethod 只能是 chaibu / chaoshen / zhirun,收到「${args.juMethod}」`);
|
|
390
|
+
}
|
|
391
|
+
// 全插件只有"晚子时"这一件事,参数名不该按体系分裂:
|
|
392
|
+
// lateZiHourMode 是奇门的历史参数名,lateZi 是八字/六壬的;两者互为别名。
|
|
393
|
+
const mode = args.lateZiHourMode !== undefined
|
|
394
|
+
? args.lateZiHourMode
|
|
395
|
+
: args.lateZi === true ? 'next-day' : args.lateZi === false ? 'same-day' : undefined;
|
|
396
|
+
if (mode !== undefined && !LATE_ZI_MODES.includes(mode)) {
|
|
397
|
+
throw fail('E_INPUT', `lateZiHourMode 只能是 same-day / next-day,收到「${mode}」`);
|
|
398
|
+
}
|
|
399
|
+
const lateZiHourMode = mode === 'next-day' ? 'next-day' : 'same-day';
|
|
400
|
+
const result = buildQimen({ ...dt, lateZiHourMode, juMethod: args.juMethod });
|
|
401
|
+
result.lateZiHourMode = lateZiHourMode;
|
|
402
|
+
return { ...result, summary: qimenText(result) };
|
|
403
|
+
}
|
|
404
|
+
if (system === 'hepan') {
|
|
405
|
+
const partner = args.partner;
|
|
406
|
+
if (!partner || typeof partner !== 'object') {
|
|
407
|
+
throw fail('E_PARTNER', '合盘需要 partner:{ datetime, gender, address? },其中 partner.datetime 是对方的公历出生时间');
|
|
408
|
+
}
|
|
409
|
+
if (!partner.datetime) {
|
|
410
|
+
throw fail('E_PARTNER', '缺少 partner.datetime(对方的公历出生时间),例如 partner: {"datetime":"1992-03-03T10:00","gender":"female"}');
|
|
411
|
+
}
|
|
412
|
+
const pa = parseDateTime(args.datetime);
|
|
413
|
+
const pb = parseDateTime(partner.datetime);
|
|
414
|
+
const chart = args.chart === 'ziwei' ? 'ziwei' : 'bazi';
|
|
415
|
+
// 性别决定双方大运/大限顺逆,两边都不能用默认值
|
|
416
|
+
const sideA = {
|
|
417
|
+
...pa,
|
|
418
|
+
gender: requireGender(args.gender, '你(主方)'),
|
|
419
|
+
trueSolar: args.trueSolar,
|
|
420
|
+
lateZi: args.lateZi,
|
|
421
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
422
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
423
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
424
|
+
};
|
|
425
|
+
const sideB = {
|
|
426
|
+
...pb,
|
|
427
|
+
gender: requireGender(partner.gender, '对方'),
|
|
428
|
+
trueSolar: partner.trueSolar,
|
|
429
|
+
lateZi: partner.lateZi,
|
|
430
|
+
address: typeof partner.address === 'string' ? partner.address : undefined,
|
|
431
|
+
longitude: typeof partner.longitude === 'number' ? partner.longitude : undefined,
|
|
432
|
+
geo: partner.geo && typeof partner.geo === 'object' ? partner.geo : undefined,
|
|
433
|
+
};
|
|
434
|
+
const result = chart === 'ziwei' ? buildZiweiHepan({ a: sideA, b: sideB }) : buildHepan({ a: sideA, b: sideB });
|
|
435
|
+
return { ...result, summary: hepanText(result) };
|
|
251
436
|
}
|
|
252
437
|
if (system === 'meihua') {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
438
|
+
let result;
|
|
439
|
+
try {
|
|
440
|
+
result = buildMeihua({
|
|
441
|
+
mode: args.mode === 'number' ? 'number' : 'time',
|
|
442
|
+
datetime: args.datetime,
|
|
443
|
+
upper: args.upper,
|
|
444
|
+
lower: args.lower,
|
|
445
|
+
changing: args.changing,
|
|
446
|
+
});
|
|
447
|
+
} catch (e) {
|
|
448
|
+
throw fail('E_INPUT', `梅花起卦参数不完整:${e && e.message ? e.message : e}`);
|
|
449
|
+
}
|
|
260
450
|
return { ...result, summary: meihuaText(result) };
|
|
261
451
|
}
|
|
262
452
|
if (system === 'liuyao') {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
453
|
+
if (!isSeed(args.seed)) throw fail('E_INPUT', `seed 必须是数字,收到「${args.seed}」`);
|
|
454
|
+
let lines;
|
|
455
|
+
if (args.lines === undefined || args.lines === null) {
|
|
456
|
+
// 没给爻值 = 随机摇卦;给了 seed 则同一次摇卦可复现
|
|
457
|
+
lines = tossLines(randomOf(args.seed));
|
|
458
|
+
} else {
|
|
459
|
+
if (!Array.isArray(args.lines)) {
|
|
460
|
+
throw fail('E_LINES', 'lines 必须是数组:自下而上 6 个爻值(6 老阴 / 7 少阳 / 8 少阴 / 9 老阳)');
|
|
461
|
+
}
|
|
462
|
+
if (args.lines.length !== 6) {
|
|
463
|
+
// 以前这里会静默改随机摇卦:用户手抖少填一个,拿到的却是一张"看起来正常"的随机盘
|
|
464
|
+
throw fail('E_LINES', `六爻需要正好 6 个爻值(自下而上),收到 ${args.lines.length} 个;留空则由引擎随机摇卦`);
|
|
465
|
+
}
|
|
466
|
+
lines = args.lines.map(Number);
|
|
467
|
+
}
|
|
266
468
|
let dayGanZhi;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
469
|
+
let asOf = null;
|
|
470
|
+
// 六神按「起卦日」的日干起:给了 datetime 用它,给了 asOf 用 asOf,都没有才回落到本机当天
|
|
471
|
+
const stamp = args.datetime ? parseDateTime(args.datetime) : checkAsOf(args.asOf);
|
|
472
|
+
if (stamp) {
|
|
473
|
+
if (!args.datetime) asOf = stamp;
|
|
474
|
+
const g = ganzhiName(dayGanzhiIndex(toJDN(stamp.year, stamp.month, stamp.day)));
|
|
270
475
|
dayGanZhi = { stem: g.stem, branch: g.branch, name: g.name };
|
|
271
476
|
}
|
|
272
|
-
|
|
273
|
-
|
|
477
|
+
let result;
|
|
478
|
+
try {
|
|
479
|
+
result = buildLiuyao({ lines, question: args.question, dayGanZhi });
|
|
480
|
+
} catch (e) {
|
|
481
|
+
throw fail('E_LINES', e && e.message ? e.message : e);
|
|
482
|
+
}
|
|
483
|
+
if (args.seed !== undefined && args.seed !== null && args.seed !== '') result.seed = Number(args.seed);
|
|
484
|
+
return { ...result, ...(asOf ? { asOf } : {}), summary: liuyaoText(result) };
|
|
274
485
|
}
|
|
275
|
-
|
|
486
|
+
if (!system) {
|
|
487
|
+
throw fail('E_INPUT', '缺少 system,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)。');
|
|
488
|
+
}
|
|
489
|
+
throw fail('E_SYSTEM', `未知体系「${system}」,可选:bazi(四柱八字)、ziwei(紫微斗数)、liuyao(六爻)、meihua(梅花易数)、daliuren(大六壬)、qimen(奇门遁甲)、hepan(合盘)、tarot(塔罗)。`);
|
|
276
490
|
}
|
|
277
491
|
|
|
278
492
|
/** 给模型的紧凑文本(去掉大字段,保留摘要;带解析时附上解析) */
|
|
@@ -287,3 +501,12 @@ export function renderText(result) {
|
|
|
287
501
|
export { geocodeAddress, resolveAddress };
|
|
288
502
|
|
|
289
503
|
export { buildBazi, buildLiuyao, tossLines };
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
package/engine/interpret.js
CHANGED
|
@@ -191,15 +191,69 @@ function baziInterpret(r) {
|
|
|
191
191
|
|
|
192
192
|
// 未来三步大运
|
|
193
193
|
const idx = cur ? list.indexOf(cur) : -1;
|
|
194
|
+
|
|
195
|
+
// ── 交叉判断:把两三个维度叠起来看,单看任一维度都会得出相反的结论 ──
|
|
196
|
+
const cross = [];
|
|
197
|
+
const GAN_WX = { 甲: '木', 乙: '木', 丙: '火', 丁: '火', 戊: '土', 己: '土', 庚: '金', 辛: '金', 壬: '水', 癸: '水' };
|
|
198
|
+
const ganWx = (gz) => GAN_WX[String(gz || '')[0]] || '';
|
|
199
|
+
const ZHI_WX = { 子: '水', 丑: '土', 寅: '木', 卯: '木', 辰: '土', 巳: '火', 午: '火', 未: '土', 申: '金', 酉: '金', 戌: '土', 亥: '水' };
|
|
200
|
+
// ① 用神 × 当前大运:这十年是「顺着走」还是「顶着走」
|
|
201
|
+
if (cur) {
|
|
202
|
+
const dw = ganWx(cur.ganzhi);
|
|
203
|
+
if (dw) {
|
|
204
|
+
if (favor.includes(dw)) cross.push(`当前大运 ${cur.ganzhi} 走${dw},正是喜用,这十年推事情比平时省力;${topName ? `可把力气放在${topName}对应的事上` : '适合把长期计划铺开'}。`);
|
|
205
|
+
else if (avoid.includes(dw)) cross.push(`当前大运 ${cur.ganzhi} 走${dw},落在忌神上,这十年更适合守成、补短板,不宜大杠杆扩张。`);
|
|
206
|
+
else cross.push(`当前大运 ${cur.ganzhi} 走${dw},既非喜用也非忌神,属于平运,结果更多取决于你在这段里做了什么。`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// ② 五行缺失 × 喜忌:缺的正好是忌神,反而是好事
|
|
210
|
+
if (missing.length) {
|
|
211
|
+
const good = missing.filter((m) => avoid.includes(m));
|
|
212
|
+
const bad = missing.filter((m) => favor.includes(m));
|
|
213
|
+
if (good.length) cross.push(`命局缺 ${good.join('、')},而它正是你的忌神——缺得刚好,不必刻意去补。`);
|
|
214
|
+
if (bad.length) cross.push(`命局缺 ${bad.join('、')},这恰是喜用,是这盘的短板:靠环境、行业与身边人来补,比自己硬扛有效。`);
|
|
215
|
+
}
|
|
216
|
+
// ③ 十神权重 × 格局:主格与最重十神一致=专注,冲突=拉扯
|
|
217
|
+
if (topName && primary) {
|
|
218
|
+
const godOfPattern = godFromPattern(primary);
|
|
219
|
+
if (godOfPattern && topName && godOfPattern === topName) cross.push(`格局 ${primary} 与最重的${topName}一致,说明你的能量集中在同一条线上,长处突出、短板也集中。`);
|
|
220
|
+
else if (godOfPattern) cross.push(`格局 ${primary}(偏${godOfPattern})与最重的${topName}不在一条线上,内在会有两股力气拉扯:一边是格局给你的路,一边是你本能投入最多的事。`);
|
|
221
|
+
}
|
|
222
|
+
// ④ 神煞 × 十神:羊刃与七杀并见,是压力型结构
|
|
223
|
+
const hasRen = Array.isArray(r.shensha) && r.shensha.some((g) => (g.items || []).some((i) => i.name === '羊刃'));
|
|
224
|
+
if (hasRen && Number(groups['官杀'] || 0) >= 1) {
|
|
225
|
+
cross.push('命见羊刃且官杀有力,属压力驱动型:遇强则强,但需要出口,长期憋着容易内耗;运动或竞技类爱好比"忍"更有用。');
|
|
226
|
+
}
|
|
227
|
+
// ⑤ 夫妻宫 × 配偶星:日支与财/官是否同气
|
|
228
|
+
if (r.pillars?.day) {
|
|
229
|
+
const isM = r.input?.gender === 'male';
|
|
230
|
+
const spouseGod = isM ? '财' : '官杀';
|
|
231
|
+
const spVal = Number(groups[spouseGod] || 0);
|
|
232
|
+
const zhiWx = ZHI_WX[r.pillars.day.branch] || '';
|
|
233
|
+
if (spVal >= 2 && zhiWx && favor.includes(zhiWx)) cross.push(`配偶星(${spouseGod})不弱,且日支 ${r.pillars.day.branch} 属喜用,感情中有实际助益,不只看感觉。`);
|
|
234
|
+
else if (spVal === 0) cross.push(`命局不见${spouseGod},感情多靠自己主动创造场景;日支 ${r.pillars.day.branch} 更像"对方的样子"而不是"缘分的强度"。`);
|
|
235
|
+
}
|
|
236
|
+
|
|
194
237
|
const ahead = idx >= 0 ? list.slice(idx + 1, idx + 4) : list.slice(0, 3);
|
|
238
|
+
// 依据:把每个维度"凭什么这么说"的盘面事实列出来,便于用户核对,也让文案不再是空口断语
|
|
239
|
+
const ev = (...xs) => xs.filter((x) => x != null && x !== '');
|
|
195
240
|
|
|
196
241
|
const dimensions = [
|
|
197
242
|
{
|
|
198
243
|
key: 'overall', label: '整体运势',
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
244
|
+
// level 本身已是「身强/偏强/中和/偏弱/身弱」,这里不能再拼一个「身」
|
|
245
|
+
text: `你的日主是${dm.stem}(五行属${dm.element}),命局${level}(${pick(st, 'score', '')} 分)。` +
|
|
246
|
+
`用神取向偏${join(favor)},忌${join(avoid)}——往${join(favor)}的方向走会更省力${ys.tiaohou && ys.tiaohou.note ? `;调候上${ys.tiaohou.note}` : ''}。` +
|
|
247
|
+
`目前走在${pick(cur, 'ganzhi', '当前')}大运(${curTier}),${pick(ln, 'year', '今年')}年流年${pick(ln, 'ganzhi', '')}为${lnTier}。`,
|
|
248
|
+
// 大运与流年各自给建议,不再把两个档位的文案并成一句(曾出现「起伏不大」后面接「留出缓冲」)
|
|
249
|
+
advice: `大运看:${TIER_TEXT[curTier] || TIER_TEXT.平};流年看:${TIER_ADVICE[lnTier] || TIER_ADVICE.平}。大方向定在${join(favor)}相关的领域,会比硬扛${join(avoid)}顺畅一些。`,
|
|
250
|
+
evidence: ev(
|
|
251
|
+
`日主 ${dm.stem}${dm.element}·${level}`,
|
|
252
|
+
st && st.parts && st.parts.season ? `月令 ${st.parts.season.branch}${st.parts.season.element}` : '',
|
|
253
|
+
`喜 ${join(favor)} / 忌 ${join(avoid)}`,
|
|
254
|
+
cur ? `大运 ${cur.ganzhi}·${curTier}` : '',
|
|
255
|
+
ln && ln.ganzhi ? `流年 ${ln.year}${ln.ganzhi}·${lnTier}` : '',
|
|
256
|
+
),
|
|
203
257
|
},
|
|
204
258
|
{
|
|
205
259
|
key: 'work', label: '工作与职业选择',
|
|
@@ -207,6 +261,11 @@ function baziInterpret(r) {
|
|
|
207
261
|
`命局主格为${primary},这类结构的人通常${TENGOD_STYLE[godFromPattern(primary)] || '有自己稳定的节奏'}。` +
|
|
208
262
|
`十神中${topName}最重(${top[0]?.[1] ?? ''}),${secondName}次之,说明你在${topName}所代表的事项上投入的精力最多。`,
|
|
209
263
|
advice: `选岗位时可以偏向「${join(favor)}属性 + ${topName}对应的事务」;如果现在的工作明显偏${join(avoid)}且长期让你疲惫,可以参考上面方向做小幅试探,不必立刻大改。`,
|
|
264
|
+
evidence: ev(
|
|
265
|
+
`主格 ${primary}`,
|
|
266
|
+
`喜用五行 ${join(favor)}`,
|
|
267
|
+
top.length ? `十神 ${top.slice(0, 3).map(([k, v]) => `${k}${v}`).join(' ')}` : '',
|
|
268
|
+
),
|
|
210
269
|
},
|
|
211
270
|
{
|
|
212
271
|
key: 'career', label: '事业发展',
|
|
@@ -214,6 +273,11 @@ function baziInterpret(r) {
|
|
|
214
273
|
`接下来几步大运依次为:${ahead.map((d) => `${d.ganzhi}(${d.luck?.tier || '平'})`).join(' → ') || '暂未排出'},` +
|
|
215
274
|
`整体节奏是${ahead.length && ahead.filter((d) => ['吉', '大吉'].includes(d.luck?.tier)).length >= 2 ? '先抑后扬、后段更顺' : '需要一步步积累,中间偶有起伏'}。`,
|
|
216
275
|
advice: `把${ahead[0]?.ganzhi || '下一步'}这一步当作铺垫期,重点补${topName === '食伤' ? '作品与专业深度' : topName === '官杀' ? '资历与组织内的信任' : '可迁移的能力与资质'};遇到${join(avoid)}过重的年份,优先保住已有成果。`,
|
|
276
|
+
evidence: ev(
|
|
277
|
+
`格局 ${primary}`,
|
|
278
|
+
ahead.length ? `后续大运 ${ahead.map((d) => d.ganzhi).join(' → ')}` : '',
|
|
279
|
+
`当前 ${pick(cur, 'ganzhi', '—')}·${curTier}`,
|
|
280
|
+
),
|
|
217
281
|
},
|
|
218
282
|
{
|
|
219
283
|
key: 'love', label: '爱情与感情',
|
|
@@ -222,6 +286,11 @@ function baziInterpret(r) {
|
|
|
222
286
|
`日支(配偶宫)为${pick(r.pillars?.day, 'branch', '')},坐${pick(r.pillars?.day, 'zizuo', '')},` +
|
|
223
287
|
`这里的信号是:${Number(groups[isMale ? '财' : '官杀'] || 0) >= 2 ? '关系里你容易被主动靠近,但要留意筛选' : '感情推进偏慢,属于需要经营的类型'}。`,
|
|
224
288
|
advice: `不必急着要结果,先看清自己在关系里最在意什么。${Number(groups[isMale ? '财' : '官杀'] || 0) >= 2 ? '选项多时,明确标准反而更省心' : '可以把社交圈稍微扩大一点,但别为了脱单降低底线'};沟通上多说具体需求,少让对方猜。`,
|
|
289
|
+
evidence: ev(
|
|
290
|
+
`${isMale ? '财' : '官杀'} 权重 ${groups[isMale ? '财' : '官杀'] ?? '—'}`,
|
|
291
|
+
`日支 ${pick(r.pillars?.day, 'branch', '')}·${pick(r.pillars?.day, 'zizuo', '')}`,
|
|
292
|
+
`配偶星 ${isMale ? '财' : '官杀'}`,
|
|
293
|
+
),
|
|
225
294
|
},
|
|
226
295
|
{
|
|
227
296
|
key: 'health', label: '健康状况',
|
|
@@ -229,14 +298,21 @@ function baziInterpret(r) {
|
|
|
229
298
|
`按五行与身体的对应,${strongest}偏旺时${WUXING_ORGAN[strongest]?.part || ''}负担相对重(${WUXING_ORGAN[strongest]?.note || ''});` +
|
|
230
299
|
`${weakest}偏弱则${WUXING_ORGAN[weakest]?.part || ''}偏弱一些,是更值得留意的部分。这只是传统命理的取象提示,不是诊断。`,
|
|
231
300
|
advice: `建议把作息规律放在第一位——尤其是睡眠时长尽量稳定;${WUXING_ORGAN[weakest]?.note ? weakest + '对应的' + WUXING_ORGAN[weakest].part + '可以按体检项目关注' : '按常规体检关注'}。任何不适请去看医生,别用命理结论替代检查。`,
|
|
301
|
+
evidence: ev(
|
|
302
|
+
`五行 ${Object.entries(wx).map(([k, v]) => `${k}${v}`).join(' ')}`,
|
|
303
|
+
`最旺 ${strongest} / 最弱 ${weakest}`,
|
|
304
|
+
missing.length ? `缺 ${join(missing)}` : '',
|
|
305
|
+
),
|
|
232
306
|
},
|
|
233
307
|
];
|
|
234
308
|
|
|
235
309
|
const sections = [
|
|
310
|
+
...(cross.length ? [{ key: 'cross', label: '交叉判断', text: cross.join(' ') }] : []),
|
|
236
311
|
{
|
|
237
312
|
key: 'yongshen', label: '用神与忌神',
|
|
313
|
+
// detail 里通常已含调候说明,这里不再补一句,避免同一段出现两次「调候上…」
|
|
238
314
|
text: `喜用:${join(favor)}(对应十神:${join(ys.favorTenGods || ys.favorRoles || [])});忌:${join(avoid)}(对应十神:${join(ys.avoidTenGods || ys.avoidRoles || [])})。` +
|
|
239
|
-
`${pick(ys, 'detail', '')}
|
|
315
|
+
`${pick(ys, 'detail', '')}`,
|
|
240
316
|
},
|
|
241
317
|
{
|
|
242
318
|
key: 'geju', label: '格局依据',
|
|
@@ -376,12 +452,19 @@ function ziweiInterpret(r) {
|
|
|
376
452
|
? `另外你本命的生年四化落在:${natalSihuaText}——这是你这辈子比较稳定的底色,不随某一年改变。`
|
|
377
453
|
: ''),
|
|
378
454
|
advice: `这一阶段的主题更接近「${daxianPalace ? daxianPalace.palace : '命宫'}」代表的事——把它当作今年的主线,其余事围绕它安排;${S.trait}是你的优势,也是需要留意别用过头的地方。`,
|
|
455
|
+
evidence: [
|
|
456
|
+
`命宫 ${soulStar}${soulOwn ? '' : '(借对宫)'}`,
|
|
457
|
+
daxianPalace ? `大限 ${daxianPalace.palace} ${daxianPalace.daxian.start}–${daxianPalace.daxian.end}岁` : '',
|
|
458
|
+
lnMutagen ? `流年四化 ${lnMutagen}` : '',
|
|
459
|
+
`格局 ${patterns.length} 条`,
|
|
460
|
+
].filter(Boolean),
|
|
379
461
|
},
|
|
380
462
|
{
|
|
381
463
|
key: 'work', label: '工作与职业选择',
|
|
382
464
|
text: `命宫${soulStar}给出的是底层偏好:${S.work}。官禄宫${careerStar ? `主星为${careerStar},${ZIWEI_STARS[careerStar]?.work || '有其自身的做事方式'}` : '无正曜,工作方式更多受环境与他人影响,适合配合型岗位'}。` +
|
|
383
465
|
`两者合起来看,你更适合${careerStar && careerStar !== soulStar ? `用${soulStar}的方式去做${careerStar}擅长的事` : '在稳定的框架里发挥自己的特点'}。`,
|
|
384
466
|
advice: `挑工作时可以优先看岗位内容是否匹配上面这条;如果暂时不能换,试着在现有岗位里争取靠近这部分的事务,会比硬熬更容易做出成绩。`,
|
|
467
|
+
evidence: [`命宫 ${soulStar}`, `官禄 ${careerStar || '无正曜'}`, `财帛 ${wealthStar || '无正曜'}`],
|
|
385
468
|
},
|
|
386
469
|
{
|
|
387
470
|
key: 'career', label: '事业发展',
|
|
@@ -389,18 +472,32 @@ function ziweiInterpret(r) {
|
|
|
389
472
|
`${wealthStar && careerStar ? '财与事业两宫都有主星坐守,说明这两块是可以主动经营的部分' : '有一宫无主星,说明该块更容易受外部条件牵动,宜借势而非强求'}。` +
|
|
390
473
|
`大限推进上,${daxianPalace ? `目前落在${daxianPalace.palace}` : '当前阶段'},下一步会转入相邻的宫位,重心也会跟着挪。`,
|
|
391
474
|
advice: `事业上建议做两件事:一是把可积累的东西(技能、资质、作品)做厚;二是${careerStar ? `发挥${careerStar}的优势——${ZIWEI_STARS[careerStar]?.trait || ''}` : '找一个能带你的前辈或平台'}。遇到四化化忌的年份,先守成再谈扩张。`,
|
|
475
|
+
evidence: [
|
|
476
|
+
`财帛 ${wealthStar || '无正曜'}`,
|
|
477
|
+
`官禄 ${careerStar || '无正曜'}`,
|
|
478
|
+
daxianPalace ? `当前大限 ${daxianPalace.palace}` : '',
|
|
479
|
+
].filter(Boolean),
|
|
392
480
|
},
|
|
393
481
|
{
|
|
394
482
|
key: 'love', label: '爱情与感情',
|
|
395
483
|
text: `夫妻宫${spouseStar ? `主星为${spouseStar},感情上的倾向是${ZIWEI_STARS[spouseStar]?.love || '有自己相处的节奏'}` : '无正曜,关系状态更多由对方与互动方式决定'}。` +
|
|
396
484
|
`${zwSha(spouse).length ? `夫妻宫同时见${join(zwSha(spouse))},相处中容易出现摩擦或第三者干扰的暗示,但煞星不等于定局,更多是提醒你留意沟通方式` : '夫妻宫未见明显煞星,关系的基调相对平顺'}。`,
|
|
397
485
|
advice: `感情里${spouseStar ? `参考${spouseStar}的特点:${ZIWEI_STARS[spouseStar]?.love || ''}` : '可以更主动地表达自己的需求'}。有分歧时先说清事实与感受,别急着下结论;关系的好坏取决于两个人怎么做,不取决于这张盘。`,
|
|
486
|
+
evidence: [
|
|
487
|
+
`夫妻宫 ${spouseStar || '无正曜'}`,
|
|
488
|
+
`煞星 ${join(zwSha(spouse)) || '无'}`,
|
|
489
|
+
`夫妻宫支 ${spouse && spouse.branch ? spouse.branch : '—'}`,
|
|
490
|
+
],
|
|
398
491
|
},
|
|
399
492
|
{
|
|
400
493
|
key: 'health', label: '健康状况',
|
|
401
494
|
text: `疾厄宫${healthStar ? `见${healthStar},传统取象上${ZIWEI_STARS[healthStar]?.health || '需要留意自身的薄弱环节'}` : '无正曜,身体状态更多受生活作息直接影响'}。` +
|
|
402
495
|
`${zwSha(health).length ? `同时见${join(zwSha(health))},提示这一块值得比其他部位多一分留意。` : ''}这些都是象征性的提示,不构成任何医学判断。`,
|
|
403
496
|
advice: `把${healthStar ? ZIWEI_STARS[healthStar]?.health?.replace('留意', '').replace('需要留意', '') || '常规' : '常规'}当作体检时重点关注的参考;真正管用的还是规律作息、适度运动与定期检查。身体不舒服请直接就医。`,
|
|
497
|
+
evidence: [
|
|
498
|
+
`疾厄宫 ${healthStar || '无正曜'}`,
|
|
499
|
+
`煞星 ${join(zwSha(health)) || '无'}`,
|
|
500
|
+
],
|
|
404
501
|
},
|
|
405
502
|
];
|
|
406
503
|
|
|
@@ -647,7 +744,7 @@ function daliurenInterpret(r) {
|
|
|
647
744
|
},
|
|
648
745
|
{
|
|
649
746
|
key: 'geju', label: '课体与四课',
|
|
650
|
-
text: `本课为${pick(gj, 'name', '未定')}${gj.detail ? `(${gj.detail})` : ''}。${ke.length ? `四课:${ke.map((k, i) => `${i + 1}课 ${k.
|
|
747
|
+
text: `本课为${pick(gj, 'name', '未定')}${gj.detail ? `(${gj.detail})` : ''}。${ke.length ? `四课:${ke.map((k, i) => `${i + 1}课 ${k.shang || ''}上${k.xia || ''}下`).join(' ')}` : ''}`,
|
|
651
748
|
},
|
|
652
749
|
];
|
|
653
750
|
|
|
@@ -812,8 +909,11 @@ const BUILDERS = {
|
|
|
812
909
|
* 传了就把最贴近的维度提前、在该维度点明问题与方向,并给出回显串
|
|
813
910
|
* @returns {object|null} 结构化解析对象
|
|
814
911
|
*/
|
|
912
|
+
import { interpretQimen } from './qimen-interpret.js';
|
|
913
|
+
|
|
815
914
|
export function interpret(r, opts) {
|
|
816
915
|
if (!isObj(r) || !r.system) return null;
|
|
916
|
+
if (r.system === 'qimen') return interpretQimen(r, opts);
|
|
817
917
|
const fn = BUILDERS[r.system];
|
|
818
918
|
if (!fn) return null;
|
|
819
919
|
let body;
|
|
@@ -848,3 +948,5 @@ export function interpret(r, opts) {
|
|
|
848
948
|
}
|
|
849
949
|
|
|
850
950
|
export default interpret;
|
|
951
|
+
|
|
952
|
+
|