ether-to-astro 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.
Files changed (138) hide show
  1. package/.env.example +13 -0
  2. package/.github/pull_request_template.md +16 -0
  3. package/.github/workflows/release.yml +35 -0
  4. package/.github/workflows/test.yml +32 -0
  5. package/AGENTS.md +99 -0
  6. package/LICENSE +18 -0
  7. package/NOTICE.md +45 -0
  8. package/README.md +301 -0
  9. package/SETUP.md +70 -0
  10. package/TESTING_SUMMARY.md +238 -0
  11. package/TEST_SUITE_STATUS.md +218 -0
  12. package/biome.json +48 -0
  13. package/dist/astro-service.d.ts +98 -0
  14. package/dist/astro-service.js +496 -0
  15. package/dist/chart-types.d.ts +52 -0
  16. package/dist/chart-types.js +51 -0
  17. package/dist/charts.d.ts +125 -0
  18. package/dist/charts.js +324 -0
  19. package/dist/cli.d.ts +7 -0
  20. package/dist/cli.js +472 -0
  21. package/dist/constants.d.ts +81 -0
  22. package/dist/constants.js +76 -0
  23. package/dist/eclipses.d.ts +85 -0
  24. package/dist/eclipses.js +184 -0
  25. package/dist/ephemeris.d.ts +120 -0
  26. package/dist/ephemeris.js +379 -0
  27. package/dist/formatter.d.ts +2 -0
  28. package/dist/formatter.js +22 -0
  29. package/dist/houses.d.ts +82 -0
  30. package/dist/houses.js +169 -0
  31. package/dist/index.d.ts +14 -0
  32. package/dist/index.js +150 -0
  33. package/dist/loader.d.ts +2 -0
  34. package/dist/loader.js +31 -0
  35. package/dist/logger.d.ts +25 -0
  36. package/dist/logger.js +73 -0
  37. package/dist/profile-store.d.ts +48 -0
  38. package/dist/profile-store.js +156 -0
  39. package/dist/riseset.d.ts +82 -0
  40. package/dist/riseset.js +185 -0
  41. package/dist/storage.d.ts +10 -0
  42. package/dist/storage.js +40 -0
  43. package/dist/time-utils.d.ts +68 -0
  44. package/dist/time-utils.js +136 -0
  45. package/dist/tool-registry.d.ts +35 -0
  46. package/dist/tool-registry.js +307 -0
  47. package/dist/tool-result.d.ts +175 -0
  48. package/dist/tool-result.js +188 -0
  49. package/dist/transits.d.ts +108 -0
  50. package/dist/transits.js +263 -0
  51. package/dist/types.d.ts +450 -0
  52. package/dist/types.js +161 -0
  53. package/example-usage.md +131 -0
  54. package/natal-chart.json +187 -0
  55. package/package.json +61 -0
  56. package/scripts/download-ephemeris.js +115 -0
  57. package/setup.sh +21 -0
  58. package/src/astro-service.ts +710 -0
  59. package/src/chart-types.ts +125 -0
  60. package/src/charts.ts +399 -0
  61. package/src/cli.ts +694 -0
  62. package/src/constants.ts +89 -0
  63. package/src/eclipses.ts +226 -0
  64. package/src/ephemeris.ts +437 -0
  65. package/src/formatter.ts +25 -0
  66. package/src/houses.ts +202 -0
  67. package/src/index.ts +170 -0
  68. package/src/loader.ts +36 -0
  69. package/src/logger.ts +104 -0
  70. package/src/profile-store.ts +285 -0
  71. package/src/riseset.ts +229 -0
  72. package/src/time-utils.ts +167 -0
  73. package/src/tool-registry.ts +357 -0
  74. package/src/tool-result.ts +283 -0
  75. package/src/transits.ts +352 -0
  76. package/src/types.ts +547 -0
  77. package/tests/README.md +173 -0
  78. package/tests/TESTING_STRATEGY.md +178 -0
  79. package/tests/fixtures/bowen-yang-chart.ts +69 -0
  80. package/tests/fixtures/calculate-expected.ts +81 -0
  81. package/tests/fixtures/expected-results.ts +117 -0
  82. package/tests/fixtures/generate-expected-simple.ts +94 -0
  83. package/tests/helpers/date-fixtures.ts +15 -0
  84. package/tests/helpers/ephem.ts +11 -0
  85. package/tests/helpers/temp.ts +9 -0
  86. package/tests/setup.ts +11 -0
  87. package/tests/unit/astro-service.test.ts +323 -0
  88. package/tests/unit/chart-types.test.ts +18 -0
  89. package/tests/unit/charts-errors.test.ts +42 -0
  90. package/tests/unit/charts.test.ts +157 -0
  91. package/tests/unit/cli-commands.test.ts +82 -0
  92. package/tests/unit/cli-profiles.test.ts +128 -0
  93. package/tests/unit/cli.test.ts +191 -0
  94. package/tests/unit/constants.test.ts +26 -0
  95. package/tests/unit/correctness-critical.test.ts +408 -0
  96. package/tests/unit/eclipses.test.ts +108 -0
  97. package/tests/unit/ephemeris.test.ts +213 -0
  98. package/tests/unit/error-handling.test.ts +116 -0
  99. package/tests/unit/formatter.test.ts +29 -0
  100. package/tests/unit/houses-errors.test.ts +27 -0
  101. package/tests/unit/houses-validation.test.ts +164 -0
  102. package/tests/unit/houses.test.ts +205 -0
  103. package/tests/unit/profile-store.test.ts +163 -0
  104. package/tests/unit/real-user-charts.test.ts +148 -0
  105. package/tests/unit/riseset.test.ts +106 -0
  106. package/tests/unit/solver-edges.test.ts +197 -0
  107. package/tests/unit/time-utils-temporal.test.ts +303 -0
  108. package/tests/unit/time-utils.test.ts +173 -0
  109. package/tests/unit/tool-registry.test.ts +222 -0
  110. package/tests/unit/tool-result.test.ts +45 -0
  111. package/tests/unit/transit-correctness.test.ts +78 -0
  112. package/tests/unit/transits.test.ts +238 -0
  113. package/tests/validation/README.md +32 -0
  114. package/tests/validation/adapters/astrolog.ts +306 -0
  115. package/tests/validation/adapters/internal.ts +184 -0
  116. package/tests/validation/compare/eclipses.ts +47 -0
  117. package/tests/validation/compare/houses.ts +76 -0
  118. package/tests/validation/compare/positions.ts +104 -0
  119. package/tests/validation/compare/riseSet.ts +48 -0
  120. package/tests/validation/compare/roots.ts +90 -0
  121. package/tests/validation/compare/transits.ts +69 -0
  122. package/tests/validation/fixtures/astrolog-parity/core.ts +194 -0
  123. package/tests/validation/fixtures/eclipses/core.ts +14 -0
  124. package/tests/validation/fixtures/houses/core.ts +47 -0
  125. package/tests/validation/fixtures/positions/core.ts +159 -0
  126. package/tests/validation/fixtures/rise-set/core.ts +20 -0
  127. package/tests/validation/fixtures/roots/core.ts +47 -0
  128. package/tests/validation/fixtures/transits/core.ts +61 -0
  129. package/tests/validation/fixtures/transits/dst.ts +21 -0
  130. package/tests/validation/oracle.spec.ts +129 -0
  131. package/tests/validation/utils/denseRootOracle.ts +269 -0
  132. package/tests/validation/utils/fixtureTypes.ts +146 -0
  133. package/tests/validation/utils/report.ts +60 -0
  134. package/tests/validation/utils/tolerances.ts +23 -0
  135. package/tests/validation/validation.spec.ts +836 -0
  136. package/tools/color-picker.html +388 -0
  137. package/tsconfig.json +17 -0
  138. package/vitest.config.ts +31 -0
@@ -0,0 +1,104 @@
1
+ import type { NormalizedBody } from '../utils/fixtureTypes.js';
2
+ import type { ValidationReport } from '../utils/report.js';
3
+ import { TOLERANCES } from '../utils/tolerances.js';
4
+
5
+ function sortBodies(rows: NormalizedBody[]): NormalizedBody[] {
6
+ return [...rows].sort((a, b) => String(a.body).localeCompare(String(b.body)));
7
+ }
8
+
9
+ export function comparePositions(
10
+ fixtureName: string,
11
+ expected: NormalizedBody[],
12
+ actual: NormalizedBody[],
13
+ report: ValidationReport,
14
+ subsystem = 'positions'
15
+ ): void {
16
+ const expectedSorted = sortBodies(expected);
17
+ const actualSorted = sortBodies(actual);
18
+
19
+ if (expectedSorted.length !== actualSorted.length) {
20
+ report.addHard({
21
+ fixture: fixtureName,
22
+ subsystem,
23
+ expected: expectedSorted.length,
24
+ actual: actualSorted.length,
25
+ delta: actualSorted.length - expectedSorted.length,
26
+ tolerance: 0,
27
+ message: 'Body count mismatch',
28
+ });
29
+ return;
30
+ }
31
+
32
+ for (let i = 0; i < expectedSorted.length; i++) {
33
+ const e = expectedSorted[i];
34
+ const a = actualSorted[i];
35
+
36
+ if (e.body !== a.body) {
37
+ report.addHard({
38
+ fixture: fixtureName,
39
+ subsystem,
40
+ expected: e.body,
41
+ actual: a.body,
42
+ delta: null,
43
+ tolerance: 'exact',
44
+ message: 'Body mismatch at sorted index',
45
+ });
46
+ continue;
47
+ }
48
+
49
+ const lonDelta = Math.abs(e.longitude - a.longitude);
50
+ if (lonDelta > TOLERANCES.positionLongitudeDeg) {
51
+ report.addHard({
52
+ fixture: fixtureName,
53
+ subsystem,
54
+ expected: e.longitude,
55
+ actual: a.longitude,
56
+ delta: lonDelta,
57
+ tolerance: TOLERANCES.positionLongitudeDeg,
58
+ message: `${e.body} longitude delta exceeds tolerance`,
59
+ });
60
+ }
61
+
62
+ if (e.latitude != null && a.latitude != null) {
63
+ const latDelta = Math.abs(e.latitude - a.latitude);
64
+ if (latDelta > TOLERANCES.positionLatitudeDeg) {
65
+ report.addHard({
66
+ fixture: fixtureName,
67
+ subsystem,
68
+ expected: e.latitude,
69
+ actual: a.latitude,
70
+ delta: latDelta,
71
+ tolerance: TOLERANCES.positionLatitudeDeg,
72
+ message: `${e.body} latitude delta exceeds tolerance`,
73
+ });
74
+ }
75
+ }
76
+
77
+ if (e.speed != null && a.speed != null) {
78
+ const speedDelta = Math.abs(e.speed - a.speed);
79
+ if (speedDelta > TOLERANCES.positionSpeedDegPerDay) {
80
+ report.addHard({
81
+ fixture: fixtureName,
82
+ subsystem,
83
+ expected: e.speed,
84
+ actual: a.speed,
85
+ delta: speedDelta,
86
+ tolerance: TOLERANCES.positionSpeedDegPerDay,
87
+ message: `${e.body} speed delta exceeds tolerance`,
88
+ });
89
+ }
90
+ }
91
+
92
+ if (e.retrograde != null && a.retrograde != null && e.retrograde !== a.retrograde) {
93
+ report.addHard({
94
+ fixture: fixtureName,
95
+ subsystem,
96
+ expected: e.retrograde,
97
+ actual: a.retrograde,
98
+ delta: null,
99
+ tolerance: 'exact',
100
+ message: `${e.body} retrograde flag mismatch`,
101
+ });
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,48 @@
1
+ import type { NormalizedRiseSet } from '../utils/fixtureTypes.js';
2
+ import type { ValidationReport } from '../utils/report.js';
3
+ import { minutesBetweenIso, TOLERANCES } from '../utils/tolerances.js';
4
+
5
+ export function compareRiseSet(
6
+ fixtureName: string,
7
+ expected: NormalizedRiseSet,
8
+ actual: NormalizedRiseSet,
9
+ report: ValidationReport
10
+ ): void {
11
+ const fields: Array<keyof Omit<NormalizedRiseSet, 'body'>> = [
12
+ 'rise',
13
+ 'set',
14
+ 'upperMeridianTransit',
15
+ 'lowerMeridianTransit',
16
+ ];
17
+
18
+ for (const field of fields) {
19
+ const e = expected[field];
20
+ const a = actual[field];
21
+ if (e == null && a == null) continue;
22
+ if ((e == null) !== (a == null)) {
23
+ report.addHard({
24
+ fixture: fixtureName,
25
+ subsystem: 'rise-set',
26
+ expected: e,
27
+ actual: a,
28
+ delta: null,
29
+ tolerance: 'exact',
30
+ message: `${field} presence mismatch`,
31
+ });
32
+ continue;
33
+ }
34
+
35
+ const delta = minutesBetweenIso(e as string, a as string);
36
+ if (delta > TOLERANCES.riseSetMinutes) {
37
+ report.addHard({
38
+ fixture: fixtureName,
39
+ subsystem: 'rise-set',
40
+ expected: e,
41
+ actual: a,
42
+ delta,
43
+ tolerance: TOLERANCES.riseSetMinutes,
44
+ message: `${field} timing exceeds tolerance`,
45
+ });
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,90 @@
1
+ import type { NormalizedRoot } from '../utils/fixtureTypes.js';
2
+ import type { ValidationReport } from '../utils/report.js';
3
+ import { minutesBetweenIso, TOLERANCES } from '../utils/tolerances.js';
4
+
5
+ function dedupeRootsByMinutes(roots: NormalizedRoot[], minutes: number): NormalizedRoot[] {
6
+ const sorted = [...roots].sort((a, b) => a.jd - b.jd);
7
+ const deduped: NormalizedRoot[] = [];
8
+ for (const r of sorted) {
9
+ const last = deduped[deduped.length - 1];
10
+ if (!last || minutesBetweenIso(last.isoUtc, r.isoUtc) > minutes) {
11
+ deduped.push(r);
12
+ }
13
+ }
14
+ return deduped;
15
+ }
16
+
17
+ export function compareRoots(
18
+ fixtureName: string,
19
+ productionRoots: NormalizedRoot[],
20
+ oracleRoots: NormalizedRoot[],
21
+ report: ValidationReport,
22
+ details?: unknown
23
+ ): void {
24
+ const dedupeMinutes = TOLERANCES.dedupeMinutes;
25
+ const normalizedProduction = dedupeRootsByMinutes(productionRoots, dedupeMinutes);
26
+ const normalizedOracle = dedupeRootsByMinutes(oracleRoots, dedupeMinutes);
27
+
28
+ if (normalizedProduction.length !== normalizedOracle.length) {
29
+ report.addHard({
30
+ fixture: fixtureName,
31
+ subsystem: 'roots',
32
+ expected: normalizedOracle.length,
33
+ actual: normalizedProduction.length,
34
+ delta: normalizedProduction.length - normalizedOracle.length,
35
+ tolerance: 0,
36
+ message: 'Root count mismatch (production vs dense-scan oracle, deduped)',
37
+ details,
38
+ });
39
+ }
40
+
41
+ const n = Math.min(normalizedProduction.length, normalizedOracle.length);
42
+ for (let i = 0; i < n; i++) {
43
+ const prod = normalizedProduction[i];
44
+ const oracle = normalizedOracle[i];
45
+ const deltaMinutes = minutesBetweenIso(prod.isoUtc, oracle.isoUtc);
46
+
47
+ if (deltaMinutes > TOLERANCES.rootHardMinutes) {
48
+ report.addHard({
49
+ fixture: fixtureName,
50
+ subsystem: 'roots',
51
+ expected: oracle.isoUtc,
52
+ actual: prod.isoUtc,
53
+ delta: deltaMinutes,
54
+ tolerance: TOLERANCES.rootHardMinutes,
55
+ message: `Root ${i} timing exceeds hard threshold`,
56
+ details,
57
+ });
58
+ continue;
59
+ }
60
+
61
+ if (deltaMinutes > TOLERANCES.rootPreferredMinutes) {
62
+ report.addWarning({
63
+ fixture: fixtureName,
64
+ subsystem: 'roots',
65
+ expected: oracle.isoUtc,
66
+ actual: prod.isoUtc,
67
+ delta: deltaMinutes,
68
+ tolerance: TOLERANCES.rootPreferredMinutes,
69
+ message: `Root ${i} timing exceeds preferred threshold`,
70
+ details,
71
+ });
72
+ }
73
+ }
74
+
75
+ for (let i = 1; i < normalizedProduction.length; i++) {
76
+ if (normalizedProduction[i].jd < normalizedProduction[i - 1].jd) {
77
+ report.addHard({
78
+ fixture: fixtureName,
79
+ subsystem: 'roots',
80
+ expected: 'sorted ascending',
81
+ actual: normalizedProduction.map((r) => r.jd),
82
+ delta: null,
83
+ tolerance: 'exact',
84
+ message: 'Production roots are not sorted earliest-first',
85
+ details,
86
+ });
87
+ break;
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,69 @@
1
+ import type { NormalizedTransit } from '../utils/fixtureTypes.js';
2
+ import type { ValidationReport } from '../utils/report.js';
3
+
4
+ export function findTransit(
5
+ transits: NormalizedTransit[],
6
+ transitingPlanet: string,
7
+ natalPlanet: string,
8
+ aspect: string
9
+ ): NormalizedTransit | undefined {
10
+ return transits.find(
11
+ (t) =>
12
+ t.transitingPlanet === transitingPlanet &&
13
+ t.natalPlanet === natalPlanet &&
14
+ t.aspect === aspect
15
+ );
16
+ }
17
+
18
+ export function assertTransitStatus(
19
+ fixtureName: string,
20
+ transit: NormalizedTransit | undefined,
21
+ expectedStatus:
22
+ | 'within_preview'
23
+ | 'outside_preview'
24
+ | 'not_found'
25
+ | 'unsupported_body'
26
+ | 'undefined',
27
+ report: ValidationReport
28
+ ): void {
29
+ if (!transit) {
30
+ report.addHard({
31
+ fixture: fixtureName,
32
+ subsystem: 'transits',
33
+ expected: 'transit exists',
34
+ actual: 'not found',
35
+ delta: null,
36
+ tolerance: 'exact',
37
+ message: 'Expected transit was not found',
38
+ });
39
+ return;
40
+ }
41
+
42
+ const actual = transit.exactTimeStatus;
43
+ if (expectedStatus === 'undefined') {
44
+ if (actual !== undefined) {
45
+ report.addHard({
46
+ fixture: fixtureName,
47
+ subsystem: 'transits',
48
+ expected: undefined,
49
+ actual,
50
+ delta: null,
51
+ tolerance: 'exact',
52
+ message: 'Expected exactTimeStatus to be undefined',
53
+ });
54
+ }
55
+ return;
56
+ }
57
+
58
+ if (actual !== expectedStatus) {
59
+ report.addHard({
60
+ fixture: fixtureName,
61
+ subsystem: 'transits',
62
+ expected: expectedStatus,
63
+ actual,
64
+ delta: null,
65
+ tolerance: 'exact',
66
+ message: 'Exact-time status mismatch',
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,194 @@
1
+ import { PLANETS } from '../../../../src/types.js';
2
+ import type {
3
+ AstrologEdgeParityFixture,
4
+ AstrologHouseParityFixture,
5
+ AstrologPositionParityFixture,
6
+ AstrologTransitSnapshotFixture,
7
+ } from '../../utils/fixtureTypes.js';
8
+
9
+ const CORE_PARITY_PLANET_IDS = [
10
+ PLANETS.SUN,
11
+ PLANETS.MOON,
12
+ PLANETS.MERCURY,
13
+ PLANETS.VENUS,
14
+ PLANETS.MARS,
15
+ PLANETS.JUPITER,
16
+ PLANETS.SATURN,
17
+ PLANETS.URANUS,
18
+ PLANETS.NEPTUNE,
19
+ PLANETS.PLUTO,
20
+ ];
21
+
22
+ export const astrologPositionParityFixtures: AstrologPositionParityFixture[] = [
23
+ {
24
+ name: 'positions-modern-baseline-noon',
25
+ isoUtc: '2024-03-26T12:00:00Z',
26
+ planetIds: CORE_PARITY_PLANET_IDS,
27
+ },
28
+ {
29
+ name: 'positions-modern-baseline-midnight',
30
+ isoUtc: '2024-03-26T00:00:00Z',
31
+ planetIds: CORE_PARITY_PLANET_IDS,
32
+ },
33
+ {
34
+ name: 'positions-leap-day-2024-02-29',
35
+ isoUtc: '2024-02-29T12:00:00Z',
36
+ planetIds: CORE_PARITY_PLANET_IDS,
37
+ },
38
+ {
39
+ name: 'positions-year-boundary-dec31',
40
+ isoUtc: '2024-12-31T23:59:00Z',
41
+ planetIds: CORE_PARITY_PLANET_IDS,
42
+ },
43
+ {
44
+ name: 'positions-spring-equinox-window',
45
+ isoUtc: '2024-03-20T12:00:00Z',
46
+ planetIds: CORE_PARITY_PLANET_IDS,
47
+ },
48
+ {
49
+ name: 'positions-autumn-equinox-window',
50
+ isoUtc: '2024-09-22T12:00:00Z',
51
+ planetIds: CORE_PARITY_PLANET_IDS,
52
+ },
53
+ {
54
+ name: 'positions-mercury-retrograde',
55
+ isoUtc: '2024-04-10T12:00:00Z',
56
+ planetIds: CORE_PARITY_PLANET_IDS,
57
+ },
58
+ {
59
+ name: 'positions-venus-retrograde',
60
+ isoUtc: '2023-08-10T12:00:00Z',
61
+ planetIds: CORE_PARITY_PLANET_IDS,
62
+ },
63
+ {
64
+ name: 'positions-mars-station-window',
65
+ isoUtc: '2024-12-08T12:00:00Z',
66
+ planetIds: CORE_PARITY_PLANET_IDS,
67
+ },
68
+ {
69
+ name: 'positions-pluto-or-uranus-station-window',
70
+ isoUtc: '2024-09-02T12:00:00Z',
71
+ planetIds: CORE_PARITY_PLANET_IDS,
72
+ },
73
+ {
74
+ name: 'positions-far-past-date',
75
+ isoUtc: '1905-06-15T12:00:00Z',
76
+ planetIds: CORE_PARITY_PLANET_IDS,
77
+ },
78
+ {
79
+ name: 'positions-near-future-date',
80
+ isoUtc: '2035-05-10T12:00:00Z',
81
+ planetIds: CORE_PARITY_PLANET_IDS,
82
+ },
83
+ ];
84
+
85
+ export const astrologHouseParityFixtures: AstrologHouseParityFixture[] = [
86
+ {
87
+ name: 'houses-whole-sign-midlat-north',
88
+ isoUtc: '2024-03-26T12:00:00Z',
89
+ latitude: 40.7128,
90
+ longitude: -74.006,
91
+ houseSystem: 'W',
92
+ },
93
+ {
94
+ name: 'houses-whole-sign-southern-hemisphere',
95
+ isoUtc: '2024-09-01T00:00:00Z',
96
+ latitude: -33.8688,
97
+ longitude: 151.2093,
98
+ houseSystem: 'W',
99
+ },
100
+ {
101
+ name: 'houses-whole-sign-date-line-ish-longitude',
102
+ isoUtc: '2024-06-01T12:00:00Z',
103
+ latitude: -18.1248,
104
+ longitude: 178.4501,
105
+ houseSystem: 'W',
106
+ },
107
+ {
108
+ name: 'houses-placidus-midlat-sanity',
109
+ isoUtc: '2024-03-26T12:00:00Z',
110
+ latitude: 51.5072,
111
+ longitude: -0.1276,
112
+ houseSystem: 'P',
113
+ },
114
+ {
115
+ name: 'houses-high-latitude-fallback',
116
+ isoUtc: '2024-12-21T00:00:00Z',
117
+ latitude: 78.2232,
118
+ longitude: 15.6267,
119
+ houseSystem: 'P',
120
+ expectFallbackToWholeSign: true,
121
+ },
122
+ ];
123
+
124
+ export const astrologTransitSnapshotFixtures: AstrologTransitSnapshotFixture[] = [
125
+ {
126
+ name: 'transits-mercury-retrograde-snapshot',
127
+ currentIsoUtc: '2024-04-10T12:00:00Z',
128
+ transitingPlanetId: PLANETS.MERCURY,
129
+ natalPlanetId: PLANETS.VENUS,
130
+ natalOffsetDegrees: 92,
131
+ expectedAspect: 'square',
132
+ maxOrb: 8,
133
+ },
134
+ {
135
+ name: 'transits-moon-fast-motion-snapshot',
136
+ currentIsoUtc: '2024-03-26T12:00:00Z',
137
+ transitingPlanetId: PLANETS.MOON,
138
+ natalPlanetId: PLANETS.MARS,
139
+ natalOffsetDegrees: 120,
140
+ expectedAspect: 'trine',
141
+ maxOrb: 8,
142
+ },
143
+ {
144
+ name: 'transits-square-dual-target',
145
+ currentIsoUtc: '2024-03-15T00:00:00Z',
146
+ transitingPlanetId: PLANETS.MARS,
147
+ natalPlanetId: PLANETS.VENUS,
148
+ natalOffsetDegrees: 90,
149
+ expectedAspect: 'square',
150
+ maxOrb: 8,
151
+ },
152
+ {
153
+ name: 'transits-trine-or-sextile-dual-target',
154
+ currentIsoUtc: '2024-03-15T00:00:00Z',
155
+ transitingPlanetId: PLANETS.JUPITER,
156
+ natalPlanetId: PLANETS.SUN,
157
+ natalOffsetDegrees: 60,
158
+ expectedAspect: 'sextile',
159
+ maxOrb: 8,
160
+ },
161
+ {
162
+ name: 'transits-slow-mover-in-orb',
163
+ currentIsoUtc: '2024-10-01T00:00:00Z',
164
+ transitingPlanetId: PLANETS.PLUTO,
165
+ natalPlanetId: PLANETS.MARS,
166
+ natalOffsetDegrees: 180,
167
+ expectedAspect: 'opposition',
168
+ maxOrb: 8,
169
+ },
170
+ ];
171
+
172
+ export const astrologEdgeParityFixtures: AstrologEdgeParityFixture[] = [
173
+ {
174
+ name: 'edge-longitude-wrap-near-zero',
175
+ isoUtc: '2024-03-20T03:06:00Z',
176
+ planetIds: CORE_PARITY_PLANET_IDS,
177
+ },
178
+ {
179
+ name: 'edge-dst-ambiguous-local-time',
180
+ isoUtc: '2024-11-03T08:30:00.000Z',
181
+ planetIds: CORE_PARITY_PLANET_IDS,
182
+ local: { year: 2024, month: 11, day: 3, hour: 1, minute: 30 },
183
+ timezone: 'America/Los_Angeles',
184
+ disambiguation: 'earlier',
185
+ },
186
+ {
187
+ name: 'edge-dst-nonexistent-local-time',
188
+ isoUtc: '2024-03-10T10:30:00.000Z',
189
+ planetIds: CORE_PARITY_PLANET_IDS,
190
+ local: { year: 2024, month: 3, day: 10, hour: 2, minute: 30 },
191
+ timezone: 'America/Los_Angeles',
192
+ disambiguation: 'compatible',
193
+ },
194
+ ];
@@ -0,0 +1,14 @@
1
+ import type { EclipseFixture } from '../../utils/fixtureTypes.js';
2
+
3
+ export const eclipseFixtures: EclipseFixture[] = [
4
+ {
5
+ name: 'next-solar-eclipse-sanity',
6
+ startIsoUtc: '2024-03-26T00:00:00Z',
7
+ type: 'solar',
8
+ },
9
+ {
10
+ name: 'next-lunar-eclipse-sanity',
11
+ startIsoUtc: '2024-03-26T00:00:00Z',
12
+ type: 'lunar',
13
+ },
14
+ ];
@@ -0,0 +1,47 @@
1
+ import type { HouseFixture } from '../../utils/fixtureTypes.js';
2
+
3
+ export const houseFixtures: HouseFixture[] = [
4
+ {
5
+ name: 'nyc-placidus',
6
+ isoUtc: '2024-03-26T12:00:00Z',
7
+ latitude: 40.7128,
8
+ longitude: -74.006,
9
+ houseSystem: 'P',
10
+ expected: {
11
+ system: 'P',
12
+ ascendant: 33.98295887285655,
13
+ mc: 288.8571573752222,
14
+ cusps: [
15
+ 33.98295887285655, 65.33379924755742, 87.90228930026863, 108.85715737522219,
16
+ 132.88655769961264, 166.17376459102024, 213.98295887285656, 245.3337992475574,
17
+ 267.90228930026865, 288.8571573752222, 312.88655769961264, 346.1737645910202,
18
+ ],
19
+ },
20
+ },
21
+ {
22
+ name: 'svalbard-polar-fallback',
23
+ isoUtc: '2024-03-26T12:00:00Z',
24
+ latitude: 78.2232,
25
+ longitude: 15.6267,
26
+ houseSystem: 'P',
27
+ expected: {
28
+ system: 'W',
29
+ ascendant: 157.0869567445154,
30
+ mc: 21.69267914127279,
31
+ cusps: [150, 180, 210, 240, 270, 300, 330, 0, 30, 60, 90, 120],
32
+ },
33
+ },
34
+ {
35
+ name: 'sydney-whole-sign',
36
+ isoUtc: '2024-09-01T00:00:00Z',
37
+ latitude: -33.8688,
38
+ longitude: 151.2093,
39
+ houseSystem: 'W',
40
+ expected: {
41
+ system: 'W',
42
+ ascendant: 238.03867274058655,
43
+ mc: 129.421733545281,
44
+ cusps: [210, 240, 270, 300, 330, 0, 30, 60, 90, 120, 150, 180],
45
+ },
46
+ },
47
+ ];