kerykeion 5.0.0a9__py3-none-any.whl → 5.0.0a10__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 +22 -1
- kerykeion/aspects/__init__.py +7 -2
- kerykeion/aspects/aspects_utils.py +1 -3
- kerykeion/aspects/natal_aspects_factory.py +236 -0
- kerykeion/aspects/synastry_aspects_factory.py +234 -0
- kerykeion/astrological_subject_factory.py +5 -9
- kerykeion/charts/charts_utils.py +12 -12
- kerykeion/charts/draw_planets.py +3 -4
- kerykeion/charts/draw_planets_v2.py +5 -6
- kerykeion/charts/kerykeion_chart_svg.py +10 -10
- kerykeion/composite_subject_factory.py +1 -1
- kerykeion/house_comparison/__init__.py +6 -0
- kerykeion/house_comparison/house_comparison_factory.py +1 -1
- kerykeion/house_comparison/house_comparison_utils.py +0 -1
- kerykeion/kr_types/__init__.py +49 -0
- kerykeion/kr_types/kr_models.py +29 -0
- kerykeion/kr_types/settings_models.py +9 -1
- kerykeion/planetary_return_factory.py +6 -5
- kerykeion/relationship_score_factory.py +27 -17
- kerykeion/report.py +0 -1
- kerykeion/settings/__init__.py +5 -0
- kerykeion/settings/config_constants.py +20 -6
- kerykeion/settings/kr.config.json +80 -0
- kerykeion/transits_time_range.py +4 -4
- kerykeion/utilities.py +1 -1
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.0.0a10.dist-info}/METADATA +9 -4
- kerykeion-5.0.0a10.dist-info/RECORD +53 -0
- kerykeion/aspects/natal_aspects.py +0 -181
- kerykeion/aspects/synastry_aspects.py +0 -141
- kerykeion/aspects/transits_time_range.py +0 -41
- kerykeion-5.0.0a9.dist-info/RECORD +0 -55
- kerykeion-5.0.0a9.dist-info/entry_points.txt +0 -2
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.0.0a10.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.0.0a10.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 SynastryAspectsFactory, NatalAspectsFactory
|
|
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
|
|
@@ -20,3 +20,24 @@ from .relationship_score_factory import RelationshipScoreFactory
|
|
|
20
20
|
from .report import Report
|
|
21
21
|
from .settings import KerykeionSettingsModel, get_settings
|
|
22
22
|
from .transits_time_range import TransitsTimeRangeFactory
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"SynastryAspectsFactory",
|
|
26
|
+
"NatalAspectsFactory",
|
|
27
|
+
"AstrologicalSubjectFactory",
|
|
28
|
+
"KerykeionChartSVG",
|
|
29
|
+
"CompositeSubjectFactory",
|
|
30
|
+
"Planets",
|
|
31
|
+
"Aspects",
|
|
32
|
+
"Signs",
|
|
33
|
+
"EphemerisDataFactory",
|
|
34
|
+
"HouseComparisonFactory",
|
|
35
|
+
"HouseComparisonModel",
|
|
36
|
+
"PlanetaryReturnFactory",
|
|
37
|
+
"PlanetReturnModel",
|
|
38
|
+
"RelationshipScoreFactory",
|
|
39
|
+
"Report",
|
|
40
|
+
"KerykeionSettingsModel",
|
|
41
|
+
"get_settings",
|
|
42
|
+
"TransitsTimeRangeFactory",
|
|
43
|
+
]
|
kerykeion/aspects/__init__.py
CHANGED
|
@@ -7,5 +7,10 @@ The aspects module contains the classes and functions for calculating
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
from .
|
|
11
|
-
from .
|
|
10
|
+
from .synastry_aspects_factory import SynastryAspectsFactory
|
|
11
|
+
from .natal_aspects_factory import NatalAspectsFactory
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"SynastryAspectsFactory",
|
|
15
|
+
"NatalAspectsFactory",
|
|
16
|
+
]
|
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
"""
|
|
5
5
|
# TODO: Better documentation and unit tests
|
|
6
6
|
|
|
7
|
-
from kerykeion.settings import KerykeionSettingsModel
|
|
8
7
|
from swisseph import difdeg2n
|
|
9
8
|
from typing import Union, TYPE_CHECKING
|
|
10
9
|
from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel
|
|
11
|
-
from kerykeion.kr_types.kr_literals import AstrologicalPoint
|
|
12
10
|
from kerykeion.kr_types.settings_models import KerykeionSettingsCelestialPointModel
|
|
13
11
|
from kerykeion.settings.legacy.legacy_celestial_points_settings import DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
if TYPE_CHECKING:
|
|
17
|
-
|
|
15
|
+
pass
|
|
18
16
|
|
|
19
17
|
def get_aspect_from_two_points(
|
|
20
18
|
aspects_settings: Union[list[dict], list[dict]],
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This is part of Kerykeion (C) 2025 Giacomo Battaglia
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Union, List, Optional
|
|
9
|
+
|
|
10
|
+
from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
|
|
11
|
+
from kerykeion.aspects.aspects_utils import get_aspect_from_two_points, get_active_points_list
|
|
12
|
+
from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, AspectModel, ActiveAspect, CompositeSubjectModel, PlanetReturnModel, NatalAspectsModel
|
|
13
|
+
from kerykeion.kr_types.kr_literals import AstrologicalPoint
|
|
14
|
+
from kerykeion.kr_types.settings_models import KerykeionSettingsModel
|
|
15
|
+
from kerykeion.settings.config_constants import DEFAULT_ACTIVE_ASPECTS, DEFAULT_AXIS_ORBIT
|
|
16
|
+
from kerykeion.settings.legacy.legacy_celestial_points_settings import DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
17
|
+
from kerykeion.settings.legacy.legacy_chart_aspects_settings import DEFAULT_CHART_ASPECTS_SETTINGS
|
|
18
|
+
from kerykeion.utilities import find_common_active_points
|
|
19
|
+
|
|
20
|
+
# Axes constants for orb filtering
|
|
21
|
+
AXES_LIST = [
|
|
22
|
+
"Ascendant",
|
|
23
|
+
"Medium_Coeli",
|
|
24
|
+
"Descendant",
|
|
25
|
+
"Imum_Coeli",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NatalAspectsFactory:
|
|
30
|
+
"""
|
|
31
|
+
Factory class for creating natal aspects analysis.
|
|
32
|
+
|
|
33
|
+
This factory calculates all aspects in a birth chart and provides both
|
|
34
|
+
comprehensive and filtered aspect lists based on orb settings and relevance.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def from_subject(
|
|
39
|
+
user: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
40
|
+
new_settings_file: Union[Path, KerykeionSettingsModel, dict, None] = None,
|
|
41
|
+
active_points: Optional[List[AstrologicalPoint]] = None,
|
|
42
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
43
|
+
) -> NatalAspectsModel:
|
|
44
|
+
"""
|
|
45
|
+
Create natal aspects analysis from an existing astrological subject.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
user: The astrological subject for aspect calculation
|
|
49
|
+
new_settings_file: Custom settings file or settings model
|
|
50
|
+
active_points: List of points to include in calculations
|
|
51
|
+
active_aspects: List of aspects with their orb settings
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
NatalAspectsModel containing all calculated aspects data
|
|
55
|
+
"""
|
|
56
|
+
# Initialize settings and configurations
|
|
57
|
+
celestial_points = DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
58
|
+
aspects_settings = DEFAULT_CHART_ASPECTS_SETTINGS
|
|
59
|
+
axes_orbit_settings = DEFAULT_AXIS_ORBIT
|
|
60
|
+
|
|
61
|
+
# Set active aspects with default fallback
|
|
62
|
+
active_aspects_resolved = active_aspects if active_aspects is not None else DEFAULT_ACTIVE_ASPECTS
|
|
63
|
+
|
|
64
|
+
# Determine active points to use
|
|
65
|
+
if active_points is None:
|
|
66
|
+
active_points_resolved = user.active_points
|
|
67
|
+
else:
|
|
68
|
+
active_points_resolved = find_common_active_points(
|
|
69
|
+
user.active_points,
|
|
70
|
+
active_points,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return NatalAspectsFactory._create_natal_aspects_model(
|
|
74
|
+
user, active_points_resolved, active_aspects_resolved,
|
|
75
|
+
aspects_settings, axes_orbit_settings, celestial_points
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _create_natal_aspects_model(
|
|
80
|
+
user: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
81
|
+
active_points_resolved: List[AstrologicalPoint],
|
|
82
|
+
active_aspects_resolved: List[ActiveAspect],
|
|
83
|
+
aspects_settings: List[dict],
|
|
84
|
+
axes_orbit_settings: float,
|
|
85
|
+
celestial_points: List[dict]
|
|
86
|
+
) -> NatalAspectsModel:
|
|
87
|
+
"""
|
|
88
|
+
Create the complete natal aspects model with all calculations.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
NatalAspectsModel containing all aspects data
|
|
92
|
+
"""
|
|
93
|
+
all_aspects = NatalAspectsFactory._calculate_all_aspects(
|
|
94
|
+
user, active_points_resolved, active_aspects_resolved, aspects_settings, celestial_points
|
|
95
|
+
)
|
|
96
|
+
relevant_aspects = NatalAspectsFactory._filter_relevant_aspects(all_aspects, axes_orbit_settings)
|
|
97
|
+
|
|
98
|
+
return NatalAspectsModel(
|
|
99
|
+
subject=user,
|
|
100
|
+
all_aspects=all_aspects,
|
|
101
|
+
relevant_aspects=relevant_aspects,
|
|
102
|
+
active_points=active_points_resolved,
|
|
103
|
+
active_aspects=active_aspects_resolved,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _calculate_all_aspects(
|
|
108
|
+
user: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
109
|
+
active_points: List[AstrologicalPoint],
|
|
110
|
+
active_aspects: List[ActiveAspect],
|
|
111
|
+
aspects_settings: List[dict],
|
|
112
|
+
celestial_points: List[dict]
|
|
113
|
+
) -> List[AspectModel]:
|
|
114
|
+
"""
|
|
115
|
+
Calculate all aspects between active points in the natal chart.
|
|
116
|
+
|
|
117
|
+
This method handles all aspect calculations including settings updates,
|
|
118
|
+
opposite pair filtering, and planet ID resolution in a single comprehensive method.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of all calculated AspectModel instances
|
|
122
|
+
"""
|
|
123
|
+
active_points_list = get_active_points_list(user, active_points)
|
|
124
|
+
|
|
125
|
+
# Update aspects settings with active aspects orbs
|
|
126
|
+
filtered_settings = []
|
|
127
|
+
for aspect_setting in aspects_settings:
|
|
128
|
+
for active_aspect in active_aspects:
|
|
129
|
+
if aspect_setting["name"] == active_aspect["name"]:
|
|
130
|
+
aspect_setting = aspect_setting.copy() # Don't modify original
|
|
131
|
+
aspect_setting["orb"] = active_aspect["orb"]
|
|
132
|
+
filtered_settings.append(aspect_setting)
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
# Define opposite pairs that should be skipped
|
|
136
|
+
opposite_pairs = {
|
|
137
|
+
("Ascendant", "Descendant"),
|
|
138
|
+
("Descendant", "Ascendant"),
|
|
139
|
+
("Medium_Coeli", "Imum_Coeli"),
|
|
140
|
+
("Imum_Coeli", "Medium_Coeli"),
|
|
141
|
+
("True_Node", "True_South_Node"),
|
|
142
|
+
("Mean_Node", "Mean_South_Node"),
|
|
143
|
+
("True_South_Node", "True_Node"),
|
|
144
|
+
("Mean_South_Node", "Mean_Node"),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
all_aspects_list = []
|
|
148
|
+
|
|
149
|
+
for first in range(len(active_points_list)):
|
|
150
|
+
# Generate aspects list without repetitions
|
|
151
|
+
for second in range(first + 1, len(active_points_list)):
|
|
152
|
+
# Skip predefined opposite pairs (AC/DC, MC/IC, North/South nodes)
|
|
153
|
+
first_name = active_points_list[first]["name"]
|
|
154
|
+
second_name = active_points_list[second]["name"]
|
|
155
|
+
|
|
156
|
+
if (first_name, second_name) in opposite_pairs:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
aspect = get_aspect_from_two_points(
|
|
160
|
+
filtered_settings,
|
|
161
|
+
active_points_list[first]["abs_pos"],
|
|
162
|
+
active_points_list[second]["abs_pos"]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if aspect["verdict"]:
|
|
166
|
+
# Get planet IDs directly from celestial points settings
|
|
167
|
+
first_planet_id = 0
|
|
168
|
+
second_planet_id = 0
|
|
169
|
+
|
|
170
|
+
for planet in celestial_points:
|
|
171
|
+
if planet["name"] == first_name:
|
|
172
|
+
first_planet_id = planet["id"]
|
|
173
|
+
if planet["name"] == second_name:
|
|
174
|
+
second_planet_id = planet["id"]
|
|
175
|
+
|
|
176
|
+
aspect_model = AspectModel(
|
|
177
|
+
p1_name=first_name,
|
|
178
|
+
p1_owner=user.name,
|
|
179
|
+
p1_abs_pos=active_points_list[first]["abs_pos"],
|
|
180
|
+
p2_name=second_name,
|
|
181
|
+
p2_owner=user.name,
|
|
182
|
+
p2_abs_pos=active_points_list[second]["abs_pos"],
|
|
183
|
+
aspect=aspect["name"],
|
|
184
|
+
orbit=aspect["orbit"],
|
|
185
|
+
aspect_degrees=aspect["aspect_degrees"],
|
|
186
|
+
diff=aspect["diff"],
|
|
187
|
+
p1=first_planet_id,
|
|
188
|
+
p2=second_planet_id,
|
|
189
|
+
)
|
|
190
|
+
all_aspects_list.append(aspect_model)
|
|
191
|
+
|
|
192
|
+
return all_aspects_list
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _filter_relevant_aspects(all_aspects: List[AspectModel], axes_orbit_settings: float) -> List[AspectModel]:
|
|
196
|
+
"""
|
|
197
|
+
Filter aspects based on orb thresholds for axes and other comprehensive criteria.
|
|
198
|
+
|
|
199
|
+
This method consolidates all filtering logic including axes checks and orb thresholds
|
|
200
|
+
into a single comprehensive filtering method.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
all_aspects: Complete list of calculated aspects
|
|
204
|
+
axes_orbit_settings: Orb threshold for axes aspects
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Filtered list of relevant aspects
|
|
208
|
+
"""
|
|
209
|
+
logging.debug("Calculating relevant aspects by filtering orbs...")
|
|
210
|
+
|
|
211
|
+
relevant_aspects = []
|
|
212
|
+
|
|
213
|
+
for aspect in all_aspects:
|
|
214
|
+
# Check if aspect involves any of the chart axes and apply stricter orb limits
|
|
215
|
+
aspect_involves_axes = (aspect.p1_name in AXES_LIST or aspect.p2_name in AXES_LIST)
|
|
216
|
+
|
|
217
|
+
if aspect_involves_axes and abs(aspect.orbit) >= axes_orbit_settings:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
relevant_aspects.append(aspect)
|
|
221
|
+
|
|
222
|
+
return relevant_aspects
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
from kerykeion.utilities import setup_logging
|
|
227
|
+
|
|
228
|
+
setup_logging(level="debug")
|
|
229
|
+
|
|
230
|
+
# Create subject using AstrologicalSubjectFactory
|
|
231
|
+
johnny = AstrologicalSubjectFactory.from_birth_data("Johnny Depp", 1963, 6, 9, 0, 0, city="Owensboro", nation="US")
|
|
232
|
+
|
|
233
|
+
# Create aspects analysis from subject
|
|
234
|
+
natal_aspects = NatalAspectsFactory.from_subject(johnny)
|
|
235
|
+
print(f"All aspects count: {len(natal_aspects.all_aspects)}")
|
|
236
|
+
print(f"Relevant aspects count: {len(natal_aspects.relevant_aspects)}")
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This is part of Kerykeion (C) 2025 Giacomo Battaglia
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Union, List, Optional
|
|
9
|
+
|
|
10
|
+
from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
|
|
11
|
+
from kerykeion.aspects.aspects_utils import get_aspect_from_two_points, get_active_points_list
|
|
12
|
+
from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, AspectModel, ActiveAspect, CompositeSubjectModel, PlanetReturnModel, SynastryAspectsModel
|
|
13
|
+
from kerykeion.kr_types.settings_models import KerykeionSettingsModel
|
|
14
|
+
from kerykeion.settings.config_constants import DEFAULT_ACTIVE_ASPECTS, DEFAULT_AXIS_ORBIT
|
|
15
|
+
from kerykeion.settings.legacy.legacy_celestial_points_settings import DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
16
|
+
from kerykeion.settings.legacy.legacy_chart_aspects_settings import DEFAULT_CHART_ASPECTS_SETTINGS
|
|
17
|
+
from kerykeion.kr_types.kr_literals import AstrologicalPoint
|
|
18
|
+
from kerykeion.utilities import find_common_active_points
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Axes constants for orb filtering
|
|
22
|
+
AXES_LIST = [
|
|
23
|
+
"Ascendant",
|
|
24
|
+
"Medium_Coeli",
|
|
25
|
+
"Descendant",
|
|
26
|
+
"Imum_Coeli",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SynastryAspectsFactory:
|
|
31
|
+
"""
|
|
32
|
+
Factory class for creating synastry aspects analysis between two subjects.
|
|
33
|
+
|
|
34
|
+
This factory calculates all aspects between two charts and provides both
|
|
35
|
+
comprehensive and filtered aspect lists based on orb settings and relevance.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def from_subjects(
|
|
40
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
41
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
42
|
+
new_settings_file: Union[Path, KerykeionSettingsModel, dict, None] = None,
|
|
43
|
+
active_points: Optional[List[AstrologicalPoint]] = None,
|
|
44
|
+
active_aspects: Optional[List[ActiveAspect]] = None,
|
|
45
|
+
) -> SynastryAspectsModel:
|
|
46
|
+
"""
|
|
47
|
+
Create synastry aspects analysis from two existing astrological subjects.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
first_subject: The first astrological subject
|
|
51
|
+
second_subject: The second astrological subject
|
|
52
|
+
new_settings_file: Custom settings file or settings model
|
|
53
|
+
active_points: List of points to include in calculations
|
|
54
|
+
active_aspects: List of aspects with their orb settings
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
SynastryAspectsModel containing all calculated aspects data
|
|
58
|
+
"""
|
|
59
|
+
# Initialize settings and configurations
|
|
60
|
+
celestial_points = DEFAULT_CELESTIAL_POINTS_SETTINGS
|
|
61
|
+
aspects_settings = DEFAULT_CHART_ASPECTS_SETTINGS
|
|
62
|
+
axes_orbit_settings = DEFAULT_AXIS_ORBIT
|
|
63
|
+
|
|
64
|
+
# Set active aspects with default fallback
|
|
65
|
+
active_aspects_resolved = active_aspects if active_aspects is not None else DEFAULT_ACTIVE_ASPECTS
|
|
66
|
+
|
|
67
|
+
# Determine active points to use - find common points between both subjects
|
|
68
|
+
if active_points is None:
|
|
69
|
+
active_points_resolved = first_subject.active_points
|
|
70
|
+
else:
|
|
71
|
+
active_points_resolved = find_common_active_points(
|
|
72
|
+
first_subject.active_points,
|
|
73
|
+
active_points,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Further filter with second subject's active points
|
|
77
|
+
active_points_resolved = find_common_active_points(
|
|
78
|
+
second_subject.active_points,
|
|
79
|
+
active_points_resolved,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return SynastryAspectsFactory._create_synastry_aspects_model(
|
|
83
|
+
first_subject, second_subject, active_points_resolved, active_aspects_resolved,
|
|
84
|
+
aspects_settings, axes_orbit_settings, celestial_points
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _create_synastry_aspects_model(
|
|
89
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
90
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
91
|
+
active_points_resolved: List[AstrologicalPoint],
|
|
92
|
+
active_aspects_resolved: List[ActiveAspect],
|
|
93
|
+
aspects_settings: List[dict],
|
|
94
|
+
axes_orbit_settings: float,
|
|
95
|
+
celestial_points: List[dict]
|
|
96
|
+
) -> SynastryAspectsModel:
|
|
97
|
+
"""
|
|
98
|
+
Create the complete synastry aspects model with all calculations.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
SynastryAspectsModel containing all aspects data
|
|
102
|
+
"""
|
|
103
|
+
all_aspects = SynastryAspectsFactory._calculate_all_aspects(
|
|
104
|
+
first_subject, second_subject, active_points_resolved, active_aspects_resolved,
|
|
105
|
+
aspects_settings, celestial_points
|
|
106
|
+
)
|
|
107
|
+
relevant_aspects = SynastryAspectsFactory._filter_relevant_aspects(all_aspects, axes_orbit_settings)
|
|
108
|
+
|
|
109
|
+
return SynastryAspectsModel(
|
|
110
|
+
first_subject=first_subject,
|
|
111
|
+
second_subject=second_subject,
|
|
112
|
+
all_aspects=all_aspects,
|
|
113
|
+
relevant_aspects=relevant_aspects,
|
|
114
|
+
active_points=active_points_resolved,
|
|
115
|
+
active_aspects=active_aspects_resolved,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _calculate_all_aspects(
|
|
120
|
+
first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
121
|
+
second_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
122
|
+
active_points: List[AstrologicalPoint],
|
|
123
|
+
active_aspects: List[ActiveAspect],
|
|
124
|
+
aspects_settings: List[dict],
|
|
125
|
+
celestial_points: List[dict]
|
|
126
|
+
) -> List[AspectModel]:
|
|
127
|
+
"""
|
|
128
|
+
Calculate all synastry aspects between two subjects.
|
|
129
|
+
|
|
130
|
+
This method handles all aspect calculations including settings updates
|
|
131
|
+
and planet ID resolution in a single comprehensive method.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of all calculated AspectModel instances
|
|
135
|
+
"""
|
|
136
|
+
# Get active points lists for both subjects
|
|
137
|
+
first_active_points_list = get_active_points_list(first_subject, active_points)
|
|
138
|
+
second_active_points_list = get_active_points_list(second_subject, active_points)
|
|
139
|
+
|
|
140
|
+
# Update aspects settings with active aspects orbs
|
|
141
|
+
filtered_settings = []
|
|
142
|
+
for aspect_setting in aspects_settings:
|
|
143
|
+
for active_aspect in active_aspects:
|
|
144
|
+
if aspect_setting["name"] == active_aspect["name"]:
|
|
145
|
+
aspect_setting = aspect_setting.copy() # Don't modify original
|
|
146
|
+
aspect_setting["orb"] = active_aspect["orb"]
|
|
147
|
+
filtered_settings.append(aspect_setting)
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
all_aspects_list = []
|
|
151
|
+
for first in range(len(first_active_points_list)):
|
|
152
|
+
# Generate aspects list between all points of first and second subjects
|
|
153
|
+
for second in range(len(second_active_points_list)):
|
|
154
|
+
aspect = get_aspect_from_two_points(
|
|
155
|
+
filtered_settings,
|
|
156
|
+
first_active_points_list[first]["abs_pos"],
|
|
157
|
+
second_active_points_list[second]["abs_pos"],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if aspect["verdict"]:
|
|
161
|
+
# Get planet IDs directly from celestial points settings
|
|
162
|
+
first_planet_id = 0
|
|
163
|
+
second_planet_id = 0
|
|
164
|
+
|
|
165
|
+
first_name = first_active_points_list[first]["name"]
|
|
166
|
+
second_name = second_active_points_list[second]["name"]
|
|
167
|
+
|
|
168
|
+
for planet in celestial_points:
|
|
169
|
+
if planet["name"] == first_name:
|
|
170
|
+
first_planet_id = planet["id"]
|
|
171
|
+
if planet["name"] == second_name:
|
|
172
|
+
second_planet_id = planet["id"]
|
|
173
|
+
|
|
174
|
+
aspect_model = AspectModel(
|
|
175
|
+
p1_name=first_name,
|
|
176
|
+
p1_owner=first_subject.name,
|
|
177
|
+
p1_abs_pos=first_active_points_list[first]["abs_pos"],
|
|
178
|
+
p2_name=second_name,
|
|
179
|
+
p2_owner=second_subject.name,
|
|
180
|
+
p2_abs_pos=second_active_points_list[second]["abs_pos"],
|
|
181
|
+
aspect=aspect["name"],
|
|
182
|
+
orbit=aspect["orbit"],
|
|
183
|
+
aspect_degrees=aspect["aspect_degrees"],
|
|
184
|
+
diff=aspect["diff"],
|
|
185
|
+
p1=first_planet_id,
|
|
186
|
+
p2=second_planet_id,
|
|
187
|
+
)
|
|
188
|
+
all_aspects_list.append(aspect_model)
|
|
189
|
+
|
|
190
|
+
return all_aspects_list
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _filter_relevant_aspects(all_aspects: List[AspectModel], axes_orbit_settings: float) -> List[AspectModel]:
|
|
194
|
+
"""
|
|
195
|
+
Filter aspects based on orb thresholds for axes and comprehensive criteria.
|
|
196
|
+
|
|
197
|
+
This method consolidates all filtering logic including axes checks and orb thresholds
|
|
198
|
+
for synastry aspects in a single comprehensive filtering method.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
all_aspects: Complete list of calculated aspects
|
|
202
|
+
axes_orbit_settings: Orb threshold for axes aspects
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Filtered list of relevant aspects
|
|
206
|
+
"""
|
|
207
|
+
logging.debug("Calculating relevant synastry aspects by filtering orbs...")
|
|
208
|
+
|
|
209
|
+
relevant_aspects = []
|
|
210
|
+
|
|
211
|
+
for aspect in all_aspects:
|
|
212
|
+
# Check if aspect involves any of the chart axes and apply stricter orb limits
|
|
213
|
+
aspect_involves_axes = (aspect.p1_name in AXES_LIST or aspect.p2_name in AXES_LIST)
|
|
214
|
+
|
|
215
|
+
if aspect_involves_axes and abs(aspect.orbit) >= axes_orbit_settings:
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
relevant_aspects.append(aspect)
|
|
219
|
+
|
|
220
|
+
return relevant_aspects
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
from kerykeion.utilities import setup_logging
|
|
225
|
+
|
|
226
|
+
setup_logging(level="debug")
|
|
227
|
+
|
|
228
|
+
john = AstrologicalSubjectFactory.from_birth_data("John", 1940, 10, 9, 10, 30, "Liverpool", "GB")
|
|
229
|
+
yoko = AstrologicalSubjectFactory.from_birth_data("Yoko", 1933, 2, 18, 10, 30, "Tokyo", "JP")
|
|
230
|
+
|
|
231
|
+
# Create synastry aspects analysis using the factory
|
|
232
|
+
synastry_aspects_model = SynastryAspectsFactory.from_subjects(john, yoko)
|
|
233
|
+
print(f"All synastry aspects: {len(synastry_aspects_model.all_aspects)}")
|
|
234
|
+
print(f"Relevant synastry aspects: {len(synastry_aspects_model.relevant_aspects)}")
|
|
@@ -6,14 +6,11 @@
|
|
|
6
6
|
import pytz
|
|
7
7
|
import swisseph as swe
|
|
8
8
|
import logging
|
|
9
|
-
import warnings
|
|
10
9
|
import math
|
|
11
10
|
from datetime import datetime
|
|
12
11
|
from pathlib import Path
|
|
13
|
-
from typing import
|
|
14
|
-
from functools import cached_property, lru_cache
|
|
12
|
+
from typing import Optional, List, Dict, Any, get_args, cast
|
|
15
13
|
from dataclasses import dataclass, field
|
|
16
|
-
from typing import Callable
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
from kerykeion.fetch_geonames import FetchGeonames
|
|
@@ -21,8 +18,6 @@ from kerykeion.kr_types import (
|
|
|
21
18
|
KerykeionException,
|
|
22
19
|
ZodiacType,
|
|
23
20
|
AstrologicalSubjectModel,
|
|
24
|
-
LunarPhaseModel,
|
|
25
|
-
KerykeionPointModel,
|
|
26
21
|
PointType,
|
|
27
22
|
SiderealMode,
|
|
28
23
|
HousesSystemIdentifier,
|
|
@@ -31,7 +26,6 @@ from kerykeion.kr_types import (
|
|
|
31
26
|
Houses,
|
|
32
27
|
)
|
|
33
28
|
from kerykeion.utilities import (
|
|
34
|
-
get_number_from_name,
|
|
35
29
|
get_kerykeion_point_from_degree,
|
|
36
30
|
get_planet_house,
|
|
37
31
|
check_and_adjust_polar_latitude,
|
|
@@ -527,7 +521,8 @@ class AstrologicalSubjectFactory:
|
|
|
527
521
|
def _calculate_houses(cls, data: Dict[str, Any], active_points: Optional[List[AstrologicalPoint]]) -> None:
|
|
528
522
|
"""Calculate house cusps and axis points"""
|
|
529
523
|
# Skip calculation if point is not in active_points
|
|
530
|
-
should_calculate:
|
|
524
|
+
def should_calculate(point: AstrologicalPoint) -> bool:
|
|
525
|
+
return not active_points or point in active_points
|
|
531
526
|
# Track which axial cusps are actually calculated
|
|
532
527
|
calculated_axial_cusps = []
|
|
533
528
|
|
|
@@ -656,7 +651,8 @@ class AstrologicalSubjectFactory:
|
|
|
656
651
|
def _calculate_planets(cls, data: Dict[str, Any], active_points: List[AstrologicalPoint]) -> None:
|
|
657
652
|
"""Calculate planetary positions and related information"""
|
|
658
653
|
# Skip calculation if point is not in active_points
|
|
659
|
-
should_calculate:
|
|
654
|
+
def should_calculate(point: AstrologicalPoint) -> bool:
|
|
655
|
+
return not active_points or point in active_points
|
|
660
656
|
|
|
661
657
|
point_type: PointType = "AstrologicalPoint"
|
|
662
658
|
julian_day = data["julian_day"]
|
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -2,7 +2,7 @@ import math
|
|
|
2
2
|
import datetime
|
|
3
3
|
from kerykeion.kr_types import KerykeionException, ChartType
|
|
4
4
|
from kerykeion.kr_types.kr_literals import AstrologicalPoint
|
|
5
|
-
from typing import Union, Literal
|
|
5
|
+
from typing import Union, Literal
|
|
6
6
|
from kerykeion.kr_types.kr_models import AspectModel, KerykeionPointModel, CompositeSubjectModel, PlanetReturnModel, AstrologicalSubjectModel
|
|
7
7
|
from kerykeion.kr_types.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsCelestialPointModel
|
|
8
8
|
from kerykeion.house_comparison import HouseComparisonModel
|
|
@@ -645,15 +645,15 @@ def draw_houses_cusps_and_text_number(
|
|
|
645
645
|
|
|
646
646
|
# Add the house number text for the second subject
|
|
647
647
|
fill_opacity = "0" if chart_type == "Transit" else ".4"
|
|
648
|
-
path +=
|
|
648
|
+
path += '<g kr:node="HouseNumber">'
|
|
649
649
|
path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: {fill_opacity}; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
|
|
650
|
-
path +=
|
|
650
|
+
path += "</g>"
|
|
651
651
|
|
|
652
652
|
# Add the house cusp line for the second subject
|
|
653
653
|
stroke_opacity = "0" if chart_type == "Transit" else ".3"
|
|
654
|
-
path +=
|
|
654
|
+
path += '<g kr:node="Cusp">'
|
|
655
655
|
path += f"<line x1='{t_x1}' y1='{t_y1}' x2='{t_x2}' y2='{t_y2}' style='stroke: {t_linecolor}; stroke-width: 1px; stroke-opacity:{stroke_opacity};'/>"
|
|
656
|
-
path +=
|
|
656
|
+
path += "</g>"
|
|
657
657
|
|
|
658
658
|
# Adjust dropin based on chart type
|
|
659
659
|
dropin = {"Transit": 84, "Synastry": 84, "Return": 84, "ExternalNatal": 100}.get(chart_type, 48)
|
|
@@ -661,14 +661,14 @@ def draw_houses_cusps_and_text_number(
|
|
|
661
661
|
ytext = sliceToY(0, (r - dropin), text_offset) + dropin
|
|
662
662
|
|
|
663
663
|
# Add the house cusp line for the first subject
|
|
664
|
-
path +=
|
|
664
|
+
path += '<g kr:node="Cusp">'
|
|
665
665
|
path += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {linecolor}; stroke-width: 1px; stroke-dasharray:3,2; stroke-opacity:.4;"/>'
|
|
666
|
-
path +=
|
|
666
|
+
path += "</g>"
|
|
667
667
|
|
|
668
668
|
# Add the house number text for the first subject
|
|
669
|
-
path +=
|
|
669
|
+
path += '<g kr:node="HouseNumber">'
|
|
670
670
|
path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: .6; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
|
|
671
|
-
path +=
|
|
671
|
+
path += "</g>"
|
|
672
672
|
|
|
673
673
|
return path
|
|
674
674
|
|
|
@@ -740,14 +740,14 @@ def draw_transit_aspect_list(
|
|
|
740
740
|
inner_path += f'<use x="15" y="0" xlink:href="#orb{id_value}" />'
|
|
741
741
|
|
|
742
742
|
# Second planet symbol
|
|
743
|
-
inner_path +=
|
|
743
|
+
inner_path += '<g transform="translate(30,0)">'
|
|
744
744
|
inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspect["p2"]]["name"]}" />'
|
|
745
|
-
inner_path +=
|
|
745
|
+
inner_path += "</g>"
|
|
746
746
|
|
|
747
747
|
# Difference in degrees
|
|
748
748
|
inner_path += f'<text y="8" x="45" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 10px;">{convert_decimal_to_degree_string(aspect["orbit"])}</text>'
|
|
749
749
|
|
|
750
|
-
inner_path +=
|
|
750
|
+
inner_path += "</g>"
|
|
751
751
|
|
|
752
752
|
out = '<g transform="translate(565,273)">'
|
|
753
753
|
out += f'<text y="-15" x="0" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 14px;">{grid_title}:</text>'
|