kerykeion 5.0.1__py3-none-any.whl → 5.1.8__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.
- kerykeion/aspects/aspects_factory.py +11 -12
- kerykeion/aspects/aspects_utils.py +2 -2
- kerykeion/astrological_subject_factory.py +18 -14
- kerykeion/backword.py +52 -5
- kerykeion/chart_data_factory.py +9 -6
- kerykeion/charts/chart_drawer.py +96 -36
- kerykeion/charts/charts_utils.py +98 -71
- kerykeion/ephemeris_data_factory.py +13 -4
- kerykeion/fetch_geonames.py +51 -13
- kerykeion/relationship_score_factory.py +1 -1
- kerykeion/report.py +7 -9
- kerykeion/schemas/kr_literals.py +1 -1
- kerykeion/schemas/kr_models.py +8 -10
- kerykeion/schemas/settings_models.py +8 -0
- kerykeion/settings/kerykeion_settings.py +1 -1
- kerykeion/settings/translation_strings.py +20 -0
- kerykeion/transits_time_range_factory.py +1 -1
- kerykeion/utilities.py +121 -121
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/METADATA +755 -138
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/RECORD +22 -22
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.1.dist-info → kerykeion-5.1.8.dist-info}/licenses/LICENSE +0 -0
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -1023,65 +1023,34 @@ def draw_transit_aspect_list(
|
|
|
1023
1023
|
|
|
1024
1024
|
|
|
1025
1025
|
def calculate_moon_phase_chart_params(
|
|
1026
|
-
degrees_between_sun_and_moon: float
|
|
1027
|
-
latitude: float
|
|
1026
|
+
degrees_between_sun_and_moon: float
|
|
1028
1027
|
) -> dict:
|
|
1029
1028
|
"""
|
|
1030
|
-
Calculate
|
|
1029
|
+
Calculate normalized parameters used by the moon phase icon.
|
|
1031
1030
|
|
|
1032
1031
|
Parameters:
|
|
1033
|
-
- degrees_between_sun_and_moon (float): The
|
|
1034
|
-
- latitude (float): The latitude for rotation calculation.
|
|
1032
|
+
- degrees_between_sun_and_moon (float): The elongation between the sun and moon.
|
|
1035
1033
|
|
|
1036
1034
|
Returns:
|
|
1037
|
-
- dict:
|
|
1038
|
-
"""
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
circle_radius = None
|
|
1044
|
-
|
|
1045
|
-
# Determine lunar phase properties based on the degree
|
|
1046
|
-
if deg < 90.0:
|
|
1047
|
-
max_radius = deg
|
|
1048
|
-
if deg > 80.0:
|
|
1049
|
-
max_radius = max_radius * max_radius
|
|
1050
|
-
circle_center_x = 20.0 + (deg / 90.0) * (max_radius + 10.0)
|
|
1051
|
-
circle_radius = 10.0 + (deg / 90.0) * max_radius
|
|
1052
|
-
|
|
1053
|
-
elif deg < 180.0:
|
|
1054
|
-
max_radius = 180.0 - deg
|
|
1055
|
-
if deg < 100.0:
|
|
1056
|
-
max_radius = max_radius * max_radius
|
|
1057
|
-
circle_center_x = 20.0 + ((deg - 90.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
|
|
1058
|
-
circle_radius = 10.0 + max_radius - ((deg - 90.0) / 90.0 * max_radius)
|
|
1059
|
-
|
|
1060
|
-
elif deg < 270.0:
|
|
1061
|
-
max_radius = deg - 180.0
|
|
1062
|
-
if deg > 260.0:
|
|
1063
|
-
max_radius = max_radius * max_radius
|
|
1064
|
-
circle_center_x = 20.0 + ((deg - 180.0) / 90.0 * (max_radius + 10.0))
|
|
1065
|
-
circle_radius = 10.0 + ((deg - 180.0) / 90.0 * max_radius)
|
|
1066
|
-
|
|
1067
|
-
elif deg < 361.0:
|
|
1068
|
-
max_radius = 360.0 - deg
|
|
1069
|
-
if deg < 280.0:
|
|
1070
|
-
max_radius = max_radius * max_radius
|
|
1071
|
-
circle_center_x = 20.0 + ((deg - 270.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
|
|
1072
|
-
circle_radius = 10.0 + max_radius - ((deg - 270.0) / 90.0 * max_radius)
|
|
1073
|
-
|
|
1074
|
-
else:
|
|
1075
|
-
raise KerykeionException(f"Invalid degree value: {deg}")
|
|
1035
|
+
- dict: Normalized phase data (angle, illuminated fraction, shadow ellipse radius).
|
|
1036
|
+
"""
|
|
1037
|
+
if not math.isfinite(degrees_between_sun_and_moon):
|
|
1038
|
+
raise KerykeionException(
|
|
1039
|
+
f"Invalid degree value: {degrees_between_sun_and_moon}"
|
|
1040
|
+
)
|
|
1076
1041
|
|
|
1042
|
+
phase_angle = degrees_between_sun_and_moon % 360.0
|
|
1043
|
+
radians = math.radians(phase_angle)
|
|
1044
|
+
cosine = math.cos(radians)
|
|
1045
|
+
illuminated_fraction = (1.0 - cosine) / 2.0
|
|
1077
1046
|
|
|
1078
|
-
#
|
|
1079
|
-
|
|
1047
|
+
# Guard against floating point spillover outside [0, 1].
|
|
1048
|
+
illuminated_fraction = max(0.0, min(1.0, illuminated_fraction))
|
|
1080
1049
|
|
|
1081
1050
|
return {
|
|
1082
|
-
"
|
|
1083
|
-
"
|
|
1084
|
-
"
|
|
1051
|
+
"phase_angle": phase_angle,
|
|
1052
|
+
"illuminated_fraction": illuminated_fraction,
|
|
1053
|
+
"shadow_ellipse_rx": 10.0 * cosine,
|
|
1085
1054
|
}
|
|
1086
1055
|
|
|
1087
1056
|
|
|
@@ -1697,34 +1666,92 @@ def makeLunarPhase(degrees_between_sun_and_moon: float, latitude: float) -> str:
|
|
|
1697
1666
|
|
|
1698
1667
|
Parameters:
|
|
1699
1668
|
- degrees_between_sun_and_moon (float): Angle between sun and moon in degrees
|
|
1700
|
-
- latitude (float): Observer's latitude for
|
|
1669
|
+
- latitude (float): Observer's latitude (no longer used, kept for backward compatibility)
|
|
1701
1670
|
|
|
1702
1671
|
Returns:
|
|
1703
1672
|
- str: SVG representation of lunar phase
|
|
1704
1673
|
"""
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1674
|
+
params = calculate_moon_phase_chart_params(degrees_between_sun_and_moon)
|
|
1675
|
+
|
|
1676
|
+
phase_angle = params["phase_angle"]
|
|
1677
|
+
illuminated_fraction = 1.0 - params["illuminated_fraction"]
|
|
1678
|
+
shadow_ellipse_rx = abs(params["shadow_ellipse_rx"])
|
|
1679
|
+
|
|
1680
|
+
radius = 10.0
|
|
1681
|
+
center_x = 20.0
|
|
1682
|
+
center_y = 10.0
|
|
1683
|
+
|
|
1684
|
+
bright_color = "var(--kerykeion-chart-color-lunar-phase-1)"
|
|
1685
|
+
shadow_color = "var(--kerykeion-chart-color-lunar-phase-0)"
|
|
1686
|
+
|
|
1687
|
+
is_waxing = phase_angle < 180.0
|
|
1688
|
+
|
|
1689
|
+
if illuminated_fraction <= 1e-6:
|
|
1690
|
+
base_fill = shadow_color
|
|
1691
|
+
overlay_path = ""
|
|
1692
|
+
overlay_fill = ""
|
|
1693
|
+
elif 1.0 - illuminated_fraction <= 1e-6:
|
|
1694
|
+
base_fill = bright_color
|
|
1695
|
+
overlay_path = ""
|
|
1696
|
+
overlay_fill = ""
|
|
1697
|
+
else:
|
|
1698
|
+
is_lit_major = illuminated_fraction >= 0.5
|
|
1699
|
+
if is_lit_major:
|
|
1700
|
+
base_fill = bright_color
|
|
1701
|
+
overlay_fill = shadow_color
|
|
1702
|
+
overlay_side = "left" if is_waxing else "right"
|
|
1703
|
+
else:
|
|
1704
|
+
base_fill = shadow_color
|
|
1705
|
+
overlay_fill = bright_color
|
|
1706
|
+
overlay_side = "right" if is_waxing else "left"
|
|
1707
|
+
|
|
1708
|
+
# The illuminated limb is the orthographic projection of the lunar terminator;
|
|
1709
|
+
# it appears as an ellipse with vertical radius equal to the lunar radius and
|
|
1710
|
+
# horizontal radius scaled by |cos(phase)|.
|
|
1711
|
+
def build_lune_path(side: str, ellipse_rx: float) -> str:
|
|
1712
|
+
ellipse_rx = max(0.0, min(radius, ellipse_rx))
|
|
1713
|
+
top_y = center_y - radius
|
|
1714
|
+
bottom_y = center_y + radius
|
|
1715
|
+
circle_sweep = 1 if side == "right" else 0
|
|
1716
|
+
|
|
1717
|
+
if ellipse_rx <= 1e-6:
|
|
1718
|
+
return (
|
|
1719
|
+
f"M {center_x:.4f} {top_y:.4f}"
|
|
1720
|
+
f" A {radius:.4f} {radius:.4f} 0 0 {circle_sweep} {center_x:.4f} {bottom_y:.4f}"
|
|
1721
|
+
f" L {center_x:.4f} {top_y:.4f}"
|
|
1722
|
+
" Z"
|
|
1723
|
+
)
|
|
1724
|
+
|
|
1725
|
+
return (
|
|
1726
|
+
f"M {center_x:.4f} {top_y:.4f}"
|
|
1727
|
+
f" A {radius:.4f} {radius:.4f} 0 0 {circle_sweep} {center_x:.4f} {bottom_y:.4f}"
|
|
1728
|
+
f" A {ellipse_rx:.4f} {radius:.4f} 0 0 {circle_sweep} {center_x:.4f} {top_y:.4f}"
|
|
1729
|
+
" Z"
|
|
1730
|
+
)
|
|
1731
|
+
|
|
1732
|
+
overlay_path = build_lune_path(overlay_side, shadow_ellipse_rx)
|
|
1733
|
+
|
|
1734
|
+
svg_lines = [
|
|
1735
|
+
'<g transform="rotate(0 20 10)">',
|
|
1736
|
+
' <defs>',
|
|
1737
|
+
' <clipPath id="moonPhaseCutOffCircle">',
|
|
1738
|
+
' <circle cx="20" cy="10" r="10" />',
|
|
1739
|
+
' </clipPath>',
|
|
1740
|
+
' </defs>',
|
|
1741
|
+
f' <circle cx="20" cy="10" r="10" style="fill: {base_fill}" />',
|
|
1742
|
+
]
|
|
1743
|
+
|
|
1744
|
+
if overlay_path:
|
|
1745
|
+
svg_lines.append(
|
|
1746
|
+
f' <path d="{overlay_path}" style="fill: {overlay_fill}" clip-path="url(#moonPhaseCutOffCircle)" />'
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1749
|
+
svg_lines.append(
|
|
1750
|
+
' <circle cx="20" cy="10" r="10" style="fill: none; stroke: var(--kerykeion-chart-color-lunar-phase-0); stroke-width: 0.5px; stroke-opacity: 0.5" />'
|
|
1725
1751
|
)
|
|
1752
|
+
svg_lines.append('</g>')
|
|
1726
1753
|
|
|
1727
|
-
return
|
|
1754
|
+
return "\n".join(svg_lines)
|
|
1728
1755
|
|
|
1729
1756
|
|
|
1730
1757
|
def calculate_quality_points(
|
|
@@ -48,10 +48,19 @@ License: AGPL-3.0
|
|
|
48
48
|
|
|
49
49
|
from kerykeion import AstrologicalSubjectFactory
|
|
50
50
|
from kerykeion.schemas.kr_models import AstrologicalSubjectModel
|
|
51
|
-
from kerykeion.utilities import
|
|
51
|
+
from kerykeion.utilities import (
|
|
52
|
+
get_houses_list,
|
|
53
|
+
get_available_astrological_points_list,
|
|
54
|
+
normalize_zodiac_type,
|
|
55
|
+
)
|
|
52
56
|
from kerykeion.astrological_subject_factory import DEFAULT_HOUSES_SYSTEM_IDENTIFIER, DEFAULT_PERSPECTIVE_TYPE, DEFAULT_ZODIAC_TYPE
|
|
53
|
-
from kerykeion.schemas import
|
|
54
|
-
|
|
57
|
+
from kerykeion.schemas import (
|
|
58
|
+
EphemerisDictModel,
|
|
59
|
+
SiderealMode,
|
|
60
|
+
HousesSystemIdentifier,
|
|
61
|
+
PerspectiveType,
|
|
62
|
+
ZodiacType,
|
|
63
|
+
)
|
|
55
64
|
from datetime import datetime, timedelta
|
|
56
65
|
from typing import Literal, Union, List
|
|
57
66
|
import logging
|
|
@@ -161,7 +170,7 @@ class EphemerisDataFactory:
|
|
|
161
170
|
self.lng = lng
|
|
162
171
|
self.tz_str = tz_str
|
|
163
172
|
self.is_dst = is_dst
|
|
164
|
-
self.zodiac_type = zodiac_type
|
|
173
|
+
self.zodiac_type = normalize_zodiac_type(zodiac_type)
|
|
165
174
|
self.sidereal_mode = sidereal_mode
|
|
166
175
|
self.houses_system_identifier = houses_system_identifier
|
|
167
176
|
self.perspective_type = perspective_type
|
kerykeion/fetch_geonames.py
CHANGED
|
@@ -6,11 +6,21 @@ License: AGPL-3.0
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
from logging import getLogger
|
|
10
10
|
from datetime import timedelta
|
|
11
|
+
from os import getenv
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, Union
|
|
14
|
+
|
|
11
15
|
from requests import Request
|
|
12
16
|
from requests_cache import CachedSession
|
|
13
|
-
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
DEFAULT_GEONAMES_CACHE_NAME = Path("cache") / "kerykeion_geonames_cache"
|
|
23
|
+
GEONAMES_CACHE_ENV_VAR = "KERYKEION_GEONAMES_CACHE_NAME"
|
|
14
24
|
|
|
15
25
|
|
|
16
26
|
class FetchGeonames:
|
|
@@ -25,17 +35,24 @@ class FetchGeonames:
|
|
|
25
35
|
country_code: Two-letter country code (ISO 3166-1 alpha-2).
|
|
26
36
|
username: GeoNames username for API access, defaults to "century.boy".
|
|
27
37
|
cache_expire_after_days: Number of days to cache responses, defaults to 30.
|
|
38
|
+
cache_name: Optional path (directory or filename stem) used by requests-cache.
|
|
39
|
+
Defaults to "cache/kerykeion_geonames_cache" and may also be overridden
|
|
40
|
+
via the environment variable ``KERYKEION_GEONAMES_CACHE_NAME`` or by
|
|
41
|
+
calling :meth:`FetchGeonames.set_default_cache_name`.
|
|
28
42
|
"""
|
|
29
43
|
|
|
44
|
+
default_cache_name: Path = DEFAULT_GEONAMES_CACHE_NAME
|
|
45
|
+
|
|
30
46
|
def __init__(
|
|
31
47
|
self,
|
|
32
48
|
city_name: str,
|
|
33
49
|
country_code: str,
|
|
34
50
|
username: str = "century.boy",
|
|
35
51
|
cache_expire_after_days=30,
|
|
52
|
+
cache_name: Optional[Union[str, Path]] = None,
|
|
36
53
|
):
|
|
37
54
|
self.session = CachedSession(
|
|
38
|
-
cache_name=
|
|
55
|
+
cache_name=str(self._resolve_cache_name(cache_name)),
|
|
39
56
|
backend="sqlite",
|
|
40
57
|
expire_after=timedelta(days=cache_expire_after_days),
|
|
41
58
|
)
|
|
@@ -46,6 +63,25 @@ class FetchGeonames:
|
|
|
46
63
|
self.base_url = "http://api.geonames.org/searchJSON"
|
|
47
64
|
self.timezone_url = "http://api.geonames.org/timezoneJSON"
|
|
48
65
|
|
|
66
|
+
@classmethod
|
|
67
|
+
def set_default_cache_name(cls, cache_name: Union[str, Path]) -> None:
|
|
68
|
+
"""Override the default cache name used when none is provided."""
|
|
69
|
+
|
|
70
|
+
cls.default_cache_name = Path(cache_name)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def _resolve_cache_name(cls, cache_name: Optional[Union[str, Path]]) -> Path:
|
|
74
|
+
"""Return the resolved cache name applying overrides in priority order."""
|
|
75
|
+
|
|
76
|
+
if cache_name is not None:
|
|
77
|
+
return Path(cache_name)
|
|
78
|
+
|
|
79
|
+
env_override = getenv(GEONAMES_CACHE_ENV_VAR)
|
|
80
|
+
if env_override:
|
|
81
|
+
return Path(env_override)
|
|
82
|
+
|
|
83
|
+
return cls.default_cache_name
|
|
84
|
+
|
|
49
85
|
def __get_timezone(self, lat: Union[str, float, int], lon: Union[str, float, int]) -> dict[str, str]:
|
|
50
86
|
"""
|
|
51
87
|
Get timezone information for a given latitude and longitude.
|
|
@@ -63,21 +99,21 @@ class FetchGeonames:
|
|
|
63
99
|
params = {"lat": lat, "lng": lon, "username": self.username}
|
|
64
100
|
|
|
65
101
|
prepared_request = Request("GET", self.timezone_url, params=params).prepare()
|
|
66
|
-
|
|
102
|
+
logger.debug("GeoNames timezone lookup url=%s", prepared_request.url)
|
|
67
103
|
|
|
68
104
|
try:
|
|
69
105
|
response = self.session.send(prepared_request)
|
|
70
106
|
response_json = response.json()
|
|
71
107
|
|
|
72
108
|
except Exception as e:
|
|
73
|
-
|
|
109
|
+
logger.error("GeoNames timezone request failed for %s: %s", self.timezone_url, e)
|
|
74
110
|
return {}
|
|
75
111
|
|
|
76
112
|
try:
|
|
77
113
|
timezone_data["timezonestr"] = response_json["timezoneId"]
|
|
78
114
|
|
|
79
115
|
except Exception as e:
|
|
80
|
-
|
|
116
|
+
logger.error("GeoNames timezone payload missing expected keys: %s", e)
|
|
81
117
|
return {}
|
|
82
118
|
|
|
83
119
|
if hasattr(response, "from_cache"):
|
|
@@ -109,15 +145,16 @@ class FetchGeonames:
|
|
|
109
145
|
}
|
|
110
146
|
|
|
111
147
|
prepared_request = Request("GET", self.base_url, params=params).prepare()
|
|
112
|
-
|
|
148
|
+
logger.debug("GeoNames search url=%s", prepared_request.url)
|
|
113
149
|
|
|
114
150
|
try:
|
|
115
151
|
response = self.session.send(prepared_request)
|
|
152
|
+
response.raise_for_status()
|
|
116
153
|
response_json = response.json()
|
|
117
|
-
|
|
154
|
+
logger.debug("GeoNames search response: %s", response_json)
|
|
118
155
|
|
|
119
156
|
except Exception as e:
|
|
120
|
-
|
|
157
|
+
logger.error("GeoNames search request failed for %s: %s", self.base_url, e)
|
|
121
158
|
return {}
|
|
122
159
|
|
|
123
160
|
try:
|
|
@@ -127,7 +164,7 @@ class FetchGeonames:
|
|
|
127
164
|
city_data_whitout_tz["countryCode"] = response_json["geonames"][0]["countryCode"]
|
|
128
165
|
|
|
129
166
|
except Exception as e:
|
|
130
|
-
|
|
167
|
+
logger.error("GeoNames search payload missing expected keys: %s", e)
|
|
131
168
|
return {}
|
|
132
169
|
|
|
133
170
|
if hasattr(response, "from_cache"):
|
|
@@ -147,15 +184,16 @@ class FetchGeonames:
|
|
|
147
184
|
timezone_response = self.__get_timezone(city_data_response["lat"], city_data_response["lng"])
|
|
148
185
|
|
|
149
186
|
except Exception as e:
|
|
150
|
-
|
|
187
|
+
logger.error("Unable to fetch timezone details: %s", e)
|
|
151
188
|
return {}
|
|
152
189
|
|
|
153
190
|
return {**timezone_response, **city_data_response}
|
|
154
191
|
|
|
155
192
|
|
|
156
193
|
if __name__ == "__main__":
|
|
157
|
-
|
|
158
|
-
setup_logging
|
|
194
|
+
"""Run a tiny demonstration when executing the module directly."""
|
|
195
|
+
from kerykeion.utilities import setup_logging as configure_logging
|
|
159
196
|
|
|
197
|
+
configure_logging("debug")
|
|
160
198
|
geonames = FetchGeonames("Montichiari", "IT")
|
|
161
199
|
print(geonames.get_serialized_data())
|
kerykeion/report.py
CHANGED
|
@@ -510,15 +510,14 @@ class ReportGenerator:
|
|
|
510
510
|
if not self._chart_data or not getattr(self._chart_data, "aspects", None):
|
|
511
511
|
return ""
|
|
512
512
|
|
|
513
|
-
|
|
514
|
-
relevant_aspects = list(getattr(aspects_model, "relevant_aspects", []))
|
|
513
|
+
aspects_list = list(self._chart_data.aspects)
|
|
515
514
|
|
|
516
|
-
if not
|
|
515
|
+
if not aspects_list:
|
|
517
516
|
return "No aspects data available."
|
|
518
517
|
|
|
519
|
-
total_aspects = len(
|
|
518
|
+
total_aspects = len(aspects_list)
|
|
520
519
|
if max_aspects is not None:
|
|
521
|
-
|
|
520
|
+
aspects_list = aspects_list[:max_aspects]
|
|
522
521
|
|
|
523
522
|
is_dual = isinstance(self._chart_data, DualChartDataModel)
|
|
524
523
|
if is_dual:
|
|
@@ -527,7 +526,7 @@ class ReportGenerator:
|
|
|
527
526
|
table_header = ["Point 1", "Aspect", "Point 2", "Orb", "Movement"]
|
|
528
527
|
|
|
529
528
|
aspects_table: List[List[str]] = [table_header]
|
|
530
|
-
for aspect in
|
|
529
|
+
for aspect in aspects_list:
|
|
531
530
|
aspect_name = str(aspect.aspect)
|
|
532
531
|
symbol = ASPECT_SYMBOLS.get(aspect_name.lower(), aspect_name)
|
|
533
532
|
movement_symbol = MOVEMENT_SYMBOLS.get(aspect.aspect_movement, "")
|
|
@@ -552,7 +551,7 @@ class ReportGenerator:
|
|
|
552
551
|
movement,
|
|
553
552
|
])
|
|
554
553
|
|
|
555
|
-
suffix = f" (showing {len(
|
|
554
|
+
suffix = f" (showing {len(aspects_list)} of {total_aspects})" if max_aspects is not None else ""
|
|
556
555
|
title = f"Aspects{suffix}"
|
|
557
556
|
return AsciiTable(aspects_table, title=title).table
|
|
558
557
|
|
|
@@ -728,8 +727,7 @@ if __name__ == "__main__":
|
|
|
728
727
|
natal_subject.iso_formatted_local_datetime,
|
|
729
728
|
"Solar",
|
|
730
729
|
)
|
|
731
|
-
|
|
732
|
-
# Composite chart subject
|
|
730
|
+
# Derive a composite subject representing the pair's midpoint configuration
|
|
733
731
|
composite_subject = CompositeSubjectFactory(
|
|
734
732
|
natal_subject,
|
|
735
733
|
partner_subject,
|
kerykeion/schemas/kr_literals.py
CHANGED
kerykeion/schemas/kr_models.py
CHANGED
|
@@ -353,12 +353,11 @@ class SingleChartAspectsModel(SubscriptableBaseModel):
|
|
|
353
353
|
- Composite charts
|
|
354
354
|
- Any other single chart type
|
|
355
355
|
|
|
356
|
-
Contains
|
|
357
|
-
|
|
356
|
+
Contains the filtered and relevant aspects for the astrological subject
|
|
357
|
+
based on configured orb settings.
|
|
358
358
|
"""
|
|
359
359
|
subject: Union["AstrologicalSubjectModel", "CompositeSubjectModel", "PlanetReturnModel"] = Field(description="The astrological subject for which aspects were calculated.")
|
|
360
|
-
|
|
361
|
-
relevant_aspects: List[AspectModel] = Field(description="Filtered list of relevant aspects based on orb settings.")
|
|
360
|
+
aspects: List[AspectModel] = Field(description="List of calculated aspects within the chart, filtered based on orb settings.")
|
|
362
361
|
active_points: List[AstrologicalPoint] = Field(description="List of active points used in the calculation.")
|
|
363
362
|
active_aspects: List["ActiveAspect"] = Field(description="List of active aspects with their orb settings.")
|
|
364
363
|
|
|
@@ -373,13 +372,12 @@ class DualChartAspectsModel(SubscriptableBaseModel):
|
|
|
373
372
|
- Composite vs natal comparisons
|
|
374
373
|
- Any other dual chart comparison
|
|
375
374
|
|
|
376
|
-
Contains
|
|
377
|
-
|
|
375
|
+
Contains the filtered and relevant aspects between the two charts
|
|
376
|
+
based on configured orb settings.
|
|
378
377
|
"""
|
|
379
378
|
first_subject: Union["AstrologicalSubjectModel", "CompositeSubjectModel", "PlanetReturnModel"] = Field(description="The first astrological subject.")
|
|
380
379
|
second_subject: Union["AstrologicalSubjectModel", "CompositeSubjectModel", "PlanetReturnModel"] = Field(description="The second astrological subject.")
|
|
381
|
-
|
|
382
|
-
relevant_aspects: List[AspectModel] = Field(description="Filtered list of relevant aspects based on orb settings.")
|
|
380
|
+
aspects: List[AspectModel] = Field(description="List of calculated aspects between the two charts, filtered based on orb settings.")
|
|
383
381
|
active_points: List[AstrologicalPoint] = Field(description="List of active points used in the calculation.")
|
|
384
382
|
active_aspects: List["ActiveAspect"] = Field(description="List of active aspects with their orb settings.")
|
|
385
383
|
|
|
@@ -538,7 +536,7 @@ class SingleChartDataModel(SubscriptableBaseModel):
|
|
|
538
536
|
subject: Union["AstrologicalSubjectModel", "CompositeSubjectModel", "PlanetReturnModel"]
|
|
539
537
|
|
|
540
538
|
# Internal aspects analysis
|
|
541
|
-
aspects:
|
|
539
|
+
aspects: List[AspectModel]
|
|
542
540
|
|
|
543
541
|
# Element and quality distributions
|
|
544
542
|
element_distribution: "ElementDistributionModel"
|
|
@@ -584,7 +582,7 @@ class DualChartDataModel(SubscriptableBaseModel):
|
|
|
584
582
|
second_subject: Union["AstrologicalSubjectModel", "PlanetReturnModel"]
|
|
585
583
|
|
|
586
584
|
# Inter-chart aspects analysis
|
|
587
|
-
aspects:
|
|
585
|
+
aspects: List[AspectModel]
|
|
588
586
|
|
|
589
587
|
# House comparison analysis
|
|
590
588
|
house_comparison: Optional["HouseComparisonModel"] = None
|
|
@@ -170,6 +170,14 @@ class KerykeionLanguageModel(SubscriptableBaseModel):
|
|
|
170
170
|
fixed: str = Field(title="Fixed", description="The fixed quality label in the chart, in the language")
|
|
171
171
|
mutable: str = Field(title="Mutable", description="The mutable quality label in the chart, in the language")
|
|
172
172
|
birth_chart: str = Field(title="Birth Chart", description="The birth chart label in the chart, in the language")
|
|
173
|
+
chart_info_natal_label: str = Field(
|
|
174
|
+
title="Chart Info Natal Label",
|
|
175
|
+
description="Short label for natal information panels",
|
|
176
|
+
)
|
|
177
|
+
chart_info_transit_label: str = Field(
|
|
178
|
+
title="Chart Info Transit Label",
|
|
179
|
+
description="Short label for transit information panels",
|
|
180
|
+
)
|
|
173
181
|
weekdays: Optional[dict[str, str]] = Field(default=None, title="Weekdays", description="Mapping of English weekday names to localized names")
|
|
174
182
|
|
|
175
183
|
# Settings Model
|
|
@@ -47,5 +47,5 @@ def _deep_merge(base: Mapping[str, Any], overrides: Mapping[str, Any]) -> dict[s
|
|
|
47
47
|
merged[key] = deepcopy(value)
|
|
48
48
|
return merged
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
# Keep the public surface area explicit for downstream imports.
|
|
51
51
|
__all__ = ["SettingsSource", "load_settings_mapping", "LANGUAGE_SETTINGS"]
|
|
@@ -108,6 +108,8 @@ LANGUAGE_SETTINGS = {
|
|
|
108
108
|
"fixed": "Fixed",
|
|
109
109
|
"mutable": "Mutable",
|
|
110
110
|
"birth_chart": "Birth Chart",
|
|
111
|
+
"chart_info_natal_label": "Natal",
|
|
112
|
+
"chart_info_transit_label": "Transit",
|
|
111
113
|
"celestial_points": {
|
|
112
114
|
"Sun": "Sun",
|
|
113
115
|
"Moon": "Moon",
|
|
@@ -255,6 +257,8 @@ LANGUAGE_SETTINGS = {
|
|
|
255
257
|
"fixed": "Fixe",
|
|
256
258
|
"mutable": "Mutable",
|
|
257
259
|
"birth_chart": "Thème Natal",
|
|
260
|
+
"chart_info_natal_label": "Natal",
|
|
261
|
+
"chart_info_transit_label": "Transit",
|
|
258
262
|
"celestial_points": {
|
|
259
263
|
"Sun": "Soleil",
|
|
260
264
|
"Moon": "Lune",
|
|
@@ -402,6 +406,8 @@ LANGUAGE_SETTINGS = {
|
|
|
402
406
|
"fixed": "Fixo",
|
|
403
407
|
"mutable": "Mutável",
|
|
404
408
|
"birth_chart": "Mapa Natal",
|
|
409
|
+
"chart_info_natal_label": "Natal",
|
|
410
|
+
"chart_info_transit_label": "Trânsito",
|
|
405
411
|
"celestial_points": {
|
|
406
412
|
"Sun": "Sol",
|
|
407
413
|
"Moon": "Lua",
|
|
@@ -549,6 +555,8 @@ LANGUAGE_SETTINGS = {
|
|
|
549
555
|
"fixed": "Fisso",
|
|
550
556
|
"mutable": "Mutevole",
|
|
551
557
|
"birth_chart": "Tema Natale",
|
|
558
|
+
"chart_info_natal_label": "Natale",
|
|
559
|
+
"chart_info_transit_label": "Transito",
|
|
552
560
|
"celestial_points": {
|
|
553
561
|
"Sun": "Sole",
|
|
554
562
|
"Moon": "Luna",
|
|
@@ -696,6 +704,8 @@ LANGUAGE_SETTINGS = {
|
|
|
696
704
|
"fixed": "固定",
|
|
697
705
|
"mutable": "变动",
|
|
698
706
|
"birth_chart": "出生图",
|
|
707
|
+
"chart_info_natal_label": "本命盤",
|
|
708
|
+
"chart_info_transit_label": "運行",
|
|
699
709
|
"celestial_points": {
|
|
700
710
|
"Sun": "太陽",
|
|
701
711
|
"Moon": "月亮",
|
|
@@ -843,6 +853,8 @@ LANGUAGE_SETTINGS = {
|
|
|
843
853
|
"fixed": "Fijo",
|
|
844
854
|
"mutable": "Mutable",
|
|
845
855
|
"birth_chart": "Carta Natal",
|
|
856
|
+
"chart_info_natal_label": "Natal",
|
|
857
|
+
"chart_info_transit_label": "Tránsito",
|
|
846
858
|
"celestial_points": {
|
|
847
859
|
"Sun": "Sol",
|
|
848
860
|
"Moon": "Luna",
|
|
@@ -990,6 +1002,8 @@ LANGUAGE_SETTINGS = {
|
|
|
990
1002
|
"fixed": "Фиксированный",
|
|
991
1003
|
"mutable": "Мутабельный",
|
|
992
1004
|
"birth_chart": "Натальная Карта",
|
|
1005
|
+
"chart_info_natal_label": "Натальная",
|
|
1006
|
+
"chart_info_transit_label": "Транзит",
|
|
993
1007
|
"celestial_points": {
|
|
994
1008
|
"Sun": "Солнце",
|
|
995
1009
|
"Moon": "Луна",
|
|
@@ -1137,6 +1151,8 @@ LANGUAGE_SETTINGS = {
|
|
|
1137
1151
|
"fixed": "Sabit",
|
|
1138
1152
|
"mutable": "Değişken",
|
|
1139
1153
|
"birth_chart": "Doğum Haritası",
|
|
1154
|
+
"chart_info_natal_label": "Doğum",
|
|
1155
|
+
"chart_info_transit_label": "Geçiş",
|
|
1140
1156
|
"celestial_points": {
|
|
1141
1157
|
"Sun": "Güneş",
|
|
1142
1158
|
"Moon": "Ay",
|
|
@@ -1284,6 +1300,8 @@ LANGUAGE_SETTINGS = {
|
|
|
1284
1300
|
"fixed": "Fix",
|
|
1285
1301
|
"mutable": "Veränderlich",
|
|
1286
1302
|
"birth_chart": "Geburtshoroskop",
|
|
1303
|
+
"chart_info_natal_label": "Radix",
|
|
1304
|
+
"chart_info_transit_label": "Transit",
|
|
1287
1305
|
"celestial_points": {
|
|
1288
1306
|
"Sun": "Sonne",
|
|
1289
1307
|
"Moon": "Mond",
|
|
@@ -1431,6 +1449,8 @@ LANGUAGE_SETTINGS = {
|
|
|
1431
1449
|
"fixed": "स्थिर",
|
|
1432
1450
|
"mutable": "द्विस्वभाव",
|
|
1433
1451
|
"birth_chart": "जन्म चार्ट",
|
|
1452
|
+
"chart_info_natal_label": "जन्म",
|
|
1453
|
+
"chart_info_transit_label": "गोचर",
|
|
1434
1454
|
"celestial_points": {
|
|
1435
1455
|
"Sun": "सूर्य",
|
|
1436
1456
|
"Moon": "चंद्रमा",
|
|
@@ -247,7 +247,7 @@ class TransitsTimeRangeFactory:
|
|
|
247
247
|
active_points=self.active_points,
|
|
248
248
|
active_aspects=self.active_aspects,
|
|
249
249
|
axis_orb_limit=self.axis_orb_limit,
|
|
250
|
-
).
|
|
250
|
+
).aspects
|
|
251
251
|
|
|
252
252
|
# Create a transit moment for this point in time
|
|
253
253
|
transit_moments.append(
|