[
  {
    "id": "cb45048d-c17b-4f06-9331-ff74e996e824",
    "sort_order": 0,
    "rubric_text": "In folder `1099/2025/`, file `W9_Whitfield_Consulting_Group.pdf` must exist.",
    "verifier_code": "from pathlib import Path\n\ndef verify(workspace_path, external_services_path=None):\n    target = Path(workspace_path) / '1099' / '2025' / 'W9_Whitfield_Consulting_Group.pdf'\n    if target.exists():\n        return {'pass': True, 'score': 1.0, 'feedback': 'Found W9_Whitfield_Consulting_Group.pdf in 1099/2025/'}\n    # Also try case-insensitive search\n    folder = Path(workspace_path) / '1099' / '2025'\n    if folder.exists():\n        matches = [f for f in folder.iterdir() if 'whitfield' in f.name.lower() and f.suffix.lower() == '.pdf']\n        if matches:\n            return {'pass': True, 'score': 1.0, 'feedback': f'Found Whitfield W-9 PDF: {matches[0].name}'}\n    return {'pass': False, 'score': 0.0, 'feedback': 'W9_Whitfield_Consulting_Group.pdf not found in 1099/2025/'}",
    "criterion_type": "expected_output"
  },
  {
    "id": "d5570533-815a-4694-b65d-8d62187d7c86",
    "sort_order": 1,
    "rubric_text": "In folder `1099/2025/`, file `W9_Harrison_Cole_LLP.pdf` must exist.",
    "verifier_code": "from pathlib import Path\n\ndef verify(workspace_path, external_services_path=None):\n    target = Path(workspace_path) / '1099' / '2025' / 'W9_Harrison_Cole_LLP.pdf'\n    if target.exists():\n        return {'pass': True, 'score': 1.0, 'feedback': 'Found W9_Harrison_Cole_LLP.pdf in 1099/2025/'}\n    folder = Path(workspace_path) / '1099' / '2025'\n    if folder.exists():\n        matches = [f for f in folder.iterdir() if 'harrison' in f.name.lower() and f.suffix.lower() == '.pdf']\n        if matches:\n            return {'pass': True, 'score': 1.0, 'feedback': f'Found Harrison & Cole W-9 PDF: {matches[0].name}'}\n    return {'pass': False, 'score': 0.0, 'feedback': 'W9_Harrison_Cole_LLP.pdf not found in 1099/2025/'}",
    "criterion_type": "expected_output"
  },
  {
    "id": "a9da8354-1545-447d-b665-bcd6ac61998d",
    "sort_order": 2,
    "rubric_text": "In `vendor_master.xlsx`, a row for Whitfield Consulting Group, Inc. must exist with: Tax ID 23-7891045, Address containing 450 Independence Mall West, Contact Email d.whitfield@whitfieldcg.com, Payment Terms 2/10 Net 30, Bank Routing 321081669, Bank Account 7720194583, 1099 Reportable Yes, 1099 Type NEC, Status Active, Created Date 09/04/2025",
    "verifier_code": "from pathlib import Path\nimport openpyxl\nimport re\nfrom datetime import datetime\n\ndef normalize_date(value):\n    \"\"\"Return 'MM/DD/YYYY' for any date-like value, or '' if unparseable.\"\"\"\n    if value is None:\n        return ''\n    if isinstance(value, datetime):\n        return value.strftime('%m/%d/%Y')\n    s = str(value).strip()\n    for fmt in ('%m/%d/%Y', '%m/%d/%y', '%Y-%m-%d', '%m-%d-%Y',\n                '%B %d, %Y', '%B %d %Y', '%b %d, %Y', '%b %d %Y',\n                '%d-%b-%Y', '%Y/%m/%d'):\n        try:\n            return datetime.strptime(s, fmt).strftime('%m/%d/%Y')\n        except ValueError:\n            continue\n    return ''\n\ndef verify(workspace_path, external_services_path=None):\n    candidates = list(Path(workspace_path).rglob('vendor_master.xlsx'))\n    if not candidates:\n        return {'pass': False, 'score': 0.0, 'feedback': 'vendor_master.xlsx not found in workspace.'}\n    wb = openpyxl.load_workbook(candidates[0])\n    ws = wb.active\n    headers = [str(c.value).strip().lower() if c.value else '' for c in next(ws.iter_rows(min_row=1, max_row=1))]\n\n    found_row_raw = None\n    found_row_idx = None\n    for idx, row in enumerate(ws.iter_rows(min_row=2, max_row=ws.max_row), start=2):\n        row_values = [cell.value for cell in row]\n        row_str = ' '.join(str(v) for v in row_values if v is not None).lower()\n        if 'whitfield' in row_str and 'consult' in row_str:\n            found_row_raw = row_values\n            found_row_idx = idx\n            break\n    if found_row_raw is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No row for Whitfield Consulting Group found in vendor_master.xlsx.'}\n\n    row_values = []\n    for v in found_row_raw:\n        if v is None:\n            row_values.append('')\n        elif isinstance(v, datetime):\n            row_values.append(v.strftime('%m/%d/%Y'))\n        else:\n            row_values.append(str(v).strip())\n    row_str = ' '.join(row_values)\n    row_lower = row_str.lower()\n\n    missing = []\n\n    # Tax ID: 23-7891045\n    row_str_no_sep = row_str.replace('-', '').replace(' ', '')\n    if '23-7891045' not in row_str and '237891045' not in row_str_no_sep:\n        missing.append('Tax ID (expected: 23-7891045)')\n\n    # Address containing 450 Independence Mall West\n    if '450 independence mall west' not in row_lower:\n        if not ('450' in row_lower and 'independence' in row_lower and 'mall' in row_lower and 'west' in row_lower):\n            missing.append('Address containing 450 Independence Mall West')\n\n    # Contact Email\n    if 'd.whitfield@whitfieldcg.com' not in row_lower:\n        missing.append('Contact Email (expected: d.whitfield@whitfieldcg.com)')\n\n    # Payment Terms: 2/10 Net 30\n    terms_found = False\n    for val in row_values:\n        vl = val.lower().replace(' ', '')\n        if '2/10net30' in vl or '2/10,net30' in vl or '2/10-net30' in vl:\n            terms_found = True\n            break\n        if '2/10 net 30' in val.lower():\n            terms_found = True\n            break\n        if '2/10' in val and 'net' in val.lower() and '30' in val:\n            terms_found = True\n            break\n    if not terms_found:\n        missing.append('Payment Terms (expected: 2/10 Net 30)')\n\n    # Bank Routing: 321081669\n    if '321081669' not in row_str:\n        missing.append('Bank Routing (expected: 321081669)')\n\n    # Bank Account: 7720194583\n    if '7720194583' not in row_str:\n        missing.append('Bank Account (expected: 7720194583)')\n\n    # 1099 Reportable: Yes\n    reportable_found = False\n    for val in row_values:\n        if val.strip().lower() in ('yes', 'y', 'true', '1'):\n            reportable_found = True\n            break\n    if not reportable_found:\n        missing.append('1099 Reportable (expected: Yes)')\n\n    # 1099 Type: NEC\n    nec_found = False\n    for val in row_values:\n        if val.strip().upper() == 'NEC' or 'nec' in val.lower().split():\n            nec_found = True\n            break\n    if not nec_found:\n        # Also check as part of a combined value\n        for val in row_values:\n            if 'nec' in val.lower():\n                nec_found = True\n                break\n    if not nec_found:\n        missing.append('1099 Type (expected: NEC)')\n\n    # Status: Active\n    active_found = False\n    for val in row_values:\n        if val.strip().lower() == 'active':\n            active_found = True\n            break\n    if not active_found:\n        missing.append('Status (expected: Active)')\n\n    # Created Date: 09/04/2025\n    date_found = False\n    for v in found_row_raw:\n        nd = normalize_date(v)\n        if nd == '09/04/2025':\n            date_found = True\n            break\n    if not date_found:\n        # Also check string representations in row_values\n        for val in row_values:\n            if '09/04/2025' in val or '9/4/2025' in val or '2025-09-04' in val:\n                date_found = True\n                break\n    if not date_found:\n        missing.append('Created Date (expected: 09/04/2025)')\n\n    if missing:\n        return {\n            'pass': False,\n            'score': max(0.0, 1.0 - len(missing) * 0.1),\n            'feedback': f'Whitfield Consulting Group row found at row {found_row_idx} but missing/incorrect fields: {missing}. Row data: {row_str[:500]}'\n        }\n    return {\n        'pass': True,\n        'score': 1.0,\n        'feedback': f'Whitfield Consulting Group, Inc. row found at row {found_row_idx} with all required fields: Tax ID (23-7891045), Address (450 Independence Mall West), Email (d.whitfield@whitfieldcg.com), Payment Terms (2/10 Net 30), Bank Routing (321081669), Bank Account (7720194583), 1099 Reportable (Yes), 1099 Type (NEC), Status (Active), Created Date (09/04/2025).'\n    }\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "970d0671-7df2-4425-8f51-254cf30b82b4",
    "sort_order": 3,
    "rubric_text": "In `vendor_master.xlsx`, a row for Harrison & Cole LLP must exist with: Tax ID 23-7654321, Address containing '1200 Market Street', Contact Email s.harrison@harrisoncolellp.com, Payment Terms 2/10 Net 30, Bank Routing 031100209, Bank Account 7742851903, 1099 Reportable Yes, 1099 Type NEC, Status Active, Created Date 09/04/2025",
    "verifier_code": "from pathlib import Path\nimport openpyxl\nimport re\nfrom datetime import datetime\n\ndef normalize_date(value):\n    \"\"\"Return 'MM/DD/YYYY' for any date-like value, or '' if unparseable.\"\"\"\n    if value is None:\n        return ''\n    if isinstance(value, datetime):\n        return value.strftime('%m/%d/%Y')\n    s = str(value).strip()\n    # Handle cases like '09/04/25' or '9/4/2025' etc.\n    for fmt in ('%m/%d/%Y', '%m/%d/%y', '%Y-%m-%d', '%m-%d-%Y',\n                '%B %d, %Y', '%B %d %Y', '%b %d, %Y', '%b %d %Y',\n                '%d-%b-%Y', '%Y/%m/%d', '%m/%d/%Y %H:%M:%S',\n                '%Y-%m-%d %H:%M:%S', '%m-%d-%y', '%d/%m/%Y'):\n        try:\n            return datetime.strptime(s, fmt).strftime('%m/%d/%Y')\n        except ValueError:\n            continue\n    return ''\n\ndef verify(workspace_path, external_services_path=None):\n    # Search for vendor_master.xlsx broadly\n    candidates = list(Path(workspace_path).rglob('vendor_master.xlsx'))\n    if not candidates:\n        candidates = list(Path(workspace_path).rglob('*vendor_master*.xlsx'))\n    if not candidates:\n        # Try any xlsx that might contain vendor master data\n        candidates = list(Path(workspace_path).rglob('*.xlsx'))\n        candidates = [c for c in candidates if 'vendor' in c.name.lower()]\n    if not candidates:\n        return {'pass': False, 'score': 0.0, 'feedback': 'vendor_master.xlsx not found in workspace.'}\n\n    wb = openpyxl.load_workbook(candidates[0])\n    ws = wb.active\n\n    # Try to find header row to map columns\n    headers = []\n    header_row_idx = 1\n    for row in ws.iter_rows(min_row=1, max_row=3, values_only=False):\n        row_vals = [str(cell.value).strip().lower() if cell.value else '' for cell in row]\n        # Check if this looks like a header row\n        if any(h in ' '.join(row_vals) for h in ['vendor', 'name', 'tax', 'status']):\n            headers = row_vals\n            header_row_idx = row[0].row\n            break\n\n    found_row = None\n    for row in ws.iter_rows(min_row=2, values_only=True):\n        row_str = ' '.join(str(v) for v in row if v is not None).lower()\n        if 'harrison' in row_str and 'cole' in row_str:\n            found_row = row\n            break\n\n    if found_row is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No row for Harrison & Cole LLP found in vendor_master.xlsx.'}\n\n    row_str = ' '.join(str(v) for v in found_row if v is not None)\n    row_lower = row_str.lower()\n\n    missing = []\n\n    # Tax ID - accept with or without dash\n    if '23-7654321' not in row_lower and '237654321' not in row_lower:\n        missing.append('Tax ID 23-7654321')\n\n    # Address containing 1200 Market Street\n    if '1200 market street' not in row_lower and '1200 market st' not in row_lower:\n        missing.append('Address 1200 Market Street')\n\n    # Contact Email\n    if 's.harrison@harrisoncolellp.com' not in row_lower:\n        missing.append('Contact Email s.harrison@harrisoncolellp.com')\n\n    # Payment Terms - be generous with variations\n    terms_found = False\n    for variant in ['2/10 net 30', '2/10 net30', '2/10net30', '2/10, net 30',\n                     '2/10-net 30', '2% 10 net 30', '2/10 net-30', '2/10net 30',\n                     '2/10  net 30', '2%10 net 30', '2% 10, net 30']:\n        if variant in row_lower:\n            terms_found = True\n            break\n    if not terms_found:\n        missing.append('Payment Terms 2/10 Net 30')\n\n    # Bank Routing\n    if '031100209' not in row_lower:\n        missing.append('Bank Routing 031100209')\n\n    # Bank Account\n    if '7742851903' not in row_lower:\n        missing.append('Bank Account 7742851903')\n\n    # 1099 Reportable Yes\n    yes_found = False\n    for v in found_row:\n        if v is not None and str(v).strip().lower() in ('yes', 'y', 'true', '1'):\n            yes_found = True\n            break\n    if not yes_found:\n        missing.append('1099 Reportable Yes')\n\n    # 1099 Type NEC\n    if 'nec' not in row_lower:\n        missing.append('1099 Type NEC')\n\n    # Status Active\n    if 'active' not in row_lower:\n        missing.append('Status Active')\n\n    # Created Date 09/04/2025 - be generous with date formats\n    date_found = False\n    for v in found_row:\n        nd = normalize_date(v)\n        if nd == '09/04/2025':\n            date_found = True\n            break\n        # Also check string representation directly\n        if v is not None:\n            vs = str(v).strip()\n            if vs in ('09/04/2025', '9/4/2025', '2025-09-04', '09-04-2025',\n                       'September 4, 2025', 'Sep 4, 2025', '09/04/25', '9/4/25'):\n                date_found = True\n                break\n    if not date_found:\n        missing.append('Created Date 09/04/2025')\n\n    if missing:\n        return {\n            'pass': False,\n            'score': max(0.1, 1.0 - len(missing) * 0.1),\n            'feedback': f'Harrison & Cole row found but missing/incorrect fields: {missing}. Row data: {row_str[:500]}'\n        }\n    return {\n        'pass': True,\n        'score': 1.0,\n        'feedback': f'Harrison & Cole LLP row found with all required fields in vendor_master.xlsx. Row data: {row_str[:500]}'\n    }\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "61f3c9bd-e56d-4d07-937e-7c5ca2857a79",
    "sort_order": 4,
    "rubric_text": "In `vendor_master.xlsx`, NO new row for ClearPath Talent Solutions must be added (duplicate vendor — Tax ID 58-8901234 matches existing vendor Premier Staffing Group V-00008).",
    "verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n    candidates = list(Path(workspace_path).rglob('vendor_master.xlsx'))\n    if not candidates:\n        return {'pass': True, 'score': 1.0, 'feedback': 'vendor_master.xlsx not found; no ClearPath row added.'}\n    wb = openpyxl.load_workbook(candidates[0])\n    ws = wb.active\n    for row in ws.iter_rows(min_row=2, values_only=True):\n        row_str = ' '.join(str(v) for v in row if v is not None).lower()\n        if 'clearpath' in row_str or ('58-8901234' in row_str and 'premier' not in row_str):\n            return {'pass': False, 'score': 0.0, 'feedback': f'ClearPath Talent Solutions should NOT be added to vendor_master.xlsx (duplicate vendor). Found row: {row_str[:200]}'}\n    return {'pass': True, 'score': 1.0, 'feedback': 'No ClearPath Talent Solutions row found in vendor_master.xlsx — correct, duplicate vendor blocked.'}",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "50e0d257-697e-49f9-9cd3-3d7e544f9092",
    "sort_order": 5,
    "rubric_text": "In ap_ledger.xlsx Invoice Register tab, a row for Whitfield Consulting Group invoice WCG-2025-0087 must exist with: Invoice Date 08/28/2025, PO Number blank/none, Category Non-PO, Amount 4950.00, GL Code 1000-120-6100, Status Entered, Payment Terms 2/10 Net 30, Due Date 09/27/2025, Entered By T. Okonkwo, Entered Date 09/04/2025.",
    "verifier_code": "from pathlib import Path\nimport openpyxl\nfrom datetime import datetime\n\ndef normalize_date(value):\n    \"\"\"Return 'MM/DD/YYYY' for any date-like value, or '' if unparseable.\"\"\"\n    if value is None:\n        return ''\n    if isinstance(value, datetime):\n        return value.strftime('%m/%d/%Y')\n    s = str(value).strip()\n    for fmt in ('%m/%d/%Y', '%m/%d/%y', '%Y-%m-%d', '%m-%d-%Y',\n                '%B %d, %Y', '%B %d %Y', '%b %d, %Y', '%b %d %Y',\n                '%d-%b-%Y', '%Y/%m/%d'):\n        try:\n            return datetime.strptime(s, fmt).strftime('%m/%d/%Y')\n        except ValueError:\n            continue\n    return ''\n\ndef normalize_amount(value):\n    \"\"\"Return a float for any amount-like value, or None if unparseable.\"\"\"\n    if value is None:\n        return None\n    if isinstance(value, (int, float)):\n        return float(value)\n    s = str(value).strip().replace('$', '').replace(',', '')\n    try:\n        return float(s)\n    except ValueError:\n        return None\n\ndef norm_header(h):\n    return str(h).strip().lower() if h is not None else ''\n\ndef verify(workspace_path, external_services_path=None):\n    candidates = list(Path(workspace_path).rglob('ap_ledger.xlsx'))\n    if not candidates:\n        candidates = list(Path(workspace_path).rglob('*.xlsx'))\n        candidates = [c for c in candidates if 'ap' in c.stem.lower() or 'ledger' in c.stem.lower()]\n    if not candidates:\n        return {'pass': False, 'score': 0.0, 'feedback': 'ap_ledger.xlsx not found in workspace.'}\n\n    wb = openpyxl.load_workbook(candidates[0])\n\n    # Find the Invoice Register sheet\n    target_ws = None\n    for name in wb.sheetnames:\n        nl = name.lower()\n        if 'invoice' in nl and 'register' in nl:\n            target_ws = wb[name]\n            break\n    if target_ws is None:\n        for name in wb.sheetnames:\n            nl = name.lower()\n            if 'invoice' in nl or 'register' in nl:\n                target_ws = wb[name]\n                break\n    if target_ws is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'Invoice Register tab not found in ap_ledger.xlsx.'}\n\n    # Read header row\n    rows = list(target_ws.iter_rows(values_only=True))\n    if not rows:\n        return {'pass': False, 'score': 0.0, 'feedback': 'Invoice Register tab is empty.'}\n    header = [norm_header(h) for h in rows[0]]\n\n    # Required columns (plus Invoice Number column to locate the row)\n    required_cols = [\n        'Invoice Number',\n        'Invoice Date', 'PO Number', 'Category', 'Amount', 'GL Code',\n        'Status', 'Payment Terms', 'Due Date', 'Entered By', 'Entered Date',\n    ]\n    col_idx = {}\n    for col in required_cols:\n        try:\n            col_idx[col] = header.index(col.lower())\n        except ValueError:\n            return {'pass': False, 'score': 0.0,\n                    'feedback': f'Column \"{col}\" not found in Invoice Register header. Header row: {rows[0]}'}\n\n    # Locate the WCG-2025-0087 row by Invoice Number column\n    inv_col = col_idx['Invoice Number']\n    found_row = None\n    for r in rows[1:]:\n        if r is None or inv_col >= len(r):\n            continue\n        cell = r[inv_col]\n        if cell is not None and str(cell).strip().lower() == 'wcg-2025-0087':\n            found_row = r\n            break\n    if found_row is None:\n        return {'pass': False, 'score': 0.0,\n                'feedback': 'No row with Invoice Number WCG-2025-0087 found in Invoice Register.'}\n\n    def cell(col_name):\n        idx = col_idx[col_name]\n        return found_row[idx] if idx < len(found_row) else None\n\n    missing = []\n\n    # Invoice Date: 08/28/2025\n    if normalize_date(cell('Invoice Date')) != '08/28/2025':\n        missing.append(f'Invoice Date should be 08/28/2025, got {cell(\"Invoice Date\")!r}')\n\n    # PO Number: blank/none\n    po_val = cell('PO Number')\n    if po_val is not None and str(po_val).strip() != '':\n        missing.append(f'PO Number should be blank, got {po_val!r}')\n\n    # Category: Non-PO\n    cat_val = cell('Category')\n    cat_norm = str(cat_val).strip().lower().replace(' ', '').replace('_', '') if cat_val is not None else ''\n    if cat_norm != 'non-po' and cat_norm != 'nonpo':\n        missing.append(f'Category should be Non-PO, got {cat_val!r}')\n\n    # Amount: 4950.00\n    amt = normalize_amount(cell('Amount'))\n    if amt is None or abs(amt - 4950.00) > 0.005:\n        missing.append(f'Amount should be 4950.00, got {cell(\"Amount\")!r}')\n\n    # GL Code: 1000-120-6100\n    gl_val = cell('GL Code')\n    if gl_val is None or str(gl_val).strip() != '1000-120-6100':\n        missing.append(f'GL Code should be 1000-120-6100, got {gl_val!r}')\n\n    # Status: Entered (exact, case-insensitive)\n    status_val = cell('Status')\n    if status_val is None or str(status_val).strip().lower() != 'entered':\n        missing.append(f'Status should be Entered, got {status_val!r}')\n\n    # Payment Terms: 2/10 Net 30\n    pt_val = cell('Payment Terms')\n    pt_norm = str(pt_val).strip().lower().replace(',', '').replace('  ', ' ') if pt_val is not None else ''\n    accepted_terms = {'2/10 net 30', '2/10net30', '2% 10 net 30'}\n    if pt_norm not in accepted_terms:\n        missing.append(f'Payment Terms should be 2/10 Net 30, got {pt_val!r}')\n\n    # Due Date: 09/27/2025\n    if normalize_date(cell('Due Date')) != '09/27/2025':\n        missing.append(f'Due Date should be 09/27/2025, got {cell(\"Due Date\")!r}')\n\n    # Entered By: exactly 'T. Okonkwo'\n    eb_val = cell('Entered By')\n    if eb_val is None or str(eb_val).strip() != 'T. Okonkwo':\n        missing.append(f'Entered By should be exactly \"T. Okonkwo\", got {eb_val!r}')\n\n    # Entered Date: 09/04/2025\n    if normalize_date(cell('Entered Date')) != '09/04/2025':\n        missing.append(f'Entered Date should be 09/04/2025, got {cell(\"Entered Date\")!r}')\n\n    if missing:\n        return {'pass': False, 'score': 0.5,\n                'feedback': 'WCG-2025-0087 row found but fields incorrect: ' + '; '.join(missing)}\n    return {'pass': True, 'score': 1.0,\n            'feedback': 'WCG-2025-0087 row in Invoice Register has all required fields with correct values.'}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "4d825a27-1baf-4c9c-a7ac-e06b45e1a18b",
    "sort_order": 6,
    "rubric_text": "In ap_ledger.xlsx Invoice Register tab, a row for Harrison & Cole LLP invoice HC-2025-0001 must exist with: Invoice Date 09/04/2025, PO Number blank/none, Category Non-PO, Amount 5000.00, GL Code 1000-120-6100, Status Entered, Payment Terms 2/10 Net 30, Due Date 10/04/2025, Entered By T. Okonkwo, Entered Date 09/04/2025.",
    "verifier_code": "from pathlib import Path\nimport openpyxl\nfrom datetime import datetime\n\ndef normalize_date(value):\n    \"\"\"Return 'MM/DD/YYYY' for any date-like value, or '' if unparseable.\"\"\"\n    if value is None:\n        return ''\n    if isinstance(value, datetime):\n        return value.strftime('%m/%d/%Y')\n    s = str(value).strip()\n    for fmt in ('%m/%d/%Y', '%m/%d/%y', '%Y-%m-%d', '%m-%d-%Y',\n                '%B %d, %Y', '%B %d %Y', '%b %d, %Y', '%b %d %Y',\n                '%d-%b-%Y', '%Y/%m/%d'):\n        try:\n            return datetime.strptime(s, fmt).strftime('%m/%d/%Y')\n        except ValueError:\n            continue\n    return ''\n\ndef normalize_amount(value):\n    \"\"\"Return a float for any number-like value, or None if unparseable.\"\"\"\n    if value is None:\n        return None\n    if isinstance(value, (int, float)):\n        return float(value)\n    s = str(value).strip().replace('$', '').replace(',', '')\n    try:\n        return float(s)\n    except ValueError:\n        return None\n\ndef verify(workspace_path, external_services_path=None):\n    # Find ap_ledger.xlsx\n    candidates = list(Path(workspace_path).rglob('ap_ledger.xlsx'))\n    if not candidates:\n        return {'pass': False, 'score': 0.0, 'feedback': 'ap_ledger.xlsx not found in workspace.'}\n\n    wb = openpyxl.load_workbook(candidates[0])\n\n    # Find the Invoice Register tab\n    target_ws = None\n    for name in wb.sheetnames:\n        nl = name.lower()\n        if 'invoice' in nl and 'register' in nl:\n            target_ws = wb[name]\n            break\n    if target_ws is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'Invoice Register tab not found in ap_ledger.xlsx.'}\n\n    # Build header -> column index map (case-insensitive, trimmed)\n    headers = {}\n    for i, cell in enumerate(target_ws[1]):\n        if cell.value is not None:\n            headers[str(cell.value).strip().lower()] = i\n\n    # Required column headers\n    required_cols = [\n        'invoice date', 'po number', 'category', 'amount', 'gl code',\n        'status', 'payment terms', 'due date', 'entered by', 'entered date'\n    ]\n    # We also need an Invoice Number column to find the right row\n    invoice_num_col = None\n    for key in ('invoice number', 'invoice #', 'invoice no', 'invoice no.'):\n        if key in headers:\n            invoice_num_col = headers[key]\n            break\n\n    if invoice_num_col is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'Could not find an Invoice Number column in Invoice Register header.'}\n\n    missing_cols = [c for c in required_cols if c not in headers]\n    if missing_cols:\n        return {'pass': False, 'score': 0.0, 'feedback': f'Missing required columns in Invoice Register: {missing_cols}'}\n\n    # Locate the HC-2025-0001 row\n    found_row = None\n    for row in target_ws.iter_rows(min_row=2, values_only=True):\n        if invoice_num_col < len(row) and row[invoice_num_col] is not None:\n            if str(row[invoice_num_col]).strip().lower() == 'hc-2025-0001':\n                found_row = row\n                break\n\n    if found_row is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No row for invoice HC-2025-0001 found in Invoice Register.'}\n\n    def cell(col_name):\n        idx = headers[col_name]\n        if idx >= len(found_row):\n            return None\n        return found_row[idx]\n\n    def cell_str(col_name):\n        v = cell(col_name)\n        return '' if v is None else str(v).strip()\n\n    missing = []\n\n    # Invoice Date == 09/04/2025\n    if normalize_date(cell('invoice date')) != '09/04/2025':\n        missing.append(f\"Invoice Date should be 09/04/2025, found: {cell('invoice date')!r}\")\n\n    # PO Number blank/none\n    po_val = cell('po number')\n    if po_val is not None and str(po_val).strip().lower() not in ('', 'none', 'n/a', '-'):\n        missing.append(f\"PO Number should be blank/none, found: {po_val!r}\")\n\n    # Category == Non-PO\n    cat = cell_str('category').lower().replace(' ', '').replace('-', '')\n    if cat != 'nonpo':\n        missing.append(f\"Category should be Non-PO, found: {cell_str('category')!r}\")\n\n    # Amount == 5000.00\n    amt = normalize_amount(cell('amount'))\n    if amt is None or abs(amt - 5000.00) > 0.001:\n        missing.append(f\"Amount should be 5000.00, found: {cell('amount')!r}\")\n\n    # GL Code == 1000-120-6100\n    if cell_str('gl code') != '1000-120-6100':\n        missing.append(f\"GL Code should be 1000-120-6100, found: {cell_str('gl code')!r}\")\n\n    # Status == Entered\n    if cell_str('status').lower() != 'entered':\n        missing.append(f\"Status should be Entered, found: {cell_str('status')!r}\")\n\n    # Payment Terms == 2/10 Net 30 (allow minor spacing/comma variation)\n    pt = cell_str('payment terms').lower().replace(',', '').replace(' ', '')\n    if pt != '2/10net30':\n        missing.append(f\"Payment Terms should be 2/10 Net 30, found: {cell_str('payment terms')!r}\")\n\n    # Due Date == 10/04/2025\n    if normalize_date(cell('due date')) != '10/04/2025':\n        missing.append(f\"Due Date should be 10/04/2025, found: {cell('due date')!r}\")\n\n    # Entered By == exactly 'T. Okonkwo'\n    if cell_str('entered by') != 'T. Okonkwo':\n        missing.append(f\"Entered By should be exactly 'T. Okonkwo', found: {cell_str('entered by')!r}\")\n\n    # Entered Date == 09/04/2025\n    if normalize_date(cell('entered date')) != '09/04/2025':\n        missing.append(f\"Entered Date should be 09/04/2025, found: {cell('entered date')!r}\")\n\n    if missing:\n        return {'pass': False, 'score': 0.5, 'feedback': f'HC-2025-0001 row found but has issues: {missing}'}\n\n    return {'pass': True, 'score': 1.0, 'feedback': 'HC-2025-0001 row in Invoice Register has all required field values.'}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "b909c1a5-256a-45b2-a355-4bc1554e47b1",
    "sort_order": 7,
    "rubric_text": "In `ap_ledger.xlsx` Invoice Register tab, NO row for ClearPath Talent Solutions or invoice CPT-2025-0412 must exist (duplicate vendor — entire pipeline blocked).",
    "verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n    candidates = list(Path(workspace_path).rglob('ap_ledger.xlsx'))\n    if not candidates:\n        return {'pass': True, 'score': 1.0, 'feedback': 'ap_ledger.xlsx not found; no ClearPath invoice entered.'}\n    wb = openpyxl.load_workbook(candidates[0])\n    for ws in wb.worksheets:\n        for row in ws.iter_rows(min_row=2, values_only=True):\n            row_str = ' '.join(str(v) for v in row if v is not None).lower()\n            if 'clearpath' in row_str or 'cpt-2025-0412' in row_str:\n                return {'pass': False, 'score': 0.0, 'feedback': f'ClearPath invoice CPT-2025-0412 should NOT be entered in ap_ledger.xlsx. Found: {row_str[:200]}'}\n    return {'pass': True, 'score': 1.0, 'feedback': 'No ClearPath Talent Solutions / CPT-2025-0412 entry in ap_ledger.xlsx — correct.'}",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "e1811786-91b9-483d-b2f4-06dab61c2cef",
    "sort_order": 8,
    "rubric_text": "In folder `Invoices/2025-09/`, invoice file for Whitfield Consulting Group (WCG-2025-0087) must exist (e.g., INV-V-00038-WCG-2025-0087.pdf or similar).",
    "verifier_code": "from pathlib import Path\n\ndef verify(workspace_path, external_services_path=None):\n    folder = Path(workspace_path) / 'Invoices' / '2025-09'\n    if not folder.exists():\n        # Try alternate casing\n        for alt in Path(workspace_path).rglob('2025-09'):\n            if alt.is_dir():\n                folder = alt\n                break\n        else:\n            return {'pass': False, 'score': 0.0, 'feedback': 'Invoices/2025-09/ folder not found in workspace.'}\n    matches = [f for f in folder.iterdir() if 'wcg-2025-0087' in f.name.lower() or ('whitfield' in f.name.lower() and f.suffix.lower() == '.pdf')]\n    if matches:\n        return {'pass': True, 'score': 1.0, 'feedback': f'Found Whitfield invoice file: {[f.name for f in matches]}'}\n    return {'pass': False, 'score': 0.0, 'feedback': f'No invoice file for WCG-2025-0087 found in Invoices/2025-09/. Files present: {[f.name for f in folder.iterdir()]}'}",
    "criterion_type": "expected_output"
  },
  {
    "id": "3f78f653-6bab-4c33-98b4-9bca1b8448fb",
    "sort_order": 9,
    "rubric_text": "In folder `Invoices/2025-09/`, invoice file for Harrison & Cole LLP (HC-2025-0001) must exist (e.g., INV-V-00039-HC-2025-0001.pdf or similar).",
    "verifier_code": "from pathlib import Path\n\ndef verify(workspace_path, external_services_path=None):\n    folder = Path(workspace_path) / 'Invoices' / '2025-09'\n    if not folder.exists():\n        for alt in Path(workspace_path).rglob('2025-09'):\n            if alt.is_dir():\n                folder = alt\n                break\n        else:\n            return {'pass': False, 'score': 0.0, 'feedback': 'Invoices/2025-09/ folder not found in workspace.'}\n    matches = [f for f in folder.iterdir() if 'hc-2025-0001' in f.name.lower() or ('harrison' in f.name.lower() and f.suffix.lower() == '.pdf')]\n    if matches:\n        return {'pass': True, 'score': 1.0, 'feedback': f'Found Harrison & Cole invoice file: {[f.name for f in matches]}'}\n    return {'pass': False, 'score': 0.0, 'feedback': f'No invoice file for HC-2025-0001 found in Invoices/2025-09/. Files present: {[f.name for f in folder.iterdir()]}'}",
    "criterion_type": "expected_output"
  },
  {
    "id": "4e0d1eea-4c10-47d0-a7d4-ceb532b6377f",
    "sort_order": 10,
    "rubric_text": "In folder `Invoices/2025-09/`, NO invoice file for ClearPath Talent Solutions (CPT-2025-0412) must exist.",
    "verifier_code": "from pathlib import Path\n\ndef verify(workspace_path, external_services_path=None):\n    folder = Path(workspace_path) / 'Invoices' / '2025-09'\n    if not folder.exists():\n        for alt in Path(workspace_path).rglob('2025-09'):\n            if alt.is_dir():\n                folder = alt\n                break\n        else:\n            return {'pass': True, 'score': 1.0, 'feedback': 'Invoices/2025-09/ folder not found; no ClearPath invoice filed.'}\n    matches = [f for f in folder.iterdir() if 'clearpath' in f.name.lower() or 'cpt-2025-0412' in f.name.lower() or 'cpt' in f.name.lower()]\n    if matches:\n        return {'pass': False, 'score': 0.0, 'feedback': f'ClearPath invoice should NOT be filed in Invoices/2025-09/. Found: {[f.name for f in matches]}'}\n    return {'pass': True, 'score': 1.0, 'feedback': 'No ClearPath Talent Solutions invoice found in Invoices/2025-09/ — correct.'}",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "251ff8e0-bb26-4e6f-9d77-cbd264f4d756",
    "sort_order": 11,
    "rubric_text": "In mailbox sent mail (mailbox.json), an approval email for Whitfield Consulting Group invoice WCG-2025-0087 must be sent to jennifer.walsh@meridianpartners.com and maria.santos@meridianpartners.com with CC ap.team@meridianpartners.com, subject containing 'AP APPROVAL REQUIRED' and 'WCG-2025-0087', body containing vendor name, vendor ID (V-00038 or V-00039), invoice number WCG-2025-0087, invoice date 08/28/2025 (or similar format), amount $4,950.00, GL Code 1000-120-6100, and '3 business days'.",
    "verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No external_services_path provided; cannot check mailbox for approval email.'}\n    mailbox_path = Path(external_services_path) / 'mailbox.json'\n    if not mailbox_path.exists():\n        return {'pass': False, 'score': 0.0, 'feedback': 'mailbox.json not found.'}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    sent_emails = [e for e in data.get('emails', []) if e.get('folder', '').lower() == 'sent']\n    if not sent_emails:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No sent emails found in mailbox.'}\n\n    best_match = None\n    best_missing = None\n\n    for email in sent_emails:\n        subject = (email.get('subject') or '').lower()\n        body = (email.get('body_text') or email.get('body') or '').lower()\n        combined = subject + ' ' + body\n\n        # Collect all address fields flexibly\n        to_raw = email.get('to_addr') or email.get('to') or ''\n        if isinstance(to_raw, list):\n            to_addr = ' '.join(str(x) for x in to_raw).lower()\n        else:\n            to_addr = str(to_raw).lower()\n\n        cc_raw = email.get('cc_addr') or email.get('cc') or ''\n        if isinstance(cc_raw, list):\n            cc_addr = ' '.join(str(x) for x in cc_raw).lower()\n        else:\n            cc_addr = str(cc_raw).lower()\n\n        all_addr = to_addr + ' ' + cc_addr\n\n        # Must reference WCG-2025-0087 somewhere in subject or body\n        if 'wcg-2025-0087' not in combined:\n            continue\n\n        # Must have AP APPROVAL REQUIRED in subject (lenient with casing/spacing)\n        if 'ap approval required' not in subject:\n            continue\n\n        missing = []\n\n        # Check To recipients\n        if 'jennifer.walsh@meridianpartners.com' not in all_addr:\n            missing.append('jennifer.walsh@meridianpartners.com in To')\n        if 'maria.santos@meridianpartners.com' not in all_addr:\n            missing.append('maria.santos@meridianpartners.com in To')\n\n        # Check CC - be lenient, also accept if it's in To\n        if 'ap.team@meridianpartners.com' not in all_addr:\n            missing.append('ap.team@meridianpartners.com in CC')\n\n        # Body checks\n        # Vendor name - accept various forms\n        if 'whitfield' not in combined:\n            missing.append('vendor name Whitfield')\n\n        # Invoice number in body\n        if 'wcg-2025-0087' not in body:\n            missing.append('invoice number WCG-2025-0087 in body')\n\n        # Invoice date - accept multiple date formats for 08/28/2025\n        date_found = False\n        date_patterns = ['08/28/2025', '8/28/2025', '08-28-2025', '2025-08-28', 'august 28, 2025', 'august 28 2025', '28 august 2025', '08/28/25', '8/28/25']\n        for dp in date_patterns:\n            if dp in combined:\n                date_found = True\n                break\n        if not date_found:\n            missing.append('invoice date 08/28/2025 (or similar)')\n\n        # Amount - accept various formats for $4,950.00\n        amount_found = False\n        amount_patterns = ['4,950', '4950.00', '4950', '$4,950', '4,950.00']\n        for ap in amount_patterns:\n            if ap in combined:\n                amount_found = True\n                break\n        if not amount_found:\n            missing.append('amount $4,950.00')\n\n        # GL Code - accept with or without spaces around dashes\n        gl_found = False\n        gl_patterns = ['1000-120-6100']\n        for gp in gl_patterns:\n            if gp in combined:\n                gl_found = True\n                break\n        if not gl_found:\n            missing.append('GL Code 1000-120-6100')\n\n        # 3 business days - be lenient\n        if '3 business day' not in combined and 'three business day' not in combined:\n            missing.append('3 business days response deadline')\n\n        # Vendor ID\n        if not ('v-00038' in combined or 'v-00039' in combined):\n            missing.append('Vendor ID V-00038 or V-00039')\n\n        if not missing:\n            return {'pass': True, 'score': 1.0, 'feedback': 'Whitfield Consulting Group approval email found with all required fields.'}\n\n        # Track best match (fewest missing items)\n        if best_missing is None or len(missing) < len(best_missing):\n            best_missing = missing\n            best_match = email\n\n    if best_match is not None:\n        return {'pass': False, 'score': max(0.1, 0.5 - 0.05 * len(best_missing)), 'feedback': f'Whitfield approval email found but missing: {best_missing}'}\n\n    return {'pass': False, 'score': 0.0, 'feedback': 'No sent approval email for WCG-2025-0087 found in mailbox.'}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "3e47971f-c177-4411-b691-0f09f3dd9d2f",
    "sort_order": 12,
    "rubric_text": "In mailbox sent mail (mailbox.json), an approval email for Harrison & Cole LLP invoice HC-2025-0001 must be sent to jennifer.walsh@meridianpartners.com and maria.santos@meridianpartners.com with CC ap.team@meridianpartners.com, subject containing 'AP APPROVAL REQUIRED' and 'HC-2025-0001', body containing vendor name, vendor ID (V-00038 or V-00039), invoice number HC-2025-0001, invoice date 09/04/2025 (or similar format), amount $5,000.00, GL Code 1000-120-6100, and '3 business days'.",
    "verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No external_services_path provided; cannot check mailbox for approval email.'}\n    mailbox_path = Path(external_services_path) / 'mailbox.json'\n    if not mailbox_path.exists():\n        return {'pass': False, 'score': 0.0, 'feedback': 'mailbox.json not found.'}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    sent_emails = [e for e in data.get('emails', []) if e.get('folder', '').lower() == 'sent']\n    if not sent_emails:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No sent emails found in mailbox.json.'}\n\n    best_match = None\n    best_missing = None\n\n    for email in sent_emails:\n        subject = (email.get('subject') or '').lower()\n        body = (email.get('body_text') or email.get('body') or '').lower()\n        # Combine subject and body for broad searching\n        full_text = subject + ' ' + body\n\n        # Normalize to/cc fields - could be string or list\n        to_raw = email.get('to_addr') or email.get('to') or ''\n        cc_raw = email.get('cc_addr') or email.get('cc') or ''\n        if isinstance(to_raw, list):\n            to_addr = ' '.join([str(x) for x in to_raw]).lower()\n        else:\n            to_addr = str(to_raw).lower()\n        if isinstance(cc_raw, list):\n            cc_addr = ' '.join([str(x) for x in cc_raw]).lower()\n        else:\n            cc_addr = str(cc_raw).lower()\n\n        # Also check combined to+cc for loose matching\n        all_recipients = to_addr + ' ' + cc_addr\n\n        # Must reference HC-2025-0001 somewhere\n        if 'hc-2025-0001' not in full_text:\n            continue\n\n        # Must have AP APPROVAL REQUIRED in subject (loose: accept with or without extra words)\n        if 'ap approval required' not in subject:\n            continue\n\n        missing = []\n\n        # Check To addresses - be loose, accept in either To or CC\n        if 'jennifer.walsh@meridianpartners.com' not in to_addr:\n            # Check if it's anywhere in recipients\n            if 'jennifer.walsh@meridianpartners.com' not in all_recipients:\n                missing.append('jennifer.walsh@meridianpartners.com in To')\n        if 'maria.santos@meridianpartners.com' not in to_addr:\n            if 'maria.santos@meridianpartners.com' not in all_recipients:\n                missing.append('maria.santos@meridianpartners.com in To')\n\n        # Check CC\n        if 'ap.team@meridianpartners.com' not in cc_addr:\n            if 'ap.team@meridianpartners.com' not in all_recipients:\n                missing.append('ap.team@meridianpartners.com in CC')\n\n        # Check vendor name - be very loose\n        if 'harrison' not in full_text and 'cole' not in full_text:\n            missing.append('vendor name Harrison & Cole')\n\n        # Check invoice number in body\n        if 'hc-2025-0001' not in body:\n            missing.append('invoice number HC-2025-0001 in body')\n\n        # Check invoice date - accept many formats\n        date_found = False\n        date_patterns = ['09/04/2025', '9/4/2025', '2025-09-04', 'september 4, 2025', 'september 4 2025',\n                         'sep 4, 2025', 'sep 04, 2025', 'sep 4 2025', 'sep 04 2025',\n                         '09-04-2025', '04/09/2025', '09/04/25', '9/4/25', '2025/09/04']\n        for dp in date_patterns:\n            if dp in body:\n                date_found = True\n                break\n        if not date_found:\n            missing.append('invoice date 09/04/2025 (or similar)')\n\n        # Check amount - accept many formats: 5,000, 5000, $5,000, $5000, 5,000.00, 5000.00\n        amount_found = False\n        amount_patterns = ['5,000', '5000', '$5,000', '$5000']\n        for ap in amount_patterns:\n            if ap in body:\n                amount_found = True\n                break\n        if not amount_found:\n            missing.append('amount $5,000.00')\n\n        # Check GL Code - accept with or without spaces/dashes\n        gl_found = False\n        gl_patterns = ['1000-120-6100', '1000 120 6100', '10001206100']\n        for gp in gl_patterns:\n            if gp in body:\n                gl_found = True\n                break\n        if not gl_found:\n            missing.append('GL Code 1000-120-6100')\n\n        # Check 3 business days - be loose\n        biz_day_found = False\n        biz_patterns = ['3 business day', '3 business-day', 'three business day', '3-business-day',\n                        'three business-day', '3 working day', 'three working day']\n        for bp in biz_patterns:\n            if bp in body:\n                biz_day_found = True\n                break\n        if not biz_day_found:\n            missing.append('3 business days response deadline')\n\n        # Check vendor ID\n        if 'v-00038' not in body and 'v-00039' not in body:\n            # Also check full_text in case it's in subject\n            if 'v-00038' not in full_text and 'v-00039' not in full_text:\n                missing.append('Vendor ID V-00038 or V-00039')\n\n        if not missing:\n            return {'pass': True, 'score': 1.0, 'feedback': 'Harrison & Cole LLP approval email found with all required fields: correct To/CC recipients, subject with AP APPROVAL REQUIRED and HC-2025-0001, body with vendor name, vendor ID, invoice number, date, amount $5,000.00, GL Code 1000-120-6100, and 3 business days deadline.'}\n\n        # Track best match (fewest missing items)\n        if best_missing is None or len(missing) < len(best_missing):\n            best_missing = missing\n            best_match = email\n\n    if best_match is not None:\n        score = max(0.1, 1.0 - (len(best_missing) * 0.1))\n        return {'pass': False, 'score': score, 'feedback': f'Harrison & Cole approval email found but missing: {best_missing}'}\n\n    return {'pass': False, 'score': 0.0, 'feedback': 'No sent approval email for HC-2025-0001 found in mailbox. Looked for email with subject containing AP APPROVAL REQUIRED and HC-2025-0001.'}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "8a358c9a-e182-4438-b0be-9351c0351bb8",
    "sort_order": 13,
    "rubric_text": "In mailbox sent mail (mailbox.json), NO approval email for ClearPath Talent Solutions or invoice CPT-2025-0412 must exist.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': True, 'score': 1.0, 'feedback': 'No external_services_path provided; skipping mailbox check.'}\n    mailbox_path = Path(external_services_path) / 'mailbox.json'\n    if not mailbox_path.exists():\n        return {'pass': True, 'score': 1.0, 'feedback': 'mailbox.json not found; no ClearPath email sent.'}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    sent_emails = [e for e in data.get('emails', []) if e.get('folder', '').lower() == 'sent']\n    for email in sent_emails:\n        subject = (email.get('subject') or '').lower()\n        body = (email.get('body_text') or '').lower()\n        if 'clearpath' in subject or 'clearpath' in body or 'cpt-2025-0412' in subject or 'cpt-2025-0412' in body:\n            return {'pass': False, 'score': 0.0, 'feedback': f'Approval email for ClearPath should NOT have been sent. Found email with subject: {email.get(\"subject\")}'}\n    return {'pass': True, 'score': 1.0, 'feedback': 'No approval email for ClearPath Talent Solutions found in sent mail — correct.'}",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "e23b68e1-2412-4b5d-9e87-fbe7636fcad8",
    "sort_order": 14,
    "rubric_text": "In Slack (slack_data.json), channel #approvals must contain a new vendor notification post with exact text: `NEW VENDOR ADDED: Whitfield Consulting Group, Inc., V-00038 or V-00039 (either is acceptable), 1099 Reportable: YES`",
    "verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No external_services_path provided; cannot check Slack data.'}\n    slack_path = Path(external_services_path) / 'slack_data.json'\n    if not slack_path.exists():\n        return {'pass': False, 'score': 0.0, 'feedback': 'slack_data.json not found.'}\n    with open(slack_path) as f:\n        data = json.load(f)\n    \n    # Find the #approvals channel\n    approvals_channel_id = None\n    for cid, cinfo in data.get('channels', {}).items():\n        if cinfo.get('name', '').lower() == 'approvals':\n            approvals_channel_id = cid\n            break\n    \n    if approvals_channel_id is None:\n        return {'pass': False, 'score': 0.0, 'feedback': '#approvals channel not found in slack_data.json.'}\n    \n    messages = data.get('messages', {}).get(approvals_channel_id, [])\n    \n    # The rubric says the message should contain:\n    # NEW VENDOR ADDED: Whitfield Consulting Group, Inc., V-00038 or V-00039, 1099 Reportable: YES\n    # Be loose in matching - check for key fragments case-insensitively\n    expected_fragments_base = [\n        'new vendor added',\n        'whitfield consulting group',\n    ]\n    valid_vendor_ids = ['v-00038', 'v-00039']\n    \n    # Accept various forms of 1099 reportable yes\n    reportable_patterns = [\n        '1099 reportable: yes',\n        '1099 reportable:yes',\n        '1099-reportable: yes',\n        '1099 reportable - yes',\n        '1099 reportable yes',\n        '1099reportable: yes',\n        '1099reportable:yes',\n        '1099: yes',\n        '1099 yes',\n    ]\n    \n    for msg in messages:\n        text = (msg.get('text') or '').lower()\n        # Check base fragments\n        if all(frag in text for frag in expected_fragments_base):\n            has_vendor_id = any(vid in text for vid in valid_vendor_ids)\n            if has_vendor_id:\n                # Split on \"new vendor added\" to isolate each notification\n                # This prevents Harrison & Cole's \"YES\" from satisfying Whitfield's check\n                segments = re.split(r'new vendor added', text)\n                for segment in segments:\n                    if 'whitfield consulting group' in segment:\n                        has_reportable = any(rp in segment for rp in reportable_patterns)\n                        if has_reportable:\n                            return {'pass': True, 'score': 1.0, 'feedback': f'Found Whitfield new vendor notification in #approvals: {msg.get(\"text\")}'}\n                        else:\n                            return {'pass': False, 'score': 0.0, 'feedback': f'Found Whitfield notification but 1099 Reportable is not YES. Message: {msg.get(\"text\")}'}\n    \n    # Collect all messages in the channel for debugging\n    all_texts = [msg.get('text', '') for msg in messages]\n    return {'pass': False, 'score': 0.0, 'feedback': f'No Whitfield Consulting Group new vendor notification found in #approvals channel. Expected message containing: NEW VENDOR ADDED, Whitfield Consulting Group, Inc., V-00038 or V-00039, 1099 Reportable: YES. Messages found in #approvals: {all_texts}'}",
    "criterion_type": "expected_output"
  },
  {
    "id": "995de855-5315-444e-8765-584fad8962e4",
    "sort_order": 15,
    "rubric_text": "In Slack (slack_data.json), channel #approvals must contain a new vendor notification post with exact text: `NEW VENDOR ADDED: Harrison & Cole LLP, V-00038 or V-00039 (either is acceptable), 1099 Reportable: YES`",
    "verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No external_services_path provided; cannot check Slack data.'}\n    slack_path = Path(external_services_path) / 'slack_data.json'\n    if not slack_path.exists():\n        return {'pass': False, 'score': 0.0, 'feedback': 'slack_data.json not found.'}\n    with open(slack_path) as f:\n        data = json.load(f)\n\n    approvals_channel_id = None\n    for cid, cinfo in data.get('channels', {}).items():\n        if cinfo.get('name', '').lower() == 'approvals':\n            approvals_channel_id = cid\n            break\n\n    if approvals_channel_id is None:\n        return {'pass': False, 'score': 0.0, 'feedback': '#approvals channel not found in slack_data.json.'}\n\n    messages = data.get('messages', {}).get(approvals_channel_id, [])\n\n    all_texts = []\n    for msg in messages:\n        text = msg.get('text') or ''\n        all_texts.append(text)\n        lower_text = text.lower()\n\n        # Check for key components loosely\n        has_new_vendor = 'new vendor' in lower_text\n        has_harrison_cole = 'harrison' in lower_text and 'cole' in lower_text\n        has_vendor_id = 'v-00038' in lower_text or 'v-00039' in lower_text\n        has_1099_yes = ('1099' in lower_text and 'yes' in lower_text)\n\n        if has_new_vendor and has_harrison_cole and has_vendor_id and has_1099_yes:\n            return {\n                'pass': True,\n                'score': 1.0,\n                'feedback': f'Found Harrison & Cole new vendor notification in #approvals: {text}'\n            }\n\n    # Provide detailed failure feedback\n    feedback_parts = ['No Harrison & Cole LLP new vendor notification found in #approvals.']\n    feedback_parts.append('Expected text containing: NEW VENDOR ADDED: Harrison & Cole LLP, V-00038 or V-00039, 1099 Reportable: YES')\n    if all_texts:\n        feedback_parts.append(f'Found {len(all_texts)} message(s) in #approvals. Sample texts: {all_texts[:5]}')\n    else:\n        feedback_parts.append('No messages found in #approvals channel.')\n\n    return {'pass': False, 'score': 0.0, 'feedback': ' | '.join(feedback_parts)}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "1671ba3e-b81e-461b-ac11-e133196369fb",
    "sort_order": 16,
    "rubric_text": "In Slack (slack_data.json), channel #vendor-management must contain a duplicate vendor notification with exact text: `DUPLICATE VENDOR SUSPECTED: ClearPath Talent Solutions matches Tax ID of existing vendor Premier Staffing Group, V-00008`",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No external_services_path provided; cannot check Slack data.'}\n    slack_path = Path(external_services_path) / 'slack_data.json'\n    if not slack_path.exists():\n        return {'pass': False, 'score': 0.0, 'feedback': 'slack_data.json not found.'}\n    with open(slack_path) as f:\n        data = json.load(f)\n\n    # Find #vendor-management channel (loose matching)\n    target_channel_id = None\n    for cid, cinfo in data.get('channels', {}).items():\n        cname = cinfo.get('name', '').lower().replace('-', '').replace('_', '').replace(' ', '')\n        if 'vendor' in cname and 'management' in cname:\n            target_channel_id = cid\n            break\n\n    if target_channel_id is None:\n        return {'pass': False, 'score': 0.0, 'feedback': '#vendor-management channel not found in slack_data.json.'}\n\n    messages = data.get('messages', {}).get(target_channel_id, [])\n\n    # Key fragments to look for (case-insensitive)\n    expected_fragments = [\n        'duplicate vendor suspected',\n        'clearpath',\n        'premier staffing group',\n        'v-00008',\n    ]\n\n    for msg in messages:\n        text = (msg.get('text') or '').lower()\n        if all(frag.lower() in text for frag in expected_fragments):\n            return {'pass': True, 'score': 1.0, 'feedback': f'Found ClearPath duplicate vendor notification in #vendor-management: {msg.get(\"text\")}'}\n\n    # Collect all messages for debugging\n    all_texts = [msg.get('text', '') for msg in messages]\n    return {'pass': False, 'score': 0.0, 'feedback': f'No ClearPath duplicate vendor notification found in #vendor-management. Expected message containing: DUPLICATE VENDOR SUSPECTED: ClearPath Talent Solutions matches Tax ID of existing vendor Premier Staffing Group, V-00008. Found {len(messages)} message(s) in channel: {all_texts[:5]}'}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "d87b42b7-4674-4037-bea8-407baa5fc939",
    "sort_order": 17,
    "rubric_text": "In Slack (slack_data.json), channel #accounts-payable must contain an early payment discount notification for Harrison & Cole LLP with exact text: `EARLY PAYMENT DISCOUNT: Harrison & Cole LLP, Invoice HC-2025-0001, Discount $100, Deadline 09/14/2025 (or similar format)`",
    "verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': False, 'score': 0.0, 'feedback': 'No external_services_path provided; cannot check Slack for early payment discount notification.'}\n    slack_path = Path(external_services_path) / 'slack_data.json'\n    if not slack_path.exists():\n        return {'pass': False, 'score': 0.0, 'feedback': 'slack_data.json not found.'}\n    with open(slack_path) as f:\n        data = json.load(f)\n\n    # Find #accounts-payable channel\n    target_channel_id = None\n    for cid, cinfo in data.get('channels', {}).items():\n        name = cinfo.get('name', '').lower().replace(' ', '').replace('-', '').replace('_', '')\n        if 'accountspayable' in name:\n            target_channel_id = cid\n            break\n\n    if target_channel_id is None:\n        return {'pass': False, 'score': 0.0, 'feedback': '#accounts-payable channel not found in slack_data.json.'}\n\n    messages = data.get('messages', {}).get(target_channel_id, [])\n    if not isinstance(messages, list):\n        return {'pass': False, 'score': 0.0, 'feedback': 'Messages for #accounts-payable channel is not a list.'}\n\n    # Be lenient: check for key fragments that indicate the early payment discount notification\n    # Required concepts: early payment discount, harrison (& cole), invoice HC-2025-0001, some mention of $100 discount, deadline around 09/14/2025\n    for msg in messages:\n        text = (msg.get('text') or '').lower()\n        \n        # Check for core fragments with loose matching\n        has_early_discount = ('early payment' in text and 'discount' in text) or 'early payment discount' in text or ('early' in text and 'discount' in text)\n        has_harrison = 'harrison' in text\n        has_invoice = 'hc-2025-0001' in text or 'hc2025-0001' in text or 'hc 2025 0001' in text or 'hc-2025-001' in text or 'hc20250001' in text\n        # Accept $100, 100.00, $100.00, etc.\n        has_amount = bool(re.search(r'\\$?100(\\.00)?', text))\n        # Accept various date formats for Sep 14, 2025\n        has_deadline = bool(re.search(r'09[/\\-]14[/\\-]2025|9[/\\-]14[/\\-]2025|september\\s+14|sep\\.?\\s*14|2025-09-14', text))\n\n        if has_early_discount and has_harrison and has_invoice and has_amount and has_deadline:\n            return {'pass': True, 'score': 1.0, 'feedback': f'Found Harrison & Cole early payment discount notification in #accounts-payable: {msg.get(\"text\")}'}\n\n    # Provide helpful feedback about what was found\n    all_texts = [msg.get('text', '') for msg in messages]\n    harrison_msgs = [t for t in all_texts if 'harrison' in t.lower()]\n    discount_msgs = [t for t in all_texts if 'discount' in t.lower()]\n    \n    detail = ''\n    if harrison_msgs:\n        detail += f' Found {len(harrison_msgs)} message(s) mentioning Harrison but missing other required elements. Texts: {harrison_msgs[:3]}'\n    if discount_msgs:\n        detail += f' Found {len(discount_msgs)} message(s) mentioning discount. Texts: {discount_msgs[:3]}'\n    if not harrison_msgs and not discount_msgs:\n        detail = f' Total messages in channel: {len(messages)}. None mention Harrison or discount.'\n        if messages:\n            sample = [msg.get('text', '')[:100] for msg in messages[:5]]\n            detail += f' Sample messages: {sample}'\n\n    return {'pass': False, 'score': 0.0, 'feedback': f'No Harrison & Cole early payment discount notification found in #accounts-payable. Expected text containing: EARLY PAYMENT DISCOUNT, Harrison & Cole, Invoice HC-2025-0001, Discount $100, Deadline 09/14/2025.{detail}'}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "55327dea-2c59-4e4e-a870-8c79e2373a64",
    "sort_order": 18,
    "rubric_text": "In Slack (slack_data.json), NO early payment discount notification for Whitfield Consulting Group invoice WCG-2025-0087 must exist in any channel (discount is $99.00, below $100 minimum threshold).",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {'pass': True, 'score': 1.0, 'feedback': 'No external_services_path provided; skipping Slack check.'}\n    slack_path = Path(external_services_path) / 'slack_data.json'\n    if not slack_path.exists():\n        return {'pass': True, 'score': 1.0, 'feedback': 'slack_data.json not found; no early payment discount posted.'}\n    with open(slack_path) as f:\n        data = json.load(f)\n    \n    for channel_id, msgs in data.get('messages', {}).items():\n        for msg in msgs:\n            text = (msg.get('text') or '').lower()\n            if 'early payment discount' in text and ('whitfield' in text or 'wcg-2025-0087' in text):\n                return {'pass': False, 'score': 0.0, 'feedback': f'Early payment discount notification for Whitfield (WCG-2025-0087) should NOT have been posted — $99.00 discount is below $100 minimum threshold. Found: {msg.get(\"text\")}'}\n    return {'pass': True, 'score': 1.0, 'feedback': 'No early payment discount notification for Whitfield Consulting Group found — correct, discount ($99) is below $100 threshold.'}",
    "criterion_type": "incorrect_behavior"
  }
]
