UNPKG

astrology-insights

Version:

Comprehensive Vedic astrology engine for Node.js — Panchang, birth charts (Kundli), Vimshottari Dasha, divisional charts, dosha analysis, and planetary remedies. Swiss Ephemeris precision, validated against Drik Panchang.

180 lines (179 loc) 7.09 kB
"use strict"; /** * Birth Chart Orchestrator * The main entry point that ties together all birth chart modules. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateBirthChart = void 0; const ephemeris_1 = require("../calculations/ephemeris"); const ascendant_1 = require("./core/ascendant"); const planets_1 = require("./core/planets"); const houses_1 = require("./core/houses"); const chart_layout_1 = require("./layout/chart-layout"); /** * Calculate a complete birth chart (Kundli). * * @param birthData - Birth date, time, location, timezone * @param options - Ayanamsa and house system preferences * @returns Complete BirthChartResult */ function calculateBirthChart(birthData, options) { const ayanamsaType = options?.ayanamsa || 'lahiri'; const houseSystem = options?.houseSystem || 'whole_sign'; // 1. Parse birth date/time to UTC const utcDate = parseBirthDateTime(birthData.date, birthData.time, birthData.timezone); // 2. Create Ephemeris instance const ephemeris = new ephemeris_1.Ephemeris(); try { // 3. Get ayanamsa value const ayanamsa = getAyanamsaValue(utcDate, ayanamsaType, ephemeris); // 4. Calculate Julian Day for metadata const jd = dateToJulianDay(utcDate); // 5. Calculate ascendant + house cusps const { lagna, cusps } = (0, ascendant_1.calculateLagna)(utcDate, birthData.latitude, birthData.longitude, ayanamsa, houseSystem, ephemeris); // 6. Calculate all 9 planet positions const planets = (0, planets_1.calculateAllPlanets)(utcDate, ayanamsa, ephemeris); // 7. Build houses let houses = (0, houses_1.calculateHouses)(lagna.signNumber, houseSystem, cusps); // 8. Assign planets to houses const assignment = (0, houses_1.assignPlanetsToHouses)(planets, lagna.signNumber, houseSystem); houses = (0, houses_1.populateHousePlanets)(houses, planets, assignment); // 9. Generate chart layouts const layouts = (0, chart_layout_1.generateChartLayouts)(houses); // 10. Assemble result const result = { birthData, ayanamsa: { type: ayanamsaType, degree: Math.round(ayanamsa * 10000) / 10000, }, lagna, planets, houses, layout: { northIndian: layouts.northIndian, southIndian: layouts.southIndian, western: layouts.western, }, meta: { calculatedAt: new Date().toISOString(), houseSystem, julianDay: jd, utcDate: utcDate.toISOString(), }, }; return result; } finally { ephemeris.cleanup(); } } exports.calculateBirthChart = calculateBirthChart; // ── Helpers ────────────────────────────────────────────────────────────────── /** * Parse birth date + time + timezone into a UTC Date object. * * Input: "2000-01-01", "04:30", "Asia/Kolkata" * Asia/Kolkata is UTC+5:30, so UTC = 2000-01-01 04:30 - 5:30 = 1999-12-31 23:00 */ function parseBirthDateTime(dateStr, timeStr, timezone) { // Build a local datetime string const [year, month, day] = dateStr.split('-').map(Number); const [hour, minute] = timeStr.split(':').map(Number); // Try to use Intl to resolve the timezone offset try { // Create a date in UTC first const tentativeUtc = new Date(Date.UTC(year, month - 1, day, hour, minute, 0, 0)); // Get the offset of the target timezone at this tentative moment const offsetMinutes = getTimezoneOffsetMinutes(tentativeUtc, timezone); // Adjust: local time = UTC + offset, so UTC = local - offset const utcMs = tentativeUtc.getTime() - offsetMinutes * 60000; return new Date(utcMs); } catch { // Fallback: treat as UTC console.warn(`Could not resolve timezone "${timezone}", treating as UTC`); return new Date(Date.UTC(year, month - 1, day, hour, minute, 0, 0)); } } /** * Get timezone offset in minutes (positive = east of UTC). * Uses Intl.DateTimeFormat to resolve IANA timezone names. */ function getTimezoneOffsetMinutes(refDate, timezone) { try { // Format the date in the target timezone and in UTC, then diff const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }); const parts = formatter.formatToParts(refDate); const get = (type) => { const p = parts.find(p => p.type === type); return p ? parseInt(p.value, 10) : 0; }; const localYear = get('year'); const localMonth = get('month'); const localDay = get('day'); let localHour = get('hour'); // Handle midnight edge case (hour12:false gives '24' in some locales) if (localHour === 24) localHour = 0; const localMinute = get('minute'); const localSecond = get('second'); const localMs = Date.UTC(localYear, localMonth - 1, localDay, localHour, localMinute, localSecond); const utcMs = Date.UTC(refDate.getUTCFullYear(), refDate.getUTCMonth(), refDate.getUTCDate(), refDate.getUTCHours(), refDate.getUTCMinutes(), refDate.getUTCSeconds()); return Math.round((localMs - utcMs) / 60000); } catch { // Common offsets fallback const offsets = { 'Asia/Kolkata': 330, 'Asia/Calcutta': 330, 'UTC': 0, 'America/New_York': -300, 'America/Los_Angeles': -480, 'Europe/London': 0, }; return offsets[timezone] || 0; } } /** * Get ayanamsa degree for the given date and type. */ function getAyanamsaValue(utcDate, type, ephemeris) { if (type === 'lahiri') { return ephemeris.calculate_lahiri_ayanamsa(utcDate); } // KP (Krishnamurti) — id = 5 const kpInfo = ephemeris.getSpecificAyanamsa(utcDate, 5); if (kpInfo) { return kpInfo.degree; } // Fallback to Lahiri return ephemeris.calculate_lahiri_ayanamsa(utcDate); } /** * Convert a Date to Julian Day number. */ function dateToJulianDay(date) { let year = date.getUTCFullYear(); let month = date.getUTCMonth() + 1; const day = date.getUTCDate(); const hour = date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600; if (month <= 2) { year -= 1; month += 12; } const a = Math.floor(year / 100); const b = 2 - a + Math.floor(a / 4); return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + hour / 24 + b - 1524.5; }