padmapriyagosakan commited on
Commit
37f8601
·
1 Parent(s): d7aea6e

fix: rewrite openenv.yaml grader fields to Python callable path format

Browse files

Platform reads openenv.yaml directly and requires:
grader: payops_env.server.graders:EASY001Grader

instead of an inline YAML dict. Created server/graders.py with 30
grader classes (one per task) using a factory pattern. All 30 tasks
in openenv.yaml now have string-format grader path references.

Verified: openenv validate passes, all 30 tasks have valid grader paths.

Files changed (3) hide show
  1. _rewrite_tasks_yaml.py +67 -0
  2. openenv.yaml +91 -414
  3. server/graders.py +80 -0
_rewrite_tasks_yaml.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Rewrite the tasks section of openenv.yaml to use Python callable grader paths.
3
+ The platform expects: grader: payops_env.server.graders:EASY001Grader
4
+ instead of an inline YAML dict.
5
+ """
6
+ import sys
7
+ sys.path.insert(0, ".")
8
+ from payops_env.tasks import TASKS
9
+
10
+ with open("openenv.yaml") as f:
11
+ content = f.read()
12
+
13
+ # Build new tasks block — one entry per task with string grader path
14
+ lines = []
15
+ lines.append("# tasks: grader field points to a Python callable (module:Class format)")
16
+ lines.append("tasks:")
17
+ for t in TASKS:
18
+ class_name = t.task_id.replace("-", "") + "Grader"
19
+ lines.append(f" - id: {t.task_id}")
20
+ lines.append(f" name: \"{t.task_id}\"")
21
+ lines.append(f" difficulty: {t.difficulty}")
22
+ lines.append(f" grader: payops_env.server.graders:{class_name}")
23
+
24
+ new_tasks_block = "\n".join(lines)
25
+
26
+ # Verify counts
27
+ assert new_tasks_block.count(" - id:") == 30, "Expected 30 task entries"
28
+ assert new_tasks_block.count(" grader: payops_env.server.graders:") == 30, "Expected 30 grader paths"
29
+ print("Block verification: 30 tasks, 30 string grader paths - OK")
30
+
31
+ # Replace in openenv.yaml: from "# tasks:" comment to just before "# ── Budget"
32
+ old_start = "# tasks: flat list — platform iterates this directly for grader check"
33
+ budget_marker = "\n# ── Budget"
34
+
35
+ if old_start not in content:
36
+ print("ERROR: Could not find tasks section start marker")
37
+ sys.exit(1)
38
+
39
+ tasks_start_pos = content.index(old_start)
40
+ budget_start_pos = content.index(budget_marker)
41
+
42
+ new_content = content[:tasks_start_pos] + new_tasks_block + "\n" + content[budget_start_pos:]
43
+
44
+ with open("openenv.yaml", "w") as f:
45
+ f.write(new_content)
46
+
47
+ print("openenv.yaml rewritten successfully")
48
+
49
+ # Parse and verify
50
+ import yaml
51
+ with open("openenv.yaml") as f:
52
+ d = yaml.safe_load(f.read())
53
+
54
+ tasks = d.get("tasks", [])
55
+ print(f"Parsed tasks: type={type(tasks).__name__}, count={len(tasks)}")
56
+ assert isinstance(tasks, list) and len(tasks) == 30
57
+
58
+ grader_strings = [t for t in tasks if isinstance(t.get("grader"), str)]
59
+ print(f"Tasks with string grader: {len(grader_strings)}/30")
60
+ assert len(grader_strings) == 30, "All graders must be strings"
61
+
62
+ # Spot-check format
63
+ g = tasks[0]["grader"]
64
+ assert "payops_env.server.graders:" in g, f"Bad grader format: {g}"
65
+ assert g.endswith("Grader"), f"Grader should end with Grader: {g}"
66
+ print(f"First task grader: {g}")
67
+ print("VERIFICATION PASSED")
openenv.yaml CHANGED
@@ -126,451 +126,128 @@ task_difficulty_weights:
126
  hard: 1.5
127
  critical: 2.0
128
 
129
- # tasks: flat list platform iterates this directly for grader check
130
  tasks:
131
- - task_id: EASY-001
 
132
  difficulty: easy
133
- grader:
134
- enabled: true
135
- type: action_match
136
- correct_action: approve
137
- partial_credit: {}
138
- requires_investigation: []
139
- regulatory_action: false
140
- key_flags: []
141
- - task_id: EASY-002
142
  difficulty: easy
143
- grader:
144
- enabled: true
145
- type: action_match
146
- correct_action: reject
147
- partial_credit:
148
- escalate: 0.4
149
- requires_investigation: []
150
- regulatory_action: false
151
- key_flags:
152
- - sanctioned_country
153
- - unknown_sender
154
- - task_id: EASY-003
155
  difficulty: easy
156
- grader:
157
- enabled: true
158
- type: action_match
159
- correct_action: approve
160
- partial_credit: {}
161
- requires_investigation: []
162
- regulatory_action: false
163
- key_flags: []
164
- - task_id: EASY-004
165
  difficulty: easy
166
- grader:
167
- enabled: true
168
- type: action_match
169
- correct_action: flag
170
- partial_credit:
171
- escalate: 0.6
172
- hold: 0.5
173
- requires_investigation: []
174
- regulatory_action: false
175
- key_flags:
176
- - velocity_breach
177
- - task_id: EASY-005
178
  difficulty: easy
179
- grader:
180
- enabled: true
181
- type: action_match
182
- correct_action: approve
183
- partial_credit: {}
184
- requires_investigation: []
185
- regulatory_action: false
186
- key_flags: []
187
- - task_id: EASY-006
188
  difficulty: easy
189
- grader:
190
- enabled: true
191
- type: action_match
192
- correct_action: flag
193
- partial_credit:
194
- hold: 0.6
195
- requires_investigation: []
196
- regulatory_action: false
197
- key_flags:
198
- - duplicate_transaction
199
- - task_id: MED-001
200
  difficulty: medium
201
- grader:
202
- enabled: true
203
- type: action_match
204
- correct_action: escalate
205
- partial_credit:
206
- flag: 0.5
207
- hold: 0.4
208
- requires_investigation:
209
- - inspect
210
- regulatory_action: false
211
- key_flags:
212
- - cross_border
213
- - high_value
214
- - task_id: MED-002
215
  difficulty: medium
216
- grader:
217
- enabled: true
218
- type: action_match
219
- correct_action: hold
220
- partial_credit:
221
- escalate: 0.6
222
- flag: 0.4
223
- requires_investigation:
224
- - verify_kyc
225
- regulatory_action: false
226
- key_flags:
227
- - kyc_expiry_90d
228
- - task_id: MED-003
229
  difficulty: medium
230
- grader:
231
- enabled: true
232
- type: action_match
233
- correct_action: flag
234
- partial_credit:
235
- hold: 0.5
236
- escalate: 0.3
237
- requires_investigation: []
238
- regulatory_action: false
239
- key_flags:
240
- - amount_spike
241
- - pattern_deviation
242
- - task_id: MED-004
243
  difficulty: medium
244
- grader:
245
- enabled: true
246
- type: action_match
247
- correct_action: flag
248
- partial_credit:
249
- escalate: 0.5
250
- hold: 0.4
251
- requires_investigation: []
252
- regulatory_action: false
253
- key_flags:
254
- - crypto_exchange
255
- - task_id: MED-005
256
  difficulty: medium
257
- grader:
258
- enabled: true
259
- type: action_match
260
- correct_action: hold
261
- partial_credit:
262
- flag: 0.5
263
- escalate: 0.4
264
- requires_investigation:
265
- - verify_kyc
266
- regulatory_action: false
267
- key_flags:
268
- - kyc_expired
269
- - task_id: MED-006
270
  difficulty: medium
271
- grader:
272
- enabled: true
273
- type: action_match
274
- correct_action: escalate
275
- partial_credit:
276
- flag: 0.4
277
- hold: 0.5
278
- requires_investigation:
279
- - request_docs
280
- regulatory_action: false
281
- key_flags:
282
- - first_time_payee
283
- - large_first_transfer
284
- - task_id: MED-007
285
  difficulty: medium
286
- grader:
287
- enabled: true
288
- type: action_match
289
- correct_action: hold
290
- partial_credit:
291
- escalate: 0.6
292
- flag: 0.4
293
- requires_investigation:
294
- - contact_sender
295
- - request_docs
296
- regulatory_action: false
297
- key_flags:
298
- - bank_detail_change
299
- - first_time_payee
300
- - task_id: MED-008
301
  difficulty: medium
302
- grader:
303
- enabled: true
304
- type: action_match
305
- correct_action: flag
306
- partial_credit:
307
- hold: 0.6
308
- escalate: 0.4
309
- requires_investigation:
310
- - verify_kyc
311
- regulatory_action: false
312
- key_flags:
313
- - thin_credit_file
314
- - new_account
315
- - task_id: HARD-001
316
  difficulty: hard
317
- grader:
318
- enabled: true
319
- type: action_match
320
- correct_action: escalate
321
- partial_credit:
322
- flag: 0.6
323
- hold: 0.5
324
- reject: 0.3
325
- requires_investigation:
326
- - inspect
327
- regulatory_action: false
328
- key_flags:
329
- - solicitor_mule_pattern
330
- - task_id: HARD-002
331
  difficulty: hard
332
- grader:
333
- enabled: true
334
- type: action_match
335
- correct_action: reject
336
- partial_credit:
337
- escalate: 0.6
338
- hold: 0.5
339
- flag: 0.3
340
- requires_investigation:
341
- - contact_sender
342
- regulatory_action: false
343
- key_flags:
344
- - app_scam_indicator
345
- - mule_account_pattern
346
- - task_id: HARD-003
347
  difficulty: hard
348
- grader:
349
- enabled: true
350
- type: action_match
351
- correct_action: reject
352
- partial_credit:
353
- escalate: 0.5
354
- flag: 0.2
355
- requires_investigation:
356
- - inspect
357
- regulatory_action: true
358
- key_flags:
359
- - structuring_pattern
360
- - ctr_threshold_avoidance
361
- - task_id: HARD-004
362
  difficulty: hard
363
- grader:
364
- enabled: true
365
- type: action_match
366
- correct_action: approve
367
- partial_credit:
368
- escalate: 0.5
369
- hold: 0.4
370
- flag: 0.3
371
- requires_investigation:
372
- - inspect
373
- regulatory_action: false
374
- key_flags:
375
- - fx_settlement
376
- - task_id: HARD-005
377
  difficulty: hard
378
- grader:
379
- enabled: true
380
- type: action_match
381
- correct_action: escalate
382
- partial_credit:
383
- flag: 0.5
384
- hold: 0.4
385
- reject: 0.3
386
- requires_investigation:
387
- - contact_sender
388
- - inspect
389
- regulatory_action: false
390
- key_flags:
391
- - internal_to_personal
392
- - unusual_beneficiary
393
- - task_id: HARD-006
394
  difficulty: hard
395
- grader:
396
- enabled: true
397
- type: action_match
398
- correct_action: flag
399
- partial_credit:
400
- hold: 0.6
401
- escalate: 0.5
402
- requires_investigation:
403
- - inspect
404
- regulatory_action: false
405
- key_flags:
406
- - dormant_receiver
407
- - sudden_activity
408
- - task_id: HARD-007
409
  difficulty: hard
410
- grader:
411
- enabled: true
412
- type: action_match
413
- correct_action: reject
414
- partial_credit:
415
- hold: 0.5
416
- escalate: 0.4
417
- flag: 0.3
418
- requires_investigation:
419
- - contact_sender
420
- - inspect
421
- regulatory_action: false
422
- key_flags:
423
- - sim_swap_detected
424
- - new_withdrawal_address
425
- - task_id: HARD-008
426
  difficulty: hard
427
- grader:
428
- enabled: true
429
- type: action_match
430
- correct_action: reject
431
- partial_credit:
432
- hold: 0.5
433
- escalate: 0.4
434
- flag: 0.3
435
- requires_investigation:
436
- - contact_sender
437
- - inspect
438
- regulatory_action: false
439
- key_flags:
440
- - romance_scam_indicator
441
- - escalating_transfers
442
- - task_id: HARD-009
443
  difficulty: hard
444
- grader:
445
- enabled: true
446
- type: action_match
447
- correct_action: escalate
448
- partial_credit:
449
- hold: 0.5
450
- flag: 0.4
451
- reject: 0.3
452
- requires_investigation:
453
- - inspect
454
- - verify_kyc
455
- regulatory_action: false
456
- key_flags:
457
- - new_account_10d
458
- - perfect_score_anomaly
459
- - task_id: HARD-010
460
  difficulty: hard
461
- grader:
462
- enabled: true
463
- type: action_match
464
- correct_action: reject
465
- partial_credit:
466
- hold: 0.5
467
- escalate: 0.4
468
- flag: 0.3
469
- requires_investigation:
470
- - contact_sender
471
- - inspect
472
- regulatory_action: false
473
- key_flags:
474
- - direct_deposit_changed
475
- - payroll_diversion_indicator
476
- - task_id: CRIT-001
477
  difficulty: critical
478
- grader:
479
- enabled: true
480
- type: action_match
481
- correct_action: approve
482
- partial_credit:
483
- escalate: 0.5
484
- hold: 0.4
485
- flag: 0.3
486
- requires_investigation:
487
- - inspect
488
- - request_docs
489
- regulatory_action: false
490
- key_flags:
491
- - first_time_payee
492
- - new_counterparty
493
- - task_id: CRIT-002
494
  difficulty: critical
495
- grader:
496
- enabled: true
497
- type: action_match
498
- correct_action: reject
499
- partial_credit:
500
- escalate: 0.4
501
- flag: 0.2
502
- requires_investigation:
503
- - inspect
504
- regulatory_action: true
505
- key_flags:
506
- - fraud_ring_indicator
507
- - coordinated_transfers
508
- - task_id: CRIT-003
509
  difficulty: critical
510
- grader:
511
- enabled: true
512
- type: action_match
513
- correct_action: escalate
514
- partial_credit:
515
- flag: 0.4
516
- hold: 0.35
517
- reject: 0.3
518
- requires_investigation:
519
- - inspect
520
- - request_docs
521
- regulatory_action: true
522
- key_flags:
523
- - invoice_mismatch
524
- - trade_finance
525
- - task_id: CRIT-004
526
  difficulty: critical
527
- grader:
528
- enabled: true
529
- type: action_match
530
- correct_action: reject
531
- partial_credit:
532
- escalate: 0.5
533
- hold: 0.4
534
- requires_investigation:
535
- - contact_sender
536
- - inspect
537
- regulatory_action: false
538
- key_flags:
539
- - geo_impossible_login
540
- - account_takeover_indicator
541
- - task_id: CRIT-005
542
  difficulty: critical
543
- grader:
544
- enabled: true
545
- type: action_match
546
- correct_action: reject
547
- partial_credit:
548
- escalate: 0.5
549
- hold: 0.4
550
- requires_investigation:
551
- - inspect
552
- - request_docs
553
- - verify_kyc
554
- regulatory_action: true
555
- key_flags:
556
- - shell_company_indicator
557
- - sanctions_adjacent
558
- - task_id: CRIT-006
559
  difficulty: critical
560
- grader:
561
- enabled: true
562
- type: action_match
563
- correct_action: escalate
564
- partial_credit:
565
- hold: 0.5
566
- reject: 0.4
567
- requires_investigation:
568
- - inspect
569
- - request_docs
570
- regulatory_action: true
571
- key_flags:
572
- - fincen_311_alert
573
- - correspondent_risk
574
 
575
  # ── Budget ───────────────────────────────────────────────────────────────────
576
  budget:
 
126
  hard: 1.5
127
  critical: 2.0
128
 
129
+ # tasks: grader field points to a Python callable (module:Class format)
130
  tasks:
131
+ - id: EASY-001
132
+ name: "EASY-001"
133
  difficulty: easy
134
+ grader: payops_env.server.graders:EASY001Grader
135
+ - id: EASY-002
136
+ name: "EASY-002"
 
 
 
 
 
 
137
  difficulty: easy
138
+ grader: payops_env.server.graders:EASY002Grader
139
+ - id: EASY-003
140
+ name: "EASY-003"
 
 
 
 
 
 
 
 
 
141
  difficulty: easy
142
+ grader: payops_env.server.graders:EASY003Grader
143
+ - id: EASY-004
144
+ name: "EASY-004"
 
 
 
 
 
 
145
  difficulty: easy
146
+ grader: payops_env.server.graders:EASY004Grader
147
+ - id: EASY-005
148
+ name: "EASY-005"
 
 
 
 
 
 
 
 
 
149
  difficulty: easy
150
+ grader: payops_env.server.graders:EASY005Grader
151
+ - id: EASY-006
152
+ name: "EASY-006"
 
 
 
 
 
 
153
  difficulty: easy
154
+ grader: payops_env.server.graders:EASY006Grader
155
+ - id: MED-001
156
+ name: "MED-001"
 
 
 
 
 
 
 
 
157
  difficulty: medium
158
+ grader: payops_env.server.graders:MED001Grader
159
+ - id: MED-002
160
+ name: "MED-002"
 
 
 
 
 
 
 
 
 
 
 
161
  difficulty: medium
162
+ grader: payops_env.server.graders:MED002Grader
163
+ - id: MED-003
164
+ name: "MED-003"
 
 
 
 
 
 
 
 
 
 
165
  difficulty: medium
166
+ grader: payops_env.server.graders:MED003Grader
167
+ - id: MED-004
168
+ name: "MED-004"
 
 
 
 
 
 
 
 
 
 
169
  difficulty: medium
170
+ grader: payops_env.server.graders:MED004Grader
171
+ - id: MED-005
172
+ name: "MED-005"
 
 
 
 
 
 
 
 
 
173
  difficulty: medium
174
+ grader: payops_env.server.graders:MED005Grader
175
+ - id: MED-006
176
+ name: "MED-006"
 
 
 
 
 
 
 
 
 
 
177
  difficulty: medium
178
+ grader: payops_env.server.graders:MED006Grader
179
+ - id: MED-007
180
+ name: "MED-007"
 
 
 
 
 
 
 
 
 
 
 
181
  difficulty: medium
182
+ grader: payops_env.server.graders:MED007Grader
183
+ - id: MED-008
184
+ name: "MED-008"
 
 
 
 
 
 
 
 
 
 
 
 
185
  difficulty: medium
186
+ grader: payops_env.server.graders:MED008Grader
187
+ - id: HARD-001
188
+ name: "HARD-001"
 
 
 
 
 
 
 
 
 
 
 
189
  difficulty: hard
190
+ grader: payops_env.server.graders:HARD001Grader
191
+ - id: HARD-002
192
+ name: "HARD-002"
 
 
 
 
 
 
 
 
 
 
 
193
  difficulty: hard
194
+ grader: payops_env.server.graders:HARD002Grader
195
+ - id: HARD-003
196
+ name: "HARD-003"
 
 
 
 
 
 
 
 
 
 
 
 
197
  difficulty: hard
198
+ grader: payops_env.server.graders:HARD003Grader
199
+ - id: HARD-004
200
+ name: "HARD-004"
 
 
 
 
 
 
 
 
 
 
 
201
  difficulty: hard
202
+ grader: payops_env.server.graders:HARD004Grader
203
+ - id: HARD-005
204
+ name: "HARD-005"
 
 
 
 
 
 
 
 
 
 
 
205
  difficulty: hard
206
+ grader: payops_env.server.graders:HARD005Grader
207
+ - id: HARD-006
208
+ name: "HARD-006"
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  difficulty: hard
210
+ grader: payops_env.server.graders:HARD006Grader
211
+ - id: HARD-007
212
+ name: "HARD-007"
 
 
 
 
 
 
 
 
 
 
 
213
  difficulty: hard
214
+ grader: payops_env.server.graders:HARD007Grader
215
+ - id: HARD-008
216
+ name: "HARD-008"
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  difficulty: hard
218
+ grader: payops_env.server.graders:HARD008Grader
219
+ - id: HARD-009
220
+ name: "HARD-009"
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  difficulty: hard
222
+ grader: payops_env.server.graders:HARD009Grader
223
+ - id: HARD-010
224
+ name: "HARD-010"
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  difficulty: hard
226
+ grader: payops_env.server.graders:HARD010Grader
227
+ - id: CRIT-001
228
+ name: "CRIT-001"
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  difficulty: critical
230
+ grader: payops_env.server.graders:CRIT001Grader
231
+ - id: CRIT-002
232
+ name: "CRIT-002"
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  difficulty: critical
234
+ grader: payops_env.server.graders:CRIT002Grader
235
+ - id: CRIT-003
236
+ name: "CRIT-003"
 
 
 
 
 
 
 
 
 
 
 
237
  difficulty: critical
238
+ grader: payops_env.server.graders:CRIT003Grader
239
+ - id: CRIT-004
240
+ name: "CRIT-004"
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  difficulty: critical
242
+ grader: payops_env.server.graders:CRIT004Grader
243
+ - id: CRIT-005
244
+ name: "CRIT-005"
 
 
 
 
 
 
 
 
 
 
 
 
245
  difficulty: critical
246
+ grader: payops_env.server.graders:CRIT005Grader
247
+ - id: CRIT-006
248
+ name: "CRIT-006"
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  difficulty: critical
250
+ grader: payops_env.server.graders:CRIT006Grader
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
  # ── Budget ───────────────────────────────────────────────────────────────────
253
  budget:
server/graders.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grader callables for PayOps OpenEnv tasks.
3
+
4
+ Each class is referenced from openenv.yaml as:
5
+ payops_env.server.graders:EASY001Grader
6
+
7
+ The platform reads the grader field from openenv.yaml, imports the class,
8
+ and uses it to grade agent actions. Each grader is a callable that accepts
9
+ an action string and returns a float reward in [0.0, 1.0].
10
+ """
11
+ from __future__ import annotations
12
+
13
+
14
+ class _BaseGrader:
15
+ """Base grader: correct action → 1.0, partial credit if configured, else 0.0."""
16
+
17
+ task_id: str = ""
18
+
19
+ def grade(self, action: str, **kwargs) -> float:
20
+ from payops_env.tasks import TASKS_BY_ID # lazy import avoids circular deps
21
+
22
+ t = TASKS_BY_ID.get(self.task_id)
23
+ if t is None:
24
+ return 0.0
25
+ if action == t.correct_action:
26
+ return 1.0
27
+ return float(
28
+ dict(getattr(t, "partial_credit_actions", {})).get(action, 0.0)
29
+ )
30
+
31
+ def __call__(self, action: str, **kwargs) -> float:
32
+ return self.grade(action, **kwargs)
33
+
34
+
35
+ def _make(task_id: str) -> type:
36
+ """Factory: create a named grader class for the given task_id."""
37
+ return type(
38
+ task_id.replace("-", "") + "Grader",
39
+ (_BaseGrader,),
40
+ {"task_id": task_id},
41
+ )
42
+
43
+
44
+ # ── Easy ─────────────────────────────────────────────────────────────────────
45
+ EASY001Grader = _make("EASY-001") # approve
46
+ EASY002Grader = _make("EASY-002") # reject
47
+ EASY003Grader = _make("EASY-003") # approve
48
+ EASY004Grader = _make("EASY-004") # flag
49
+ EASY005Grader = _make("EASY-005") # approve
50
+ EASY006Grader = _make("EASY-006") # flag
51
+
52
+ # ── Medium ────────────────────────────────────────────────────────────────────
53
+ MED001Grader = _make("MED-001") # escalate
54
+ MED002Grader = _make("MED-002") # hold
55
+ MED003Grader = _make("MED-003") # flag
56
+ MED004Grader = _make("MED-004") # flag
57
+ MED005Grader = _make("MED-005") # hold
58
+ MED006Grader = _make("MED-006") # escalate
59
+ MED007Grader = _make("MED-007") # hold
60
+ MED008Grader = _make("MED-008") # flag
61
+
62
+ # ── Hard ──────────────────────────────────────────────────────────────────────
63
+ HARD001Grader = _make("HARD-001") # escalate
64
+ HARD002Grader = _make("HARD-002") # reject
65
+ HARD003Grader = _make("HARD-003") # reject
66
+ HARD004Grader = _make("HARD-004") # approve
67
+ HARD005Grader = _make("HARD-005") # escalate
68
+ HARD006Grader = _make("HARD-006") # flag
69
+ HARD007Grader = _make("HARD-007") # reject
70
+ HARD008Grader = _make("HARD-008") # reject
71
+ HARD009Grader = _make("HARD-009") # escalate
72
+ HARD010Grader = _make("HARD-010") # reject
73
+
74
+ # ── Critical ──────────────────────────────────────────────────────────────────
75
+ CRIT001Grader = _make("CRIT-001") # approve
76
+ CRIT002Grader = _make("CRIT-002") # reject
77
+ CRIT003Grader = _make("CRIT-003") # escalate
78
+ CRIT004Grader = _make("CRIT-004") # reject
79
+ CRIT005Grader = _make("CRIT-005") # reject
80
+ CRIT006Grader = _make("CRIT-006") # escalate