@gracefullight/saju 0.4.2 → 0.6.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/dist/__tests__/relations.test.js +6 -6
- package/dist/__tests__/saju.test.js +3 -3
- package/dist/__tests__/sinsals.test.js +5 -5
- package/dist/__tests__/solar-terms.test.js +26 -16
- package/dist/__tests__/strength.test.js +2 -2
- package/dist/__tests__/ten-gods.test.js +52 -38
- package/dist/__tests__/twelve-stages.test.js +55 -51
- package/dist/__tests__/yongshen.test.js +3 -3
- package/dist/core/luck.d.ts.map +1 -1
- package/dist/core/luck.js +2 -1
- package/dist/core/relations.d.ts +35 -20
- package/dist/core/relations.d.ts.map +1 -1
- package/dist/core/relations.js +105 -33
- package/dist/core/sinsals.d.ts +12 -6
- package/dist/core/sinsals.d.ts.map +1 -1
- package/dist/core/sinsals.js +19 -8
- package/dist/core/solar-terms.d.ts +19 -105
- package/dist/core/solar-terms.d.ts.map +1 -1
- package/dist/core/solar-terms.js +56 -29
- package/dist/core/strength.d.ts +7 -3
- package/dist/core/strength.d.ts.map +1 -1
- package/dist/core/strength.js +52 -28
- package/dist/core/ten-gods.d.ts +27 -64
- package/dist/core/ten-gods.d.ts.map +1 -1
- package/dist/core/ten-gods.js +65 -119
- package/dist/core/twelve-stages.d.ts +12 -12
- package/dist/core/twelve-stages.d.ts.map +1 -1
- package/dist/core/twelve-stages.js +28 -24
- package/dist/core/yongshen.d.ts +15 -6
- package/dist/core/yongshen.d.ts.map +1 -1
- package/dist/core/yongshen.js +67 -38
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/types/common.d.ts +8 -0
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -48,7 +48,7 @@ describe("relations", () => {
|
|
|
48
48
|
describe("analyzeRelations", () => {
|
|
49
49
|
it("finds stem combinations in four pillars", () => {
|
|
50
50
|
const result = analyzeRelations("甲子", "己丑", "丙寅", "辛亥");
|
|
51
|
-
const stemCombos = result.combinations.filter((c) => c.type === "
|
|
51
|
+
const stemCombos = result.combinations.filter((c) => c.type.key === "stemCombination");
|
|
52
52
|
expect(stemCombos.length).toBeGreaterThan(0);
|
|
53
53
|
});
|
|
54
54
|
it("finds branch clashes in four pillars", () => {
|
|
@@ -57,23 +57,23 @@ describe("relations", () => {
|
|
|
57
57
|
});
|
|
58
58
|
it("finds 삼합 (triple combination)", () => {
|
|
59
59
|
const result = analyzeRelations("甲寅", "丙午", "戊戌", "庚子");
|
|
60
|
-
const tripleCombos = result.combinations.filter((c) => c.type === "
|
|
60
|
+
const tripleCombos = result.combinations.filter((c) => c.type.key === "tripleCombination");
|
|
61
61
|
expect(tripleCombos.length).toBeGreaterThan(0);
|
|
62
|
-
expect(tripleCombos[0].resultElement).toBe("fire");
|
|
62
|
+
expect(tripleCombos[0].resultElement.key).toBe("fire");
|
|
63
63
|
});
|
|
64
64
|
it("finds 육합 (six combination)", () => {
|
|
65
65
|
const result = analyzeRelations("甲子", "乙丑", "丙寅", "丁卯");
|
|
66
|
-
const sixCombos = result.combinations.filter((c) => c.type === "
|
|
66
|
+
const sixCombos = result.combinations.filter((c) => c.type.key === "sixCombination");
|
|
67
67
|
expect(sixCombos.length).toBeGreaterThan(0);
|
|
68
68
|
});
|
|
69
69
|
it("finds 형 (punishment)", () => {
|
|
70
70
|
const result = analyzeRelations("甲寅", "丙巳", "戊申", "庚子");
|
|
71
71
|
expect(result.punishments.length).toBeGreaterThan(0);
|
|
72
|
-
expect(result.punishments[0].punishmentType).toBe("
|
|
72
|
+
expect(result.punishments[0].punishmentType.key).toBe("ungrateful");
|
|
73
73
|
});
|
|
74
74
|
it("includes transformStatus and transformReason for combinations", () => {
|
|
75
75
|
const result = analyzeRelations("甲子", "己丑", "丙寅", "辛亥");
|
|
76
|
-
const stemCombos = result.combinations.filter((c) => c.type === "
|
|
76
|
+
const stemCombos = result.combinations.filter((c) => c.type.key === "stemCombination");
|
|
77
77
|
expect(stemCombos.length).toBeGreaterThan(0);
|
|
78
78
|
expect(stemCombos[0]).toHaveProperty("transformStatus");
|
|
79
79
|
expect(stemCombos[0]).toHaveProperty("transformReason");
|
|
@@ -32,8 +32,8 @@ describe("getSaju integration", () => {
|
|
|
32
32
|
gender: "female",
|
|
33
33
|
preset: STANDARD_PRESET,
|
|
34
34
|
});
|
|
35
|
-
expect(result.solarTerms.current.
|
|
36
|
-
expect(result.solarTerms.next.
|
|
35
|
+
expect(result.solarTerms.current.korean).toBe("소한");
|
|
36
|
+
expect(result.solarTerms.next.korean).toBe("대한");
|
|
37
37
|
expect(result.solarTerms.daysSinceCurrent).toBeGreaterThanOrEqual(0);
|
|
38
38
|
expect(result.solarTerms.daysUntilNext).toBeGreaterThan(0);
|
|
39
39
|
expect(result.solarTerms.currentDate).toBeDefined();
|
|
@@ -99,7 +99,7 @@ describe("getSaju integration", () => {
|
|
|
99
99
|
preset: STANDARD_PRESET,
|
|
100
100
|
});
|
|
101
101
|
expect(result.tenGods.dayMaster).toBe("丁");
|
|
102
|
-
expect(result.tenGods.day.stem.tenGod).toBe("
|
|
102
|
+
expect(result.tenGods.day.stem.tenGod.key).toBe("dayMaster");
|
|
103
103
|
});
|
|
104
104
|
it("calculates major luck start age based on actual solar terms (not fixed 10)", async () => {
|
|
105
105
|
const adapter = await createLuxonAdapter();
|
|
@@ -4,17 +4,17 @@ describe("sinsals", () => {
|
|
|
4
4
|
describe("analyzeSinsals", () => {
|
|
5
5
|
it("should detect peach blossom (도화살)", () => {
|
|
6
6
|
const result = analyzeSinsals("甲寅", "丙寅", "戊卯", "庚午");
|
|
7
|
-
const peachBlossoms = result.matches.filter((m) => m.sinsal === "peachBlossom");
|
|
7
|
+
const peachBlossoms = result.matches.filter((m) => m.sinsal.key === "peachBlossom");
|
|
8
8
|
expect(peachBlossoms.length).toBeGreaterThan(0);
|
|
9
9
|
});
|
|
10
10
|
it("should detect sky horse (역마살)", () => {
|
|
11
11
|
const result = analyzeSinsals("甲寅", "丙寅", "戊申", "庚午");
|
|
12
|
-
const skyHorses = result.matches.filter((m) => m.sinsal === "skyHorse");
|
|
12
|
+
const skyHorses = result.matches.filter((m) => m.sinsal.key === "skyHorse");
|
|
13
13
|
expect(skyHorses.length).toBeGreaterThan(0);
|
|
14
14
|
});
|
|
15
15
|
it("should detect flowery canopy (화개살)", () => {
|
|
16
16
|
const result = analyzeSinsals("甲寅", "丙戌", "戊子", "庚午");
|
|
17
|
-
const floweryCanopies = result.matches.filter((m) => m.sinsal === "floweryCanopy");
|
|
17
|
+
const floweryCanopies = result.matches.filter((m) => m.sinsal.key === "floweryCanopy");
|
|
18
18
|
expect(floweryCanopies.length).toBeGreaterThan(0);
|
|
19
19
|
});
|
|
20
20
|
it("should return summary grouped by sinsal", () => {
|
|
@@ -29,14 +29,14 @@ describe("sinsals", () => {
|
|
|
29
29
|
const result = analyzeSinsals("甲子", "丙寅", "戊辰", "庚午");
|
|
30
30
|
const seen = new Set();
|
|
31
31
|
for (const match of result.matches) {
|
|
32
|
-
const key = `${match.sinsal}-${match.position}`;
|
|
32
|
+
const key = `${match.sinsal.key}-${match.position}`;
|
|
33
33
|
expect(seen.has(key)).toBe(false);
|
|
34
34
|
seen.add(key);
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
it("should detect sky noble (천을귀인)", () => {
|
|
38
38
|
const result = analyzeSinsals("甲丑", "丙寅", "甲子", "庚午");
|
|
39
|
-
const skyNobles = result.matches.filter((m) => m.sinsal === "skyNoble");
|
|
39
|
+
const skyNobles = result.matches.filter((m) => m.sinsal.key === "skyNoble");
|
|
40
40
|
expect(skyNobles.length).toBeGreaterThan(0);
|
|
41
41
|
});
|
|
42
42
|
});
|
|
@@ -15,51 +15,61 @@ describe("solar-terms", async () => {
|
|
|
15
15
|
}
|
|
16
16
|
});
|
|
17
17
|
it("should start with 소한 at 285°", () => {
|
|
18
|
-
expect(SOLAR_TERMS[0]).toEqual({
|
|
18
|
+
expect(SOLAR_TERMS[0]).toEqual({
|
|
19
|
+
key: "minorCold",
|
|
20
|
+
korean: "소한",
|
|
21
|
+
hanja: "小寒",
|
|
22
|
+
longitude: 285,
|
|
23
|
+
});
|
|
19
24
|
});
|
|
20
25
|
it("should have 입춘 at 315°", () => {
|
|
21
|
-
const ipchun = SOLAR_TERMS.find((t) => t.
|
|
22
|
-
expect(ipchun).toEqual({
|
|
26
|
+
const ipchun = SOLAR_TERMS.find((t) => t.korean === "입춘");
|
|
27
|
+
expect(ipchun).toEqual({
|
|
28
|
+
key: "springBegins",
|
|
29
|
+
korean: "입춘",
|
|
30
|
+
hanja: "立春",
|
|
31
|
+
longitude: 315,
|
|
32
|
+
});
|
|
23
33
|
});
|
|
24
34
|
});
|
|
25
35
|
describe("analyzeSolarTerms", () => {
|
|
26
36
|
it("should return current and next solar terms for mid-January", () => {
|
|
27
37
|
const dt = DateTime.fromObject({ year: 2024, month: 1, day: 15 }, { zone: "Asia/Seoul" });
|
|
28
38
|
const result = analyzeSolarTerms(adapter, dt);
|
|
29
|
-
expect(result.current.
|
|
30
|
-
expect(result.next.
|
|
39
|
+
expect(result.current.korean).toBe("소한");
|
|
40
|
+
expect(result.next.korean).toBe("대한");
|
|
31
41
|
expect(result.daysSinceCurrent).toBeGreaterThanOrEqual(0);
|
|
32
42
|
expect(result.daysUntilNext).toBeGreaterThan(0);
|
|
33
43
|
});
|
|
34
44
|
it("should return current and next solar terms for early February", () => {
|
|
35
45
|
const dt = DateTime.fromObject({ year: 2024, month: 2, day: 3 }, { zone: "Asia/Seoul" });
|
|
36
46
|
const result = analyzeSolarTerms(adapter, dt);
|
|
37
|
-
expect(result.current.
|
|
38
|
-
expect(result.next.
|
|
47
|
+
expect(result.current.korean).toBe("대한");
|
|
48
|
+
expect(result.next.korean).toBe("입춘");
|
|
39
49
|
});
|
|
40
50
|
it("should return current and next solar terms for summer solstice period", () => {
|
|
41
51
|
const dt = DateTime.fromObject({ year: 2024, month: 6, day: 25 }, { zone: "Asia/Seoul" });
|
|
42
52
|
const result = analyzeSolarTerms(adapter, dt);
|
|
43
|
-
expect(result.current.
|
|
44
|
-
expect(result.next.
|
|
53
|
+
expect(result.current.korean).toBe("하지");
|
|
54
|
+
expect(result.next.korean).toBe("소서");
|
|
45
55
|
});
|
|
46
56
|
it("should return current and next solar terms for winter solstice period", () => {
|
|
47
57
|
const dt = DateTime.fromObject({ year: 2024, month: 12, day: 25 }, { zone: "Asia/Seoul" });
|
|
48
58
|
const result = analyzeSolarTerms(adapter, dt);
|
|
49
|
-
expect(result.current.
|
|
50
|
-
expect(result.next.
|
|
59
|
+
expect(result.current.korean).toBe("동지");
|
|
60
|
+
expect(result.next.korean).toBe("소한");
|
|
51
61
|
});
|
|
52
62
|
it("should calculate days since current term correctly", () => {
|
|
53
63
|
const dt = DateTime.fromObject({ year: 2024, month: 3, day: 25 }, { zone: "Asia/Seoul" });
|
|
54
64
|
const result = analyzeSolarTerms(adapter, dt);
|
|
55
|
-
expect(result.current.
|
|
65
|
+
expect(result.current.korean).toBe("춘분");
|
|
56
66
|
expect(result.daysSinceCurrent).toBeGreaterThanOrEqual(0);
|
|
57
67
|
expect(result.daysSinceCurrent).toBeLessThan(16);
|
|
58
68
|
});
|
|
59
69
|
it("should calculate days until next term correctly", () => {
|
|
60
70
|
const dt = DateTime.fromObject({ year: 2024, month: 4, day: 1 }, { zone: "Asia/Seoul" });
|
|
61
71
|
const result = analyzeSolarTerms(adapter, dt);
|
|
62
|
-
expect(result.next.
|
|
72
|
+
expect(result.next.korean).toBe("청명");
|
|
63
73
|
expect(result.daysUntilNext).toBeGreaterThan(0);
|
|
64
74
|
expect(result.daysUntilNext).toBeLessThan(16);
|
|
65
75
|
});
|
|
@@ -85,7 +95,7 @@ describe("solar-terms", async () => {
|
|
|
85
95
|
});
|
|
86
96
|
it("should have all terms in order", () => {
|
|
87
97
|
const terms = getSolarTermsForYear(adapter, 2024, "Asia/Seoul");
|
|
88
|
-
const names = terms.map((t) => t.term.
|
|
98
|
+
const names = terms.map((t) => t.term.korean);
|
|
89
99
|
expect(names[0]).toBe("소한");
|
|
90
100
|
expect(names[2]).toBe("입춘");
|
|
91
101
|
expect(names[5]).toBe("춘분");
|
|
@@ -105,14 +115,14 @@ describe("solar-terms", async () => {
|
|
|
105
115
|
});
|
|
106
116
|
it("should have 입춘 around February 4", () => {
|
|
107
117
|
const terms = getSolarTermsForYear(adapter, 2024, "Asia/Seoul");
|
|
108
|
-
const ipchun = terms.find((t) => t.term.
|
|
118
|
+
const ipchun = terms.find((t) => t.term.korean === "입춘");
|
|
109
119
|
expect(ipchun?.date.month).toBe(2);
|
|
110
120
|
expect(ipchun?.date.day).toBeGreaterThanOrEqual(3);
|
|
111
121
|
expect(ipchun?.date.day).toBeLessThanOrEqual(5);
|
|
112
122
|
});
|
|
113
123
|
it("should have 하지 around June 21", () => {
|
|
114
124
|
const terms = getSolarTermsForYear(adapter, 2024, "Asia/Seoul");
|
|
115
|
-
const haji = terms.find((t) => t.term.
|
|
125
|
+
const haji = terms.find((t) => t.term.korean === "하지");
|
|
116
126
|
expect(haji?.date.month).toBe(6);
|
|
117
127
|
expect(haji?.date.day).toBeGreaterThanOrEqual(20);
|
|
118
128
|
expect(haji?.date.day).toBeLessThanOrEqual(22);
|
|
@@ -29,11 +29,11 @@ describe("strength", () => {
|
|
|
29
29
|
});
|
|
30
30
|
it("returns 신강 for strong day master", () => {
|
|
31
31
|
const result = analyzeStrength("甲子", "甲寅", "甲寅", "甲寅");
|
|
32
|
-
expect(["신강", "태강", "극왕", "중화신강"]).toContain(result.level);
|
|
32
|
+
expect(["신강", "태강", "극왕", "중화신강"]).toContain(result.level.korean);
|
|
33
33
|
});
|
|
34
34
|
it("returns 신약 for weak day master", () => {
|
|
35
35
|
const result = analyzeStrength("庚申", "庚申", "甲申", "庚申");
|
|
36
|
-
expect(["신약", "태약", "극약", "중화신약"]).toContain(result.level);
|
|
36
|
+
expect(["신약", "태약", "극약", "중화신약"]).toContain(result.level.korean);
|
|
37
37
|
});
|
|
38
38
|
it("includes description with day master info", () => {
|
|
39
39
|
const result = analyzeStrength("甲子", "丙寅", "甲辰", "乙亥");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { analyzeTenGods, countElements, countTenGods, getBranchElement, getHiddenStems, getStemElement, getStemPolarity,
|
|
2
|
+
import { analyzeTenGods, countElements, countTenGods, getBranchElement, getHiddenStems, getStemElement, getStemPolarity, getTenGodKey, getTenGodLabel, } from "../core/ten-gods";
|
|
3
3
|
describe("ten-gods", () => {
|
|
4
4
|
describe("getStemElement", () => {
|
|
5
5
|
it("returns correct element for each stem", () => {
|
|
@@ -42,56 +42,70 @@ describe("ten-gods", () => {
|
|
|
42
42
|
expect(getHiddenStems("午")).toEqual(["丁", "己"]);
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
|
-
describe("
|
|
46
|
-
it("correctly identifies
|
|
47
|
-
expect(
|
|
48
|
-
expect(
|
|
45
|
+
describe("getTenGodKey", () => {
|
|
46
|
+
it("correctly identifies companion (same element, same polarity)", () => {
|
|
47
|
+
expect(getTenGodKey("甲", "甲")).toBe("companion");
|
|
48
|
+
expect(getTenGodKey("乙", "乙")).toBe("companion");
|
|
49
49
|
});
|
|
50
|
-
it("correctly identifies
|
|
51
|
-
expect(
|
|
52
|
-
expect(
|
|
50
|
+
it("correctly identifies robWealth (same element, different polarity)", () => {
|
|
51
|
+
expect(getTenGodKey("甲", "乙")).toBe("robWealth");
|
|
52
|
+
expect(getTenGodKey("乙", "甲")).toBe("robWealth");
|
|
53
53
|
});
|
|
54
|
-
it("correctly identifies
|
|
55
|
-
expect(
|
|
56
|
-
expect(
|
|
54
|
+
it("correctly identifies eatingGod (I generate, same polarity)", () => {
|
|
55
|
+
expect(getTenGodKey("甲", "丙")).toBe("eatingGod");
|
|
56
|
+
expect(getTenGodKey("丙", "戊")).toBe("eatingGod");
|
|
57
57
|
});
|
|
58
|
-
it("correctly identifies
|
|
59
|
-
expect(
|
|
60
|
-
expect(
|
|
58
|
+
it("correctly identifies hurtingOfficer (I generate, different polarity)", () => {
|
|
59
|
+
expect(getTenGodKey("甲", "丁")).toBe("hurtingOfficer");
|
|
60
|
+
expect(getTenGodKey("丙", "己")).toBe("hurtingOfficer");
|
|
61
61
|
});
|
|
62
|
-
it("correctly identifies
|
|
63
|
-
expect(
|
|
64
|
-
expect(
|
|
62
|
+
it("correctly identifies indirectWealth (I control, same polarity)", () => {
|
|
63
|
+
expect(getTenGodKey("甲", "戊")).toBe("indirectWealth");
|
|
64
|
+
expect(getTenGodKey("丙", "庚")).toBe("indirectWealth");
|
|
65
65
|
});
|
|
66
|
-
it("correctly identifies
|
|
67
|
-
expect(
|
|
68
|
-
expect(
|
|
66
|
+
it("correctly identifies directWealth (I control, different polarity)", () => {
|
|
67
|
+
expect(getTenGodKey("甲", "己")).toBe("directWealth");
|
|
68
|
+
expect(getTenGodKey("丙", "辛")).toBe("directWealth");
|
|
69
69
|
});
|
|
70
|
-
it("correctly identifies
|
|
71
|
-
expect(
|
|
72
|
-
expect(
|
|
70
|
+
it("correctly identifies sevenKillings (controls me, same polarity)", () => {
|
|
71
|
+
expect(getTenGodKey("甲", "庚")).toBe("sevenKillings");
|
|
72
|
+
expect(getTenGodKey("丙", "壬")).toBe("sevenKillings");
|
|
73
73
|
});
|
|
74
|
-
it("correctly identifies
|
|
75
|
-
expect(
|
|
76
|
-
expect(
|
|
74
|
+
it("correctly identifies directOfficer (controls me, different polarity)", () => {
|
|
75
|
+
expect(getTenGodKey("甲", "辛")).toBe("directOfficer");
|
|
76
|
+
expect(getTenGodKey("丙", "癸")).toBe("directOfficer");
|
|
77
77
|
});
|
|
78
|
-
it("correctly identifies
|
|
79
|
-
expect(
|
|
80
|
-
expect(
|
|
78
|
+
it("correctly identifies indirectSeal (generates me, same polarity)", () => {
|
|
79
|
+
expect(getTenGodKey("甲", "壬")).toBe("indirectSeal");
|
|
80
|
+
expect(getTenGodKey("丙", "甲")).toBe("indirectSeal");
|
|
81
81
|
});
|
|
82
|
-
it("correctly identifies
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
82
|
+
it("correctly identifies directSeal (generates me, different polarity)", () => {
|
|
83
|
+
expect(getTenGodKey("甲", "癸")).toBe("directSeal");
|
|
84
|
+
expect(getTenGodKey("丙", "乙")).toBe("directSeal");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("getTenGodLabel", () => {
|
|
88
|
+
it("returns correct Korean labels", () => {
|
|
89
|
+
expect(getTenGodLabel("companion").korean).toBe("비견");
|
|
90
|
+
expect(getTenGodLabel("robWealth").korean).toBe("겁재");
|
|
91
|
+
expect(getTenGodLabel("eatingGod").korean).toBe("식신");
|
|
92
|
+
expect(getTenGodLabel("hurtingOfficer").korean).toBe("상관");
|
|
93
|
+
expect(getTenGodLabel("indirectWealth").korean).toBe("편재");
|
|
94
|
+
expect(getTenGodLabel("directWealth").korean).toBe("정재");
|
|
95
|
+
expect(getTenGodLabel("sevenKillings").korean).toBe("편관");
|
|
96
|
+
expect(getTenGodLabel("directOfficer").korean).toBe("정관");
|
|
97
|
+
expect(getTenGodLabel("indirectSeal").korean).toBe("편인");
|
|
98
|
+
expect(getTenGodLabel("directSeal").korean).toBe("정인");
|
|
85
99
|
});
|
|
86
100
|
});
|
|
87
101
|
describe("analyzeTenGods", () => {
|
|
88
102
|
it("analyzes four pillars correctly", () => {
|
|
89
103
|
const result = analyzeTenGods("甲子", "丙寅", "甲辰", "乙亥");
|
|
90
104
|
expect(result.dayMaster).toBe("甲");
|
|
91
|
-
expect(result.year.stem.tenGod).toBe("
|
|
92
|
-
expect(result.month.stem.tenGod).toBe("
|
|
93
|
-
expect(result.day.stem.tenGod).toBe("
|
|
94
|
-
expect(result.hour.stem.tenGod).toBe("
|
|
105
|
+
expect(result.year.stem.tenGod.key).toBe("companion");
|
|
106
|
+
expect(result.month.stem.tenGod.key).toBe("eatingGod");
|
|
107
|
+
expect(result.day.stem.tenGod.key).toBe("dayMaster");
|
|
108
|
+
expect(result.hour.stem.tenGod.key).toBe("robWealth");
|
|
95
109
|
});
|
|
96
110
|
it("includes hidden stems analysis", () => {
|
|
97
111
|
const result = analyzeTenGods("甲子", "丙寅", "甲辰", "乙亥");
|
|
@@ -104,8 +118,8 @@ describe("ten-gods", () => {
|
|
|
104
118
|
it("counts ten gods correctly", () => {
|
|
105
119
|
const analysis = analyzeTenGods("甲子", "丙寅", "甲辰", "乙亥");
|
|
106
120
|
const counts = countTenGods(analysis);
|
|
107
|
-
expect(counts
|
|
108
|
-
expect(counts
|
|
121
|
+
expect(counts.companion).toBeGreaterThanOrEqual(1);
|
|
122
|
+
expect(counts.eatingGod).toBeGreaterThanOrEqual(1);
|
|
109
123
|
});
|
|
110
124
|
});
|
|
111
125
|
describe("countElements", () => {
|
|
@@ -1,86 +1,90 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { analyzeTwelveStages,
|
|
2
|
+
import { analyzeTwelveStages, getTwelveStageLabel, TWELVE_STAGES } from "../core/twelve-stages";
|
|
3
3
|
describe("twelve-stages", () => {
|
|
4
|
-
describe("
|
|
4
|
+
describe("analyzeTwelveStages", () => {
|
|
5
|
+
it("should analyze all four pillars", () => {
|
|
6
|
+
const result = analyzeTwelveStages("甲子", "丙寅", "戊辰", "庚午");
|
|
7
|
+
expect(result.year).toBeDefined();
|
|
8
|
+
expect(result.month).toBeDefined();
|
|
9
|
+
expect(result.day).toBeDefined();
|
|
10
|
+
expect(result.hour).toBeDefined();
|
|
11
|
+
expect(TWELVE_STAGES).toContain(result.year.key);
|
|
12
|
+
expect(TWELVE_STAGES).toContain(result.month.key);
|
|
13
|
+
expect(TWELVE_STAGES).toContain(result.day.key);
|
|
14
|
+
expect(TWELVE_STAGES).toContain(result.hour.key);
|
|
15
|
+
});
|
|
16
|
+
it("should correctly analyze 戊 day master", () => {
|
|
17
|
+
const result = analyzeTwelveStages("甲子", "丙寅", "戊辰", "庚午");
|
|
18
|
+
expect(result.month.key).toBe("longLife");
|
|
19
|
+
expect(result.hour.key).toBe("imperial");
|
|
20
|
+
});
|
|
5
21
|
it("should return longLife for 甲 at 亥", () => {
|
|
6
|
-
|
|
22
|
+
const result = analyzeTwelveStages("甲亥", "甲亥", "甲亥", "甲亥");
|
|
23
|
+
expect(result.year.key).toBe("longLife");
|
|
7
24
|
});
|
|
8
25
|
it("should return imperial for 甲 at 卯", () => {
|
|
9
|
-
|
|
26
|
+
const result = analyzeTwelveStages("甲卯", "甲卯", "甲卯", "甲卯");
|
|
27
|
+
expect(result.year.key).toBe("imperial");
|
|
10
28
|
});
|
|
11
29
|
it("should return tomb for 甲 at 未", () => {
|
|
12
|
-
|
|
30
|
+
const result = analyzeTwelveStages("甲未", "甲未", "甲未", "甲未");
|
|
31
|
+
expect(result.year.key).toBe("tomb");
|
|
13
32
|
});
|
|
14
33
|
it("should return longLife for 乙 at 午", () => {
|
|
15
|
-
|
|
34
|
+
const result = analyzeTwelveStages("乙午", "乙午", "乙午", "乙午");
|
|
35
|
+
expect(result.year.key).toBe("longLife");
|
|
16
36
|
});
|
|
17
37
|
it("should return imperial for 乙 at 寅", () => {
|
|
18
|
-
|
|
38
|
+
const result = analyzeTwelveStages("乙寅", "乙寅", "乙寅", "乙寅");
|
|
39
|
+
expect(result.year.key).toBe("imperial");
|
|
19
40
|
});
|
|
20
41
|
it("should return longLife for 丙 at 寅", () => {
|
|
21
|
-
|
|
42
|
+
const result = analyzeTwelveStages("丙寅", "丙寅", "丙寅", "丙寅");
|
|
43
|
+
expect(result.year.key).toBe("longLife");
|
|
22
44
|
});
|
|
23
45
|
it("should return imperial for 丙 at 午", () => {
|
|
24
|
-
|
|
46
|
+
const result = analyzeTwelveStages("丙午", "丙午", "丙午", "丙午");
|
|
47
|
+
expect(result.year.key).toBe("imperial");
|
|
25
48
|
});
|
|
26
49
|
it("should return longLife for 庚 at 巳", () => {
|
|
27
|
-
|
|
50
|
+
const result = analyzeTwelveStages("庚巳", "庚巳", "庚巳", "庚巳");
|
|
51
|
+
expect(result.year.key).toBe("longLife");
|
|
28
52
|
});
|
|
29
53
|
it("should return imperial for 庚 at 酉", () => {
|
|
30
|
-
|
|
54
|
+
const result = analyzeTwelveStages("庚酉", "庚酉", "庚酉", "庚酉");
|
|
55
|
+
expect(result.year.key).toBe("imperial");
|
|
31
56
|
});
|
|
32
57
|
it("should return longLife for 壬 at 申", () => {
|
|
33
|
-
|
|
58
|
+
const result = analyzeTwelveStages("壬申", "壬申", "壬申", "壬申");
|
|
59
|
+
expect(result.year.key).toBe("longLife");
|
|
34
60
|
});
|
|
35
61
|
it("should return imperial for 壬 at 子", () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
it("should throw error for invalid stem", () => {
|
|
39
|
-
expect(() => getTwelveStage("X", "子")).toThrow("Invalid stem: X");
|
|
40
|
-
});
|
|
41
|
-
it("should throw error for invalid branch", () => {
|
|
42
|
-
expect(() => getTwelveStage("甲", "X")).toThrow("Invalid branch: X");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
describe("analyzeTwelveStages", () => {
|
|
46
|
-
it("should analyze all four pillars", () => {
|
|
47
|
-
const result = analyzeTwelveStages("甲子", "丙寅", "戊辰", "庚午");
|
|
48
|
-
expect(result.year).toBeDefined();
|
|
49
|
-
expect(result.month).toBeDefined();
|
|
50
|
-
expect(result.day).toBeDefined();
|
|
51
|
-
expect(result.hour).toBeDefined();
|
|
52
|
-
expect(TWELVE_STAGES).toContain(result.year);
|
|
53
|
-
expect(TWELVE_STAGES).toContain(result.month);
|
|
54
|
-
expect(TWELVE_STAGES).toContain(result.day);
|
|
55
|
-
expect(TWELVE_STAGES).toContain(result.hour);
|
|
56
|
-
});
|
|
57
|
-
it("should correctly analyze 戊 day master", () => {
|
|
58
|
-
const result = analyzeTwelveStages("甲子", "丙寅", "戊辰", "庚午");
|
|
59
|
-
expect(result.month).toBe("longLife");
|
|
60
|
-
expect(result.hour).toBe("imperial");
|
|
62
|
+
const result = analyzeTwelveStages("壬子", "壬子", "壬子", "壬子");
|
|
63
|
+
expect(result.year.key).toBe("imperial");
|
|
61
64
|
});
|
|
62
65
|
});
|
|
63
|
-
describe("
|
|
66
|
+
describe("getTwelveStageLabel", () => {
|
|
64
67
|
it("should have info for all twelve stages", () => {
|
|
65
68
|
for (const stage of TWELVE_STAGES) {
|
|
66
|
-
|
|
67
|
-
expect(
|
|
68
|
-
expect(
|
|
69
|
-
expect(
|
|
70
|
-
expect(
|
|
69
|
+
const label = getTwelveStageLabel(stage);
|
|
70
|
+
expect(label.key).toBe(stage);
|
|
71
|
+
expect(label.korean).toBeDefined();
|
|
72
|
+
expect(label.hanja).toBeDefined();
|
|
73
|
+
expect(label.meaning).toBeDefined();
|
|
74
|
+
expect(["strong", "neutral", "weak"]).toContain(label.strength);
|
|
71
75
|
}
|
|
72
76
|
});
|
|
73
77
|
it("should have correct korean names", () => {
|
|
74
|
-
expect(
|
|
75
|
-
expect(
|
|
76
|
-
expect(
|
|
77
|
-
expect(
|
|
78
|
+
expect(getTwelveStageLabel("longLife").korean).toBe("장생");
|
|
79
|
+
expect(getTwelveStageLabel("bathing").korean).toBe("목욕");
|
|
80
|
+
expect(getTwelveStageLabel("imperial").korean).toBe("제왕");
|
|
81
|
+
expect(getTwelveStageLabel("death").korean).toBe("사");
|
|
78
82
|
});
|
|
79
83
|
it("should have correct strength classifications", () => {
|
|
80
|
-
expect(
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
84
|
+
expect(getTwelveStageLabel("longLife").strength).toBe("strong");
|
|
85
|
+
expect(getTwelveStageLabel("imperial").strength).toBe("strong");
|
|
86
|
+
expect(getTwelveStageLabel("decline").strength).toBe("weak");
|
|
87
|
+
expect(getTwelveStageLabel("tomb").strength).toBe("neutral");
|
|
84
88
|
});
|
|
85
89
|
});
|
|
86
90
|
});
|
|
@@ -11,7 +11,7 @@ describe("yongshen", () => {
|
|
|
11
11
|
});
|
|
12
12
|
it("identifies yongshen elements correctly", () => {
|
|
13
13
|
const result = analyzeYongShen("甲子", "丙寅", "甲辰", "乙亥");
|
|
14
|
-
expect(result.allElements[result.primary].isYongShen).toBe(true);
|
|
14
|
+
expect(result.allElements[result.primary.key].isYongShen).toBe(true);
|
|
15
15
|
});
|
|
16
16
|
it("identifies kishen elements correctly", () => {
|
|
17
17
|
const result = analyzeYongShen("甲子", "丙寅", "甲辰", "乙亥");
|
|
@@ -20,11 +20,11 @@ describe("yongshen", () => {
|
|
|
20
20
|
});
|
|
21
21
|
it("uses 억부 method for 중화 strength (조후 is adjustment, not primary)", () => {
|
|
22
22
|
const result = analyzeYongShen("甲子", "丙寅", "庚申", "丁亥");
|
|
23
|
-
expect(["
|
|
23
|
+
expect(["balance", "formation"]).toContain(result.method.key);
|
|
24
24
|
});
|
|
25
25
|
it("uses 억부 method for extreme strength", () => {
|
|
26
26
|
const result = analyzeYongShen("甲寅", "甲寅", "甲寅", "甲寅");
|
|
27
|
-
expect(result.method).toBe("
|
|
27
|
+
expect(result.method.key).toBe("balance");
|
|
28
28
|
});
|
|
29
29
|
it("includes johuAdjustment field in result", () => {
|
|
30
30
|
const result = analyzeYongShen("甲子", "丙寅", "甲辰", "乙亥");
|
package/dist/core/luck.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"luck.d.ts","sourceRoot":"","sources":["../../src/core/luck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGhD,YAAY,EAAE,MAAM,EAAE,CAAC;AAEvB,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,QAAQ,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EACvB,aAAa,EAAE,CAAC,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,GACL,eAAe,
|
|
1
|
+
{"version":3,"file":"luck.d.ts","sourceRoot":"","sources":["../../src/core/luck.ts"],"names":[],"mappings":"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"}
|
package/dist/core/luck.js
CHANGED
|
@@ -24,7 +24,8 @@ export function calculateMajorLuck(adapter, birthDateTime, gender, yearPillar, m
|
|
|
24
24
|
const years = Math.floor(totalMonths / 12);
|
|
25
25
|
const months = totalMonths % 12;
|
|
26
26
|
const days = Math.round(((daysToTerm / 3) * 12 - totalMonths) * 30);
|
|
27
|
-
|
|
27
|
+
// 전통적으로 6개월 이상이면 반올림하여 1년 추가
|
|
28
|
+
const startAge = months >= 6 ? years + 1 : years;
|
|
28
29
|
const startAgeDetail = { years, months, days: Math.abs(days) };
|
|
29
30
|
const monthIdx60 = getPillarIndex(monthPillar);
|
|
30
31
|
const pillars = [];
|