balloonmann commited on
Commit
60e34d7
·
1 Parent(s): f76bbd6

Fix step reward flow, wire investigation API, and refresh README scores

Browse files
README.md CHANGED
@@ -237,10 +237,10 @@ python inference.py --env-url http://localhost:8000
237
 
238
  | Task | Difficulty | F1 Score | Precision | Recall |
239
  |------|-----------|----------|-----------|--------|
240
- | Expense Audit | Easy | 0.4285 | 0.31 | 0.68 |
241
- | Invoice Match | Medium | 0.2667 | 0.17 | 0.67 |
242
- | GST Reconciliation | Hard | 0.1212 | 0.07 | 0.33 |
243
- | Fraud Detection | Expert | 0.0384 | 0.02 | 0.14 |
244
 
245
  **Why did it fail?**
246
  The model frequently drops to 0.00 on the tasks because it struggles with abstract rules (like date math for weekend expenses, or tracking cumulative limits). It actively gets tricked by "red herrings"—perfectly legal expenses that it hallucinates as errors—which entirely destroys its precision score.
 
237
 
238
  | Task | Difficulty | F1 Score | Precision | Recall |
239
  |------|-----------|----------|-----------|--------|
240
+ | Expense Audit | Easy | 0.1200 | 0.07 | 0.43 |
241
+ | Invoice Match | Medium | 0.1800 | 0.11 | 0.44 |
242
+ | GST Reconciliation | Hard | 0.0100 | 0.01 | 0.01 |
243
+ | Fraud Detection | Expert | 0.1100 | 0.11 | 0.10 |
244
 
245
  **Why did it fail?**
246
  The model frequently drops to 0.00 on the tasks because it struggles with abstract rules (like date math for weekend expenses, or tracking cumulative limits). It actively gets tricked by "red herrings"—perfectly legal expenses that it hallucinates as errors—which entirely destroys its precision score.
financial_audit_env/server/app.py CHANGED
@@ -128,6 +128,7 @@ class StepRequest(BaseModel):
128
  """Request body for the /step endpoint."""
129
  action: AuditAction
130
  session_id: Optional[str] = None
 
131
 
132
 
133
  class BaselineResponse(BaseModel):
@@ -240,7 +241,10 @@ async def step_endpoint(request: StepRequest):
240
  """
241
  try:
242
  env = _get_env(request.session_id)
243
- obs = env.step(request.action)
 
 
 
244
  _metrics["total_steps"] += 1
245
  if obs.done:
246
  _metrics["total_episodes_completed"] += 1
@@ -304,24 +308,18 @@ async def get_grader_score(session_id: Optional[str] = None):
304
  "message": "No episode completed. Call /reset then /step with submit_final=True.",
305
  }
306
 
307
- def final_clamp(val: float) -> float:
308
- """Keep score-like fields within a stable open interval."""
309
- return max(0.01, min(0.99, val))
310
-
311
  return {
312
  "status": "completed",
313
  "task_id": env.state.task_id,
314
- # Primary score fields with a final stability clamp.
315
- "score": final_clamp(result["score"]),
316
- "precision": final_clamp(result["precision"]),
317
- "recall": final_clamp(result["recall"]),
318
  "true_positives": result["true_positives"],
319
  "false_positives": result["false_positives"],
320
  "false_negatives": result["false_negatives"],
321
  "total_errors": result["total_errors"],
322
- # Enhanced scoring fields with the same stability clamp.
323
- "weighted_score": final_clamp(result.get("weighted_score", result["score"])),
324
- "partial_credit_score": final_clamp(result.get("partial_credit_score", result["score"])),
325
  "partial_matches": result.get("partial_matches", 0),
326
  # Confusion matrix
327
  "confusion_matrix": result.get("confusion_matrix", {}),
 
128
  """Request body for the /step endpoint."""
129
  action: AuditAction
130
  session_id: Optional[str] = None
131
+ request_categories: Optional[List[str]] = None
132
 
133
 
134
  class BaselineResponse(BaseModel):
 
241
  """
242
  try:
243
  env = _get_env(request.session_id)
244
+ obs = env.step(
245
+ request.action,
246
+ request_categories=request.request_categories or [],
247
+ )
248
  _metrics["total_steps"] += 1
249
  if obs.done:
250
  _metrics["total_episodes_completed"] += 1
 
308
  "message": "No episode completed. Call /reset then /step with submit_final=True.",
309
  }
310
 
 
 
 
 
311
  return {
312
  "status": "completed",
313
  "task_id": env.state.task_id,
314
+ "score": result["score"],
315
+ "precision": result["precision"],
316
+ "recall": result["recall"],
 
317
  "true_positives": result["true_positives"],
318
  "false_positives": result["false_positives"],
319
  "false_negatives": result["false_negatives"],
320
  "total_errors": result["total_errors"],
321
+ "weighted_score": result.get("weighted_score", result["score"]),
322
+ "partial_credit_score": result.get("partial_credit_score", result["score"]),
 
323
  "partial_matches": result.get("partial_matches", 0),
324
  # Confusion matrix
325
  "confusion_matrix": result.get("confusion_matrix", {}),
financial_audit_env/server/environment.py CHANGED
@@ -234,14 +234,15 @@ class FinancialAuditEnvironment(Environment):
234
  # Check if episode should end
235
  is_final = action.submit_final or step_num >= self._task["max_steps"]
236
 
237
- # Compute step reward.
238
- # Keep cumulative episode rewards aligned with the final task score.
239
- if not is_final:
240
- step_reward = 0.0
241
- else:
242
- final_grader = compute_f1_score(self._findings, self._ground_truth)
243
- # Use the bounded final score as the terminal reward.
244
- step_reward = max(0.01, min(0.99, final_grader["score"]))
 
245
 
246
  self._episode_reward += step_reward
247
 
 
234
  # Check if episode should end
235
  is_final = action.submit_final or step_num >= self._task["max_steps"]
236
 
237
+ # Compute dense per-step reward using only new findings plus episode context.
238
+ step_reward = compute_step_reward(
239
+ new_findings=new_finding_dicts,
240
+ all_findings_so_far=self._findings,
241
+ ground_truth=self._ground_truth,
242
+ step_number=step_num,
243
+ is_final=is_final,
244
+ max_steps=self._task["max_steps"],
245
+ )
246
 
247
  self._episode_reward += step_reward
248
 
test_http.py CHANGED
@@ -17,7 +17,7 @@ print(f"GET /health → {r.status_code} {r.json()}")
17
  r = requests.get(f"{BASE}/tasks")
18
  assert r.status_code == 200
19
  data = r.json()
20
- assert data["total_tasks"] == 3
21
  print(f"GET /tasks → {r.status_code}, {data['total_tasks']} tasks")
22
  for t in data["tasks"]:
23
  print(f" - {t['id']}: {t['name']} ({t['difficulty']})")
@@ -56,8 +56,8 @@ assert r.status_code == 200
56
  grader = r.json()
57
  print(f"GET /grader → score={grader['score']}, P={grader['precision']}, R={grader['recall']}")
58
 
59
- # 6. Test all 3 tasks work
60
- for task_id in ["expense_audit", "invoice_match", "gst_reconciliation"]:
61
  r = requests.post(f"{BASE}/reset", json={"task_id": task_id, "seed": 42})
62
  assert r.status_code == 200
63
  print(f"POST /reset({task_id}) → {r.status_code} ✓")
 
17
  r = requests.get(f"{BASE}/tasks")
18
  assert r.status_code == 200
19
  data = r.json()
20
+ assert data["total_tasks"] == 4
21
  print(f"GET /tasks → {r.status_code}, {data['total_tasks']} tasks")
22
  for t in data["tasks"]:
23
  print(f" - {t['id']}: {t['name']} ({t['difficulty']})")
 
56
  grader = r.json()
57
  print(f"GET /grader → score={grader['score']}, P={grader['precision']}, R={grader['recall']}")
58
 
59
+ # 6. Test all 4 tasks work
60
+ for task_id in ["expense_audit", "invoice_match", "gst_reconciliation", "fraud_detection"]:
61
  r = requests.post(f"{BASE}/reset", json={"task_id": task_id, "seed": 42})
62
  assert r.status_code == 200
63
  print(f"POST /reset({task_id}) → {r.status_code} ✓")