@metago-ai/engine 2.1.3 → 2.1.4
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/BOUNDARIES.md +109 -0
- package/CHANGES.md +4 -4
- package/CONSTITUTION/AXIOMS.md +75 -113
- package/CORE/ATOMS.md +611 -0
- package/CORE/ATTRIBUTES.md +74 -105
- package/CORE/PROTOCOLS.md +134 -116
- package/ENGINE.md +115 -115
- package/ENGINES/engine-runtime.cjs +318 -0
- package/ENGINES/final-engines-runtime.cjs +683 -0
- package/ENGINES/governance-runtime.cjs +582 -0
- package/ENGINES/math-capability-runtime.cjs +451 -0
- package/ENGINES/philosophy-runtime.cjs +427 -0
- package/ENGINES/yili/data/yaoci-1.js +24 -0
- package/ENGINES/yili/data/yaoci-2.js +21 -0
- package/ENGINES/yili/data/yaoci-3.js +21 -0
- package/ENGINES/yili/data/yaoci-4.js +21 -0
- package/ENGINES/yili/data/yaoci.js +17 -0
- package/ENGINES/yili/lib/almanac.js +416 -0
- package/ENGINES/yili/lib/bazi.js +127 -0
- package/ENGINES/yili/lib/diagnose.js +71 -0
- package/ENGINES/yili/lib/hetu-luoshu.js +123 -0
- package/ENGINES/yili/lib/index.js +23 -0
- package/ENGINES/yili/lib/liuyao.js +226 -0
- package/ENGINES/yili/lib/qimen.js +167 -0
- package/ENGINES/yili/lib/shushu-calculus.js +85 -0
- package/ENGINES/yili/lib/taiyi.js +83 -0
- package/ENGINES/yili/lib/vendor/shouxing-core.js +627 -0
- package/ENGINES/yili/lib/wuxing.js +106 -0
- package/ENGINES/yili/lib/yijing.js +226 -0
- package/ENGINES/yili/lib/zeri.js +80 -0
- package/ENGINES/yili/lib/ziwei.js +145 -0
- package/ENGINES/yili/package.json +1 -0
- package/EXECUTORS.md +85 -0
- package/GENOME.json +11 -10
- package/INDEX/ENGINES.md +168 -0
- package/INDEX/engines.json +2 -2
- package/INDEX/knowledge.json +87 -87
- package/INDEX/skills.json +227 -203
- package/PATENTS.md +1 -1
- package/PROTOCOLS/closure-runtime.cjs +288 -0
- package/PROTOCOLS/covenant-runtime.cjs +409 -0
- package/PROTOCOLS/final-runtime.cjs +680 -0
- package/PROTOCOLS/formula-runtime.cjs +399 -0
- package/PROTOCOLS/proactive-runtime.cjs +368 -0
- package/PROTOCOLS/rule-runtime.cjs +448 -0
- package/PROTOCOLS/runtime.cjs +441 -0
- package/README.md +5 -5
- package/RUNTIME/dist/kmwi-memory.d.ts.map +1 -1
- package/RUNTIME/dist/kmwi-memory.js +14 -2
- package/RUNTIME/dist/kmwi-memory.js.map +1 -1
- package/RUNTIME/dist/validators.js +11 -2
- package/atoms/approx.cjs +293 -0
- package/atoms/atom-factory.cjs +550 -0
- package/atoms/atoms-cosmic-bio.cjs +340 -0
- package/atoms/atoms-crypto-emotion.cjs +215 -0
- package/atoms/atoms_engines.json +5821 -0
- package/atoms/blueprint-atoms-2.cjs +583 -0
- package/atoms/blueprint-atoms.cjs +975 -0
- package/atoms/constraints.cjs +257 -0
- package/atoms/contract-validator.cjs +213 -0
- package/atoms/engine.cjs +849 -0
- package/atoms/specialized.cjs +530 -0
- package/atoms/verify.cjs +94 -0
- package/package.json +12 -6
- package/scripts/verify-dist.cjs +80 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 易理工程学 · 历法引擎(P1 · 一切的地基)
|
|
3
|
+
*
|
|
4
|
+
* 100% 确定性、零网络依赖、纯 JS 实现:
|
|
5
|
+
* - 儒略日(JDN)/ 公历 ↔ 干支纪日(JDN mod 60,锚点 2024-01-01 = 甲子)
|
|
6
|
+
* - 二十四节气时刻(寿星天文历公版公式:VSOP87 太阳黄经精确求根,秒级精度)
|
|
7
|
+
* - 农历转换(朔望月构造 + 无中气置闰,锚点 2024-02-10 = 甲辰年正月初一)
|
|
8
|
+
* - 旬空 / 月破 / 十二长生 / 纳音 / 建除十二神(表驱动)
|
|
9
|
+
*
|
|
10
|
+
* 算法来源(计划 §二 指定):寿星天文历公版近似公式(Sxing),核心见 vendor/shouxing-core.js
|
|
11
|
+
* 回测:test/almanac.test.mjs 对 test/基准表/ 权威基准 100% 对齐(节气到分、干支逐日)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { ShouXingUtil } from './vendor/shouxing-core.js';
|
|
15
|
+
|
|
16
|
+
// ═════════════════════════ 基础常数 ═════════════════════════
|
|
17
|
+
|
|
18
|
+
const GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
|
19
|
+
const ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
|
20
|
+
const GANZHI = (() => {
|
|
21
|
+
const out = [];
|
|
22
|
+
for (let i = 0; i < 60; i++) out.push(GAN[i % 10] + ZHI[i % 12]);
|
|
23
|
+
return out;
|
|
24
|
+
})();
|
|
25
|
+
|
|
26
|
+
const JIE_ORDER = ['立春', '雨水', '惊蛰', '春分', '清明', '谷雨', '立夏', '小满', '芒种', '夏至',
|
|
27
|
+
'小暑', '大暑', '立秋', '处暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪',
|
|
28
|
+
'大雪', '冬至', '小寒', '大寒'];
|
|
29
|
+
|
|
30
|
+
/** 中气(节气中的"气",用于置闰判定):黄经 0°(春分)起偶数序 */
|
|
31
|
+
const ZHONGQI_INDEX = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23]; // 雨水,春分,谷雨,小满,夏至,大暑,处暑,秋分,霜降,小雪,冬至,大寒
|
|
32
|
+
|
|
33
|
+
/** 六十甲子纳音表(水二局/木三局/金四局/土五局/火六局 之纳音五行) */
|
|
34
|
+
const NAYIN = (() => {
|
|
35
|
+
const seq = [
|
|
36
|
+
'海中金', '炉中火', '大林木', '路旁土', '剑锋金', '山头火', '涧下水', '城头土', '白蜡金', '杨柳木',
|
|
37
|
+
'泉中水', '屋上土', '霹雳火', '松柏木', '长流水', '沙中金', '山下火', '平地木', '壁上土', '金箔金',
|
|
38
|
+
'覆灯火', '天河水', '大驿土', '钗钏金', '桑柘木', '大溪水', '沙中土', '天上火', '石榴木', '大海水'
|
|
39
|
+
];
|
|
40
|
+
const out = new Array(60);
|
|
41
|
+
for (let i = 0; i < 60; i++) out[i] = seq[Math.floor(i / 2)];
|
|
42
|
+
return out;
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
/** 纳音五行(五局):海中金→金 … */
|
|
46
|
+
const NAYIN_WUXING = {
|
|
47
|
+
'海中金': '金', '炉中火': '火', '大林木': '木', '路旁土': '土', '剑锋金': '金', '山头火': '火',
|
|
48
|
+
'涧下水': '水', '城头土': '土', '白蜡金': '金', '杨柳木': '木', '泉中水': '水', '屋上土': '土',
|
|
49
|
+
'霹雳火': '火', '松柏木': '木', '长流水': '水', '沙中金': '金', '山下火': '火', '平地木': '木',
|
|
50
|
+
'壁上土': '土', '金箔金': '金', '覆灯火': '火', '天河水': '水', '大驿土': '土', '钗钏金': '金',
|
|
51
|
+
'桑柘木': '木', '大溪水': '水', '沙中土': '土', '天上火': '火', '石榴木': '木', '大海水': '水'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** 十二长生(天干×地支 → 12 态序),长生=0 起:长生,沐浴,冠带,临官,帝旺,衰,病,死,墓,绝,胎,养 */
|
|
55
|
+
const CHANGSHENG_NAMES = ['长生', '沐浴', '冠带', '临官', '帝旺', '衰', '病', '死', '墓', '绝', '胎', '养'];
|
|
56
|
+
/** 阳干顺行 / 阴干逆行,起点:甲长生于亥、乙长生于午 … */
|
|
57
|
+
const CHANGSHENG_START = {
|
|
58
|
+
'甲': { zhi: '亥', dir: 1 }, '丙': { zhi: '寅', dir: 1 }, '戊': { zhi: '寅', dir: 1 },
|
|
59
|
+
'庚': { zhi: '巳', dir: 1 }, '壬': { zhi: '申', dir: 1 },
|
|
60
|
+
'乙': { zhi: '午', dir: -1 }, '丁': { zhi: '酉', dir: -1 }, '己': { zhi: '酉', dir: -1 },
|
|
61
|
+
'辛': { zhi: '子', dir: -1 }, '癸': { zhi: '卯', dir: -1 }
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** 地支冲(六冲) */
|
|
65
|
+
const ZHI_CHONG = { '子': '午', '午': '子', '丑': '未', '未': '丑', '寅': '申', '申': '寅', '卯': '酉', '酉': '卯', '辰': '戌', '戌': '辰', '巳': '亥', '亥': '巳' };
|
|
66
|
+
|
|
67
|
+
/** 建除十二神(月支定序:寅月建寅起建) */
|
|
68
|
+
const JIANCHU = ['建', '除', '满', '平', '定', '执', '破', '危', '成', '收', '开', '闭'];
|
|
69
|
+
|
|
70
|
+
/** 旬空表:每旬(10 天)的空亡二支 */
|
|
71
|
+
const XUNKONG = (() => {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (let i = 0; i < 60; i += 10) {
|
|
74
|
+
const head = GANZHI[i];
|
|
75
|
+
const z1 = ZHI[(i + 10) % 12];
|
|
76
|
+
const z2 = ZHI[(i + 11) % 12];
|
|
77
|
+
out[head] = [z1, z2];
|
|
78
|
+
// 旬首 5 个干支同旬
|
|
79
|
+
for (let j = 1; j < 10; j++) out[GANZHI[i + j]] = [z1, z2];
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
// ═════════════════════════ 儒略日与干支 ═════════════════════════
|
|
85
|
+
|
|
86
|
+
/** 公历 → 儒略日(noon-based JDN,UTC 语境;date 为 {y,m,d} 整数日) */
|
|
87
|
+
export function julianDay(y, m, d) {
|
|
88
|
+
const a = Math.floor((14 - m) / 12);
|
|
89
|
+
const yy = y + 4800 - a;
|
|
90
|
+
const mm = m + 12 * a - 3;
|
|
91
|
+
let jdn = d + Math.floor((153 * mm + 2) / 5) + 365 * yy + Math.floor(yy / 4) - Math.floor(yy / 100) + Math.floor(yy / 400) - 32045;
|
|
92
|
+
return jdn; // noon-based integer JDN
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 儒略日 → 公历 {y,m,d}(整数日,UTC) */
|
|
96
|
+
export function julianDayToDate(jdn) {
|
|
97
|
+
let a = jdn + 32044;
|
|
98
|
+
const b = Math.floor((4 * a + 3) / 146097);
|
|
99
|
+
const c = a - Math.floor(146097 * b / 4);
|
|
100
|
+
const dd = Math.floor((4 * c + 3) / 1461);
|
|
101
|
+
const e = c - Math.floor(1461 * dd / 4);
|
|
102
|
+
const mm = Math.floor((5 * e + 2) / 153);
|
|
103
|
+
const day = e - Math.floor((153 * mm + 2) / 5) + 1;
|
|
104
|
+
const month = mm + 3 - 12 * Math.floor(mm / 10);
|
|
105
|
+
const year = 100 * b + dd - 4800 + Math.floor(mm / 10);
|
|
106
|
+
return { y: year, m: month, d: day };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 儒略日 → 干支日名(锚点:2024-01-01 = 甲子,JDN=2460311) */
|
|
110
|
+
export function ganzhiDay(jdn) {
|
|
111
|
+
const idx = ((jdn - 2460311) % 60 + 60) % 60;
|
|
112
|
+
return GANZHI[idx];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 公历日期 → 干支日名 */
|
|
116
|
+
export function ganzhiDayOfDate(y, m, d) {
|
|
117
|
+
return ganzhiDay(julianDay(y, m, d));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 干支序号(0-59) */
|
|
121
|
+
export function ganzhiIndex(name) {
|
|
122
|
+
return GANZHI.indexOf(name);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function dayXunKong(ganzhiName) {
|
|
126
|
+
return XUNKONG[ganzhiName] || [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ═════════════════════════ 节气(太阳黄经)════════════════════════
|
|
130
|
+
|
|
131
|
+
const DEG = Math.PI / 180;
|
|
132
|
+
|
|
133
|
+
/** 太阳视黄经(Meeus 25.2 平黄经 + 6 项中心差[含月球摄动] + 章动前 4 项 + 光行差 + ΔT) */
|
|
134
|
+
export function sunApparentLongitude(jdeTT) {
|
|
135
|
+
const T = (jdeTT - 2451545.0) / 36525;
|
|
136
|
+
const L0 = 280.46646 + 36000.76983 * T + 0.0003032 * T * T;
|
|
137
|
+
const M = 357.52911 + 35999.05029 * T - 0.0001537 * T * T;
|
|
138
|
+
const Mp = 134.96298139 + 477198.8673981 * T; // 月球平近点角
|
|
139
|
+
const Mr = M * DEG, Mpr = Mp * DEG;
|
|
140
|
+
const C =
|
|
141
|
+
(1.914602 - 0.004817 * T - 0.000014 * T * T) * Math.sin(Mr)
|
|
142
|
+
+ (0.019993 - 0.000101 * T) * Math.sin(2 * Mr)
|
|
143
|
+
+ 0.000289 * Math.sin(3 * Mr)
|
|
144
|
+
+ (0.000025 - 0.000002 * T) * Math.sin(Mpr)
|
|
145
|
+
+ 0.000006 * Math.sin(2 * Mpr - Mr)
|
|
146
|
+
+ 0.000003 * Math.sin(3 * Mpr);
|
|
147
|
+
const geometric = L0 + C;
|
|
148
|
+
// 章动经度主项(前 4 项)+ 光行差 -0.00569
|
|
149
|
+
const omega = 125.04452 - 1934.136261 * T + 0.0020708 * T * T + (T * T * T) / 450000;
|
|
150
|
+
const omegaR = omega * DEG;
|
|
151
|
+
const lSun = geometric * DEG;
|
|
152
|
+
const dPsi =
|
|
153
|
+
-0.00478 * Math.sin(omegaR)
|
|
154
|
+
- 0.0004 * Math.sin(2 * omegaR)
|
|
155
|
+
+ 0.0003 * Math.sin(2 * lSun)
|
|
156
|
+
- 0.0001 * Math.sin(3 * lSun);
|
|
157
|
+
const apparent = geometric - 0.00569 + dPsi;
|
|
158
|
+
return ((apparent % 360) + 360) % 360;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** ΔT(TT−UT,秒):Espenak & Meeus 2006 近似,适用 2005-2050 */
|
|
162
|
+
export function deltaTSeconds(year) {
|
|
163
|
+
const t = year - 2000;
|
|
164
|
+
return 62.92 + 0.32217 * t + 0.005589 * t * t;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** 求太阳黄经到达 targetLong 的最近儒略日(二分法,返回 JDE 时刻) */
|
|
168
|
+
export function sunLongitudeTime(targetLong, guessJde) {
|
|
169
|
+
const target = ((targetLong % 360) + 360) % 360;
|
|
170
|
+
let lo = guessJde - 20;
|
|
171
|
+
let hi = guessJde + 20;
|
|
172
|
+
const f = (jde) => {
|
|
173
|
+
let d = sunApparentLongitude(jde) - target;
|
|
174
|
+
if (d > 180) d -= 360;
|
|
175
|
+
if (d < -180) d += 360;
|
|
176
|
+
return d;
|
|
177
|
+
};
|
|
178
|
+
for (let i = 0; i < 60; i++) {
|
|
179
|
+
const mid = (lo + hi) / 2;
|
|
180
|
+
if (f(mid) * f(lo) <= 0) hi = mid; else lo = mid;
|
|
181
|
+
if (hi - lo < 1e-7) break; // ~0.0086 秒
|
|
182
|
+
}
|
|
183
|
+
return (lo + hi) / 2;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** JDE(TT)→ 北京时间 Date 对象(TT 减 ΔT 得 UT,再 +8h) */
|
|
187
|
+
function jdeToBeijingDate(jdeTT) {
|
|
188
|
+
const y = Math.floor(2000 + (jdeTT - 2451545.0) / 365.25);
|
|
189
|
+
const ms = (jdeTT - deltaTSeconds(y) / 86400 - 2440587.5) * 86400000;
|
|
190
|
+
return new Date(ms + 8 * 3600000); // +8h → 北京时间
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 计算某公历年全部 24 节气时刻(北京时间,精确到秒)
|
|
195
|
+
* 以该年冬至(黄经 270°)为锚,前后扩展覆盖全年,过滤出公历年份 == year 的 24 个。
|
|
196
|
+
* 计算核心:寿星天文历公版公式(VSOP87 太阳黄经精确求根,秒级精度)。
|
|
197
|
+
* @returns {Array<{name, solar, jde}>} solar = 'YYYY-MM-DD HH:mm:ss'(北京时间)
|
|
198
|
+
*/
|
|
199
|
+
export function jieqiOfYear(year) {
|
|
200
|
+
const out = [];
|
|
201
|
+
// 寿星 qiAccurate2 输入输出均为 J2000 偏移天数(JD - 2451545)
|
|
202
|
+
// 锚:该年立春(2月初,粗猜偏差 <15° 内,一次收敛)
|
|
203
|
+
const lichun = ShouXingUtil.qiAccurate2(julianDay(year, 2, 4) - 2451545);
|
|
204
|
+
// 立春起:向前 2 个(小寒/大寒属该公历年 1 月)、向后 23 个覆盖至年末
|
|
205
|
+
for (let off = -2; off <= 23; off++) {
|
|
206
|
+
const idx = (((off % 24) + 24) % 24);
|
|
207
|
+
const offJd = ShouXingUtil.qiAccurate2(lichun + 15.2184 * off);
|
|
208
|
+
const jdeFull = offJd + 2451545;
|
|
209
|
+
// 寿星 JD 为北京时间语义(+1/3 天基准),直接按 UTC 转换即得北京时间
|
|
210
|
+
const dt = new Date((jdeFull - 2440587.5) * 86400000);
|
|
211
|
+
if (dt.getUTCFullYear() !== year) continue;
|
|
212
|
+
out.push({
|
|
213
|
+
name: JIE_ORDER[idx],
|
|
214
|
+
solar: `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, '0')}-${String(dt.getUTCDate()).padStart(2, '0')} ${String(dt.getUTCHours()).padStart(2, '0')}:${String(dt.getUTCMinutes()).padStart(2, '0')}:${String(dt.getUTCSeconds()).padStart(2, '0')}`,
|
|
215
|
+
jde: +jdeFull.toFixed(6)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
out.sort((a, b) => a.solar.localeCompare(b.solar)); // 时间序(公历年内 1 月的小寒/大寒在前)
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** 给定日期(北京)→ 最近的节气(前/后) */
|
|
223
|
+
export function nearJieqi(y, m, d, hour = 0, minute = 0) {
|
|
224
|
+
const ts = Date.UTC(y, m - 1, d, hour - 8, minute); // 北京 → UTC ms
|
|
225
|
+
const all = [];
|
|
226
|
+
for (const yy of [y - 1, y, y + 1]) all.push(...jieqiOfYear(yy));
|
|
227
|
+
let prev = null, next = null;
|
|
228
|
+
for (const j of all) {
|
|
229
|
+
const jt = Date.parse(j.solar.replace(' ', 'T') + '+08:00');
|
|
230
|
+
if (jt <= ts) prev = j; else { next = j; break; }
|
|
231
|
+
}
|
|
232
|
+
return { prev, next };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ═════════════════════════ 表驱动(旬空/月破/长生/纳音/神煞)════════════════════════
|
|
236
|
+
|
|
237
|
+
/** 月破:与月建(月支)相冲的地支 */
|
|
238
|
+
export function monthBreak(monthZhi) {
|
|
239
|
+
return ZHI_CHONG[monthZhi];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** 十二长生:干×支 → 12 态名 */
|
|
243
|
+
export function changsheng(gan, zhi) {
|
|
244
|
+
const cfg = CHANGSHENG_START[gan];
|
|
245
|
+
if (!cfg) return null;
|
|
246
|
+
const startIdx = ZHI.indexOf(cfg.zhi);
|
|
247
|
+
const zhiIdx = ZHI.indexOf(zhi);
|
|
248
|
+
let delta = (zhiIdx - startIdx + 12) % 12;
|
|
249
|
+
if (cfg.dir === -1) delta = (startIdx - zhiIdx + 12) % 12;
|
|
250
|
+
return CHANGSHENG_NAMES[delta % 12];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** 六十甲子纳音 */
|
|
254
|
+
export function nayin(ganzhiName) {
|
|
255
|
+
const i = GANZHI.indexOf(ganzhiName);
|
|
256
|
+
if (i < 0) return null;
|
|
257
|
+
return { name: NAYIN[i], wuxing: NAYIN_WUXING[NAYIN[i]] };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** 建除十二神:由月支与日支定(月支建月起建) */
|
|
261
|
+
export function jianchu(monthZhi, dayZhi) {
|
|
262
|
+
const mi = ZHI.indexOf(monthZhi);
|
|
263
|
+
const di = ZHI.indexOf(dayZhi);
|
|
264
|
+
if (mi < 0 || di < 0) return null;
|
|
265
|
+
// 建除起于月建(寅月起建):(日支 - 月支 + 12) % 12
|
|
266
|
+
const idx = ((di - mi) % 12 + 12) % 12;
|
|
267
|
+
return JIANCHU[idx];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** 十二地支合(六合) */
|
|
271
|
+
export const ZHI_HE = { '子': '丑', '丑': '子', '寅': '亥', '亥': '寅', '卯': '戌', '戌': '卯', '辰': '酉', '酉': '辰', '巳': '申', '申': '巳', '午': '未', '未': '午' };
|
|
272
|
+
/** 三合局 */
|
|
273
|
+
export const ZHI_SANHE = { '申子辰': '水', '寅午戌': '火', '巳酉丑': '金', '亥卯未': '木' };
|
|
274
|
+
/** 地支刑 */
|
|
275
|
+
export const ZHI_XING = { '寅': '巳', '巳': '申', '申': '寅', '丑': '戌', '戌': '未', '未': '丑', '子': '卯', '卯': '子', '辰': '辰', '午': '午', '酉': '酉', '亥': '亥' };
|
|
276
|
+
/** 地支害 */
|
|
277
|
+
export const ZHI_HAI = { '子': '未', '未': '子', '丑': '午', '午': '丑', '寅': '巳', '巳': '寅', '卯': '辰', '辰': '卯', '申': '亥', '亥': '申', '酉': '戌', '戌': '酉' };
|
|
278
|
+
|
|
279
|
+
// ═════════════════════════ 农历(朔望月构造 + 无中气置闰)════════════════════════
|
|
280
|
+
|
|
281
|
+
/** 农历月名(1=正月 … 11=冬月/十一月, 12=腊月/十二月) */
|
|
282
|
+
export const LUNAR_MONTH_NAMES = ['', '正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
|
|
283
|
+
export const LUNAR_DAY_NAMES = ['', '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
|
|
284
|
+
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
|
|
285
|
+
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'];
|
|
286
|
+
|
|
287
|
+
/** 闰冬月(闰十一月)特例农历年表(寿星公版数据) */
|
|
288
|
+
const LEAP_11 = [75, 94, 170, 265, 322, 398, 469, 553, 583, 610, 678, 735, 754, 773, 849, 887, 936, 1050, 1069, 1126, 1145, 1164, 1183, 1259, 1278, 1308, 1373, 1403, 1441, 1460, 1498, 1555, 1593, 1612, 1631, 1642, 2033, 2128, 2147, 2242, 2614, 2728, 2910, 3062, 3244, 3339, 3616, 3711, 3730, 3825, 4007, 4159, 4197, 4322, 4341, 4379, 4417, 4531, 4599, 4694, 4713, 4789, 4808, 4971, 5085, 5104, 5161, 5180, 5199, 5294, 5305, 5476, 5677, 5696, 5772, 5791, 5848, 5886, 6049, 6068, 6144, 6163, 6258, 6402, 6440, 6497, 6516, 6630, 6641, 6660, 6679, 6736, 6774, 6850, 6869, 6899, 6918, 6994, 7013, 7032, 7051, 7070, 7089, 7108, 7127, 7146, 7222, 7271, 7290, 7309, 7366, 7385, 7404, 7442, 7461, 7480, 7491, 7499, 7594, 7624, 7643, 7662, 7681, 7719, 7738, 7814, 7863, 7882, 7901, 7939, 7958, 7977, 7996, 8034, 8053, 8072, 8091, 8121, 8159, 8186, 8216, 8235, 8254, 8273, 8311, 8330, 8341, 8349, 8368, 8444, 8463, 8474, 8493, 8531, 8569, 8588, 8626, 8664, 8683, 8694, 8702, 8713, 8721, 8751, 8789, 8808, 8816, 8827, 8846, 8884, 8903, 8922, 8941, 8971, 9036, 9066, 9085, 9104, 9123, 9142, 9161, 9180, 9199, 9218, 9256, 9294, 9313, 9324, 9343, 9362, 9381, 9419, 9438, 9476, 9514, 9533, 9544, 9552, 9563, 9571, 9582, 9601, 9639, 9658, 9666, 9677, 9696, 9734, 9753, 9772, 9791, 9802, 9821, 9886, 9897, 9916, 9935, 9954, 9973, 9992];
|
|
289
|
+
/** 闰腊月(闰十二月)特例农历年表(寿星公版数据) */
|
|
290
|
+
const LEAP_12 = [37, 56, 113, 132, 151, 189, 208, 227, 246, 284, 303, 341, 360, 379, 417, 436, 458, 477, 496, 515, 534, 572, 591, 629, 648, 667, 697, 716, 792, 811, 830, 868, 906, 925, 944, 963, 982, 1001, 1020, 1039, 1058, 1088, 1153, 1202, 1221, 1240, 1297, 1335, 1392, 1411, 1422, 1430, 1517, 1525, 1536, 1574, 3358, 3472, 3806, 3988, 4751, 4941, 5066, 5123, 5275, 5343, 5438, 5457, 5495, 5533, 5552, 5715, 5810, 5829, 5905, 5924, 6421, 6535, 6793, 6812, 6888, 6907, 7002, 7184, 7260, 7279, 7374, 7556, 7746, 7757, 7776, 7833, 7852, 7871, 7966, 8015, 8110, 8129, 8148, 8224, 8243, 8338, 8406, 8425, 8482, 8501, 8520, 8558, 8596, 8607, 8615, 8645, 8740, 8778, 8835, 8865, 8930, 8960, 8979, 8998, 9017, 9055, 9074, 9093, 9112, 9150, 9188, 9237, 9275, 9332, 9351, 9370, 9408, 9427, 9446, 9457, 9465, 9495, 9560, 9590, 9628, 9647, 9685, 9715, 9742, 9780, 9810, 9818, 9829, 9848, 9867, 9905, 9924, 9943, 9962, 10000];
|
|
291
|
+
const _YMC = [11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
292
|
+
const _inLeap = (arr, n) => arr.includes(n);
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 构造农历年 lunarYear 的月份表(覆盖 lunarYear 冬月 → lunarYear+1 十月),
|
|
296
|
+
* 算法 = 寿星公版(整日粒度朔/节气 + 闰冬腊特例表 + 无中气置闰)。
|
|
297
|
+
* @returns {Array<{shuo, month, isLeap}>} shuo 为 J2000 偏移整日
|
|
298
|
+
*/
|
|
299
|
+
function buildLunarYearMonths(lunarYear) {
|
|
300
|
+
const jd0 = Math.floor((lunarYear - 2000) * 365.2422 + 180);
|
|
301
|
+
let w = Math.floor((jd0 - 355 + 183) / 365.2422) * 365.2422 + 355;
|
|
302
|
+
if (ShouXingUtil.calcQi(w) > jd0) w -= 365.2422;
|
|
303
|
+
const jq = [];
|
|
304
|
+
for (let i = 0; i < 26; i++) jq.push(ShouXingUtil.calcQi(w + 15.2184 * i));
|
|
305
|
+
let w2 = ShouXingUtil.calcShuo(jq[0]);
|
|
306
|
+
if (w2 > jq[0]) w2 -= 29.53;
|
|
307
|
+
const hs = [];
|
|
308
|
+
for (let i = 0; i < 16; i++) hs.push(ShouXingUtil.calcShuo(w2 + 29.5306 * i));
|
|
309
|
+
const months = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
|
|
310
|
+
let leapIndex = 16;
|
|
311
|
+
if (_inLeap(LEAP_11, lunarYear)) leapIndex = 13;
|
|
312
|
+
else if (_inLeap(LEAP_12, lunarYear)) leapIndex = 14;
|
|
313
|
+
else if (hs[13] <= jq[24]) {
|
|
314
|
+
let i = 1;
|
|
315
|
+
while (hs[i + 1] > jq[2 * i] && i < 13) i++;
|
|
316
|
+
leapIndex = i;
|
|
317
|
+
}
|
|
318
|
+
for (let j = leapIndex; j < 15; j++) months[j] -= 1;
|
|
319
|
+
const out = [];
|
|
320
|
+
for (let i = 0; i < 15; i++) {
|
|
321
|
+
out.push({ shuo: hs[i], month: _YMC[((months[i] % 12) + 12) % 12], isLeap: i === leapIndex });
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 公历日期 → 农历信息(寿星公式:朔望月 + 特例表/无中气置闰)
|
|
328
|
+
* @returns {lunarYear, lunarMonth, lunarDay, isLeap, monthName, dayName, ganzhiYear, ganzhiMonth, ganzhiDay, shengxiao, nayinDay, nayinWuxing}
|
|
329
|
+
*/
|
|
330
|
+
export function lunarOfSolarDate(y, m, d, hour = 12) {
|
|
331
|
+
const jdn = julianDay(y, m, d);
|
|
332
|
+
const offset = jdn - 2451545;
|
|
333
|
+
// 表键 = 立春年(立春前退一年,与年干支同源)
|
|
334
|
+
let baseYear = y;
|
|
335
|
+
const lichun = jieqiOfYear(y).find(j => j.name === '立春');
|
|
336
|
+
if (lichun) {
|
|
337
|
+
const lcTs = Date.parse(lichun.solar.replace(' ', 'T') + '+08:00');
|
|
338
|
+
const curTs = Date.UTC(y, m - 1, d, hour - 8);
|
|
339
|
+
if (curTs < lcTs) baseYear = y - 1;
|
|
340
|
+
}
|
|
341
|
+
let tableKey = baseYear;
|
|
342
|
+
let table = buildLunarYearMonths(tableKey);
|
|
343
|
+
if (offset < table[0].shuo) { tableKey -= 1; table = buildLunarYearMonths(tableKey); }
|
|
344
|
+
let idx = -1;
|
|
345
|
+
for (let i = 0; i < table.length; i++) {
|
|
346
|
+
const start = table[i].shuo;
|
|
347
|
+
const end = i + 1 < table.length ? table[i + 1].shuo : start + 30;
|
|
348
|
+
if (offset >= start && offset < end) { idx = i; break; }
|
|
349
|
+
}
|
|
350
|
+
if (idx < 0) return null;
|
|
351
|
+
const mon = table[idx];
|
|
352
|
+
const lunarDay = Math.floor(offset - mon.shuo) + 1;
|
|
353
|
+
// 农历年号:以表内第一个正月为界——正月及之后属表键年;正月前(冬/腊)属表键年-1
|
|
354
|
+
const zhengIdx = table.findIndex(mm => mm.month === 1 && !mm.isLeap);
|
|
355
|
+
const lunarYear = (zhengIdx >= 0 && idx >= zhengIdx) ? tableKey : tableKey - 1;
|
|
356
|
+
const dayGz = ganzhiDay(jdn);
|
|
357
|
+
const gzIdx = GANZHI.indexOf(dayGz);
|
|
358
|
+
const nai = NAYIN[gzIdx] || '';
|
|
359
|
+
const yearGanZhi = ganzhiYearOf(y, m, d, hour);
|
|
360
|
+
return {
|
|
361
|
+
lunarYear,
|
|
362
|
+
lunarMonth: mon.month,
|
|
363
|
+
lunarDay,
|
|
364
|
+
isLeap: mon.isLeap,
|
|
365
|
+
monthName: (mon.isLeap ? '闰' : '') + LUNAR_MONTH_NAMES[mon.month] + '月',
|
|
366
|
+
dayName: LUNAR_DAY_NAMES[lunarDay] || String(lunarDay),
|
|
367
|
+
ganzhiYear: yearGanZhi,
|
|
368
|
+
ganzhiMonth: ganzhiMonthOf(yearGanZhi, y, m, d, hour),
|
|
369
|
+
ganzhiDay: dayGz,
|
|
370
|
+
shengxiao: ZHI[(((GANZHI.indexOf(yearGanZhi) % 12) + 12) % 12)],
|
|
371
|
+
nayinDay: nai,
|
|
372
|
+
nayinWuxing: NAYIN_WUXING[nai] || ''
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** 年干支(立春为界):(年-4) mod 60,立春前退一年 */
|
|
377
|
+
export function ganzhiYearOf(y, m, d, hour = 12) {
|
|
378
|
+
let yy = y;
|
|
379
|
+
const lichun = jieqiOfYear(y).find(j => j.name === '立春');
|
|
380
|
+
if (lichun) {
|
|
381
|
+
const lcTs = Date.parse(lichun.solar.replace(' ', 'T') + '+08:00');
|
|
382
|
+
const curTs = Date.UTC(y, m - 1, d, hour - 8);
|
|
383
|
+
if (curTs < lcTs) yy = y - 1;
|
|
384
|
+
}
|
|
385
|
+
return GANZHI[((((yy - 4) % 60) + 60) % 60)];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** 月干支(节气月:立春起寅月;五虎遁:甲己丙作首,乙庚戊为头,丙辛寻庚起,丁壬壬位流,戊癸甲寅求) */
|
|
389
|
+
export function ganzhiMonthOf(yearGanZhi, y, m, d, hour = 12) {
|
|
390
|
+
const ygan = GANZHI.indexOf(yearGanZhi) % 10;
|
|
391
|
+
// 定月支:立春→寅(2),每节气+1
|
|
392
|
+
let zhiIdx = 2;
|
|
393
|
+
for (const j of jieqiOfYear(y)) {
|
|
394
|
+
const jt = Date.parse(j.solar.replace(' ', 'T') + '+08:00');
|
|
395
|
+
const curTs = Date.UTC(y, m - 1, d, hour - 8);
|
|
396
|
+
if (curTs >= jt) {
|
|
397
|
+
const order = JIE_ORDER.indexOf(j.name);
|
|
398
|
+
if (order >= 0 && order % 2 === 0) zhiIdx = (order / 2 + 2) % 12; // 节(偶数序):立春→寅 惊蛰→卯 …
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const WUHU_START = { 0: 2, 1: 4, 2: 6, 3: 8, 4: 0 }; // 甲己→丙(2), 乙庚→戊(4), 丙辛→庚(6), 丁壬→壬(8), 戊癸→甲(0)
|
|
402
|
+
const startGan = WUHU_START[ygan % 5];
|
|
403
|
+
const ganIdx = (startGan + (((zhiIdx - 2) % 12) + 12) % 12) % 10;
|
|
404
|
+
return GAN[ganIdx] + ZHI[zhiIdx];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** 时干支(五鼠遁:甲己还加甲,乙庚丙作初,丙辛从戊起,丁壬庚子居,戊癸壬子头) */
|
|
408
|
+
export function ganzhiHourOf(dayGanZhi, hour) {
|
|
409
|
+
const dgan = GANZHI.indexOf(dayGanZhi) % 10;
|
|
410
|
+
const WUSHU_START = { 0: 0, 1: 2, 2: 4, 3: 6, 4: 8 };
|
|
411
|
+
const zhiIdx = Math.floor(((hour + 1) % 24) / 2) % 12;
|
|
412
|
+
const ganIdx = (WUSHU_START[dgan % 5] + zhiIdx) % 10;
|
|
413
|
+
return GAN[ganIdx] + ZHI[zhiIdx];
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export { GAN, ZHI, GANZHI, JIE_ORDER, NAYIN, NAYIN_WUXING, CHANGSHENG_NAMES };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 易理工程学 · 四柱八字引擎(P3)
|
|
3
|
+
* 四柱排盘 / 十神 / 大运(阳男阴女顺行)/ 流年 / 十二长生旺衰
|
|
4
|
+
* 红线:不做命运论断,只做时间结构标注与周期框架
|
|
5
|
+
*/
|
|
6
|
+
import { GAN, ZHI, GANZHI, ganzhiYearOf, ganzhiMonthOf, ganzhiDayOfDate, ganzhiHourOf, changsheng } from './almanac.js';
|
|
7
|
+
import { GAN_WUXING } from './wuxing.js';
|
|
8
|
+
|
|
9
|
+
/** 十神(日干 × 他干):比肩/劫财/食神/伤官/偏财/正财/七杀/正官/偏印/正印 */
|
|
10
|
+
export function shishen(dayGan, otherGan) {
|
|
11
|
+
if (dayGan === otherGan) return '比肩';
|
|
12
|
+
const d = GAN.indexOf(dayGan), o = GAN.indexOf(otherGan);
|
|
13
|
+
const dYang = d % 2 === 0, oYang = o % 2 === 0;
|
|
14
|
+
const dWx = GAN_WUXING[dayGan], oWx = GAN_WUXING[otherGan];
|
|
15
|
+
const rel = dWx === oWx ? '比' : dWx === '木' ? (oWx === '火' ? '生' : oWx === '土' ? '克' : oWx === '金' ? '被克' : '被生') : null;
|
|
16
|
+
const r = wuxingRel(dWx, oWx);
|
|
17
|
+
if (r === '比') return dYang === oYang ? '比肩' : '劫财';
|
|
18
|
+
if (r === '生') return dYang === oYang ? '食神' : '伤官'; // 我生
|
|
19
|
+
if (r === '被生') return dYang === oYang ? '偏印' : '正印'; // 生我
|
|
20
|
+
if (r === '克') return dYang === oYang ? '偏财' : '正财'; // 我克
|
|
21
|
+
return dYang === oYang ? '七杀' : '正官'; // 克我
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function wuxingRel(a, b) {
|
|
25
|
+
const s = { '木': '火', '火': '土', '土': '金', '金': '水', '水': '木' };
|
|
26
|
+
const k = { '木': '土', '土': '水', '水': '火', '火': '金', '金': '木' };
|
|
27
|
+
if (a === b) return '比';
|
|
28
|
+
if (s[a] === b) return '生';
|
|
29
|
+
if (k[a] === b) return '克';
|
|
30
|
+
if (s[b] === a) return '被生';
|
|
31
|
+
return '被克';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 藏干(十二支主气):子癸 丑己癸辛 寅甲丙戊 卯乙 辰戊乙癸 巳丙戊庚 午丁己 未己丁乙 申庚壬戊 酉辛 戌戊辛丁 亥壬甲 */
|
|
35
|
+
export const CANG_GAN = {
|
|
36
|
+
'子': ['癸'], '丑': ['己', '癸', '辛'], '寅': ['甲', '丙', '戊'], '卯': ['乙'],
|
|
37
|
+
'辰': ['戊', '乙', '癸'], '巳': ['丙', '戊', '庚'], '午': ['丁', '己'], '未': ['己', '丁', '乙'],
|
|
38
|
+
'申': ['庚', '壬', '戊'], '酉': ['辛'], '戌': ['戊', '辛', '丁'], '亥': ['壬', '甲']
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** 地支五行(主气) */
|
|
42
|
+
const ZHI_WX = { '子': '水', '丑': '土', '寅': '木', '卯': '木', '辰': '土', '巳': '火', '午': '火', '未': '土', '申': '金', '酉': '金', '戌': '土', '亥': '水' };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 四柱排盘
|
|
46
|
+
* @returns {year, month, day, hour, pillars{...}, shishen{}, dayMaster, dayMasterWuxing, cangGan{}, naYin{}}
|
|
47
|
+
*/
|
|
48
|
+
export function baziCalc(y, m, d, hour) {
|
|
49
|
+
const dayGz = ganzhiDayOfDate(y, m, d);
|
|
50
|
+
const yearGz = ganzhiYearOf(y, m, d, hour);
|
|
51
|
+
const monthGz = ganzhiMonthOf(yearGz, y, m, d, hour);
|
|
52
|
+
const hourGz = ganzhiHourOf(dayGz, hour);
|
|
53
|
+
const dayMaster = dayGz[0];
|
|
54
|
+
const dmWx = GAN_WUXING[dayMaster];
|
|
55
|
+
const pillars = {
|
|
56
|
+
year: { ganZhi: yearGz, gan: yearGz[0], zhi: yearGz[1], wuxing: GAN_WUXING[yearGz[0]] },
|
|
57
|
+
month: { ganZhi: monthGz, gan: monthGz[0], zhi: monthGz[1], wuxing: GAN_WUXING[monthGz[0]] },
|
|
58
|
+
day: { ganZhi: dayGz, gan: dayGz[0], zhi: dayGz[1], wuxing: GAN_WUXING[dayGz[0]] },
|
|
59
|
+
hour: { ganZhi: hourGz, gan: hourGz[0], zhi: hourGz[1], wuxing: GAN_WUXING[hourGz[0]] }
|
|
60
|
+
};
|
|
61
|
+
const shishenMap = {};
|
|
62
|
+
for (const [k, p] of Object.entries(pillars)) {
|
|
63
|
+
shishenMap[k + 'Gan'] = shishen(dayMaster, p.gan);
|
|
64
|
+
shishenMap[k + 'ZhiMain'] = shishen(dayMaster, CANG_GAN[p.zhi][0]);
|
|
65
|
+
}
|
|
66
|
+
const cangGan = {};
|
|
67
|
+
for (const [k, p] of Object.entries(pillars)) {
|
|
68
|
+
cangGan[k] = CANG_GAN[p.zhi].map(g => ({ gan: g, shishen: shishen(dayMaster, g) }));
|
|
69
|
+
}
|
|
70
|
+
const naYin = {};
|
|
71
|
+
for (const [k, p] of Object.entries(pillars)) {
|
|
72
|
+
const idx = GANZHI.indexOf(p.ganZhi);
|
|
73
|
+
const nai = ['海中金', '炉中火', '大林木', '路旁土', '剑锋金', '山头火', '涧下水', '城头土', '白蜡金', '杨柳木',
|
|
74
|
+
'泉中水', '屋上土', '霹雳火', '松柏木', '长流水', '沙中金', '山下火', '平地木', '壁上土', '金箔金',
|
|
75
|
+
'覆灯火', '天河水', '大驿土', '钗钏金', '桑柘木', '大溪水', '沙中土', '天上火', '石榴木', '大海水'][Math.floor(idx / 2)];
|
|
76
|
+
naYin[k] = nai;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
y, m, d, hour,
|
|
80
|
+
pillars,
|
|
81
|
+
dayMaster,
|
|
82
|
+
dayMasterWuxing: dmWx,
|
|
83
|
+
shishenMap,
|
|
84
|
+
cangGan,
|
|
85
|
+
naYin,
|
|
86
|
+
wuxingBalance: balanceOf(pillars)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 五行力量粗算(天干 1 + 地支主气 1 + 藏干 0.5) */
|
|
91
|
+
export function balanceOf(pillars) {
|
|
92
|
+
const score = { '木': 0, '火': 0, '土': 0, '金': 0, '水': 0 };
|
|
93
|
+
for (const [k, p] of Object.entries(pillars)) {
|
|
94
|
+
score[p.wuxing] += 1;
|
|
95
|
+
score[ZHI_WX[p.zhi]] += 1;
|
|
96
|
+
const cg = CANG_GAN[p.zhi];
|
|
97
|
+
for (let i = 1; i < cg.length; i++) score[GAN_WUXING[cg[i]]] += 0.3;
|
|
98
|
+
}
|
|
99
|
+
return score;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 大运排法:阳男阴女顺行、阴男阳女逆行;从月柱起排,每运一柱(10 年)
|
|
104
|
+
* @returns {forward, list[], gender, yearGan, monthGanZhi}
|
|
105
|
+
*/
|
|
106
|
+
export function dayunCalc(y, m, d, hour, gender) {
|
|
107
|
+
const yearGz = ganzhiYearOf(y, m, d, hour);
|
|
108
|
+
const yearGan = yearGz[0];
|
|
109
|
+
const ganYang = GAN.indexOf(yearGan) % 2 === 0;
|
|
110
|
+
const forward = (ganYang && gender === '男') || (!ganYang && gender === '女');
|
|
111
|
+
const monthGz = ganzhiMonthOf(yearGz, y, m, d, hour);
|
|
112
|
+
const monthIdx = GANZHI.indexOf(monthGz);
|
|
113
|
+
const step = forward ? 1 : -1;
|
|
114
|
+
const list = [];
|
|
115
|
+
for (let i = 1; i <= 8; i++) {
|
|
116
|
+
const idx = (((monthIdx + step * i) % 60) + 60) % 60;
|
|
117
|
+
list.push({ ganZhi: GANZHI[idx], index: i });
|
|
118
|
+
}
|
|
119
|
+
return { forward, list, gender, yearGan, monthGanZhi: monthGz };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 流年:起运年后每年干支(简化:年龄 → 干支) */
|
|
123
|
+
export function liunianCalc(age) {
|
|
124
|
+
// 以 2024 甲辰为锚:流年干支 = (2024 + age - 1) 年干支(立春界简化)
|
|
125
|
+
const idx = (((2024 - 4 + age - 1) % 60) + 60) % 60;
|
|
126
|
+
return GANZHI[idx];
|
|
127
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 易理工程学 · 象数诊断引擎(P4)
|
|
3
|
+
* 组织/要素五行映射 → 生克回路分析 → 断"穴位"(最小干预杠杆点)
|
|
4
|
+
*/
|
|
5
|
+
import { relation, wangxiangOfSeason, circuitAnalysis } from './wuxing.js';
|
|
6
|
+
import { ZHI, GAN } from './almanac.js';
|
|
7
|
+
|
|
8
|
+
/** 要素五行判定(关键词 → 五行,基于五行属性表) */
|
|
9
|
+
const KEYWORD_WX = {
|
|
10
|
+
'木': ['教育', '文化', '出版', '纺织', '农林', '培训', '文案', '设计', '成长', '创新'],
|
|
11
|
+
'火': ['市场', '营销', '品牌', '传媒', '公关', '视觉', '热情', '传播', '餐饮', '能源'],
|
|
12
|
+
'土': ['管理', '运营', '人力', '财务', '合规', '基建', '仓储', '稳定', '协调', '地产'],
|
|
13
|
+
'金': ['销售', '商务', '法务', '风控', '机械', '金融', '审计', '纪律', '执行', '制造'],
|
|
14
|
+
'水': ['研发', '数据', '物流', '调研', '情报', '流动', '创新', '知识', '渠道', '客户']
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function detectWuxing(name) {
|
|
18
|
+
for (const [wx, keys] of Object.entries(KEYWORD_WX)) {
|
|
19
|
+
if (keys.some(k => name.includes(k))) return wx;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 象数诊断:要素集 → 五行映射 → 生克回路 → 杠杆点
|
|
26
|
+
* @param {Array<{name, weight?}>} elements 组织要素
|
|
27
|
+
* @param {string} season 季节五行(旺相休囚死)
|
|
28
|
+
*/
|
|
29
|
+
export function shushuDiagnose(elements, season) {
|
|
30
|
+
const mapped = elements.map(e => ({
|
|
31
|
+
name: e.name,
|
|
32
|
+
wuxing: detectWuxing(e.name) || '土', // 未识别默认土(中枢)
|
|
33
|
+
weight: e.weight || 1
|
|
34
|
+
}));
|
|
35
|
+
const ws = wangxiangOfSeason(season);
|
|
36
|
+
const analysis = [];
|
|
37
|
+
for (const el of mapped) {
|
|
38
|
+
const rels = {};
|
|
39
|
+
for (const other of mapped) {
|
|
40
|
+
if (other === el) continue;
|
|
41
|
+
rels[other.name] = relation(el.wuxing, other.wuxing);
|
|
42
|
+
}
|
|
43
|
+
analysis.push({
|
|
44
|
+
name: el.name,
|
|
45
|
+
wuxing: el.wuxing,
|
|
46
|
+
seasonState: ws[el.wuxing].label,
|
|
47
|
+
relations: rels
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// 杠杆点(最小干预穴位):被克最多且权重高的要素(调整它影响面最大)
|
|
51
|
+
const leverage = mapped.map(el => {
|
|
52
|
+
const beKe = mapped.filter(o => o !== el && relation(o.wuxing, el.wuxing) === '克').length;
|
|
53
|
+
const sheng = mapped.filter(o => o !== el && relation(el.wuxing, o.wuxing) === '生').length;
|
|
54
|
+
return {
|
|
55
|
+
name: el.name,
|
|
56
|
+
wuxing: el.wuxing,
|
|
57
|
+
被克数: beKe,
|
|
58
|
+
生数: sheng,
|
|
59
|
+
score: beKe * 2 + sheng + el.weight
|
|
60
|
+
};
|
|
61
|
+
}).sort((a, b) => b.score - a.score);
|
|
62
|
+
const loops = circuitAnalysis(mapped.map(e => e.wuxing));
|
|
63
|
+
return {
|
|
64
|
+
elements: analysis,
|
|
65
|
+
leverage: leverage[0] || null,
|
|
66
|
+
loops: loops.loops,
|
|
67
|
+
season,
|
|
68
|
+
rule: 'R-DIAG-01 五行透镜(启发式)',
|
|
69
|
+
note: '断"穴位"为最小干预心法杠杆点定位,非因果断言'
|
|
70
|
+
};
|
|
71
|
+
}
|