klio 1.4.9 → 1.5.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/README.md +1 -0
- package/package.json +1 -1
- package/src/astrology/astrologyConstants.js +7 -0
- package/src/astrology/astrologyService.js +284 -143
- package/src/cli/cli.js +55 -5
package/README.md
CHANGED
|
@@ -98,6 +98,7 @@ It's possible to analyze a csv with a column of either ISO date time or unix tim
|
|
|
98
98
|
- **Show house and sign distribution of the datetime column**: `klio [planet] --csv <file-path>`
|
|
99
99
|
- **Show aspect type distribution between two planets:** `klio [planet1] [planet2] --csv <file-path> --a`
|
|
100
100
|
- **Filter CSV data by column value:** `klio [planet] --csv <file-path> --filter "column:value"` (e.g., `--filter "Item:coffee"`)
|
|
101
|
+
- **Filter CSV data by multiple conditions:** `klio [planet] --csv <file-path> --filter "column1:value1,column2:value2"` (e.g., `--filter "FTR:H,HomeTeam:Liverpool"`)
|
|
101
102
|
- **Creating a bar chart**: Create a bar chart and save the image to the downloads' folder. The image shows the aspect distribution of your csv datetime values: `klio moon sun --csv /home/user/Downloads/coffee.csv --filter "Item:cookie" --a --title "Eaten cookies during sun-moon aspects"`
|
|
102
103
|
|
|
103
104
|
- The command also returns a Chi-Square.
|
package/package.json
CHANGED
|
@@ -24,6 +24,12 @@ const elements = [
|
|
|
24
24
|
'Air', 'Water', 'Fire', 'Earth', 'Air', 'Water'
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
+
// Sign types (Cardinal, Fixed, Mutable)
|
|
28
|
+
const signTypes = [
|
|
29
|
+
'Cardinal', 'Fixed', 'Mutable', 'Cardinal', 'Fixed', 'Mutable',
|
|
30
|
+
'Cardinal', 'Fixed', 'Mutable', 'Cardinal', 'Fixed', 'Mutable'
|
|
31
|
+
];
|
|
32
|
+
|
|
27
33
|
// Decans
|
|
28
34
|
const decans = [
|
|
29
35
|
'1st Decan', '2nd Decan', '3rd Decan',
|
|
@@ -50,6 +56,7 @@ module.exports = {
|
|
|
50
56
|
planets,
|
|
51
57
|
signs,
|
|
52
58
|
elements,
|
|
59
|
+
signTypes,
|
|
53
60
|
decans,
|
|
54
61
|
dignities
|
|
55
62
|
};
|
|
@@ -3,7 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const moment = require('moment-timezone');
|
|
4
4
|
const csvParser = require('csv-parser');
|
|
5
5
|
const { parse } = require('csv-parse/sync');
|
|
6
|
-
const { planets, signs, elements, decans, dignities } = require('./astrologyConstants');
|
|
6
|
+
const { planets, signs, elements, signTypes, decans, dignities } = require('./astrologyConstants');
|
|
7
7
|
const { loadConfig } = require('../config/configService');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
|
|
@@ -454,12 +454,18 @@ function getAstrologicalData(planetName, customDate = null) {
|
|
|
454
454
|
};
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
|
|
458
|
+
|
|
457
459
|
// Function to identify critical planets
|
|
458
460
|
function getCriticalPlanets(customDate = null) {
|
|
459
461
|
const criticalPlanets = [];
|
|
460
462
|
|
|
461
|
-
// Critical degrees
|
|
462
|
-
|
|
463
|
+
// Critical degrees by sign type:
|
|
464
|
+
// Cardinal signs (Aries, Cancer, Libra, Capricorn): 0°, 13°, 26°
|
|
465
|
+
// Fixed signs (Taurus, Leo, Scorpio, Aquarius): 8-9°, 21-22°
|
|
466
|
+
// Mutable signs (Gemini, Virgo, Sagittarius, Pisces): 4°, 17°
|
|
467
|
+
// Anaretic degree (all signs): 29°
|
|
468
|
+
|
|
463
469
|
const orb = 1; // Tolerance of 1 degree
|
|
464
470
|
|
|
465
471
|
// Use the specified date or current time
|
|
@@ -502,19 +508,55 @@ function getCriticalPlanets(customDate = null) {
|
|
|
502
508
|
const degreeInSign = longitude % 30;
|
|
503
509
|
const signIndex = Math.floor(longitude / 30);
|
|
504
510
|
const sign = signs[signIndex];
|
|
511
|
+
const signType = signTypes[signIndex];
|
|
512
|
+
|
|
513
|
+
// Determine critical degrees based on sign type
|
|
514
|
+
let isCritical = false;
|
|
515
|
+
let criticalType = '';
|
|
505
516
|
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
517
|
+
// First check for Anaretic degree (29°) - applies to all signs
|
|
518
|
+
if (degreeInSign >= 28.8) { // 29° with 0.2° orb for precision
|
|
519
|
+
isCritical = true;
|
|
520
|
+
criticalType = 'Anaretic (29°)';
|
|
521
|
+
} else if (signType === 'Cardinal') {
|
|
522
|
+
// Cardinal: exact degrees 0°, 13°, 26° with orb
|
|
523
|
+
isCritical = [0, 13, 26].some(criticalDegree => {
|
|
524
|
+
return Math.abs(degreeInSign - criticalDegree) <= orb;
|
|
525
|
+
});
|
|
526
|
+
criticalType = 'Cardinal (0°, 13°, 26°)';
|
|
527
|
+
} else if (signType === 'Fixed') {
|
|
528
|
+
// Fixed: ranges 8-9° and 21-22°
|
|
529
|
+
isCritical = (degreeInSign >= 8 && degreeInSign <= 9) || (degreeInSign >= 21 && degreeInSign <= 22);
|
|
530
|
+
criticalType = 'Fixed (8-9°, 21-22°)';
|
|
531
|
+
} else if (signType === 'Mutable') {
|
|
532
|
+
// Mutable: exact degrees 4°, 17° with orb
|
|
533
|
+
isCritical = [4, 17].some(criticalDegree => {
|
|
534
|
+
return Math.abs(degreeInSign - criticalDegree) <= orb;
|
|
535
|
+
});
|
|
536
|
+
criticalType = 'Mutable (4°, 17°)';
|
|
537
|
+
}
|
|
538
|
+
|
|
511
539
|
if (isCritical) {
|
|
540
|
+
// Simple interpretation based on modality challenges
|
|
541
|
+
let interpretation = '';
|
|
542
|
+
|
|
543
|
+
if (criticalType === 'Cardinal (0°, 13°, 26°)') {
|
|
544
|
+
interpretation = 'Tendency to over-express. May push too hard or initiate prematurely.';
|
|
545
|
+
} else if (criticalType === 'Fixed (8-9°, 21-22°)') {
|
|
546
|
+
interpretation = 'Tendency to under-express. May resist change or hold on too tightly.';
|
|
547
|
+
} else if (criticalType === 'Mutable (4°, 17°)') {
|
|
548
|
+
interpretation = 'Path of ambivalence. May struggle with indecision or adapt excessively.';
|
|
549
|
+
} else if (criticalType === 'Anaretic (29°)') {
|
|
550
|
+
interpretation = 'Poised for change. At the end of a cycle, facing transition.';
|
|
551
|
+
}
|
|
552
|
+
|
|
512
553
|
criticalPlanets.push({
|
|
513
554
|
name,
|
|
514
555
|
sign,
|
|
515
556
|
degree: degreeInSign.toFixed(2),
|
|
516
557
|
isCritical: true,
|
|
517
|
-
criticalType:
|
|
558
|
+
criticalType: criticalType,
|
|
559
|
+
interpretation: interpretation
|
|
518
560
|
});
|
|
519
561
|
}
|
|
520
562
|
}
|
|
@@ -1904,16 +1946,25 @@ function analyzeSignDistributionSignificance(signDistribution) {
|
|
|
1904
1946
|
function parseFilterCriteria(filterString) {
|
|
1905
1947
|
if (!filterString) return null;
|
|
1906
1948
|
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1949
|
+
// Check if multiple filters are provided (comma-separated)
|
|
1950
|
+
const filterConditions = filterString.split(',').map(cond => cond.trim());
|
|
1951
|
+
|
|
1952
|
+
const criteria = [];
|
|
1953
|
+
|
|
1954
|
+
for (const condition of filterConditions) {
|
|
1955
|
+
const parts = condition.split(':');
|
|
1956
|
+
if (parts.length !== 2) {
|
|
1957
|
+
console.warn(`Invalid filter format. Expected "column:value" but got "${condition}"`);
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
criteria.push({
|
|
1962
|
+
column: parts[0].trim(),
|
|
1963
|
+
value: parts[1].trim()
|
|
1964
|
+
});
|
|
1911
1965
|
}
|
|
1912
1966
|
|
|
1913
|
-
return
|
|
1914
|
-
column: parts[0].trim(),
|
|
1915
|
-
value: parts[1].trim()
|
|
1916
|
-
};
|
|
1967
|
+
return criteria.length === 1 ? criteria[0] : criteria;
|
|
1917
1968
|
}
|
|
1918
1969
|
|
|
1919
1970
|
// Helper function to apply filter to records
|
|
@@ -1926,10 +1977,21 @@ function applyFilter(records, filterCriteria) {
|
|
|
1926
1977
|
|
|
1927
1978
|
if (!parsedCriteria) return records;
|
|
1928
1979
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
return
|
|
1932
|
-
|
|
1980
|
+
// Handle both single criteria and multiple criteria
|
|
1981
|
+
if (Array.isArray(parsedCriteria)) {
|
|
1982
|
+
return records.filter(record => {
|
|
1983
|
+
return parsedCriteria.every(criterion => {
|
|
1984
|
+
const recordValue = record[criterion.column];
|
|
1985
|
+
return recordValue && recordValue.toString() === criterion.value;
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1988
|
+
} else {
|
|
1989
|
+
// Single criteria (backward compatibility)
|
|
1990
|
+
return records.filter(record => {
|
|
1991
|
+
const recordValue = record[parsedCriteria.column];
|
|
1992
|
+
return recordValue && recordValue.toString() === parsedCriteria.value;
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1933
1995
|
}
|
|
1934
1996
|
|
|
1935
1997
|
function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'koch', analyzeAspects = false, partnerPlanet = null, filterCriteria = null) {
|
|
@@ -1939,6 +2001,7 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
1939
2001
|
|
|
1940
2002
|
// Helper function to process results
|
|
1941
2003
|
function processResults() {
|
|
2004
|
+
|
|
1942
2005
|
// Calculate house distribution
|
|
1943
2006
|
const houseCounts = {};
|
|
1944
2007
|
results.forEach(result => {
|
|
@@ -2004,15 +2067,33 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2004
2067
|
|
|
2005
2068
|
// Process each row
|
|
2006
2069
|
for (const data of filteredRecords) {
|
|
2007
|
-
// Look for a column with
|
|
2008
|
-
const datetimeColumns =
|
|
2070
|
+
// Look for a column with datetime values, prioritizing specific date formats
|
|
2071
|
+
const datetimeColumns = [];
|
|
2072
|
+
|
|
2073
|
+
// First pass: look for YYYY-MM-DD format (most specific)
|
|
2074
|
+
const yyyyMmDdColumns = Object.keys(data).filter(key => {
|
|
2009
2075
|
const value = data[key];
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2076
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
2077
|
+
});
|
|
2078
|
+
|
|
2079
|
+
// Second pass: look for Unix timestamps (10 or 13 digits)
|
|
2080
|
+
const unixTimestampColumns = Object.keys(data).filter(key => {
|
|
2081
|
+
const value = data[key];
|
|
2082
|
+
return /^\d{10,13}$/.test(value);
|
|
2015
2083
|
});
|
|
2084
|
+
|
|
2085
|
+
// Third pass: look for ISO-8601 dates (least specific, as it can match many formats)
|
|
2086
|
+
const isoDateColumns = Object.keys(data).filter(key => {
|
|
2087
|
+
const value = data[key];
|
|
2088
|
+
// Only consider it an ISO date if it's not already matched by more specific patterns
|
|
2089
|
+
if (yyyyMmDdColumns.includes(key) || unixTimestampColumns.includes(key)) {
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
return moment(value, moment.ISO_8601, true).isValid();
|
|
2093
|
+
});
|
|
2094
|
+
|
|
2095
|
+
// Prioritize columns: YYYY-MM-DD first, then Unix timestamps, then ISO dates
|
|
2096
|
+
datetimeColumns.push(...yyyyMmDdColumns, ...unixTimestampColumns, ...isoDateColumns);
|
|
2016
2097
|
|
|
2017
2098
|
if (datetimeColumns.length > 0) {
|
|
2018
2099
|
const datetimeValue = data[datetimeColumns[0]];
|
|
@@ -2023,6 +2104,10 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2023
2104
|
// Convert Unix-Timestamp to milliseconds (if 10 digits, multiply by 1000)
|
|
2024
2105
|
const timestamp = datetimeValue.length === 10 ? parseInt(datetimeValue) * 1000 : parseInt(datetimeValue);
|
|
2025
2106
|
datetime = moment(timestamp);
|
|
2107
|
+
} else if (/^\d{4}-\d{2}-\d{2}$/.test(datetimeValue)) {
|
|
2108
|
+
// Handle as YYYY-MM-DD date format
|
|
2109
|
+
// Parse as date only, set time to 12:00 (noon) as default
|
|
2110
|
+
datetime = moment(datetimeValue + 'T12:00:00');
|
|
2026
2111
|
} else {
|
|
2027
2112
|
// Handle as ISO-8601 date
|
|
2028
2113
|
datetime = moment(datetimeValue);
|
|
@@ -2121,8 +2206,10 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2121
2206
|
}
|
|
2122
2207
|
}
|
|
2123
2208
|
|
|
2124
|
-
// Check if all operations are completed
|
|
2125
|
-
|
|
2209
|
+
// Check if all operations are completed (only if no operations were started)
|
|
2210
|
+
if (pendingOperations === 0) {
|
|
2211
|
+
processResults();
|
|
2212
|
+
}
|
|
2126
2213
|
|
|
2127
2214
|
// Helper function to check completion
|
|
2128
2215
|
function checkCompletion() {
|
|
@@ -2139,139 +2226,193 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2139
2226
|
}
|
|
2140
2227
|
} else {
|
|
2141
2228
|
// Standard file processing for local files
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2229
|
+
try {
|
|
2230
|
+
// Read the file content first
|
|
2231
|
+
const csvData = fs.readFileSync(filePath, 'utf8');
|
|
2232
|
+
|
|
2233
|
+
// Try to auto-detect delimiter by checking first few lines
|
|
2234
|
+
let delimiter = ',';
|
|
2235
|
+
const firstLines = csvData.split('\n').slice(0, 5).join('\n');
|
|
2236
|
+
const commaCount = (firstLines.match(/,/g) || []).length;
|
|
2237
|
+
const semicolonCount = (firstLines.match(/;/g) || []).length;
|
|
2238
|
+
|
|
2239
|
+
// Use semicolon if it appears more frequently than comma
|
|
2240
|
+
if (semicolonCount > commaCount) {
|
|
2241
|
+
delimiter = ';';
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// Parse the CSV data with detected delimiter
|
|
2245
|
+
const records = parse(csvData, {
|
|
2246
|
+
columns: true,
|
|
2247
|
+
skip_empty_lines: true,
|
|
2248
|
+
delimiter: delimiter
|
|
2249
|
+
});
|
|
2250
|
+
|
|
2251
|
+
// Process each record
|
|
2252
|
+
for (const data of records) {
|
|
2253
|
+
// Apply filter if specified
|
|
2254
|
+
if (filterCriteria) {
|
|
2255
|
+
const parsedCriteria = typeof filterCriteria === 'string'
|
|
2256
|
+
? parseFilterCriteria(filterCriteria)
|
|
2149
2257
|
: filterCriteria;
|
|
2258
|
+
|
|
2259
|
+
if (parsedCriteria) {
|
|
2260
|
+
let shouldSkip = false;
|
|
2150
2261
|
|
|
2151
|
-
if (parsedCriteria) {
|
|
2262
|
+
if (Array.isArray(parsedCriteria)) {
|
|
2263
|
+
// Multiple criteria - all must match
|
|
2264
|
+
shouldSkip = !parsedCriteria.every(criterion => {
|
|
2265
|
+
const recordValue = data[criterion.column];
|
|
2266
|
+
return recordValue && recordValue.toString() === criterion.value;
|
|
2267
|
+
});
|
|
2268
|
+
} else {
|
|
2269
|
+
// Single criteria
|
|
2152
2270
|
const recordValue = data[parsedCriteria.column];
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2271
|
+
shouldSkip = !recordValue || recordValue.toString() !== parsedCriteria.value;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
if (shouldSkip) {
|
|
2275
|
+
continue; // Skip this record
|
|
2156
2276
|
}
|
|
2157
2277
|
}
|
|
2278
|
+
}
|
|
2158
2279
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
});
|
|
2280
|
+
// Look for a column with ISO-Datetime values, YYYY-MM-DD dates, or Unix-Timestamps
|
|
2281
|
+
const datetimeColumns = Object.keys(data).filter(key => {
|
|
2282
|
+
const value = data[key];
|
|
2283
|
+
// Check for ISO-8601 date
|
|
2284
|
+
const isISO = moment(value, moment.ISO_8601, true).isValid();
|
|
2285
|
+
// Check for YYYY-MM-DD date format
|
|
2286
|
+
const isYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
2287
|
+
// Check for Unix-Timestamp (number with 10 or 13 digits)
|
|
2288
|
+
const isUnixTimestamp = /^\d{10,13}$/.test(value);
|
|
2289
|
+
return isISO || isYYYYMMDD || isUnixTimestamp;
|
|
2290
|
+
});
|
|
2168
2291
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2292
|
+
if (datetimeColumns.length > 0) {
|
|
2293
|
+
const datetimeValue = data[datetimeColumns[0]];
|
|
2294
|
+
let datetime;
|
|
2172
2295
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2296
|
+
// Check if it's a Unix-Timestamp
|
|
2297
|
+
if (/^\d{10,13}$/.test(datetimeValue)) {
|
|
2298
|
+
// Convert Unix-Timestamp to milliseconds (if 10 digits, multiply by 1000)
|
|
2299
|
+
const timestamp = datetimeValue.length === 10 ? parseInt(datetimeValue) * 1000 : parseInt(datetimeValue);
|
|
2300
|
+
datetime = moment(timestamp);
|
|
2301
|
+
} else if (/^\d{4}-\d{2}-\d{2}$/.test(datetimeValue)) {
|
|
2302
|
+
// Handle as YYYY-MM-DD date format
|
|
2303
|
+
// Parse as date only, set time to 12:00 (noon) as default
|
|
2304
|
+
datetime = moment(datetimeValue + 'T12:00:00');
|
|
2305
|
+
} else {
|
|
2306
|
+
// Handle as ISO-8601 date
|
|
2307
|
+
datetime = moment(datetimeValue);
|
|
2308
|
+
}
|
|
2182
2309
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2310
|
+
// Convert the date to the format needed for astrological calculations
|
|
2311
|
+
const dateComponents = {
|
|
2312
|
+
year: datetime.year(),
|
|
2313
|
+
month: datetime.month() + 1, // moment months are 0-based
|
|
2314
|
+
day: datetime.date(),
|
|
2315
|
+
hour: datetime.hour(),
|
|
2316
|
+
minute: datetime.minute()
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2319
|
+
// Calculate the astrological data for the specified planet
|
|
2320
|
+
let astroData;
|
|
2321
|
+
try {
|
|
2322
|
+
astroData = getAstrologicalData(planetName, dateComponents);
|
|
2323
|
+
} catch (error) {
|
|
2324
|
+
// If error, skip this record (e.g., if date is out of range)
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
// Increase the counter for pending operations
|
|
2329
|
+
pendingOperations++;
|
|
2200
2330
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
}
|
|
2218
|
-
} catch (error) {
|
|
2219
|
-
// Ignoriere Fehler bei der Aspektberechnung für einzelne Datensätze
|
|
2331
|
+
// Calculate the houses
|
|
2332
|
+
const julianDay = calculateJulianDayUTC(dateComponents, -datetime.utcOffset());
|
|
2333
|
+
calculateHouses(julianDay, getHouseSystemCode(houseSystem), false)
|
|
2334
|
+
.then(houses => {
|
|
2335
|
+
const planetLongitude = parseFloat(astroData.degreeInSign) + (signs.indexOf(astroData.sign) * 30);
|
|
2336
|
+
const house = getPlanetHouse(planetLongitude, houses.house);
|
|
2337
|
+
|
|
2338
|
+
// Berechne Aspekte, falls angefordert
|
|
2339
|
+
let aspects = [];
|
|
2340
|
+
if (analyzeAspects) {
|
|
2341
|
+
try {
|
|
2342
|
+
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2343
|
+
|
|
2344
|
+
// Filter nach Partner-Planet, falls angegeben
|
|
2345
|
+
if (partnerPlanet) {
|
|
2346
|
+
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2220
2347
|
}
|
|
2348
|
+
} catch (error) {
|
|
2349
|
+
// Ignoriere Fehler bei der Aspektberechnung für einzelne Datensätze
|
|
2221
2350
|
}
|
|
2351
|
+
}
|
|
2222
2352
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2353
|
+
const result = {
|
|
2354
|
+
datetime: datetimeValue,
|
|
2355
|
+
planet: planetName,
|
|
2356
|
+
sign: astroData.sign,
|
|
2357
|
+
degreeInSign: astroData.degreeInSign,
|
|
2358
|
+
house: house
|
|
2359
|
+
};
|
|
2230
2360
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2361
|
+
if (analyzeAspects) {
|
|
2362
|
+
result.aspects = aspects;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
results.push(result);
|
|
2366
|
+
checkCompletion();
|
|
2367
|
+
})
|
|
2368
|
+
.catch(error => {
|
|
2369
|
+
console.error('Fehler bei der Hausberechnung:', error);
|
|
2370
|
+
|
|
2371
|
+
// Berechne Aspekte auch bei Hausberechnungsfehler, falls angefordert
|
|
2372
|
+
let aspects = [];
|
|
2373
|
+
if (analyzeAspects) {
|
|
2374
|
+
try {
|
|
2375
|
+
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2234
2376
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
console.error('Fehler bei der Hausberechnung:', error);
|
|
2239
|
-
|
|
2240
|
-
// Berechne Aspekte auch bei Hausberechnungsfehler, falls angefordert
|
|
2241
|
-
let aspects = [];
|
|
2242
|
-
if (analyzeAspects) {
|
|
2243
|
-
try {
|
|
2244
|
-
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2245
|
-
|
|
2246
|
-
// Filter nach Partner-Planet, falls angegeben
|
|
2247
|
-
if (partnerPlanet) {
|
|
2248
|
-
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2249
|
-
}
|
|
2250
|
-
} catch (error) {
|
|
2251
|
-
console.error('Fehler bei der Aspektberechnung:', error);
|
|
2377
|
+
// Filter nach Partner-Planet, falls angegeben
|
|
2378
|
+
if (partnerPlanet) {
|
|
2379
|
+
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2252
2380
|
}
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
console.error('Fehler bei der Aspektberechnung:', error);
|
|
2253
2383
|
}
|
|
2384
|
+
}
|
|
2254
2385
|
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
});
|
|
2386
|
+
results.push({
|
|
2387
|
+
datetime: datetimeValue,
|
|
2388
|
+
planet: planetName,
|
|
2389
|
+
sign: astroData.sign,
|
|
2390
|
+
degreeInSign: astroData.degreeInSign,
|
|
2391
|
+
house: 'N/A',
|
|
2392
|
+
...(analyzeAspects ? {aspects} : {})
|
|
2263
2393
|
});
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2394
|
+
checkCompletion();
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
// Helper function to check completion
|
|
2400
|
+
function checkCompletion() {
|
|
2401
|
+
pendingOperations--;
|
|
2402
|
+
if (pendingOperations === 0) {
|
|
2268
2403
|
processResults();
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// Check if all operations are completed (only if no operations were started)
|
|
2408
|
+
if (pendingOperations === 0) {
|
|
2409
|
+
processResults();
|
|
2410
|
+
}
|
|
2411
|
+
} catch (error) {
|
|
2412
|
+
console.error('Fehler beim Lesen der CSV-Datei:', error);
|
|
2413
|
+
reject(error);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2275
2416
|
})
|
|
2276
2417
|
}
|
|
2277
2418
|
|
package/src/cli/cli.js
CHANGED
|
@@ -341,6 +341,54 @@ program
|
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// Use custom date if specified (overrides person data)
|
|
345
|
+
if (options.d) {
|
|
346
|
+
// Try to parse the date as DD.MM.YYYY or DD.MM.YYYY HH:MM
|
|
347
|
+
const dateRegex = /^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?$/;
|
|
348
|
+
const match = options.d.match(dateRegex);
|
|
349
|
+
|
|
350
|
+
if (match) {
|
|
351
|
+
const day = parseInt(match[1], 10);
|
|
352
|
+
const month = parseInt(match[2], 10);
|
|
353
|
+
const year = parseInt(match[3], 10);
|
|
354
|
+
const hour = match[4] ? parseInt(match[4], 10) : 12; // Default: 12 o'clock
|
|
355
|
+
const minute = match[5] ? parseInt(match[5], 10) : 0; // Default: 0 minutes
|
|
356
|
+
|
|
357
|
+
// Check if the date is valid
|
|
358
|
+
const date = new Date(year, month - 1, day, hour, minute);
|
|
359
|
+
if (date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day) {
|
|
360
|
+
customDate = {
|
|
361
|
+
day: day,
|
|
362
|
+
month: month,
|
|
363
|
+
year: year,
|
|
364
|
+
hour: hour,
|
|
365
|
+
minute: minute
|
|
366
|
+
};
|
|
367
|
+
console.log(`Using custom date: ${day}.${month}.${year} ${hour}:${minute.toString().padStart(2, '0')}`);
|
|
368
|
+
} else {
|
|
369
|
+
console.error('Invalid date:', options.d);
|
|
370
|
+
console.error('💡 Please use the format: DD.MM.YYYY or "DD.MM.YYYY HH:MM" (with quotes for date with time)');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
console.error('Invalid date:', options.d);
|
|
375
|
+
console.error('💡 Please use the format: DD.MM.YYYY or "DD.MM.YYYY HH:MM" (with quotes for date with time)');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// If no custom date is specified, use current date
|
|
381
|
+
if (!customDate) {
|
|
382
|
+
const currentTime = getCurrentTimeInTimezone();
|
|
383
|
+
customDate = {
|
|
384
|
+
day: currentTime.day,
|
|
385
|
+
month: currentTime.month,
|
|
386
|
+
year: currentTime.year,
|
|
387
|
+
hour: currentTime.hour,
|
|
388
|
+
minute: currentTime.minute
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
344
392
|
const criticalPlanets = getCriticalPlanets(customDate);
|
|
345
393
|
|
|
346
394
|
if (criticalPlanets.length === 0) {
|
|
@@ -348,18 +396,20 @@ program
|
|
|
348
396
|
return;
|
|
349
397
|
}
|
|
350
398
|
|
|
351
|
-
console.log('
|
|
352
|
-
console.log('| Planet | Sign | Degree | Type |');
|
|
353
|
-
console.log('
|
|
399
|
+
console.log('================================================================================================================');
|
|
400
|
+
console.log('| Planet | Sign | Degree | Type | Interpretation |');
|
|
401
|
+
console.log('================================================================================================================');
|
|
354
402
|
|
|
355
403
|
criticalPlanets.forEach(planet => {
|
|
356
404
|
const planetName = planet.name.charAt(0).toUpperCase() + planet.name.slice(1);
|
|
357
405
|
const sign = planet.sign.padEnd(10, ' ');
|
|
358
406
|
const degree = planet.degree.padEnd(5, ' ');
|
|
359
|
-
|
|
407
|
+
const criticalType = planet.criticalType.padEnd(20, ' ');
|
|
408
|
+
const interpretation = planet.interpretation.padEnd(46, ' ');
|
|
409
|
+
console.log(`| ${planetName.padEnd(8)} | ${sign} | ${degree} | ${criticalType} | ${interpretation} |`);
|
|
360
410
|
});
|
|
361
411
|
|
|
362
|
-
console.log('
|
|
412
|
+
console.log('================================================================================================================');
|
|
363
413
|
if (shouldUseBirthData(options)) {
|
|
364
414
|
console.log('\nThis analysis is based on your birth chart.');
|
|
365
415
|
} else {
|