kerykeion.relationship_score.relationship_score_factory

This is part of Kerykeion (C) 2025 Giacomo Battaglia

  1# -*- coding: utf-8 -*-
  2"""
  3    This is part of Kerykeion (C) 2025 Giacomo Battaglia
  4"""
  5
  6from kerykeion import AstrologicalSubject
  7from kerykeion.aspects.synastry_aspects import SynastryAspects
  8import logging
  9from pathlib import Path
 10from typing import Union
 11from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, RelationshipScoreAspectModel, RelationshipScoreModel
 12from kerykeion.kr_types.kr_literals import RelationshipScoreDescription
 13
 14
 15class RelationshipScoreFactory:
 16    """
 17    Calculates the relevance of the relationship between two subjects using the Ciro Discepolo method.
 18
 19    Results:
 20        - 0 to 5: Minimal relationship
 21        - 5 to 10: Medium relationship
 22        - 10 to 15: Important relationship
 23        - 15 to 20: Very important relationship
 24        - 20 to 35: Exceptional relationship
 25        - 30 and above: Rare Exceptional relationship
 26
 27    Documentation: http://www.cirodiscepolo.it/Articoli/Discepoloele.htm
 28
 29    Args:
 30        first_subject (Union[AstrologicalSubject, AstrologicalSubjectModel]): First subject instance
 31        second_subject (Union[AstrologicalSubject, AstrologicalSubjectModel]): Second subject instance
 32    """
 33
 34    SCORE_MAPPING = [
 35        ("Minimal", 5),
 36        ("Medium", 10),
 37        ("Important", 15),
 38        ("Very Important", 20),
 39        ("Exceptional", 30),
 40        ("Rare Exceptional", float("inf")),
 41    ]
 42
 43    MAJOR_ASPECTS = {"conjunction", "opposition", "square", "trine", "sextile"}
 44
 45    def __init__(
 46        self,
 47        first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 48        second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 49        use_only_major_aspects: bool = True,
 50    ):
 51        if isinstance(first_subject, AstrologicalSubject):
 52            self.first_subject = first_subject.model()
 53        if isinstance(second_subject, AstrologicalSubject):
 54            self.second_subject = second_subject.model()
 55
 56        self.use_only_major_aspects = use_only_major_aspects
 57
 58        self.score_value = 0
 59        self.relationship_score_description: RelationshipScoreDescription = "Minimal"
 60        self.is_destiny_sign = True
 61        self.relationship_score_aspects: list[RelationshipScoreAspectModel] = []
 62        self._synastry_aspects = SynastryAspects(self.first_subject, self.second_subject).all_aspects
 63
 64    def _evaluate_destiny_sign(self):
 65        """
 66        Evaluates if the subjects share the same sun sign quality and adds points if true.
 67        """
 68        if self.first_subject.sun["quality"] == self.second_subject.sun["quality"]:
 69            self.is_destiny_sign = True
 70            self.score_value += 5
 71            logging.debug(f"Destiny sign found, adding 5 points, total score: {self.score_value}")
 72
 73    def _evaluate_aspect(self, aspect, points):
 74        """
 75        Evaluates an aspect and adds points to the score.
 76
 77        Args:
 78            aspect (dict): Aspect information.
 79            points (int): Points to add.
 80        """
 81        if self.use_only_major_aspects and aspect["aspect"] not in self.MAJOR_ASPECTS:
 82            return
 83
 84        self.score_value += points
 85        self.relationship_score_aspects.append(
 86            RelationshipScoreAspectModel(
 87                p1_name=aspect["p1_name"],
 88                p2_name=aspect["p2_name"],
 89                aspect=aspect["aspect"],
 90                orbit=aspect["orbit"],
 91            )
 92        )
 93        logging.debug(f"{aspect['p1_name']}-{aspect['p2_name']} aspect: {aspect['aspect']} with orbit {aspect['orbit']} degrees, adding {points} points, total score: {self.score_value}, total aspects: {len(self.relationship_score_aspects)}")
 94
 95    def _evaluate_sun_sun_main_aspect(self, aspect):
 96        """
 97        Evaluates Sun-Sun main aspects and adds points accordingly:
 98        - 8 points for conjunction/opposition/square
 99        - 11 points if the aspect's orbit is <= 2 degrees
100
101        Args:
102            aspect (dict): Aspect information.
103        """
104        if aspect["p1_name"] == "Sun" and aspect["p2_name"] == "Sun" and aspect["aspect"] in {"conjunction", "opposition", "square"}:
105            points = 11 if aspect["orbit"] <= 2 else 8
106            self._evaluate_aspect(aspect, points)
107
108    def _evaluate_sun_moon_conjunction(self, aspect):
109        """
110        Evaluates Sun-Moon conjunctions and adds points accordingly:
111        - 8 points for conjunction
112        - 11 points if the aspect's orbit is <= 2 degrees
113
114        Args:
115            aspect (dict): Aspect information.
116        """
117        if {aspect["p1_name"], aspect["p2_name"]} == {"Moon", "Sun"} and aspect["aspect"] == "conjunction":
118            points = 11 if aspect["orbit"] <= 2 else 8
119            self._evaluate_aspect(aspect, points)
120
121    def _evaluate_sun_sun_other_aspects(self, aspect):
122        """
123        Evaluates Sun-Sun aspects that are not conjunctions and adds points accordingly:
124        - 4 points for other aspects
125
126        Args:
127            aspect (dict): Aspect information.
128        """
129        if aspect["p1_name"] == "Sun" and aspect["p2_name"] == "Sun" and aspect["aspect"] not in {"conjunction", "opposition", "square"}:
130            points = 4
131            self._evaluate_aspect(aspect, points)
132
133    def _evaluate_sun_moon_other_aspects(self, aspect):
134        """
135        Evaluates Sun-Moon aspects that are not conjunctions and adds points accordingly:
136        - 4 points for other aspects
137
138        Args:
139            aspect (dict): Aspect information.
140        """
141        if {aspect["p1_name"], aspect["p2_name"]} == {"Moon", "Sun"} and aspect["aspect"] != "conjunction":
142            points = 4
143            self._evaluate_aspect(aspect, points)
144
145    def _evaluate_sun_ascendant_aspect(self, aspect):
146        """
147        Evaluates Sun-Ascendant aspects and adds points accordingly:
148        - 4 points for any aspect
149
150        Args:
151            aspect (dict): Aspect information.
152        """
153        if {aspect["p1_name"], aspect["p2_name"]} == {"Sun", "Ascendant"}:
154            points = 4
155            self._evaluate_aspect(aspect, points)
156
157    def _evaluate_moon_ascendant_aspect(self, aspect):
158        """
159        Evaluates Moon-Ascendant aspects and adds points accordingly:
160        - 4 points for any aspect
161
162        Args:
163            aspect (dict): Aspect information.
164        """
165        if {aspect["p1_name"], aspect["p2_name"]} == {"Moon", "Ascendant"}:
166            points = 4
167            self._evaluate_aspect(aspect, points)
168
169    def _evaluate_venus_mars_aspect(self, aspect):
170        """
171        Evaluates Venus-Mars aspects and adds points accordingly:
172        - 4 points for any aspect
173
174        Args:
175            aspect (dict): Aspect information.
176        """
177        if {aspect["p1_name"], aspect["p2_name"]} == {"Venus", "Mars"}:
178            points = 4
179            self._evaluate_aspect(aspect, points)
180
181    def _evaluate_relationship_score_description(self):
182        """
183        Evaluates the relationship score description based on the total score.
184        """
185        for description, threshold in self.SCORE_MAPPING:
186            if self.score_value < threshold:
187                self.relationship_score_description = description
188                break
189
190    def get_relationship_score(self):
191        """
192        Calculates the relationship score based on synastry aspects.
193
194        Returns:
195            RelationshipScoreModel: The calculated relationship score.
196        """
197        self._evaluate_destiny_sign()
198
199        for aspect in self._synastry_aspects:
200            self._evaluate_sun_sun_main_aspect(aspect)
201            self._evaluate_sun_moon_conjunction(aspect)
202            self._evaluate_sun_moon_other_aspects(aspect)
203            self._evaluate_sun_sun_other_aspects(aspect)
204            self._evaluate_sun_ascendant_aspect(aspect)
205            self._evaluate_moon_ascendant_aspect(aspect)
206            self._evaluate_venus_mars_aspect(aspect)
207
208        self._evaluate_relationship_score_description()
209
210        return RelationshipScoreModel(
211            score_value=self.score_value,
212            score_description=self.relationship_score_description,
213            is_destiny_sign=self.is_destiny_sign,
214            aspects=self.relationship_score_aspects,
215            subjects=[self.first_subject, self.second_subject],
216        )
217
218
219if __name__ == "__main__":
220    from kerykeion.utilities import setup_logging
221
222    setup_logging(level="critical")
223
224    giacomo = AstrologicalSubject("Giacomo", 1993, 6, 10, 12, 15, "Montichiari", "IT")
225    yoko = AstrologicalSubject("Susie Cox", 1949, 6, 17, 9, 40, "Tucson", "US")
226
227    factory = RelationshipScoreFactory(giacomo, yoko)
228    score = factory.get_relationship_score()
229    print(score)
class RelationshipScoreFactory:
 16class RelationshipScoreFactory:
 17    """
 18    Calculates the relevance of the relationship between two subjects using the Ciro Discepolo method.
 19
 20    Results:
 21        - 0 to 5: Minimal relationship
 22        - 5 to 10: Medium relationship
 23        - 10 to 15: Important relationship
 24        - 15 to 20: Very important relationship
 25        - 20 to 35: Exceptional relationship
 26        - 30 and above: Rare Exceptional relationship
 27
 28    Documentation: http://www.cirodiscepolo.it/Articoli/Discepoloele.htm
 29
 30    Args:
 31        first_subject (Union[AstrologicalSubject, AstrologicalSubjectModel]): First subject instance
 32        second_subject (Union[AstrologicalSubject, AstrologicalSubjectModel]): Second subject instance
 33    """
 34
 35    SCORE_MAPPING = [
 36        ("Minimal", 5),
 37        ("Medium", 10),
 38        ("Important", 15),
 39        ("Very Important", 20),
 40        ("Exceptional", 30),
 41        ("Rare Exceptional", float("inf")),
 42    ]
 43
 44    MAJOR_ASPECTS = {"conjunction", "opposition", "square", "trine", "sextile"}
 45
 46    def __init__(
 47        self,
 48        first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 49        second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 50        use_only_major_aspects: bool = True,
 51    ):
 52        if isinstance(first_subject, AstrologicalSubject):
 53            self.first_subject = first_subject.model()
 54        if isinstance(second_subject, AstrologicalSubject):
 55            self.second_subject = second_subject.model()
 56
 57        self.use_only_major_aspects = use_only_major_aspects
 58
 59        self.score_value = 0
 60        self.relationship_score_description: RelationshipScoreDescription = "Minimal"
 61        self.is_destiny_sign = True
 62        self.relationship_score_aspects: list[RelationshipScoreAspectModel] = []
 63        self._synastry_aspects = SynastryAspects(self.first_subject, self.second_subject).all_aspects
 64
 65    def _evaluate_destiny_sign(self):
 66        """
 67        Evaluates if the subjects share the same sun sign quality and adds points if true.
 68        """
 69        if self.first_subject.sun["quality"] == self.second_subject.sun["quality"]:
 70            self.is_destiny_sign = True
 71            self.score_value += 5
 72            logging.debug(f"Destiny sign found, adding 5 points, total score: {self.score_value}")
 73
 74    def _evaluate_aspect(self, aspect, points):
 75        """
 76        Evaluates an aspect and adds points to the score.
 77
 78        Args:
 79            aspect (dict): Aspect information.
 80            points (int): Points to add.
 81        """
 82        if self.use_only_major_aspects and aspect["aspect"] not in self.MAJOR_ASPECTS:
 83            return
 84
 85        self.score_value += points
 86        self.relationship_score_aspects.append(
 87            RelationshipScoreAspectModel(
 88                p1_name=aspect["p1_name"],
 89                p2_name=aspect["p2_name"],
 90                aspect=aspect["aspect"],
 91                orbit=aspect["orbit"],
 92            )
 93        )
 94        logging.debug(f"{aspect['p1_name']}-{aspect['p2_name']} aspect: {aspect['aspect']} with orbit {aspect['orbit']} degrees, adding {points} points, total score: {self.score_value}, total aspects: {len(self.relationship_score_aspects)}")
 95
 96    def _evaluate_sun_sun_main_aspect(self, aspect):
 97        """
 98        Evaluates Sun-Sun main aspects and adds points accordingly:
 99        - 8 points for conjunction/opposition/square
100        - 11 points if the aspect's orbit is <= 2 degrees
101
102        Args:
103            aspect (dict): Aspect information.
104        """
105        if aspect["p1_name"] == "Sun" and aspect["p2_name"] == "Sun" and aspect["aspect"] in {"conjunction", "opposition", "square"}:
106            points = 11 if aspect["orbit"] <= 2 else 8
107            self._evaluate_aspect(aspect, points)
108
109    def _evaluate_sun_moon_conjunction(self, aspect):
110        """
111        Evaluates Sun-Moon conjunctions and adds points accordingly:
112        - 8 points for conjunction
113        - 11 points if the aspect's orbit is <= 2 degrees
114
115        Args:
116            aspect (dict): Aspect information.
117        """
118        if {aspect["p1_name"], aspect["p2_name"]} == {"Moon", "Sun"} and aspect["aspect"] == "conjunction":
119            points = 11 if aspect["orbit"] <= 2 else 8
120            self._evaluate_aspect(aspect, points)
121
122    def _evaluate_sun_sun_other_aspects(self, aspect):
123        """
124        Evaluates Sun-Sun aspects that are not conjunctions and adds points accordingly:
125        - 4 points for other aspects
126
127        Args:
128            aspect (dict): Aspect information.
129        """
130        if aspect["p1_name"] == "Sun" and aspect["p2_name"] == "Sun" and aspect["aspect"] not in {"conjunction", "opposition", "square"}:
131            points = 4
132            self._evaluate_aspect(aspect, points)
133
134    def _evaluate_sun_moon_other_aspects(self, aspect):
135        """
136        Evaluates Sun-Moon aspects that are not conjunctions and adds points accordingly:
137        - 4 points for other aspects
138
139        Args:
140            aspect (dict): Aspect information.
141        """
142        if {aspect["p1_name"], aspect["p2_name"]} == {"Moon", "Sun"} and aspect["aspect"] != "conjunction":
143            points = 4
144            self._evaluate_aspect(aspect, points)
145
146    def _evaluate_sun_ascendant_aspect(self, aspect):
147        """
148        Evaluates Sun-Ascendant aspects and adds points accordingly:
149        - 4 points for any aspect
150
151        Args:
152            aspect (dict): Aspect information.
153        """
154        if {aspect["p1_name"], aspect["p2_name"]} == {"Sun", "Ascendant"}:
155            points = 4
156            self._evaluate_aspect(aspect, points)
157
158    def _evaluate_moon_ascendant_aspect(self, aspect):
159        """
160        Evaluates Moon-Ascendant aspects and adds points accordingly:
161        - 4 points for any aspect
162
163        Args:
164            aspect (dict): Aspect information.
165        """
166        if {aspect["p1_name"], aspect["p2_name"]} == {"Moon", "Ascendant"}:
167            points = 4
168            self._evaluate_aspect(aspect, points)
169
170    def _evaluate_venus_mars_aspect(self, aspect):
171        """
172        Evaluates Venus-Mars aspects and adds points accordingly:
173        - 4 points for any aspect
174
175        Args:
176            aspect (dict): Aspect information.
177        """
178        if {aspect["p1_name"], aspect["p2_name"]} == {"Venus", "Mars"}:
179            points = 4
180            self._evaluate_aspect(aspect, points)
181
182    def _evaluate_relationship_score_description(self):
183        """
184        Evaluates the relationship score description based on the total score.
185        """
186        for description, threshold in self.SCORE_MAPPING:
187            if self.score_value < threshold:
188                self.relationship_score_description = description
189                break
190
191    def get_relationship_score(self):
192        """
193        Calculates the relationship score based on synastry aspects.
194
195        Returns:
196            RelationshipScoreModel: The calculated relationship score.
197        """
198        self._evaluate_destiny_sign()
199
200        for aspect in self._synastry_aspects:
201            self._evaluate_sun_sun_main_aspect(aspect)
202            self._evaluate_sun_moon_conjunction(aspect)
203            self._evaluate_sun_moon_other_aspects(aspect)
204            self._evaluate_sun_sun_other_aspects(aspect)
205            self._evaluate_sun_ascendant_aspect(aspect)
206            self._evaluate_moon_ascendant_aspect(aspect)
207            self._evaluate_venus_mars_aspect(aspect)
208
209        self._evaluate_relationship_score_description()
210
211        return RelationshipScoreModel(
212            score_value=self.score_value,
213            score_description=self.relationship_score_description,
214            is_destiny_sign=self.is_destiny_sign,
215            aspects=self.relationship_score_aspects,
216            subjects=[self.first_subject, self.second_subject],
217        )

Calculates the relevance of the relationship between two subjects using the Ciro Discepolo method.

Results: - 0 to 5: Minimal relationship - 5 to 10: Medium relationship - 10 to 15: Important relationship - 15 to 20: Very important relationship - 20 to 35: Exceptional relationship - 30 and above: Rare Exceptional relationship

Documentation: http://www.cirodiscepolo.it/Articoli/Discepoloele.htm

Args: first_subject (Union[AstrologicalSubject, AstrologicalSubjectModel]): First subject instance second_subject (Union[AstrologicalSubject, AstrologicalSubjectModel]): Second subject instance

46    def __init__(
47        self,
48        first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
49        second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
50        use_only_major_aspects: bool = True,
51    ):
52        if isinstance(first_subject, AstrologicalSubject):
53            self.first_subject = first_subject.model()
54        if isinstance(second_subject, AstrologicalSubject):
55            self.second_subject = second_subject.model()
56
57        self.use_only_major_aspects = use_only_major_aspects
58
59        self.score_value = 0
60        self.relationship_score_description: RelationshipScoreDescription = "Minimal"
61        self.is_destiny_sign = True
62        self.relationship_score_aspects: list[RelationshipScoreAspectModel] = []
63        self._synastry_aspects = SynastryAspects(self.first_subject, self.second_subject).all_aspects
SCORE_MAPPING = [('Minimal', 5), ('Medium', 10), ('Important', 15), ('Very Important', 20), ('Exceptional', 30), ('Rare Exceptional', inf)]
MAJOR_ASPECTS = {'square', 'trine', 'conjunction', 'sextile', 'opposition'}
use_only_major_aspects
score_value
relationship_score_description: Literal['Minimal', 'Medium', 'Important', 'Very Important', 'Exceptional', 'Rare Exceptional']
is_destiny_sign
def get_relationship_score(self):
191    def get_relationship_score(self):
192        """
193        Calculates the relationship score based on synastry aspects.
194
195        Returns:
196            RelationshipScoreModel: The calculated relationship score.
197        """
198        self._evaluate_destiny_sign()
199
200        for aspect in self._synastry_aspects:
201            self._evaluate_sun_sun_main_aspect(aspect)
202            self._evaluate_sun_moon_conjunction(aspect)
203            self._evaluate_sun_moon_other_aspects(aspect)
204            self._evaluate_sun_sun_other_aspects(aspect)
205            self._evaluate_sun_ascendant_aspect(aspect)
206            self._evaluate_moon_ascendant_aspect(aspect)
207            self._evaluate_venus_mars_aspect(aspect)
208
209        self._evaluate_relationship_score_description()
210
211        return RelationshipScoreModel(
212            score_value=self.score_value,
213            score_description=self.relationship_score_description,
214            is_destiny_sign=self.is_destiny_sign,
215            aspects=self.relationship_score_aspects,
216            subjects=[self.first_subject, self.second_subject],
217        )

Calculates the relationship score based on synastry aspects.

Returns: RelationshipScoreModel: The calculated relationship score.