@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.
Files changed (39) hide show
  1. package/dist/__tests__/relations.test.js +6 -6
  2. package/dist/__tests__/saju.test.js +3 -3
  3. package/dist/__tests__/sinsals.test.js +5 -5
  4. package/dist/__tests__/solar-terms.test.js +26 -16
  5. package/dist/__tests__/strength.test.js +2 -2
  6. package/dist/__tests__/ten-gods.test.js +52 -38
  7. package/dist/__tests__/twelve-stages.test.js +55 -51
  8. package/dist/__tests__/yongshen.test.js +3 -3
  9. package/dist/core/luck.d.ts.map +1 -1
  10. package/dist/core/luck.js +2 -1
  11. package/dist/core/relations.d.ts +35 -20
  12. package/dist/core/relations.d.ts.map +1 -1
  13. package/dist/core/relations.js +105 -33
  14. package/dist/core/sinsals.d.ts +12 -6
  15. package/dist/core/sinsals.d.ts.map +1 -1
  16. package/dist/core/sinsals.js +19 -8
  17. package/dist/core/solar-terms.d.ts +19 -105
  18. package/dist/core/solar-terms.d.ts.map +1 -1
  19. package/dist/core/solar-terms.js +56 -29
  20. package/dist/core/strength.d.ts +7 -3
  21. package/dist/core/strength.d.ts.map +1 -1
  22. package/dist/core/strength.js +52 -28
  23. package/dist/core/ten-gods.d.ts +27 -64
  24. package/dist/core/ten-gods.d.ts.map +1 -1
  25. package/dist/core/ten-gods.js +65 -119
  26. package/dist/core/twelve-stages.d.ts +12 -12
  27. package/dist/core/twelve-stages.d.ts.map +1 -1
  28. package/dist/core/twelve-stages.js +28 -24
  29. package/dist/core/yongshen.d.ts +15 -6
  30. package/dist/core/yongshen.d.ts.map +1 -1
  31. package/dist/core/yongshen.js +67 -38
  32. package/dist/index.d.ts +7 -7
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -6
  35. package/dist/types/common.d.ts +8 -0
  36. package/dist/types/common.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. 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.name).toBe("소한");
36
- expect(result.solarTerms.next.name).toBe("대한");
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({ name: "소한", hanja: "小寒", longitude: 285 });
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.name === "입춘");
22
- expect(ipchun).toEqual({ name: "입춘", hanja: "立春", longitude: 315 });
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.name).toBe("소한");
30
- expect(result.next.name).toBe("대한");
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.name).toBe("대한");
38
- expect(result.next.name).toBe("입춘");
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.name).toBe("하지");
44
- expect(result.next.name).toBe("소서");
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.name).toBe("동지");
50
- expect(result.next.name).toBe("소한");
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.name).toBe("춘분");
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.name).toBe("청명");
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.name);
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.name === "입춘");
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.name === "하지");
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, getTenGod, } from "../core/ten-gods";
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("getTenGod", () => {
46
- it("correctly identifies 비견 (same element, same polarity)", () => {
47
- expect(getTenGod("甲", "甲")).toBe("비견");
48
- expect(getTenGod("乙", "乙")).toBe("비견");
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 겁재 (same element, different polarity)", () => {
51
- expect(getTenGod("甲", "乙")).toBe("겁재");
52
- expect(getTenGod("乙", "甲")).toBe("겁재");
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 식신 (I generate, same polarity)", () => {
55
- expect(getTenGod("甲", "丙")).toBe("식신");
56
- expect(getTenGod("丙", "戊")).toBe("식신");
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 상관 (I generate, different polarity)", () => {
59
- expect(getTenGod("甲", "丁")).toBe("상관");
60
- expect(getTenGod("丙", "己")).toBe("상관");
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 편재 (I control, same polarity)", () => {
63
- expect(getTenGod("甲", "戊")).toBe("편재");
64
- expect(getTenGod("丙", "庚")).toBe("편재");
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 정재 (I control, different polarity)", () => {
67
- expect(getTenGod("甲", "己")).toBe("정재");
68
- expect(getTenGod("丙", "辛")).toBe("정재");
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 편관 (controls me, same polarity)", () => {
71
- expect(getTenGod("甲", "庚")).toBe("편관");
72
- expect(getTenGod("丙", "壬")).toBe("편관");
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 정관 (controls me, different polarity)", () => {
75
- expect(getTenGod("甲", "辛")).toBe("정관");
76
- expect(getTenGod("丙", "癸")).toBe("정관");
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 편인 (generates me, same polarity)", () => {
79
- expect(getTenGod("甲", "壬")).toBe("편인");
80
- expect(getTenGod("丙", "甲")).toBe("편인");
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 정인 (generates me, different polarity)", () => {
83
- expect(getTenGod("甲", "癸")).toBe("정인");
84
- expect(getTenGod("丙", "乙")).toBe("정인");
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.비견).toBeGreaterThanOrEqual(1);
108
- expect(counts.식신).toBeGreaterThanOrEqual(1);
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, getTwelveStage, STAGE_INFO, TWELVE_STAGES, } from "../core/twelve-stages";
2
+ import { analyzeTwelveStages, getTwelveStageLabel, TWELVE_STAGES } from "../core/twelve-stages";
3
3
  describe("twelve-stages", () => {
4
- describe("getTwelveStage", () => {
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
- expect(getTwelveStage("", "")).toBe("longLife");
22
+ const result = analyzeTwelveStages("甲亥", "甲亥", "甲亥", "甲亥");
23
+ expect(result.year.key).toBe("longLife");
7
24
  });
8
25
  it("should return imperial for 甲 at 卯", () => {
9
- expect(getTwelveStage("", "")).toBe("imperial");
26
+ const result = analyzeTwelveStages("甲卯", "甲卯", "甲卯", "甲卯");
27
+ expect(result.year.key).toBe("imperial");
10
28
  });
11
29
  it("should return tomb for 甲 at 未", () => {
12
- expect(getTwelveStage("", "")).toBe("tomb");
30
+ const result = analyzeTwelveStages("甲未", "甲未", "甲未", "甲未");
31
+ expect(result.year.key).toBe("tomb");
13
32
  });
14
33
  it("should return longLife for 乙 at 午", () => {
15
- expect(getTwelveStage("", "")).toBe("longLife");
34
+ const result = analyzeTwelveStages("乙午", "乙午", "乙午", "乙午");
35
+ expect(result.year.key).toBe("longLife");
16
36
  });
17
37
  it("should return imperial for 乙 at 寅", () => {
18
- expect(getTwelveStage("", "")).toBe("imperial");
38
+ const result = analyzeTwelveStages("乙寅", "乙寅", "乙寅", "乙寅");
39
+ expect(result.year.key).toBe("imperial");
19
40
  });
20
41
  it("should return longLife for 丙 at 寅", () => {
21
- expect(getTwelveStage("", "")).toBe("longLife");
42
+ const result = analyzeTwelveStages("丙寅", "丙寅", "丙寅", "丙寅");
43
+ expect(result.year.key).toBe("longLife");
22
44
  });
23
45
  it("should return imperial for 丙 at 午", () => {
24
- expect(getTwelveStage("", "")).toBe("imperial");
46
+ const result = analyzeTwelveStages("丙午", "丙午", "丙午", "丙午");
47
+ expect(result.year.key).toBe("imperial");
25
48
  });
26
49
  it("should return longLife for 庚 at 巳", () => {
27
- expect(getTwelveStage("", "")).toBe("longLife");
50
+ const result = analyzeTwelveStages("庚巳", "庚巳", "庚巳", "庚巳");
51
+ expect(result.year.key).toBe("longLife");
28
52
  });
29
53
  it("should return imperial for 庚 at 酉", () => {
30
- expect(getTwelveStage("", "")).toBe("imperial");
54
+ const result = analyzeTwelveStages("庚酉", "庚酉", "庚酉", "庚酉");
55
+ expect(result.year.key).toBe("imperial");
31
56
  });
32
57
  it("should return longLife for 壬 at 申", () => {
33
- expect(getTwelveStage("", "")).toBe("longLife");
58
+ const result = analyzeTwelveStages("壬申", "壬申", "壬申", "壬申");
59
+ expect(result.year.key).toBe("longLife");
34
60
  });
35
61
  it("should return imperial for 壬 at 子", () => {
36
- expect(getTwelveStage("", "")).toBe("imperial");
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("STAGE_INFO", () => {
66
+ describe("getTwelveStageLabel", () => {
64
67
  it("should have info for all twelve stages", () => {
65
68
  for (const stage of TWELVE_STAGES) {
66
- expect(STAGE_INFO[stage]).toBeDefined();
67
- expect(STAGE_INFO[stage].korean).toBeDefined();
68
- expect(STAGE_INFO[stage].hanja).toBeDefined();
69
- expect(STAGE_INFO[stage].meaning).toBeDefined();
70
- expect(["strong", "neutral", "weak"]).toContain(STAGE_INFO[stage].strength);
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(STAGE_INFO.longLife.korean).toBe("장생");
75
- expect(STAGE_INFO.bathing.korean).toBe("목욕");
76
- expect(STAGE_INFO.imperial.korean).toBe("제왕");
77
- expect(STAGE_INFO.death.korean).toBe("사");
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(STAGE_INFO.longLife.strength).toBe("strong");
81
- expect(STAGE_INFO.imperial.strength).toBe("strong");
82
- expect(STAGE_INFO.decline.strength).toBe("weak");
83
- expect(STAGE_INFO.tomb.strength).toBe("neutral");
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(["억부", "격국"]).toContain(result.method);
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("甲子", "丙寅", "甲辰", "乙亥");
@@ -1 +1 @@
1
- {"version":3,"file":"luck.d.ts","sourceRoot":"","sources":["../../src/core/luck.ts"],"names":[],"mappings":"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"}
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
- const startAge = years;
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 = [];