invoiceguard-code / tasks /definitions.py
piyush-mk's picture
Sync InvoiceGuard code for GRPO training job
9a88af0 verified
Raw
History Blame Contribute Delete
81.6 kB
"""
Task definitions and synthetic case templates for InvoiceGuard.
Four tasks with increasing difficulty, each with fully synthetic
business documents and deterministic ground truth.
"""
from typing import Dict, List
try:
from ..models import (
CaseData, CaseHistory, CompanyPolicy, DecisionType, Difficulty,
ExceptionType, GRNLineItem, GoodsReceiptNote, GroundTruth,
Invoice, InvoiceLineItem, POLineItem, PurchaseOrder,
TaskID, VendorProfile,
)
from .hard_definitions import HARD_TASK_BUILDERS, HARD_TASK_LIST
except ImportError:
from models import (
CaseData, CaseHistory, CompanyPolicy, DecisionType, Difficulty,
ExceptionType, GRNLineItem, GoodsReceiptNote, GroundTruth,
Invoice, InvoiceLineItem, POLineItem, PurchaseOrder,
TaskID, VendorProfile,
)
from tasks.hard_definitions import HARD_TASK_BUILDERS, HARD_TASK_LIST
TASK_LIST: List[TaskID] = [
TaskID.task_1_clean_match,
TaskID.task_2_partial_receipt,
TaskID.task_3_price_variance,
TaskID.task_4_duplicate_invoice,
TaskID.task_5_mixed_discrepancy,
TaskID.task_6_false_positive_duplicate,
TaskID.task_7_retroactive_price,
TaskID.task_8_split_invoice_pattern,
TaskID.task_9_clean_from_risky_vendor,
TaskID.task_10_rounding_false_alarm,
TaskID.task_11_authorized_overship,
TaskID.task_12_corrected_resubmission,
]
ALL_TASKS: List[TaskID] = [
TaskID.task_1_clean_match,
TaskID.task_1b_multi_line_clean,
TaskID.task_1c_preferred_vendor_clean,
TaskID.task_2_partial_receipt,
TaskID.task_2b_missing_receipt,
TaskID.task_2c_over_receipt,
TaskID.task_3_price_variance,
TaskID.task_3b_within_tolerance,
TaskID.task_3c_total_mismatch,
TaskID.task_4_duplicate_invoice,
TaskID.task_4b_corrected_invoice_trap,
TaskID.task_4c_policy_violation,
TaskID.task_5_mixed_discrepancy,
TaskID.task_6_false_positive_duplicate,
TaskID.task_7_retroactive_price,
TaskID.task_8_split_invoice_pattern,
TaskID.task_9_clean_from_risky_vendor,
TaskID.task_10_rounding_false_alarm,
TaskID.task_11_authorized_overship,
TaskID.task_12_corrected_resubmission,
*HARD_TASK_LIST,
]
def get_task_case(task_id: TaskID) -> CaseData:
"""Return the case data for a given task (canonical or variant)."""
builders: Dict[TaskID, callable] = {
TaskID.task_1_clean_match: _build_task_1,
TaskID.task_1b_multi_line_clean: _build_task_1b,
TaskID.task_1c_preferred_vendor_clean: _build_task_1c,
TaskID.task_2_partial_receipt: _build_task_2,
TaskID.task_2b_missing_receipt: _build_task_2b,
TaskID.task_2c_over_receipt: _build_task_2c,
TaskID.task_3_price_variance: _build_task_3,
TaskID.task_3b_within_tolerance: _build_task_3b,
TaskID.task_3c_total_mismatch: _build_task_3c,
TaskID.task_4_duplicate_invoice: _build_task_4,
TaskID.task_4b_corrected_invoice_trap: _build_task_4b,
TaskID.task_4c_policy_violation: _build_task_4c,
TaskID.task_5_mixed_discrepancy: _build_task_5,
TaskID.task_6_false_positive_duplicate: _build_task_6,
TaskID.task_7_retroactive_price: _build_task_7,
TaskID.task_8_split_invoice_pattern: _build_task_8,
TaskID.task_9_clean_from_risky_vendor: _build_task_9,
TaskID.task_10_rounding_false_alarm: _build_task_10,
TaskID.task_11_authorized_overship: _build_task_11,
TaskID.task_12_corrected_resubmission: _build_task_12,
**HARD_TASK_BUILDERS,
}
return builders[task_id]()
# -- Task 1: Clean Match Approval --------------------------------------------
# Difficulty: easy | Decision: approve_for_payment | Exception: clean_match
# All three documents align within tolerance. Tests whether the agent can
# confirm a clean case without inventing problems.
def _build_task_1() -> CaseData:
return CaseData(
case_id="CASE-1001",
task_id=TaskID.task_1_clean_match,
difficulty=Difficulty.easy,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0042",
supplier_name="Apex Office Supplies",
supplier_id="SUP-1001",
invoice_date="2024-11-15",
po_reference="PO-5500",
line_items=[
InvoiceLineItem(
item_code="ITEM-A01",
description="Premium Copy Paper A4 (500 sheets)",
quantity_billed=100,
unit_price_billed=25.00,
line_total_billed=2500.00,
),
],
subtotal=2500.00,
tax=200.00,
total_amount=2700.00,
),
purchase_order=PurchaseOrder(
po_number="PO-5500",
supplier_id="SUP-1001",
order_date="2024-10-20",
line_items=[
POLineItem(
item_code="ITEM-A01",
description="Premium Copy Paper A4 (500 sheets)",
ordered_quantity=100,
unit_price_ordered=25.00,
line_total_ordered=2500.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-7801",
po_reference="PO-5500",
receipt_date="2024-11-10",
line_items=[
GRNLineItem(
item_code="ITEM-A01",
quantity_received=100,
accepted_quantity=100,
),
],
warehouse_note="All items received in good condition.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-1001",
supplier_name="Apex Office Supplies",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=100.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
],
key_findings=[
"Quantities match across invoice, PO, and GRN",
"Prices match between invoice and PO",
"No discrepancies detected",
],
),
)
# -- Task 2: Quantity & Partial Receipt Hold ----------------------------------
# Difficulty: moderate | Decision: place_on_hold | Exception: partial_receipt
# Invoice bills 100 units, PO ordered 100, but GRN only received 60.
# Agent must recognize billed qty exceeds received qty.
def _build_task_2() -> CaseData:
return CaseData(
case_id="CASE-2002",
task_id=TaskID.task_2_partial_receipt,
difficulty=Difficulty.moderate,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0187",
supplier_name="Delta Industrial Parts",
supplier_id="SUP-2050",
invoice_date="2024-12-01",
po_reference="PO-6200",
line_items=[
InvoiceLineItem(
item_code="ITEM-D10",
description="Stainless Steel Bearings (Box of 50)",
quantity_billed=100,
unit_price_billed=50.00,
line_total_billed=5000.00,
),
],
subtotal=5000.00,
tax=400.00,
total_amount=5400.00,
note="Please process payment promptly.",
),
purchase_order=PurchaseOrder(
po_number="PO-6200",
supplier_id="SUP-2050",
order_date="2024-11-01",
line_items=[
POLineItem(
item_code="ITEM-D10",
description="Stainless Steel Bearings (Box of 50)",
ordered_quantity=100,
unit_price_ordered=50.00,
line_total_ordered=5000.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-8830",
po_reference="PO-6200",
receipt_date="2024-11-25",
line_items=[
GRNLineItem(
item_code="ITEM-D10",
quantity_received=60,
accepted_quantity=60,
rejected_quantity=0,
),
],
warehouse_note="Partial shipment. Remaining 40 units expected next week.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-2050",
supplier_name="Delta Industrial Parts",
risk_tier="medium",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
),
case_history=CaseHistory(
prior_comments=["Supplier has had partial shipments before."],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.place_on_hold,
correct_exception_type=ExceptionType.partial_receipt,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
],
key_findings=[
"Invoice billed 100 units but GRN shows only 60 received",
"Billed quantity exceeds received quantity by 40 units (40%)",
"Variance exceeds quantity tolerance of 5%",
],
),
)
# -- Task 3: Price Variance with Policy Tolerance ----------------------------
# Difficulty: moderate | Decision: escalate_for_supervisor_review
# Exception: price_mismatch
# Invoice unit price is $110 vs PO price of $100 (10% variance).
# Company tolerance is 5%, so this must be escalated.
def _build_task_3() -> CaseData:
return CaseData(
case_id="CASE-3003",
task_id=TaskID.task_3_price_variance,
difficulty=Difficulty.moderate,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0315",
supplier_name="Quantum Tech Solutions",
supplier_id="SUP-3100",
invoice_date="2024-12-10",
po_reference="PO-7100",
line_items=[
InvoiceLineItem(
item_code="ITEM-Q22",
description="Enterprise SSD 1TB NVMe",
quantity_billed=50,
unit_price_billed=110.00,
line_total_billed=5500.00,
),
],
subtotal=5500.00,
tax=440.00,
total_amount=5940.00,
note="Price adjusted due to component shortage surcharge.",
),
purchase_order=PurchaseOrder(
po_number="PO-7100",
supplier_id="SUP-3100",
order_date="2024-11-15",
line_items=[
POLineItem(
item_code="ITEM-Q22",
description="Enterprise SSD 1TB NVMe",
ordered_quantity=50,
unit_price_ordered=100.00,
line_total_ordered=5000.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-9150",
po_reference="PO-7100",
receipt_date="2024-12-05",
line_items=[
GRNLineItem(
item_code="ITEM-Q22",
quantity_received=50,
accepted_quantity=50,
),
],
warehouse_note="All units received and tested OK.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-3100",
supplier_name="Quantum Tech Solutions",
risk_tier="medium",
preferred_vendor=True,
escalation_threshold=5000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
high_value_threshold=50000.0,
),
case_history=CaseHistory(
prior_comments=[
"Supplier occasionally applies surcharges without prior notice.",
],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.escalate_for_supervisor_review,
correct_exception_type=ExceptionType.price_mismatch,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_policy_rules",
"compare_price",
],
key_findings=[
"Invoice unit price $110.00 vs PO unit price $100.00",
"Price variance of 10% exceeds company tolerance of 5%",
"Requires supervisor review per escalation policy",
],
),
)
# -- Task 4: Duplicate Invoice with Mixed Signals ----------------------------
# Difficulty: hard | Decision: reject_invoice | Exception: duplicate_invoice
# Quantities and prices match perfectly, but case history reveals a
# previously processed invoice with near-identical details. A misleading
# vendor note tries to distract.
def _build_task_4() -> CaseData:
return CaseData(
case_id="CASE-4004",
task_id=TaskID.task_4_duplicate_invoice,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-0512",
supplier_name="Redline Logistics Corp",
supplier_id="SUP-4200",
invoice_date="2024-12-18",
po_reference="PO-8800",
line_items=[
InvoiceLineItem(
item_code="ITEM-R05",
description="Heavy-Duty Shipping Pallets",
quantity_billed=200,
unit_price_billed=30.00,
line_total_billed=6000.00,
),
],
subtotal=6000.00,
tax=480.00,
total_amount=6480.00,
note="URGENT: Payment overdue. Please expedite. Ref: follow-up to INV-2024-0511.",
),
purchase_order=PurchaseOrder(
po_number="PO-8800",
supplier_id="SUP-4200",
order_date="2024-11-20",
line_items=[
POLineItem(
item_code="ITEM-R05",
description="Heavy-Duty Shipping Pallets",
ordered_quantity=200,
unit_price_ordered=30.00,
line_total_ordered=6000.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-9900",
po_reference="PO-8800",
receipt_date="2024-12-12",
line_items=[
GRNLineItem(
item_code="ITEM-R05",
quantity_received=200,
accepted_quantity=200,
),
],
warehouse_note="Full shipment received.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-4200",
supplier_name="Redline Logistics Corp",
risk_tier="high",
duplicate_risk_count=2,
preferred_vendor=False,
escalation_threshold=3000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(
prior_comments=[
"Previous duplicate submission flagged for this supplier in Q3.",
"Supplier has been warned about duplicate invoicing.",
],
similar_invoice_refs=["INV-2024-0511"],
processed_invoice_numbers=[
"INV-2024-0511",
"INV-2024-0388",
],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.reject_invoice,
correct_exception_type=ExceptionType.duplicate_invoice,
acceptable_evidence=[
"check_for_duplicate_invoice",
"inspect_vendor_profile",
],
key_findings=[
"Previously processed INV-2024-0511 has same supplier, same PO, same total",
"Current invoice INV-2024-0512 is sequential to processed INV-2024-0511",
"Supplier flagged as high risk with 2 prior duplicate incidents",
"Invoice note references INV-2024-0511 as 'follow-up'",
],
),
)
# ===========================================================================
# VARIANT TASKS
# ===========================================================================
# -- Task 1b: Multi-Line Clean Match --------------------------------------
# Three line items, all matching. Tests multi-item verification.
def _build_task_1b() -> CaseData:
return CaseData(
case_id="CASE-1002",
task_id=TaskID.task_1b_multi_line_clean,
difficulty=Difficulty.easy,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0099",
supplier_name="Summit Facility Services",
supplier_id="SUP-1050",
invoice_date="2024-11-20",
po_reference="PO-5510",
line_items=[
InvoiceLineItem(
item_code="ITEM-F01",
description="Industrial Floor Cleaner 5L",
quantity_billed=20,
unit_price_billed=35.00,
line_total_billed=700.00,
),
InvoiceLineItem(
item_code="ITEM-F02",
description="Microfiber Mop Heads (Pack of 10)",
quantity_billed=15,
unit_price_billed=18.00,
line_total_billed=270.00,
),
InvoiceLineItem(
item_code="ITEM-F03",
description="Nitrile Gloves Box (100ct)",
quantity_billed=50,
unit_price_billed=12.00,
line_total_billed=600.00,
),
],
subtotal=1570.00,
tax=125.60,
total_amount=1695.60,
),
purchase_order=PurchaseOrder(
po_number="PO-5510",
supplier_id="SUP-1050",
order_date="2024-10-25",
line_items=[
POLineItem(
item_code="ITEM-F01",
description="Industrial Floor Cleaner 5L",
ordered_quantity=20,
unit_price_ordered=35.00,
line_total_ordered=700.00,
),
POLineItem(
item_code="ITEM-F02",
description="Microfiber Mop Heads (Pack of 10)",
ordered_quantity=15,
unit_price_ordered=18.00,
line_total_ordered=270.00,
),
POLineItem(
item_code="ITEM-F03",
description="Nitrile Gloves Box (100ct)",
ordered_quantity=50,
unit_price_ordered=12.00,
line_total_ordered=600.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-7820",
po_reference="PO-5510",
receipt_date="2024-11-15",
line_items=[
GRNLineItem(item_code="ITEM-F01", quantity_received=20, accepted_quantity=20),
GRNLineItem(item_code="ITEM-F02", quantity_received=15, accepted_quantity=15),
GRNLineItem(item_code="ITEM-F03", quantity_received=50, accepted_quantity=50),
],
warehouse_note="All items received, inspected, shelved.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-1050",
supplier_name="Summit Facility Services",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=100.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
],
key_findings=[
"All 3 line items match across invoice, PO, and GRN",
"No price or quantity discrepancies",
],
),
)
# -- Task 1c: Preferred Vendor Clean Match --------------------------------
# Single item, low-risk preferred vendor, very straightforward.
def _build_task_1c() -> CaseData:
return CaseData(
case_id="CASE-1003",
task_id=TaskID.task_1c_preferred_vendor_clean,
difficulty=Difficulty.easy,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0155",
supplier_name="BrightStar IT Solutions",
supplier_id="SUP-1200",
invoice_date="2024-12-02",
po_reference="PO-5600",
line_items=[
InvoiceLineItem(
item_code="ITEM-B10",
description="Wireless Keyboard & Mouse Combo",
quantity_billed=30,
unit_price_billed=45.00,
line_total_billed=1350.00,
),
],
subtotal=1350.00,
tax=108.00,
total_amount=1458.00,
),
purchase_order=PurchaseOrder(
po_number="PO-5600",
supplier_id="SUP-1200",
order_date="2024-11-10",
line_items=[
POLineItem(
item_code="ITEM-B10",
description="Wireless Keyboard & Mouse Combo",
ordered_quantity=30,
unit_price_ordered=45.00,
line_total_ordered=1350.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-7850",
po_reference="PO-5600",
receipt_date="2024-11-28",
line_items=[
GRNLineItem(item_code="ITEM-B10", quantity_received=30, accepted_quantity=30),
],
warehouse_note="Full shipment. All items functional.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-1200",
supplier_name="BrightStar IT Solutions",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=100.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
],
key_findings=[
"Quantities match (30 ordered, 30 billed, 30 received)",
"Price matches ($45.00 agreed and billed)",
],
),
)
# -- Task 2b: Missing Receipt (GRN shows 0 received) ---------------------
# GRN was filed but zero units accepted. Decision: hold.
def _build_task_2b() -> CaseData:
return CaseData(
case_id="CASE-2003",
task_id=TaskID.task_2b_missing_receipt,
difficulty=Difficulty.moderate,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0201",
supplier_name="NorthWind Chemicals",
supplier_id="SUP-2100",
invoice_date="2024-12-05",
po_reference="PO-6300",
line_items=[
InvoiceLineItem(
item_code="ITEM-N05",
description="Laboratory Grade Ethanol 1L",
quantity_billed=200,
unit_price_billed=22.00,
line_total_billed=4400.00,
),
],
subtotal=4400.00,
tax=352.00,
total_amount=4752.00,
note="Shipment dispatched per schedule.",
),
purchase_order=PurchaseOrder(
po_number="PO-6300",
supplier_id="SUP-2100",
order_date="2024-11-05",
line_items=[
POLineItem(
item_code="ITEM-N05",
description="Laboratory Grade Ethanol 1L",
ordered_quantity=200,
unit_price_ordered=22.00,
line_total_ordered=4400.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-8850",
po_reference="PO-6300",
receipt_date="2024-11-30",
line_items=[
GRNLineItem(
item_code="ITEM-N05",
quantity_received=0,
accepted_quantity=0,
rejected_quantity=0,
),
],
warehouse_note="Shipment not yet received. GRN opened pending delivery.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-2100",
supplier_name="NorthWind Chemicals",
risk_tier="medium",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.place_on_hold,
correct_exception_type=ExceptionType.missing_receipt,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
],
key_findings=[
"GRN shows 0 units received against 200 billed",
"Shipment has not been delivered or accepted",
"Cannot pay before goods are received",
],
),
)
# -- Task 2c: Over-Receipt (billed more than ordered, received matches ordered) -
# Billed 120 but PO was 100 and GRN accepted 100. Overbilling.
def _build_task_2c() -> CaseData:
return CaseData(
case_id="CASE-2004",
task_id=TaskID.task_2c_over_receipt,
difficulty=Difficulty.moderate,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0220",
supplier_name="Pacific Hardware Co",
supplier_id="SUP-2200",
invoice_date="2024-12-08",
po_reference="PO-6400",
line_items=[
InvoiceLineItem(
item_code="ITEM-P15",
description="Stainless Steel Bolts M10x50 (Box of 100)",
quantity_billed=120,
unit_price_billed=8.50,
line_total_billed=1020.00,
),
],
subtotal=1020.00,
tax=81.60,
total_amount=1101.60,
note="Additional 20 boxes included as courtesy stock.",
),
purchase_order=PurchaseOrder(
po_number="PO-6400",
supplier_id="SUP-2200",
order_date="2024-11-10",
line_items=[
POLineItem(
item_code="ITEM-P15",
description="Stainless Steel Bolts M10x50 (Box of 100)",
ordered_quantity=100,
unit_price_ordered=8.50,
line_total_ordered=850.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-8870",
po_reference="PO-6400",
receipt_date="2024-12-03",
line_items=[
GRNLineItem(
item_code="ITEM-P15",
quantity_received=100,
accepted_quantity=100,
),
],
warehouse_note="Received 100 boxes per PO. No extra stock delivered.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-2200",
supplier_name="Pacific Hardware Co",
risk_tier="medium",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=100.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.place_on_hold,
correct_exception_type=ExceptionType.quantity_mismatch,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
],
key_findings=[
"Invoice bills 120 units but only 100 were ordered and received",
"Overbilling of 20 units (20% over received)",
"Variance far exceeds 5% quantity tolerance",
],
),
)
# -- Task 3b: Price Within Tolerance (should approve) ---------------------
# Price is 2% above PO, which is within the 5% tolerance. Clean approval.
def _build_task_3b() -> CaseData:
return CaseData(
case_id="CASE-3004",
task_id=TaskID.task_3b_within_tolerance,
difficulty=Difficulty.moderate,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0330",
supplier_name="EcoTech Green Supplies",
supplier_id="SUP-3200",
invoice_date="2024-12-12",
po_reference="PO-7200",
line_items=[
InvoiceLineItem(
item_code="ITEM-E08",
description="Recycled Paper Reams (500 sheets)",
quantity_billed=80,
unit_price_billed=20.40,
line_total_billed=1632.00,
),
],
subtotal=1632.00,
tax=130.56,
total_amount=1762.56,
note="Slight price increase due to recycled material costs.",
),
purchase_order=PurchaseOrder(
po_number="PO-7200",
supplier_id="SUP-3200",
order_date="2024-11-18",
line_items=[
POLineItem(
item_code="ITEM-E08",
description="Recycled Paper Reams (500 sheets)",
ordered_quantity=80,
unit_price_ordered=20.00,
line_total_ordered=1600.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-9170",
po_reference="PO-7200",
receipt_date="2024-12-08",
line_items=[
GRNLineItem(item_code="ITEM-E08", quantity_received=80, accepted_quantity=80),
],
warehouse_note="All reams received and stored.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-3200",
supplier_name="EcoTech Green Supplies",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_price",
],
key_findings=[
"Price variance of 2% is within 5% tolerance",
"Quantities match (80 ordered, 80 billed, 80 received)",
"Acceptable per company policy",
],
),
)
# -- Task 3c: Total Amount Mismatch --------------------------------------
# Line-item prices match, but the invoice subtotal is inflated (arithmetic error).
def _build_task_3c() -> CaseData:
return CaseData(
case_id="CASE-3005",
task_id=TaskID.task_3c_total_mismatch,
difficulty=Difficulty.moderate,
max_steps=10,
invoice=Invoice(
invoice_number="INV-2024-0345",
supplier_name="Vanguard Medical Supplies",
supplier_id="SUP-3300",
invoice_date="2024-12-14",
po_reference="PO-7300",
line_items=[
InvoiceLineItem(
item_code="ITEM-V12",
description="Disposable Surgical Masks (Box of 50)",
quantity_billed=500,
unit_price_billed=6.00,
line_total_billed=3000.00,
),
],
subtotal=3500.00,
tax=280.00,
total_amount=3780.00,
note="Bulk order -- standard pricing applied.",
),
purchase_order=PurchaseOrder(
po_number="PO-7300",
supplier_id="SUP-3300",
order_date="2024-11-20",
line_items=[
POLineItem(
item_code="ITEM-V12",
description="Disposable Surgical Masks (Box of 50)",
ordered_quantity=500,
unit_price_ordered=6.00,
line_total_ordered=3000.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-9200",
po_reference="PO-7300",
receipt_date="2024-12-10",
line_items=[
GRNLineItem(item_code="ITEM-V12", quantity_received=500, accepted_quantity=500),
],
warehouse_note="Full shipment received.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-3300",
supplier_name="Vanguard Medical Supplies",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.escalate_for_supervisor_review,
correct_exception_type=ExceptionType.total_amount_mismatch,
acceptable_evidence=[
"inspect_purchase_order",
"compare_totals",
],
key_findings=[
"Invoice subtotal $3,500 does not match line items sum $3,000",
"Invoice subtotal exceeds PO total by $500 (beyond $200 tolerance)",
"Possible arithmetic error or hidden charges",
],
),
)
# -- Task 4b: Corrected Invoice Trap --------------------------------------
# Invoice claims to be a "corrected version" of a prior invoice, but the
# prior invoice was already paid. Still a duplicate.
def _build_task_4b() -> CaseData:
return CaseData(
case_id="CASE-4005",
task_id=TaskID.task_4b_corrected_invoice_trap,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-0601",
supplier_name="Atlas Freight Solutions",
supplier_id="SUP-4300",
invoice_date="2024-12-20",
po_reference="PO-8900",
line_items=[
InvoiceLineItem(
item_code="ITEM-A20",
description="Corrugated Shipping Boxes (Large)",
quantity_billed=300,
unit_price_billed=4.50,
line_total_billed=1350.00,
),
],
subtotal=1350.00,
tax=108.00,
total_amount=1458.00,
note="CORRECTED INVOICE -- replaces INV-2024-0600. Please disregard previous version.",
),
purchase_order=PurchaseOrder(
po_number="PO-8900",
supplier_id="SUP-4300",
order_date="2024-11-22",
line_items=[
POLineItem(
item_code="ITEM-A20",
description="Corrugated Shipping Boxes (Large)",
ordered_quantity=300,
unit_price_ordered=4.50,
line_total_ordered=1350.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-9920",
po_reference="PO-8900",
receipt_date="2024-12-15",
line_items=[
GRNLineItem(item_code="ITEM-A20", quantity_received=300, accepted_quantity=300),
],
warehouse_note="All boxes received per PO.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-4300",
supplier_name="Atlas Freight Solutions",
risk_tier="medium",
duplicate_risk_count=1,
preferred_vendor=True,
escalation_threshold=5000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(
prior_comments=[
"INV-2024-0600 was processed and paid on 2024-12-17.",
],
similar_invoice_refs=["INV-2024-0600"],
processed_invoice_numbers=["INV-2024-0600"],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.reject_invoice,
correct_exception_type=ExceptionType.duplicate_invoice,
acceptable_evidence=[
"check_for_duplicate_invoice",
"inspect_vendor_profile",
],
key_findings=[
"INV-2024-0600 was already processed and paid",
"Current invoice claims to be a 'correction' but has identical amounts",
"Supplier has 1 prior duplicate incident",
],
),
)
# -- Task 4c: Policy Violation (high-value without authorization) ---------
# Invoice total exceeds high-value threshold. Everything else matches,
# but policy requires escalation for high-value invoices.
def _build_task_4c() -> CaseData:
return CaseData(
case_id="CASE-4006",
task_id=TaskID.task_4c_policy_violation,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-0700",
supplier_name="TitanWorks Engineering",
supplier_id="SUP-4400",
invoice_date="2024-12-22",
po_reference="PO-9100",
line_items=[
InvoiceLineItem(
item_code="ITEM-T30",
description="CNC Machined Aluminum Housing Unit",
quantity_billed=100,
unit_price_billed=520.00,
line_total_billed=52000.00,
),
],
subtotal=52000.00,
tax=4160.00,
total_amount=56160.00,
),
purchase_order=PurchaseOrder(
po_number="PO-9100",
supplier_id="SUP-4400",
order_date="2024-12-01",
line_items=[
POLineItem(
item_code="ITEM-T30",
description="CNC Machined Aluminum Housing Unit",
ordered_quantity=100,
unit_price_ordered=520.00,
line_total_ordered=52000.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-9950",
po_reference="PO-9100",
receipt_date="2024-12-18",
line_items=[
GRNLineItem(item_code="ITEM-T30", quantity_received=100, accepted_quantity=100),
],
warehouse_note="All units received. Quality inspection passed.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-4400",
supplier_name="TitanWorks Engineering",
risk_tier="medium",
preferred_vendor=True,
escalation_threshold=40000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=500.0,
high_value_threshold=50000.0,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.escalate_for_supervisor_review,
correct_exception_type=ExceptionType.policy_violation,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_policy_rules",
"inspect_vendor_profile",
],
key_findings=[
"Invoice total $56,160 exceeds high-value threshold of $50,000",
"All quantities and prices match, but policy requires supervisor review",
"Invoice also exceeds vendor escalation threshold of $40,000",
],
),
)
# ===========================================================================
# GENUINELY HARD TASKS
# ===========================================================================
# -- Task 5: Mixed Discrepancy (price over tolerance + partial receipt) ---
# Difficulty: hard | Decision: escalate_for_supervisor_review
# Exception: mixed_discrepancy
#
# This case has TWO simultaneous issues:
# 1. Price variance: $55 billed vs $50 PO (10%, exceeds 5% tolerance)
# 2. Partial receipt: billed 200, only 150 received
# Both "hold" and "escalate" seem reasonable. Per policy, price variance
# exceeding tolerance triggers escalation, which takes priority over hold.
# The agent must read the policy rules and recognize escalation outranks hold.
def _build_task_5() -> CaseData:
return CaseData(
case_id="CASE-5001",
task_id=TaskID.task_5_mixed_discrepancy,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-0850",
supplier_name="GlobalParts Manufacturing",
supplier_id="SUP-5100",
invoice_date="2024-12-20",
po_reference="PO-9500",
line_items=[
InvoiceLineItem(
item_code="ITEM-G40",
description="Precision Titanium Fasteners (Box of 25)",
quantity_billed=200,
unit_price_billed=55.00,
line_total_billed=11000.00,
),
],
subtotal=11000.00,
tax=880.00,
total_amount=11880.00,
note="Price reflects updated material costs. Partial shipment billed in full per contract terms.",
),
purchase_order=PurchaseOrder(
po_number="PO-9500",
supplier_id="SUP-5100",
order_date="2024-11-25",
line_items=[
POLineItem(
item_code="ITEM-G40",
description="Precision Titanium Fasteners (Box of 25)",
ordered_quantity=200,
unit_price_ordered=50.00,
line_total_ordered=10000.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-10200",
po_reference="PO-9500",
receipt_date="2024-12-15",
line_items=[
GRNLineItem(
item_code="ITEM-G40",
quantity_received=150,
accepted_quantity=150,
rejected_quantity=0,
),
],
warehouse_note="Partial shipment. 50 boxes backordered, ETA 2 weeks.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-5100",
supplier_name="GlobalParts Manufacturing",
risk_tier="medium",
preferred_vendor=True,
escalation_threshold=8000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=300.0,
high_value_threshold=50000.0,
),
case_history=CaseHistory(
prior_comments=[
"Supplier has a history of price adjustments on raw material orders.",
],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.escalate_for_supervisor_review,
correct_exception_type=ExceptionType.mixed_discrepancy,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
"compare_price",
"inspect_policy_rules",
],
key_findings=[
"Price variance: $55 billed vs $50 PO (10%, exceeds 5% tolerance)",
"Quantity discrepancy: billed 200 but only 150 received (25% short)",
"Both price escalation and quantity hold conditions triggered",
"Per policy, price variance exceeding tolerance requires escalation",
"Escalation takes priority over hold when both apply",
],
),
)
# -- Task 6: False Positive Duplicate (legitimate second order) -----------
# Difficulty: hard | Decision: approve_for_payment
# Exception: clean_match
#
# This case is designed to trap agents that rely on superficial pattern
# matching. The invoice is from the SAME supplier, for the SAME item,
# with a SIMILAR amount -- but references a DIFFERENT PO number. The case
# history has a processed invoice for PO-9600, but this invoice is for
# PO-9700 (a separate legitimate order). The vendor note mentioning
# "same as last month" is a red herring.
#
# The agent must carefully check PO references rather than assuming
# duplicate based on supplier + amount similarity.
def _build_task_6() -> CaseData:
return CaseData(
case_id="CASE-6001",
task_id=TaskID.task_6_false_positive_duplicate,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-0920",
supplier_name="Metro Cleaning Services",
supplier_id="SUP-6100",
invoice_date="2024-12-28",
po_reference="PO-9700",
line_items=[
InvoiceLineItem(
item_code="ITEM-M15",
description="Monthly Office Cleaning Service - January",
quantity_billed=1,
unit_price_billed=4500.00,
line_total_billed=4500.00,
),
],
subtotal=4500.00,
tax=360.00,
total_amount=4860.00,
note="Monthly service invoice -- same as last month's order.",
),
purchase_order=PurchaseOrder(
po_number="PO-9700",
supplier_id="SUP-6100",
order_date="2024-12-15",
line_items=[
POLineItem(
item_code="ITEM-M15",
description="Monthly Office Cleaning Service - January",
ordered_quantity=1,
unit_price_ordered=4500.00,
line_total_ordered=4500.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-10500",
po_reference="PO-9700",
receipt_date="2024-12-26",
line_items=[
GRNLineItem(
item_code="ITEM-M15",
quantity_received=1,
accepted_quantity=1,
),
],
warehouse_note="Service confirmed completed for January cycle.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-6100",
supplier_name="Metro Cleaning Services",
risk_tier="low",
duplicate_risk_count=0,
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(
prior_comments=[
"Monthly recurring service contract. New PO issued each month.",
],
processed_invoice_numbers=[
"INV-2024-0880",
],
similar_invoice_refs=[],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"check_for_duplicate_invoice",
],
key_findings=[
"Invoice references PO-9700, which is a separate order from prior PO-9600",
"Prior invoice INV-2024-0880 was for a different PO (different month)",
"Quantities and prices match PO exactly",
"No actual duplicate -- this is a legitimate recurring service invoice",
"Vendor has 0 duplicate risk incidents",
],
),
)
# -- Task 7: Retroactive Price Adjustment -----------------------------------
# Difficulty: hard | Decision: escalate_for_supervisor_review | Exception: price_mismatch
# Vendor had a contract price change effective 2024-11-01, but the PO was issued
# at the old price on 2024-10-20. The invoice uses the NEW (higher) price.
# Agent must notice the temporal mismatch: PO predates the price change, so the
# PO price is the agreed-upon price. The invoice price exceeds tolerance.
# Tests temporal reasoning and policy awareness.
def _build_task_7() -> CaseData:
return CaseData(
case_id="CASE-7001",
task_id=TaskID.task_7_retroactive_price,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-1150",
supplier_name="Precision Parts Ltd",
supplier_id="SUP-7100",
invoice_date="2024-11-10",
po_reference="PO-7500",
line_items=[
InvoiceLineItem(
item_code="ITEM-PP01",
description="CNC Bearing Assembly Type-R",
quantity_billed=200,
unit_price_billed=47.50,
line_total_billed=9500.00,
),
InvoiceLineItem(
item_code="ITEM-PP02",
description="Hydraulic Seal Kit Grade-A",
quantity_billed=500,
unit_price_billed=12.80,
line_total_billed=6400.00,
),
],
subtotal=15900.00,
tax=1272.00,
total_amount=17172.00,
note="Prices reflect updated catalog effective 2024-11-01.",
),
purchase_order=PurchaseOrder(
po_number="PO-7500",
supplier_id="SUP-7100",
order_date="2024-10-20",
line_items=[
POLineItem(
item_code="ITEM-PP01",
description="CNC Bearing Assembly Type-R",
ordered_quantity=200,
unit_price_ordered=42.00,
line_total_ordered=8400.00,
),
POLineItem(
item_code="ITEM-PP02",
description="Hydraulic Seal Kit Grade-A",
ordered_quantity=500,
unit_price_ordered=11.50,
line_total_ordered=5750.00,
),
],
payment_terms="Net 45",
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-12500",
po_reference="PO-7500",
receipt_date="2024-11-05",
line_items=[
GRNLineItem(
item_code="ITEM-PP01",
quantity_received=200,
accepted_quantity=200,
),
GRNLineItem(
item_code="ITEM-PP02",
quantity_received=500,
accepted_quantity=500,
),
],
warehouse_note="All items received in good condition. Full order.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-7100",
supplier_name="Precision Parts Ltd",
risk_tier="medium",
duplicate_risk_count=0,
preferred_vendor=True,
escalation_threshold=15000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=500.0,
duplicate_check_enabled=True,
high_value_threshold=50000.0,
),
case_history=CaseHistory(
prior_comments=[
"Vendor announced price increase effective 2024-11-01.",
"PO-7500 was placed on 2024-10-20 at the old catalog prices.",
"Contract terms: PO price governs unless amended before shipment.",
],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.escalate_for_supervisor_review,
correct_exception_type=ExceptionType.price_mismatch,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_price",
"inspect_policy_rules",
"inspect_vendor_profile",
],
key_findings=[
"ITEM-PP01 billed at $47.50 vs PO price $42.00 (13.1% variance)",
"ITEM-PP02 billed at $12.80 vs PO price $11.50 (11.3% variance)",
"Both exceed 5% price tolerance",
"PO was issued 2024-10-20, before price change effective 2024-11-01",
"Per contract terms, PO price governs unless PO is amended",
"Invoice total $17,172 exceeds vendor escalation threshold $15,000",
],
),
)
# -- Task 8: Adversarial Split-Invoice Pattern --------------------------------
# Difficulty: hard | Decision: escalate_for_supervisor_review | Exception: policy_violation
# Supplier splits a large order into multiple smaller invoices to stay below
# the auto-approval threshold ($50,000). Each individual invoice looks clean,
# but case history reveals a pattern of split invoicing from this vendor.
# Agent must check vendor profile (high duplicate risk count) and case history
# to detect the pattern. Tests cross-case pattern recognition.
def _build_task_8() -> CaseData:
return CaseData(
case_id="CASE-8001",
task_id=TaskID.task_8_split_invoice_pattern,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-2210",
supplier_name="Global Tech Supplies",
supplier_id="SUP-8200",
invoice_date="2024-12-01",
po_reference="PO-8800",
line_items=[
InvoiceLineItem(
item_code="ITEM-GT10",
description="Enterprise Server Rack Unit 42U",
quantity_billed=3,
unit_price_billed=14500.00,
line_total_billed=43500.00,
),
],
subtotal=43500.00,
tax=3480.00,
total_amount=46980.00,
note="Shipment 3 of 3. Final batch for data center buildout.",
),
purchase_order=PurchaseOrder(
po_number="PO-8800",
supplier_id="SUP-8200",
order_date="2024-11-15",
line_items=[
POLineItem(
item_code="ITEM-GT10",
description="Enterprise Server Rack Unit 42U",
ordered_quantity=3,
unit_price_ordered=14500.00,
line_total_ordered=43500.00,
),
],
payment_terms="Net 30",
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-15200",
po_reference="PO-8800",
receipt_date="2024-11-28",
line_items=[
GRNLineItem(
item_code="ITEM-GT10",
quantity_received=3,
accepted_quantity=3,
),
],
warehouse_note="Final batch received. All 9 rack units now in inventory.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-8200",
supplier_name="Global Tech Supplies",
risk_tier="high",
duplicate_risk_count=4,
preferred_vendor=False,
escalation_threshold=40000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=500.0,
duplicate_check_enabled=True,
high_value_threshold=50000.00,
mandatory_escalation_above=100000.00,
),
case_history=CaseHistory(
prior_comments=[
"ALERT: Vendor SUP-8200 has submitted 3 invoices this month "
"for similar items across 3 separate POs (PO-8600, PO-8700, PO-8800).",
"Each invoice is just under the $50,000 auto-approval threshold.",
"Combined value: $46,200 + $47,800 + $46,980 = $140,980.",
"Finance team flagged potential invoice-splitting pattern.",
"Vendor is non-preferred with 4 prior risk incidents.",
],
similar_invoice_refs=["INV-2024-2180", "INV-2024-2195"],
processed_invoice_numbers=["INV-2024-2180", "INV-2024-2195"],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.escalate_for_supervisor_review,
correct_exception_type=ExceptionType.policy_violation,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"inspect_vendor_profile",
"inspect_policy_rules",
"check_for_duplicate_invoice",
],
key_findings=[
"Invoice $46,980 is just under auto-approval threshold of $50,000",
"3 similar invoices from same vendor total $140,980",
"Vendor is high-risk tier with 4 prior risk incidents",
"Vendor is non-preferred",
"Case history explicitly flags split-invoice pattern concern",
"Combined value exceeds mandatory escalation threshold of $100,000",
"Individual prices and quantities match PO -- no line-level discrepancy",
],
),
)
# -- Task 9: Clean Invoice from High-Risk Vendor ----------------------------
# Difficulty: hard | Decision: approve_for_payment | Exception: clean_match
# Every document matches perfectly. But the vendor is HIGH RISK with 5 prior
# incidents and is non-preferred. The trap: agents escalate or reject based
# on vendor risk alone. Per policy, risk tier affects escalation thresholds
# for discrepancies, not clean invoices. No discrepancy = approve.
def _build_task_9() -> CaseData:
return CaseData(
case_id="CASE-9001",
task_id=TaskID.task_9_clean_from_risky_vendor,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-3300",
supplier_name="Delta Industrial Components",
supplier_id="SUP-9300",
invoice_date="2024-12-10",
po_reference="PO-9200",
line_items=[
InvoiceLineItem(
item_code="ITEM-DC01",
description="Steel Flanged Coupling 6-inch",
quantity_billed=50,
unit_price_billed=85.00,
line_total_billed=4250.00,
),
InvoiceLineItem(
item_code="ITEM-DC02",
description="Gasket Kit Industrial Grade",
quantity_billed=200,
unit_price_billed=12.50,
line_total_billed=2500.00,
),
],
subtotal=6750.00,
tax=540.00,
total_amount=7290.00,
),
purchase_order=PurchaseOrder(
po_number="PO-9200",
supplier_id="SUP-9300",
order_date="2024-11-25",
line_items=[
POLineItem(
item_code="ITEM-DC01",
description="Steel Flanged Coupling 6-inch",
ordered_quantity=50,
unit_price_ordered=85.00,
line_total_ordered=4250.00,
),
POLineItem(
item_code="ITEM-DC02",
description="Gasket Kit Industrial Grade",
ordered_quantity=200,
unit_price_ordered=12.50,
line_total_ordered=2500.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-16000",
po_reference="PO-9200",
receipt_date="2024-12-05",
line_items=[
GRNLineItem(
item_code="ITEM-DC01",
quantity_received=50,
accepted_quantity=50,
),
GRNLineItem(
item_code="ITEM-DC02",
quantity_received=200,
accepted_quantity=200,
),
],
warehouse_note="Full order received. Quality inspection passed.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-9300",
supplier_name="Delta Industrial Components",
risk_tier="high",
duplicate_risk_count=5,
preferred_vendor=False,
escalation_threshold=5000.00,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(
prior_comments=[
"Vendor under enhanced monitoring due to past billing errors.",
"Last 3 invoices from this vendor were all clean and processed normally.",
"Risk tier remains 'high' pending annual vendor review in Q1 2025.",
],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
"compare_price",
"inspect_vendor_profile",
"inspect_policy_rules",
],
key_findings=[
"All quantities match exactly: 50 couplings and 200 gasket kits",
"All prices match PO exactly: $85.00 and $12.50",
"Vendor is high-risk but no actual discrepancy exists",
"Risk tier affects escalation thresholds, not clean invoices",
"Last 3 invoices from this vendor were processed normally",
"No policy rule mandates rejection of clean invoices from high-risk vendors",
],
),
)
# -- Task 10: Rounding Error False Alarm ------------------------------------
# Difficulty: hard | Decision: approve_for_payment | Exception: clean_match
# Invoice total is $0.01 off from calculated sum of line items due to
# per-line rounding. All line items match PO exactly. The trivial rounding
# difference is well within any tolerance. Trap: compare_totals reports a
# "mismatch" finding and agents escalate over one cent.
def _build_task_10() -> CaseData:
return CaseData(
case_id="CASE-10001",
task_id=TaskID.task_10_rounding_false_alarm,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-4410",
supplier_name="Kensington Paper & Print",
supplier_id="SUP-10400",
invoice_date="2024-12-15",
po_reference="PO-10100",
line_items=[
InvoiceLineItem(
item_code="ITEM-KP01",
description="A4 Premium Copy Paper (case of 10 reams)",
quantity_billed=33,
unit_price_billed=42.99,
line_total_billed=1418.67,
),
InvoiceLineItem(
item_code="ITEM-KP02",
description="Color Laser Toner Cartridge Black",
quantity_billed=7,
unit_price_billed=189.99,
line_total_billed=1329.93,
),
InvoiceLineItem(
item_code="ITEM-KP03",
description="Binding Covers Glossy A4 (pack of 100)",
quantity_billed=15,
unit_price_billed=28.33,
line_total_billed=424.95,
),
],
subtotal=3173.55,
tax=253.88,
total_amount=3427.43,
),
purchase_order=PurchaseOrder(
po_number="PO-10100",
supplier_id="SUP-10400",
order_date="2024-12-01",
line_items=[
POLineItem(
item_code="ITEM-KP01",
description="A4 Premium Copy Paper (case of 10 reams)",
ordered_quantity=33,
unit_price_ordered=42.99,
line_total_ordered=1418.67,
),
POLineItem(
item_code="ITEM-KP02",
description="Color Laser Toner Cartridge Black",
ordered_quantity=7,
unit_price_ordered=189.99,
line_total_ordered=1329.93,
),
POLineItem(
item_code="ITEM-KP03",
description="Binding Covers Glossy A4 (pack of 100)",
ordered_quantity=15,
unit_price_ordered=28.33,
line_total_ordered=424.95,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-17500",
po_reference="PO-10100",
receipt_date="2024-12-10",
line_items=[
GRNLineItem(
item_code="ITEM-KP01",
quantity_received=33,
accepted_quantity=33,
),
GRNLineItem(
item_code="ITEM-KP02",
quantity_received=7,
accepted_quantity=7,
),
GRNLineItem(
item_code="ITEM-KP03",
quantity_received=15,
accepted_quantity=15,
),
],
),
vendor_profile=VendorProfile(
supplier_id="SUP-10400",
supplier_name="Kensington Paper & Print",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=100.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
"compare_price",
"compare_totals",
"inspect_policy_rules",
],
key_findings=[
"All quantities match exactly",
"All unit prices match PO exactly",
"Subtotal rounding difference of $0.01 is within tolerance",
"Total tolerance is $100.00 -- a $0.01 difference is negligible",
"Rounding is a normal artifact of multi-line calculation",
"Preferred vendor with low risk tier",
],
),
)
# -- Task 11: Authorized Overship -------------------------------------------
# Difficulty: hard | Decision: approve_for_payment | Exception: clean_match
# GRN shows 110 units received vs 100 ordered. Invoice bills for 110.
# Case history contains explicit authorization for up to 10% overship.
# Trap: agents see quantity mismatch and hold. But the overship was
# pre-authorized, making this a clean match per the authorization.
def _build_task_11() -> CaseData:
return CaseData(
case_id="CASE-11001",
task_id=TaskID.task_11_authorized_overship,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-5520",
supplier_name="Pacific Coast Fasteners",
supplier_id="SUP-11500",
invoice_date="2024-12-18",
po_reference="PO-11300",
line_items=[
InvoiceLineItem(
item_code="ITEM-PF01",
description="M8 Hex Bolt Grade 8.8 Zinc Plated (box of 100)",
quantity_billed=110,
unit_price_billed=24.50,
line_total_billed=2695.00,
),
],
subtotal=2695.00,
tax=215.60,
total_amount=2910.60,
note="Quantity includes authorized 10% overship per PO amendment.",
),
purchase_order=PurchaseOrder(
po_number="PO-11300",
supplier_id="SUP-11500",
order_date="2024-12-01",
line_items=[
POLineItem(
item_code="ITEM-PF01",
description="M8 Hex Bolt Grade 8.8 Zinc Plated (box of 100)",
ordered_quantity=100,
unit_price_ordered=24.50,
line_total_ordered=2450.00,
),
],
payment_terms="Net 30",
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-18800",
po_reference="PO-11300",
receipt_date="2024-12-12",
line_items=[
GRNLineItem(
item_code="ITEM-PF01",
quantity_received=110,
accepted_quantity=110,
),
],
warehouse_note="110 boxes received. Overship authorized per buyer memo dated 2024-12-02.",
),
vendor_profile=VendorProfile(
supplier_id="SUP-11500",
supplier_name="Pacific Coast Fasteners",
risk_tier="low",
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=500.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(
prior_comments=[
"PO-11300 Amendment 1 (2024-12-02): Authorized up to 10% overship "
"at the same unit price to ensure inventory buffer for Q1.",
"Buyer confirmed acceptance of up to 110 boxes at $24.50/box.",
"Warehouse confirmed receipt of full 110 boxes in good condition.",
],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_quantity",
"inspect_policy_rules",
],
key_findings=[
"Billed 110 vs ordered 100 -- 10% overship",
"PO Amendment 1 explicitly authorized up to 10% overship",
"Buyer confirmed acceptance of 110 units at $24.50",
"GRN confirms all 110 accepted in good condition",
"Unit price matches PO exactly",
"Overship is within the authorized range",
],
),
)
# -- Task 12: Corrected Invoice Resubmission --------------------------------
# Difficulty: hard | Decision: approve_for_payment | Exception: clean_match
# Invoice number INV-2024-0500-R1 looks like a duplicate of INV-2024-0500.
# Case history shows the original was rejected for a $500 overcharge, and
# this is the corrected resubmission at the correct PO price. All documents
# now match. Trap: duplicate check flags the similar invoice number, and
# agents reject as duplicate without reading the correction context.
def _build_task_12() -> CaseData:
return CaseData(
case_id="CASE-12001",
task_id=TaskID.task_12_corrected_resubmission,
difficulty=Difficulty.hard,
max_steps=12,
invoice=Invoice(
invoice_number="INV-2024-0500-R1",
supplier_name="Westfield Electronics",
supplier_id="SUP-12600",
invoice_date="2024-12-20",
po_reference="PO-12400",
line_items=[
InvoiceLineItem(
item_code="ITEM-WE01",
description="Industrial Ethernet Switch 24-Port PoE+",
quantity_billed=5,
unit_price_billed=1250.00,
line_total_billed=6250.00,
),
InvoiceLineItem(
item_code="ITEM-WE02",
description="SFP+ 10G Transceiver Module",
quantity_billed=20,
unit_price_billed=85.00,
line_total_billed=1700.00,
),
],
subtotal=7950.00,
tax=636.00,
total_amount=8586.00,
note="Corrected invoice. Replaces INV-2024-0500 which was rejected for pricing error.",
),
purchase_order=PurchaseOrder(
po_number="PO-12400",
supplier_id="SUP-12600",
order_date="2024-12-01",
line_items=[
POLineItem(
item_code="ITEM-WE01",
description="Industrial Ethernet Switch 24-Port PoE+",
ordered_quantity=5,
unit_price_ordered=1250.00,
line_total_ordered=6250.00,
),
POLineItem(
item_code="ITEM-WE02",
description="SFP+ 10G Transceiver Module",
ordered_quantity=20,
unit_price_ordered=85.00,
line_total_ordered=1700.00,
),
],
),
goods_receipt_note=GoodsReceiptNote(
grn_number="GRN-20100",
po_reference="PO-12400",
receipt_date="2024-12-10",
line_items=[
GRNLineItem(
item_code="ITEM-WE01",
quantity_received=5,
accepted_quantity=5,
),
GRNLineItem(
item_code="ITEM-WE02",
quantity_received=20,
accepted_quantity=20,
),
],
),
vendor_profile=VendorProfile(
supplier_id="SUP-12600",
supplier_name="Westfield Electronics",
risk_tier="medium",
duplicate_risk_count=1,
preferred_vendor=True,
),
company_policy=CompanyPolicy(
quantity_tolerance_pct=5.0,
price_tolerance_pct=5.0,
total_tolerance_amt=200.0,
duplicate_check_enabled=True,
),
case_history=CaseHistory(
prior_comments=[
"INV-2024-0500 was rejected on 2024-12-15: switches billed at $1,350 "
"instead of PO price $1,250. Vendor acknowledged error.",
"Vendor issued corrected invoice INV-2024-0500-R1 with correct pricing.",
"AP team confirmed: process INV-2024-0500-R1 as the replacement invoice.",
],
processed_invoice_numbers=[],
similar_invoice_refs=["INV-2024-0500"],
),
ground_truth=GroundTruth(
correct_decision=DecisionType.approve_for_payment,
correct_exception_type=ExceptionType.clean_match,
acceptable_evidence=[
"inspect_purchase_order",
"inspect_goods_receipt_note",
"compare_price",
"check_for_duplicate_invoice",
"inspect_policy_rules",
],
key_findings=[
"INV-2024-0500-R1 is the corrected version of rejected INV-2024-0500",
"Original invoice was rejected for $100/unit price overcharge on switches",
"Corrected invoice has the correct PO price of $1,250 per switch",
"All quantities and prices now match PO exactly",
"AP team explicitly approved processing the replacement invoice",
"Not a duplicate -- it is a correction of a rejected invoice",
],
),
)