kerykeion 4.24.7__py3-none-any.whl → 4.25.0__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.

@@ -0,0 +1,200 @@
1
+ from typing import Union
2
+
3
+ from kerykeion import AstrologicalSubject
4
+ from kerykeion import KerykeionException
5
+ from kerykeion.kr_types.kr_models import CompositeSubjectModel, AstrologicalSubjectModel
6
+ from kerykeion.kr_types.kr_literals import ZodiacType, PerspectiveType, HousesSystemIdentifier, SiderealMode, Planet, Houses, AxialCusps, CompositeChartType
7
+ from kerykeion.utilities import (
8
+ get_kerykeion_point_from_degree,
9
+ get_planet_house,
10
+ circular_mean,
11
+ calculate_moon_phase
12
+ )
13
+
14
+
15
+ class CompositeSubjectFactory:
16
+ """
17
+ Factory class to create a Composite Subject Model from two Astrological Subjects
18
+ Currently, the only available method for creating composite charts is the midpoint method.
19
+ The composite houses and planets are calculated based on the midpoint of the corresponding points of the two subjects.
20
+ The house are then reordered to match the original house system of the first subject.
21
+
22
+ Args:
23
+ first_subject (AstrologicalSubject): First astrological subject
24
+ second_subject (AstrologicalSubject): Second astrological subject
25
+ chart_name (str): Name of the composite chart. If None, it will be automatically generated.
26
+ """
27
+
28
+ model: Union[CompositeSubjectModel, None]
29
+ first_subject: AstrologicalSubjectModel
30
+ second_subject: AstrologicalSubjectModel
31
+ name: str
32
+ composite_chart_type: CompositeChartType
33
+ zodiac_type: ZodiacType
34
+ sidereal_mode: Union[SiderealMode, None]
35
+ houses_system_identifier: HousesSystemIdentifier
36
+ houses_system_name: str
37
+ perspective_type: PerspectiveType
38
+ planets_names_list: list[Planet]
39
+ houses_names_list: list[Houses]
40
+ axial_cusps_names_list: list[AxialCusps]
41
+
42
+ def __init__(
43
+ self,
44
+ first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
45
+ second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
46
+ chart_name: Union[str, None] = None
47
+ ):
48
+ self.model: Union[CompositeSubjectModel, None] = None
49
+ self.composite_chart_type = "Midpoint"
50
+
51
+ # Subjects
52
+ if isinstance(first_subject, AstrologicalSubject) or isinstance(first_subject, AstrologicalSubjectModel):
53
+ self.first_subject = first_subject.model() # type: ignore
54
+ self.second_subject = second_subject.model() # type: ignore
55
+ else:
56
+ self.first_subject = first_subject
57
+ self.second_subject = second_subject
58
+
59
+ # Name
60
+ if chart_name is None:
61
+ self.name = f"{first_subject.name} and {second_subject.name} Composite Chart"
62
+ else:
63
+ self.name = chart_name
64
+
65
+ # Zodiac Type
66
+ if first_subject.zodiac_type != second_subject.zodiac_type:
67
+ raise KerykeionException("Both subjects must have the same zodiac type")
68
+ self.zodiac_type = first_subject.zodiac_type
69
+
70
+ # Sidereal Mode
71
+ if first_subject.sidereal_mode != second_subject.sidereal_mode:
72
+ raise KerykeionException("Both subjects must have the same sidereal mode")
73
+
74
+ if first_subject.sidereal_mode is not None:
75
+ self.sidereal_mode = first_subject.sidereal_mode
76
+ else:
77
+ self.sidereal_mode = None
78
+
79
+ # Houses System
80
+ if first_subject.houses_system_identifier != second_subject.houses_system_identifier:
81
+ raise KerykeionException("Both subjects must have the same houses system")
82
+ self.houses_system_identifier = first_subject.houses_system_identifier
83
+
84
+ # Houses System Name
85
+ if first_subject.houses_system_name != second_subject.houses_system_name:
86
+ raise KerykeionException("Both subjects must have the same houses system name")
87
+ self.houses_system_name = first_subject.houses_system_name
88
+
89
+ # Perspective Type
90
+ if first_subject.perspective_type != second_subject.perspective_type:
91
+ raise KerykeionException("Both subjects must have the same perspective type")
92
+ self.perspective_type = first_subject.perspective_type
93
+
94
+ # Planets Names List
95
+ self.planets_names_list = []
96
+ for planet in first_subject.planets_names_list:
97
+ if planet in second_subject.planets_names_list:
98
+ self.planets_names_list.append(planet)
99
+
100
+ # Houses Names List
101
+ self.houses_names_list = self.first_subject.houses_names_list
102
+
103
+ # Axial Cusps Names List
104
+ self.axial_cusps_names_list = self.first_subject.axial_cusps_names_list
105
+
106
+ def __str__(self):
107
+ return f"Composite Chart Data for {self.name}"
108
+
109
+ def __repr__(self):
110
+ return f"Composite Chart Data for {self.name}"
111
+
112
+ def __eq__(self, other):
113
+ return self.first_subject == other.first_subject and self.second_subject == other.second_subject and self.name == other.chart_name
114
+
115
+ def __ne__(self, other):
116
+ return not self.__eq__(other)
117
+
118
+ def __hash__(self):
119
+ return hash((self.first_subject, self.second_subject, self.name))
120
+
121
+ def __copy__(self):
122
+ return CompositeSubjectFactory(self.first_subject, self.second_subject, self.name)
123
+
124
+ def __setitem__(self, key, value):
125
+ setattr(self, key, value)
126
+
127
+ def __getitem__(self, key):
128
+ return getattr(self, key)
129
+
130
+ def _calculate_midpoint_composite_points_and_houses(self):
131
+ # Houses
132
+ house_degree_list_ut = []
133
+ for house in self.first_subject.houses_names_list:
134
+ house_lower = house.lower()
135
+ self[house_lower] = get_kerykeion_point_from_degree(
136
+ circular_mean(
137
+ self.first_subject[house_lower]["abs_pos"],
138
+ self.second_subject[house_lower]["abs_pos"]
139
+ ),
140
+ house,
141
+ "House"
142
+ )
143
+ house_degree_list_ut.append(self[house_lower]["abs_pos"])
144
+
145
+ house_degree_list_ut = sorted(house_degree_list_ut)
146
+
147
+ # Planets
148
+ common_planets = []
149
+ for planet in self.first_subject.planets_names_list:
150
+ if planet in self.second_subject.planets_names_list:
151
+ common_planets.append(planet)
152
+
153
+ planets = {}
154
+ for planet in common_planets:
155
+ planet_lower = planet.lower()
156
+ planets[planet_lower] = {}
157
+ planets[planet_lower]["abs_pos"] = circular_mean(
158
+ self.first_subject[planet_lower]["abs_pos"],
159
+ self.second_subject[planet_lower]["abs_pos"]
160
+ )
161
+ self[planet_lower] = get_kerykeion_point_from_degree(planets[planet_lower]["abs_pos"], planet, "Planet")
162
+ self[planet_lower]["house"] = get_planet_house(self[planet_lower]['abs_pos'], house_degree_list_ut)
163
+
164
+
165
+ # Axial Cusps
166
+ for cusp in self.first_subject.axial_cusps_names_list:
167
+ cusp_lower = cusp.lower()
168
+ self[cusp_lower] = get_kerykeion_point_from_degree(
169
+ circular_mean(
170
+ self.first_subject[cusp_lower]["abs_pos"],
171
+ self.second_subject[cusp_lower]["abs_pos"]
172
+ ),
173
+ cusp,
174
+ "AxialCusps"
175
+ )
176
+ self[cusp_lower]["house"] = get_planet_house(self[cusp_lower]['abs_pos'], house_degree_list_ut)
177
+
178
+ def _calculate_composite_lunar_phase(self):
179
+ self.lunar_phase = calculate_moon_phase(
180
+ self['moon'].abs_pos,
181
+ self['sun'].abs_pos
182
+ )
183
+
184
+ def get_midpoint_composite_subject_model(self):
185
+ self._calculate_midpoint_composite_points_and_houses()
186
+ self._calculate_composite_lunar_phase()
187
+
188
+ return CompositeSubjectModel(
189
+ **self.__dict__
190
+ )
191
+
192
+
193
+ if __name__ == "__main__":
194
+ from kerykeion.astrological_subject import AstrologicalSubject
195
+
196
+ first = AstrologicalSubject("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
197
+ second = AstrologicalSubject("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
198
+
199
+ composite_chart = CompositeSubjectFactory(first, second)
200
+ print(composite_chart.get_midpoint_composite_subject_model().model_dump_json(indent=4))
@@ -13,18 +13,17 @@ class ChartTemplateDictionary(TypedDict):
13
13
  chart_width: float
14
14
  viewbox: str
15
15
  stringTitle: str
16
- stringName: str
17
- bottomLeft0: str
18
- bottomLeft1: str
19
- bottomLeft2: str
20
- bottomLeft3: str
21
- bottomLeft4: str
22
- moon_phase: str
23
- stringLocation: str
24
- stringDateTime: str
25
- stringLat: str
26
- stringLon: str
27
- stringPosition: str
16
+ top_left_0: str
17
+ bottom_left_0: str
18
+ bottom_left_1: str
19
+ bottom_left_2: str
20
+ bottom_left_3: str
21
+ bottom_left_4: str
22
+ top_left_1: str
23
+ top_left_2: str
24
+ top_left_3: str
25
+ top_left_4: str
26
+ top_left_5: str
28
27
 
29
28
  # Font color
30
29
  paper_color_0: str
@@ -81,8 +80,16 @@ class ChartTemplateDictionary(TypedDict):
81
80
  makeZodiac: str
82
81
  makeHouses: str
83
82
  makePlanets: str
84
- elements_percentages: str
85
83
  makePlanetGrid: str
86
84
  makeHousesGrid: str
87
85
 
88
86
  color_style_tag: str
87
+
88
+ fire_string: str
89
+ earth_string: str
90
+ air_string: str
91
+ water_string: str
92
+
93
+ lunar_phase_rotate: str
94
+ lunar_phase_circle_center_x: str
95
+ lunar_phase_circle_radius: str
@@ -40,7 +40,7 @@ Quality = Literal["Cardinal", "Fixed", "Mutable"]
40
40
  """Literal type for Qualities"""
41
41
 
42
42
 
43
- ChartType = Literal["Natal", "ExternalNatal", "Synastry", "Transit"]
43
+ ChartType = Literal["Natal", "ExternalNatal", "Synastry", "Transit", "Composite"]
44
44
  """Literal type for Chart Types"""
45
45
 
46
46
 
@@ -114,9 +114,14 @@ KerykeionChartTheme = Literal["light", "dark", "dark-high-contrast", "classic"]
114
114
  KerykeionChartLanguage = Literal["EN", "FR", "PT", "IT", "CN", "ES", "RU", "TR", "DE", "HI"]
115
115
  """Literal type for Kerykeion Chart Languages"""
116
116
 
117
+
117
118
  RelationshipScoreDescription = Literal["Minimal", "Medium", "Important", "Very Important", "Exceptional", "Rare Exceptional"]
118
119
  """Literal type for Relationship Score Description"""
119
120
 
121
+
122
+ CompositeChartType = Literal["Midpoint"]
123
+ """Literal type for Composite Chart Types"""
124
+
120
125
  AspectName = Literal[
121
126
  "conjunction",
122
127
  "semi-sextile",
@@ -25,7 +25,8 @@ from kerykeion.kr_types import (
25
25
  HousesSystemIdentifier,
26
26
  Houses,
27
27
  SignsEmoji,
28
- RelationshipScoreDescription
28
+ RelationshipScoreDescription,
29
+ PerspectiveType
29
30
  )
30
31
 
31
32
 
@@ -74,6 +75,10 @@ class KerykeionPointModel(SubscriptableBaseModel):
74
75
 
75
76
 
76
77
  class AstrologicalSubjectModel(SubscriptableBaseModel):
78
+ """
79
+ Pydantic Model for Astrological Subject
80
+ """
81
+
77
82
  # Data
78
83
  name: str
79
84
  year: int
@@ -90,7 +95,7 @@ class AstrologicalSubjectModel(SubscriptableBaseModel):
90
95
  sidereal_mode: Union[SiderealMode, None]
91
96
  houses_system_identifier: HousesSystemIdentifier
92
97
  houses_system_name: str
93
- perspective_type: str
98
+ perspective_type: PerspectiveType
94
99
  iso_formatted_local_datetime: str
95
100
  iso_formatted_utc_datetime: str
96
101
  julian_day: float
@@ -198,6 +203,78 @@ class RelationshipScoreModel(SubscriptableBaseModel):
198
203
  subjects: list[AstrologicalSubjectModel]
199
204
 
200
205
 
206
+ class CompositeSubjectModel(SubscriptableBaseModel):
207
+ """
208
+ Pydantic Model for Composite Subject
209
+ """
210
+
211
+ # Data
212
+ name: str
213
+ first_subject: AstrologicalSubjectModel
214
+ second_subject: AstrologicalSubjectModel
215
+ composite_chart_type: str
216
+
217
+ zodiac_type: ZodiacType
218
+ sidereal_mode: Union[SiderealMode, None]
219
+ houses_system_identifier: HousesSystemIdentifier
220
+ houses_system_name: str
221
+ perspective_type: PerspectiveType
222
+
223
+ # Planets
224
+ sun: KerykeionPointModel
225
+ moon: KerykeionPointModel
226
+ mercury: KerykeionPointModel
227
+ venus: KerykeionPointModel
228
+ mars: KerykeionPointModel
229
+ jupiter: KerykeionPointModel
230
+ saturn: KerykeionPointModel
231
+ uranus: KerykeionPointModel
232
+ neptune: KerykeionPointModel
233
+ pluto: KerykeionPointModel
234
+
235
+ # Axes
236
+ ascendant: KerykeionPointModel
237
+ descendant: KerykeionPointModel
238
+ medium_coeli: KerykeionPointModel
239
+ imum_coeli: KerykeionPointModel
240
+
241
+ # Optional Planets:
242
+ chiron: Union[KerykeionPointModel, None]
243
+ mean_lilith: Union[KerykeionPointModel, None]
244
+
245
+ # Houses
246
+ first_house: KerykeionPointModel
247
+ second_house: KerykeionPointModel
248
+ third_house: KerykeionPointModel
249
+ fourth_house: KerykeionPointModel
250
+ fifth_house: KerykeionPointModel
251
+ sixth_house: KerykeionPointModel
252
+ seventh_house: KerykeionPointModel
253
+ eighth_house: KerykeionPointModel
254
+ ninth_house: KerykeionPointModel
255
+ tenth_house: KerykeionPointModel
256
+ eleventh_house: KerykeionPointModel
257
+ twelfth_house: KerykeionPointModel
258
+
259
+ # Nodes
260
+ mean_node: KerykeionPointModel
261
+ true_node: KerykeionPointModel
262
+ mean_south_node: KerykeionPointModel
263
+ true_south_node: KerykeionPointModel
264
+
265
+ planets_names_list: list[Planet]
266
+ """Ordered list of available planets names"""
267
+
268
+ axial_cusps_names_list: list[AxialCusps]
269
+ """Ordered list of available axes names"""
270
+
271
+ houses_names_list: list[Houses]
272
+ """Ordered list of houses names"""
273
+
274
+ lunar_phase: LunarPhaseModel
275
+ """Lunar phase model"""
276
+
277
+
201
278
  class ActiveAspect(TypedDict):
202
279
  name: AspectName
203
280
  orb: int
@@ -137,6 +137,12 @@ class KerykeionLanguageModel(SubscriptableBaseModel):
137
137
  lunar_phase: str = Field(title="Lunar Phase", description="The name of the Lunar Phase label in the chart, in the language")
138
138
  day: str = Field(title="Day", description="The name of the Day label in the chart, in the language")
139
139
  celestial_points: KerykeionLanguageCelestialPointModel
140
+ composite_chart: str = Field(title="Composite Chart", description="The name of the Composite Chart label in the chart, in the language")
141
+ midpoints: str = Field(title="Midpoints", description="The name of the Midpoints label in the chart, in the language")
142
+ north_letter: str = Field(title="North Letter", description="The name of the North Letter label in the chart, in the language")
143
+ east_letter: str = Field(title="East Letter", description="The name of the East Letter label in the chart, in the language")
144
+ south_letter: str = Field(title="South Letter", description="The name of the South Letter label in the chart, in the language")
145
+ west_letter: str = Field(title="West Letter", description="The name of the West Letter label in the chart, in the language")
140
146
 
141
147
 
142
148
  class KerykeionGeneralSettingsModel(SubscriptableBaseModel):
@@ -21,6 +21,12 @@
21
21
  "transit_name": "At the time of the transit",
22
22
  "lunar_phase": "Lunar phase",
23
23
  "day": "Day",
24
+ "composite_chart": "Composite Chart",
25
+ "midpoints": "Midpoints",
26
+ "north_letter": "N",
27
+ "east_letter": "E",
28
+ "south_letter": "S",
29
+ "west_letter": "W",
24
30
  "celestial_points": {
25
31
  "Sun": "Sun",
26
32
  "Moon": "Moon",
@@ -65,6 +71,12 @@
65
71
  "transit_name": "Au moment de la transition",
66
72
  "lunar_phase": "Phase Lunaire",
67
73
  "day": "Jour",
74
+ "composite_chart": "Carte Composite",
75
+ "midpoints": "Milieux",
76
+ "north_letter": "N",
77
+ "east_letter": "E",
78
+ "south_letter": "S",
79
+ "west_letter": "W",
68
80
  "celestial_points": {
69
81
  "Sun": "Soleil",
70
82
  "Moon": "Lune",
@@ -109,6 +121,12 @@
109
121
  "transit_name": "No momento do trânsito",
110
122
  "lunar_phase": "Fase lunar",
111
123
  "day": "Dia",
124
+ "composite_chart": "Carta Composta",
125
+ "midpoints": "Meios",
126
+ "north_letter": "N",
127
+ "east_letter": "E",
128
+ "south_letter": "S",
129
+ "west_letter": "W",
112
130
  "celestial_points": {
113
131
  "Sun": "Sol",
114
132
  "Moon": "Lua",
@@ -153,6 +171,12 @@
153
171
  "transit_name": "Al momento del transito",
154
172
  "lunar_phase": "Fase lunare",
155
173
  "day": "Giorno",
174
+ "composite_chart": "Tema Composito",
175
+ "midpoints": "Punti Medi",
176
+ "north_letter": "N",
177
+ "east_letter": "E",
178
+ "south_letter": "S",
179
+ "west_letter": "W",
156
180
  "celestial_points": {
157
181
  "Sun": "Sole",
158
182
  "Moon": "Luna",
@@ -197,6 +221,12 @@
197
221
  "transit_name": "流年名稱",
198
222
  "lunar_phase": "月相",
199
223
  "day": "日",
224
+ "composite_chart": "合盤",
225
+ "midpoints": "中點",
226
+ "north_letter": "北",
227
+ "east_letter": "東",
228
+ "south_letter": "南",
229
+ "west_letter": "西",
200
230
  "celestial_points": {
201
231
  "Sun": "太陽",
202
232
  "Moon": "月亮",
@@ -241,6 +271,12 @@
241
271
  "transit_name": "En el momento del tránsito",
242
272
  "lunar_phase": "Fase lunar",
243
273
  "day": "Día",
274
+ "composite_chart": "Carta Compuesta",
275
+ "midpoints": "Puntos Medios",
276
+ "north_letter": "N",
277
+ "east_letter": "E",
278
+ "south_letter": "S",
279
+ "west_letter": "W",
244
280
  "celestial_points": {
245
281
  "Sun": "Sol",
246
282
  "Moon": "Luna",
@@ -285,6 +321,12 @@
285
321
  "transit_name": "В момент транзита",
286
322
  "lunar_phase": "Лунная фаза",
287
323
  "day": "День",
324
+ "composite_chart": "Композитная карта",
325
+ "midpoints": "Средние точки",
326
+ "north_letter": "С",
327
+ "east_letter": "В",
328
+ "south_letter": "Ю",
329
+ "west_letter": "З",
288
330
  "celestial_points": {
289
331
  "Sun": "Солнце",
290
332
  "Moon": "Луна",
@@ -329,6 +371,12 @@
329
371
  "transit_name": "Geçiş anında",
330
372
  "lunar_phase": "Ay Aşaması",
331
373
  "day": "Gün",
374
+ "composite_chart": "Kompozit Harita",
375
+ "midpoints": "Orta Noktalar",
376
+ "north_letter": "K",
377
+ "east_letter": "D",
378
+ "south_letter": "G",
379
+ "west_letter": "B",
332
380
  "celestial_points": {
333
381
  "Sun": "Güneş",
334
382
  "Moon": "Ay",
@@ -373,6 +421,12 @@
373
421
  "transit_name": "Zum Zeitpunkt des Transits",
374
422
  "lunar_phase": "Mondphase",
375
423
  "day": "Tag",
424
+ "composite_chart": "Komposit-Chart",
425
+ "midpoints": "Mittelpunkte",
426
+ "north_letter": "N",
427
+ "east_letter": "E",
428
+ "south_letter": "S",
429
+ "west_letter": "W",
376
430
  "celestial_points": {
377
431
  "Sun": "Sonne",
378
432
  "Moon": "Mond",
@@ -417,6 +471,12 @@
417
471
  "transit_name": "गोचर के समय",
418
472
  "lunar_phase": "चंद्र चरण",
419
473
  "day": "दिन",
474
+ "composite_chart": "संयुक्त चार्ट",
475
+ "midpoints": "मध्य बिंदु",
476
+ "north_letter": "उ",
477
+ "east_letter": "पू",
478
+ "south_letter": "द",
479
+ "west_letter": "प",
420
480
  "celestial_points": {
421
481
  "Sun": "सूर्य",
422
482
  "Moon": "चंद्रमा",
kerykeion/utilities.py CHANGED
@@ -1,4 +1,4 @@
1
- from kerykeion.kr_types import KerykeionPointModel, KerykeionException, ZodiacSignModel, AstrologicalSubjectModel
1
+ from kerykeion.kr_types import KerykeionPointModel, KerykeionException, ZodiacSignModel, AstrologicalSubjectModel, LunarPhaseModel
2
2
  from kerykeion.kr_types.kr_literals import LunarPhaseEmoji, LunarPhaseName, PointType, Planet, Houses, AxialCusps
3
3
  from typing import Union, get_args, TYPE_CHECKING
4
4
  import logging
@@ -318,3 +318,75 @@ def get_available_astrological_points_list(subject: Union["AstrologicalSubject",
318
318
  planets_absolute_position_list.append(subject[axis.lower()])
319
319
 
320
320
  return planets_absolute_position_list
321
+
322
+
323
+ def circular_mean(first_position: Union[int, float], second_position: Union[int, float]) -> float:
324
+ """
325
+ Computes the circular mean of two astrological positions (e.g., house cusps, planets).
326
+
327
+ This function ensures that positions crossing 0° Aries (360°) are correctly averaged,
328
+ avoiding errors that occur with simple linear means.
329
+
330
+ Args:
331
+ position1 (Union[int, float]): First position in degrees (0-360).
332
+ position2 (Union[int, float]): Second position in degrees (0-360).
333
+
334
+ Returns:
335
+ float: The circular mean position in degrees (0-360).
336
+ """
337
+ x = (math.cos(math.radians(first_position)) + math.cos(math.radians(second_position))) / 2
338
+ y = (math.sin(math.radians(first_position)) + math.sin(math.radians(second_position))) / 2
339
+ mean_position = math.degrees(math.atan2(y, x))
340
+
341
+ # Ensure the result is within 0-360°
342
+ if mean_position < 0:
343
+ mean_position += 360
344
+
345
+ return mean_position
346
+
347
+
348
+ def calculate_moon_phase(moon_abs_pos: float, sun_abs_pos: float) -> LunarPhaseModel:
349
+ """
350
+ Calculate the lunar phase based on the positions of the moon and sun.
351
+
352
+ Args:
353
+ - moon_abs_pos (float): The absolute position of the moon.
354
+ - sun_abs_pos (float): The absolute position of the sun.
355
+
356
+ Returns:
357
+ - dict: A dictionary containing the lunar phase information.
358
+ """
359
+ # Initialize moon_phase and sun_phase to None in case of an error
360
+ moon_phase, sun_phase = None, None
361
+
362
+ # Calculate the anti-clockwise degrees between the sun and moon
363
+ degrees_between = (moon_abs_pos - sun_abs_pos) % 360
364
+
365
+ # Calculate the moon phase (1-28) based on the degrees between the sun and moon
366
+ step = 360.0 / 28.0
367
+ moon_phase = int(degrees_between // step) + 1
368
+
369
+ # Define the sun phase steps
370
+ sunstep = [
371
+ 0, 30, 40, 50, 60, 70, 80, 90, 120, 130, 140, 150, 160, 170, 180,
372
+ 210, 220, 230, 240, 250, 260, 270, 300, 310, 320, 330, 340, 350
373
+ ]
374
+
375
+ # Calculate the sun phase (1-28) based on the degrees between the sun and moon
376
+ for x in range(len(sunstep)):
377
+ low = sunstep[x]
378
+ high = sunstep[x + 1] if x < len(sunstep) - 1 else 360
379
+ if low <= degrees_between < high:
380
+ sun_phase = x + 1
381
+ break
382
+
383
+ # Create a dictionary with the lunar phase information
384
+ lunar_phase_dictionary = {
385
+ "degrees_between_s_m": degrees_between,
386
+ "moon_phase": moon_phase,
387
+ "sun_phase": sun_phase,
388
+ "moon_emoji": get_moon_emoji_from_phase_int(moon_phase),
389
+ "moon_phase_name": get_moon_phase_name_from_phase_int(moon_phase)
390
+ }
391
+
392
+ return LunarPhaseModel(**lunar_phase_dictionary)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kerykeion
3
- Version: 4.24.7
3
+ Version: 4.25.0
4
4
  Summary: A python library for astrology.
5
5
  Home-page: https://www.kerykeion.net/
6
6
  License: AGPL-3.0
@@ -1,30 +1,31 @@
1
1
  LICENSE,sha256=UTLH8EdbAsgQei4PA2PnBCPGLSZkq5J-dhkyJuXgWQU,34273
2
- kerykeion/__init__.py,sha256=XtKNBNSVsQ-PAbnJfNUcw-femdF4NgQqohMtM0cRH18,639
2
+ kerykeion/__init__.py,sha256=5uWwosgfy16r2g4pl6TJsee8Nz5-UTrpyrH1KTgfvc8,702
3
3
  kerykeion/aspects/__init__.py,sha256=9kx_Rx1NJx5SM7nDCSbI79S1neZ3c-q2NvQr-S6A9PY,292
4
4
  kerykeion/aspects/aspects_utils.py,sha256=Ej-E7Uvfi8x_ydP9dOhzhCp2uSpvX67T_VXuOjEKdoQ,3071
5
- kerykeion/aspects/natal_aspects.py,sha256=LHSorTAMLry-0KJWRZ0QQCBiRbuYnZkoIEFVwaAG-e0,6585
5
+ kerykeion/aspects/natal_aspects.py,sha256=YAhf8Dqkx9kouealIzPae0VNK77P9-9oQhJw6dYXqo0,6661
6
6
  kerykeion/aspects/synastry_aspects.py,sha256=5vU8yJgAGRyVBoKvRpsg-0AcQg-imOzH91tvDM_RUNQ,5044
7
- kerykeion/astrological_subject.py,sha256=pzy9Fipg-ywCk5bZoGoA8bhs4M2r2JZBWoC3ZxHQrQ8,38245
7
+ kerykeion/astrological_subject.py,sha256=643axkLtIVPkOOTHhB4InHYn3XVEOBZdXLS9B-N7kSQ,36451
8
8
  kerykeion/charts/__init__.py,sha256=i9NMZ7LdkllPlqQSi1or9gTobHbROGDKmJhBDO4R0mA,128
9
- kerykeion/charts/charts_utils.py,sha256=Oai_hlWIDjZ9MDs2werutMblOZqklf5FMl3l5oPN70w,42132
9
+ kerykeion/charts/charts_utils.py,sha256=TGmya60LMswUvQMi6irJWaboK6QRWCZ52wv5FMgaUT8,40424
10
10
  kerykeion/charts/draw_planets.py,sha256=Uty3zpWYMQZvvK7ZHhlmynCHeL8DIN3qL2ifnBXVciM,17393
11
- kerykeion/charts/kerykeion_chart_svg.py,sha256=DbtfKA3ieM56YTSTBV3i1bd5zhDlsDyYafHRci6C_jw,46006
11
+ kerykeion/charts/kerykeion_chart_svg.py,sha256=KdUA4aQgVXQ91gNaNHr2K8EX-0lpMxO0GnN9C_X6u8Y,50660
12
12
  kerykeion/charts/templates/aspect_grid_only.xml,sha256=ZiBVeToVmCA8QxYlB_cfnsAO1NNeQAnJ_6rIYkr4F58,70091
13
- kerykeion/charts/templates/chart.xml,sha256=yr13hN0xsxvMQpVb9cMMK86lxrqy_PxGuOzj-4jdM4E,73733
13
+ kerykeion/charts/templates/chart.xml,sha256=2p2LNNib9MfZiq521tFoEUwOBEN6KlbKOY4ZINdf7Dc,74985
14
14
  kerykeion/charts/templates/wheel_only.xml,sha256=E0JCxcnjTjxuaomw7PQEc_3xRVErj1y2hIOGTrShuhc,71304
15
15
  kerykeion/charts/themes/classic.css,sha256=-b6XllAZmqUDjBwDtIkfzfI3Wtc8AImuGMpfAQ_2wa0,3552
16
16
  kerykeion/charts/themes/dark-high-contrast.css,sha256=9tdyFC-4Ytnv4lrVwKnOjZJ0YNgbRrP_HNnfJRlnbn0,6099
17
17
  kerykeion/charts/themes/dark.css,sha256=ml2lnzQVuS5DhCdoeUrDmMv9kB1nNceRt7hHbOHSbjM,6099
18
18
  kerykeion/charts/themes/light.css,sha256=ALf5U8tQsb6ky0N9R7ZLOHDrfKEXNM-sBR3JIRxFrCI,6092
19
+ kerykeion/composite_subject_factory.py,sha256=3wyY03mefF9OPfeFyV7Ck4BIq8oY-kSqkHi4f8Ap9B0,8049
19
20
  kerykeion/enums.py,sha256=nPXgP_ocsRnRno5H-yunZy3fp-hLZ9aYRaUb-2gBdvw,1199
20
21
  kerykeion/ephemeris_data.py,sha256=Cd5TK5nTkFHYU6bkLKjZRfiU4_6AgsWGcwBDrBHKrQk,8096
21
22
  kerykeion/fetch_geonames.py,sha256=e66Nh6yq9A4VjnuvVSiV1TW1IkJ9m3Q2LKPWrkOGgO0,4764
22
23
  kerykeion/kr_types/__init__.py,sha256=jshJOccCQcYZuoOvrILRZH6imy4RBvKpFPujlNLFyGE,295
23
- kerykeion/kr_types/chart_types.py,sha256=zO8pVFgklRFX60BS__CWAkFZ70v9ThKX2z1js-s1fQA,1986
24
+ kerykeion/kr_types/chart_types.py,sha256=EFXTddX1wwTzbLSDKw_ipg4tbOihTKPEnn2T9ooFSig,2123
24
25
  kerykeion/kr_types/kerykeion_exception.py,sha256=kE1y0K0rmuz32b4K_ZppSsZ59I2Get0ZkvOkTE5HejI,314
25
- kerykeion/kr_types/kr_literals.py,sha256=UDTgG6Hoe2378WzLpobCpYy1gLcHulbiamP8l3_GgFw,4225
26
- kerykeion/kr_types/kr_models.py,sha256=bDV_xULcg8vdI40fFjcmMMPnSicHK0potZkEdQINHB4,4722
27
- kerykeion/kr_types/settings_models.py,sha256=D2a3PugfPKgx1_-oNsFPecxgdO0RiJlzacLxG6rQNaY,11779
26
+ kerykeion/kr_types/kr_literals.py,sha256=b1JEgByA8-PWtkM8TdkNb2aePr8dUApI-OH3ciulJF8,4327
27
+ kerykeion/kr_types/kr_models.py,sha256=N7VhwxcMkXMjn5UNArleuxZ684woJzBjvvNC1CF7ttg,6824
28
+ kerykeion/kr_types/settings_models.py,sha256=zCA7xsmRBb2nH6YMf4q33DC3lG4-3eGrX_jovRoABnI,12565
28
29
  kerykeion/relationship_score/__init__.py,sha256=cLaEBQXQBfyRkv0OaS3ceLROzvWcvKXWiRq0PS6LDjY,114
29
30
  kerykeion/relationship_score/relationship_score.py,sha256=lJkSbHw9nOUaPMrPxqcGhnVQIwAgI52K8BQzXXswb6A,6504
30
31
  kerykeion/relationship_score/relationship_score_factory.py,sha256=NbQZ7jh2-7CGk83NWA8lfkJszT1uZAQQH9KtaUhdAo8,10962
@@ -32,12 +33,12 @@ kerykeion/report.py,sha256=snqnrJzb89q2ixL74qS9ksvzNSh_WXtZ_haBOIvHYeY,2814
32
33
  kerykeion/settings/__init__.py,sha256=QQNFCl7sgN27MKaVscqtpPk10HGz4wZS3I_7KEGMaVA,69
33
34
  kerykeion/settings/config_constants.py,sha256=OUi28L2l8s09Df3GHiQUiBZuTjOdfL4A5zSRNqBRHc8,1255
34
35
  kerykeion/settings/kerykeion_settings.py,sha256=7GCGUzcctEg5uyWlzRk2YIotJSkCZDOVAo_CXwgMeK4,2934
35
- kerykeion/settings/kr.config.json,sha256=01xm7VOmGxMa86VM8VL8giYmnDTkfo2aE9qjIfIouM0,21113
36
+ kerykeion/settings/kr.config.json,sha256=NJKaKkLXnNfyJXrBgwLbvOwQigDum5ls8qSqGDmhjmA,23022
36
37
  kerykeion/sweph/README.md,sha256=L7FtNAJTWtrZNGKa8MX87SjduFYPYxwWhaI5fmtzNZo,73
37
38
  kerykeion/sweph/seas_18.se1,sha256=X9nCqhZU43wJpq61WAdueVQJt9xL2UjrwPqn1Kdoa1s,223002
38
- kerykeion/utilities.py,sha256=vuEDWs5Htl2zi6_3flLvlAsFC9YT9LaOQDpRirFf3Cg,11160
39
- kerykeion-4.24.7.dist-info/LICENSE,sha256=UTLH8EdbAsgQei4PA2PnBCPGLSZkq5J-dhkyJuXgWQU,34273
40
- kerykeion-4.24.7.dist-info/METADATA,sha256=G2rliphV4dNmNQELo_0TuLfJxCRb0qXn_hRe3QezRYE,17722
41
- kerykeion-4.24.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
42
- kerykeion-4.24.7.dist-info/entry_points.txt,sha256=5SmANYscFDDTdeovHvGQ-cnj0hdFvGoxPaWLCpyDFnQ,49
43
- kerykeion-4.24.7.dist-info/RECORD,,
39
+ kerykeion/utilities.py,sha256=6TQpxENYDMacH4CsRJGJ4rmoV2Pn7pmQc8MTOUR2CSg,13824
40
+ kerykeion-4.25.0.dist-info/LICENSE,sha256=UTLH8EdbAsgQei4PA2PnBCPGLSZkq5J-dhkyJuXgWQU,34273
41
+ kerykeion-4.25.0.dist-info/METADATA,sha256=ma3O2egm-y2IuJMo70HaVtWQp_asnqw23w4VRs5aOr8,17722
42
+ kerykeion-4.25.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
43
+ kerykeion-4.25.0.dist-info/entry_points.txt,sha256=5SmANYscFDDTdeovHvGQ-cnj0hdFvGoxPaWLCpyDFnQ,49
44
+ kerykeion-4.25.0.dist-info/RECORD,,