dsh-metaphysics 0.15.0 → 0.16.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.
- package/CHANGELOG.md +26 -0
- package/README.md +8 -0
- package/client.js +729 -24
- package/engine/crosscheck.js +133 -0
- package/engine/followup.js +173 -0
- package/engine/geo.js +1 -1
- package/engine/index.js +48 -4
- package/engine/interpret.js +126 -10
- package/index.js +8 -1
- package/package.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// 八字 × 紫微 交叉验证:两套体系各自看同一件事,把「同向 / 有张力 / 只有一边有信号」摆在一起。
|
|
2
|
+
//
|
|
3
|
+
// 注意口径:这不是数学意义上的「一致性检验」,两套体系的取象单位不同(十神 vs 星曜宫位)。
|
|
4
|
+
// 这里只做三件事:把两边对同一维度的说法并列、标出方向、注明各自的依据。
|
|
5
|
+
import { ZIWEI_STARS } from './interpret-data.js';
|
|
6
|
+
|
|
7
|
+
const groupsOf = (bazi) => (bazi && bazi.frame && bazi.frame.godStats && bazi.frame.godStats.groups) || {};
|
|
8
|
+
const topOf = (groups) => Object.entries(groups).sort((a, b) => Number(b[1]) - Number(a[1]))[0] || null;
|
|
9
|
+
const zwPalace = (zw, name) => (zw.palaces || []).find((p) => p.palace === name) || null;
|
|
10
|
+
const zwMain = (p) => (p && p.stars ? p.stars.filter((s) => s.type === '主星').map((s) => s.name) : []);
|
|
11
|
+
const countSha = (p) => (p && p.stars ? p.stars.filter((s) => s.type === '煞星').length : 0);
|
|
12
|
+
const tierOf = (bazi) => {
|
|
13
|
+
const age = Number(bazi.liunian && bazi.liunian[0] ? bazi.liunian[0].age : NaN);
|
|
14
|
+
const list = (bazi.dayun && bazi.dayun.list) || [];
|
|
15
|
+
let cur = null;
|
|
16
|
+
for (const d of list) if (Number.isFinite(age) && Number(d.startAge) <= age) cur = d;
|
|
17
|
+
return cur ? (cur.luck && cur.luck.tier) || '平' : '—';
|
|
18
|
+
};
|
|
19
|
+
const daxianOf = (zw) => {
|
|
20
|
+
const age = Number(zw.liunian && zw.liunian[0] ? zw.liunian[0].age : NaN);
|
|
21
|
+
for (const p of zw.palaces || []) {
|
|
22
|
+
const d = p.daxian || {};
|
|
23
|
+
if (Number.isFinite(age) && Number(d.start) <= age && age <= Number(d.end)) return p;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} bazi buildBazi 的结果
|
|
30
|
+
* @param {object} zw buildZiwei 的结果
|
|
31
|
+
* @returns {{items: Array<{key:string,label:string,bazi:string,ziwei:string,stance:string,note:string}>}}
|
|
32
|
+
*/
|
|
33
|
+
export function crossCheck(bazi, zw) {
|
|
34
|
+
const groups = groupsOf(bazi);
|
|
35
|
+
const top = topOf(groups);
|
|
36
|
+
const items = [];
|
|
37
|
+
const push = (key, label, baziText, ziweiText, stance, note) => items.push({ key, label, bazi: baziText, ziwei: ziweiText, stance, note });
|
|
38
|
+
|
|
39
|
+
// ① 事业:八字看十神里的官杀/食伤,紫微看官禄宫主星
|
|
40
|
+
{
|
|
41
|
+
const career = zwPalace(zw, '官禄');
|
|
42
|
+
const main = zwMain(career);
|
|
43
|
+
const baziStrong = Number(groups['官杀'] || 0) + Number(groups['食伤'] || 0) >= 2;
|
|
44
|
+
const ziweiHas = main.length > 0;
|
|
45
|
+
const stance = baziStrong && ziweiHas ? '同向' : !baziStrong && !ziweiHas ? '同向' : '有张力';
|
|
46
|
+
push('career', '事业主线',
|
|
47
|
+
`${top ? `十神以${top[0]}最重(${top[1]})` : '十神未取到'};官杀+食伤权重 ${(Number(groups['官杀'] || 0) + Number(groups['食伤'] || 0)).toFixed(1)}`,
|
|
48
|
+
main.length ? `官禄宫主星 ${main.join('、')}${ZIWEI_STARS[main[0]] ? `(${ZIWEI_STARS[main[0]].work})` : ''}` : '官禄无正曜',
|
|
49
|
+
stance,
|
|
50
|
+
stance === '同向' ? '两边都指向(或都不强调)事业主线,方向可互参' : '一边强调事业主线、一边信号弱,建议以信号强的一边为主,另一边作补充');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ② 财富:八字看财星权重,紫微看财帛宫主星与煞星
|
|
54
|
+
{
|
|
55
|
+
const wealth = zwPalace(zw, '财帛');
|
|
56
|
+
const main = zwMain(wealth);
|
|
57
|
+
const sha = countSha(wealth);
|
|
58
|
+
const w = Number(groups['财'] || 0);
|
|
59
|
+
const baziStrong = w >= 2;
|
|
60
|
+
const ziweiStrong = main.length > 0;
|
|
61
|
+
const stance = baziStrong && ziweiStrong ? '同向' : !baziStrong && !ziweiStrong ? '同向' : '有张力';
|
|
62
|
+
push('wealth', '财运结构',
|
|
63
|
+
`财星权重 ${w}${baziStrong ? '(偏强)' : '(偏弱)'}`,
|
|
64
|
+
`${main.length ? `财帛主星 ${main.join('、')}` : '财帛无正曜'}${sha ? `;煞星 ${sha} 颗` : ''}`,
|
|
65
|
+
stance,
|
|
66
|
+
'两套体系对「财」的定义不同:八字看十神财星,紫微看财帛宫,只作对照,不作数值合并');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ③ 感情:八字看配偶星,紫微看夫妻宫主星
|
|
70
|
+
{
|
|
71
|
+
const spouse = zwPalace(zw, '夫妻');
|
|
72
|
+
const main = zwMain(spouse);
|
|
73
|
+
const isMale = bazi.input && bazi.input.gender === 'male';
|
|
74
|
+
const key = isMale ? '财' : '官杀';
|
|
75
|
+
const v = Number(groups[key] || 0);
|
|
76
|
+
const baziStrong = v >= 2;
|
|
77
|
+
const ziweiStrong = main.length > 0;
|
|
78
|
+
const stance = baziStrong && ziweiStrong ? '同向' : !baziStrong && !ziweiStrong ? '同向' : '有张力';
|
|
79
|
+
push('love', '感情线索',
|
|
80
|
+
`${isMale ? '妻' : '夫'}星(${key})权重 ${v}${baziStrong ? '(不弱)' : '(偏弱)'}`,
|
|
81
|
+
main.length ? `夫妻宫主星 ${main.join('、')}${ZIWEI_STARS[main[0]] ? `(${ZIWEI_STARS[main[0]].love})` : ''}` : '夫妻宫无正曜',
|
|
82
|
+
stance,
|
|
83
|
+
stance === '同向' ? '两边对感情的强弱判断接近' : '一边说缘分不弱、一边主星信号弱,宜按「多主动创造场景」处理');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ④ 身体:八字看最弱五行,紫微看疾厄宫主星与煞星
|
|
87
|
+
{
|
|
88
|
+
const wx = bazi.wuxing || {};
|
|
89
|
+
const weakest = Object.entries(wx).sort((a, b) => Number(a[1]) - Number(b[1]))[0];
|
|
90
|
+
const health = zwPalace(zw, '疾厄');
|
|
91
|
+
const main = zwMain(health);
|
|
92
|
+
const sha = countSha(health);
|
|
93
|
+
push('health', '身体关注点',
|
|
94
|
+
weakest ? `最弱五行 ${weakest[0]}(${weakest[1]})` : '五行未取到',
|
|
95
|
+
`${main.length ? `疾厄主星 ${main.join('、')}` : '疾厄无正曜'}${sha ? `;煞星 ${sha} 颗` : ''}`,
|
|
96
|
+
'互补',
|
|
97
|
+
'两边指向的器官/系统不一定相同,合并起来看更稳;任何健康问题以体检与医生为准');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ⑤ 节奏:八字当前大运档位 vs 紫微当前大限的煞星密度
|
|
101
|
+
{
|
|
102
|
+
const t = tierOf(bazi);
|
|
103
|
+
const d = daxianOf(zw);
|
|
104
|
+
const sha = d ? countSha(d) : 0;
|
|
105
|
+
const good = ['吉', '大吉'];
|
|
106
|
+
const bad = ['忌', '大忌'];
|
|
107
|
+
const baziGood = good.includes(t);
|
|
108
|
+
const baziBad = bad.includes(t);
|
|
109
|
+
const ziweiHeavy = sha >= 2;
|
|
110
|
+
let stance = '有张力';
|
|
111
|
+
let note = '一边顺、一边压,按「大方向跟顺的一边、具体动作避开压的年份」处理';
|
|
112
|
+
if ((baziGood && !ziweiHeavy) || (baziBad && ziweiHeavy) || (!baziGood && !baziBad && !ziweiHeavy)) {
|
|
113
|
+
stance = '同向';
|
|
114
|
+
note = '两边对当前阶段的松紧判断接近';
|
|
115
|
+
}
|
|
116
|
+
push('rhythm', '当前节奏',
|
|
117
|
+
`大运档位 ${t}`,
|
|
118
|
+
`${d ? `大限在${d.palace}` : '大限未取到'}${sha ? `;煞星 ${sha} 颗` : ';无煞星'}`,
|
|
119
|
+
stance, note);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { items };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default crossCheck;
|
|
126
|
+
|
|
127
|
+
/** 把交叉验证结果拼成可读文本(interpret 的分节与工具输出共用) */
|
|
128
|
+
export function crossText(cross) {
|
|
129
|
+
if (!cross || !Array.isArray(cross.items)) return '';
|
|
130
|
+
return cross.items
|
|
131
|
+
.map((i) => `· ${i.label}:八字——${i.bazi};紫微——${i.ziwei};判断:${i.stance}(${i.note})`)
|
|
132
|
+
.join('\n');
|
|
133
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// 追问:围绕已经算好的一盘,回答「某一年 / 某个方面」的具体问题。
|
|
2
|
+
//
|
|
3
|
+
// 约束:这里不重新排盘、不调用模型、不引入新算法——只从盘面已有字段里取数,
|
|
4
|
+
// 并明确写出「依据的是哪一条」。找不到就直说找不到,并给出可问的范围。
|
|
5
|
+
import { ZIWEI_STARS } from './interpret-data.js';
|
|
6
|
+
|
|
7
|
+
const TOPICS = [
|
|
8
|
+
{ key: 'career', label: '事业', words: ['事业', '工作', '跳槽', '升职', '创业', '职业', '岗位', '离职', '加薪', '面试', 'offer'] },
|
|
9
|
+
{ key: 'wealth', label: '财富', words: ['财', '钱', '收入', '投资', '买房', '存款', '亏', '赚', '分红'] },
|
|
10
|
+
{ key: 'love', label: '感情', words: ['感情', '婚', '恋', '对象', '分手', '复合', '姻缘', '结婚', '桃花'] },
|
|
11
|
+
{ key: 'health', label: '健康', words: ['健康', '病', '身体', '手术', '失眠', '体检', '医院'] },
|
|
12
|
+
{ key: 'study', label: '学业', words: ['学业', '考试', '学习', '考研', '考公', '证书', '升学'] },
|
|
13
|
+
{ key: 'travel', label: '出行', words: ['出行', '搬家', '旅行', '出国', '迁', '远行'] },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const ZW_PALACE_BY_TOPIC = { career: '官禄', wealth: '财帛', love: '夫妻', health: '疾厄', study: '官禄', travel: '迁移', overall: '命宫' };
|
|
17
|
+
|
|
18
|
+
const parseYear = (q, r) => {
|
|
19
|
+
const m = String(q).match(/(20\d{2})\s*年?/);
|
|
20
|
+
if (m) return Number(m[1]);
|
|
21
|
+
const base = Array.isArray(r.liunian) && r.liunian[0] ? Number(r.liunian[0].year) : null;
|
|
22
|
+
if (!base) return null;
|
|
23
|
+
if (/明年/.test(q)) return base + 1;
|
|
24
|
+
if (/后年/.test(q)) return base + 2;
|
|
25
|
+
if (/去年/.test(q)) return base - 1;
|
|
26
|
+
if (/今年|本年/.test(q)) return base;
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const topicOf = (q) => TOPICS.find((t) => t.words.some((w) => String(q).includes(w))) || null;
|
|
31
|
+
|
|
32
|
+
function baziAnswer(r, q) {
|
|
33
|
+
const year = parseYear(q, r);
|
|
34
|
+
const topic = topicOf(q);
|
|
35
|
+
const liunian = Array.isArray(r.liunian) ? r.liunian : [];
|
|
36
|
+
const dayun = Array.isArray(r.dayun?.list) ? r.dayun.list : [];
|
|
37
|
+
const years = liunian.map((l) => l.year);
|
|
38
|
+
if (year && !liunian.some((l) => Number(l.year) === year)) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false, year, topic,
|
|
41
|
+
answer: `这一盘已排的流年范围是 ${years[0]}–${years[years.length - 1]},没有 ${year} 年。可以把问题改到范围内,或用 asOf 指定你想看的年份重新排盘。`,
|
|
42
|
+
evidence: [`已排流年:${years.join('、')}`], confidence: '弱',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (year) {
|
|
46
|
+
const l = liunian.find((x) => Number(x.year) === year);
|
|
47
|
+
const d = dayun.find((x) => Number(x.startAge) <= Number(l.age) && Number(l.age) <= Number(x.startAge) + 9) || null;
|
|
48
|
+
const tier = l.luck?.tier || '平';
|
|
49
|
+
const reasons = (l.luck?.reasons || []).slice(0, 3);
|
|
50
|
+
const rel = (l.relations || []).join('、');
|
|
51
|
+
const topicLine = topic ? topicSentence(r, topic.key) : '';
|
|
52
|
+
return {
|
|
53
|
+
ok: true, year, topic,
|
|
54
|
+
answer: `${year} 年(${l.ganzhi})流年,十神为${l.stemTenGod || '—'},档位「${tier}」;所行大运 ${d ? `${d.ganzhi}(${d.luck?.tier || '平'})` : '未取到'}。` +
|
|
55
|
+
`${reasons.length ? `理由:${reasons.join(',')}。` : ''}${rel ? `与该年地支相关的原局关系:${rel}。` : ''}${topicLine}`,
|
|
56
|
+
evidence: [
|
|
57
|
+
`流年 ${l.year} ${l.ganzhi}·${l.stemTenGod || ''}·${tier}`,
|
|
58
|
+
d ? `大运 ${d.ganzhi}·${d.luck?.tier || '平'}` : '',
|
|
59
|
+
rel ? `原局关系 ${rel}` : '',
|
|
60
|
+
topic ? `问题方向:${topic.label}` : '',
|
|
61
|
+
].filter(Boolean),
|
|
62
|
+
confidence: d && reasons.length ? '强' : '中',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (topic) {
|
|
66
|
+
// 维度只有「整体/工作/事业/感情/健康」,财富与学业、出行没有独立维度:
|
|
67
|
+
// 就近取一条最相关的维度,再补一句该方面的盘面值,避免答非所问。
|
|
68
|
+
const near = { wealth: 'overall', study: 'work', travel: 'overall' }[topic.key] || topic.key;
|
|
69
|
+
const dim = (r.interpret?.dimensions || []).find((x) => x.key === topic.key || x.key === near);
|
|
70
|
+
if (dim) {
|
|
71
|
+
const groups = (r.frame && r.frame.godStats && r.frame.godStats.groups) || {};
|
|
72
|
+
const quant = topic.key === 'wealth'
|
|
73
|
+
? ` 财星权重 ${Number(groups['财'] || 0)}(只作结构参考,不含金额)。`
|
|
74
|
+
: '';
|
|
75
|
+
return {
|
|
76
|
+
ok: true, year: null, topic,
|
|
77
|
+
answer: `按${topic.label}这条线看:${dim.text}${quant}${dim.advice ? ` 建议:${dim.advice}` : ''}`,
|
|
78
|
+
evidence: (dim.evidence || []).slice(0, 4), confidence: dim.confidence || '中',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
ok: false, year: null, topic,
|
|
84
|
+
answer: `这个问题里没有识别到具体的年份或方面。可以这样问:「2027 年事业怎么样」「财运如何」「感情在 2028 年」;已排流年是 ${years[0]}–${years[years.length - 1]}。`,
|
|
85
|
+
evidence: [`可问年份:${years.join('、')}`], confidence: '弱',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ziweiAnswer(r, q) {
|
|
90
|
+
const year = parseYear(q, r);
|
|
91
|
+
const topic = topicOf(q);
|
|
92
|
+
const liunian = Array.isArray(r.liunian) ? r.liunian : [];
|
|
93
|
+
if (year && !liunian.some((l) => Number(l.year) === year)) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false, year, topic,
|
|
96
|
+
answer: `这一盘的流年表覆盖 ${liunian[0]?.year}–${liunian[liunian.length - 1]?.year},没有 ${year} 年;可以换一个年份,或用 asOf 指定起点重新排盘。`,
|
|
97
|
+
evidence: [`流年表:${liunian.map((l) => l.year).join('、')}`], confidence: '弱',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (year) {
|
|
101
|
+
const l = liunian.find((x) => Number(x.year) === year);
|
|
102
|
+
const dx = l.daxian || {};
|
|
103
|
+
return {
|
|
104
|
+
ok: true, year, topic,
|
|
105
|
+
answer: `${year} 年(${l.ganzhi},${l.age} 岁)流年命宫落在「${l.lifePalace ? l.lifePalace.palace : '—'}」,四化为 ${(l.mutagen || []).join('、') || '不明显'};所行大限${dx.palace ? `在${dx.palace}(${dx.start}–${dx.end} 岁)` : '未取到'}。${topic ? `与「${topic.label}」对应的宫位信号见下条依据。` : ''}`,
|
|
106
|
+
evidence: [
|
|
107
|
+
`流年命宫 ${l.lifePalace ? l.lifePalace.palace : '—'}(${l.lifePalace ? l.lifePalace.branch : '—'})`,
|
|
108
|
+
`四化 ${(l.mutagen || []).join('、') || '不明显'}`,
|
|
109
|
+
dx.palace ? `大限 ${dx.palace}·${dx.start}–${dx.end}岁` : '',
|
|
110
|
+
].filter(Boolean),
|
|
111
|
+
confidence: (l.mutagen || []).length >= 3 ? '强' : '中',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (topic) {
|
|
115
|
+
const name = ZW_PALACE_BY_TOPIC[topic.key] || '命宫';
|
|
116
|
+
const p = (r.palaces || []).find((x) => x.palace === name);
|
|
117
|
+
if (p) {
|
|
118
|
+
const stars = (p.stars || []).filter((s) => s.type === '主星').map((s) => s.name);
|
|
119
|
+
const trait = stars.map((s) => ZIWEI_STARS[s]?.trait).filter(Boolean).slice(0, 2).join(';');
|
|
120
|
+
return {
|
|
121
|
+
ok: true, year: null, topic,
|
|
122
|
+
answer: `「${topic.label}」看${name}:主星${stars.length ? stars.join('、') : '无正曜(借对宫)'}${trait ? `——${trait}` : ''}。宫支${p.branch},${p.stems ? '' : ''}生年四化与煞星的影响需结合报告里的对应分节一起看。`,
|
|
123
|
+
evidence: [`${name} ${p.branch}·${stars.join('、') || '无正曜'}`, ...(p.shensha || []).slice(0, 3)],
|
|
124
|
+
confidence: stars.length ? '强' : '弱',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
ok: false, year: null, topic,
|
|
130
|
+
answer: '没有识别到具体年份或方面。可以问「2028 年事业」「感情怎么样」「财运在 2030 年」;也可以直接问某个宫位,如「夫妻宫」。',
|
|
131
|
+
evidence: [], confidence: '弱',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function topicSentence(r, key) {
|
|
136
|
+
const map = { career: '事业', wealth: '财富', love: '感情', health: '健康', study: '学业', travel: '出行' };
|
|
137
|
+
return `这段时间可以重点对照报告里「${map[key] || '整体'}」一节看。`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function genericAnswer(r, q) {
|
|
141
|
+
const topic = topicOf(q);
|
|
142
|
+
const sections = Array.isArray(r.interpret?.sections) ? r.interpret.sections : [];
|
|
143
|
+
const hit = topic
|
|
144
|
+
? sections.find((s) => s.label.includes(topic.label) || s.text.includes(topic.label))
|
|
145
|
+
: null;
|
|
146
|
+
if (hit) {
|
|
147
|
+
return { ok: true, year: null, topic, answer: hit.text, evidence: [`引用分节:${hit.label}`], confidence: '中' };
|
|
148
|
+
}
|
|
149
|
+
const first = sections[0];
|
|
150
|
+
return {
|
|
151
|
+
ok: false, year: null, topic,
|
|
152
|
+
answer: first
|
|
153
|
+
? `这一盘的追问需要落到具体方面(事业 / 财富 / 感情 / 健康 / 学业 / 出行)。先看「${first.label}」这一节,或把问题说得更具体一点。`
|
|
154
|
+
: '这一盘暂时没有可追问的分节;先看完整解析,再问具体方面。',
|
|
155
|
+
evidence: [], confidence: '弱',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 回答一个追问。
|
|
161
|
+
* @param {object} r 已算好的盘(compute 的结果,带不带 interpret 都行)
|
|
162
|
+
* @param {string} question 用户问题
|
|
163
|
+
* @returns {{question:string, ok:boolean, year:number|null, topic:object|null, answer:string, evidence:string[], confidence:string}}
|
|
164
|
+
*/
|
|
165
|
+
export function followup(r, question) {
|
|
166
|
+
const q = String(question || '').trim();
|
|
167
|
+
const result = r && r.system === 'bazi' ? baziAnswer(r, q)
|
|
168
|
+
: r && r.system === 'ziwei' ? ziweiAnswer(r, q)
|
|
169
|
+
: genericAnswer(r, q);
|
|
170
|
+
return { question: q, ...result };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default followup;
|
package/engine/geo.js
CHANGED
|
@@ -143,7 +143,7 @@ const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
|
|
|
143
143
|
|
|
144
144
|
/** OpenStreetMap Nominatim:免密钥、支持中文。失败返回 null。
|
|
145
145
|
* UA 按 OSM 使用政策要求写明应用与联系方式(改版本时同步这里)。 */
|
|
146
|
-
const USER_AGENT = 'dsh-metaphysics/0.
|
|
146
|
+
const USER_AGENT = 'dsh-metaphysics/0.16.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
|
|
147
147
|
|
|
148
148
|
async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
|
|
149
149
|
if (typeof fetch !== 'function') return null;
|
package/engine/index.js
CHANGED
|
@@ -12,6 +12,8 @@ import { geocodeAddress, resolveAddress } from './geo.js';
|
|
|
12
12
|
import { toJDN, ganzhiName, dayGanzhiIndex } from './calendar.js';
|
|
13
13
|
import { randomOf, isSeed } from './random.js';
|
|
14
14
|
import { interpret, DIRECTIONS, DIRECTION_DEFAULT } from './interpret.js';
|
|
15
|
+
import { followup } from './followup.js';
|
|
16
|
+
import { crossCheck, crossText } from './crosscheck.js';
|
|
15
17
|
import { fail } from './fail.js';
|
|
16
18
|
import { assertCivil } from './validate.js';
|
|
17
19
|
|
|
@@ -291,6 +293,28 @@ export function compute(args = {}) {
|
|
|
291
293
|
direction: typeof args.direction === 'string' ? args.direction : 'general',
|
|
292
294
|
});
|
|
293
295
|
}
|
|
296
|
+
// 八字 × 紫微交叉验证:同一份出生信息再排另一套盘,并列两边对同一件事的说法
|
|
297
|
+
if (args.cross === true && (r.system === 'bazi' || r.system === 'ziwei')) {
|
|
298
|
+
const cdt = parseDateTime(args.datetime);
|
|
299
|
+
const common = {
|
|
300
|
+
...cdt,
|
|
301
|
+
gender: requireGender(args.gender),
|
|
302
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
303
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
304
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
305
|
+
trueSolar: args.trueSolar !== false,
|
|
306
|
+
};
|
|
307
|
+
const asOf = checkAsOf(args.asOf);
|
|
308
|
+
const other = r.system === 'bazi'
|
|
309
|
+
? buildZiwei({ ...common, leapAsNext: args.leapAsNext === true, asOf })
|
|
310
|
+
: buildBazi({ ...common, lateZi: args.lateZi !== false, nowYear: asOf ? asOf.year : undefined });
|
|
311
|
+
r.cross = crossCheck(r.system === 'bazi' ? r : other, r.system === 'ziwei' ? r : other);
|
|
312
|
+
if (r.interpret) {
|
|
313
|
+
r.interpret.sections.push({ key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: crossText(r.cross) });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// 追问:围绕已算好的盘回答「某一年 / 某方面」的具体问题
|
|
317
|
+
if (typeof args.ask === 'string' && args.ask.trim()) r.followup = followup(r, args.ask);
|
|
294
318
|
return r;
|
|
295
319
|
}
|
|
296
320
|
|
|
@@ -302,10 +326,14 @@ export function interpretText(i) {
|
|
|
302
326
|
if (i.answer) out.push(`▶ 先答所问:${i.answer}`);
|
|
303
327
|
if (i.plain) out.push(`▶ 说人话:${i.plain}`);
|
|
304
328
|
if (i.lead) out.push(i.lead);
|
|
329
|
+
// 一页读懂:报告顶部的五条结论,先给读的人一个「整体形状」
|
|
330
|
+
if (Array.isArray(i.outline) && i.outline.length) {
|
|
331
|
+
out.push('一页读懂:' + i.outline.map((o) => `${o.label}·${o.text}`).join(' '));
|
|
332
|
+
}
|
|
305
333
|
// 解析生成失败时不能只返回一个空壳:盘面数据仍然可用,但要让调用方知道"这段没生成出来"
|
|
306
334
|
if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
|
|
307
335
|
for (const d of i.dimensions || []) {
|
|
308
|
-
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}` : '';
|
|
336
|
+
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}${d.confidence ? ` 置信度:${d.confidence}` : ''}` : '';
|
|
309
337
|
const plain = d.plain ? `(${d.plain})` : '';
|
|
310
338
|
// 知识条目:「为什么这样断」+出处+可信度标注,是结论与依据之外的第三层
|
|
311
339
|
const know = (d.knowledge || [])
|
|
@@ -314,6 +342,12 @@ export function interpretText(i) {
|
|
|
314
342
|
out.push(`${d.label}:${plain}${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}${know}`);
|
|
315
343
|
}
|
|
316
344
|
for (const s of i.sections || []) out.push(`${s.label}:${s.text}`);
|
|
345
|
+
// 三张表:大运 / 流年 / 触发事件(纯文本对齐,工具输出也能直接读)
|
|
346
|
+
for (const t of i.tables || []) {
|
|
347
|
+
if (!Array.isArray(t.rows) || !t.rows.length) continue;
|
|
348
|
+
out.push(`【${t.title}】`);
|
|
349
|
+
out.push([t.columns.join(' | '), ...t.rows.map((r) => r.join(' | '))].join('\n'));
|
|
350
|
+
}
|
|
317
351
|
if (Array.isArray(i.followups) && i.followups.length) {
|
|
318
352
|
out.push('还可以这样追问:' + i.followups.map((q) => `「${q}」`).join(' '));
|
|
319
353
|
}
|
|
@@ -502,15 +536,25 @@ function computeRaw(args = {}) {
|
|
|
502
536
|
/** 给模型的紧凑文本(去掉大字段,保留摘要;带解析时附上解析) */
|
|
503
537
|
export function renderText(result) {
|
|
504
538
|
const base = result.summary ?? JSON.stringify(result, null, 2);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
539
|
+
const parts = [base];
|
|
540
|
+
if (result.interpret) {
|
|
541
|
+
const extra = interpretText(result.interpret);
|
|
542
|
+
if (extra) parts.push(extra);
|
|
543
|
+
}
|
|
544
|
+
if (result.cross) parts.push(`【八字 × 紫微 交叉验证】\n${crossText(result.cross)}`);
|
|
545
|
+
if (result.followup) {
|
|
546
|
+
const f = result.followup;
|
|
547
|
+
parts.push(`【追问】${f.question}\n${f.answer}${f.evidence && f.evidence.length ? `\n 依据:${f.evidence.join(';')} 置信度:${f.confidence}` : ''}`);
|
|
548
|
+
}
|
|
549
|
+
return parts.filter(Boolean).join('\n\n');
|
|
508
550
|
}
|
|
509
551
|
|
|
510
552
|
/** 对外暴露:地址解析(异步,含联网) */
|
|
511
553
|
export { geocodeAddress, resolveAddress };
|
|
512
554
|
|
|
513
555
|
export { buildBazi, buildLiuyao, tossLines };
|
|
556
|
+
export { followup };
|
|
557
|
+
export { crossCheck, crossText };
|
|
514
558
|
|
|
515
559
|
|
|
516
560
|
|
package/engine/interpret.js
CHANGED
|
@@ -162,6 +162,7 @@ function baziInterpret(r) {
|
|
|
162
162
|
const primary = pick(frame, 'primaryName', '未取定');
|
|
163
163
|
const gender = pick(r.input, 'gender', '');
|
|
164
164
|
const isMale = gender === 'male';
|
|
165
|
+
const spouseKey = isMale ? '财' : '官杀';
|
|
165
166
|
|
|
166
167
|
// 当前大运与流年
|
|
167
168
|
const age = Number(pick(r.liunian?.[0], 'age', NaN));
|
|
@@ -368,15 +369,47 @@ function baziInterpret(r) {
|
|
|
368
369
|
key: 'geju', label: '格局依据',
|
|
369
370
|
text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) => `${g.name}(${g.kind}):${g.evidence}`).join(';') || '未取到格局',
|
|
370
371
|
},
|
|
372
|
+
{
|
|
373
|
+
key: 'quant', label: '量化口径',
|
|
374
|
+
text: `本报告的量化只到「档位」:事业看十神权重(${topName} ${top[0]?.[1] ?? '—'})、财富看财星权重(${groups['财'] ?? 0})、感情看配偶星权重(${spouseKey} ${groups[spouseKey] ?? 0});大运/流年用「大吉 / 吉 / 平 / 忌」四档。不换算金额、不给健康诊断——那需要地域、家庭、行业与体检数据,命盘本身推不出来。`,
|
|
375
|
+
},
|
|
371
376
|
{
|
|
372
377
|
key: 'limits', label: '未纳入',
|
|
373
378
|
text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用以扶抑为主、调候只做简化提示,各派取法不同;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
|
|
374
379
|
},
|
|
375
380
|
];
|
|
376
381
|
|
|
382
|
+
// 一页读懂:报告顶部的五条结论(整体 / 事业 / 财富 / 感情 / 节奏)。
|
|
383
|
+
// 只做「摘要」,不引入新判断——全部取自下面已经算好的字段。
|
|
384
|
+
const outline = [
|
|
385
|
+
{ key: 'overall', label: '整体', text: `${level},喜${join(favor) || '—'}、忌${join(avoid) || '—'}` },
|
|
386
|
+
{ key: 'career', label: '事业', text: `主格${primary},${topName}偏旺` },
|
|
387
|
+
{ key: 'wealth', label: '财富', text: `财星权重 ${groups['财'] ?? 0}${favor.includes('金') ? ',喜金助财' : ''}` },
|
|
388
|
+
{ key: 'love', label: '感情', text: `${spouseKey}星权重 ${groups[spouseKey] ?? 0},日支 ${pick(r.pillars?.day, 'branch', '—')}` },
|
|
389
|
+
{ key: 'rhythm', label: '节奏', text: `当前${pick(cur, 'ganzhi', '—')}(${curTier}),${pick(ln, 'year', '')} ${pick(ln, 'ganzhi', '')}(${lnTier})` },
|
|
390
|
+
];
|
|
391
|
+
// 三张表:大运 / 流年 / 触发事件。都是「把已有字段摊平」,不新增算法。
|
|
392
|
+
const tables = [
|
|
393
|
+
{
|
|
394
|
+
key: 'dayun', title: '大运表', columns: ['大运', '年龄', '十神', '档位', '理由'],
|
|
395
|
+
rows: list.map((d) => [d.ganzhi, `${d.startAge}–${d.startAge + 9}`, d.stemTenGod || '', d.luck?.tier || '平', (d.luck?.reasons || []).join(',')]),
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
key: 'liunian', title: '流年表(近十年)', columns: ['年份', '干支', '十神', '档位', '与原局'],
|
|
399
|
+
rows: (r.liunian || []).slice(0, 10).map((l) => [String(l.year), l.ganzhi, l.stemTenGod || '', l.luck?.tier || '平', (l.relations || []).join('、')]),
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
const triggerRows = (r.liunian || [])
|
|
403
|
+
.filter((l) => Array.isArray(l.relations) && l.relations.length)
|
|
404
|
+
.slice(0, 10)
|
|
405
|
+
.map((l) => [String(l.year), l.relations.join('、'), `流年${l.ganzhi}与原局地支:${l.relations.join('、')}`]);
|
|
406
|
+
if (triggerRows.length) {
|
|
407
|
+
tables.push({ key: 'triggers', title: '触发事件表', columns: ['年份', '触发', '依据'], rows: triggerRows });
|
|
408
|
+
}
|
|
409
|
+
|
|
377
410
|
return {
|
|
378
411
|
lead: `下面按五个维度拆开说。所有内容来自你这张命盘的结构,讲的是倾向与建议,不是预言——方向供你参考,怎么走在你自己。`,
|
|
379
|
-
dimensions, sections,
|
|
412
|
+
dimensions, sections, outline, tables,
|
|
380
413
|
};
|
|
381
414
|
}
|
|
382
415
|
|
|
@@ -586,6 +619,10 @@ function ziweiInterpret(r) {
|
|
|
586
619
|
return `大限:${dx || '—'}。\n近五年流年:${yrs || '—'}。\n读法:大限定十年的主题,流年四化指出今年往哪边偏;两层方向相反时,以守住大限主题为主。`;
|
|
587
620
|
})(),
|
|
588
621
|
},
|
|
622
|
+
{
|
|
623
|
+
key: 'quant', label: '量化口径',
|
|
624
|
+
text: '紫微这边只数「宫位里有什么」:大限看落宫与四化命中数、流年看命宫与四化星数;不把星曜折算成分数,也不预测金额与疾病诊断。四化命中数只是结构计数,不等于吉凶强度。',
|
|
625
|
+
},
|
|
589
626
|
{
|
|
590
627
|
key: 'limits', label: '未纳入',
|
|
591
628
|
text: '本盘没有计算:飞星四化(宫干引发的连锁)、小限与大限的加权、流日流时的具体事项定位,以及双星组合的强弱细分。四化表取中州派常用口径,其他流派(如飞星派)结论会不同;这些不改变盘面事实,只影响轻重,所以只作方向参考。',
|
|
@@ -623,9 +660,56 @@ function ziweiInterpret(r) {
|
|
|
623
660
|
});
|
|
624
661
|
}
|
|
625
662
|
|
|
663
|
+
// 一页读懂:五条结论(整体 / 事业 / 财富 / 感情 / 节奏),全部取自下面已算好的字段
|
|
664
|
+
const outline = [
|
|
665
|
+
{ key: 'overall', label: '整体', text: `命宫${soulStar}${soulOwn ? '' : '(借对宫)'}${S.trait ? `,${S.trait}` : ''}` },
|
|
666
|
+
{ key: 'career', label: '事业', text: `官禄${careerStar || '无正曜'}` },
|
|
667
|
+
{ key: 'wealth', label: '财富', text: `财帛${wealthStar || '无正曜'}` },
|
|
668
|
+
{ key: 'love', label: '感情', text: `夫妻${spouseStar || '无正曜'}` },
|
|
669
|
+
{
|
|
670
|
+
key: 'rhythm', label: '节奏',
|
|
671
|
+
text: daxianPalace && daxianPalace.daxian
|
|
672
|
+
? `当前大限${daxianPalace.palace}(${daxianPalace.daxian.start}–${daxianPalace.daxian.end}岁)${r.liunian?.[0]?.year ? `,${r.liunian[0].year} 流年` : ''}`
|
|
673
|
+
: '当前大限未取到',
|
|
674
|
+
},
|
|
675
|
+
];
|
|
676
|
+
// 三张表:大限 / 流年 / 触发事件(大限四化)。只摊平已有字段,不加新判断。
|
|
677
|
+
const daxianSorted = (r.daxianList || []).slice().sort((a, b) => a.ages[0] - b.ages[0]);
|
|
678
|
+
const tables = [
|
|
679
|
+
{
|
|
680
|
+
key: 'daxian', title: '大限表', columns: ['大限', '年龄', '会主星', '四化'],
|
|
681
|
+
rows: daxianSorted.map((d) => [
|
|
682
|
+
d.palace,
|
|
683
|
+
`${d.ages[0]}–${d.ages[1]}`,
|
|
684
|
+
(d.mainStars || []).join('、'),
|
|
685
|
+
(d.mutagenHits || []).map((m) => `${m.star}${m.type}→${m.palace}`).join('、'),
|
|
686
|
+
]),
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
key: 'liunian', title: '流年表(近六年)', columns: ['年份', '干支', '年龄', '命宫', '四化'],
|
|
690
|
+
rows: (r.liunian || []).slice(0, 6).map((l) => [
|
|
691
|
+
String(l.year),
|
|
692
|
+
l.ganzhi,
|
|
693
|
+
String(l.age),
|
|
694
|
+
l.lifePalace ? l.lifePalace.palace : '—',
|
|
695
|
+
(l.mutagen || []).join('、'),
|
|
696
|
+
]),
|
|
697
|
+
},
|
|
698
|
+
];
|
|
699
|
+
const triggerRows = daxianSorted
|
|
700
|
+
.flatMap((d) => (d.mutagenHits || []).map((m) => [
|
|
701
|
+
`${d.palace}(${d.ages[0]}–${d.ages[1]}岁)`,
|
|
702
|
+
`${m.star}化${m.type}→${m.palace}`,
|
|
703
|
+
`大限${d.palace}引动`,
|
|
704
|
+
]))
|
|
705
|
+
.slice(0, 12);
|
|
706
|
+
if (triggerRows.length) {
|
|
707
|
+
tables.push({ key: 'triggers', title: '触发事件表(大限四化)', columns: ['大限', '触发', '依据'], rows: triggerRows });
|
|
708
|
+
}
|
|
709
|
+
|
|
626
710
|
return {
|
|
627
711
|
lead: `紫微斗数看的是你与环境的互动方式。下面按五个维度拆开,讲的是性格与处境的倾向,具体怎么选仍然在你。`,
|
|
628
|
-
dimensions, sections,
|
|
712
|
+
dimensions, sections, outline, tables,
|
|
629
713
|
};
|
|
630
714
|
}
|
|
631
715
|
|
|
@@ -1198,10 +1282,43 @@ function enrichPlain(result, r, ask) {
|
|
|
1198
1282
|
};
|
|
1199
1283
|
}
|
|
1200
1284
|
|
|
1285
|
+
/** 各体系采用的技法标签:证据卡第一行要写「这条结论是用什么方法得出的」 */
|
|
1286
|
+
const TECHNIQUE = {
|
|
1287
|
+
bazi: '八字(月令 · 十神 · 大运流年)',
|
|
1288
|
+
ziwei: '紫微(宫位 · 四化 · 大限流年)',
|
|
1289
|
+
liuyao: '六爻(卦宫 · 世应 · 动爻 · 日辰)',
|
|
1290
|
+
meihua: '梅花(体用 · 互卦 · 变卦)',
|
|
1291
|
+
daliuren: '六壬(月将加时 · 四课 · 三传)',
|
|
1292
|
+
qimen: '奇门(九宫 · 值符值使 · 用神)',
|
|
1293
|
+
tarot: '塔罗(牌阵 · 正逆位 · 元素)',
|
|
1294
|
+
hepan: '合盘(日主 · 夫妻宫 · 五行互补)',
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
/** 置信度:只按公开证据条数分档,不引入新算法——3 条以上「强」、2 条「中」、其余「弱」 */
|
|
1298
|
+
const confidenceOf = (dim) => {
|
|
1299
|
+
const n = Array.isArray(dim && dim.evidence) ? dim.evidence.length : 0;
|
|
1300
|
+
return n >= 3 ? '强' : n >= 2 ? '中' : '弱';
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
const withReportMeta = (body, r, ask) => enrichPlain({
|
|
1304
|
+
...body,
|
|
1305
|
+
moduleType: MODULE_TYPE[r.system] || 'natal',
|
|
1306
|
+
system: r.system,
|
|
1307
|
+
systemLabel: SYSTEM_LABEL[r.system] || r.system,
|
|
1308
|
+
title: '解析与详解',
|
|
1309
|
+
dimensions: (body.dimensions || []).map((d) => ({ ...d, confidence: d.confidence || confidenceOf(d) })),
|
|
1310
|
+
sections: body.sections || [],
|
|
1311
|
+
outline: body.outline || [],
|
|
1312
|
+
tables: body.tables || [],
|
|
1313
|
+
technique: TECHNIQUE[r.system] || SYSTEM_LABEL[r.system] || r.system,
|
|
1314
|
+
disclaimer: body.disclaimer || DISCLAIMER,
|
|
1315
|
+
ask,
|
|
1316
|
+
}, r, ask);
|
|
1317
|
+
|
|
1201
1318
|
export function interpret(r, opts) {
|
|
1202
1319
|
if (!isObj(r) || !r.system) return null;
|
|
1203
1320
|
const ask = askOf(opts);
|
|
1204
|
-
if (r.system === 'qimen') return
|
|
1321
|
+
if (r.system === 'qimen') return withReportMeta(interpretQimen(r, opts), r, ask);
|
|
1205
1322
|
const fn = BUILDERS[r.system];
|
|
1206
1323
|
if (!fn) return null;
|
|
1207
1324
|
let body;
|
|
@@ -1216,22 +1333,21 @@ export function interpret(r, opts) {
|
|
|
1216
1333
|
lead: '这一盘的解析暂未生成,请稍后重试。',
|
|
1217
1334
|
dimensions: [],
|
|
1218
1335
|
sections: [],
|
|
1336
|
+
outline: [],
|
|
1337
|
+
tables: [],
|
|
1338
|
+
technique: TECHNIQUE[r.system] || '',
|
|
1219
1339
|
disclaimer: DISCLAIMER,
|
|
1220
1340
|
error: String(e && e.message),
|
|
1221
1341
|
ask,
|
|
1222
1342
|
};
|
|
1223
1343
|
}
|
|
1224
1344
|
const tuned = applyAsk(body.dimensions || [], ask, body.lead || '');
|
|
1225
|
-
return
|
|
1226
|
-
moduleType: MODULE_TYPE[r.system] || 'natal',
|
|
1227
|
-
system: r.system,
|
|
1228
|
-
systemLabel: SYSTEM_LABEL[r.system] || r.system,
|
|
1229
|
-
title: '解析与详解',
|
|
1345
|
+
return withReportMeta({
|
|
1230
1346
|
lead: tuned.lead,
|
|
1231
1347
|
dimensions: tuned.dims,
|
|
1232
1348
|
sections: body.sections || [],
|
|
1233
|
-
|
|
1234
|
-
|
|
1349
|
+
outline: body.outline || [],
|
|
1350
|
+
tables: body.tables || [],
|
|
1235
1351
|
}, r, ask);
|
|
1236
1352
|
}
|
|
1237
1353
|
|
package/index.js
CHANGED
|
@@ -142,7 +142,14 @@ export function apply(ctx) {
|
|
|
142
142
|
enum: ['general', 'wealth', 'career', 'love', 'health', 'study', 'travel'],
|
|
143
143
|
description: '询问方向(仅配合 withInterpret 生效):general 综合、wealth 财运、career 事业、love 感情、health 健康、study 学业、travel 出行。默认 general。',
|
|
144
144
|
},
|
|
145
|
-
|
|
145
|
+
cross: {
|
|
146
|
+
type: 'boolean',
|
|
147
|
+
description: '八字 / 紫微专用:为同一份出生信息再排另一套盘,返回「八字 × 紫微 交叉验证」(同向 / 有张力 / 互补),不合并数值。',
|
|
148
|
+
},
|
|
149
|
+
ask: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
description: '追问:围绕已算好的盘回答「某一年 / 某方面」的问题,如「2027 年事业怎么样」「感情如何」。只取盘面已有字段,不重排、不猜测。',
|
|
152
|
+
}, withInterpret: {
|
|
146
153
|
type: 'boolean',
|
|
147
154
|
description: '是否附带「解析与详解」(五个维度 + 用神/格局等分节 + 免责声明);默认 false,避免默认输出过长。',
|
|
148
155
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-metaphysics",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "命理排盘 for DeepSeek Harness: eight systems — BaZi four pillars (42 shensha, pattern / useful-god / luck scoring), Zi Wei Dou Shu (34 patterns, 大限, 流年/流月/流日/流时四化), LiuYao with real line rendering, MeiHua YiShu, Da Liu Ren, Qi Men Dun Jia (chaibu / chaoshen / zhirun), compatibility charts (BaZi + Zi Wei) and Tarot; Ask(所问之事 + 询问方向)driven interpretation, plus a plain-language 解析与详解 module. Reproducible via seed/asOf. All computed locally, networking off by default — as an agent tool plus a Web panel.",
|
|
6
6
|
"keywords": [
|