Files
handbook/tasks/hr_ridgeline_gear_co_b337f86b/tests/rubrics.json
T
2026-06-24 14:16:58 -07:00

80 lines
52 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"id": "8811a7ab-3093-4dcc-9023-50790db17899",
"sort_order": 0,
"rubric_text": "In `mailbox.json`, all emails on April 9, 2026 (1775692800) must be marked as read (is_read: true) or filed into a folder beyond \"Inbox\".",
"verifier_code": "from pathlib import Path\nimport json\nfrom datetime import datetime, timezone\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) as f:\n data = json.load(f)\n emails = data.get(\"emails\", [])\n\n # April 9, 2026 UTC: from 1775692800 to 1775779199\n april9_start = 1775692800\n april9_end = 1775779199\n\n april9_emails = []\n for e in emails:\n date_val = e.get(\"date\", \"\")\n matched = False\n\n # Check if date is a unix timestamp (int or numeric string)\n if isinstance(date_val, (int, float)):\n if april9_start <= int(date_val) <= april9_end:\n matched = True\n elif isinstance(date_val, str):\n # Try numeric string\n try:\n ts = int(float(date_val))\n if april9_start <= ts <= april9_end:\n matched = True\n except (ValueError, TypeError):\n pass\n\n # Try string date matching for April 9, 2026 in various formats\n if not matched:\n date_lower = date_val.strip()\n # Check common date patterns\n if any(pattern in date_lower for pattern in [\n \"2026-04-09\", \"04-09-2026\", \"04/09/2026\", \"4/9/2026\",\n \"april 9, 2026\", \"april 09, 2026\", \"apr 9, 2026\", \"apr 09, 2026\",\n \"9 apr 2026\", \"09 apr 2026\", \"9 april 2026\", \"09 april 2026\"\n ]):\n matched = True\n\n # Also check for just April 9 without year (be generous)\n if not matched:\n if any(pattern in date_lower for pattern in [\n \"04-09\", \"04/09\", \"4/9\",\n ]):\n # Only match if year is 2026 or not specified\n if \"2026\" in date_lower or not any(str(y) in date_lower for y in range(2020, 2035)):\n matched = True\n\n if matched:\n april9_emails.append(e)\n\n if not april9_emails:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No emails on April 9, 2026 found in mailbox.json\"}\n\n failing = []\n for e in april9_emails:\n is_read = e.get(\"is_read\", False)\n # Accept truthy values for is_read\n if isinstance(is_read, str):\n is_read = is_read.lower() in (\"true\", \"1\", \"yes\")\n else:\n is_read = bool(is_read)\n\n folder = str(e.get(\"folder\", \"Inbox\")).strip()\n filed_beyond_inbox = folder.lower() != \"inbox\" and folder != \"\"\n\n if not is_read and not filed_beyond_inbox:\n failing.append(\n f\"Email (id={e.get('email_id', 'unknown')}, from={e.get('from_addr', e.get('from', 'unknown'))}) \"\n f\"is neither marked as read (is_read={e.get('is_read', False)}) \"\n f\"nor filed into a folder beyond Inbox (folder='{folder}')\"\n )\n\n if failing:\n return {\n \"pass\": False,\n \"score\": 0.0,\n \"feedback\": \"Some April 9, 2026 emails are not marked as read or filed beyond Inbox: \" + \"; \".join(failing)\n }\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": (\n f\"All {len(april9_emails)} April 9, 2026 email(s) are marked as read or filed beyond Inbox.\"\n )\n }\n",
"criterion_type": "expected_output"
},
{
"id": "052d404a-5602-4ad1-9899-46bd121250f0",
"sort_order": 1,
"rubric_text": "In `mailbox.json`, there must an email sent to Claire (claire.ndubisi@ridgelinegear.com) that attaches both WH-380-F.pdf and WH-381-Claire.pdf.",
"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) as f:\n data = json.load(f)\n emails = data.get(\"emails\", [])\n \n # Find all emails sent TO Claire\n sent_to_claire = []\n for e in emails:\n to_addr = e.get(\"to_addr\", \"\")\n # Handle both string and list formats\n if isinstance(to_addr, list):\n to_str = \" \".join(str(a) for a in to_addr).lower()\n else:\n to_str = str(to_addr).lower()\n if \"claire\" in to_str or \"ndubisi\" in to_str:\n sent_to_claire.append(e)\n \n if not sent_to_claire:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No email sent to Claire found in mailbox.json\"}\n \n # Check each candidate for the required attachments\n for e in sent_to_claire:\n attachments = e.get(\"attachments\", [])\n attachment_names = []\n for a in attachments:\n if isinstance(a, str):\n attachment_names.append(a.lower())\n elif isinstance(a, dict):\n name = a.get(\"filename\", a.get(\"name\", \"\"))\n attachment_names.append(str(name).lower())\n \n has_380 = any(\n \"wh-380-f\" in a or \"wh_380_f\" in a or \"wh380f\" in a or \"wh-380f\" in a or \"380-f\" in a\n for a in attachment_names\n )\n has_381 = any(\n (\"wh-381\" in a or \"wh_381\" in a or \"wh381\" in a) and \"claire\" in a\n for a in attachment_names\n )\n if has_380 and has_381:\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": f\"Email to Claire found with both WH-380-F.pdf and WH-381-Claire.pdf attached (email_id={e.get('email_id')}). Attachments: {attachment_names}\"\n }\n \n # Collect summary for feedback\n attachment_summary = []\n for e in sent_to_claire:\n attachments = e.get(\"attachments\", [])\n att_names = []\n for a in attachments:\n if isinstance(a, str):\n att_names.append(a)\n elif isinstance(a, dict):\n att_names.append(a.get(\"filename\", a.get(\"name\", str(a))))\n attachment_summary.append({\"email_id\": e.get(\"email_id\"), \"subject\": e.get(\"subject\", \"\"), \"attachments\": att_names})\n \n return {\n \"pass\": False,\n \"score\": 0.0,\n \"feedback\": f\"Found {len(sent_to_claire)} email(s) sent to Claire but none have both WH-380-F.pdf and WH-381-Claire.pdf attached. Details: {attachment_summary}\"\n }\n",
"criterion_type": "expected_output"
},
{
"id": "c5a28a21-868e-4357-9368-30926c48af6e",
"sort_order": 2,
"rubric_text": "In `mailbox.json`, there must be an email sent to claire.ndubisi@ridgelinegear.com that uses the exact FMLA eligibility script from email template #2 in its body text without additional context or modifications: (Hi [First Name],\nThank you for submitting your FMLA leave request. I have reviewed your eligibility and\nconfirmed that you ARE eligible for FMLA leave under all three federal eligibility criteria as of\nyour requested leave start date of [Start Date].\nAttached is your official Notice of Eligibility and Rights & Responsibilities (DOL Form WH-381),\nwhich explains your rights and obligations under FMLA. Please read it carefully.\nTo complete your FMLA designation, I also need a medical certification from your healthcare\nprovider. Please find attached the appropriate certification form ([WH-380-E for your own\ncondition / WH-380-F for a family member's condition]). The completed certification is due by\n[Cert Due Date — 15 calendar days from today].\nIf the certification is not returned by the due date, your leave may be denied. If you anticipate\nany difficulty obtaining the certification by the deadline, please reply to this email so we can\ndiscuss.\nCase ID: LC-2026-0050 [must match this exactly]\nBest regards,\nRidgeline Gear Co. — Leave Coordinator\nleaves@ridgelinegear.com\n) Noting additional information, like Colorado FAMLI qualifications, is not allowed. The case ID must match what is shown here exactly, no leniency. ",
"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) as f:\n data = json.load(f)\n emails = data.get(\"emails\", [])\n\n # Find all emails sent TO Claire\n candidates = []\n for e in emails:\n to_addr = e.get(\"to_addr\", \"\") or \"\"\n if isinstance(to_addr, list):\n to_str = \" \".join(str(a) for a in to_addr).lower()\n else:\n to_str = str(to_addr).lower()\n if \"claire\" in to_str or \"ndubisi\" in to_str:\n candidates.append(e)\n\n if not candidates:\n # Also check for replies that might not have to_addr but have in_reply_to\n claire_msg_ids = set()\n for e in emails:\n from_addr = (e.get(\"from_addr\", \"\") or \"\").lower()\n if \"claire\" in from_addr or \"ndubisi\" in from_addr:\n msg_id = e.get(\"message_id\")\n if msg_id:\n claire_msg_ids.add(msg_id)\n if claire_msg_ids:\n candidates = [e for e in emails if e.get(\"in_reply_to\") in claire_msg_ids]\n\n if not candidates:\n all_to_addrs = [(e.get(\"email_id\", \"?\"), e.get(\"to_addr\", \"\"), e.get(\"from_addr\", \"\")) for e in emails]\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No email sent to Claire found. All emails to/from: {all_to_addrs[:20]}\"}\n\n # The rubric requires the email to use the exact FMLA eligibility script from\n # email template #2. We check for key phrases from that template in order.\n key_phrases = [\n r\"thank you for submitting your fmla leave request\",\n r\"(are\\s+)?eligible for fmla leave\",\n r\"eligibility criteria\",\n r\"(notice of eligibility|wh[\\-\\s]?381)\",\n r\"medical certification\",\n r\"wh[\\-\\s]?380\",\n r\"certification.{0,50}due\",\n r\"leave may be denied\",\n r\"case\\s*id\",\n r\"ridgeline gear\"\n ]\n\n # Check that Case ID is specifically LC-2026-0050\n case_id_pattern = r\"lc[\\-\\s]?2026[\\-\\s]?0050\"\n\n # Phrases/topics that indicate additional context beyond the template\n # The rubric specifically says noting additional info like Colorado FAMLI is not allowed\n disallowed_phrases = [\n r\"famli\",\n r\"colorado\",\n r\"state\\s+(leave|benefit|program)\",\n ]\n\n best_match = None\n best_score = 0\n best_details = {}\n best_extra_content_issue = False\n best_case_id_correct = False\n bodies_info = []\n\n for e in candidates:\n body = (e.get(\"body_text\", \"\") or e.get(\"body\", \"\") or \"\").lower()\n # Normalize whitespace for matching\n body_norm = re.sub(r'\\s+', ' ', body)\n\n matched = []\n missed = []\n for phrase in key_phrases:\n if re.search(phrase, body_norm):\n matched.append(phrase)\n else:\n missed.append(phrase)\n\n match_score = len(matched)\n\n # Check case ID specifically\n case_id_correct = bool(re.search(case_id_pattern, body_norm))\n\n # Check for disallowed extra content\n extra_content_found = []\n for dp in disallowed_phrases:\n if re.search(dp, body_norm):\n extra_content_found.append(dp)\n\n bodies_info.append({\n \"email_id\": e.get(\"email_id\"),\n \"match_score\": match_score,\n \"matched\": matched,\n \"missed\": missed,\n \"extra_content\": extra_content_found,\n \"case_id_correct\": case_id_correct,\n \"body_preview\": body[:500]\n })\n\n if match_score > best_score:\n best_score = match_score\n best_match = e\n best_details = {\"matched\": matched, \"missed\": missed, \"extra_content\": extra_content_found}\n best_extra_content_issue = len(extra_content_found) > 0\n best_case_id_correct = case_id_correct\n\n total_phrases = len(key_phrases)\n\n # If best match has disallowed extra content, it fails regardless of key phrase score\n if best_extra_content_issue and best_score >= 8:\n body = (best_match.get(\"body_text\", \"\") or best_match.get(\"body\", \"\") or \"\")\n return {\n \"pass\": False,\n \"score\": 0.3,\n \"feedback\": f\"Reply to Claire (email_id={best_match.get('email_id')}) follows the template structure ({best_score}/{total_phrases} key phrases matched), but contains disallowed additional content/modifications: {best_details['extra_content']}. The rubric says 'Noting additional information, like Colorado FAMLI qualifications, is not allowed.' Body preview: {body[:500]}\"\n }\n\n # Strict: require at least 8 out of 10 key phrases for a full pass, no disallowed content, and correct case ID\n if best_score >= 8 and not best_extra_content_issue and best_case_id_correct:\n body = (best_match.get(\"body_text\", \"\") or best_match.get(\"body\", \"\") or \"\")\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": f\"Reply to Claire (email_id={best_match.get('email_id')}) closely follows email template #2 script ({best_score}/{total_phrases} key phrases matched) without disallowed additional content. Case ID LC-2026-0050 found. Matched: {best_details['matched']}. Body preview: {body[:500]}\"\n }\n\n # If template matches well but case ID is wrong or missing\n if best_score >= 8 and not best_extra_content_issue and not best_case_id_correct:\n body = (best_match.get(\"body_text\", \"\") or best_match.get(\"body\", \"\") or \"\")\n return {\n \"pass\": False,\n \"score\": 0.7,\n \"feedback\": f\"Reply to Claire (email_id={best_match.get('email_id')}) follows the template structure ({best_score}/{total_phrases} key phrases matched) but Case ID LC-2026-0050 was not found in the email body. Body preview: {body[:500]}\"\n }\n\n # Partial: 6-7 out of 10 -- the template is mostly there but with some modifications\n if best_score >= 6:\n body = (best_match.get(\"body_text\", \"\") or best_match.get(\"body\", \"\") or \"\")\n extra_note = \"\"\n if best_extra_content_issue:\n extra_note = f\" Also contains disallowed additional content: {best_details['extra_content']}.\"\n return {\n \"pass\": False,\n \"score\": 0.5,\n \"feedback\": f\"Reply to Claire (email_id={best_match.get('email_id')}) partially follows template #2 but not closely enough ({best_score}/{total_phrases} key phrases matched). Matched: {best_details['matched']}. Missed: {best_details['missed']}.{extra_note} Body preview: {body[:500]}\"\n }\n\n return {\n \"pass\": False,\n \"score\": 0.0,\n \"feedback\": f\"Reply(s) to Claire found but none adhere to the exact FMLA eligibility template #2 script. Best match scored {best_score}/{total_phrases}. Candidate details: {json.dumps(bodies_info, indent=2)[:2000]}\"\n }\n",
"criterion_type": "expected_output"
},
{
"id": "3a943865-2fa2-446c-8d32-7a5e3e11e41f",
"sort_order": 3,
"rubric_text": "In `mailbox.json`, the email regarding FMLA approval sent to claire.ndubisi@ridgelinegear.com on April 9, 2026 (1775692800) must be placed in the 'FMLA Cases' 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) as f:\n data = json.load(f)\n emails = data.get(\"emails\", [])\n\n # Find the reply/sent email to Claire regarding FMLA\n candidate_emails = []\n for e in emails:\n # Check all possible recipient fields\n to_addr = (e.get(\"to_addr\", \"\") or \"\").lower()\n to_field = (e.get(\"to\", \"\") or \"\").lower()\n combined_to = to_addr + \" \" + to_field\n\n if \"claire\" not in combined_to:\n continue\n\n # Gather text from body, subject, attachments\n body = (e.get(\"body\", \"\") or \"\").lower()\n subject = (e.get(\"subject\", \"\") or \"\").lower()\n\n attachments = e.get(\"attachments\", []) or []\n attachment_names = \"\"\n if isinstance(attachments, list):\n for att in attachments:\n if isinstance(att, str):\n attachment_names += att.lower() + \" \"\n elif isinstance(att, dict):\n attachment_names += (att.get(\"filename\", \"\") or att.get(\"name\", \"\") or \"\").lower() + \" \"\n\n combined_text = body + \" \" + subject + \" \" + attachment_names\n\n # Check for FMLA-related content: forms (WH-380, WH-381), or keyword \"fmla\"\n has_380 = \"wh-380\" in combined_text or \"wh380\" in combined_text or \"380-f\" in combined_text or \"380f\" in combined_text\n has_381 = \"wh-381\" in combined_text or \"wh381\" in combined_text\n has_fmla = \"fmla\" in combined_text\n\n if has_380 or has_381 or has_fmla:\n candidate_emails.append(e)\n\n if not candidate_emails:\n # Fallback: look for any email to Claire around April 9 (check both 2025 and 2026)\n for e in emails:\n to_addr = (e.get(\"to_addr\", \"\") or \"\").lower()\n to_field = (e.get(\"to\", \"\") or \"\").lower()\n combined_to = to_addr + \" \" + to_field\n date = (e.get(\"date\", \"\") or \"\").lower()\n timestamp = e.get(\"timestamp\", e.get(\"ts\", e.get(\"time\", None)))\n\n date_match = (\"2026-04-09\" in date or \"2025-04-09\" in date or \"april 9\" in date or \"apr 9\" in date)\n # Also check timestamp around April 9, 2026 (1775692800 +/- 1 day = 86400)\n ts_match = False\n if timestamp is not None:\n try:\n ts_val = float(timestamp)\n if abs(ts_val - 1775692800) < 86400:\n ts_match = True\n except (ValueError, TypeError):\n pass\n\n if \"claire\" in combined_to and (date_match or ts_match):\n candidate_emails.append(e)\n\n if not candidate_emails:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No FMLA-related email sent to Claire found in mailbox.json\"}\n\n # Check that at least one of the candidate emails is in the FMLA Cases folder\n failures = []\n successes = []\n for e in candidate_emails:\n folder = (e.get(\"folder\", \"\") or \"\").strip()\n eid = e.get(\"email_id\", e.get(\"message_id\", e.get(\"id\", \"unknown\")))\n subject = e.get(\"subject\", \"\")\n # Be loose: accept any folder containing \"fmla\" (case-insensitive)\n if \"fmla\" in folder.lower():\n successes.append(f\"Email (id={eid}, subject='{subject}') is in folder '{folder}'\")\n else:\n failures.append(f\"Email to Claire (id={eid}, subject='{subject}') is in folder '{folder}', expected 'FMLA Cases'\")\n\n if successes:\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found {len(successes)} email(s) to Claire in FMLA Cases folder. \" + \"; \".join(successes)}\n\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Email(s) to Claire regarding FMLA not in FMLA Cases folder: \" + \"; \".join(failures)}\n",
"criterion_type": "expected_output"
},
{
"id": "0d6543f1-5d24-49a5-a9b3-670d80174c93",
"sort_order": 4,
"rubric_text": "In slack_data.json, there must be a message in #leave-intake on April 9, 2026 that follows the exact prescribed format from SOP §8.4 Step 8: FMLA INTAKE: [Case ID], [Employee Name], [Eligibility Status], [Reason Category], [Start Date], [Priority]. The case ID must be LC-2026-0050, the eligibility status must reflect that Claire is eligible, the reason category must be family care (not Claire's own condition), and the priority must be P2 ",
"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) as f:\n data = json.load(f)\n channels = data.get(\"channels\", {})\n leave_intake_id = None\n for cid, ch in channels.items():\n if ch.get(\"name\", \"\").lower() == \"leave-intake\":\n leave_intake_id = cid\n break\n if leave_intake_id is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"#leave-intake channel not found in slack_data.json\"}\n messages = data.get(\"messages\", {}).get(leave_intake_id, [])\n\n april_9_start = 1775692800.0 - 86400 * 3\n april_9_end = 1775692800.0 + 86400 * 4\n\n def is_around_april_9(ts_val):\n try:\n return april_9_start <= float(ts_val) <= april_9_end\n except (ValueError, TypeError):\n return False\n\n april_9_msgs = [m for m in messages if is_around_april_9(m.get(\"ts\", 0))]\n candidates = april_9_msgs if april_9_msgs else messages\n\n def check_message(text):\n t = text\n tl = text.lower()\n findings = {}\n score = 0.0\n\n # 1. FMLA INTAKE header\n has_header = bool(re.search(r\"fmla\\s*intake\", tl))\n findings[\"fmla_intake_header\"] = has_header\n if has_header:\n score += 0.1\n\n # 2. Case ID LC-2026-0050\n has_case_id = bool(re.search(r\"LC[\\-\\s]?2026[\\-\\s]?0050\", t, re.IGNORECASE))\n findings[\"case_id_LC-2026-0050\"] = has_case_id\n if has_case_id:\n score += 0.2\n\n # 3. Employee name (Claire or Ndubisi)\n has_name = bool(re.search(r\"claire|ndubisi\", tl))\n findings[\"employee_name\"] = has_name\n if has_name:\n score += 0.1\n\n # 4. Eligibility — eligib* covers eligible, eligibility, eligibility confirmed, etc.\n has_eligible = bool(re.search(r\"eligib\", tl))\n has_ineligible = bool(re.search(r\"ineligible|not\\s+eligible\", tl))\n is_eligible = has_eligible and not has_ineligible\n findings[\"eligible\"] = is_eligible\n if is_eligible:\n score += 0.2\n\n # 5. Reason category is family care (not own condition)\n family_patterns = [\n r\"family\\s*(member|care|caregiver)\",\n r\"care\\s*(for|of)\\s*(a\\s+)?family\",\n r\"\\(b\\)\", # SOP reason code (b) = family member serious health condition\n r\"family.*serious\",\n r\"serious.*family\",\n r\"mother|father|parent|spouse|domestic\\s+partner\",\n ]\n own_patterns = [r\"own\\s*(serious)?\\s*condition\", r\"employee.{0,5}own\", r\"self[\\-\\s]care\"]\n has_family = any(re.search(p, tl) for p in family_patterns)\n has_own = any(re.search(p, tl) for p in own_patterns)\n is_family_care = has_family and not has_own\n findings[\"family_care_reason\"] = is_family_care\n if is_family_care:\n score += 0.2\n\n # 6. Priority must be P2\n has_p2 = bool(re.search(r\"\\bP2\\b|priority\\s*[=:]?\\s*2\\b\", t, re.IGNORECASE))\n findings[\"priority_P2\"] = has_p2\n if has_p2:\n score += 0.2\n\n return score, findings\n\n best_score = 0.0\n best_findings = {}\n best_text = \"\"\n best_msg = None\n\n for msg in candidates:\n text = msg.get(\"text\", \"\")\n # Must mention the case ID to be considered\n if \"LC-2026-0050\" not in text and \"lc-2026-0050\" not in text.lower():\n continue\n score, findings = check_message(text)\n if score > best_score:\n best_score = score\n best_findings = findings\n best_text = text\n best_msg = msg\n\n # Fallback: search all messages if nothing found in date window\n if best_msg is None:\n for msg in messages:\n text = msg.get(\"text\", \"\")\n if \"LC-2026-0050\" not in text and \"lc-2026-0050\" not in text.lower():\n continue\n score, findings = check_message(text)\n if score > best_score:\n best_score = score\n best_findings = findings\n best_text = text\n best_msg = msg\n\n if best_msg is None:\n all_texts = [m.get(\"text\", \"\")[:80] for m in messages[-5:]]\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No message mentioning case LC-2026-0050 found in #leave-intake. Total messages: {len(messages)}. Recent: {all_texts}\"}\n\n required = [\"case_id_LC-2026-0050\", \"eligible\", \"family_care_reason\", \"priority_P2\"]\n missing = [k for k in required if not best_findings.get(k, False)]\n\n if not missing:\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"FMLA intake message meets all rubric requirements. Findings: {best_findings}. Preview: {best_text[:300]}\"}\n else:\n return {\"pass\": False, \"score\": round(best_score, 2), \"feedback\": f\"Intake message found but missing: {missing}. Findings: {best_findings}. Preview: {best_text[:300]}\"}",
"criterion_type": "expected_output"
},
{
"id": "069630d5-05c5-41ee-b74b-63a1fbe62c3f",
"sort_order": 5,
"rubric_text": "In `slack_data.json`, there must be a message in #leave-intake related to Claire's leave request on April 9, 2026 (1775692800) tagging <@U004> and <@U005>",
"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) as f:\n data = json.load(f)\n channels = data.get(\"channels\", {})\n leave_intake_id = None\n for cid, ch in channels.items():\n if ch.get(\"name\", \"\").lower() == \"leave-intake\":\n leave_intake_id = cid\n break\n if leave_intake_id is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"#leave-intake channel not found\"}\n messages = data.get(\"messages\", {}).get(leave_intake_id, [])\n if not messages:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No messages found in #leave-intake\"}\n\n required_uids = {\"U004\", \"U005\"}\n\n # April 9, 2026 — the rubric states the timestamp 1775692800 corresponds to April 9, 2026.\n # We'll give a generous 48-hour window around that timestamp to account for timezone differences.\n day_start = 1775692800 - 86400 # one day before\n day_end = 1775692800 + 2 * 86400 # two days after\n\n # Collect messages in the date window\n day_messages = []\n for msg in messages:\n ts_str = msg.get(\"ts\", \"\")\n try:\n ts_val = float(ts_str)\n except (ValueError, TypeError):\n continue\n if day_start <= ts_val <= day_end:\n day_messages.append(msg)\n\n if not day_messages:\n # Be lenient: also check ALL messages regardless of date\n day_messages = messages\n date_strict = False\n else:\n date_strict = True\n\n # Check if ANY message mentions Claire and tags both U004 and U005\n for msg in day_messages:\n text = msg.get(\"text\", \"\")\n text_lower = text.lower()\n \n # Check for Claire mention (be lenient)\n has_claire = \"claire\" in text_lower\n \n # Check for both required user tags\n found_all_tags = True\n for uid in required_uids:\n # Accept any occurrence of the UID in the message text\n # e.g. <@U004>, @U004, or just U004 as part of a mention\n if uid not in text:\n found_all_tags = False\n break\n \n if found_all_tags and has_claire:\n date_note = \"around April 9, 2026\" if date_strict else \"(date check relaxed)\"\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found a message in #leave-intake {date_note} related to Claire's leave that correctly tags both <@U004> and <@U005>. Text snippet: {text[:200]}\"}\n\n # Second pass: be even more lenient - just check for tags (maybe Claire is implied by context)\n for msg in day_messages:\n text = msg.get(\"text\", \"\")\n found_all_tags = True\n for uid in required_uids:\n if uid not in text:\n found_all_tags = False\n break\n if found_all_tags:\n # Check for any leave-related keywords\n text_lower = text.lower()\n leave_keywords = [\"leave\", \"fmla\", \"absence\", \"request\", \"claire\", \"wh-381\", \"wh-380\"]\n if any(kw in text_lower for kw in leave_keywords):\n date_note = \"around April 9, 2026\" if date_strict else \"(date check relaxed)\"\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found a message in #leave-intake {date_note} related to leave that correctly tags both <@U004> and <@U005>. Text snippet: {text[:200]}\"}\n\n # If we get here, no single message tagged both with relevant content\n all_texts = []\n for msg in day_messages:\n text = msg.get(\"text\", \"\")\n found = [uid for uid in required_uids if uid in text]\n missing = [uid for uid in required_uids if uid not in text]\n has_claire = \"claire\" in text.lower()\n all_texts.append(f\"ts={msg.get('ts')}: found_tags={found}, missing_tags={missing}, mentions_claire={has_claire}, text={text[:150]}\")\n\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No single message in #leave-intake tags both <@U004> and <@U005> in relation to Claire's leave. Messages inspected: \" + \"; \".join(all_texts)}\n",
"criterion_type": "expected_output"
},
{
"id": "16c7d90d-7485-4fe5-8161-0cce09e26e81",
"sort_order": 6,
"rubric_text": "In `slack_data.json`, no new messages on April 9, 2026 (1775692800) must contain em-dashes (\\u2014 or ---).",
"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) as f:\n data = json.load(f)\n\n # The rubric says April 9, 2026 with reference timestamp 1775692800\n # 1775692800 is 2026-04-09 00:00:00 UTC\n # End of that day: 1775692800 + 86400 - 1 = 1775779199\n april_9_start = 1775692800.0\n april_9_end = 1775779199.0\n\n em_dash_violations = []\n messages_dict = data.get(\"messages\", {})\n for channel_id, msgs in messages_dict.items():\n if not isinstance(msgs, list):\n continue\n for msg in msgs:\n ts_raw = msg.get(\"ts\", 0)\n try:\n ts = float(ts_raw)\n except (ValueError, TypeError):\n continue\n if april_9_start <= ts <= april_9_end:\n text = msg.get(\"text\", \"\")\n # Check for em-dash unicode character (\\u2014) or triple dash (---)\n if \"\\u2014\" in text or \"---\" in text:\n channel_name = channel_id\n # Try to resolve channel name for better feedback\n channels = data.get(\"channels\", {})\n if channel_id in channels:\n channel_name = channels[channel_id].get(\"name\", channel_id)\n snippet = text[:120].replace(\"\\n\", \" \")\n em_dash_violations.append(f\"Channel #{channel_name} (ts={ts_raw}): '{snippet}'\")\n\n if em_dash_violations:\n return {\n \"pass\": False,\n \"score\": 0.0,\n \"feedback\": f\"Found {len(em_dash_violations)} message(s) on April 9, 2026 containing em-dashes: \" + \"; \".join(em_dash_violations)\n }\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"No new messages on April 9, 2026 contain em-dashes (checked for \\u2014 and ---)\"\n }\n",
"criterion_type": "incorrect_behavior"
},
{
"id": "rubric_1776288776327",
"sort_order": 7,
"rubric_text": "In `mailbox.json`, there must be an email sent to `greg.ostrowski@ridgelinegear.com` on April 9, 2026 containing Claire Ndubisi's name, her leave start date (April 14, 2026), and her leave category. The email must not include the medical reason for her leave or any diagnosis. The email must be filed in the FMLA Cases folder, not in Sent Items.",
"verifier_code": "from pathlib import Path\nimport json\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n mailbox_path = None\n if external_services_path:\n c = Path(external_services_path) / \"mailbox.json\"\n if c.exists():\n mailbox_path = c\n if not mailbox_path:\n c = Path(workspace_path) / \"mailbox.json\"\n if c.exists():\n mailbox_path = c\n if not mailbox_path:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"mailbox.json not found.\"}\n\n with open(mailbox_path, \"r\", encoding=\"utf-8\") as f:\n data = json.load(f)\n emails = data.get(\"emails\", [])\n\n greg_emails = []\n for e in emails:\n to = (e.get(\"to_addr\") or \"\").lower()\n if \"greg.ostrowski\" in to or (\"ostrowski\" in to and \"ridgelinegear\" in to):\n greg_emails.append(e)\n\n if not greg_emails:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No email found addressed to greg.ostrowski@ridgelinegear.com.\"}\n\n date_patterns = [\n r\"april\\s*14\", r\"apr\\s*14\", r\"4[/\\-]14\", r\"04[/\\-]14\",\n r\"2026[/\\-]04[/\\-]14\", r\"14[/\\-]04\", r\"14\\s+april\",\n ]\n category_terms = [\n \"fmla\", \"family and medical leave\", \"family medical leave\",\n \"medical leave\", \"leave of absence\", \"approved leave\",\n \"leave request\", \"leave notification\", \"leave\",\n ]\n medical_leak_terms = [\n \"surgery\", \"cancer\", \"chemotherapy\", \"tumor\", \"diagnosis\",\n \"diabetes\", \"heart attack\", \"cardiac\", \"stroke\", \"fracture\",\n \"depression\", \"anxiety\", \"chronic illness\", \"autoimmune\",\n \"serious health condition details\", \"diagnosed with\",\n \"treatment for\", \"condition is\", \"hospitalized for\",\n ]\n\n issues_by_email = []\n for e in greg_emails:\n body = (e.get(\"body_text\") or \"\").lower()\n subject = (e.get(\"subject\") or \"\").lower()\n folder = (e.get(\"folder\") or \"\").lower()\n full = subject + \" \" + body\n issues = []\n\n if \"fmla\" not in folder:\n issues.append(f\"filed in '{e.get('folder', '')}' instead of FMLA Cases\")\n\n if \"claire\" not in full and \"ndubisi\" not in full:\n issues.append(\"missing Claire Ndubisi's name\")\n\n if not any(re.search(p, full) for p in date_patterns):\n issues.append(\"missing leave start date (April 14, 2026)\")\n\n if not any(t in full for t in category_terms):\n issues.append(\"missing leave category\")\n\n # Strip standard FMLA boilerplate before checking for diagnosis leaks\n cleaned = full\n for phrase in [\"family and medical leave act\", \"family and medical leave\",\n \"family medical leave act\", \"family medical leave\", \"medical leave\"]:\n cleaned = cleaned.replace(phrase, \"\")\n leaked = [t for t in medical_leak_terms if t in cleaned]\n if leaked:\n issues.append(f\"leaks medical reason/diagnosis ({leaked})\")\n\n issues_by_email.append((e, issues))\n\n passing = [(e, iss) for e, iss in issues_by_email if not iss]\n if passing:\n e = passing[0][0]\n return {\"pass\": True, \"score\": 1.0,\n \"feedback\": f\"Found qualifying manager notification to Greg Ostrowski. \"\n f\"folder='{e.get('folder')}', subject='{e.get('subject')}'.\"}\n\n feedback = \"No email to Greg Ostrowski met all criteria. Details: \" + \" | \".join(\n f\"subject='{e.get('subject')}', folder='{e.get('folder')}': {'; '.join(iss)}\"\n for e, iss in issues_by_email\n )\n return {\"pass\": False, \"score\": 0.0, \"feedback\": feedback}",
"criterion_type": "expected_output"
},
{
"id": "rubric_1776288802340",
"sort_order": 8,
"rubric_text": "In mailbox.json, the original intake email from claire.ndubisi@ridgelinegear.com (Email 1, received April 9, 2026) must be marked as read and moved to the \"FMLA Cases\" folder.",
"verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n # This rubric checks mailbox.json which lives in external_services_path\n if external_services_path is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"external_services_path is None; cannot locate mailbox.json to verify email state.\"}\n\n mailbox_path = Path(external_services_path) / \"mailbox.json\"\n if not mailbox_path.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"mailbox.json not found at {mailbox_path}\"}\n\n try:\n with open(mailbox_path, \"r\", encoding=\"utf-8\") as f:\n data = json.load(f)\n except Exception as e:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Failed to parse mailbox.json: {e}\"}\n\n emails = data.get(\"emails\", [])\n if not emails:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No emails found in mailbox.json.\"}\n\n # Find the email from Claire\n claire_emails = []\n for em in emails:\n from_addr = (em.get(\"from_addr\") or \"\").strip().lower()\n if \"claire.ndubisi@ridgelinegear.com\" in from_addr:\n claire_emails.append(em)\n\n if not claire_emails:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No email from claire.ndubisi@ridgelinegear.com found in mailbox.json.\"}\n\n # Try to find the specific email (Email 1 / the intake email received April 9 2026)\n # Be generous: check all emails from Claire and see if any matches\n target = None\n for em in claire_emails:\n # Prefer email_id \"1\" if present\n if str(em.get(\"email_id\", \"\")).strip() == \"1\":\n target = em\n break\n # If no email_id 1, just take the first one from Claire (be lenient)\n if target is None:\n target = claire_emails[0]\n\n is_read = target.get(\"is_read\", False)\n folder = (target.get(\"folder\") or \"\").strip()\n\n # Check folder - be flexible with naming\n folder_lower = folder.lower().replace(\" \", \"\").replace(\"_\", \"\").replace(\"-\", \"\")\n acceptable_folders = [\"fmlacases\", \"fmla cases\", \"fmla_cases\", \"fmla-cases\"]\n folder_ok = any(folder_lower == af.lower().replace(\" \", \"\").replace(\"_\", \"\").replace(\"-\", \"\") for af in acceptable_folders)\n\n # Also accept if the folder contains \"fmla\" and \"case\" loosely\n if not folder_ok:\n folder_ok = \"fmla\" in folder_lower and \"case\" in folder_lower\n\n issues = []\n if not is_read:\n issues.append(f\"Email is_read is {is_read} (expected True).\")\n if not folder_ok:\n issues.append(f\"Email folder is '{folder}' (expected 'FMLA Cases' or similar).\")\n\n if issues:\n score = 0.0\n # Give partial credit\n if is_read and not folder_ok:\n score = 0.5\n elif folder_ok and not is_read:\n score = 0.5\n return {\"pass\": False, \"score\": score, \"feedback\": \"Claire's intake email issues: \" + \" \".join(issues)}\n\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Claire's intake email (id={target.get('email_id')}) is marked as read (is_read={is_read}) and moved to folder '{folder}'.\"}",
"criterion_type": "expected_output"
},
{
"id": "rubric_1776288832903",
"sort_order": 9,
"rubric_text": "In `mailbox.json`, all outgoing emails sent from `leaves@ridgelinegear.com` on April 9, 2026 must correspond to actions required by the SOP for this case: the Template 2 eligibility notice to Claire and the Template 11 manager notification to Greg Ostrowski. No additional outgoing emails — including unsolicited acknowledgments, status updates, or notifications to parties not specified by the SOP — are permitted. These outgoing emails should be in the FMLA Cases 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\n mailbox_path = Path(external_services_path) / \"mailbox.json\"\n if not mailbox_path.exists():\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"mailbox.json not found at {mailbox_path}\"}\n\n with open(mailbox_path, \"r\", encoding=\"utf-8\") as f:\n data = json.load(f)\n\n emails = data.get(\"emails\", [])\n\n # The initial mailbox has emails with id 126 (pre-populated before the agent runs).\n # Any email with id >= 27 was sent by the agent during the task.\n # We identify agent-sent emails by email_id > 26, then verify they are from leaves@.\n INITIAL_EMAIL_THRESHOLD = 26\n\n agent_sent = []\n for e in emails:\n eid = e.get(\"email_id\")\n if eid is None:\n continue\n try:\n eid_int = int(eid)\n except (ValueError, TypeError):\n continue\n if eid_int <= INITIAL_EMAIL_THRESHOLD:\n continue\n from_addr = (e.get(\"from_addr\") or e.get(\"from\") or \"\").strip().lower()\n if from_addr == \"leaves@ridgelinegear.com\":\n agent_sent.append(e)\n\n if not agent_sent:\n any_outgoing = [e for e in emails if\n (e.get(\"from_addr\") or \"\").strip().lower() == \"leaves@ridgelinegear.com\"]\n agent_ids = [(e.get(\"email_id\"), e.get(\"date\", \"no date\")) for e in any_outgoing if\n (e.get(\"email_id\") or 0) > INITIAL_EMAIL_THRESHOLD]\n return {\"pass\": False, \"score\": 0.0,\n \"feedback\": f\"No agent-sent emails (id > {INITIAL_EMAIL_THRESHOLD}) from leaves@ridgelinegear.com found. \"\n f\"Found {len(any_outgoing)} total outgoing email(s) from leaves@. \"\n f\"Agent-id candidates: {agent_ids}\"}\n\n CLAIRE_TERMS = {\"claire\", \"ndubisi\"}\n GREG_TERMS = {\"greg\", \"ostrowski\"}\n\n eligibility_emails = []\n manager_emails = []\n extra_emails = []\n\n for e in agent_sent:\n to_addr = (e.get(\"to_addr\") or e.get(\"to\") or \"\").strip().lower()\n to_claire = any(t in to_addr for t in CLAIRE_TERMS)\n to_greg = any(t in to_addr for t in GREG_TERMS)\n\n if to_claire and not to_greg:\n eligibility_emails.append(e)\n elif to_greg and not to_claire:\n manager_emails.append(e)\n else:\n extra_emails.append(e)\n\n found_eligibility = len(eligibility_emails) > 0\n found_manager = len(manager_emails) > 0\n\n # More than one email to the same permitted recipient = extra\n extra_claire = eligibility_emails[1:]\n extra_greg = manager_emails[1:]\n all_extras = extra_emails + extra_claire + extra_greg\n\n feedback_parts = [f\"Agent-sent emails (id > {INITIAL_EMAIL_THRESHOLD}) from leaves@ridgelinegear.com: {len(agent_sent)}.\"]\n score = 0.0\n\n if found_eligibility:\n e = eligibility_emails[0]\n feedback_parts.append(f\"FOUND: Eligibility notice to Claire (id={e.get('email_id')}, folder='{e.get('folder')}').\")\n score += 0.40\n else:\n feedback_parts.append(\"MISSING: No outgoing email to Claire.\")\n\n if found_manager:\n e = manager_emails[0]\n feedback_parts.append(f\"FOUND: Manager notification to Greg Ostrowski (id={e.get('email_id')}, folder='{e.get('folder')}').\")\n score += 0.40\n else:\n feedback_parts.append(\"MISSING: No outgoing email to Greg Ostrowski.\")\n\n if not all_extras:\n feedback_parts.append(\"No extra/unsolicited outgoing emails.\")\n score += 0.20\n else:\n descs = [f\"id={ex.get('email_id')}, To: {ex.get('to_addr', '?')}, Subject: {ex.get('subject', '?')}\" for ex in all_extras]\n feedback_parts.append(f\"EXTRA EMAILS ({len(all_extras)}) not permitted by SOP: \" + \"; \".join(descs))\n\n passed = found_eligibility and found_manager and not all_extras\n\n return {\n \"pass\": passed,\n \"score\": round(min(score, 1.0), 2),\n \"feedback\": \" \".join(feedback_parts),\n }",
"criterion_type": "expected_output"
},
{
"id": "rubric_1776288881485",
"sort_order": 10,
"rubric_text": "In `leave_tracker.xlsx`, the row for case LC-2026-0050 must be identical to its initial state (columns G and L are blank):\nLC-2026-0050 | E-04471 | Claire Ndubisi | FMLA + FAMLI | 04/14/2026 | 05/26/2026 | (blank) | Continuous | 0 | 480 | Not Yet Requested | (blank) | Not Yet | P3 | Intake — Pending Info | N | N | Yes | Pending | Request received 04/09/2026 08:47 MT. Reason: family member (mother) serious health condition. Leave type: continuous. Foreseeable: No. Manager: Greg Ostrowski. Contact preference: work email. Status: not yet actioned. | 04/09/2026 | (blank) | (blank) | (blank) | (blank)",
"verifier_code": "from pathlib import Path\nimport openpyxl\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n file_path = Path(workspace_path) / \"leave_tracker.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 ws = wb.active\n\n # Find the row with LC-2026-0050\n target_row = None\n for row in ws.iter_rows(min_row=1, max_row=ws.max_row):\n for cell in row:\n if cell.value is not None and str(cell.value).strip() == \"LC-2026-0050\":\n target_row = cell.row\n break\n if target_row:\n break\n\n if target_row is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Could not find row with case LC-2026-0050 in leave_tracker.xlsx\"}\n\n # Expected values by column index (1-based)\n # Columns: A=1, B=2, C=3, D=4, E=5, F=6, G=7(blank), H=8, I=9, J=10, K=11, L=12(blank),\n # M=13, N=14, O=15, P=16, Q=17, R=18, S=19, T=20, U=21, V=22(blank), W=23(blank), X=24(blank), Y=25(blank)\n expected = {\n 1: \"LC-2026-0050\",\n 2: \"E-04471\",\n 3: \"Claire Ndubisi\",\n 4: \"FMLA + FAMLI\",\n 5: \"04/14/2026\",\n 6: \"05/26/2026\",\n 7: None, # blank\n 8: \"Continuous\",\n 9: 0,\n 10: 480,\n 11: \"Not Yet Requested\",\n 12: None, # blank\n 13: \"Not Yet\",\n 14: \"P3\",\n 15: \"Intake \\u2014 Pending Info\", # em-dash\n 16: \"N\",\n 17: \"N\",\n 18: \"Yes\",\n 19: \"Pending\",\n 20: \"Request received 04/09/2026 08:47 MT. Reason: family member (mother) serious health condition. Leave type: continuous. Foreseeable: No. Manager: Greg Ostrowski. Contact preference: work email. Status: not yet actioned.\",\n 21: \"04/09/2026\",\n 22: None,\n 23: None,\n 24: None,\n 25: None,\n }\n\n def normalize(val):\n if val is None:\n return None\n # Handle dates\n if hasattr(val, 'strftime'):\n return val.strftime('%m/%d/%Y')\n s = str(val).strip()\n if s == '' or s.lower() == 'none':\n return None\n return s\n\n def values_match(actual, expected_val):\n a = normalize(actual)\n e = normalize(expected_val)\n # Both blank\n if a is None and e is None:\n return True\n if a is None or e is None:\n return False\n # Try numeric comparison\n try:\n a_num = float(a)\n e_num = float(e)\n if e_num == 0:\n return abs(a_num) < 0.01\n return abs(a_num - e_num) / max(abs(e_num), 1e-9) < 0.05\n except (ValueError, TypeError):\n pass\n # String comparison (case-insensitive for short values, exact for long ones)\n return a.lower() == e.lower() or a == e\n\n mismatches = []\n for col_idx, exp_val in expected.items():\n actual_val = ws.cell(row=target_row, column=col_idx).value\n if not values_match(actual_val, exp_val):\n mismatches.append(f\"Column {col_idx}: expected {repr(normalize(exp_val))}, got {repr(normalize(actual_val))}\")\n\n if not mismatches:\n return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Row for LC-2026-0050 (row {target_row}) matches expected initial state across all columns.\"}\n else:\n detail = \"; \".join(mismatches)\n return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Row for LC-2026-0050 has mismatches: {detail}\"}\n",
"criterion_type": "incorrect_behavior"
}
]