@gracefullight/saju 0.3.0 → 0.4.2

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 (62) hide show
  1. package/README.en.md +6 -10
  2. package/README.md +6 -10
  3. package/dist/__tests__/date-fns-adapter.test.js +1 -1
  4. package/dist/__tests__/four-pillars.test.js +10 -9
  5. package/dist/__tests__/luck.test.js +2 -2
  6. package/dist/__tests__/lunar.test.js +1 -1
  7. package/dist/__tests__/luxon-adapter.test.js +1 -1
  8. package/dist/__tests__/relations.test.js +2 -2
  9. package/dist/__tests__/saju.test.js +3 -3
  10. package/dist/__tests__/sinsals.test.d.ts +2 -0
  11. package/dist/__tests__/sinsals.test.d.ts.map +1 -0
  12. package/dist/__tests__/sinsals.test.js +64 -0
  13. package/dist/__tests__/solar-terms.test.js +2 -2
  14. package/dist/__tests__/strength.test.js +2 -2
  15. package/dist/__tests__/ten-gods.test.js +4 -4
  16. package/dist/__tests__/twelve-stages.test.d.ts +2 -0
  17. package/dist/__tests__/twelve-stages.test.d.ts.map +1 -0
  18. package/dist/__tests__/twelve-stages.test.js +86 -0
  19. package/dist/__tests__/utils.test.d.ts +2 -0
  20. package/dist/__tests__/utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils.test.js +130 -0
  22. package/dist/__tests__/yongshen.test.js +2 -2
  23. package/dist/adapters/date-fns.d.ts +1 -1
  24. package/dist/adapters/luxon.d.ts +1 -1
  25. package/dist/core/four-pillars.d.ts +6 -6
  26. package/dist/core/four-pillars.d.ts.map +1 -1
  27. package/dist/core/four-pillars.js +10 -26
  28. package/dist/core/luck.d.ts +22 -3
  29. package/dist/core/luck.d.ts.map +1 -1
  30. package/dist/core/luck.js +60 -19
  31. package/dist/core/relations.d.ts.map +1 -1
  32. package/dist/core/relations.js +5 -1
  33. package/dist/core/sinsals.d.ts +19 -0
  34. package/dist/core/sinsals.d.ts.map +1 -0
  35. package/dist/core/sinsals.js +339 -0
  36. package/dist/core/solar-terms.d.ts +1 -1
  37. package/dist/core/strength.d.ts.map +1 -1
  38. package/dist/core/strength.js +1 -1
  39. package/dist/core/ten-gods.d.ts +4 -7
  40. package/dist/core/ten-gods.d.ts.map +1 -1
  41. package/dist/core/ten-gods.js +2 -6
  42. package/dist/core/twelve-stages.d.ts +17 -0
  43. package/dist/core/twelve-stages.d.ts.map +1 -0
  44. package/dist/core/twelve-stages.js +77 -0
  45. package/dist/core/yongshen.d.ts.map +1 -1
  46. package/dist/core/yongshen.js +2 -2
  47. package/dist/index.d.ts +26 -19
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +23 -15
  50. package/dist/types/common.d.ts +12 -0
  51. package/dist/types/common.d.ts.map +1 -0
  52. package/dist/types/common.js +1 -0
  53. package/dist/types/index.d.ts +2 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/index.js +1 -0
  56. package/dist/utils/constants.d.ts +13 -0
  57. package/dist/utils/constants.d.ts.map +1 -0
  58. package/dist/utils/constants.js +59 -0
  59. package/dist/utils/index.d.ts +2 -0
  60. package/dist/utils/index.d.ts.map +1 -0
  61. package/dist/utils/index.js +1 -0
  62. package/package.json +3 -2
package/README.en.md CHANGED
@@ -65,7 +65,7 @@ pnpm add date-fns date-fns-tz
65
65
  ```typescript
66
66
  import { DateTime } from "luxon";
67
67
  import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
68
- import { getSaju, STANDARD_PRESET } from "@gracefullight/saju";
68
+ import { getSaju } from "@gracefullight/saju";
69
69
 
70
70
  const adapter = await createLuxonAdapter();
71
71
 
@@ -76,11 +76,10 @@ const birthDateTime = DateTime.fromObject(
76
76
 
77
77
  // getSaju: Calculate pillars, ten gods, strength, relations, yongshen, solar terms, major luck, yearly luck all at once
78
78
  const result = getSaju(adapter, birthDateTime, {
79
- longitudeDeg: 126.9778,
80
79
  gender: "male", // Required: needed for major luck calculation
81
- preset: STANDARD_PRESET,
82
- currentYear: 2024, // For default yearly luck range (optional)
83
- yearlyLuckRange: { from: 2024, to: 2030 }, // Specify yearly luck range directly (optional)
80
+ // longitudeDeg: 126.9778, // Optional: uses timezone-based longitude if omitted
81
+ // preset: STANDARD_PRESET, // Optional: defaults to STANDARD_PRESET
82
+ // yearlyLuckRange: { from: 2024, to: 2030 }, // Optional: specify yearly luck range
84
83
  });
85
84
 
86
85
  console.log(result.pillars); // { year: "己卯", month: "丙子", ... }
@@ -98,7 +97,7 @@ console.log(result.yearlyLuck); // Yearly luck info
98
97
  ```typescript
99
98
  import { DateTime } from "luxon";
100
99
  import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
101
- import { getFourPillars, STANDARD_PRESET } from "@gracefullight/saju";
100
+ import { getFourPillars } from "@gracefullight/saju";
102
101
 
103
102
  const adapter = await createLuxonAdapter();
104
103
 
@@ -107,10 +106,7 @@ const birthDateTime = DateTime.fromObject(
107
106
  { zone: "Asia/Seoul" }
108
107
  );
109
108
 
110
- const result = getFourPillars(adapter, birthDateTime, {
111
- longitudeDeg: 126.9778, // Seoul longitude
112
- preset: STANDARD_PRESET,
113
- });
109
+ const result = getFourPillars(adapter, birthDateTime);
114
110
 
115
111
  console.log(result);
116
112
  ```
package/README.md CHANGED
@@ -65,7 +65,7 @@ pnpm add date-fns date-fns-tz
65
65
  ```typescript
66
66
  import { DateTime } from "luxon";
67
67
  import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
68
- import { getSaju, STANDARD_PRESET } from "@gracefullight/saju";
68
+ import { getSaju } from "@gracefullight/saju";
69
69
 
70
70
  const adapter = await createLuxonAdapter();
71
71
 
@@ -76,11 +76,10 @@ const birthDateTime = DateTime.fromObject(
76
76
 
77
77
  // getSaju: 사주 팔자, 십신, 신강약, 합충, 용신, 절기, 대운, 세운을 한 번에 계산
78
78
  const result = getSaju(adapter, birthDateTime, {
79
- longitudeDeg: 126.9778,
80
79
  gender: "male", // 필수: 대운 계산에 필요
81
- preset: STANDARD_PRESET,
82
- currentYear: 2024, // 세운 기본 범위 계산용 (선택)
83
- yearlyLuckRange: { from: 2024, to: 2030 }, // 세운 범위 직접 지정 (선택)
80
+ // longitudeDeg: 126.9778, // 선택: 생략 시 타임존 기준 경도 사용
81
+ // preset: STANDARD_PRESET, // 선택: 기본값은 STANDARD_PRESET
82
+ // yearlyLuckRange: { from: 2024, to: 2030 }, // 선택: 세운 범위 지정
84
83
  });
85
84
 
86
85
  console.log(result.pillars); // { year: "己卯", month: "丙子", ... }
@@ -98,7 +97,7 @@ console.log(result.yearlyLuck); // 세운 정보
98
97
  ```typescript
99
98
  import { DateTime } from "luxon";
100
99
  import { createLuxonAdapter } from "@gracefullight/saju/adapters/luxon";
101
- import { getFourPillars, STANDARD_PRESET } from "@gracefullight/saju";
100
+ import { getFourPillars } from "@gracefullight/saju";
102
101
 
103
102
  const adapter = await createLuxonAdapter();
104
103
 
@@ -107,10 +106,7 @@ const birthDateTime = DateTime.fromObject(
107
106
  { zone: "Asia/Seoul" }
108
107
  );
109
108
 
110
- const result = getFourPillars(adapter, birthDateTime, {
111
- longitudeDeg: 126.9778, // 서울 경도
112
- preset: STANDARD_PRESET,
113
- });
109
+ const result = getFourPillars(adapter, birthDateTime);
114
110
 
115
111
  console.log(result);
116
112
  ```
@@ -1,5 +1,5 @@
1
1
  import { beforeAll, describe, expect, it } from "vitest";
2
- import { createDateFnsAdapter } from "@/adapters/date-fns";
2
+ import { createDateFnsAdapter } from "../adapters/date-fns";
3
3
  describe("date-fns Adapter", () => {
4
4
  let adapter;
5
5
  beforeAll(async () => {
@@ -1,7 +1,7 @@
1
1
  import { DateTime } from "luxon";
2
2
  import { beforeAll, describe, expect, it } from "vitest";
3
- import { createLuxonAdapter } from "@/adapters/luxon";
4
- import { BRANCHES, dayPillarFromDate, effectiveDayDate, getFourPillars, hourPillar, monthPillar, presetA, presetB, STEMS, yearPillar, } from "@/core/four-pillars";
3
+ import { createLuxonAdapter } from "../adapters/luxon";
4
+ import { BRANCHES, dayPillarFromDate, effectiveDayDate, getFourPillars, hourPillar, monthPillar, presetA, presetB, STEMS, yearPillar, } from "../core/four-pillars";
5
5
  describe("Four Pillars Core", () => {
6
6
  let adapter;
7
7
  beforeAll(async () => {
@@ -240,14 +240,15 @@ describe("Four Pillars Core", () => {
240
240
  });
241
241
  expect(resultA.meta.effectiveDayDate.day).toBe(1);
242
242
  });
243
- it("should require longitudeDeg parameter", () => {
243
+ it("should use default longitude from timezone offset when longitudeDeg is omitted", () => {
244
244
  const dt = DateTime.fromObject({ year: 2000, month: 1, day: 1, hour: 18, minute: 0 }, { zone: "Asia/Seoul" });
245
- expect(() => {
246
- getFourPillars(adapter, dt, {
247
- longitudeDeg: undefined,
248
- preset: presetA,
249
- });
250
- }).toThrow("longitudeDeg is required");
245
+ const result = getFourPillars(adapter, dt, {
246
+ preset: presetA,
247
+ });
248
+ expect(result.year).toBeDefined();
249
+ expect(result.month).toBeDefined();
250
+ expect(result.day).toBeDefined();
251
+ expect(result.hour).toBeDefined();
251
252
  });
252
253
  it("should handle leap year dates", () => {
253
254
  const dt = DateTime.fromObject({ year: 2000, month: 2, day: 29 }, { zone: "UTC" });
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from "vitest";
2
- import { calculateYearlyLuck, getYearPillar } from "@/core/luck";
1
+ import { describe, expect, it } from "vitest";
2
+ import { calculateYearlyLuck, getYearPillar } from "../core/luck";
3
3
  describe("luck", () => {
4
4
  describe("getYearPillar", () => {
5
5
  it("returns correct pillar for 1984 (甲子)", () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { getLunarDate, getSolarDate } from "@/core/lunar";
2
+ import { getLunarDate, getSolarDate } from "../core/lunar";
3
3
  describe("Lunar Calendar", () => {
4
4
  describe("getLunarDate", () => {
5
5
  it("should convert solar date to lunar date (2000-01-01)", () => {
@@ -1,6 +1,6 @@
1
1
  import { DateTime } from "luxon";
2
2
  import { beforeAll, describe, expect, it } from "vitest";
3
- import { createLuxonAdapter } from "@/adapters/luxon";
3
+ import { createLuxonAdapter } from "../adapters/luxon";
4
4
  describe("Luxon Adapter", () => {
5
5
  let adapter;
6
6
  beforeAll(async () => {
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from "vitest";
2
- import { analyzeRelations, findStemCombination, findBranchClash, findBranchSixCombination, } from "@/core/relations";
1
+ import { describe, expect, it } from "vitest";
2
+ import { analyzeRelations, findBranchClash, findBranchSixCombination, findStemCombination, } from "../core/relations";
3
3
  describe("relations", () => {
4
4
  describe("findStemCombination", () => {
5
5
  it("finds 甲己 combination (earth)", () => {
@@ -1,7 +1,7 @@
1
- import { describe, it, expect } from "vitest";
2
1
  import { DateTime } from "luxon";
3
- import { createLuxonAdapter } from "@/adapters/luxon";
4
- import { getSaju, STANDARD_PRESET } from "@/index";
2
+ import { describe, expect, it } from "vitest";
3
+ import { createLuxonAdapter } from "../adapters/luxon";
4
+ import { getSaju, STANDARD_PRESET } from "../index";
5
5
  describe("getSaju integration", () => {
6
6
  it("returns complete saju analysis with all required fields", async () => {
7
7
  const adapter = await createLuxonAdapter();
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sinsals.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sinsals.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sinsals.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { analyzeSinsals, SINSAL_INFO, SINSALS } from "../core/sinsals";
3
+ describe("sinsals", () => {
4
+ describe("analyzeSinsals", () => {
5
+ it("should detect peach blossom (도화살)", () => {
6
+ const result = analyzeSinsals("甲寅", "丙寅", "戊卯", "庚午");
7
+ const peachBlossoms = result.matches.filter((m) => m.sinsal === "peachBlossom");
8
+ expect(peachBlossoms.length).toBeGreaterThan(0);
9
+ });
10
+ it("should detect sky horse (역마살)", () => {
11
+ const result = analyzeSinsals("甲寅", "丙寅", "戊申", "庚午");
12
+ const skyHorses = result.matches.filter((m) => m.sinsal === "skyHorse");
13
+ expect(skyHorses.length).toBeGreaterThan(0);
14
+ });
15
+ it("should detect flowery canopy (화개살)", () => {
16
+ const result = analyzeSinsals("甲寅", "丙戌", "戊子", "庚午");
17
+ const floweryCanopies = result.matches.filter((m) => m.sinsal === "floweryCanopy");
18
+ expect(floweryCanopies.length).toBeGreaterThan(0);
19
+ });
20
+ it("should return summary grouped by sinsal", () => {
21
+ const result = analyzeSinsals("甲子", "丙寅", "戊辰", "庚午");
22
+ expect(result.summary).toBeDefined();
23
+ for (const [sinsal, positions] of Object.entries(result.summary)) {
24
+ expect(SINSALS).toContain(sinsal);
25
+ expect(Array.isArray(positions)).toBe(true);
26
+ }
27
+ });
28
+ it("should not have duplicate matches", () => {
29
+ const result = analyzeSinsals("甲子", "丙寅", "戊辰", "庚午");
30
+ const seen = new Set();
31
+ for (const match of result.matches) {
32
+ const key = `${match.sinsal}-${match.position}`;
33
+ expect(seen.has(key)).toBe(false);
34
+ seen.add(key);
35
+ }
36
+ });
37
+ it("should detect sky noble (천을귀인)", () => {
38
+ const result = analyzeSinsals("甲丑", "丙寅", "甲子", "庚午");
39
+ const skyNobles = result.matches.filter((m) => m.sinsal === "skyNoble");
40
+ expect(skyNobles.length).toBeGreaterThan(0);
41
+ });
42
+ });
43
+ describe("SINSAL_INFO", () => {
44
+ it("should have info for all sinsals", () => {
45
+ for (const sinsal of SINSALS) {
46
+ expect(SINSAL_INFO[sinsal]).toBeDefined();
47
+ expect(SINSAL_INFO[sinsal].korean).toBeDefined();
48
+ expect(SINSAL_INFO[sinsal].hanja).toBeDefined();
49
+ expect(SINSAL_INFO[sinsal].meaning).toBeDefined();
50
+ expect(["auspicious", "inauspicious", "neutral"]).toContain(SINSAL_INFO[sinsal].type);
51
+ }
52
+ });
53
+ it("should have correct korean names", () => {
54
+ expect(SINSAL_INFO.peachBlossom.korean).toBe("도화살");
55
+ expect(SINSAL_INFO.skyHorse.korean).toBe("역마살");
56
+ expect(SINSAL_INFO.skyNoble.korean).toBe("천을귀인");
57
+ });
58
+ it("should have correct type classifications", () => {
59
+ expect(SINSAL_INFO.peachBlossom.type).toBe("neutral");
60
+ expect(SINSAL_INFO.skyNoble.type).toBe("auspicious");
61
+ expect(SINSAL_INFO.ghostGate.type).toBe("inauspicious");
62
+ });
63
+ });
64
+ });
@@ -1,7 +1,7 @@
1
1
  import { DateTime } from "luxon";
2
2
  import { describe, expect, it } from "vitest";
3
- import { createLuxonAdapter } from "@/adapters/luxon";
4
- import { analyzeSolarTerms, getSolarTermsForYear, SOLAR_TERMS } from "@/core/solar-terms";
3
+ import { createLuxonAdapter } from "../adapters/luxon";
4
+ import { analyzeSolarTerms, getSolarTermsForYear, SOLAR_TERMS } from "../core/solar-terms";
5
5
  describe("solar-terms", async () => {
6
6
  const adapter = await createLuxonAdapter();
7
7
  describe("SOLAR_TERMS", () => {
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from "vitest";
2
- import { analyzeStrength } from "@/core/strength";
1
+ import { describe, expect, it } from "vitest";
2
+ import { analyzeStrength } from "../core/strength";
3
3
  describe("strength", () => {
4
4
  describe("analyzeStrength", () => {
5
5
  it("returns a strength level", () => {
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from "vitest";
2
- import { getTenGod, getStemElement, getStemPolarity, getBranchElement, getHiddenStems, analyzeTenGods, countTenGods, countElements, } from "@/core/ten-gods";
1
+ import { describe, expect, it } from "vitest";
2
+ import { analyzeTenGods, countElements, countTenGods, getBranchElement, getHiddenStems, getStemElement, getStemPolarity, getTenGod, } from "../core/ten-gods";
3
3
  describe("ten-gods", () => {
4
4
  describe("getStemElement", () => {
5
5
  it("returns correct element for each stem", () => {
@@ -104,8 +104,8 @@ describe("ten-gods", () => {
104
104
  it("counts ten gods correctly", () => {
105
105
  const analysis = analyzeTenGods("甲子", "丙寅", "甲辰", "乙亥");
106
106
  const counts = countTenGods(analysis);
107
- expect(counts["비견"]).toBeGreaterThanOrEqual(1);
108
- expect(counts["식신"]).toBeGreaterThanOrEqual(1);
107
+ expect(counts.비견).toBeGreaterThanOrEqual(1);
108
+ expect(counts.식신).toBeGreaterThanOrEqual(1);
109
109
  });
110
110
  });
111
111
  describe("countElements", () => {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=twelve-stages.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twelve-stages.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/twelve-stages.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { analyzeTwelveStages, getTwelveStage, STAGE_INFO, TWELVE_STAGES, } from "../core/twelve-stages";
3
+ describe("twelve-stages", () => {
4
+ describe("getTwelveStage", () => {
5
+ it("should return longLife for 甲 at 亥", () => {
6
+ expect(getTwelveStage("甲", "亥")).toBe("longLife");
7
+ });
8
+ it("should return imperial for 甲 at 卯", () => {
9
+ expect(getTwelveStage("甲", "卯")).toBe("imperial");
10
+ });
11
+ it("should return tomb for 甲 at 未", () => {
12
+ expect(getTwelveStage("甲", "未")).toBe("tomb");
13
+ });
14
+ it("should return longLife for 乙 at 午", () => {
15
+ expect(getTwelveStage("乙", "午")).toBe("longLife");
16
+ });
17
+ it("should return imperial for 乙 at 寅", () => {
18
+ expect(getTwelveStage("乙", "寅")).toBe("imperial");
19
+ });
20
+ it("should return longLife for 丙 at 寅", () => {
21
+ expect(getTwelveStage("丙", "寅")).toBe("longLife");
22
+ });
23
+ it("should return imperial for 丙 at 午", () => {
24
+ expect(getTwelveStage("丙", "午")).toBe("imperial");
25
+ });
26
+ it("should return longLife for 庚 at 巳", () => {
27
+ expect(getTwelveStage("庚", "巳")).toBe("longLife");
28
+ });
29
+ it("should return imperial for 庚 at 酉", () => {
30
+ expect(getTwelveStage("庚", "酉")).toBe("imperial");
31
+ });
32
+ it("should return longLife for 壬 at 申", () => {
33
+ expect(getTwelveStage("壬", "申")).toBe("longLife");
34
+ });
35
+ 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");
61
+ });
62
+ });
63
+ describe("STAGE_INFO", () => {
64
+ it("should have info for all twelve stages", () => {
65
+ 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);
71
+ }
72
+ });
73
+ 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
+ });
79
+ 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
+ });
85
+ });
86
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,130 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { BRANCHES, dayPillarIndexFromJdn, ELEMENTS, getBranchIndex, getPillarIndex, getStemIndex, isYangStem, isYinStem, jdnFromDate, pillarFromIndex, STEMS, } from "../utils";
3
+ describe("constants", () => {
4
+ describe("STEMS", () => {
5
+ it("should have 10 stems", () => {
6
+ expect(STEMS).toHaveLength(10);
7
+ });
8
+ it("should start with 甲 and end with 癸", () => {
9
+ expect(STEMS[0]).toBe("甲");
10
+ expect(STEMS[9]).toBe("癸");
11
+ });
12
+ });
13
+ describe("BRANCHES", () => {
14
+ it("should have 12 branches", () => {
15
+ expect(BRANCHES).toHaveLength(12);
16
+ });
17
+ it("should start with 子 and end with 亥", () => {
18
+ expect(BRANCHES[0]).toBe("子");
19
+ expect(BRANCHES[11]).toBe("亥");
20
+ });
21
+ });
22
+ describe("ELEMENTS", () => {
23
+ it("should have 5 elements", () => {
24
+ expect(ELEMENTS).toHaveLength(5);
25
+ });
26
+ it("should contain wood, fire, earth, metal, water", () => {
27
+ expect(ELEMENTS).toContain("wood");
28
+ expect(ELEMENTS).toContain("fire");
29
+ expect(ELEMENTS).toContain("earth");
30
+ expect(ELEMENTS).toContain("metal");
31
+ expect(ELEMENTS).toContain("water");
32
+ });
33
+ });
34
+ });
35
+ describe("getStemIndex", () => {
36
+ it("should return correct index for each stem", () => {
37
+ expect(getStemIndex("甲")).toBe(0);
38
+ expect(getStemIndex("乙")).toBe(1);
39
+ expect(getStemIndex("癸")).toBe(9);
40
+ });
41
+ it("should return -1 for invalid stem", () => {
42
+ expect(getStemIndex("X")).toBe(-1);
43
+ });
44
+ });
45
+ describe("getBranchIndex", () => {
46
+ it("should return correct index for each branch", () => {
47
+ expect(getBranchIndex("子")).toBe(0);
48
+ expect(getBranchIndex("丑")).toBe(1);
49
+ expect(getBranchIndex("亥")).toBe(11);
50
+ });
51
+ it("should return -1 for invalid branch", () => {
52
+ expect(getBranchIndex("X")).toBe(-1);
53
+ });
54
+ });
55
+ describe("pillarFromIndex", () => {
56
+ it("should return 甲子 for index 0", () => {
57
+ expect(pillarFromIndex(0)).toBe("甲子");
58
+ });
59
+ it("should return 乙丑 for index 1", () => {
60
+ expect(pillarFromIndex(1)).toBe("乙丑");
61
+ });
62
+ it("should handle negative indices", () => {
63
+ expect(pillarFromIndex(-1)).toBe("癸亥");
64
+ });
65
+ it("should wrap around for indices >= 60", () => {
66
+ expect(pillarFromIndex(60)).toBe("甲子");
67
+ expect(pillarFromIndex(61)).toBe("乙丑");
68
+ });
69
+ });
70
+ describe("getPillarIndex", () => {
71
+ it("should return 0 for 甲子", () => {
72
+ expect(getPillarIndex("甲子")).toBe(0);
73
+ });
74
+ it("should return correct index for 乙丑", () => {
75
+ expect(getPillarIndex("乙丑")).toBe(1);
76
+ });
77
+ it("should be inverse of pillarFromIndex", () => {
78
+ for (let i = 0; i < 60; i++) {
79
+ const pillar = pillarFromIndex(i);
80
+ expect(getPillarIndex(pillar)).toBe(i);
81
+ }
82
+ });
83
+ });
84
+ describe("jdnFromDate", () => {
85
+ it("should return correct JDN for known date", () => {
86
+ expect(jdnFromDate(2000, 1, 1)).toBe(2451545);
87
+ });
88
+ it("should return correct JDN for epoch date", () => {
89
+ expect(jdnFromDate(1970, 1, 1)).toBe(2440588);
90
+ });
91
+ });
92
+ describe("dayPillarIndexFromJdn", () => {
93
+ it("should return value between 0 and 59", () => {
94
+ const idx = dayPillarIndexFromJdn(2451545);
95
+ expect(idx).toBeGreaterThanOrEqual(0);
96
+ expect(idx).toBeLessThan(60);
97
+ });
98
+ });
99
+ describe("isYangStem", () => {
100
+ it("should return true for yang stems", () => {
101
+ expect(isYangStem("甲")).toBe(true);
102
+ expect(isYangStem("丙")).toBe(true);
103
+ expect(isYangStem("戊")).toBe(true);
104
+ expect(isYangStem("庚")).toBe(true);
105
+ expect(isYangStem("壬")).toBe(true);
106
+ });
107
+ it("should return false for yin stems", () => {
108
+ expect(isYangStem("乙")).toBe(false);
109
+ expect(isYangStem("丁")).toBe(false);
110
+ expect(isYangStem("己")).toBe(false);
111
+ expect(isYangStem("辛")).toBe(false);
112
+ expect(isYangStem("癸")).toBe(false);
113
+ });
114
+ });
115
+ describe("isYinStem", () => {
116
+ it("should return true for yin stems", () => {
117
+ expect(isYinStem("乙")).toBe(true);
118
+ expect(isYinStem("丁")).toBe(true);
119
+ expect(isYinStem("己")).toBe(true);
120
+ expect(isYinStem("辛")).toBe(true);
121
+ expect(isYinStem("癸")).toBe(true);
122
+ });
123
+ it("should return false for yang stems", () => {
124
+ expect(isYinStem("甲")).toBe(false);
125
+ expect(isYinStem("丙")).toBe(false);
126
+ expect(isYinStem("戊")).toBe(false);
127
+ expect(isYinStem("庚")).toBe(false);
128
+ expect(isYinStem("壬")).toBe(false);
129
+ });
130
+ });
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from "vitest";
2
- import { analyzeYongShen, getElementRecommendations } from "@/core/yongshen";
1
+ import { describe, expect, it } from "vitest";
2
+ import { analyzeYongShen, getElementRecommendations } from "../core/yongshen";
3
3
  describe("yongshen", () => {
4
4
  describe("analyzeYongShen", () => {
5
5
  it("returns yongshen analysis result", () => {
@@ -1,4 +1,4 @@
1
- import type { DateAdapter } from "@/adapters/date-adapter";
1
+ import type { DateAdapter } from "../adapters/date-adapter";
2
2
  interface DateFnsDate {
3
3
  date: Date;
4
4
  timeZone: string;
@@ -1,3 +1,3 @@
1
- import type { DateAdapter } from "@/adapters/date-adapter";
1
+ import type { DateAdapter } from "../adapters/date-adapter";
2
2
  export declare function createLuxonAdapter(): Promise<DateAdapter<import("luxon").DateTime>>;
3
3
  //# sourceMappingURL=luxon.d.ts.map
@@ -1,7 +1,7 @@
1
- import type { DateAdapter } from "@/adapters/date-adapter";
2
- import { type LunarDate } from "@/core/lunar";
3
- export declare const STEMS: string[];
4
- export declare const BRANCHES: string[];
1
+ import type { DateAdapter } from "../adapters/date-adapter";
2
+ import { type LunarDate } from "../core/lunar";
3
+ import { BRANCHES, STEMS } from "../utils";
4
+ export { STEMS, BRANCHES };
5
5
  export declare const STANDARD_PRESET: {
6
6
  dayBoundary: "midnight";
7
7
  useMeanSolarTimeForHour: boolean;
@@ -65,8 +65,8 @@ export declare function hourPillar<T>(adapter: DateAdapter<T>, dtLocal: T, { lon
65
65
  day: number;
66
66
  };
67
67
  };
68
- export declare function getFourPillars<T>(adapter: DateAdapter<T>, dtLocal: T, { longitudeDeg, tzOffsetHours, preset, }: {
69
- longitudeDeg: number;
68
+ export declare function getFourPillars<T>(adapter: DateAdapter<T>, dtLocal: T, { longitudeDeg, tzOffsetHours, preset, }?: {
69
+ longitudeDeg?: number;
70
70
  tzOffsetHours?: number;
71
71
  preset?: typeof presetA | typeof presetB;
72
72
  }): {
@@ -1 +1 @@
1
- {"version":3,"file":"four-pillars.d.ts","sourceRoot":"","sources":["../../src/core/four-pillars.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"four-pillars.d.ts","sourceRoot":"","sources":["../../src/core/four-pillars.ts"],"names":[],"mappings":"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"}