@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 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
+ ![NPM Version](https://img.shields.io/npm/v/@openfate/bazi-engine)
4
+ ![License](https://img.shields.io/npm/l/@openfate/bazi-engine)
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
+ }
@@ -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
+ }
@@ -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
+ }