ether-to-astro 1.2.0 → 1.3.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 (66) hide show
  1. package/README.md +15 -5
  2. package/dist/astro-service/chart-output-service.d.ts +44 -0
  3. package/dist/astro-service/chart-output-service.js +110 -0
  4. package/dist/astro-service/date-input.d.ts +14 -0
  5. package/dist/astro-service/date-input.js +30 -0
  6. package/dist/astro-service/electional-service.d.ts +45 -0
  7. package/dist/astro-service/electional-service.js +305 -0
  8. package/dist/astro-service/natal-service.d.ts +41 -0
  9. package/dist/astro-service/natal-service.js +179 -0
  10. package/dist/astro-service/rising-sign-service.d.ts +37 -0
  11. package/dist/astro-service/rising-sign-service.js +137 -0
  12. package/dist/astro-service/service-types.d.ts +82 -0
  13. package/dist/astro-service/service-types.js +1 -0
  14. package/dist/astro-service/shared.d.ts +65 -0
  15. package/dist/astro-service/shared.js +98 -0
  16. package/dist/astro-service/sky-service.d.ts +48 -0
  17. package/dist/astro-service/sky-service.js +144 -0
  18. package/dist/astro-service/transit-service.d.ts +82 -0
  19. package/dist/astro-service/transit-service.js +353 -0
  20. package/dist/astro-service.d.ts +101 -89
  21. package/dist/astro-service.js +162 -1042
  22. package/dist/tool-registry.js +1 -1
  23. package/docs/product/architecture-boundaries.md +8 -0
  24. package/docs/releases/1.3.0.md +51 -0
  25. package/docs/releases/README.md +17 -0
  26. package/package.json +4 -1
  27. package/src/astro-service/chart-output-service.ts +155 -0
  28. package/src/astro-service/date-input.ts +40 -0
  29. package/src/astro-service/electional-service.ts +395 -0
  30. package/src/astro-service/natal-service.ts +235 -0
  31. package/src/astro-service/rising-sign-service.ts +181 -0
  32. package/src/astro-service/service-types.ts +90 -0
  33. package/src/astro-service/shared.ts +128 -0
  34. package/src/astro-service/sky-service.ts +191 -0
  35. package/src/astro-service/transit-service.ts +507 -0
  36. package/src/astro-service.ts +177 -1386
  37. package/src/tool-registry.ts +1 -1
  38. package/tests/README.md +15 -0
  39. package/tests/property/electional-service.property.test.ts +67 -0
  40. package/tests/property/helpers/arbitraries.ts +126 -0
  41. package/tests/property/helpers/config.ts +52 -0
  42. package/tests/property/helpers/runtime.ts +12 -0
  43. package/tests/property/houses.property.test.ts +74 -0
  44. package/tests/property/rising-sign-service.property.test.ts +255 -0
  45. package/tests/property/service-transits.property.test.ts +154 -0
  46. package/tests/property/time-utils.property.test.ts +91 -0
  47. package/tests/property/transits.property.test.ts +113 -0
  48. package/tests/unit/astro-service/chart-output-service.test.ts +102 -0
  49. package/tests/unit/astro-service/electional-service.test.ts +182 -0
  50. package/tests/unit/astro-service/natal-service.test.ts +126 -0
  51. package/tests/unit/astro-service/rising-sign-service.test.ts +145 -0
  52. package/tests/unit/astro-service/sky-service.test.ts +130 -0
  53. package/tests/unit/astro-service/transit-service.test.ts +312 -0
  54. package/tests/unit/astro-service.test.ts +136 -781
  55. package/tests/unit/rising-sign-windows.test.ts +93 -0
  56. package/tests/unit/tool-registry.test.ts +11 -0
  57. package/tests/validation/README.md +14 -0
  58. package/tests/validation/adapters/internal.ts +234 -4
  59. package/tests/validation/compare/electional.ts +151 -0
  60. package/tests/validation/compare/rising-sign-windows.ts +347 -0
  61. package/tests/validation/compare/service-transits.ts +205 -0
  62. package/tests/validation/fixtures/electional/core.ts +88 -0
  63. package/tests/validation/fixtures/rising-sign-windows/core.ts +57 -0
  64. package/tests/validation/fixtures/service-transits/core.ts +89 -0
  65. package/tests/validation/utils/fixtureTypes.ts +139 -1
  66. package/tests/validation/validation.spec.ts +82 -0
@@ -0,0 +1,89 @@
1
+ import { PLANETS } from '../../../../src/types.js';
2
+ import type { ServiceTransitFixture } from '../../utils/fixtureTypes.js';
3
+
4
+ export const serviceTransitFixtures: ServiceTransitFixture[] = [
5
+ {
6
+ name: 'tokyo-local-noon-sign-carry-snapshot',
7
+ natalChart: {
8
+ name: 'Tokyo Boundary Chart',
9
+ latitude: 35.6762,
10
+ longitude: 139.6503,
11
+ timezone: 'Asia/Tokyo',
12
+ julianDayIsoUtc: '1990-06-12T03:00:00Z',
13
+ houseSystem: 'P',
14
+ planetOffsets: [
15
+ {
16
+ transitingPlanetId: PLANETS.SUN,
17
+ natalPlanetId: PLANETS.SUN,
18
+ natalOffsetDegrees: 0,
19
+ },
20
+ ],
21
+ },
22
+ input: {
23
+ date: '2024-03-20',
24
+ mode: 'snapshot',
25
+ categories: ['personal'],
26
+ },
27
+ startupDefaults: {
28
+ preferredTimezone: 'UTC',
29
+ },
30
+ expected: {
31
+ mode: 'snapshot',
32
+ timezone: 'UTC',
33
+ calculationTimezone: 'Asia/Tokyo',
34
+ reportingTimezone: 'UTC',
35
+ windowStart: '2024-03-20',
36
+ windowEnd: '2024-03-20',
37
+ expectTransit: {
38
+ transitingPlanet: 'Sun',
39
+ natalPlanet: 'Sun',
40
+ aspect: 'conjunction',
41
+ transitSign: 'Aries',
42
+ transitDegree: 0,
43
+ natalSign: 'Aries',
44
+ natalDegree: 0,
45
+ },
46
+ },
47
+ },
48
+ {
49
+ name: 'los-angeles-to-tokyo-forecast-grouping',
50
+ natalChart: {
51
+ name: 'Pacific Forecast Chart',
52
+ latitude: 37.7749,
53
+ longitude: -122.4194,
54
+ timezone: 'America/Los_Angeles',
55
+ julianDayIsoUtc: '1990-06-12T21:35:00Z',
56
+ houseSystem: 'P',
57
+ planetOffsets: [
58
+ {
59
+ transitingPlanetId: PLANETS.SUN,
60
+ natalPlanetId: PLANETS.SUN,
61
+ natalOffsetDegrees: 0,
62
+ },
63
+ ],
64
+ },
65
+ input: {
66
+ date: '2024-03-26',
67
+ mode: 'forecast',
68
+ days_ahead: 1,
69
+ categories: ['personal'],
70
+ },
71
+ startupDefaults: {
72
+ preferredTimezone: 'Asia/Tokyo',
73
+ },
74
+ expected: {
75
+ mode: 'forecast',
76
+ timezone: 'Asia/Tokyo',
77
+ calculationTimezone: 'America/Los_Angeles',
78
+ reportingTimezone: 'Asia/Tokyo',
79
+ windowStart: '2024-03-27',
80
+ windowEnd: '2024-03-28',
81
+ forecastDays: 2,
82
+ expectTransit: {
83
+ transitingPlanet: 'Sun',
84
+ natalPlanet: 'Sun',
85
+ aspect: 'conjunction',
86
+ },
87
+ },
88
+ },
89
+ ];
@@ -1,4 +1,10 @@
1
- import type { PlanetName } from '../../../src/types.js';
1
+ import type {
2
+ GetElectionalContextInput,
3
+ GetRisingSignWindowsInput,
4
+ GetTransitsInput,
5
+ } from '../../../src/astro-service/service-types.js';
6
+ import type { McpStartupDefaults } from '../../../src/entrypoint.js';
7
+ import type { HouseSystem, PlanetName } from '../../../src/types.js';
2
8
 
3
9
  export interface NormalizedBody {
4
10
  body: PlanetName | string;
@@ -44,6 +50,70 @@ export interface NormalizedEclipse {
44
50
  maxTime: string;
45
51
  }
46
52
 
53
+ export interface NormalizedElectionalContext {
54
+ houseSystem: string;
55
+ classification: 'day' | 'night';
56
+ isDayChart: boolean;
57
+ sunAltitudeDegrees: number;
58
+ rawSunAltitudeDegrees: number;
59
+ sunAltitudeDisplaysZero: boolean;
60
+ warnings: string[];
61
+ hasApplyingAspects: boolean;
62
+ applyingAspectCount: number;
63
+ hasMoonApplyingAspects: boolean;
64
+ moonApplyingAspectCount: number;
65
+ hasRulerBasics: boolean;
66
+ }
67
+
68
+ export interface NormalizedRisingSignWindow {
69
+ sign: string;
70
+ start: string;
71
+ end: string;
72
+ durationMs: number;
73
+ }
74
+
75
+ export interface NormalizedRisingSignWindowResult {
76
+ date: string;
77
+ timezone: string;
78
+ mode: 'approximate' | 'exact';
79
+ windows: NormalizedRisingSignWindow[];
80
+ }
81
+
82
+ export interface NormalizedServiceTransit {
83
+ transitingPlanet: string;
84
+ natalPlanet: string;
85
+ aspect: string;
86
+ orb: number;
87
+ exactTime?: string;
88
+ exactTimeStatus?: 'within_preview' | 'outside_preview' | 'not_found' | 'unsupported_body';
89
+ isApplying: boolean;
90
+ transitSign?: string;
91
+ transitDegree?: number;
92
+ transitHouse?: number;
93
+ natalSign?: string;
94
+ natalDegree?: number;
95
+ natalHouse?: number;
96
+ }
97
+
98
+ export interface NormalizedServiceTransitForecastDay {
99
+ date: string;
100
+ transits: NormalizedServiceTransit[];
101
+ }
102
+
103
+ export interface NormalizedServiceTransitResult {
104
+ mode?: 'snapshot' | 'best_hit' | 'forecast';
105
+ modeSource?: 'legacy_default' | 'explicit';
106
+ date?: string;
107
+ timezone: string;
108
+ calculationTimezone?: string;
109
+ reportingTimezone?: string;
110
+ daysAhead?: number;
111
+ windowStart?: string;
112
+ windowEnd?: string;
113
+ transits?: NormalizedServiceTransit[];
114
+ forecast?: NormalizedServiceTransitForecastDay[];
115
+ }
116
+
47
117
  export interface PositionFixture {
48
118
  name: string;
49
119
  isoUtc: string;
@@ -144,3 +214,71 @@ export interface AstrologEdgeParityFixture {
144
214
  timezone?: string;
145
215
  disambiguation?: 'compatible' | 'earlier' | 'later' | 'reject';
146
216
  }
217
+
218
+ export interface ElectionalFixture {
219
+ name: string;
220
+ input: GetElectionalContextInput;
221
+ expected: {
222
+ classification: 'day' | 'night';
223
+ isDayChart: boolean;
224
+ houseSystem?: string;
225
+ rawSunAltitudeSign?: 'positive' | 'negative' | 'zero';
226
+ sunAltitudeDisplaysZero?: boolean;
227
+ warningsContain?: string[];
228
+ hasApplyingAspects?: boolean;
229
+ hasMoonApplyingAspects?: boolean;
230
+ hasRulerBasics?: boolean;
231
+ };
232
+ }
233
+
234
+ export interface RisingSignWindowsFixture {
235
+ name: string;
236
+ input: GetRisingSignWindowsInput;
237
+ expectedTotalDurationMinutes: number;
238
+ minWindows?: number;
239
+ expectOffsetChange?: boolean;
240
+ }
241
+
242
+ export interface RisingSignModeComparisonFixture {
243
+ name: string;
244
+ baseInput: Omit<GetRisingSignWindowsInput, 'mode'>;
245
+ }
246
+
247
+ export interface ServiceTransitNatalFixture {
248
+ name: string;
249
+ latitude: number;
250
+ longitude: number;
251
+ timezone: string;
252
+ julianDayIsoUtc: string;
253
+ houseSystem?: HouseSystem;
254
+ planetOffsets: Array<{
255
+ transitingPlanetId: number;
256
+ natalPlanetId: number;
257
+ natalOffsetDegrees: number;
258
+ }>;
259
+ }
260
+
261
+ export interface ServiceTransitFixture {
262
+ name: string;
263
+ natalChart: ServiceTransitNatalFixture;
264
+ input: GetTransitsInput;
265
+ startupDefaults?: McpStartupDefaults;
266
+ expected: {
267
+ mode: 'snapshot' | 'best_hit' | 'forecast';
268
+ timezone: string;
269
+ calculationTimezone: string;
270
+ reportingTimezone: string;
271
+ windowStart?: string;
272
+ windowEnd?: string;
273
+ forecastDays?: number;
274
+ expectTransit: {
275
+ transitingPlanet: string;
276
+ natalPlanet: string;
277
+ aspect: string;
278
+ transitSign?: string;
279
+ transitDegree?: number;
280
+ natalSign?: string;
281
+ natalDegree?: number;
282
+ };
283
+ };
284
+ }
@@ -4,9 +4,15 @@ import { localToUTC } from '../../src/time-utils.js';
4
4
  import { PLANETS, type PlanetPosition } from '../../src/types.js';
5
5
  import { getAstrologHouses, getAstrologPositions, probeAstrolog } from './adapters/astrolog.js';
6
6
  import { InternalValidationAdapter } from './adapters/internal.js';
7
+ import { compareElectionalContext } from './compare/electional.js';
7
8
  import { compareHouses } from './compare/houses.js';
8
9
  import { comparePositions } from './compare/positions.js';
10
+ import {
11
+ compareRisingSignModePrecision,
12
+ compareRisingSignWindows,
13
+ } from './compare/rising-sign-windows.js';
9
14
  import { compareRoots } from './compare/roots.js';
15
+ import { compareServiceTransitFixture } from './compare/service-transits.js';
10
16
  import { assertTransitStatus, findTransit } from './compare/transits.js';
11
17
  import {
12
18
  astrologEdgeParityFixtures,
@@ -15,10 +21,16 @@ import {
15
21
  astrologTransitSnapshotFixtures,
16
22
  } from './fixtures/astrolog-parity/core.js';
17
23
  import { eclipseFixtures } from './fixtures/eclipses/core.js';
24
+ import { electionalFixtures } from './fixtures/electional/core.js';
18
25
  import { houseFixtures } from './fixtures/houses/core.js';
19
26
  import { positionFixtures } from './fixtures/positions/core.js';
20
27
  import { riseSetFixtures } from './fixtures/rise-set/core.js';
28
+ import {
29
+ risingSignModeComparisonFixtures,
30
+ risingSignWindowFixtures,
31
+ } from './fixtures/rising-sign-windows/core.js';
21
32
  import { rootFixtures } from './fixtures/roots/core.js';
33
+ import { serviceTransitFixtures } from './fixtures/service-transits/core.js';
22
34
  import { transitFixtures } from './fixtures/transits/core.js';
23
35
  import { dstFixtures } from './fixtures/transits/dst.js';
24
36
  import { denseScanRootOracleWithDebug } from './utils/denseRootOracle.js';
@@ -826,6 +838,76 @@ describe('Astro Validation Harness', () => {
826
838
  });
827
839
  });
828
840
 
841
+ describe('H. Electional context invariants', () => {
842
+ it('covers sect classification, near-horizon warnings, fallback semantics, and optional fields', () => {
843
+ const report = new ValidationReport();
844
+
845
+ for (const fixture of electionalFixtures) {
846
+ const actual = adapter.getElectionalContext(fixture.input);
847
+ compareElectionalContext(fixture, actual, report);
848
+ }
849
+
850
+ aggregateReport.hardFailures.push(...report.hardFailures);
851
+ aggregateReport.warnings.push(...report.warnings);
852
+ assertNoHardFailures(report);
853
+ });
854
+ });
855
+
856
+ describe('I. Rising-sign window invariants', () => {
857
+ it('validates contiguous local-day coverage, DST offsets, and deterministic repeats', () => {
858
+ const report = new ValidationReport();
859
+
860
+ for (const fixture of risingSignWindowFixtures) {
861
+ const actual = adapter.getRisingSignWindows(fixture.input);
862
+ const repeated = adapter.getRisingSignWindows(fixture.input);
863
+ compareRisingSignWindows(fixture, actual, repeated, report);
864
+ }
865
+
866
+ aggregateReport.hardFailures.push(...report.hardFailures);
867
+ aggregateReport.warnings.push(...report.warnings);
868
+ assertNoHardFailures(report);
869
+ });
870
+
871
+ it('checks that exact mode is at least as precise as approximate mode for the same day', () => {
872
+ const report = new ValidationReport();
873
+
874
+ for (const fixture of risingSignModeComparisonFixtures) {
875
+ const approximate = adapter.getRisingSignWindows({
876
+ ...fixture.baseInput,
877
+ mode: 'approximate',
878
+ });
879
+ const exact = adapter.getRisingSignWindows({
880
+ ...fixture.baseInput,
881
+ mode: 'exact',
882
+ });
883
+ compareRisingSignModePrecision(fixture, approximate, exact, adapter, report);
884
+ }
885
+
886
+ aggregateReport.hardFailures.push(...report.hardFailures);
887
+ aggregateReport.warnings.push(...report.warnings);
888
+ assertNoHardFailures(report);
889
+ });
890
+ });
891
+
892
+ describe('J. Service-level transit serialization invariants', () => {
893
+ it('covers enriched placement fields, explicit timezones, forecast grouping, and sign carry', () => {
894
+ const report = new ValidationReport();
895
+
896
+ for (const fixture of serviceTransitFixtures) {
897
+ const actual = adapter.getServiceTransits({
898
+ natalChart: fixture.natalChart,
899
+ transitInput: fixture.input,
900
+ startupDefaults: fixture.startupDefaults,
901
+ });
902
+ compareServiceTransitFixture(fixture, actual, report);
903
+ }
904
+
905
+ aggregateReport.hardFailures.push(...report.hardFailures);
906
+ aggregateReport.warnings.push(...report.warnings);
907
+ assertNoHardFailures(report);
908
+ });
909
+ });
910
+
829
911
  describe('DST scenario fixtures', () => {
830
912
  it('covers ambiguous and nonexistent local times under reject policy', () => {
831
913
  for (const fixture of dstFixtures) {