File size: 9,249 Bytes
2697782
824f1f1
 
 
4f2b7a8
 
824f1f1
 
4f2b7a8
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0169d33
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47e4146
db54019
 
 
d083c8f
0169d33
47e4146
0169d33
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db54019
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
 
47188a8
824f1f1
4c8413a
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
47188a8
824f1f1
 
 
 
 
 
 
 
 
 
 
14146bb
824f1f1
 
 
 
 
 
 
 
47188a8
 
 
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47188a8
47e4146
d083c8f
824f1f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4f2b7a8
824f1f1
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
from datetime import datetime, timedelta
import swisseph as swe
from typing import Dict, Any, List, Tuple
import math

class ChartCalculator:
    """Enhanced astrological calculations with modern planets and aspects"""
    
    def __init__(self):
        # Initialize Swiss Ephemeris
        swe.set_ephe_path()
        
        self.planets = {
            'Sun': swe.SUN,
            'Moon': swe.MOON,
            'Mercury': swe.MERCURY,
            'Venus': swe.VENUS,
            'Mars': swe.MARS,
            'Jupiter': swe.JUPITER,
            'Saturn': swe.SATURN,
            'Uranus': swe.URANUS,
            'Neptune': swe.NEPTUNE,
            'Pluto': swe.PLUTO,
            'Chiron': swe.CHIRON,
            'North Node': swe.MEAN_NODE
        }
        
        self.zodiac_signs = [
            'Aries', 'Taurus', 'Gemini', 'Cancer',
            'Leo', 'Virgo', 'Libra', 'Scorpio',
            'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
        ]
        
        self.elements = {
            'Fire': ['Aries', 'Leo', 'Sagittarius'],
            'Earth': ['Taurus', 'Virgo', 'Capricorn'],
            'Air': ['Gemini', 'Libra', 'Aquarius'],
            'Water': ['Cancer', 'Scorpio', 'Pisces']
        }
        
        self.qualities = {
            'Cardinal': ['Aries', 'Cancer', 'Libra', 'Capricorn'],
            'Fixed': ['Taurus', 'Leo', 'Scorpio', 'Aquarius'],
            'Mutable': ['Gemini', 'Virgo', 'Sagittarius', 'Pisces']
        }
        
        self.aspect_types = {
            0: {'name': 'Conjunction', 'orb': 8, 'nature': 'Neutral'},
            60: {'name': 'Sextile', 'orb': 6, 'nature': 'Harmonious'},
            90: {'name': 'Square', 'orb': 8, 'nature': 'Challenging'},
            120: {'name': 'Trine', 'orb': 8, 'nature': 'Harmonious'},
            180: {'name': 'Opposition', 'orb': 8, 'nature': 'Challenging'}
        }

    def _normalize_longitude(self, lon: float) -> float:
        """Normalize longitude to 0-360 range"""
        return lon % 360

    def _get_zodiac_sign(self, longitude: float) -> str:
        """Get zodiac sign from longitude"""
        sign_num = int(self._normalize_longitude(longitude) / 30)
        return self.zodiac_signs[sign_num]

    def _get_sign_element(self, sign: str) -> str:
        """Get the element of a zodiac sign"""
        for element, signs in self.elements.items():
            if sign in signs:
                return element
        return "Unknown"

    def _get_sign_quality(self, sign: str) -> str:
        """Get the quality (modality) of a zodiac sign"""
        for quality, signs in self.qualities.items():
            if sign in signs:
                return quality
        return "Unknown"

    def calculate_birth_chart(self, birth_datetime: datetime, latitude: float, longitude: float) -> Dict[str, Any]:
        """Calculate comprehensive birth chart including modern planets"""
        try:
            # Convert to Julian day
            julian_day = swe.julday(
                birth_datetime.year,
                birth_datetime.month,
                birth_datetime.day,
                birth_datetime.hour + birth_datetime.minute/60.0
            )

            # Calculate houses (Placidus system)
            houses = swe.houses(julian_day, latitude, longitude)[0]
            ascendant = houses[0]
            midheaven = houses[9]

            # Calculate planetary positions
            positions = {}
            for planet_name, planet_id in self.planets.items():
                try:
                    result = swe.calc_ut(julian_day, planet_id)
                    lon = self._normalize_longitude(result[0])
                    sign = self._get_zodiac_sign(lon)
                    
                    positions[planet_name] = {
                        'longitude': lon,
                        'latitude': result[1],
                        'distance': result[2],
                        'speed': result[3],  # Negative means retrograde
                        'sign': sign,
                        'element': self._get_sign_element(sign),
                        'quality': self._get_sign_quality(sign),
                        'retrograde': result[3] < 0
                    }
                    
                    # Calculate house placement
                    for i in range(12):
                        house_start = self._normalize_longitude(houses[i])
                        house_end = self._normalize_longitude(houses[(i + 1) % 12])
                        
                        if (house_start <= lon < house_end) or \
                           (house_start > house_end and (lon >= house_start or lon < house_end)):
                            positions[planet_name]['house'] = i + 1
                            break
                
                except Exception as e:
                    positions[planet_name] = {'error': str(e)}

            # Calculate aspects
            aspects = self._calculate_aspects(positions)

            # Calculate element and modality balances
            element_balance = self._calculate_element_balance(positions)
            modality_balance = self._calculate_modality_balance(positions)

            return {
                'planets': positions,
                'houses': {
                    'ascendant': {
                        'longitude': ascendant,
                        'sign': self._get_zodiac_sign(ascendant)
                    },
                    'midheaven': {
                        'longitude': midheaven,
                        'sign': self._get_zodiac_sign(midheaven)
                    },
                    'cusps': [self._normalize_longitude(h) for h in houses]
                },
                'aspects': aspects,
                'patterns': {
                    'elements': element_balance,
                    'modalities': modality_balance
                }
            }

        except Exception as e:
            return {'error': f"Failed to calculate birth chart: {str(e)}"}

    def _calculate_aspects(self, positions: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Calculate planetary aspects"""
        aspects = []
        planet_list = list(positions.keys())
        
        for i in range(len(planet_list)):
            for j in range(i + 1, len(planet_list)):
                planet1 = planet_list[i]
                planet2 = planet_list[j]
                
                if 'error' in positions[planet1] or 'error' in positions[planet2]:
                    continue

                lon1 = positions[planet1]['longitude']
                lon2 = positions[planet2]['longitude']
                
                # Calculate angular separation
                diff = abs(lon1 - lon2)
                if diff > 180:
                    diff = 360 - diff

                # Check for aspects
                for angle, aspect_info in self.aspect_types.items():
                    orb = aspect_info['orb']
                    if abs(diff - angle) <= orb:
                        aspects.append({
                            'planet1': planet1,
                            'planet2': planet2,
                            'aspect': aspect_info['name'],
                            'orb': round(abs(diff - angle), 2),
                            'nature': aspect_info['nature'],
                            'applying': positions[planet1].get('speed', 0) > positions[planet2].get('speed', 0)
                        })

        return aspects

    def _calculate_element_balance(self, positions: Dict[str, Any]) -> Dict[str, int]:
        """Calculate the distribution of elements"""
        balance = {'Fire': 0, 'Earth': 0, 'Air': 0, 'Water': 0}
        for planet_data in positions.values():
            if 'element' in planet_data:
                balance[planet_data['element']] += 1
        return balance

    def _calculate_modality_balance(self, positions: Dict[str, Any]) -> Dict[str, int]:
        """Calculate the distribution of modalities"""
        balance = {'Cardinal': 0, 'Fixed': 0, 'Mutable': 0}
        for planet_data in positions.values():
            if 'quality' in planet_data:
                balance[planet_data['quality']] += 1
        return balance

    def get_chart_analysis(self, chart_data: Dict[str, Any]) -> Dict[str, Any]:
        """Generate a comprehensive chart analysis"""
        if 'error' in chart_data:
            return {'error': chart_data['error']}

        analysis = {
            'dominant_element': max(chart_data['patterns']['elements'].items(), key=lambda x: x[1])[0],
            'dominant_modality': max(chart_data['patterns']['modalities'].items(), key=lambda x: x[1])[0],
            'retrograde_planets': [
                planet for planet, data in chart_data['planets'].items()
                if data.get('retrograde', False)
            ],
            'aspect_patterns': {
                'harmonious': len([a for a in chart_data['aspects'] if a['nature'] == 'Harmonious']),
                'challenging': len([a for a in chart_data['aspects'] if a['nature'] == 'Challenging']),
                'neutral': len([a for a in chart_data['aspects'] if a['nature'] == 'Neutral'])
            }
        }
        
        return analysis