Spaces:
Running
Running
Commit ·
60e34d7
1
Parent(s): f76bbd6
Fix step reward flow, wire investigation API, and refresh README scores
Browse files- README.md +4 -4
- financial_audit_env/server/app.py +10 -12
- financial_audit_env/server/environment.py +9 -8
- test_http.py +3 -3
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.
|
| 241 |
-
| Invoice Match | Medium | 0.
|
| 242 |
-
| GST Reconciliation | Hard | 0.
|
| 243 |
-
| Fraud Detection | Expert | 0.
|
| 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(
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 315 |
-
"
|
| 316 |
-
"
|
| 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 |
-
|
| 323 |
-
"
|
| 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 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
|
|
|
| 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"] ==
|
| 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
|
| 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} ✓")
|