@openfate/bazi-engine 1.0.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/LICENSE +21 -0
- package/README.md +206 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +75 -0
- package/dist/core/cycles.d.ts +17 -0
- package/dist/core/cycles.js +49 -0
- package/dist/core/interactions.d.ts +16 -0
- package/dist/core/interactions.js +179 -0
- package/dist/core/pillars.d.ts +61 -0
- package/dist/core/pillars.js +78 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +123 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +5 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenFate Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# @openfate/bazi-engine
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
> **The accurate, production-ready Four Pillars (八字) engine for JavaScript & TypeScript.**
|
|
7
|
+
> Powered by [OpenFate.ai](https://openfate.ai) — the AI-native metaphysical analysis platform.
|
|
8
|
+
|
|
9
|
+
Getting the Four Pillars right is hard. Solar Term boundaries (节气), True Solar Time correction (真太阳时), day-change boundaries, Lunar calendar conversion — one wrong edge case and every chart is off. **We've solved all of it.**
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why use this?
|
|
14
|
+
|
|
15
|
+
| Problem | Without this package | With `@openfate/bazi-engine` |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| True Solar Time | Complex astronomy, most ignore it | Built-in via `@openfate/true-solar-time` |
|
|
18
|
+
| Solar Term boundaries | Easy to get LiChun wrong | Handles all 24 节气 precisely |
|
|
19
|
+
| Zi Hour boundary (23:00) | Confusing, often skipped | Configurable `dayBoundaryMode` |
|
|
20
|
+
| Lunar Calendar conversion | Requires separate library | Built-in, one function call |
|
|
21
|
+
| Interaction detection | Implement 7 types yourself | All 7 types detected, zero effort |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Use Cases
|
|
26
|
+
|
|
27
|
+
### 1. 🔮 Bazi Consulting Software / CRM
|
|
28
|
+
Professional Fengshui consultants manage hundreds of clients. Build a dashboard that generates accurate charts on-demand:
|
|
29
|
+
```typescript
|
|
30
|
+
const chart = calculateBaziChart({ year: 1985, month: 3, day: 12, hour: 9,
|
|
31
|
+
gender: 'male', longitude: 121.47, timezone: 8 });
|
|
32
|
+
// Render chart.pillars in your UI, show chart.daYun timeline
|
|
33
|
+
```
|
|
34
|
+
No more relying on unreliable online tools or expensive licensed software — own your calculation stack.
|
|
35
|
+
|
|
36
|
+
### 2. 💌 Matchmaking & Dating Apps
|
|
37
|
+
Check compatibility by comparing two users' charts:
|
|
38
|
+
```typescript
|
|
39
|
+
const userA = calculateBaziChart({ ... });
|
|
40
|
+
const userB = calculateBaziChart({ ... });
|
|
41
|
+
|
|
42
|
+
// Do their Day Masters combine? (六合)
|
|
43
|
+
const combined = detectInteractions(
|
|
44
|
+
{ year: userA.pillars.year.branch, month: userA.pillars.month.branch,
|
|
45
|
+
day: userA.pillars.day.branch, hour: userA.pillars.hour?.branch ?? '' },
|
|
46
|
+
userB.pillars.day.branch // Check against UserB's Day Branch
|
|
47
|
+
);
|
|
48
|
+
const compatible = combined.some(i => i.type === 'COMBINATION_2');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. 📅 Daily Horoscope / Almanac Push Notifications
|
|
52
|
+
Calculate today's pillar and send personalized notifications:
|
|
53
|
+
```typescript
|
|
54
|
+
const today = new Date();
|
|
55
|
+
const todayChart = calculateBaziChart({
|
|
56
|
+
year: today.getFullYear(), month: today.getMonth()+1, day: today.getDate(),
|
|
57
|
+
hour: 12, gender: 'male'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// If today's Day Branch clashes with user's Day Branch → alert!
|
|
61
|
+
const todayBranch = todayChart.pillars.day.branch;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. 🤖 AI Metaphysics Agents / Discord Bots
|
|
65
|
+
Feed the structured JSON into your own AI agent:
|
|
66
|
+
```typescript
|
|
67
|
+
const chart = calculateBaziChart({ ... });
|
|
68
|
+
|
|
69
|
+
// Pass to OpenAI / Claude with your own prompts
|
|
70
|
+
const prompt = `Analyze this Bazi chart: ${JSON.stringify(chart.pillars)}
|
|
71
|
+
Major cycles: ${chart.daYun.cycles.map(c => c.ganZhi).join(', ')}
|
|
72
|
+
Key interactions: ${chart.interactions.map(i => i.description).join(', ')}`;
|
|
73
|
+
```
|
|
74
|
+
You get the data layer right with this package, then apply your own interpretation model on top.
|
|
75
|
+
|
|
76
|
+
### 5. 🏫 Education & Research
|
|
77
|
+
Teach traditional metaphysics with accurate, verifiable data. All outputs are explainable — `solarTimeInfo` shows exactly how True Solar Time was calculated.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install @openfate/bazi-engine
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quick Start
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { calculateBaziChart } from '@openfate/bazi-engine';
|
|
91
|
+
|
|
92
|
+
const chart = calculateBaziChart({
|
|
93
|
+
year: 1998, month: 12, day: 13,
|
|
94
|
+
hour: 12, minute: 0,
|
|
95
|
+
gender: 'female',
|
|
96
|
+
|
|
97
|
+
// Location — enables True Solar Time correction
|
|
98
|
+
longitude: 116.39, // Beijing
|
|
99
|
+
timezone: 8, // UTC+8
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Four Pillars
|
|
103
|
+
console.log(chart.pillars.year.stem + chart.pillars.year.branch); // 戊寅
|
|
104
|
+
console.log(chart.pillars.month.stem + chart.pillars.month.branch); // 甲子
|
|
105
|
+
console.log(chart.pillars.day.stem + chart.pillars.day.branch); // 甲午
|
|
106
|
+
console.log(chart.pillars.hour!.stem + chart.pillars.hour!.branch); // 庚午
|
|
107
|
+
|
|
108
|
+
// Day Master
|
|
109
|
+
console.log(chart.dayMaster.pinyin); // 'jia'
|
|
110
|
+
console.log(chart.dayMaster.element); // 'wood'
|
|
111
|
+
|
|
112
|
+
// Major Luck Cycles (大运)
|
|
113
|
+
chart.daYun.cycles.forEach(cycle => {
|
|
114
|
+
console.log(`Age ${cycle.startAge}: ${cycle.ganZhi}`);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Branch Interactions
|
|
118
|
+
chart.interactions.forEach(i => {
|
|
119
|
+
console.log(`${i.type}: ${i.description}`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// True Solar Time details
|
|
123
|
+
console.log(chart.solarTimeInfo?.trueSolarTime); // '12:09'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Lunar Calendar Input
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const chart = calculateBaziChart({
|
|
130
|
+
year: 1998, month: 10, day: 25, // Lunar date
|
|
131
|
+
calendarType: 'lunar',
|
|
132
|
+
hour: 12, gender: 'male', longitude: 116.39, timezone: 8,
|
|
133
|
+
});
|
|
134
|
+
// Automatically converts to Solar 1998-12-13 before calculating
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
calculateBaziChart({
|
|
141
|
+
// ...
|
|
142
|
+
enableTrueSolarTime: true, // default: true (requires longitude)
|
|
143
|
+
dayBoundaryMode: 'MIDNIGHT_00', // 'MIDNIGHT_00' (default) or 'ZI_HOUR_23' (traditional)
|
|
144
|
+
calendarType: 'solar', // 'solar' (default) or 'lunar'
|
|
145
|
+
dstOffset: 1, // For historical/DST dates (e.g. China pre-1992)
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## API Reference
|
|
150
|
+
|
|
151
|
+
### `calculateBaziChart(input: BaziInput): BaziChart`
|
|
152
|
+
Main entry point. Returns a `BaziChart` with:
|
|
153
|
+
- `pillars` — Four Pillars (year, month, day, hour)
|
|
154
|
+
- `dayMaster` — Day Master stem with element, polarity, pinyin
|
|
155
|
+
- `daYun` — 9 Major Luck Cycles with start year/age
|
|
156
|
+
- `interactions` — All detected branch interactions (7 types)
|
|
157
|
+
- `solarTimeInfo` — True Solar Time details (or null if disabled)
|
|
158
|
+
|
|
159
|
+
### `detectInteractions(natal, annualBranch?): BranchInteraction[]`
|
|
160
|
+
Detect interactions in a natal chart, optionally against an annual branch (太岁).
|
|
161
|
+
|
|
162
|
+
### `generatePillarsFromSolar(...): PillarResult`
|
|
163
|
+
Low-level pillar generator — use when you've already handled time correction yourself.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 🛡️ Testing & Reliability
|
|
168
|
+
|
|
169
|
+
Bazi calculations are notoriously prone to edge-case bugs. We maintain a **comprehensive regression suite** of 100+ global edge cases, verified against astronomical standards:
|
|
170
|
+
|
|
171
|
+
- **24 Solar Terms**: Minute-level precision for *Li Chun*, *Jing Zhe*, etc.
|
|
172
|
+
- **Early/Late Zi Hour**: Handles the 23:00 day-rollover across month/year boundaries.
|
|
173
|
+
- **Global Distortion**: Extreme longitudes (e.g., Xinjiang, Iceland) and fractional timezones (e.g., India UTC+5.5).
|
|
174
|
+
- **Historical DST**: Automatic handling of Chinese DST (1986-1991), UK Double DST, and more.
|
|
175
|
+
- **Century Boundaries**: Verified accuracy for dates across 1800, 1900, 2000, and 2100.
|
|
176
|
+
|
|
177
|
+
Run the tests yourself:
|
|
178
|
+
```bash
|
|
179
|
+
npm test
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## How It Works
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
Birth input (civil time)
|
|
188
|
+
↓
|
|
189
|
+
[@openfate/true-solar-time] ← Longitude correction + Meeus Equation of Time
|
|
190
|
+
↓
|
|
191
|
+
True Solar Time
|
|
192
|
+
↓
|
|
193
|
+
[lunar-javascript] ← Solar Term boundary detection + Ganzhi calculation
|
|
194
|
+
↓
|
|
195
|
+
Four Pillars → Da Yun → Interactions
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 🔗 Try the Full Engine
|
|
201
|
+
|
|
202
|
+
`@openfate/bazi-engine` gives you the data. **[OpenFate.ai](https://openfate.ai/bazi)** gives you the complete AI-powered interpretation — relationship analysis, career forecasting, and interactive AI chat with your chart.
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT — Free to use, modify, and distribute commercially.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FiveElement, Polarity, StemInfo, BranchInfo } from './types';
|
|
2
|
+
/** Heavenly Stem → Five Element */
|
|
3
|
+
export declare const STEM_TO_ELEMENT: Record<string, FiveElement>;
|
|
4
|
+
/** Heavenly Stem → Polarity */
|
|
5
|
+
export declare const STEM_TO_POLARITY: Record<string, Polarity>;
|
|
6
|
+
/** Heavenly Stem → Pinyin */
|
|
7
|
+
export declare const STEM_TO_PINYIN: Record<string, string>;
|
|
8
|
+
/** All Heavenly Stems in order */
|
|
9
|
+
export declare const STEMS: StemInfo[];
|
|
10
|
+
/**
|
|
11
|
+
* Classical Hidden Stems (藏干) — Traditional proportions.
|
|
12
|
+
* Main qi (本气) listed first with isMain = true.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BRANCH_HIDDEN_STEMS: Record<string, {
|
|
15
|
+
stem: string;
|
|
16
|
+
isMain?: boolean;
|
|
17
|
+
}[]>;
|
|
18
|
+
/** Earthly Branch → Primary Five Element (from Main Qi) */
|
|
19
|
+
export declare const BRANCH_TO_ELEMENT: Record<string, FiveElement>;
|
|
20
|
+
/** All Earthly Branches */
|
|
21
|
+
export declare const BRANCHES: BranchInfo[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// @openfate/bazi-engine — Ganzhi Reference Constants
|
|
4
|
+
// (Public classical lookup tables — no engine weights or scoring)
|
|
5
|
+
// ============================================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.BRANCHES = exports.BRANCH_TO_ELEMENT = exports.BRANCH_HIDDEN_STEMS = exports.STEMS = exports.STEM_TO_PINYIN = exports.STEM_TO_POLARITY = exports.STEM_TO_ELEMENT = void 0;
|
|
8
|
+
/** Heavenly Stem → Five Element */
|
|
9
|
+
exports.STEM_TO_ELEMENT = {
|
|
10
|
+
甲: 'wood', 乙: 'wood',
|
|
11
|
+
丙: 'fire', 丁: 'fire',
|
|
12
|
+
戊: 'earth', 己: 'earth',
|
|
13
|
+
庚: 'metal', 辛: 'metal',
|
|
14
|
+
壬: 'water', 癸: 'water',
|
|
15
|
+
};
|
|
16
|
+
/** Heavenly Stem → Polarity */
|
|
17
|
+
exports.STEM_TO_POLARITY = {
|
|
18
|
+
甲: 'yang', 乙: 'yin',
|
|
19
|
+
丙: 'yang', 丁: 'yin',
|
|
20
|
+
戊: 'yang', 己: 'yin',
|
|
21
|
+
庚: 'yang', 辛: 'yin',
|
|
22
|
+
壬: 'yang', 癸: 'yin',
|
|
23
|
+
};
|
|
24
|
+
/** Heavenly Stem → Pinyin */
|
|
25
|
+
exports.STEM_TO_PINYIN = {
|
|
26
|
+
甲: 'jia', 乙: 'yi', 丙: 'bing', 丁: 'ding', 戊: 'wu',
|
|
27
|
+
己: 'ji', 庚: 'geng', 辛: 'xin', 壬: 'ren', 癸: 'gui',
|
|
28
|
+
};
|
|
29
|
+
/** All Heavenly Stems in order */
|
|
30
|
+
exports.STEMS = [
|
|
31
|
+
{ char: '甲', pinyin: 'jia', element: 'wood', polarity: 'yang' },
|
|
32
|
+
{ char: '乙', pinyin: 'yi', element: 'wood', polarity: 'yin' },
|
|
33
|
+
{ char: '丙', pinyin: 'bing', element: 'fire', polarity: 'yang' },
|
|
34
|
+
{ char: '丁', pinyin: 'ding', element: 'fire', polarity: 'yin' },
|
|
35
|
+
{ char: '戊', pinyin: 'wu', element: 'earth', polarity: 'yang' },
|
|
36
|
+
{ char: '己', pinyin: 'ji', element: 'earth', polarity: 'yin' },
|
|
37
|
+
{ char: '庚', pinyin: 'geng', element: 'metal', polarity: 'yang' },
|
|
38
|
+
{ char: '辛', pinyin: 'xin', element: 'metal', polarity: 'yin' },
|
|
39
|
+
{ char: '壬', pinyin: 'ren', element: 'water', polarity: 'yang' },
|
|
40
|
+
{ char: '癸', pinyin: 'gui', element: 'water', polarity: 'yin' },
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Classical Hidden Stems (藏干) — Traditional proportions.
|
|
44
|
+
* Main qi (本气) listed first with isMain = true.
|
|
45
|
+
*/
|
|
46
|
+
exports.BRANCH_HIDDEN_STEMS = {
|
|
47
|
+
子: [{ stem: '癸', isMain: true }],
|
|
48
|
+
丑: [{ stem: '己', isMain: true }, { stem: '癸' }, { stem: '辛' }],
|
|
49
|
+
寅: [{ stem: '甲', isMain: true }, { stem: '丙' }, { stem: '戊' }],
|
|
50
|
+
卯: [{ stem: '乙', isMain: true }],
|
|
51
|
+
辰: [{ stem: '戊', isMain: true }, { stem: '乙' }, { stem: '癸' }],
|
|
52
|
+
巳: [{ stem: '丙', isMain: true }, { stem: '戊' }, { stem: '庚' }],
|
|
53
|
+
午: [{ stem: '丁', isMain: true }, { stem: '己' }],
|
|
54
|
+
未: [{ stem: '己', isMain: true }, { stem: '丁' }, { stem: '乙' }],
|
|
55
|
+
申: [{ stem: '庚', isMain: true }, { stem: '壬' }, { stem: '戊' }],
|
|
56
|
+
酉: [{ stem: '辛', isMain: true }],
|
|
57
|
+
戌: [{ stem: '戊', isMain: true }, { stem: '辛' }, { stem: '丁' }],
|
|
58
|
+
亥: [{ stem: '壬', isMain: true }, { stem: '甲' }],
|
|
59
|
+
};
|
|
60
|
+
/** Earthly Branch → Primary Five Element (from Main Qi) */
|
|
61
|
+
exports.BRANCH_TO_ELEMENT = {
|
|
62
|
+
子: 'water', 丑: 'earth', 寅: 'wood', 卯: 'wood',
|
|
63
|
+
辰: 'earth', 巳: 'fire', 午: 'fire', 未: 'earth',
|
|
64
|
+
申: 'metal', 酉: 'metal', 戌: 'earth', 亥: 'water',
|
|
65
|
+
};
|
|
66
|
+
/** All Earthly Branches */
|
|
67
|
+
exports.BRANCHES = Object.entries(exports.BRANCH_HIDDEN_STEMS).map(([char, hidden]) => {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
return ({
|
|
70
|
+
char,
|
|
71
|
+
element: exports.BRANCH_TO_ELEMENT[char],
|
|
72
|
+
hiddenStems: hidden.map(h => h.stem),
|
|
73
|
+
mainQi: (_b = (_a = hidden.find(h => h.isMain)) === null || _a === void 0 ? void 0 : _a.stem) !== null && _b !== void 0 ? _b : hidden[0].stem,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DaYunInfo } from '../types';
|
|
2
|
+
import { LunarEightChar } from './pillars';
|
|
3
|
+
/**
|
|
4
|
+
* calculateDaYun
|
|
5
|
+
*
|
|
6
|
+
* Calculates the nine 10-year Major Luck Cycles (Da Yun) from the natal EightChar.
|
|
7
|
+
* Direction (forward/backward) is determined by gender and the Yin/Yang of the year stem:
|
|
8
|
+
* - Yang Year + Male → Forward (Shun)
|
|
9
|
+
* - Yang Year + Female → Backward (Ni)
|
|
10
|
+
* - Yin Year + Male → Backward (Ni)
|
|
11
|
+
* - Yin Year + Female → Forward (Shun)
|
|
12
|
+
*
|
|
13
|
+
* @param eightChar - The EightChar object from lunar-javascript (returned by generatePillarsFromSolar)
|
|
14
|
+
* @param gender - 'male' or 'female'
|
|
15
|
+
* @param birthYear - Solar birth year (used to compute startAge)
|
|
16
|
+
*/
|
|
17
|
+
export declare function calculateDaYun(eightChar: LunarEightChar, gender: 'male' | 'female', birthYear: number): DaYunInfo;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// @openfate/bazi-engine — Da Yun (Major 10-Year Luck Cycles) Calculator
|
|
4
|
+
// ============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.calculateDaYun = calculateDaYun;
|
|
7
|
+
/**
|
|
8
|
+
* calculateDaYun
|
|
9
|
+
*
|
|
10
|
+
* Calculates the nine 10-year Major Luck Cycles (Da Yun) from the natal EightChar.
|
|
11
|
+
* Direction (forward/backward) is determined by gender and the Yin/Yang of the year stem:
|
|
12
|
+
* - Yang Year + Male → Forward (Shun)
|
|
13
|
+
* - Yang Year + Female → Backward (Ni)
|
|
14
|
+
* - Yin Year + Male → Backward (Ni)
|
|
15
|
+
* - Yin Year + Female → Forward (Shun)
|
|
16
|
+
*
|
|
17
|
+
* @param eightChar - The EightChar object from lunar-javascript (returned by generatePillarsFromSolar)
|
|
18
|
+
* @param gender - 'male' or 'female'
|
|
19
|
+
* @param birthYear - Solar birth year (used to compute startAge)
|
|
20
|
+
*/
|
|
21
|
+
function calculateDaYun(eightChar, gender, birthYear) {
|
|
22
|
+
const genderInt = gender === 'male' ? 1 : 0;
|
|
23
|
+
const yun = eightChar.getYun(genderInt);
|
|
24
|
+
const rawCycles = yun.getDaYun();
|
|
25
|
+
const cycles = [];
|
|
26
|
+
// Index 0 is the natal chart segment — cycles start at index 1
|
|
27
|
+
const MAX_CYCLES = 9;
|
|
28
|
+
for (let i = 1; i <= MAX_CYCLES && i < rawCycles.length; i++) {
|
|
29
|
+
const dy = rawCycles[i];
|
|
30
|
+
const ganZhi = dy.getGanZhi();
|
|
31
|
+
const stem = ganZhi.substring(0, 1);
|
|
32
|
+
const branch = ganZhi.substring(1, 2);
|
|
33
|
+
const startYear = dy.getStartYear();
|
|
34
|
+
cycles.push({
|
|
35
|
+
index: i,
|
|
36
|
+
stem,
|
|
37
|
+
branch,
|
|
38
|
+
ganZhi,
|
|
39
|
+
startYear,
|
|
40
|
+
startAge: startYear - birthYear,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
cycles,
|
|
45
|
+
isForward: yun.isForward(),
|
|
46
|
+
startYear: yun.getStartYear(),
|
|
47
|
+
startAge: yun.getStartYear() - birthYear,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BranchInteraction } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* detectInteractions
|
|
4
|
+
*
|
|
5
|
+
* Scans the natal chart branches for all classical Ganzhi interactions.
|
|
6
|
+
* An optional `annualBranch` can be passed to detect dynamic year interactions.
|
|
7
|
+
*
|
|
8
|
+
* @param natal - The four natal chart branches { year, month, day, hour }
|
|
9
|
+
* @param annualBranch - Optional: the Taisui (太岁) branch for the year being analyzed
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectInteractions(natal: {
|
|
12
|
+
year: string;
|
|
13
|
+
month: string;
|
|
14
|
+
day: string;
|
|
15
|
+
hour: string;
|
|
16
|
+
}, annualBranch?: string): BranchInteraction[];
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// @openfate/bazi-engine — Branch Interaction Detector
|
|
4
|
+
// Detects classical Clash, Combination, Punishment, Destruction, Harm.
|
|
5
|
+
// Returns presence/type only — scoring and energy deltas are NOT included.
|
|
6
|
+
// ============================================================================
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.detectInteractions = detectInteractions;
|
|
9
|
+
// ── Classical Detection Tables ───────────────────────────────────────────────
|
|
10
|
+
/** Six Clashes (六冲) */
|
|
11
|
+
const CLASHES = [
|
|
12
|
+
['子', '午'], ['丑', '未'], ['寅', '申'],
|
|
13
|
+
['卯', '酉'], ['辰', '戌'], ['巳', '亥'],
|
|
14
|
+
];
|
|
15
|
+
/** Six Combinations (六合 — 2-branch) */
|
|
16
|
+
const COMBINATIONS_2 = [
|
|
17
|
+
{ pair: ['子', '丑'], result: 'earth' },
|
|
18
|
+
{ pair: ['寅', '亥'], result: 'wood' },
|
|
19
|
+
{ pair: ['卯', '戌'], result: 'fire' },
|
|
20
|
+
{ pair: ['辰', '酉'], result: 'metal' },
|
|
21
|
+
{ pair: ['巳', '申'], result: 'water' },
|
|
22
|
+
{ pair: ['午', '未'], result: 'fire' },
|
|
23
|
+
];
|
|
24
|
+
/** Trines (三合 — 3-branch) */
|
|
25
|
+
const TRINES = [
|
|
26
|
+
{ group: ['申', '子', '辰'], result: 'water' },
|
|
27
|
+
{ group: ['亥', '卯', '未'], result: 'wood' },
|
|
28
|
+
{ group: ['寅', '午', '戌'], result: 'fire' },
|
|
29
|
+
{ group: ['巳', '酉', '丑'], result: 'metal' },
|
|
30
|
+
];
|
|
31
|
+
/** Directional (三会 — Seasonal) */
|
|
32
|
+
const DIRECTIONAL = [
|
|
33
|
+
{ group: ['寅', '卯', '辰'], result: 'wood' },
|
|
34
|
+
{ group: ['巳', '午', '未'], result: 'fire' },
|
|
35
|
+
{ group: ['申', '酉', '戌'], result: 'metal' },
|
|
36
|
+
{ group: ['亥', '子', '丑'], result: 'water' },
|
|
37
|
+
];
|
|
38
|
+
/** Punishments (刑) */
|
|
39
|
+
const PUNISHMENTS = [
|
|
40
|
+
{ branches: ['寅', '巳', '申'], desc: 'Ungrateful Punishment (无恩之刑)' },
|
|
41
|
+
{ branches: ['丑', '戌', '未'], desc: 'Bullying Punishment (持势之刑)' },
|
|
42
|
+
{ branches: ['子', '卯'], desc: 'Uncivilized Punishment (无礼之刑)' },
|
|
43
|
+
{ branches: ['辰', '辰'], desc: 'Self-Punishment (自刑)' },
|
|
44
|
+
{ branches: ['午', '午'], desc: 'Self-Punishment (自刑)' },
|
|
45
|
+
{ branches: ['酉', '酉'], desc: 'Self-Punishment (自刑)' },
|
|
46
|
+
{ branches: ['亥', '亥'], desc: 'Self-Punishment (自刑)' },
|
|
47
|
+
];
|
|
48
|
+
/** Destructions (破) */
|
|
49
|
+
const DESTRUCTIONS = [
|
|
50
|
+
['子', '酉'], ['丑', '辰'], ['寅', '亥'],
|
|
51
|
+
['卯', '午'], ['巳', '申'], ['未', '戌'],
|
|
52
|
+
];
|
|
53
|
+
/** Harms (害) */
|
|
54
|
+
const HARMS = [
|
|
55
|
+
['子', '未'], ['丑', '午'], ['寅', '巳'],
|
|
56
|
+
['卯', '辰'], ['申', '亥'], ['酉', '戌'],
|
|
57
|
+
];
|
|
58
|
+
function hasBranch(branches, target) {
|
|
59
|
+
return branches.find(b => b.branch === target);
|
|
60
|
+
}
|
|
61
|
+
function groupHas(branches, group) {
|
|
62
|
+
const found = group.map(b => hasBranch(branches, b)).filter(Boolean);
|
|
63
|
+
return found.length === group.length ? found : null;
|
|
64
|
+
}
|
|
65
|
+
// ── Main Detector ────────────────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* detectInteractions
|
|
68
|
+
*
|
|
69
|
+
* Scans the natal chart branches for all classical Ganzhi interactions.
|
|
70
|
+
* An optional `annualBranch` can be passed to detect dynamic year interactions.
|
|
71
|
+
*
|
|
72
|
+
* @param natal - The four natal chart branches { year, month, day, hour }
|
|
73
|
+
* @param annualBranch - Optional: the Taisui (太岁) branch for the year being analyzed
|
|
74
|
+
*/
|
|
75
|
+
function detectInteractions(natal, annualBranch) {
|
|
76
|
+
const results = [];
|
|
77
|
+
const allBranches = [
|
|
78
|
+
{ branch: natal.year, pillar: 'year' },
|
|
79
|
+
{ branch: natal.month, pillar: 'month' },
|
|
80
|
+
{ branch: natal.day, pillar: 'day' },
|
|
81
|
+
{ branch: natal.hour, pillar: 'hour' },
|
|
82
|
+
].filter(b => b.branch !== '');
|
|
83
|
+
if (annualBranch) {
|
|
84
|
+
allBranches.push({ branch: annualBranch, pillar: 'annual' });
|
|
85
|
+
}
|
|
86
|
+
// 1. Clashes
|
|
87
|
+
for (const [a, b] of CLASHES) {
|
|
88
|
+
const na = hasBranch(allBranches, a);
|
|
89
|
+
const nb = hasBranch(allBranches, b);
|
|
90
|
+
if (na && nb) {
|
|
91
|
+
results.push({
|
|
92
|
+
type: 'CLASH',
|
|
93
|
+
branches: [a, b],
|
|
94
|
+
pillars: [na.pillar, nb.pillar],
|
|
95
|
+
description: `Clash (${a}${b}相冲)`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 2. Six Combinations (六合)
|
|
100
|
+
for (const combo of COMBINATIONS_2) {
|
|
101
|
+
const [a, b] = combo.pair;
|
|
102
|
+
const na = hasBranch(allBranches, a);
|
|
103
|
+
const nb = hasBranch(allBranches, b);
|
|
104
|
+
if (na && nb) {
|
|
105
|
+
results.push({
|
|
106
|
+
type: 'COMBINATION_2',
|
|
107
|
+
branches: [a, b],
|
|
108
|
+
pillars: [na.pillar, nb.pillar],
|
|
109
|
+
resultElement: combo.result,
|
|
110
|
+
description: `Combination (${a}${b}六合 — ${combo.result})`,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 3. Trines (三合)
|
|
115
|
+
for (const trine of TRINES) {
|
|
116
|
+
const found = groupHas(allBranches, [...trine.group]);
|
|
117
|
+
if (found) {
|
|
118
|
+
results.push({
|
|
119
|
+
type: 'TRINE',
|
|
120
|
+
branches: [...trine.group],
|
|
121
|
+
pillars: found.map(n => n.pillar),
|
|
122
|
+
resultElement: trine.result,
|
|
123
|
+
description: `Trine (${trine.group.join('')}三合 — ${trine.result})`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// 4. Directional (三会)
|
|
128
|
+
for (const dir of DIRECTIONAL) {
|
|
129
|
+
const found = groupHas(allBranches, [...dir.group]);
|
|
130
|
+
if (found) {
|
|
131
|
+
results.push({
|
|
132
|
+
type: 'DIRECTIONAL',
|
|
133
|
+
branches: [...dir.group],
|
|
134
|
+
pillars: found.map(n => n.pillar),
|
|
135
|
+
resultElement: dir.result,
|
|
136
|
+
description: `Directional (${dir.group.join('')}三会 — ${dir.result})`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// 5. Punishments (刑)
|
|
141
|
+
for (const pun of PUNISHMENTS) {
|
|
142
|
+
const found = groupHas(allBranches, pun.branches);
|
|
143
|
+
if (found) {
|
|
144
|
+
results.push({
|
|
145
|
+
type: 'PUNISHMENT',
|
|
146
|
+
branches: [...pun.branches],
|
|
147
|
+
pillars: found.map(n => n.pillar),
|
|
148
|
+
description: `${pun.desc} — ${pun.branches.join('')}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 6. Destructions (破)
|
|
153
|
+
for (const [a, b] of DESTRUCTIONS) {
|
|
154
|
+
const na = hasBranch(allBranches, a);
|
|
155
|
+
const nb = hasBranch(allBranches, b);
|
|
156
|
+
if (na && nb) {
|
|
157
|
+
results.push({
|
|
158
|
+
type: 'DESTRUCTION',
|
|
159
|
+
branches: [a, b],
|
|
160
|
+
pillars: [na.pillar, nb.pillar],
|
|
161
|
+
description: `Destruction (${a}${b}相破)`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 7. Harms (害)
|
|
166
|
+
for (const [a, b] of HARMS) {
|
|
167
|
+
const na = hasBranch(allBranches, a);
|
|
168
|
+
const nb = hasBranch(allBranches, b);
|
|
169
|
+
if (na && nb) {
|
|
170
|
+
results.push({
|
|
171
|
+
type: 'HARM',
|
|
172
|
+
branches: [a, b],
|
|
173
|
+
pillars: [na.pillar, nb.pillar],
|
|
174
|
+
description: `Harm (${a}${b}相害)`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return results;
|
|
179
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FourPillars, DayBoundaryMode } from '../types';
|
|
2
|
+
/** Typed interface for the EightChar object returned by lunar-javascript */
|
|
3
|
+
export interface LunarEightChar {
|
|
4
|
+
setSect(sect: number): void;
|
|
5
|
+
getYearGan(): string;
|
|
6
|
+
getYearZhi(): string;
|
|
7
|
+
getMonthGan(): string;
|
|
8
|
+
getMonthZhi(): string;
|
|
9
|
+
getDayGan(): string;
|
|
10
|
+
getDayZhi(): string;
|
|
11
|
+
getTimeGan(): string;
|
|
12
|
+
getTimeZhi(): string;
|
|
13
|
+
getYun(gender: number): LunarYun;
|
|
14
|
+
}
|
|
15
|
+
/** Typed interface for the Yun (Luck Cycles) manager */
|
|
16
|
+
export interface LunarYun {
|
|
17
|
+
isForward(): boolean;
|
|
18
|
+
getStartYear(): number;
|
|
19
|
+
getStartMonth(): number;
|
|
20
|
+
getStartDay(): number;
|
|
21
|
+
getDaYun(): LunarDaYun[];
|
|
22
|
+
}
|
|
23
|
+
/** Typed interface for a single Da Yun period */
|
|
24
|
+
export interface LunarDaYun {
|
|
25
|
+
getGanZhi(): string;
|
|
26
|
+
getStartYear(): number;
|
|
27
|
+
getEndYear(): number;
|
|
28
|
+
}
|
|
29
|
+
export interface PillarResult {
|
|
30
|
+
pillars: FourPillars;
|
|
31
|
+
eightChar: LunarEightChar;
|
|
32
|
+
dayStem: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* generatePillarsFromSolar
|
|
36
|
+
*
|
|
37
|
+
* Converts a solar datetime (post True Solar Time correction) into the Four Pillars.
|
|
38
|
+
* Uses lunar-javascript for Solar Term (节气) boundary detection and pillar generation.
|
|
39
|
+
*
|
|
40
|
+
* @param year - Solar year (after TST correction)
|
|
41
|
+
* @param month - Solar month
|
|
42
|
+
* @param day - Solar day
|
|
43
|
+
* @param hour - Hour (0-23), undefined if birth time unknown
|
|
44
|
+
* @param minute - Minute
|
|
45
|
+
* @param second - Second
|
|
46
|
+
* @param dayBoundaryMode - MIDNIGHT_00 (default) or ZI_HOUR_23 (traditional)
|
|
47
|
+
*/
|
|
48
|
+
export declare function generatePillarsFromSolar(year: number, month: number, day: number, hour?: number, minute?: number, second?: number, dayBoundaryMode?: DayBoundaryMode): PillarResult;
|
|
49
|
+
/**
|
|
50
|
+
* getMainQi - Returns the main hidden stem of a branch
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMainQi(branch: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* getStemInfo - Returns enriched stem details
|
|
55
|
+
*/
|
|
56
|
+
export declare function getStemInfo(stem: string): {
|
|
57
|
+
char: string;
|
|
58
|
+
pinyin: string;
|
|
59
|
+
element: import("../types").FiveElement;
|
|
60
|
+
polarity: import("../types").Polarity;
|
|
61
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// @openfate/bazi-engine — Four Pillars Generator
|
|
4
|
+
// ============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generatePillarsFromSolar = generatePillarsFromSolar;
|
|
7
|
+
exports.getMainQi = getMainQi;
|
|
8
|
+
exports.getStemInfo = getStemInfo;
|
|
9
|
+
const lunar_javascript_1 = require("lunar-javascript");
|
|
10
|
+
const constants_1 = require("../constants");
|
|
11
|
+
function buildPillar(stem, branch) {
|
|
12
|
+
var _a;
|
|
13
|
+
return {
|
|
14
|
+
stem,
|
|
15
|
+
branch,
|
|
16
|
+
element: (_a = constants_1.STEM_TO_ELEMENT[stem]) !== null && _a !== void 0 ? _a : 'earth',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* generatePillarsFromSolar
|
|
21
|
+
*
|
|
22
|
+
* Converts a solar datetime (post True Solar Time correction) into the Four Pillars.
|
|
23
|
+
* Uses lunar-javascript for Solar Term (节气) boundary detection and pillar generation.
|
|
24
|
+
*
|
|
25
|
+
* @param year - Solar year (after TST correction)
|
|
26
|
+
* @param month - Solar month
|
|
27
|
+
* @param day - Solar day
|
|
28
|
+
* @param hour - Hour (0-23), undefined if birth time unknown
|
|
29
|
+
* @param minute - Minute
|
|
30
|
+
* @param second - Second
|
|
31
|
+
* @param dayBoundaryMode - MIDNIGHT_00 (default) or ZI_HOUR_23 (traditional)
|
|
32
|
+
*/
|
|
33
|
+
function generatePillarsFromSolar(year, month, day, hour, minute, second, dayBoundaryMode = 'MIDNIGHT_00') {
|
|
34
|
+
const hasTime = hour !== undefined && hour !== null;
|
|
35
|
+
const h = hasTime ? hour : 0;
|
|
36
|
+
const m = minute !== null && minute !== void 0 ? minute : 0;
|
|
37
|
+
const s = second !== null && second !== void 0 ? second : 0;
|
|
38
|
+
const solar = hasTime
|
|
39
|
+
? lunar_javascript_1.Solar.fromYmdHms(year, month, day, h, m, s)
|
|
40
|
+
: lunar_javascript_1.Solar.fromYmd(year, month, day);
|
|
41
|
+
const lunar = solar.getLunar();
|
|
42
|
+
const eightChar = lunar.getEightChar();
|
|
43
|
+
// Sect 1 = Day shifts at 23:00 (traditional Zi Hour)
|
|
44
|
+
// Sect 2 = Day shifts at 00:00 (midnight, modern default)
|
|
45
|
+
eightChar.setSect(dayBoundaryMode === 'ZI_HOUR_23' ? 1 : 2);
|
|
46
|
+
const dayStem = eightChar.getDayGan();
|
|
47
|
+
return {
|
|
48
|
+
pillars: {
|
|
49
|
+
year: buildPillar(eightChar.getYearGan(), eightChar.getYearZhi()),
|
|
50
|
+
month: buildPillar(eightChar.getMonthGan(), eightChar.getMonthZhi()),
|
|
51
|
+
day: buildPillar(dayStem, eightChar.getDayZhi()),
|
|
52
|
+
hour: hasTime ? buildPillar(eightChar.getTimeGan(), eightChar.getTimeZhi()) : null,
|
|
53
|
+
},
|
|
54
|
+
eightChar,
|
|
55
|
+
dayStem,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* getMainQi - Returns the main hidden stem of a branch
|
|
60
|
+
*/
|
|
61
|
+
function getMainQi(branch) {
|
|
62
|
+
var _a, _b, _c;
|
|
63
|
+
const hidden = (_a = constants_1.BRANCH_HIDDEN_STEMS[branch]) !== null && _a !== void 0 ? _a : [];
|
|
64
|
+
const main = (_b = hidden.find(h => h.isMain)) !== null && _b !== void 0 ? _b : hidden[0];
|
|
65
|
+
return (_c = main === null || main === void 0 ? void 0 : main.stem) !== null && _c !== void 0 ? _c : '';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* getStemInfo - Returns enriched stem details
|
|
69
|
+
*/
|
|
70
|
+
function getStemInfo(stem) {
|
|
71
|
+
var _a, _b, _c;
|
|
72
|
+
return {
|
|
73
|
+
char: stem,
|
|
74
|
+
pinyin: (_a = constants_1.STEM_TO_PINYIN[stem]) !== null && _a !== void 0 ? _a : '',
|
|
75
|
+
element: (_b = constants_1.STEM_TO_ELEMENT[stem]) !== null && _b !== void 0 ? _b : 'earth',
|
|
76
|
+
polarity: (_c = constants_1.STEM_TO_POLARITY[stem]) !== null && _c !== void 0 ? _c : 'yang',
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BaziChart, BaziInput } from './types';
|
|
2
|
+
export * from './types';
|
|
3
|
+
export * from './constants';
|
|
4
|
+
export { detectInteractions } from './core/interactions';
|
|
5
|
+
export { generatePillarsFromSolar, getStemInfo, getMainQi } from './core/pillars';
|
|
6
|
+
export { calculateDaYun } from './core/cycles';
|
|
7
|
+
/**
|
|
8
|
+
* calculateBaziChart
|
|
9
|
+
*
|
|
10
|
+
* The main entry point. Takes a birth configuration and returns a fully
|
|
11
|
+
* calculated Bazi chart with Four Pillars, Da Yun cycles, and branch interactions.
|
|
12
|
+
*
|
|
13
|
+
* True Solar Time correction is applied automatically if longitude is provided.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const chart = calculateBaziChart({
|
|
18
|
+
* year: 1998, month: 12, day: 13, hour: 12, minute: 0,
|
|
19
|
+
* gender: 'female', longitude: 116.39, timezone: 8,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* console.log(chart.pillars.year.stem); // '戊'
|
|
23
|
+
* console.log(chart.pillars.year.branch); // '寅'
|
|
24
|
+
* console.log(chart.daYun.cycles[0].ganZhi); // First major cycle
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function calculateBaziChart(input: BaziInput): BaziChart;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// @openfate/bazi-engine — Public Entry Point
|
|
4
|
+
// ============================================================================
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.calculateDaYun = exports.getMainQi = exports.getStemInfo = exports.generatePillarsFromSolar = exports.detectInteractions = void 0;
|
|
21
|
+
exports.calculateBaziChart = calculateBaziChart;
|
|
22
|
+
const true_solar_time_1 = require("@openfate/true-solar-time");
|
|
23
|
+
const lunar_javascript_1 = require("lunar-javascript");
|
|
24
|
+
const pillars_1 = require("./core/pillars");
|
|
25
|
+
const cycles_1 = require("./core/cycles");
|
|
26
|
+
const interactions_1 = require("./core/interactions");
|
|
27
|
+
// Re-export all public types and constants
|
|
28
|
+
__exportStar(require("./types"), exports);
|
|
29
|
+
__exportStar(require("./constants"), exports);
|
|
30
|
+
var interactions_2 = require("./core/interactions");
|
|
31
|
+
Object.defineProperty(exports, "detectInteractions", { enumerable: true, get: function () { return interactions_2.detectInteractions; } });
|
|
32
|
+
var pillars_2 = require("./core/pillars");
|
|
33
|
+
Object.defineProperty(exports, "generatePillarsFromSolar", { enumerable: true, get: function () { return pillars_2.generatePillarsFromSolar; } });
|
|
34
|
+
Object.defineProperty(exports, "getStemInfo", { enumerable: true, get: function () { return pillars_2.getStemInfo; } });
|
|
35
|
+
Object.defineProperty(exports, "getMainQi", { enumerable: true, get: function () { return pillars_2.getMainQi; } });
|
|
36
|
+
var cycles_2 = require("./core/cycles");
|
|
37
|
+
Object.defineProperty(exports, "calculateDaYun", { enumerable: true, get: function () { return cycles_2.calculateDaYun; } });
|
|
38
|
+
/**
|
|
39
|
+
* calculateBaziChart
|
|
40
|
+
*
|
|
41
|
+
* The main entry point. Takes a birth configuration and returns a fully
|
|
42
|
+
* calculated Bazi chart with Four Pillars, Da Yun cycles, and branch interactions.
|
|
43
|
+
*
|
|
44
|
+
* True Solar Time correction is applied automatically if longitude is provided.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const chart = calculateBaziChart({
|
|
49
|
+
* year: 1998, month: 12, day: 13, hour: 12, minute: 0,
|
|
50
|
+
* gender: 'female', longitude: 116.39, timezone: 8,
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* console.log(chart.pillars.year.stem); // '戊'
|
|
54
|
+
* console.log(chart.pillars.year.branch); // '寅'
|
|
55
|
+
* console.log(chart.daYun.cycles[0].ganZhi); // First major cycle
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
function calculateBaziChart(input) {
|
|
59
|
+
var _a, _b, _c, _d, _e, _f;
|
|
60
|
+
let solarYear = input.year;
|
|
61
|
+
let solarMonth = input.month;
|
|
62
|
+
let solarDay = input.day;
|
|
63
|
+
let solarHour = input.hour;
|
|
64
|
+
let solarMinute = (_a = input.minute) !== null && _a !== void 0 ? _a : 0;
|
|
65
|
+
let solarSecond = 0;
|
|
66
|
+
let solarTimeInfo = null;
|
|
67
|
+
// ── 1. Lunar → Solar Calendar Conversion ────────────────────────────────
|
|
68
|
+
if (input.calendarType === 'lunar') {
|
|
69
|
+
// lunar-javascript: negative month represents a leap month
|
|
70
|
+
const lunarMonth = input.isLeapMonth ? -input.month : input.month;
|
|
71
|
+
const lunar = lunar_javascript_1.Lunar.fromYmd(input.year, lunarMonth, input.day);
|
|
72
|
+
const solar = lunar.getSolar();
|
|
73
|
+
solarYear = solar.getYear();
|
|
74
|
+
solarMonth = solar.getMonth();
|
|
75
|
+
solarDay = solar.getDay();
|
|
76
|
+
}
|
|
77
|
+
// ── 2. True Solar Time Correction ───────────────────────────────────────
|
|
78
|
+
const enableTST = ((_b = input.enableTrueSolarTime) !== null && _b !== void 0 ? _b : true) &&
|
|
79
|
+
input.longitude !== undefined && input.longitude !== null &&
|
|
80
|
+
input.hour !== undefined;
|
|
81
|
+
if (enableTST) {
|
|
82
|
+
const detail = (0, true_solar_time_1.calculateTrueSolarTime)({
|
|
83
|
+
year: solarYear, month: solarMonth, day: solarDay,
|
|
84
|
+
hour: solarHour, minute: solarMinute,
|
|
85
|
+
timeZoneOffset: input.timezone,
|
|
86
|
+
dstOffset: (_c = input.dstOffset) !== null && _c !== void 0 ? _c : 0,
|
|
87
|
+
timeZoneId: input.timezoneId,
|
|
88
|
+
}, {
|
|
89
|
+
longitude: input.longitude,
|
|
90
|
+
});
|
|
91
|
+
const [dateStr, timeStr] = detail.trueSolarDateTime.split(' ');
|
|
92
|
+
const [yS, mS, dS] = dateStr.split('-');
|
|
93
|
+
const [hS, minS, secS] = timeStr.split(':');
|
|
94
|
+
solarYear = parseInt(yS, 10);
|
|
95
|
+
solarMonth = parseInt(mS, 10);
|
|
96
|
+
solarDay = parseInt(dS, 10);
|
|
97
|
+
solarHour = parseInt(hS, 10);
|
|
98
|
+
solarMinute = parseInt(minS, 10);
|
|
99
|
+
solarSecond = parseInt(secS, 10);
|
|
100
|
+
solarTimeInfo = {
|
|
101
|
+
trueSolarTime: detail.trueSolarTime.substring(0, 5),
|
|
102
|
+
trueSolarDateTime: detail.trueSolarDateTime,
|
|
103
|
+
solarDate: dateStr,
|
|
104
|
+
longitudeCorrectionMinutes: detail.longitudeCorrectionMinutes,
|
|
105
|
+
equationOfTimeMinutes: detail.equationOfTimeMinutes,
|
|
106
|
+
algorithm: detail.algorithm,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// ── 3. Generate Four Pillars ─────────────────────────────────────────────
|
|
110
|
+
const { pillars, eightChar, dayStem } = (0, pillars_1.generatePillarsFromSolar)(solarYear, solarMonth, solarDay, solarHour, solarMinute, solarSecond, (_d = input.dayBoundaryMode) !== null && _d !== void 0 ? _d : 'MIDNIGHT_00');
|
|
111
|
+
// ── 4. Day Master ────────────────────────────────────────────────────────
|
|
112
|
+
const dayMaster = (0, pillars_1.getStemInfo)(dayStem);
|
|
113
|
+
// ── 5. Da Yun Cycles ─────────────────────────────────────────────────────
|
|
114
|
+
const daYun = (0, cycles_1.calculateDaYun)(eightChar, input.gender, solarYear);
|
|
115
|
+
// ── 6. Branch Interactions ───────────────────────────────────────────────
|
|
116
|
+
const interactions = (0, interactions_1.detectInteractions)({
|
|
117
|
+
year: pillars.year.branch,
|
|
118
|
+
month: pillars.month.branch,
|
|
119
|
+
day: pillars.day.branch,
|
|
120
|
+
hour: (_f = (_e = pillars.hour) === null || _e === void 0 ? void 0 : _e.branch) !== null && _f !== void 0 ? _f : '',
|
|
121
|
+
});
|
|
122
|
+
return { pillars, dayMaster, daYun, interactions, solarTimeInfo };
|
|
123
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export type FiveElement = 'wood' | 'fire' | 'earth' | 'metal' | 'water';
|
|
2
|
+
export type Polarity = 'yang' | 'yin';
|
|
3
|
+
export type DayBoundaryMode = 'MIDNIGHT_00' | 'ZI_HOUR_23';
|
|
4
|
+
/** A single Heavenly Stem (天干) */
|
|
5
|
+
export interface StemInfo {
|
|
6
|
+
char: string;
|
|
7
|
+
pinyin: string;
|
|
8
|
+
element: FiveElement;
|
|
9
|
+
polarity: Polarity;
|
|
10
|
+
}
|
|
11
|
+
/** A single Earthly Branch (地支) */
|
|
12
|
+
export interface BranchInfo {
|
|
13
|
+
char: string;
|
|
14
|
+
element: FiveElement;
|
|
15
|
+
hiddenStems: string[];
|
|
16
|
+
mainQi: string;
|
|
17
|
+
}
|
|
18
|
+
/** One of the Four Pillars (一柱) */
|
|
19
|
+
export interface Pillar {
|
|
20
|
+
stem: string;
|
|
21
|
+
branch: string;
|
|
22
|
+
element: FiveElement;
|
|
23
|
+
}
|
|
24
|
+
/** The Four Pillars (四柱) */
|
|
25
|
+
export interface FourPillars {
|
|
26
|
+
year: Pillar;
|
|
27
|
+
month: Pillar;
|
|
28
|
+
day: Pillar;
|
|
29
|
+
hour: Pillar | null;
|
|
30
|
+
}
|
|
31
|
+
/** A single 10-year Major Luck Cycle (大运) */
|
|
32
|
+
export interface DaYunCycle {
|
|
33
|
+
index: number;
|
|
34
|
+
stem: string;
|
|
35
|
+
branch: string;
|
|
36
|
+
ganZhi: string;
|
|
37
|
+
startYear: number;
|
|
38
|
+
startAge: number;
|
|
39
|
+
}
|
|
40
|
+
/** Da Yun metadata */
|
|
41
|
+
export interface DaYunInfo {
|
|
42
|
+
cycles: DaYunCycle[];
|
|
43
|
+
isForward: boolean;
|
|
44
|
+
startYear: number;
|
|
45
|
+
startAge: number;
|
|
46
|
+
}
|
|
47
|
+
export type InteractionType = 'CLASH' | 'COMBINATION_2' | 'TRINE' | 'DIRECTIONAL' | 'PUNISHMENT' | 'DESTRUCTION' | 'HARM';
|
|
48
|
+
/** A detected branch interaction */
|
|
49
|
+
export interface BranchInteraction {
|
|
50
|
+
type: InteractionType;
|
|
51
|
+
branches: string[];
|
|
52
|
+
pillars: string[];
|
|
53
|
+
resultElement?: FiveElement;
|
|
54
|
+
description: string;
|
|
55
|
+
}
|
|
56
|
+
export interface SolarTimeInfo {
|
|
57
|
+
trueSolarTime: string;
|
|
58
|
+
trueSolarDateTime: string;
|
|
59
|
+
solarDate: string;
|
|
60
|
+
longitudeCorrectionMinutes: number;
|
|
61
|
+
equationOfTimeMinutes: number;
|
|
62
|
+
algorithm: string;
|
|
63
|
+
}
|
|
64
|
+
/** Full output of calculateBaziChart() */
|
|
65
|
+
export interface BaziChart {
|
|
66
|
+
pillars: FourPillars;
|
|
67
|
+
dayMaster: StemInfo;
|
|
68
|
+
daYun: DaYunInfo;
|
|
69
|
+
interactions: BranchInteraction[];
|
|
70
|
+
solarTimeInfo: SolarTimeInfo | null;
|
|
71
|
+
}
|
|
72
|
+
export interface BaziInput {
|
|
73
|
+
year: number;
|
|
74
|
+
month: number;
|
|
75
|
+
day: number;
|
|
76
|
+
hour?: number;
|
|
77
|
+
minute?: number;
|
|
78
|
+
longitude?: number;
|
|
79
|
+
timezone?: number;
|
|
80
|
+
timezoneId?: string;
|
|
81
|
+
dstOffset?: number;
|
|
82
|
+
gender: 'male' | 'female';
|
|
83
|
+
calendarType?: 'solar' | 'lunar';
|
|
84
|
+
isLeapMonth?: boolean;
|
|
85
|
+
enableTrueSolarTime?: boolean;
|
|
86
|
+
dayBoundaryMode?: DayBoundaryMode;
|
|
87
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// @openfate/bazi-engine — Public Type Definitions
|
|
4
|
+
// ============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openfate/bazi-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Accurate Four Pillars (八字) chart engine with True Solar Time correction, Da Yun cycles, and interaction detection. Extracted from the OpenFate.ai core engine.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"test": "tsx --test tests/*.test.ts",
|
|
15
|
+
"release": "npm version patch && git push --follow-tags",
|
|
16
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
17
|
+
},
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "OpenFate Engineering",
|
|
20
|
+
"url": "https://openfate.ai"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://openfate.ai",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/openfate-ai/bazi-engine.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"bazi",
|
|
29
|
+
"八字",
|
|
30
|
+
"四柱",
|
|
31
|
+
"chinese-astrology",
|
|
32
|
+
"four-pillars",
|
|
33
|
+
"da-yun",
|
|
34
|
+
"true-solar-time",
|
|
35
|
+
"openfate"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@openfate/true-solar-time": "^4.0.0",
|
|
40
|
+
"lunar-javascript": "^1.7.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"tsx": "^4.0.0",
|
|
45
|
+
"typescript": "^5.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|