Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 11 additions & 26 deletions score.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
convert_blog_data_to_text,
)
from config import DEVELOPMENT_MODE
from scoring import calculate_total_score

logger = logging.getLogger(__name__)

Expand All @@ -56,34 +57,18 @@ def print_evaluation_results(
print("❌ No evaluation data available")
return

# Calculate overall score
total_score = 0
max_score = 0

if hasattr(evaluation, "scores") and evaluation.scores:
for category_name, category_data in evaluation.scores.model_dump().items():
category_score = min(category_data["score"], category_data["max"])
total_score += category_score
max_score += category_data["max"]

# Log warning if score was capped
if category_score < category_data["score"]:
print(
f"⚠️ Warning: {category_name} score capped from {category_data['score']} to {category_score} (max: {category_data['max']})"
)

# Add bonus points
if hasattr(evaluation, "bonus_points") and evaluation.bonus_points:
total_score += evaluation.bonus_points.total
total_score, max_score, capped_scores, capped_at_maximum = calculate_total_score(
evaluation
)

# Subtract deductions
if hasattr(evaluation, "deductions") and evaluation.deductions:
total_score -= evaluation.deductions.total
for category_name, category_data in evaluation.scores.model_dump().items():
category_score = capped_scores[category_name]
if category_score < category_data["score"]:
print(
f"⚠️ Warning: {category_name} score capped from {category_data['score']} to {category_score} (max: {category_data['max']})"
)

# Ensure total score doesn't exceed maximum possible score
max_possible_score = max_score + 20 # 120 (100 categories + 20 bonus)
if total_score > max_possible_score:
total_score = max_possible_score
if capped_at_maximum:
print(f"⚠️ Warning: Total score capped at maximum possible value")

# Overall Score
Expand Down
38 changes: 38 additions & 0 deletions scoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Deterministic score arithmetic used by the resume evaluation CLI."""

from typing import Dict, Tuple

from models import EvaluationData

MAX_BONUS_POINTS = 20


def calculate_total_score(
evaluation: EvaluationData,
) -> Tuple[float, int, Dict[str, float], bool]:
"""Return total score, category maximum, capped category scores, and cap status.

LLM output is validated by Pydantic, but category scores can still be above
their declared maximum. Keeping the cap arithmetic in one pure helper makes
the CLI behavior deterministic and easy to regression-test.
"""

capped_scores: Dict[str, float] = {}
max_score = 0
total_score = 0.0

for category_name, category_data in evaluation.scores.model_dump().items():
capped_score = min(category_data["score"], category_data["max"])
capped_scores[category_name] = capped_score
total_score += capped_score
max_score += category_data["max"]

total_score += evaluation.bonus_points.total
total_score -= evaluation.deductions.total

max_possible_score = max_score + MAX_BONUS_POINTS
capped_at_maximum = total_score > max_possible_score
if capped_at_maximum:
total_score = float(max_possible_score)

return total_score, max_score, capped_scores, capped_at_maximum
60 changes: 60 additions & 0 deletions test_scoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Regression tests for deterministic score arithmetic.

Run with: python -m unittest test_scoring.py
"""

import unittest

from models import EvaluationData
from scoring import calculate_total_score


def evaluation(**overrides):
payload = {
"scores": {
"open_source": {"score": 10, "max": 35, "evidence": "evidence"},
"self_projects": {"score": 20, "max": 30, "evidence": "evidence"},
"production": {"score": 15, "max": 25, "evidence": "evidence"},
"technical_skills": {"score": 8, "max": 10, "evidence": "evidence"},
},
"bonus_points": {"total": 5, "breakdown": "bonus"},
"deductions": {"total": 2, "reasons": "deduction"},
"key_strengths": ["strength"],
"areas_for_improvement": ["improvement"],
}
for key, value in overrides.items():
payload[key] = value
return EvaluationData(**payload)


class ScoreArithmeticTests(unittest.TestCase):
def test_adds_categories_bonus_and_deductions(self):
total, maximum, capped, was_capped = calculate_total_score(evaluation())
self.assertEqual(total, 56)
self.assertEqual(maximum, 100)
self.assertEqual(capped["open_source"], 10)
self.assertFalse(was_capped)

def test_caps_category_scores_before_total(self):
item = evaluation()
item.scores.open_source.score = 99
total, _, capped, _ = calculate_total_score(item)
self.assertEqual(capped["open_source"], 35)
self.assertEqual(total, 81)

def test_caps_total_at_category_maximum_plus_bonus(self):
item = evaluation()
item.scores.open_source.score = 35
item.scores.self_projects.score = 30
item.scores.production.score = 25
item.scores.technical_skills.score = 10
item.bonus_points.total = 50
item.deductions.total = 0
total, maximum, _, was_capped = calculate_total_score(item)
self.assertEqual(maximum, 100)
self.assertEqual(total, 120)
self.assertTrue(was_capped)


if __name__ == "__main__":
unittest.main()