mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
fix(rank): report malformed scoring results in apply instead of a traceback (#635)
* fix(rank): report malformed scoring results in apply instead of a traceback A result that is not an object, a non-string key or a non-object scores value raised AttributeError/TypeError and discarded the batch. They now become errors rows and valid results in the same file still apply. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * changelog: place the #635 entry under [Unreleased] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
This commit is contained in:
3 files changed
+40
-1
No files matched your search
@@ -77,6 +77,12 @@ per-file diff commands.
|
|||||||
`AttributeError` traceback on `.rstrip`, although the audit already reports other malformed state
|
`AttributeError` traceback on `.rstrip`, although the audit already reports other malformed state
|
||||||
with a one-line error and exit 1. It now names the offending key the same way.
|
with a one-line error and exit 1. It now names the offending key the same way.
|
||||||
|
|
||||||
|
- **`rank_state.py apply` reports a malformed scoring result instead of crashing** (#635, `tools/rank_state.py`,
|
||||||
|
`tests/test_rank_state.py`) - a result that is not an object, a `key` that is not a string, or a
|
||||||
|
`scores` value that is not an object raised an `AttributeError`/`TypeError` traceback and discarded the
|
||||||
|
whole batch, although `apply` already collects per-result problems in `errors`. Each now becomes an
|
||||||
|
`errors` row (exit 1) and the valid results in the same file are still written.
|
||||||
|
|
||||||
## [1.7.3] - 2026-10-19
|
## [1.7.3] - 2026-10-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -805,6 +805,34 @@ class Unreachable(RankStateCase):
|
|||||||
self.assertIn("unknown status 'unreachble'", out["errors"][0]["error"])
|
self.assertIn("unknown status 'unreachble'", out["errors"][0]["error"])
|
||||||
self.assertEqual(self.read_state()["a"]["status"], "new", "a rejected result never half-writes an entry")
|
self.assertEqual(self.read_state()["a"]["status"], "new", "a rejected result never half-writes an entry")
|
||||||
|
|
||||||
|
def test_malformed_results_are_errors_not_a_traceback(self):
|
||||||
|
self.write_state({"a": entry(), "b": entry(title="Other")})
|
||||||
|
out = self.run_tool(
|
||||||
|
"apply",
|
||||||
|
"--results",
|
||||||
|
self.results(
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
"a",
|
||||||
|
{"key": ["a"], "scores": {}},
|
||||||
|
{"key": "a", "scores": [90, 80]},
|
||||||
|
{"key": "b", "scores": {"technical": 80, "experience": 80, "behavioral": 80, "career": 80}},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
expect=1,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
[e["error"] for e in out["errors"]],
|
||||||
|
[
|
||||||
|
"result is not an object",
|
||||||
|
"result is not an object",
|
||||||
|
"no such key in seen_jobs.json",
|
||||||
|
"scores must be an object",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.assertEqual([r["key"] for r in out["ranked"]], ["b"], "valid results in the same batch still apply")
|
||||||
|
self.assertEqual(self.read_state()["a"]["status"], "new")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
+6
-1
@@ -273,6 +273,8 @@ def cmd_sweep(args) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def overall_score(scores: dict) -> int:
|
def overall_score(scores: dict) -> int:
|
||||||
|
if not isinstance(scores, dict):
|
||||||
|
raise ValueError("scores must be an object")
|
||||||
total = 0.0
|
total = 0.0
|
||||||
for dim, weight in WEIGHTS.items():
|
for dim, weight in WEIGHTS.items():
|
||||||
value = scores.get(dim)
|
value = scores.get(dim)
|
||||||
@@ -306,8 +308,11 @@ def cmd_apply(args) -> int:
|
|||||||
|
|
||||||
rows, expired, unreachable, gave_up, errors = [], [], [], [], []
|
rows, expired, unreachable, gave_up, errors = [], [], [], [], []
|
||||||
for result in results:
|
for result in results:
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
errors.append({"key": None, "error": "result is not an object"})
|
||||||
|
continue
|
||||||
key = result.get("key")
|
key = result.get("key")
|
||||||
entry = seen.get(key)
|
entry = seen.get(key) if isinstance(key, str) else None
|
||||||
if entry is None:
|
if entry is None:
|
||||||
errors.append({"key": key, "error": "no such key in seen_jobs.json"})
|
errors.append({"key": key, "error": "no such key in seen_jobs.json"})
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in new issue
Block a user