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.

446 lines (381 loc) 13.2 kB
/** * Dosha Analysis Module * * Detects and analyzes major Vedic astrology doshas: * - Manglik (Kuja) Dosha * - Kaal Sarp Dosha * - Ganda Moola Dosha * - Gandanta Analysis */ import type { GrahaPosition, HouseInfo, LagnaInfo } from '../types'; // ── Manglik Dosha ────────────────────────────────────────────────────────────── export interface ManglikResult { isManglik: boolean; severity: 'none' | 'mild' | 'full'; marsHouse: number; details: string; cancellations: string[]; } const MANGLIK_HOUSES = [1, 2, 4, 7, 8, 12]; const MARS_OWN_SIGNS = ['Aries', 'Scorpio']; const MARS_EXALTATION_SIGN = 'Capricorn'; const FIRE_SIGNS = ['Aries', 'Leo', 'Sagittarius']; /** * Detect Manglik (Kuja) Dosha from a birth chart. */ export function analyzeManglik( planets: GrahaPosition[], houses: HouseInfo[], ): ManglikResult { const mars = planets.find((p) => p.name === 'Mars'); const jupiter = planets.find((p) => p.name === 'Jupiter'); if (!mars) { return { isManglik: false, severity: 'none', marsHouse: 0, details: 'Mars position not available.', cancellations: [], }; } // Find Mars house const marsHouse = findPlanetHouse(mars.name, houses); if (!MANGLIK_HOUSES.includes(marsHouse)) { return { isManglik: false, severity: 'none', marsHouse, details: `Mars is in house ${marsHouse}, which does not cause Manglik Dosha.`, cancellations: [], }; } // Mars is in a Manglik house — check cancellations const cancellations: string[] = []; // 1. Mars in own sign (Aries, Scorpio) if (MARS_OWN_SIGNS.includes(mars.signName)) { cancellations.push(`Mars is in own sign (${mars.signName}) — Manglik cancelled.`); } // 2. Mars in exaltation (Capricorn) if (mars.signName === MARS_EXALTATION_SIGN) { cancellations.push('Mars is exalted in Capricorn — Manglik cancelled.'); } // 3. Mars conjunct Jupiter in same house const jupiterHouse = jupiter ? findPlanetHouse(jupiter.name, houses) : 0; if (jupiter && jupiterHouse === marsHouse) { cancellations.push('Mars is conjunct Jupiter in the same house — Manglik cancelled.'); } // 4. Mars aspected by Jupiter (Jupiter aspects houses 5, 7, 9 from itself) if (jupiter && !cancellations.some((c) => c.includes('conjunct Jupiter'))) { const jupiterAspects = getJupiterAspectedHouses(jupiterHouse); if (jupiterAspects.includes(marsHouse)) { cancellations.push('Mars is aspected by Jupiter — Manglik mitigated.'); } } // 5. Mars in fire sign in 1st house if (marsHouse === 1 && FIRE_SIGNS.includes(mars.signName)) { cancellations.push( `Mars is in a fire sign (${mars.signName}) in the 1st house — Manglik cancelled.`, ); } // 6. Both-partner note cancellations.push( 'Note: If both partners are Manglik, the dosha is considered cancelled (cannot be verified from a single chart).', ); // Determine severity const hasFullCancellation = cancellations.some( (c) => c.includes('cancelled') && !c.includes('both partners') && !c.includes('mitigated'), ); let severity: ManglikResult['severity']; if (hasFullCancellation) { severity = 'none'; } else if ([7, 8].includes(marsHouse)) { severity = 'full'; } else { // Houses 1, 2, 4, 12 — check if Jupiter aspect mitigates const jupiterMitigates = cancellations.some((c) => c.includes('mitigated')); severity = jupiterMitigates ? 'mild' : 'full'; } // If severity ended up 'none' due to cancellation, not Manglik const isManglik = severity !== 'none'; const details = isManglik ? `Mars in house ${marsHouse} causes ${severity} Manglik Dosha.` : `Mars in house ${marsHouse} would cause Manglik Dosha, but it is cancelled.`; return { isManglik, severity, marsHouse, details, cancellations, }; } // ── Kaal Sarp Dosha ──────────────────────────────────────────────────────────── export interface KaalSarpResult { hasDosha: boolean; type: string | null; rahuHouse: number; ketuHouse: number; allPlanetsOnOneSide: boolean; details: string; affectedHouses: number[]; } const KAAL_SARP_TYPES: Record<number, string> = { 1: 'Anant', 2: 'Kulik', 3: 'Vasuki', 4: 'Shankhpal', 5: 'Padma', 6: 'Mahapadma', 7: 'Takshak', 8: 'Karkotak', 9: 'Shankhchur', 10: 'Ghatak', 11: 'Vishdhar', 12: 'Sheshnag', }; /** * Detect Kaal Sarp Dosha. * * All 7 planets (Sun through Saturn) must be on one side of the Rahu-Ketu axis. */ export function analyzeKaalSarp( planets: GrahaPosition[], houses: HouseInfo[], ): KaalSarpResult { const rahu = planets.find((p) => p.name === 'Rahu'); const ketu = planets.find((p) => p.name === 'Ketu'); if (!rahu || !ketu) { return { hasDosha: false, type: null, rahuHouse: 0, ketuHouse: 0, allPlanetsOnOneSide: false, details: 'Rahu/Ketu positions not available.', affectedHouses: [], }; } const rahuHouse = findPlanetHouse('Rahu', houses); const ketuHouse = findPlanetHouse('Ketu', houses); // Get the 7 main planets (exclude Rahu/Ketu) const mainPlanets = planets.filter( (p) => p.name !== 'Rahu' && p.name !== 'Ketu', ); const mainPlanetHouses = mainPlanets.map((p) => findPlanetHouse(p.name, houses)); // Houses from Rahu to Ketu (clockwise, i.e. ascending house numbers) const rahuToKetu = getHousesBetween(rahuHouse, ketuHouse); const ketuToRahu = getHousesBetween(ketuHouse, rahuHouse); const allInRahuToKetu = mainPlanetHouses.every((h) => rahuToKetu.includes(h)); const allInKetuToRahu = mainPlanetHouses.every((h) => ketuToRahu.includes(h)); const allOnOneSide = allInRahuToKetu || allInKetuToRahu; if (!allOnOneSide) { return { hasDosha: false, type: null, rahuHouse, ketuHouse, allPlanetsOnOneSide: false, details: 'Planets are on both sides of the Rahu-Ketu axis. No Kaal Sarp Dosha.', affectedHouses: [], }; } const isKaalAmrit = allInKetuToRahu; const type = KAAL_SARP_TYPES[rahuHouse] || null; const affectedHouses = allInRahuToKetu ? rahuToKetu : ketuToRahu; const details = isKaalAmrit ? `Kaal Amrit Yoga (${type} type) — all planets between Ketu (house ${ketuHouse}) and Rahu (house ${rahuHouse}). This is considered auspicious.` : `Kaal Sarp Dosha (${type} type) — all planets between Rahu (house ${rahuHouse}) and Ketu (house ${ketuHouse}).`; return { hasDosha: !isKaalAmrit, type, rahuHouse, ketuHouse, allPlanetsOnOneSide: true, details, affectedHouses, }; } // ── Ganda Moola Dosha ────────────────────────────────────────────────────────── export interface GandaMoolaResult { hasDosha: boolean; moonNakshatra: string; moonPada: number; affectedNakshatras: string[]; details: string; severity: 'none' | 'mild' | 'severe'; } const GANDA_MOOLA_NAKSHATRAS = [ 'Ashwini', // 1 'Ashlesha', // 9 'Magha', // 10 'Jyeshtha', // 18 'Moola', // 19 'Revati', // 27 ]; // Junction nakshatras where Pada 1 = severe const SEVERE_PADA_1 = ['Ashwini', 'Magha', 'Moola']; // Junction nakshatras where Pada 4 = severe const SEVERE_PADA_4 = ['Ashlesha', 'Jyeshtha', 'Revati']; /** * Detect Ganda Moola Dosha based on Moon's nakshatra. */ export function analyzeGandaMoola( planets: GrahaPosition[], ): GandaMoolaResult { const moon = planets.find((p) => p.name === 'Moon'); if (!moon) { return { hasDosha: false, moonNakshatra: '', moonPada: 0, affectedNakshatras: [], details: 'Moon position not available.', severity: 'none', }; } const { nakshatra, nakshatraPada } = moon; if (!GANDA_MOOLA_NAKSHATRAS.includes(nakshatra)) { return { hasDosha: false, moonNakshatra: nakshatra, moonPada: nakshatraPada, affectedNakshatras: [], details: `Moon is in ${nakshatra}, which is not a Ganda Moola nakshatra.`, severity: 'none', }; } // Determine severity let severity: GandaMoolaResult['severity'] = 'mild'; if (SEVERE_PADA_1.includes(nakshatra) && nakshatraPada === 1) { severity = 'severe'; } else if (SEVERE_PADA_4.includes(nakshatra) && nakshatraPada === 4) { severity = 'severe'; } const details = severity === 'severe' ? `Moon in ${nakshatra} Pada ${nakshatraPada} causes severe Ganda Moola Dosha (junction point of nakshatra).` : `Moon in ${nakshatra} Pada ${nakshatraPada} causes mild Ganda Moola Dosha.`; return { hasDosha: true, moonNakshatra: nakshatra, moonPada: nakshatraPada, affectedNakshatras: [nakshatra], details, severity, }; } // ── Gandanta Analysis ────────────────────────────────────────────────────────── export interface GandantaPlanet { name: string; signName: string; degree: number; type: 'nakshatra_gandanta' | 'rashi_gandanta' | 'tithi_gandanta'; details: string; } export interface GandantaResult { hasGandanta: boolean; planets: GandantaPlanet[]; } /** * Water-Fire sign junctions (sign numbers). * Water sign last 3°20' or Fire sign first 3°20'. */ const GANDANTA_JUNCTIONS: { water: number; fire: number }[] = [ { water: 4, fire: 5 }, // Cancer → Leo { water: 8, fire: 9 }, // Scorpio → Sagittarius { water: 12, fire: 1 }, // Pisces → Aries ]; const GANDANTA_ORB = 3 + 20 / 60; // 3°20' = one nakshatra pada const SIGN_NAMES: Record<number, string> = { 1: 'Aries', 2: 'Taurus', 3: 'Gemini', 4: 'Cancer', 5: 'Leo', 6: 'Virgo', 7: 'Libra', 8: 'Scorpio', 9: 'Sagittarius', 10: 'Capricorn', 11: 'Aquarius', 12: 'Pisces', }; /** * Analyze Gandanta positions for all planets and Lagna. */ export function analyzeGandanta( planets: GrahaPosition[], lagna: LagnaInfo, ): GandantaResult { const affected: GandantaPlanet[] = []; // Check all planets for (const planet of planets) { const result = checkGandanta(planet.name, planet.signNumber, planet.degreeInSign); if (result) { affected.push(result); } } // Check Lagna const lagnaResult = checkGandanta('Lagna', lagna.signNumber, lagna.degreeInSign); if (lagnaResult) { affected.push(lagnaResult); } return { hasGandanta: affected.length > 0, planets: affected, }; } function checkGandanta( name: string, signNumber: number, degreeInSign: number, ): GandantaPlanet | null { for (const junction of GANDANTA_JUNCTIONS) { // Water sign: last 3°20' (i.e., degree >= 30 - 3.333...) if (signNumber === junction.water && degreeInSign >= 30 - GANDANTA_ORB) { return { name, signName: SIGN_NAMES[signNumber], degree: degreeInSign, type: 'rashi_gandanta', details: `${name} at ${degreeInSign.toFixed(2)}° in ${SIGN_NAMES[signNumber]} is in Gandanta zone (last ${GANDANTA_ORB.toFixed(2)}° of water sign, junction with ${SIGN_NAMES[junction.fire]}).`, }; } // Fire sign: first 3°20' if (signNumber === junction.fire && degreeInSign <= GANDANTA_ORB) { return { name, signName: SIGN_NAMES[signNumber], degree: degreeInSign, type: 'rashi_gandanta', details: `${name} at ${degreeInSign.toFixed(2)}° in ${SIGN_NAMES[signNumber]} is in Gandanta zone (first ${GANDANTA_ORB.toFixed(2)}° of fire sign, junction with ${SIGN_NAMES[junction.water]}).`, }; } } return null; } // ── Helpers ──────────────────────────────────────────────────────────────────── /** * Find which house a planet occupies from the houses array. */ function findPlanetHouse(planetName: string, houses: HouseInfo[]): number { for (const house of houses) { if (house.planets.includes(planetName as any)) { return house.number; } } return 0; } /** * Get houses between two houses (exclusive of both endpoints), * moving clockwise (ascending house numbers wrapping at 12). */ function getHousesBetween(fromHouse: number, toHouse: number): number[] { const result: number[] = []; let current = fromHouse; while (true) { current = current === 12 ? 1 : current + 1; if (current === toHouse) break; result.push(current); } return result; } /** * Get houses that Jupiter aspects from a given house. * Jupiter has special aspects on 5th, 7th, and 9th houses from itself. */ function getJupiterAspectedHouses(jupiterHouse: number): number[] { return [5, 7, 9].map((offset) => ((jupiterHouse - 1 + offset) % 12) + 1); }