kerykeion 4.18.3__py3-none-any.whl → 5.1.9__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 (76) hide show
  1. kerykeion/__init__.py +56 -11
  2. kerykeion/aspects/__init__.py +7 -4
  3. kerykeion/aspects/aspects_factory.py +568 -0
  4. kerykeion/aspects/aspects_utils.py +86 -13
  5. kerykeion/astrological_subject_factory.py +1901 -0
  6. kerykeion/backword.py +820 -0
  7. kerykeion/chart_data_factory.py +552 -0
  8. kerykeion/charts/__init__.py +2 -2
  9. kerykeion/charts/chart_drawer.py +2794 -0
  10. kerykeion/charts/charts_utils.py +1066 -309
  11. kerykeion/charts/draw_planets.py +602 -351
  12. kerykeion/charts/templates/aspect_grid_only.xml +337 -193
  13. kerykeion/charts/templates/chart.xml +441 -240
  14. kerykeion/charts/templates/wheel_only.xml +365 -211
  15. kerykeion/charts/themes/black-and-white.css +148 -0
  16. kerykeion/charts/themes/classic.css +107 -76
  17. kerykeion/charts/themes/dark-high-contrast.css +145 -107
  18. kerykeion/charts/themes/dark.css +146 -107
  19. kerykeion/charts/themes/light.css +146 -103
  20. kerykeion/charts/themes/strawberry.css +158 -0
  21. kerykeion/composite_subject_factory.py +408 -0
  22. kerykeion/ephemeris_data_factory.py +443 -0
  23. kerykeion/fetch_geonames.py +81 -21
  24. kerykeion/house_comparison/__init__.py +6 -0
  25. kerykeion/house_comparison/house_comparison_factory.py +103 -0
  26. kerykeion/house_comparison/house_comparison_utils.py +126 -0
  27. kerykeion/kr_types/__init__.py +66 -6
  28. kerykeion/kr_types/chart_template_model.py +20 -0
  29. kerykeion/kr_types/kerykeion_exception.py +15 -9
  30. kerykeion/kr_types/kr_literals.py +14 -106
  31. kerykeion/kr_types/kr_models.py +14 -179
  32. kerykeion/kr_types/settings_models.py +15 -152
  33. kerykeion/planetary_return_factory.py +805 -0
  34. kerykeion/relationship_score_factory.py +301 -0
  35. kerykeion/report.py +750 -65
  36. kerykeion/schemas/__init__.py +106 -0
  37. kerykeion/schemas/chart_template_model.py +367 -0
  38. kerykeion/schemas/kerykeion_exception.py +20 -0
  39. kerykeion/schemas/kr_literals.py +181 -0
  40. kerykeion/schemas/kr_models.py +603 -0
  41. kerykeion/schemas/settings_models.py +188 -0
  42. kerykeion/settings/__init__.py +20 -1
  43. kerykeion/settings/chart_defaults.py +444 -0
  44. kerykeion/settings/config_constants.py +152 -0
  45. kerykeion/settings/kerykeion_settings.py +36 -61
  46. kerykeion/settings/translation_strings.py +1499 -0
  47. kerykeion/settings/translations.py +74 -0
  48. kerykeion/sweph/ast136/s136108s.se1 +0 -0
  49. kerykeion/sweph/ast136/s136199s.se1 +0 -0
  50. kerykeion/sweph/ast136/s136472s.se1 +0 -0
  51. kerykeion/sweph/ast28/se28978s.se1 +0 -0
  52. kerykeion/sweph/ast50/se50000s.se1 +0 -0
  53. kerykeion/sweph/ast90/se90377s.se1 +0 -0
  54. kerykeion/sweph/ast90/se90482s.se1 +0 -0
  55. kerykeion/sweph/sefstars.txt +1602 -0
  56. kerykeion/transits_time_range_factory.py +302 -0
  57. kerykeion/utilities.py +626 -125
  58. kerykeion-5.1.9.dist-info/METADATA +1793 -0
  59. kerykeion-5.1.9.dist-info/RECORD +63 -0
  60. {kerykeion-4.18.3.dist-info → kerykeion-5.1.9.dist-info}/WHEEL +1 -1
  61. kerykeion/aspects/natal_aspects.py +0 -143
  62. kerykeion/aspects/synastry_aspects.py +0 -113
  63. kerykeion/astrological_subject.py +0 -818
  64. kerykeion/charts/kerykeion_chart_svg.py +0 -894
  65. kerykeion/enums.py +0 -51
  66. kerykeion/ephemeris_data.py +0 -178
  67. kerykeion/kr_types/chart_types.py +0 -88
  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 -275
  71. kerykeion/settings/kr.config.json +0 -721
  72. kerykeion-4.18.3.dist-info/LICENSE +0 -661
  73. kerykeion-4.18.3.dist-info/METADATA +0 -396
  74. kerykeion-4.18.3.dist-info/RECORD +0 -42
  75. kerykeion-4.18.3.dist-info/entry_points.txt +0 -3
  76. /LICENSE → /kerykeion-5.1.9.dist-info/licenses/LICENSE +0 -0
@@ -0,0 +1,552 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Chart Data Factory Module
4
+
5
+ This module provides factory classes for creating comprehensive chart data models that include
6
+ all the pure data from astrological charts, including subjects, aspects, house comparisons,
7
+ and other analytical data without the visual rendering components.
8
+
9
+ This is designed to be the "pure data" counterpart to ChartDrawer, providing structured
10
+ access to all chart information for API consumption, data analysis, or other programmatic uses.
11
+
12
+ Key Features:
13
+ - Comprehensive chart data including subjects and aspects
14
+ - House comparison analysis for dual charts
15
+ - Element and quality distributions
16
+ - Relationship scoring for synastry charts
17
+ - Flexible point and aspect filtering
18
+ - Support for all chart types (Natal, Transit, Synastry, Composite, Return)
19
+
20
+ Classes:
21
+ ElementDistributionModel: Model for element distribution analysis
22
+ QualityDistributionModel: Model for quality distribution analysis
23
+ SingleChartDataModel: Model for single-subject chart data
24
+ DualChartDataModel: Model for dual-subject chart data
25
+ ChartDataFactory: Factory for creating chart data models
26
+
27
+ Author: Giacomo Battaglia
28
+ Copyright: (C) 2025 Kerykeion Project
29
+ License: AGPL-3.0
30
+ """
31
+
32
+ from typing import Mapping, Union, Optional, List, Literal, cast
33
+
34
+ from kerykeion.aspects import AspectsFactory
35
+ from kerykeion.house_comparison.house_comparison_factory import HouseComparisonFactory
36
+ from kerykeion.relationship_score_factory import RelationshipScoreFactory
37
+ from kerykeion.schemas import (
38
+ KerykeionException,
39
+ ChartType,
40
+ ActiveAspect
41
+ )
42
+ from kerykeion.schemas.kr_models import (
43
+ AstrologicalSubjectModel,
44
+ CompositeSubjectModel,
45
+ PlanetReturnModel,
46
+ SingleChartAspectsModel,
47
+ DualChartAspectsModel,
48
+ ElementDistributionModel,
49
+ QualityDistributionModel,
50
+ SingleChartDataModel,
51
+ DualChartDataModel,
52
+ ChartDataModel
53
+ )
54
+ from kerykeion.schemas.settings_models import KerykeionSettingsCelestialPointModel
55
+ from kerykeion.schemas.kr_literals import (
56
+ AstrologicalPoint,
57
+ )
58
+ from kerykeion.utilities import find_common_active_points, distribute_percentages_to_100
59
+ from kerykeion.settings.config_constants import DEFAULT_ACTIVE_ASPECTS
60
+ from kerykeion.settings.chart_defaults import DEFAULT_CELESTIAL_POINTS_SETTINGS
61
+ from kerykeion.charts.charts_utils import (
62
+ ElementQualityDistributionMethod,
63
+ calculate_element_points,
64
+ calculate_quality_points,
65
+ calculate_synastry_element_points,
66
+ calculate_synastry_quality_points,
67
+ )
68
+
69
+
70
+
71
+ class ChartDataFactory:
72
+ """
73
+ Factory class for creating comprehensive chart data models.
74
+
75
+ This factory creates ChartDataModel instances containing all the pure data
76
+ from astrological charts, including subjects, aspects, house comparisons,
77
+ and analytical metrics. It provides the structured data equivalent of
78
+ ChartDrawer's visual output.
79
+
80
+ The factory handles all chart types and automatically includes relevant
81
+ analyses based on chart type (e.g., house comparison for dual charts,
82
+ relationship scoring for synastry charts).
83
+
84
+ Example:
85
+ >>> # Create natal chart data
86
+ >>> john = AstrologicalSubjectFactory.from_birth_data("John", 1990, 1, 1, 12, 0, "London", "GB")
87
+ >>> natal_data = ChartDataFactory.create_chart_data("Natal", john)
88
+ >>> print(f"Elements: Fire {natal_data.element_distribution.fire_percentage}%")
89
+ >>>
90
+ >>> # Create synastry chart data
91
+ >>> jane = AstrologicalSubjectFactory.from_birth_data("Jane", 1992, 6, 15, 14, 30, "Paris", "FR")
92
+ >>> synastry_data = ChartDataFactory.create_chart_data("Synastry", john, jane)
93
+ >>> print(f"Relationship score: {synastry_data.relationship_score.score_value}")
94
+ """
95
+
96
+ @staticmethod
97
+ def create_chart_data(
98
+ chart_type: ChartType,
99
+ first_subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
100
+ second_subject: Optional[Union[AstrologicalSubjectModel, PlanetReturnModel]] = None,
101
+ active_points: Optional[List[AstrologicalPoint]] = None,
102
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
103
+ include_house_comparison: bool = True,
104
+ include_relationship_score: bool = True,
105
+ *,
106
+ axis_orb_limit: Optional[float] = None,
107
+ distribution_method: ElementQualityDistributionMethod = "weighted",
108
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
109
+ ) -> ChartDataModel:
110
+ """
111
+ Create comprehensive chart data for the specified chart type.
112
+
113
+ Args:
114
+ chart_type: Type of chart to create data for
115
+ first_subject: Primary astrological subject
116
+ second_subject: Secondary subject (required for dual charts)
117
+ active_points: Points to include in calculations (defaults to first_subject.active_points)
118
+ active_aspects: Aspect types and orbs to use
119
+ include_house_comparison: Whether to include house comparison for dual charts
120
+ include_relationship_score: Whether to include relationship scoring for synastry
121
+ axis_orb_limit: Optional orb threshold for chart axes (applies only to single chart aspects)
122
+ distribution_method: Strategy for element/modality weighting ("pure_count" or "weighted")
123
+ custom_distribution_weights: Optional overrides for the distribution weights
124
+
125
+ Returns:
126
+ ChartDataModel: Comprehensive chart data model
127
+
128
+ Raises:
129
+ KerykeionException: If chart type requirements are not met
130
+ """
131
+
132
+ # Validate chart type requirements
133
+ if chart_type in ["Transit", "Synastry", "DualReturnChart"] and not second_subject:
134
+ raise KerykeionException(f"Second subject is required for {chart_type} charts.")
135
+
136
+ if chart_type == "Composite" and not isinstance(first_subject, CompositeSubjectModel):
137
+ raise KerykeionException("First subject must be a CompositeSubjectModel for Composite charts.")
138
+
139
+ if chart_type == "Return" and not isinstance(second_subject, PlanetReturnModel):
140
+ raise KerykeionException("Second subject must be a PlanetReturnModel for Return charts.")
141
+
142
+ if chart_type == "SingleReturnChart" and not isinstance(first_subject, PlanetReturnModel):
143
+ raise KerykeionException("First subject must be a PlanetReturnModel for SingleReturnChart charts.")
144
+
145
+ # Determine active points
146
+ if not active_points:
147
+ effective_active_points = first_subject.active_points
148
+ else:
149
+ effective_active_points = find_common_active_points(
150
+ active_points,
151
+ first_subject.active_points
152
+ )
153
+
154
+ # For dual charts, further filter by second subject's active points
155
+ if second_subject:
156
+ effective_active_points = find_common_active_points(
157
+ effective_active_points,
158
+ second_subject.active_points
159
+ )
160
+
161
+ # Calculate aspects based on chart type
162
+ aspects_model: Union[SingleChartAspectsModel, DualChartAspectsModel]
163
+ if chart_type in ["Natal", "Composite", "SingleReturnChart"]:
164
+ # Single chart aspects
165
+ aspects_model = AspectsFactory.single_chart_aspects(
166
+ first_subject,
167
+ active_points=effective_active_points,
168
+ active_aspects=active_aspects,
169
+ axis_orb_limit=axis_orb_limit,
170
+ )
171
+ else:
172
+ # Dual chart aspects - second_subject is guaranteed to exist here due to validation above
173
+ if second_subject is None:
174
+ raise KerykeionException(f"Second subject is required for {chart_type} charts.")
175
+ aspects_model = AspectsFactory.dual_chart_aspects(
176
+ first_subject,
177
+ second_subject,
178
+ active_points=effective_active_points,
179
+ active_aspects=active_aspects,
180
+ axis_orb_limit=axis_orb_limit,
181
+ )
182
+
183
+ # Calculate house comparison for dual charts
184
+ house_comparison = None
185
+ if second_subject and include_house_comparison and chart_type in ["Transit", "Synastry", "DualReturnChart"]:
186
+ if isinstance(first_subject, AstrologicalSubjectModel) and isinstance(second_subject, (AstrologicalSubjectModel, PlanetReturnModel)):
187
+ house_comparison_factory = HouseComparisonFactory(
188
+ first_subject,
189
+ second_subject,
190
+ active_points=effective_active_points
191
+ )
192
+ house_comparison = house_comparison_factory.get_house_comparison()
193
+
194
+ # Calculate relationship score for synastry
195
+ relationship_score = None
196
+ if chart_type == "Synastry" and include_relationship_score and second_subject:
197
+ if isinstance(first_subject, AstrologicalSubjectModel) and isinstance(second_subject, AstrologicalSubjectModel):
198
+ relationship_score_factory = RelationshipScoreFactory(
199
+ first_subject,
200
+ second_subject,
201
+ axis_orb_limit=axis_orb_limit,
202
+ )
203
+ relationship_score = relationship_score_factory.get_relationship_score()
204
+
205
+ # Calculate element and quality distributions
206
+ available_planets_setting_dicts: list[dict[str, object]] = []
207
+ for body in DEFAULT_CELESTIAL_POINTS_SETTINGS:
208
+ if body["name"] in effective_active_points:
209
+ body_dict = dict(body)
210
+ body_dict["is_active"] = True
211
+ available_planets_setting_dicts.append(body_dict)
212
+
213
+ # Convert to models for type safety
214
+ available_planets_setting: list[KerykeionSettingsCelestialPointModel] = [
215
+ KerykeionSettingsCelestialPointModel(**body) for body in available_planets_setting_dicts # type: ignore
216
+ ]
217
+
218
+ celestial_points_names = [body.name.lower() for body in available_planets_setting]
219
+
220
+ if chart_type == "Synastry" and second_subject:
221
+ # Calculate combined element/quality points for synastry
222
+ # Type narrowing: ensure both subjects are AstrologicalSubjectModel for synastry
223
+ if isinstance(first_subject, AstrologicalSubjectModel) and isinstance(second_subject, AstrologicalSubjectModel):
224
+ element_totals = calculate_synastry_element_points(
225
+ available_planets_setting,
226
+ celestial_points_names,
227
+ first_subject,
228
+ second_subject,
229
+ method=distribution_method,
230
+ custom_weights=custom_distribution_weights,
231
+ )
232
+ quality_totals = calculate_synastry_quality_points(
233
+ available_planets_setting,
234
+ celestial_points_names,
235
+ first_subject,
236
+ second_subject,
237
+ method=distribution_method,
238
+ custom_weights=custom_distribution_weights,
239
+ )
240
+ else:
241
+ # Fallback to single chart calculation for incompatible types
242
+ element_totals = calculate_element_points(
243
+ available_planets_setting,
244
+ celestial_points_names,
245
+ first_subject,
246
+ method=distribution_method,
247
+ custom_weights=custom_distribution_weights,
248
+ )
249
+ quality_totals = calculate_quality_points(
250
+ available_planets_setting,
251
+ celestial_points_names,
252
+ first_subject,
253
+ method=distribution_method,
254
+ custom_weights=custom_distribution_weights,
255
+ )
256
+ else:
257
+ # Calculate element/quality points for single chart
258
+ element_totals = calculate_element_points(
259
+ available_planets_setting,
260
+ celestial_points_names,
261
+ first_subject,
262
+ method=distribution_method,
263
+ custom_weights=custom_distribution_weights,
264
+ )
265
+ quality_totals = calculate_quality_points(
266
+ available_planets_setting,
267
+ celestial_points_names,
268
+ first_subject,
269
+ method=distribution_method,
270
+ custom_weights=custom_distribution_weights,
271
+ )
272
+
273
+ # Calculate percentages
274
+ total_elements = element_totals["fire"] + element_totals["water"] + element_totals["earth"] + element_totals["air"]
275
+ element_percentages = distribute_percentages_to_100(element_totals) if total_elements > 0 else {"fire": 0, "earth": 0, "air": 0, "water": 0}
276
+ element_distribution = ElementDistributionModel(
277
+ fire=element_totals["fire"],
278
+ earth=element_totals["earth"],
279
+ air=element_totals["air"],
280
+ water=element_totals["water"],
281
+ fire_percentage=element_percentages["fire"],
282
+ earth_percentage=element_percentages["earth"],
283
+ air_percentage=element_percentages["air"],
284
+ water_percentage=element_percentages["water"],
285
+ )
286
+
287
+ total_qualities = quality_totals["cardinal"] + quality_totals["fixed"] + quality_totals["mutable"]
288
+ quality_percentages = distribute_percentages_to_100(quality_totals) if total_qualities > 0 else {"cardinal": 0, "fixed": 0, "mutable": 0}
289
+ quality_distribution = QualityDistributionModel(
290
+ cardinal=quality_totals["cardinal"],
291
+ fixed=quality_totals["fixed"],
292
+ mutable=quality_totals["mutable"],
293
+ cardinal_percentage=quality_percentages["cardinal"],
294
+ fixed_percentage=quality_percentages["fixed"],
295
+ mutable_percentage=quality_percentages["mutable"],
296
+ )
297
+
298
+ # Create and return the appropriate chart data model
299
+ if chart_type in ["Natal", "Composite", "SingleReturnChart"]:
300
+ # Single chart data model - cast types since they're already validated
301
+ return SingleChartDataModel(
302
+ chart_type=cast(Literal["Natal", "Composite", "SingleReturnChart"], chart_type),
303
+ subject=first_subject,
304
+ aspects=cast(SingleChartAspectsModel, aspects_model).aspects,
305
+ element_distribution=element_distribution,
306
+ quality_distribution=quality_distribution,
307
+ active_points=effective_active_points,
308
+ active_aspects=active_aspects,
309
+ )
310
+ else:
311
+ # Dual chart data model - cast types since they're already validated
312
+ if second_subject is None:
313
+ raise KerykeionException(f"Second subject is required for {chart_type} charts.")
314
+ return DualChartDataModel(
315
+ chart_type=cast(Literal["Transit", "Synastry", "DualReturnChart"], chart_type),
316
+ first_subject=first_subject,
317
+ second_subject=second_subject,
318
+ aspects=cast(DualChartAspectsModel, aspects_model).aspects,
319
+ house_comparison=house_comparison,
320
+ relationship_score=relationship_score,
321
+ element_distribution=element_distribution,
322
+ quality_distribution=quality_distribution,
323
+ active_points=effective_active_points,
324
+ active_aspects=active_aspects,
325
+ )
326
+
327
+ @staticmethod
328
+ def create_natal_chart_data(
329
+ subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
330
+ active_points: Optional[List[AstrologicalPoint]] = None,
331
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
332
+ *,
333
+ distribution_method: ElementQualityDistributionMethod = "weighted",
334
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
335
+ ) -> ChartDataModel:
336
+ """
337
+ Convenience method for creating natal chart data.
338
+
339
+ Args:
340
+ subject: Astrological subject
341
+ active_points: Points to include in calculations
342
+ active_aspects: Aspect types and orbs to use
343
+ distribution_method: Strategy for element/modality weighting
344
+ custom_distribution_weights: Optional overrides for distribution weights
345
+
346
+ Returns:
347
+ ChartDataModel: Natal chart data
348
+ """
349
+ return ChartDataFactory.create_chart_data(
350
+ first_subject=subject,
351
+ chart_type="Natal",
352
+ active_points=active_points,
353
+ active_aspects=active_aspects,
354
+ distribution_method=distribution_method,
355
+ custom_distribution_weights=custom_distribution_weights,
356
+ )
357
+
358
+ @staticmethod
359
+ def create_synastry_chart_data(
360
+ first_subject: AstrologicalSubjectModel,
361
+ second_subject: AstrologicalSubjectModel,
362
+ active_points: Optional[List[AstrologicalPoint]] = None,
363
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
364
+ include_house_comparison: bool = True,
365
+ include_relationship_score: bool = True,
366
+ *,
367
+ distribution_method: ElementQualityDistributionMethod = "weighted",
368
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
369
+ ) -> ChartDataModel:
370
+ """
371
+ Convenience method for creating synastry chart data.
372
+
373
+ Args:
374
+ first_subject: First astrological subject
375
+ second_subject: Second astrological subject
376
+ active_points: Points to include in calculations
377
+ active_aspects: Aspect types and orbs to use
378
+ include_house_comparison: Whether to include house comparison
379
+ include_relationship_score: Whether to include relationship scoring
380
+ distribution_method: Strategy for element/modality weighting
381
+ custom_distribution_weights: Optional overrides for distribution weights
382
+
383
+ Returns:
384
+ ChartDataModel: Synastry chart data
385
+ """
386
+ return ChartDataFactory.create_chart_data(
387
+ first_subject=first_subject,
388
+ chart_type="Synastry",
389
+ second_subject=second_subject,
390
+ active_points=active_points,
391
+ active_aspects=active_aspects,
392
+ include_house_comparison=include_house_comparison,
393
+ include_relationship_score=include_relationship_score,
394
+ distribution_method=distribution_method,
395
+ custom_distribution_weights=custom_distribution_weights,
396
+ )
397
+
398
+ @staticmethod
399
+ def create_transit_chart_data(
400
+ natal_subject: AstrologicalSubjectModel,
401
+ transit_subject: AstrologicalSubjectModel,
402
+ active_points: Optional[List[AstrologicalPoint]] = None,
403
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
404
+ include_house_comparison: bool = True,
405
+ *,
406
+ distribution_method: ElementQualityDistributionMethod = "weighted",
407
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
408
+ ) -> ChartDataModel:
409
+ """
410
+ Convenience method for creating transit chart data.
411
+
412
+ Args:
413
+ natal_subject: Natal astrological subject
414
+ transit_subject: Transit astrological subject
415
+ active_points: Points to include in calculations
416
+ active_aspects: Aspect types and orbs to use
417
+ include_house_comparison: Whether to include house comparison
418
+ distribution_method: Strategy for element/modality weighting
419
+ custom_distribution_weights: Optional overrides for distribution weights
420
+
421
+ Returns:
422
+ ChartDataModel: Transit chart data
423
+ """
424
+ return ChartDataFactory.create_chart_data(
425
+ first_subject=natal_subject,
426
+ chart_type="Transit",
427
+ second_subject=transit_subject,
428
+ active_points=active_points,
429
+ active_aspects=active_aspects,
430
+ include_house_comparison=include_house_comparison,
431
+ distribution_method=distribution_method,
432
+ custom_distribution_weights=custom_distribution_weights,
433
+ )
434
+
435
+ @staticmethod
436
+ def create_composite_chart_data(
437
+ composite_subject: CompositeSubjectModel,
438
+ active_points: Optional[List[AstrologicalPoint]] = None,
439
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
440
+ *,
441
+ distribution_method: ElementQualityDistributionMethod = "weighted",
442
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
443
+ ) -> ChartDataModel:
444
+ """
445
+ Convenience method for creating composite chart data.
446
+
447
+ Args:
448
+ composite_subject: Composite astrological subject
449
+ active_points: Points to include in calculations
450
+ active_aspects: Aspect types and orbs to use
451
+ distribution_method: Strategy for element/modality weighting
452
+ custom_distribution_weights: Optional overrides for distribution weights
453
+
454
+ Returns:
455
+ ChartDataModel: Composite chart data
456
+ """
457
+ return ChartDataFactory.create_chart_data(
458
+ first_subject=composite_subject,
459
+ chart_type="Composite",
460
+ active_points=active_points,
461
+ active_aspects=active_aspects,
462
+ distribution_method=distribution_method,
463
+ custom_distribution_weights=custom_distribution_weights,
464
+ )
465
+
466
+ @staticmethod
467
+ def create_return_chart_data(
468
+ natal_subject: AstrologicalSubjectModel,
469
+ return_subject: PlanetReturnModel,
470
+ active_points: Optional[List[AstrologicalPoint]] = None,
471
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
472
+ include_house_comparison: bool = True,
473
+ *,
474
+ distribution_method: ElementQualityDistributionMethod = "weighted",
475
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
476
+ ) -> ChartDataModel:
477
+ """
478
+ Convenience method for creating planetary return chart data.
479
+
480
+ Args:
481
+ natal_subject: Natal astrological subject
482
+ return_subject: Planetary return subject
483
+ active_points: Points to include in calculations
484
+ active_aspects: Aspect types and orbs to use
485
+ include_house_comparison: Whether to include house comparison
486
+ distribution_method: Strategy for element/modality weighting
487
+ custom_distribution_weights: Optional overrides for distribution weights
488
+
489
+ Returns:
490
+ ChartDataModel: Return chart data
491
+ """
492
+ return ChartDataFactory.create_chart_data(
493
+ first_subject=natal_subject,
494
+ chart_type="DualReturnChart",
495
+ second_subject=return_subject,
496
+ active_points=active_points,
497
+ active_aspects=active_aspects,
498
+ include_house_comparison=include_house_comparison,
499
+ distribution_method=distribution_method,
500
+ custom_distribution_weights=custom_distribution_weights,
501
+ )
502
+
503
+ @staticmethod
504
+ def create_single_wheel_return_chart_data(
505
+ return_subject: PlanetReturnModel,
506
+ active_points: Optional[List[AstrologicalPoint]] = None,
507
+ active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
508
+ *,
509
+ distribution_method: ElementQualityDistributionMethod = "weighted",
510
+ custom_distribution_weights: Optional[Mapping[str, float]] = None,
511
+ ) -> ChartDataModel:
512
+ """
513
+ Convenience method for creating single wheel planetary return chart data.
514
+
515
+ Args:
516
+ return_subject: Planetary return subject
517
+ active_points: Points to include in calculations
518
+ active_aspects: Aspect types and orbs to use
519
+ distribution_method: Strategy for element/modality weighting
520
+ custom_distribution_weights: Optional overrides for distribution weights
521
+
522
+ Returns:
523
+ ChartDataModel: Single wheel return chart data
524
+ """
525
+ return ChartDataFactory.create_chart_data(
526
+ first_subject=return_subject,
527
+ chart_type="SingleReturnChart",
528
+ active_points=active_points,
529
+ active_aspects=active_aspects,
530
+ distribution_method=distribution_method,
531
+ custom_distribution_weights=custom_distribution_weights,
532
+ )
533
+
534
+
535
+ if __name__ == "__main__":
536
+ # Example usage
537
+ from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
538
+
539
+ # Create a natal chart data
540
+ subject = AstrologicalSubjectFactory.from_current_time(name="Test Subject")
541
+ natal_data = ChartDataFactory.create_natal_chart_data(subject)
542
+
543
+ print(f"Chart Type: {natal_data.chart_type}")
544
+ print(f"Active Points: {len(natal_data.active_points)}")
545
+ print(f"Aspects: {len(natal_data.aspects)}")
546
+ print(f"Fire: {natal_data.element_distribution.fire_percentage}%")
547
+ print(f"Earth: {natal_data.element_distribution.earth_percentage}%")
548
+ print(f"Air: {natal_data.element_distribution.air_percentage}%")
549
+ print(f"Water: {natal_data.element_distribution.water_percentage}%")
550
+
551
+ print("\n---\n")
552
+ print(natal_data.model_dump_json(indent=4))
@@ -1,5 +1,5 @@
1
1
  """
2
- This is part of Kerykeion (C) 2024 Giacomo Battaglia
2
+ This is part of Kerykeion (C) 2025 Giacomo Battaglia
3
3
 
4
4
  This modules contains the charts logic for the Kerykeion project.
5
- """
5
+ """