cryogenic22 commited on
Commit
824f1f1
·
verified ·
1 Parent(s): b341f9d

Update astro_core.py

Browse files
Files changed (1) hide show
  1. astro_core.py +202 -108
astro_core.py CHANGED
@@ -1,27 +1,56 @@
1
  from datetime import datetime, timedelta
2
- import zoneinfo
3
- from typing import Dict, Any
4
- from flatlib.datetime import Datetime
5
- from flatlib.geopos import GeoPos
6
- from flatlib.chart import Chart
7
- import matplotlib.pyplot as plt
8
- import numpy as np
9
-
10
- # Define constants
11
- ZODIAC_SIGNS = [
12
- 'Aries', 'Taurus', 'Gemini', 'Cancer',
13
- 'Leo', 'Virgo', 'Libra', 'Scorpio',
14
- 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
15
- ]
16
 
17
  class ChartCalculator:
18
- """Handles astrological calculations"""
19
-
20
  def __init__(self):
21
- self.planets = [
22
- 'Sun', 'Moon', 'Mercury', 'Venus', 'Mars',
23
- 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  def _normalize_longitude(self, lon: float) -> float:
27
  """Normalize longitude to 0-360 range"""
@@ -30,104 +59,169 @@ class ChartCalculator:
30
  def _get_zodiac_sign(self, longitude: float) -> str:
31
  """Get zodiac sign from longitude"""
32
  sign_num = int(self._normalize_longitude(longitude) / 30)
33
- return ZODIAC_SIGNS[sign_num]
34
-
35
- def calculate_birth_chart(self,
36
- birth_datetime: datetime,
37
- latitude: float,
38
- longitude: float,
39
- timezone: str) -> Dict[str, Any]:
40
- """
41
- Calculate birth chart positions
42
- Returns a dictionary of planetary positions and house cusps
43
- """
 
 
 
 
 
 
 
44
  try:
45
- tz = zoneinfo.ZoneInfo(timezone)
46
- birth_datetime = birth_datetime.replace(tzinfo=tz)
47
- utc_offset = int(birth_datetime.utcoffset().total_seconds() / 3600)
48
-
49
- adjusted_datetime = birth_datetime - timedelta(hours=utc_offset)
50
- date_str = adjusted_datetime.strftime('%Y/%m/%d')
51
- time_str = adjusted_datetime.strftime('%H:%M')
52
-
53
- date = Datetime(date_str, time_str, utc_offset)
54
- pos = GeoPos(latitude, longitude)
55
-
56
- chart = Chart(date, pos)
57
-
 
58
  positions = {}
59
- for planet in self.planets:
60
  try:
61
- obj = chart.get(planet)
62
- if obj:
63
- lon = self._normalize_longitude(obj.lon)
64
- positions[planet] = {
65
- 'sign': self._get_zodiac_sign(lon),
66
- 'degrees': lon
67
- }
68
- else:
69
- positions[planet] = {
70
- 'error': f"{planet} data unavailable"
71
- }
72
- except Exception as e:
73
- positions[planet] = {
74
- 'error': f"Error calculating {planet}: {str(e)}"
75
  }
76
-
77
- houses = {}
78
- for i in range(1, 13):
79
- try:
80
- house = chart.houses.get(i)
81
- if house:
82
- lon = self._normalize_longitude(house.lon)
83
- houses[f'House_{i}'] = {
84
- 'sign': self._get_zodiac_sign(lon),
85
- 'degrees': lon
86
- }
87
- else:
88
- houses[f'House_{i}'] = {
89
- 'error': f"House {i} data unavailable"
90
- }
91
  except Exception as e:
92
- houses[f'House_{i}'] = {
93
- 'error': f"Error calculating House {i}: {str(e)}"
94
- }
 
 
 
 
 
95
 
96
  return {
97
  'planets': positions,
98
- 'houses': houses
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
  except Exception as e:
102
- print(f"Unexpected error: {str(e)}")
103
- return {
104
- 'error': f"Failed to calculate birth chart: {str(e)}",
105
- 'planets': {},
106
- 'houses': {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
-
109
- def draw_chart(self, chart_data: Dict[str, Any]):
110
- """Generate a graphical view of the astrology chart"""
111
- fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={'projection': 'polar'})
112
- ax.set_theta_zero_location("S")
113
- ax.set_theta_direction(-1)
114
-
115
- zodiac_angles = np.linspace(0, 2 * np.pi, 13)
116
- for i, sign in enumerate(ZODIAC_SIGNS):
117
- angle = zodiac_angles[i]
118
- ax.text(angle, 1.05, sign, ha='center', va='center', fontsize=10)
119
-
120
- house_angles = np.linspace(0, 2 * np.pi, 13)
121
- for i in range(1, 13):
122
- angle = house_angles[i - 1]
123
- ax.text(angle, 0.8, f"H{i}", ha='center', va='center', fontsize=10)
124
-
125
- for planet, data in chart_data['planets'].items():
126
- if 'error' not in data:
127
- degrees = data['degrees']
128
- angle = np.deg2rad(degrees)
129
- ax.plot([angle], [0.5], marker='o', label=planet, markersize=10)
130
-
131
- ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1))
132
- plt.title("Astrology Chart", va='bottom', fontsize=15)
133
- plt.show()
 
1
  from datetime import datetime, timedelta
2
+ import swisseph as swe
3
+ from typing import Dict, Any, List, Tuple
4
+ import math
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  class ChartCalculator:
7
+ """Enhanced astrological calculations with modern planets and aspects"""
8
+
9
  def __init__(self):
10
+ # Initialize Swiss Ephemeris
11
+ swe.set_ephe_path()
12
+
13
+ self.planets = {
14
+ 'Sun': swe.SUN,
15
+ 'Moon': swe.MOON,
16
+ 'Mercury': swe.MERCURY,
17
+ 'Venus': swe.VENUS,
18
+ 'Mars': swe.MARS,
19
+ 'Jupiter': swe.JUPITER,
20
+ 'Saturn': swe.SATURN,
21
+ 'Uranus': swe.URANUS,
22
+ 'Neptune': swe.NEPTUNE,
23
+ 'Pluto': swe.PLUTO,
24
+ 'Chiron': swe.CHIRON,
25
+ 'North Node': swe.MEAN_NODE
26
+ }
27
+
28
+ self.zodiac_signs = [
29
+ 'Aries', 'Taurus', 'Gemini', 'Cancer',
30
+ 'Leo', 'Virgo', 'Libra', 'Scorpio',
31
+ 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
32
  ]
33
+
34
+ self.elements = {
35
+ 'Fire': ['Aries', 'Leo', 'Sagittarius'],
36
+ 'Earth': ['Taurus', 'Virgo', 'Capricorn'],
37
+ 'Air': ['Gemini', 'Libra', 'Aquarius'],
38
+ 'Water': ['Cancer', 'Scorpio', 'Pisces']
39
+ }
40
+
41
+ self.qualities = {
42
+ 'Cardinal': ['Aries', 'Cancer', 'Libra', 'Capricorn'],
43
+ 'Fixed': ['Taurus', 'Leo', 'Scorpio', 'Aquarius'],
44
+ 'Mutable': ['Gemini', 'Virgo', 'Sagittarius', 'Pisces']
45
+ }
46
+
47
+ self.aspect_types = {
48
+ 0: {'name': 'Conjunction', 'orb': 8, 'nature': 'Neutral'},
49
+ 60: {'name': 'Sextile', 'orb': 6, 'nature': 'Harmonious'},
50
+ 90: {'name': 'Square', 'orb': 8, 'nature': 'Challenging'},
51
+ 120: {'name': 'Trine', 'orb': 8, 'nature': 'Harmonious'},
52
+ 180: {'name': 'Opposition', 'orb': 8, 'nature': 'Challenging'}
53
+ }
54
 
55
  def _normalize_longitude(self, lon: float) -> float:
56
  """Normalize longitude to 0-360 range"""
 
59
  def _get_zodiac_sign(self, longitude: float) -> str:
60
  """Get zodiac sign from longitude"""
61
  sign_num = int(self._normalize_longitude(longitude) / 30)
62
+ return self.zodiac_signs[sign_num]
63
+
64
+ def _get_sign_element(self, sign: str) -> str:
65
+ """Get the element of a zodiac sign"""
66
+ for element, signs in self.elements.items():
67
+ if sign in signs:
68
+ return element
69
+ return "Unknown"
70
+
71
+ def _get_sign_quality(self, sign: str) -> str:
72
+ """Get the quality (modality) of a zodiac sign"""
73
+ for quality, signs in self.qualities.items():
74
+ if sign in signs:
75
+ return quality
76
+ return "Unknown"
77
+
78
+ def calculate_birth_chart(self, birth_datetime: datetime, latitude: float, longitude: float) -> Dict[str, Any]:
79
+ """Calculate comprehensive birth chart including modern planets"""
80
  try:
81
+ # Convert to Julian day
82
+ julian_day = swe.julday(
83
+ birth_datetime.year,
84
+ birth_datetime.month,
85
+ birth_datetime.day,
86
+ birth_datetime.hour + birth_datetime.minute/60.0
87
+ )
88
+
89
+ # Calculate houses (Placidus system)
90
+ houses = swe.houses(julian_day, latitude, longitude)[0]
91
+ ascendant = houses[0]
92
+ midheaven = houses[9]
93
+
94
+ # Calculate planetary positions
95
  positions = {}
96
+ for planet_name, planet_id in self.planets.items():
97
  try:
98
+ result = swe.calc_ut(julian_day, planet_id)
99
+ lon = self._normalize_longitude(result[0])
100
+ sign = self._get_zodiac_sign(lon)
101
+
102
+ positions[planet_name] = {
103
+ 'longitude': lon,
104
+ 'latitude': result[1],
105
+ 'distance': result[2],
106
+ 'speed': result[3], # Negative means retrograde
107
+ 'sign': sign,
108
+ 'element': self._get_sign_element(sign),
109
+ 'quality': self._get_sign_quality(sign),
110
+ 'retrograde': result[3] < 0
 
111
  }
112
+
113
+ # Calculate house placement
114
+ for i in range(12):
115
+ house_start = self._normalize_longitude(houses[i])
116
+ house_end = self._normalize_longitude(houses[(i + 1) % 12])
117
+
118
+ if (house_start <= lon < house_end) or \
119
+ (house_start > house_end and (lon >= house_start or lon < house_end)):
120
+ positions[planet_name]['house'] = i + 1
121
+ break
122
+
 
 
 
 
123
  except Exception as e:
124
+ positions[planet_name] = {'error': str(e)}
125
+
126
+ # Calculate aspects
127
+ aspects = self._calculate_aspects(positions)
128
+
129
+ # Calculate element and modality balances
130
+ element_balance = self._calculate_element_balance(positions)
131
+ modality_balance = self._calculate_modality_balance(positions)
132
 
133
  return {
134
  'planets': positions,
135
+ 'houses': {
136
+ 'ascendant': {
137
+ 'longitude': ascendant,
138
+ 'sign': self._get_zodiac_sign(ascendant)
139
+ },
140
+ 'midheaven': {
141
+ 'longitude': midheaven,
142
+ 'sign': self._get_zodiac_sign(midheaven)
143
+ },
144
+ 'cusps': [self._normalize_longitude(h) for h in houses]
145
+ },
146
+ 'aspects': aspects,
147
+ 'patterns': {
148
+ 'elements': element_balance,
149
+ 'modalities': modality_balance
150
+ }
151
  }
152
 
153
  except Exception as e:
154
+ return {'error': f"Failed to calculate birth chart: {str(e)}"}
155
+
156
+ def _calculate_aspects(self, positions: Dict[str, Any]) -> List[Dict[str, Any]]:
157
+ """Calculate planetary aspects"""
158
+ aspects = []
159
+ planet_list = list(positions.keys())
160
+
161
+ for i in range(len(planet_list)):
162
+ for j in range(i + 1, len(planet_list)):
163
+ planet1 = planet_list[i]
164
+ planet2 = planet_list[j]
165
+
166
+ if 'error' in positions[planet1] or 'error' in positions[planet2]:
167
+ continue
168
+
169
+ lon1 = positions[planet1]['longitude']
170
+ lon2 = positions[planet2]['longitude']
171
+
172
+ # Calculate angular separation
173
+ diff = abs(lon1 - lon2)
174
+ if diff > 180:
175
+ diff = 360 - diff
176
+
177
+ # Check for aspects
178
+ for angle, aspect_info in self.aspect_types.items():
179
+ orb = aspect_info['orb']
180
+ if abs(diff - angle) <= orb:
181
+ aspects.append({
182
+ 'planet1': planet1,
183
+ 'planet2': planet2,
184
+ 'aspect': aspect_info['name'],
185
+ 'orb': round(abs(diff - angle), 2),
186
+ 'nature': aspect_info['nature'],
187
+ 'applying': positions[planet1].get('speed', 0) > positions[planet2].get('speed', 0)
188
+ })
189
+
190
+ return aspects
191
+
192
+ def _calculate_element_balance(self, positions: Dict[str, Any]) -> Dict[str, int]:
193
+ """Calculate the distribution of elements"""
194
+ balance = {'Fire': 0, 'Earth': 0, 'Air': 0, 'Water': 0}
195
+ for planet_data in positions.values():
196
+ if 'element' in planet_data:
197
+ balance[planet_data['element']] += 1
198
+ return balance
199
+
200
+ def _calculate_modality_balance(self, positions: Dict[str, Any]) -> Dict[str, int]:
201
+ """Calculate the distribution of modalities"""
202
+ balance = {'Cardinal': 0, 'Fixed': 0, 'Mutable': 0}
203
+ for planet_data in positions.values():
204
+ if 'quality' in planet_data:
205
+ balance[planet_data['quality']] += 1
206
+ return balance
207
+
208
+ def get_chart_analysis(self, chart_data: Dict[str, Any]) -> Dict[str, Any]:
209
+ """Generate a comprehensive chart analysis"""
210
+ if 'error' in chart_data:
211
+ return {'error': chart_data['error']}
212
+
213
+ analysis = {
214
+ 'dominant_element': max(chart_data['patterns']['elements'].items(), key=lambda x: x[1])[0],
215
+ 'dominant_modality': max(chart_data['patterns']['modalities'].items(), key=lambda x: x[1])[0],
216
+ 'retrograde_planets': [
217
+ planet for planet, data in chart_data['planets'].items()
218
+ if data.get('retrograde', False)
219
+ ],
220
+ 'aspect_patterns': {
221
+ 'harmonious': len([a for a in chart_data['aspects'] if a['nature'] == 'Harmonious']),
222
+ 'challenging': len([a for a in chart_data['aspects'] if a['nature'] == 'Challenging']),
223
+ 'neutral': len([a for a in chart_data['aspects'] if a['nature'] == 'Neutral'])
224
  }
225
+ }
226
+
227
+ return analysis