klio 1.4.9 → 1.5.1
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/.dockerignore +9 -0
- package/.nvmrc +1 -0
- package/Dockerfile +18 -0
- package/README.md +22 -0
- package/compose.yaml +6 -0
- package/package.json +8 -3
- package/src/astrology/astrologyConstants.js +7 -0
- package/src/astrology/astrologyService.js +327 -302
- package/src/astrology/astrologyServiceWeb.js +369 -0
- package/src/astrology/swephWasmLoader.js +106 -0
- package/src/astrology/swissephAdapter.js +279 -0
- package/src/cli/cli.js +148 -152
- package/src/cli/cliService.js +1197 -0
- package/src/cli/cliServiceWeb.js +406 -0
- package/src/config/configService.js +59 -35
- package/src/gui/public/index.html +839 -298
- package/src/gui/public/sweph/astro.data +0 -0
- package/src/gui/public/sweph/astro.js +3934 -0
- package/src/gui/public/sweph/astro.wasm +0 -0
- package/src/gui/public/tailwind.css +3 -0
- package/src/gui/public/tailwind.generated.css +1 -0
- package/src/gui/public/webcontainerService.js +435 -0
- package/src/gui/routes/api.js +64 -101
- package/src/gui/server.js +80 -31
- package/src/gui/webcontainerSetup.js +244 -0
- package/src/health/fileAnalysis.js +2 -2
- package/commands.db +0 -0
- package/src/gui/commandLogger.js +0 -67
- package/src/gui/database.js +0 -135
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const swisseph = require('
|
|
1
|
+
const swisseph = require('./swissephAdapter');
|
|
2
2
|
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
|
|
|
@@ -50,11 +50,11 @@ function getCurrentTimeInTimezone() {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Function to load birth data from configuration
|
|
53
|
-
function getBirthDataFromConfig() {
|
|
53
|
+
function getBirthDataFromConfig(userId = null) {
|
|
54
54
|
try {
|
|
55
55
|
// First try to load the new configuration
|
|
56
56
|
const { loadConfig } = require('../config/configService');
|
|
57
|
-
const config = loadConfig();
|
|
57
|
+
const config = loadConfig(userId);
|
|
58
58
|
|
|
59
59
|
if (config && config.birthData) {
|
|
60
60
|
// Parse birth date (Format: DD.MM.YYYY)
|
|
@@ -78,32 +78,34 @@ function getBirthDataFromConfig() {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// If the new configuration was not found, try the old file
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (oldConfig && oldConfig.birthData) {
|
|
88
|
-
// Parse birth date (Format: DD.MM.YYYY)
|
|
89
|
-
const dateParts = oldConfig.birthData.date.split('.');
|
|
90
|
-
const day = parseInt(dateParts[0]);
|
|
91
|
-
const month = parseInt(dateParts[1]);
|
|
92
|
-
const year = parseInt(dateParts[2]);
|
|
93
|
-
|
|
94
|
-
// Parse birth time (Format: HH:MM)
|
|
95
|
-
const timeParts = oldConfig.birthData.time.split(':');
|
|
96
|
-
const hour = parseInt(timeParts[0]);
|
|
97
|
-
const minute = parseInt(timeParts[1]);
|
|
81
|
+
// If the new configuration was not found, try the old file (only for global config)
|
|
82
|
+
if (!userId) {
|
|
83
|
+
const configPath = path.join(__dirname, '../../astrocli-config.json');
|
|
84
|
+
if (fs.existsSync(configPath)) {
|
|
85
|
+
const configData = fs.readFileSync(configPath, 'utf8');
|
|
86
|
+
const oldConfig = JSON.parse(configData);
|
|
98
87
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
day
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
if (oldConfig && oldConfig.birthData) {
|
|
89
|
+
// Parse birth date (Format: DD.MM.YYYY)
|
|
90
|
+
const dateParts = oldConfig.birthData.date.split('.');
|
|
91
|
+
const day = parseInt(dateParts[0]);
|
|
92
|
+
const month = parseInt(dateParts[1]);
|
|
93
|
+
const year = parseInt(dateParts[2]);
|
|
94
|
+
|
|
95
|
+
// Parse birth time (Format: HH:MM)
|
|
96
|
+
const timeParts = oldConfig.birthData.time.split(':');
|
|
97
|
+
const hour = parseInt(timeParts[0]);
|
|
98
|
+
const minute = parseInt(timeParts[1]);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
year: year,
|
|
102
|
+
month: month,
|
|
103
|
+
day: day,
|
|
104
|
+
hour: hour,
|
|
105
|
+
minute: minute,
|
|
106
|
+
location: oldConfig.birthData.location
|
|
107
|
+
};
|
|
108
|
+
}
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
} catch (error) {
|
|
@@ -114,14 +116,14 @@ function getBirthDataFromConfig() {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
// Function to load person data (including birth data)
|
|
117
|
-
function getPersonDataFromConfig(personId = 'birth') {
|
|
119
|
+
function getPersonDataFromConfig(personId = 'birth', userId = null) {
|
|
118
120
|
try {
|
|
119
121
|
const { getPersonData } = require('../config/configService');
|
|
120
122
|
|
|
121
123
|
if (personId === 'birth') {
|
|
122
|
-
return getBirthDataFromConfig();
|
|
124
|
+
return getBirthDataFromConfig(userId);
|
|
123
125
|
} else {
|
|
124
|
-
return getPersonData(personId);
|
|
126
|
+
return getPersonData(personId, userId);
|
|
125
127
|
}
|
|
126
128
|
} catch (error) {
|
|
127
129
|
console.error(`Error loading data for ${personId}:`, error.message);
|
|
@@ -454,12 +456,18 @@ function getAstrologicalData(planetName, customDate = null) {
|
|
|
454
456
|
};
|
|
455
457
|
}
|
|
456
458
|
|
|
459
|
+
|
|
460
|
+
|
|
457
461
|
// Function to identify critical planets
|
|
458
462
|
function getCriticalPlanets(customDate = null) {
|
|
459
463
|
const criticalPlanets = [];
|
|
460
464
|
|
|
461
|
-
// Critical degrees
|
|
462
|
-
|
|
465
|
+
// Critical degrees by sign type:
|
|
466
|
+
// Cardinal signs (Aries, Cancer, Libra, Capricorn): 0°, 13°, 26°
|
|
467
|
+
// Fixed signs (Taurus, Leo, Scorpio, Aquarius): 8-9°, 21-22°
|
|
468
|
+
// Mutable signs (Gemini, Virgo, Sagittarius, Pisces): 4°, 17°
|
|
469
|
+
// Anaretic degree (all signs): 29°
|
|
470
|
+
|
|
463
471
|
const orb = 1; // Tolerance of 1 degree
|
|
464
472
|
|
|
465
473
|
// Use the specified date or current time
|
|
@@ -502,19 +510,55 @@ function getCriticalPlanets(customDate = null) {
|
|
|
502
510
|
const degreeInSign = longitude % 30;
|
|
503
511
|
const signIndex = Math.floor(longitude / 30);
|
|
504
512
|
const sign = signs[signIndex];
|
|
513
|
+
const signType = signTypes[signIndex];
|
|
514
|
+
|
|
515
|
+
// Determine critical degrees based on sign type
|
|
516
|
+
let isCritical = false;
|
|
517
|
+
let criticalType = '';
|
|
505
518
|
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
519
|
+
// First check for Anaretic degree (29°) - applies to all signs
|
|
520
|
+
if (degreeInSign >= 28.8) { // 29° with 0.2° orb for precision
|
|
521
|
+
isCritical = true;
|
|
522
|
+
criticalType = 'Anaretic (29°)';
|
|
523
|
+
} else if (signType === 'Cardinal') {
|
|
524
|
+
// Cardinal: exact degrees 0°, 13°, 26° with orb
|
|
525
|
+
isCritical = [0, 13, 26].some(criticalDegree => {
|
|
526
|
+
return Math.abs(degreeInSign - criticalDegree) <= orb;
|
|
527
|
+
});
|
|
528
|
+
criticalType = 'Cardinal (0°, 13°, 26°)';
|
|
529
|
+
} else if (signType === 'Fixed') {
|
|
530
|
+
// Fixed: ranges 8-9° and 21-22°
|
|
531
|
+
isCritical = (degreeInSign >= 8 && degreeInSign <= 9) || (degreeInSign >= 21 && degreeInSign <= 22);
|
|
532
|
+
criticalType = 'Fixed (8-9°, 21-22°)';
|
|
533
|
+
} else if (signType === 'Mutable') {
|
|
534
|
+
// Mutable: exact degrees 4°, 17° with orb
|
|
535
|
+
isCritical = [4, 17].some(criticalDegree => {
|
|
536
|
+
return Math.abs(degreeInSign - criticalDegree) <= orb;
|
|
537
|
+
});
|
|
538
|
+
criticalType = 'Mutable (4°, 17°)';
|
|
539
|
+
}
|
|
540
|
+
|
|
511
541
|
if (isCritical) {
|
|
542
|
+
// Simple interpretation based on modality challenges
|
|
543
|
+
let interpretation = '';
|
|
544
|
+
|
|
545
|
+
if (criticalType === 'Cardinal (0°, 13°, 26°)') {
|
|
546
|
+
interpretation = 'Tendency to over-express. May push too hard or initiate prematurely.';
|
|
547
|
+
} else if (criticalType === 'Fixed (8-9°, 21-22°)') {
|
|
548
|
+
interpretation = 'Tendency to under-express. May resist change or hold on too tightly.';
|
|
549
|
+
} else if (criticalType === 'Mutable (4°, 17°)') {
|
|
550
|
+
interpretation = 'Path of ambivalence. May struggle with indecision or adapt excessively.';
|
|
551
|
+
} else if (criticalType === 'Anaretic (29°)') {
|
|
552
|
+
interpretation = 'Poised for change. At the end of a cycle, facing transition.';
|
|
553
|
+
}
|
|
554
|
+
|
|
512
555
|
criticalPlanets.push({
|
|
513
556
|
name,
|
|
514
557
|
sign,
|
|
515
558
|
degree: degreeInSign.toFixed(2),
|
|
516
559
|
isCritical: true,
|
|
517
|
-
criticalType:
|
|
560
|
+
criticalType: criticalType,
|
|
561
|
+
interpretation: interpretation
|
|
518
562
|
});
|
|
519
563
|
}
|
|
520
564
|
}
|
|
@@ -642,7 +686,7 @@ function showPlanetAspects(planetName, dateComponents, useBirthData = false, use
|
|
|
642
686
|
|
|
643
687
|
console.log(`Aspects for ${planetLabel}:`);
|
|
644
688
|
console.log('================================================================================');
|
|
645
|
-
console.log('| Aspect | Planet | Orb |
|
|
689
|
+
console.log('| Aspect | Planet | Orb |');
|
|
646
690
|
console.log('================================================================================');
|
|
647
691
|
|
|
648
692
|
if (aspects.length === 0) {
|
|
@@ -654,14 +698,8 @@ function showPlanetAspects(planetName, dateComponents, useBirthData = false, use
|
|
|
654
698
|
const planetFormatted = planetNameFormatted.padEnd(8, ' ');
|
|
655
699
|
const orb = aspect.orb.padEnd(4, ' ');
|
|
656
700
|
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
if (exactDateTime) {
|
|
660
|
-
const dateTimeStr = `${String(exactDateTime.day).padStart(2, '0')}.${String(exactDateTime.month).padStart(2, '0')}.${exactDateTime.year} ${String(exactDateTime.hour).padStart(2, '0')}:${String(exactDateTime.minute).padStart(2, '0')}`;
|
|
661
|
-
console.log(`| ${aspectName} | ${planetFormatted} | ${orb}° | ${dateTimeStr} |`);
|
|
662
|
-
} else {
|
|
663
|
-
console.log(`| ${aspectName} | ${planetFormatted} | ${orb}° | Calculation failed |`);
|
|
664
|
-
}
|
|
701
|
+
// Exact time calculation removed
|
|
702
|
+
console.log(`| ${aspectName} | ${planetFormatted} | ${orb}° |`);
|
|
665
703
|
});
|
|
666
704
|
}
|
|
667
705
|
|
|
@@ -682,7 +720,7 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
|
|
|
682
720
|
|
|
683
721
|
console.log(`Aspects between ${planetLabels}:`);
|
|
684
722
|
console.log('================================================================================');
|
|
685
|
-
console.log('| Planet 1 | Planet 2 | Aspect | Orb |
|
|
723
|
+
console.log('| Planet 1 | Planet 2 | Aspect | Orb |');
|
|
686
724
|
console.log('================================================================================');
|
|
687
725
|
|
|
688
726
|
if (aspects.length === 0) {
|
|
@@ -696,14 +734,8 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
|
|
|
696
734
|
const aspectName = aspect.type.padEnd(11, ' ');
|
|
697
735
|
const orb = aspect.orb.padEnd(4, ' ');
|
|
698
736
|
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
if (exactDateTime) {
|
|
702
|
-
const dateTimeStr = `${String(exactDateTime.day).padStart(2, '0')}.${String(exactDateTime.month).padStart(2, '0')}.${exactDateTime.year} ${String(exactDateTime.hour).padStart(2, '0')}:${String(exactDateTime.minute).padStart(2, '0')}`;
|
|
703
|
-
console.log(`| ${planet1Padded} | ${planet2Padded} | ${aspectName} | ${orb}° | ${dateTimeStr} |`);
|
|
704
|
-
} else {
|
|
705
|
-
console.log(`| ${planet1Padded} | ${planet2Padded} | ${aspectName} | ${orb}° | Calculation failed |`);
|
|
706
|
-
}
|
|
737
|
+
// Exact time calculation removed
|
|
738
|
+
console.log(`| ${planet1Padded} | ${planet2Padded} | ${aspectName} | ${orb}° |`);
|
|
707
739
|
});
|
|
708
740
|
}
|
|
709
741
|
|
|
@@ -716,107 +748,7 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
|
|
|
716
748
|
}
|
|
717
749
|
}
|
|
718
750
|
|
|
719
|
-
// Helper function to calculate exact date and time for an exact aspect
|
|
720
|
-
function findExactAspectTime(planet1, planet2, aspectType, startDate) {
|
|
721
|
-
const targetAngle = getAspectAngle(aspectType);
|
|
722
|
-
|
|
723
|
-
if (targetAngle === null) {
|
|
724
|
-
return null;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// We search in a reasonable time window (90 days in the future)
|
|
728
|
-
// to find the next exact aspect
|
|
729
|
-
// Especially important for slow planets like Saturn, Uranus, Neptune, Pluto
|
|
730
|
-
let bestMatch = null;
|
|
731
|
-
let bestDistance = Infinity;
|
|
732
|
-
|
|
733
|
-
// Search only in the future (0-90 days after start date)
|
|
734
|
-
const searchWindow = 90; // days
|
|
735
|
-
|
|
736
|
-
// We search in smaller steps (every 2 hours) to get more precise results
|
|
737
|
-
const stepsPerDay = 12; // 2-hour steps
|
|
738
|
-
|
|
739
|
-
for (let daysOffset = 0; daysOffset <= searchWindow; daysOffset++) {
|
|
740
|
-
for (let hourOffset = 0; hourOffset < 24; hourOffset++) { // Every hour
|
|
741
|
-
for (let minuteOffset = 0; minuteOffset < 60; minuteOffset += 5) { // Every 5 minutes
|
|
742
|
-
const testDate = addDays(startDate, daysOffset);
|
|
743
|
-
testDate.hour = hourOffset;
|
|
744
|
-
testDate.minute = minuteOffset;
|
|
745
|
-
|
|
746
|
-
// Calculate positions of both planets
|
|
747
|
-
const planet1Data = getAstrologicalData(planet1, testDate);
|
|
748
|
-
const planet2Data = getAstrologicalData(planet2, testDate);
|
|
749
|
-
|
|
750
|
-
// Calculate angle between planets
|
|
751
|
-
const angleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
|
|
752
|
-
const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
|
|
753
|
-
const distanceToTarget = Math.abs(normalizedAngle - targetAngle);
|
|
754
|
-
|
|
755
|
-
// Store the best result
|
|
756
|
-
if (distanceToTarget < bestDistance) {
|
|
757
|
-
bestDistance = distanceToTarget;
|
|
758
|
-
bestMatch = {...testDate};
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// If we found an aspect close enough to the target, return it
|
|
765
|
-
// Stricter tolerance of 0.5° for better accuracy
|
|
766
|
-
if (bestMatch && bestDistance <= 0.5) { // Tolerance of 0.5°
|
|
767
|
-
return bestMatch;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
return null;
|
|
771
|
-
}
|
|
772
751
|
|
|
773
|
-
// Function to find the last exact aspect in the past (for separative phases)
|
|
774
|
-
function findLastExactAspectTime(planet1, planet2, aspectType, startDate) {
|
|
775
|
-
const targetAngle = getAspectAngle(aspectType);
|
|
776
|
-
|
|
777
|
-
if (targetAngle === null) {
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// Search in the past (up to 180 days before start date)
|
|
782
|
-
let bestMatch = null;
|
|
783
|
-
let bestDistance = Infinity;
|
|
784
|
-
|
|
785
|
-
// Search in a time window of 180 days before start date
|
|
786
|
-
const searchWindow = 180; // days
|
|
787
|
-
|
|
788
|
-
for (let daysOffset = -searchWindow; daysOffset <= 0; daysOffset++) {
|
|
789
|
-
for (let hourOffset = 0; hourOffset < 24; hourOffset++) { // Every hour
|
|
790
|
-
for (let minuteOffset = 0; minuteOffset < 60; minuteOffset += 5) { // Every 5 minutes
|
|
791
|
-
const testDate = addDays(startDate, daysOffset);
|
|
792
|
-
testDate.hour = hourOffset;
|
|
793
|
-
testDate.minute = minuteOffset;
|
|
794
|
-
|
|
795
|
-
// Calculate positions of both planets
|
|
796
|
-
const planet1Data = getAstrologicalData(planet1, testDate);
|
|
797
|
-
const planet2Data = getAstrologicalData(planet2, testDate);
|
|
798
|
-
|
|
799
|
-
// Calculate angle between planets
|
|
800
|
-
const angleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
|
|
801
|
-
const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
|
|
802
|
-
const distanceToTarget = Math.abs(normalizedAngle - targetAngle);
|
|
803
|
-
|
|
804
|
-
// Store the best result
|
|
805
|
-
if (distanceToTarget < bestDistance) {
|
|
806
|
-
bestDistance = distanceToTarget;
|
|
807
|
-
bestMatch = {...testDate};
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// If we found an aspect close enough to the target, return it
|
|
814
|
-
if (bestMatch && bestDistance <= 0.5) { // Tolerance of 0.5°
|
|
815
|
-
return bestMatch;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
return null;
|
|
819
|
-
}
|
|
820
752
|
|
|
821
753
|
// Function to calculate all active aspects between all planets
|
|
822
754
|
function getAllActiveAspects(dateComponents) {
|
|
@@ -1733,7 +1665,7 @@ function showAllActiveAspects(dateComponents, useBirthData = false) {
|
|
|
1733
1665
|
|
|
1734
1666
|
console.log('Active Aspects (all planets) - Classified by Precision:');
|
|
1735
1667
|
console.log('═══════════════════════════════════════════════════════════════════════════');
|
|
1736
|
-
console.log('║ Planet 1 │ Planet 2 │ Orb │ Status
|
|
1668
|
+
console.log('║ Planet 1 │ Planet 2 │ Orb │ Status ║');
|
|
1737
1669
|
console.log('═══════════════════════════════════════════════════════════════════════════');
|
|
1738
1670
|
|
|
1739
1671
|
if (sortedAspects.length === 0) {
|
|
@@ -1784,14 +1716,10 @@ function showAllActiveAspects(dateComponents, useBirthData = false) {
|
|
|
1784
1716
|
);
|
|
1785
1717
|
const statusPadded = phase.padEnd(11, ' ');
|
|
1786
1718
|
|
|
1787
|
-
//
|
|
1788
|
-
const
|
|
1789
|
-
let dateTimeStr = '-';
|
|
1790
|
-
if (exactDateTime) {
|
|
1791
|
-
dateTimeStr = `${String(exactDateTime.day).padStart(2, '0')}.${String(exactDateTime.month).padStart(2, '0')}.${exactDateTime.year} ${String(exactDateTime.hour).padStart(2, '0')}:${String(exactDateTime.minute).padStart(2, '0')}`;
|
|
1792
|
-
}
|
|
1719
|
+
// Exact time calculation removed
|
|
1720
|
+
const dateTimeStr = '-';
|
|
1793
1721
|
|
|
1794
|
-
console.log(`║ ${planet1Padded} │ ${planet2Padded} │ ${orb}° │ ${statusPadded}
|
|
1722
|
+
console.log(`║ ${planet1Padded} │ ${planet2Padded} │ ${orb}° │ ${statusPadded} ║`);
|
|
1795
1723
|
});
|
|
1796
1724
|
}
|
|
1797
1725
|
});
|
|
@@ -1904,16 +1832,25 @@ function analyzeSignDistributionSignificance(signDistribution) {
|
|
|
1904
1832
|
function parseFilterCriteria(filterString) {
|
|
1905
1833
|
if (!filterString) return null;
|
|
1906
1834
|
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1835
|
+
// Check if multiple filters are provided (comma-separated)
|
|
1836
|
+
const filterConditions = filterString.split(',').map(cond => cond.trim());
|
|
1837
|
+
|
|
1838
|
+
const criteria = [];
|
|
1839
|
+
|
|
1840
|
+
for (const condition of filterConditions) {
|
|
1841
|
+
const parts = condition.split(':');
|
|
1842
|
+
if (parts.length !== 2) {
|
|
1843
|
+
console.warn(`Invalid filter format. Expected "column:value" but got "${condition}"`);
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
criteria.push({
|
|
1848
|
+
column: parts[0].trim(),
|
|
1849
|
+
value: parts[1].trim()
|
|
1850
|
+
});
|
|
1911
1851
|
}
|
|
1912
1852
|
|
|
1913
|
-
return
|
|
1914
|
-
column: parts[0].trim(),
|
|
1915
|
-
value: parts[1].trim()
|
|
1916
|
-
};
|
|
1853
|
+
return criteria.length === 1 ? criteria[0] : criteria;
|
|
1917
1854
|
}
|
|
1918
1855
|
|
|
1919
1856
|
// Helper function to apply filter to records
|
|
@@ -1926,10 +1863,21 @@ function applyFilter(records, filterCriteria) {
|
|
|
1926
1863
|
|
|
1927
1864
|
if (!parsedCriteria) return records;
|
|
1928
1865
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
return
|
|
1932
|
-
|
|
1866
|
+
// Handle both single criteria and multiple criteria
|
|
1867
|
+
if (Array.isArray(parsedCriteria)) {
|
|
1868
|
+
return records.filter(record => {
|
|
1869
|
+
return parsedCriteria.every(criterion => {
|
|
1870
|
+
const recordValue = record[criterion.column];
|
|
1871
|
+
return recordValue && recordValue.toString() === criterion.value;
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
} else {
|
|
1875
|
+
// Single criteria (backward compatibility)
|
|
1876
|
+
return records.filter(record => {
|
|
1877
|
+
const recordValue = record[parsedCriteria.column];
|
|
1878
|
+
return recordValue && recordValue.toString() === parsedCriteria.value;
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1933
1881
|
}
|
|
1934
1882
|
|
|
1935
1883
|
function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'koch', analyzeAspects = false, partnerPlanet = null, filterCriteria = null) {
|
|
@@ -1939,6 +1887,7 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
1939
1887
|
|
|
1940
1888
|
// Helper function to process results
|
|
1941
1889
|
function processResults() {
|
|
1890
|
+
|
|
1942
1891
|
// Calculate house distribution
|
|
1943
1892
|
const houseCounts = {};
|
|
1944
1893
|
results.forEach(result => {
|
|
@@ -2004,15 +1953,33 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2004
1953
|
|
|
2005
1954
|
// Process each row
|
|
2006
1955
|
for (const data of filteredRecords) {
|
|
2007
|
-
// Look for a column with
|
|
2008
|
-
const datetimeColumns =
|
|
1956
|
+
// Look for a column with datetime values, prioritizing specific date formats
|
|
1957
|
+
const datetimeColumns = [];
|
|
1958
|
+
|
|
1959
|
+
// First pass: look for YYYY-MM-DD format (most specific)
|
|
1960
|
+
const yyyyMmDdColumns = Object.keys(data).filter(key => {
|
|
2009
1961
|
const value = data[key];
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
1962
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
// Second pass: look for Unix timestamps (10 or 13 digits)
|
|
1966
|
+
const unixTimestampColumns = Object.keys(data).filter(key => {
|
|
1967
|
+
const value = data[key];
|
|
1968
|
+
return /^\d{10,13}$/.test(value);
|
|
1969
|
+
});
|
|
1970
|
+
|
|
1971
|
+
// Third pass: look for ISO-8601 dates (least specific, as it can match many formats)
|
|
1972
|
+
const isoDateColumns = Object.keys(data).filter(key => {
|
|
1973
|
+
const value = data[key];
|
|
1974
|
+
// Only consider it an ISO date if it's not already matched by more specific patterns
|
|
1975
|
+
if (yyyyMmDdColumns.includes(key) || unixTimestampColumns.includes(key)) {
|
|
1976
|
+
return false;
|
|
1977
|
+
}
|
|
1978
|
+
return moment(value, moment.ISO_8601, true).isValid();
|
|
2015
1979
|
});
|
|
1980
|
+
|
|
1981
|
+
// Prioritize columns: YYYY-MM-DD first, then Unix timestamps, then ISO dates
|
|
1982
|
+
datetimeColumns.push(...yyyyMmDdColumns, ...unixTimestampColumns, ...isoDateColumns);
|
|
2016
1983
|
|
|
2017
1984
|
if (datetimeColumns.length > 0) {
|
|
2018
1985
|
const datetimeValue = data[datetimeColumns[0]];
|
|
@@ -2023,6 +1990,10 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2023
1990
|
// Convert Unix-Timestamp to milliseconds (if 10 digits, multiply by 1000)
|
|
2024
1991
|
const timestamp = datetimeValue.length === 10 ? parseInt(datetimeValue) * 1000 : parseInt(datetimeValue);
|
|
2025
1992
|
datetime = moment(timestamp);
|
|
1993
|
+
} else if (/^\d{4}-\d{2}-\d{2}$/.test(datetimeValue)) {
|
|
1994
|
+
// Handle as YYYY-MM-DD date format
|
|
1995
|
+
// Parse as date only, set time to 12:00 (noon) as default
|
|
1996
|
+
datetime = moment(datetimeValue + 'T12:00:00');
|
|
2026
1997
|
} else {
|
|
2027
1998
|
// Handle as ISO-8601 date
|
|
2028
1999
|
datetime = moment(datetimeValue);
|
|
@@ -2121,8 +2092,10 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2121
2092
|
}
|
|
2122
2093
|
}
|
|
2123
2094
|
|
|
2124
|
-
// Check if all operations are completed
|
|
2125
|
-
|
|
2095
|
+
// Check if all operations are completed (only if no operations were started)
|
|
2096
|
+
if (pendingOperations === 0) {
|
|
2097
|
+
processResults();
|
|
2098
|
+
}
|
|
2126
2099
|
|
|
2127
2100
|
// Helper function to check completion
|
|
2128
2101
|
function checkCompletion() {
|
|
@@ -2139,139 +2112,193 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2139
2112
|
}
|
|
2140
2113
|
} else {
|
|
2141
2114
|
// Standard file processing for local files
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2115
|
+
try {
|
|
2116
|
+
// Read the file content first
|
|
2117
|
+
const csvData = fs.readFileSync(filePath, 'utf8');
|
|
2118
|
+
|
|
2119
|
+
// Try to auto-detect delimiter by checking first few lines
|
|
2120
|
+
let delimiter = ',';
|
|
2121
|
+
const firstLines = csvData.split('\n').slice(0, 5).join('\n');
|
|
2122
|
+
const commaCount = (firstLines.match(/,/g) || []).length;
|
|
2123
|
+
const semicolonCount = (firstLines.match(/;/g) || []).length;
|
|
2124
|
+
|
|
2125
|
+
// Use semicolon if it appears more frequently than comma
|
|
2126
|
+
if (semicolonCount > commaCount) {
|
|
2127
|
+
delimiter = ';';
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// Parse the CSV data with detected delimiter
|
|
2131
|
+
const records = parse(csvData, {
|
|
2132
|
+
columns: true,
|
|
2133
|
+
skip_empty_lines: true,
|
|
2134
|
+
delimiter: delimiter
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
// Process each record
|
|
2138
|
+
for (const data of records) {
|
|
2139
|
+
// Apply filter if specified
|
|
2140
|
+
if (filterCriteria) {
|
|
2141
|
+
const parsedCriteria = typeof filterCriteria === 'string'
|
|
2142
|
+
? parseFilterCriteria(filterCriteria)
|
|
2149
2143
|
: filterCriteria;
|
|
2144
|
+
|
|
2145
|
+
if (parsedCriteria) {
|
|
2146
|
+
let shouldSkip = false;
|
|
2150
2147
|
|
|
2151
|
-
if (parsedCriteria) {
|
|
2148
|
+
if (Array.isArray(parsedCriteria)) {
|
|
2149
|
+
// Multiple criteria - all must match
|
|
2150
|
+
shouldSkip = !parsedCriteria.every(criterion => {
|
|
2151
|
+
const recordValue = data[criterion.column];
|
|
2152
|
+
return recordValue && recordValue.toString() === criterion.value;
|
|
2153
|
+
});
|
|
2154
|
+
} else {
|
|
2155
|
+
// Single criteria
|
|
2152
2156
|
const recordValue = data[parsedCriteria.column];
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2157
|
+
shouldSkip = !recordValue || recordValue.toString() !== parsedCriteria.value;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
if (shouldSkip) {
|
|
2161
|
+
continue; // Skip this record
|
|
2156
2162
|
}
|
|
2157
2163
|
}
|
|
2164
|
+
}
|
|
2158
2165
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
});
|
|
2166
|
+
// Look for a column with ISO-Datetime values, YYYY-MM-DD dates, or Unix-Timestamps
|
|
2167
|
+
const datetimeColumns = Object.keys(data).filter(key => {
|
|
2168
|
+
const value = data[key];
|
|
2169
|
+
// Check for ISO-8601 date
|
|
2170
|
+
const isISO = moment(value, moment.ISO_8601, true).isValid();
|
|
2171
|
+
// Check for YYYY-MM-DD date format
|
|
2172
|
+
const isYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
2173
|
+
// Check for Unix-Timestamp (number with 10 or 13 digits)
|
|
2174
|
+
const isUnixTimestamp = /^\d{10,13}$/.test(value);
|
|
2175
|
+
return isISO || isYYYYMMDD || isUnixTimestamp;
|
|
2176
|
+
});
|
|
2168
2177
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2178
|
+
if (datetimeColumns.length > 0) {
|
|
2179
|
+
const datetimeValue = data[datetimeColumns[0]];
|
|
2180
|
+
let datetime;
|
|
2172
2181
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
+
// Check if it's a Unix-Timestamp
|
|
2183
|
+
if (/^\d{10,13}$/.test(datetimeValue)) {
|
|
2184
|
+
// Convert Unix-Timestamp to milliseconds (if 10 digits, multiply by 1000)
|
|
2185
|
+
const timestamp = datetimeValue.length === 10 ? parseInt(datetimeValue) * 1000 : parseInt(datetimeValue);
|
|
2186
|
+
datetime = moment(timestamp);
|
|
2187
|
+
} else if (/^\d{4}-\d{2}-\d{2}$/.test(datetimeValue)) {
|
|
2188
|
+
// Handle as YYYY-MM-DD date format
|
|
2189
|
+
// Parse as date only, set time to 12:00 (noon) as default
|
|
2190
|
+
datetime = moment(datetimeValue + 'T12:00:00');
|
|
2191
|
+
} else {
|
|
2192
|
+
// Handle as ISO-8601 date
|
|
2193
|
+
datetime = moment(datetimeValue);
|
|
2194
|
+
}
|
|
2182
2195
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2196
|
+
// Convert the date to the format needed for astrological calculations
|
|
2197
|
+
const dateComponents = {
|
|
2198
|
+
year: datetime.year(),
|
|
2199
|
+
month: datetime.month() + 1, // moment months are 0-based
|
|
2200
|
+
day: datetime.date(),
|
|
2201
|
+
hour: datetime.hour(),
|
|
2202
|
+
minute: datetime.minute()
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
// Calculate the astrological data for the specified planet
|
|
2206
|
+
let astroData;
|
|
2207
|
+
try {
|
|
2208
|
+
astroData = getAstrologicalData(planetName, dateComponents);
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
// If error, skip this record (e.g., if date is out of range)
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2200
2213
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2214
|
+
// Increase the counter for pending operations
|
|
2215
|
+
pendingOperations++;
|
|
2216
|
+
|
|
2217
|
+
// Calculate the houses
|
|
2218
|
+
const julianDay = calculateJulianDayUTC(dateComponents, -datetime.utcOffset());
|
|
2219
|
+
calculateHouses(julianDay, getHouseSystemCode(houseSystem), false)
|
|
2220
|
+
.then(houses => {
|
|
2221
|
+
const planetLongitude = parseFloat(astroData.degreeInSign) + (signs.indexOf(astroData.sign) * 30);
|
|
2222
|
+
const house = getPlanetHouse(planetLongitude, houses.house);
|
|
2223
|
+
|
|
2224
|
+
// Berechne Aspekte, falls angefordert
|
|
2225
|
+
let aspects = [];
|
|
2226
|
+
if (analyzeAspects) {
|
|
2227
|
+
try {
|
|
2228
|
+
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2229
|
+
|
|
2230
|
+
// Filter nach Partner-Planet, falls angegeben
|
|
2231
|
+
if (partnerPlanet) {
|
|
2232
|
+
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2220
2233
|
}
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
// Ignoriere Fehler bei der Aspektberechnung für einzelne Datensätze
|
|
2221
2236
|
}
|
|
2237
|
+
}
|
|
2222
2238
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2239
|
+
const result = {
|
|
2240
|
+
datetime: datetimeValue,
|
|
2241
|
+
planet: planetName,
|
|
2242
|
+
sign: astroData.sign,
|
|
2243
|
+
degreeInSign: astroData.degreeInSign,
|
|
2244
|
+
house: house
|
|
2245
|
+
};
|
|
2230
2246
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2247
|
+
if (analyzeAspects) {
|
|
2248
|
+
result.aspects = aspects;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
results.push(result);
|
|
2252
|
+
checkCompletion();
|
|
2253
|
+
})
|
|
2254
|
+
.catch(error => {
|
|
2255
|
+
console.error('Fehler bei der Hausberechnung:', error);
|
|
2256
|
+
|
|
2257
|
+
// Berechne Aspekte auch bei Hausberechnungsfehler, falls angefordert
|
|
2258
|
+
let aspects = [];
|
|
2259
|
+
if (analyzeAspects) {
|
|
2260
|
+
try {
|
|
2261
|
+
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2234
2262
|
|
|
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);
|
|
2263
|
+
// Filter nach Partner-Planet, falls angegeben
|
|
2264
|
+
if (partnerPlanet) {
|
|
2265
|
+
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2252
2266
|
}
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
console.error('Fehler bei der Aspektberechnung:', error);
|
|
2253
2269
|
}
|
|
2270
|
+
}
|
|
2254
2271
|
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
});
|
|
2272
|
+
results.push({
|
|
2273
|
+
datetime: datetimeValue,
|
|
2274
|
+
planet: planetName,
|
|
2275
|
+
sign: astroData.sign,
|
|
2276
|
+
degreeInSign: astroData.degreeInSign,
|
|
2277
|
+
house: 'N/A',
|
|
2278
|
+
...(analyzeAspects ? {aspects} : {})
|
|
2263
2279
|
});
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2280
|
+
checkCompletion();
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// Helper function to check completion
|
|
2286
|
+
function checkCompletion() {
|
|
2287
|
+
pendingOperations--;
|
|
2288
|
+
if (pendingOperations === 0) {
|
|
2268
2289
|
processResults();
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// Check if all operations are completed (only if no operations were started)
|
|
2294
|
+
if (pendingOperations === 0) {
|
|
2295
|
+
processResults();
|
|
2296
|
+
}
|
|
2297
|
+
} catch (error) {
|
|
2298
|
+
console.error('Fehler beim Lesen der CSV-Datei:', error);
|
|
2299
|
+
reject(error);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2275
2302
|
})
|
|
2276
2303
|
}
|
|
2277
2304
|
|
|
@@ -2812,8 +2839,6 @@ module.exports = {
|
|
|
2812
2839
|
getPastAspects,
|
|
2813
2840
|
getAspectAngle,
|
|
2814
2841
|
getFutureAspects,
|
|
2815
|
-
findExactAspectTime,
|
|
2816
|
-
findLastExactAspectTime,
|
|
2817
2842
|
analyzeCSVWithDatetime,
|
|
2818
2843
|
detectAspectFigures,
|
|
2819
2844
|
showAspectFigures,
|