# kerykeion.relationship_score.relationship_score_factory

This is part of Kerykeion (C) 2025 Giacomo Battaglia

View Source

      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: View Source

     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

RelationshipScoreFactory( first_subject: Union[kerykeion.astrological_subject.AstrologicalSubject, kerykeion.kr_types.kr_models.AstrologicalSubjectModel], second_subject: Union[kerykeion.astrological_subject.AstrologicalSubject, kerykeion.kr_types.kr_models.AstrologicalSubjectModel], use_only_major_aspects: bool = True) View Source

    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 = {'opposition', 'conjunction', 'sextile', 'square', 'trine'}
use_only_major_aspects
score_value
relationship_score_description: Literal['Minimal', 'Medium', 'Important', 'Very Important', 'Exceptional', 'Rare Exceptional']
is_destiny_sign
relationship_score_aspects: list[kerykeion.kr_types.kr_models.RelationshipScoreAspectModel]
def get_relationship_score(self): View Source

    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.