Ephemeris10 MIN READ15 / 120

Ephemeris engines and adapters — High-Level Design

Status: Implemented modules · Part of: Ephemeris · Updated: 2026-10-28

Three calculation engines stand behind the ephemeris layer: Swiss Ephemeris, NASA JPL Horizons and Astronomy Engine. Each has exactly one adapter that translates its native calls into the shared contract. One Binder boundary owns validation, routing, normalization and base semantics, so Vedic and Western services never choose an engine or combine two answers.

This page is the architecture view. Calculation internals remain in the Swiss, NASA and Astronomy Engine module guides.

1. Purpose

A chart is only reproducible if every number can name the engine that produced it. Supporting three engines is therefore not “pick whichever is available”:

  • Swiss Ephemeris is the default provider and source of record. It computes locally, supports houses, native sidereal modes and solar events, and is the only engine the product treats as authoritative by default.
  • NASA JPL Horizons is the independent reference. The drift validator compares against it, and a host may declare it as a coverage fallback when Swiss files cannot serve a tropical position request.
  • Astronomy Engine is an optional local provider. It removes the native binding and data-file requirement from the calculation path for hosts that select it, without changing the contract seen by consumers.

One decision per complete request keeps “the same chart” stable across natal, transit and panchang views, and preserves a single provenance record.

2. Where they sit

Ephemeris engines and adapters — high-level design Reference design over current source. Three calculation engines — Swiss Ephemeris, NASA JPL Horizons and Astronomy Engine — each behind one adapter. One Binder boundary validates a complete request, resolves Terrestrial Time, routes it to one eligible provider, normalizes the answer, checks provider identity and adds shared astrology semantics. Vedic and Western services consume canonical facts and never select adapters or merge providers. Boxes are modules or hosting boundaries, not separately deployed services. Ephemeris Engines and Adapters — High-Level Design Three calculation engines, one adapter each, one Binder decision per request. Vedic and Western servicesruntime assembly · shared Binder reader One Binder boundary · @astrology/ephemeris · reference design Provider routerwhole-request capability matchruntime gates · no adapter call Binder assemblyvalidate · resolve TT · routeinvoke one adapter · check identity Normalizing adaptersone wrapper per enginecontext · order · wrap · provenance SwissAdapternative worker · pinned files HorizonsAdapterHTTPS · one query per body AstronomyEngineAdapterin-process JS · no I/O chart request route invoke one selected provider fork · IPC HTTPS GET in-process Swiss Ephemeris · local nativechild process per operationpinned .se1 files · DE441positions · houses · solar eventsprimary provider by default NASA JPL Horizons · remoteHTTPS · one GET per bodyDE441 tropical positionsno houses · nodes · siderealexplicit coverage fallback only Astronomy Engine · in-processno native binding or networklocal ayanamsa · houses · eventsTT 1899–2100 coverageoptional third provider synchronous call or dependency Binder entry module hosting / trust boundary Reference design over current source. Provider source presence does not assert deployment, a live comparison or numerical accuracy. Hosts own civil-time resolution, caching, provenance storage, transport and logging; adapters never merge providers or retry outside their own policy. Vedic and Western services consume canonical facts. They never select adapters, or reimplement signs, houses, motion and aspects.

The diagram is a component map, not a request sequence:

Question Answer
Who owns the feature? The Binder boundary inside @astrology/ephemeris.
Who consumes it? The runtime-assembled Vedic and Western services, through a shared reader.
What are the dependencies? One router, one normalizing wrapper per engine and three adapters.
What is the source of truth? Swiss Ephemeris by default; the router’s recorded decision names any other.
Where are the integration boundaries? A local native child process for Swiss, HTTPS to JPL for Horizons, and an in-process JavaScript library for Astronomy Engine.

Boxes represent modules or hosting boundaries. Source presence does not assert deployment, a live NASA comparison or production accuracy.

3. The three engines

Concern Swiss Ephemeris NASA JPL Horizons Astronomy Engine
Role Primary provider and source of record Independent reference; explicit coverage fallback Optional local provider
Transport One native child process per operation, over IPC HTTPS GET, one query per body In-process JavaScript call
Operations positions, houses, rise-set positions only positions, houses, rise-set
Zodiac Tropical and native sidereal Tropical only Tropical and locally derived sidereal
Bodies All canonical bodies, including Rahu and Ketu Sun through Pluto; no lunar nodes All canonical bodies, including Rahu and Ketu
Ayanamsas Lahiri, Raman, Krishnamurti, Fagan–Bradley None; sidereal requests are refused The same four, computed locally
House systems Placidus, Whole Sign, Equal, Sripati, Koch, Campanus None The same six
Coverage (TT Julian day) Installed file range, about 1800–2400 2378497.5–2597650.3591467496 2415019.5–2488069.5 (1899-12-31–2100-01-01)
Time model Swiss UTC → TT/UT1 with pinned DE441 tidal acceleration Horizons TDB−UT declared per response UTC approximated as UT1 with the engine’s Delta-T
Isolation Disposable child process; bounded queue Semaphore, deadlines and circuit breaker Shares the host event loop
Failure style Typed error, no retry, no fallback Typed error after bounded retries Typed error, no retry

3.1 Swiss Ephemeris

The adapter starts a fresh child process for every operation so Swiss’s process-wide observer, sidereal and time settings cannot leak between simultaneous requests. It verifies the pinned .se1 files, refuses the Moshier analytical fallback, pins the DE441 tidal model and computes houses from UT1. Sidereal conversion happens once inside the native calculation and is declared as provider-applied. Rahu uses the selected mean or true node; Ketu is its exact antipode.

The adapter advertises only the installed file range, not the library’s theoretical range. Polar house failures reject as undefined geometry rather than returning native fallback cusps under the requested system name. Worker deadline, concurrency and queue limits are per adapter instance and shared by every caller that reuses it.

3.2 NASA JPL Horizons

The Horizons adapter owns the remote service’s awkwardness: a pinned query template, one call per body, a descriptive user agent, per-attempt timeouts, a whole-operation deadline, bounded concurrency, backoff for throttling and 5xx, and a circuit breaker. It verifies the response signature, markers, headers and rows before reading a number; a format change fails loudly instead of producing plausible zeros.

It returns tropical positions in the apparent ecliptic of date and declares DE441 plus any per-body source identifiers. It refuses sidereal requests, lunar nodes and houses before any network call, and refuses topocentric requests unless the host explicitly allows sending observer coordinates to JPL. One body’s failure aborts its siblings, so a partial position set is never returned.

3.3 Astronomy Engine

The Astronomy Engine adapter is pure JavaScript: no Swiss code, native binding, data files or network. The engine supplies planetary states, parallax and frame transformations; local algorithms supply the named ayanamsas, mean and osculating nodes, six house systems and Hindu solar events. It supports both zodiacs and the full canonical body list.

Its advertised window is deliberately narrower than the library’s theoretical range, and its results are not numerically identical to Swiss apparent places. It blocks the calling event loop while calculating, so a host that selects it accepts that scheduling trade-off. Registering the adapter does not make it primary; routing policy does.

4. One adapter contract

Every engine implements the EphemerisAdapter port from @astrology/astronomy-contracts:

Member Responsibility
capabilities() Report provider identity, versions, supported operations, bodies, zodiacs, ayanamsas, house systems, event definitions, coverage and file evidence
positions(request) Return every requested body in request order with one declaration
houses(request) Return twelve cusps and eight named angles with one declaration
riseSet(request) Return bounded solar events, or nulls the adapter can justify

The port is deliberately narrow. An adapter validates before provider I/O, preserves or rejects the whole request, and never:

  • selects itself or another provider;
  • retries a failed calculation through a different engine;
  • normalizes values, changes the requested zodiac convention, or rounds results;
  • derives signs, nakshatras, motion, aspects or chart meaning;
  • persists results, caches answers or exposes HTTP statuses.

Those choices belong to the router, normalizer, Binder and host, which is why adding an engine does not change the Vedic or Western public API.

5. How a request moves

The Binder is the only execution owner. Its stages are:

  1. Validate the canonical request and reject unsupported shapes before any provider work.
  2. Resolve Terrestrial Time through the host-supplied resolver; the Binder does not convert civil time itself.
  3. Route the complete request to one eligible provider and obtain a decision with a reason and cache key.
  4. Invoke the normalizing wrapper for that provider exactly once.
  5. Normalize and check context: origin, zodiac, ayanamsa, body order and event window must match the admitted request.
  6. Check identity against the startup capability snapshot so a swapped engine, binding or file set cannot inherit another result’s evidence.
  7. Add shared semantics for position requests — signs, nakshatras and padas, motion and aspects — and return the canonical facts with provenance.

Derived reads such as declinations, mean longitudes and disc diameters are computed by the Binder from one routed position read. They do not trigger a second provider call.

6. Routing and admission

Routing answers one question: which single provider can serve this whole request now?

  • The primary defaults to swiss-ephemeris. The host may set another registered provider as primary, but registration alone does not change the default.
  • Capability matching checks feature, zodiac, ayanamsa, event definition, every requested body, house system and inclusive TT coverage, in that order.
  • A coverage fallback is considered only for a positions request in the tropical zodiac, only when the primary failed with UNSUPPORTED_BODY or OUT_OF_EPHEMERIS_RANGE, and only when an explicit rule names the provider and every requested body.
  • Runtime admission then checks health, breaker state and quota. The cache key is computed from provider identity, engine/binding/file versions and the complete request; a cache hit still requires health and quota admission.
  • The breaker is rechecked after the cache probe, because that probe may await external I/O.

Two invariants matter more than the stage list:

  1. One provider per response. Bodies are never split across engines and answers are never merged.
  2. A failure is not a fallback trigger. Outages, open breakers, quota exhaustion, houses and sidereal requests never authorize substitution. The request fails with a typed error instead.

7. Normalization, identity and evidence

Each adapter is wrapped in a NormalizingAdapter, so normalization cannot be skipped by choosing a different engine. The wrapper validates the request again, calls the adapter once, then:

  • validates canonical units, finite geometry and angle ranges;
  • wraps angular fields once under the configured numeric policy;
  • rejects a declaration whose origin, zodiac or ayanamsa differs from the request;
  • rejects a body count or order that differs from the request;
  • rejects a solar event outside the requested window or an unexpected sunset field;
  • returns normalization provenance separately from the provider declaration.

Provider accuracy stays separate from decimal places. precisionArcsec remains null unless an engine can actually declare one; rounding to eight decimals is not an accuracy claim.

8. Failure modes

Situation Public code
Malformed request, missing TT resolver or invalid adapter limits INVALID_REQUEST
Unparseable instant, invalid observer, or sidereal/node request missing its convention INVALID_INSTANT, INVALID_OBSERVER, AYANAMSA_REQUIRED, NODE_TYPE_REQUIRED
Feature, zodiac or event definition not offered by any eligible provider UNSUPPORTED_FEATURE
A requested body is unavailable UNSUPPORTED_BODY
A requested house system is unavailable UNSUPPORTED_HOUSE_SYSTEM
Instant outside the advertised coverage OUT_OF_EPHEMERIS_RANGE
Provider unhealthy, breaker open or quota exhausted PROVIDER_UNAVAILABLE
Worker, queue or HTTP deadline exceeded PROVIDER_TIMEOUT
Unrecognized provider response shape or inconsistent values PROVIDER_RESPONSE
House system mathematically undefined at the location HOUSE_CALCULATION_UNDEFINED
Provider identity changed after startup snapshots INTERNAL

Consumers translate these into their own transport or UI behavior. Adapters do not invent HTTP statuses, and provider error strings or filesystem paths do not escape.

9. Ownership boundaries

Owner Owns
Engine + adapter Native/remote astronomy, provider refusals, provider declaration, adapter-local retry or breaker policy
Binder Validation, TT resolution call, routing, one-adapter execution, normalization, identity check, base semantics
Vedic and Western services Tradition judgments, profiles, rulebooks and operation results
Host application Civil-time resolution, logging configuration, caching, provenance persistence, transport, scheduling and drift runs
Drift validator Independent comparison, thresholds and reporting; never the request path

A host that constructs the default Binder probes both Swiss and Horizons at startup, even when only one tradition is enabled. Astronomy Engine is constructed only when requested. Capability snapshots are detached at creation; a provider or configuration change requires a new Binder.

10. Limits and verification

  • No deployment claim. These are workspace modules inside one package, not separately hosted services.
  • No accuracy claim. Coverage windows, benchmark latencies and sampled agreement do not establish per-request precision. The recorded Astronomy Engine comparison is a sample at named dates, not a certification.
  • No silent degradation. A provider that cannot serve the request fails; the Binder does not substitute another engine or synthesize a missing event.
  • No shared native state. Swiss isolation depends on one child process per operation; reusing a single native engine is a diagnostic control, not a supported production path.
  • No birth-data leakage. Horizons topocentric calls are opt-in because they send observer coordinates to JPL; logs omit request geometry.

The recorded Astronomy Engine benchmark and the drift validator are the current comparison evidence. Neither replaces independent fixtures or published-almanac acceptance for a new calculation family.

Source: