# How Bonus Points and Deductions Are Calculated in the Resume Scoring System

> Understand how bonus points and deductions are calculated in the resume scoring system. Learn how scores are adjusted to reflect candidate strengths and weaknesses.

- Repository: [HackerRank/hiring-agent](https://github.com/interviewstreet/hiring-agent)
- Tags: internals
- Published: 2026-07-19

---

**Bonus points are added and deductions subtracted from the aggregated core category scores, with the final total capped at the maximum category score plus a 20-point bonus ceiling.**

The interviewstreet/hiring-agent repository implements a structured resume scoring system that evaluates candidates across four core categories before applying bonus points and deductions to reach a final score. According to the source code in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) and [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py), the calculation follows a strict order of operations where adjustments are applied after initial aggregation and bounded by configurable limits.

## Order of Operations for Score Calculation

In [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) (lines 57–68), the scoring logic processes the evaluation data in three distinct steps after the initial resume parsing:

1. **Aggregate core scores** – Sum the scores from the four primary categories (Open Source, Self Projects, Production, Technical Skills).
2. **Apply adjustments** – Add the value of `evaluation.bonus_points.total` and subtract `evaluation.deductions.total`.
3. **Enforce ceiling** – Compare the result against `max_possible_score`, defined as the sum of category maximums plus 20 (the bonus ceiling). If the total exceeds this value, it is truncated and a warning is printed.

This sequence ensures that deductions are always calculated after bonus points are applied, and neither adjustment can push the score beyond the predefined theoretical maximum.

## Data Models and Validation Constraints

The Pydantic models governing these adjustments are defined in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py) (lines 31–42):

- **`BonusPoints`** – Contains a `total` field constrained to the range **0–20** and a `breakdown` string describing the justification (e.g., “Hackathon winner”).
- **`Deductions`** – Contains a `total` non-negative integer or float and a `reasons` string. The sign is applied programmatically during calculation rather than stored in the model.

These constraints prevent configuration errors that could award excessive bonus points or store invalid negative deduction values.

## Reporting and Export Functionality

The `print_evaluation_results` function in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) (lines 71–84) displays these adjustments in formatted output blocks labeled “BONUS POINTS” and “DEDUCTIONS,” mirroring the internal calculation logic for auditing purposes.

For downstream analysis, `transform_evaluation_response` in [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py) (lines 15–22) serializes the data into four distinct CSV columns:

- `bonus_points` – The numeric total.
- `bonus_breakdown` – The human-readable description.
- `deductions` – The numeric total.
- `deduction_reasons` – The explanation for the penalty.

## Practical Example: Creating a Scored Evaluation

The following example demonstrates how to construct an `EvaluationData` object with both bonus points and deductions, then calculate the final score:

```python
from models import (
    CategoryScore,
    Scores,
    BonusPoints,
    Deductions,
    EvaluationData,
)

# Core category scores

scores = Scores(
    open_source=CategoryScore(score=30, max=35, evidence="..."),
    self_projects=CategoryScore(score=25, max=30, evidence="..."),
    production=CategoryScore(score=20, max=25, evidence="..."),
    technical_skills=CategoryScore(score=8, max=10, evidence="..."),
)

# Bonus points earned (e.g., for a hackathon win)

bonus = BonusPoints(total=12.5, breakdown="Hackathon winner (+12.5)")

# Deductions (e.g., missing contact info)

deductions = Deductions(total=3, reasons="Missing phone number")

# Full evaluation object

evaluation = EvaluationData(
    scores=scores,
    bonus_points=bonus,
    deductions=deductions,
    key_strengths=["Strong open‑source contributions"],
    areas_for_improvement=["Add a professional summary"],
)

# Calculate and display final results

from score import print_evaluation_results
print_evaluation_results(evaluation, candidate_name="Alice Example")

```

Running this script produces formatted output showing the adjustments:

```

⭐ BONUS POINTS: 12.5
   Hackathon winner (+12.5)

⚠️  DEDUCTIONS: -3
   Missing phone number

```

To export these values for spreadsheet analysis:

```python
from transform import transform_evaluation_response

row = transform_evaluation_response(
    file_name="alice_resume.pdf",
    resume_data=None,
    github_data=None,
    evaluation=evaluation,
)

print(row["bonus_points"], row["bonus_breakdown"])

# → 12.5 Hackathon winner (+12.5)

```

## Summary

- **Bonus points** (0–20 max) are added to the sum of core category scores.
- **Deductions** are subtracted after bonus points are applied.
- The **final total** is capped at `max_score + 20` to prevent overflow.
- Adjustments are persisted in CSV exports via [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py) using dedicated columns for totals and descriptions.
- All logic is centralized in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) for calculation and [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py) for validation.

## Frequently Asked Questions

### What is the maximum number of bonus points allowed?

The `BonusPoints.total` field is constrained to a maximum of **20 points** by the Pydantic model definition in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py). This limit is strictly enforced at the data validation layer before the score calculation begins.

### Where is the final score capping logic implemented?

The capping logic resides in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) (lines 57–68), where the code calculates `max_possible_score` as the sum of all category maximums plus the 20-point bonus ceiling. If the aggregated total exceeds this value, the score is truncated to the ceiling value.

### How are bonus and deduction details stored in CSV exports?

The `transform_evaluation_response` function in [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py) (lines 15–22) maps the data to four columns: `bonus_points` (numeric), `bonus_breakdown` (description), `deductions` (numeric), and `deduction_reasons` (description). This structure separates the quantitative values from the qualitative justifications for analytics purposes.

### Can deductions reduce the final score below zero?

The source code analysis explicitly describes an **upper bound** cap (`max_possible_score`) but does not specify a lower bound floor at zero. The calculation subtracts the deduction total from the sum of core scores and bonus points, though the `Deductions` model requires a non-negative `total` value with the negative sign applied mathematically during the final aggregation step.