@nrweb/astro-calc
Version:
Comprehensive astrological calculations using Swiss Ephemeris and astronomy-engine for positions, aspects, houses, lots, and scoring metrics
1,186 lines • 79.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFullAstrologicalData = void 0;
exports.getAstrologicalPositions = getAstrologicalPositions;
exports.calculateRomanceScore = calculateRomanceScore;
exports.calculateCareerScore = calculateCareerScore;
exports.calculateWealthScore = calculateWealthScore;
exports.calculateMagicScore = calculateMagicScore;
exports.calculateFriendshipScore = calculateFriendshipScore;
exports.calculateHealthScore = calculateHealthScore;
const astronomy_engine_1 = require("astronomy-engine");
const utils_1 = require("./utils");
const aspects_1 = require("./aspects");
const retrograde_1 = require("./retrograde");
const constants_1 = require("./constants");
const SunCalc = __importStar(require("suncalc"));
const react_native_swisseph_1 = require("@nrweb/react-native-swisseph");
// @ts-ignore
const lune = __importStar(require("lune"));
const SE_MEAN_NODE = 10;
const SEFLG_SWIEPH = 2;
const SEFLG_SPEED = 256;
const SE_GREG_CAL = 1;
function findRetrogradePeriod(date, planet) {
const calendar = (0, retrograde_1.loadCalendar)();
const periods = calendar[planet] || [];
const dateStr = date.toISOString().split("T")[0];
return (periods.find((period) => (period.startDate <= dateStr && period.endDate >= dateStr) || // Current retrograde
period.startDate > dateStr // Next retrograde
) || null);
}
function getSunriseSunset(date, latitude, longitude) {
const times = SunCalc.getTimes(date, latitude, longitude);
return {
sunrise: times.sunrise,
sunset: times.sunset,
};
}
// Helper function to calculate houses
function calculateHouses(date, latitude, longitude) {
const julianDay = (0, react_native_swisseph_1.sweJulday)(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours() +
date.getUTCMinutes() / 60 +
date.getUTCSeconds() / 3600, 1);
const housesResult = (0, react_native_swisseph_1.sweHouses)(julianDay, SEFLG_SWIEPH, latitude, longitude, "P");
if ("error" in housesResult) {
throw new Error(`Failed to calculate houses: ${housesResult.error}`);
}
const ascendant = housesResult.ascmc[0];
const cusps = housesResult.cusp;
// Create houses array (houses 1-12)
const houses = [];
const zodiacSigns = [
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces",
];
for (let i = 0; i < 12; i++) {
const startPosition = cusps[i];
const endPosition = cusps[i + 1] || cusps[0]; // House 12 ends at House 1 (Ascendant)
const startSignIndex = Math.floor(startPosition / 30);
const startDegrees = startPosition % 30;
const startSign = zodiacSigns[startSignIndex];
const endSignIndex = Math.floor(endPosition / 30);
const endDegrees = endPosition % 30;
const endSign = zodiacSigns[endSignIndex];
houses.push({
number: i + 1,
startSign,
startDegrees: Math.round(startDegrees * 100) / 100,
endSign,
endDegrees: Math.round(endDegrees * 100) / 100,
bodies: [],
});
}
return { ascendant, houses };
}
const getPlanetPosition = (time, body, sunLongitude) => {
const julianDay = (0, react_native_swisseph_1.sweJulday)(time.getUTCFullYear(), time.getUTCMonth() + 1, time.getUTCDate(), time.getUTCHours() +
time.getUTCMinutes() / 60 +
time.getUTCSeconds() / 3600, SE_GREG_CAL);
const sePlanet = constants_1.SE_PLANETS[body];
if (sePlanet === undefined) {
throw new Error(`Unsupported planet: ${astronomy_engine_1.Body[body]}`);
}
const result = (0, react_native_swisseph_1.sweCalcUt)(julianDay, sePlanet, SEFLG_SWIEPH | SEFLG_SPEED);
const longitude = result.longitude;
const longitudeSpeed = result.longitudeSpeed; // degrees per day
const sign = (0, utils_1.getZodiacSign)(longitude);
const degreesInSign = longitude % 30;
const bodyName = astronomy_engine_1.Body[body];
const planetPosition = {
name: bodyName,
position: longitude,
sign,
degrees: degreesInSign,
dignity: (0, utils_1.getDignity)(bodyName, sign),
sect: constants_1.PLANET_SECTS[bodyName],
planetaryMotion: longitudeSpeed > 0
? "direct"
: longitudeSpeed < 0
? "retrograde"
: "stationary",
speed: longitudeSpeed,
decanRuler: (0, utils_1.getDecanRuler)(sign, degreesInSign),
boundRuler: (0, utils_1.getBoundRuler)(sign, degreesInSign),
aspects: [],
};
// Determine if planet is moving slowly
const lowerBound = constants_1.PLANET_SPEED_LOWER_BOUND[body];
if (lowerBound !== undefined) {
planetPosition.slowOfMotion = Math.abs(longitudeSpeed) <= lowerBound;
}
// Determine morning/evening star status by comparing with Sun's longitude
if (body !== astronomy_engine_1.Body.Sun && sunLongitude !== undefined) {
planetPosition.isMorningStar =
longitude >= sunLongitude + 15 && longitude <= sunLongitude + 90;
}
// Handle retrograde period information
if (longitudeSpeed < 0) {
// Find when retrograde ends by looking ahead
const period = findRetrogradePeriod(time, bodyName);
if (period) {
const dateStr = time.toISOString().split("T")[0];
if (dateStr >= period.startDate && dateStr <= period.endDate) {
planetPosition.retrogradeEnds = period.endDate;
}
}
}
else {
// Find next retrograde
const period = findRetrogradePeriod(time, bodyName);
if (period) {
const dateStr = time.toISOString().split("T")[0];
if (dateStr < period.startDate) {
planetPosition.nextRetrograde = period.startDate;
}
}
}
return planetPosition;
};
function serializePlanetPosition(pos) {
return {
...pos,
position: (0, utils_1.decimalToDegreesMinutes)(pos.position),
degrees: (0, utils_1.decimalToDegreesMinutes)(pos.degrees),
speed: pos.speed.toFixed(2),
};
}
function serializeHouse(house) {
return {
...house,
startDegrees: (0, utils_1.decimalToDegreesMinutes)(house.startDegrees),
endDegrees: (0, utils_1.decimalToDegreesMinutes)(house.endDegrees),
};
}
function serializeAspect(aspect) {
return {
...aspect,
deviation: (0, utils_1.decimalToDegreesMinutes)(Math.abs(aspect.deviation)),
};
}
// Helper function to assign planets to houses and aspects to planets
function assignPlanetsToHouses(positions, houses) {
// Calculate all aspects first
const allAspects = (0, aspects_1.calculateAspects)(positions);
// Create a map of planet names to their aspects
const planetAspects = new Map();
// Initialize empty arrays for each planet
positions.forEach((planet) => {
planetAspects.set(planet.name, []);
});
// Assign aspects to each planet
allAspects.forEach((aspect) => {
const leftPlanet = aspect.planets.left;
const rightPlanet = aspect.planets.right;
// Add aspect to both planets involved
if (planetAspects.has(leftPlanet)) {
planetAspects.get(leftPlanet).push(aspect);
}
if (planetAspects.has(rightPlanet)) {
planetAspects.get(rightPlanet).push(aspect);
}
});
// Create a copy of houses with empty bodies arrays
const housesWithBodies = houses.map((house) => ({
...house,
bodies: [],
}));
// Assign each planet to the appropriate house and add its aspects
positions.forEach((planet) => {
const planetPosition = planet.position;
// Add aspects to the planet
const planetWithAspects = {
...planet,
aspects: planetAspects.get(planet.name) || [],
};
for (let i = 0; i < housesWithBodies.length; i++) {
const house = housesWithBodies[i];
const nextHouse = housesWithBodies[(i + 1) % housesWithBodies.length];
let houseStart = house.startDegrees + getZodiacSignIndex(house.startSign) * 30;
let houseEnd = nextHouse.startDegrees + getZodiacSignIndex(nextHouse.startSign) * 30;
// Handle case where house spans across 0° Aries
if (houseEnd < houseStart) {
houseEnd += 360;
}
// Check if planet is in this house
let planetPos = planetPosition;
if (houseEnd > 360 && planetPos < houseStart) {
planetPos += 360;
}
if (planetPos >= houseStart && planetPos < houseEnd) {
house.bodies.push(planetWithAspects);
break;
}
}
});
return housesWithBodies;
}
// Helper function to get zodiac sign index
function getZodiacSignIndex(sign) {
const zodiacSigns = [
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces",
];
return zodiacSigns.indexOf(sign);
}
// Helper function to find which house a position falls in
function findHouseForPosition(position, houses) {
for (let i = 0; i < houses.length; i++) {
const house = houses[i];
const nextHouse = houses[(i + 1) % houses.length];
let houseStart = house.startDegrees + getZodiacSignIndex(house.startSign) * 30;
let houseEnd = nextHouse.startDegrees + getZodiacSignIndex(nextHouse.startSign) * 30;
// Handle case where house spans across 0° Aries
if (houseEnd < houseStart) {
houseEnd += 360;
}
// Check if position is in this house
let pos = position;
if (houseEnd > 360 && pos < houseStart) {
pos += 360;
}
if (pos >= houseStart && pos < houseEnd) {
return house.number;
}
}
// Fallback to house 1 if not found
return 1;
}
// Helper function to calculate lots
function calculateLots(ascendant, sunPosition, moonPosition, venusPosition, marsPosition, jupiterPosition, saturnPosition, midheaven, isDayChart, houses) {
const lots = [];
// Lot of Fortune
const fortunePosition = isDayChart
? (ascendant + moonPosition - sunPosition + 360) % 360
: (ascendant + sunPosition - moonPosition + 360) % 360;
lots.push({
name: "Lot of Fortune",
position: fortunePosition,
sign: (0, utils_1.getZodiacSign)(fortunePosition),
degrees: fortunePosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(fortunePosition), fortunePosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(fortunePosition), fortunePosition % 30),
house: findHouseForPosition(fortunePosition, houses),
aspects: [],
});
// Lot of Marriage
const marriagePosition = (ascendant + venusPosition - marsPosition + 360) % 360;
lots.push({
name: "Lot of Marriage",
position: marriagePosition,
sign: (0, utils_1.getZodiacSign)(marriagePosition),
degrees: marriagePosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(marriagePosition), marriagePosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(marriagePosition), marriagePosition % 30),
house: findHouseForPosition(marriagePosition, houses),
aspects: [],
});
// Lot of Children
const childrenPosition = (ascendant + jupiterPosition - saturnPosition + 360) % 360;
lots.push({
name: "Lot of Children",
position: childrenPosition,
sign: (0, utils_1.getZodiacSign)(childrenPosition),
degrees: childrenPosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(childrenPosition), childrenPosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(childrenPosition), childrenPosition % 30),
house: findHouseForPosition(childrenPosition, houses),
aspects: [],
});
// Lot of Career
const careerPosition = (ascendant + midheaven - sunPosition + 360) % 360;
lots.push({
name: "Lot of Career",
position: careerPosition,
sign: (0, utils_1.getZodiacSign)(careerPosition),
degrees: careerPosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(careerPosition), careerPosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(careerPosition), careerPosition % 30),
house: findHouseForPosition(careerPosition, houses),
aspects: [],
});
// Lot of Health
const healthPosition = (ascendant + moonPosition - saturnPosition + 360) % 360;
lots.push({
name: "Lot of Health",
position: healthPosition,
sign: (0, utils_1.getZodiacSign)(healthPosition),
degrees: healthPosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(healthPosition), healthPosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(healthPosition), healthPosition % 30),
house: findHouseForPosition(healthPosition, houses),
aspects: [],
});
// Lot of Wealth
const wealthPosition = (ascendant + venusPosition - jupiterPosition + 360) % 360;
lots.push({
name: "Lot of Wealth",
position: wealthPosition,
sign: (0, utils_1.getZodiacSign)(wealthPosition),
degrees: wealthPosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(wealthPosition), wealthPosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(wealthPosition), wealthPosition % 30),
house: findHouseForPosition(wealthPosition, houses),
aspects: [],
});
// Lot of Love
const lovePosition = (venusPosition + jupiterPosition - ascendant + 360) % 360;
lots.push({
name: "Lot of Love",
position: lovePosition,
sign: (0, utils_1.getZodiacSign)(lovePosition),
degrees: lovePosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(lovePosition), lovePosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(lovePosition), lovePosition % 30),
house: findHouseForPosition(lovePosition, houses),
aspects: [],
});
// Calculate Lot of Spirit (Ascendant + Sun - Moon) for day, (Ascendant + Moon - Sun) for night
let lotOfSpiritPosition = isDayChart
? ascendant + sunPosition - moonPosition
: ascendant + moonPosition - sunPosition;
lotOfSpiritPosition = ((lotOfSpiritPosition % 360) + 360) % 360;
const lotOfSpirit = {
name: "Lot of Spirit",
position: lotOfSpiritPosition,
sign: (0, utils_1.getZodiacSign)(lotOfSpiritPosition),
degrees: lotOfSpiritPosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(lotOfSpiritPosition), lotOfSpiritPosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(lotOfSpiritPosition), lotOfSpiritPosition % 30),
house: findHouseForPosition(lotOfSpiritPosition, houses),
aspects: [],
};
lots.push(lotOfSpirit);
// Calculate Lot of Friendship (Moon + (Jupiter - Saturn)) for day, (Moon + (Saturn - Jupiter)) for night
let lotOfFriendshipPosition = isDayChart
? moonPosition + (jupiterPosition - saturnPosition)
: moonPosition + (saturnPosition - jupiterPosition);
lotOfFriendshipPosition = ((lotOfFriendshipPosition % 360) + 360) % 360;
const lotOfFriendship = {
name: "Lot of Friendship",
position: lotOfFriendshipPosition,
sign: (0, utils_1.getZodiacSign)(lotOfFriendshipPosition),
degrees: lotOfFriendshipPosition % 30,
decanRuler: (0, utils_1.getDecanRuler)((0, utils_1.getZodiacSign)(lotOfFriendshipPosition), lotOfFriendshipPosition % 30),
boundRuler: (0, utils_1.getBoundRuler)((0, utils_1.getZodiacSign)(lotOfFriendshipPosition), lotOfFriendshipPosition % 30),
house: findHouseForPosition(lotOfFriendshipPosition, houses),
aspects: [],
};
lots.push(lotOfFriendship);
return lots;
}
// Helper function to serialize a lot
function serializeLot(lot) {
return {
...lot,
position: (0, utils_1.decimalToDegreesMinutes)(lot.position),
degrees: (0, utils_1.decimalToDegreesMinutes)(lot.degrees),
};
}
// Helper function to get aspect type
function getAspectType(diff) {
if (Math.abs(diff - 0) <= 10)
return "conjunction";
if (Math.abs(diff - 60) <= 10)
return "sextile";
if (Math.abs(diff - 90) <= 10)
return "square";
if (Math.abs(diff - 120) <= 10)
return "trine";
if (Math.abs(diff - 180) <= 10)
return "opposition";
return null;
}
// Helper function to detect astrological challenges
function detectChallenges(positions) {
const challenges = [];
// Algol is at 26° Taurus (26° + 30° = 56° in zodiac)
const algolPosition = 56;
// Via Combusta is from 15° Libra to 15° Scorpio (195° to 225° in zodiac)
const viaCombustaStart = 195;
const viaCombustaEnd = 225;
positions.forEach((planet) => {
// Check for Algol transit (within 1° of 26° Taurus)
const distanceFromAlgol = Math.abs(planet.position - algolPosition);
if (distanceFromAlgol <= 1 || distanceFromAlgol >= 359) {
challenges.push(`${planet.name} transits Algol`);
}
// Check for aneretic degree (29° of any sign)
const degreesInSign = planet.position % 30;
if (Math.floor(degreesInSign) === 29) {
challenges.push(`${planet.name} is at Aneretic Degree`);
}
// Check for Via Combusta (Moon between 15° Libra and 15° Scorpio)
if (planet.name === "Moon") {
if (planet.position >= viaCombustaStart &&
planet.position <= viaCombustaEnd) {
challenges.push("Moon is in Via Combusta");
}
// Check for Void Course of the Moon
if (isVoidCourseMoon(planet, positions)) {
challenges.push("Void Course of the Moon");
}
}
});
return challenges;
}
// Helper function to check if Moon is in void course
function isVoidCourseMoon(moon, positions) {
const moonPosition = moon.position;
const moonSpeed = moon.speed;
// Calculate the end of the current sign
const currentSignStart = Math.floor(moonPosition / 30) * 30;
const signEnd = currentSignStart + 30;
const degreesToSignEnd = signEnd - moonPosition;
// Calculate how long it takes Moon to reach the end of the sign
const timeToSignEnd = degreesToSignEnd / Math.abs(moonSpeed);
// Check if Moon will form any aspects before reaching the end of the sign
for (let i = 0; i < 24; i++) {
// Check next 24 hours
const futureTime = i / 24; // Convert hours to days
const futureMoonPosition = moonPosition + moonSpeed * futureTime;
// If Moon has passed the sign end, it's void course
if (futureMoonPosition >= signEnd) {
return true;
}
// Check if Moon forms any aspects in this future position
for (const planet of positions) {
if (planet.name === "Moon" || planet.name === "Ascendant")
continue;
const diff = Math.abs(futureMoonPosition - planet.position);
const adjustedDiff = diff > 180 ? 360 - diff : diff;
const aspectType = getAspectType(adjustedDiff);
if (aspectType) {
return false; // Moon will form an aspect, so not void course
}
}
}
// If we reach here, Moon didn't form any aspects before sign end
return true;
}
// Helper function to calculate planetary hours
function calculatePlanetaryHours(date, latitude, longitude) {
const planetaryHours = {};
const dateStr = date.toISOString().split("T")[0];
// Get timezone offset in minutes for local time conversion
const timezoneOffset = date.getTimezoneOffset();
// Get sunrise and sunset for current day
const currentDayTimes = SunCalc.getTimes(date, latitude, longitude);
const sunrise = currentDayTimes.sunrise;
const sunset = currentDayTimes.sunset;
// Get sunrise for next day
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1);
const nextDayTimes = SunCalc.getTimes(nextDay, latitude, longitude);
const nextSunrise = nextDayTimes.sunrise;
// Calculate day and night hour lengths
const dayDuration = sunset.getTime() - sunrise.getTime();
const nightDuration = nextSunrise.getTime() - sunset.getTime();
const dayHourLength = dayDuration / 12; // 12 day hours
const nightHourLength = nightDuration / 12; // 12 night hours
// Determine day ruler based on day of week
const dayOfWeek = date.getDay(); // 0 = Sunday, 1 = Monday, etc.
const dayRuler = constants_1.DAY_RULERS[dayOfWeek];
// Find starting position in Chaldean order
const chaldeanStartIndex = constants_1.CHALDEAN_ORDER.indexOf(dayRuler);
// Initialize hours array for this date
planetaryHours[dateStr] = [];
// Helper function to convert UTC to local time string
const toLocalTimeString = (utcDate) => {
const localDate = new Date(utcDate.getTime() - timezoneOffset * 60 * 1000);
const hours = localDate.getHours();
const minutes = localDate.getMinutes();
const ampm = hours >= 12 ? "PM" : "AM";
const displayHours = hours % 12 || 12;
return `${displayHours}:${minutes.toString().padStart(2, "0")} ${ampm}`;
};
// Helper function to format duration in minutes
const formatDuration = (durationMs) => {
const minutes = Math.round(durationMs / (1000 * 60));
return `${minutes} minutes`;
};
// Calculate day hours (1-12)
for (let i = 0; i < 12; i++) {
const rulerIndex = (chaldeanStartIndex + i) % constants_1.CHALDEAN_ORDER.length;
const ruler = constants_1.CHALDEAN_ORDER[rulerIndex];
const startTime = new Date(sunrise.getTime() + i * dayHourLength);
const endTime = new Date(sunrise.getTime() + (i + 1) * dayHourLength);
planetaryHours[dateStr].push({
ruler: astronomy_engine_1.Body[ruler],
startTime: toLocalTimeString(startTime),
endTime: toLocalTimeString(endTime),
length: formatDuration(dayHourLength),
});
}
// Calculate night hours (13-24)
for (let i = 0; i < 12; i++) {
const rulerIndex = (chaldeanStartIndex + i) % constants_1.CHALDEAN_ORDER.length;
const ruler = constants_1.CHALDEAN_ORDER[rulerIndex];
const startTime = new Date(sunset.getTime() + i * nightHourLength);
const endTime = new Date(sunset.getTime() + (i + 1) * nightHourLength);
planetaryHours[dateStr].push({
ruler: astronomy_engine_1.Body[ruler],
startTime: toLocalTimeString(startTime),
endTime: toLocalTimeString(endTime),
length: formatDuration(nightHourLength),
});
}
return planetaryHours;
}
// Helper function to calculate romance score
function calculateRomanceScore(positions, houses, lots, moonPhaseData) {
let positiveScore = 0;
let negativeScore = 0;
// Get Venus, Jupiter, Saturn, Mars, Moon positions
const venus = positions.find((p) => p.name === "Venus");
const jupiter = positions.find((p) => p.name === "Jupiter");
const saturn = positions.find((p) => p.name === "Saturn");
const mars = positions.find((p) => p.name === "Mars");
const moon = positions.find((p) => p.name === "Moon");
const lotOfLove = lots.find((l) => l.name === "Lot of Love");
if (!venus)
return 0.5; // Default score if Venus not found
// Helper function to find which house a planet is in
const findHouseForPlanet = (planetPosition) => {
for (let i = 0; i < houses.length; i++) {
const house = houses[i];
const nextHouse = houses[(i + 1) % houses.length];
let houseStart = house.startDegrees + getZodiacSignIndex(house.startSign) * 30;
let houseEnd = nextHouse.startDegrees + getZodiacSignIndex(nextHouse.startSign) * 30;
if (houseEnd < houseStart) {
houseEnd += 360;
}
let planetPos = planetPosition;
if (houseEnd > 360 && planetPos < houseStart) {
planetPos += 360;
}
if (planetPos >= houseStart && planetPos < houseEnd) {
return i + 1;
}
}
return 0;
};
// Helper function to check aspects between two planets
const checkAspects = (planet1, planet2, aspectType) => {
const diff = Math.abs(planet1.position - planet2.position);
const adjustedDiff = diff > 180 ? 360 - diff : diff;
const aspect = getAspectType(adjustedDiff);
if (!aspect)
return null;
if (aspectType && aspect !== aspectType)
return null;
const target = {
conjunction: 0,
sextile: 60,
square: 90,
trine: 120,
opposition: 180,
}[aspect];
const deviation = Math.abs(adjustedDiff - target);
const grade = deviation <= 1 ? "perfect" : deviation <= 3 ? "joining" : "assembly";
return { type: aspect, grade };
};
// Helper function to get aspect score
const getAspectScore = (aspect, isPositive) => {
if (!aspect)
return 0;
const baseScores = {
trine: { perfect: 10, joining: 8, assembly: 6 }, // doubled
sextile: { perfect: 6, joining: 4, assembly: 2 }, // doubled
square: { perfect: -6, joining: -4, assembly: -2 }, // doubled
opposition: { perfect: -10, joining: -8, assembly: -6 }, // doubled
};
const score = baseScores[aspect.type]?.[aspect.grade] || 0;
return isPositive ? Math.max(0, score) : Math.min(0, score);
};
// Venus dignities
if (venus.dignity === "Rulership")
positiveScore += 6; // doubled
if (venus.dignity === "Exaltation")
positiveScore += 4; // doubled
if (venus.dignity === "Fall")
negativeScore += 6; // doubled
if (venus.dignity === "Detriment")
negativeScore += 4; // doubled
if (venus.dignity === "Peregrine")
negativeScore += 2; // doubled
// Venus house placements
const venusHouse = findHouseForPlanet(venus.position);
if (venusHouse === 5)
positiveScore += 4; // doubled
if (venusHouse === 7)
positiveScore += 4; // doubled
if ([1, 4, 7, 10].includes(venusHouse))
positiveScore += 2; // doubled
if ([3, 6, 9, 12].includes(venusHouse))
negativeScore += 2; // doubled
// Jupiter house placements
if (jupiter) {
const jupiterHouse = findHouseForPlanet(jupiter.position);
if (jupiterHouse === 5)
positiveScore += 2; // doubled
if (jupiterHouse === 7)
positiveScore += 2; // doubled
}
// Saturn house placements
if (saturn) {
const saturnHouse = findHouseForPlanet(saturn.position);
if (saturnHouse === 5)
negativeScore += 4; // doubled
if (saturnHouse === 7)
negativeScore += 4; // doubled
}
// Mars house placements
if (mars) {
const marsHouse = findHouseForPlanet(mars.position);
if (marsHouse === 5)
negativeScore += 2; // doubled
if (marsHouse === 7)
negativeScore += 2; // doubled
}
// Moon phase
const moonPhase = moonPhaseData.phase;
if (moonPhase > 0.5 && moonPhase < 1)
positiveScore += 4; // doubled
if (moonPhase === 1)
positiveScore += 2; // doubled
if (moonPhase === 0)
negativeScore += 4; // doubled
if (moonPhase < 0.5 && moonPhase > 0)
negativeScore += 2; // doubled
// House cusp signs
const house5Sign = houses[4].startSign;
const house7Sign = houses[6].startSign;
if (house5Sign === "Leo")
positiveScore += 4; // doubled
if (house7Sign === "Libra")
positiveScore += 4; // doubled
if (house5Sign === "Aquarius")
negativeScore += 4; // doubled
if (house7Sign === "Pisces")
negativeScore += 4; // doubled
// House cusp rulerships
const sunRuledSigns = ["Leo"];
const venusRuledSigns = ["Taurus", "Libra"];
const sunFallSigns = ["Libra"];
const venusFallSigns = ["Virgo"];
if (sunRuledSigns.includes(house5Sign))
positiveScore += 2; // doubled
if (venusRuledSigns.includes(house7Sign))
positiveScore += 2; // doubled
if (sunFallSigns.includes(house5Sign))
negativeScore += 2; // doubled
if (venusFallSigns.includes(house7Sign))
negativeScore += 2; // doubled
// Lot of Love house placement
if (lotOfLove) {
const lotHouse = findHouseForPlanet(lotOfLove.position);
if ([1, 4, 7, 10].includes(lotHouse))
positiveScore += 2; // doubled
if ([3, 6, 9, 12].includes(lotHouse))
negativeScore += 2; // doubled
}
// Venus aspects with Moon
if (moon) {
const aspect = checkAspects(venus, moon);
if (aspect) {
if (aspect.type === "trine") {
positiveScore += getAspectScore(aspect, true);
}
else if (aspect.type === "sextile") {
positiveScore += getAspectScore(aspect, true);
}
else if (aspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(aspect, false));
}
else if (aspect.type === "square") {
negativeScore += Math.abs(getAspectScore(aspect, false));
}
}
}
// Venus aspects with Saturn and Mars (general aspects)
if (saturn) {
const aspect = checkAspects(venus, saturn);
if (aspect) {
positiveScore += 4; // doubled
negativeScore += 4; // doubled
}
}
if (mars) {
const aspect = checkAspects(venus, mars);
if (aspect) {
positiveScore += 2; // doubled
negativeScore += 2; // doubled
}
}
// Lot of Love aspects with Saturn and Mars
if (lotOfLove) {
if (saturn) {
const aspect = checkAspects(lotOfLove, saturn);
if (aspect) {
positiveScore += 4; // doubled
negativeScore += 4; // doubled
}
}
if (mars) {
const aspect = checkAspects(lotOfLove, mars);
if (aspect) {
positiveScore += 2; // doubled
negativeScore += 2; // doubled
}
}
}
// Venus motion
if (venus.planetaryMotion === "direct")
positiveScore += 2; // doubled
// Venus solar influence
if (venus.solarInfluence === "cazimi")
positiveScore += 6; // doubled
if (venus.solarInfluence === "combust")
negativeScore += 6; // doubled
// Normalize scores
const maxPositiveScore = 50; // Estimate of maximum possible positive score
const maxNegativeScore = 30; // Estimate of maximum possible negative score
const normalizedPositive = Math.min(positiveScore / maxPositiveScore, 1) * 0.5;
const normalizedNegative = Math.min(negativeScore / maxNegativeScore, 1) * 0.5;
// Final score: 0.5 + normalized positive - normalized negative
const finalScore = Math.max(0, Math.min(1, 0.5 + normalizedPositive - normalizedNegative));
return Math.round(finalScore * 1000) / 1000; // Round to 3 decimal places
}
// Helper function to calculate career score
function calculateCareerScore(positions, houses, lots, moonPhaseData) {
let positiveScore = 0;
let negativeScore = 0;
// Get relevant planets
const saturn = positions.find((p) => p.name === "Saturn");
const sun = positions.find((p) => p.name === "Sun");
const mars = positions.find((p) => p.name === "Mars");
const lotOfCareer = lots.find((l) => l.name === "Lot of Career");
if (!saturn || !sun || !mars)
return 0.5; // Default score if key planets not found
// Helper function to find which house a planet is in
const findHouseForPlanet = (planetPosition) => {
for (let i = 0; i < houses.length; i++) {
const house = houses[i];
const nextHouse = houses[(i + 1) % houses.length];
let houseStart = house.startDegrees + getZodiacSignIndex(house.startSign) * 30;
let houseEnd = nextHouse.startDegrees + getZodiacSignIndex(nextHouse.startSign) * 30;
if (houseEnd < houseStart) {
houseEnd += 360;
}
let planetPos = planetPosition;
if (houseEnd > 360 && planetPos < houseStart) {
planetPos += 360;
}
if (planetPos >= houseStart && planetPos < houseEnd) {
return i + 1;
}
}
return 0;
};
// Helper function to check aspects between two planets
const checkAspects = (planet1, planet2, aspectType) => {
const diff = Math.abs(planet1.position - planet2.position);
const adjustedDiff = diff > 180 ? 360 - diff : diff;
const aspect = getAspectType(adjustedDiff);
if (!aspect)
return null;
if (aspectType && aspect !== aspectType)
return null;
const target = {
conjunction: 0,
sextile: 60,
square: 90,
trine: 120,
opposition: 180,
}[aspect];
const deviation = Math.abs(adjustedDiff - target);
const grade = deviation <= 1 ? "perfect" : deviation <= 3 ? "joining" : "assembly";
return { type: aspect, grade };
};
// Helper function to get aspect score
const getAspectScore = (aspect, isPositive) => {
if (!aspect)
return 0;
const baseScores = {
trine: { perfect: 10, joining: 8, assembly: 6 }, // doubled
sextile: { perfect: 6, joining: 4, assembly: 2 }, // doubled
square: { perfect: -6, joining: -4, assembly: -2 }, // doubled
opposition: { perfect: -10, joining: -8, assembly: -6 }, // doubled
};
const score = baseScores[aspect.type]?.[aspect.grade] || 0;
return isPositive ? Math.max(0, score) : Math.min(0, score);
};
// Saturn dignities
if (saturn.dignity === "Rulership")
positiveScore += 6; // doubled
if (saturn.dignity === "Exaltation")
positiveScore += 4; // doubled
if (saturn.dignity === "Fall")
negativeScore += 6; // doubled
if (saturn.dignity === "Detriment")
negativeScore += 4; // doubled
// Sun dignities
if (sun.dignity === "Rulership")
positiveScore += 6; // doubled
if (sun.dignity === "Exaltation")
positiveScore += 4; // doubled
if (sun.dignity === "Fall")
negativeScore += 6; // doubled
if (sun.dignity === "Detriment")
negativeScore += 4; // doubled
// Mars dignities
if (mars.dignity === "Rulership")
positiveScore += 4; // doubled
if (mars.dignity === "Exaltation")
positiveScore += 2; // doubled
if (mars.dignity === "Fall")
negativeScore += 4; // doubled
if (mars.dignity === "Detriment")
negativeScore += 2; // doubled
// House placements
const saturnHouse = findHouseForPlanet(saturn.position);
const sunHouse = findHouseForPlanet(sun.position);
const marsHouse = findHouseForPlanet(mars.position);
if (saturnHouse === 10)
positiveScore += 6; // doubled
if (sunHouse === 10)
positiveScore += 4; // doubled
if (marsHouse === 10 || marsHouse === 6)
positiveScore += 2; // doubled
if (saturnHouse === 6 || saturnHouse === 12)
negativeScore += 4; // doubled
// House cusp signs
const house10Sign = houses[9].startSign;
if (house10Sign === "Capricorn" || house10Sign === "Aquarius")
positiveScore += 4; // doubled
if (house10Sign === "Cancer" || house10Sign === "Leo")
negativeScore += 4; // doubled
// Lot of Career house placement
if (lotOfCareer) {
const lotHouse = findHouseForPlanet(lotOfCareer.position);
if ([1, 4, 7, 10].includes(lotHouse))
positiveScore += 2; // doubled
if ([3, 6, 9, 12].includes(lotHouse))
negativeScore += 2; // doubled
}
// Aspects between Saturn, Sun, and Mars
const saturnSunAspect = checkAspects(saturn, sun);
const saturnMarsAspect = checkAspects(saturn, mars);
const sunMarsAspect = checkAspects(sun, mars);
if (saturnSunAspect) {
if (saturnSunAspect.type === "trine" ||
saturnSunAspect.type === "sextile") {
positiveScore += getAspectScore(saturnSunAspect, true);
}
else if (saturnSunAspect.type === "square" ||
saturnSunAspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(saturnSunAspect, false));
}
}
if (saturnMarsAspect) {
if (saturnMarsAspect.type === "trine" ||
saturnMarsAspect.type === "sextile") {
positiveScore += getAspectScore(saturnMarsAspect, true);
}
else if (saturnMarsAspect.type === "square" ||
saturnMarsAspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(saturnMarsAspect, false));
}
}
if (sunMarsAspect) {
if (sunMarsAspect.type === "trine" || sunMarsAspect.type === "sextile") {
positiveScore += getAspectScore(sunMarsAspect, true);
}
else if (sunMarsAspect.type === "square" ||
sunMarsAspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(sunMarsAspect, false));
}
}
// Planetary motion
if (saturn.planetaryMotion === "direct")
positiveScore += 2; // doubled
if (sun.planetaryMotion === "direct")
positiveScore += 2; // doubled
if (saturn.planetaryMotion === "retrograde")
negativeScore += 2; // doubled
// Solar influence (combust only applies to planets that can be combust)
if (mars.solarInfluence === "combust")
negativeScore += 4; // doubled
// Normalize scores
const maxPositiveScore = 40;
const maxNegativeScore = 30;
const normalizedPositive = Math.min(positiveScore / maxPositiveScore, 1) * 0.5;
const normalizedNegative = Math.min(negativeScore / maxNegativeScore, 1) * 0.5;
const finalScore = Math.max(0, Math.min(1, 0.5 + normalizedPositive - normalizedNegative));
return Math.round(finalScore * 1000) / 1000;
}
// Helper function to calculate wealth score
function calculateWealthScore(positions, houses, lots, moonPhaseData) {
let positiveScore = 0;
let negativeScore = 0;
// Get relevant planets
const jupiter = positions.find((p) => p.name === "Jupiter");
const venus = positions.find((p) => p.name === "Venus");
const moon = positions.find((p) => p.name === "Moon");
const lotOfFortune = lots.find((l) => l.name === "Lot of Fortune");
if (!jupiter || !venus || !moon)
return 0.5; // Default score if key planets not found
// Helper function to find which house a planet is in
const findHouseForPlanet = (planetPosition) => {
for (let i = 0; i < houses.length; i++) {
const house = houses[i];
const nextHouse = houses[(i + 1) % houses.length];
let houseStart = house.startDegrees + getZodiacSignIndex(house.startSign) * 30;
let houseEnd = nextHouse.startDegrees + getZodiacSignIndex(nextHouse.startSign) * 30;
if (houseEnd < houseStart) {
houseEnd += 360;
}
let planetPos = planetPosition;
if (houseEnd > 360 && planetPos < houseStart) {
planetPos += 360;
}
if (planetPos >= houseStart && planetPos < houseEnd) {
return i + 1;
}
}
return 0;
};
// Helper function to check aspects between two planets
const checkAspects = (planet1, planet2, aspectType) => {
const diff = Math.abs(planet1.position - planet2.position);
const adjustedDiff = diff > 180 ? 360 - diff : diff;
const aspect = getAspectType(adjustedDiff);
if (!aspect)
return null;
if (aspectType && aspect !== aspectType)
return null;
const target = {
conjunction: 0,
sextile: 60,
square: 90,
trine: 120,
opposition: 180,
}[aspect];
const deviation = Math.abs(adjustedDiff - target);
const grade = deviation <= 1 ? "perfect" : deviation <= 3 ? "joining" : "assembly";
return { type: aspect, grade };
};
// Helper function to get aspect score
const getAspectScore = (aspect, isPositive) => {
if (!aspect)
return 0;
const baseScores = {
trine: { perfect: 5, joining: 4, assembly: 3 },
sextile: { perfect: 3, joining: 2, assembly: 1 },
square: { perfect: -3, joining: -2, assembly: -1 },
opposition: { perfect: -5, joining: -4, assembly: -3 },
};
const score = baseScores[aspect.type]?.[aspect.grade] || 0;
return isPositive ? Math.max(0, score) : Math.min(0, score);
};
// Jupiter dignities
if (jupiter.dignity === "Rulership")
positiveScore += 6; // doubled
if (jupiter.dignity === "Exaltation")
positiveScore += 4; // doubled
if (jupiter.dignity === "Fall")
negativeScore += 6; // doubled
if (jupiter.dignity === "Detriment")
negativeScore += 4; // doubled
// Venus dignities
if (venus.dignity === "Rulership")
positiveScore += 6; // doubled
if (venus.dignity === "Exaltation")
positiveScore += 4; // doubled
if (venus.dignity === "Fall")
negativeScore += 4; // doubled
if (venus.dignity === "Detriment")
negativeScore += 2; // doubled
// Moon phase
const moonPhase = moonPhaseData.phase;
if (moonPhase > 0.5 && moonPhase < 1)
positiveScore += 4; // doubled
if (moonPhase === 1)
positiveScore += 2; // doubled
if (moonPhase === 0)
negativeScore += 4; // doubled
if (moonPhase < 0.5 && moonPhase > 0)
negativeScore += 2; // doubled
// House placements
const jupiterHouse = findHouseForPlanet(jupiter.position);
const venusHouse = findHouseForPlanet(venus.position);
if (jupiterHouse === 2 || jupiterHouse === 8 || jupiterHouse === 11)
positiveScore += 4; // doubled
if (venusHouse === 2 || venusHouse === 8 || venusHouse === 11)
positiveScore += 4; // doubled
if (jupiterHouse === 3 ||
jupiterHouse === 6 ||
jupiterHouse === 9 ||
jupiterHouse === 12)
negativeScore += 2; // doubled
if (venusHouse === 3 ||
venusHouse === 6 ||
venusHouse === 9 ||
venusHouse === 12)
negativeScore += 2; // doubled
// Lot of Fortune house placement
if (lotOfFortune) {
const lotHouse = findHouseForPlanet(lotOfFortune.position);
if ([1, 4, 7, 10].includes(lotHouse))
positiveScore += 2; // doubled
if ([3, 6, 9, 12].includes(lotHouse))
negativeScore += 2; // doubled
}
// Aspects between Jupiter, Venus, and Moon
const jupiterVenusAspect = checkAspects(jupiter, venus);
const venusMoonAspect = checkAspects(venus, moon);
const jupiterMoonAspect = checkAspects(jupiter, moon);
if (jupiterVenusAspect) {
if (jupiterVenusAspect.type === "trine" ||
jupiterVenusAspect.type === "sextile") {
positiveScore += getAspectScore(jupiterVenusAspect, true);
}
else if (jupiterVenusAspect.type === "square" ||
jupiterVenusAspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(jupiterVenusAspect, false));
}
}
if (venusMoonAspect) {
if (venusMoonAspect.type === "trine" ||
venusMoonAspect.type === "sextile") {
positiveScore += getAspectScore(venusMoonAspect, true);
}
else if (venusMoonAspect.type === "square" ||
venusMoonAspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(venusMoonAspect, false));
}
}
if (jupiterMoonAspect) {
if (jupiterMoonAspect.type === "trine" ||
jupiterMoonAspect.type === "sextile") {
positiveScore += getAspectScore(jupiterMoonAspect, true);
}
else if (jupiterMoonAspect.type === "square" ||
jupiterMoonAspect.type === "opposition") {
negativeScore += Math.abs(getAspectScore(jupiterMoonAspect, false));
}
}
// Planetary motion
if (jupiter.planetaryMotion === "direct")
positiveScore += 2; // doubled
if (venus.planetaryMotion === "direct")
positiveScore += 2; // doubled
if (jupiter.planetaryMotion === "retrograde")
negativeScore += 2; // doubled
// Solar influence (combust only applies to planets that can be combust)
if (jupiter.solarInfluence === "combust")
negativeScore += 6; // doubled
if (venus.solarInfluence === "combust")
negativeScore += 6; // doubled
// Normalize scores
const maxPositiveScore = 40;
const maxNegativeScore = 30;
const normalizedPositive = Math.min(positiveScore / maxPositiveScore, 1) * 0.5;
const normalizedNegative = Math.min(negativeScore / maxNegativeScore, 1) * 0.5;
const finalScore = Math.max(0, Math.min(1, 0.5 + normalizedPositive - normalizedNegative));
return Math.round(finalScore * 1000) / 1000;
}
// Helper function to calculate magic score
function calculateMagicScore(positions, houses, lots, moonPhaseData) {
let positiveScore = 0;
let negativeScore = 0;
// Get relevant planets
const mercury = positions.find((p) => p.name === "Mercury");
const moon = positions.find((p) => p.name === "Moon");
const neptune = positions.find((p) => p.name === "Neptune");
const lotOfSpirit = lots.find((l) => l.name === "Lot of Spirit");
if (!mercury || !moon)
return 0.5; // Default score if key planets not found
// Helper function to find which house a planet is in
const findHouseForPlanet = (planetPosition) => {
for (let i = 0; i < houses.length; i++) {
const house = houses[i];
const nextHouse = houses[(i + 1) % houses.length];
let houseStart = house.startDegrees + getZodiacSignIndex(house.startSign) * 30;
let houseEnd = nextHouse.startDegrees + getZodiacSignIndex(nextHouse.startSign) * 30;
if (houseEnd < houseStart) {
houseEnd += 360;
}
let planetPos = planetPosition;
if (houseEnd > 360 && planetPos < houseStart) {
planetPos += 360;
}
if (planetPos >= houseStart && planetPos < houseEnd) {
return i + 1;
}
}
return 0;
};
// Helper function to check aspects between two planets
const checkAspects = (planet1, planet2, aspectType) => {
const diff = Math.abs(planet1.position - planet2.position);
const adjustedDiff = diff > 180 ? 360 - diff : diff;
const aspect = getAspectType(adjustedDiff);
if (!aspect)
return null;
if (aspectType && aspect !== aspectType)
return null;
const target = {
conjunction