Skip to content

Scoring

Module computing scores.

Score dataclass

Class representing a score.

Source code in src/dbt_score/scoring.py
@dataclass
class Score:
    """Class representing a score."""

    value: float
    badge: str

    @property
    def rounded_value(self) -> float:
        """Auto-round score down to 1 decimal place."""
        return math.floor(self.value * 10) / 10

rounded_value: float property

Auto-round score down to 1 decimal place.

Scorer

Logic for computing scores.

Source code in src/dbt_score/scoring.py
class Scorer:
    """Logic for computing scores."""

    # This magic number comes from rule severity.
    # Assuming a rule violation:
    # - A low severity yields a score 2/3
    # - A medium severity yields a score 1/3
    # - A high severity yields a score 0/3
    score_cardinality = 3

    min_score = 0.0
    max_score = 10.0

    def __init__(self, config: Config) -> None:
        """Create a Scorer object."""
        self._config = config

    def score_evaluable(self, evaluable_results: EvaluableResultsType) -> Score:
        """Compute the score of a given evaluable."""
        rule_count = len(evaluable_results)

        if rule_count == 0:
            # No rule? No problem
            score = self.max_score
        elif any(
            rule.severity == Severity.CRITICAL and isinstance(result, RuleViolation)
            for rule, result in evaluable_results.items()
        ):
            # If there's a CRITICAL violation, the score is 0
            score = self.min_score
        else:
            # Otherwise, the score is the weighted average (by severity) of the results
            score = (
                sum(
                    [
                        # The more severe the violation, the more points are lost
                        self.score_cardinality - rule.severity.value
                        if isinstance(result, RuleViolation)  # Either 0/3, 1/3 or 2/3
                        else self.score_cardinality  # 3/3
                        for rule, result in evaluable_results.items()
                    ]
                )
                / (self.score_cardinality * rule_count)
                * self.max_score
            )

        return Score(score, self._badge(score))

    def score_aggregate_evaluables(self, scores: list[Score]) -> Score:
        """Compute the score of a list of evaluables."""
        actual_scores = [s.value for s in scores]
        if 0.0 in actual_scores:
            # Any evaluable with a CRITICAL violation makes the project score 0
            score = Score(self.min_score, self._badge(self.min_score))
        elif len(actual_scores) == 0:
            score = Score(self.max_score, self._badge(self.max_score))
        else:
            average_score = sum(actual_scores) / len(actual_scores)
            score = Score(average_score, self._badge(average_score))
        return score

    def _badge(self, score: float) -> str:
        """Compute the badge of a given score."""
        if score >= self._config.badge_config.first.threshold:
            return self._config.badge_config.first.icon
        elif score >= self._config.badge_config.second.threshold:
            return self._config.badge_config.second.icon
        elif score >= self._config.badge_config.third.threshold:
            return self._config.badge_config.third.icon
        else:
            return self._config.badge_config.wip.icon

__init__(config)

Create a Scorer object.

Source code in src/dbt_score/scoring.py
def __init__(self, config: Config) -> None:
    """Create a Scorer object."""
    self._config = config

score_aggregate_evaluables(scores)

Compute the score of a list of evaluables.

Source code in src/dbt_score/scoring.py
def score_aggregate_evaluables(self, scores: list[Score]) -> Score:
    """Compute the score of a list of evaluables."""
    actual_scores = [s.value for s in scores]
    if 0.0 in actual_scores:
        # Any evaluable with a CRITICAL violation makes the project score 0
        score = Score(self.min_score, self._badge(self.min_score))
    elif len(actual_scores) == 0:
        score = Score(self.max_score, self._badge(self.max_score))
    else:
        average_score = sum(actual_scores) / len(actual_scores)
        score = Score(average_score, self._badge(average_score))
    return score

score_evaluable(evaluable_results)

Compute the score of a given evaluable.

Source code in src/dbt_score/scoring.py
def score_evaluable(self, evaluable_results: EvaluableResultsType) -> Score:
    """Compute the score of a given evaluable."""
    rule_count = len(evaluable_results)

    if rule_count == 0:
        # No rule? No problem
        score = self.max_score
    elif any(
        rule.severity == Severity.CRITICAL and isinstance(result, RuleViolation)
        for rule, result in evaluable_results.items()
    ):
        # If there's a CRITICAL violation, the score is 0
        score = self.min_score
    else:
        # Otherwise, the score is the weighted average (by severity) of the results
        score = (
            sum(
                [
                    # The more severe the violation, the more points are lost
                    self.score_cardinality - rule.severity.value
                    if isinstance(result, RuleViolation)  # Either 0/3, 1/3 or 2/3
                    else self.score_cardinality  # 3/3
                    for rule, result in evaluable_results.items()
                ]
            )
            / (self.score_cardinality * rule_count)
            * self.max_score
        )

    return Score(score, self._badge(score))