| name | ephemeris-pipeline |
|---|---|
| description | Understand the caelundas ephemeris calculation pipeline - NASA JPL API integration, astronomical event detection, and calendar generation. Use this skill when working on caelundas. |
| license | MIT |
This skill covers the caelundas astronomical calendar generation pipeline, including NASA JPL Horizons API integration, ephemeris caching, event detection, and output formatting.
Caelundas generates astronomical calendars by:
- Fetching ephemeris data from NASA JPL Horizons API
- Caching calculations in SQLite for performance
- Detecting events (aspects, phases, eclipses, etc.)
- Generating calendars in iCal and JSON formats
For comprehensive architecture details, see applications/caelundas/AGENTS.md.
┌─────────────────┐
│ Configuration │ (dates, location, output format)
└────────┬────────┘
│
v
┌─────────────────┐
│ Fetch Data │ NASA JPL Horizons API
│ ├─ Planets │ (Sun, Moon, Mercury...Pluto)
│ ├─ Lunar Node │
│ └─ Chiron │
└────────┬────────┘
│
v
┌─────────────────┐
│ SQLite Cache │ (ephemeris + active aspects)
│ ├─ Check cache │
│ ├─ Store new │
│ └─ Query │
└────────┬────────┘
│
v
┌─────────────────┐
│ Event Detection │
│ ├─ Aspects │ (conjunctions, oppositions, etc.)
│ ├─ Phases │ (new moon, full moon, quarters)
│ ├─ Stelliums │ (3+ planets close together)
│ ├─ Eclipses │
│ └─ Retrogrades │
└────────┬────────┘
│
v
┌─────────────────┐
│ Output Format │
│ ├─ iCalendar │ (.ics)
│ └─ JSON │
└─────────────────┘
Caelundas uses NASA's JPL Horizons system for high-precision ephemeris data:
// Fetch positions for a planet over date range
const response = await fetch("https://ssd.jpl.nasa.gov/api/horizons.api", {
method: "GET",
params: {
COMMAND: "10", // Body ID (Mercury)
EPHEM_TYPE: "OBSERVER",
CENTER: "coord@399", // Geocentric
START_TIME: "2024-01-01",
STOP_TIME: "2024-12-31",
STEP_SIZE: "1d", // Daily positions
QUANTITIES: "1,3", // RA/Dec and distance
CSV_FORMAT: "YES",
},
});- Sun: 10
- Moon: 301
- Mercury: 199
- Venus: 299
- Mars: 499
- Jupiter: 599
- Saturn: 699
- Uranus: 799
- Neptune: 899
- Pluto: 999
- Lunar Node: calculated from Moon's orbit
- Chiron: 2060
JPL returns CSV with columns:
Date, Julian Day, RA (deg), Dec (deg), Distance (AU)
2024-01-01, 2460310.5, 280.5, -23.0, 0.983
Caelundas parses and converts to internal format with:
- Ecliptic longitude (0-360°)
- Zodiac sign (Aries-Pisces)
- Degree within sign (0-30°)
API is rate-limited. Caelundas implements:
- Exponential backoff on 429 errors
- Batch requests for multiple bodies
- SQLite caching to minimize API calls
ephemerides table:
CREATE TABLE ephemerides (
id INTEGER PRIMARY KEY,
body TEXT NOT NULL, -- 'Sun', 'Moon', 'Mercury', etc.
date TEXT NOT NULL, -- ISO date
longitude REAL NOT NULL, -- Ecliptic longitude (0-360)
latitude REAL, -- Ecliptic latitude
distance REAL, -- Distance from Earth (AU)
speed REAL, -- Daily motion (degrees/day)
UNIQUE(body, date)
);active_aspects table:
CREATE TABLE active_aspects (
id INTEGER PRIMARY KEY,
body1 TEXT NOT NULL,
body2 TEXT NOT NULL,
aspect_type TEXT NOT NULL, -- 'conjunction', 'opposition', etc.
exact_time TEXT NOT NULL, -- ISO timestamp
orb REAL NOT NULL, -- Degrees from exact
applying BOOLEAN NOT NULL, -- Approaching (true) or separating (false)
start_date TEXT NOT NULL,
end_date TEXT NOT NULL
);-
Check cache before API request:
const cached = await db.get( "SELECT * FROM ephemerides WHERE body = ? AND date = ?", [body, date], ); if (cached) return cached;
-
Fetch from API if not cached:
const data = await fetchFromJPL(body, date) await db.run( 'INSERT INTO ephemerides (body, date, longitude, ...) VALUES (?, ?, ?, ...)', [body, date, data.longitude, ...] )
-
Query cached data for event detection:
const positions = await db.all( "SELECT * FROM ephemerides WHERE date BETWEEN ? AND ? ORDER BY date", [startDate, endDate], );
- Reduces API calls by ~99% on repeat runs
- Fast local queries for event detection
- Offline capability once data is cached
- Consistent results across runs
Major aspects (exact angles between planets):
- Conjunction (0°): Planets aligned
- Opposition (180°): Planets opposite
- Trine (120°): Harmonious angle
- Square (90°): Challenging angle
- Sextile (60°): Opportunity angle
Minor aspects:
- Semi-sextile (30°)
- Semi-square (45°)
- Quintile (72°)
- Sesquiquadrate (135°)
- Quincunx (150°)
Specialty aspects:
- Bi-quintile (144°)
- Septile (51.43°)
- Novile (40°)
Orbs define how close to exact an aspect must be:
const MAJOR_ORB = 8; // ±8° for major aspects
const MINOR_ORB = 3; // ±3° for minor aspectsExample: Mars at 45° and Jupiter at 50° form a semi-square (45° aspect) with 5° orb.
For each date in range:
- Get all planet positions from cache
- Calculate angles between all planet pairs
- Check if angle matches aspect within orb
- Determine applying/separating:
- Applying: Planets moving toward exact aspect
- Separating: Planets moving away from exact aspect
- Find exact time using interpolation
- Store in active_aspects table
Special case for Moon phases (Sun-Moon angles):
- New Moon: Sun-Moon conjunction (0°)
- Waxing Crescent: 0-90°
- First Quarter: Sun-Moon square (90°)
- Waxing Gibbous: 90-180°
- Full Moon: Sun-Moon opposition (180°)
- Waning Gibbous: 180-270°
- Last Quarter: Sun-Moon square (270°)
- Waning Crescent: 270-360°
A stellium is 3+ planets within 10° in the same zodiac sign:
// Group planets by sign
const planetsBySign = positions.reduce((acc, planet) => {
const sign = getZodiacSign(planet.longitude);
acc[sign] = acc[sign] || [];
acc[sign].push(planet);
return acc;
}, {});
// Find stelliums
const stelliums = Object.entries(planetsBySign)
.filter(([sign, planets]) => planets.length >= 3)
.map(([sign, planets]) => ({ sign, planets }));Solar and lunar eclipses occur when:
Solar Eclipse (Sun-Moon conjunction):
- Moon is near lunar node (within ~18°)
- New Moon
Lunar Eclipse (Sun-Moon opposition):
- Moon is near lunar node (within ~12°)
- Full Moon
A planet is retrograde when its speed (daily motion) is negative:
const isRetrograde = planet.speed < 0;Retrograde periods are when a planet appears to move backward in the sky from Earth's perspective.
Standard calendar format compatible with Google Calendar, Apple Calendar, Outlook, etc.
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//caelundas//astronomical-calendar//EN
BEGIN:VEVENT
UID:mars-square-saturn-2024-01-15@caelundas
DTSTART:20240115T143000Z
SUMMARY:Mars square Saturn
DESCRIPTION:Exact aspect at 14:30 UTC
LOCATION:Geocentric
END:VEVENT
END:VCALENDAREvent properties:
- UID: Unique identifier
- DTSTART: Exact time of aspect
- SUMMARY: Human-readable event name
- DESCRIPTION: Additional details (orb, planets, etc.)
Structured data for programmatic access:
{
"events": [
{
"type": "aspect",
"aspect": "square",
"body1": "Mars",
"body2": "Saturn",
"exactTime": "2024-01-15T14:30:00Z",
"orb": 0.5,
"applying": false,
"longitude1": 45.5,
"longitude2": 135.5
}
]
}More flexible for custom processing and analysis.
# Date range
START_DATE=2024-01-01
END_DATE=2024-12-31
# Location (for local coordinates)
LATITUDE=40.7128
LONGITUDE=-74.0060
TIMEZONE=America/New_York
# Output format
OUTPUT_FORMAT=ical # or 'json' or 'both'
OUTPUT_PATH=/app/output/calendar.ics
# Event types to include
INCLUDE_MAJOR_ASPECTS=true
INCLUDE_MINOR_ASPECTS=true
INCLUDE_PHASES=true
INCLUDE_ECLIPSES=true
INCLUDE_RETROGRADES=true
INCLUDE_STELLIUMS=trueOverride default orbs in configuration:
const customOrbs = {
conjunction: 10,
opposition: 10,
trine: 8,
square: 8,
sextile: 6,
// ...minor aspects
};- Parallel API requests for different bodies
- SQLite indexing on (body, date) columns
- Batch inserts for cache updates
- Lazy loading of event detection
- Incremental updates for overlapping date ranges
For full year calendar (365 days, 11 bodies):
- First run (no cache): ~5-10 minutes
- Subsequent runs (cached): ~30 seconds
- Incremental update (7 days): ~5 seconds
Unit tests (*.unit.test.ts):
- Angle calculations
- Zodiac sign conversions
- Orb checking
- Date utilities
Integration tests (*.integration.test.ts):
- NASA API requests
- SQLite caching
- Event detection algorithms
End-to-end tests (*.end-to-end.test.ts):
- Full pipeline execution
- Output file generation
- Multi-day processing
nx run caelundas:developrm applications/caelundas/ephemeris.db# Enable debug logging
DEBUG=nasa-api nx run caelundas:develop# Check iCal syntax
icalendar-validate output/calendar.ics
# Parse JSON
jq . output/calendar.json- applications/caelundas/AGENTS.md - Full architecture
- applications/caelundas/README.md - Usage guide
- NASA JPL Horizons - API documentation
- Ephemeris - Table of planetary positions
- Ecliptic coordinate system
- Astrological aspects
- Zodiac