kerykeion.aspects.natal_aspects
This is part of Kerykeion (C) 2025 Giacomo Battaglia
1# -*- coding: utf-8 -*- 2""" 3 This is part of Kerykeion (C) 2025 Giacomo Battaglia 4""" 5 6from pathlib import Path 7from kerykeion import AstrologicalSubject 8from kerykeion.kr_types import CompositeSubjectModel 9import logging 10from typing import Union, List 11from kerykeion.settings.kerykeion_settings import get_settings 12from dataclasses import dataclass, field 13from functools import cached_property 14from kerykeion.aspects.aspects_utils import planet_id_decoder, get_aspect_from_two_points, get_active_points_list 15from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, AspectModel, ActiveAspect 16from kerykeion.kr_types.kr_literals import AxialCusps, Planet 17from kerykeion.kr_types.settings_models import KerykeionSettingsModel 18from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS, DEFAULT_ACTIVE_ASPECTS 19 20 21 22AXES_LIST = [ 23 "Ascendant", 24 "Medium_Coeli", 25 "Descendant", 26 "Imum_Coeli", 27] 28 29 30@dataclass 31class NatalAspects: 32 """ 33 Generates an object with all the aspects of a birthcart. 34 """ 35 36 user: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel] 37 new_settings_file: Union[Path, KerykeionSettingsModel, dict, None] = None 38 active_points: List[Union[AxialCusps, Planet]] = field(default_factory=lambda: DEFAULT_ACTIVE_POINTS) 39 active_aspects: List[ActiveAspect] = field(default_factory=lambda: DEFAULT_ACTIVE_ASPECTS) 40 41 def __post_init__(self): 42 self.settings = get_settings(self.new_settings_file) 43 44 self.celestial_points = self.settings.celestial_points 45 self.aspects_settings = self.settings.aspects 46 self.axes_orbit_settings = self.settings.general_settings.axes_orbit 47 self.active_points = self.active_points 48 49 @cached_property 50 def all_aspects(self): 51 """ 52 Return all the aspects of the points in the natal chart in a dictionary, 53 first all the individual aspects of each planet, second the aspects 54 without repetitions. 55 """ 56 57 active_points_list = get_active_points_list(self.user, self.settings, self.active_points) 58 59 # ---> TODO: Clean this up 60 filtered_settings = [] 61 for a in self.aspects_settings: 62 for aspect in self.active_aspects: 63 if a["name"] == aspect["name"]: 64 a["orb"] = aspect["orb"] # Assign the aspect's orb 65 filtered_settings.append(a) 66 break # Exit the inner loop once a match is found 67 self.aspects_settings = filtered_settings 68 # <--- TODO: Clean this up 69 70 self.all_aspects_list = [] 71 for first in range(len(active_points_list)): 72 # Generates the aspects list without repetitions 73 for second in range(first + 1, len(active_points_list)): 74 # AC/DC, MC/IC and North/South nodes are always in opposition 75 opposite_pairs = { 76 ("Ascendant", "Descendant"), 77 ("Descendant", "Ascendant"), 78 ("Medium_Coeli", "Imum_Coeli"), 79 ("Imum_Coeli", "Medium_Coeli"), 80 ("True_Node", "True_South_Node"), 81 ("Mean_Node", "Mean_South_Node"), 82 ("True_South_Node", "True_Node"), 83 ("Mean_South_Node", "Mean_Node"), 84 } 85 if (active_points_list[first]["name"], active_points_list[second]["name"]) in opposite_pairs: 86 continue 87 88 aspect = get_aspect_from_two_points( 89 self.aspects_settings, 90 active_points_list[first]["abs_pos"], 91 active_points_list[second]["abs_pos"] 92 ) 93 94 verdict = aspect["verdict"] 95 name = aspect["name"] 96 orbit = aspect["orbit"] 97 aspect_degrees = aspect["aspect_degrees"] 98 diff = aspect["diff"] 99 100 if verdict == True: 101 aspect_model = AspectModel( 102 p1_name=active_points_list[first]["name"], 103 p1_abs_pos=active_points_list[first]["abs_pos"], 104 p2_name=active_points_list[second]["name"], 105 p2_abs_pos=active_points_list[second]["abs_pos"], 106 aspect=name, 107 orbit=orbit, 108 aspect_degrees=aspect_degrees, 109 diff=diff, 110 p1=planet_id_decoder(self.celestial_points, active_points_list[first]["name"]), 111 p2=planet_id_decoder(self.celestial_points, active_points_list[second]["name"]), 112 ) 113 self.all_aspects_list.append(aspect_model) 114 115 return self.all_aspects_list 116 117 @cached_property 118 def relevant_aspects(self): 119 """ 120 Filters the aspects list with the desired points, in this case 121 the most important are hardcoded. 122 Set the list with set_points and creating a list with the names 123 or the numbers of the houses. 124 The relevant aspects are the ones that are set as looping in the available_aspects list. 125 """ 126 127 logging.debug("Relevant aspects not already calculated, calculating now...") 128 self.all_aspects 129 130 axes_list = AXES_LIST 131 counter = 0 132 133 # Remove aspects where the orbits exceed the maximum orb thresholds specified in the settings 134 # (specified usually in kr.config.json file) 135 aspects_filtered = self.all_aspects 136 aspects_list_subtract = [] 137 for a in aspects_filtered: 138 counter += 1 139 name_p1 = str(a["p1_name"]) 140 name_p2 = str(a["p2_name"]) 141 142 if name_p1 in axes_list: 143 if abs(a["orbit"]) >= self.axes_orbit_settings: 144 aspects_list_subtract.append(a) 145 146 elif name_p2 in axes_list: 147 if abs(a["orbit"]) >= self.axes_orbit_settings: 148 aspects_list_subtract.append(a) 149 150 self.aspects = [item for item in aspects_filtered if item not in aspects_list_subtract] 151 152 return self.aspects 153 154 155if __name__ == "__main__": 156 from kerykeion.utilities import setup_logging 157 158 setup_logging(level="debug") 159 160 johnny = AstrologicalSubject("Johnny Depp", 1963, 6, 9, 0, 0, "Owensboro", "US") 161 162 # All aspects as a list of dictionaries 163 aspects = NatalAspects(johnny) 164 #print([a.model_dump() for a in aspects.all_aspects]) 165 166 print("\n") 167 168 # Relevant aspects as a list of dictionaries 169 aspects = NatalAspects(johnny) 170 print([a.model_dump() for a in aspects.relevant_aspects])
AXES_LIST =
['Ascendant', 'Medium_Coeli', 'Descendant', 'Imum_Coeli']
@dataclass
class
NatalAspects:
31@dataclass 32class NatalAspects: 33 """ 34 Generates an object with all the aspects of a birthcart. 35 """ 36 37 user: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel] 38 new_settings_file: Union[Path, KerykeionSettingsModel, dict, None] = None 39 active_points: List[Union[AxialCusps, Planet]] = field(default_factory=lambda: DEFAULT_ACTIVE_POINTS) 40 active_aspects: List[ActiveAspect] = field(default_factory=lambda: DEFAULT_ACTIVE_ASPECTS) 41 42 def __post_init__(self): 43 self.settings = get_settings(self.new_settings_file) 44 45 self.celestial_points = self.settings.celestial_points 46 self.aspects_settings = self.settings.aspects 47 self.axes_orbit_settings = self.settings.general_settings.axes_orbit 48 self.active_points = self.active_points 49 50 @cached_property 51 def all_aspects(self): 52 """ 53 Return all the aspects of the points in the natal chart in a dictionary, 54 first all the individual aspects of each planet, second the aspects 55 without repetitions. 56 """ 57 58 active_points_list = get_active_points_list(self.user, self.settings, self.active_points) 59 60 # ---> TODO: Clean this up 61 filtered_settings = [] 62 for a in self.aspects_settings: 63 for aspect in self.active_aspects: 64 if a["name"] == aspect["name"]: 65 a["orb"] = aspect["orb"] # Assign the aspect's orb 66 filtered_settings.append(a) 67 break # Exit the inner loop once a match is found 68 self.aspects_settings = filtered_settings 69 # <--- TODO: Clean this up 70 71 self.all_aspects_list = [] 72 for first in range(len(active_points_list)): 73 # Generates the aspects list without repetitions 74 for second in range(first + 1, len(active_points_list)): 75 # AC/DC, MC/IC and North/South nodes are always in opposition 76 opposite_pairs = { 77 ("Ascendant", "Descendant"), 78 ("Descendant", "Ascendant"), 79 ("Medium_Coeli", "Imum_Coeli"), 80 ("Imum_Coeli", "Medium_Coeli"), 81 ("True_Node", "True_South_Node"), 82 ("Mean_Node", "Mean_South_Node"), 83 ("True_South_Node", "True_Node"), 84 ("Mean_South_Node", "Mean_Node"), 85 } 86 if (active_points_list[first]["name"], active_points_list[second]["name"]) in opposite_pairs: 87 continue 88 89 aspect = get_aspect_from_two_points( 90 self.aspects_settings, 91 active_points_list[first]["abs_pos"], 92 active_points_list[second]["abs_pos"] 93 ) 94 95 verdict = aspect["verdict"] 96 name = aspect["name"] 97 orbit = aspect["orbit"] 98 aspect_degrees = aspect["aspect_degrees"] 99 diff = aspect["diff"] 100 101 if verdict == True: 102 aspect_model = AspectModel( 103 p1_name=active_points_list[first]["name"], 104 p1_abs_pos=active_points_list[first]["abs_pos"], 105 p2_name=active_points_list[second]["name"], 106 p2_abs_pos=active_points_list[second]["abs_pos"], 107 aspect=name, 108 orbit=orbit, 109 aspect_degrees=aspect_degrees, 110 diff=diff, 111 p1=planet_id_decoder(self.celestial_points, active_points_list[first]["name"]), 112 p2=planet_id_decoder(self.celestial_points, active_points_list[second]["name"]), 113 ) 114 self.all_aspects_list.append(aspect_model) 115 116 return self.all_aspects_list 117 118 @cached_property 119 def relevant_aspects(self): 120 """ 121 Filters the aspects list with the desired points, in this case 122 the most important are hardcoded. 123 Set the list with set_points and creating a list with the names 124 or the numbers of the houses. 125 The relevant aspects are the ones that are set as looping in the available_aspects list. 126 """ 127 128 logging.debug("Relevant aspects not already calculated, calculating now...") 129 self.all_aspects 130 131 axes_list = AXES_LIST 132 counter = 0 133 134 # Remove aspects where the orbits exceed the maximum orb thresholds specified in the settings 135 # (specified usually in kr.config.json file) 136 aspects_filtered = self.all_aspects 137 aspects_list_subtract = [] 138 for a in aspects_filtered: 139 counter += 1 140 name_p1 = str(a["p1_name"]) 141 name_p2 = str(a["p2_name"]) 142 143 if name_p1 in axes_list: 144 if abs(a["orbit"]) >= self.axes_orbit_settings: 145 aspects_list_subtract.append(a) 146 147 elif name_p2 in axes_list: 148 if abs(a["orbit"]) >= self.axes_orbit_settings: 149 aspects_list_subtract.append(a) 150 151 self.aspects = [item for item in aspects_filtered if item not in aspects_list_subtract] 152 153 return self.aspects
Generates an object with all the aspects of a birthcart.
NatalAspects( user: Union[kerykeion.astrological_subject.AstrologicalSubject, kerykeion.kr_types.kr_models.AstrologicalSubjectModel, kerykeion.kr_types.kr_models.CompositeSubjectModel], new_settings_file: Union[pathlib.Path, kerykeion.kr_types.settings_models.KerykeionSettingsModel, dict, NoneType] = None, active_points: List[Union[Literal['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Mean_Node', 'True_Node', 'Mean_South_Node', 'True_South_Node', 'Chiron', 'Mean_Lilith'], Literal['Ascendant', 'Medium_Coeli', 'Descendant', 'Imum_Coeli']]] = <factory>, active_aspects: List[kerykeion.kr_types.kr_models.ActiveAspect] = <factory>)
user: Union[kerykeion.astrological_subject.AstrologicalSubject, kerykeion.kr_types.kr_models.AstrologicalSubjectModel, kerykeion.kr_types.kr_models.CompositeSubjectModel]
new_settings_file: Union[pathlib.Path, kerykeion.kr_types.settings_models.KerykeionSettingsModel, dict, NoneType] =
None
active_points: List[Union[Literal['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Mean_Node', 'True_Node', 'Mean_South_Node', 'True_South_Node', 'Chiron', 'Mean_Lilith'], Literal['Ascendant', 'Medium_Coeli', 'Descendant', 'Imum_Coeli']]]
active_aspects: List[kerykeion.kr_types.kr_models.ActiveAspect]
all_aspects
50 @cached_property 51 def all_aspects(self): 52 """ 53 Return all the aspects of the points in the natal chart in a dictionary, 54 first all the individual aspects of each planet, second the aspects 55 without repetitions. 56 """ 57 58 active_points_list = get_active_points_list(self.user, self.settings, self.active_points) 59 60 # ---> TODO: Clean this up 61 filtered_settings = [] 62 for a in self.aspects_settings: 63 for aspect in self.active_aspects: 64 if a["name"] == aspect["name"]: 65 a["orb"] = aspect["orb"] # Assign the aspect's orb 66 filtered_settings.append(a) 67 break # Exit the inner loop once a match is found 68 self.aspects_settings = filtered_settings 69 # <--- TODO: Clean this up 70 71 self.all_aspects_list = [] 72 for first in range(len(active_points_list)): 73 # Generates the aspects list without repetitions 74 for second in range(first + 1, len(active_points_list)): 75 # AC/DC, MC/IC and North/South nodes are always in opposition 76 opposite_pairs = { 77 ("Ascendant", "Descendant"), 78 ("Descendant", "Ascendant"), 79 ("Medium_Coeli", "Imum_Coeli"), 80 ("Imum_Coeli", "Medium_Coeli"), 81 ("True_Node", "True_South_Node"), 82 ("Mean_Node", "Mean_South_Node"), 83 ("True_South_Node", "True_Node"), 84 ("Mean_South_Node", "Mean_Node"), 85 } 86 if (active_points_list[first]["name"], active_points_list[second]["name"]) in opposite_pairs: 87 continue 88 89 aspect = get_aspect_from_two_points( 90 self.aspects_settings, 91 active_points_list[first]["abs_pos"], 92 active_points_list[second]["abs_pos"] 93 ) 94 95 verdict = aspect["verdict"] 96 name = aspect["name"] 97 orbit = aspect["orbit"] 98 aspect_degrees = aspect["aspect_degrees"] 99 diff = aspect["diff"] 100 101 if verdict == True: 102 aspect_model = AspectModel( 103 p1_name=active_points_list[first]["name"], 104 p1_abs_pos=active_points_list[first]["abs_pos"], 105 p2_name=active_points_list[second]["name"], 106 p2_abs_pos=active_points_list[second]["abs_pos"], 107 aspect=name, 108 orbit=orbit, 109 aspect_degrees=aspect_degrees, 110 diff=diff, 111 p1=planet_id_decoder(self.celestial_points, active_points_list[first]["name"]), 112 p2=planet_id_decoder(self.celestial_points, active_points_list[second]["name"]), 113 ) 114 self.all_aspects_list.append(aspect_model) 115 116 return self.all_aspects_list
Return all the aspects of the points in the natal chart in a dictionary, first all the individual aspects of each planet, second the aspects without repetitions.
relevant_aspects
118 @cached_property 119 def relevant_aspects(self): 120 """ 121 Filters the aspects list with the desired points, in this case 122 the most important are hardcoded. 123 Set the list with set_points and creating a list with the names 124 or the numbers of the houses. 125 The relevant aspects are the ones that are set as looping in the available_aspects list. 126 """ 127 128 logging.debug("Relevant aspects not already calculated, calculating now...") 129 self.all_aspects 130 131 axes_list = AXES_LIST 132 counter = 0 133 134 # Remove aspects where the orbits exceed the maximum orb thresholds specified in the settings 135 # (specified usually in kr.config.json file) 136 aspects_filtered = self.all_aspects 137 aspects_list_subtract = [] 138 for a in aspects_filtered: 139 counter += 1 140 name_p1 = str(a["p1_name"]) 141 name_p2 = str(a["p2_name"]) 142 143 if name_p1 in axes_list: 144 if abs(a["orbit"]) >= self.axes_orbit_settings: 145 aspects_list_subtract.append(a) 146 147 elif name_p2 in axes_list: 148 if abs(a["orbit"]) >= self.axes_orbit_settings: 149 aspects_list_subtract.append(a) 150 151 self.aspects = [item for item in aspects_filtered if item not in aspects_list_subtract] 152 153 return self.aspects
Filters the aspects list with the desired points, in this case the most important are hardcoded. Set the list with set_points and creating a list with the names or the numbers of the houses. The relevant aspects are the ones that are set as looping in the available_aspects list.