[
  {
    "id": "5a168b3e-f630-4242-8d93-3c57a0b096b4",
    "sort_order": 0,
    "rubric_text": "In `mailbox.json`, there must be no emails in the Sent folder",
    "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\": \"external_services_path is None; cannot check mailbox.json\"}\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, \"r\") 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 sent_emails:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found {len(sent_emails)} email(s) in Sent folder; expected none\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No emails found in Sent folder\"}\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "186f0d01-4504-4393-b453-11f3e54d335c",
    "sort_order": 1,
    "rubric_text": "In `mailbox.json`, there must be exactly 2 emails in the Drafts folder",
    "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\": \"external_services_path is None; cannot check mailbox.json\"}\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, \"r\") as f:\n        data = json.load(f)\n    draft_emails = [e for e in data.get(\"emails\", []) if e.get(\"folder\", \"\").lower() in [\"drafts\", \"draft\"]]\n    if len(draft_emails) != 2:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found {len(draft_emails)} draft email(s); expected exactly 2\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"Found exactly 2 emails in Drafts folder\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "be8ac84d-c515-4bb2-adfb-413166231d6c",
    "sort_order": 2,
    "rubric_text": "In `mailbox.json`, one draft email must be addressed to joem@pathfinder.com with mayj@pathfinder.com as BCC, and contain 'Bullion Gold Insurance', 'refund', claim IDs 'BGI-31199221', 'BGI-33991911', 'BGI-36110292', 'BGI-37918822', 'BGI-39667288', 'BGI-39882091', 'BGI-38399991' associated with $350, 'BGI-40001965' associated with $75, '$850', and '$1,275'",
    "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\": \"external_services_path is None; cannot check mailbox.json\"}\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, \"r\") as f:\n        data = json.load(f)\n    draft_emails = [e for e in data.get(\"emails\", []) if e.get(\"folder\", \"\").lower() in [\"drafts\", \"draft\"]]\n    \n    # Find the draft addressed to joem@pathfinder.com\n    joe_drafts = []\n    for e in draft_emails:\n        to_addr = e.get(\"to_addr\", \"\") or \"\"\n        if \"joem@pathfinder.com\" in to_addr.lower():\n            joe_drafts.append(e)\n    \n    if not joe_drafts:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No draft email addressed to joem@pathfinder.com found\"}\n    \n    failures = []\n    for email in joe_drafts:\n        f_list = []\n        # Check BCC\n        bcc = (email.get(\"bcc_addr\") or email.get(\"bcc\") or \"\").lower()\n        # Also check body/headers combined\n        body = (email.get(\"body_text\") or email.get(\"body\") or \"\").lower()\n        subject = (email.get(\"subject\") or \"\").lower()\n        full_text = body + \" \" + subject\n        \n        if \"mayj@pathfinder.com\" not in bcc:\n            f_list.append(\"BCC does not contain mayj@pathfinder.com\")\n        \n        required_strings = [\n            \"bullion gold insurance\",\n            \"refund\",\n            \"bgi-31199221\",\n            \"bgi-33991911\",\n            \"bgi-36110292\",\n            \"bgi-37918822\",\n            \"bgi-39667288\",\n            \"bgi-39882091\",\n            \"bgi-38399991\",\n            \"bgi-40001965\",\n        ]\n        for s in required_strings:\n            if s not in full_text:\n                f_list.append(f\"Missing '{s}' in email body\")\n        \n        # Check BGI-38399991 associated with $350\n        # Check BGI-40001965 associated with $75\n        # Check $850 and $1,275 present\n        amount_checks = [\"850\", \"1,275\", \"1275\"]\n        found_850 = \"850\" in full_text\n        found_1275 = \"1,275\" in full_text or \"1275\" in full_text\n        if not found_850:\n            f_list.append(\"Missing '$850' in email body\")\n        if not found_1275:\n            f_list.append(\"Missing '$1,275' (or '$1275') in email body\")\n        \n        # Check $350 in context of BGI-38399991\n        if \"bgi-38399991\" in full_text and \"350\" not in full_text:\n            f_list.append(\"Missing '$350' associated with BGI-38399991\")\n        \n        # Check $75 in context of BGI-40001965\n        if \"bgi-40001965\" in full_text and \"75\" not in full_text:\n            f_list.append(\"Missing '$75' associated with BGI-40001965\")\n        \n        if not f_list:\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": \"Draft email to joem@pathfinder.com contains all required content with correct BCC\"}\n        failures.append(f_list)\n    \n    return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Draft email to joem@pathfinder.com failed checks: {failures[0]}\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "c8a402ed-e9dc-4be5-aa7c-7e182d9a1f3c",
    "sort_order": 3,
    "rubric_text": "In `mailbox.json`, one draft email must be addressed to popler.wen@bulliongoldinsurance.com with mayj@pathfinder.com as BCC, contain 'overpayment'/'overpaid', 're-classed', 'unpaid claims', 'same patient', 'refund', 'subject to approval', 'updates', and claim ID/amount pairs: BGI-31199221/$350, BGI-33991911/$75, BGI-36110292/$75, BGI-37918822/$75, BGI-39667288/$350, BGI-39882091/$350",
    "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\": \"external_services_path is None; cannot check mailbox.json\"}\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, \"r\") as f:\n        data = json.load(f)\n    draft_emails = [e for e in data.get(\"emails\", []) if e.get(\"folder\", \"\").lower() in [\"drafts\", \"draft\"]]\n    \n    payer_drafts = []\n    for e in draft_emails:\n        to_addr = (e.get(\"to_addr\") or \"\").lower()\n        if \"popler.wen@bulliongoldinsurance.com\" in to_addr:\n            payer_drafts.append(e)\n    \n    if not payer_drafts:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No draft email addressed to popler.wen@bulliongoldinsurance.com found\"}\n    \n    for email in payer_drafts:\n        f_list = []\n        bcc = (email.get(\"bcc_addr\") or email.get(\"bcc\") or \"\").lower()\n        body = (email.get(\"body_text\") or email.get(\"body\") or \"\").lower()\n        subject = (email.get(\"subject\") or \"\").lower()\n        full_text = body + \" \" + subject\n        \n        if \"mayj@pathfinder.com\" not in bcc:\n            f_list.append(\"BCC does not contain mayj@pathfinder.com\")\n        \n        # Check for overpayment/overpaid\n        if not any(word in full_text for word in [\"overpayment\", \"overpaid\", \"over payment\", \"over paid\"]):\n            f_list.append(\"Missing 'overpayment' or 'overpaid'\")\n        \n        required_phrases = [\"re-classed\", \"unpaid claims\", \"same patient\", \"refund\", \"subject to approval\", \"updates\"]\n        # Be flexible with re-classed (also accept reclassed, re classed)\n        reclassed_variants = [\"re-classed\", \"reclassed\", \"re classed\"]\n        if not any(v in full_text for v in reclassed_variants):\n            f_list.append(\"Missing 're-classed' or equivalent\")\n        \n        for phrase in [\"unpaid claims\", \"same patient\", \"refund\", \"subject to approval\", \"updates\"]:\n            if phrase not in full_text:\n                f_list.append(f\"Missing '{phrase}'\")\n        \n        # Check claim ID/amount pairs\n        # BGI-31199221 with $350\n        # BGI-33991911 with $75\n        # BGI-36110292 with $75\n        # BGI-37918822 with $75\n        # BGI-39667288 with $350\n        # BGI-39882091 with $350\n        claim_ids = [\"bgi-31199221\", \"bgi-33991911\", \"bgi-36110292\", \"bgi-37918822\", \"bgi-39667288\", \"bgi-39882091\"]\n        for cid in claim_ids:\n            if cid not in full_text:\n                f_list.append(f\"Missing claim ID '{cid.upper()}'\")\n        \n        # Check amounts present\n        if \"350\" not in full_text:\n            f_list.append(\"Missing '$350' amount\")\n        if \"75\" not in full_text:\n            f_list.append(\"Missing '$75' amount\")\n        \n        if not f_list:\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": \"Draft email to popler.wen@bulliongoldinsurance.com contains all required content with correct BCC\"}\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Draft email to payer failed checks: {f_list}\"}\n    \n    return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No valid payer draft email found\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "cfa447fa-41b4-4058-b1f5-deeb4e157b27",
    "sort_order": 4,
    "rubric_text": "In `PBC_Claim_Register.xlsx`, cells I40, I41, I46, I47, I48, and I49 must each contain the value 'Unapplied'",
    "verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n    wb_files = list(Path(workspace_path).glob(\"*Claim_Register*.xlsx\")) + list(Path(workspace_path).glob(\"*claim_register*.xlsx\"))\n    if not wb_files:\n        wb_files = list(Path(workspace_path).glob(\"PBC_Claim_Register.xlsx\"))\n    if not wb_files:\n        # Try case-insensitive search\n        all_xlsx = list(Path(workspace_path).glob(\"*.xlsx\"))\n        wb_files = [f for f in all_xlsx if \"claim\" in f.name.lower() and \"register\" in f.name.lower()]\n    if not wb_files:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"PBC_Claim_Register.xlsx not found in workspace\"}\n    \n    wb_path = wb_files[0]\n    try:\n        wb = openpyxl.load_workbook(wb_path, data_only=True)\n    except Exception as e:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Failed to open workbook: {e}\"}\n    \n    ws = wb.active\n    cells_to_check = [\"I40\", \"I41\", \"I46\", \"I47\", \"I48\", \"I49\"]\n    failures = []\n    for cell_ref in cells_to_check:\n        val = ws[cell_ref].value\n        if val is None or str(val).strip().lower() != \"unapplied\":\n            failures.append(f\"{cell_ref}='{val}'\")\n    \n    if failures:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Cells not set to 'Unapplied': {failures}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"All cells I40, I41, I46, I47, I48, I49 contain 'Unapplied'\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "a7957495-62c0-4d52-9783-31d6d8e2f574",
    "sort_order": 5,
    "rubric_text": "In `PBC_Escalation_Log.xlsx`, cells row 3 through 8 must each contain 'Financial' as their Category, and 'Escalated to RCM Manager - 2026-04-09' as their Resolution_Tkg.",
    "verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n    # Find the escalation log file\n    wb_files = list(Path(workspace_path).glob(\"*Escalation_Log*.xlsx\")) + list(Path(workspace_path).glob(\"*escalation_log*.xlsx\"))\n    if not wb_files:\n        all_xlsx = list(Path(workspace_path).glob(\"*.xlsx\"))\n        wb_files = [f for f in all_xlsx if \"escalation\" in f.name.lower() and \"log\" in f.name.lower()]\n    if not wb_files:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"PBC_Escalation_Log.xlsx not found in workspace\"}\n    \n    wb_path = wb_files[0]\n    try:\n        wb = openpyxl.load_workbook(wb_path, data_only=True)\n    except Exception as e:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Failed to open workbook: {e}\"}\n    \n    ws = wb.active\n    \n    # Determine column indices for Category and Resolution_Tkg from header row\n    header_row = 1\n    max_col = ws.max_column or 20\n    cat_col = None\n    res_col = None\n    for col_idx in range(1, max_col + 1):\n        val = ws.cell(row=header_row, column=col_idx).value\n        if val is not None:\n            val_str = str(val).strip().lower()\n            if val_str in (\"category\",):\n                cat_col = col_idx\n            if \"resolution\" in val_str or \"tkg\" in val_str:\n                res_col = col_idx\n    \n    # Fallback: if headers not detected, assume common column positions\n    # Try row 2 as header as well if row 1 didn't work\n    if cat_col is None or res_col is None:\n        for try_header in [2]:\n            for col_idx in range(1, max_col + 1):\n                val = ws.cell(row=try_header, column=col_idx).value\n                if val is not None:\n                    val_str = str(val).strip().lower()\n                    if val_str in (\"category\",) and cat_col is None:\n                        cat_col = col_idx\n                    if (\"resolution\" in val_str or \"tkg\" in val_str) and res_col is None:\n                        res_col = col_idx\n    \n    # If still not found, use defaults (common layout: Category might be col D or E, Resolution_Tkg might be col E or later)\n    # We'll do a broader search across all columns for the expected values\n    if cat_col is None or res_col is None:\n        # Scan all columns to find which ones contain 'Financial' and which contain 'Escalated to RCM Manager'\n        for col_idx in range(1, max_col + 1):\n            for row_idx in range(3, 9):\n                val = ws.cell(row=row_idx, column=col_idx).value\n                if val is not None:\n                    val_str = str(val).strip()\n                    if val_str.lower() == \"financial\" and cat_col is None:\n                        cat_col = col_idx\n                    if \"escalated to rcm manager\" in val_str.lower() and res_col is None:\n                        res_col = col_idx\n    \n    failures = []\n    \n    expected_category = \"Financial\"\n    expected_resolution = \"Escalated to RCM Manager - 2026-04-09\"\n    \n    rows_to_check = [3, 4, 5, 6, 7, 8]\n    \n    # Check Category column\n    if cat_col is not None:\n        for row in rows_to_check:\n            val = ws.cell(row=row, column=cat_col).value\n            val_str = str(val).strip() if val is not None else \"\"\n            if val_str.lower() != expected_category.lower():\n                failures.append(f\"Row {row} Category (col {cat_col})='{val_str}', expected '{expected_category}'\")\n    else:\n        failures.append(\"Could not identify Category column in the spreadsheet\")\n    \n    # Check Resolution_Tkg column\n    if res_col is not None:\n        for row in rows_to_check:\n            val = ws.cell(row=row, column=res_col).value\n            val_str = str(val).strip() if val is not None else \"\"\n            if val_str != expected_resolution:\n                # Be a bit lenient: check case-insensitive and allow minor formatting differences\n                if val_str.lower().replace(\" \", \"\") != expected_resolution.lower().replace(\" \", \"\"):\n                    failures.append(f\"Row {row} Resolution_Tkg (col {res_col})='{val_str}', expected '{expected_resolution}'\")\n    else:\n        failures.append(\"Could not identify Resolution_Tkg column in the spreadsheet\")\n    \n    if failures:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Failures found: {'; '.join(failures)}\"}\n    \n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"All rows 3-8 contain Category='{expected_category}' and Resolution_Tkg='{expected_resolution}'\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "963d503e-dcb7-4cb6-aa32-a208b87bfa94",
    "sort_order": 6,
    "rubric_text": "In `slack_data.json`, channel C001 must contain a message with text 'Escalation: 2026-04-09. Category: Financial. Payer: Bullion Gold Insurance.'",
    "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\": \"external_services_path is None; cannot check slack_data.json\"}\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, \"r\") as f:\n        data = json.load(f)\n    \n    messages = data.get(\"messages\", {})\n    c001_messages = messages.get(\"C001\", [])\n    \n    expected = \"Escalation: 2026-04-09. Category: Financial. Payer: Bullion Gold Insurance.\"\n    expected_lower = expected.lower().strip()\n    \n    for msg in c001_messages:\n        text = (msg.get(\"text\") or \"\").strip().lower()\n        # Normalize whitespace\n        import re\n        text_norm = re.sub(r'\\s+', ' ', text)\n        exp_norm = re.sub(r'\\s+', ' ', expected_lower)\n        if exp_norm in text_norm or text_norm == exp_norm:\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found expected escalation message in C001\"}\n    \n    return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No message matching '{expected}' found in C001. Messages found: {[m.get('text','') for m in c001_messages]}\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "530d25ac-62c7-4548-a433-f90b592628dd",
    "sort_order": 7,
    "rubric_text": "In `slack_data.json`, channels C003 and C004 must both contain a message that reads 'Refund case - AA45175 - B8475Q'",
    "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\": \"external_services_path is None; cannot check slack_data.json\"}\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, \"r\") as f:\n        data = json.load(f)\n    \n    messages = data.get(\"messages\", {})\n    expected = \"Refund case - AA45175 - B8475Q\"\n    expected_lower = expected.lower().strip()\n    \n    failures = []\n    for channel in [\"C003\", \"C004\"]:\n        channel_messages = messages.get(channel, [])\n        found = False\n        for msg in channel_messages:\n            text = (msg.get(\"text\") or \"\").strip().lower()\n            text_norm = re.sub(r'\\s+', ' ', text)\n            exp_norm = re.sub(r'\\s+', ' ', expected_lower)\n            if exp_norm in text_norm:\n                found = True\n                break\n        if not found:\n            failures.append(f\"{channel} missing message '{expected}'\")\n    \n    if failures:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Missing refund messages: {failures}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Both C003 and C004 contain message '{expected}'\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "718f72d9-4438-443a-8dd4-3d3bc33e95ae",
    "sort_order": 8,
    "rubric_text": "In `calendar_data.json`, there must be an event titled 'Escalation Attempt - Jill - RCM Manager' scheduled on April 9th, 2026 from 1:30 PM to 3:30 PM",
    "verifier_code": "from pathlib import Path\nimport json\nfrom datetime import datetime\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"external_services_path is None; cannot check calendar_data.json\"}\n    cal_path = Path(external_services_path) / \"calendar_data.json\"\n    if not cal_path.exists():\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"calendar_data.json not found\"}\n    with open(cal_path, \"r\") as f:\n        data = json.load(f)\n    \n    events = data.get(\"events\", {})\n    expected_title = \"Escalation Attempt - Jill - RCM Manager\"\n    expected_date = \"2026-04-09\"\n    expected_start_time = \"13:30\"\n    expected_end_time = \"15:30\"\n    \n    for event_id, event in events.items():\n        summary = (event.get(\"summary\") or \"\").strip()\n        if summary.lower() != expected_title.lower():\n            continue\n        \n        start = event.get(\"start\", \"\")\n        end = event.get(\"end\", \"\")\n        start_str = str(start)\n        end_str = str(end)\n        \n        # Check date is 2026-04-09\n        if expected_date not in start_str:\n            continue\n        \n        # Check times - accept various formats (13:30, 1:30 PM, T13:30, etc.)\n        start_ok = \"13:30\" in start_str or \"1:30\" in start_str.lower()\n        end_ok = \"15:30\" in end_str or \"3:30\" in end_str.lower()\n        \n        if start_ok and end_ok:\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found event '{expected_title}' on 2026-04-09 from 1:30 PM to 3:30 PM\"}\n        else:\n            return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found event '{expected_title}' on correct date but wrong times: start='{start}', end='{end}'\"}\n    \n    found_titles = [e.get(\"summary\", \"\") for e in events.values()]\n    return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No event titled '{expected_title}' found on 2026-04-09. Events found: {found_titles}\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "rubric_1775842232676",
    "sort_order": 9,
    "rubric_text": "In `PBC_Escalation_Log.xlsx`, there must be exactly 7 rows of data (excluding headers).",
    "verifier_code": "from pathlib import Path\nimport openpyxl\n\ndef verify(workspace_path, external_services_path=None):\n    file_path = Path(workspace_path) / \"PBC_Escalation_Log.xlsx\"\n    \n    if not file_path.exists():\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"File 'PBC_Escalation_Log.xlsx' not found in workspace at {file_path}.\"}\n    \n    try:\n        wb = openpyxl.load_workbook(file_path)\n        ws = wb.active\n        \n        # Count data rows (non-empty rows excluding the header)\n        # Assume the first row is the header\n        data_row_count = 0\n        for row_idx, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):\n            # A row is considered a data row if it has at least one non-None, non-empty-string value\n            if any(cell is not None and str(cell).strip() != '' for cell in row):\n                data_row_count += 1\n        \n        wb.close()\n        \n        if data_row_count == 7:\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"PBC_Escalation_Log.xlsx has exactly 7 data rows (excluding headers) as required. Found {data_row_count} data rows.\"}\n        else:\n            return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"PBC_Escalation_Log.xlsx should have exactly 7 data rows (excluding headers), but found {data_row_count} data rows.\"}\n    except Exception as e:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Error reading 'PBC_Escalation_Log.xlsx': {str(e)}\"}\n",
    "criterion_type": "expected_output"
  }
]
