""" 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", ], ), )