kerykeion 5.0.0a9__py3-none-any.whl → 5.0.0a11__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.

Files changed (42) hide show
  1. kerykeion/__init__.py +21 -4
  2. kerykeion/aspects/__init__.py +7 -2
  3. kerykeion/aspects/aspects_utils.py +1 -3
  4. kerykeion/aspects/natal_aspects_factory.py +235 -0
  5. kerykeion/aspects/synastry_aspects_factory.py +275 -0
  6. kerykeion/astrological_subject_factory.py +688 -86
  7. kerykeion/charts/charts_utils.py +12 -12
  8. kerykeion/charts/draw_planets.py +584 -344
  9. kerykeion/charts/kerykeion_chart_svg.py +11 -16
  10. kerykeion/charts/templates/wheel_only.xml +1 -1
  11. kerykeion/composite_subject_factory.py +229 -10
  12. kerykeion/ephemeris_data_factory.py +431 -0
  13. kerykeion/fetch_geonames.py +27 -8
  14. kerykeion/house_comparison/__init__.py +6 -0
  15. kerykeion/house_comparison/house_comparison_factory.py +1 -1
  16. kerykeion/house_comparison/house_comparison_utils.py +0 -1
  17. kerykeion/kr_types/__init__.py +49 -0
  18. kerykeion/kr_types/kerykeion_exception.py +6 -0
  19. kerykeion/kr_types/kr_models.py +84 -2
  20. kerykeion/kr_types/settings_models.py +9 -1
  21. kerykeion/planetary_return_factory.py +538 -37
  22. kerykeion/relationship_score_factory.py +123 -59
  23. kerykeion/report.py +7 -1
  24. kerykeion/settings/__init__.py +5 -0
  25. kerykeion/settings/config_constants.py +20 -6
  26. kerykeion/settings/kr.config.json +80 -0
  27. kerykeion/transits_time_range_factory.py +293 -0
  28. kerykeion/utilities.py +130 -68
  29. {kerykeion-5.0.0a9.dist-info → kerykeion-5.0.0a11.dist-info}/METADATA +9 -4
  30. kerykeion-5.0.0a11.dist-info/RECORD +50 -0
  31. kerykeion/aspects/natal_aspects.py +0 -181
  32. kerykeion/aspects/synastry_aspects.py +0 -141
  33. kerykeion/aspects/transits_time_range.py +0 -41
  34. kerykeion/charts/draw_planets_v2.py +0 -649
  35. kerykeion/charts/draw_planets_v3.py +0 -679
  36. kerykeion/enums.py +0 -57
  37. kerykeion/ephemeris_data.py +0 -238
  38. kerykeion/transits_time_range.py +0 -128
  39. kerykeion-5.0.0a9.dist-info/RECORD +0 -55
  40. kerykeion-5.0.0a9.dist-info/entry_points.txt +0 -2
  41. {kerykeion-5.0.0a9.dist-info → kerykeion-5.0.0a11.dist-info}/WHEEL +0 -0
  42. {kerykeion-5.0.0a9.dist-info → kerykeion-5.0.0a11.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,43 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- This is part of Kerykeion (C) 2025 Giacomo Battaglia
3
+ Astrological Subject Factory Module
4
+
5
+ This module provides factory classes for creating astrological subjects with comprehensive
6
+ astrological calculations including planetary positions, house cusps, aspects, and various
7
+ astrological points.
8
+
9
+ The main factory class AstrologicalSubjectFactory offers multiple creation methods for
10
+ different initialization scenarios, supporting both online and offline calculation modes,
11
+ various zodiac systems (Tropical/Sidereal), house systems, and coordinate perspectives.
12
+
13
+ Key Features:
14
+ - Planetary position calculations for all traditional and modern planets
15
+ - House cusp calculations with multiple house systems
16
+ - Lunar nodes, Lilith points, asteroids, and trans-Neptunian objects
17
+ - Arabic parts (lots) calculations
18
+ - Fixed star positions
19
+ - Automatic location data fetching via GeoNames API
20
+ - Comprehensive timezone and coordinate handling
21
+ - Flexible point selection for performance optimization
22
+
23
+ Classes:
24
+ ChartConfiguration: Configuration settings for astrological calculations
25
+ LocationData: Geographical location information and utilities
26
+ AstrologicalSubjectFactory: Main factory for creating astrological subjects
27
+
28
+ Author: Giacomo Battaglia
29
+ Copyright: (C) 2025 Kerykeion Project
30
+ License: AGPL-3.0
4
31
  """
5
32
 
6
33
  import pytz
7
34
  import swisseph as swe
8
35
  import logging
9
- import warnings
10
36
  import math
11
37
  from datetime import datetime
12
38
  from pathlib import Path
13
- from typing import Union, Optional, List, Dict, Any, Literal, get_args, cast, TypedDict, Set
14
- from functools import cached_property, lru_cache
39
+ from typing import Optional, List, Dict, Any, get_args, cast
15
40
  from dataclasses import dataclass, field
16
- from typing import Callable
17
41
 
18
42
 
19
43
  from kerykeion.fetch_geonames import FetchGeonames
@@ -21,8 +45,6 @@ from kerykeion.kr_types import (
21
45
  KerykeionException,
22
46
  ZodiacType,
23
47
  AstrologicalSubjectModel,
24
- LunarPhaseModel,
25
- KerykeionPointModel,
26
48
  PointType,
27
49
  SiderealMode,
28
50
  HousesSystemIdentifier,
@@ -31,7 +53,6 @@ from kerykeion.kr_types import (
31
53
  Houses,
32
54
  )
33
55
  from kerykeion.utilities import (
34
- get_number_from_name,
35
56
  get_kerykeion_point_from_degree,
36
57
  get_planet_house,
37
58
  check_and_adjust_polar_latitude,
@@ -65,14 +86,62 @@ NOW = datetime.now()
65
86
 
66
87
  @dataclass
67
88
  class ChartConfiguration:
68
- """Configuration settings for astrological chart calculations"""
89
+ """
90
+ Configuration settings for astrological chart calculations.
91
+
92
+ This class encapsulates all the configuration parameters needed for astrological
93
+ calculations, including zodiac type, coordinate systems, house systems, and
94
+ calculation perspectives. It provides validation to ensure compatible settings
95
+ combinations.
96
+
97
+ Attributes:
98
+ zodiac_type (ZodiacType): The zodiac system to use ('Tropic' or 'Sidereal').
99
+ Defaults to 'Tropic'.
100
+ sidereal_mode (Optional[SiderealMode]): The sidereal calculation mode when using
101
+ sidereal zodiac. Only required/used when zodiac_type is 'Sidereal'.
102
+ Defaults to None (auto-set to FAGAN_BRADLEY for sidereal).
103
+ houses_system_identifier (HousesSystemIdentifier): The house system to use for
104
+ house cusp calculations. Defaults to 'P' (Placidus).
105
+ perspective_type (PerspectiveType): The coordinate perspective for calculations.
106
+ Options include 'Apparent Geocentric', 'True Geocentric', 'Heliocentric',
107
+ or 'Topocentric'. Defaults to 'Apparent Geocentric'.
108
+
109
+ Raises:
110
+ KerykeionException: When invalid configuration combinations are detected,
111
+ such as setting sidereal_mode with tropical zodiac, or using invalid
112
+ enumeration values.
113
+
114
+ Example:
115
+ >>> config = ChartConfiguration(
116
+ ... zodiac_type="Sidereal",
117
+ ... sidereal_mode="LAHIRI",
118
+ ... houses_system_identifier="K",
119
+ ... perspective_type="Topocentric"
120
+ ... )
121
+ >>> config.validate()
122
+ """
69
123
  zodiac_type: ZodiacType = DEFAULT_ZODIAC_TYPE
70
124
  sidereal_mode: Optional[SiderealMode] = None
71
125
  houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER
72
126
  perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE
73
127
 
74
128
  def validate(self) -> None:
75
- """Validate configuration settings"""
129
+ """
130
+ Validate configuration settings for internal consistency.
131
+
132
+ Performs comprehensive validation of all configuration parameters to ensure
133
+ they form a valid, compatible combination. This includes checking enumeration
134
+ values, zodiac/sidereal mode compatibility, and setting defaults where needed.
135
+
136
+ Raises:
137
+ KerykeionException: If any configuration parameter is invalid or if
138
+ incompatible parameter combinations are detected.
139
+
140
+ Side Effects:
141
+ - Sets default sidereal_mode to FAGAN_BRADLEY if zodiac_type is Sidereal
142
+ and no sidereal_mode is specified
143
+ - Logs informational message when setting default sidereal mode
144
+ """
76
145
  # Validate zodiac type
77
146
  if self.zodiac_type not in get_args(ZodiacType):
78
147
  raise KerykeionException(
@@ -107,7 +176,39 @@ class ChartConfiguration:
107
176
 
108
177
  @dataclass
109
178
  class LocationData:
110
- """Information about a geographical location"""
179
+ """
180
+ Information about a geographical location for astrological calculations.
181
+
182
+ This class handles all location-related data including coordinates, timezone
183
+ information, and interaction with the GeoNames API for automatic location
184
+ data retrieval. It provides methods for fetching location data online and
185
+ preparing coordinates for astrological calculations.
186
+
187
+ Attributes:
188
+ city (str): Name of the city or location. Defaults to "Greenwich".
189
+ nation (str): ISO country code (2-letter). Defaults to "GB" (United Kingdom).
190
+ lat (float): Latitude in decimal degrees. Positive for North, negative for South.
191
+ Defaults to 51.5074 (Greenwich).
192
+ lng (float): Longitude in decimal degrees. Positive for East, negative for West.
193
+ Defaults to 0.0 (Greenwich).
194
+ tz_str (str): IANA timezone identifier (e.g., 'Europe/London', 'America/New_York').
195
+ Defaults to "Etc/GMT".
196
+ altitude (Optional[float]): Altitude above sea level in meters. Used for
197
+ topocentric calculations. Defaults to None (sea level assumed).
198
+ city_data (Dict[str, str]): Raw data retrieved from GeoNames API. Used internally
199
+ for caching and validation. Defaults to empty dictionary.
200
+
201
+ Note:
202
+ When using online mode, the initial coordinate and timezone values may be
203
+ overridden by data fetched from the GeoNames API based on city and nation.
204
+ For polar regions, latitude values are automatically adjusted to prevent
205
+ calculation errors.
206
+
207
+ Example:
208
+ >>> location = LocationData(city="Rome", nation="IT")
209
+ >>> location.fetch_from_geonames("your_username", 30)
210
+ >>> location.prepare_for_calculation()
211
+ """
111
212
  city: str = "Greenwich"
112
213
  nation: str = "GB"
113
214
  lat: float = 51.5074
@@ -119,7 +220,32 @@ class LocationData:
119
220
  city_data: Dict[str, str] = field(default_factory=dict)
120
221
 
121
222
  def fetch_from_geonames(self, username: str, cache_expire_after_days: int) -> None:
122
- """Fetch location data from geonames API"""
223
+ """
224
+ Fetch location data from GeoNames API.
225
+
226
+ Retrieves accurate coordinates, timezone, and country code information
227
+ for the specified city and country from the GeoNames web service.
228
+ Updates the instance attributes with the fetched data.
229
+
230
+ Args:
231
+ username (str): GeoNames API username. Must be registered at geonames.org.
232
+ Free accounts are limited to 2000 requests per hour.
233
+ cache_expire_after_days (int): Number of days to cache the location data
234
+ locally before refreshing from the API.
235
+
236
+ Raises:
237
+ KerykeionException: If required data is missing from the API response,
238
+ typically due to network issues, invalid location names, or API limits.
239
+
240
+ Side Effects:
241
+ - Updates city_data with raw API response
242
+ - Updates nation, lng, lat, and tz_str with fetched values
243
+ - May create or update local cache files
244
+
245
+ Note:
246
+ The method validates that all required fields (countryCode, timezonestr,
247
+ lat, lng) are present in the API response before updating instance attributes.
248
+ """
123
249
  logging.info(f"Fetching timezone/coordinates for {self.city}, {self.nation} from geonames")
124
250
 
125
251
  geonames = FetchGeonames(
@@ -148,18 +274,90 @@ class LocationData:
148
274
  self.tz_str = self.city_data["timezonestr"]
149
275
 
150
276
  def prepare_for_calculation(self) -> None:
151
- """Prepare location data for astrological calculations"""
277
+ """
278
+ Prepare location data for astrological calculations.
279
+
280
+ Performs final adjustments to location data to ensure compatibility
281
+ with Swiss Ephemeris calculations. This includes handling special cases
282
+ like polar regions where extreme latitudes can cause calculation errors.
283
+
284
+ Side Effects:
285
+ - Adjusts latitude values for polar regions (beyond ±66.5°) to
286
+ prevent Swiss Ephemeris calculation failures
287
+ - May log warnings about latitude adjustments
288
+
289
+ Note:
290
+ This method should be called after all location data has been set,
291
+ either manually or via fetch_from_geonames(), and before performing
292
+ any astrological calculations.
293
+ """
152
294
  # Adjust latitude for polar regions
153
295
  self.lat = check_and_adjust_polar_latitude(self.lat)
154
296
 
155
297
 
156
298
  class AstrologicalSubjectFactory:
157
299
  """
158
- Factory class for creating astrological subjects with planetary positions,
159
- houses, and other astrological information for a specific time and location.
160
-
161
- This factory creates and returns AstrologicalSubjectModel instances and provides
162
- multiple creation methods for different initialization scenarios.
300
+ Factory class for creating comprehensive astrological subjects.
301
+
302
+ This factory creates AstrologicalSubjectModel instances with complete astrological
303
+ information including planetary positions, house cusps, aspects, lunar phases, and
304
+ various specialized astrological points. It provides multiple class methods for
305
+ different initialization scenarios and supports both online and offline calculation modes.
306
+
307
+ The factory handles complex astrological calculations using the Swiss Ephemeris library,
308
+ supports multiple coordinate systems and house systems, and can automatically fetch
309
+ location data from online sources.
310
+
311
+ Supported Astrological Points:
312
+ - Traditional Planets: Sun through Pluto
313
+ - Lunar Nodes: Mean and True North/South Nodes
314
+ - Lilith Points: Mean and True Black Moon
315
+ - Asteroids: Ceres, Pallas, Juno, Vesta
316
+ - Centaurs: Chiron, Pholus
317
+ - Trans-Neptunian Objects: Eris, Sedna, Haumea, Makemake, Ixion, Orcus, Quaoar
318
+ - Fixed Stars: Regulus, Spica (extensible)
319
+ - Arabic Parts: Pars Fortunae, Pars Spiritus, Pars Amoris, Pars Fidei
320
+ - Special Points: Vertex, Anti-Vertex, Earth (for heliocentric charts)
321
+ - House Cusps: All 12 houses with configurable house systems
322
+ - Angles: Ascendant, Medium Coeli, Descendant, Imum Coeli
323
+
324
+ Supported Features:
325
+ - Multiple zodiac systems (Tropical/Sidereal with various ayanamshas)
326
+ - Multiple house systems (Placidus, Koch, Equal, Whole Sign, etc.)
327
+ - Multiple coordinate perspectives (Geocentric, Heliocentric, Topocentric)
328
+ - Automatic timezone and coordinate resolution via GeoNames API
329
+ - Lunar phase calculations
330
+ - Day/night chart detection for Arabic parts
331
+ - Performance optimization through selective point calculation
332
+ - Comprehensive error handling and validation
333
+
334
+ Class Methods:
335
+ from_birth_data: Create subject from standard birth data (most flexible)
336
+ from_iso_utc_time: Create subject from ISO UTC timestamp
337
+ from_current_time: Create subject for current moment
338
+
339
+ Example:
340
+ >>> # Create natal chart
341
+ >>> subject = AstrologicalSubjectFactory.from_birth_data(
342
+ ... name="John Doe",
343
+ ... year=1990, month=6, day=15,
344
+ ... hour=14, minute=30,
345
+ ... city="Rome", nation="IT",
346
+ ... online=True
347
+ ... )
348
+ >>> print(f"Sun: {subject.sun.sign} {subject.sun.degree_ut}°")
349
+ >>> print(f"Active points: {len(subject.active_points)}")
350
+
351
+ >>> # Create chart for current time
352
+ >>> now_subject = AstrologicalSubjectFactory.from_current_time(
353
+ ... name="Current Moment",
354
+ ... city="London", nation="GB"
355
+ ... )
356
+
357
+ Thread Safety:
358
+ This factory is not thread-safe due to its use of the Swiss Ephemeris library
359
+ which maintains global state. Use separate instances in multi-threaded applications
360
+ or implement appropriate locking mechanisms.
163
361
  """
164
362
 
165
363
  @classmethod
@@ -192,29 +390,110 @@ class AstrologicalSubjectFactory:
192
390
 
193
391
  ) -> AstrologicalSubjectModel:
194
392
  """
195
- Create an astrological subject from standard birth/event details.
393
+ Create an astrological subject from standard birth or event data.
394
+
395
+ This is the most flexible and commonly used factory method. It creates a complete
396
+ astrological subject with planetary positions, house cusps, and specialized points
397
+ for a specific date, time, and location. Supports both online location resolution
398
+ and offline calculation modes.
196
399
 
197
400
  Args:
198
- name: Subject name
199
- year, month, day, hour, minute, seconds: Time components
200
- city: Location name
201
- nation: Country code
202
- lng, lat: Coordinates (optional if online=True)
203
- tz_str: Timezone string (optional if online=True)
204
- geonames_username: Username for geonames API
205
- online: Whether to fetch location data online
206
- zodiac_type: Type of zodiac (Tropical or Sidereal)
207
- sidereal_mode: Mode for sidereal calculations
208
- houses_system_identifier: House system for calculations
209
- perspective_type: Perspective for calculations
210
- cache_expire_after_days: Cache duration for geonames data
211
- is_dst: Daylight saving time flag
212
- altitude: Location altitude for topocentric calculations
213
- active_points: Set of points to calculate (optimization)
214
- calculate_lunar_phase: Whether to calculate lunar phase (requires Sun and Moon)
401
+ name (str, optional): Name or identifier for the subject. Defaults to "Now".
402
+ year (int, optional): Year of birth/event. Defaults to current year.
403
+ month (int, optional): Month of birth/event (1-12). Defaults to current month.
404
+ day (int, optional): Day of birth/event (1-31). Defaults to current day.
405
+ hour (int, optional): Hour of birth/event (0-23). Defaults to current hour.
406
+ minute (int, optional): Minute of birth/event (0-59). Defaults to current minute.
407
+ seconds (int, optional): Seconds of birth/event (0-59). Defaults to 0.
408
+ city (str, optional): City name for location lookup. Used with online=True.
409
+ Defaults to None (Greenwich if not specified).
410
+ nation (str, optional): ISO country code (e.g., 'US', 'GB', 'IT'). Used with
411
+ online=True. Defaults to None ('GB' if not specified).
412
+ lng (float, optional): Longitude in decimal degrees. East is positive, West
413
+ is negative. If not provided and online=True, fetched from GeoNames.
414
+ lat (float, optional): Latitude in decimal degrees. North is positive, South
415
+ is negative. If not provided and online=True, fetched from GeoNames.
416
+ tz_str (str, optional): IANA timezone identifier (e.g., 'Europe/London').
417
+ If not provided and online=True, fetched from GeoNames.
418
+ geonames_username (str, optional): Username for GeoNames API. Required for
419
+ online location lookup. Get one free at geonames.org.
420
+ online (bool, optional): Whether to fetch location data online. If False,
421
+ lng, lat, and tz_str must be provided. Defaults to True.
422
+ zodiac_type (ZodiacType, optional): Zodiac system - 'Tropic' or 'Sidereal'.
423
+ Defaults to 'Tropic'.
424
+ sidereal_mode (SiderealMode, optional): Sidereal calculation mode (e.g.,
425
+ 'FAGAN_BRADLEY', 'LAHIRI'). Only used with zodiac_type='Sidereal'.
426
+ houses_system_identifier (HousesSystemIdentifier, optional): House system
427
+ for cusp calculations (e.g., 'P'=Placidus, 'K'=Koch, 'E'=Equal).
428
+ Defaults to 'P' (Placidus).
429
+ perspective_type (PerspectiveType, optional): Calculation perspective:
430
+ - 'Apparent Geocentric': Standard geocentric with light-time correction
431
+ - 'True Geocentric': Geometric geocentric positions
432
+ - 'Heliocentric': Sun-centered coordinates
433
+ - 'Topocentric': Earth surface perspective (requires altitude)
434
+ Defaults to 'Apparent Geocentric'.
435
+ cache_expire_after_days (int, optional): Days to cache GeoNames data locally.
436
+ Defaults to 30.
437
+ is_dst (bool, optional): Daylight Saving Time flag for ambiguous times.
438
+ If None, pytz attempts automatic detection. Set explicitly for
439
+ times during DST transitions.
440
+ altitude (float, optional): Altitude above sea level in meters. Used for
441
+ topocentric calculations and atmospheric corrections. Defaults to None
442
+ (sea level assumed).
443
+ active_points (List[AstrologicalPoint], optional): List of astrological
444
+ points to calculate. Omitting points can improve performance for
445
+ specialized applications. Defaults to DEFAULT_ACTIVE_POINTS.
446
+ calculate_lunar_phase (bool, optional): Whether to calculate lunar phase.
447
+ Requires Sun and Moon in active_points. Defaults to True.
215
448
 
216
449
  Returns:
217
- An AstrologicalSubjectModel with calculated data
450
+ AstrologicalSubjectModel: Complete astrological subject with calculated
451
+ positions, houses, and metadata. Access planetary positions via
452
+ attributes like .sun, .moon, .mercury, etc.
453
+
454
+ Raises:
455
+ KerykeionException:
456
+ - If offline mode is used without required location data
457
+ - If invalid zodiac/sidereal mode combinations are specified
458
+ - If GeoNames data is missing or invalid
459
+ - If timezone localization fails (ambiguous DST times)
460
+
461
+ Examples:
462
+ >>> # Basic natal chart with online location lookup
463
+ >>> chart = AstrologicalSubjectFactory.from_birth_data(
464
+ ... name="Jane Doe",
465
+ ... year=1985, month=3, day=21,
466
+ ... hour=15, minute=30,
467
+ ... city="Paris", nation="FR",
468
+ ... geonames_username="your_username"
469
+ ... )
470
+
471
+ >>> # Offline calculation with manual coordinates
472
+ >>> chart = AstrologicalSubjectFactory.from_birth_data(
473
+ ... name="John Smith",
474
+ ... year=1990, month=12, day=25,
475
+ ... hour=0, minute=0,
476
+ ... lng=-74.006, lat=40.7128, tz_str="America/New_York",
477
+ ... online=False
478
+ ... )
479
+
480
+ >>> # Sidereal chart with specific points
481
+ >>> chart = AstrologicalSubjectFactory.from_birth_data(
482
+ ... name="Vedic Chart",
483
+ ... year=2000, month=6, day=15, hour=12,
484
+ ... city="Mumbai", nation="IN",
485
+ ... zodiac_type="Sidereal",
486
+ ... sidereal_mode="LAHIRI",
487
+ ... active_points=["Sun", "Moon", "Mercury", "Venus", "Mars",
488
+ ... "Jupiter", "Saturn", "Ascendant"]
489
+ ... )
490
+
491
+ Note:
492
+ - For high-precision calculations, consider providing seconds parameter
493
+ - Use topocentric perspective for observer-specific calculations
494
+ - Some Arabic parts automatically activate required base points
495
+ - The method handles polar regions by adjusting extreme latitudes
496
+ - Time zones are handled with full DST awareness via pytz
218
497
  """
219
498
  # Create a calculation data container
220
499
  calc_data = {}
@@ -331,27 +610,78 @@ class AstrologicalSubjectFactory:
331
610
  calculate_lunar_phase: bool = True
332
611
  ) -> AstrologicalSubjectModel:
333
612
  """
334
- Create an astrological subject from an ISO formatted UTC time.
613
+ Create an astrological subject from an ISO formatted UTC timestamp.
614
+
615
+ This method is ideal for creating astrological subjects from standardized
616
+ time formats, such as those stored in databases or received from APIs.
617
+ It automatically handles timezone conversion from UTC to the specified
618
+ local timezone.
335
619
 
336
620
  Args:
337
- name: Subject name
338
- iso_utc_time: ISO formatted UTC time string
339
- city: Location name
340
- nation: Country code
341
- tz_str: Timezone string
342
- online: Whether to fetch location data online
343
- lng, lat: Coordinates
344
- geonames_username: Username for geonames API
345
- zodiac_type: Type of zodiac
346
- sidereal_mode: Mode for sidereal calculations
347
- houses_system_identifier: House system
348
- perspective_type: Perspective for calculations
349
- altitude: Location altitude
350
- active_points: Set of points to calculate
351
- calculate_lunar_phase: Whether to calculate lunar phase
621
+ name (str): Name or identifier for the subject.
622
+ iso_utc_time (str): ISO 8601 formatted UTC timestamp. Supported formats:
623
+ - "2023-06-15T14:30:00Z" (with Z suffix)
624
+ - "2023-06-15T14:30:00+00:00" (with UTC offset)
625
+ - "2023-06-15T14:30:00.123Z" (with milliseconds)
626
+ city (str, optional): City name for location. Defaults to "Greenwich".
627
+ nation (str, optional): ISO country code. Defaults to "GB".
628
+ tz_str (str, optional): IANA timezone identifier for result conversion.
629
+ The ISO time is assumed to be in UTC and will be converted to this
630
+ timezone. Defaults to "Etc/GMT".
631
+ online (bool, optional): Whether to fetch coordinates online. If True,
632
+ coordinates are fetched via GeoNames API. Defaults to True.
633
+ lng (float, optional): Longitude in decimal degrees. Used when online=False
634
+ or as fallback. Defaults to 0.0 (Greenwich).
635
+ lat (float, optional): Latitude in decimal degrees. Used when online=False
636
+ or as fallback. Defaults to 51.5074 (Greenwich).
637
+ geonames_username (str, optional): GeoNames API username. Required when
638
+ online=True. Defaults to DEFAULT_GEONAMES_USERNAME.
639
+ zodiac_type (ZodiacType, optional): Zodiac system. Defaults to 'Tropic'.
640
+ sidereal_mode (SiderealMode, optional): Sidereal mode when zodiac_type
641
+ is 'Sidereal'. Defaults to None.
642
+ houses_system_identifier (HousesSystemIdentifier, optional): House system.
643
+ Defaults to 'P' (Placidus).
644
+ perspective_type (PerspectiveType, optional): Calculation perspective.
645
+ Defaults to 'Apparent Geocentric'.
646
+ altitude (float, optional): Altitude in meters for topocentric calculations.
647
+ Defaults to None (sea level).
648
+ active_points (List[AstrologicalPoint], optional): Points to calculate.
649
+ Defaults to DEFAULT_ACTIVE_POINTS.
650
+ calculate_lunar_phase (bool, optional): Whether to calculate lunar phase.
651
+ Defaults to True.
352
652
 
353
653
  Returns:
354
- AstrologicalSubjectModel instance
654
+ AstrologicalSubjectModel: Astrological subject with positions calculated
655
+ for the specified UTC time converted to local timezone.
656
+
657
+ Raises:
658
+ ValueError: If the ISO timestamp format is invalid or cannot be parsed.
659
+ KerykeionException: If location data cannot be fetched or is invalid.
660
+
661
+ Examples:
662
+ >>> # From API timestamp with online location lookup
663
+ >>> subject = AstrologicalSubjectFactory.from_iso_utc_time(
664
+ ... name="Event Chart",
665
+ ... iso_utc_time="2023-12-25T12:00:00Z",
666
+ ... city="Tokyo", nation="JP",
667
+ ... tz_str="Asia/Tokyo",
668
+ ... geonames_username="your_username"
669
+ ... )
670
+
671
+ >>> # From database timestamp with manual coordinates
672
+ >>> subject = AstrologicalSubjectFactory.from_iso_utc_time(
673
+ ... name="Historical Event",
674
+ ... iso_utc_time="1969-07-20T20:17:00Z",
675
+ ... lng=-95.0969, lat=37.4419, # Houston
676
+ ... tz_str="America/Chicago",
677
+ ... online=False
678
+ ... )
679
+
680
+ Note:
681
+ - The method assumes the input timestamp is in UTC
682
+ - Local time conversion respects DST rules for the target timezone
683
+ - Milliseconds in the timestamp are supported but truncated to seconds
684
+ - When online=True, the city/nation parameters override lng/lat
355
685
  """
356
686
  # Parse the ISO time
357
687
  dt = datetime.fromisoformat(iso_utc_time.replace('Z', '+00:00'))
@@ -419,25 +749,79 @@ class AstrologicalSubjectFactory:
419
749
  calculate_lunar_phase: bool = True
420
750
  ) -> AstrologicalSubjectModel:
421
751
  """
422
- Create an astrological subject for the current time.
752
+ Create an astrological subject for the current moment in time.
753
+
754
+ This convenience method creates a "now" chart, capturing the current
755
+ astrological conditions at the moment of execution. Useful for horary
756
+ astrology, electional astrology, or real-time astrological monitoring.
423
757
 
424
758
  Args:
425
- name: Subject name
426
- city: Location name
427
- nation: Country code
428
- lng, lat: Coordinates
429
- tz_str: Timezone string
430
- geonames_username: Username for geonames API
431
- online: Whether to fetch location data online
432
- zodiac_type: Type of zodiac
433
- sidereal_mode: Mode for sidereal calculations
434
- houses_system_identifier: House system
435
- perspective_type: Perspective for calculations
436
- active_points: Set of points to calculate
437
- calculate_lunar_phase: Whether to calculate lunar phase
759
+ name (str, optional): Name for the current moment chart.
760
+ Defaults to "Now".
761
+ city (str, optional): City name for location lookup. If not provided
762
+ and online=True, defaults to Greenwich.
763
+ nation (str, optional): ISO country code. If not provided and
764
+ online=True, defaults to 'GB'.
765
+ lng (float, optional): Longitude in decimal degrees. If not provided
766
+ and online=True, fetched from GeoNames API.
767
+ lat (float, optional): Latitude in decimal degrees. If not provided
768
+ and online=True, fetched from GeoNames API.
769
+ tz_str (str, optional): IANA timezone identifier. If not provided
770
+ and online=True, fetched from GeoNames API.
771
+ geonames_username (str, optional): GeoNames API username for location
772
+ lookup. Required when online=True and location is not fully specified.
773
+ online (bool, optional): Whether to fetch location data online.
774
+ Defaults to True.
775
+ zodiac_type (ZodiacType, optional): Zodiac system to use.
776
+ Defaults to 'Tropic'.
777
+ sidereal_mode (SiderealMode, optional): Sidereal calculation mode.
778
+ Only used when zodiac_type is 'Sidereal'. Defaults to None.
779
+ houses_system_identifier (HousesSystemIdentifier, optional): House
780
+ system for calculations. Defaults to 'P' (Placidus).
781
+ perspective_type (PerspectiveType, optional): Calculation perspective.
782
+ Defaults to 'Apparent Geocentric'.
783
+ active_points (List[AstrologicalPoint], optional): Astrological points
784
+ to calculate. Defaults to DEFAULT_ACTIVE_POINTS.
785
+ calculate_lunar_phase (bool, optional): Whether to calculate lunar phase.
786
+ Defaults to True.
438
787
 
439
788
  Returns:
440
- AstrologicalSubjectModel for current time
789
+ AstrologicalSubjectModel: Astrological subject representing current
790
+ astrological conditions at the specified or default location.
791
+
792
+ Raises:
793
+ KerykeionException: If online location lookup fails or if offline mode
794
+ is used without sufficient location data.
795
+
796
+ Examples:
797
+ >>> # Current moment for your location
798
+ >>> now_chart = AstrologicalSubjectFactory.from_current_time(
799
+ ... name="Current Transits",
800
+ ... city="New York", nation="US",
801
+ ... geonames_username="your_username"
802
+ ... )
803
+
804
+ >>> # Horary chart with specific coordinates
805
+ >>> horary = AstrologicalSubjectFactory.from_current_time(
806
+ ... name="Horary Question",
807
+ ... lng=-0.1278, lat=51.5074, # London
808
+ ... tz_str="Europe/London",
809
+ ... online=False
810
+ ... )
811
+
812
+ >>> # Current sidereal positions
813
+ >>> sidereal_now = AstrologicalSubjectFactory.from_current_time(
814
+ ... name="Sidereal Now",
815
+ ... city="Mumbai", nation="IN",
816
+ ... zodiac_type="Sidereal",
817
+ ... sidereal_mode="LAHIRI"
818
+ ... )
819
+
820
+ Note:
821
+ - The exact time is captured at method execution, including seconds
822
+ - For horary astrology, consider the moment of understanding the question
823
+ - System clock accuracy affects precision; ensure accurate system time
824
+ - Time zone detection is automatic when using online location lookup
441
825
  """
442
826
  now = datetime.now()
443
827
 
@@ -466,7 +850,32 @@ class AstrologicalSubjectFactory:
466
850
 
467
851
  @classmethod
468
852
  def _calculate_time_conversions(cls, data: Dict[str, Any], location: LocationData) -> None:
469
- """Calculate time conversions between local time, UTC and Julian day"""
853
+ """
854
+ Calculate time conversions between local time, UTC, and Julian Day Number.
855
+
856
+ Handles timezone-aware conversion from local civil time to UTC and astronomical
857
+ Julian Day Number, including proper DST handling and timezone localization.
858
+
859
+ Args:
860
+ data (Dict[str, Any]): Calculation data dictionary containing time components
861
+ (year, month, day, hour, minute, seconds) and optional DST flag.
862
+ location (LocationData): Location data containing timezone information.
863
+
864
+ Raises:
865
+ KerykeionException: If DST ambiguity occurs during timezone transitions
866
+ and is_dst parameter is not explicitly set to resolve the ambiguity.
867
+
868
+ Side Effects:
869
+ Updates data dictionary with:
870
+ - iso_formatted_utc_datetime: ISO 8601 UTC timestamp
871
+ - iso_formatted_local_datetime: ISO 8601 local timestamp
872
+ - julian_day: Julian Day Number for astronomical calculations
873
+
874
+ Note:
875
+ During DST transitions, times may be ambiguous (fall back) or non-existent
876
+ (spring forward). The method raises an exception for ambiguous times unless
877
+ the is_dst parameter is explicitly set to True or False.
878
+ """
470
879
  # Convert local time to UTC
471
880
  local_timezone = pytz.timezone(location.tz_str)
472
881
  naive_datetime = datetime(
@@ -492,7 +901,37 @@ class AstrologicalSubjectFactory:
492
901
 
493
902
  @classmethod
494
903
  def _setup_ephemeris(cls, data: Dict[str, Any], config: ChartConfiguration) -> None:
495
- """Set up Swiss Ephemeris with appropriate flags"""
904
+ """
905
+ Configure Swiss Ephemeris with appropriate calculation flags and settings.
906
+
907
+ Sets up the Swiss Ephemeris library with the correct ephemeris data path,
908
+ calculation flags for the specified perspective type, and sidereal mode
909
+ configuration if applicable.
910
+
911
+ Args:
912
+ data (Dict[str, Any]): Calculation data dictionary to store configuration.
913
+ config (ChartConfiguration): Validated chart configuration settings.
914
+
915
+ Side Effects:
916
+ - Sets Swiss Ephemeris data path to bundled ephemeris files
917
+ - Configures calculation flags (SWIEPH, SPEED, perspective flags)
918
+ - Sets sidereal mode for sidereal zodiac calculations
919
+ - Sets topocentric observer coordinates for topocentric perspective
920
+ - Updates data dictionary with houses_system_name and _iflag
921
+
922
+ Calculation Flags Set:
923
+ - FLG_SWIEPH: Use Swiss Ephemeris data files
924
+ - FLG_SPEED: Calculate planetary velocities
925
+ - FLG_TRUEPOS: True geometric positions (True Geocentric)
926
+ - FLG_HELCTR: Heliocentric coordinates (Heliocentric perspective)
927
+ - FLG_TOPOCTR: Topocentric coordinates (Topocentric perspective)
928
+ - FLG_SIDEREAL: Sidereal calculations (Sidereal zodiac)
929
+
930
+ Note:
931
+ The method assumes the Swiss Ephemeris data files are located in the
932
+ 'sweph' subdirectory relative to this module. For topocentric calculations,
933
+ observer coordinates must be set via longitude, latitude, and altitude.
934
+ """
496
935
  # Set ephemeris path
497
936
  swe.set_ephe_path(str(Path(__file__).parent.absolute() / "sweph"))
498
937
 
@@ -525,9 +964,45 @@ class AstrologicalSubjectFactory:
525
964
 
526
965
  @classmethod
527
966
  def _calculate_houses(cls, data: Dict[str, Any], active_points: Optional[List[AstrologicalPoint]]) -> None:
528
- """Calculate house cusps and axis points"""
967
+ """
968
+ Calculate house cusps and angular points (Ascendant, MC, etc.).
969
+
970
+ Computes the 12 house cusps using the specified house system and calculates
971
+ the four main angles of the chart. Only calculates angular points that are
972
+ included in the active_points list for performance optimization.
973
+
974
+ Args:
975
+ data (Dict[str, Any]): Calculation data dictionary containing configuration
976
+ and location information. Updated with calculated house and angle data.
977
+ active_points (Optional[List[AstrologicalPoint]]): List of points to calculate.
978
+ If None, all points are calculated. Angular points not in this list
979
+ are skipped for performance.
980
+
981
+ Side Effects:
982
+ Updates data dictionary with:
983
+ - House cusp objects: first_house through twelfth_house
984
+ - Angular points: ascendant, medium_coeli, descendant, imum_coeli
985
+ - houses_names_list: List of all house names
986
+ - _houses_degree_ut: Raw house cusp degrees for internal use
987
+
988
+ House Systems Supported:
989
+ All systems supported by Swiss Ephemeris including Placidus, Koch,
990
+ Equal House, Whole Sign, Regiomontanus, Campanus, Topocentric, etc.
991
+
992
+ Angular Points Calculated:
993
+ - Ascendant: Eastern horizon point (1st house cusp)
994
+ - Medium Coeli (Midheaven): Southern meridian point (10th house cusp)
995
+ - Descendant: Western horizon point (opposite Ascendant)
996
+ - Imum Coeli: Northern meridian point (opposite Medium Coeli)
997
+
998
+ Note:
999
+ House calculations respect the zodiac type (Tropical/Sidereal) and use
1000
+ the appropriate Swiss Ephemeris function. Angular points include house
1001
+ position and retrograde status (always False for angles).
1002
+ """
529
1003
  # Skip calculation if point is not in active_points
530
- should_calculate: Callable[[AstrologicalPoint], bool] = lambda point: not active_points or point in active_points
1004
+ def should_calculate(point: AstrologicalPoint) -> bool:
1005
+ return not active_points or point in active_points
531
1006
  # Track which axial cusps are actually calculated
532
1007
  calculated_axial_cusps = []
533
1008
 
@@ -616,18 +1091,45 @@ class AstrologicalSubjectFactory:
616
1091
  active_points: List[AstrologicalPoint]
617
1092
  ) -> None:
618
1093
  """
619
- Calculate a single planet's position with error handling and store it in the data dictionary.
1094
+ Calculate a single celestial body's position with comprehensive error handling.
1095
+
1096
+ Computes the position of a single planet, asteroid, or other celestial object
1097
+ using Swiss Ephemeris, creates a Kerykeion point object, determines house
1098
+ position, and assesses retrograde status. Handles calculation errors gracefully
1099
+ by logging and removing failed points from the active list.
620
1100
 
621
1101
  Args:
622
- data: The data dictionary to store the planet information
623
- planet_name: Name of the planet
624
- planet_id: Swiss Ephemeris planet ID
625
- julian_day: Julian day for the calculation
626
- iflag: Swiss Ephemeris calculation flags
627
- houses_degree_ut: House degrees for house calculation
628
- point_type: Type of point being calculated
629
- calculated_planets: List to track calculated planets
630
- active_points: List of active points to modify if error occurs
1102
+ data (Dict[str, Any]): Main calculation data dictionary to store results.
1103
+ planet_name (AstrologicalPoint): Name identifier for the celestial body.
1104
+ planet_id (int): Swiss Ephemeris numerical identifier for the object.
1105
+ julian_day (float): Julian Day Number for the calculation moment.
1106
+ iflag (int): Swiss Ephemeris calculation flags (perspective, zodiac, etc.).
1107
+ houses_degree_ut (List[float]): House cusp degrees for house determination.
1108
+ point_type (PointType): Classification of the point type for the object.
1109
+ calculated_planets (List[str]): Running list of successfully calculated objects.
1110
+ active_points (List[AstrologicalPoint]): Active points list (modified on error).
1111
+
1112
+ Side Effects:
1113
+ - Adds calculated object to data dictionary using lowercase planet_name as key
1114
+ - Appends planet_name to calculated_planets list on success
1115
+ - Removes planet_name from active_points list on calculation failure
1116
+ - Logs error messages for calculation failures
1117
+
1118
+ Calculated Properties:
1119
+ - Zodiacal position (longitude) in degrees
1120
+ - House position based on house cusp positions
1121
+ - Retrograde status based on velocity (negative = retrograde)
1122
+ - Sign, degree, and minute components
1123
+
1124
+ Error Handling:
1125
+ If Swiss Ephemeris calculation fails (e.g., for distant asteroids outside
1126
+ ephemeris range), the method logs the error and removes the object from
1127
+ active_points to prevent cascade failures.
1128
+
1129
+ Note:
1130
+ The method uses the Swiss Ephemeris calc_ut function which returns position
1131
+ and velocity data. Retrograde determination is based on the velocity
1132
+ component being negative (element index 3).
631
1133
  """
632
1134
  try:
633
1135
  # Calculate planet position using Swiss Ephemeris
@@ -654,9 +1156,91 @@ class AstrologicalSubjectFactory:
654
1156
 
655
1157
  @classmethod
656
1158
  def _calculate_planets(cls, data: Dict[str, Any], active_points: List[AstrologicalPoint]) -> None:
657
- """Calculate planetary positions and related information"""
1159
+ """
1160
+ Calculate positions for all requested celestial bodies and special points.
1161
+
1162
+ This comprehensive method calculates positions for a wide range of astrological
1163
+ points including traditional planets, lunar nodes, asteroids, trans-Neptunian
1164
+ objects, fixed stars, Arabic parts, and specialized points like Vertex.
1165
+
1166
+ The calculation is performed selectively based on the active_points list for
1167
+ performance optimization. Some Arabic parts automatically activate their
1168
+ prerequisite points if needed.
1169
+
1170
+ Args:
1171
+ data (Dict[str, Any]): Main calculation data dictionary. Updated with all
1172
+ calculated planetary positions and related metadata.
1173
+ active_points (List[AstrologicalPoint]): Mutable list of points to calculate.
1174
+ Modified during execution to remove failed calculations and add
1175
+ automatically required points for Arabic parts.
1176
+
1177
+ Celestial Bodies Calculated:
1178
+ Traditional Planets:
1179
+ - Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn
1180
+ - Uranus, Neptune, Pluto
1181
+
1182
+ Lunar Nodes:
1183
+ - Mean Node, True Node (North nodes)
1184
+ - Mean South Node, True South Node (calculated as opposites)
1185
+
1186
+ Lilith Points:
1187
+ - Mean Lilith (Mean Black Moon Lilith)
1188
+ - True Lilith (Osculating Black Moon Lilith)
1189
+
1190
+ Asteroids:
1191
+ - Ceres, Pallas, Juno, Vesta (main belt asteroids)
1192
+
1193
+ Centaurs:
1194
+ - Chiron, Pholus
1195
+
1196
+ Trans-Neptunian Objects:
1197
+ - Eris, Sedna, Haumea, Makemake
1198
+ - Ixion, Orcus, Quaoar
1199
+
1200
+ Fixed Stars:
1201
+ - Regulus, Spica (examples, extensible)
1202
+
1203
+ Arabic Parts (Lots):
1204
+ - Pars Fortunae (Part of Fortune)
1205
+ - Pars Spiritus (Part of Spirit)
1206
+ - Pars Amoris (Part of Love/Eros)
1207
+ - Pars Fidei (Part of Faith)
1208
+
1209
+ Special Points:
1210
+ - Earth (for heliocentric perspectives)
1211
+ - Vertex and Anti-Vertex
1212
+
1213
+ Side Effects:
1214
+ - Updates data dictionary with all calculated positions
1215
+ - Modifies active_points list by removing failed calculations
1216
+ - Adds prerequisite points for Arabic parts calculations
1217
+ - Updates data["active_points"] with successfully calculated objects
1218
+
1219
+ Error Handling:
1220
+ Individual calculation failures (e.g., asteroids outside ephemeris range)
1221
+ are handled gracefully with logging and removal from active_points.
1222
+ This prevents cascade failures while maintaining calculation integrity.
1223
+
1224
+ Arabic Parts Logic:
1225
+ - Day/night birth detection based on Sun's house position
1226
+ - Automatic activation of required base points (Sun, Moon, Ascendant, etc.)
1227
+ - Classical formulae with day/night variations where applicable
1228
+ - All parts marked as non-retrograde (conceptual points)
1229
+
1230
+ Performance Notes:
1231
+ - Only points in active_points are calculated (selective computation)
1232
+ - Failed calculations are removed to prevent repeated attempts
1233
+ - Some expensive calculations (like distant TNOs) may timeout
1234
+ - Fixed stars use different calculation methods than planets
1235
+
1236
+ Note:
1237
+ The method maintains a running list of successfully calculated planets
1238
+ and updates the active_points list to reflect actual availability.
1239
+ This ensures that dependent calculations and aspects only use valid data.
1240
+ """
658
1241
  # Skip calculation if point is not in active_points
659
- should_calculate: Callable[[AstrologicalPoint], bool] = lambda point: not active_points or point in active_points
1242
+ def should_calculate(point: AstrologicalPoint) -> bool:
1243
+ return not active_points or point in active_points
660
1244
 
661
1245
  point_type: PointType = "AstrologicalPoint"
662
1246
  julian_day = data["julian_day"]
@@ -1125,7 +1709,25 @@ class AstrologicalSubjectFactory:
1125
1709
 
1126
1710
  @classmethod
1127
1711
  def _calculate_day_of_week(cls, data: Dict[str, Any]) -> None:
1128
- """Calculate the day of the week for the given Julian Day"""
1712
+ """
1713
+ Calculate the day of the week for the given astronomical event.
1714
+
1715
+ Determines the day of the week corresponding to the Julian Day Number
1716
+ of the astrological event using Swiss Ephemeris calendar functions.
1717
+
1718
+ Args:
1719
+ data (Dict[str, Any]): Calculation data dictionary containing julian_day.
1720
+ Updated with the calculated day_of_week string.
1721
+
1722
+ Side Effects:
1723
+ Updates data dictionary with:
1724
+ - day_of_week: Human-readable day name (e.g., "Monday", "Tuesday")
1725
+
1726
+ Note:
1727
+ The Swiss Ephemeris day_of_week function returns an integer where
1728
+ 0=Monday, 1=Tuesday, ..., 6=Sunday. This is converted to readable
1729
+ day names for user convenience.
1730
+ """
1129
1731
  # Calculate the day of the week (0=Sunday, 1=Monday, ..., 6=Saturday)
1130
1732
  day_of_week = swe.day_of_week(data["julian_day"])
1131
1733
  # Map to human-readable names