# How Hiring Agent Handles Negative Signals and Deductions: A Code-Level Guide

> Discover how Hiring Agent processes negative signals and deductions by storing them as positive values and subtracting from the total score. Get a code-level understanding.

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

---

**Hiring Agent applies negative signals by storing them as positive values in a Pydantic `Deductions` model, then subtracting the `total` from the aggregated score before displaying warnings in the CLI and exporting to CSV.**

The interviewstreet/hiring-agent repository provides an LLM-powered resume evaluation system that scores candidates across multiple categories. Understanding how Hiring Agent handles negative signals requires examining the data structures in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py), the aggregation logic in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py), and the export formatting in [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py).

## The Deductions Data Model in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py)

Deductions are formally defined as a Pydantic model that stores a **positive** floating-point number representing the penalty amount. According to the source code, the `total` field uses a `ge=0` constraint because the value is stored as a positive number but applied as a negative adjustment during scoring.

In [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py) (lines 236-242), the `Deductions` class is implemented as follows:

```python
class Deductions(BaseModel):
    total: float = Field(ge=0,
        description="Total deduction points (stored as positive, applied as negative)")
    reasons: str = Field(description="Reasons for deductions")

```

This design ensures type safety while keeping the mathematical operation explicit: the evaluator subtracts this positive value from the running total.

## How Deductions Are Applied in the Scoring Pipeline

The scoring logic in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) aggregates positive scores first, then applies penalties. After summing category scores and adding any bonus points, the evaluator checks for the presence of a `deductions` attribute and subtracts the `total` field.

In [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) (lines 57-64), the flow follows this strict sequence:

```python

# Add bonus points

if hasattr(evaluation, "bonus_points") and evaluation.bonus_points:
    total_score += evaluation.bonus_points.total

# Subtract deductions

if hasattr(evaluation, "deductions") and evaluation.deductions:
    total_score -= evaluation.deductions.total

```

**Key implementation detail**: The code uses defensive `hasattr` checks to ensure backward compatibility with evaluation objects that may not contain deduction data.

## Reporting Deductions in CLI Output and CSV Exports

When presenting results to users, the system surfaces deductions with visual warnings in the terminal and structured fields in CSV exports.

### Console Display

In [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) (lines 131-140), the `print_evaluation_results` function checks for non-zero deductions and renders them with a warning icon:

```python
if (hasattr(evaluation, "deductions") and evaluation.deductions
    and evaluation.deductions.total > 0):
    print(f"\n⚠️  DEDUCTIONS: -{evaluation.deductions.total}")
    if evaluation.deductions.reasons:
        print(f"   {evaluation.deductions.reasons}")

```

### CSV Export Serialization

For data pipeline integration, [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py) (lines 720-727) maps deductions to dedicated columns. If no deductions exist, the fields default to zero and empty strings:

```python
if evaluation and hasattr(evaluation, "deductions"):
    csv_row["deductions"] = evaluation.deductions.total
    csv_row["deduction_reasons"] = evaluation.deductions.reasons
else:
    csv_row["deductions"] = 0
    csv_row["deduction_reasons"] = ""

```

This guarantees consistent schema across all exported rows, regardless of whether the LLM identified negative signals for a specific candidate.

## Complete Working Example

The following example demonstrates how to construct an `EvaluationData` object containing deductions and render the results using the same functions called by the Hiring Agent CLI:

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

# Example evaluation data that includes a deduction

example = EvaluationData(
    scores=Scores(
        open_source=CategoryScore(score=30, max=35, evidence="Contributed to 5 repos"),
        self_projects=CategoryScore(score=25, max=30, evidence="Built 3 apps"),
        production=CategoryScore(score=20, max=25, evidence="2 years in SaaS"),
        technical_skills=CategoryScore(score=8, max=10, evidence="Python, Go"),
    ),
    bonus_points=BonusPoints(total=5, breakdown="Extra certifications"),
    deductions=Deductions(total=4, reasons="Missing senior‑level leadership experience"),
    key_strengths=["Strong open‑source contributions"],
    areas_for_improvement=["Leadership experience"],
)

# Print the formatted results (calls the function used by the CLI)

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

```

Running this snippet produces output including the penalty notification:

```

⚠️  DEDUCTIONS: -4
   Missing senior‑level leadership experience

```

## Summary

- **Data Structure**: Deductions are encapsulated in the `Deductions` Pydantic model in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py), storing a positive `total` and string `reasons`.
- **Score Calculation**: The evaluator in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) subtracts the deduction total after adding bonus points but before finalizing the score.
- **User Interface**: Negative signals render with a warning icon (⚠️) in CLI output, showing both the numeric penalty and explanatory text.
- **Data Export**: [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py) serializes deductions into `deductions` and `deduction_reasons` CSV columns, defaulting to zero and empty strings when absent.

## Frequently Asked Questions

### What triggers a deduction in Hiring Agent?

Deductions are generated by the LLM evaluator when it identifies negative signals in a resume, such as missing critical experience, employment gaps, or lack of required technical skills. The model returns a `Deductions` object with the penalty amount and justification text.

### Why are deduction values stored as positive numbers?

The `total` field in the `Deductions` model uses a positive value with a `ge=0` constraint to maintain clarity in the data layer. The scoring logic explicitly subtracts this value (`total_score -= evaluation.deductions.total`), making the mathematical operation transparent in the codebase rather than hiding it in the data model.

### How are deductions displayed when there are no negative signals?

When no deductions exist or the `total` is zero, the CLI output skips the warning section entirely. In CSV exports, the `deductions` column contains `0` and `deduction_reasons` contains an empty string, ensuring consistent column presence across all output rows.

### Can deductions be customized in the evaluation schema?

Yes. Since `Deductions` is a standard Pydantic model defined in [`models.py`](https://github.com/interviewstreet/hiring-agent/blob/main/models.py), you can extend it with additional fields (such as severity levels or category tags) and update the serialization logic in [`transform.py`](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py) and the display handlers in [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) to accommodate custom negative signal types.