dsh-metaphysics 0.15.0 → 0.17.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 +40 -0
- package/README.md +11 -0
- package/client.js +1028 -25
- package/engine/crosscheck.js +133 -0
- package/engine/followup.js +173 -0
- package/engine/geo.js +1 -1
- package/engine/index.js +99 -4
- package/engine/interpret.js +126 -10
- package/engine/rectify.js +130 -0
- package/engine/verify.js +64 -0
- package/index.js +28 -1
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -746,7 +746,7 @@ function resolveAddress(input) {
|
|
|
746
746
|
|
|
747
747
|
const DETAIL = /(区|县|镇|乡|村|小区|街道|路|号|巷|弄|组|队)/;
|
|
748
748
|
|
|
749
|
-
const USER_AGENT = 'dsh-metaphysics/0.
|
|
749
|
+
const USER_AGENT = 'dsh-metaphysics/0.17.0 (+https://github.com/xuanfu-web/dsh-metaphysics)';
|
|
750
750
|
|
|
751
751
|
async function onlineLookup(text, { timeoutMs = 2500 } = {}) {
|
|
752
752
|
if (typeof fetch !== 'function') return null;
|
|
@@ -5449,6 +5449,7 @@ function baziInterpret(r) {
|
|
|
5449
5449
|
const primary = pick(frame, 'primaryName', '未取定');
|
|
5450
5450
|
const gender = pick(r.input, 'gender', '');
|
|
5451
5451
|
const isMale = gender === 'male';
|
|
5452
|
+
const spouseKey = isMale ? '财' : '官杀';
|
|
5452
5453
|
|
|
5453
5454
|
const age = Number(pick(r.liunian?.[0], 'age', NaN));
|
|
5454
5455
|
const list = Array.isArray(r.dayun?.list) ? r.dayun.list : [];
|
|
@@ -5640,15 +5641,44 @@ function baziInterpret(r) {
|
|
|
5640
5641
|
key: 'geju', label: '格局依据',
|
|
5641
5642
|
text: (Array.isArray(frame.geju) ? frame.geju : []).map((g) => `${g.name}(${g.kind}):${g.evidence}`).join(';') || '未取到格局',
|
|
5642
5643
|
},
|
|
5644
|
+
{
|
|
5645
|
+
key: 'quant', label: '量化口径',
|
|
5646
|
+
text: `本报告的量化只到「档位」:事业看十神权重(${topName} ${top[0]?.[1] ?? '—'})、财富看财星权重(${groups['财'] ?? 0})、感情看配偶星权重(${spouseKey} ${groups[spouseKey] ?? 0});大运/流年用「大吉 / 吉 / 平 / 忌」四档。不换算金额、不给健康诊断——那需要地域、家庭、行业与体检数据,命盘本身推不出来。`,
|
|
5647
|
+
},
|
|
5643
5648
|
{
|
|
5644
5649
|
key: 'limits', label: '未纳入',
|
|
5645
5650
|
text: '本盘没有计算:流月与流日、神煞的加权轻重、地支藏干的动态引化(合冲后再论)。取用以扶抑为主、调候只做简化提示,各派取法不同;真太阳时未含历史时区与夏令时。把这些补上会改变"强弱"的边际判断,所以这里只给方向,不下断语。',
|
|
5646
5651
|
},
|
|
5647
5652
|
];
|
|
5648
5653
|
|
|
5654
|
+
const outline = [
|
|
5655
|
+
{ key: 'overall', label: '整体', text: `${level},喜${join(favor) || '—'}、忌${join(avoid) || '—'}` },
|
|
5656
|
+
{ key: 'career', label: '事业', text: `主格${primary},${topName}偏旺` },
|
|
5657
|
+
{ key: 'wealth', label: '财富', text: `财星权重 ${groups['财'] ?? 0}${favor.includes('金') ? ',喜金助财' : ''}` },
|
|
5658
|
+
{ key: 'love', label: '感情', text: `${spouseKey}星权重 ${groups[spouseKey] ?? 0},日支 ${pick(r.pillars?.day, 'branch', '—')}` },
|
|
5659
|
+
{ key: 'rhythm', label: '节奏', text: `当前${pick(cur, 'ganzhi', '—')}(${curTier}),${pick(ln, 'year', '')} ${pick(ln, 'ganzhi', '')}(${lnTier})` },
|
|
5660
|
+
];
|
|
5661
|
+
const tables = [
|
|
5662
|
+
{
|
|
5663
|
+
key: 'dayun', title: '大运表', columns: ['大运', '年龄', '十神', '档位', '理由'],
|
|
5664
|
+
rows: list.map((d) => [d.ganzhi, `${d.startAge}–${d.startAge + 9}`, d.stemTenGod || '', d.luck?.tier || '平', (d.luck?.reasons || []).join(',')]),
|
|
5665
|
+
},
|
|
5666
|
+
{
|
|
5667
|
+
key: 'liunian', title: '流年表(近十年)', columns: ['年份', '干支', '十神', '档位', '与原局'],
|
|
5668
|
+
rows: (r.liunian || []).slice(0, 10).map((l) => [String(l.year), l.ganzhi, l.stemTenGod || '', l.luck?.tier || '平', (l.relations || []).join('、')]),
|
|
5669
|
+
},
|
|
5670
|
+
];
|
|
5671
|
+
const triggerRows = (r.liunian || [])
|
|
5672
|
+
.filter((l) => Array.isArray(l.relations) && l.relations.length)
|
|
5673
|
+
.slice(0, 10)
|
|
5674
|
+
.map((l) => [String(l.year), l.relations.join('、'), `流年${l.ganzhi}与原局地支:${l.relations.join('、')}`]);
|
|
5675
|
+
if (triggerRows.length) {
|
|
5676
|
+
tables.push({ key: 'triggers', title: '触发事件表', columns: ['年份', '触发', '依据'], rows: triggerRows });
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5649
5679
|
return {
|
|
5650
5680
|
lead: `下面按五个维度拆开说。所有内容来自你这张命盘的结构,讲的是倾向与建议,不是预言——方向供你参考,怎么走在你自己。`,
|
|
5651
|
-
dimensions, sections,
|
|
5681
|
+
dimensions, sections, outline, tables,
|
|
5652
5682
|
};
|
|
5653
5683
|
}
|
|
5654
5684
|
|
|
@@ -5836,6 +5866,10 @@ function ziweiInterpret(r) {
|
|
|
5836
5866
|
return `大限:${dx || '—'}。\n近五年流年:${yrs || '—'}。\n读法:大限定十年的主题,流年四化指出今年往哪边偏;两层方向相反时,以守住大限主题为主。`;
|
|
5837
5867
|
})(),
|
|
5838
5868
|
},
|
|
5869
|
+
{
|
|
5870
|
+
key: 'quant', label: '量化口径',
|
|
5871
|
+
text: '紫微这边只数「宫位里有什么」:大限看落宫与四化命中数、流年看命宫与四化星数;不把星曜折算成分数,也不预测金额与疾病诊断。四化命中数只是结构计数,不等于吉凶强度。',
|
|
5872
|
+
},
|
|
5839
5873
|
{
|
|
5840
5874
|
key: 'limits', label: '未纳入',
|
|
5841
5875
|
text: '本盘没有计算:飞星四化(宫干引发的连锁)、小限与大限的加权、流日流时的具体事项定位,以及双星组合的强弱细分。四化表取中州派常用口径,其他流派(如飞星派)结论会不同;这些不改变盘面事实,只影响轻重,所以只作方向参考。',
|
|
@@ -5871,9 +5905,54 @@ function ziweiInterpret(r) {
|
|
|
5871
5905
|
});
|
|
5872
5906
|
}
|
|
5873
5907
|
|
|
5908
|
+
const outline = [
|
|
5909
|
+
{ key: 'overall', label: '整体', text: `命宫${soulStar}${soulOwn ? '' : '(借对宫)'}${S.trait ? `,${S.trait}` : ''}` },
|
|
5910
|
+
{ key: 'career', label: '事业', text: `官禄${careerStar || '无正曜'}` },
|
|
5911
|
+
{ key: 'wealth', label: '财富', text: `财帛${wealthStar || '无正曜'}` },
|
|
5912
|
+
{ key: 'love', label: '感情', text: `夫妻${spouseStar || '无正曜'}` },
|
|
5913
|
+
{
|
|
5914
|
+
key: 'rhythm', label: '节奏',
|
|
5915
|
+
text: daxianPalace && daxianPalace.daxian
|
|
5916
|
+
? `当前大限${daxianPalace.palace}(${daxianPalace.daxian.start}–${daxianPalace.daxian.end}岁)${r.liunian?.[0]?.year ? `,${r.liunian[0].year} 流年` : ''}`
|
|
5917
|
+
: '当前大限未取到',
|
|
5918
|
+
},
|
|
5919
|
+
];
|
|
5920
|
+
const daxianSorted = (r.daxianList || []).slice().sort((a, b) => a.ages[0] - b.ages[0]);
|
|
5921
|
+
const tables = [
|
|
5922
|
+
{
|
|
5923
|
+
key: 'daxian', title: '大限表', columns: ['大限', '年龄', '会主星', '四化'],
|
|
5924
|
+
rows: daxianSorted.map((d) => [
|
|
5925
|
+
d.palace,
|
|
5926
|
+
`${d.ages[0]}–${d.ages[1]}`,
|
|
5927
|
+
(d.mainStars || []).join('、'),
|
|
5928
|
+
(d.mutagenHits || []).map((m) => `${m.star}${m.type}→${m.palace}`).join('、'),
|
|
5929
|
+
]),
|
|
5930
|
+
},
|
|
5931
|
+
{
|
|
5932
|
+
key: 'liunian', title: '流年表(近六年)', columns: ['年份', '干支', '年龄', '命宫', '四化'],
|
|
5933
|
+
rows: (r.liunian || []).slice(0, 6).map((l) => [
|
|
5934
|
+
String(l.year),
|
|
5935
|
+
l.ganzhi,
|
|
5936
|
+
String(l.age),
|
|
5937
|
+
l.lifePalace ? l.lifePalace.palace : '—',
|
|
5938
|
+
(l.mutagen || []).join('、'),
|
|
5939
|
+
]),
|
|
5940
|
+
},
|
|
5941
|
+
];
|
|
5942
|
+
const triggerRows = daxianSorted
|
|
5943
|
+
.flatMap((d) => (d.mutagenHits || []).map((m) => [
|
|
5944
|
+
`${d.palace}(${d.ages[0]}–${d.ages[1]}岁)`,
|
|
5945
|
+
`${m.star}化${m.type}→${m.palace}`,
|
|
5946
|
+
`大限${d.palace}引动`,
|
|
5947
|
+
]))
|
|
5948
|
+
.slice(0, 12);
|
|
5949
|
+
if (triggerRows.length) {
|
|
5950
|
+
tables.push({ key: 'triggers', title: '触发事件表(大限四化)', columns: ['大限', '触发', '依据'], rows: triggerRows });
|
|
5951
|
+
}
|
|
5952
|
+
|
|
5874
5953
|
return {
|
|
5875
5954
|
lead: `紫微斗数看的是你与环境的互动方式。下面按五个维度拆开,讲的是性格与处境的倾向,具体怎么选仍然在你。`,
|
|
5876
|
-
dimensions, sections,
|
|
5955
|
+
dimensions, sections, outline, tables,
|
|
5877
5956
|
};
|
|
5878
5957
|
}
|
|
5879
5958
|
|
|
@@ -6409,10 +6488,41 @@ function enrichPlain(result, r, ask) {
|
|
|
6409
6488
|
};
|
|
6410
6489
|
}
|
|
6411
6490
|
|
|
6491
|
+
const TECHNIQUE = {
|
|
6492
|
+
bazi: '八字(月令 · 十神 · 大运流年)',
|
|
6493
|
+
ziwei: '紫微(宫位 · 四化 · 大限流年)',
|
|
6494
|
+
liuyao: '六爻(卦宫 · 世应 · 动爻 · 日辰)',
|
|
6495
|
+
meihua: '梅花(体用 · 互卦 · 变卦)',
|
|
6496
|
+
daliuren: '六壬(月将加时 · 四课 · 三传)',
|
|
6497
|
+
qimen: '奇门(九宫 · 值符值使 · 用神)',
|
|
6498
|
+
tarot: '塔罗(牌阵 · 正逆位 · 元素)',
|
|
6499
|
+
hepan: '合盘(日主 · 夫妻宫 · 五行互补)',
|
|
6500
|
+
};
|
|
6501
|
+
|
|
6502
|
+
const confidenceOf = (dim) => {
|
|
6503
|
+
const n = Array.isArray(dim && dim.evidence) ? dim.evidence.length : 0;
|
|
6504
|
+
return n >= 3 ? '强' : n >= 2 ? '中' : '弱';
|
|
6505
|
+
};
|
|
6506
|
+
|
|
6507
|
+
const withReportMeta = (body, r, ask) => enrichPlain({
|
|
6508
|
+
...body,
|
|
6509
|
+
moduleType: MODULE_TYPE[r.system] || 'natal',
|
|
6510
|
+
system: r.system,
|
|
6511
|
+
systemLabel: SYSTEM_LABEL[r.system] || r.system,
|
|
6512
|
+
title: '解析与详解',
|
|
6513
|
+
dimensions: (body.dimensions || []).map((d) => ({ ...d, confidence: d.confidence || confidenceOf(d) })),
|
|
6514
|
+
sections: body.sections || [],
|
|
6515
|
+
outline: body.outline || [],
|
|
6516
|
+
tables: body.tables || [],
|
|
6517
|
+
technique: TECHNIQUE[r.system] || SYSTEM_LABEL[r.system] || r.system,
|
|
6518
|
+
disclaimer: body.disclaimer || DISCLAIMER,
|
|
6519
|
+
ask,
|
|
6520
|
+
}, r, ask);
|
|
6521
|
+
|
|
6412
6522
|
function interpret(r, opts) {
|
|
6413
6523
|
if (!isObj(r) || !r.system) return null;
|
|
6414
6524
|
const ask = askOf(opts);
|
|
6415
|
-
if (r.system === 'qimen') return
|
|
6525
|
+
if (r.system === 'qimen') return withReportMeta(interpretQimen(r, opts), r, ask);
|
|
6416
6526
|
const fn = BUILDERS[r.system];
|
|
6417
6527
|
if (!fn) return null;
|
|
6418
6528
|
let body;
|
|
@@ -6427,28 +6537,475 @@ function interpret(r, opts) {
|
|
|
6427
6537
|
lead: '这一盘的解析暂未生成,请稍后重试。',
|
|
6428
6538
|
dimensions: [],
|
|
6429
6539
|
sections: [],
|
|
6540
|
+
outline: [],
|
|
6541
|
+
tables: [],
|
|
6542
|
+
technique: TECHNIQUE[r.system] || '',
|
|
6430
6543
|
disclaimer: DISCLAIMER,
|
|
6431
6544
|
error: String(e && e.message),
|
|
6432
6545
|
ask,
|
|
6433
6546
|
};
|
|
6434
6547
|
}
|
|
6435
6548
|
const tuned = applyAsk(body.dimensions || [], ask, body.lead || '');
|
|
6436
|
-
return
|
|
6437
|
-
moduleType: MODULE_TYPE[r.system] || 'natal',
|
|
6438
|
-
system: r.system,
|
|
6439
|
-
systemLabel: SYSTEM_LABEL[r.system] || r.system,
|
|
6440
|
-
title: '解析与详解',
|
|
6549
|
+
return withReportMeta({
|
|
6441
6550
|
lead: tuned.lead,
|
|
6442
6551
|
dimensions: tuned.dims,
|
|
6443
6552
|
sections: body.sections || [],
|
|
6444
|
-
|
|
6445
|
-
|
|
6553
|
+
outline: body.outline || [],
|
|
6554
|
+
tables: body.tables || [],
|
|
6446
6555
|
}, r, ask);
|
|
6447
6556
|
}
|
|
6448
6557
|
|
|
6449
6558
|
return { DIMENSIONS, MODULE_TYPE, SYSTEM_LABEL, DIRECTIONS, DIRECTION_DEFAULT, dirOf, hexInfo, interpret };
|
|
6450
6559
|
})();
|
|
6451
6560
|
|
|
6561
|
+
__mods.followup = (function () {
|
|
6562
|
+
const { ZIWEI_STARS } = __mods['interpret-data'];
|
|
6563
|
+
|
|
6564
|
+
const TOPICS = [
|
|
6565
|
+
{ key: 'career', label: '事业', words: ['事业', '工作', '跳槽', '升职', '创业', '职业', '岗位', '离职', '加薪', '面试', 'offer'] },
|
|
6566
|
+
{ key: 'wealth', label: '财富', words: ['财', '钱', '收入', '投资', '买房', '存款', '亏', '赚', '分红'] },
|
|
6567
|
+
{ key: 'love', label: '感情', words: ['感情', '婚', '恋', '对象', '分手', '复合', '姻缘', '结婚', '桃花'] },
|
|
6568
|
+
{ key: 'health', label: '健康', words: ['健康', '病', '身体', '手术', '失眠', '体检', '医院'] },
|
|
6569
|
+
{ key: 'study', label: '学业', words: ['学业', '考试', '学习', '考研', '考公', '证书', '升学'] },
|
|
6570
|
+
{ key: 'travel', label: '出行', words: ['出行', '搬家', '旅行', '出国', '迁', '远行'] },
|
|
6571
|
+
];
|
|
6572
|
+
|
|
6573
|
+
const ZW_PALACE_BY_TOPIC = { career: '官禄', wealth: '财帛', love: '夫妻', health: '疾厄', study: '官禄', travel: '迁移', overall: '命宫' };
|
|
6574
|
+
|
|
6575
|
+
const parseYear = (q, r) => {
|
|
6576
|
+
const m = String(q).match(/(20\d{2})\s*年?/);
|
|
6577
|
+
if (m) return Number(m[1]);
|
|
6578
|
+
const base = Array.isArray(r.liunian) && r.liunian[0] ? Number(r.liunian[0].year) : null;
|
|
6579
|
+
if (!base) return null;
|
|
6580
|
+
if (/明年/.test(q)) return base + 1;
|
|
6581
|
+
if (/后年/.test(q)) return base + 2;
|
|
6582
|
+
if (/去年/.test(q)) return base - 1;
|
|
6583
|
+
if (/今年|本年/.test(q)) return base;
|
|
6584
|
+
return null;
|
|
6585
|
+
};
|
|
6586
|
+
|
|
6587
|
+
const topicOf = (q) => TOPICS.find((t) => t.words.some((w) => String(q).includes(w))) || null;
|
|
6588
|
+
|
|
6589
|
+
function baziAnswer(r, q) {
|
|
6590
|
+
const year = parseYear(q, r);
|
|
6591
|
+
const topic = topicOf(q);
|
|
6592
|
+
const liunian = Array.isArray(r.liunian) ? r.liunian : [];
|
|
6593
|
+
const dayun = Array.isArray(r.dayun?.list) ? r.dayun.list : [];
|
|
6594
|
+
const years = liunian.map((l) => l.year);
|
|
6595
|
+
if (year && !liunian.some((l) => Number(l.year) === year)) {
|
|
6596
|
+
return {
|
|
6597
|
+
ok: false, year, topic,
|
|
6598
|
+
answer: `这一盘已排的流年范围是 ${years[0]}–${years[years.length - 1]},没有 ${year} 年。可以把问题改到范围内,或用 asOf 指定你想看的年份重新排盘。`,
|
|
6599
|
+
evidence: [`已排流年:${years.join('、')}`], confidence: '弱',
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
if (year) {
|
|
6603
|
+
const l = liunian.find((x) => Number(x.year) === year);
|
|
6604
|
+
const d = dayun.find((x) => Number(x.startAge) <= Number(l.age) && Number(l.age) <= Number(x.startAge) + 9) || null;
|
|
6605
|
+
const tier = l.luck?.tier || '平';
|
|
6606
|
+
const reasons = (l.luck?.reasons || []).slice(0, 3);
|
|
6607
|
+
const rel = (l.relations || []).join('、');
|
|
6608
|
+
const topicLine = topic ? topicSentence(r, topic.key) : '';
|
|
6609
|
+
return {
|
|
6610
|
+
ok: true, year, topic,
|
|
6611
|
+
answer: `${year} 年(${l.ganzhi})流年,十神为${l.stemTenGod || '—'},档位「${tier}」;所行大运 ${d ? `${d.ganzhi}(${d.luck?.tier || '平'})` : '未取到'}。` +
|
|
6612
|
+
`${reasons.length ? `理由:${reasons.join(',')}。` : ''}${rel ? `与该年地支相关的原局关系:${rel}。` : ''}${topicLine}`,
|
|
6613
|
+
evidence: [
|
|
6614
|
+
`流年 ${l.year} ${l.ganzhi}·${l.stemTenGod || ''}·${tier}`,
|
|
6615
|
+
d ? `大运 ${d.ganzhi}·${d.luck?.tier || '平'}` : '',
|
|
6616
|
+
rel ? `原局关系 ${rel}` : '',
|
|
6617
|
+
topic ? `问题方向:${topic.label}` : '',
|
|
6618
|
+
].filter(Boolean),
|
|
6619
|
+
confidence: d && reasons.length ? '强' : '中',
|
|
6620
|
+
};
|
|
6621
|
+
}
|
|
6622
|
+
if (topic) {
|
|
6623
|
+
const near = { wealth: 'overall', study: 'work', travel: 'overall' }[topic.key] || topic.key;
|
|
6624
|
+
const dim = (r.interpret?.dimensions || []).find((x) => x.key === topic.key || x.key === near);
|
|
6625
|
+
if (dim) {
|
|
6626
|
+
const groups = (r.frame && r.frame.godStats && r.frame.godStats.groups) || {};
|
|
6627
|
+
const quant = topic.key === 'wealth'
|
|
6628
|
+
? ` 财星权重 ${Number(groups['财'] || 0)}(只作结构参考,不含金额)。`
|
|
6629
|
+
: '';
|
|
6630
|
+
return {
|
|
6631
|
+
ok: true, year: null, topic,
|
|
6632
|
+
answer: `按${topic.label}这条线看:${dim.text}${quant}${dim.advice ? ` 建议:${dim.advice}` : ''}`,
|
|
6633
|
+
evidence: (dim.evidence || []).slice(0, 4), confidence: dim.confidence || '中',
|
|
6634
|
+
};
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
return {
|
|
6638
|
+
ok: false, year: null, topic,
|
|
6639
|
+
answer: `这个问题里没有识别到具体的年份或方面。可以这样问:「2027 年事业怎么样」「财运如何」「感情在 2028 年」;已排流年是 ${years[0]}–${years[years.length - 1]}。`,
|
|
6640
|
+
evidence: [`可问年份:${years.join('、')}`], confidence: '弱',
|
|
6641
|
+
};
|
|
6642
|
+
}
|
|
6643
|
+
|
|
6644
|
+
function ziweiAnswer(r, q) {
|
|
6645
|
+
const year = parseYear(q, r);
|
|
6646
|
+
const topic = topicOf(q);
|
|
6647
|
+
const liunian = Array.isArray(r.liunian) ? r.liunian : [];
|
|
6648
|
+
if (year && !liunian.some((l) => Number(l.year) === year)) {
|
|
6649
|
+
return {
|
|
6650
|
+
ok: false, year, topic,
|
|
6651
|
+
answer: `这一盘的流年表覆盖 ${liunian[0]?.year}–${liunian[liunian.length - 1]?.year},没有 ${year} 年;可以换一个年份,或用 asOf 指定起点重新排盘。`,
|
|
6652
|
+
evidence: [`流年表:${liunian.map((l) => l.year).join('、')}`], confidence: '弱',
|
|
6653
|
+
};
|
|
6654
|
+
}
|
|
6655
|
+
if (year) {
|
|
6656
|
+
const l = liunian.find((x) => Number(x.year) === year);
|
|
6657
|
+
const dx = l.daxian || {};
|
|
6658
|
+
return {
|
|
6659
|
+
ok: true, year, topic,
|
|
6660
|
+
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}」对应的宫位信号见下条依据。` : ''}`,
|
|
6661
|
+
evidence: [
|
|
6662
|
+
`流年命宫 ${l.lifePalace ? l.lifePalace.palace : '—'}(${l.lifePalace ? l.lifePalace.branch : '—'})`,
|
|
6663
|
+
`四化 ${(l.mutagen || []).join('、') || '不明显'}`,
|
|
6664
|
+
dx.palace ? `大限 ${dx.palace}·${dx.start}–${dx.end}岁` : '',
|
|
6665
|
+
].filter(Boolean),
|
|
6666
|
+
confidence: (l.mutagen || []).length >= 3 ? '强' : '中',
|
|
6667
|
+
};
|
|
6668
|
+
}
|
|
6669
|
+
if (topic) {
|
|
6670
|
+
const name = ZW_PALACE_BY_TOPIC[topic.key] || '命宫';
|
|
6671
|
+
const p = (r.palaces || []).find((x) => x.palace === name);
|
|
6672
|
+
if (p) {
|
|
6673
|
+
const stars = (p.stars || []).filter((s) => s.type === '主星').map((s) => s.name);
|
|
6674
|
+
const trait = stars.map((s) => ZIWEI_STARS[s]?.trait).filter(Boolean).slice(0, 2).join(';');
|
|
6675
|
+
return {
|
|
6676
|
+
ok: true, year: null, topic,
|
|
6677
|
+
answer: `「${topic.label}」看${name}:主星${stars.length ? stars.join('、') : '无正曜(借对宫)'}${trait ? `——${trait}` : ''}。宫支${p.branch},${p.stems ? '' : ''}生年四化与煞星的影响需结合报告里的对应分节一起看。`,
|
|
6678
|
+
evidence: [`${name} ${p.branch}·${stars.join('、') || '无正曜'}`, ...(p.shensha || []).slice(0, 3)],
|
|
6679
|
+
confidence: stars.length ? '强' : '弱',
|
|
6680
|
+
};
|
|
6681
|
+
}
|
|
6682
|
+
}
|
|
6683
|
+
return {
|
|
6684
|
+
ok: false, year: null, topic,
|
|
6685
|
+
answer: '没有识别到具体年份或方面。可以问「2028 年事业」「感情怎么样」「财运在 2030 年」;也可以直接问某个宫位,如「夫妻宫」。',
|
|
6686
|
+
evidence: [], confidence: '弱',
|
|
6687
|
+
};
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
function topicSentence(r, key) {
|
|
6691
|
+
const map = { career: '事业', wealth: '财富', love: '感情', health: '健康', study: '学业', travel: '出行' };
|
|
6692
|
+
return `这段时间可以重点对照报告里「${map[key] || '整体'}」一节看。`;
|
|
6693
|
+
}
|
|
6694
|
+
|
|
6695
|
+
function genericAnswer(r, q) {
|
|
6696
|
+
const topic = topicOf(q);
|
|
6697
|
+
const sections = Array.isArray(r.interpret?.sections) ? r.interpret.sections : [];
|
|
6698
|
+
const hit = topic
|
|
6699
|
+
? sections.find((s) => s.label.includes(topic.label) || s.text.includes(topic.label))
|
|
6700
|
+
: null;
|
|
6701
|
+
if (hit) {
|
|
6702
|
+
return { ok: true, year: null, topic, answer: hit.text, evidence: [`引用分节:${hit.label}`], confidence: '中' };
|
|
6703
|
+
}
|
|
6704
|
+
const first = sections[0];
|
|
6705
|
+
return {
|
|
6706
|
+
ok: false, year: null, topic,
|
|
6707
|
+
answer: first
|
|
6708
|
+
? `这一盘的追问需要落到具体方面(事业 / 财富 / 感情 / 健康 / 学业 / 出行)。先看「${first.label}」这一节,或把问题说得更具体一点。`
|
|
6709
|
+
: '这一盘暂时没有可追问的分节;先看完整解析,再问具体方面。',
|
|
6710
|
+
evidence: [], confidence: '弱',
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
|
|
6714
|
+
function followup(r, question) {
|
|
6715
|
+
const q = String(question || '').trim();
|
|
6716
|
+
const result = r && r.system === 'bazi' ? baziAnswer(r, q)
|
|
6717
|
+
: r && r.system === 'ziwei' ? ziweiAnswer(r, q)
|
|
6718
|
+
: genericAnswer(r, q);
|
|
6719
|
+
return { question: q, ...result };
|
|
6720
|
+
}
|
|
6721
|
+
|
|
6722
|
+
return { followup };
|
|
6723
|
+
})();
|
|
6724
|
+
|
|
6725
|
+
__mods.crosscheck = (function () {
|
|
6726
|
+
const { ZIWEI_STARS } = __mods['interpret-data'];
|
|
6727
|
+
|
|
6728
|
+
const groupsOf = (bazi) => (bazi && bazi.frame && bazi.frame.godStats && bazi.frame.godStats.groups) || {};
|
|
6729
|
+
const topOf = (groups) => Object.entries(groups).sort((a, b) => Number(b[1]) - Number(a[1]))[0] || null;
|
|
6730
|
+
const zwPalace = (zw, name) => (zw.palaces || []).find((p) => p.palace === name) || null;
|
|
6731
|
+
const zwMain = (p) => (p && p.stars ? p.stars.filter((s) => s.type === '主星').map((s) => s.name) : []);
|
|
6732
|
+
const countSha = (p) => (p && p.stars ? p.stars.filter((s) => s.type === '煞星').length : 0);
|
|
6733
|
+
const tierOf = (bazi) => {
|
|
6734
|
+
const age = Number(bazi.liunian && bazi.liunian[0] ? bazi.liunian[0].age : NaN);
|
|
6735
|
+
const list = (bazi.dayun && bazi.dayun.list) || [];
|
|
6736
|
+
let cur = null;
|
|
6737
|
+
for (const d of list) if (Number.isFinite(age) && Number(d.startAge) <= age) cur = d;
|
|
6738
|
+
return cur ? (cur.luck && cur.luck.tier) || '平' : '—';
|
|
6739
|
+
};
|
|
6740
|
+
const daxianOf = (zw) => {
|
|
6741
|
+
const age = Number(zw.liunian && zw.liunian[0] ? zw.liunian[0].age : NaN);
|
|
6742
|
+
for (const p of zw.palaces || []) {
|
|
6743
|
+
const d = p.daxian || {};
|
|
6744
|
+
if (Number.isFinite(age) && Number(d.start) <= age && age <= Number(d.end)) return p;
|
|
6745
|
+
}
|
|
6746
|
+
return null;
|
|
6747
|
+
};
|
|
6748
|
+
|
|
6749
|
+
function crossCheck(bazi, zw) {
|
|
6750
|
+
const groups = groupsOf(bazi);
|
|
6751
|
+
const top = topOf(groups);
|
|
6752
|
+
const items = [];
|
|
6753
|
+
const push = (key, label, baziText, ziweiText, stance, note) => items.push({ key, label, bazi: baziText, ziwei: ziweiText, stance, note });
|
|
6754
|
+
|
|
6755
|
+
{
|
|
6756
|
+
const career = zwPalace(zw, '官禄');
|
|
6757
|
+
const main = zwMain(career);
|
|
6758
|
+
const baziStrong = Number(groups['官杀'] || 0) + Number(groups['食伤'] || 0) >= 2;
|
|
6759
|
+
const ziweiHas = main.length > 0;
|
|
6760
|
+
const stance = baziStrong && ziweiHas ? '同向' : !baziStrong && !ziweiHas ? '同向' : '有张力';
|
|
6761
|
+
push('career', '事业主线',
|
|
6762
|
+
`${top ? `十神以${top[0]}最重(${top[1]})` : '十神未取到'};官杀+食伤权重 ${(Number(groups['官杀'] || 0) + Number(groups['食伤'] || 0)).toFixed(1)}`,
|
|
6763
|
+
main.length ? `官禄宫主星 ${main.join('、')}${ZIWEI_STARS[main[0]] ? `(${ZIWEI_STARS[main[0]].work})` : ''}` : '官禄无正曜',
|
|
6764
|
+
stance,
|
|
6765
|
+
stance === '同向' ? '两边都指向(或都不强调)事业主线,方向可互参' : '一边强调事业主线、一边信号弱,建议以信号强的一边为主,另一边作补充');
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
{
|
|
6769
|
+
const wealth = zwPalace(zw, '财帛');
|
|
6770
|
+
const main = zwMain(wealth);
|
|
6771
|
+
const sha = countSha(wealth);
|
|
6772
|
+
const w = Number(groups['财'] || 0);
|
|
6773
|
+
const baziStrong = w >= 2;
|
|
6774
|
+
const ziweiStrong = main.length > 0;
|
|
6775
|
+
const stance = baziStrong && ziweiStrong ? '同向' : !baziStrong && !ziweiStrong ? '同向' : '有张力';
|
|
6776
|
+
push('wealth', '财运结构',
|
|
6777
|
+
`财星权重 ${w}${baziStrong ? '(偏强)' : '(偏弱)'}`,
|
|
6778
|
+
`${main.length ? `财帛主星 ${main.join('、')}` : '财帛无正曜'}${sha ? `;煞星 ${sha} 颗` : ''}`,
|
|
6779
|
+
stance,
|
|
6780
|
+
'两套体系对「财」的定义不同:八字看十神财星,紫微看财帛宫,只作对照,不作数值合并');
|
|
6781
|
+
}
|
|
6782
|
+
|
|
6783
|
+
{
|
|
6784
|
+
const spouse = zwPalace(zw, '夫妻');
|
|
6785
|
+
const main = zwMain(spouse);
|
|
6786
|
+
const isMale = bazi.input && bazi.input.gender === 'male';
|
|
6787
|
+
const key = isMale ? '财' : '官杀';
|
|
6788
|
+
const v = Number(groups[key] || 0);
|
|
6789
|
+
const baziStrong = v >= 2;
|
|
6790
|
+
const ziweiStrong = main.length > 0;
|
|
6791
|
+
const stance = baziStrong && ziweiStrong ? '同向' : !baziStrong && !ziweiStrong ? '同向' : '有张力';
|
|
6792
|
+
push('love', '感情线索',
|
|
6793
|
+
`${isMale ? '妻' : '夫'}星(${key})权重 ${v}${baziStrong ? '(不弱)' : '(偏弱)'}`,
|
|
6794
|
+
main.length ? `夫妻宫主星 ${main.join('、')}${ZIWEI_STARS[main[0]] ? `(${ZIWEI_STARS[main[0]].love})` : ''}` : '夫妻宫无正曜',
|
|
6795
|
+
stance,
|
|
6796
|
+
stance === '同向' ? '两边对感情的强弱判断接近' : '一边说缘分不弱、一边主星信号弱,宜按「多主动创造场景」处理');
|
|
6797
|
+
}
|
|
6798
|
+
|
|
6799
|
+
{
|
|
6800
|
+
const wx = bazi.wuxing || {};
|
|
6801
|
+
const weakest = Object.entries(wx).sort((a, b) => Number(a[1]) - Number(b[1]))[0];
|
|
6802
|
+
const health = zwPalace(zw, '疾厄');
|
|
6803
|
+
const main = zwMain(health);
|
|
6804
|
+
const sha = countSha(health);
|
|
6805
|
+
push('health', '身体关注点',
|
|
6806
|
+
weakest ? `最弱五行 ${weakest[0]}(${weakest[1]})` : '五行未取到',
|
|
6807
|
+
`${main.length ? `疾厄主星 ${main.join('、')}` : '疾厄无正曜'}${sha ? `;煞星 ${sha} 颗` : ''}`,
|
|
6808
|
+
'互补',
|
|
6809
|
+
'两边指向的器官/系统不一定相同,合并起来看更稳;任何健康问题以体检与医生为准');
|
|
6810
|
+
}
|
|
6811
|
+
|
|
6812
|
+
{
|
|
6813
|
+
const t = tierOf(bazi);
|
|
6814
|
+
const d = daxianOf(zw);
|
|
6815
|
+
const sha = d ? countSha(d) : 0;
|
|
6816
|
+
const good = ['吉', '大吉'];
|
|
6817
|
+
const bad = ['忌', '大忌'];
|
|
6818
|
+
const baziGood = good.includes(t);
|
|
6819
|
+
const baziBad = bad.includes(t);
|
|
6820
|
+
const ziweiHeavy = sha >= 2;
|
|
6821
|
+
let stance = '有张力';
|
|
6822
|
+
let note = '一边顺、一边压,按「大方向跟顺的一边、具体动作避开压的年份」处理';
|
|
6823
|
+
if ((baziGood && !ziweiHeavy) || (baziBad && ziweiHeavy) || (!baziGood && !baziBad && !ziweiHeavy)) {
|
|
6824
|
+
stance = '同向';
|
|
6825
|
+
note = '两边对当前阶段的松紧判断接近';
|
|
6826
|
+
}
|
|
6827
|
+
push('rhythm', '当前节奏',
|
|
6828
|
+
`大运档位 ${t}`,
|
|
6829
|
+
`${d ? `大限在${d.palace}` : '大限未取到'}${sha ? `;煞星 ${sha} 颗` : ';无煞星'}`,
|
|
6830
|
+
stance, note);
|
|
6831
|
+
}
|
|
6832
|
+
|
|
6833
|
+
return { items };
|
|
6834
|
+
}
|
|
6835
|
+
|
|
6836
|
+
function crossText(cross) {
|
|
6837
|
+
if (!cross || !Array.isArray(cross.items)) return '';
|
|
6838
|
+
return cross.items
|
|
6839
|
+
.map((i) => `· ${i.label}:八字——${i.bazi};紫微——${i.ziwei};判断:${i.stance}(${i.note})`)
|
|
6840
|
+
.join('\n');
|
|
6841
|
+
}
|
|
6842
|
+
|
|
6843
|
+
return { crossCheck, crossText };
|
|
6844
|
+
})();
|
|
6845
|
+
|
|
6846
|
+
__mods.verify = (function () {
|
|
6847
|
+
const KIND_LABEL = {
|
|
6848
|
+
career: '事业变动',
|
|
6849
|
+
wealth: '财运起伏',
|
|
6850
|
+
love: '感情变化',
|
|
6851
|
+
move: '搬迁 / 出行',
|
|
6852
|
+
health: '健康问题',
|
|
6853
|
+
other: '其他大事',
|
|
6854
|
+
};
|
|
6855
|
+
|
|
6856
|
+
const THEME = (l) => {
|
|
6857
|
+
const rel = (l.relations || []).join('');
|
|
6858
|
+
if (/冲/.test(rel)) return '有冲:变动 / 摩擦';
|
|
6859
|
+
if (/刑/.test(rel)) return '有刑:内耗 / 反复';
|
|
6860
|
+
if (/合/.test(rel)) return '有合:合作 / 牵引';
|
|
6861
|
+
const tier = l.luck && l.luck.tier;
|
|
6862
|
+
if (tier === '大吉' || tier === '吉') return '流年偏顺';
|
|
6863
|
+
if (tier === '大忌' || tier === '忌') return '流年偏压';
|
|
6864
|
+
return '信号平缓';
|
|
6865
|
+
};
|
|
6866
|
+
|
|
6867
|
+
function pastSignals(chart, untilYear) {
|
|
6868
|
+
const until = Number(untilYear) || Infinity;
|
|
6869
|
+
return (chart.liunian || [])
|
|
6870
|
+
.filter((l) => Number(l.year) <= until)
|
|
6871
|
+
.map((l) => ({
|
|
6872
|
+
year: Number(l.year),
|
|
6873
|
+
ganzhi: l.ganzhi,
|
|
6874
|
+
tier: (l.luck && l.luck.tier) || '平',
|
|
6875
|
+
tenGod: l.stemTenGod || '',
|
|
6876
|
+
relations: l.relations || [],
|
|
6877
|
+
theme: THEME(l),
|
|
6878
|
+
}));
|
|
6879
|
+
}
|
|
6880
|
+
|
|
6881
|
+
function verifyFit(rows, marks) {
|
|
6882
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
6883
|
+
const m = marks || {};
|
|
6884
|
+
const picked = list.filter((r) => m[r.year] === 'yes' || m[r.year] === 'no');
|
|
6885
|
+
const yes = picked.filter((r) => m[r.year] === 'yes').length;
|
|
6886
|
+
const no = picked.length - yes;
|
|
6887
|
+
const rate = picked.length ? yes / picked.length : null;
|
|
6888
|
+
const level = rate == null ? '未评' : rate >= 0.7 ? '高' : rate >= 0.4 ? '中' : '低';
|
|
6889
|
+
let advice = '';
|
|
6890
|
+
if (rate == null) advice = '先勾几项「准 / 不准 / 不确定」,再看得出的贴合度。';
|
|
6891
|
+
else if (level === '高') advice = '前事与流年信号对得较顺,可以按这张盘继续看;有分歧时以事实为准。';
|
|
6892
|
+
else if (level === '中') advice = '有一部分对得上:先用「追问」核对具体年份,再决定信到什么程度。';
|
|
6893
|
+
else advice = '对得不多:优先核对出生时间、出生地(时区/真太阳时)与晚子时口径,而不是改结论。';
|
|
6894
|
+
return { answered: picked.length, yes, no, rate, level, advice, labels: { ...KIND_LABEL } };
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
return { KIND_LABEL, pastSignals, verifyFit };
|
|
6898
|
+
})();
|
|
6899
|
+
|
|
6900
|
+
__mods.rectify = (function () {
|
|
6901
|
+
const { buildBazi } = __mods.bazi;
|
|
6902
|
+
|
|
6903
|
+
const KINDS = ['career', 'wealth', 'love', 'move', 'health', 'other'];
|
|
6904
|
+
const HOURS = [0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 23];
|
|
6905
|
+
|
|
6906
|
+
const TEN_GOD = (l) => l.stemTenGod || '';
|
|
6907
|
+
const has = (l, re) => re.test((l.relations || []).join(''));
|
|
6908
|
+
const tier = (l) => (l.luck && l.luck.tier) || '平';
|
|
6909
|
+
|
|
6910
|
+
function matchEvent(l, kind) {
|
|
6911
|
+
if (!l) return false;
|
|
6912
|
+
const tg = TEN_GOD(l);
|
|
6913
|
+
const t = tier(l);
|
|
6914
|
+
switch (kind) {
|
|
6915
|
+
case 'career': return /官|杀|食|伤/.test(tg) || has(l, /冲|合/) || t === '大吉' || t === '大忌';
|
|
6916
|
+
case 'wealth': return /财|食|伤/.test(tg) || t === '大吉' || t === '吉';
|
|
6917
|
+
case 'love': return /财|官|杀/.test(tg) || has(l, /合|冲/);
|
|
6918
|
+
case 'move': return has(l, /冲|合/);
|
|
6919
|
+
case 'health': return has(l, /刑|害|冲/) || t === '大忌' || t === '忌';
|
|
6920
|
+
default: return t === '大吉' || t === '大忌' || has(l, /冲|刑|合/);
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
|
|
6924
|
+
function rectifyBirth(opts) {
|
|
6925
|
+
const base = opts && opts.base;
|
|
6926
|
+
const events = (opts && opts.events) || [];
|
|
6927
|
+
const gender = opts && opts.gender;
|
|
6928
|
+
if (!base || !Number.isInteger(base.year) || !Number.isInteger(base.month) || !Number.isInteger(base.day)) {
|
|
6929
|
+
const e = new Error('生时校正需要出生日期 { year, month, day }');
|
|
6930
|
+
e.code = 'E_INPUT';
|
|
6931
|
+
throw e;
|
|
6932
|
+
}
|
|
6933
|
+
if (!gender) {
|
|
6934
|
+
const e = new Error('生时校正需要性别');
|
|
6935
|
+
e.code = 'E_INPUT';
|
|
6936
|
+
throw e;
|
|
6937
|
+
}
|
|
6938
|
+
const clean = events
|
|
6939
|
+
.filter((x) => x && Number.isInteger(Number(x.year)))
|
|
6940
|
+
.map((x) => ({ year: Number(x.year), kind: KINDS.includes(x.kind) ? x.kind : 'other' }));
|
|
6941
|
+
if (!clean.length) {
|
|
6942
|
+
const e = new Error('生时校正需要至少一条「过去发生的事」(年份 + 类别)');
|
|
6943
|
+
e.code = 'E_INPUT';
|
|
6944
|
+
throw e;
|
|
6945
|
+
}
|
|
6946
|
+
const nowYear = Math.min(...clean.map((x) => x.year));
|
|
6947
|
+
const hours = Array.isArray(opts.hours) && opts.hours.length
|
|
6948
|
+
? opts.hours.filter((h) => Number.isInteger(h) && h >= 0 && h <= 23)
|
|
6949
|
+
: HOURS;
|
|
6950
|
+
|
|
6951
|
+
const candidates = hours.map((hour) => {
|
|
6952
|
+
const chart = buildBazi({
|
|
6953
|
+
...base,
|
|
6954
|
+
hour,
|
|
6955
|
+
minute: 0,
|
|
6956
|
+
gender,
|
|
6957
|
+
lateZi: opts.lateZi !== false,
|
|
6958
|
+
trueSolar: opts.trueSolar !== false,
|
|
6959
|
+
address: typeof opts.address === 'string' ? opts.address : undefined,
|
|
6960
|
+
longitude: typeof opts.longitude === 'number' ? opts.longitude : undefined,
|
|
6961
|
+
geo: opts.geo && typeof opts.geo === 'object' ? opts.geo : undefined,
|
|
6962
|
+
nowYear,
|
|
6963
|
+
});
|
|
6964
|
+
const matched = [];
|
|
6965
|
+
const missed = [];
|
|
6966
|
+
for (const ev of clean) {
|
|
6967
|
+
const l = (chart.liunian || []).find((x) => Number(x.year) === ev.year);
|
|
6968
|
+
(matchEvent(l, ev.kind) ? matched : missed).push(ev);
|
|
6969
|
+
}
|
|
6970
|
+
return {
|
|
6971
|
+
hour,
|
|
6972
|
+
label: `${String(hour).padStart(2, '0')}:00 起`,
|
|
6973
|
+
dayGanzhi: chart.pillars.day.ganzhi,
|
|
6974
|
+
hourGanzhi: chart.pillars.hour.ganzhi,
|
|
6975
|
+
score: clean.length ? matched.length / clean.length : 0,
|
|
6976
|
+
hits: matched.length,
|
|
6977
|
+
total: clean.length,
|
|
6978
|
+
matchedYears: matched.map((x) => x.year),
|
|
6979
|
+
missedYears: missed.map((x) => x.year),
|
|
6980
|
+
};
|
|
6981
|
+
});
|
|
6982
|
+
candidates.sort((a, b) => b.score - a.score || a.hour - b.hour);
|
|
6983
|
+
|
|
6984
|
+
const days = [...new Set(candidates.map((c) => c.dayGanzhi))];
|
|
6985
|
+
const best = candidates[0];
|
|
6986
|
+
const second = candidates[1];
|
|
6987
|
+
const tie = second && second.score === best.score;
|
|
6988
|
+
|
|
6989
|
+
return {
|
|
6990
|
+
system: 'bazi',
|
|
6991
|
+
base,
|
|
6992
|
+
events: clean,
|
|
6993
|
+
hours,
|
|
6994
|
+
candidates,
|
|
6995
|
+
best: best || null,
|
|
6996
|
+
tie,
|
|
6997
|
+
dayChanged: days.length > 1,
|
|
6998
|
+
note: `候选 ${candidates.length} 个:命中数最高 ${best ? best.hits : 0}/${clean.length}${tie ? `(有并列:${candidates.filter((c) => c.score === best.score).map((c) => c.hour).join('、')} 点)` : ''}。` +
|
|
6999
|
+
(days.length > 1 ? `注意:跨 23:00 的候选会改变日柱(${days.join(' / ')}),这类差异比时柱更关键。` : '候选之间只差时柱(日柱未变)。') +
|
|
7000
|
+
' 这是缩小范围的参考,不替代出生记录;命中数接近时不要硬选。',
|
|
7001
|
+
};
|
|
7002
|
+
}
|
|
7003
|
+
|
|
7004
|
+
const RECTIFY_KINDS = KINDS;
|
|
7005
|
+
|
|
7006
|
+
return { matchEvent, rectifyBirth, RECTIFY_KINDS };
|
|
7007
|
+
})();
|
|
7008
|
+
|
|
6452
7009
|
__mods.index = (function () {
|
|
6453
7010
|
const { buildBazi } = __mods.bazi;
|
|
6454
7011
|
const { buildLiuyao, tossLines } = __mods.liuyao;
|
|
@@ -6463,6 +7020,10 @@ const { geocodeAddress, resolveAddress } = __mods.geo;
|
|
|
6463
7020
|
const { toJDN, ganzhiName, dayGanzhiIndex } = __mods.calendar;
|
|
6464
7021
|
const { randomOf, isSeed } = __mods.random;
|
|
6465
7022
|
const { interpret, DIRECTIONS, DIRECTION_DEFAULT } = __mods.interpret;
|
|
7023
|
+
const { followup } = __mods.followup;
|
|
7024
|
+
const { crossCheck, crossText } = __mods.crosscheck;
|
|
7025
|
+
const { pastSignals, verifyFit } = __mods.verify;
|
|
7026
|
+
const { rectifyBirth } = __mods.rectify;
|
|
6466
7027
|
const { fail } = __mods.fail;
|
|
6467
7028
|
const { assertCivil } = __mods.validate;
|
|
6468
7029
|
|
|
@@ -6722,6 +7283,59 @@ function compute(args = {}) {
|
|
|
6722
7283
|
direction: typeof args.direction === 'string' ? args.direction : 'general',
|
|
6723
7284
|
});
|
|
6724
7285
|
}
|
|
7286
|
+
if (args.cross === true && (r.system === 'bazi' || r.system === 'ziwei')) {
|
|
7287
|
+
const cdt = parseDateTime(args.datetime);
|
|
7288
|
+
const common = {
|
|
7289
|
+
...cdt,
|
|
7290
|
+
gender: requireGender(args.gender),
|
|
7291
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
7292
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
7293
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
7294
|
+
trueSolar: args.trueSolar !== false,
|
|
7295
|
+
};
|
|
7296
|
+
const asOf = checkAsOf(args.asOf);
|
|
7297
|
+
const other = r.system === 'bazi'
|
|
7298
|
+
? buildZiwei({ ...common, leapAsNext: args.leapAsNext === true, asOf })
|
|
7299
|
+
: buildBazi({ ...common, lateZi: args.lateZi !== false, nowYear: asOf ? asOf.year : undefined });
|
|
7300
|
+
r.cross = crossCheck(r.system === 'bazi' ? r : other, r.system === 'ziwei' ? r : other);
|
|
7301
|
+
if (r.interpret) {
|
|
7302
|
+
r.interpret.sections.push({ key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: crossText(r.cross) });
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
if (Number.isInteger(args.verifyPast) && args.verifyPast > 0 && r.system === 'bazi') {
|
|
7306
|
+
const years = Math.min(Math.max(args.verifyPast, 1), 10);
|
|
7307
|
+
const from = Number(r.liunianFrom) - years;
|
|
7308
|
+
const pastChart = buildBazi({
|
|
7309
|
+
...parseDateTime(args.datetime),
|
|
7310
|
+
gender: requireGender(args.gender),
|
|
7311
|
+
lateZi: args.lateZi !== false,
|
|
7312
|
+
trueSolar: args.trueSolar !== false,
|
|
7313
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
7314
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
7315
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
7316
|
+
nowYear: from,
|
|
7317
|
+
});
|
|
7318
|
+
r.past = {
|
|
7319
|
+
rows: pastSignals(pastChart, Number(r.liunianFrom) - 1),
|
|
7320
|
+
years,
|
|
7321
|
+
until: Number(r.liunianFrom) - 1,
|
|
7322
|
+
};
|
|
7323
|
+
}
|
|
7324
|
+
if (args.rectify === true && r.system === 'bazi') {
|
|
7325
|
+
const base = parseDateTime(args.datetime);
|
|
7326
|
+
r.rectify = rectifyBirth({
|
|
7327
|
+
base: { year: base.year, month: base.month, day: base.day, minute: base.minute },
|
|
7328
|
+
gender: requireGender(args.gender),
|
|
7329
|
+
events: Array.isArray(args.events) ? args.events : [],
|
|
7330
|
+
hours: Array.isArray(args.rectifyHours) ? args.rectifyHours : undefined,
|
|
7331
|
+
lateZi: args.lateZi !== false,
|
|
7332
|
+
trueSolar: args.trueSolar !== false,
|
|
7333
|
+
longitude: typeof args.longitude === 'number' ? args.longitude : undefined,
|
|
7334
|
+
address: typeof args.address === 'string' ? args.address : undefined,
|
|
7335
|
+
geo: args.geo && typeof args.geo === 'object' ? args.geo : undefined,
|
|
7336
|
+
});
|
|
7337
|
+
}
|
|
7338
|
+
if (typeof args.ask === 'string' && args.ask.trim()) r.followup = followup(r, args.ask);
|
|
6725
7339
|
return r;
|
|
6726
7340
|
}
|
|
6727
7341
|
|
|
@@ -6731,9 +7345,12 @@ function interpretText(i) {
|
|
|
6731
7345
|
if (i.answer) out.push(`▶ 先答所问:${i.answer}`);
|
|
6732
7346
|
if (i.plain) out.push(`▶ 说人话:${i.plain}`);
|
|
6733
7347
|
if (i.lead) out.push(i.lead);
|
|
7348
|
+
if (Array.isArray(i.outline) && i.outline.length) {
|
|
7349
|
+
out.push('一页读懂:' + i.outline.map((o) => `${o.label}·${o.text}`).join(' '));
|
|
7350
|
+
}
|
|
6734
7351
|
if (i.error) out.push(`⚠ 解析生成失败:${i.error}(上方盘面数据本身可用)`);
|
|
6735
7352
|
for (const d of i.dimensions || []) {
|
|
6736
|
-
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}` : '';
|
|
7353
|
+
const evidence = Array.isArray(d.evidence) && d.evidence.length ? `\n 依据:${d.evidence.join(';')}${d.confidence ? ` 置信度:${d.confidence}` : ''}` : '';
|
|
6737
7354
|
const plain = d.plain ? `(${d.plain})` : '';
|
|
6738
7355
|
const know = (d.knowledge || [])
|
|
6739
7356
|
.map((k) => `\n 规则·${k.gist}〔${k.statusLabel}〕${k.detail} 出处:${k.source}${k.note ? ` 注:${k.note}` : ''}`)
|
|
@@ -6741,6 +7358,11 @@ function interpretText(i) {
|
|
|
6741
7358
|
out.push(`${d.label}:${plain}${d.text}${d.advice ? ` 建议:${d.advice}` : ''}${evidence}${know}`);
|
|
6742
7359
|
}
|
|
6743
7360
|
for (const s of i.sections || []) out.push(`${s.label}:${s.text}`);
|
|
7361
|
+
for (const t of i.tables || []) {
|
|
7362
|
+
if (!Array.isArray(t.rows) || !t.rows.length) continue;
|
|
7363
|
+
out.push(`【${t.title}】`);
|
|
7364
|
+
out.push([t.columns.join(' | '), ...t.rows.map((r) => r.join(' | '))].join('\n'));
|
|
7365
|
+
}
|
|
6744
7366
|
if (Array.isArray(i.followups) && i.followups.length) {
|
|
6745
7367
|
out.push('还可以这样追问:' + i.followups.map((q) => `「${q}」`).join(' '));
|
|
6746
7368
|
}
|
|
@@ -6918,12 +7540,31 @@ function computeRaw(args = {}) {
|
|
|
6918
7540
|
|
|
6919
7541
|
function renderText(result) {
|
|
6920
7542
|
const base = result.summary ?? JSON.stringify(result, null, 2);
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
7543
|
+
const parts = [base];
|
|
7544
|
+
if (result.interpret) {
|
|
7545
|
+
const extra = interpretText(result.interpret);
|
|
7546
|
+
if (extra) parts.push(extra);
|
|
7547
|
+
}
|
|
7548
|
+
if (result.cross) parts.push(`【八字 × 紫微 交叉验证】\n${crossText(result.cross)}`);
|
|
7549
|
+
if (result.followup) {
|
|
7550
|
+
const f = result.followup;
|
|
7551
|
+
parts.push(`【追问】${f.question}\n${f.answer}${f.evidence && f.evidence.length ? `\n 依据:${f.evidence.join(';')} 置信度:${f.confidence}` : ''}`);
|
|
7552
|
+
}
|
|
7553
|
+
if (result.past && Array.isArray(result.past.rows) && result.past.rows.length) {
|
|
7554
|
+
parts.push(`【验前事】过去 ${result.past.years} 年的流年信号(只读,不改盘)\n` +
|
|
7555
|
+
result.past.rows.map((r) => `· ${r.year} ${r.ganzhi} · ${r.theme}(${r.tier}${r.tenGod ? `,${r.tenGod}` : ''})`).join('\n') +
|
|
7556
|
+
'\n用法:在面板里勾「准 / 不准 / 不确定」后算贴合度;贴合度低时先核对出生时间、时区与晚子时口径。');
|
|
7557
|
+
}
|
|
7558
|
+
if (result.rectify && Array.isArray(result.rectify.candidates)) {
|
|
7559
|
+
const rc = result.rectify;
|
|
7560
|
+
parts.push(`【生时校正】候选时辰 ${rc.candidates.length} 个(固定规则打分,只缩小范围)\n` +
|
|
7561
|
+
rc.candidates.slice(0, 8).map((c) => `· ${c.label} 时柱 ${c.hourGanzhi} / 日柱 ${c.dayGanzhi} · 命中 ${c.hits}/${c.total}`).join('\n') +
|
|
7562
|
+
`\n${rc.note}`);
|
|
7563
|
+
}
|
|
7564
|
+
return parts.filter(Boolean).join('\n\n');
|
|
6924
7565
|
}
|
|
6925
7566
|
|
|
6926
|
-
return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, parseDateTime, compute, interpretText, renderText };
|
|
7567
|
+
return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAddress, resolveAddress, buildBazi, buildLiuyao, tossLines, followup, pastSignals, verifyFit, rectifyBirth, crossCheck, crossText, parseDateTime, compute, interpretText, renderText };
|
|
6927
7568
|
})();
|
|
6928
7569
|
|
|
6929
7570
|
const Engine = __mods.index;
|
|
@@ -7311,6 +7952,48 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
7311
7952
|
.mp-it-sec{margin-bottom:11px}
|
|
7312
7953
|
.mp-it-sh{font-size:13px;font-weight:700;color:var(--lu);letter-spacing:.06em;margin-bottom:4px}
|
|
7313
7954
|
.mp-it-stext{font-size:13px;line-height:1.9;color:var(--ink2);text-align:justify}
|
|
7955
|
+
.mp-outline{margin:12px 16px 0;border:1px solid var(--line);border-left:3px solid var(--lu);border-radius:8px;background:var(--card);padding:10px 12px}
|
|
7956
|
+
.mp-outline-h{font-size:12px;font-weight:700;letter-spacing:.12em;color:var(--lu);margin-bottom:6px;font-family:system-ui,sans-serif}
|
|
7957
|
+
.mp-outline-row{display:grid;grid-template-columns:56px minmax(0,1fr);gap:8px;padding:3px 0;font-size:13px;line-height:1.7;color:var(--ink2)}
|
|
7958
|
+
.mp-outline-row b{color:var(--label);font-weight:600;font-family:system-ui,sans-serif}
|
|
7959
|
+
.mp-toc{display:flex;flex-wrap:wrap;gap:6px 10px;margin:10px 16px 0;padding:8px 10px;border:1px dashed var(--line);border-radius:8px;font-size:12px;font-family:system-ui,sans-serif}
|
|
7960
|
+
.mp-toc a{color:var(--lu);text-decoration:none;border-bottom:1px dotted rgba(var(--lu-rgb),.5)}
|
|
7961
|
+
.mp-toc a:hover{color:var(--ink)}
|
|
7962
|
+
.mp-it-ev{margin-top:7px;border:1px solid var(--line);border-radius:6px;background:var(--band);font-size:12px;font-family:system-ui,sans-serif}
|
|
7963
|
+
.mp-it-ev>summary{cursor:pointer;padding:6px 9px;color:var(--muted);list-style:none}
|
|
7964
|
+
.mp-it-ev>summary::-webkit-details-marker{display:none}
|
|
7965
|
+
.mp-it-ev>summary::before{content:'▸ ';color:var(--label)}
|
|
7966
|
+
.mp-it-ev[open]>summary::before{content:'▾ '}
|
|
7967
|
+
.mp-it-ev-body{padding:2px 9px 8px;display:flex;flex-direction:column;gap:4px}
|
|
7968
|
+
.mp-it-ev-item{color:var(--ink2);line-height:1.7;padding-left:12px;position:relative}
|
|
7969
|
+
.mp-it-ev-item::before{content:'·';position:absolute;left:2px;color:var(--label)}
|
|
7970
|
+
.mp-it-ev-tech{margin-top:4px;color:var(--label);font-size:11px;letter-spacing:.06em}
|
|
7971
|
+
.mp-it-conf{font-size:11px;font-family:system-ui,sans-serif;border-radius:999px;padding:1px 8px;margin-left:6px;border:1px solid var(--line);color:var(--muted)}
|
|
7972
|
+
.mp-it-conf.c-强{color:var(--lu);border-color:rgba(var(--lu-rgb),.4)}
|
|
7973
|
+
.mp-it-conf.c-中{color:var(--label);border-color:var(--line)}
|
|
7974
|
+
.mp-it-conf.c-弱{color:var(--red);border-color:rgba(var(--red-rgb),.4)}
|
|
7975
|
+
.mp-tables{border-top:1px solid var(--line);padding:12px 16px 4px;background:var(--card)}
|
|
7976
|
+
.mp-table-wrap{margin-bottom:12px}
|
|
7977
|
+
.mp-table-h{font-size:12px;font-weight:700;letter-spacing:.1em;color:var(--lu);margin-bottom:5px;font-family:system-ui,sans-serif}
|
|
7978
|
+
.mp-table-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:6px}
|
|
7979
|
+
.mp-table{width:100%;border-collapse:collapse;font-size:12px;line-height:1.6;color:var(--ink2);font-family:system-ui,sans-serif}
|
|
7980
|
+
.mp-table th,.mp-table td{padding:5px 9px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);white-space:nowrap}
|
|
7981
|
+
.mp-table th{background:var(--band);color:var(--label);font-weight:600;font-size:11px;letter-spacing:.06em}
|
|
7982
|
+
.mp-table tbody tr:last-child td{border-bottom:none}
|
|
7983
|
+
.mp-table tbody tr:nth-child(even) td{background:rgba(0,0,0,.02)}
|
|
7984
|
+
@media print{
|
|
7985
|
+
.mp-toc{display:none}
|
|
7986
|
+
.mp-it.folded .mp-it-wrap{display:block}
|
|
7987
|
+
.mp-table-scroll{overflow:visible}
|
|
7988
|
+
.mp-table th,.mp-table td{white-space:normal}
|
|
7989
|
+
.mp-table-wrap,.mp-it-sec,.mp-it-dim{break-inside:avoid}
|
|
7990
|
+
.mp-table-wrap{page-break-inside:avoid}
|
|
7991
|
+
}
|
|
7992
|
+
.mp-fu{margin:12px 16px;border:1px solid var(--line);border-left:3px solid var(--lu);border-radius:8px;background:var(--card);padding:10px 12px;font-size:13px;line-height:1.8;color:var(--ink2)}
|
|
7993
|
+
.mp-fu.miss{border-left-color:var(--red)}
|
|
7994
|
+
.mp-fu-h{font-size:12px;font-weight:700;letter-spacing:.08em;color:var(--lu);margin-bottom:4px;font-family:system-ui,sans-serif}
|
|
7995
|
+
.mp-fu.miss .mp-fu-h{color:var(--red)}
|
|
7996
|
+
.mp-fu-ev{margin-top:5px;font-size:12px;color:var(--muted);font-family:system-ui,sans-serif}
|
|
7314
7997
|
.mp-it-dis{padding:11px 16px;border-top:1px solid var(--line);background:var(--band);font-size:12px;line-height:1.75;color:var(--muted);font-family:system-ui,sans-serif}
|
|
7315
7998
|
|
|
7316
7999
|
@media (max-width:640px){
|
|
@@ -8161,15 +8844,30 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
8161
8844
|
if (!hasBody && !it.error && !it.answer) return '';
|
|
8162
8845
|
const folded = !!(opt && opt.folded);
|
|
8163
8846
|
const dims = (it.dimensions || []).map((d, i) => `<div class="mp-it-dim${d.focused ? ' focus' : ''}">
|
|
8164
|
-
<div class="mp-it-dh"><span class="mp-it-dno">${i + 1}</span><span class="mp-it-dlabel">${esc(d.label)}</span>${d.focused ? '<span class="mp-it-focus">最贴近你的问题</span>' : ''}</div>
|
|
8847
|
+
<div class="mp-it-dh"><span class="mp-it-dno">${i + 1}</span><span class="mp-it-dlabel">${esc(d.label)}</span>${d.focused ? '<span class="mp-it-focus">最贴近你的问题</span>' : ''}${d.confidence ? `<span class="mp-it-conf c-${esc(d.confidence)}">置信度 ${esc(d.confidence)}</span>` : ''}</div>
|
|
8165
8848
|
${d.plain ? `<div class="mp-it-dplain">${esc(d.plain)}</div>` : ''}
|
|
8166
8849
|
<div class="mp-it-dtext">${esc(d.text)}</div>
|
|
8167
|
-
${(d.evidence && d.evidence.length) ? `<div class="mp-it-
|
|
8850
|
+
${(d.evidence && d.evidence.length) ? `<details class="mp-it-ev"><summary>依据 · ${esc(d.evidence.length)} 条盘面事实</summary><div class="mp-it-ev-body">${d.evidence.map((x) => `<span class="mp-it-ev-item">${esc(x)}</span>`).join('')}<span class="mp-it-ev-tech">技法:${esc(it.technique || it.systemLabel || '')}</span></div></details>` : ''}
|
|
8168
8851
|
${(d.knowledge && d.knowledge.length) ? `<div class="mp-it-know">${d.knowledge.map((k) => `<div class="know-row"><span class="know-status ${k.status}">${esc(k.statusLabel)}</span><span class="know-body"><b>${esc(k.gist)}</b>${esc(k.detail)}${k.source ? `<i>出处:${esc(k.source)}</i>` : ''}${k.note ? `<em>注:${esc(k.note)}</em>` : ''}</span></div>`).join('')}</div>` : ''}
|
|
8169
8852
|
${d.advice ? `<div class="mp-it-adv"><b>建议</b>${esc(d.advice)}</div>` : ''}
|
|
8170
8853
|
</div>`).join('');
|
|
8854
|
+
const outline = (it.outline && it.outline.length)
|
|
8855
|
+
? `<div class="mp-outline"><div class="mp-outline-h">一页读懂</div>${it.outline.map((o) => `<div class="mp-outline-row"><b>${esc(o.label)}</b><span>${esc(o.text)}</span></div>`).join('')}</div>`
|
|
8856
|
+
: '';
|
|
8857
|
+
const toc = (it.sections && it.sections.length)
|
|
8858
|
+
? `<nav class="mp-toc" aria-label="报告目录">${it.sections.map((s) => `<a href="#mp-sec-${esc(s.key)}">${esc(s.label)}</a>`).join('')}</nav>`
|
|
8859
|
+
: '';
|
|
8860
|
+
const tables = (it.tables && it.tables.length)
|
|
8861
|
+
? `<div class="mp-tables">${it.tables.filter((t) => Array.isArray(t.rows) && t.rows.length).map((t) => `<div class="mp-table-wrap">
|
|
8862
|
+
<div class="mp-table-h">${esc(t.title)}</div>
|
|
8863
|
+
<div class="mp-table-scroll"><table class="mp-table">
|
|
8864
|
+
<thead><tr>${t.columns.map((c) => `<th>${esc(c)}</th>`).join('')}</tr></thead>
|
|
8865
|
+
<tbody>${t.rows.map((row) => `<tr>${row.map((c) => `<td>${esc(c)}</td>`).join('')}</tr>`).join('')}</tbody>
|
|
8866
|
+
</table></div>
|
|
8867
|
+
</div>`).join('')}</div>`
|
|
8868
|
+
: '';
|
|
8171
8869
|
const secs = (it.sections && it.sections.length)
|
|
8172
|
-
? `<div class="mp-it-secs">${it.sections.map((s) => `<div class="mp-it-sec">
|
|
8870
|
+
? `<div class="mp-it-secs">${it.sections.map((s) => `<div class="mp-it-sec" id="mp-sec-${esc(s.key)}">
|
|
8173
8871
|
<div class="mp-it-sh">${esc(s.label)}</div>
|
|
8174
8872
|
<div class="mp-it-stext">${esc(s.text)}</div>
|
|
8175
8873
|
</div>`).join('')}</div>`
|
|
@@ -8199,8 +8897,11 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
8199
8897
|
${plain}
|
|
8200
8898
|
${it.lead ? `<div class="mp-it-lead">${esc(it.lead)}</div>` : ''}
|
|
8201
8899
|
${it.error ? `<div class="mp-it-err"><b>⚠ 解析生成失败</b>${esc(it.error)}——上方盘面数据本身可用</div>` : ''}
|
|
8900
|
+
${outline}
|
|
8901
|
+
${toc}
|
|
8202
8902
|
<div class="mp-it-body">${dims}</div>
|
|
8203
8903
|
${secs}
|
|
8904
|
+
${tables}
|
|
8204
8905
|
${fu}
|
|
8205
8906
|
${gloss}
|
|
8206
8907
|
<div class="mp-it-dis">${esc(it.disclaimer)}</div>
|
|
@@ -8208,9 +8909,127 @@ return { interpret, DIRECTIONS, DIRECTION_DEFAULT, BRIGHTNESS, flowOf, geocodeAd
|
|
|
8208
8909
|
</div>`;
|
|
8209
8910
|
};
|
|
8210
8911
|
|
|
8912
|
+
pp.followupHTML = function (fx) {
|
|
8913
|
+
if (!fx) return '';
|
|
8914
|
+
const ev = (fx.evidence && fx.evidence.length)
|
|
8915
|
+
? `<div class="mp-fu-ev">依据:${fx.evidence.map((x) => esc(x)).join(';')} 置信度:${esc(fx.confidence || '弱')}</div>`
|
|
8916
|
+
: '';
|
|
8917
|
+
return `<div class="mp-fu${fx.ok ? '' : ' miss'}">
|
|
8918
|
+
<div class="mp-fu-h">追问 · ${esc(fx.question)}</div>
|
|
8919
|
+
<div class="mp-fu-a">${esc(fx.answer)}</div>
|
|
8920
|
+
${ev}
|
|
8921
|
+
</div>`;
|
|
8922
|
+
};
|
|
8923
|
+
|
|
8211
8924
|
pp.resultWithInterpret = function (html, it, opt) {
|
|
8212
8925
|
return (html || '') + pp.interpretHTML(it, opt);
|
|
8213
8926
|
};
|
|
8927
|
+
|
|
8928
|
+
pp.summaryCardPNG = function (r, it) {
|
|
8929
|
+
try {
|
|
8930
|
+
if (typeof document === 'undefined' || !document.createElement) return '';
|
|
8931
|
+
const outline = (it && it.outline) || [];
|
|
8932
|
+
const tables = ((it && it.tables) || []).filter((t) => Array.isArray(t.rows) && t.rows.length);
|
|
8933
|
+
const systemLabel = (it && it.systemLabel) || (r && r.system) || '命理报告';
|
|
8934
|
+
const W = 900;
|
|
8935
|
+
const PAD = 44;
|
|
8936
|
+
const line = 26;
|
|
8937
|
+
const font = (size, bold) => `${bold ? '600 ' : ''}${size}px "PingFang SC","Microsoft YaHei","Noto Sans CJK SC",sans-serif`;
|
|
8938
|
+
const canvas = document.createElement('canvas');
|
|
8939
|
+
const ctx = canvas.getContext ? canvas.getContext('2d') : null;
|
|
8940
|
+
if (!ctx) return '';
|
|
8941
|
+
const wrap = (text, maxWidth) => {
|
|
8942
|
+
const out = [];
|
|
8943
|
+
let cur = '';
|
|
8944
|
+
for (const ch of String(text)) {
|
|
8945
|
+
const test = cur + ch;
|
|
8946
|
+
if (ctx.measureText(test).width > maxWidth && cur) { out.push(cur); cur = ch; } else cur = test;
|
|
8947
|
+
}
|
|
8948
|
+
if (cur) out.push(cur);
|
|
8949
|
+
return out.length ? out : [''];
|
|
8950
|
+
};
|
|
8951
|
+
ctx.font = font(15);
|
|
8952
|
+
let bodyH = 0;
|
|
8953
|
+
const outlineBlocks = outline.map((o) => {
|
|
8954
|
+
const lines = wrap(o.text, W - PAD * 2 - 70);
|
|
8955
|
+
bodyH += Math.min(lines.length, 2) * line + 10;
|
|
8956
|
+
return { o, lines: lines.slice(0, 2) };
|
|
8957
|
+
});
|
|
8958
|
+
const table = tables[0] || null;
|
|
8959
|
+
const tableRows = table ? table.rows.slice(0, 6) : [];
|
|
8960
|
+
const tableH = table ? 54 + tableRows.length * 34 : 0;
|
|
8961
|
+
const H = 150 + bodyH + (outlineBlocks.length ? 34 : 0) + tableH + 120;
|
|
8962
|
+
|
|
8963
|
+
canvas.width = W;
|
|
8964
|
+
canvas.height = H;
|
|
8965
|
+
ctx.fillStyle = 'rgb(255,255,255)';
|
|
8966
|
+
ctx.fillRect(0, 0, W, H);
|
|
8967
|
+
ctx.fillStyle = 'rgb(31,111,63)';
|
|
8968
|
+
ctx.fillRect(0, 0, W, 8);
|
|
8969
|
+
|
|
8970
|
+
let y = 52;
|
|
8971
|
+
ctx.fillStyle = 'rgb(27,27,27)';
|
|
8972
|
+
ctx.font = font(28, true);
|
|
8973
|
+
ctx.fillText(`${systemLabel} · 摘要`, PAD, y);
|
|
8974
|
+
y += 30;
|
|
8975
|
+
ctx.fillStyle = 'rgb(111,106,96)';
|
|
8976
|
+
ctx.font = font(14);
|
|
8977
|
+
const meta = [r && r.input && r.input.datetime ? `出生/起局:${r.input.datetime}` : '', '本地计算 · 可复现'].filter(Boolean).join(' ');
|
|
8978
|
+
ctx.fillText(meta, PAD, y);
|
|
8979
|
+
y += 34;
|
|
8980
|
+
|
|
8981
|
+
if (outlineBlocks.length) {
|
|
8982
|
+
ctx.fillStyle = 'rgb(31,111,63)';
|
|
8983
|
+
ctx.font = font(16, true);
|
|
8984
|
+
ctx.fillText('一页读懂', PAD, y);
|
|
8985
|
+
y += 26;
|
|
8986
|
+
for (const { o, lines } of outlineBlocks) {
|
|
8987
|
+
ctx.fillStyle = 'rgb(138,131,120)';
|
|
8988
|
+
ctx.font = font(14, true);
|
|
8989
|
+
ctx.fillText(o.label, PAD, y);
|
|
8990
|
+
ctx.fillStyle = 'rgb(63,58,51)';
|
|
8991
|
+
ctx.font = font(15);
|
|
8992
|
+
lines.forEach((text, i) => ctx.fillText(text, PAD + 70, y + i * line));
|
|
8993
|
+
y += lines.length * line + 10;
|
|
8994
|
+
}
|
|
8995
|
+
y += 8;
|
|
8996
|
+
}
|
|
8997
|
+
|
|
8998
|
+
if (table) {
|
|
8999
|
+
ctx.fillStyle = 'rgb(31,111,63)';
|
|
9000
|
+
ctx.font = font(16, true);
|
|
9001
|
+
ctx.fillText(table.title, PAD, y);
|
|
9002
|
+
y += 24;
|
|
9003
|
+
ctx.fillStyle = 'rgb(138,131,120)';
|
|
9004
|
+
ctx.font = font(13);
|
|
9005
|
+
ctx.fillText(table.columns.join(' · '), PAD, y);
|
|
9006
|
+
y += 8;
|
|
9007
|
+
ctx.strokeStyle = 'rgb(229,225,216)';
|
|
9008
|
+
ctx.beginPath(); ctx.moveTo(PAD, y); ctx.lineTo(W - PAD, y); ctx.stroke();
|
|
9009
|
+
y += 22;
|
|
9010
|
+
ctx.fillStyle = 'rgb(63,58,51)';
|
|
9011
|
+
ctx.font = font(14);
|
|
9012
|
+
for (const row of tableRows) {
|
|
9013
|
+
const text = row.filter(Boolean).join(' · ');
|
|
9014
|
+
const clipped = wrap(text, W - PAD * 2)[0];
|
|
9015
|
+
ctx.fillText(clipped, PAD, y);
|
|
9016
|
+
y += 34;
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
|
|
9020
|
+
y = H - 62;
|
|
9021
|
+
ctx.strokeStyle = 'rgb(229,225,216)';
|
|
9022
|
+
ctx.beginPath(); ctx.moveTo(PAD, y - 18); ctx.lineTo(W - PAD, y - 18); ctx.stroke();
|
|
9023
|
+
ctx.fillStyle = 'rgb(138,131,120)';
|
|
9024
|
+
ctx.font = font(12);
|
|
9025
|
+
ctx.fillText('内容用于传统文化学习与自我探索,不构成医疗、投资、法律或人事决策依据。', PAD, y + 6);
|
|
9026
|
+
ctx.fillText('dsh-metaphysics · 本机计算,未上传任何数据', PAD, y + 26);
|
|
9027
|
+
|
|
9028
|
+
return canvas.toDataURL ? canvas.toDataURL('image/png') : '';
|
|
9029
|
+
} catch (e) {
|
|
9030
|
+
return '';
|
|
9031
|
+
}
|
|
9032
|
+
};
|
|
8214
9033
|
})();
|
|
8215
9034
|
|
|
8216
9035
|
const HIST_KEY = 'dsh-metaphysics/history-v1';
|
|
@@ -8295,6 +9114,24 @@ const CSS = `
|
|
|
8295
9114
|
.mp-empty b{display:block;font-size:15px;color:var(--dsw-alias-label-primary);margin-bottom:6px;font-weight:600}
|
|
8296
9115
|
.mp-note{margin-top:12px;padding:9px 14px;border-radius:10px;font-size:12px;background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 14%,transparent);color:var(--dsw-alias-state-warning-primary)}
|
|
8297
9116
|
.mp-bar{display:flex;gap:8px;align-items:center;margin-top:14px}
|
|
9117
|
+
.mp-askbox{display:flex;gap:8px;align-items:center;margin-top:12px}
|
|
9118
|
+
.mp-askbox .mp-input{flex:1;min-width:0}
|
|
9119
|
+
.mp-fx{margin-top:10px}
|
|
9120
|
+
.mp-verify,.mp-rect{margin-top:12px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}
|
|
9121
|
+
.mp-verify-h{font-size:12px;font-weight:700;letter-spacing:.08em;color:var(--dsw-alias-label-secondary);margin-bottom:8px}
|
|
9122
|
+
.mp-verify-row{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0}
|
|
9123
|
+
.mp-verify-btns{display:flex;gap:4px}
|
|
9124
|
+
.mp-chip{font-size:11.5px;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
9125
|
+
.mp-chip.on{background:var(--dsw-alias-brand-primary);color:#fff;border-color:transparent}
|
|
9126
|
+
.mp-verify-foot{display:flex;align-items:flex-start;gap:8px;margin-top:8px;flex-wrap:wrap}
|
|
9127
|
+
.mp-verify-fit{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.7;flex:1;min-width:0}
|
|
9128
|
+
.mp-rect-row{display:flex;gap:6px;margin-bottom:6px}
|
|
9129
|
+
.mp-rect-row .mp-input{flex:1;min-width:0}
|
|
9130
|
+
.mp-rect-cand{font-size:12px;padding:5px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-1);margin-top:4px;color:var(--dsw-alias-label-secondary)}
|
|
9131
|
+
.mp-rect-cand.best{border-left:3px solid var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary)}
|
|
9132
|
+
.mp-rect-err{color:var(--dsw-alias-label-secondary);font-size:12px}
|
|
9133
|
+
.mp-checks{flex-direction:row;align-items:center;gap:7px;font-size:12.5px}
|
|
9134
|
+
.mp-checks input{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary)}
|
|
8298
9135
|
.mp-auto{margin:-8px 0 14px;padding-left:2px;font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary)}
|
|
8299
9136
|
.mp-cast{margin:0 0 14px;padding-left:2px;font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary)}
|
|
8300
9137
|
.mp-cast b{color:var(--dsw-alias-label-primary);font-weight:600}
|
|
@@ -8380,6 +9217,15 @@ function App() {
|
|
|
8380
9217
|
const [hpChart, setHpChart] = useState('bazi');
|
|
8381
9218
|
const [qimenRule, setQimenRule] = useState('chaibu');
|
|
8382
9219
|
const [trueSolar, setTrueSolar] = useState(true);
|
|
9220
|
+
const [lateZi, setLateZi] = useState(true);
|
|
9221
|
+
const [qLateZi, setQLateZi] = useState('same-day');
|
|
9222
|
+
const [crossOn, setCrossOn] = useState(false);
|
|
9223
|
+
const [askText, setAskText] = useState('');
|
|
9224
|
+
const [fx, setFx] = useState(null);
|
|
9225
|
+
const [pastMarks, setPastMarks] = useState({});
|
|
9226
|
+
const [fit, setFit] = useState(null);
|
|
9227
|
+
const [rectEvents, setRectEvents] = useState([{ year: '2019', kind: 'career' }, { year: '2022', kind: 'love' }]);
|
|
9228
|
+
const [rectRes, setRectRes] = useState(null);
|
|
8383
9229
|
const [onlineGeo, setOnlineGeo] = useState(false);
|
|
8384
9230
|
const [lines, setLines] = useState('');
|
|
8385
9231
|
const [result, setResult] = useState(null);
|
|
@@ -8405,19 +9251,21 @@ function App() {
|
|
|
8405
9251
|
const usesTimeInput = !(tab === 'meihua' && mhMode === 'number');
|
|
8406
9252
|
const showDT = tab !== 'tarot' && usesTimeInput;
|
|
8407
9253
|
|
|
8408
|
-
const run = async (kind, override, modeOverride) => {
|
|
9254
|
+
const run = async (kind, override, modeOverride, over) => {
|
|
8409
9255
|
const k = kind || tab;
|
|
8410
9256
|
const mh = modeOverride || mhMode;
|
|
8411
9257
|
const dt = override ? override : { date, time };
|
|
9258
|
+
const genderV = over && over.gender ? over.gender : gender;
|
|
9259
|
+
const addressV = over && typeof over.address === 'string' ? over.address : address;
|
|
8412
9260
|
try {
|
|
8413
9261
|
const datetime = `${dt.date}T${dt.time}`;
|
|
8414
9262
|
const args = { system: k };
|
|
8415
9263
|
if (k !== 'tarot') args.datetime = datetime;
|
|
8416
9264
|
if (k === 'bazi' || k === 'ziwei' || k === 'hepan') {
|
|
8417
|
-
args.gender =
|
|
9265
|
+
args.gender = genderV;
|
|
8418
9266
|
args.trueSolar = trueSolar;
|
|
8419
|
-
if (
|
|
8420
|
-
args.address =
|
|
9267
|
+
if (addressV.trim()) {
|
|
9268
|
+
args.address = addressV.trim();
|
|
8421
9269
|
try {
|
|
8422
9270
|
const g = await Engine.geocodeAddress(args.address, { online: onlineGeo });
|
|
8423
9271
|
if (g && typeof g.lon === 'number') { args.longitude = g.lon; args.geo = g; }
|
|
@@ -8425,13 +9273,15 @@ function App() {
|
|
|
8425
9273
|
console.error('[metaphysics] 地址解析失败,退回内置表', e);
|
|
8426
9274
|
}
|
|
8427
9275
|
}
|
|
9276
|
+
if (k !== 'hepan') args.cross = crossOn;
|
|
9277
|
+
args.lateZi = lateZi;
|
|
8428
9278
|
}
|
|
8429
9279
|
if (k === 'liuyao') {
|
|
8430
9280
|
const parsed = Core.parseLines(lines);
|
|
8431
9281
|
if (parsed.lines) args.lines = parsed.lines;
|
|
8432
9282
|
else if (parsed.warning) setNotice(parsed.warning);
|
|
8433
9283
|
}
|
|
8434
|
-
if (k === 'qimen') args.juMethod = qimenRule;
|
|
9284
|
+
if (k === 'qimen') { args.juMethod = qimenRule; args.lateZiHourMode = qLateZi; }
|
|
8435
9285
|
if (k === 'tarot') args.count = Number(count);
|
|
8436
9286
|
if (k === 'hepan') {
|
|
8437
9287
|
args.chart = hpChart;
|
|
@@ -8475,8 +9325,12 @@ function App() {
|
|
|
8475
9325
|
? { question: needAsk(k) ? question.trim() : '', direction }
|
|
8476
9326
|
: undefined;
|
|
8477
9327
|
const it = Engine.interpret ? Engine.interpret(r, ask) : null;
|
|
9328
|
+
if (it && r.cross && Engine.crossText) {
|
|
9329
|
+
it.sections = [...(it.sections || []), { key: 'crossSystem', label: '八字 × 紫微 交叉验证', text: Engine.crossText(r.cross) }];
|
|
9330
|
+
}
|
|
8478
9331
|
const timed = kindOf(k) === 'cast' && (k !== 'meihua' || mh === 'time');
|
|
8479
9332
|
setResult({ kind: k, r, it, at: dt, timed });
|
|
9333
|
+
setFx(null); setFit(null); setPastMarks({}); setRectRes(null);
|
|
8480
9334
|
setError(''); setCopied('');
|
|
8481
9335
|
if (k !== 'tarot') {
|
|
8482
9336
|
const entry = {
|
|
@@ -8539,6 +9393,17 @@ function App() {
|
|
|
8539
9393
|
run(tab);
|
|
8540
9394
|
};
|
|
8541
9395
|
|
|
9396
|
+
const openSample = () => {
|
|
9397
|
+
const sample = { date: '1990-05-12', time: '14:30' };
|
|
9398
|
+
setTab('bazi');
|
|
9399
|
+
setGender('male');
|
|
9400
|
+
setAddress('河北石家庄');
|
|
9401
|
+
setManual((m) => Object.assign({}, m, { birth: sample }));
|
|
9402
|
+
setDate(sample.date);
|
|
9403
|
+
setTime(sample.time);
|
|
9404
|
+
run('bazi', sample, null, { gender: 'male', address: '河北石家庄' });
|
|
9405
|
+
};
|
|
9406
|
+
|
|
8542
9407
|
const switchMode = (m) => {
|
|
8543
9408
|
setMhMode(m);
|
|
8544
9409
|
run(tab, { date, time }, m);
|
|
@@ -8583,6 +9448,74 @@ function App() {
|
|
|
8583
9448
|
} catch (e) { setCopied('复制失败'); }
|
|
8584
9449
|
};
|
|
8585
9450
|
|
|
9451
|
+
const doAsk = () => {
|
|
9452
|
+
if (!result || !result.r || !askText.trim()) return;
|
|
9453
|
+
try {
|
|
9454
|
+
setFx(Engine.followup ? Engine.followup(result.r, askText.trim()) : null);
|
|
9455
|
+
} catch (e) { setFx({ question: askText.trim(), ok: false, answer: '追问失败:' + (e && e.message ? e.message : e), evidence: [], confidence: '弱' }); }
|
|
9456
|
+
};
|
|
9457
|
+
|
|
9458
|
+
const downloadReport = () => {
|
|
9459
|
+
if (!result || !result.r) return;
|
|
9460
|
+
try {
|
|
9461
|
+
const text = Engine.renderText ? Engine.renderText(result.r) : (result.r.summary || '');
|
|
9462
|
+
const blob = new Blob([text], { type: 'text/markdown;charset=utf-8' });
|
|
9463
|
+
const a = document.createElement('a');
|
|
9464
|
+
a.href = URL.createObjectURL(blob);
|
|
9465
|
+
a.download = `命理报告-${result.r.system}.md`;
|
|
9466
|
+
document.body.appendChild(a);
|
|
9467
|
+
a.click();
|
|
9468
|
+
a.remove();
|
|
9469
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 2000);
|
|
9470
|
+
} catch (e) {
|
|
9471
|
+
console.error('[metaphysics] 导出失败', e);
|
|
9472
|
+
}
|
|
9473
|
+
};
|
|
9474
|
+
|
|
9475
|
+
const downloadCard = () => {
|
|
9476
|
+
if (!result || !result.r) return;
|
|
9477
|
+
try {
|
|
9478
|
+
const url = PP.summaryCardPNG ? PP.summaryCardPNG(result.r, result.it) : '';
|
|
9479
|
+
if (!url) { setCopied('当前环境不支持 Canvas 导出'); return; }
|
|
9480
|
+
const a = document.createElement('a');
|
|
9481
|
+
a.href = url;
|
|
9482
|
+
a.download = `命理摘要-${result.r.system}.png`;
|
|
9483
|
+
document.body.appendChild(a);
|
|
9484
|
+
a.click();
|
|
9485
|
+
a.remove();
|
|
9486
|
+
} catch (e) {
|
|
9487
|
+
console.error('[metaphysics] 长图导出失败', e);
|
|
9488
|
+
}
|
|
9489
|
+
};
|
|
9490
|
+
|
|
9491
|
+
const btnStyle = { fontSize: 12, padding: '5px 12px' };
|
|
9492
|
+
|
|
9493
|
+
const calcFit = () => {
|
|
9494
|
+
if (!result || !result.r || !result.r.past) return;
|
|
9495
|
+
setFit(Engine.verifyFit ? Engine.verifyFit(result.r.past.rows, pastMarks) : null);
|
|
9496
|
+
};
|
|
9497
|
+
|
|
9498
|
+
const runRectify = () => {
|
|
9499
|
+
if (!result || result.kind !== 'bazi') return;
|
|
9500
|
+
try {
|
|
9501
|
+
const [y, m, d] = String(date).split('-').map(Number);
|
|
9502
|
+
const [, mm] = String(time).split(':').map(Number);
|
|
9503
|
+
const events = rectEvents
|
|
9504
|
+
.filter((e) => Number(e.year))
|
|
9505
|
+
.map((e) => ({ year: Number(e.year), kind: e.kind }));
|
|
9506
|
+
setRectRes(Engine.rectifyBirth ? Engine.rectifyBirth({
|
|
9507
|
+
base: { year: y, month: m, day: d, minute: Number.isFinite(mm) ? mm : 0 },
|
|
9508
|
+
gender,
|
|
9509
|
+
events,
|
|
9510
|
+
lateZi,
|
|
9511
|
+
trueSolar,
|
|
9512
|
+
address: address.trim() || undefined,
|
|
9513
|
+
}) : { error: '当前客户端未加载生时校正模块' });
|
|
9514
|
+
} catch (e) {
|
|
9515
|
+
setRectRes({ error: e && e.message ? e.message : String(e) });
|
|
9516
|
+
}
|
|
9517
|
+
};
|
|
9518
|
+
|
|
8586
9519
|
const field = (label, node) => h('label', { className: 'mp-field' }, h('span', { className: 'mp-lbl' }, label), node);
|
|
8587
9520
|
|
|
8588
9521
|
const replay = (x) => {
|
|
@@ -8636,6 +9569,12 @@ function App() {
|
|
|
8636
9569
|
className: 'mp-input', placeholder: '如 河北石家庄 / 成都', value: address,
|
|
8637
9570
|
onChange: (e) => setAddress(e.target.value),
|
|
8638
9571
|
})) : null,
|
|
9572
|
+
(tab === 'bazi' || tab === 'ziwei') ? h('label', { className: 'mp-field mp-checks' },
|
|
9573
|
+
h('input', {
|
|
9574
|
+
type: 'checkbox', checked: crossOn,
|
|
9575
|
+
onChange: (e) => setCrossOn(e.target.checked),
|
|
9576
|
+
}),
|
|
9577
|
+
h('span', { className: 'mp-lbl' }, '八字 × 紫微 交叉验证')) : null,
|
|
8639
9578
|
tab === 'hepan' ? field('对方出生日期', h('input', {
|
|
8640
9579
|
className: 'mp-input', type: 'date', value: pDate, onChange: (e) => setPDate(e.target.value),
|
|
8641
9580
|
})) : null,
|
|
@@ -8663,6 +9602,10 @@ function App() {
|
|
|
8663
9602
|
onChange: () => setQimenRule(value),
|
|
8664
9603
|
}),
|
|
8665
9604
|
h('span', null, label)))),
|
|
9605
|
+
h('div', { className: 'mp-field' }, h('span', { className: 'mp-lbl' }, '晚子时'),
|
|
9606
|
+
h('select', { className: 'mp-input', value: qLateZi, onChange: (e) => setQLateZi(e.target.value) },
|
|
9607
|
+
h('option', { value: 'same-day' }, '23:00 后归当日'),
|
|
9608
|
+
h('option', { value: 'next-day' }, '23:00 后归次日'))),
|
|
8666
9609
|
h('details', { className: 'mp-qmhelp' },
|
|
8667
9610
|
h('summary', null, '三种规则有什么区别?'),
|
|
8668
9611
|
h('p', null, '决定符头与节气如何对齐、是否插入闰局;同一个时刻在不同规则下可能得到不同局数。默认「拆补」与工具层一致。'))) : null,
|
|
@@ -8696,6 +9639,9 @@ function App() {
|
|
|
8696
9639
|
isBirth ? h('div', { className: 'mp-checks' },
|
|
8697
9640
|
h('label', { title: '按出生地经度把钟表时间换算成真太阳时' },
|
|
8698
9641
|
h('input', { type: 'checkbox', checked: trueSolar, onChange: (e) => setTrueSolar(e.target.checked) }), '真太阳时'),
|
|
9642
|
+
isBirth ? h('label', { className: 'mp-field mp-checks' },
|
|
9643
|
+
h('input', { type: 'checkbox', checked: lateZi, onChange: (e) => setLateZi(e.target.checked) }),
|
|
9644
|
+
h('span', { className: 'mp-lbl' }, '晚子时换日')) : null,
|
|
8699
9645
|
h('label', { title: '开启后会请求 OpenStreetMap,把区县/村落到更精确的坐标' },
|
|
8700
9646
|
h('input', { type: 'checkbox', checked: onlineGeo, onChange: (e) => setOnlineGeo(e.target.checked) }), '联网精确到区县')) : null,
|
|
8701
9647
|
needAsk(tab) ? h('div', { className: 'mp-askblock' },
|
|
@@ -8708,6 +9654,10 @@ function App() {
|
|
|
8708
9654
|
onBlur: (e) => refreshInterpret(undefined, e.target.value),
|
|
8709
9655
|
})) : null,
|
|
8710
9656
|
h('div', { key: 'actions', className: 'mp-actions' },
|
|
9657
|
+
h('button', {
|
|
9658
|
+
className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px', marginRight: 8 },
|
|
9659
|
+
onClick: openSample, title: '用固定示例盘看完整报告(不改动你已填的数据)',
|
|
9660
|
+
}, '看示例'),
|
|
8711
9661
|
h('button', { className: 'mp-btn', onClick: go, title: '时间仍为自动时,点它会取当下再起卦' },
|
|
8712
9662
|
tab === 'tarot' ? '抽牌' : tab === 'bazi' || tab === 'ziwei' ? '排盘' : tab === 'hepan' ? '合盘' : '起卦'))),
|
|
8713
9663
|
|
|
@@ -8764,7 +9714,60 @@ function App() {
|
|
|
8764
9714
|
|
|
8765
9715
|
result ? h('div', { key: 'bar', className: 'mp-bar' },
|
|
8766
9716
|
h('button', { className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px' }, onClick: copyResult },
|
|
8767
|
-
copied || '复制文字结果')
|
|
9717
|
+
copied || '复制文字结果'),
|
|
9718
|
+
h('button', {
|
|
9719
|
+
className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px', marginLeft: 8 },
|
|
9720
|
+
onClick: downloadReport, title: '导出盘面 + 解析(含一页读懂、三张表)为 Markdown',
|
|
9721
|
+
}, '导出 .md'),
|
|
9722
|
+
h('button', {
|
|
9723
|
+
className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px', marginLeft: 8 },
|
|
9724
|
+
onClick: downloadCard, title: '生成 PNG 摘要卡(一页读懂 + 首张表),适合直接分享',
|
|
9725
|
+
}, '存为摘要长图')) : null,
|
|
9726
|
+
|
|
9727
|
+
result ? h('div', { key: 'ask', className: 'mp-askbox' },
|
|
9728
|
+
h('input', {
|
|
9729
|
+
className: 'mp-input', value: askText, maxLength: 120,
|
|
9730
|
+
placeholder: '追问:如 2027 年事业怎么样(回车提交)',
|
|
9731
|
+
onChange: (e) => setAskText(e.target.value),
|
|
9732
|
+
onKeyDown: (e) => { if (e.key === 'Enter') doAsk(); },
|
|
9733
|
+
}),
|
|
9734
|
+
h('button', { className: 'mp-btn ghost', style: { fontSize: 12, padding: '5px 12px' }, onClick: doAsk }, '追问')) : null,
|
|
9735
|
+
|
|
9736
|
+
fx ? h('div', { key: 'fx', className: 'mp-fx', dangerouslySetInnerHTML: { __html: PP.followupHTML(fx) } }) : null,
|
|
9737
|
+
|
|
9738
|
+
(result && result.kind === 'bazi' && result.r.past) ? h('div', { key: 'verify', className: 'mp-verify' },
|
|
9739
|
+
h('div', { className: 'mp-verify-h' }, '验前事 · 过去几年的信号(只读,不改盘)'),
|
|
9740
|
+
result.r.past.rows.map((row) => h('div', { key: row.year, className: 'mp-verify-row' },
|
|
9741
|
+
h('span', { className: 'mp-verify-txt' }, `${row.year} ${row.ganzhi} · ${row.theme}`),
|
|
9742
|
+
h('span', { className: 'mp-verify-btns' }, ['yes', 'no', 'unsure'].map((m) => h('button', {
|
|
9743
|
+
key: m,
|
|
9744
|
+
className: 'mp-chip' + (pastMarks[row.year] === m ? ' on' : ''),
|
|
9745
|
+
onClick: () => setPastMarks(Object.assign({}, pastMarks, { [row.year]: m })),
|
|
9746
|
+
}, m === 'yes' ? '准' : m === 'no' ? '不准' : '不确定'))))),
|
|
9747
|
+
h('div', { className: 'mp-verify-foot' },
|
|
9748
|
+
h('button', { className: 'mp-btn ghost', style: btnStyle, onClick: calcFit }, '算贴合度'),
|
|
9749
|
+
fit ? h('span', { className: 'mp-verify-fit' }, `命中 ${fit.yes}/${fit.answered}(${fit.level}):${fit.advice}`) : null)) : null,
|
|
9750
|
+
|
|
9751
|
+
(result && result.kind === 'bazi') ? h('div', { key: 'rect', className: 'mp-rect' },
|
|
9752
|
+
h('div', { className: 'mp-verify-h' }, '生时校正 · 用过去的事反推时辰'),
|
|
9753
|
+
rectEvents.map((ev, i) => h('div', { key: i, className: 'mp-rect-row' },
|
|
9754
|
+
h('input', {
|
|
9755
|
+
className: 'mp-input', type: 'number', min: 1900, max: 2100, value: ev.year,
|
|
9756
|
+
onChange: (e) => setRectEvents(rectEvents.map((x, j) => (j === i ? Object.assign({}, x, { year: e.target.value }) : x))),
|
|
9757
|
+
}),
|
|
9758
|
+
h('select', {
|
|
9759
|
+
className: 'mp-input', value: ev.kind,
|
|
9760
|
+
onChange: (e) => setRectEvents(rectEvents.map((x, j) => (j === i ? Object.assign({}, x, { kind: e.target.value }) : x))),
|
|
9761
|
+
}, [['career', '事业变动'], ['wealth', '财运'], ['love', '感情'], ['move', '搬迁/出行'], ['health', '健康'], ['other', '其他']]
|
|
9762
|
+
.map(([v, l]) => h('option', { key: v, value: v }, l))))),
|
|
9763
|
+
h('button', { className: 'mp-btn ghost', style: btnStyle, onClick: runRectify }, '计算候选时辰'),
|
|
9764
|
+
rectRes ? (rectRes.error
|
|
9765
|
+
? h('div', { className: 'mp-rect-err' }, rectRes.error)
|
|
9766
|
+
: h('div', { className: 'mp-rect-out' },
|
|
9767
|
+
h('div', { className: 'mp-verify-fit' }, rectRes.note),
|
|
9768
|
+
rectRes.candidates.slice(0, 6).map((c) => h('div', {
|
|
9769
|
+
key: c.hour, className: 'mp-rect-cand' + (c === rectRes.best ? ' best' : ''),
|
|
9770
|
+
}, `${c.label} · 时柱 ${c.hourGanzhi} · 日柱 ${c.dayGanzhi} · 命中 ${c.hits}/${c.total}`)))) : null) : null,
|
|
8768
9771
|
|
|
8769
9772
|
(!result && !error) ? h('div', { key: 'ph', className: 'mp-dim', style: { marginTop: 24, fontSize: 12 } },
|
|
8770
9773
|
'选择体系后点「排盘」。所有计算在本机完成,不上传任何数据。') : null,
|