kerykeion 5.0.1__py3-none-any.whl → 5.1.8__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.
- kerykeion/aspects/aspects_factory.py +11 -12
- kerykeion/aspects/aspects_utils.py +2 -2
- kerykeion/astrological_subject_factory.py +18 -14
- kerykeion/backword.py +52 -5
- kerykeion/chart_data_factory.py +9 -6
- kerykeion/charts/chart_drawer.py +96 -36
- kerykeion/charts/charts_utils.py +98 -71
- kerykeion/ephemeris_data_factory.py +13 -4
- kerykeion/fetch_geonames.py +51 -13
- kerykeion/relationship_score_factory.py +1 -1
- kerykeion/report.py +7 -9
- kerykeion/schemas/kr_literals.py +1 -1
- kerykeion/schemas/kr_models.py +8 -10
- kerykeion/schemas/settings_models.py +8 -0
- kerykeion/settings/kerykeion_settings.py +1 -1
- kerykeion/settings/translation_strings.py +20 -0
- kerykeion/transits_time_range_factory.py +1 -1
- kerykeion/utilities.py +121 -121
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/METADATA +755 -138
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/RECORD +22 -22
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -98,7 +98,7 @@ class AspectsFactory:
|
|
|
98
98
|
Example:
|
|
99
99
|
>>> johnny = AstrologicalSubjectFactory.from_birth_data("Johnny", 1963, 6, 9, 0, 0, "Owensboro", "US")
|
|
100
100
|
>>> chart_aspects = AspectsFactory.single_chart_aspects(johnny)
|
|
101
|
-
>>> print(f"Found {len(chart_aspects.
|
|
101
|
+
>>> print(f"Found {len(chart_aspects.aspects)} aspects")
|
|
102
102
|
"""
|
|
103
103
|
# Initialize settings and configurations
|
|
104
104
|
celestial_points = DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
@@ -162,7 +162,7 @@ class AspectsFactory:
|
|
|
162
162
|
>>> john = AstrologicalSubjectFactory.from_birth_data("John", 1990, 1, 1, 12, 0, "London", "GB")
|
|
163
163
|
>>> jane = AstrologicalSubjectFactory.from_birth_data("Jane", 1992, 6, 15, 14, 30, "Paris", "FR")
|
|
164
164
|
>>> synastry = AspectsFactory.dual_chart_aspects(john, jane)
|
|
165
|
-
>>> print(f"Found {len(synastry.
|
|
165
|
+
>>> print(f"Found {len(synastry.aspects)} aspects")
|
|
166
166
|
"""
|
|
167
167
|
# Initialize settings and configurations
|
|
168
168
|
celestial_points = DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
@@ -208,12 +208,12 @@ class AspectsFactory:
|
|
|
208
208
|
Create the complete single chart aspects model with all calculations.
|
|
209
209
|
|
|
210
210
|
Returns:
|
|
211
|
-
SingleChartAspectsModel containing
|
|
211
|
+
SingleChartAspectsModel containing filtered aspects data
|
|
212
212
|
"""
|
|
213
213
|
all_aspects = AspectsFactory._calculate_single_chart_aspects(
|
|
214
214
|
subject, active_points_resolved, active_aspects_resolved, aspects_settings, celestial_points
|
|
215
215
|
)
|
|
216
|
-
|
|
216
|
+
filtered_aspects = AspectsFactory._filter_relevant_aspects(
|
|
217
217
|
all_aspects,
|
|
218
218
|
axis_orb_limit,
|
|
219
219
|
apply_axis_orb_filter=axis_orb_limit is not None,
|
|
@@ -221,8 +221,7 @@ class AspectsFactory:
|
|
|
221
221
|
|
|
222
222
|
return SingleChartAspectsModel(
|
|
223
223
|
subject=subject,
|
|
224
|
-
|
|
225
|
-
relevant_aspects=relevant_aspects,
|
|
224
|
+
aspects=filtered_aspects,
|
|
226
225
|
active_points=active_points_resolved,
|
|
227
226
|
active_aspects=active_aspects_resolved,
|
|
228
227
|
)
|
|
@@ -250,13 +249,13 @@ class AspectsFactory:
|
|
|
250
249
|
celestial_points: Celestial points configuration
|
|
251
250
|
|
|
252
251
|
Returns:
|
|
253
|
-
DualChartAspectsModel: Complete model containing
|
|
252
|
+
DualChartAspectsModel: Complete model containing filtered aspects data
|
|
254
253
|
"""
|
|
255
254
|
all_aspects = AspectsFactory._calculate_dual_chart_aspects(
|
|
256
255
|
first_subject, second_subject, active_points_resolved, active_aspects_resolved,
|
|
257
256
|
aspects_settings, celestial_points
|
|
258
257
|
)
|
|
259
|
-
|
|
258
|
+
filtered_aspects = AspectsFactory._filter_relevant_aspects(
|
|
260
259
|
all_aspects,
|
|
261
260
|
axis_orb_limit,
|
|
262
261
|
apply_axis_orb_filter=False,
|
|
@@ -265,8 +264,7 @@ class AspectsFactory:
|
|
|
265
264
|
return DualChartAspectsModel(
|
|
266
265
|
first_subject=first_subject,
|
|
267
266
|
second_subject=second_subject,
|
|
268
|
-
|
|
269
|
-
relevant_aspects=relevant_aspects,
|
|
267
|
+
aspects=filtered_aspects,
|
|
270
268
|
active_points=active_points_resolved,
|
|
271
269
|
active_aspects=active_aspects_resolved,
|
|
272
270
|
)
|
|
@@ -560,10 +558,11 @@ if __name__ == "__main__":
|
|
|
560
558
|
# Test single chart aspects (replaces natal aspects)
|
|
561
559
|
johnny = AstrologicalSubjectFactory.from_birth_data("Johnny Depp", 1963, 6, 9, 0, 0, city="Owensboro", nation="US")
|
|
562
560
|
single_chart_aspects = AspectsFactory.single_chart_aspects(johnny)
|
|
563
|
-
print(f"Single chart aspects
|
|
561
|
+
print(f"Single chart aspects: {len(single_chart_aspects.aspects)}")
|
|
564
562
|
|
|
565
563
|
# Test dual chart aspects (replaces synastry aspects)
|
|
566
564
|
john = AstrologicalSubjectFactory.from_birth_data("John", 1940, 10, 9, 10, 30, "Liverpool", "GB")
|
|
567
565
|
yoko = AstrologicalSubjectFactory.from_birth_data("Yoko", 1933, 2, 18, 10, 30, "Tokyo", "JP")
|
|
568
566
|
dual_chart_aspects = AspectsFactory.dual_chart_aspects(john, yoko)
|
|
569
|
-
print(f"Dual chart aspects
|
|
567
|
+
print(f"Dual chart aspects: {len(dual_chart_aspects.aspects)}")
|
|
568
|
+
|
|
@@ -49,8 +49,8 @@ def get_aspect_from_two_points(
|
|
|
49
49
|
return {
|
|
50
50
|
"verdict": verdict,
|
|
51
51
|
"name": name,
|
|
52
|
-
"orbit": distance - aspect_degrees,
|
|
53
|
-
"distance": distance - aspect_degrees,
|
|
52
|
+
"orbit": abs(distance - aspect_degrees),
|
|
53
|
+
"distance": abs(distance - aspect_degrees),
|
|
54
54
|
"aspect_degrees": aspect_degrees,
|
|
55
55
|
"diff": diff,
|
|
56
56
|
}
|
|
@@ -59,7 +59,8 @@ from kerykeion.utilities import (
|
|
|
59
59
|
check_and_adjust_polar_latitude,
|
|
60
60
|
calculate_moon_phase,
|
|
61
61
|
datetime_to_julian,
|
|
62
|
-
get_house_number
|
|
62
|
+
get_house_number,
|
|
63
|
+
normalize_zodiac_type,
|
|
63
64
|
)
|
|
64
65
|
from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS
|
|
65
66
|
|
|
@@ -67,7 +68,7 @@ from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS
|
|
|
67
68
|
DEFAULT_GEONAMES_USERNAME = "century.boy"
|
|
68
69
|
DEFAULT_SIDEREAL_MODE: SiderealMode = "FAGAN_BRADLEY"
|
|
69
70
|
DEFAULT_HOUSES_SYSTEM_IDENTIFIER: HousesSystemIdentifier = "P"
|
|
70
|
-
DEFAULT_ZODIAC_TYPE: ZodiacType = "
|
|
71
|
+
DEFAULT_ZODIAC_TYPE: ZodiacType = "Tropical"
|
|
71
72
|
DEFAULT_PERSPECTIVE_TYPE: PerspectiveType = "Apparent Geocentric"
|
|
72
73
|
DEFAULT_GEONAMES_CACHE_EXPIRE_AFTER_DAYS = 30
|
|
73
74
|
|
|
@@ -148,8 +149,8 @@ class ChartConfiguration:
|
|
|
148
149
|
combinations.
|
|
149
150
|
|
|
150
151
|
Attributes:
|
|
151
|
-
zodiac_type (ZodiacType): The zodiac system to use ('
|
|
152
|
-
Defaults to '
|
|
152
|
+
zodiac_type (ZodiacType): The zodiac system to use ('Tropical' or 'Sidereal').
|
|
153
|
+
Defaults to 'Tropical'.
|
|
153
154
|
sidereal_mode (Optional[SiderealMode]): The sidereal calculation mode when using
|
|
154
155
|
sidereal zodiac. Only required/used when zodiac_type is 'Sidereal'.
|
|
155
156
|
Defaults to None (auto-set to FAGAN_BRADLEY for sidereal).
|
|
@@ -198,14 +199,17 @@ class ChartConfiguration:
|
|
|
198
199
|
- Logs informational message when setting default sidereal mode
|
|
199
200
|
"""
|
|
200
201
|
# Validate zodiac type
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
202
|
+
try:
|
|
203
|
+
normalized_zodiac_type = normalize_zodiac_type(self.zodiac_type)
|
|
204
|
+
except ValueError as exc:
|
|
205
|
+
raise KerykeionException(str(exc)) from exc
|
|
206
|
+
else:
|
|
207
|
+
if normalized_zodiac_type != self.zodiac_type:
|
|
208
|
+
self.zodiac_type = normalized_zodiac_type
|
|
205
209
|
|
|
206
210
|
# Validate sidereal mode settings
|
|
207
|
-
if self.sidereal_mode and self.zodiac_type == "
|
|
208
|
-
raise KerykeionException("You can't set a sidereal mode with a
|
|
211
|
+
if self.sidereal_mode and self.zodiac_type == "Tropical":
|
|
212
|
+
raise KerykeionException("You can't set a sidereal mode with a Tropical zodiac type!")
|
|
209
213
|
|
|
210
214
|
if self.zodiac_type == "Sidereal":
|
|
211
215
|
if not self.sidereal_mode:
|
|
@@ -475,8 +479,8 @@ class AstrologicalSubjectFactory:
|
|
|
475
479
|
online location lookup. Get one free at geonames.org.
|
|
476
480
|
online (bool, optional): Whether to fetch location data online. If False,
|
|
477
481
|
lng, lat, and tz_str must be provided. Defaults to True.
|
|
478
|
-
zodiac_type (ZodiacType, optional): Zodiac system - '
|
|
479
|
-
Defaults to '
|
|
482
|
+
zodiac_type (ZodiacType, optional): Zodiac system - 'Tropical' or 'Sidereal'.
|
|
483
|
+
Defaults to 'Tropical'.
|
|
480
484
|
sidereal_mode (SiderealMode, optional): Sidereal calculation mode (e.g.,
|
|
481
485
|
'FAGAN_BRADLEY', 'LAHIRI'). Only used with zodiac_type='Sidereal'.
|
|
482
486
|
houses_system_identifier (HousesSystemIdentifier, optional): House system
|
|
@@ -726,7 +730,7 @@ class AstrologicalSubjectFactory:
|
|
|
726
730
|
or as fallback. Defaults to 51.5074 (Greenwich).
|
|
727
731
|
geonames_username (str, optional): GeoNames API username. Required when
|
|
728
732
|
online=True. Defaults to DEFAULT_GEONAMES_USERNAME.
|
|
729
|
-
zodiac_type (ZodiacType, optional): Zodiac system. Defaults to '
|
|
733
|
+
zodiac_type (ZodiacType, optional): Zodiac system. Defaults to 'Tropical'.
|
|
730
734
|
sidereal_mode (SiderealMode, optional): Sidereal mode when zodiac_type
|
|
731
735
|
is 'Sidereal'. Defaults to None.
|
|
732
736
|
houses_system_identifier (HousesSystemIdentifier, optional): House system.
|
|
@@ -866,7 +870,7 @@ class AstrologicalSubjectFactory:
|
|
|
866
870
|
online (bool, optional): Whether to fetch location data online.
|
|
867
871
|
Defaults to True.
|
|
868
872
|
zodiac_type (ZodiacType, optional): Zodiac system to use.
|
|
869
|
-
Defaults to '
|
|
873
|
+
Defaults to 'Tropical'.
|
|
870
874
|
sidereal_mode (SiderealMode, optional): Sidereal calculation mode.
|
|
871
875
|
Only used when zodiac_type is 'Sidereal'. Defaults to None.
|
|
872
876
|
houses_system_identifier (HousesSystemIdentifier, optional): House
|
kerykeion/backword.py
CHANGED
|
@@ -36,6 +36,7 @@ from .chart_data_factory import ChartDataFactory
|
|
|
36
36
|
from .charts.chart_drawer import ChartDrawer
|
|
37
37
|
from .aspects import AspectsFactory
|
|
38
38
|
from .settings.config_constants import DEFAULT_ACTIVE_POINTS, DEFAULT_ACTIVE_ASPECTS
|
|
39
|
+
from .utilities import normalize_zodiac_type
|
|
39
40
|
from .schemas.kr_models import (
|
|
40
41
|
AstrologicalSubjectModel,
|
|
41
42
|
CompositeSubjectModel,
|
|
@@ -74,6 +75,42 @@ LEGACY_NODE_NAMES_MAP = {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def _normalize_zodiac_type_with_warning(zodiac_type: Optional[Union[str, ZodiacType]]) -> Optional[ZodiacType]:
|
|
79
|
+
"""Normalize legacy zodiac type values with deprecation warning.
|
|
80
|
+
|
|
81
|
+
Wraps the utilities.normalize_zodiac_type function and adds a deprecation
|
|
82
|
+
warning for legacy formats like "tropic" or case-insensitive variants.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
zodiac_type: Input zodiac type (may be legacy format)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Normalized ZodiacType or None if input was None
|
|
89
|
+
"""
|
|
90
|
+
if zodiac_type is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
zodiac_str = str(zodiac_type)
|
|
94
|
+
|
|
95
|
+
# Check if this is a legacy format (case-insensitive "tropic" or non-canonical case)
|
|
96
|
+
zodiac_lower = zodiac_str.lower()
|
|
97
|
+
if zodiac_lower in ("tropic", "tropical", "sidereal") and zodiac_str not in ("Tropical", "Sidereal"):
|
|
98
|
+
# Normalize using the utilities function
|
|
99
|
+
normalized = normalize_zodiac_type(zodiac_str)
|
|
100
|
+
|
|
101
|
+
# Emit deprecation warning for legacy usage
|
|
102
|
+
warnings.warn(
|
|
103
|
+
f"Zodiac type '{zodiac_str}' is deprecated in Kerykeion v5. "
|
|
104
|
+
f"Use '{normalized}' instead.",
|
|
105
|
+
DeprecationWarning,
|
|
106
|
+
stacklevel=4,
|
|
107
|
+
)
|
|
108
|
+
return normalized
|
|
109
|
+
|
|
110
|
+
# Already in correct format or will be normalized by utilities function
|
|
111
|
+
return cast(ZodiacType, normalize_zodiac_type(zodiac_str))
|
|
112
|
+
|
|
113
|
+
|
|
77
114
|
def _normalize_active_points(points: Optional[Iterable[Union[str, AstrologicalPoint]]]) -> Optional[List[AstrologicalPoint]]:
|
|
78
115
|
"""Best-effort normalization of legacy string active points list.
|
|
79
116
|
|
|
@@ -159,7 +196,10 @@ class AstrologicalSubject:
|
|
|
159
196
|
stacklevel=2,
|
|
160
197
|
)
|
|
161
198
|
|
|
199
|
+
# Normalize legacy zodiac type values
|
|
200
|
+
zodiac_type = _normalize_zodiac_type_with_warning(zodiac_type)
|
|
162
201
|
zodiac_type = DEFAULT_ZODIAC_TYPE if zodiac_type is None else zodiac_type
|
|
202
|
+
|
|
163
203
|
houses_system_identifier = (
|
|
164
204
|
DEFAULT_HOUSES_SYSTEM_IDENTIFIER if houses_system_identifier is None else houses_system_identifier
|
|
165
205
|
)
|
|
@@ -352,6 +392,9 @@ class AstrologicalSubject:
|
|
|
352
392
|
if online and resolved_geonames == DEFAULT_GEONAMES_USERNAME:
|
|
353
393
|
warnings.warn(GEONAMES_DEFAULT_USERNAME_WARNING, UserWarning, stacklevel=2)
|
|
354
394
|
|
|
395
|
+
# Normalize legacy zodiac type values
|
|
396
|
+
normalized_zodiac_type = _normalize_zodiac_type_with_warning(zodiac_type)
|
|
397
|
+
|
|
355
398
|
model = AstrologicalSubjectFactory.from_iso_utc_time(
|
|
356
399
|
name=name,
|
|
357
400
|
iso_utc_time=iso_utc_time,
|
|
@@ -362,7 +405,7 @@ class AstrologicalSubject:
|
|
|
362
405
|
lng=float(lng),
|
|
363
406
|
lat=float(lat),
|
|
364
407
|
geonames_username=resolved_geonames,
|
|
365
|
-
zodiac_type=(
|
|
408
|
+
zodiac_type=(normalized_zodiac_type or DEFAULT_ZODIAC_TYPE), # type: ignore[arg-type]
|
|
366
409
|
sidereal_mode=sidereal_mode,
|
|
367
410
|
houses_system_identifier=(houses_system_identifier or DEFAULT_HOUSES_SYSTEM_IDENTIFIER), # type: ignore[arg-type]
|
|
368
411
|
perspective_type=(perspective_type or DEFAULT_PERSPECTIVE_TYPE), # type: ignore[arg-type]
|
|
@@ -676,14 +719,16 @@ class NatalAspects:
|
|
|
676
719
|
|
|
677
720
|
@cached_property
|
|
678
721
|
def all_aspects(self):
|
|
722
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
679
723
|
if self._all_aspects_cache is None:
|
|
680
|
-
self._all_aspects_cache = list(self._build_aspects_model().
|
|
724
|
+
self._all_aspects_cache = list(self._build_aspects_model().aspects)
|
|
681
725
|
return self._all_aspects_cache
|
|
682
726
|
|
|
683
727
|
@cached_property
|
|
684
728
|
def relevant_aspects(self):
|
|
729
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
685
730
|
if self._relevant_aspects_cache is None:
|
|
686
|
-
self._relevant_aspects_cache = list(self._build_aspects_model().
|
|
731
|
+
self._relevant_aspects_cache = list(self._build_aspects_model().aspects)
|
|
687
732
|
return self._relevant_aspects_cache
|
|
688
733
|
|
|
689
734
|
# ---------------------------------------------------------------------------
|
|
@@ -748,14 +793,16 @@ class SynastryAspects:
|
|
|
748
793
|
|
|
749
794
|
@cached_property
|
|
750
795
|
def all_aspects(self):
|
|
796
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
751
797
|
if self._all_aspects_cache is None:
|
|
752
|
-
self._all_aspects_cache = list(self._build_dual_model().
|
|
798
|
+
self._all_aspects_cache = list(self._build_dual_model().aspects)
|
|
753
799
|
return self._all_aspects_cache
|
|
754
800
|
|
|
755
801
|
@cached_property
|
|
756
802
|
def relevant_aspects(self):
|
|
803
|
+
"""Legacy property - returns the same as aspects for backwards compatibility."""
|
|
757
804
|
if self._relevant_aspects_cache is None:
|
|
758
|
-
self._relevant_aspects_cache = list(self._build_dual_model().
|
|
805
|
+
self._relevant_aspects_cache = list(self._build_dual_model().aspects)
|
|
759
806
|
return self._relevant_aspects_cache
|
|
760
807
|
|
|
761
808
|
def get_relevant_aspects(self):
|
kerykeion/chart_data_factory.py
CHANGED
|
@@ -159,10 +159,10 @@ class ChartDataFactory:
|
|
|
159
159
|
)
|
|
160
160
|
|
|
161
161
|
# Calculate aspects based on chart type
|
|
162
|
-
|
|
162
|
+
aspects_model: Union[SingleChartAspectsModel, DualChartAspectsModel]
|
|
163
163
|
if chart_type in ["Natal", "Composite", "SingleReturnChart"]:
|
|
164
164
|
# Single chart aspects
|
|
165
|
-
|
|
165
|
+
aspects_model = AspectsFactory.single_chart_aspects(
|
|
166
166
|
first_subject,
|
|
167
167
|
active_points=effective_active_points,
|
|
168
168
|
active_aspects=active_aspects,
|
|
@@ -172,7 +172,7 @@ class ChartDataFactory:
|
|
|
172
172
|
# Dual chart aspects - second_subject is guaranteed to exist here due to validation above
|
|
173
173
|
if second_subject is None:
|
|
174
174
|
raise KerykeionException(f"Second subject is required for {chart_type} charts.")
|
|
175
|
-
|
|
175
|
+
aspects_model = AspectsFactory.dual_chart_aspects(
|
|
176
176
|
first_subject,
|
|
177
177
|
second_subject,
|
|
178
178
|
active_points=effective_active_points,
|
|
@@ -301,7 +301,7 @@ class ChartDataFactory:
|
|
|
301
301
|
return SingleChartDataModel(
|
|
302
302
|
chart_type=cast(Literal["Natal", "Composite", "SingleReturnChart"], chart_type),
|
|
303
303
|
subject=first_subject,
|
|
304
|
-
aspects=cast(SingleChartAspectsModel, aspects
|
|
304
|
+
aspects=cast(SingleChartAspectsModel, aspects_model).aspects,
|
|
305
305
|
element_distribution=element_distribution,
|
|
306
306
|
quality_distribution=quality_distribution,
|
|
307
307
|
active_points=effective_active_points,
|
|
@@ -315,7 +315,7 @@ class ChartDataFactory:
|
|
|
315
315
|
chart_type=cast(Literal["Transit", "Synastry", "DualReturnChart"], chart_type),
|
|
316
316
|
first_subject=first_subject,
|
|
317
317
|
second_subject=second_subject,
|
|
318
|
-
aspects=cast(DualChartAspectsModel, aspects
|
|
318
|
+
aspects=cast(DualChartAspectsModel, aspects_model).aspects,
|
|
319
319
|
house_comparison=house_comparison,
|
|
320
320
|
relationship_score=relationship_score,
|
|
321
321
|
element_distribution=element_distribution,
|
|
@@ -542,8 +542,11 @@ if __name__ == "__main__":
|
|
|
542
542
|
|
|
543
543
|
print(f"Chart Type: {natal_data.chart_type}")
|
|
544
544
|
print(f"Active Points: {len(natal_data.active_points)}")
|
|
545
|
-
print(f"Aspects: {len(natal_data.aspects
|
|
545
|
+
print(f"Aspects: {len(natal_data.aspects)}")
|
|
546
546
|
print(f"Fire: {natal_data.element_distribution.fire_percentage}%")
|
|
547
547
|
print(f"Earth: {natal_data.element_distribution.earth_percentage}%")
|
|
548
548
|
print(f"Air: {natal_data.element_distribution.air_percentage}%")
|
|
549
549
|
print(f"Water: {natal_data.element_distribution.water_percentage}%")
|
|
550
|
+
|
|
551
|
+
print("\n---\n")
|
|
552
|
+
print(natal_data.model_dump_json(indent=4))
|
kerykeion/charts/chart_drawer.py
CHANGED
|
@@ -78,6 +78,9 @@ from kerykeion.settings.chart_defaults import (
|
|
|
78
78
|
from typing import List, Literal
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
|
|
81
84
|
class ChartDrawer:
|
|
82
85
|
"""
|
|
83
86
|
ChartDrawer generates astrological chart visualizations as SVG files from pre-computed chart data.
|
|
@@ -315,7 +318,7 @@ class ChartDrawer:
|
|
|
315
318
|
|
|
316
319
|
active_points_count = len(self.available_planets_setting)
|
|
317
320
|
if active_points_count > 24:
|
|
318
|
-
|
|
321
|
+
logger.warning(
|
|
319
322
|
"ChartDrawer detected %s active celestial points; rendering may look crowded beyond 24.",
|
|
320
323
|
active_points_count,
|
|
321
324
|
)
|
|
@@ -343,7 +346,7 @@ class ChartDrawer:
|
|
|
343
346
|
# --- NATAL CHART SETUP ---
|
|
344
347
|
|
|
345
348
|
# Extract aspects from pre-computed chart data
|
|
346
|
-
self.aspects_list = chart_data.aspects
|
|
349
|
+
self.aspects_list = chart_data.aspects
|
|
347
350
|
|
|
348
351
|
# Screen size
|
|
349
352
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -366,7 +369,7 @@ class ChartDrawer:
|
|
|
366
369
|
# --- COMPOSITE CHART SETUP ---
|
|
367
370
|
|
|
368
371
|
# Extract aspects from pre-computed chart data
|
|
369
|
-
self.aspects_list = chart_data.aspects
|
|
372
|
+
self.aspects_list = chart_data.aspects
|
|
370
373
|
|
|
371
374
|
# Screen size
|
|
372
375
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -384,7 +387,7 @@ class ChartDrawer:
|
|
|
384
387
|
# --- TRANSIT CHART SETUP ---
|
|
385
388
|
|
|
386
389
|
# Extract aspects from pre-computed chart data
|
|
387
|
-
self.aspects_list = chart_data.aspects
|
|
390
|
+
self.aspects_list = chart_data.aspects
|
|
388
391
|
|
|
389
392
|
# Screen size
|
|
390
393
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -405,7 +408,7 @@ class ChartDrawer:
|
|
|
405
408
|
# --- SYNASTRY CHART SETUP ---
|
|
406
409
|
|
|
407
410
|
# Extract aspects from pre-computed chart data
|
|
408
|
-
self.aspects_list = chart_data.aspects
|
|
411
|
+
self.aspects_list = chart_data.aspects
|
|
409
412
|
|
|
410
413
|
# Screen size
|
|
411
414
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -423,7 +426,7 @@ class ChartDrawer:
|
|
|
423
426
|
# --- RETURN CHART SETUP ---
|
|
424
427
|
|
|
425
428
|
# Extract aspects from pre-computed chart data
|
|
426
|
-
self.aspects_list = chart_data.aspects
|
|
429
|
+
self.aspects_list = chart_data.aspects
|
|
427
430
|
|
|
428
431
|
# Screen size
|
|
429
432
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -441,7 +444,7 @@ class ChartDrawer:
|
|
|
441
444
|
# --- SINGLE WHEEL RETURN CHART SETUP ---
|
|
442
445
|
|
|
443
446
|
# Extract aspects from pre-computed chart data
|
|
444
|
-
self.aspects_list = chart_data.aspects
|
|
447
|
+
self.aspects_list = chart_data.aspects
|
|
445
448
|
|
|
446
449
|
# Screen size
|
|
447
450
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -856,7 +859,7 @@ class ChartDrawer:
|
|
|
856
859
|
try:
|
|
857
860
|
required_width = self._estimate_required_width_full()
|
|
858
861
|
except Exception as e:
|
|
859
|
-
|
|
862
|
+
logger.debug("Auto-size width calculation failed: %s", e)
|
|
860
863
|
return
|
|
861
864
|
|
|
862
865
|
minimum_width = self._minimum_width_for_chart_type()
|
|
@@ -1071,24 +1074,28 @@ class ChartDrawer:
|
|
|
1071
1074
|
return f"{synastry_label}: {name1} {and_word} {name2}"
|
|
1072
1075
|
|
|
1073
1076
|
elif self.chart_type == "DualReturnChart":
|
|
1074
|
-
|
|
1077
|
+
return_datetime = datetime.fromisoformat(self.second_obj.iso_formatted_local_datetime) # type: ignore
|
|
1078
|
+
year = return_datetime.year
|
|
1079
|
+
month_year = return_datetime.strftime("%m/%Y")
|
|
1075
1080
|
truncated_name = self._truncate_name(self.first_obj.name)
|
|
1076
1081
|
if self.second_obj is not None and isinstance(self.second_obj, PlanetReturnModel) and self.second_obj.return_type == "Solar":
|
|
1077
1082
|
solar_label = self._translate("solar_return", "Solar")
|
|
1078
1083
|
return f"{truncated_name} - {solar_label} {year}"
|
|
1079
1084
|
else:
|
|
1080
1085
|
lunar_label = self._translate("lunar_return", "Lunar")
|
|
1081
|
-
return f"{truncated_name} - {lunar_label} {
|
|
1086
|
+
return f"{truncated_name} - {lunar_label} {month_year}"
|
|
1082
1087
|
|
|
1083
1088
|
elif self.chart_type == "SingleReturnChart":
|
|
1084
|
-
|
|
1089
|
+
return_datetime = datetime.fromisoformat(self.first_obj.iso_formatted_local_datetime) # type: ignore
|
|
1090
|
+
year = return_datetime.year
|
|
1091
|
+
month_year = return_datetime.strftime("%m/%Y")
|
|
1085
1092
|
truncated_name = self._truncate_name(self.first_obj.name)
|
|
1086
1093
|
if isinstance(self.first_obj, PlanetReturnModel) and self.first_obj.return_type == "Solar":
|
|
1087
1094
|
solar_label = self._translate("solar_return", "Solar")
|
|
1088
1095
|
return f"{truncated_name} - {solar_label} {year}"
|
|
1089
1096
|
else:
|
|
1090
1097
|
lunar_label = self._translate("lunar_return", "Lunar")
|
|
1091
|
-
return f"{truncated_name} - {lunar_label} {
|
|
1098
|
+
return f"{truncated_name} - {lunar_label} {month_year}"
|
|
1092
1099
|
|
|
1093
1100
|
# Fallback for unknown chart types
|
|
1094
1101
|
return self._truncate_name(self.first_obj.name)
|
|
@@ -1267,7 +1274,7 @@ class ChartDrawer:
|
|
|
1267
1274
|
template_dict["top_left_5"] = f"{self._translate('day_of_week', 'Day of Week')}: {localized_weekday}" # type: ignore
|
|
1268
1275
|
|
|
1269
1276
|
# Bottom left section
|
|
1270
|
-
if self.first_obj.zodiac_type == "
|
|
1277
|
+
if self.first_obj.zodiac_type == "Tropical":
|
|
1271
1278
|
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1272
1279
|
else:
|
|
1273
1280
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
@@ -1429,7 +1436,7 @@ class ChartDrawer:
|
|
|
1429
1436
|
template_dict["top_left_5"] = f"{latitude_string} / {longitude_string}"
|
|
1430
1437
|
|
|
1431
1438
|
# Bottom left section
|
|
1432
|
-
if self.first_obj.zodiac_type == "
|
|
1439
|
+
if self.first_obj.zodiac_type == "Tropical":
|
|
1433
1440
|
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1434
1441
|
else:
|
|
1435
1442
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
@@ -1573,19 +1580,64 @@ class ChartDrawer:
|
|
|
1573
1580
|
|
|
1574
1581
|
template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
|
|
1575
1582
|
|
|
1576
|
-
# Top left section
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1583
|
+
# Top left section (clear separation of Natal vs Transit details)
|
|
1584
|
+
natal_latitude_string = (
|
|
1585
|
+
convert_latitude_coordinate_to_string(
|
|
1586
|
+
self.first_obj.lat, # type: ignore[arg-type]
|
|
1587
|
+
self._translate("north_letter", "N"),
|
|
1588
|
+
self._translate("south_letter", "S"),
|
|
1589
|
+
)
|
|
1590
|
+
if getattr(self.first_obj, "lat", None) is not None
|
|
1591
|
+
else ""
|
|
1592
|
+
)
|
|
1593
|
+
natal_longitude_string = (
|
|
1594
|
+
convert_longitude_coordinate_to_string(
|
|
1595
|
+
self.first_obj.lng, # type: ignore[arg-type]
|
|
1596
|
+
self._translate("east_letter", "E"),
|
|
1597
|
+
self._translate("west_letter", "W"),
|
|
1598
|
+
)
|
|
1599
|
+
if getattr(self.first_obj, "lng", None) is not None
|
|
1600
|
+
else ""
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1603
|
+
transit_latitude_string = ""
|
|
1604
|
+
transit_longitude_string = ""
|
|
1605
|
+
if self.second_obj is not None:
|
|
1606
|
+
if getattr(self.second_obj, "lat", None) is not None:
|
|
1607
|
+
transit_latitude_string = convert_latitude_coordinate_to_string(
|
|
1608
|
+
self.second_obj.lat, # type: ignore[arg-type]
|
|
1609
|
+
self._translate("north_letter", "N"),
|
|
1610
|
+
self._translate("south_letter", "S"),
|
|
1611
|
+
)
|
|
1612
|
+
if getattr(self.second_obj, "lng", None) is not None:
|
|
1613
|
+
transit_longitude_string = convert_longitude_coordinate_to_string(
|
|
1614
|
+
self.second_obj.lng, # type: ignore[arg-type]
|
|
1615
|
+
self._translate("east_letter", "E"),
|
|
1616
|
+
self._translate("west_letter", "W"),
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
natal_dt = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore[arg-type]
|
|
1620
|
+
natal_place = f"{format_location_string(self.first_obj.city)}, {self.first_obj.nation}" # type: ignore[arg-type]
|
|
1621
|
+
transit_dt = (
|
|
1622
|
+
format_datetime_with_timezone(self.second_obj.iso_formatted_local_datetime) # type: ignore[arg-type]
|
|
1623
|
+
if self.second_obj is not None and getattr(self.second_obj, "iso_formatted_local_datetime", None) is not None
|
|
1624
|
+
else ""
|
|
1625
|
+
)
|
|
1626
|
+
transit_place = (
|
|
1627
|
+
f"{format_location_string(self.second_obj.city)}, {self.second_obj.nation}" # type: ignore[arg-type]
|
|
1628
|
+
if self.second_obj is not None
|
|
1629
|
+
else ""
|
|
1630
|
+
)
|
|
1631
|
+
|
|
1632
|
+
template_dict["top_left_0"] = f"{self._translate('chart_info_natal_label', 'Natal')}: {natal_dt}"
|
|
1633
|
+
template_dict["top_left_1"] = natal_place
|
|
1634
|
+
template_dict["top_left_2"] = f"{natal_latitude_string} · {natal_longitude_string}"
|
|
1635
|
+
template_dict["top_left_3"] = f"{self._translate('chart_info_transit_label', 'Transit')}: {transit_dt}"
|
|
1636
|
+
template_dict["top_left_4"] = transit_place
|
|
1637
|
+
template_dict["top_left_5"] = f"{transit_latitude_string} · {transit_longitude_string}"
|
|
1586
1638
|
|
|
1587
1639
|
# Bottom left section
|
|
1588
|
-
if self.first_obj.zodiac_type == "
|
|
1640
|
+
if self.first_obj.zodiac_type == "Tropical":
|
|
1589
1641
|
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1590
1642
|
else:
|
|
1591
1643
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
@@ -1597,17 +1649,25 @@ class ChartDrawer:
|
|
|
1597
1649
|
|
|
1598
1650
|
# Lunar phase information from second object (Transit) (optional)
|
|
1599
1651
|
if self.second_obj is not None and hasattr(self.second_obj, 'lunar_phase') and self.second_obj.lunar_phase is not None:
|
|
1600
|
-
template_dict["
|
|
1601
|
-
|
|
1652
|
+
template_dict["bottom_left_3"] = (
|
|
1653
|
+
f"{self._translate('Transit', 'Transit')} "
|
|
1654
|
+
f"{self._translate('lunation_day', 'Lunation Day')}: "
|
|
1655
|
+
f"{self.second_obj.lunar_phase.get('moon_phase', '')}"
|
|
1656
|
+
) # type: ignore
|
|
1657
|
+
template_dict["bottom_left_4"] = (
|
|
1658
|
+
f"{self._translate('Transit', 'Transit')} "
|
|
1659
|
+
f"{self._translate('lunar_phase', 'Lunar Phase')}: "
|
|
1660
|
+
f"{self._translate(self.second_obj.lunar_phase.moon_phase_name.lower().replace(' ', '_'), self.second_obj.lunar_phase.moon_phase_name)}"
|
|
1661
|
+
)
|
|
1602
1662
|
else:
|
|
1603
|
-
template_dict["bottom_left_2"] = ""
|
|
1604
1663
|
template_dict["bottom_left_3"] = ""
|
|
1664
|
+
template_dict["bottom_left_4"] = ""
|
|
1605
1665
|
|
|
1606
|
-
template_dict["
|
|
1666
|
+
template_dict["bottom_left_2"] = f'{self._translate("perspective_type", "Perspective")}: {self._translate(self.second_obj.perspective_type.lower().replace(" ", "_"), self.second_obj.perspective_type)}' # type: ignore
|
|
1607
1667
|
|
|
1608
|
-
# Moon phase section calculations - use
|
|
1609
|
-
if self.
|
|
1610
|
-
template_dict["makeLunarPhase"] = makeLunarPhase(self.
|
|
1668
|
+
# Moon phase section calculations - use transit subject data only
|
|
1669
|
+
if self.second_obj is not None and getattr(self.second_obj, "lunar_phase", None):
|
|
1670
|
+
template_dict["makeLunarPhase"] = makeLunarPhase(self.second_obj.lunar_phase["degrees_between_s_m"], self.geolat)
|
|
1611
1671
|
else:
|
|
1612
1672
|
template_dict["makeLunarPhase"] = ""
|
|
1613
1673
|
|
|
@@ -1763,7 +1823,7 @@ class ChartDrawer:
|
|
|
1763
1823
|
template_dict["top_left_5"] = format_datetime_with_timezone(self.second_obj.iso_formatted_local_datetime) # type: ignore
|
|
1764
1824
|
|
|
1765
1825
|
# Bottom left section
|
|
1766
|
-
if self.first_obj.zodiac_type == "
|
|
1826
|
+
if self.first_obj.zodiac_type == "Tropical":
|
|
1767
1827
|
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1768
1828
|
else:
|
|
1769
1829
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
@@ -1963,7 +2023,7 @@ class ChartDrawer:
|
|
|
1963
2023
|
template_dict["top_left_5"] = f"{latitude_string} / {longitude_string}"
|
|
1964
2024
|
|
|
1965
2025
|
# Bottom left section
|
|
1966
|
-
if self.first_obj.zodiac_type == "
|
|
2026
|
+
if self.first_obj.zodiac_type == "Tropical":
|
|
1967
2027
|
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1968
2028
|
else:
|
|
1969
2029
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
@@ -2136,7 +2196,7 @@ class ChartDrawer:
|
|
|
2136
2196
|
template_dict["top_left_5"] = f"{self._translate('type', 'Type')}: {self._translate('lunar_return', 'Lunar Return')}"
|
|
2137
2197
|
|
|
2138
2198
|
# Bottom left section
|
|
2139
|
-
if self.first_obj.zodiac_type == "
|
|
2199
|
+
if self.first_obj.zodiac_type == "Tropical":
|
|
2140
2200
|
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
2141
2201
|
else:
|
|
2142
2202
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
@@ -2233,7 +2293,7 @@ class ChartDrawer:
|
|
|
2233
2293
|
|
|
2234
2294
|
# return filename
|
|
2235
2295
|
|
|
2236
|
-
|
|
2296
|
+
logger.debug("Template dictionary includes %s fields", len(td.model_dump()))
|
|
2237
2297
|
|
|
2238
2298
|
self._create_template_dictionary()
|
|
2239
2299
|
|