dsh-metaphysics 0.62.1 → 0.62.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -1
- package/client.almanac.js +69 -65
- package/client.bazi.js +123 -119
- package/client.chenggu.js +68 -64
- package/client.convert.js +71 -67
- package/client.daliuren.js +119 -115
- package/client.fengshui.js +68 -64
- package/client.hepan.js +123 -119
- package/client.js +192 -192
- package/client.liuyao.js +122 -118
- package/client.mbti.js +69 -65
- package/client.name.js +69 -65
- package/client.numerology.js +68 -64
- package/client.qimen.js +124 -120
- package/client.star.js +69 -65
- package/client.tarot.js +119 -115
- package/client.trend.js +113 -109
- package/client.xiaoliuren.js +78 -74
- package/client.zeri.js +68 -64
- package/client.ziwei.js +123 -119
- package/engine/bazi.js +6 -5
- package/engine/bool.js +195 -0
- package/engine/calendar.js +2 -1
- package/engine/chenggu.js +2 -1
- package/engine/convert.js +2 -1
- package/engine/daliuren.js +2 -1
- package/engine/diff.js +5 -3
- package/engine/fengshui.js +19 -6
- package/engine/geo.js +2 -1
- package/engine/hepan.js +4 -3
- package/engine/index.js +202 -72
- package/engine/liuyao.js +24 -5
- package/engine/mbti.js +6 -1
- package/engine/meihua.js +21 -7
- package/engine/name.js +20 -12
- package/engine/random.js +17 -3
- package/engine/rectify.js +4 -3
- package/engine/tarot.js +22 -8
- package/engine/trace.js +2 -1
- package/engine/trend.js +7 -3
- package/engine/xiaoliuren.js +2 -1
- package/engine/ziwei.js +4 -3
- package/index.js +367 -42
- package/package.json +1 -1
- package//345/221/275/347/220/206/346/216/222/347/233/230.html +577 -143
package/engine/liuyao.js
CHANGED
|
@@ -3,6 +3,7 @@ import { STEMS, BRANCHES, toJDN, ganzhiName, dayGanzhiIndex, sexagenaryIndex, xu
|
|
|
3
3
|
import { jingText } from './yijing-text.js';
|
|
4
4
|
import { fail } from './fail.js';
|
|
5
5
|
import { assertObject } from './validate.js';
|
|
6
|
+
import { randomOf, isSeed } from './random.js';
|
|
6
7
|
|
|
7
8
|
// 八卦三位(自下而上,1 = 阳)
|
|
8
9
|
const TRIGRAM_BITS = {
|
|
@@ -335,10 +336,8 @@ const sideOf = (position) => (position <= 3 ? 'inner' : 'outer');
|
|
|
335
336
|
* @param {{direction?:string, gender?:string}} [opts]
|
|
336
337
|
*/
|
|
337
338
|
export function yongshenOf(result, opts = {}) {
|
|
338
|
-
|
|
339
|
+
opts = liuyaoOpts(opts, 'yongshenOf 的 opts', '{ direction?, gender? }');
|
|
339
340
|
// 以前 yongshenOf(r, null) 会一路走到读 opts.direction,抛裸 TypeError
|
|
340
|
-
if (opts === null || opts === undefined) opts = {};
|
|
341
|
-
assertObject(opts, 'yongshenOf 的 opts', '{ direction?, gender? };省略或 null 按默认口径');
|
|
342
341
|
const r = result && typeof result === 'object' ? result : {};
|
|
343
342
|
const ben = r.ben && typeof r.ben === 'object' ? r.ben : {};
|
|
344
343
|
const lines = Array.isArray(ben.lines) ? ben.lines : [];
|
|
@@ -490,6 +489,14 @@ function isMoving(v) {
|
|
|
490
489
|
*/
|
|
491
490
|
export function buildLiuyao(input = {}) {
|
|
492
491
|
if (!input || typeof input !== 'object') throw fail('E_INPUT', '六爻起卦参数不能为空:至少给 lines 或 seed');
|
|
492
|
+
// seed 以前只写在文档里:直接 buildLiuyao({ seed }) 仍走 Math.random,
|
|
493
|
+
// 同一个 seed 摇不出同一卦,句柄却按 seed 算了身份。这里与工具层同一把尺。
|
|
494
|
+
if (input.seed !== undefined && input.seed !== null && input.seed !== '' && !isSeed(input.seed)) {
|
|
495
|
+
throw fail('E_INPUT', `seed 必须是数字,收到「${String(input.seed).slice(0, 24)}」`);
|
|
496
|
+
}
|
|
497
|
+
if (input.random !== undefined && input.random !== null && typeof input.random !== 'function') {
|
|
498
|
+
throw fail('E_INPUT', `random 必须是函数,收到 ${typeof input.random}`);
|
|
499
|
+
}
|
|
493
500
|
// lines 给了但不是数组("7,8,9,6,7,8" / 789678 / {…})以前等同于「没给」:
|
|
494
501
|
// 用户以为摇的是自己记下的那一卦,实际拿到一张随机盘,而且看不出来。
|
|
495
502
|
if (input.lines !== undefined && input.lines !== null && !Array.isArray(input.lines)) {
|
|
@@ -501,7 +508,8 @@ export function buildLiuyao(input = {}) {
|
|
|
501
508
|
if (given && given.length !== 6) {
|
|
502
509
|
throw fail('E_LINES', `lines 必须是 6 个爻值(自下而上),收到 ${given.length} 个`);
|
|
503
510
|
}
|
|
504
|
-
const
|
|
511
|
+
const rand = typeof input.random === 'function' ? input.random : randomOf(input.seed);
|
|
512
|
+
const rawLines = given ? given.slice() : tossLines(rand);
|
|
505
513
|
|
|
506
514
|
const invalid = rawLines.filter((v) => ![6, 7, 8, 9].includes(v));
|
|
507
515
|
if (invalid.length) throw fail('E_LINES', `爻值只能是 6/7/8/9,收到 ${JSON.stringify(rawLines)}`);
|
|
@@ -513,7 +521,7 @@ export function buildLiuyao(input = {}) {
|
|
|
513
521
|
const bian = bianBits ? hexagramFromBits(bianBits) : null;
|
|
514
522
|
|
|
515
523
|
const dayGanZhi = input.dayGanZhi ?? (() => {
|
|
516
|
-
const now = new Date();
|
|
524
|
+
const now = new Date(); // allow-clock: 未传日辰时按起卦当天取日干起六神;asOf 可钉死
|
|
517
525
|
const jdn = toJDN(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
518
526
|
const g = ganzhiName(dayGanzhiIndex(jdn));
|
|
519
527
|
return { stem: g.stem, branch: g.branch, name: g.name };
|
|
@@ -662,6 +670,12 @@ export function dayRelationOf(element, dayElement) {
|
|
|
662
670
|
return 'unknown';
|
|
663
671
|
}
|
|
664
672
|
|
|
673
|
+
/** opts 省略或传 null 都按默认口径;显式给了别的类型则当场报 E_INPUT。 */
|
|
674
|
+
function liuyaoOpts(opts, who, shape) {
|
|
675
|
+
if (opts === null || opts === undefined) return {};
|
|
676
|
+
return assertObject(opts, who, `${shape};省略或 null 按默认口径`);
|
|
677
|
+
}
|
|
678
|
+
|
|
665
679
|
/**
|
|
666
680
|
* 用神旺衰:把月建、日辰两股外部力量落到用神那几爻上。
|
|
667
681
|
* 月建按月支、日辰按日支,各出自己的五行,再与本爻五行论生克;
|
|
@@ -671,6 +685,7 @@ export function dayRelationOf(element, dayElement) {
|
|
|
671
685
|
* @param {{monthBranch?: string, dayBranch?: string}} opts
|
|
672
686
|
*/
|
|
673
687
|
export function yongshenStrength(result, useGod, opts = {}) {
|
|
688
|
+
opts = liuyaoOpts(opts, 'yongshenStrength 的 opts', '{ monthBranch?, dayBranch? }');
|
|
674
689
|
if (!useGod || !Array.isArray(useGod.found) || !useGod.found.length) return null;
|
|
675
690
|
const monthBranch = opts.monthBranch || null;
|
|
676
691
|
const dayBranch = opts.dayBranch || (result && result.dayGanZhi ? result.dayGanZhi.branch : null);
|
|
@@ -747,6 +762,7 @@ export const FUSHEN_VERDICTS = ['可出', '有助有压', '受压难出', '未
|
|
|
747
762
|
* @param {{monthBranch?: string, dayBranch?: string}} opts
|
|
748
763
|
*/
|
|
749
764
|
export function fushenAccess(result, useGod, opts = {}) {
|
|
765
|
+
opts = liuyaoOpts(opts, 'fushenAccess 的 opts', '{ monthBranch?, dayBranch? }');
|
|
750
766
|
if (!useGod || !Array.isArray(useGod.fushen) || !useGod.fushen.length) return null;
|
|
751
767
|
const monthBranch = opts.monthBranch || null;
|
|
752
768
|
const dayBranch = opts.dayBranch || (result && result.dayGanZhi ? result.dayGanZhi.branch : null);
|
|
@@ -822,6 +838,7 @@ export function fushenAccess(result, useGod, opts = {}) {
|
|
|
822
838
|
// 与用神旺衰同一套判定(月令五态、日辰五关系、三种冲法),区别只在不挑用神:
|
|
823
839
|
// 六爻每一爻都给一次,世应、动爻、旬空另标,看得出整张卦的气压在哪一头。
|
|
824
840
|
export function hexagramStrength(result, opts = {}) {
|
|
841
|
+
opts = liuyaoOpts(opts, 'hexagramStrength 的 opts', '{ monthBranch?, dayBranch? }');
|
|
825
842
|
const rows = (result && result.ben && Array.isArray(result.ben.lines))
|
|
826
843
|
? result.ben.lines
|
|
827
844
|
: (result && Array.isArray(result.lines) ? result.lines : []);
|
|
@@ -910,6 +927,7 @@ const TUI_VERDICT = { 旺: '退得缓', 相: '退得缓', 休: '退得实', 囚:
|
|
|
910
927
|
* @param {{monthBranch?: string, dayBranch?: string}} opts
|
|
911
928
|
*/
|
|
912
929
|
export function jinTuiPower(result, opts = {}) {
|
|
930
|
+
opts = liuyaoOpts(opts, 'jinTuiPower 的 opts', '{ monthBranch?, dayBranch? }');
|
|
913
931
|
const items = result && result.body && Array.isArray(result.body.jinTui) ? result.body.jinTui : [];
|
|
914
932
|
if (!items.length) return null;
|
|
915
933
|
const rows = result && result.ben && Array.isArray(result.ben.lines) ? result.ben.lines : [];
|
|
@@ -983,6 +1001,7 @@ const TRIGGER_ORDER = ['逢值', '逢冲', '逢合', '出空填实', '冲空'];
|
|
|
983
1001
|
* @param {{monthBranch?: string}} opts
|
|
984
1002
|
*/
|
|
985
1003
|
export function yingqiWindow(result, useGod, opts = {}) {
|
|
1004
|
+
opts = liuyaoOpts(opts, 'yingqiWindow 的 opts', '{ monthBranch? }');
|
|
986
1005
|
if (!useGod || !useGod.liuqin) return null;
|
|
987
1006
|
const found = Array.isArray(useGod.found) ? useGod.found : [];
|
|
988
1007
|
const fushen = Array.isArray(useGod.fushen) ? useGod.fushen : [];
|
package/engine/mbti.js
CHANGED
|
@@ -385,8 +385,11 @@ export function buildMbti(input = {}) {
|
|
|
385
385
|
picked,
|
|
386
386
|
strength: strengthOf(pct, answered),
|
|
387
387
|
tie: sum === 0,
|
|
388
|
+
unanswered: answered === 0,
|
|
388
389
|
};
|
|
389
390
|
});
|
|
391
|
+
// 整维未答时 letter 只是 tie 占位;把它单独列出来,摘要与面板才不会当成「测出来了」
|
|
392
|
+
const unanswered = dims.filter((d) => d.unanswered).map((d) => d.key);
|
|
390
393
|
const type = dims.map((d) => d.picked).join('');
|
|
391
394
|
const profile = TYPES[type];
|
|
392
395
|
if (!profile) throw fail('E_SYSTEM', `内部错误:类型 ${type} 没有对应文案`);
|
|
@@ -413,6 +416,8 @@ export function buildMbti(input = {}) {
|
|
|
413
416
|
answered: answeredAll,
|
|
414
417
|
skipped: QUESTIONS.length - answeredAll,
|
|
415
418
|
total: QUESTIONS.length,
|
|
416
|
-
|
|
419
|
+
unanswered,
|
|
420
|
+
note: '四维各 6 题、李克特 1–5 计分,按维度净分取字母(平手时取 I / N / F / P 一侧并标「临界」)。这是 MBTI 风格的问卷测算,不是官方量表;类型只作自我参照,不是能力评价,也不用它给人下结论。'
|
|
421
|
+
+ (unanswered.length ? ` 本轮 ${unanswered.join('、')} 维度一题未答,该维按平手取字母,只作占位,参考价值有限。` : ''),
|
|
417
422
|
};
|
|
418
423
|
}
|
package/engine/meihua.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { BRANCHES, fromJDN, toJDN } from './calendar.js';
|
|
3
3
|
import { solarToLunar } from './lunar.js';
|
|
4
4
|
import { fail } from './fail.js';
|
|
5
|
+
import { bool } from './bool.js';
|
|
5
6
|
import { parseDateTime } from './datetime.js';
|
|
6
7
|
import { assertShifted } from './validate.js';
|
|
7
8
|
import { hexagramFromBits } from './liuyao.js';
|
|
@@ -55,16 +56,29 @@ export function buildMeihua(input = {}) {
|
|
|
55
56
|
let source;
|
|
56
57
|
|
|
57
58
|
if (mode === 'number') {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// 只认真正的数字:Number(null) / Number(false) / Number([]) 都是 0,
|
|
60
|
+
// 以前缺参或传错类型会静默起成「坤为地」,盘面看着正常却完全不是用户给的字数。
|
|
61
|
+
const a = input.upper;
|
|
62
|
+
const b = input.lower;
|
|
63
|
+
// 小数以前只过 Number.isFinite,mod8(3.5) 得到 4.5,bitsOf 查不到卦再在深处抛
|
|
64
|
+
// 「bitsOf is not a function」——参数错却报成内部错误。数字起卦只认整数。
|
|
65
|
+
if (typeof a !== 'number' || typeof b !== 'number' || !Number.isInteger(a) || !Number.isInteger(b)) {
|
|
66
|
+
throw fail('E_INPUT', '数字起卦需要两个整数(upper / lower),例如 upper=3、lower=5');
|
|
67
|
+
}
|
|
61
68
|
upperNum = mod8(a);
|
|
62
69
|
lowerNum = mod8(b);
|
|
63
70
|
if (input.changing !== undefined && input.changing !== null && input.changing !== '') {
|
|
64
|
-
// 以前 99 会被 mod 6 折成 3
|
|
65
|
-
|
|
71
|
+
// 以前 99 会被 mod 6 折成 3:用户指定动爻却得到另一爻,结果里看不出来。
|
|
72
|
+
// Number(true)=1、Number([3])=3 也会静默通过,故只收数字或十进制数字串。
|
|
73
|
+
const raw = input.changing;
|
|
74
|
+
const ch = typeof raw === 'number'
|
|
75
|
+
? raw
|
|
76
|
+
: typeof raw === 'string' && /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/.test(raw.trim())
|
|
77
|
+
? Number(raw.trim())
|
|
78
|
+
: NaN;
|
|
66
79
|
if (!Number.isInteger(ch) || ch < 1 || ch > 6) {
|
|
67
|
-
|
|
80
|
+
const shown = typeof raw === 'string' ? raw : JSON.stringify(raw);
|
|
81
|
+
throw fail('E_INPUT', `动爻 changing 必须是 1–6 的整数,收到「${shown}」`);
|
|
68
82
|
}
|
|
69
83
|
moving = ch;
|
|
70
84
|
} else {
|
|
@@ -74,7 +88,7 @@ export function buildMeihua(input = {}) {
|
|
|
74
88
|
} else {
|
|
75
89
|
const dt = parseDateTime(input.datetime);
|
|
76
90
|
// 晚子时换日与八字/六壬/奇门同口径:23:00–23:59 归次日,年月日电数才落在同一子时
|
|
77
|
-
const lateZi = input.lateZi
|
|
91
|
+
const lateZi = bool(input.lateZi, true);
|
|
78
92
|
let sy = dt.year;
|
|
79
93
|
let sm = dt.month;
|
|
80
94
|
let sd = dt.day;
|
package/engine/name.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// 五格按康熙字典繁体笔画计(见 name-data.js 的口径说明),81 数理取通行本;
|
|
5
5
|
// 音韵只做机械检查(同调、相邻同声母/同韵母、叠字),不做语义谐音判断。
|
|
6
6
|
import { buildBazi } from './bazi.js';
|
|
7
|
+
import { bool } from './bool.js';
|
|
7
8
|
import { assertCivil, assertObject } from './validate.js';
|
|
8
9
|
import { fail } from './fail.js';
|
|
9
10
|
import {
|
|
@@ -267,19 +268,23 @@ const WX_REL = (a, b) => (a === b ? '同' : SHENG[a] === b ? '生' : KE[a] === b
|
|
|
267
268
|
|
|
268
269
|
/** 数理超过 81 按传统「减 80 循环」取(如 104 → 24)。 */
|
|
269
270
|
export function shuliOf(n) {
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
// 用取模代替 while 递减:strokeOverrides 允许用户给任意正整数,
|
|
272
|
+
// 以前给 1e12 会在这里空转一百多亿次,整次排盘卡死。
|
|
273
|
+
const x = n > 81 ? ((n - 1) % 80) + 1 : n;
|
|
272
274
|
if (!(x >= 1)) return null;
|
|
273
275
|
const row = SHULI_81[x];
|
|
274
276
|
return row ? { num: x, level: row[0], name: row[1] } : null;
|
|
275
277
|
}
|
|
276
278
|
|
|
277
|
-
/** 合并两处笔画覆盖:surnameStrokes(按姓给的)+ strokeOverrides(逐字覆盖),后者优先。
|
|
279
|
+
/** 合并两处笔画覆盖:surnameStrokes(按姓给的)+ strokeOverrides(逐字覆盖),后者优先。
|
|
280
|
+
* 笔画值允许数字串(`{"秋":"9"}`):模型经 JSON 传参时常见,只在入口归一还不够——
|
|
281
|
+
* 这里再兜一层,免得「用户给了笔画却被静默按字库算」这种错法又冒出来。 */
|
|
278
282
|
function overridesOf(input) {
|
|
279
283
|
const out = {};
|
|
280
284
|
for (const src of [input && input.surnameStrokes, input && input.strokeOverrides]) {
|
|
281
285
|
if (!src || typeof src !== 'object') continue;
|
|
282
|
-
for (const [k,
|
|
286
|
+
for (const [k, raw] of Object.entries(src)) {
|
|
287
|
+
const v = typeof raw === 'string' ? Number(raw.trim()) : raw;
|
|
283
288
|
if (Number.isInteger(v) && v > 0) out[k] = v;
|
|
284
289
|
}
|
|
285
290
|
}
|
|
@@ -507,12 +512,14 @@ export function assessName(input) {
|
|
|
507
512
|
assertCivil(input, 'birth');
|
|
508
513
|
const r = buildBazi({
|
|
509
514
|
year: input.year, month: input.month, day: input.day,
|
|
510
|
-
|
|
515
|
+
// 只给日期时与 datetime:'YYYY-MM-DD' 同一口径(00:00 占位);引擎摘要会标明缺时刻,
|
|
516
|
+
// 以前这里悄悄补 12:00,同一个生日走 datetime 和 year/month/day 会得到两档旺衰。
|
|
517
|
+
hour: Number.isInteger(input.hour) ? input.hour : 0,
|
|
511
518
|
minute: Number.isInteger(input.minute) ? input.minute : 0,
|
|
512
519
|
gender: input.gender || 'male',
|
|
513
|
-
lateZi: input.lateZi
|
|
514
|
-
trueSolar: input.trueSolar
|
|
515
|
-
dst: input.dst
|
|
520
|
+
lateZi: bool(input.lateZi, true),
|
|
521
|
+
trueSolar: bool(input.trueSolar, true),
|
|
522
|
+
dst: bool(input.dst, true),
|
|
516
523
|
longitude: input.longitude, address: input.address, geo: input.geo,
|
|
517
524
|
});
|
|
518
525
|
strength = { level: r.strength.level, score: r.strength.score, dayElement: r.pillars.day.stemElement };
|
|
@@ -608,12 +615,13 @@ export function generateNames(input) {
|
|
|
608
615
|
const limit = Math.max(1, Math.min(20, Number.isInteger(input.limit) ? input.limit : 10));
|
|
609
616
|
const r = buildBazi({
|
|
610
617
|
year: input.year, month: input.month, day: input.day,
|
|
611
|
-
|
|
618
|
+
// 与 assessName 同一口径:缺时刻按 00:00 占位,由引擎摘要统一提示
|
|
619
|
+
hour: Number.isInteger(input.hour) ? input.hour : 0,
|
|
612
620
|
minute: Number.isInteger(input.minute) ? input.minute : 0,
|
|
613
621
|
gender,
|
|
614
|
-
lateZi: input.lateZi
|
|
615
|
-
trueSolar: input.trueSolar
|
|
616
|
-
dst: input.dst
|
|
622
|
+
lateZi: bool(input.lateZi, true),
|
|
623
|
+
trueSolar: bool(input.trueSolar, true),
|
|
624
|
+
dst: bool(input.dst, true),
|
|
617
625
|
longitude: input.longitude, address: input.address, geo: input.geo,
|
|
618
626
|
});
|
|
619
627
|
const strength = { level: r.strength.level, score: r.strength.score, dayElement: r.pillars.day.stemElement };
|
package/engine/random.js
CHANGED
|
@@ -22,20 +22,34 @@ export function mulberry32(seed) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// 与宿主 / bool.js 同一把尺:只认十进制(可带指数、尾点)。Number() 会把
|
|
26
|
+
// "0x10"、false、[] 也变成有效数字,于是直接调引擎能排出盘、走工具层却被拒——
|
|
27
|
+
// 同一份输入两种命运,正是句柄与复现最容易分叉的地方。
|
|
28
|
+
const NUMERIC_TEXT = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
|
|
29
|
+
|
|
30
|
+
const seedNumber = (seed) => {
|
|
31
|
+
if (typeof seed === 'number') return Number.isFinite(seed) ? seed : NaN;
|
|
32
|
+
if (typeof seed === 'string') {
|
|
33
|
+
const s = seed.trim();
|
|
34
|
+
if (s !== '' && NUMERIC_TEXT.test(s)) return Number(s);
|
|
35
|
+
}
|
|
36
|
+
return NaN;
|
|
37
|
+
};
|
|
38
|
+
|
|
25
39
|
/**
|
|
26
40
|
* 从参数里取随机源:
|
|
27
41
|
* - 没给 seed:沿用 Math.random(保持"真随机"的手感)
|
|
28
|
-
* -
|
|
42
|
+
* - 给了数字或十进制数字串:mulberry32(seed),可复现
|
|
29
43
|
* - 给了别的东西:调用方应当先报错,这里兜底回 Math.random
|
|
30
44
|
*/
|
|
31
45
|
export function randomOf(seed) {
|
|
32
46
|
if (seed === undefined || seed === null || seed === '') return Math.random;
|
|
33
|
-
const n =
|
|
47
|
+
const n = seedNumber(seed);
|
|
34
48
|
return Number.isFinite(n) ? mulberry32(n) : Math.random;
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
/** 是否是一个可以接受的 seed */
|
|
38
52
|
export function isSeed(v) {
|
|
39
53
|
if (v === undefined || v === null || v === '') return true;
|
|
40
|
-
return Number.isFinite(
|
|
54
|
+
return Number.isFinite(seedNumber(v));
|
|
41
55
|
}
|
package/engine/rectify.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// 2. 只有时柱(以及跨 23:00 时的日柱)会变,年/月柱通常不变,所以候选之间的差异集中在晚运、子女、事业后半段;
|
|
7
7
|
// 3. 打分规则是显式、固定的(见 matchEvent),不是拟合;换一套规则排序可能变。
|
|
8
8
|
import { buildBazi } from './bazi.js';
|
|
9
|
+
import { bool } from './bool.js';
|
|
9
10
|
|
|
10
11
|
const KINDS = ['career', 'wealth', 'love', 'move', 'health', 'other'];
|
|
11
12
|
const HOURS = [0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 23];
|
|
@@ -120,9 +121,9 @@ export function rectifyBirth(opts) {
|
|
|
120
121
|
hour,
|
|
121
122
|
minute: 0,
|
|
122
123
|
gender,
|
|
123
|
-
lateZi: opts.lateZi
|
|
124
|
-
trueSolar: opts.trueSolar
|
|
125
|
-
dst: opts.dst
|
|
124
|
+
lateZi: bool(opts.lateZi, true),
|
|
125
|
+
trueSolar: bool(opts.trueSolar, true),
|
|
126
|
+
dst: bool(opts.dst, true),
|
|
126
127
|
address: typeof opts.address === 'string' ? opts.address : undefined,
|
|
127
128
|
longitude: typeof opts.longitude === 'number' ? opts.longitude : undefined,
|
|
128
129
|
geo: opts.geo && typeof opts.geo === 'object' ? opts.geo : undefined,
|
package/engine/tarot.js
CHANGED
|
@@ -277,12 +277,17 @@ export function tarotDeck() {
|
|
|
277
277
|
// 名称 → 牌面数据(解读时按名字回查,保证确定性、且不依赖调用方是否带全字段)
|
|
278
278
|
const BY_NAME = new Map(tarotDeck().map((c) => [c.name, c]));
|
|
279
279
|
|
|
280
|
+
// 外部手输/旧数据常见的另一种译名。只做显式别名,不做模糊匹配:
|
|
281
|
+
// 拼错的牌名不能被猜成另一张,否则解读会披着别的牌义,错得看不出来。
|
|
282
|
+
const CARD_ALIASES = { 愚人: '愚者' };
|
|
283
|
+
const cardMeta = (name) => BY_NAME.get(CARD_ALIASES[name] || name) || null;
|
|
284
|
+
|
|
280
285
|
const splitKeywords = (s) => String(s || '').split(/\s+/).filter(Boolean);
|
|
281
286
|
|
|
282
287
|
/** 抽牌时刻(只做记录,不影响牌面) */
|
|
283
288
|
function stamp(at) {
|
|
284
289
|
if (typeof at === 'string' && at.trim()) return at.trim().replace('T', ' ').slice(0, 16);
|
|
285
|
-
const d = new Date();
|
|
290
|
+
const d = new Date(); // allow-clock: 未传 asOf 时记录抽牌时刻;牌面由 seed 固定
|
|
286
291
|
const p = (x) => String(x).padStart(2, '0');
|
|
287
292
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
288
293
|
}
|
|
@@ -306,12 +311,17 @@ export function tarotReading(input = {}) {
|
|
|
306
311
|
if (!n) return ['没有抽到牌面,无法解读。'];
|
|
307
312
|
|
|
308
313
|
const names = list.map((c) => c.name).join(' / ');
|
|
314
|
+
// 牌库外的牌名不能悄悄读成「空牌义」:开场点名是哪几张,逐张处退回正/逆位泛义。
|
|
315
|
+
const unknown = list.map((c) => c.name).filter((name) => !cardMeta(name));
|
|
316
|
+
const unknownNote = unknown.length
|
|
317
|
+
? `其中「${unknown.join('」「')}」不在韦特 78 张牌库里,这几张只按你给的位置与正逆位泛读,不补牌面、元素与占星对应。`
|
|
318
|
+
: '';
|
|
309
319
|
const paras = [];
|
|
310
320
|
|
|
311
321
|
// ① 开场:所问 + 象征系统声明
|
|
312
322
|
paras.push(q
|
|
313
|
-
? `你问的是「${q}」,抽到的是 ${n} 张牌阵(${names})。塔罗是一套象征系统而不是判决:不同流派对牌义、编号与逆位的取舍略有差异,这里按 RWS
|
|
314
|
-
: `这次没有具体提问,那就先看牌面本身在说什么。牌阵 ${n} 张(${names})。塔罗是象征系统,牌义随流派略有差异,这里按 RWS
|
|
323
|
+
? `你问的是「${q}」,抽到的是 ${n} 张牌阵(${names})。塔罗是一套象征系统而不是判决:不同流派对牌义、编号与逆位的取舍略有差异,这里按 RWS(韦特)牌名与黄金黎明的元素对应来读,请把下面的话当作镜子,对照自己的处境取用。${unknownNote}`
|
|
324
|
+
: `这次没有具体提问,那就先看牌面本身在说什么。牌阵 ${n} 张(${names})。塔罗是象征系统,牌义随流派略有差异,这里按 RWS(韦特)与黄金黎明的常见对应来读,读到合用的部分就好。${unknownNote}`);
|
|
315
325
|
|
|
316
326
|
// ② 逐张:位义 + 牌名/正逆 + 关键词 + 一句与位义结合的解读
|
|
317
327
|
// 1-3 张时每张一段;4 张以上按前后两段分组(前段取 ceil(n/2) 张),
|
|
@@ -321,8 +331,10 @@ export function tarotReading(input = {}) {
|
|
|
321
331
|
for (const group of groups) {
|
|
322
332
|
const lines = group.map((i) => {
|
|
323
333
|
const c = list[i];
|
|
324
|
-
const meta =
|
|
325
|
-
|
|
334
|
+
const meta = cardMeta(c.name) || {};
|
|
335
|
+
// 牌库外又没有关键词时,退回正/逆位的泛义,别留下「正位:。」的空句
|
|
336
|
+
const kws = (c.keywords && c.keywords.length ? c.keywords : splitKeywords(c.upright ? meta.up : meta.down)).join('、')
|
|
337
|
+
|| (c.upright === false ? '逆位泛义(阻滞、内转或用力过头)' : '正位泛义(推进、显化、顺势)');
|
|
326
338
|
const ori = c.upright === false ? '逆位' : '正位';
|
|
327
339
|
const oriNote = c.upright === false
|
|
328
340
|
? '逆位并不等于凶,更像这股力量还没顺过来,或用力过了头'
|
|
@@ -347,7 +359,7 @@ export function tarotReading(input = {}) {
|
|
|
347
359
|
const upCount = n - revCount;
|
|
348
360
|
const elementCount = {};
|
|
349
361
|
for (const c of list) {
|
|
350
|
-
const e = (
|
|
362
|
+
const e = (cardMeta(c.name) || {}).element || c.element;
|
|
351
363
|
if (e) elementCount[e] = (elementCount[e] || 0) + 1;
|
|
352
364
|
}
|
|
353
365
|
const present = ELEMENT_ORDER.filter((e) => elementCount[e]);
|
|
@@ -376,7 +388,7 @@ export function tarotReading(input = {}) {
|
|
|
376
388
|
const majorCard = list.find((c) => c.arcana === '大阿卡纳');
|
|
377
389
|
const revCard = list.find((c) => c.upright === false);
|
|
378
390
|
const key = majorCard || revCard || list[0];
|
|
379
|
-
const keyMeta =
|
|
391
|
+
const keyMeta = cardMeta(key.name) || {};
|
|
380
392
|
const keyReason = majorCard
|
|
381
393
|
? '它出现的地方,往往就是这件事真正被牵动的层级'
|
|
382
394
|
: revCard
|
|
@@ -449,7 +461,9 @@ export function drawTarot(input = {}) {
|
|
|
449
461
|
keywords: splitKeywords(upright ? c.up : c.down),
|
|
450
462
|
};
|
|
451
463
|
});
|
|
452
|
-
|
|
464
|
+
// 直接调引擎时 question 可能是数字(工具层 schema 的 string 不做强转),
|
|
465
|
+
// 以前 (123 || '').trim() 会抛裸 TypeError,整张牌只剩一句内部错误。
|
|
466
|
+
const q = String(question == null ? '' : question).trim();
|
|
453
467
|
return {
|
|
454
468
|
system: 'tarot',
|
|
455
469
|
question: q || null,
|
package/engine/trace.js
CHANGED
|
@@ -9,6 +9,7 @@ import { PATTERN_TONE } from './ziwei.js';
|
|
|
9
9
|
import { yongshenOf, yongshenStrength, fushenAccess } from './liuyao.js';
|
|
10
10
|
import { qimenYongshen } from './qimen-interpret.js';
|
|
11
11
|
import { GAN_JI_GONG } from './daliuren.js';
|
|
12
|
+
import { bool } from './bool.js';
|
|
12
13
|
|
|
13
14
|
const SYSTEM_LABEL = { liuyao: '六爻', bazi: '八字', ziwei: '紫微', qimen: '奇门', daliuren: '大六壬' };
|
|
14
15
|
|
|
@@ -429,7 +430,7 @@ export function buildTrace(result, opts = {}) {
|
|
|
429
430
|
if (result.system === 'liuyao') {
|
|
430
431
|
let view = result;
|
|
431
432
|
// 面板改了「询问方向」但不想重摇一卦:只按新方向重算用神相关三层
|
|
432
|
-
if (opts.refresh
|
|
433
|
+
if (bool(opts.refresh)) {
|
|
433
434
|
try {
|
|
434
435
|
const ug = yongshenOf(result, { direction: opts.direction, gender: opts.gender });
|
|
435
436
|
const monthBranch = (result.hexStrength && result.hexStrength.month && result.hexStrength.month.branch)
|
package/engine/trend.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// 口径:这是一套**简化启发式**——十神计分表固定、正负号随日主旺衰方向翻转,
|
|
5
5
|
// 只用于看形态与相对起伏;分高不等于真实吉凶,旺衰落在换档边界时连排序都不给。
|
|
6
6
|
import { buildBazi, HIDDEN_STEMS, tenGod } from './bazi.js';
|
|
7
|
+
import { bool } from './bool.js';
|
|
7
8
|
import { ganzhiName, sexagenaryIndex, STEMS, BRANCHES } from './calendar.js';
|
|
8
9
|
import { fail } from './fail.js';
|
|
9
10
|
|
|
@@ -107,10 +108,13 @@ export function buildTrend(input) {
|
|
|
107
108
|
day: input.day,
|
|
108
109
|
hour: Number.isInteger(input.hour) ? input.hour : 12,
|
|
109
110
|
minute: Number.isInteger(input.minute) ? input.minute : 0,
|
|
111
|
+
// 时辰未知必须一路传到 buildBazi:它在内部统一按正午占位并收回时柱。
|
|
112
|
+
// 少了这一个键,摘要写着「按正午暂算」,旺衰却按 00:00 算成了另一档。
|
|
113
|
+
hourUnknown: bool(input.hourUnknown),
|
|
110
114
|
gender,
|
|
111
|
-
lateZi: input.lateZi
|
|
112
|
-
trueSolar: input.trueSolar
|
|
113
|
-
dst: input.dst
|
|
115
|
+
lateZi: bool(input.lateZi, true),
|
|
116
|
+
trueSolar: bool(input.trueSolar, true),
|
|
117
|
+
dst: bool(input.dst, true),
|
|
114
118
|
address: typeof input.address === 'string' ? input.address : undefined,
|
|
115
119
|
longitude: typeof input.longitude === 'number' ? input.longitude : undefined,
|
|
116
120
|
geo: input.geo && typeof input.geo === 'object' ? input.geo : undefined,
|
package/engine/xiaoliuren.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// 小六壬(诸葛马前课):月/日/时(或报三个数)掐指落宫,六宫循环。
|
|
2
2
|
// 口径:通行掌诀;断语为白话提炼(非原文照录)。轻量快占,属民俗参考。
|
|
3
3
|
import { solarToLunar } from './lunar.js';
|
|
4
|
+
import { bool } from './bool.js';
|
|
4
5
|
import { hourToBranch } from './almanac.js';
|
|
5
6
|
import { BRANCHES, fromJDN, toJDN } from './calendar.js';
|
|
6
7
|
import { parseYmd } from './datetime.js';
|
|
@@ -49,7 +50,7 @@ export function buildXiaoliuren(input) {
|
|
|
49
50
|
let y = mt.year;
|
|
50
51
|
let mo = mt.month;
|
|
51
52
|
let dy = mt.day;
|
|
52
|
-
const lateZi = input.lateZi
|
|
53
|
+
const lateZi = bool(input.lateZi, true);
|
|
53
54
|
if (lateZi && input.hour >= 23) {
|
|
54
55
|
const next = assertShifted(fromJDN(toJDN(y, mo, dy) + 1), '小六壬时间起课');
|
|
55
56
|
y = next.year;
|
package/engine/ziwei.js
CHANGED
|
@@ -8,6 +8,7 @@ import { nayinOf } from './nayin.js';
|
|
|
8
8
|
import { resolveAddress } from './geo.js';
|
|
9
9
|
import { placeZuoyao, zuoyaoVerseBlock, ZUOYAO_SKIPPED } from './ziwei-zuoyao.js';
|
|
10
10
|
import { SHA_STAR } from './ziwei-words.js';
|
|
11
|
+
import { bool } from './bool.js';
|
|
11
12
|
|
|
12
13
|
const fix = (i) => ((i % 12) + 12) % 12;
|
|
13
14
|
|
|
@@ -397,7 +398,7 @@ export function buildZiwei(input) {
|
|
|
397
398
|
assertCivil(input);
|
|
398
399
|
const schools = ziweiSchoolsOf(input);
|
|
399
400
|
const { year: cy, month: cm, day: cd, hour: ch = 0, minute: cmin = 0, gender = 'male' } = input;
|
|
400
|
-
const useTrueSolar = input.trueSolar
|
|
401
|
+
const useTrueSolar = bool(input.trueSolar, true);
|
|
401
402
|
const geo = input.geo || (input.address ? resolveAddress(input.address) : null);
|
|
402
403
|
const longitude = typeof input.longitude === 'number'
|
|
403
404
|
? input.longitude
|
|
@@ -671,7 +672,7 @@ export function buildZiwei(input) {
|
|
|
671
672
|
// 直接读挂钟的话,同一组参数(同一个句柄)跨年会排出不同的流年表,
|
|
672
673
|
// 「同参数同盘」的承诺就只在当年成立。
|
|
673
674
|
const asOfForLiunian = asOfParts(input.asOf);
|
|
674
|
-
const thisYear = (asOfForLiunian && asOfForLiunian.year) || new Date().getFullYear();
|
|
675
|
+
const thisYear = (asOfForLiunian && asOfForLiunian.year) || new Date().getFullYear(); // allow-clock: 未传 asOf 时按本机年份起流年;工具层 asOf 可钉死
|
|
675
676
|
const liunian = [];
|
|
676
677
|
for (let y = thisYear; y < thisYear + 12; y++) {
|
|
677
678
|
const gz = ganzhiName((((y - 4) % 60) + 60) % 60);
|
|
@@ -1323,7 +1324,7 @@ export function asOfParts(v) {
|
|
|
1323
1324
|
}
|
|
1324
1325
|
|
|
1325
1326
|
export function flowOf(chart, asOf) {
|
|
1326
|
-
const now = new Date();
|
|
1327
|
+
const now = new Date(); // allow-clock: 流日/流时按调用时刻;显式 asOf 会覆盖
|
|
1327
1328
|
const a = asOfParts(asOf) || {};
|
|
1328
1329
|
const Y = a.year || now.getFullYear();
|
|
1329
1330
|
const M = a.month || now.getMonth() + 1;
|