Issue #432: Bug: role.json min_final_score / max_final_score are loaded but never applied to the final score

interviewstreet/hiring-agent · commit e0ece80 · investigation verified · 2026-10-11 · investigation cost $0.04

No open PR fixes this and merges clean

0 fix it but don't merge clean, 2 fix it partly. See the board below.

8/8
citations verified in code
192 → 18
open PRs shortlisted by cited lines
0 fix
0 merge clean · 2 partly
$0.04
investigation cost

What's wrong

The report is confirmed. `min_final_score` and `max_final_score` are parsed into `Role` in roles.py, but nothing outside roles.py reads them. `print_evaluation_results` in score.py computes the total as category scores + bonus − deductions.
Show full explanationIt caps the total only at `sum(category max) + role.bonus_max`, which is a value it recomputes on its own, and it has no lower bound. `Deductions.total` is only constrained by `ge=0`, so a large deduction can push the total below `min_final_score`. A role author who sets `max_final_score` to something other than `sum(categories) + bonus_max` sees no effect. The CSV path in transform.py is also unclamped: its `total_score` sums only the category scores and leaves out bonus and deductions. One detail differs from the report: the intern role.json in this checkout has `max_final_score` 130, not 120.
Model confidence: high.

How to reproduce

  1. Use the software_engineering_intern role, whose role.json sets min_final_score to -20.
  2. Produce or mock an evaluation whose category scores are low and whose deductions.total is large, for example 100. Deductions.total has no upper bound, only ge=0.
  3. Call print_evaluation_results(evaluation, role, ...) from score.py. The printed OVERALL SCORE is below -20 (for example -60). Nothing raises the total to min_final_score.
  4. Edit role.json to set max_final_score to a value lower than sum(category max) + bonus_max, for example 50, and use an evaluation that scores high. The printout is still capped at max_score + bonus_max, not at 50.
  5. Run `grep -rn 'min_final_score\|max_final_score' --include=*.py .`. Only roles.py matches. score.py and transform.py never reference these fields.

Evidence

Each citation was checked against the code at e0ece80.

roles.py2 citations
line 227
verified in code
min_final_score=int(manifest.get("min_final_score", 0)),

The field is parsed into Role here. The grep found no read of it anywhere else in the Python code.

line 228
verified in code
max_final_score=int(

max_final_score is parsed here. Its default is sum(category max) + bonus_max, and nothing else uses it.

score.py3 citations
line 84
verified in code
total_score -= evaluation.deductions.total

Deductions are subtracted with no lower bound or floor.

line 87
verified in code
max_possible_score = max_score + role.bonus_max

The cap is recomputed from category maxima plus bonus_max instead of using role.max_final_score.

line 88
verified in code
if total_score > max_possible_score:

Only an upper clamp exists. There is no comparison against role.min_final_score.

models.py1 citation
line 236
verified in code
ge=0,

Deductions.total has only a lower bound, so deductions can be arbitrarily large.

transform.py1 citation
line 690
verified in code
csv_row["total_score"] = total_score

The CSV total sums only the category scores and applies no bonus, deductions, or min/max clamp.

roles/software_engineering_intern/role.json1 citation
line 36
verified in code
"min_final_score": -20,

The intern role configures a floor that is never enforced.

Pull requests

Verdicts are judged from the diff, not run. Merge status is a real git check against e0ece80.

192open PRs
18change a cited line or mention the issue
2change the cause
0fix and merge clean
#292 and #443 all change the cause. Once one is merged, the others duplicate it.
Merges clean
Conflicts or unknown
Fixes
—
—
Partly
Partly Merges clean
#443 · fix: clamp final score to role min/max_final_score by rajarshidattapy
Why each verdict

It adds a lower-bound clamp and puts bonus and deductions into the CSV total, but it uses hardcoded MIN/MAX_FINAL_SCORE constants from evaluator instead of role.min_final_score and role.max_final_score, so role.json settings still have no effect.

Shortlisted because: changes score.py:84, changes score.py:87, changes score.py:88
Partly Merges clean

It clamps the print_evaluation_results total to [role.min_final_score, role.max_final_score], which removes the missing floor and the recomputed cap, but it doesn't touch transform.py, so the CSV total still sums only category scores with no bonus, deductions or clamp.

Shortlisted because: mentions the issue, changes score.py:84, changes score.py:87, changes score.py:88
16 PRs that don't fix itShow

It migrates GeminiProvider to the google-genai SDK and doesn't touch scoring.

Shortlisted because: changes models.py:236

It adds an optional job_match score and job description plumbing, and leaves the cap and floor logic unchanged.

Shortlisted because: changes transform.py:690

It adds PDF and resume validation before the LLM call and leaves the hardcoded 'max_score + 20' cap with no floor and no use of the role bounds.

Shortlisted because: changes score.py:84, changes score.py:87, changes score.py:88

It adds a scholarship_mentorship category and provider fixes, and leaves the final-score cap and floor logic unchanged.

Shortlisted because: changes score.py:84, changes score.py:87

It adds Gemini structured output and strips pydantic constraints from the models; it doesn't enforce min or max final score anywhere.

Shortlisted because: changes models.py:236

Its visible diff is the same models.py structured-output change (constraints removed); it adds no final-score clamping.

Shortlisted because: changes models.py:236

It adds an optional role_fit category and CSV columns, and leaves the final-score cap and floor logic unchanged.

Shortlisted because: changes score.py:84, changes score.py:87, changes transform.py:690

It adds Codeforces, LeetCode and token-usage plumbing and a problem_solving category, and leaves the final-score cap and floor logic unchanged.

Shortlisted because: changes score.py:84, changes score.py:87, changes transform.py:690

It clamps individual LLM fields (deductions, bonus, category score) before validation so the run doesn't crash, and it doesn't apply the role's final-score bounds to the total.

Shortlisted because: changes models.py:236

It adds per-experience-level category maxima in roles.py and removes the hardcoded category_maxes dict, and doesn't touch final-score clamping or the CSV total.

Shortlisted because: changes score.py:84, changes score.py:87, changes score.py:88

It adds a competitive_programming category and raises the hardcoded bonus cap comment to 140, and leaves the cap and floor logic as it was.

Shortlisted because: changes score.py:84, changes score.py:87, changes transform.py:690

It is a refactor into calculate_total_score that keeps the same 'max + 20' cap, adds no floor, and leaves the CSV path alone.

Shortlisted because: changes score.py:84, changes score.py:87, changes score.py:88

It moves the arithmetic into compute_totals but keeps the 'max_score + role.bonus_max' cap, and nothing in the diff reads role.min_final_score or role.max_final_score.

Shortlisted because: changes score.py:84, changes score.py:87, changes score.py:88, changes transform.py:690

It only switches the category cap and denominators to role.categories[*].max and never reads min_final_score or max_final_score, and it adds no lower bound or bonus/deduction handling to the CSV total.

Shortlisted because: mentions the issue, changes transform.py:690

It validates min/max_final_score types and ordering in load_role, but nothing in the scoring path starts reading those fields, so they remain unenforced.

Shortlisted because: changes roles.py:227, changes roles.py:228

It adds an optional evidence-trace feature with citations and reports, and leaves the total computation and clamping unchanged.

Shortlisted because: changes models.py:236

What this packet does not prove

  • Citations are checked against the code. The explanation that links them, the order of the chain, and any claim about outside systems (APIs, libraries) are the model's reasoning and are not checked.
  • PR verdicts are judged from the diff, not run. No PR was applied or tested.
  • PRs were shortlisted because they change a cited line or mention the issue. A fix in another file, or a PR whose line numbers have drifted, is missed, so this is not proof that no other PR fixes it.