kerykeion 5.0.0a11__py3-none-any.whl → 5.0.0a12__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/__init__.py +2 -3
- kerykeion/aspects/__init__.py +2 -4
- kerykeion/aspects/aspects_factory.py +514 -0
- kerykeion/astrological_subject_factory.py +3 -3
- kerykeion/charts/kerykeion_chart_svg.py +9 -10
- kerykeion/house_comparison/house_comparison_factory.py +48 -15
- kerykeion/house_comparison/house_comparison_models.py +51 -13
- kerykeion/house_comparison/house_comparison_utils.py +35 -5
- kerykeion/kr_types/kr_models.py +27 -10
- kerykeion/relationship_score_factory.py +2 -2
- kerykeion/sweph/sefstars.txt +1602 -0
- kerykeion/transits_time_range_factory.py +4 -4
- {kerykeion-5.0.0a11.dist-info → kerykeion-5.0.0a12.dist-info}/METADATA +49 -22
- {kerykeion-5.0.0a11.dist-info → kerykeion-5.0.0a12.dist-info}/RECORD +16 -16
- kerykeion/aspects/natal_aspects_factory.py +0 -235
- kerykeion/aspects/synastry_aspects_factory.py +0 -275
- {kerykeion-5.0.0a11.dist-info → kerykeion-5.0.0a12.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0a11.dist-info → kerykeion-5.0.0a12.dist-info}/licenses/LICENSE +0 -0
kerykeion/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ This is part of Kerykeion (C) 2025 Giacomo Battaglia
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
# Local
|
|
9
|
-
from .aspects import
|
|
9
|
+
from .aspects import AspectsFactory
|
|
10
10
|
from .astrological_subject_factory import AstrologicalSubjectFactory
|
|
11
11
|
from .charts.kerykeion_chart_svg import KerykeionChartSVG
|
|
12
12
|
from .composite_subject_factory import CompositeSubjectFactory
|
|
@@ -21,8 +21,7 @@ from .settings import KerykeionSettingsModel, get_settings
|
|
|
21
21
|
from .transits_time_range_factory import TransitsTimeRangeFactory
|
|
22
22
|
|
|
23
23
|
__all__ = [
|
|
24
|
-
"
|
|
25
|
-
"NatalAspectsFactory",
|
|
24
|
+
"AspectsFactory",
|
|
26
25
|
"AstrologicalSubjectFactory",
|
|
27
26
|
"KerykeionChartSVG",
|
|
28
27
|
"CompositeSubjectFactory",
|
kerykeion/aspects/__init__.py
CHANGED
|
@@ -7,10 +7,8 @@ The aspects module contains the classes and functions for calculating
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
from .
|
|
11
|
-
from .natal_aspects_factory import NatalAspectsFactory
|
|
10
|
+
from .aspects_factory import AspectsFactory
|
|
12
11
|
|
|
13
12
|
__all__ = [
|
|
14
|
-
"
|
|
15
|
-
"NatalAspectsFactory",
|
|
13
|
+
"AspectsFactory",
|
|
16
14
|
]
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This is part of Kerykeion (C) 2025 Giacomo Battaglia
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Union, List, Optional
|
|
8
|
+
|
|
9
|
+
from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
|
|
10
|
+
from kerykeion.aspects.aspects_utils import get_aspect_from_two_points, get_active_points_list
|
|
11
|
+
from kerykeion.kr_types.kr_models import (
|
|
12
|
+
AstrologicalSubjectModel,
|
|
13
|
+
AspectModel,
|
|
14
|
+
ActiveAspect,
|
|
15
|
+
CompositeSubjectModel,
|
|
16
|
+
PlanetReturnModel,
|
|
17
|
+
SingleChartAspectsModel,
|
|
18
|
+
DualChartAspectsModel,
|
|
19
|
+
# Legacy aliases for backward compatibility
|
|
20
|
+
NatalAspectsModel,
|
|
21
|
+
SynastryAspectsModel
|
|
22
|
+
)
|
|
23
|
+
from kerykeion.kr_types.kr_literals import AstrologicalPoint
|
|
24
|
+
from kerykeion.settings.config_constants import DEFAULT_ACTIVE_ASPECTS, DEFAULT_AXIS_ORBIT
|
|
25
|
+
from kerykeion.settings.legacy.legacy_celestial_points_settings import DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
26
|
+
from kerykeion.settings.legacy.legacy_chart_aspects_settings import DEFAULT_CHART_ASPECTS_SETTINGS
|
|
27
|
+
from kerykeion.utilities import find_common_active_points
|
|
28
|
+
|
|
29
|
+
# Axes constants for orb filtering
|
|
30
|
+
AXES_LIST = [
|
|
31
|
+
"Ascendant",
|
|
32
|
+
"Medium_Coeli",
|
|
33
|
+
"Descendant",
|
|
34
|
+
"Imum_Coeli",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AspectsFactory:
|
|
39
|
+
"""
|
|
40
|
+
Unified factory class for creating both single chart and dual chart aspects analysis.
|
|
41
|
+
|
|
42
|
+
This factory provides methods to calculate all aspects within a single chart or
|
|
43
|
+
between two charts. It consolidates the common functionality between different
|
|
44
|
+
types of aspect calculations while providing specialized methods for each type.
|
|
45
|
+
|
|
46
|
+
The factory provides both comprehensive and filtered aspect lists based on orb settings
|
|
47
|
+
and relevance criteria.
|
|
48
|
+
|
|
49
|
+
Key Features:
|
|
50
|
+
- Calculates aspects within a single chart (natal, returns, composite, etc.)
|
|
51
|
+
- Calculates aspects between two charts (synastry, transits, comparisons, etc.)
|
|
52
|
+
- Filters aspects based on orb thresholds
|
|
53
|
+
- Applies stricter orb limits for chart axes (ASC, MC, DSC, IC)
|
|
54
|
+
- Supports multiple subject types (natal, composite, planetary returns)
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> # For single chart aspects (natal, returns, etc.)
|
|
58
|
+
>>> johnny = AstrologicalSubjectFactory.from_birth_data("Johnny", 1963, 6, 9, 0, 0, "Owensboro", "US")
|
|
59
|
+
>>> single_chart_aspects = AspectsFactory.single_chart_aspects(johnny)
|
|
60
|
+
>>>
|
|
61
|
+
>>> # For dual chart aspects (synastry, comparisons, etc.)
|
|
62
|
+
>>> john = AstrologicalSubjectFactory.from_birth_data("John", 1990, 1, 1, 12, 0, "London", "GB")
|
|
63
|
+
>>> jane = AstrologicalSubjectFactory.from_birth_data("Jane", 1992, 6, 15, 14, 30, "Paris", "FR")
|
|
64
|
+
>>> dual_chart_aspects = AspectsFactory.dual_chart_aspects(john, jane)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def single_chart_aspects(
|
|
69
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
70
|
+
*,
|
|
71
|
+
active_points: Optional[List[AstrologicalPoint]] = None,
|
|
72
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
73
|
+
) -> SingleChartAspectsModel:
|
|
74
|
+
"""
|
|
75
|
+
Create aspects analysis for a single astrological chart.
|
|
76
|
+
|
|
77
|
+
This method calculates all astrological aspects (angular relationships)
|
|
78
|
+
within a single chart. Can be used for any type of chart including:
|
|
79
|
+
- Natal charts
|
|
80
|
+
- Planetary return charts
|
|
81
|
+
- Composite charts
|
|
82
|
+
- Any other single chart type
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
subject: The astrological subject for aspect calculation
|
|
86
|
+
|
|
87
|
+
Kwargs:
|
|
88
|
+
active_points: List of points to include in calculations
|
|
89
|
+
active_aspects: List of aspects with their orb settings
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
SingleChartAspectsModel containing all calculated aspects data
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> johnny = AstrologicalSubjectFactory.from_birth_data("Johnny", 1963, 6, 9, 0, 0, "Owensboro", "US")
|
|
96
|
+
>>> chart_aspects = AspectsFactory.single_chart_aspects(johnny)
|
|
97
|
+
>>> print(f"Found {len(chart_aspects.relevant_aspects)} relevant aspects")
|
|
98
|
+
"""
|
|
99
|
+
# Initialize settings and configurations
|
|
100
|
+
celestial_points = DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
101
|
+
aspects_settings = DEFAULT_CHART_ASPECTS_SETTINGS
|
|
102
|
+
axes_orbit_settings = DEFAULT_AXIS_ORBIT
|
|
103
|
+
|
|
104
|
+
# Set active aspects with default fallback
|
|
105
|
+
active_aspects_resolved = active_aspects if active_aspects is not None else DEFAULT_ACTIVE_ASPECTS
|
|
106
|
+
|
|
107
|
+
# Determine active points to use
|
|
108
|
+
if active_points is None:
|
|
109
|
+
active_points_resolved = subject.active_points
|
|
110
|
+
else:
|
|
111
|
+
active_points_resolved = find_common_active_points(
|
|
112
|
+
subject.active_points,
|
|
113
|
+
active_points,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return AspectsFactory._create_single_chart_aspects_model(
|
|
117
|
+
subject, active_points_resolved, active_aspects_resolved,
|
|
118
|
+
aspects_settings, axes_orbit_settings, celestial_points
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def dual_chart_aspects(
|
|
123
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
124
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
125
|
+
*,
|
|
126
|
+
active_points: Optional[List[AstrologicalPoint]] = None,
|
|
127
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
128
|
+
) -> DualChartAspectsModel:
|
|
129
|
+
"""
|
|
130
|
+
Create aspects analysis between two astrological charts.
|
|
131
|
+
|
|
132
|
+
This method calculates all astrological aspects (angular relationships)
|
|
133
|
+
between planets and points in two different charts. Can be used for:
|
|
134
|
+
- Synastry (relationship compatibility)
|
|
135
|
+
- Transit comparisons
|
|
136
|
+
- Composite vs natal comparisons
|
|
137
|
+
- Any other dual chart analysis
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
first_subject: The first astrological subject
|
|
141
|
+
second_subject: The second astrological subject to compare with the first
|
|
142
|
+
|
|
143
|
+
Kwargs:
|
|
144
|
+
active_points: Optional list of celestial points to include in calculations.
|
|
145
|
+
If None, uses common points between both subjects.
|
|
146
|
+
active_aspects: Optional list of aspect types with their orb settings.
|
|
147
|
+
If None, uses default aspect configuration.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
DualChartAspectsModel: Complete model containing all calculated aspects data,
|
|
151
|
+
including both comprehensive and filtered relevant aspects.
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> john = AstrologicalSubjectFactory.from_birth_data("John", 1990, 1, 1, 12, 0, "London", "GB")
|
|
155
|
+
>>> jane = AstrologicalSubjectFactory.from_birth_data("Jane", 1992, 6, 15, 14, 30, "Paris", "FR")
|
|
156
|
+
>>> synastry = AspectsFactory.dual_chart_aspects(john, jane)
|
|
157
|
+
>>> print(f"Found {len(synastry.relevant_aspects)} relevant aspects")
|
|
158
|
+
"""
|
|
159
|
+
# Initialize settings and configurations
|
|
160
|
+
celestial_points = DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
161
|
+
aspects_settings = DEFAULT_CHART_ASPECTS_SETTINGS
|
|
162
|
+
axes_orbit_settings = DEFAULT_AXIS_ORBIT
|
|
163
|
+
|
|
164
|
+
# Set active aspects with default fallback
|
|
165
|
+
active_aspects_resolved = active_aspects if active_aspects is not None else DEFAULT_ACTIVE_ASPECTS
|
|
166
|
+
|
|
167
|
+
# Determine active points to use - find common points between both subjects
|
|
168
|
+
if active_points is None:
|
|
169
|
+
active_points_resolved = first_subject.active_points
|
|
170
|
+
else:
|
|
171
|
+
active_points_resolved = find_common_active_points(
|
|
172
|
+
first_subject.active_points,
|
|
173
|
+
active_points,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Further filter with second subject's active points
|
|
177
|
+
active_points_resolved = find_common_active_points(
|
|
178
|
+
second_subject.active_points,
|
|
179
|
+
active_points_resolved,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return AspectsFactory._create_dual_chart_aspects_model(
|
|
183
|
+
first_subject, second_subject, active_points_resolved, active_aspects_resolved,
|
|
184
|
+
aspects_settings, axes_orbit_settings, celestial_points
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def _create_single_chart_aspects_model(
|
|
189
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
190
|
+
active_points_resolved: List[AstrologicalPoint],
|
|
191
|
+
active_aspects_resolved: List[ActiveAspect],
|
|
192
|
+
aspects_settings: List[dict],
|
|
193
|
+
axes_orbit_settings: float,
|
|
194
|
+
celestial_points: List[dict]
|
|
195
|
+
) -> SingleChartAspectsModel:
|
|
196
|
+
"""
|
|
197
|
+
Create the complete single chart aspects model with all calculations.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
SingleChartAspectsModel containing all aspects data
|
|
201
|
+
"""
|
|
202
|
+
all_aspects = AspectsFactory._calculate_single_chart_aspects(
|
|
203
|
+
subject, active_points_resolved, active_aspects_resolved, aspects_settings, celestial_points
|
|
204
|
+
)
|
|
205
|
+
relevant_aspects = AspectsFactory._filter_relevant_aspects(all_aspects, axes_orbit_settings)
|
|
206
|
+
|
|
207
|
+
return SingleChartAspectsModel(
|
|
208
|
+
subject=subject,
|
|
209
|
+
all_aspects=all_aspects,
|
|
210
|
+
relevant_aspects=relevant_aspects,
|
|
211
|
+
active_points=active_points_resolved,
|
|
212
|
+
active_aspects=active_aspects_resolved,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def _create_dual_chart_aspects_model(
|
|
217
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
218
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
219
|
+
active_points_resolved: List[AstrologicalPoint],
|
|
220
|
+
active_aspects_resolved: List[ActiveAspect],
|
|
221
|
+
aspects_settings: List[dict],
|
|
222
|
+
axes_orbit_settings: float,
|
|
223
|
+
celestial_points: List[dict]
|
|
224
|
+
) -> DualChartAspectsModel:
|
|
225
|
+
"""
|
|
226
|
+
Create the complete dual chart aspects model with all calculations.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
first_subject: First astrological subject
|
|
230
|
+
second_subject: Second astrological subject
|
|
231
|
+
active_points_resolved: Resolved list of active celestial points
|
|
232
|
+
active_aspects_resolved: Resolved list of active aspects with orbs
|
|
233
|
+
aspects_settings: Chart aspect configuration settings
|
|
234
|
+
axes_orbit_settings: Orb threshold for chart axes
|
|
235
|
+
celestial_points: Celestial points configuration
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
DualChartAspectsModel: Complete model containing all aspects data
|
|
239
|
+
"""
|
|
240
|
+
all_aspects = AspectsFactory._calculate_dual_chart_aspects(
|
|
241
|
+
first_subject, second_subject, active_points_resolved, active_aspects_resolved,
|
|
242
|
+
aspects_settings, celestial_points
|
|
243
|
+
)
|
|
244
|
+
relevant_aspects = AspectsFactory._filter_relevant_aspects(all_aspects, axes_orbit_settings)
|
|
245
|
+
|
|
246
|
+
return DualChartAspectsModel(
|
|
247
|
+
first_subject=first_subject,
|
|
248
|
+
second_subject=second_subject,
|
|
249
|
+
all_aspects=all_aspects,
|
|
250
|
+
relevant_aspects=relevant_aspects,
|
|
251
|
+
active_points=active_points_resolved,
|
|
252
|
+
active_aspects=active_aspects_resolved,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
@staticmethod
|
|
256
|
+
def _calculate_single_chart_aspects(
|
|
257
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
258
|
+
active_points: List[AstrologicalPoint],
|
|
259
|
+
active_aspects: List[ActiveAspect],
|
|
260
|
+
aspects_settings: List[dict],
|
|
261
|
+
celestial_points: List[dict]
|
|
262
|
+
) -> List[AspectModel]:
|
|
263
|
+
"""
|
|
264
|
+
Calculate all aspects within a single chart.
|
|
265
|
+
|
|
266
|
+
This method handles all aspect calculations including settings updates,
|
|
267
|
+
opposite pair filtering, and planet ID resolution for single charts.
|
|
268
|
+
Works with any chart type (natal, return, composite, etc.).
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of all calculated AspectModel instances
|
|
272
|
+
"""
|
|
273
|
+
active_points_list = get_active_points_list(subject, active_points)
|
|
274
|
+
|
|
275
|
+
# Update aspects settings with active aspects orbs
|
|
276
|
+
filtered_settings = AspectsFactory._update_aspect_settings(aspects_settings, active_aspects)
|
|
277
|
+
|
|
278
|
+
# Create a lookup dictionary for planet IDs to optimize performance
|
|
279
|
+
planet_id_lookup = {planet["name"]: planet["id"] for planet in celestial_points}
|
|
280
|
+
|
|
281
|
+
# Define opposite pairs that should be skipped for single chart aspects
|
|
282
|
+
opposite_pairs = {
|
|
283
|
+
("Ascendant", "Descendant"),
|
|
284
|
+
("Descendant", "Ascendant"),
|
|
285
|
+
("Medium_Coeli", "Imum_Coeli"),
|
|
286
|
+
("Imum_Coeli", "Medium_Coeli"),
|
|
287
|
+
("True_Node", "True_South_Node"),
|
|
288
|
+
("Mean_Node", "Mean_South_Node"),
|
|
289
|
+
("True_South_Node", "True_Node"),
|
|
290
|
+
("Mean_South_Node", "Mean_Node"),
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
all_aspects_list = []
|
|
294
|
+
|
|
295
|
+
for first in range(len(active_points_list)):
|
|
296
|
+
# Generate aspects list without repetitions (single chart - same chart)
|
|
297
|
+
for second in range(first + 1, len(active_points_list)):
|
|
298
|
+
# Skip predefined opposite pairs (AC/DC, MC/IC, North/South nodes)
|
|
299
|
+
first_name = active_points_list[first]["name"]
|
|
300
|
+
second_name = active_points_list[second]["name"]
|
|
301
|
+
|
|
302
|
+
if (first_name, second_name) in opposite_pairs:
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
aspect = get_aspect_from_two_points(
|
|
306
|
+
filtered_settings,
|
|
307
|
+
active_points_list[first]["abs_pos"],
|
|
308
|
+
active_points_list[second]["abs_pos"]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if aspect["verdict"]:
|
|
312
|
+
# Get planet IDs using lookup dictionary for better performance
|
|
313
|
+
first_planet_id = planet_id_lookup.get(first_name, 0)
|
|
314
|
+
second_planet_id = planet_id_lookup.get(second_name, 0)
|
|
315
|
+
|
|
316
|
+
aspect_model = AspectModel(
|
|
317
|
+
p1_name=first_name,
|
|
318
|
+
p1_owner=subject.name,
|
|
319
|
+
p1_abs_pos=active_points_list[first]["abs_pos"],
|
|
320
|
+
p2_name=second_name,
|
|
321
|
+
p2_owner=subject.name,
|
|
322
|
+
p2_abs_pos=active_points_list[second]["abs_pos"],
|
|
323
|
+
aspect=aspect["name"],
|
|
324
|
+
orbit=aspect["orbit"],
|
|
325
|
+
aspect_degrees=aspect["aspect_degrees"],
|
|
326
|
+
diff=aspect["diff"],
|
|
327
|
+
p1=first_planet_id,
|
|
328
|
+
p2=second_planet_id,
|
|
329
|
+
)
|
|
330
|
+
all_aspects_list.append(aspect_model)
|
|
331
|
+
|
|
332
|
+
return all_aspects_list
|
|
333
|
+
|
|
334
|
+
@staticmethod
|
|
335
|
+
def _calculate_dual_chart_aspects(
|
|
336
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
337
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
338
|
+
active_points: List[AstrologicalPoint],
|
|
339
|
+
active_aspects: List[ActiveAspect],
|
|
340
|
+
aspects_settings: List[dict],
|
|
341
|
+
celestial_points: List[dict]
|
|
342
|
+
) -> List[AspectModel]:
|
|
343
|
+
"""
|
|
344
|
+
Calculate all aspects between two charts.
|
|
345
|
+
|
|
346
|
+
This method performs comprehensive aspect calculations between all active points
|
|
347
|
+
of both subjects, applying the specified orb settings and creating detailed
|
|
348
|
+
aspect models with planet IDs and positional information.
|
|
349
|
+
Works with any chart types (synastry, transits, comparisons, etc.).
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
first_subject: First astrological subject
|
|
353
|
+
second_subject: Second astrological subject
|
|
354
|
+
active_points: List of celestial points to include in calculations
|
|
355
|
+
active_aspects: List of aspect types with their orb settings
|
|
356
|
+
aspects_settings: Base aspect configuration settings
|
|
357
|
+
celestial_points: Celestial points configuration with IDs
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
List[AspectModel]: Complete list of all calculated aspect instances
|
|
361
|
+
"""
|
|
362
|
+
# Get active points lists for both subjects
|
|
363
|
+
first_active_points_list = get_active_points_list(first_subject, active_points)
|
|
364
|
+
second_active_points_list = get_active_points_list(second_subject, active_points)
|
|
365
|
+
|
|
366
|
+
# Create a lookup dictionary for planet IDs to optimize performance
|
|
367
|
+
planet_id_lookup = {planet["name"]: planet["id"] for planet in celestial_points}
|
|
368
|
+
|
|
369
|
+
# Update aspects settings with active aspects orbs
|
|
370
|
+
filtered_settings = AspectsFactory._update_aspect_settings(aspects_settings, active_aspects)
|
|
371
|
+
|
|
372
|
+
all_aspects_list = []
|
|
373
|
+
for first in range(len(first_active_points_list)):
|
|
374
|
+
# Generate aspects list between all points of first and second subjects
|
|
375
|
+
for second in range(len(second_active_points_list)):
|
|
376
|
+
aspect = get_aspect_from_two_points(
|
|
377
|
+
filtered_settings,
|
|
378
|
+
first_active_points_list[first]["abs_pos"],
|
|
379
|
+
second_active_points_list[second]["abs_pos"],
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if aspect["verdict"]:
|
|
383
|
+
first_name = first_active_points_list[first]["name"]
|
|
384
|
+
second_name = second_active_points_list[second]["name"]
|
|
385
|
+
|
|
386
|
+
# Get planet IDs using lookup dictionary for better performance
|
|
387
|
+
first_planet_id = planet_id_lookup.get(first_name, 0)
|
|
388
|
+
second_planet_id = planet_id_lookup.get(second_name, 0)
|
|
389
|
+
|
|
390
|
+
aspect_model = AspectModel(
|
|
391
|
+
p1_name=first_name,
|
|
392
|
+
p1_owner=first_subject.name,
|
|
393
|
+
p1_abs_pos=first_active_points_list[first]["abs_pos"],
|
|
394
|
+
p2_name=second_name,
|
|
395
|
+
p2_owner=second_subject.name,
|
|
396
|
+
p2_abs_pos=second_active_points_list[second]["abs_pos"],
|
|
397
|
+
aspect=aspect["name"],
|
|
398
|
+
orbit=aspect["orbit"],
|
|
399
|
+
aspect_degrees=aspect["aspect_degrees"],
|
|
400
|
+
diff=aspect["diff"],
|
|
401
|
+
p1=first_planet_id,
|
|
402
|
+
p2=second_planet_id,
|
|
403
|
+
)
|
|
404
|
+
all_aspects_list.append(aspect_model)
|
|
405
|
+
|
|
406
|
+
return all_aspects_list
|
|
407
|
+
|
|
408
|
+
@staticmethod
|
|
409
|
+
def _update_aspect_settings(
|
|
410
|
+
aspects_settings: List[dict],
|
|
411
|
+
active_aspects: List[ActiveAspect]
|
|
412
|
+
) -> List[dict]:
|
|
413
|
+
"""
|
|
414
|
+
Update aspects settings with active aspects orbs.
|
|
415
|
+
|
|
416
|
+
This is a common utility method used by both single chart and dual chart calculations.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
aspects_settings: Base aspect settings
|
|
420
|
+
active_aspects: Active aspects with their orb configurations
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
List of filtered and updated aspect settings
|
|
424
|
+
"""
|
|
425
|
+
filtered_settings = []
|
|
426
|
+
for aspect_setting in aspects_settings:
|
|
427
|
+
for active_aspect in active_aspects:
|
|
428
|
+
if aspect_setting["name"] == active_aspect["name"]:
|
|
429
|
+
aspect_setting = aspect_setting.copy() # Don't modify original
|
|
430
|
+
aspect_setting["orb"] = active_aspect["orb"]
|
|
431
|
+
filtered_settings.append(aspect_setting)
|
|
432
|
+
break
|
|
433
|
+
return filtered_settings
|
|
434
|
+
|
|
435
|
+
@staticmethod
|
|
436
|
+
def _filter_relevant_aspects(all_aspects: List[AspectModel], axes_orbit_settings: float) -> List[AspectModel]:
|
|
437
|
+
"""
|
|
438
|
+
Filter aspects based on orb thresholds for axes and comprehensive criteria.
|
|
439
|
+
|
|
440
|
+
This method consolidates all filtering logic including axes checks and orb thresholds
|
|
441
|
+
for both single chart and dual chart aspects in a single comprehensive filtering method.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
all_aspects: Complete list of calculated aspects
|
|
445
|
+
axes_orbit_settings: Orb threshold for axes aspects
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Filtered list of relevant aspects
|
|
449
|
+
"""
|
|
450
|
+
logging.debug("Calculating relevant aspects by filtering orbs...")
|
|
451
|
+
|
|
452
|
+
relevant_aspects = []
|
|
453
|
+
|
|
454
|
+
for aspect in all_aspects:
|
|
455
|
+
# Check if aspect involves any of the chart axes and apply stricter orb limits
|
|
456
|
+
aspect_involves_axes = (aspect.p1_name in AXES_LIST or aspect.p2_name in AXES_LIST)
|
|
457
|
+
|
|
458
|
+
if aspect_involves_axes and abs(aspect.orbit) >= axes_orbit_settings:
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
relevant_aspects.append(aspect)
|
|
462
|
+
|
|
463
|
+
return relevant_aspects
|
|
464
|
+
|
|
465
|
+
# Legacy methods for temporary backward compatibility
|
|
466
|
+
@staticmethod
|
|
467
|
+
def natal_aspects(
|
|
468
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
469
|
+
*,
|
|
470
|
+
active_points: Optional[List[AstrologicalPoint]] = None,
|
|
471
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
472
|
+
) -> NatalAspectsModel:
|
|
473
|
+
"""
|
|
474
|
+
Legacy method - use single_chart_aspects() instead.
|
|
475
|
+
|
|
476
|
+
⚠️ DEPRECATION WARNING ⚠️
|
|
477
|
+
This method is deprecated. Use AspectsFactory.single_chart_aspects() instead.
|
|
478
|
+
"""
|
|
479
|
+
return AspectsFactory.single_chart_aspects(subject, active_points=active_points, active_aspects=active_aspects)
|
|
480
|
+
|
|
481
|
+
@staticmethod
|
|
482
|
+
def synastry_aspects(
|
|
483
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
484
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
485
|
+
*,
|
|
486
|
+
active_points: Optional[List[AstrologicalPoint]] = None,
|
|
487
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
488
|
+
) -> SynastryAspectsModel:
|
|
489
|
+
"""
|
|
490
|
+
Legacy method - use dual_chart_aspects() instead.
|
|
491
|
+
|
|
492
|
+
⚠️ DEPRECATION WARNING ⚠️
|
|
493
|
+
This method is deprecated. Use AspectsFactory.dual_chart_aspects() instead.
|
|
494
|
+
"""
|
|
495
|
+
return AspectsFactory.dual_chart_aspects(
|
|
496
|
+
first_subject, second_subject, active_points=active_points, active_aspects=active_aspects
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
if __name__ == "__main__":
|
|
501
|
+
from kerykeion.utilities import setup_logging
|
|
502
|
+
|
|
503
|
+
setup_logging(level="debug")
|
|
504
|
+
|
|
505
|
+
# Test single chart aspects (replaces natal aspects)
|
|
506
|
+
johnny = AstrologicalSubjectFactory.from_birth_data("Johnny Depp", 1963, 6, 9, 0, 0, city="Owensboro", nation="US")
|
|
507
|
+
single_chart_aspects = AspectsFactory.single_chart_aspects(johnny)
|
|
508
|
+
print(f"Single chart aspects - All: {len(single_chart_aspects.all_aspects)}, Relevant: {len(single_chart_aspects.relevant_aspects)}")
|
|
509
|
+
|
|
510
|
+
# Test dual chart aspects (replaces synastry aspects)
|
|
511
|
+
john = AstrologicalSubjectFactory.from_birth_data("John", 1940, 10, 9, 10, 30, "Liverpool", "GB")
|
|
512
|
+
yoko = AstrologicalSubjectFactory.from_birth_data("Yoko", 1933, 2, 18, 10, 30, "Tokyo", "JP")
|
|
513
|
+
dual_chart_aspects = AspectsFactory.dual_chart_aspects(john, yoko)
|
|
514
|
+
print(f"Dual chart aspects - All: {len(dual_chart_aspects.all_aspects)}, Relevant: {len(dual_chart_aspects.relevant_aspects)}")
|
|
@@ -345,7 +345,7 @@ class AstrologicalSubjectFactory:
|
|
|
345
345
|
... city="Rome", nation="IT",
|
|
346
346
|
... online=True
|
|
347
347
|
... )
|
|
348
|
-
>>> print(f"Sun: {subject.sun.sign} {subject.sun.
|
|
348
|
+
>>> print(f"Sun: {subject.sun.sign} {subject.sun.abs_pos}°")
|
|
349
349
|
>>> print(f"Active points: {len(subject.active_points)}")
|
|
350
350
|
|
|
351
351
|
>>> # Create chart for current time
|
|
@@ -1496,7 +1496,7 @@ class AstrologicalSubjectFactory:
|
|
|
1496
1496
|
# Calculate Regulus (example fixed star)
|
|
1497
1497
|
if should_calculate("Regulus"):
|
|
1498
1498
|
try:
|
|
1499
|
-
star_name =
|
|
1499
|
+
star_name = "Regulus"
|
|
1500
1500
|
swe.fixstar_ut(star_name, julian_day, iflag)
|
|
1501
1501
|
regulus_deg = swe.fixstar_ut(star_name, julian_day, iflag)[0][0]
|
|
1502
1502
|
data["regulus"] = get_kerykeion_point_from_degree(regulus_deg, "Regulus", point_type=point_type)
|
|
@@ -1510,7 +1510,7 @@ class AstrologicalSubjectFactory:
|
|
|
1510
1510
|
# Calculate Spica (example fixed star)
|
|
1511
1511
|
if should_calculate("Spica"):
|
|
1512
1512
|
try:
|
|
1513
|
-
star_name =
|
|
1513
|
+
star_name = "Spica"
|
|
1514
1514
|
swe.fixstar_ut(star_name, julian_day, iflag)
|
|
1515
1515
|
spica_deg = swe.fixstar_ut(star_name, julian_day, iflag)[0][0]
|
|
1516
1516
|
data["spica"] = get_kerykeion_point_from_degree(spica_deg, "Spica", point_type=point_type)
|
|
@@ -9,8 +9,7 @@ import swisseph as swe
|
|
|
9
9
|
from typing import get_args, Union, Optional
|
|
10
10
|
|
|
11
11
|
from kerykeion.settings.kerykeion_settings import get_settings
|
|
12
|
-
from kerykeion.aspects
|
|
13
|
-
from kerykeion.aspects.natal_aspects_factory import NatalAspectsFactory
|
|
12
|
+
from kerykeion.aspects import AspectsFactory
|
|
14
13
|
from kerykeion.house_comparison.house_comparison_factory import HouseComparisonFactory
|
|
15
14
|
from kerykeion.kr_types import (
|
|
16
15
|
KerykeionException,
|
|
@@ -312,12 +311,12 @@ class KerykeionChartSVG:
|
|
|
312
311
|
raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
|
|
313
312
|
|
|
314
313
|
# Calculate aspects
|
|
315
|
-
|
|
314
|
+
aspects_instance = AspectsFactory.single_chart_aspects(
|
|
316
315
|
self.first_obj,
|
|
317
316
|
active_points=self.active_points,
|
|
318
317
|
active_aspects=active_aspects,
|
|
319
318
|
)
|
|
320
|
-
self.aspects_list =
|
|
319
|
+
self.aspects_list = aspects_instance.relevant_aspects
|
|
321
320
|
|
|
322
321
|
# Screen size
|
|
323
322
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -346,7 +345,7 @@ class KerykeionChartSVG:
|
|
|
346
345
|
raise KerykeionException("First object must be a CompositeSubjectModel instance.")
|
|
347
346
|
|
|
348
347
|
# Calculate aspects
|
|
349
|
-
self.aspects_list =
|
|
348
|
+
self.aspects_list = AspectsFactory.single_chart_aspects(self.first_obj, active_points=self.active_points).relevant_aspects
|
|
350
349
|
|
|
351
350
|
# Screen size
|
|
352
351
|
self.height = self._DEFAULT_HEIGHT
|
|
@@ -377,7 +376,7 @@ class KerykeionChartSVG:
|
|
|
377
376
|
self.second_obj = second_obj
|
|
378
377
|
|
|
379
378
|
# Calculate aspects (transit to natal)
|
|
380
|
-
synastry_aspects_instance =
|
|
379
|
+
synastry_aspects_instance = AspectsFactory.dual_chart_aspects(
|
|
381
380
|
self.first_obj,
|
|
382
381
|
self.second_obj,
|
|
383
382
|
active_points=self.active_points,
|
|
@@ -421,7 +420,7 @@ class KerykeionChartSVG:
|
|
|
421
420
|
self.second_obj = second_obj
|
|
422
421
|
|
|
423
422
|
# Calculate aspects (natal to partner)
|
|
424
|
-
synastry_aspects_instance =
|
|
423
|
+
synastry_aspects_instance = AspectsFactory.dual_chart_aspects(
|
|
425
424
|
self.first_obj,
|
|
426
425
|
self.second_obj,
|
|
427
426
|
active_points=self.active_points,
|
|
@@ -461,7 +460,7 @@ class KerykeionChartSVG:
|
|
|
461
460
|
self.second_obj = second_obj
|
|
462
461
|
|
|
463
462
|
# Calculate aspects (natal to return)
|
|
464
|
-
synastry_aspects_instance =
|
|
463
|
+
synastry_aspects_instance = AspectsFactory.dual_chart_aspects(
|
|
465
464
|
self.first_obj,
|
|
466
465
|
self.second_obj,
|
|
467
466
|
active_points=self.active_points,
|
|
@@ -494,12 +493,12 @@ class KerykeionChartSVG:
|
|
|
494
493
|
raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
|
|
495
494
|
|
|
496
495
|
# Calculate aspects
|
|
497
|
-
|
|
496
|
+
aspects_instance = AspectsFactory.single_chart_aspects(
|
|
498
497
|
self.first_obj,
|
|
499
498
|
active_points=self.active_points,
|
|
500
499
|
active_aspects=active_aspects,
|
|
501
500
|
)
|
|
502
|
-
self.aspects_list =
|
|
501
|
+
self.aspects_list = aspects_instance.relevant_aspects
|
|
503
502
|
|
|
504
503
|
# Screen size
|
|
505
504
|
self.height = self._DEFAULT_HEIGHT
|