dsh-metaphysics 0.14.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ // 地支三合局:申子辰合水、亥卯未合木、寅午戌合火、巳酉丑合金。
2
+ //
3
+ // 之前三处各写一遍:bazi 用「键 → 五行」对象判三合/半合,hepan 用三个字的数组判半合,
4
+ // daliuren 又用「单支 → 五行」查驿马桃花。三份表只要有一份改动不同步,
5
+ // 「半合」与「驿马」就会互相矛盾。这里统一成一份表 + 三种派生视图。
6
+
7
+ /** 四组三合,顺序沿用传统口诀(申子辰 → 亥卯未 → 寅午戌 → 巳酉丑) */
8
+ export const SANHE_GROUPS = [
9
+ { branches: ['申', '子', '辰'], element: '水' },
10
+ { branches: ['亥', '卯', '未'], element: '木' },
11
+ { branches: ['寅', '午', '戌'], element: '火' },
12
+ { branches: ['巳', '酉', '丑'], element: '金' },
13
+ ];
14
+
15
+ /** 三字键 → 五行:bazi 的「三合/半合/拱合」判定按这个遍历 */
16
+ export const SANHE = Object.fromEntries(SANHE_GROUPS.map((g) => [g.branches.join(''), g.element]));
17
+
18
+ /** 单支 → 它所处三合局的五行:daliuren 查驿马桃花用 */
19
+ export const SANHE_ELEMENT = Object.fromEntries(
20
+ SANHE_GROUPS.flatMap((g) => g.branches.map((b) => [b, g.element])),
21
+ );
22
+
23
+ /** 某支所在的三合局(找不到返回 null) */
24
+ export function sanheGroupOf(branch) {
25
+ return SANHE_GROUPS.find((g) => g.branches.includes(branch)) || null;
26
+ }
@@ -0,0 +1,56 @@
1
+ // 引擎边界的入参守卫。
2
+ //
3
+ // 引擎的导出函数(buildBazi / buildZiwei / buildDaliuren / buildQimen…)假设调用方
4
+ // 已经把「1990-05-12T14:30」解析成了 { year, month, day, hour, minute }。传错形状时,
5
+ // 以前会在内部某行抛裸 TypeError(例如 buildBazi({ datetime: '...' })),
6
+ // 调用方只能看见 "Cannot read properties of null",不知道是自己传错了什么。
7
+ //
8
+ // 这里统一在入口判一次,失败一律 E_INPUT,并在文案里写清「该传什么」。
9
+ import { fail } from './fail.js';
10
+
11
+ /** 支持的公历年份范围:更早的年份时区与历法口径都不可靠,宁可拒绝 */
12
+ export const MIN_YEAR = 1600;
13
+ export const MAX_YEAR = 2400;
14
+
15
+ export function isLeapYear(year) {
16
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
17
+ }
18
+
19
+ export function daysInMonth(year, month) {
20
+ if (month === 2) return isLeapYear(year) ? 29 : 28;
21
+ return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] || 0;
22
+ }
23
+
24
+ const show = (v) => (typeof v === 'string' ? `「${v}」` : String(v));
25
+ const HINT = '引擎只接受解析好的公历字段 { year, month, day, hour?, minute? };字符串请先交给宿主层的 parseDateTime 解析';
26
+
27
+ /**
28
+ * 校验公历入参;不合法时抛 E_INPUT。
29
+ * @param {object} input 至少含整数 year / month / day;hour、minute 可省略(按 0)
30
+ * @param {string} [who] 出错时用于定位是谁的入参(合盘两侧各传 'a' / 'b')
31
+ */
32
+ export function assertCivil(input, who = '') {
33
+ const p = who ? `${who} 的` : '';
34
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
35
+ const got = input === null ? 'null' : Array.isArray(input) ? '数组' : typeof input;
36
+ throw fail('E_INPUT', `${p}入参必须是对象(收到 ${got});${HINT}`);
37
+ }
38
+ const hour = input.hour === undefined ? 0 : input.hour;
39
+ const minute = input.minute === undefined ? 0 : input.minute;
40
+ for (const [key, value] of [['year', input.year], ['month', input.month], ['day', input.day], ['hour', hour], ['minute', minute]]) {
41
+ if (!Number.isInteger(value)) throw fail('E_INPUT', `${p}${key} 必须是整数(收到 ${show(value)});${HINT}`);
42
+ }
43
+ if (input.year < MIN_YEAR || input.year > MAX_YEAR) {
44
+ throw fail('E_INPUT', `${p}year 超出支持范围 ${MIN_YEAR}–${MAX_YEAR}(收到 ${input.year});更早的年份时区与历法口径不可靠`);
45
+ }
46
+ if (input.month < 1 || input.month > 12) {
47
+ throw fail('E_INPUT', `${p}month 超出范围 1–12(收到 ${input.month})`);
48
+ }
49
+ const max = daysInMonth(input.year, input.month);
50
+ if (input.day < 1 || input.day > max) {
51
+ throw fail('E_INPUT', `${p}日期不存在:${input.year}-${input.month}-${input.day}(${input.year} 年 ${input.month} 月只有 ${max} 天)`);
52
+ }
53
+ if (hour < 0 || hour > 23) throw fail('E_INPUT', `${p}hour 超出范围 0–23(收到 ${hour})`);
54
+ if (minute < 0 || minute > 59) throw fail('E_INPUT', `${p}minute 超出范围 0–59(收到 ${minute})`);
55
+ return input;
56
+ }
@@ -1,120 +1,123 @@
1
- // 紫微合盘:两盘并排 + 互落宫 + 四化引动。
2
- // 口径:把一方的「命宫 / 夫妻宫」地支落到对方盘的宫位上,判断影响落在对方哪一领域;
3
- // 再把一方生年四化所化的星,找它在对方盘的位置,判断「你的某种特质会引动对方哪一宫」。
4
- // 这是并排对照的读法,不涉及两盘叠加安星(那是另一派做法,本文不采用)。
5
- import { buildZiwei } from './ziwei.js';
6
-
7
- const NAMES = ['命宫', '兄弟', '夫妻', '子女', '财帛', '疾厄', '迁移', '交友', '官禄', '田宅', '福德', '父母'];
8
- const MUT_MEAN = {
9
- 禄: '会把资源、机会与松弛感带过去',
10
- 权: '会不自觉地主导或施压',
11
- 科: '带来的更多是名声、体面与理性',
12
- 忌: '会勾起压力点与执念',
13
- };
14
- const MUT_SCORE = { 禄: 10, 权: 5, 科: 8, 忌: -10 };
15
- // 落在对方这些宫位时影响最直接
16
- const STRONG_LANDS = ['命宫', '夫妻', '福德', '财帛', '官禄'];
17
-
18
- const palaceByBranch = (chart, branch) => (chart.palaces || []).find((p) => p.branch === branch) || null;
19
- const mainStars = (p) => (p && p.stars ? p.stars.filter((s) => s.type === 'main' || s.type === '吉星' || s.type === '煞星').map((s) => s.name) : []);
20
-
21
- /** 一方的某宫(按地支)在对方盘中落到哪个宫位 */
22
- function landOn(host, branch) {
23
- const p = palaceByBranch(host, branch);
24
- return p ? { palace: p.palace, branch: p.branch, stars: mainStars(p) } : null;
25
- }
26
-
27
- export function buildZiweiHepan(input) {
28
- const A = buildZiwei({ ...input.a });
29
- const B = buildZiwei({ ...input.b });
30
-
31
- // ① 互落宫:命宫、夫妻宫各落到对方哪一宫
32
- const soulToB = landOn(B, A.soul.branch);
33
- const soulToA = landOn(A, B.soul.branch);
34
- const spouseBranchA = (A.palaces.find((p) => p.palace === '夫妻') || {}).branch;
35
- const spouseBranchB = (B.palaces.find((p) => p.palace === '夫妻') || {}).branch;
36
- const spouseAtoB = spouseBranchA ? landOn(B, spouseBranchA) : null;
37
- const spouseBtoA = spouseBranchB ? landOn(A, spouseBranchB) : null;
38
-
39
- // ② 四化引动:A 生年四化的星,在 B 盘哪一宫
40
- const hits = [];
41
- const collect = (from, to, who, targetWho) => {
42
- for (const p of from.palaces || []) {
43
- for (const s of p.stars || []) {
44
- if (!s.mutagen) continue;
45
- const target = (to.palaces || []).find((q) => (q.stars || []).some((x) => x.name === s.name));
46
- const land = target ? target.palace : null;
47
- hits.push({
48
- who,
49
- star: s.name,
50
- mutagen: s.mutagen,
51
- ownPalace: p.palace,
52
- landingPalace: land,
53
- strong: land ? STRONG_LANDS.includes(land) : false,
54
- text: `${who}的${s.name}化${s.mutagen}(在本命${p.palace})落在${targetWho}的${land || '盘外'}——${who}${MUT_MEAN[s.mutagen]}`,
55
- });
56
- }
57
- }
58
- };
59
- collect(A, B, '你', '对方');
60
- collect(B, A, '对方', '你');
61
-
62
- // ③ 评分:互落宫 + 四化
63
- let score = 40;
64
- const dims = [];
65
- if (soulToB) {
66
- const strong = STRONG_LANDS.includes(soulToB.palace);
67
- const s = strong ? 12 : 5;
68
- score += s;
69
- dims.push({ key: 'soul', label: '命宫互落', score: s, text: `你的命宫在${A.soul.branch},落到对方盘的「${soulToB.palace}」${soulToB.stars.length ? `(同宫见 ${soulToB.stars.join('、')})` : ''}——${strong ? '影响直接,脾气与做派会明显落在对方这一块' : '影响偏间接,主要体现在日常协作上'}。` });
70
- }
71
- if (soulToA) {
72
- dims.push({ key: 'soulBack', label: '对方命宫落你盘', score: 0, text: `对方的命宫在${B.soul.branch},落到你盘的「${soulToA.palace}」${soulToA.stars.length ? `(同宫见 ${soulToA.stars.join('、')})` : ''}。` });
73
- }
74
- if (spouseAtoB && spouseBtoA) {
75
- const good = ['命宫', '夫妻', '福德'].includes(spouseAtoB.palace) || ['命宫', '夫妻', '福德'].includes(spouseBtoA.palace);
76
- const s = good ? 8 : 2;
77
- score += s;
78
- dims.push({ key: 'spouse', label: '夫妻宫互落', score: s, text: `你的夫妻宫在${spouseBranchA},落在对方「${spouseAtoB.palace}」;对方的夫妻宫在${spouseBranchB},落在你「${spouseBtoA.palace}」。${good ? '双方的感情定位能对上彼此在意的事' : '彼此的期待更容易落在不同领域,需要说清楚'}` });
79
- }
80
- // 四化净分:禄科为正、忌为负;落在强宫加倍
81
- let mutScore = 0;
82
- for (const h of hits) {
83
- const base = MUT_SCORE[h.mutagen] || 0;
84
- mutScore += h.strong ? Math.round(base * 1.5) : base;
85
- }
86
- mutScore = Math.max(-25, Math.min(25, mutScore));
87
- score += mutScore;
88
- dims.push({ key: 'mutagen', label: '四化引动', score: mutScore, text: hits.filter((h) => h.who === '你').map((h) => h.text).join(';') || '你这边没有生年四化引动对方的宫位。' });
89
-
90
- const total = Math.max(0, Math.min(100, Math.round(score)));
91
- const level = total >= 75 ? '契合度较高' : total >= 55 ? '中等偏顺' : total >= 40 ? '需要磨合' : '差异较大';
92
-
93
- const side = (z, who) => ({
94
- who,
95
- soul: { branch: z.soul.branch, palace: z.soul.palace },
96
- body: { branch: z.body.branch, lands: z.body.lands },
97
- bureau: z.bureau.name,
98
- ziwei: z.ziwei,
99
- tianfu: z.tianfu,
100
- soulStars: mainStars(z.palaces.find((p) => p.palace === '命宫') || {}),
101
- spouseStars: mainStars(z.palaces.find((p) => p.palace === '夫妻') || {}),
102
- natalSihua: (z.palaces || []).flatMap((p) => (p.stars || []).filter((s) => s.mutagen).map((s) => `${s.name}化${s.mutagen}(${p.palace})`)),
103
- summary: z.summary,
104
- });
105
-
106
- return {
107
- system: 'hepan',
108
- chart: 'ziwei',
109
- a: side(A, '你'),
110
- b: side(B, '对方'),
111
- score: { total, level },
112
- dimensions: dims,
113
- interactions: hits,
114
- notes: [
115
- '紫微合盘看的是「彼此会在对方生活里的哪一块起作用」,不是关系成败的判决。',
116
- '口径:命宫与夫妻宫按地支落到对方盘的宫位;四化按「一方的生年四化星在对方盘的落宫」读,落在命宫/夫妻/福德/财帛/官禄加倍计分。',
117
- ],
118
- };
119
- }
120
-
1
+ // 紫微合盘:两盘并排 + 互落宫 + 四化引动。
2
+ // 口径:把一方的「命宫 / 夫妻宫」地支落到对方盘的宫位上,判断影响落在对方哪一领域;
3
+ // 再把一方生年四化所化的星,找它在对方盘的位置,判断「你的某种特质会引动对方哪一宫」。
4
+ // 这是并排对照的读法,不涉及两盘叠加安星(那是另一派做法,本文不采用)。
5
+ import { buildZiwei } from './ziwei.js';
6
+ import { assertCivil } from './validate.js';
7
+
8
+ const NAMES = ['命宫', '兄弟', '夫妻', '子女', '财帛', '疾厄', '迁移', '交友', '官禄', '田宅', '福德', '父母'];
9
+ const MUT_MEAN = {
10
+ 禄: '会把资源、机会与松弛感带过去',
11
+ 权: '会不自觉地主导或施压',
12
+ 科: '带来的更多是名声、体面与理性',
13
+ 忌: '会勾起压力点与执念',
14
+ };
15
+ const MUT_SCORE = { 禄: 10, 权: 5, 科: 8, 忌: -10 };
16
+ // 落在对方这些宫位时影响最直接
17
+ const STRONG_LANDS = ['命宫', '夫妻', '福德', '财帛', '官禄'];
18
+
19
+ const palaceByBranch = (chart, branch) => (chart.palaces || []).find((p) => p.branch === branch) || null;
20
+ const mainStars = (p) => (p && p.stars ? p.stars.filter((s) => s.type === 'main' || s.type === '吉星' || s.type === '煞星').map((s) => s.name) : []);
21
+
22
+ /** 一方的某宫(按地支)在对方盘中落到哪个宫位 */
23
+ function landOn(host, branch) {
24
+ const p = palaceByBranch(host, branch);
25
+ return p ? { palace: p.palace, branch: p.branch, stars: mainStars(p) } : null;
26
+ }
27
+
28
+ export function buildZiweiHepan(input) {
29
+ assertCivil(input && input.a, 'a');
30
+ assertCivil(input && input.b, 'b');
31
+ const A = buildZiwei({ ...input.a });
32
+ const B = buildZiwei({ ...input.b });
33
+
34
+ // ① 互落宫:命宫、夫妻宫各落到对方哪一宫
35
+ const soulToB = landOn(B, A.soul.branch);
36
+ const soulToA = landOn(A, B.soul.branch);
37
+ const spouseBranchA = (A.palaces.find((p) => p.palace === '夫妻') || {}).branch;
38
+ const spouseBranchB = (B.palaces.find((p) => p.palace === '夫妻') || {}).branch;
39
+ const spouseAtoB = spouseBranchA ? landOn(B, spouseBranchA) : null;
40
+ const spouseBtoA = spouseBranchB ? landOn(A, spouseBranchB) : null;
41
+
42
+ // ② 四化引动:A 生年四化的星,在 B 盘哪一宫
43
+ const hits = [];
44
+ const collect = (from, to, who, targetWho) => {
45
+ for (const p of from.palaces || []) {
46
+ for (const s of p.stars || []) {
47
+ if (!s.mutagen) continue;
48
+ const target = (to.palaces || []).find((q) => (q.stars || []).some((x) => x.name === s.name));
49
+ const land = target ? target.palace : null;
50
+ hits.push({
51
+ who,
52
+ star: s.name,
53
+ mutagen: s.mutagen,
54
+ ownPalace: p.palace,
55
+ landingPalace: land,
56
+ strong: land ? STRONG_LANDS.includes(land) : false,
57
+ text: `${who}的${s.name}化${s.mutagen}(在本命${p.palace})落在${targetWho}的${land || '盘外'}——${who}${MUT_MEAN[s.mutagen]}`,
58
+ });
59
+ }
60
+ }
61
+ };
62
+ collect(A, B, '你', '对方');
63
+ collect(B, A, '对方', '你');
64
+
65
+ // ③ 评分:互落宫 + 四化
66
+ let score = 40;
67
+ const dims = [];
68
+ if (soulToB) {
69
+ const strong = STRONG_LANDS.includes(soulToB.palace);
70
+ const s = strong ? 12 : 5;
71
+ score += s;
72
+ dims.push({ key: 'soul', label: '命宫互落', score: s, text: `你的命宫在${A.soul.branch},落到对方盘的「${soulToB.palace}」${soulToB.stars.length ? `(同宫见 ${soulToB.stars.join('、')})` : ''}——${strong ? '影响直接,脾气与做派会明显落在对方这一块' : '影响偏间接,主要体现在日常协作上'}。` });
73
+ }
74
+ if (soulToA) {
75
+ dims.push({ key: 'soulBack', label: '对方命宫落你盘', score: 0, text: `对方的命宫在${B.soul.branch},落到你盘的「${soulToA.palace}」${soulToA.stars.length ? `(同宫见 ${soulToA.stars.join('、')})` : ''}。` });
76
+ }
77
+ if (spouseAtoB && spouseBtoA) {
78
+ const good = ['命宫', '夫妻', '福德'].includes(spouseAtoB.palace) || ['命宫', '夫妻', '福德'].includes(spouseBtoA.palace);
79
+ const s = good ? 8 : 2;
80
+ score += s;
81
+ dims.push({ key: 'spouse', label: '夫妻宫互落', score: s, text: `你的夫妻宫在${spouseBranchA},落在对方「${spouseAtoB.palace}」;对方的夫妻宫在${spouseBranchB},落在你「${spouseBtoA.palace}」。${good ? '双方的感情定位能对上彼此在意的事' : '彼此的期待更容易落在不同领域,需要说清楚'}` });
82
+ }
83
+ // 四化净分:禄科为正、忌为负;落在强宫加倍
84
+ let mutScore = 0;
85
+ for (const h of hits) {
86
+ const base = MUT_SCORE[h.mutagen] || 0;
87
+ mutScore += h.strong ? Math.round(base * 1.5) : base;
88
+ }
89
+ mutScore = Math.max(-25, Math.min(25, mutScore));
90
+ score += mutScore;
91
+ dims.push({ key: 'mutagen', label: '四化引动', score: mutScore, text: hits.filter((h) => h.who === '你').map((h) => h.text).join(';') || '你这边没有生年四化引动对方的宫位。' });
92
+
93
+ const total = Math.max(0, Math.min(100, Math.round(score)));
94
+ const level = total >= 75 ? '契合度较高' : total >= 55 ? '中等偏顺' : total >= 40 ? '需要磨合' : '差异较大';
95
+
96
+ const side = (z, who) => ({
97
+ who,
98
+ soul: { branch: z.soul.branch, palace: z.soul.palace },
99
+ body: { branch: z.body.branch, lands: z.body.lands },
100
+ bureau: z.bureau.name,
101
+ ziwei: z.ziwei,
102
+ tianfu: z.tianfu,
103
+ soulStars: mainStars(z.palaces.find((p) => p.palace === '命宫') || {}),
104
+ spouseStars: mainStars(z.palaces.find((p) => p.palace === '夫妻') || {}),
105
+ natalSihua: (z.palaces || []).flatMap((p) => (p.stars || []).filter((s) => s.mutagen).map((s) => `${s.name}化${s.mutagen}(${p.palace})`)),
106
+ summary: z.summary,
107
+ });
108
+
109
+ return {
110
+ system: 'hepan',
111
+ chart: 'ziwei',
112
+ a: side(A, '你'),
113
+ b: side(B, '对方'),
114
+ score: { total, level },
115
+ dimensions: dims,
116
+ interactions: hits,
117
+ notes: [
118
+ '紫微合盘看的是「彼此会在对方生活里的哪一块起作用」,不是关系成败的判决。',
119
+ '口径:命宫与夫妻宫按地支落到对方盘的宫位;四化按「一方的生年四化星在对方盘的落宫」读,落在命宫/夫妻/福德/财帛/官禄加倍计分。',
120
+ ],
121
+ };
122
+ }
123
+