kerykeion 5.0.0a10__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.

@@ -1,6 +1,33 @@
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
@@ -59,14 +86,62 @@ NOW = datetime.now()
59
86
 
60
87
  @dataclass
61
88
  class ChartConfiguration:
62
- """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
+ """
63
123
  zodiac_type: ZodiacType = DEFAULT_ZODIAC_TYPE
64
124
  sidereal_mode: Optional[SiderealMode] = None
65
125
  houses_system_identifier: HousesSystemIdentifier = DEFAULT_HOUSES_SYSTEM_IDENTIFIER
66
126
  perspective_type: PerspectiveType = DEFAULT_PERSPECTIVE_TYPE
67
127
 
68
128
  def validate(self) -> None:
69
- """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
+ """
70
145
  # Validate zodiac type
71
146
  if self.zodiac_type not in get_args(ZodiacType):
72
147
  raise KerykeionException(
@@ -101,7 +176,39 @@ class ChartConfiguration:
101
176
 
102
177
  @dataclass
103
178
  class LocationData:
104
- """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
+ """
105
212
  city: str = "Greenwich"
106
213
  nation: str = "GB"
107
214
  lat: float = 51.5074
@@ -113,7 +220,32 @@ class LocationData:
113
220
  city_data: Dict[str, str] = field(default_factory=dict)
114
221
 
115
222
  def fetch_from_geonames(self, username: str, cache_expire_after_days: int) -> None:
116
- """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
+ """
117
249
  logging.info(f"Fetching timezone/coordinates for {self.city}, {self.nation} from geonames")
118
250
 
119
251
  geonames = FetchGeonames(
@@ -142,18 +274,90 @@ class LocationData:
142
274
  self.tz_str = self.city_data["timezonestr"]
143
275
 
144
276
  def prepare_for_calculation(self) -> None:
145
- """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
+ """
146
294
  # Adjust latitude for polar regions
147
295
  self.lat = check_and_adjust_polar_latitude(self.lat)
148
296
 
149
297
 
150
298
  class AstrologicalSubjectFactory:
151
299
  """
152
- Factory class for creating astrological subjects with planetary positions,
153
- houses, and other astrological information for a specific time and location.
154
-
155
- This factory creates and returns AstrologicalSubjectModel instances and provides
156
- 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.
157
361
  """
158
362
 
159
363
  @classmethod
@@ -186,29 +390,110 @@ class AstrologicalSubjectFactory:
186
390
 
187
391
  ) -> AstrologicalSubjectModel:
188
392
  """
189
- 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.
190
399
 
191
400
  Args:
192
- name: Subject name
193
- year, month, day, hour, minute, seconds: Time components
194
- city: Location name
195
- nation: Country code
196
- lng, lat: Coordinates (optional if online=True)
197
- tz_str: Timezone string (optional if online=True)
198
- geonames_username: Username for geonames API
199
- online: Whether to fetch location data online
200
- zodiac_type: Type of zodiac (Tropical or Sidereal)
201
- sidereal_mode: Mode for sidereal calculations
202
- houses_system_identifier: House system for calculations
203
- perspective_type: Perspective for calculations
204
- cache_expire_after_days: Cache duration for geonames data
205
- is_dst: Daylight saving time flag
206
- altitude: Location altitude for topocentric calculations
207
- active_points: Set of points to calculate (optimization)
208
- 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.
209
448
 
210
449
  Returns:
211
- 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
212
497
  """
213
498
  # Create a calculation data container
214
499
  calc_data = {}
@@ -325,27 +610,78 @@ class AstrologicalSubjectFactory:
325
610
  calculate_lunar_phase: bool = True
326
611
  ) -> AstrologicalSubjectModel:
327
612
  """
328
- 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.
329
619
 
330
620
  Args:
331
- name: Subject name
332
- iso_utc_time: ISO formatted UTC time string
333
- city: Location name
334
- nation: Country code
335
- tz_str: Timezone string
336
- online: Whether to fetch location data online
337
- lng, lat: Coordinates
338
- geonames_username: Username for geonames API
339
- zodiac_type: Type of zodiac
340
- sidereal_mode: Mode for sidereal calculations
341
- houses_system_identifier: House system
342
- perspective_type: Perspective for calculations
343
- altitude: Location altitude
344
- active_points: Set of points to calculate
345
- 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.
346
652
 
347
653
  Returns:
348
- 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
349
685
  """
350
686
  # Parse the ISO time
351
687
  dt = datetime.fromisoformat(iso_utc_time.replace('Z', '+00:00'))
@@ -413,25 +749,79 @@ class AstrologicalSubjectFactory:
413
749
  calculate_lunar_phase: bool = True
414
750
  ) -> AstrologicalSubjectModel:
415
751
  """
416
- 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.
417
757
 
418
758
  Args:
419
- name: Subject name
420
- city: Location name
421
- nation: Country code
422
- lng, lat: Coordinates
423
- tz_str: Timezone string
424
- geonames_username: Username for geonames API
425
- online: Whether to fetch location data online
426
- zodiac_type: Type of zodiac
427
- sidereal_mode: Mode for sidereal calculations
428
- houses_system_identifier: House system
429
- perspective_type: Perspective for calculations
430
- active_points: Set of points to calculate
431
- 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.
432
787
 
433
788
  Returns:
434
- 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
435
825
  """
436
826
  now = datetime.now()
437
827
 
@@ -460,7 +850,32 @@ class AstrologicalSubjectFactory:
460
850
 
461
851
  @classmethod
462
852
  def _calculate_time_conversions(cls, data: Dict[str, Any], location: LocationData) -> None:
463
- """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
+ """
464
879
  # Convert local time to UTC
465
880
  local_timezone = pytz.timezone(location.tz_str)
466
881
  naive_datetime = datetime(
@@ -486,7 +901,37 @@ class AstrologicalSubjectFactory:
486
901
 
487
902
  @classmethod
488
903
  def _setup_ephemeris(cls, data: Dict[str, Any], config: ChartConfiguration) -> None:
489
- """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
+ """
490
935
  # Set ephemeris path
491
936
  swe.set_ephe_path(str(Path(__file__).parent.absolute() / "sweph"))
492
937
 
@@ -519,7 +964,42 @@ class AstrologicalSubjectFactory:
519
964
 
520
965
  @classmethod
521
966
  def _calculate_houses(cls, data: Dict[str, Any], active_points: Optional[List[AstrologicalPoint]]) -> None:
522
- """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
+ """
523
1003
  # Skip calculation if point is not in active_points
524
1004
  def should_calculate(point: AstrologicalPoint) -> bool:
525
1005
  return not active_points or point in active_points
@@ -611,18 +1091,45 @@ class AstrologicalSubjectFactory:
611
1091
  active_points: List[AstrologicalPoint]
612
1092
  ) -> None:
613
1093
  """
614
- 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.
615
1100
 
616
1101
  Args:
617
- data: The data dictionary to store the planet information
618
- planet_name: Name of the planet
619
- planet_id: Swiss Ephemeris planet ID
620
- julian_day: Julian day for the calculation
621
- iflag: Swiss Ephemeris calculation flags
622
- houses_degree_ut: House degrees for house calculation
623
- point_type: Type of point being calculated
624
- calculated_planets: List to track calculated planets
625
- 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).
626
1133
  """
627
1134
  try:
628
1135
  # Calculate planet position using Swiss Ephemeris
@@ -649,7 +1156,88 @@ class AstrologicalSubjectFactory:
649
1156
 
650
1157
  @classmethod
651
1158
  def _calculate_planets(cls, data: Dict[str, Any], active_points: List[AstrologicalPoint]) -> None:
652
- """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
+ """
653
1241
  # Skip calculation if point is not in active_points
654
1242
  def should_calculate(point: AstrologicalPoint) -> bool:
655
1243
  return not active_points or point in active_points
@@ -1121,7 +1709,25 @@ class AstrologicalSubjectFactory:
1121
1709
 
1122
1710
  @classmethod
1123
1711
  def _calculate_day_of_week(cls, data: Dict[str, Any]) -> None:
1124
- """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
+ """
1125
1731
  # Calculate the day of the week (0=Sunday, 1=Monday, ..., 6=Saturday)
1126
1732
  day_of_week = swe.day_of_week(data["julian_day"])
1127
1733
  # Map to human-readable names