kerykeion 5.0.0a4__py3-none-any.whl → 5.0.0a6__py3-none-any.whl

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.

Potentially problematic release.


This version of kerykeion might be problematic. Click here for more details.

@@ -186,6 +186,7 @@ class AstrologicalSubjectFactory:
186
186
  is_dst: Optional[bool] = None,
187
187
  altitude: Optional[float] = None,
188
188
  active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS,
189
+ calculate_lunar_phase: bool = True,
189
190
  *,
190
191
  seconds: int = 0,
191
192
 
@@ -205,53 +206,16 @@ class AstrologicalSubjectFactory:
205
206
  zodiac_type: Type of zodiac (Tropical or Sidereal)
206
207
  sidereal_mode: Mode for sidereal calculations
207
208
  houses_system_identifier: House system for calculations
208
- perspective_type: Perspective type for calculations
209
+ perspective_type: Perspective for calculations
209
210
  cache_expire_after_days: Cache duration for geonames data
210
211
  is_dst: Daylight saving time flag
211
212
  altitude: Location altitude for topocentric calculations
212
213
  active_points: Set of points to calculate (optimization)
214
+ calculate_lunar_phase: Whether to calculate lunar phase (requires Sun and Moon)
213
215
 
214
216
  Returns:
215
217
  An AstrologicalSubjectModel with calculated data
216
218
  """
217
- logging.debug("Starting Kerykeion calculation")
218
-
219
- if "Sun" not in active_points:
220
- logging.info("Automatically adding 'Sun' to active points")
221
- active_points.append("Sun")
222
-
223
- if "Moon" not in active_points:
224
- logging.info("Automatically adding 'Moon' to active points")
225
- active_points.append("Moon")
226
-
227
- if "Ascendant" not in active_points:
228
- logging.info("Automatically adding 'Ascendant' to active points")
229
- active_points.append("Ascendant")
230
-
231
- if "Medium_Coeli" not in active_points:
232
- logging.info("Automatically adding 'Medium_Coeli' to active points")
233
- active_points.append("Medium_Coeli")
234
-
235
- if "Mercury" not in active_points:
236
- logging.info("Automatically adding 'Mercury' to active points")
237
- active_points.append("Mercury")
238
-
239
- if "Venus" not in active_points:
240
- logging.info("Automatically adding 'Venus' to active points")
241
- active_points.append("Venus")
242
-
243
- if "Mars" not in active_points:
244
- logging.info("Automatically adding 'Mars' to active points")
245
- active_points.append("Mars")
246
-
247
- if "Jupiter" not in active_points:
248
- logging.info("Automatically adding 'Jupiter' to active points")
249
- active_points.append("Jupiter")
250
-
251
- if "Saturn" not in active_points:
252
- logging.info("Automatically adding 'Saturn' to active points")
253
- active_points.append("Saturn")
254
-
255
219
  # Create a calculation data container
256
220
  calc_data = {}
257
221
 
@@ -259,6 +223,11 @@ class AstrologicalSubjectFactory:
259
223
  calc_data["name"] = name
260
224
  calc_data["json_dir"] = str(Path.home())
261
225
 
226
+ # Create a deep copy of active points to avoid modifying the original list
227
+ active_points = list(active_points)
228
+
229
+ calc_data["active_points"] = active_points
230
+
262
231
  # Initialize configuration
263
232
  config = ChartConfiguration(
264
233
  zodiac_type=zodiac_type,
@@ -290,13 +259,13 @@ class AstrologicalSubjectFactory:
290
259
  )
291
260
 
292
261
  # If offline mode is requested but required data is missing, raise error
293
- if not online and (not tz_str or not lat or not lng):
262
+ if not online and (not tz_str or lat is None or lng is None):
294
263
  raise KerykeionException(
295
264
  "For offline mode, you must provide timezone (tz_str) and coordinates (lat, lng)"
296
265
  )
297
266
 
298
267
  # Fetch location data if needed
299
- if online and (not tz_str or not lat or not lng):
268
+ if online and (not tz_str or lat is None or lng is None):
300
269
  location.fetch_from_geonames(
301
270
  username=geonames_username or DEFAULT_GEONAMES_USERNAME,
302
271
  cache_expire_after_days=cache_expire_after_days
@@ -321,22 +290,22 @@ class AstrologicalSubjectFactory:
321
290
  calc_data["minute"] = minute
322
291
  calc_data["seconds"] = seconds
323
292
  calc_data["is_dst"] = is_dst
324
- calc_data["active_points"] = active_points
325
293
 
326
294
  # Calculate time conversions
327
295
  cls._calculate_time_conversions(calc_data, location)
328
296
 
329
297
  # Initialize Swiss Ephemeris and calculate houses and planets
330
298
  cls._setup_ephemeris(calc_data, config)
331
- cls._calculate_houses(calc_data, active_points)
332
- cls._calculate_planets(calc_data, active_points)
299
+ cls._calculate_houses(calc_data, calc_data["active_points"])
300
+ cls._calculate_planets(calc_data, calc_data["active_points"])
333
301
  cls._calculate_day_of_week(calc_data)
334
302
 
335
- # Calculate lunar phase
336
- calc_data["lunar_phase"] = calculate_moon_phase(
337
- calc_data["moon"].abs_pos,
338
- calc_data["sun"].abs_pos
339
- )
303
+ # Calculate lunar phase (optional - only if requested and Sun and Moon are available)
304
+ if calculate_lunar_phase and "moon" in calc_data and "sun" in calc_data:
305
+ calc_data["lunar_phase"] = calculate_moon_phase(
306
+ calc_data["moon"].abs_pos,
307
+ calc_data["sun"].abs_pos
308
+ )
340
309
 
341
310
  # Create and return the AstrologicalSubjectModel
342
311
  return AstrologicalSubjectModel(**calc_data)
@@ -349,7 +318,7 @@ class AstrologicalSubjectFactory:
349
318
  city: str = "Greenwich",
350
319
  nation: str = "GB",
351
320
  tz_str: str = "Etc/GMT",
352
- online: bool = False,
321
+ online: bool = True,
353
322
  lng: float = 0.0,
354
323
  lat: float = 51.5074,
355
324
  geonames_username: str = DEFAULT_GEONAMES_USERNAME,
@@ -358,7 +327,8 @@ class AstrologicalSubjectFactory:
358
327
  houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
359
328
  perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE,
360
329
  altitude: Optional[float] = None,
361
- active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS
330
+ active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS,
331
+ calculate_lunar_phase: bool = True
362
332
  ) -> AstrologicalSubjectModel:
363
333
  """
364
334
  Create an astrological subject from an ISO formatted UTC time.
@@ -378,6 +348,7 @@ class AstrologicalSubjectFactory:
378
348
  perspective_type: Perspective for calculations
379
349
  altitude: Location altitude
380
350
  active_points: Set of points to calculate
351
+ calculate_lunar_phase: Whether to calculate lunar phase
381
352
 
382
353
  Returns:
383
354
  AstrologicalSubjectModel instance
@@ -425,7 +396,8 @@ class AstrologicalSubjectFactory:
425
396
  houses_system_identifier=houses_system_identifier,
426
397
  perspective_type=perspective_type,
427
398
  altitude=altitude,
428
- active_points=active_points
399
+ active_points=active_points,
400
+ calculate_lunar_phase=calculate_lunar_phase
429
401
  )
430
402
 
431
403
  @classmethod
@@ -443,7 +415,8 @@ class AstrologicalSubjectFactory:
443
415
  sidereal_mode: Optional[SiderealMode] = None,
444
416
  houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
445
417
  perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE,
446
- active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS
418
+ active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS,
419
+ calculate_lunar_phase: bool = True
447
420
  ) -> AstrologicalSubjectModel:
448
421
  """
449
422
  Create an astrological subject for the current time.
@@ -461,6 +434,7 @@ class AstrologicalSubjectFactory:
461
434
  houses_system_identifier: House system
462
435
  perspective_type: Perspective for calculations
463
436
  active_points: Set of points to calculate
437
+ calculate_lunar_phase: Whether to calculate lunar phase
464
438
 
465
439
  Returns:
466
440
  AstrologicalSubjectModel for current time
@@ -486,7 +460,8 @@ class AstrologicalSubjectFactory:
486
460
  sidereal_mode=sidereal_mode,
487
461
  houses_system_identifier=houses_system_identifier,
488
462
  perspective_type=perspective_type,
489
- active_points=active_points
463
+ active_points=active_points,
464
+ calculate_lunar_phase=calculate_lunar_phase
490
465
  )
491
466
 
492
467
  @classmethod
@@ -627,6 +602,56 @@ class AstrologicalSubjectFactory:
627
602
  data["imum_coeli"].retrograde = False
628
603
  calculated_axial_cusps.append("Imum_Coeli")
629
604
 
605
+ @classmethod
606
+ def _calculate_single_planet(
607
+ cls,
608
+ data: Dict[str, Any],
609
+ planet_name: AstrologicalPoint,
610
+ planet_id: int,
611
+ julian_day: float,
612
+ iflag: int,
613
+ houses_degree_ut: List[float],
614
+ point_type: PointType,
615
+ calculated_planets: List[str],
616
+ active_points: List[AstrologicalPoint]
617
+ ) -> None:
618
+ """
619
+ Calculate a single planet's position with error handling and store it in the data dictionary.
620
+
621
+ Args:
622
+ data: The data dictionary to store the planet information
623
+ planet_name: Name of the planet
624
+ planet_id: Swiss Ephemeris planet ID
625
+ julian_day: Julian day for the calculation
626
+ iflag: Swiss Ephemeris calculation flags
627
+ houses_degree_ut: House degrees for house calculation
628
+ point_type: Type of point being calculated
629
+ calculated_planets: List to track calculated planets
630
+ active_points: List of active points to modify if error occurs
631
+ """
632
+ try:
633
+ # Calculate planet position using Swiss Ephemeris
634
+ planet_calc = swe.calc_ut(julian_day, planet_id, iflag)[0]
635
+
636
+ # Create Kerykeion point from degree
637
+ data[planet_name.lower()] = get_kerykeion_point_from_degree(
638
+ planet_calc[0], planet_name, point_type=point_type
639
+ )
640
+
641
+ # Calculate house position
642
+ data[planet_name.lower()].house = get_planet_house(planet_calc[0], houses_degree_ut)
643
+
644
+ # Determine if planet is retrograde
645
+ data[planet_name.lower()].retrograde = planet_calc[3] < 0
646
+
647
+ # Track calculated planet
648
+ calculated_planets.append(planet_name)
649
+
650
+ except Exception as e:
651
+ logging.error(f"Error calculating {planet_name}: {e}")
652
+ if planet_name in active_points:
653
+ active_points.remove(planet_name)
654
+
630
655
  @classmethod
631
656
  def _calculate_planets(cls, data: Dict[str, Any], active_points: List[AstrologicalPoint]) -> None:
632
657
  """Calculate planetary positions and related information"""
@@ -647,83 +672,43 @@ class AstrologicalSubjectFactory:
647
672
 
648
673
  # Calculate Sun
649
674
  if should_calculate("Sun"):
650
- sun_deg = swe.calc_ut(julian_day, 0, iflag)[0][0]
651
- data["sun"] = get_kerykeion_point_from_degree(sun_deg, "Sun", point_type=point_type)
652
- data["sun"].house = get_planet_house(sun_deg, houses_degree_ut)
653
- data["sun"].retrograde = swe.calc_ut(julian_day, 0, iflag)[0][3] < 0
654
- calculated_planets.append("Sun")
675
+ cls._calculate_single_planet(data, "Sun", 0, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
655
676
 
656
677
  # Calculate Moon
657
678
  if should_calculate("Moon"):
658
- moon_deg = swe.calc_ut(julian_day, 1, iflag)[0][0]
659
- data["moon"] = get_kerykeion_point_from_degree(moon_deg, "Moon", point_type=point_type)
660
- data["moon"].house = get_planet_house(moon_deg, houses_degree_ut)
661
- data["moon"].retrograde = swe.calc_ut(julian_day, 1, iflag)[0][3] < 0
662
- calculated_planets.append("Moon")
679
+ cls._calculate_single_planet(data, "Moon", 1, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
663
680
 
664
681
  # Calculate Mercury
665
682
  if should_calculate("Mercury"):
666
- mercury_deg = swe.calc_ut(julian_day, 2, iflag)[0][0]
667
- data["mercury"] = get_kerykeion_point_from_degree(mercury_deg, "Mercury", point_type=point_type)
668
- data["mercury"].house = get_planet_house(mercury_deg, houses_degree_ut)
669
- data["mercury"].retrograde = swe.calc_ut(julian_day, 2, iflag)[0][3] < 0
670
- calculated_planets.append("Mercury")
683
+ cls._calculate_single_planet(data, "Mercury", 2, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
671
684
 
672
685
  # Calculate Venus
673
686
  if should_calculate("Venus"):
674
- venus_deg = swe.calc_ut(julian_day, 3, iflag)[0][0]
675
- data["venus"] = get_kerykeion_point_from_degree(venus_deg, "Venus", point_type=point_type)
676
- data["venus"].house = get_planet_house(venus_deg, houses_degree_ut)
677
- data["venus"].retrograde = swe.calc_ut(julian_day, 3, iflag)[0][3] < 0
678
- calculated_planets.append("Venus")
687
+ cls._calculate_single_planet(data, "Venus", 3, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
679
688
 
680
689
  # Calculate Mars
681
690
  if should_calculate("Mars"):
682
- mars_deg = swe.calc_ut(julian_day, 4, iflag)[0][0]
683
- data["mars"] = get_kerykeion_point_from_degree(mars_deg, "Mars", point_type=point_type)
684
- data["mars"].house = get_planet_house(mars_deg, houses_degree_ut)
685
- data["mars"].retrograde = swe.calc_ut(julian_day, 4, iflag)[0][3] < 0
686
- calculated_planets.append("Mars")
691
+ cls._calculate_single_planet(data, "Mars", 4, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
687
692
 
688
693
  # Calculate Jupiter
689
694
  if should_calculate("Jupiter"):
690
- jupiter_deg = swe.calc_ut(julian_day, 5, iflag)[0][0]
691
- data["jupiter"] = get_kerykeion_point_from_degree(jupiter_deg, "Jupiter", point_type=point_type)
692
- data["jupiter"].house = get_planet_house(jupiter_deg, houses_degree_ut)
693
- data["jupiter"].retrograde = swe.calc_ut(julian_day, 5, iflag)[0][3] < 0
694
- calculated_planets.append("Jupiter")
695
+ cls._calculate_single_planet(data, "Jupiter", 5, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
695
696
 
696
697
  # Calculate Saturn
697
698
  if should_calculate("Saturn"):
698
- saturn_deg = swe.calc_ut(julian_day, 6, iflag)[0][0]
699
- data["saturn"] = get_kerykeion_point_from_degree(saturn_deg, "Saturn", point_type=point_type)
700
- data["saturn"].house = get_planet_house(saturn_deg, houses_degree_ut)
701
- data["saturn"].retrograde = swe.calc_ut(julian_day, 6, iflag)[0][3] < 0
702
- calculated_planets.append("Saturn")
699
+ cls._calculate_single_planet(data, "Saturn", 6, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
703
700
 
704
701
  # Calculate Uranus
705
702
  if should_calculate("Uranus"):
706
- uranus_deg = swe.calc_ut(julian_day, 7, iflag)[0][0]
707
- data["uranus"] = get_kerykeion_point_from_degree(uranus_deg, "Uranus", point_type=point_type)
708
- data["uranus"].house = get_planet_house(uranus_deg, houses_degree_ut)
709
- data["uranus"].retrograde = swe.calc_ut(julian_day, 7, iflag)[0][3] < 0
710
- calculated_planets.append("Uranus")
703
+ cls._calculate_single_planet(data, "Uranus", 7, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
711
704
 
712
705
  # Calculate Neptune
713
706
  if should_calculate("Neptune"):
714
- neptune_deg = swe.calc_ut(julian_day, 8, iflag)[0][0]
715
- data["neptune"] = get_kerykeion_point_from_degree(neptune_deg, "Neptune", point_type=point_type)
716
- data["neptune"].house = get_planet_house(neptune_deg, houses_degree_ut)
717
- data["neptune"].retrograde = swe.calc_ut(julian_day, 8, iflag)[0][3] < 0
718
- calculated_planets.append("Neptune")
707
+ cls._calculate_single_planet(data, "Neptune", 8, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
719
708
 
720
709
  # Calculate Pluto
721
710
  if should_calculate("Pluto"):
722
- pluto_deg = swe.calc_ut(julian_day, 9, iflag)[0][0]
723
- data["pluto"] = get_kerykeion_point_from_degree(pluto_deg, "Pluto", point_type=point_type)
724
- data["pluto"].house = get_planet_house(pluto_deg, houses_degree_ut)
725
- data["pluto"].retrograde = swe.calc_ut(julian_day, 9, iflag)[0][3] < 0
726
- calculated_planets.append("Pluto")
711
+ cls._calculate_single_planet(data, "Pluto", 9, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
727
712
 
728
713
  # ==================
729
714
  # LUNAR NODES
@@ -731,19 +716,11 @@ class AstrologicalSubjectFactory:
731
716
 
732
717
  # Calculate Mean Lunar Node
733
718
  if should_calculate("Mean_Node"):
734
- mean_node_deg = swe.calc_ut(julian_day, 10, iflag)[0][0]
735
- data["mean_node"] = get_kerykeion_point_from_degree(mean_node_deg, "Mean_Node", point_type=point_type)
736
- data["mean_node"].house = get_planet_house(mean_node_deg, houses_degree_ut)
737
- data["mean_node"].retrograde = swe.calc_ut(julian_day, 10, iflag)[0][3] < 0
738
- calculated_planets.append("Mean_Node")
719
+ cls._calculate_single_planet(data, "Mean_Node", 10, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
739
720
 
740
721
  # Calculate True Lunar Node
741
722
  if should_calculate("True_Node"):
742
- true_node_deg = swe.calc_ut(julian_day, 11, iflag)[0][0]
743
- data["true_node"] = get_kerykeion_point_from_degree(true_node_deg, "True_Node", point_type=point_type)
744
- data["true_node"].house = get_planet_house(true_node_deg, houses_degree_ut)
745
- data["true_node"].retrograde = swe.calc_ut(julian_day, 11, iflag)[0][3] < 0
746
- calculated_planets.append("True_Node")
723
+ cls._calculate_single_planet(data, "True_Node", 11, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
747
724
 
748
725
  # Calculate Mean South Node (opposite to Mean North Node)
749
726
  if should_calculate("Mean_South_Node") and "mean_node" in data:
@@ -771,28 +748,17 @@ class AstrologicalSubjectFactory:
771
748
 
772
749
  # Calculate Mean Lilith (Mean Black Moon)
773
750
  if should_calculate("Mean_Lilith"):
774
- try:
775
- mean_lilith_deg = swe.calc_ut(julian_day, 12, iflag)[0][0]
776
- data["mean_lilith"] = get_kerykeion_point_from_degree(mean_lilith_deg, "Mean_Lilith", point_type=point_type)
777
- data["mean_lilith"].house = get_planet_house(mean_lilith_deg, houses_degree_ut)
778
- data["mean_lilith"].retrograde = swe.calc_ut(julian_day, 12, iflag)[0][3] < 0
779
- calculated_planets.append("Mean_Lilith")
780
- except Exception as e:
781
- logging.error(f"Error calculating Mean Lilith: {e}")
782
- active_points.remove("Mean_Lilith")
783
-
751
+ cls._calculate_single_planet(
752
+ data, "Mean_Lilith", 12, julian_day, iflag, houses_degree_ut,
753
+ point_type, calculated_planets, active_points
754
+ )
784
755
 
785
756
  # Calculate True Lilith (Osculating Black Moon)
786
757
  if should_calculate("True_Lilith"):
787
- try:
788
- true_lilith_deg = swe.calc_ut(julian_day, 13, iflag)[0][0]
789
- data["true_lilith"] = get_kerykeion_point_from_degree(true_lilith_deg, "True_Lilith", point_type=point_type)
790
- data["true_lilith"].house = get_planet_house(true_lilith_deg, houses_degree_ut)
791
- data["true_lilith"].retrograde = swe.calc_ut(julian_day, 13, iflag)[0][3] < 0
792
- calculated_planets.append("True_Lilith")
793
- except Exception as e:
794
- logging.error(f"Error calculating True Lilith: {e}")
795
- active_points.remove("True_Lilith")
758
+ cls._calculate_single_planet(
759
+ data, "True_Lilith", 13, julian_day, iflag, houses_degree_ut,
760
+ point_type, calculated_planets, active_points
761
+ )
796
762
 
797
763
  # ==================
798
764
  # SPECIAL POINTS
@@ -800,39 +766,24 @@ class AstrologicalSubjectFactory:
800
766
 
801
767
  # Calculate Earth - useful for heliocentric charts
802
768
  if should_calculate("Earth"):
803
- try:
804
- earth_deg = swe.calc_ut(julian_day, 14, iflag)[0][0]
805
- data["earth"] = get_kerykeion_point_from_degree(earth_deg, "Earth", point_type=point_type)
806
- data["earth"].house = get_planet_house(earth_deg, houses_degree_ut)
807
- data["earth"].retrograde = swe.calc_ut(julian_day, 14, iflag)[0][3] < 0
808
- calculated_planets.append("Earth")
809
- except Exception as e:
810
- logging.error(f"Error calculating Earth position: {e}")
811
- active_points.remove("Earth")
769
+ cls._calculate_single_planet(
770
+ data, "Earth", 14, julian_day, iflag, houses_degree_ut,
771
+ point_type, calculated_planets, active_points
772
+ )
812
773
 
813
774
  # Calculate Chiron
814
775
  if should_calculate("Chiron"):
815
- try:
816
- chiron_deg = swe.calc_ut(julian_day, 15, iflag)[0][0]
817
- data["chiron"] = get_kerykeion_point_from_degree(chiron_deg, "Chiron", point_type=point_type)
818
- data["chiron"].house = get_planet_house(chiron_deg, houses_degree_ut)
819
- data["chiron"].retrograde = swe.calc_ut(julian_day, 15, iflag)[0][3] < 0
820
- calculated_planets.append("Chiron")
821
- except Exception as e:
822
- logging.error(f"Error calculating Chiron position: {e}")
823
- active_points.remove("Chiron")
776
+ cls._calculate_single_planet(
777
+ data, "Chiron", 15, julian_day, iflag, houses_degree_ut,
778
+ point_type, calculated_planets, active_points
779
+ )
824
780
 
825
781
  # Calculate Pholus
826
782
  if should_calculate("Pholus"):
827
- try:
828
- pholus_deg = swe.calc_ut(julian_day, 16, iflag)[0][0]
829
- data["pholus"] = get_kerykeion_point_from_degree(pholus_deg, "Pholus", point_type=point_type)
830
- data["pholus"].house = get_planet_house(pholus_deg, houses_degree_ut)
831
- data["pholus"].retrograde = swe.calc_ut(julian_day, 16, iflag)[0][3] < 0
832
- calculated_planets.append("Pholus")
833
- except Exception as e:
834
- logging.error(f"Error calculating Pholus position: {e}")
835
- active_points.remove("Pholus")
783
+ cls._calculate_single_planet(
784
+ data, "Pholus", 16, julian_day, iflag, houses_degree_ut,
785
+ point_type, calculated_planets, active_points
786
+ )
836
787
 
837
788
  # ==================
838
789
  # ASTEROIDS
@@ -840,51 +791,31 @@ class AstrologicalSubjectFactory:
840
791
 
841
792
  # Calculate Ceres
842
793
  if should_calculate("Ceres"):
843
- try:
844
- ceres_deg = swe.calc_ut(julian_day, 17, iflag)[0][0]
845
- data["ceres"] = get_kerykeion_point_from_degree(ceres_deg, "Ceres", point_type=point_type)
846
- data["ceres"].house = get_planet_house(ceres_deg, houses_degree_ut)
847
- data["ceres"].retrograde = swe.calc_ut(julian_day, 17, iflag)[0][3] < 0
848
- calculated_planets.append("Ceres")
849
- except Exception as e:
850
- logging.error(f"Error calculating Ceres position: {e}")
851
- active_points.remove("Ceres")
794
+ cls._calculate_single_planet(
795
+ data, "Ceres", 17, julian_day, iflag, houses_degree_ut,
796
+ point_type, calculated_planets, active_points
797
+ )
852
798
 
853
799
  # Calculate Pallas
854
800
  if should_calculate("Pallas"):
855
- try:
856
- pallas_deg = swe.calc_ut(julian_day, 18, iflag)[0][0]
857
- data["pallas"] = get_kerykeion_point_from_degree(pallas_deg, "Pallas", point_type=point_type)
858
- data["pallas"].house = get_planet_house(pallas_deg, houses_degree_ut)
859
- data["pallas"].retrograde = swe.calc_ut(julian_day, 18, iflag)[0][3] < 0
860
- calculated_planets.append("Pallas")
861
- except Exception as e:
862
- logging.error(f"Error calculating Pallas position: {e}")
863
- active_points.remove("Pallas")
801
+ cls._calculate_single_planet(
802
+ data, "Pallas", 18, julian_day, iflag, houses_degree_ut,
803
+ point_type, calculated_planets, active_points
804
+ )
864
805
 
865
806
  # Calculate Juno
866
807
  if should_calculate("Juno"):
867
- try:
868
- juno_deg = swe.calc_ut(julian_day, 19, iflag)[0][0]
869
- data["juno"] = get_kerykeion_point_from_degree(juno_deg, "Juno", point_type=point_type)
870
- data["juno"].house = get_planet_house(juno_deg, houses_degree_ut)
871
- data["juno"].retrograde = swe.calc_ut(julian_day, 19, iflag)[0][3] < 0
872
- calculated_planets.append("Juno")
873
- except Exception as e:
874
- logging.error(f"Error calculating Juno position: {e}")
875
- active_points.remove("Juno")
808
+ cls._calculate_single_planet(
809
+ data, "Juno", 19, julian_day, iflag, houses_degree_ut,
810
+ point_type, calculated_planets, active_points
811
+ )
876
812
 
877
813
  # Calculate Vesta
878
814
  if should_calculate("Vesta"):
879
- try:
880
- vesta_deg = swe.calc_ut(julian_day, 20, iflag)[0][0]
881
- data["vesta"] = get_kerykeion_point_from_degree(vesta_deg, "Vesta", point_type=point_type)
882
- data["vesta"].house = get_planet_house(vesta_deg, houses_degree_ut)
883
- data["vesta"].retrograde = swe.calc_ut(julian_day, 20, iflag)[0][3] < 0
884
- calculated_planets.append("Vesta")
885
- except Exception as e:
886
- logging.error(f"Error calculating Vesta position: {e}")
887
- active_points.remove("Vesta")
815
+ cls._calculate_single_planet(
816
+ data, "Vesta", 20, julian_day, iflag, houses_degree_ut,
817
+ point_type, calculated_planets, active_points
818
+ )
888
819
 
889
820
  # ==================
890
821
  # TRANS-NEPTUNIAN OBJECTS
@@ -893,10 +824,10 @@ class AstrologicalSubjectFactory:
893
824
  # Calculate Eris
894
825
  if should_calculate("Eris"):
895
826
  try:
896
- eris_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 136199, iflag)[0][0]
897
- data["eris"] = get_kerykeion_point_from_degree(eris_deg, "Eris", point_type=point_type)
898
- data["eris"].house = get_planet_house(eris_deg, houses_degree_ut)
899
- data["eris"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 136199, iflag)[0][3] < 0
827
+ eris_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 136199, iflag)[0]
828
+ data["eris"] = get_kerykeion_point_from_degree(eris_calc[0], "Eris", point_type=point_type)
829
+ data["eris"].house = get_planet_house(eris_calc[0], houses_degree_ut)
830
+ data["eris"].retrograde = eris_calc[3] < 0
900
831
  calculated_planets.append("Eris")
901
832
  except Exception as e:
902
833
  logging.warning(f"Could not calculate Eris position: {e}")
@@ -905,10 +836,10 @@ class AstrologicalSubjectFactory:
905
836
  # Calculate Sedna
906
837
  if should_calculate("Sedna"):
907
838
  try:
908
- sedna_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 90377, iflag)[0][0]
909
- data["sedna"] = get_kerykeion_point_from_degree(sedna_deg, "Sedna", point_type=point_type)
910
- data["sedna"].house = get_planet_house(sedna_deg, houses_degree_ut)
911
- data["sedna"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 90377, iflag)[0][3] < 0
839
+ sedna_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 90377, iflag)[0]
840
+ data["sedna"] = get_kerykeion_point_from_degree(sedna_calc[0], "Sedna", point_type=point_type)
841
+ data["sedna"].house = get_planet_house(sedna_calc[0], houses_degree_ut)
842
+ data["sedna"].retrograde = sedna_calc[3] < 0
912
843
  calculated_planets.append("Sedna")
913
844
  except Exception as e:
914
845
  logging.warning(f"Could not calculate Sedna position: {e}")
@@ -917,10 +848,10 @@ class AstrologicalSubjectFactory:
917
848
  # Calculate Haumea
918
849
  if should_calculate("Haumea"):
919
850
  try:
920
- haumea_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 136108, iflag)[0][0]
921
- data["haumea"] = get_kerykeion_point_from_degree(haumea_deg, "Haumea", point_type=point_type)
922
- data["haumea"].house = get_planet_house(haumea_deg, houses_degree_ut)
923
- data["haumea"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 136108, iflag)[0][3] < 0
851
+ haumea_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 136108, iflag)[0]
852
+ data["haumea"] = get_kerykeion_point_from_degree(haumea_calc[0], "Haumea", point_type=point_type)
853
+ data["haumea"].house = get_planet_house(haumea_calc[0], houses_degree_ut)
854
+ data["haumea"].retrograde = haumea_calc[3] < 0
924
855
  calculated_planets.append("Haumea")
925
856
  except Exception as e:
926
857
  logging.warning(f"Could not calculate Haumea position: {e}")
@@ -929,10 +860,10 @@ class AstrologicalSubjectFactory:
929
860
  # Calculate Makemake
930
861
  if should_calculate("Makemake"):
931
862
  try:
932
- makemake_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 136472, iflag)[0][0]
933
- data["makemake"] = get_kerykeion_point_from_degree(makemake_deg, "Makemake", point_type=point_type)
934
- data["makemake"].house = get_planet_house(makemake_deg, houses_degree_ut)
935
- data["makemake"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 136472, iflag)[0][3] < 0
863
+ makemake_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 136472, iflag)[0]
864
+ data["makemake"] = get_kerykeion_point_from_degree(makemake_calc[0], "Makemake", point_type=point_type)
865
+ data["makemake"].house = get_planet_house(makemake_calc[0], houses_degree_ut)
866
+ data["makemake"].retrograde = makemake_calc[3] < 0
936
867
  calculated_planets.append("Makemake")
937
868
  except Exception as e:
938
869
  logging.warning(f"Could not calculate Makemake position: {e}")
@@ -941,10 +872,10 @@ class AstrologicalSubjectFactory:
941
872
  # Calculate Ixion
942
873
  if should_calculate("Ixion"):
943
874
  try:
944
- ixion_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 28978, iflag)[0][0]
945
- data["ixion"] = get_kerykeion_point_from_degree(ixion_deg, "Ixion", point_type=point_type)
946
- data["ixion"].house = get_planet_house(ixion_deg, houses_degree_ut)
947
- data["ixion"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 28978, iflag)[0][3] < 0
875
+ ixion_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 28978, iflag)[0]
876
+ data["ixion"] = get_kerykeion_point_from_degree(ixion_calc[0], "Ixion", point_type=point_type)
877
+ data["ixion"].house = get_planet_house(ixion_calc[0], houses_degree_ut)
878
+ data["ixion"].retrograde = ixion_calc[3] < 0
948
879
  calculated_planets.append("Ixion")
949
880
  except Exception as e:
950
881
  logging.warning(f"Could not calculate Ixion position: {e}")
@@ -953,10 +884,10 @@ class AstrologicalSubjectFactory:
953
884
  # Calculate Orcus
954
885
  if should_calculate("Orcus"):
955
886
  try:
956
- orcus_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 90482, iflag)[0][0]
957
- data["orcus"] = get_kerykeion_point_from_degree(orcus_deg, "Orcus", point_type=point_type)
958
- data["orcus"].house = get_planet_house(orcus_deg, houses_degree_ut)
959
- data["orcus"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 90482, iflag)[0][3] < 0
887
+ orcus_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 90482, iflag)[0]
888
+ data["orcus"] = get_kerykeion_point_from_degree(orcus_calc[0], "Orcus", point_type=point_type)
889
+ data["orcus"].house = get_planet_house(orcus_calc[0], houses_degree_ut)
890
+ data["orcus"].retrograde = orcus_calc[3] < 0
960
891
  calculated_planets.append("Orcus")
961
892
  except Exception as e:
962
893
  logging.warning(f"Could not calculate Orcus position: {e}")
@@ -965,10 +896,10 @@ class AstrologicalSubjectFactory:
965
896
  # Calculate Quaoar
966
897
  if should_calculate("Quaoar"):
967
898
  try:
968
- quaoar_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 50000, iflag)[0][0]
969
- data["quaoar"] = get_kerykeion_point_from_degree(quaoar_deg, "Quaoar", point_type=point_type)
970
- data["quaoar"].house = get_planet_house(quaoar_deg, houses_degree_ut)
971
- data["quaoar"].retrograde = swe.calc_ut(julian_day, swe.AST_OFFSET + 50000, iflag)[0][3] < 0
899
+ quaoar_calc = swe.calc_ut(julian_day, swe.AST_OFFSET + 50000, iflag)[0]
900
+ data["quaoar"] = get_kerykeion_point_from_degree(quaoar_calc[0], "Quaoar", point_type=point_type)
901
+ data["quaoar"].house = get_planet_house(quaoar_calc[0], houses_degree_ut)
902
+ data["quaoar"].retrograde = quaoar_calc[3] < 0
972
903
  calculated_planets.append("Quaoar")
973
904
  except Exception as e:
974
905
  logging.warning(f"Could not calculate Quaoar position: {e}")
@@ -1012,12 +943,34 @@ class AstrologicalSubjectFactory:
1012
943
 
1013
944
  # Calculate Pars Fortunae (Part of Fortune)
1014
945
  if should_calculate("Pars_Fortunae"):
946
+ # Auto-activate required points with notification
947
+ required_points: List[AstrologicalPoint] = ["Ascendant", "Sun", "Moon"]
948
+ missing_points = [point for point in required_points if point not in active_points]
949
+ if missing_points:
950
+ logging.info(f"Automatically adding required points for Pars_Fortunae: {missing_points}")
951
+ active_points.extend(cast(List[AstrologicalPoint], missing_points))
952
+ # Recalculate the missing points
953
+ for point in missing_points:
954
+ if point == "Sun" and point not in data:
955
+ sun_calc = swe.calc_ut(julian_day, 0, iflag)[0]
956
+ data["sun"] = get_kerykeion_point_from_degree(sun_calc[0], "Sun", point_type=point_type)
957
+ data["sun"].house = get_planet_house(sun_calc[0], houses_degree_ut)
958
+ data["sun"].retrograde = sun_calc[3] < 0
959
+ elif point == "Moon" and point not in data:
960
+ moon_calc = swe.calc_ut(julian_day, 1, iflag)[0]
961
+ data["moon"] = get_kerykeion_point_from_degree(moon_calc[0], "Moon", point_type=point_type)
962
+ data["moon"].house = get_planet_house(moon_calc[0], houses_degree_ut)
963
+ data["moon"].retrograde = moon_calc[3] < 0
964
+
1015
965
  # Check if required points are available
1016
966
  if all(k in data for k in ["ascendant", "sun", "moon"]):
1017
967
  # Different calculation for day and night charts
1018
968
  # Day birth (Sun above horizon): ASC + Moon - Sun
1019
969
  # Night birth (Sun below horizon): ASC + Sun - Moon
1020
- is_day_chart = get_house_number(data["sun"].house) < 7 # Houses 1-6 are above horizon
970
+ if data["sun"].house:
971
+ is_day_chart = get_house_number(data["sun"].house) < 7 # Houses 1-6 are above horizon
972
+ else:
973
+ is_day_chart = True # Default to day chart if house is None
1021
974
 
1022
975
  if is_day_chart:
1023
976
  fortune_deg = math.fmod(data["ascendant"].abs_pos + data["moon"].abs_pos - data["sun"].abs_pos, 360)
@@ -1031,11 +984,33 @@ class AstrologicalSubjectFactory:
1031
984
 
1032
985
  # Calculate Pars Spiritus (Part of Spirit)
1033
986
  if should_calculate("Pars_Spiritus"):
987
+ # Auto-activate required points with notification
988
+ required_points: List[AstrologicalPoint] = ["Ascendant", "Sun", "Moon"]
989
+ missing_points = [point for point in required_points if point not in active_points]
990
+ if missing_points:
991
+ logging.info(f"Automatically adding required points for Pars_Spiritus: {missing_points}")
992
+ active_points.extend(cast(List[AstrologicalPoint], missing_points))
993
+ # Recalculate the missing points
994
+ for point in missing_points:
995
+ if point == "Sun" and point not in data:
996
+ sun_calc = swe.calc_ut(julian_day, 0, iflag)[0]
997
+ data["sun"] = get_kerykeion_point_from_degree(sun_calc[0], "Sun", point_type=point_type)
998
+ data["sun"].house = get_planet_house(sun_calc[0], houses_degree_ut)
999
+ data["sun"].retrograde = sun_calc[3] < 0
1000
+ elif point == "Moon" and point not in data:
1001
+ moon_calc = swe.calc_ut(julian_day, 1, iflag)[0]
1002
+ data["moon"] = get_kerykeion_point_from_degree(moon_calc[0], "Moon", point_type=point_type)
1003
+ data["moon"].house = get_planet_house(moon_calc[0], houses_degree_ut)
1004
+ data["moon"].retrograde = moon_calc[3] < 0
1005
+
1034
1006
  # Check if required points are available
1035
1007
  if all(k in data for k in ["ascendant", "sun", "moon"]):
1036
1008
  # Day birth: ASC + Sun - Moon
1037
1009
  # Night birth: ASC + Moon - Sun
1038
- is_day_chart = get_house_number(data["sun"].house) < 7
1010
+ if data["sun"].house:
1011
+ is_day_chart = get_house_number(data["sun"].house) < 7
1012
+ else:
1013
+ is_day_chart = True # Default to day chart if house is None
1039
1014
 
1040
1015
  if is_day_chart:
1041
1016
  spirit_deg = math.fmod(data["ascendant"].abs_pos + data["sun"].abs_pos - data["moon"].abs_pos, 360)
@@ -1049,19 +1024,56 @@ class AstrologicalSubjectFactory:
1049
1024
 
1050
1025
  # Calculate Pars Amoris (Part of Eros/Love)
1051
1026
  if should_calculate("Pars_Amoris"):
1027
+ # Auto-activate required points with notification
1028
+ required_points: List[AstrologicalPoint] = ["Ascendant", "Venus", "Sun"]
1029
+ missing_points = [point for point in required_points if point not in active_points]
1030
+ if missing_points:
1031
+ logging.info(f"Automatically adding required points for Pars_Amoris: {missing_points}")
1032
+ active_points.extend(cast(List[AstrologicalPoint], missing_points))
1033
+ # Recalculate the missing points
1034
+ for point in missing_points:
1035
+ if point == "Sun" and point not in data:
1036
+ sun_calc = swe.calc_ut(julian_day, 0, iflag)[0]
1037
+ data["sun"] = get_kerykeion_point_from_degree(sun_calc[0], "Sun", point_type=point_type)
1038
+ data["sun"].house = get_planet_house(sun_calc[0], houses_degree_ut)
1039
+ data["sun"].retrograde = sun_calc[3] < 0
1040
+ elif point == "Venus" and point not in data:
1041
+ venus_calc = swe.calc_ut(julian_day, 3, iflag)[0]
1042
+ data["venus"] = get_kerykeion_point_from_degree(venus_calc[0], "Venus", point_type=point_type)
1043
+ data["venus"].house = get_planet_house(venus_calc[0], houses_degree_ut)
1044
+ data["venus"].retrograde = venus_calc[3] < 0
1045
+
1052
1046
  # Check if required points are available
1053
- if all(k in data for k in ["ascendant", "venus"]):
1047
+ if all(k in data for k in ["ascendant", "venus", "sun"]):
1054
1048
  # ASC + Venus - Sun
1055
- if "sun" in data:
1056
- amoris_deg = math.fmod(data["ascendant"].abs_pos + data["venus"].abs_pos - data["sun"].abs_pos, 360)
1049
+ amoris_deg = math.fmod(data["ascendant"].abs_pos + data["venus"].abs_pos - data["sun"].abs_pos, 360)
1057
1050
 
1058
- data["pars_amoris"] = get_kerykeion_point_from_degree(amoris_deg, "Pars_Amoris", point_type=point_type)
1059
- data["pars_amoris"].house = get_planet_house(amoris_deg, houses_degree_ut)
1060
- data["pars_amoris"].retrograde = False
1061
- calculated_planets.append("Pars_Amoris")
1051
+ data["pars_amoris"] = get_kerykeion_point_from_degree(amoris_deg, "Pars_Amoris", point_type=point_type)
1052
+ data["pars_amoris"].house = get_planet_house(amoris_deg, houses_degree_ut)
1053
+ data["pars_amoris"].retrograde = False
1054
+ calculated_planets.append("Pars_Amoris")
1062
1055
 
1063
1056
  # Calculate Pars Fidei (Part of Faith)
1064
1057
  if should_calculate("Pars_Fidei"):
1058
+ # Auto-activate required points with notification
1059
+ required_points: List[AstrologicalPoint] = ["Ascendant", "Jupiter", "Saturn"]
1060
+ missing_points = [point for point in required_points if point not in active_points]
1061
+ if missing_points:
1062
+ logging.info(f"Automatically adding required points for Pars_Fidei: {missing_points}")
1063
+ active_points.extend(cast(List[AstrologicalPoint], missing_points))
1064
+ # Recalculate the missing points
1065
+ for point in missing_points:
1066
+ if point == "Jupiter" and point not in data:
1067
+ jupiter_calc = swe.calc_ut(julian_day, 5, iflag)[0]
1068
+ data["jupiter"] = get_kerykeion_point_from_degree(jupiter_calc[0], "Jupiter", point_type=point_type)
1069
+ data["jupiter"].house = get_planet_house(jupiter_calc[0], houses_degree_ut)
1070
+ data["jupiter"].retrograde = jupiter_calc[3] < 0
1071
+ elif point == "Saturn" and point not in data:
1072
+ saturn_calc = swe.calc_ut(julian_day, 6, iflag)[0]
1073
+ data["saturn"] = get_kerykeion_point_from_degree(saturn_calc[0], "Saturn", point_type=point_type)
1074
+ data["saturn"].house = get_planet_house(saturn_calc[0], houses_degree_ut)
1075
+ data["saturn"].retrograde = saturn_calc[3] < 0
1076
+
1065
1077
  # Check if required points are available
1066
1078
  if all(k in data for k in ["ascendant", "jupiter", "saturn"]):
1067
1079
  # ASC + Jupiter - Saturn