kerykeion 5.0.0a5__tar.gz → 5.0.0a6__tar.gz

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.

Files changed (53) hide show
  1. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/PKG-INFO +1 -1
  2. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/astrological_subject_factory.py +237 -228
  3. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/kerykeion_chart_svg.py +65 -22
  4. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/kr_types/kr_models.py +5 -5
  5. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/pyproject.toml +1 -1
  6. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/LICENSE +0 -0
  7. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/README.md +0 -0
  8. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/__init__.py +0 -0
  9. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/aspects/__init__.py +0 -0
  10. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/aspects/aspects_utils.py +0 -0
  11. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/aspects/natal_aspects.py +0 -0
  12. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/aspects/synastry_aspects.py +0 -0
  13. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/aspects/transits_time_range.py +0 -0
  14. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/__init__.py +0 -0
  15. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/charts_utils.py +0 -0
  16. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/draw_planets.py +0 -0
  17. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/draw_planets_v2.py +0 -0
  18. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/templates/aspect_grid_only.xml +0 -0
  19. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/templates/chart.xml +0 -0
  20. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/templates/wheel_only.xml +0 -0
  21. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/themes/classic.css +0 -0
  22. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/themes/dark-high-contrast.css +0 -0
  23. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/themes/dark.css +0 -0
  24. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/themes/light.css +0 -0
  25. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/charts/themes/strawberry.css +0 -0
  26. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/composite_subject_factory.py +0 -0
  27. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/enums.py +0 -0
  28. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/ephemeris_data.py +0 -0
  29. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/fetch_geonames.py +0 -0
  30. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/house_comparison/__init__.py +0 -0
  31. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/house_comparison/house_comparison_factory.py +0 -0
  32. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/house_comparison/house_comparison_models.py +0 -0
  33. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/house_comparison/house_comparison_utils.py +0 -0
  34. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/kr_types/__init__.py +0 -0
  35. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/kr_types/chart_types.py +0 -0
  36. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/kr_types/kerykeion_exception.py +0 -0
  37. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/kr_types/kr_literals.py +0 -0
  38. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/kr_types/settings_models.py +0 -0
  39. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/planetary_return_factory.py +0 -0
  40. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/relationship_score_factory.py +0 -0
  41. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/report.py +0 -0
  42. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/__init__.py +0 -0
  43. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/config_constants.py +0 -0
  44. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/kerykeion_settings.py +0 -0
  45. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/kr.config.json +0 -0
  46. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/legacy/__init__.py +0 -0
  47. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/legacy/legacy_celestial_points_settings.py +0 -0
  48. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/legacy/legacy_chart_aspects_settings.py +0 -0
  49. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/settings/legacy/legacy_color_settings.py +0 -0
  50. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/sweph/README.md +0 -0
  51. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/sweph/seas_18.se1 +0 -0
  52. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/transits_time_range.py +0 -0
  53. {kerykeion-5.0.0a5 → kerykeion-5.0.0a6}/kerykeion/utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kerykeion
3
- Version: 5.0.0a5
3
+ Version: 5.0.0a6
4
4
  Summary: A python library for astrology.
5
5
  License: AGPL-3.0
6
6
  Keywords: astrology,ephemeris,astrology library,birtchart,svg,zodiac,zodiac-sing,astronomical-algorithms,synastry,astrology-calculator
@@ -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,11 +206,12 @@ 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
@@ -226,43 +228,6 @@ class AstrologicalSubjectFactory:
226
228
 
227
229
  calc_data["active_points"] = active_points
228
230
 
229
-
230
- if "Sun" not in calc_data["active_points"]:
231
- logging.info("Automatically adding 'Sun' to active points")
232
- calc_data["active_points"].append("Sun")
233
-
234
- if "Moon" not in calc_data["active_points"]:
235
- logging.info("Automatically adding 'Moon' to active points")
236
- calc_data["active_points"].append("Moon")
237
-
238
- if "Ascendant" not in calc_data["active_points"]:
239
- logging.info("Automatically adding 'Ascendant' to active points")
240
- calc_data["active_points"].append("Ascendant")
241
-
242
- if "Medium_Coeli" not in calc_data["active_points"]:
243
- logging.info("Automatically adding 'Medium_Coeli' to active points")
244
- calc_data["active_points"].append("Medium_Coeli")
245
-
246
- if "Mercury" not in calc_data["active_points"]:
247
- logging.info("Automatically adding 'Mercury' to active points")
248
- calc_data["active_points"].append("Mercury")
249
-
250
- if "Venus" not in calc_data["active_points"]:
251
- logging.info("Automatically adding 'Venus' to active points")
252
- calc_data["active_points"].append("Venus")
253
-
254
- if "Mars" not in calc_data["active_points"]:
255
- logging.info("Automatically adding 'Mars' to active points")
256
- calc_data["active_points"].append("Mars")
257
-
258
- if "Jupiter" not in calc_data["active_points"]:
259
- logging.info("Automatically adding 'Jupiter' to active points")
260
- calc_data["active_points"].append("Jupiter")
261
-
262
- if "Saturn" not in calc_data["active_points"]:
263
- logging.info("Automatically adding 'Saturn' to active points")
264
- calc_data["active_points"].append("Saturn")
265
-
266
231
  # Initialize configuration
267
232
  config = ChartConfiguration(
268
233
  zodiac_type=zodiac_type,
@@ -294,13 +259,13 @@ class AstrologicalSubjectFactory:
294
259
  )
295
260
 
296
261
  # If offline mode is requested but required data is missing, raise error
297
- 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):
298
263
  raise KerykeionException(
299
264
  "For offline mode, you must provide timezone (tz_str) and coordinates (lat, lng)"
300
265
  )
301
266
 
302
267
  # Fetch location data if needed
303
- 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):
304
269
  location.fetch_from_geonames(
305
270
  username=geonames_username or DEFAULT_GEONAMES_USERNAME,
306
271
  cache_expire_after_days=cache_expire_after_days
@@ -335,11 +300,12 @@ class AstrologicalSubjectFactory:
335
300
  cls._calculate_planets(calc_data, calc_data["active_points"])
336
301
  cls._calculate_day_of_week(calc_data)
337
302
 
338
- # Calculate lunar phase
339
- calc_data["lunar_phase"] = calculate_moon_phase(
340
- calc_data["moon"].abs_pos,
341
- calc_data["sun"].abs_pos
342
- )
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
+ )
343
309
 
344
310
  # Create and return the AstrologicalSubjectModel
345
311
  return AstrologicalSubjectModel(**calc_data)
@@ -361,7 +327,8 @@ class AstrologicalSubjectFactory:
361
327
  houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
362
328
  perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE,
363
329
  altitude: Optional[float] = None,
364
- active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS
330
+ active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS,
331
+ calculate_lunar_phase: bool = True
365
332
  ) -> AstrologicalSubjectModel:
366
333
  """
367
334
  Create an astrological subject from an ISO formatted UTC time.
@@ -381,6 +348,7 @@ class AstrologicalSubjectFactory:
381
348
  perspective_type: Perspective for calculations
382
349
  altitude: Location altitude
383
350
  active_points: Set of points to calculate
351
+ calculate_lunar_phase: Whether to calculate lunar phase
384
352
 
385
353
  Returns:
386
354
  AstrologicalSubjectModel instance
@@ -428,7 +396,8 @@ class AstrologicalSubjectFactory:
428
396
  houses_system_identifier=houses_system_identifier,
429
397
  perspective_type=perspective_type,
430
398
  altitude=altitude,
431
- active_points=active_points
399
+ active_points=active_points,
400
+ calculate_lunar_phase=calculate_lunar_phase
432
401
  )
433
402
 
434
403
  @classmethod
@@ -446,7 +415,8 @@ class AstrologicalSubjectFactory:
446
415
  sidereal_mode: Optional[SiderealMode] = None,
447
416
  houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER,
448
417
  perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE,
449
- active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS
418
+ active_points: List[AstrologicalPoint] = DEFAULT_ACTIVE_POINTS,
419
+ calculate_lunar_phase: bool = True
450
420
  ) -> AstrologicalSubjectModel:
451
421
  """
452
422
  Create an astrological subject for the current time.
@@ -464,6 +434,7 @@ class AstrologicalSubjectFactory:
464
434
  houses_system_identifier: House system
465
435
  perspective_type: Perspective for calculations
466
436
  active_points: Set of points to calculate
437
+ calculate_lunar_phase: Whether to calculate lunar phase
467
438
 
468
439
  Returns:
469
440
  AstrologicalSubjectModel for current time
@@ -489,7 +460,8 @@ class AstrologicalSubjectFactory:
489
460
  sidereal_mode=sidereal_mode,
490
461
  houses_system_identifier=houses_system_identifier,
491
462
  perspective_type=perspective_type,
492
- active_points=active_points
463
+ active_points=active_points,
464
+ calculate_lunar_phase=calculate_lunar_phase
493
465
  )
494
466
 
495
467
  @classmethod
@@ -630,6 +602,56 @@ class AstrologicalSubjectFactory:
630
602
  data["imum_coeli"].retrograde = False
631
603
  calculated_axial_cusps.append("Imum_Coeli")
632
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
+
633
655
  @classmethod
634
656
  def _calculate_planets(cls, data: Dict[str, Any], active_points: List[AstrologicalPoint]) -> None:
635
657
  """Calculate planetary positions and related information"""
@@ -650,83 +672,43 @@ class AstrologicalSubjectFactory:
650
672
 
651
673
  # Calculate Sun
652
674
  if should_calculate("Sun"):
653
- sun_deg = swe.calc_ut(julian_day, 0, iflag)[0][0]
654
- data["sun"] = get_kerykeion_point_from_degree(sun_deg, "Sun", point_type=point_type)
655
- data["sun"].house = get_planet_house(sun_deg, houses_degree_ut)
656
- data["sun"].retrograde = swe.calc_ut(julian_day, 0, iflag)[0][3] < 0
657
- calculated_planets.append("Sun")
675
+ cls._calculate_single_planet(data, "Sun", 0, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
658
676
 
659
677
  # Calculate Moon
660
678
  if should_calculate("Moon"):
661
- moon_deg = swe.calc_ut(julian_day, 1, iflag)[0][0]
662
- data["moon"] = get_kerykeion_point_from_degree(moon_deg, "Moon", point_type=point_type)
663
- data["moon"].house = get_planet_house(moon_deg, houses_degree_ut)
664
- data["moon"].retrograde = swe.calc_ut(julian_day, 1, iflag)[0][3] < 0
665
- calculated_planets.append("Moon")
679
+ cls._calculate_single_planet(data, "Moon", 1, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
666
680
 
667
681
  # Calculate Mercury
668
682
  if should_calculate("Mercury"):
669
- mercury_deg = swe.calc_ut(julian_day, 2, iflag)[0][0]
670
- data["mercury"] = get_kerykeion_point_from_degree(mercury_deg, "Mercury", point_type=point_type)
671
- data["mercury"].house = get_planet_house(mercury_deg, houses_degree_ut)
672
- data["mercury"].retrograde = swe.calc_ut(julian_day, 2, iflag)[0][3] < 0
673
- calculated_planets.append("Mercury")
683
+ cls._calculate_single_planet(data, "Mercury", 2, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
674
684
 
675
685
  # Calculate Venus
676
686
  if should_calculate("Venus"):
677
- venus_deg = swe.calc_ut(julian_day, 3, iflag)[0][0]
678
- data["venus"] = get_kerykeion_point_from_degree(venus_deg, "Venus", point_type=point_type)
679
- data["venus"].house = get_planet_house(venus_deg, houses_degree_ut)
680
- data["venus"].retrograde = swe.calc_ut(julian_day, 3, iflag)[0][3] < 0
681
- calculated_planets.append("Venus")
687
+ cls._calculate_single_planet(data, "Venus", 3, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
682
688
 
683
689
  # Calculate Mars
684
690
  if should_calculate("Mars"):
685
- mars_deg = swe.calc_ut(julian_day, 4, iflag)[0][0]
686
- data["mars"] = get_kerykeion_point_from_degree(mars_deg, "Mars", point_type=point_type)
687
- data["mars"].house = get_planet_house(mars_deg, houses_degree_ut)
688
- data["mars"].retrograde = swe.calc_ut(julian_day, 4, iflag)[0][3] < 0
689
- calculated_planets.append("Mars")
691
+ cls._calculate_single_planet(data, "Mars", 4, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
690
692
 
691
693
  # Calculate Jupiter
692
694
  if should_calculate("Jupiter"):
693
- jupiter_deg = swe.calc_ut(julian_day, 5, iflag)[0][0]
694
- data["jupiter"] = get_kerykeion_point_from_degree(jupiter_deg, "Jupiter", point_type=point_type)
695
- data["jupiter"].house = get_planet_house(jupiter_deg, houses_degree_ut)
696
- data["jupiter"].retrograde = swe.calc_ut(julian_day, 5, iflag)[0][3] < 0
697
- calculated_planets.append("Jupiter")
695
+ cls._calculate_single_planet(data, "Jupiter", 5, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
698
696
 
699
697
  # Calculate Saturn
700
698
  if should_calculate("Saturn"):
701
- saturn_deg = swe.calc_ut(julian_day, 6, iflag)[0][0]
702
- data["saturn"] = get_kerykeion_point_from_degree(saturn_deg, "Saturn", point_type=point_type)
703
- data["saturn"].house = get_planet_house(saturn_deg, houses_degree_ut)
704
- data["saturn"].retrograde = swe.calc_ut(julian_day, 6, iflag)[0][3] < 0
705
- calculated_planets.append("Saturn")
699
+ cls._calculate_single_planet(data, "Saturn", 6, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
706
700
 
707
701
  # Calculate Uranus
708
702
  if should_calculate("Uranus"):
709
- uranus_deg = swe.calc_ut(julian_day, 7, iflag)[0][0]
710
- data["uranus"] = get_kerykeion_point_from_degree(uranus_deg, "Uranus", point_type=point_type)
711
- data["uranus"].house = get_planet_house(uranus_deg, houses_degree_ut)
712
- data["uranus"].retrograde = swe.calc_ut(julian_day, 7, iflag)[0][3] < 0
713
- calculated_planets.append("Uranus")
703
+ cls._calculate_single_planet(data, "Uranus", 7, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
714
704
 
715
705
  # Calculate Neptune
716
706
  if should_calculate("Neptune"):
717
- neptune_deg = swe.calc_ut(julian_day, 8, iflag)[0][0]
718
- data["neptune"] = get_kerykeion_point_from_degree(neptune_deg, "Neptune", point_type=point_type)
719
- data["neptune"].house = get_planet_house(neptune_deg, houses_degree_ut)
720
- data["neptune"].retrograde = swe.calc_ut(julian_day, 8, iflag)[0][3] < 0
721
- calculated_planets.append("Neptune")
707
+ cls._calculate_single_planet(data, "Neptune", 8, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
722
708
 
723
709
  # Calculate Pluto
724
710
  if should_calculate("Pluto"):
725
- pluto_deg = swe.calc_ut(julian_day, 9, iflag)[0][0]
726
- data["pluto"] = get_kerykeion_point_from_degree(pluto_deg, "Pluto", point_type=point_type)
727
- data["pluto"].house = get_planet_house(pluto_deg, houses_degree_ut)
728
- data["pluto"].retrograde = swe.calc_ut(julian_day, 9, iflag)[0][3] < 0
729
- calculated_planets.append("Pluto")
711
+ cls._calculate_single_planet(data, "Pluto", 9, julian_day, iflag, houses_degree_ut, point_type, calculated_planets, active_points)
730
712
 
731
713
  # ==================
732
714
  # LUNAR NODES
@@ -734,19 +716,11 @@ class AstrologicalSubjectFactory:
734
716
 
735
717
  # Calculate Mean Lunar Node
736
718
  if should_calculate("Mean_Node"):
737
- mean_node_deg = swe.calc_ut(julian_day, 10, iflag)[0][0]
738
- data["mean_node"] = get_kerykeion_point_from_degree(mean_node_deg, "Mean_Node", point_type=point_type)
739
- data["mean_node"].house = get_planet_house(mean_node_deg, houses_degree_ut)
740
- data["mean_node"].retrograde = swe.calc_ut(julian_day, 10, iflag)[0][3] < 0
741
- 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)
742
720
 
743
721
  # Calculate True Lunar Node
744
722
  if should_calculate("True_Node"):
745
- true_node_deg = swe.calc_ut(julian_day, 11, iflag)[0][0]
746
- data["true_node"] = get_kerykeion_point_from_degree(true_node_deg, "True_Node", point_type=point_type)
747
- data["true_node"].house = get_planet_house(true_node_deg, houses_degree_ut)
748
- data["true_node"].retrograde = swe.calc_ut(julian_day, 11, iflag)[0][3] < 0
749
- 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)
750
724
 
751
725
  # Calculate Mean South Node (opposite to Mean North Node)
752
726
  if should_calculate("Mean_South_Node") and "mean_node" in data:
@@ -774,28 +748,17 @@ class AstrologicalSubjectFactory:
774
748
 
775
749
  # Calculate Mean Lilith (Mean Black Moon)
776
750
  if should_calculate("Mean_Lilith"):
777
- try:
778
- mean_lilith_deg = swe.calc_ut(julian_day, 12, iflag)[0][0]
779
- data["mean_lilith"] = get_kerykeion_point_from_degree(mean_lilith_deg, "Mean_Lilith", point_type=point_type)
780
- data["mean_lilith"].house = get_planet_house(mean_lilith_deg, houses_degree_ut)
781
- data["mean_lilith"].retrograde = swe.calc_ut(julian_day, 12, iflag)[0][3] < 0
782
- calculated_planets.append("Mean_Lilith")
783
- except Exception as e:
784
- logging.error(f"Error calculating Mean Lilith: {e}")
785
- active_points.remove("Mean_Lilith")
786
-
751
+ cls._calculate_single_planet(
752
+ data, "Mean_Lilith", 12, julian_day, iflag, houses_degree_ut,
753
+ point_type, calculated_planets, active_points
754
+ )
787
755
 
788
756
  # Calculate True Lilith (Osculating Black Moon)
789
757
  if should_calculate("True_Lilith"):
790
- try:
791
- true_lilith_deg = swe.calc_ut(julian_day, 13, iflag)[0][0]
792
- data["true_lilith"] = get_kerykeion_point_from_degree(true_lilith_deg, "True_Lilith", point_type=point_type)
793
- data["true_lilith"].house = get_planet_house(true_lilith_deg, houses_degree_ut)
794
- data["true_lilith"].retrograde = swe.calc_ut(julian_day, 13, iflag)[0][3] < 0
795
- calculated_planets.append("True_Lilith")
796
- except Exception as e:
797
- logging.error(f"Error calculating True Lilith: {e}")
798
- 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
+ )
799
762
 
800
763
  # ==================
801
764
  # SPECIAL POINTS
@@ -803,39 +766,24 @@ class AstrologicalSubjectFactory:
803
766
 
804
767
  # Calculate Earth - useful for heliocentric charts
805
768
  if should_calculate("Earth"):
806
- try:
807
- earth_deg = swe.calc_ut(julian_day, 14, iflag)[0][0]
808
- data["earth"] = get_kerykeion_point_from_degree(earth_deg, "Earth", point_type=point_type)
809
- data["earth"].house = get_planet_house(earth_deg, houses_degree_ut)
810
- data["earth"].retrograde = swe.calc_ut(julian_day, 14, iflag)[0][3] < 0
811
- calculated_planets.append("Earth")
812
- except Exception as e:
813
- logging.error(f"Error calculating Earth position: {e}")
814
- 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
+ )
815
773
 
816
774
  # Calculate Chiron
817
775
  if should_calculate("Chiron"):
818
- try:
819
- chiron_deg = swe.calc_ut(julian_day, 15, iflag)[0][0]
820
- data["chiron"] = get_kerykeion_point_from_degree(chiron_deg, "Chiron", point_type=point_type)
821
- data["chiron"].house = get_planet_house(chiron_deg, houses_degree_ut)
822
- data["chiron"].retrograde = swe.calc_ut(julian_day, 15, iflag)[0][3] < 0
823
- calculated_planets.append("Chiron")
824
- except Exception as e:
825
- logging.error(f"Error calculating Chiron position: {e}")
826
- 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
+ )
827
780
 
828
781
  # Calculate Pholus
829
782
  if should_calculate("Pholus"):
830
- try:
831
- pholus_deg = swe.calc_ut(julian_day, 16, iflag)[0][0]
832
- data["pholus"] = get_kerykeion_point_from_degree(pholus_deg, "Pholus", point_type=point_type)
833
- data["pholus"].house = get_planet_house(pholus_deg, houses_degree_ut)
834
- data["pholus"].retrograde = swe.calc_ut(julian_day, 16, iflag)[0][3] < 0
835
- calculated_planets.append("Pholus")
836
- except Exception as e:
837
- logging.error(f"Error calculating Pholus position: {e}")
838
- 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
+ )
839
787
 
840
788
  # ==================
841
789
  # ASTEROIDS
@@ -843,51 +791,31 @@ class AstrologicalSubjectFactory:
843
791
 
844
792
  # Calculate Ceres
845
793
  if should_calculate("Ceres"):
846
- try:
847
- ceres_deg = swe.calc_ut(julian_day, 17, iflag)[0][0]
848
- data["ceres"] = get_kerykeion_point_from_degree(ceres_deg, "Ceres", point_type=point_type)
849
- data["ceres"].house = get_planet_house(ceres_deg, houses_degree_ut)
850
- data["ceres"].retrograde = swe.calc_ut(julian_day, 17, iflag)[0][3] < 0
851
- calculated_planets.append("Ceres")
852
- except Exception as e:
853
- logging.error(f"Error calculating Ceres position: {e}")
854
- 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
+ )
855
798
 
856
799
  # Calculate Pallas
857
800
  if should_calculate("Pallas"):
858
- try:
859
- pallas_deg = swe.calc_ut(julian_day, 18, iflag)[0][0]
860
- data["pallas"] = get_kerykeion_point_from_degree(pallas_deg, "Pallas", point_type=point_type)
861
- data["pallas"].house = get_planet_house(pallas_deg, houses_degree_ut)
862
- data["pallas"].retrograde = swe.calc_ut(julian_day, 18, iflag)[0][3] < 0
863
- calculated_planets.append("Pallas")
864
- except Exception as e:
865
- logging.error(f"Error calculating Pallas position: {e}")
866
- 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
+ )
867
805
 
868
806
  # Calculate Juno
869
807
  if should_calculate("Juno"):
870
- try:
871
- juno_deg = swe.calc_ut(julian_day, 19, iflag)[0][0]
872
- data["juno"] = get_kerykeion_point_from_degree(juno_deg, "Juno", point_type=point_type)
873
- data["juno"].house = get_planet_house(juno_deg, houses_degree_ut)
874
- data["juno"].retrograde = swe.calc_ut(julian_day, 19, iflag)[0][3] < 0
875
- calculated_planets.append("Juno")
876
- except Exception as e:
877
- logging.error(f"Error calculating Juno position: {e}")
878
- 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
+ )
879
812
 
880
813
  # Calculate Vesta
881
814
  if should_calculate("Vesta"):
882
- try:
883
- vesta_deg = swe.calc_ut(julian_day, 20, iflag)[0][0]
884
- data["vesta"] = get_kerykeion_point_from_degree(vesta_deg, "Vesta", point_type=point_type)
885
- data["vesta"].house = get_planet_house(vesta_deg, houses_degree_ut)
886
- data["vesta"].retrograde = swe.calc_ut(julian_day, 20, iflag)[0][3] < 0
887
- calculated_planets.append("Vesta")
888
- except Exception as e:
889
- logging.error(f"Error calculating Vesta position: {e}")
890
- 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
+ )
891
819
 
892
820
  # ==================
893
821
  # TRANS-NEPTUNIAN OBJECTS
@@ -896,10 +824,10 @@ class AstrologicalSubjectFactory:
896
824
  # Calculate Eris
897
825
  if should_calculate("Eris"):
898
826
  try:
899
- eris_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 136199, iflag)[0][0]
900
- data["eris"] = get_kerykeion_point_from_degree(eris_deg, "Eris", point_type=point_type)
901
- data["eris"].house = get_planet_house(eris_deg, houses_degree_ut)
902
- 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
903
831
  calculated_planets.append("Eris")
904
832
  except Exception as e:
905
833
  logging.warning(f"Could not calculate Eris position: {e}")
@@ -908,10 +836,10 @@ class AstrologicalSubjectFactory:
908
836
  # Calculate Sedna
909
837
  if should_calculate("Sedna"):
910
838
  try:
911
- sedna_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 90377, iflag)[0][0]
912
- data["sedna"] = get_kerykeion_point_from_degree(sedna_deg, "Sedna", point_type=point_type)
913
- data["sedna"].house = get_planet_house(sedna_deg, houses_degree_ut)
914
- 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
915
843
  calculated_planets.append("Sedna")
916
844
  except Exception as e:
917
845
  logging.warning(f"Could not calculate Sedna position: {e}")
@@ -920,10 +848,10 @@ class AstrologicalSubjectFactory:
920
848
  # Calculate Haumea
921
849
  if should_calculate("Haumea"):
922
850
  try:
923
- haumea_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 136108, iflag)[0][0]
924
- data["haumea"] = get_kerykeion_point_from_degree(haumea_deg, "Haumea", point_type=point_type)
925
- data["haumea"].house = get_planet_house(haumea_deg, houses_degree_ut)
926
- 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
927
855
  calculated_planets.append("Haumea")
928
856
  except Exception as e:
929
857
  logging.warning(f"Could not calculate Haumea position: {e}")
@@ -932,10 +860,10 @@ class AstrologicalSubjectFactory:
932
860
  # Calculate Makemake
933
861
  if should_calculate("Makemake"):
934
862
  try:
935
- makemake_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 136472, iflag)[0][0]
936
- data["makemake"] = get_kerykeion_point_from_degree(makemake_deg, "Makemake", point_type=point_type)
937
- data["makemake"].house = get_planet_house(makemake_deg, houses_degree_ut)
938
- 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
939
867
  calculated_planets.append("Makemake")
940
868
  except Exception as e:
941
869
  logging.warning(f"Could not calculate Makemake position: {e}")
@@ -944,10 +872,10 @@ class AstrologicalSubjectFactory:
944
872
  # Calculate Ixion
945
873
  if should_calculate("Ixion"):
946
874
  try:
947
- ixion_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 28978, iflag)[0][0]
948
- data["ixion"] = get_kerykeion_point_from_degree(ixion_deg, "Ixion", point_type=point_type)
949
- data["ixion"].house = get_planet_house(ixion_deg, houses_degree_ut)
950
- 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
951
879
  calculated_planets.append("Ixion")
952
880
  except Exception as e:
953
881
  logging.warning(f"Could not calculate Ixion position: {e}")
@@ -956,10 +884,10 @@ class AstrologicalSubjectFactory:
956
884
  # Calculate Orcus
957
885
  if should_calculate("Orcus"):
958
886
  try:
959
- orcus_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 90482, iflag)[0][0]
960
- data["orcus"] = get_kerykeion_point_from_degree(orcus_deg, "Orcus", point_type=point_type)
961
- data["orcus"].house = get_planet_house(orcus_deg, houses_degree_ut)
962
- 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
963
891
  calculated_planets.append("Orcus")
964
892
  except Exception as e:
965
893
  logging.warning(f"Could not calculate Orcus position: {e}")
@@ -968,10 +896,10 @@ class AstrologicalSubjectFactory:
968
896
  # Calculate Quaoar
969
897
  if should_calculate("Quaoar"):
970
898
  try:
971
- quaoar_deg = swe.calc_ut(julian_day, swe.AST_OFFSET + 50000, iflag)[0][0]
972
- data["quaoar"] = get_kerykeion_point_from_degree(quaoar_deg, "Quaoar", point_type=point_type)
973
- data["quaoar"].house = get_planet_house(quaoar_deg, houses_degree_ut)
974
- 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
975
903
  calculated_planets.append("Quaoar")
976
904
  except Exception as e:
977
905
  logging.warning(f"Could not calculate Quaoar position: {e}")
@@ -1015,12 +943,34 @@ class AstrologicalSubjectFactory:
1015
943
 
1016
944
  # Calculate Pars Fortunae (Part of Fortune)
1017
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
+
1018
965
  # Check if required points are available
1019
966
  if all(k in data for k in ["ascendant", "sun", "moon"]):
1020
967
  # Different calculation for day and night charts
1021
968
  # Day birth (Sun above horizon): ASC + Moon - Sun
1022
969
  # Night birth (Sun below horizon): ASC + Sun - Moon
1023
- 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
1024
974
 
1025
975
  if is_day_chart:
1026
976
  fortune_deg = math.fmod(data["ascendant"].abs_pos + data["moon"].abs_pos - data["sun"].abs_pos, 360)
@@ -1034,11 +984,33 @@ class AstrologicalSubjectFactory:
1034
984
 
1035
985
  # Calculate Pars Spiritus (Part of Spirit)
1036
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
+
1037
1006
  # Check if required points are available
1038
1007
  if all(k in data for k in ["ascendant", "sun", "moon"]):
1039
1008
  # Day birth: ASC + Sun - Moon
1040
1009
  # Night birth: ASC + Moon - Sun
1041
- 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
1042
1014
 
1043
1015
  if is_day_chart:
1044
1016
  spirit_deg = math.fmod(data["ascendant"].abs_pos + data["sun"].abs_pos - data["moon"].abs_pos, 360)
@@ -1052,19 +1024,56 @@ class AstrologicalSubjectFactory:
1052
1024
 
1053
1025
  # Calculate Pars Amoris (Part of Eros/Love)
1054
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
+
1055
1046
  # Check if required points are available
1056
- if all(k in data for k in ["ascendant", "venus"]):
1047
+ if all(k in data for k in ["ascendant", "venus", "sun"]):
1057
1048
  # ASC + Venus - Sun
1058
- if "sun" in data:
1059
- 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)
1060
1050
 
1061
- data["pars_amoris"] = get_kerykeion_point_from_degree(amoris_deg, "Pars_Amoris", point_type=point_type)
1062
- data["pars_amoris"].house = get_planet_house(amoris_deg, houses_degree_ut)
1063
- data["pars_amoris"].retrograde = False
1064
- 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")
1065
1055
 
1066
1056
  # Calculate Pars Fidei (Part of Faith)
1067
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
+
1068
1077
  # Check if required points are available
1069
1078
  if all(k in data for k in ["ascendant", "jupiter", "saturn"]):
1070
1079
  # ASC + Jupiter - Saturn
@@ -823,12 +823,22 @@ class KerykeionChartSVG:
823
823
 
824
824
  template_dict["bottom_left_0"] = zodiac_info
825
825
  template_dict["bottom_left_1"] = f"{self.language_settings.get('domification', 'Domification')}: {self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
826
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
827
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
826
+
827
+ # Lunar phase information (optional)
828
+ if self.first_obj.lunar_phase is not None:
829
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
830
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
831
+ else:
832
+ template_dict["bottom_left_2"] = ""
833
+ template_dict["bottom_left_3"] = ""
834
+
828
835
  template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
829
836
 
830
837
  # Moon phase section calculations
831
- template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
838
+ if self.first_obj.lunar_phase is not None:
839
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
840
+ else:
841
+ template_dict["makeLunarPhase"] = ""
832
842
 
833
843
  # Houses and planet drawing
834
844
  template_dict["makeMainHousesGrid"] = draw_main_house_grid(
@@ -964,7 +974,10 @@ class KerykeionChartSVG:
964
974
  template_dict["bottom_left_4"] = ""
965
975
 
966
976
  # Moon phase section calculations
967
- template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
977
+ if self.first_obj.lunar_phase is not None:
978
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
979
+ else:
980
+ template_dict["makeLunarPhase"] = ""
968
981
 
969
982
  # Houses and planet drawing
970
983
  template_dict["makeMainHousesGrid"] = draw_main_house_grid(
@@ -1101,12 +1114,22 @@ class KerykeionChartSVG:
1101
1114
 
1102
1115
  template_dict["bottom_left_0"] = zodiac_info
1103
1116
  template_dict["bottom_left_1"] = f"{self.language_settings.get('domification', 'Domification')}: {self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
1104
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.second_obj.lunar_phase.get("moon_phase", "")}' # type: ignore
1105
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.second_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1117
+
1118
+ # Lunar phase information from second object (Transit) (optional)
1119
+ if self.second_obj is not None and hasattr(self.second_obj, 'lunar_phase') and self.second_obj.lunar_phase is not None:
1120
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.second_obj.lunar_phase.get("moon_phase", "")}' # type: ignore
1121
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.second_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.second_obj.lunar_phase.moon_phase_name)}'
1122
+ else:
1123
+ template_dict["bottom_left_2"] = ""
1124
+ template_dict["bottom_left_3"] = ""
1125
+
1106
1126
  template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.second_obj.perspective_type.lower().replace(" ", "_"), self.second_obj.perspective_type)}' # type: ignore
1107
1127
 
1108
- # Moon phase section calculations
1109
- template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1128
+ # Moon phase section calculations - use first_obj for visualization
1129
+ if self.first_obj.lunar_phase is not None:
1130
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1131
+ else:
1132
+ template_dict["makeLunarPhase"] = ""
1110
1133
 
1111
1134
  # Houses and planet drawing
1112
1135
  template_dict["makeMainHousesGrid"] = draw_main_house_grid(
@@ -1379,7 +1402,7 @@ class KerykeionChartSVG:
1379
1402
  template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
1380
1403
 
1381
1404
  # Chart title
1382
- if self.second_obj.return_type == "Solar":
1405
+ if self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Solar":
1383
1406
  template_dict["stringTitle"] = f"{self.first_obj.name} - {self.language_settings.get('solar_return', 'Solar Return')}"
1384
1407
  else:
1385
1408
  template_dict["stringTitle"] = f"{self.first_obj.name} - {self.language_settings.get('lunar_return', 'Lunar Return')}"
@@ -1394,7 +1417,7 @@ class KerykeionChartSVG:
1394
1417
  return_latitude_string = convert_latitude_coordinate_to_string(self.second_obj.lat, self.language_settings["north"], self.language_settings["south"]) # type: ignore
1395
1418
  return_longitude_string = convert_longitude_coordinate_to_string(self.second_obj.lng, self.language_settings["east"], self.language_settings["west"]) # type: ignore
1396
1419
 
1397
- if self.second_obj.return_type == "Solar":
1420
+ if self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Solar":
1398
1421
  template_dict["top_left_0"] = f"{self.language_settings.get('solar_return', 'Solar Return')}:"
1399
1422
  else:
1400
1423
  template_dict["top_left_0"] = f"{self.language_settings.get('lunar_return', 'Lunar Return')}:"
@@ -1414,12 +1437,22 @@ class KerykeionChartSVG:
1414
1437
 
1415
1438
  template_dict["bottom_left_0"] = zodiac_info
1416
1439
  template_dict["bottom_left_1"] = f"{self.language_settings.get('domification', 'Domification')}: {self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
1417
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
1418
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1440
+
1441
+ # Lunar phase information (optional)
1442
+ if self.first_obj.lunar_phase is not None:
1443
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
1444
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1445
+ else:
1446
+ template_dict["bottom_left_2"] = ""
1447
+ template_dict["bottom_left_3"] = ""
1448
+
1419
1449
  template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
1420
1450
 
1421
1451
  # Moon phase section calculations
1422
- template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1452
+ if self.first_obj.lunar_phase is not None:
1453
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1454
+ else:
1455
+ template_dict["makeLunarPhase"] = ""
1423
1456
 
1424
1457
  # Houses and planet drawing
1425
1458
  template_dict["makeMainHousesGrid"] = draw_main_house_grid(
@@ -1461,7 +1494,7 @@ class KerykeionChartSVG:
1461
1494
  )
1462
1495
 
1463
1496
  # Planet grid
1464
- if self.second_obj.return_type == "Solar":
1497
+ if self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Solar":
1465
1498
  first_return_grid_title = f"{self.first_obj.name} ({self.language_settings.get('inner_wheel', 'Inner Wheel')})"
1466
1499
  second_return_grid_title = f"{self.language_settings.get('solar_return', 'Solar Return')} ({self.language_settings.get('outer_wheel', 'Outer Wheel')})"
1467
1500
  else:
@@ -1481,7 +1514,7 @@ class KerykeionChartSVG:
1481
1514
  second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
1482
1515
  chart_type=self.chart_type,
1483
1516
  text_color=self.chart_colors_settings["paper_0"],
1484
- celestial_point_language=self.language_settings["celestial_points"]
1517
+ celestial_point_language=self.language_settings["celestial_points"],
1485
1518
  )
1486
1519
 
1487
1520
  house_comparison_factory = HouseComparisonFactory(
@@ -1557,7 +1590,7 @@ class KerykeionChartSVG:
1557
1590
  template_dict["top_left_3"] = f"{self.language_settings['latitude']}: {latitude_string}"
1558
1591
  template_dict["top_left_4"] = f"{self.language_settings['longitude']}: {longitude_string}"
1559
1592
 
1560
- if self.first_obj.return_type == "Solar":
1593
+ if hasattr(self.first_obj, 'return_type') and self.first_obj.return_type == "Solar":
1561
1594
  template_dict["top_left_5"] = f"{self.language_settings['type']}: {self.language_settings.get('solar_return', 'Solar Return')}"
1562
1595
  else:
1563
1596
  template_dict["top_left_5"] = f"{self.language_settings['type']}: {self.language_settings.get('lunar_return', 'Lunar Return')}"
@@ -1572,12 +1605,22 @@ class KerykeionChartSVG:
1572
1605
 
1573
1606
  template_dict["bottom_left_0"] = zodiac_info
1574
1607
  template_dict["bottom_left_1"] = f"{self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self.language_settings.get('houses', 'Houses')}"
1575
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
1576
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1608
+
1609
+ # Lunar phase information (optional)
1610
+ if self.first_obj.lunar_phase is not None:
1611
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
1612
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1613
+ else:
1614
+ template_dict["bottom_left_2"] = ""
1615
+ template_dict["bottom_left_3"] = ""
1616
+
1577
1617
  template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
1578
1618
 
1579
1619
  # Moon phase section calculations
1580
- template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1620
+ if self.first_obj.lunar_phase is not None:
1621
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1622
+ else:
1623
+ template_dict["makeLunarPhase"] = ""
1581
1624
 
1582
1625
  # Houses and planet drawing
1583
1626
  template_dict["makeMainHousesGrid"] = draw_main_house_grid(
@@ -1680,9 +1723,9 @@ class KerykeionChartSVG:
1680
1723
 
1681
1724
  self.template = self.makeTemplate(minify, remove_css_variables)
1682
1725
 
1683
- if self.chart_type == "Return" and self.second_obj.return_type == "Lunar":
1726
+ if self.chart_type == "Return" and self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Lunar":
1684
1727
  chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart - Lunar Return.svg"
1685
- elif self.chart_type == "Return" and self.second_obj.return_type == "Solar":
1728
+ elif self.chart_type == "Return" and self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Solar":
1686
1729
  chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart - Solar Return.svg"
1687
1730
  else:
1688
1731
  chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart.svg"
@@ -1835,7 +1878,7 @@ if __name__ == "__main__":
1835
1878
  from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
1836
1879
 
1837
1880
  ACTIVE_PLANETS: list[AstrologicalPoint] = [
1838
- "Sun", "Moon", "Pars_Fortunae"
1881
+ "Sun", "Moon", "Pars_Fortunae", "Mercury", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "Chiron", "True_Node"
1839
1882
  ]
1840
1883
 
1841
1884
  setup_logging(level="info")
@@ -100,9 +100,9 @@ class AstrologicalBaseModel(SubscriptableBaseModel):
100
100
  perspective_type: PerspectiveType
101
101
 
102
102
  # Common celestial points
103
- # Main planets
104
- sun: KerykeionPointModel
105
- moon: KerykeionPointModel
103
+ # Main planets (all optional to support selective calculations)
104
+ sun: Optional[KerykeionPointModel] = None
105
+ moon: Optional[KerykeionPointModel] = None
106
106
  mercury: Optional[KerykeionPointModel] = None
107
107
  venus: Optional[KerykeionPointModel] = None
108
108
  mars: Optional[KerykeionPointModel] = None
@@ -180,8 +180,8 @@ class AstrologicalBaseModel(SubscriptableBaseModel):
180
180
  houses_names_list: List[Houses] = Field(description="Ordered list of houses names")
181
181
  active_points: List[AstrologicalPoint] = Field(description="List of active points in the chart or aspects calculations.")
182
182
 
183
- # Common lunar phase data
184
- lunar_phase: LunarPhaseModel = Field(description="Lunar phase model")
183
+ # Common lunar phase data (optional)
184
+ lunar_phase: Optional[LunarPhaseModel] = Field(default=None, description="Lunar phase model")
185
185
 
186
186
 
187
187
  class AstrologicalSubjectModel(AstrologicalBaseModel):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "kerykeion"
3
- version = "5.0.0a5"
3
+ version = "5.0.0a6"
4
4
  authors = ["Giacomo Battaglia <kerykeion.astrology@gmail.com>"]
5
5
  description = "A python library for astrology."
6
6
  license = "AGPL-3.0"
File without changes
File without changes