chart2txt 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chart2txt.min.js +1 -1
- package/dist/config/ChartSettings.d.ts +1 -0
- package/dist/config/ChartSettings.js +1 -0
- package/dist/constants.js +4 -2
- package/dist/core/aspectPatterns.d.ts +5 -0
- package/dist/core/aspectPatterns.js +444 -0
- package/dist/core/aspects.js +94 -9
- package/dist/core/astrology.d.ts +8 -2
- package/dist/core/astrology.js +23 -6
- package/dist/core/dignities.d.ts +27 -0
- package/dist/core/dignities.js +136 -0
- package/dist/core/dispositors.d.ts +22 -0
- package/dist/core/dispositors.js +143 -0
- package/dist/core/signDistributions.d.ts +31 -0
- package/dist/core/signDistributions.js +150 -0
- package/dist/formatters/text/sections/angles.js +4 -4
- package/dist/formatters/text/sections/aspectPatterns.d.ts +7 -0
- package/dist/formatters/text/sections/aspectPatterns.js +175 -0
- package/dist/formatters/text/sections/aspects.js +7 -4
- package/dist/formatters/text/sections/birthdata.js +1 -1
- package/dist/formatters/text/sections/dispositors.d.ts +7 -0
- package/dist/formatters/text/sections/dispositors.js +20 -0
- package/dist/formatters/text/sections/houseOverlays.js +10 -34
- package/dist/formatters/text/sections/houses.d.ts +6 -0
- package/dist/formatters/text/sections/houses.js +36 -0
- package/dist/formatters/text/sections/planets.js +11 -26
- package/dist/formatters/text/sections/signDistributions.d.ts +25 -0
- package/dist/formatters/text/sections/signDistributions.js +67 -0
- package/dist/formatters/text/textFormatter.js +37 -3
- package/dist/types.d.ts +61 -0
- package/dist/utils/formatting.d.ts +6 -0
- package/dist/utils/formatting.js +13 -0
- package/dist/utils/houseCalculations.d.ts +13 -0
- package/dist/utils/houseCalculations.js +65 -0
- package/dist/utils/precision.d.ts +49 -0
- package/dist/utils/precision.js +71 -0
- package/dist/utils/validation.d.ts +37 -0
- package/dist/utils/validation.js +181 -0
- package/package.json +2 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateElementDistributionOutput = generateElementDistributionOutput;
|
|
4
|
+
exports.generateModalityDistributionOutput = generateModalityDistributionOutput;
|
|
5
|
+
exports.generatePolarityOutput = generatePolarityOutput;
|
|
6
|
+
const signDistributions_1 = require("../../../core/signDistributions");
|
|
7
|
+
/**
|
|
8
|
+
* Generates the [ELEMENT DISTRIBUTION] section of the chart output.
|
|
9
|
+
* @param planets Array of planet points.
|
|
10
|
+
* @param chartName Optional chart name for the header.
|
|
11
|
+
* @param ascendant Optional ascendant degree to include in analysis.
|
|
12
|
+
* @returns An array of strings for the output.
|
|
13
|
+
*/
|
|
14
|
+
function generateElementDistributionOutput(planets, chartName, ascendant) {
|
|
15
|
+
const header = chartName
|
|
16
|
+
? `[ELEMENT DISTRIBUTION: ${chartName}]`
|
|
17
|
+
: '[ELEMENT DISTRIBUTION]';
|
|
18
|
+
const output = [header];
|
|
19
|
+
if (planets.length === 0) {
|
|
20
|
+
output.push('No planets available for element analysis.');
|
|
21
|
+
return output;
|
|
22
|
+
}
|
|
23
|
+
const distributions = (0, signDistributions_1.analyzeSignDistributions)(planets, ascendant);
|
|
24
|
+
const formattedElements = (0, signDistributions_1.formatElementDistribution)(distributions.elements);
|
|
25
|
+
output.push(...formattedElements);
|
|
26
|
+
return output;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generates the [MODALITY DISTRIBUTION] section of the chart output.
|
|
30
|
+
* @param planets Array of planet points.
|
|
31
|
+
* @param chartName Optional chart name for the header.
|
|
32
|
+
* @param ascendant Optional ascendant degree to include in analysis.
|
|
33
|
+
* @returns An array of strings for the output.
|
|
34
|
+
*/
|
|
35
|
+
function generateModalityDistributionOutput(planets, chartName, ascendant) {
|
|
36
|
+
const header = chartName
|
|
37
|
+
? `[MODALITY DISTRIBUTION: ${chartName}]`
|
|
38
|
+
: '[MODALITY DISTRIBUTION]';
|
|
39
|
+
const output = [header];
|
|
40
|
+
if (planets.length === 0) {
|
|
41
|
+
output.push('No planets available for modality analysis.');
|
|
42
|
+
return output;
|
|
43
|
+
}
|
|
44
|
+
const distributions = (0, signDistributions_1.analyzeSignDistributions)(planets, ascendant);
|
|
45
|
+
const formattedModalities = (0, signDistributions_1.formatModalityDistribution)(distributions.modalities);
|
|
46
|
+
output.push(...formattedModalities);
|
|
47
|
+
return output;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generates the [POLARITY] section of the chart output.
|
|
51
|
+
* @param planets Array of planet points.
|
|
52
|
+
* @param chartName Optional chart name for the header.
|
|
53
|
+
* @param ascendant Optional ascendant degree to include in analysis.
|
|
54
|
+
* @returns An array of strings for the output.
|
|
55
|
+
*/
|
|
56
|
+
function generatePolarityOutput(planets, chartName, ascendant) {
|
|
57
|
+
const header = chartName ? `[POLARITY: ${chartName}]` : '[POLARITY]';
|
|
58
|
+
const output = [header];
|
|
59
|
+
if (planets.length === 0) {
|
|
60
|
+
output.push('No planets available for polarity analysis.');
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
const distributions = (0, signDistributions_1.analyzeSignDistributions)(planets, ascendant);
|
|
64
|
+
const formattedPolarities = (0, signDistributions_1.formatPolarityDistribution)(distributions.polarities);
|
|
65
|
+
output.push(...formattedPolarities);
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
@@ -3,23 +3,52 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.formatChartToText = formatChartToText;
|
|
4
4
|
const types_1 = require("../../types");
|
|
5
5
|
const ChartSettings_1 = require("../../config/ChartSettings");
|
|
6
|
+
const validation_1 = require("../../utils/validation");
|
|
6
7
|
const aspects_1 = require("../../core/aspects");
|
|
8
|
+
const aspectPatterns_1 = require("../../core/aspectPatterns");
|
|
9
|
+
/**
|
|
10
|
+
* Helper function to get all points (planets + angles) from a chart for aspect calculation
|
|
11
|
+
*/
|
|
12
|
+
function getAllPointsFromChart(chartData) {
|
|
13
|
+
const allPoints = [...chartData.planets];
|
|
14
|
+
if (chartData.ascendant !== undefined) {
|
|
15
|
+
allPoints.push({ name: 'Ascendant', degree: chartData.ascendant });
|
|
16
|
+
}
|
|
17
|
+
if (chartData.midheaven !== undefined) {
|
|
18
|
+
allPoints.push({ name: 'Midheaven', degree: chartData.midheaven });
|
|
19
|
+
}
|
|
20
|
+
return allPoints;
|
|
21
|
+
}
|
|
7
22
|
const metadata_1 = require("./sections/metadata");
|
|
8
23
|
const chartHeader_1 = require("./sections/chartHeader");
|
|
9
24
|
const birthdata_1 = require("./sections/birthdata");
|
|
10
25
|
const angles_1 = require("./sections/angles");
|
|
26
|
+
const houses_1 = require("./sections/houses");
|
|
11
27
|
const planets_1 = require("./sections/planets");
|
|
28
|
+
const dispositors_1 = require("./sections/dispositors");
|
|
12
29
|
const aspects_2 = require("./sections/aspects");
|
|
30
|
+
const aspectPatterns_2 = require("./sections/aspectPatterns");
|
|
13
31
|
const houseOverlays_1 = require("./sections/houseOverlays");
|
|
32
|
+
const signDistributions_1 = require("./sections/signDistributions");
|
|
14
33
|
const processSingleChartOutput = (settings, chartData, chartTitlePrefix) => {
|
|
15
34
|
const outputLines = [];
|
|
16
35
|
outputLines.push(...(0, chartHeader_1.generateChartHeaderOutput)(chartData.name, chartTitlePrefix));
|
|
17
36
|
outputLines.push(...(0, birthdata_1.generateBirthdataOutput)(chartData.location, chartData.timestamp, settings));
|
|
18
37
|
outputLines.push(...(0, angles_1.generateAnglesOutput)(chartData.ascendant, chartData.midheaven));
|
|
38
|
+
outputLines.push(...(0, houses_1.generateHousesOutput)(chartData.houseCusps));
|
|
19
39
|
outputLines.push(...(0, planets_1.generatePlanetsOutput)(chartData.planets, chartData.houseCusps, settings));
|
|
20
|
-
|
|
40
|
+
outputLines.push(...(0, dispositors_1.generateDispositorsOutput)(chartData.planets));
|
|
41
|
+
outputLines.push(...(0, signDistributions_1.generateElementDistributionOutput)(chartData.planets, undefined, chartData.ascendant));
|
|
42
|
+
outputLines.push(...(0, signDistributions_1.generateModalityDistributionOutput)(chartData.planets, undefined, chartData.ascendant));
|
|
43
|
+
outputLines.push(...(0, signDistributions_1.generatePolarityOutput)(chartData.planets, undefined, chartData.ascendant));
|
|
44
|
+
const aspects = (0, aspects_1.calculateAspects)(settings.aspectDefinitions, getAllPointsFromChart(chartData), settings.skipOutOfSignAspects);
|
|
21
45
|
// For single chart, p1ChartName and p2ChartName are not needed for aspect string generation
|
|
22
46
|
outputLines.push(...(0, aspects_2.generateAspectsOutput)('[ASPECTS]', aspects, settings));
|
|
47
|
+
// Detect and display aspect patterns (if enabled)
|
|
48
|
+
if (settings.includeAspectPatterns) {
|
|
49
|
+
const aspectPatterns = (0, aspectPatterns_1.detectAspectPatterns)(chartData.planets, chartData.houseCusps);
|
|
50
|
+
outputLines.push(...(0, aspectPatterns_2.generateAspectPatternsOutput)(aspectPatterns));
|
|
51
|
+
}
|
|
23
52
|
outputLines.push('');
|
|
24
53
|
return outputLines;
|
|
25
54
|
};
|
|
@@ -31,7 +60,7 @@ const processChartPairOutput = (settings, chart1, chart2) => {
|
|
|
31
60
|
? 'NATAL_EVENT'
|
|
32
61
|
: 'SYNASTRY';
|
|
33
62
|
outputLines.push(...(0, chartHeader_1.generateChartHeaderOutput)(`${chart1.name}-${chart2.name}`, header));
|
|
34
|
-
const synastryAspects = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, chart1
|
|
63
|
+
const synastryAspects = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, getAllPointsFromChart(chart1), getAllPointsFromChart(chart2), settings.skipOutOfSignAspects);
|
|
35
64
|
outputLines.push(...(0, aspects_2.generateAspectsOutput)('[PLANET-PLANET ASPECTS]', synastryAspects, settings, chart1.name, chart2.name));
|
|
36
65
|
outputLines.push('');
|
|
37
66
|
outputLines.push(...(0, houseOverlays_1.generateHouseOverlaysOutput)(chart1, chart2, settings));
|
|
@@ -107,6 +136,11 @@ const determineChartType = (data) => {
|
|
|
107
136
|
* @returns A string representing the full chart report.
|
|
108
137
|
*/
|
|
109
138
|
function formatChartToText(data, partialSettings = {}) {
|
|
139
|
+
// Validate input data
|
|
140
|
+
const validationError = (0, validation_1.validateInputData)(data);
|
|
141
|
+
if (validationError) {
|
|
142
|
+
throw new Error(`Invalid chart data: ${validationError}`);
|
|
143
|
+
}
|
|
110
144
|
const settings = new ChartSettings_1.ChartSettings(partialSettings);
|
|
111
145
|
const houseSystemName = settings.houseSystemName;
|
|
112
146
|
const outputLines = [];
|
|
@@ -142,7 +176,7 @@ function formatChartToText(data, partialSettings = {}) {
|
|
|
142
176
|
outputLines.push(...processTransitChartInfoOutput(settings, transitChart));
|
|
143
177
|
for (const chart of nonTransitCharts) {
|
|
144
178
|
// Transit Aspects to Chart 1
|
|
145
|
-
const transitAspectsC1 = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, chart
|
|
179
|
+
const transitAspectsC1 = (0, aspects_1.calculateMultichartAspects)(settings.aspectDefinitions, getAllPointsFromChart(chart), getAllPointsFromChart(transitChart), settings.skipOutOfSignAspects);
|
|
146
180
|
outputLines.push(...(0, aspects_2.generateAspectsOutput)(`[TRANSIT ASPECTS: ${chart.name}]`, transitAspectsC1, settings, chart.name, transitChart.name, true));
|
|
147
181
|
outputLines.push('');
|
|
148
182
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -27,12 +27,72 @@ export interface AspectData {
|
|
|
27
27
|
planetB: string;
|
|
28
28
|
aspectType: string;
|
|
29
29
|
orb: number;
|
|
30
|
+
application?: 'applying' | 'separating' | 'exact';
|
|
30
31
|
}
|
|
31
32
|
export interface AspectCategory {
|
|
32
33
|
name: string;
|
|
33
34
|
minOrb?: number;
|
|
34
35
|
maxOrb: number;
|
|
35
36
|
}
|
|
37
|
+
export interface PlanetPosition {
|
|
38
|
+
name: string;
|
|
39
|
+
degree: number;
|
|
40
|
+
sign: string;
|
|
41
|
+
house?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface TSquare {
|
|
44
|
+
type: 'T-Square';
|
|
45
|
+
apex: PlanetPosition;
|
|
46
|
+
opposition: [PlanetPosition, PlanetPosition];
|
|
47
|
+
mode: 'Cardinal' | 'Fixed' | 'Mutable';
|
|
48
|
+
averageOrb: number;
|
|
49
|
+
}
|
|
50
|
+
export interface GrandTrine {
|
|
51
|
+
type: 'Grand Trine';
|
|
52
|
+
planets: [PlanetPosition, PlanetPosition, PlanetPosition];
|
|
53
|
+
element: 'Fire' | 'Earth' | 'Air' | 'Water';
|
|
54
|
+
averageOrb: number;
|
|
55
|
+
}
|
|
56
|
+
export interface Stellium {
|
|
57
|
+
type: 'Stellium';
|
|
58
|
+
planets: PlanetPosition[];
|
|
59
|
+
sign?: string;
|
|
60
|
+
houses: number[];
|
|
61
|
+
span: number;
|
|
62
|
+
}
|
|
63
|
+
export interface GrandCross {
|
|
64
|
+
type: 'Grand Cross';
|
|
65
|
+
planets: [PlanetPosition, PlanetPosition, PlanetPosition, PlanetPosition];
|
|
66
|
+
mode: 'Cardinal' | 'Fixed' | 'Mutable';
|
|
67
|
+
averageOrb: number;
|
|
68
|
+
}
|
|
69
|
+
export interface Yod {
|
|
70
|
+
type: 'Yod';
|
|
71
|
+
apex: PlanetPosition;
|
|
72
|
+
base: [PlanetPosition, PlanetPosition];
|
|
73
|
+
averageOrb: number;
|
|
74
|
+
}
|
|
75
|
+
export interface MysticRectangle {
|
|
76
|
+
type: 'Mystic Rectangle';
|
|
77
|
+
oppositions: [
|
|
78
|
+
[
|
|
79
|
+
PlanetPosition,
|
|
80
|
+
PlanetPosition
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
PlanetPosition,
|
|
84
|
+
PlanetPosition
|
|
85
|
+
]
|
|
86
|
+
];
|
|
87
|
+
averageOrb: number;
|
|
88
|
+
}
|
|
89
|
+
export interface Kite {
|
|
90
|
+
type: 'Kite';
|
|
91
|
+
grandTrine: [PlanetPosition, PlanetPosition, PlanetPosition];
|
|
92
|
+
opposition: PlanetPosition;
|
|
93
|
+
averageOrb: number;
|
|
94
|
+
}
|
|
95
|
+
export type AspectPattern = TSquare | GrandTrine | Stellium | GrandCross | Yod | MysticRectangle | Kite;
|
|
36
96
|
export interface Settings {
|
|
37
97
|
includeSignDegree: boolean;
|
|
38
98
|
includeAscendant: boolean;
|
|
@@ -41,6 +101,7 @@ export interface Settings {
|
|
|
41
101
|
aspectDefinitions: Aspect[];
|
|
42
102
|
aspectCategories: AspectCategory[];
|
|
43
103
|
skipOutOfSignAspects: boolean;
|
|
104
|
+
includeAspectPatterns: boolean;
|
|
44
105
|
dateFormat: string;
|
|
45
106
|
}
|
|
46
107
|
export type PartialSettings = Partial<Settings>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOrdinal = getOrdinal;
|
|
4
|
+
/**
|
|
5
|
+
* Converts a number to its ordinal form (1st, 2nd, 3rd, etc.)
|
|
6
|
+
* @param num The number to convert
|
|
7
|
+
* @returns The ordinal string
|
|
8
|
+
*/
|
|
9
|
+
function getOrdinal(num) {
|
|
10
|
+
const suffix = ['th', 'st', 'nd', 'rd'];
|
|
11
|
+
const v = num % 100;
|
|
12
|
+
return num + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates house cusps array for correctness
|
|
3
|
+
* @param houseCusps Array of 12 house cusp degrees
|
|
4
|
+
* @returns True if valid, false otherwise
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateHouseCusps(houseCusps: number[] | undefined): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Determines which house a point falls into based on house cusps
|
|
9
|
+
* @param pointDegree The degree of the point (will be normalized)
|
|
10
|
+
* @param houseCusps Array of 12 house cusp degrees
|
|
11
|
+
* @returns House number (1-12) or null if calculation fails
|
|
12
|
+
*/
|
|
13
|
+
export declare function getHouseForPoint(pointDegree: number, houseCusps: number[] | undefined): number | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateHouseCusps = validateHouseCusps;
|
|
4
|
+
exports.getHouseForPoint = getHouseForPoint;
|
|
5
|
+
const astrology_1 = require("../core/astrology");
|
|
6
|
+
const precision_1 = require("./precision");
|
|
7
|
+
/**
|
|
8
|
+
* Validates house cusps array for correctness
|
|
9
|
+
* @param houseCusps Array of 12 house cusp degrees
|
|
10
|
+
* @returns True if valid, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
function validateHouseCusps(houseCusps) {
|
|
13
|
+
if (!houseCusps || houseCusps.length !== 12) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// Check that all cusp values are finite numbers
|
|
17
|
+
for (const cusp of houseCusps) {
|
|
18
|
+
if (!isFinite(cusp)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Determines which house a point falls into based on house cusps
|
|
26
|
+
* @param pointDegree The degree of the point (will be normalized)
|
|
27
|
+
* @param houseCusps Array of 12 house cusp degrees
|
|
28
|
+
* @returns House number (1-12) or null if calculation fails
|
|
29
|
+
*/
|
|
30
|
+
function getHouseForPoint(pointDegree, houseCusps) {
|
|
31
|
+
if (!validateHouseCusps(houseCusps)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!isFinite(pointDegree)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const normalizedPoint = (0, precision_1.roundDegrees)((0, astrology_1.normalizeDegree)(pointDegree));
|
|
38
|
+
const normalizedCusps = houseCusps.map((cusp) => (0, precision_1.roundDegrees)((0, astrology_1.normalizeDegree)(cusp)));
|
|
39
|
+
// Check if point is exactly on any cusp (within precision tolerance)
|
|
40
|
+
for (let i = 0; i < 12; i++) {
|
|
41
|
+
if ((0, precision_1.isOnCusp)(normalizedPoint, normalizedCusps[i])) {
|
|
42
|
+
// Point is exactly on cusp - assign to the house that starts at this cusp
|
|
43
|
+
return i + 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (let i = 0; i < 12; i++) {
|
|
47
|
+
const cuspStart = normalizedCusps[i];
|
|
48
|
+
const cuspEnd = normalizedCusps[(i + 1) % 12];
|
|
49
|
+
if (cuspStart < cuspEnd) {
|
|
50
|
+
// Normal case: cusp doesn't cross 0° boundary
|
|
51
|
+
if (normalizedPoint > cuspStart && normalizedPoint < cuspEnd) {
|
|
52
|
+
return i + 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Wraparound case: cusp crosses 0°/360° boundary
|
|
57
|
+
if (normalizedPoint > cuspStart || normalizedPoint < cuspEnd) {
|
|
58
|
+
return i + 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// This should never happen if cusps properly cover 360 degrees
|
|
63
|
+
console.warn(`Point at ${normalizedPoint}° does not fall in any house`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Floating point precision utilities for astrological calculations
|
|
3
|
+
*/
|
|
4
|
+
export declare const DEFAULT_EPSILON = 0.0001;
|
|
5
|
+
/**
|
|
6
|
+
* Compares two floating-point numbers with epsilon tolerance
|
|
7
|
+
* @param a First number
|
|
8
|
+
* @param b Second number
|
|
9
|
+
* @param epsilon Tolerance value (default: DEFAULT_EPSILON)
|
|
10
|
+
* @returns True if numbers are equal within tolerance
|
|
11
|
+
*/
|
|
12
|
+
export declare function floatEquals(a: number, b: number, epsilon?: number): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Checks if a number is close to zero within epsilon tolerance
|
|
15
|
+
* @param value The number to check
|
|
16
|
+
* @param epsilon Tolerance value (default: DEFAULT_EPSILON)
|
|
17
|
+
* @returns True if number is close to zero
|
|
18
|
+
*/
|
|
19
|
+
export declare function isNearZero(value: number, epsilon?: number): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Rounds a degree value to a reasonable precision (4 decimal places)
|
|
22
|
+
* This prevents accumulation of floating-point errors in calculations
|
|
23
|
+
* @param degrees The degree value to round
|
|
24
|
+
* @returns Rounded degree value
|
|
25
|
+
*/
|
|
26
|
+
export declare function roundDegrees(degrees: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Compares two degree values with appropriate epsilon for astrological calculations
|
|
29
|
+
* @param deg1 First degree value
|
|
30
|
+
* @param deg2 Second degree value
|
|
31
|
+
* @param epsilon Tolerance in degrees (default: 0.0001°)
|
|
32
|
+
* @returns True if degrees are equal within tolerance
|
|
33
|
+
*/
|
|
34
|
+
export declare function degreeEquals(deg1: number, deg2: number, epsilon?: number): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a planet is exactly on a house cusp within floating-point precision
|
|
37
|
+
* @param planetDegree Planet's degree position
|
|
38
|
+
* @param cuspDegree House cusp degree
|
|
39
|
+
* @param epsilon Tolerance in degrees (default: 0.001° = about 3.6 arc-seconds)
|
|
40
|
+
* @returns True if planet is on the cusp within tolerance
|
|
41
|
+
*/
|
|
42
|
+
export declare function isOnCusp(planetDegree: number, cuspDegree: number, epsilon?: number): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Checks if an aspect is exact within floating-point precision
|
|
45
|
+
* @param actualOrb The actual orb of the aspect
|
|
46
|
+
* @param epsilon Tolerance in degrees (default: 0.1° = 6 arc-minutes)
|
|
47
|
+
* @returns True if aspect is exact within tolerance
|
|
48
|
+
*/
|
|
49
|
+
export declare function isExactAspect(actualOrb: number, epsilon?: number): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Floating point precision utilities for astrological calculations
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_EPSILON = void 0;
|
|
7
|
+
exports.floatEquals = floatEquals;
|
|
8
|
+
exports.isNearZero = isNearZero;
|
|
9
|
+
exports.roundDegrees = roundDegrees;
|
|
10
|
+
exports.degreeEquals = degreeEquals;
|
|
11
|
+
exports.isOnCusp = isOnCusp;
|
|
12
|
+
exports.isExactAspect = isExactAspect;
|
|
13
|
+
// Default epsilon for floating-point comparisons (about 0.0036 arc-minutes)
|
|
14
|
+
exports.DEFAULT_EPSILON = 1e-4;
|
|
15
|
+
/**
|
|
16
|
+
* Compares two floating-point numbers with epsilon tolerance
|
|
17
|
+
* @param a First number
|
|
18
|
+
* @param b Second number
|
|
19
|
+
* @param epsilon Tolerance value (default: DEFAULT_EPSILON)
|
|
20
|
+
* @returns True if numbers are equal within tolerance
|
|
21
|
+
*/
|
|
22
|
+
function floatEquals(a, b, epsilon = exports.DEFAULT_EPSILON) {
|
|
23
|
+
return Math.abs(a - b) < epsilon;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Checks if a number is close to zero within epsilon tolerance
|
|
27
|
+
* @param value The number to check
|
|
28
|
+
* @param epsilon Tolerance value (default: DEFAULT_EPSILON)
|
|
29
|
+
* @returns True if number is close to zero
|
|
30
|
+
*/
|
|
31
|
+
function isNearZero(value, epsilon = exports.DEFAULT_EPSILON) {
|
|
32
|
+
return Math.abs(value) < epsilon;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Rounds a degree value to a reasonable precision (4 decimal places)
|
|
36
|
+
* This prevents accumulation of floating-point errors in calculations
|
|
37
|
+
* @param degrees The degree value to round
|
|
38
|
+
* @returns Rounded degree value
|
|
39
|
+
*/
|
|
40
|
+
function roundDegrees(degrees) {
|
|
41
|
+
return Math.round(degrees * 10000) / 10000;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compares two degree values with appropriate epsilon for astrological calculations
|
|
45
|
+
* @param deg1 First degree value
|
|
46
|
+
* @param deg2 Second degree value
|
|
47
|
+
* @param epsilon Tolerance in degrees (default: 0.0001°)
|
|
48
|
+
* @returns True if degrees are equal within tolerance
|
|
49
|
+
*/
|
|
50
|
+
function degreeEquals(deg1, deg2, epsilon = exports.DEFAULT_EPSILON) {
|
|
51
|
+
return floatEquals(deg1, deg2, epsilon);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks if a planet is exactly on a house cusp within floating-point precision
|
|
55
|
+
* @param planetDegree Planet's degree position
|
|
56
|
+
* @param cuspDegree House cusp degree
|
|
57
|
+
* @param epsilon Tolerance in degrees (default: 0.001° = about 3.6 arc-seconds)
|
|
58
|
+
* @returns True if planet is on the cusp within tolerance
|
|
59
|
+
*/
|
|
60
|
+
function isOnCusp(planetDegree, cuspDegree, epsilon = 0.001) {
|
|
61
|
+
return degreeEquals(planetDegree, cuspDegree, epsilon);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Checks if an aspect is exact within floating-point precision
|
|
65
|
+
* @param actualOrb The actual orb of the aspect
|
|
66
|
+
* @param epsilon Tolerance in degrees (default: 0.1° = 6 arc-minutes)
|
|
67
|
+
* @returns True if aspect is exact within tolerance
|
|
68
|
+
*/
|
|
69
|
+
function isExactAspect(actualOrb, epsilon = 0.1) {
|
|
70
|
+
return isNearZero(actualOrb, epsilon);
|
|
71
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ChartData, Point, MultiChartData } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a Point object
|
|
4
|
+
* @param point The point to validate
|
|
5
|
+
* @returns Error message if invalid, null if valid
|
|
6
|
+
*/
|
|
7
|
+
export declare function validatePoint(point: Point): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Validates an array of Points
|
|
10
|
+
* @param points The points array to validate
|
|
11
|
+
* @returns Error message if invalid, null if valid
|
|
12
|
+
*/
|
|
13
|
+
export declare function validatePoints(points: Point[]): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Validates house cusps array
|
|
16
|
+
* @param houseCusps The house cusps to validate
|
|
17
|
+
* @returns Error message if invalid, null if valid
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateHouseCusps(houseCusps: number[] | undefined): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Validates a ChartData object
|
|
22
|
+
* @param chartData The chart data to validate
|
|
23
|
+
* @returns Error message if invalid, null if valid
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateChartData(chartData: ChartData): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Validates MultiChartData
|
|
28
|
+
* @param multiChartData The multi-chart data to validate
|
|
29
|
+
* @returns Error message if invalid, null if valid
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateMultiChartData(multiChartData: MultiChartData): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Validates chart data (single or multi-chart)
|
|
34
|
+
* @param data The data to validate
|
|
35
|
+
* @returns Error message if invalid, null if valid
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateInputData(data: ChartData | MultiChartData): string | null;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validatePoint = validatePoint;
|
|
4
|
+
exports.validatePoints = validatePoints;
|
|
5
|
+
exports.validateHouseCusps = validateHouseCusps;
|
|
6
|
+
exports.validateChartData = validateChartData;
|
|
7
|
+
exports.validateMultiChartData = validateMultiChartData;
|
|
8
|
+
exports.validateInputData = validateInputData;
|
|
9
|
+
/**
|
|
10
|
+
* Validates a Point object
|
|
11
|
+
* @param point The point to validate
|
|
12
|
+
* @returns Error message if invalid, null if valid
|
|
13
|
+
*/
|
|
14
|
+
function validatePoint(point) {
|
|
15
|
+
if (!point || typeof point !== 'object') {
|
|
16
|
+
return 'Point must be an object';
|
|
17
|
+
}
|
|
18
|
+
if (typeof point.name !== 'string' || point.name.trim() === '') {
|
|
19
|
+
return 'Point name must be a non-empty string';
|
|
20
|
+
}
|
|
21
|
+
if (typeof point.degree !== 'number' || !isFinite(point.degree)) {
|
|
22
|
+
return `Point ${point.name} has invalid degree value: ${point.degree}`;
|
|
23
|
+
}
|
|
24
|
+
if (point.speed !== undefined &&
|
|
25
|
+
(typeof point.speed !== 'number' || !isFinite(point.speed))) {
|
|
26
|
+
return `Point ${point.name} has invalid speed value: ${point.speed}`;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validates an array of Points
|
|
32
|
+
* @param points The points array to validate
|
|
33
|
+
* @returns Error message if invalid, null if valid
|
|
34
|
+
*/
|
|
35
|
+
function validatePoints(points) {
|
|
36
|
+
if (!Array.isArray(points)) {
|
|
37
|
+
return 'Points must be an array';
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < points.length; i++) {
|
|
40
|
+
const error = validatePoint(points[i]);
|
|
41
|
+
if (error) {
|
|
42
|
+
return `Point at index ${i}: ${error}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Check for duplicate point names
|
|
46
|
+
const names = points.map((p) => p.name);
|
|
47
|
+
const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
|
|
48
|
+
if (duplicates.length > 0) {
|
|
49
|
+
return `Duplicate point names found: ${duplicates.join(', ')}`;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validates house cusps array
|
|
55
|
+
* @param houseCusps The house cusps to validate
|
|
56
|
+
* @returns Error message if invalid, null if valid
|
|
57
|
+
*/
|
|
58
|
+
function validateHouseCusps(houseCusps) {
|
|
59
|
+
if (houseCusps === undefined) {
|
|
60
|
+
return null; // Optional field
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(houseCusps)) {
|
|
63
|
+
return 'House cusps must be an array';
|
|
64
|
+
}
|
|
65
|
+
if (houseCusps.length !== 12) {
|
|
66
|
+
return `House cusps must contain exactly 12 values, got ${houseCusps.length}`;
|
|
67
|
+
}
|
|
68
|
+
for (let i = 0; i < houseCusps.length; i++) {
|
|
69
|
+
if (typeof houseCusps[i] !== 'number' || !isFinite(houseCusps[i])) {
|
|
70
|
+
return `House cusp ${i + 1} has invalid value: ${houseCusps[i]}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Validates a ChartData object
|
|
77
|
+
* @param chartData The chart data to validate
|
|
78
|
+
* @returns Error message if invalid, null if valid
|
|
79
|
+
*/
|
|
80
|
+
function validateChartData(chartData) {
|
|
81
|
+
if (!chartData || typeof chartData !== 'object') {
|
|
82
|
+
return 'Chart data must be an object';
|
|
83
|
+
}
|
|
84
|
+
if (typeof chartData.name !== 'string' || chartData.name.trim() === '') {
|
|
85
|
+
return 'Chart name must be a non-empty string';
|
|
86
|
+
}
|
|
87
|
+
const pointsError = validatePoints(chartData.planets);
|
|
88
|
+
if (pointsError) {
|
|
89
|
+
return `Planets validation failed: ${pointsError}`;
|
|
90
|
+
}
|
|
91
|
+
if (chartData.ascendant !== undefined) {
|
|
92
|
+
if (typeof chartData.ascendant !== 'number' ||
|
|
93
|
+
!isFinite(chartData.ascendant)) {
|
|
94
|
+
return `Ascendant has invalid value: ${chartData.ascendant}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (chartData.midheaven !== undefined) {
|
|
98
|
+
if (typeof chartData.midheaven !== 'number' ||
|
|
99
|
+
!isFinite(chartData.midheaven)) {
|
|
100
|
+
return `Midheaven has invalid value: ${chartData.midheaven}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const houseCuspsError = validateHouseCusps(chartData.houseCusps);
|
|
104
|
+
if (houseCuspsError) {
|
|
105
|
+
return `House cusps validation failed: ${houseCuspsError}`;
|
|
106
|
+
}
|
|
107
|
+
if (chartData.points !== undefined) {
|
|
108
|
+
const pointsError = validatePoints(chartData.points);
|
|
109
|
+
if (pointsError) {
|
|
110
|
+
return `Additional points validation failed: ${pointsError}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (chartData.timestamp !== undefined) {
|
|
114
|
+
if (!(chartData.timestamp instanceof Date) ||
|
|
115
|
+
isNaN(chartData.timestamp.getTime())) {
|
|
116
|
+
return 'Timestamp must be a valid Date object';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (chartData.location !== undefined) {
|
|
120
|
+
if (typeof chartData.location !== 'string') {
|
|
121
|
+
return 'Location must be a string';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (chartData.chartType !== undefined) {
|
|
125
|
+
const validTypes = ['natal', 'event', 'transit'];
|
|
126
|
+
if (!validTypes.includes(chartData.chartType)) {
|
|
127
|
+
return `Chart type must be one of: ${validTypes.join(', ')}`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Validates MultiChartData
|
|
134
|
+
* @param multiChartData The multi-chart data to validate
|
|
135
|
+
* @returns Error message if invalid, null if valid
|
|
136
|
+
*/
|
|
137
|
+
function validateMultiChartData(multiChartData) {
|
|
138
|
+
if (!Array.isArray(multiChartData)) {
|
|
139
|
+
return 'Multi-chart data must be an array';
|
|
140
|
+
}
|
|
141
|
+
if (multiChartData.length === 0) {
|
|
142
|
+
return 'Multi-chart data must contain at least one chart';
|
|
143
|
+
}
|
|
144
|
+
if (multiChartData.length > 10) {
|
|
145
|
+
return 'Multi-chart data cannot contain more than 10 charts';
|
|
146
|
+
}
|
|
147
|
+
for (let i = 0; i < multiChartData.length; i++) {
|
|
148
|
+
const error = validateChartData(multiChartData[i]);
|
|
149
|
+
if (error) {
|
|
150
|
+
return `Chart at index ${i} (${multiChartData[i]?.name || 'unnamed'}): ${error}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Check for duplicate chart names
|
|
154
|
+
const names = multiChartData.map((chart) => chart.name);
|
|
155
|
+
const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
|
|
156
|
+
if (duplicates.length > 0) {
|
|
157
|
+
return `Duplicate chart names found: ${duplicates.join(', ')}`;
|
|
158
|
+
}
|
|
159
|
+
// Validate transit charts
|
|
160
|
+
const transitCharts = multiChartData.filter((chart) => chart.chartType === 'transit');
|
|
161
|
+
if (transitCharts.length > 1) {
|
|
162
|
+
return 'Cannot have more than one transit chart';
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Validates chart data (single or multi-chart)
|
|
168
|
+
* @param data The data to validate
|
|
169
|
+
* @returns Error message if invalid, null if valid
|
|
170
|
+
*/
|
|
171
|
+
function validateInputData(data) {
|
|
172
|
+
if (!data) {
|
|
173
|
+
return 'Data is required';
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(data)) {
|
|
176
|
+
return validateMultiChartData(data);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return validateChartData(data);
|
|
180
|
+
}
|
|
181
|
+
}
|