kerykeion 4.26.2__py3-none-any.whl → 5.0.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.

Files changed (77) hide show
  1. kerykeion/__init__.py +54 -11
  2. kerykeion/aspects/__init__.py +5 -2
  3. kerykeion/aspects/aspects_factory.py +569 -0
  4. kerykeion/aspects/aspects_utils.py +81 -8
  5. kerykeion/astrological_subject_factory.py +1897 -0
  6. kerykeion/backword.py +773 -0
  7. kerykeion/chart_data_factory.py +549 -0
  8. kerykeion/charts/chart_drawer.py +2601 -0
  9. kerykeion/charts/charts_utils.py +948 -177
  10. kerykeion/charts/draw_planets.py +602 -351
  11. kerykeion/charts/templates/aspect_grid_only.xml +328 -202
  12. kerykeion/charts/templates/chart.xml +432 -272
  13. kerykeion/charts/templates/wheel_only.xml +350 -214
  14. kerykeion/charts/themes/black-and-white.css +148 -0
  15. kerykeion/charts/themes/classic.css +107 -76
  16. kerykeion/charts/themes/dark-high-contrast.css +145 -107
  17. kerykeion/charts/themes/dark.css +146 -107
  18. kerykeion/charts/themes/light.css +146 -103
  19. kerykeion/charts/themes/strawberry.css +158 -0
  20. kerykeion/composite_subject_factory.py +253 -51
  21. kerykeion/ephemeris_data_factory.py +434 -0
  22. kerykeion/fetch_geonames.py +27 -8
  23. kerykeion/house_comparison/__init__.py +6 -0
  24. kerykeion/house_comparison/house_comparison_factory.py +103 -0
  25. kerykeion/house_comparison/house_comparison_utils.py +126 -0
  26. kerykeion/kr_types/__init__.py +66 -6
  27. kerykeion/kr_types/chart_template_model.py +20 -0
  28. kerykeion/kr_types/kerykeion_exception.py +15 -9
  29. kerykeion/kr_types/kr_literals.py +14 -132
  30. kerykeion/kr_types/kr_models.py +14 -318
  31. kerykeion/kr_types/settings_models.py +15 -203
  32. kerykeion/planetary_return_factory.py +805 -0
  33. kerykeion/relationship_score_factory.py +301 -0
  34. kerykeion/report.py +751 -64
  35. kerykeion/schemas/__init__.py +106 -0
  36. kerykeion/schemas/chart_template_model.py +367 -0
  37. kerykeion/schemas/kerykeion_exception.py +20 -0
  38. kerykeion/schemas/kr_literals.py +181 -0
  39. kerykeion/schemas/kr_models.py +605 -0
  40. kerykeion/schemas/settings_models.py +180 -0
  41. kerykeion/settings/__init__.py +20 -1
  42. kerykeion/settings/chart_defaults.py +444 -0
  43. kerykeion/settings/config_constants.py +117 -12
  44. kerykeion/settings/kerykeion_settings.py +31 -73
  45. kerykeion/settings/translation_strings.py +1479 -0
  46. kerykeion/settings/translations.py +74 -0
  47. kerykeion/sweph/ast136/s136108s.se1 +0 -0
  48. kerykeion/sweph/ast136/s136199s.se1 +0 -0
  49. kerykeion/sweph/ast136/s136472s.se1 +0 -0
  50. kerykeion/sweph/ast28/se28978s.se1 +0 -0
  51. kerykeion/sweph/ast50/se50000s.se1 +0 -0
  52. kerykeion/sweph/ast90/se90377s.se1 +0 -0
  53. kerykeion/sweph/ast90/se90482s.se1 +0 -0
  54. kerykeion/sweph/sefstars.txt +1602 -0
  55. kerykeion/transits_time_range_factory.py +302 -0
  56. kerykeion/utilities.py +393 -114
  57. kerykeion-5.0.0.dist-info/METADATA +1176 -0
  58. kerykeion-5.0.0.dist-info/RECORD +63 -0
  59. {kerykeion-4.26.2.dist-info → kerykeion-5.0.0.dist-info}/WHEEL +1 -1
  60. kerykeion/aspects/natal_aspects.py +0 -172
  61. kerykeion/aspects/synastry_aspects.py +0 -124
  62. kerykeion/aspects/transits_time_range.py +0 -41
  63. kerykeion/astrological_subject.py +0 -841
  64. kerykeion/charts/kerykeion_chart_svg.py +0 -1219
  65. kerykeion/enums.py +0 -57
  66. kerykeion/ephemeris_data.py +0 -242
  67. kerykeion/kr_types/chart_types.py +0 -95
  68. kerykeion/relationship_score/__init__.py +0 -2
  69. kerykeion/relationship_score/relationship_score.py +0 -175
  70. kerykeion/relationship_score/relationship_score_factory.py +0 -230
  71. kerykeion/settings/kr.config.json +0 -1258
  72. kerykeion/transits_time_range.py +0 -124
  73. kerykeion-4.26.2.dist-info/LICENSE +0 -661
  74. kerykeion-4.26.2.dist-info/METADATA +0 -629
  75. kerykeion-4.26.2.dist-info/RECORD +0 -46
  76. kerykeion-4.26.2.dist-info/entry_points.txt +0 -3
  77. /LICENSE → /kerykeion-5.0.0.dist-info/licenses/LICENSE +0 -0
@@ -0,0 +1,158 @@
1
+ :root {
2
+ /* -------- */
3
+ /* General */
4
+ /* ------- */
5
+ --kerykeion-color-black: #141012; /* quasi nero con traccia impercettibile di rosa */
6
+ --kerykeion-color-white: #fef5f9; /* rosa tenue */
7
+ --kerykeion-color-neutral-content: #5c2d41; /* oklch(25% 0.08 330) - rosa scuro */
8
+ --kerykeion-color-base-content: #5c2d41; /* stessa di neutral-content */
9
+ --kerykeion-color-primary: #d91aa6; /* oklch(55% 0.35 320) - rosa magenta vibrante */
10
+ --kerykeion-color-secondary: #ff1b6b; /* oklch(70% 0.35 340) - rosa fucsia brillante */
11
+ --kerykeion-color-accent: #ff4dd2; /* oklch(80% 0.3 330) - rosa elettrico */
12
+ --kerykeion-color-neutral: #3d1f2a; /* oklch(16% 0.05 330) - rosa scuro */
13
+ --kerykeion-color-base-100: #fef0f5; /* oklch(96% 0.03 330) - sfondo rosa chiaro */
14
+ --kerykeion-color-info: #e056f2; /* oklch(74% 0.3 310) - rosa-viola brillante */
15
+ --kerykeion-color-info-content: #6b2e5d; /* oklch(32% 0.12 310) */
16
+ --kerykeion-color-success: #4fdc9a; /* oklch(78% 0.18 130) - verde con base rosa */
17
+ --kerykeion-color-warning: #ff8a6c; /* oklch(80% 0.25 50) - arancione rosato */
18
+ --kerykeion-color-error: #ff4f9a; /* oklch(73% 0.28 340) - rosa error vibrante */
19
+ /* Zodiac */
20
+ --kerykeion-color-base-200: #fce8f1; /* oklch(93% 0.05 330) - rosa delicato evidente */
21
+ --kerykeion-color-base-300: #f5d1e3; /* rosa chiaro invece di grigio */
22
+
23
+ /* ----------- */
24
+ /* Chart Color */
25
+ /* ----------- */
26
+
27
+ /* Main colors */
28
+ --kerykeion-chart-color-paper-0: var(--kerykeion-color-neutral-content);
29
+ --kerykeion-chart-color-paper-1: var(--kerykeion-color-base-100);
30
+
31
+ /* Zodiac Sign Sectors */
32
+ --kerykeion-chart-color-zodiac-bg-0: var(--kerykeion-color-base-200);
33
+ --kerykeion-chart-color-zodiac-bg-1: var(--kerykeion-color-base-300);
34
+ --kerykeion-chart-color-zodiac-bg-2: var(--kerykeion-color-base-200);
35
+ --kerykeion-chart-color-zodiac-bg-3: var(--kerykeion-color-base-300);
36
+ --kerykeion-chart-color-zodiac-bg-4: var(--kerykeion-color-base-200);
37
+ --kerykeion-chart-color-zodiac-bg-5: var(--kerykeion-color-base-300);
38
+ --kerykeion-chart-color-zodiac-bg-6: var(--kerykeion-color-base-200);
39
+ --kerykeion-chart-color-zodiac-bg-7: var(--kerykeion-color-base-300);
40
+ --kerykeion-chart-color-zodiac-bg-8: var(--kerykeion-color-base-200);
41
+ --kerykeion-chart-color-zodiac-bg-9: var(--kerykeion-color-base-300);
42
+ --kerykeion-chart-color-zodiac-bg-10: var(--kerykeion-color-base-200);
43
+ --kerykeion-chart-color-zodiac-bg-11: var(--kerykeion-color-base-300);
44
+
45
+ /* Zodiac Sign Rings */
46
+ --kerykeion-chart-color-zodiac-radix-ring-0: var(--kerykeion-color-neutral);
47
+ --kerykeion-chart-color-zodiac-radix-ring-1: var(--kerykeion-color-neutral);
48
+ --kerykeion-chart-color-zodiac-radix-ring-2: var(--kerykeion-color-neutral);
49
+ --kerykeion-chart-color-zodiac-transit-ring-0: var(
50
+ --kerykeion-color-neutral
51
+ );
52
+ --kerykeion-chart-color-zodiac-transit-ring-1: var(
53
+ --kerykeion-color-neutral
54
+ );
55
+ --kerykeion-chart-color-zodiac-transit-ring-2: var(
56
+ --kerykeion-color-neutral
57
+ );
58
+ --kerykeion-chart-color-zodiac-transit-ring-3: var(
59
+ --kerykeion-color-neutral
60
+ );
61
+ --kerykeion-chart-color-houses-radix-line: var(
62
+ --kerykeion-color-base-content
63
+ );
64
+ --kerykeion-chart-color-houses-transit-line: var(
65
+ --kerykeion-color-base-content
66
+ );
67
+
68
+ /* Aspects */
69
+ --kerykeion-chart-color-conjunction: var(--kerykeion-color-success);
70
+ --kerykeion-chart-color-semi-sextile: var(--kerykeion-color-success);
71
+ --kerykeion-chart-color-semi-square: var(--kerykeion-color-error);
72
+ --kerykeion-chart-color-sextile: var(--kerykeion-color-success);
73
+ --kerykeion-chart-color-quintile: var(--kerykeion-color-secondary);
74
+ --kerykeion-chart-color-square: var(--kerykeion-color-error);
75
+ --kerykeion-chart-color-trine: var(--kerykeion-color-success);
76
+ --kerykeion-chart-color-sesquiquadrate: var(--kerykeion-color-error);
77
+ --kerykeion-chart-color-biquintile: var(--kerykeion-color-secondary);
78
+ --kerykeion-chart-color-quincunx: var(--kerykeion-color-secondary);
79
+ --kerykeion-chart-color-opposition: var(--kerykeion-color-error);
80
+
81
+ /* Planets */
82
+ --kerykeion-chart-color-sun: var(--kerykeion-color-warning);
83
+ --kerykeion-chart-color-moon: var(--kerykeion-color-secondary);
84
+ --kerykeion-chart-color-mercury: var(--kerykeion-color-primary);
85
+ --kerykeion-chart-color-venus: var(--kerykeion-color-accent);
86
+ --kerykeion-chart-color-mars: var(--kerykeion-color-warning);
87
+ --kerykeion-chart-color-jupiter: var(--kerykeion-color-primary);
88
+ --kerykeion-chart-color-saturn: var(--kerykeion-color-secondary);
89
+ --kerykeion-chart-color-uranus: var(--kerykeion-color-accent);
90
+ --kerykeion-chart-color-neptune: var(--kerykeion-color-primary);
91
+ --kerykeion-chart-color-pluto: var(--kerykeion-color-secondary);
92
+ --kerykeion-chart-color-true-node: var(--kerykeion-color-warning);
93
+ --kerykeion-chart-color-mean-node: var(--kerykeion-color-warning);
94
+ --kerykeion-chart-color-chiron: var(--kerykeion-color-secondary);
95
+ --kerykeion-chart-color-first-house: var(--kerykeion-color-warning);
96
+ --kerykeion-chart-color-tenth-house: var(--kerykeion-color-warning);
97
+ --kerykeion-chart-color-seventh-house: var(--kerykeion-color-warning);
98
+ --kerykeion-chart-color-fourth-house: var(--kerykeion-color-warning);
99
+ --kerykeion-chart-color-mean-lilith: var(--kerykeion-color-secondary);
100
+ --kerykeion-chart-color-true-lilith: var(--kerykeion-color-secondary);
101
+ --kerykeion-chart-color-ceres: var(--kerykeion-color-accent);
102
+ --kerykeion-chart-color-pallas: var(--kerykeion-color-primary);
103
+ --kerykeion-chart-color-juno: var(--kerykeion-color-accent);
104
+ --kerykeion-chart-color-vesta: var(--kerykeion-color-warning);
105
+ --kerykeion-chart-color-pars-fortunae: var(--kerykeion-color-success);
106
+ --kerykeion-chart-color-vertex: var(--kerykeion-color-secondary);
107
+ --kerykeion-chart-color-east-point: var(--kerykeion-color-primary);
108
+ --kerykeion-chart-color-eris: var(--kerykeion-color-error);
109
+ --kerykeion-chart-color-earth: var(--kerykeion-color-warning);
110
+ --kerykeion-chart-color-pholus: var(--kerykeion-color-secondary);
111
+ --kerykeion-chart-color-sedna: var(--kerykeion-color-warning);
112
+ --kerykeion-chart-color-haumea: var(--kerykeion-color-accent);
113
+ --kerykeion-chart-color-makemake: var(--kerykeion-color-primary);
114
+ --kerykeion-chart-color-ixion: var(--kerykeion-color-secondary);
115
+ --kerykeion-chart-color-orcus: var(--kerykeion-color-accent);
116
+ --kerykeion-chart-color-quaoar: var(--kerykeion-color-primary);
117
+ --kerykeion-chart-color-regulus: var(--kerykeion-color-warning);
118
+ --kerykeion-chart-color-spica: var(--kerykeion-color-accent);
119
+ --kerykeion-chart-color-anti-vertex: var(--kerykeion-color-secondary);
120
+
121
+ /* Arab Parts */
122
+ --kerykeion-chart-color-pars-spiritus: var(--kerykeion-color-success);
123
+ --kerykeion-chart-color-pars-amoris: var(--kerykeion-color-accent);
124
+ --kerykeion-chart-color-pars-fortunae: var(--kerykeion-color-success);
125
+ --kerykeion-chart-color-pars-fidei: var(--kerykeion-color-success);
126
+
127
+ /* Zodiac Signs */
128
+ --kerykeion-chart-color-zodiac-icon-0: var(--kerykeion-color-accent);
129
+ --kerykeion-chart-color-zodiac-icon-1: var(--kerykeion-color-warning);
130
+ --kerykeion-chart-color-zodiac-icon-2: var(--kerykeion-color-primary);
131
+ --kerykeion-chart-color-zodiac-icon-3: var(--kerykeion-color-secondary);
132
+ --kerykeion-chart-color-zodiac-icon-4: var(--kerykeion-color-accent);
133
+ --kerykeion-chart-color-zodiac-icon-5: var(--kerykeion-color-warning);
134
+ --kerykeion-chart-color-zodiac-icon-6: var(--kerykeion-color-primary);
135
+ --kerykeion-chart-color-zodiac-icon-7: var(--kerykeion-color-secondary);
136
+ --kerykeion-chart-color-zodiac-icon-8: var(--kerykeion-color-accent);
137
+ --kerykeion-chart-color-zodiac-icon-9: var(--kerykeion-color-warning);
138
+ --kerykeion-chart-color-zodiac-icon-10: var(--kerykeion-color-primary);
139
+ --kerykeion-chart-color-zodiac-icon-11: var(--kerykeion-color-secondary);
140
+
141
+ /* Elements Percentage */
142
+ --kerykeion-chart-color-air-percentage: var(--kerykeion-color-primary);
143
+ --kerykeion-chart-color-earth-percentage: var(--kerykeion-color-warning);
144
+ --kerykeion-chart-color-fire-percentage: var(--kerykeion-color-accent);
145
+ --kerykeion-chart-color-water-percentage: var(--kerykeion-color-secondary);
146
+
147
+ /* Modalities Percentage */
148
+ --kerykeion-chart-color-cardinal-percentage: var(--kerykeion-color-success);
149
+ --kerykeion-chart-color-fixed-percentage: var(--kerykeion-color-warning);
150
+ --kerykeion-chart-color-mutable-percentage: var(--kerykeion-color-error);
151
+
152
+ /* Aspects */
153
+ --kerykeion-chart-color-lunar-phase-0: var(--kerykeion-color-black);
154
+ --kerykeion-chart-color-lunar-phase-1: var(--kerykeion-color-white);
155
+
156
+ /* Houses Numbers */
157
+ --kerykeion-chart-color-house-number: var(--kerykeion-color-base-content);
158
+ }
@@ -1,29 +1,104 @@
1
+ """
2
+ Composite Subject Factory Module
3
+
4
+ This module provides functionality for creating composite astrological charts from two
5
+ individual astrological subjects. A composite chart represents the relationship between
6
+ two people by calculating midpoint positions between corresponding planetary placements
7
+ and house cusps.
8
+
9
+ The module implements the midpoint composite technique, which is the most commonly used
10
+ method for relationship astrology. This technique creates a single chart that symbolizes
11
+ the energy and dynamics of the relationship itself, rather than comparing individual charts.
12
+
13
+ Key Features:
14
+ - Midpoint calculation for all planetary positions
15
+ - Midpoint calculation for house cusp positions
16
+ - Proper handling of zodiacal boundary crossings (0°/360°)
17
+ - Validation of compatible astrological settings between subjects
18
+ - Lunar phase calculation for composite charts
19
+ - Support for all standard astrological points and house systems
20
+
21
+ Classes:
22
+ CompositeSubjectFactory: Main factory class for creating composite charts
23
+
24
+ Dependencies:
25
+ - AstrologicalSubjectFactory: For working with individual astrological subjects
26
+ - Various schemas modules: For type definitions and models
27
+ - utilities module: For astrological calculations and helper functions
28
+
29
+ Example Usage:
30
+ >>> from kerykeion import AstrologicalSubjectFactory, CompositeSubjectFactory
31
+ >>> person1 = AstrologicalSubjectFactory.from_birth_data(...)
32
+ >>> person2 = AstrologicalSubjectFactory.from_birth_data(...)
33
+ >>> composite = CompositeSubjectFactory(person1, person2)
34
+ >>> composite_chart = composite.get_midpoint_composite_subject_model()
35
+
36
+ Author: Giacomo Battaglia
37
+ Copyright: (C) 2025 Kerykeion Project
38
+ License: AGPL-3.0
39
+ """
40
+
1
41
  from typing import Union
2
42
 
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
43
+ # Fix the circular import by changing this import
44
+ from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
45
+ from kerykeion.schemas.kerykeion_exception import KerykeionException
46
+ from kerykeion.schemas.kr_models import CompositeSubjectModel, AstrologicalSubjectModel
47
+ from kerykeion.schemas.kr_literals import ZodiacType, PerspectiveType, HousesSystemIdentifier, SiderealMode, AstrologicalPoint, Houses, CompositeChartType
7
48
  from kerykeion.utilities import (
8
49
  get_kerykeion_point_from_degree,
9
50
  get_planet_house,
10
51
  circular_mean,
11
52
  calculate_moon_phase,
12
- circular_sort
53
+ circular_sort,
54
+ find_common_active_points
13
55
  )
14
56
 
15
57
 
16
58
  class CompositeSubjectFactory:
17
59
  """
18
- Factory class to create a Composite Subject Model from two Astrological Subjects
19
- Currently, the only available method for creating composite charts is the midpoint method.
20
- The composite houses and planets are calculated based on the midpoint of the corresponding points of the two subjects.
21
- The house are then reordered to match the original house system of the first subject.
22
-
23
- Args:
24
- first_subject (AstrologicalSubject): First astrological subject
25
- second_subject (AstrologicalSubject): Second astrological subject
26
- chart_name (str): Name of the composite chart. If None, it will be automatically generated.
60
+ Factory class to create composite astrological charts from two astrological subjects.
61
+
62
+ A composite chart represents the relationship between two people by calculating the midpoint
63
+ between corresponding planetary positions and house cusps. This creates a single chart
64
+ that symbolizes the energy of the relationship itself.
65
+
66
+ Currently supports the midpoint method for composite chart calculation, where:
67
+ - Planetary positions are calculated as the circular mean of corresponding planets
68
+ - House cusps are calculated as the circular mean of corresponding houses
69
+ - Houses are reordered to maintain consistency with the original house system
70
+ - Only common active points between both subjects are included
71
+
72
+ The resulting composite chart maintains the zodiac type, sidereal mode, houses system,
73
+ and perspective type of the input subjects (which must be identical between subjects).
74
+
75
+ Attributes:
76
+ model (CompositeSubjectModel | None): The generated composite subject model
77
+ first_subject (AstrologicalSubjectModel): First astrological subject
78
+ second_subject (AstrologicalSubjectModel): Second astrological subject
79
+ name (str): Name of the composite chart
80
+ composite_chart_type (CompositeChartType): Type of composite chart (currently "Midpoint")
81
+ zodiac_type (ZodiacType): Zodiac system used (Tropical or Sidereal)
82
+ sidereal_mode (SiderealMode | None): Sidereal calculation mode if applicable
83
+ houses_system_identifier (HousesSystemIdentifier): House system identifier
84
+ houses_system_name (str): Human-readable house system name
85
+ perspective_type (PerspectiveType): Astrological perspective type
86
+ houses_names_list (list[Houses]): List of house names
87
+ active_points (list[AstrologicalPoint]): Common active planetary points
88
+
89
+ Example:
90
+ >>> first_person = AstrologicalSubjectFactory.from_birth_data(
91
+ ... "John", 1990, 1, 1, 12, 0, "New York", "US"
92
+ ... )
93
+ >>> second_person = AstrologicalSubjectFactory.from_birth_data(
94
+ ... "Jane", 1992, 6, 15, 14, 30, "Los Angeles", "US"
95
+ ... )
96
+ >>> composite = CompositeSubjectFactory(first_person, second_person)
97
+ >>> composite_model = composite.get_midpoint_composite_subject_model()
98
+
99
+ Raises:
100
+ KerykeionException: When subjects have incompatible settings (different zodiac types,
101
+ sidereal modes, house systems, or perspective types)
27
102
  """
28
103
 
29
104
  model: Union[CompositeSubjectModel, None]
@@ -36,26 +111,45 @@ class CompositeSubjectFactory:
36
111
  houses_system_identifier: HousesSystemIdentifier
37
112
  houses_system_name: str
38
113
  perspective_type: PerspectiveType
39
- planets_names_list: list[Planet]
40
114
  houses_names_list: list[Houses]
41
- axial_cusps_names_list: list[AxialCusps]
115
+ active_points: list[AstrologicalPoint]
42
116
 
43
117
  def __init__(
44
118
  self,
45
- first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
46
- second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
119
+ first_subject: AstrologicalSubjectModel,
120
+ second_subject: AstrologicalSubjectModel,
47
121
  chart_name: Union[str, None] = None
48
122
  ):
123
+ """
124
+ Initialize the composite subject factory with two astrological subjects.
125
+
126
+ Validates that both subjects have compatible settings and extracts common
127
+ active points for composite chart calculation.
128
+
129
+ Args:
130
+ first_subject (AstrologicalSubjectModel): First astrological subject for the composite
131
+ second_subject (AstrologicalSubjectModel): Second astrological subject for the composite
132
+ chart_name (str | None, optional): Custom name for the composite chart.
133
+ If None, generates name from subject names.
134
+ Defaults to None.
135
+
136
+ Raises:
137
+ KerykeionException: If subjects have different zodiac types, sidereal modes,
138
+ house systems, house system names, or perspective types.
139
+
140
+ Note:
141
+ Both subjects must have identical astrological calculation settings to ensure
142
+ meaningful composite chart calculations.
143
+ """
49
144
  self.model: Union[CompositeSubjectModel, None] = None
50
145
  self.composite_chart_type = "Midpoint"
51
146
 
52
- # Subjects
53
- if isinstance(first_subject, AstrologicalSubject) or isinstance(first_subject, AstrologicalSubjectModel):
54
- self.first_subject = first_subject.model() # type: ignore
55
- self.second_subject = second_subject.model() # type: ignore
56
- else:
57
- self.first_subject = first_subject
58
- self.second_subject = second_subject
147
+ self.first_subject = first_subject
148
+ self.second_subject = second_subject
149
+ self.active_points = find_common_active_points(
150
+ first_subject.active_points,
151
+ second_subject.active_points
152
+ )
59
153
 
60
154
  # Name
61
155
  if chart_name is None:
@@ -93,42 +187,123 @@ class CompositeSubjectFactory:
93
187
  self.perspective_type = first_subject.perspective_type
94
188
 
95
189
  # Planets Names List
96
- self.planets_names_list = []
97
- for planet in first_subject.planets_names_list:
98
- if planet in second_subject.planets_names_list:
99
- self.planets_names_list.append(planet)
190
+ self.active_points = []
191
+ for planet in first_subject.active_points:
192
+ if planet in second_subject.active_points:
193
+ self.active_points.append(planet)
100
194
 
101
195
  # Houses Names List
102
196
  self.houses_names_list = self.first_subject.houses_names_list
103
197
 
104
- # Axial Cusps Names List
105
- self.axial_cusps_names_list = self.first_subject.axial_cusps_names_list
106
-
107
198
  def __str__(self):
199
+ """
200
+ Return string representation of the composite subject.
201
+
202
+ Returns:
203
+ str: Human-readable string describing the composite chart.
204
+ """
108
205
  return f"Composite Chart Data for {self.name}"
109
206
 
110
207
  def __repr__(self):
208
+ """
209
+ Return detailed string representation of the composite subject.
210
+
211
+ Returns:
212
+ str: Detailed string representation for debugging purposes.
213
+ """
111
214
  return f"Composite Chart Data for {self.name}"
112
215
 
113
216
  def __eq__(self, other):
217
+ """
218
+ Check equality with another composite subject.
219
+
220
+ Args:
221
+ other (CompositeSubjectFactory): Another composite subject to compare with.
222
+
223
+ Returns:
224
+ bool: True if both subjects and chart name are identical.
225
+ """
114
226
  return self.first_subject == other.first_subject and self.second_subject == other.second_subject and self.name == other.chart_name
115
227
 
116
228
  def __ne__(self, other):
229
+ """
230
+ Check inequality with another composite subject.
231
+
232
+ Args:
233
+ other (CompositeSubjectFactory): Another composite subject to compare with.
234
+
235
+ Returns:
236
+ bool: True if subjects or chart name are different.
237
+ """
117
238
  return not self.__eq__(other)
118
239
 
119
240
  def __hash__(self):
241
+ """
242
+ Generate hash for the composite subject.
243
+
244
+ Returns:
245
+ int: Hash value based on both subjects and chart name.
246
+ """
120
247
  return hash((self.first_subject, self.second_subject, self.name))
121
248
 
122
249
  def __copy__(self):
250
+ """
251
+ Create a shallow copy of the composite subject.
252
+
253
+ Returns:
254
+ CompositeSubjectFactory: New instance with the same subjects and name.
255
+ """
123
256
  return CompositeSubjectFactory(self.first_subject, self.second_subject, self.name)
124
257
 
125
258
  def __setitem__(self, key, value):
259
+ """
260
+ Set an attribute using dictionary-style access.
261
+
262
+ Args:
263
+ key (str): Attribute name to set.
264
+ value: Value to assign to the attribute.
265
+ """
126
266
  setattr(self, key, value)
127
267
 
128
268
  def __getitem__(self, key):
269
+ """
270
+ Get an attribute using dictionary-style access.
271
+
272
+ Args:
273
+ key (str): Attribute name to retrieve.
274
+
275
+ Returns:
276
+ Any: Value of the requested attribute.
277
+
278
+ Raises:
279
+ AttributeError: If the attribute doesn't exist.
280
+ """
129
281
  return getattr(self, key)
130
282
 
131
283
  def _calculate_midpoint_composite_points_and_houses(self):
284
+ """
285
+ Calculate midpoint positions for all planets and house cusps in the composite chart.
286
+
287
+ This method implements the midpoint composite technique by:
288
+ 1. Computing circular means of house cusp positions from both subjects
289
+ 2. Sorting house positions to maintain proper house order
290
+ 3. Creating composite house cusps with calculated positions
291
+ 4. Computing circular means of planetary positions for common active points
292
+ 5. Assigning planets to their appropriate houses in the composite chart
293
+
294
+ The circular mean calculation ensures proper handling of zodiacal positions
295
+ around the 360-degree boundary (e.g., when one position is at 350° and
296
+ another at 10°, the midpoint is correctly calculated as 0°).
297
+
298
+ Side Effects:
299
+ - Updates instance attributes with calculated house cusp positions
300
+ - Updates instance attributes with calculated planetary positions
301
+ - Sets house assignments for each planetary position
302
+
303
+ Note:
304
+ This is an internal method called by get_midpoint_composite_subject_model().
305
+ Only planets that exist in both subjects' active_points are included.
306
+ """
132
307
  # Houses
133
308
  house_degree_list_ut = []
134
309
  for house in self.first_subject.houses_names_list:
@@ -152,8 +327,8 @@ class CompositeSubjectFactory:
152
327
 
153
328
  # Planets
154
329
  common_planets = []
155
- for planet in self.first_subject.planets_names_list:
156
- if planet in self.second_subject.planets_names_list:
330
+ for planet in self.first_subject.active_points:
331
+ if planet in self.second_subject.active_points:
157
332
  common_planets.append(planet)
158
333
 
159
334
  planets = {}
@@ -164,30 +339,57 @@ class CompositeSubjectFactory:
164
339
  self.first_subject[planet_lower]["abs_pos"],
165
340
  self.second_subject[planet_lower]["abs_pos"]
166
341
  )
167
- self[planet_lower] = get_kerykeion_point_from_degree(planets[planet_lower]["abs_pos"], planet, "Planet")
342
+ self[planet_lower] = get_kerykeion_point_from_degree(planets[planet_lower]["abs_pos"], planet, "AstrologicalPoint")
168
343
  self[planet_lower]["house"] = get_planet_house(self[planet_lower]['abs_pos'], house_degree_list_ut)
169
344
 
345
+ def _calculate_composite_lunar_phase(self):
346
+ """
347
+ Calculate the lunar phase for the composite chart based on Sun-Moon midpoints.
170
348
 
171
- # Axial Cusps
172
- for cusp in self.first_subject.axial_cusps_names_list:
173
- cusp_lower = cusp.lower()
174
- self[cusp_lower] = get_kerykeion_point_from_degree(
175
- circular_mean(
176
- self.first_subject[cusp_lower]["abs_pos"],
177
- self.second_subject[cusp_lower]["abs_pos"]
178
- ),
179
- cusp,
180
- "AxialCusps"
181
- )
182
- self[cusp_lower]["house"] = get_planet_house(self[cusp_lower]['abs_pos'], house_degree_list_ut)
349
+ Uses the composite positions of the Sun and Moon to determine the lunar phase
350
+ angle, representing the relationship's emotional and instinctual dynamics.
183
351
 
184
- def _calculate_composite_lunar_phase(self):
352
+ Side Effects:
353
+ Sets the lunar_phase attribute with the calculated phase information.
354
+
355
+ Note:
356
+ This method should be called after _calculate_midpoint_composite_points_and_houses()
357
+ to ensure Sun and Moon composite positions are available.
358
+ """
185
359
  self.lunar_phase = calculate_moon_phase(
186
360
  self['moon'].abs_pos,
187
361
  self['sun'].abs_pos
188
362
  )
189
363
 
190
364
  def get_midpoint_composite_subject_model(self):
365
+ """
366
+ Generate the complete composite chart model using the midpoint technique.
367
+
368
+ This is the main public method for creating a composite chart. It orchestrates
369
+ the calculation of all composite positions and creates a complete CompositeSubjectModel
370
+ containing all necessary astrological data for the relationship chart.
371
+
372
+ The process includes:
373
+ 1. Calculating midpoint positions for all planets and house cusps
374
+ 2. Computing the composite lunar phase
375
+ 3. Assembling all data into a comprehensive model
376
+
377
+ Returns:
378
+ CompositeSubjectModel: Complete composite chart data model containing:
379
+ - All calculated planetary positions and their house placements
380
+ - House cusp positions maintaining proper house system order
381
+ - Lunar phase information for the composite chart
382
+ - All metadata from the original subjects (names, chart type, etc.)
383
+
384
+ Example:
385
+ >>> composite = CompositeSubjectFactory(person1, person2, "Our Relationship")
386
+ >>> model = composite.get_midpoint_composite_subject_model()
387
+ >>> print(f"Composite Sun at {model.sun.abs_pos}° in House {model.sun.house}")
388
+
389
+ Note:
390
+ This method performs all calculations internally and returns a complete,
391
+ ready-to-use composite chart model suitable for analysis or chart drawing.
392
+ """
191
393
  self._calculate_midpoint_composite_points_and_houses()
192
394
  self._calculate_composite_lunar_phase()
193
395
 
@@ -197,10 +399,10 @@ class CompositeSubjectFactory:
197
399
 
198
400
 
199
401
  if __name__ == "__main__":
200
- from kerykeion.astrological_subject import AstrologicalSubject
402
+ from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
201
403
 
202
- first = AstrologicalSubject("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
203
- second = AstrologicalSubject("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
404
+ first = AstrologicalSubjectFactory.from_birth_data("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
405
+ second = AstrologicalSubjectFactory.from_birth_data("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
204
406
 
205
407
  composite_chart = CompositeSubjectFactory(first, second)
206
408
  print(composite_chart.get_midpoint_composite_subject_model().model_dump_json(indent=4))