Files
handbook/tasks/insurance_vanguard_shield_mutual_177fce83/tests/rubrics.json
T

115 lines
112 KiB
JSON
Raw Normal View History

2026-06-24 12:44:34 -07:00
[
{
"id": "d3a28051-4876-4719-a90d-022d0626ae48",
"sort_order": 0,
"rubric_text": "In file `expense_reports.xlsx`, the file must exist in the workspace and contain exactly five worksheets named 'Amina Hassan', 'Leila Rahimi', 'Hiroshi Tanaka', 'Carlos Mendez', and 'Priya Natarajan'.",
"verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found in workspace'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open expense_reports.xlsx: {e}'}\n required_sheets = {'Amina Hassan', 'Leila Rahimi', 'Hiroshi Tanaka', 'Carlos Mendez', 'Priya Natarajan'}\n actual_sheets = set(wb.sheetnames)\n missing = required_sheets - actual_sheets\n if missing:\n return {'pass': False, 'score': 0.0, 'feedback': f'Missing worksheets: {missing}. Found: {actual_sheets}'}\n return {'pass': True, 'score': 1.0, 'feedback': f'All 5 required worksheets found: {required_sheets}'}\n",
"criterion_type": "expected_output"
},
{
"id": "685533ea-406a-4a1c-bb3f-d42dd95e87a3",
"sort_order": 1,
"rubric_text": "In file `expense_reports.xlsx`, each worksheet ('Amina Hassan', 'Leila Rahimi', 'Hiroshi Tanaka', 'Carlos Mendez', 'Priya Natarajan') must have row 1 as a header row containing exactly these eight columns in order: 'Jira Key', 'Date of Purchase', 'Merchant', 'Amount', 'Expense Category', 'Approved Amount', 'Denied Amount', 'Denial Reason'.",
"verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open file: {e}'}\n required_headers = ['Jira Key', 'Date of Purchase', 'Merchant', 'Amount', 'Expense Category', 'Approved Amount', 'Denied Amount', 'Denial Reason']\n sheets_to_check = ['Amina Hassan', 'Leila Rahimi', 'Hiroshi Tanaka', 'Carlos Mendez', 'Priya Natarajan']\n failures = []\n for sheet_name in sheets_to_check:\n if sheet_name not in wb.sheetnames:\n failures.append(f'Sheet \"{sheet_name}\" not found')\n continue\n ws = wb[sheet_name]\n actual_headers = [str(ws.cell(row=1, column=i).value or '').strip() for i in range(1, 9)]\n for i, (expected, actual) in enumerate(zip(required_headers, actual_headers), 1):\n if expected.lower() != actual.lower():\n failures.append(f'Sheet \"{sheet_name}\" col {i}: expected \"{expected}\", got \"{actual}\"')\n if failures:\n return {'pass': False, 'score': 0.0, 'feedback': 'Header issues: ' + '; '.join(failures)}\n return {'pass': True, 'score': 1.0, 'feedback': 'All 5 sheets have correct 8-column headers in row 1'}\n",
"criterion_type": "expected_output"
},
{
"id": "f3da8b58-f81a-45a4-82eb-ade5d4f85521",
"sort_order": 2,
"rubric_text": "In file expense_reports.xlsx, the 'Amina Hassan' worksheet must contain all 17 required line-item rows with the correct Jira Keys, dates, merchants, amounts, expense categories, approved amounts, and denied amounts (0 or blank). Specifically: OPS-1 (4/23/2026, Shopify (promotional), 43.89, Client Relations Expense, approved 43.89), OPS-2 (5/5/2026, Uber, 63.18, Staff Expense, approved 63.18), OPS-3 (5/6/2026, Blackstone Chophouse, 46.86, Staff Expense, approved 46.86), OPS-4 (5/5/2026, Delta, 389.75, Staff Expense, approved 389.75), OPS-5 (5/6/2026, Helios Coffee Shop, 24.75, Staff Expense, approved 24.75), OPS-6 (5/6/2026, Gold Coast Eatery, 27.01, Staff Expense, approved 27.01), OPS-7 (5/6/2026, Asgard Restaurant, 541.33, Client Relations Expense, approved 541.33), OPS-8 (5/7/2026, Helios Coffee Shop, 24.75, Staff Expense, approved 24.75), OPS-9 (5/7/2026, Pilsen Table, 18.74, Staff Expense, approved 18.74), OPS-10 (5/7/2026, The Greektown Table, 45.75, Staff Expense, approved 45.75), OPS-11 (5/8/2026, Helios Coffee Shop, 24.48, Staff Expense, approved 24.48), OPS-12 (5/8/2026, Wicker Park Kitchen, 28.67, Staff Expense, approved 28.67), OPS-13 (5/7/2026, Melody Bistro, 747.49, Client Relations Expense, approved 747.49), OPS-14 (5/9/2026, Uber, 59.80, Staff Expense, approved 59.80), OPS-15 (5/9/2026, Helios Coffee Shop, 22.55, Staff Expense, approved 22.55), OPS-16 (5/9/2026, Bucktown Bites, 33.02, Staff Expense, approved 33.02), OPS-17 (5/5/2026 - 5/8/2026, Hyatt Regency Chicago, 2098.94, Staff Expense, approved 2098.94).",
"verifier_code": "from pathlib import Path\nimport openpyxl\nimport re\nimport datetime\n\ndef normalize_str(s):\n return str(s or '').strip().lower()\n\ndef parse_amount(val):\n if val is None:\n return None\n s = str(val).replace('$', '').replace(',', '').strip()\n try:\n return float(s)\n except:\n return None\n\ndef parse_date_tuple(val):\n \"\"\"Return (month, day, year) tuple, or None if unparseable.\"\"\"\n if val is None:\n return None\n if isinstance(val, (datetime.datetime, datetime.date)):\n return (val.month, val.day, val.year)\n s = str(val).strip()\n # M/D/YYYY or MM/DD/YYYY\n m = re.match(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', s)\n if m:\n return (int(m.group(1)), int(m.group(2)), int(m.group(3)))\n # YYYY-MM-DD\n m = re.match(r'(\\d{4})-(\\d{1,2})-(\\d{1,2})', s)\n if m:\n return (int(m.group(2)), int(m.group(3)), int(m.group(1)))\n return None\n\ndef date_matches(actual_cell, expected_str):\n \"\"\"Flexible date comparison. Handles datetime objects, M/D/YYYY, YYYY-MM-DD,\n and range strings like '5/5/2026 - 5/8/2026'.\"\"\"\n exp_lower = expected_str.lower().strip()\n # Range case: accept if actual text contains the range, or actual parses\n # to either the start or end date of the range.\n if '-' in exp_lower and '/' in exp_lower:\n parts = re.findall(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', exp_lower)\n if len(parts) >= 2:\n start = (int(parts[0][0]), int(parts[0][1]), int(parts[0][2]))\n end = (int(parts[1][0]), int(parts[1][1]), int(parts[1][2]))\n act_str = normalize_str(actual_cell)\n # Accept a textual range that mentions both dates\n if all(f\"{m}/{d}\" in act_str or f\"{m:02d}/{d:02d}\" in act_str\n for m, d, _ in (start, end)):\n return True\n act_tuple = parse_date_tuple(actual_cell)\n if act_tuple in (start, end):\n return True\n return False\n # Single date case\n exp_tuple = parse_date_tuple(expected_str)\n act_tuple = parse_date_tuple(actual_cell)\n if exp_tuple and act_tuple and exp_tuple == act_tuple:\n return True\n # Fallback: substring match on normalized strings\n act_str = normalize_str(actual_cell)\n if exp_lower in act_str or act_str in exp_lower:\n return True\n return False\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open: {e}'}\n if 'Amina Hassan' not in wb.sheetnames:\n return {'pass': False, 'score': 0.0, 'feedback': 'Sheet \"Amina Hassan\" not found'}\n ws = wb['Amina Hassan']\n rows_by_key = {}\n for row in ws.iter_rows(min_row=2, values_only=True):\n if row[0] and str(row[0]).startswith('OPS-HR-TE-Hassan'):\n rows_by_key[str(row[0]).strip()] = row\n expected = [\n ('OPS-1', '4/23/2026', 'shopify (promotional)', 43.89, 'client relations expense', 43.89),\n ('OPS-2', '5/5/2026', 'uber', 63.18, 'staff expense', 63.18),\n ('OPS-3', '5/6/2026', 'blackstone chophouse', 46.86, 'staff expense', 46.86),\n ('OPS-4', '5/5/2026', 'delta', 389.75, 'staff expense', 389.75),\n ('OPS-5', '5/6/2026', 'helios coffee shop', 24.75, 'staff expense', 24.75),\n ('OPS-6', '5/6/2026', 'gold coast eatery', 27.01, 'staff expense', 27.01),\n ('OPS-7', '5/6/2026', 'asgard restaurant', 541.33, 'client relations expense', 541.33),\n ('OPS-8', '5/7/2026', 'helios coffee shop', 24.75, 'staff expense', 24.75),\n ('OPS-9', '5/7/2026', 'pilsen table', 18.74, 'staff expense', 18.74),\n ('OPS-10', '5/7/2026', 'the greektown table', 45.75, 'staff expense', 45
"criterion_type": "expected_output"
},
{
"id": "d5283083-687f-489b-9202-dd746e84afc2",
"sort_order": 3,
"rubric_text": "In file `expense_reports.xlsx`, the 'Amina Hassan' worksheet must contain a Report Summary section with: Jira Key = 'OPS-18', Total Approved = 4240.96 (±0.05), Total Denied = 0, Final Status = 'Approved', Manager Notified = 'N', and a Comment/Comments value of 'T&E review complete. All expenses approved.'",
"verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef normalize_str(s):\n return str(s or '').strip().lower()\n\ndef parse_amount(val):\n if val is None:\n return None\n s = str(val).replace('$', '').replace(',', '').strip()\n try:\n return float(s)\n except:\n return None\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open: {e}'}\n if 'Amina Hassan' not in wb.sheetnames:\n return {'pass': False, 'score': 0.0, 'feedback': 'Sheet \"Amina Hassan\" not found'}\n ws = wb['Amina Hassan']\n # Find the Report Summary section by scanning col A for 'report summary'\n summary_start = None\n for row in ws.iter_rows(min_row=1):\n cell_a = normalize_str(row[0].value)\n if 'report summary' in cell_a:\n summary_start = row[0].row\n break\n if summary_start is None:\n return {'pass': False, 'score': 0.0, 'feedback': 'No \"Report Summary\" section found in Amina Hassan sheet'}\n # Read label->value pairs after summary_start\n summary = {}\n for r in range(summary_start + 1, summary_start + 20):\n label = normalize_str(ws.cell(row=r, column=1).value)\n value = ws.cell(row=r, column=2).value\n if label:\n summary[label] = value\n failures = []\n # Jira Key\n jk = normalize_str(summary.get('jira key', ''))\n if 'ops-hr-te-hassan-2026-05-11' not in jk:\n failures.append(f'Jira Key expected OPS-18, got \"{summary.get(\"jira key\", \"\")}\"')\n # Total Approved\n ta = parse_amount(summary.get('total approved'))\n if ta is None or abs(ta - 4240.96) > 0.05:\n failures.append(f'Total Approved expected 4240.96 (±0.05), got {summary.get(\"total approved\")}')\n # Total Denied\n td = parse_amount(summary.get('total denied'))\n if td is None:\n td = 0.0\n if abs(td) > 0.05:\n failures.append(f'Total Denied expected 0, got {summary.get(\"total denied\")}')\n # Final Status\n fs = normalize_str(summary.get('final status', ''))\n if 'approved' not in fs or 'partial' in fs or 'denied' in fs:\n failures.append(f'Final Status expected \"Approved\", got \"{summary.get(\"final status\", \"\")}\"')\n # Manager Notified\n mn = normalize_str(summary.get('manager notified', ''))\n if 'n' not in mn:\n failures.append(f'Manager Notified expected \"N\", got \"{summary.get(\"manager notified\", \"\")}\"')\n # Comments\n comment_key = None\n for k in summary:\n if 'comment' in k:\n comment_key = k\n break\n if comment_key is None:\n failures.append('No Comment/Comments label found in summary')\n else:\n comment_val = normalize_str(summary[comment_key])\n expected_comment = 't&e review complete. all expenses approved.'\n if expected_comment not in comment_val and comment_val not in expected_comment:\n failures.append(f'Comment expected \"T&E review complete. All expenses approved.\", got \"{summary[comment_key]}\"')\n if failures:\n return {'pass': False, 'score': 0.0, 'feedback': 'Amina Hassan summary issues: ' + '; '.join(failures)}\n return {'pass': True, 'score': 1.0, 'feedback': 'Amina Hassan Report Summary section verified: Jira Key OPS-18, Total Approved 4240.96, Total Denied 0, Final Status Approved, Manager Notified N, correct comment'}\n",
"criterion_type": "expected_output"
},
{
"id": "81155ee6-2fb9-4155-a4a3-7b02b7eb9d34",
"sort_order": 4,
"rubric_text": "In file `expense_reports.xlsx`, the 'Leila Rahimi' worksheet must contain all 17 required line-item rows with correct Jira Keys, dates, merchants, amounts, expense categories, approved amounts, and denied amounts. Specifically all rows except OPS-50 must have Denied Amount = 0. Row OPS-50 (Lock & Lantern Escape Room, amount 308.61) must have Approved Amount = 192.88 (±1.50) and Denied Amount = 115.73 (±1.50).",
"verifier_code": "from pathlib import Path\nimport openpyxl\nimport re\nimport datetime\n\ndef normalize_str(s):\n return str(s or '').strip().lower()\n\ndef parse_amount(val):\n if val is None:\n return None\n s = str(val).replace('$', '').replace(',', '').strip()\n try:\n return float(s)\n except:\n return None\n\ndef parse_date_tuple(val):\n \"\"\"Return (month, day, year) tuple, or None if unparseable.\"\"\"\n if val is None:\n return None\n if isinstance(val, (datetime.datetime, datetime.date)):\n return (val.month, val.day, val.year)\n s = str(val).strip()\n m = re.match(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', s)\n if m:\n return (int(m.group(1)), int(m.group(2)), int(m.group(3)))\n m = re.match(r'(\\d{4})-(\\d{1,2})-(\\d{1,2})', s)\n if m:\n return (int(m.group(2)), int(m.group(3)), int(m.group(1)))\n return None\n\ndef date_matches(actual_cell, expected_str):\n \"\"\"Flexible date comparison. Handles datetime objects, M/D/YYYY, YYYY-MM-DD,\n and range strings like '5/5/2026 - 5/9/2026'.\"\"\"\n exp_lower = expected_str.lower().strip()\n if '-' in exp_lower and '/' in exp_lower:\n parts = re.findall(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', exp_lower)\n if len(parts) >= 2:\n start = (int(parts[0][0]), int(parts[0][1]), int(parts[0][2]))\n end = (int(parts[1][0]), int(parts[1][1]), int(parts[1][2]))\n act_str = normalize_str(actual_cell)\n if all(f\"{m}/{d}\" in act_str or f\"{m:02d}/{d:02d}\" in act_str\n for m, d, _ in (start, end)):\n return True\n act_tuple = parse_date_tuple(actual_cell)\n if act_tuple in (start, end):\n return True\n return False\n exp_tuple = parse_date_tuple(expected_str)\n act_tuple = parse_date_tuple(actual_cell)\n if exp_tuple and act_tuple and exp_tuple == act_tuple:\n return True\n act_str = normalize_str(actual_cell)\n if exp_lower in act_str or act_str in exp_lower:\n return True\n return False\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open: {e}'}\n if 'Leila Rahimi' not in wb.sheetnames:\n return {'pass': False, 'score': 0.0, 'feedback': 'Sheet \"Leila Rahimi\" not found'}\n ws = wb['Leila Rahimi']\n rows_by_key = {}\n for row in ws.iter_rows(min_row=2, values_only=True):\n if row[0] and str(row[0]).startswith('OPS-HR-TE-Rahimi'):\n rows_by_key[str(row[0]).strip()] = row\n expected = [\n ('OPS-40', '4/21/2026', 'shopify (client gifts)', 119.54, 'client relations expense', 119.54, 0, 0.01, 0.05),\n ('OPS-41', '5/5/2026', 'the greektown table', 47.96, 'staff expense', 47.96, 0, 0.01, 0.05),\n ('OPS-42', '5/5/2026', 'delta', 384.40, 'staff expense', 384.40, 0, 0.01, 0.05),\n ('OPS-43', '5/6/2026', 'helios coffee shop', 10.69, 'staff expense', 10.69, 0, 0.01, 0.05),\n ('OPS-44', '5/6/2026', 'the riverwalk grille', 21.72, 'staff expense', 21.72, 0, 0.01, 0.05),\n ('OPS-45', '5/7/2026', 'helios coffee shop', 12.40, 'staff expense', 12.40, 0, 0.01, 0.05),\n ('OPS-46', '5/7/2026', 'gold coast eatery', 28.94, 'staff expense', 28.94, 0, 0.01, 0.05),\n ('OPS-47', '5/7/2026', 'sable & oak kitchen', 37.76, 'staff expense', 37.76, 0, 0.01, 0.05),\n ('OPS-48', '5/8/2026', 'helios coffee shop', 12.35, 'staff expense', 12.35, 0, 0.01, 0.05),\n ('OPS-49', '5/8/2026', 'pilsen table', 24.81, 'staff expense', 24.81, 0, 0.01, 0.05),\n ('OPS-50', '5/8/2026', 'lock & lantern escape room', 308.61, 'client relations expense', 192.88, 115.73, 1.50, 1.50),\n ('OPS-51', '5/
"criterion_type": "expected_output"
},
{
"id": "10d0ebad-b9fe-4cd5-a939-7f8b7d912193",
"sort_order": 5,
"rubric_text": "In file expense_reports.xlsx, the 'Leila Rahimi' worksheet row for OPS-50 (Lock & Lantern Escape Room) must have a Denial Reason in column H that references more tickets purchased (8) than valid attendees (5, being 2 staff and 3 clients) and indicates 3 tickets were denied.",
"verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef normalize_str(s):\n return str(s or '').strip().lower()\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open: {e}'}\n if 'Leila Rahimi' not in wb.sheetnames:\n return {'pass': False, 'score': 0.0, 'feedback': 'Sheet \"Leila Rahimi\" not found'}\n ws = wb['Leila Rahimi']\n target_key = 'OPS-50'\n denial_reason = None\n for row in ws.iter_rows(min_row=2, values_only=True):\n if row[0] and str(row[0]).strip() == target_key:\n denial_reason = str(row[7] or '').strip() if len(row) > 7 else ''\n break\n if denial_reason is None:\n return {'pass': False, 'score': 0.0, 'feedback': f'Row {target_key} not found in Leila Rahimi sheet'}\n if not denial_reason:\n return {'pass': False, 'score': 0.0, 'feedback': f'Row {target_key} has empty Denial Reason but should explain excess tickets'}\n dr_lower = denial_reason.lower()\n failures = []\n # Must reference 8 tickets purchased\n if '8' not in dr_lower:\n failures.append('Denial reason does not mention 8 tickets purchased')\n # Must reference 5 attendees (or 2 staff + 3 clients)\n has_five = '5' in dr_lower\n has_2staff_3clients = ('2' in dr_lower and '3' in dr_lower) or ('staff' in dr_lower and 'client' in dr_lower)\n if not (has_five or has_2staff_3clients):\n failures.append('Denial reason does not reference 5 valid attendees (2 staff + 3 clients)')\n # Must reference 3 denied tickets\n if '3' not in dr_lower:\n failures.append('Denial reason does not mention 3 tickets denied')\n if failures:\n return {'pass': False, 'score': 0.0, 'feedback': f'Lock & Lantern denial reason issues: {failures}. Actual reason: \"{denial_reason}\"'}\n return {'pass': True, 'score': 1.0, 'feedback': f'Lock & Lantern Escape Room denial reason correctly references 8 tickets, 5 valid attendees, and 3 denied tickets. Reason: \"{denial_reason}\"'}\n",
"criterion_type": "expected_output"
},
{
"id": "3f5acb9f-fb7d-482f-aa7d-90a4cae678bc",
"sort_order": 6,
"rubric_text": "In file `expense_reports.xlsx`, the 'Leila Rahimi' worksheet must contain a Report Summary section with: Jira Key = 'OPS-57', Total Approved = 2966.61 (±1.50), Total Denied = 115.73 (±1.50), Final Status = 'Partially Approved', Manager Notified = 'N', and Comment/Comments = 'T&E review complete. Expenses approved except where noted.'",
"verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef normalize_str(s):\n return str(s or '').strip().lower()\n\ndef parse_amount(val):\n if val is None:\n return None\n s = str(val).replace('$', '').replace(',', '').strip()\n try:\n return float(s)\n except:\n return None\n\ndef verify(workspace_path, external_services_path=None):\n xlsx_path = Path(workspace_path) / 'expense_reports.xlsx'\n if not xlsx_path.exists():\n return {'pass': False, 'score': 0.0, 'feedback': 'expense_reports.xlsx not found'}\n try:\n wb = openpyxl.load_workbook(xlsx_path)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open: {e}'}\n if 'Leila Rahimi' not in wb.sheetnames:\n return {'pass': False, 'score': 0.0, 'feedback': 'Sheet \"Leila Rahimi\" not found'}\n ws = wb['Leila Rahimi']\n summary_start = None\n for row in ws.iter_rows(min_row=1):\n cell_a = normalize_str(row[0].value)\n if 'report summary' in cell_a:\n summary_start = row[0].row\n break\n if summary_start is None:\n return {'pass': False, 'score': 0.0, 'feedback': 'No \"Report Summary\" section found in Leila Rahimi sheet'}\n summary = {}\n for r in range(summary_start + 1, summary_start + 20):\n label = normalize_str(ws.cell(row=r, column=1).value)\n value = ws.cell(row=r, column=2).value\n if label:\n summary[label] = value\n failures = []\n jk = normalize_str(summary.get('jira key', ''))\n if 'ops-hr-te-rahimi-2026-05-11' not in jk:\n failures.append(f'Jira Key expected OPS-57, got \"{summary.get(\"jira key\", \"\")}\"')\n ta = parse_amount(summary.get('total approved'))\n if ta is None or abs(ta - 2966.61) > 1.50:\n failures.append(f'Total Approved expected 2966.61 (±1.50), got {summary.get(\"total approved\")}')\n td = parse_amount(summary.get('total denied'))\n if td is None:\n td = 0.0\n if abs(td - 115.73) > 1.50:\n failures.append(f'Total Denied expected 115.73 (±1.50), got {summary.get(\"total denied\")}')\n fs = normalize_str(summary.get('final status', ''))\n if 'partial' not in fs:\n failures.append(f'Final Status expected \"Partially Approved\", got \"{summary.get(\"final status\", \"\")}\"')\n mn = normalize_str(summary.get('manager notified', ''))\n if 'n' not in mn:\n failures.append(f'Manager Notified expected \"N\", got \"{summary.get(\"manager notified\", \"\")}\"')\n comment_key = None\n for k in summary:\n if 'comment' in k:\n comment_key = k\n break\n if comment_key is None:\n failures.append('No Comment/Comments label found in summary')\n else:\n comment_val = normalize_str(summary[comment_key])\n expected_comment = 't&e review complete. expenses approved except where noted.'\n if expected_comment not in comment_val and comment_val not in expected_comment:\n failures.append(f'Comment expected \"T&E review complete. Expenses approved except where noted.\", got \"{summary[comment_key]}\"')\n if failures:\n return {'pass': False, 'score': 0.0, 'feedback': 'Leila Rahimi summary issues: ' + '; '.join(failures)}\n return {'pass': True, 'score': 1.0, 'feedback': 'Leila Rahimi Report Summary verified: Jira Key OPS-57, Total Approved 2966.61, Total Denied 115.73, Final Status Partially Approved, Manager Notified N, correct comment'}\n",
"criterion_type": "expected_output"
},
{
"id": "8db7af74-e1a4-4f57-b4f7-7186ebe398d9",
"sort_order": 7,
"rubric_text": "6. In the worksheet \"Hiroshi Tanaka\", each of the following line-item rows must exist (identified by the Jira Key in column A) with the listed values in columns B through H. Row order is not graded. A Denial Reason is not required for any row and all Denial Reason cells must be empty. A Denied Amount of 0 and an empty cell are both acceptable. Dollar amounts match on numeric value and ignore currency symbols or thousands separators. For mileage rows, the Amount column (D) may appear as the mileage string such as \"357 miles\" or as the computed dollar amount; the Approved Amount column (F) must be the dollar reimbursement at the IRS rate of 0.725 per mile.\nRow OPS-59: Date 5/5/2026, Merchant \"Self drove (mileage)\", Amount \"357 miles\" or equivalent, Expense Category \"Staff Expense\", Approved Amount 258.83 (tolerance plus or minus 0.05), Denied Amount 0.\nRow OPS-58: Date 5/5/2026, Merchant \"Sable & Oak Kitchen\", Amount 38.59, Expense Category \"Staff Expense\", Approved Amount 38.59, Denied Amount 0.\nRow OPS-60: Date 5/6/2026, Merchant \"Gloriana Bakery\", Amount 9.10, Expense Category \"Staff Expense\", Approved Amount 9.10, Denied Amount 0.\nRow OPS-61: Date 5/6/2026, Merchant \"Wicker Park Kitchen\", Amount 21.22, Expense Category \"Staff Expense\", Approved Amount 21.22, Denied Amount 0.\nRow OPS-62: Date 5/6/2026, Merchant \"Harbor Light Bistro\", Amount 45.20, Expense Category \"Staff Expense\", Approved Amount 45.20, Denied Amount 0.\nRow OPS-63: Date 5/7/2026, Merchant \"Gloriana Bakery\", Amount 10.47, Expense Category \"Staff Expense\", Approved Amount 10.47, Denied Amount 0.\nRow OPS-64: Date 5/7/2026, Merchant \"Bucktown Bites\", Amount 26.40, Expense Category \"Staff Expense\", Approved Amount 26.40, Denied Amount 0.\nRow OPS-65: Date 5/7/2026, Merchant \"Mariscos Del Lago\", Amount 33.07, Expense Category \"Staff Expense\", Approved Amount 33.07, Denied Amount 0.\nRow OPS-66: Date 5/8/2026, Merchant \"Gloriana Bakery\", Amount 11.02, Expense Category \"Staff Expense\", Approved Amount 11.02, Denied Amount 0.\nRow OPS-67: Date 5/8/2026, Merchant \"Lincoln Square Cafe\", Amount 26.13, Expense Category \"Staff Expense\", Approved Amount 26.13, Denied Amount 0.\nRow OPS-68: Date 5/9/2026, Merchant \"Gloriana Bakery\", Amount 11.85, Expense Category \"Staff Expense\", Approved Amount 11.85, Denied Amount 0.\nRow OPS-69: Date 5/9/2026, Merchant \"Loop Street Deli\", Amount 25.63, Expense Category \"Staff Expense\", Approved Amount 25.63, Denied Amount 0.\nRow OPS-70: Date 5/9/2026, Merchant \"Koto Ramen & Izakaya\", Amount 39.69, Expense Category \"Staff Expense\", Approved Amount 39.69, Denied Amount 0.\nRow OPS-71: Date \"5/5/2026 - 5/9/2026\", Merchant \"Hyatt Regency Chicago\", Amount 1916.99, Expense Category \"Staff Expense\", Approved Amount 1916.99, Denied Amount 0.\nRow OPS-72: Date 5/10/2026, Merchant \"Gloriana Bakery\", Amount 11.30, Expense Category \"Staff Expense\", Approved Amount 11.30, Denied Amount 0.\nRow OPS-73: Date 5/10/2026, Merchant \"Self drove (mileage)\", Amount \"360 miles\" or equivalent, Expense Category \"Staff Expense\", Approved Amount 261.00 (tolerance plus or minus 0.05), Denied Amount 0.",
"verifier_code": "from pathlib import Path\nimport json\nimport re\nfrom datetime import datetime\n\ndef verify(workspace_path, external_services_path=None):\n \"\"\"\n Verify rubric item 6: Hiroshi Tanaka worksheet line items in expense_reports.xlsx\n \"\"\"\n feedback_parts = []\n total_checks = 0\n passed_checks = 0\n\n # Find the xlsx file\n ws = Path(workspace_path)\n xlsx_files = list(ws.glob('**/expense_reports.xlsx'))\n if not xlsx_files:\n xlsx_files = list(ws.glob('**/*.xlsx'))\n if not xlsx_files:\n return {'pass': False, 'score': 0.0, 'feedback': 'No xlsx file found in workspace'}\n\n xlsx_path = xlsx_files[0]\n\n try:\n import openpyxl\n wb = openpyxl.load_workbook(str(xlsx_path), data_only=True)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open {xlsx_path.name}: {e}'}\n\n # Find the Hiroshi Tanaka worksheet\n target_sheet = None\n for name in wb.sheetnames:\n if 'tanaka' in name.lower() and 'hiroshi' in name.lower():\n target_sheet = wb[name]\n break\n if target_sheet is None:\n for name in wb.sheetnames:\n if 'tanaka' in name.lower():\n target_sheet = wb[name]\n break\n if target_sheet is None:\n return {'pass': False, 'score': 0.0, 'feedback': f'No worksheet found for Hiroshi Tanaka. Sheets: {wb.sheetnames}'}\n\n feedback_parts.append(f'Found worksheet: \"{target_sheet.title}\"')\n\n # Read all rows into a dict keyed by Jira Key (column A)\n rows_by_key = {}\n for row in target_sheet.iter_rows(min_row=1, max_col=8, values_only=False):\n cell_a = row[0].value\n if cell_a is None:\n continue\n key_str = str(cell_a).strip()\n if key_str.startswith('OPS-HR-TE-Tanaka'):\n row_vals = [c.value for c in row]\n rows_by_key[key_str] = row_vals\n\n def parse_amount(val):\n \"\"\"Parse a dollar amount from various formats, return float or None.\"\"\"\n if val is None:\n return None\n if isinstance(val, (int, float)):\n return float(val)\n s = str(val).strip()\n # Remove $, commas\n s = s.replace('$', '').replace(',', '').strip()\n try:\n return float(s)\n except ValueError:\n return None\n\n def parse_date(val):\n \"\"\"Normalize date to a comparable string. Return lowercased string.\"\"\"\n if val is None:\n return ''\n if isinstance(val, datetime):\n return val.strftime('%-m/%-d/%Y').replace('/0', '/').lstrip('0') if False else val.strftime('%m/%d/%Y').lstrip('0').replace('/0', '/')\n s = str(val).strip()\n return s\n\n def dates_match(actual, expected):\n \"\"\"Loosely compare dates.\"\"\"\n if actual is None:\n return False\n # Normalize both\n def norm(d):\n if isinstance(d, datetime):\n return d.strftime('%Y-%m-%d')\n s = str(d).strip()\n # Try multiple parsing\n for fmt in ['%m/%d/%Y', '%Y-%m-%d', '%m/%d/%y', '%d/%m/%Y']:\n try:\n return datetime.strptime(s, fmt).strftime('%Y-%m-%d')\n except ValueError:\n continue\n return s.lower()\n # For date ranges like \"5/5/2026 - 5/9/2026\"\n if '-' in str(expected) and '/' in str(expected):\n # It's a range; just check containment loosely\n act_str = str(actual).replace(' ', '').lower()\n exp_str = str(expected).replace(' ', '').lower()\n # Normalize slashes\n def norm_date_str(s):\n # Remove leading zeros\n return re.sub(r'\\b0+(\\d)', r'\\1', s)\n return norm_date_str(act_str) == norm_date_str(exp_str) or \\\n norm_date_str(exp_str) in norm_date_str(act_str) or \\\n norm_date_str(act_str) in nor
"criterion_type": "expected_output"
},
{
"id": "rubric_1776176680649",
"sort_order": 8,
"rubric_text": "The \"Hiroshi Tanaka\" worksheet in `expense_reports.xlsx` must contain a Report Summary section after the last line item with the following labels in column A and the listed values in column B (row order within the section is not graded). Monetary values may appear as numbers or as the value returned by a SUM formula over the Approved Amount or Denied Amount column; currency symbols and thousands separators are ignored.\n\n- Label \"Jira Key\" with value \"OPS-74\".\n- Label \"Total Approved\" with value 2746.49 (tolerance ±1.00).\n- Label \"Total Denied\" with value 0.\n- Label \"Final Status\" with value \"Approved\".\n- Label \"Manager Notified\" with value \"N\".\n- Label \"Comment\" (or \"Comments\") with value \"T&E review complete. All expenses approved.\"",
"verifier_code": "from pathlib import Path\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n try:\n import openpyxl\n except ImportError:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"openpyxl not available\"}\n\n file_path = Path(workspace_path) / \"expense_reports.xlsx\"\n if not file_path.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"File not found: {file_path}\"}\n\n wb = openpyxl.load_workbook(str(file_path), data_only=True)\n\n # Find Hiroshi Tanaka worksheet (case-insensitive match)\n target_ws = None\n for name in wb.sheetnames:\n if name.strip().lower() == \"hiroshi tanaka\":\n target_ws = wb[name]\n break\n\n if target_ws is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Worksheet 'Hiroshi Tanaka' not found. Available sheets: {wb.sheetnames}\"}\n\n # Collect all rows as (col_a, col_b) pairs\n # We need to find the Report Summary section and then look for label-value pairs\n rows = []\n for row in target_ws.iter_rows(min_row=1, max_col=2, values_only=True):\n rows.append(row)\n\n # Find the Report Summary header row\n summary_start = None\n for i, (a, b) in enumerate(rows):\n if a is not None and str(a).strip().lower().replace(\" \", \"\") == \"reportsummary\":\n summary_start = i\n break\n\n if summary_start is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Could not find 'Report Summary' header in column A of Hiroshi Tanaka worksheet.\"}\n\n # Extract label->value mapping from summary section\n summary = {}\n for i in range(summary_start + 1, len(rows)):\n a, b = rows[i]\n if a is not None:\n key = str(a).strip()\n summary[key.lower()] = (key, b)\n\n feedback_parts = []\n checks_passed = 0\n total_checks = 6\n\n def clean_num(val):\n \"\"\"Try to extract a numeric value from val.\"\"\"\n if val is None:\n return None\n if isinstance(val, (int, float)):\n return float(val)\n s = str(val).strip()\n # Remove currency symbols, commas, spaces\n s = re.sub(r'[\\$€£¥,\\s]', '', s)\n try:\n return float(s)\n except ValueError:\n return None\n\n def find_label(possible_keys):\n \"\"\"Find a summary entry matching any of the possible key strings (case-insensitive).\"\"\"\n for pk in possible_keys:\n pk_lower = pk.lower()\n for sk in summary:\n if pk_lower in sk or sk in pk_lower:\n return summary[sk]\n return None\n\n # 1. Jira Key\n entry = find_label([\"jira key\", \"jirakey\", \"jira\"])\n if entry:\n orig_label, val = entry\n val_str = str(val).strip() if val is not None else \"\"\n if val_str.upper() == \"OPS-HR-TE-TANAKA-2026-05-11\" or val_str == \"OPS-74\":\n checks_passed += 1\n feedback_parts.append(f\"PASS: Jira Key = '{val_str}'\")\n else:\n feedback_parts.append(f\"FAIL: Jira Key expected 'OPS-74', got '{val_str}'\")\n else:\n feedback_parts.append(\"FAIL: No 'Jira Key' label found in Report Summary.\")\n\n # 2. Total Approved ~ 2746.49 ± 1.00\n entry = find_label([\"total approved\", \"totalapproved\"])\n if entry:\n orig_label, val = entry\n num = clean_num(val)\n if num is not None and abs(num - 2746.49) <= 1.00:\n checks_passed += 1\n feedback_parts.append(f\"PASS: Total Approved = {num}\")\n else:\n feedback_parts.append(f\"FAIL: Total Approved expected ~2746.49 (±1.00), got '{val}' (parsed as {num})\")\n else:\n feedback_parts.append(\"FAIL: No 'Total Approved' label found in Report Summary.\")\n\n # 3. Total Denied = 0\n entry = find_label([\"total denied\", \"totaldenied\"])\n if entry:\n orig_label, val = entry\n num = clean_num(val
"criterion_type": "expected_output"
},
{
"id": "rubric_1776176742950",
"sort_order": 9,
"rubric_text": "In the worksheet \"Carlos Mendez\" of `expense_reports.xlsx`, each of the following line-item rows must exist (identified by the Jira Key in column A) with the listed values in columns B through H. Row order is not graded. A Denial Reason is required only where noted; for all other rows the Denial Reason cell must be empty. A Denied Amount of 0 and an empty cell are both acceptable. Dollar amounts match on numeric value and ignore currency symbols or thousands separators.\n\n1. **OPS-19**: Date 5/5/2026, Merchant \"Delta\", Amount 384.40, Expense Category \"Staff Expense\", Approved Amount 384.40, Denied Amount 0.\n2. **OPS-20**: Date 5/5/2026, Merchant \"Cosa Nostra Cucina\", Amount 46.86, Expense Category \"Staff Expense\", Approved Amount 46.86, Denied Amount 0.\n3. **OPS-21**: Date 5/6/2026, Merchant \"Bucktown Bites\", Amount 28.11, Expense Category \"Staff Expense\", Approved Amount 28.11, Denied Amount 0.\n4. **OPS-22**: Date 5/6/2026, Merchant \"Sable & Oak Kitchen\", Amount 48.23, Expense Category \"Staff Expense\", Approved Amount 48.23, Denied Amount 0.\n5. **OPS-23**: Date 5/6/2026, Merchant \"Art Institute of Chicago\", Amount 32.00, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 32.00. Denial Reason must state or be equivalent to \"Missing business justification. Please submit personal reimbursement.\"\n6. **OPS-24**: Date 5/7/2026, Merchant \"Lincoln Square Cafe\", Amount 24.25, Expense Category \"Staff Expense\", Approved Amount 24.25, Denied Amount 0.\n7. **OPS-25**: Date 5/7/2026, Merchant \"Blackstone Chophouse\", Amount 46.86, Expense Category \"Staff Expense\", Approved Amount 46.86, Denied Amount 0.\n8. **OPS-26**: Date 5/8/2026, Merchant \"Loop Street Deli\", Amount 21.22, Expense Category \"Staff Expense\", Approved Amount 21.22, Denied Amount 0.\n9. **OPS-27**: Date 5/8/2026, Merchant \"Cosa Nostra Cucina\", Amount 43.55, Expense Category \"Staff Expense\", Approved Amount 43.55, Denied Amount 0.\n10. **OPS-28**: Date 5/9/2026, Merchant \"Gloriana Bakery\", Amount 13.73, Expense Category \"Staff Expense\", Approved Amount 13.73, Denied Amount 0.\n11. **OPS-29**: Date 5/9/2026, Merchant \"The Riverwalk Grille\", Amount 36.93, Expense Category \"Staff Expense\", Approved Amount 36.93, Denied Amount 0.\n12. **OPS-30**: Date 5/9/2026, Merchant \"Harbor Light Bistro\", Amount 43.55, Expense Category \"Staff Expense\", Approved Amount 43.55, Denied Amount 0.\n13. **OPS-31**: Date \"5/5/2026 - 5/9/2026\", Merchant \"Hyatt Regency Chicago\", Amount 1916.99, Expense Category \"Staff Expense\", Approved Amount 1916.99, Denied Amount 0.",
"verifier_code": "from pathlib import Path\nimport re\nimport datetime\n\ndef verify(workspace_path, external_services_path=None):\n import openpyxl\n\n filepath = Path(workspace_path) / \"expense_reports.xlsx\"\n if not filepath.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"File expense_reports.xlsx not found.\"}\n\n wb = openpyxl.load_workbook(str(filepath))\n\n sheet = None\n for name in wb.sheetnames:\n if \"carlos\" in name.lower() and \"mendez\" in name.lower():\n sheet = wb[name]\n break\n if sheet is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Worksheet 'Carlos Mendez' not found. Available sheets: {wb.sheetnames}\"}\n\n rows_by_key = {}\n for row in sheet.iter_rows(min_row=1, values_only=False):\n cell_a = row[0].value\n if cell_a and isinstance(cell_a, str) and \"Mendez\" in cell_a and \"OPS\" in cell_a:\n key = cell_a.strip()\n vals = [c.value for c in row]\n rows_by_key[key] = vals\n\n def parse_num(v):\n if v is None:\n return 0.0\n if isinstance(v, (int, float)):\n return float(v)\n s = str(v).strip().replace(\"$\", \"\").replace(\",\", \"\").strip()\n if s == \"\" or s.lower() == \"none\":\n return 0.0\n try:\n return float(s)\n except ValueError:\n return None\n\n def nums_close(a, b, tol=0.05):\n if a is None or b is None:\n return False\n if abs(b) < 0.01:\n return abs(a) < 0.01 + tol\n return abs(a - b) <= max(abs(b) * tol, 0.05)\n\n def normalize_str(s):\n if s is None:\n return \"\"\n return str(s).strip().lower()\n\n def parse_date_tuple(val):\n if val is None:\n return None\n if isinstance(val, (datetime.datetime, datetime.date)):\n return (val.month, val.day, val.year)\n s = str(val).strip()\n m = re.match(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', s)\n if m:\n return (int(m.group(1)), int(m.group(2)), int(m.group(3)))\n m = re.match(r'(\\d{4})-(\\d{1,2})-(\\d{1,2})', s)\n if m:\n return (int(m.group(2)), int(m.group(3)), int(m.group(1)))\n return None\n\n def date_matches(actual_cell, expected_str):\n exp_lower = expected_str.lower().strip()\n # Range expected\n if ' - ' in exp_lower:\n parts = re.findall(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', exp_lower)\n if len(parts) >= 2:\n start = (int(parts[0][0]), int(parts[0][1]), int(parts[0][2]))\n end = (int(parts[1][0]), int(parts[1][1]), int(parts[1][2]))\n act_str = normalize_str(actual_cell)\n if all(f\"{m}/{d}\" in act_str or f\"{m:02d}/{d:02d}\" in act_str\n for m, d, _ in (start, end)):\n return True\n act_tuple = parse_date_tuple(actual_cell)\n if act_tuple in (start, end):\n return True\n return False\n # Single date expected\n exp_tuple = parse_date_tuple(expected_str)\n act_tuple = parse_date_tuple(actual_cell)\n if exp_tuple and act_tuple and exp_tuple == act_tuple:\n return True\n act_str = normalize_str(actual_cell)\n if exp_lower in act_str or act_str in exp_lower:\n return True\n return False\n\n def merchant_matches(cell_val, expected):\n if cell_val is None:\n return False\n return normalize_str(cell_val) == normalize_str(expected) or normalize_str(expected) in normalize_str(cell_val)\n\n def category_matches(cell_val, expected):\n return normalize_str(cell_val) == normalize_str(expected)\n\n def denial_reason_blank(cell_val):\n v = normalize_str(cell_val)\n return v == \"\" or v == \"none\" or v == \"n/a\" or v == \"0\"\n\n def denial_reason_present(cell_val, keywords):\n
"criterion_type": "expected_output"
},
{
"id": "rubric_1776176745296",
"sort_order": 10,
"rubric_text": "The \"Carlos Mendez\" worksheet in `expense_reports.xlsx` must contain a Report Summary section after the last line item with the following labels in column A and the listed values in column B (row order within the section is not graded). Monetary values may appear as numbers or as the value returned by a SUM formula over the Approved Amount or Denied Amount column; currency symbols and thousands separators are ignored.\n\n- Label \"Jira Key\" with value `OPS-32`.\n- Label \"Total Approved\" with value `2654.68` (tolerance ±1.00).\n- Label \"Total Denied\" with value `32.00`.\n- Label \"Final Status\" with value `Partially Approved`.\n- Label \"Manager Notified\" with value `N`.\n- Label \"Comment\" (or \"Comments\") with value `T&E review complete. Expenses approved except where noted.`",
"verifier_code": "from pathlib import Path\nimport re\nimport openpyxl\n\n\ndef verify(workspace_path, external_services_path=None):\n file_path = Path(workspace_path) / \"expense_reports.xlsx\"\n if not file_path.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"File not found: {file_path}\"}\n\n wb = openpyxl.load_workbook(str(file_path), data_only=True)\n\n # Find the Carlos Mendez worksheet (case-insensitive, flexible spacing)\n target_ws = None\n for name in wb.sheetnames:\n if re.search(r'carlos\\s+mendez', name, re.IGNORECASE):\n target_ws = wb[name]\n break\n\n if target_ws is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No worksheet matching 'Carlos Mendez' found. Sheets: {wb.sheetnames}\"}\n\n # Read all rows into a list of (colA, colB) tuples\n rows = []\n for row in target_ws.iter_rows(min_row=1, max_col=2, values_only=True):\n a_val = row[0] if row[0] is not None else \"\"\n b_val = row[1] if len(row) > 1 and row[1] is not None else \"\"\n rows.append((a_val, b_val))\n\n # Find the Report Summary section\n summary_start = None\n for i, (a, b) in enumerate(rows):\n if isinstance(a, str) and re.search(r'report\\s+summary', a, re.IGNORECASE):\n summary_start = i\n break\n\n if summary_start is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"'Report Summary' header not found in column A.\"}\n\n # Build a dict of label -> value from the summary section\n summary = {}\n for a, b in rows[summary_start + 1:]:\n if isinstance(a, str) and a.strip():\n summary[a.strip().lower()] = b\n\n # Also check starting from summary_start itself in case labels include that row\n a0, b0 = rows[summary_start]\n if isinstance(a0, str) and a0.strip().lower() != 'report summary':\n summary[a0.strip().lower()] = b0\n\n def clean_num(val):\n \"\"\"Extract a numeric value from a cell, stripping currency symbols, commas, etc.\"\"\"\n if val is None:\n return None\n if isinstance(val, (int, float)):\n return float(val)\n s = str(val).replace('$', '').replace(',', '').replace(' ', '').strip()\n try:\n return float(s)\n except ValueError:\n return None\n\n def find_key(dictionary, *candidates):\n \"\"\"Find a value in dict by trying multiple key variations (case-insensitive).\"\"\"\n for key in dictionary:\n for c in candidates:\n if re.fullmatch(c, key, re.IGNORECASE):\n return dictionary[key]\n return None\n\n checks_passed = 0\n total_checks = 6\n feedback_parts = []\n\n # 1. Jira Key\n jira_val = find_key(summary, r'jira\\s*key')\n if jira_val is not None and re.search(r'OPS-32', str(jira_val), re.IGNORECASE):\n checks_passed += 1\n feedback_parts.append(f\"PASS Jira Key: found '{jira_val}'\")\n else:\n feedback_parts.append(f\"FAIL Jira Key: expected 'OPS-32', found '{jira_val}'\")\n\n # 2. Total Approved ~2654.68 ± 1.00\n approved_val = find_key(summary, r'total\\s*approved')\n approved_num = clean_num(approved_val)\n if approved_num is not None and abs(approved_num - 2654.68) <= 1.00:\n checks_passed += 1\n feedback_parts.append(f\"PASS Total Approved: {approved_num}\")\n else:\n feedback_parts.append(f\"FAIL Total Approved: expected ~2654.68 (±1.00), found '{approved_val}' (numeric: {approved_num})\")\n\n # 3. Total Denied == 32.00 (use ±1.00 tolerance)\n denied_val = find_key(summary, r'total\\s*denied')\n denied_num = clean_num(denied_val)\n if denied_num is not None and abs(denied_num - 32.00) <= 1.00:\n checks_passed += 1\n feedback_parts.append(f\"PASS Total Denied: {denied_num}\")\n else:\n feedback_parts.append(f\"FAIL Total Denied: expected 32.00, found '{denied_val}' (numeric: {denied_num})\")\n\n # 4. Final S
"criterion_type": "expected_output"
},
{
"id": "rubric_1776176807994",
"sort_order": 11,
"rubric_text": "In the worksheet \"Priya Natarajan\" of `expense_reports.xlsx`, each of the following line-item rows must exist (identified by the Jira Key in column A) with the listed values in columns B through H. Row order is not graded. Every line item must be fully denied, and every Denial Reason must state or be equivalent to \"Missing business justification. Please submit personal reimbursement.\" Dollar amounts match on numeric value and ignore currency symbols or thousands separators.\n\n- **OPS-34**: Date 5/5/2026, Merchant \"Self drove (mileage)\", Amount \"200 miles\" or equivalent, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 145.00 (tolerance ±0.05).\n- **OPS-33**: Date 5/5/2026, Merchant \"Riverfront Deli\", Amount 16.59, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 16.59.\n- **OPS-35**: Date 5/6/2026, Merchant \"The Creamery Cafe\", Amount 21.40, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 21.40.\n- **OPS-36**: Date 5/7/2026, Merchant \"Canal Street Kitchen\", Amount 16.05, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 16.05.\n- **OPS-37**: Date 5/8/2026, Merchant \"The Landing Spot\", Amount 20.33, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 20.33.\n- **OPS-38**: Date \"5/5/2026 - 5/9/2026\", Merchant \"Serenity Hotel and Spa\", Amount 1471.96, Expense Category \"Staff Expense\", Approved Amount 0, Denied Amount 1471.96.",
"verifier_code": "from pathlib import Path\nimport openpyxl\nimport re\nimport datetime\n\ndef verify(workspace_path, external_services_path=None):\n filepath = Path(workspace_path) / \"expense_reports.xlsx\"\n if not filepath.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"File expense_reports.xlsx not found.\"}\n\n wb = openpyxl.load_workbook(str(filepath))\n sheet = None\n for name in wb.sheetnames:\n if \"priya\" in name.lower() and \"natarajan\" in name.lower():\n sheet = wb[name]\n break\n if sheet is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Worksheet 'Priya Natarajan' not found. Available sheets: {wb.sheetnames}\"}\n\n rows = []\n for row in sheet.iter_rows(min_row=1, values_only=False):\n vals = [cell.value for cell in row]\n rows.append(vals)\n\n row_dict = {}\n for r in rows:\n key = str(r[0]).strip() if r[0] is not None else \"\"\n if key:\n row_dict[key] = r\n\n def parse_num(val):\n if val is None:\n return None\n if isinstance(val, (int, float)):\n return float(val)\n s = str(val).strip()\n s = re.sub(r'[\\$,]', '', s)\n try:\n return float(s)\n except:\n return None\n\n def normalize_str(val):\n if val is None:\n return \"\"\n return str(val).strip().lower()\n\n def parse_date_tuple(val):\n if val is None:\n return None\n if isinstance(val, (datetime.datetime, datetime.date)):\n return (val.month, val.day, val.year)\n s = str(val).strip()\n m = re.match(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', s)\n if m:\n return (int(m.group(1)), int(m.group(2)), int(m.group(3)))\n m = re.match(r'(\\d{4})-(\\d{1,2})-(\\d{1,2})', s)\n if m:\n return (int(m.group(2)), int(m.group(3)), int(m.group(1)))\n return None\n\n def date_matches(actual_cell, expected_str):\n exp_lower = expected_str.lower().strip()\n if ' - ' in exp_lower or ' to ' in exp_lower:\n parts = re.findall(r'(\\d{1,2})/(\\d{1,2})/(\\d{4})', exp_lower)\n if len(parts) >= 2:\n start = (int(parts[0][0]), int(parts[0][1]), int(parts[0][2]))\n end = (int(parts[1][0]), int(parts[1][1]), int(parts[1][2]))\n act_str = normalize_str(actual_cell)\n # Accept a textual span mentioning both dates (slash or ISO form)\n start_iso = f\"{start[2]}-{start[0]:02d}-{start[1]:02d}\"\n end_iso = f\"{end[2]}-{end[0]:02d}-{end[1]:02d}\"\n if (start_iso in act_str and end_iso in act_str):\n return True\n if all(f\"{m}/{d}\" in act_str or f\"{m:02d}/{d:02d}\" in act_str\n for m, d, _ in (start, end)):\n return True\n act_tuple = parse_date_tuple(actual_cell)\n if act_tuple in (start, end):\n return True\n return False\n exp_tuple = parse_date_tuple(expected_str)\n act_tuple = parse_date_tuple(actual_cell)\n if exp_tuple and act_tuple and exp_tuple == act_tuple:\n return True\n act_str = normalize_str(actual_cell)\n if exp_lower in act_str or act_str in exp_lower:\n return True\n return False\n\n def merchant_matches(cell_val, expected):\n if cell_val is None:\n return False\n return normalize_str(cell_val) == normalize_str(expected) or normalize_str(expected) in normalize_str(cell_val) or normalize_str(cell_val) in normalize_str(expected)\n\n def category_matches(cell_val, expected):\n if cell_val is None:\n return False\n return normalize_str(expected) in normalize_str(cell_val) or normalize_str(cell_val) in normalize_str(expected)\n\n def denial_reason_ok(cell_val):\n if cell_val is None:\n retu
"criterion_type": "expected_output"
},
{
"id": "rubric_1776177197899",
"sort_order": 12,
"rubric_text": "The \"Priya Natarajan\" worksheet in `expense_reports.xlsx` must contain a Report Summary section after the last line item with the following labels in column A and the listed values in column B (row order within the section is not graded). Monetary values may appear as numbers or as the value returned by a SUM formula over the Approved Amount or Denied Amount column; currency symbols and thousands separators are ignored.\n\n- Label **\"Jira Key\"** with value **OPS-39**.\n- Label **\"Total Approved\"** with value **0**.\n- Label **\"Total Denied\"** with value **1691.33** (tolerance ±1.00).\n- Label **\"Final Status\"** with value **\"Denied\"**.\n- Label **\"Manager Notified\"** with value **\"Y\"**.\n- Label **\"Comment\"** (or **\"Comments\"**) with text that clearly states the expense report is denied and cites a denied total within 1.00 of 1691.33; an acceptable exact value is \"T&E review complete. Denied expenses: 1691.33 dollars.\"",
"verifier_code": "from pathlib import Path\nimport re\nimport openpyxl\n\n\ndef verify(workspace_path, external_services_path=None):\n filepath = Path(workspace_path) / \"expense_reports.xlsx\"\n if not filepath.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"File expense_reports.xlsx not found.\"}\n\n wb = openpyxl.load_workbook(str(filepath), data_only=True)\n\n # Find the worksheet for Priya Natarajan (case-insensitive match)\n ws = None\n for name in wb.sheetnames:\n if name.strip().lower() == \"priya natarajan\":\n ws = wb[name]\n break\n if ws is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Worksheet 'Priya Natarajan' not found. Found sheets: {wb.sheetnames}\"}\n\n # Build a dict of label -> value from the Report Summary section\n # We scan column A for labels and column B for values\n # The Report Summary section starts after a row containing \"Report Summary\" in column A\n summary_started = False\n summary = {} # normalized_label -> raw_value\n for row in ws.iter_rows(min_row=1, max_row=ws.max_row, min_col=1, max_col=2):\n cell_a = row[0].value\n cell_b = row[1].value if len(row) > 1 else None\n if cell_a is not None and str(cell_a).strip().lower() == \"report summary\":\n summary_started = True\n continue\n if summary_started and cell_a is not None:\n label = str(cell_a).strip()\n summary[label.lower()] = cell_b\n\n if not summary_started:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Could not find 'Report Summary' header row in the Priya Natarajan worksheet.\"}\n\n feedback_parts = []\n checks_passed = 0\n total_checks = 6\n\n def get_val(key_variants):\n for k in key_variants:\n if k.lower() in summary:\n return summary[k.lower()]\n return None\n\n def parse_number(val):\n \"\"\"Try to extract a numeric value from val.\"\"\"\n if val is None:\n return None\n if isinstance(val, (int, float)):\n return float(val)\n s = str(val).strip()\n # Remove currency symbols, commas, spaces, dollar word\n s = re.sub(r'[\\$,€£]', '', s)\n s = s.strip()\n try:\n return float(s)\n except ValueError:\n return None\n\n # 1. Jira Key\n jira_val = get_val([\"Jira Key\", \"jira key\", \"JIRA Key\"])\n if jira_val is not None and \"natarajan-2026-05-11\" in str(jira_val).strip().lower():\n checks_passed += 1\n feedback_parts.append(f\"PASS Jira Key: found '{jira_val}'\")\n else:\n feedback_parts.append(f\"FAIL Jira Key: expected 'OPS-39', found '{jira_val}'\")\n\n # 2. Total Approved == 0\n ta_val = get_val([\"Total Approved\"])\n ta_num = parse_number(ta_val)\n if ta_num is not None and abs(ta_num - 0) <= 1.0:\n checks_passed += 1\n feedback_parts.append(f\"PASS Total Approved: {ta_num}\")\n else:\n feedback_parts.append(f\"FAIL Total Approved: expected 0, found '{ta_val}' (parsed: {ta_num})\")\n\n # 3. Total Denied == 1691.33 +/- 1.00\n td_val = get_val([\"Total Denied\"])\n td_num = parse_number(td_val)\n if td_num is not None and abs(td_num - 1691.33) <= 1.00:\n checks_passed += 1\n feedback_parts.append(f\"PASS Total Denied: {td_num}\")\n else:\n feedback_parts.append(f\"FAIL Total Denied: expected 1691.33 (±1.00), found '{td_val}' (parsed: {td_num})\")\n\n # 4. Final Status == Denied\n fs_val = get_val([\"Final Status\"])\n if fs_val is not None and str(fs_val).strip().lower() == \"denied\":\n checks_passed += 1\n feedback_parts.append(f\"PASS Final Status: '{fs_val}'\")\n else:\n feedback_parts.append(f\"FAIL Final Status: expected 'Denied', found '{fs_val}'\")\n\n # 5. Manager Notified == Y\n mn_val = get_val([\"Manager Notified\"])\n if mn_val is not None and str(mn_val).strip().upper().startsw
"criterion_type": "expected_output"
},
{
"id": "rubric_1776177271321",
"sort_order": 13,
"rubric_text": "In `slack_data.json`, there must be no message in channel #finance-approvals referencing \"Rahimi\" that contains the string \"OPS-HR-TE-Rahimi\" or otherwise flags Leila Rahimi's expense report to a manager. Rahimi's report is Partially Approved with denials under $200, so per SOP section 6.2 Step 9, no manager notification is required.",
"verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n # We need to check slack_data.json for any messages in #finance-approvals\n # referencing Rahimi's expense report. Per SOP 6.2 Step 9, no manager\n # notification is required since denials are under $200.\n \n slack_path = None\n \n # Check external_services_path first\n if external_services_path:\n candidate = Path(external_services_path) / \"slack_data.json\"\n if candidate.exists():\n slack_path = candidate\n \n # Also check workspace_path\n if slack_path is None:\n candidate = Path(workspace_path) / \"slack_data.json\"\n if candidate.exists():\n slack_path = candidate\n \n # Search more broadly if not found\n if slack_path is None:\n for p in Path(workspace_path).rglob(\"slack_data.json\"):\n slack_path = p\n break\n if slack_path is None and external_services_path:\n for p in Path(external_services_path).rglob(\"slack_data.json\"):\n slack_path = p\n break\n \n if slack_path is None:\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"No slack_data.json found anywhere. Since no Slack messages exist, no manager notification about Rahimi was sent. PASS.\"\n }\n \n try:\n with open(slack_path, 'r') as f:\n slack_data = json.load(f)\n except Exception as e:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Could not read slack_data.json: {e}\"}\n \n # Find the #finance-approvals channel ID\n channels = slack_data.get(\"channels\", {})\n finance_approvals_ids = []\n for ch_id, ch_info in channels.items():\n ch_name = \"\"\n if isinstance(ch_info, dict):\n ch_name = ch_info.get(\"name\", \"\")\n elif isinstance(ch_info, str):\n ch_name = ch_info\n if ch_name.lower().replace(\"#\", \"\") == \"finance-approvals\":\n finance_approvals_ids.append(ch_id)\n \n # If no finance-approvals channel found, check all channels as a fallback\n # (be generous - maybe the channel name is slightly different)\n if not finance_approvals_ids:\n for ch_id, ch_info in channels.items():\n ch_name = \"\"\n if isinstance(ch_info, dict):\n ch_name = ch_info.get(\"name\", \"\")\n elif isinstance(ch_info, str):\n ch_name = ch_info\n if \"finance\" in ch_name.lower() and \"approv\" in ch_name.lower():\n finance_approvals_ids.append(ch_id)\n \n messages_data = slack_data.get(\"messages\", {})\n \n # Collect messages from the finance-approvals channel(s)\n flagged_messages = []\n \n rahimi_patterns = [\n r'OPS-HR-TE-Rahimi',\n r'Rahimi',\n r'Leila\\s+Rahimi',\n ]\n \n def check_text(text, source_desc):\n if not isinstance(text, str):\n return\n for pattern in rahimi_patterns:\n if re.search(pattern, text, re.IGNORECASE):\n flagged_messages.append(f\"Found '{pattern}' match in {source_desc}: {text[:300]}\")\n break # one match per text is enough\n \n def search_messages(msg_list, channel_desc):\n if not isinstance(msg_list, list):\n return\n for i, msg in enumerate(msg_list):\n if isinstance(msg, dict):\n text = msg.get(\"text\", \"\")\n check_text(text, f\"{channel_desc} message[{i}]\")\n # Also check attachments or blocks\n for key in [\"attachments\", \"blocks\"]:\n val = msg.get(key, [])\n if isinstance(val, list):\n for j, item in enumerate(val):\n if isinstance(item, dict):\n for field in [\"text\", \"fallback\", \"pretext\", \"ti
"criterion_type": "incorrect_behavior"
},
{
"id": "rubric_1776177277018",
"sort_order": 14,
"rubric_text": "In `slack_data.json`, there must be a Slack message posted to the channel named `finance-approvals` (channel id `C001`) that addresses Priya Natarajan's reporting manager `l_zahra` and contains all of the following:\n\n1. The phrase \"T&E Review Flag\"\n2. The employee name \"Priya Natarajan\"\n3. A date range covering 5/5/2026 through 5/10/2026 (any equivalent date format is accepted)\n4. A Total Amount Approved of 0\n5. A Total Amount Denied value within 1.00 of 1691.33\n6. The merchants \"Serenity Hotel and Spa\", \"Riverfront Deli\", \"The Creamery Cafe\", \"Canal Street Kitchen\", and \"The Landing Spot\", and a \"mileage reimbursement\" entry for the mileage line per Template B-2\n7. A Details line indicating that the expenses were submitted without business justification\n8. An Action Taken line indicating that expense report OPS-39 has been denied\n\nCurrency symbols and thousands separators are ignored when comparing amounts.",
"verifier_code": "import json\nimport os\nimport re\nimport datetime\n\ndef verify(workspace_path, external_services_path=None):\n # --- Locate slack_data.json, preferring post-run state --------------\n preferred_subpaths = [\n \"external_services_final/slack_data.json\",\n \"external_services/slack_data.json\",\n \"workspace/slack_data.json\",\n \"draft/external_services_final/slack_data.json\",\n \"draft/workspace/slack_data.json\",\n \"draft/slack_data.json\",\n \"slack_data.json\",\n ]\n candidates = [os.path.join(workspace_path, p) for p in preferred_subpaths]\n parent = os.path.dirname(workspace_path.rstrip(\"/\"))\n if parent:\n candidates.extend(os.path.join(parent, p) for p in preferred_subpaths)\n\n slack_path = next((c for c in candidates if os.path.isfile(c)), None)\n\n # Recursive fallback — but skip _initial, prefer _final, then anything else\n if slack_path is None:\n hits = []\n roots = [workspace_path] + ([parent] if parent else [])\n for root in roots:\n if not os.path.isdir(root):\n continue\n for dirpath, _, filenames in os.walk(root):\n if \"slack_data.json\" in filenames:\n hits.append(os.path.join(dirpath, \"slack_data.json\"))\n\n def _rank(p):\n lp = p.lower()\n if \"_initial\" in lp or \"initial\" in os.path.basename(os.path.dirname(lp)):\n return 3\n if \"_final\" in lp or \"final\" in os.path.basename(os.path.dirname(lp)):\n return 0\n if \"workspace\" in lp:\n return 1\n return 2\n\n hits.sort(key=_rank)\n if hits:\n slack_path = hits[0]\n\n if slack_path is None:\n return {\"pass\": False, \"score\": 0.0,\n \"feedback\": f\"Could not locate slack_data.json under {workspace_path}\"}\n\n try:\n with open(slack_path, \"r\") as f:\n slack_data = json.load(f)\n except Exception as e:\n return {\"pass\": False, \"score\": 0.0,\n \"feedback\": f\"Could not load slack_data.json at {slack_path}: {e}\"}\n\n # --- Figure out which channel IDs correspond to #finance-approvals ---\n target_ch_ids = {\"C001\"}\n\n def _register_channel(ch_id, ch_name):\n if ch_id:\n if ch_id == \"C001\":\n target_ch_ids.add(ch_id)\n if ch_name and \"finance-approvals\" in ch_name.lower().replace(\"#\", \"\"):\n target_ch_ids.add(ch_id)\n\n channels_field = slack_data.get(\"channels\") if isinstance(slack_data, dict) else None\n if isinstance(channels_field, dict):\n for ch_id, ch in channels_field.items():\n ch_name = ch.get(\"name\", \"\") if isinstance(ch, dict) else \"\"\n _register_channel(ch_id, ch_name)\n elif isinstance(channels_field, list):\n for ch in channels_field:\n if isinstance(ch, dict):\n _register_channel(ch.get(\"id\") or ch.get(\"channel_id\"),\n ch.get(\"name\", \"\"))\n\n # --- Collect messages from every plausible shape --------------------\n def _msg_in_target_channel(m):\n if not isinstance(m, dict):\n return False\n ch = (m.get(\"channel_id\") or m.get(\"channel\")\n or m.get(\"channel_name\") or m.get(\"conversation_id\"))\n if ch is None:\n return False\n if ch in target_ch_ids:\n return True\n if isinstance(ch, str) and \"finance-approvals\" in ch.lower().replace(\"#\", \"\"):\n return True\n return False\n\n messages = []\n\n msgs_field = slack_data.get(\"messages\") if isinstance(slack_data, dict) else None\n if isinstance(msgs_field, dict):\n for ch_id, ms in msgs_field.items():\n if ch_id in target_ch_ids and isinstance(ms, list):\n messages.extend(ms)\n elif isinstance(ch_id, str) and
"criterion_type": "expected_output"
},
{
"id": "rubric_1776177312019",
"sort_order": 15,
"rubric_text": "In `slack_data.json`, there must be no message in channel #finance-approvals referencing \"Rahimi\" that contains the string \"OPS-HR-TE-Mendez\" or otherwise flags Carlos Mendez's expense report to a manager. Mendez's report is Partially Approved with denials under $200, so per SOP section 6.2 Step 9, no manager notification is required.",
"verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n # We need to check slack_data.json for any messages in #finance-approvals\n # referencing Mendez's expense report to a manager.\n # Per SOP 6.2 Step 9, no manager notification is required since denials are under $200.\n\n slack_path = None\n candidates = []\n if external_services_path:\n candidates.append(Path(external_services_path) / \"slack_data.json\")\n candidates.append(Path(external_services_path) / \"slack\" / \"slack_data.json\")\n candidates.append(Path(workspace_path) / \"slack_data.json\")\n\n for c in candidates:\n if c.exists():\n slack_path = c\n break\n\n if slack_path is None:\n # No slack_data.json found; no messages were sent, so no manager notification for Mendez exists.\n return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No slack_data.json found; no Slack messages were sent, so no manager notification for Mendez exists. PASS.\"}\n\n try:\n with open(slack_path, 'r') as f:\n slack_data = json.load(f)\n except Exception as e:\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Could not parse slack_data.json ({e}); assuming no problematic messages. PASS (lenient).\"}\n\n # Find the channel ID for #finance-approvals\n finance_channel_id = None\n channels = slack_data.get(\"channels\", {})\n if isinstance(channels, dict):\n for cid, cinfo in channels.items():\n if isinstance(cinfo, dict):\n cname = cinfo.get(\"name\", \"\")\n if cname.lower() == \"finance-approvals\":\n finance_channel_id = cid\n break\n\n # Collect messages from #finance-approvals channel\n finance_messages = []\n messages_data = slack_data.get(\"messages\", {})\n\n if finance_channel_id and isinstance(messages_data, dict):\n channel_msgs = messages_data.get(finance_channel_id, [])\n if isinstance(channel_msgs, list):\n finance_messages.extend(channel_msgs)\n\n # Also do a broader search across all channels in case channel naming varies\n all_channel_messages = []\n if isinstance(messages_data, dict):\n for chan_id, msgs in messages_data.items():\n if isinstance(msgs, list):\n for msg in msgs:\n all_channel_messages.append((chan_id, msg))\n\n # Helper to extract all text from a message object\n def get_message_text(msg):\n texts = []\n if isinstance(msg, dict):\n if \"text\" in msg:\n texts.append(str(msg[\"text\"]))\n # Check attachments, blocks, etc.\n for key in [\"attachments\", \"blocks\"]:\n if key in msg:\n items = msg[key]\n if isinstance(items, list):\n for item in items:\n if isinstance(item, dict):\n for subkey in [\"text\", \"fallback\", \"pretext\", \"title\"]:\n if subkey in item:\n texts.append(str(item[subkey]))\n elif isinstance(msg, str):\n texts.append(msg)\n return \" \".join(texts)\n\n # Check 1: Look for messages in #finance-approvals containing \"OPS-HR-TE-Mendez\" or referencing Mendez + Rahimi\n mendez_jira_pattern = re.compile(r'OPS[-\\s]?HR[-\\s]?TE[-\\s]?Mendez', re.IGNORECASE)\n flagged_texts = []\n\n # Check finance-approvals channel specifically\n for msg in finance_messages:\n text = get_message_text(msg)\n text_lower = text.lower()\n # Check for Mendez Jira key\n if mendez_jira_pattern.search(text):\n flagged_texts.append(text)\n # Check for Mendez mentioned alongside Rahimi (manager notification)\n elif 'mendez' in text_lower and 'rahimi' in text_lower:\n flagged_texts.append(text)
"criterion_type": "incorrect_behavior"
}
]