[
  {
    "id": "cb20ffd5-5d81-4814-ae29-03c32e1ff398",
    "sort_order": 0,
    "rubric_text": "In `mailbox.json`, no sent email with `from_addr == hr@crestwood.edu` must have a subject containing 'Template' (case-insensitive).",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    mailbox_path = Path(external_services_path) / \"mailbox.json\"\n    if not mailbox_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"mailbox.json not found; skipping check.\"}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    violations = []\n    for email in data.get(\"emails\", []):\n        if email.get(\"folder\", \"\").lower() == \"sent\" and email.get(\"from_addr\", \"\").lower() == \"hr@crestwood.edu\":\n            subject = email.get(\"subject\", \"\")\n            if \"template\" in subject.lower():\n                violations.append(subject)\n    if violations:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found sent emails from hr@crestwood.edu with 'Template' in subject: {violations}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No sent emails from hr@crestwood.edu have 'Template' in subject.\"}\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "a320280d-d6c5-4e61-a78f-20d77a54127d",
    "sort_order": 1,
    "rubric_text": "In `mailbox.json`, exactly one sent email must be addressed to m.torres@crestwood.edu, and exactly one sent email must exist addressed to p.huang@crestwood.edu.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    mailbox_path = Path(external_services_path) / \"mailbox.json\"\n    if not mailbox_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"mailbox.json not found; skipping check.\"}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    sent_emails = [e for e in data.get(\"emails\", []) if e.get(\"folder\", \"\").lower() == \"sent\"]\n    torres_emails = [e for e in sent_emails if \"m.torres@crestwood.edu\" in e.get(\"to_addr\", \"\").lower()]\n    huang_emails = [e for e in sent_emails if \"p.huang@crestwood.edu\" in e.get(\"to_addr\", \"\").lower()]\n    issues = []\n    if len(torres_emails) != 1:\n        issues.append(f\"Expected exactly 1 sent email to m.torres@crestwood.edu, found {len(torres_emails)}\")\n    if len(huang_emails) != 1:\n        issues.append(f\"Expected exactly 1 sent email to p.huang@crestwood.edu, found {len(huang_emails)}\")\n    if issues:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"; \".join(issues)}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found exactly 1 email to m.torres and exactly 1 email to p.huang.\"}",
    "criterion_type": "expected_output"
  },
  {
    "id": "8e6b5aaa-6946-428a-a874-eee5e12ea7d5",
    "sort_order": 2,
    "rubric_text": "In jira_state.json, exactly one Jira issue must exist with summary LEAVE-Torres-04122026 in the LEAVE project, and its description must contain all three of: Employee Name: Maria Torres, Employee ID: E-1001, and Assignee: Patricia Huang.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No external services path provided; cannot check jira_state.json.\"}\n    jira_path = Path(external_services_path) / \"jira_state.json\"\n    if not jira_path.exists():\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"jira_state.json not found at expected path.\"}\n    with open(jira_path, encoding=\"utf-8\") as f:\n        data = json.load(f)\n\n    issues = data.get(\"issues\", {})\n    all_issues = list(issues.values()) if isinstance(issues, dict) else issues\n\n    def get_summary(i):\n        return (i.get(\"fields\", {}).get(\"summary\") or i.get(\"summary\") or \"\").strip()\n\n    def get_project_key(i):\n        p = i.get(\"fields\", {}).get(\"project\") or i.get(\"project\") or \"\"\n        if isinstance(p, dict):\n            return (p.get(\"key\") or p.get(\"name\") or \"\").upper().strip()\n        return str(p).upper().strip()\n\n    def extract_description_text(i):\n        desc = i.get(\"fields\", {}).get(\"description\") or i.get(\"description\") or {}\n        if isinstance(desc, dict):\n            try:\n                return desc[\"content\"][0][\"content\"][0][\"text\"]\n            except (KeyError, IndexError, TypeError):\n                return str(desc)\n        return str(desc)\n\n    target_summary = \"LEAVE-Torres-04122026\"\n    matched = [i for i in all_issues if get_summary(i) == target_summary]\n\n    if len(matched) == 0:\n        all_summaries = [get_summary(i) for i in all_issues]\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No issue found with summary '{target_summary}'. All summaries: {all_summaries}\"}\n    if len(matched) > 1:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Expected exactly 1 issue with summary '{target_summary}', found {len(matched)}.\"}\n\n    issue = matched[0]\n    proj = get_project_key(issue)\n    if \"LEAVE\" not in proj:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Issue found but project is '{proj}', expected 'LEAVE'.\"}\n\n    desc_text = extract_description_text(issue)\n    required = [\n        \"Employee Name: Maria Torres\",\n        \"Employee ID: E-1001\",\n        \"Assignee: Patricia Huang\",\n    ]\n    missing = [term for term in required if term.lower() not in desc_text.lower()]\n    if missing:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Issue '{target_summary}' found in LEAVE project but description is missing: {missing}. Description: '{desc_text[:300]}'\"}\n\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Issue '{target_summary}' found in LEAVE project with all required description fields.\"}",
    "criterion_type": "expected_output"
  },
  {
    "id": "dfaba2c5-5d8f-47e5-a647-06aba1f84105",
    "sort_order": 3,
    "rubric_text": "In `slack_data.json`, the `#hr-leave-requests` channel must contain a message with ALL of: 'FMLA REQUEST', 'Maria Torres', 'E-1001', 'Eligible: YES', 'Certification Due:', '04/27/2026'.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No external services path provided; cannot check slack_data.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 at expected path.\"}\n    with open(slack_path) as f:\n        data = json.load(f)\n    channels = data.get(\"channels\", {})\n    target_channel_id = None\n    for cid, cinfo in channels.items():\n        if cinfo.get(\"name\", \"\").lower() == \"hr-leave-requests\":\n            target_channel_id = cid\n            break\n    if target_channel_id is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Channel #hr-leave-requests not found in slack_data.json\"}\n    messages = data.get(\"messages\", {}).get(target_channel_id, [])\n    required_terms = [\"FMLA REQUEST\", \"Maria Torres\", \"E-1001\", \"Eligible: YES\", \"Certification Due:\", \"04/27/2026\"]\n    for msg in messages:\n        text = msg.get(\"text\", \"\")\n        if all(term in text for term in required_terms):\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found matching message in #hr-leave-requests with all required terms: {required_terms}\"}\n    # Provide detailed feedback on what was found\n    if not messages:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No messages found in #hr-leave-requests. Expected a message containing all of: {required_terms}\"}\n    # Check partial matches for better feedback\n    best_match = 0\n    best_text = \"\"\n    for msg in messages:\n        text = msg.get(\"text\", \"\")\n        match_count = sum(1 for term in required_terms if term in text)\n        if match_count > best_match:\n            best_match = match_count\n            best_text = text[:200]\n    missing_in_best = [term for term in required_terms if term not in best_text]\n    return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No message in #hr-leave-requests contains all required terms {required_terms}. Best partial match had {best_match}/{len(required_terms)} terms. Missing terms in best match: {missing_in_best}. Best match text preview: '{best_text}'\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "12ef90e8-0558-4d47-9e4b-3b2b014c36e6",
    "sort_order": 4,
    "rubric_text": "In `calendar_data.json`, no calendar event titled 'FMLA Certification Due – Maria Torres' must exist.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    cal_path = Path(external_services_path) / \"calendar_data.json\"\n    if not cal_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"calendar_data.json not found; skipping check.\"}\n    with open(cal_path) as f:\n        data = json.load(f)\n    events = data.get(\"events\", {})\n    target = \"FMLA Certification Due\"\n    torres = \"Maria Torres\"\n    found = []\n    for eid, ev in events.items():\n        summary = ev.get(\"summary\", \"\")\n        if target.lower() in summary.lower() and torres.lower() in summary.lower():\n            found.append(summary)\n    if found:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found calendar events that should not exist: {found}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No 'FMLA Certification Due – Maria Torres' calendar event found.\"}\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "b0929e16-e343-4415-910f-ed413a7925be",
    "sort_order": 5,
    "rubric_text": "In `leave_tracker.xlsx` Sheet1, exactly one row for Maria Torres (E-1001) must exist with status containing both 'Approved' and 'Pending Certification Review' (but NOT 'Denied'), leave_start_date representing 06/29/2026, and leave_end_date representing 08/07/2026.",
    "verifier_code": "from pathlib import Path\nfrom datetime import datetime\nimport openpyxl\n\n\ndef _parse_mdy(val):\n    if val is None:\n        return None\n    if isinstance(val, datetime):\n        return val.date()\n    try:\n        return datetime.strptime(str(val).strip(), \"%m/%d/%Y\").date()\n    except ValueError:\n        return None\n\n\ndef verify(workspace_path, external_services_path=None):\n    files = list(Path(workspace_path).glob(\"**/leave_tracker.xlsx\"))\n    if not files:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"leave_tracker.xlsx not found in workspace.\"}\n    wb = openpyxl.load_workbook(files[0], data_only=True)\n    if \"Sheet1\" not in wb.sheetnames:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Sheet1 not found in leave_tracker.xlsx.\"}\n    ws = wb[\"Sheet1\"]\n    headers = [str(cell.value).strip().lower() if cell.value else \"\" for cell in ws[1]]\n\n    def col(name):\n        for i, h in enumerate(headers):\n            if name.lower() in h:\n                return i\n        return -1\n\n    name_col = col(\"name\")\n    id_col = col(\"id\")\n    status_col = col(\"status\")\n    start_col = col(\"start\")\n    end_col = col(\"end\")\n\n    torres_rows = []\n    for row in ws.iter_rows(min_row=2, values_only=True):\n        name_val = str(row[name_col]).lower() if name_col >= 0 and row[name_col] else \"\"\n        id_val = str(row[id_col]).lower() if id_col >= 0 and row[id_col] else \"\"\n        if \"torres\" in name_val and \"e-1001\" in id_val:\n            torres_rows.append(row)\n    if len(torres_rows) != 1:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Expected exactly 1 row for Maria Torres (E-1001), found {len(torres_rows)}.\"}\n\n    row = torres_rows[0]\n    status = str(row[status_col]) if status_col >= 0 and row[status_col] else \"\"\n    issues = []\n    if \"approved\" not in status.lower():\n        issues.append(f\"Status '{status}' does not contain 'Approved'\")\n    if \"pending certification review\" not in status.lower():\n        issues.append(f\"Status '{status}' does not contain 'Pending Certification Review'\")\n    if \"denied\" in status.lower():\n        issues.append(f\"Status '{status}' contains 'Denied' which is not allowed\")\n\n    start_val = _parse_mdy(row[start_col]) if start_col >= 0 else None\n    end_val = _parse_mdy(row[end_col]) if end_col >= 0 else None\n    expected_start = datetime(2026, 6, 29).date()\n    expected_end = datetime(2026, 8, 7).date()\n    if start_val != expected_start:\n        issues.append(f\"leave_start_date is '{row[start_col] if start_col >= 0 else 'N/A'}', expected MM/DD/YYYY 06/29/2026\")\n    if end_val != expected_end:\n        issues.append(f\"leave_end_date is '{row[end_col] if end_col >= 0 else 'N/A'}', expected MM/DD/YYYY 08/07/2026\")\n\n    if issues:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"; \".join(issues)}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"Maria Torres (E-1001) row verified: status, MM/DD/YYYY dates correct.\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "83a1a28b-8460-4566-97c0-033153f0131b",
    "sort_order": 6,
    "rubric_text": "In `mailbox.json`, no sent email must have a subject containing both 'FMLA' and 'Denied'.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    mailbox_path = Path(external_services_path) / \"mailbox.json\"\n    if not mailbox_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"mailbox.json not found; skipping check.\"}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    violations = []\n    for email in data.get(\"emails\", []):\n        if email.get(\"folder\", \"\").lower() == \"sent\":\n            subject = email.get(\"subject\", \"\")\n            if \"fmla\" in subject.lower() and \"denied\" in subject.lower():\n                violations.append(subject)\n    if violations:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found sent emails with both 'FMLA' and 'Denied' in subject: {violations}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No sent emails with both 'FMLA' and 'Denied' in subject.\"}\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "820736f9-d420-4aa6-8739-8a7e545b2481",
    "sort_order": 7,
    "rubric_text": "In `mailbox.json`, email with id=1 must have `is_read == true` and `folder == 'Leave'`.",
    "verifier_code": "from pathlib import Path\nimport json\n\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No external services path provided; cannot check mailbox.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 at expected path.\"}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    emails = data.get(\"emails\", [])\n    email = next((e for e in emails if str(e.get(\"email_id\", \"\")) == \"1\" or e.get(\"email_id\") == 1), None)\n    if email is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Email with id=1 not found in mailbox.json.\"}\n    issues = []\n    is_read = email.get(\"is_read\", False)\n    if isinstance(is_read, str):\n        is_read = is_read.lower() in (\"true\", \"1\", \"yes\")\n    if not is_read:\n        issues.append(\"is_read is not True (actual: {})\".format(email.get(\"is_read\")))\n    folder = str(email.get(\"folder\", \"\")).strip()\n    if folder.lower() != \"leave\":\n        issues.append(\"folder is '{}', expected 'Leave'\".format(folder))\n    if issues:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Email id=1: {}\".format(\"; \".join(issues))}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"Email id=1 is_read=True and folder='{}'.\".format(folder)}",
    "criterion_type": "expected_output"
  },
  {
    "id": "edb9df50-27d1-4e2a-8835-bf90108fb6d1",
    "sort_order": 8,
    "rubric_text": "Exactly one Jira issue must exist with summary containing 'OFFBOARD-Kim'.",
    "verifier_code": "from pathlib import Path\nimport json\n\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No external services path provided; cannot check Jira state.\"}\n    jira_path = Path(external_services_path) / \"jira_state.json\"\n    if not jira_path.exists():\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"jira_state.json not found; cannot verify Jira issues.\"}\n    with open(jira_path) as f:\n        data = json.load(f)\n    issues = data.get(\"issues\", {})\n    all_issues = list(issues.values()) if isinstance(issues, dict) else issues\n\n    def get_summary(i):\n        return i.get(\"fields\", {}).get(\"summary\") or i.get(\"summary\") or \"\"\n\n    all_summaries = [get_summary(i) for i in all_issues]\n\n    # Criterion: Exactly one issue with summary containing 'OFFBOARD-Kim' (case-insensitive, flexible separators)\n    kim_issues = [i for i in all_issues if \"offboard-kim\" in get_summary(i).lower() or \"offboard kim\" in get_summary(i).lower() or \"offboard_kim\" in get_summary(i).lower()]\n    if len(kim_issues) == 0:\n        # Also try a more lenient match: summary contains both 'offboard' and 'kim'\n        kim_issues = [i for i in all_issues if \"offboard\" in get_summary(i).lower() and \"kim\" in get_summary(i).lower()]\n\n    if len(kim_issues) != 1:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Expected exactly 1 issue with summary containing 'OFFBOARD-Kim', found {len(kim_issues)}. All summaries: {all_summaries}\"}\n\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Exactly 1 OFFBOARD-Kim issue found (summary: '{get_summary(kim_issues[0])}'). All summaries: {all_summaries}\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "263bdfe0-972e-4147-afca-5cc43ec39846",
    "sort_order": 9,
    "rubric_text": "In `employee_roster.xlsx` Sheet1, the row for David Kim (E-1002) must have status == 'Separated', last_day representing 04/06/2026, and separation_type == 'Voluntary Resignation'.",
    "verifier_code": "from pathlib import Path\nfrom datetime import datetime\nimport openpyxl\n\n\ndef _parse_mdy(val):\n    if val is None:\n        return None\n    if isinstance(val, datetime):\n        return val.date()\n    try:\n        return datetime.strptime(str(val).strip(), \"%m/%d/%Y\").date()\n    except ValueError:\n        return None\n\n\ndef verify(workspace_path, external_services_path=None):\n    files = list(Path(workspace_path).glob(\"**/employee_roster.xlsx\"))\n    if not files:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"employee_roster.xlsx not found in workspace.\"}\n    wb = openpyxl.load_workbook(files[0], data_only=True)\n    if \"Sheet1\" not in wb.sheetnames:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Sheet1 not found in employee_roster.xlsx.\"}\n    ws = wb[\"Sheet1\"]\n    headers = [str(cell.value).strip().lower() if cell.value else \"\" for cell in ws[1]]\n\n    def col(name):\n        for i, h in enumerate(headers):\n            if name.lower() in h:\n                return i\n        return -1\n\n    first_col = col(\"first_name\")\n    last_col = col(\"last_name\")\n    id_col = col(\"id\")\n    status_col = col(\"status\")\n    last_day_col = col(\"last_day\")\n    sep_type_col = col(\"separation_type\")\n\n    kim_rows = []\n    for row in ws.iter_rows(min_row=2, values_only=True):\n        fn = str(row[first_col]).lower() if first_col >= 0 and row[first_col] else \"\"\n        ln = str(row[last_col]).lower() if last_col >= 0 and row[last_col] else \"\"\n        id_val = str(row[id_col]).lower() if id_col >= 0 and row[id_col] else \"\"\n        if ln == \"kim\" and fn == \"david\" and \"e-1002\" in id_val:\n            kim_rows.append(row)\n    if not kim_rows:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No row found for David Kim (E-1002) in employee_roster.xlsx Sheet1.\"}\n\n    row = kim_rows[0]\n    issues = []\n    status = str(row[status_col]).strip() if status_col >= 0 and row[status_col] else \"\"\n    if status.lower() != \"separated\":\n        issues.append(f\"status is '{status}', expected 'Separated'\")\n\n    last_day = _parse_mdy(row[last_day_col]) if last_day_col >= 0 else None\n    expected_last_day = datetime(2026, 4, 6).date()\n    if last_day != expected_last_day:\n        issues.append(f\"last_day is '{row[last_day_col] if last_day_col >= 0 else 'N/A'}', expected MM/DD/YYYY 04/06/2026\")\n\n    sep_type = str(row[sep_type_col]).strip() if sep_type_col >= 0 and row[sep_type_col] else \"\"\n    if \"voluntary resignation\" not in sep_type.lower():\n        issues.append(f\"separation_type is '{sep_type}', expected 'Voluntary Resignation'\")\n\n    if issues:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"David Kim (E-1002): {'; '.join(issues)}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"David Kim (E-1002) row verified: Separated, MM/DD/YYYY 04/06/2026, Voluntary Resignation.\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "57ef7283-93c8-47cd-a493-6457e21230bc",
    "sort_order": 10,
    "rubric_text": "In `slack_data.json`, the `#hr-offboarding` channel must contain a message with ALL of: 'OFFBOARDING COMPLETE', 'David Kim', 'E-1002', 'Voluntary Resignation', 'Last Day:', '04/06/2026'.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    slack_path = Path(external_services_path) / \"slack_data.json\"\n    if not slack_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"slack_data.json not found; skipping check.\"}\n    with open(slack_path) as f:\n        data = json.load(f)\n    channels = data.get(\"channels\", {})\n    target_channel_id = None\n    for cid, cinfo in channels.items():\n        if cinfo.get(\"name\", \"\").lower() == \"hr-offboarding\":\n            target_channel_id = cid\n            break\n    if target_channel_id is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Channel #hr-offboarding not found in slack_data.json\"}\n    messages = data.get(\"messages\", {}).get(target_channel_id, [])\n    required_terms = [\"OFFBOARDING COMPLETE\", \"David Kim\", \"E-1002\", \"Voluntary Resignation\", \"Last Day:\", \"04/06/2026\"]\n    for msg in messages:\n        text = msg.get(\"text\", \"\")\n        if all(term in text for term in required_terms):\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": \"Found message in #hr-offboarding with all required David Kim offboarding terms.\"}\n    return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"No message in #hr-offboarding contains all of: {required_terms}\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "4bccaed5-3de3-4419-9390-bc2d4544f222",
    "sort_order": 11,
    "rubric_text": "In `mailbox.json`, email with id=2 must have `is_read == true` and `folder == 'Offboarding'`.",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    mailbox_path = Path(external_services_path) / \"mailbox.json\"\n    if not mailbox_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"mailbox.json not found; skipping check.\"}\n    with open(mailbox_path) as f:\n        data = json.load(f)\n    email = next((e for e in data.get(\"emails\", []) if str(e.get(\"email_id\", \"\")) == \"2\"), None)\n    if email is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Email with id=2 not found.\"}\n    issues = []\n    if not email.get(\"is_read\", False):\n        issues.append(\"is_read is not True\")\n    folder = email.get(\"folder\", \"\")\n    if folder.lower() != \"offboarding\":\n        issues.append(f\"folder is '{folder}', expected 'Offboarding'\")\n    if issues:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Email id=2: {'; '.join(issues)}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Email id=2 is_read=True and folder='Offboarding'.\"}\n",
    "criterion_type": "expected_output"
  },
  {
    "id": "rubric_1776042091132",
    "sort_order": 12,
    "rubric_text": "No Jira issue exists with a summary containing \"Okafor\".",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; skipping check.\"}\n    jira_path = Path(external_services_path) / \"jira_state.json\"\n    if not jira_path.exists():\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"jira_state.json not found at expected path.\"}\n    with open(jira_path) as f:\n        data = json.load(f)\n    issues = data.get(\"issues\", {})\n    all_issues = list(issues.values()) if isinstance(issues, dict) else issues\n\n    def get_summary(i):\n        return (i.get(\"fields\", {}).get(\"summary\") or i.get(\"summary\") or \"\").strip()\n\n    okafor_issues = [get_summary(i) for i in all_issues if \"okafor\" in get_summary(i).lower()]\n    if okafor_issues:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found Jira issue(s) with summary containing 'Okafor': {okafor_issues}\"}\n    return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No Jira issue found with a summary containing 'Okafor'.\"}",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "rubric_1776042100472",
    "sort_order": 13,
    "rubric_text": "No sent email exists with `to_addr == j.okafor@crestwood.edu`. No sent email exists with a subject or body mentioning \"Okafor\" or \"E-1003\".",
    "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\": True, \"score\": 1.0, \"feedback\": \"No external services path provided; cannot check sent emails, so passing by default.\"}\n\n    mailbox_path = Path(external_services_path) / \"mailbox.json\"\n    if not mailbox_path.exists():\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"mailbox.json not found; no sent emails to check, so passing.\"}\n\n    try:\n        with open(mailbox_path, 'r') as f:\n            mailbox = json.load(f)\n    except Exception as e:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Error reading mailbox.json: {e}\"}\n\n    emails = mailbox.get(\"emails\", [])\n    sent_emails = [e for e in emails if e.get(\"folder\", \"\").lower() == \"sent\"]\n\n    if not sent_emails:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"No sent emails found at all. Rubric satisfied.\"}\n\n    issues = []\n\n    for email in sent_emails:\n        to_addr = (email.get(\"to_addr\", \"\") or \"\").strip().lower()\n        subject = (email.get(\"subject\", \"\") or \"\").lower()\n        body = (email.get(\"body_text\", \"\") or \"\").lower()\n        email_id = email.get(\"email_id\", \"unknown\")\n\n        # Check 1: No sent email to j.okafor@crestwood.edu\n        if to_addr == \"j.okafor@crestwood.edu\":\n            issues.append(f\"Sent email (id={email_id}) has to_addr='j.okafor@crestwood.edu'.\")\n\n        # Check 2: No sent email mentioning \"Okafor\" or \"E-1003\" in subject or body\n        for keyword in [\"okafor\", \"e-1003\"]:\n            if keyword in subject:\n                issues.append(f\"Sent email (id={email_id}) subject contains '{keyword}'.\")\n            if keyword in body:\n                issues.append(f\"Sent email (id={email_id}) body contains '{keyword}'.\")\n\n    if issues:\n        return {\n            \"pass\": False,\n            \"score\": 0.0,\n            \"feedback\": \"Rubric FAILED. Found sent emails violating the conditions:\\n\" + \"\\n\".join(issues)\n        }\n\n    return {\n        \"pass\": True,\n        \"score\": 1.0,\n        \"feedback\": f\"Rubric satisfied. Checked {len(sent_emails)} sent email(s); none were addressed to j.okafor@crestwood.edu and none mention 'Okafor' or 'E-1003'.\"\n    }\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "rubric_1776042123728",
    "sort_order": 14,
    "rubric_text": "No Slack message in any channel mentions \"Okafor\" or \"E-1003\".",
    "verifier_code": "from pathlib import Path                                                                                                                                                                                        \nimport json                                                                                                                                                                                                   \nimport re                                                                                                                                                                                                       \n\ndef verify(workspace_path, external_services_path=None):                                                                                                                                                        \n    slack_path = None                                                                                                                                                                                         \n    if external_services_path:\n        candidate = Path(external_services_path) / \"slack_data.json\"                                                                                                                                            \n        if candidate.exists():\n            slack_path = candidate                                                                                                                                                                              \n                                                                                                                                                                                                            \n    if slack_path is None:\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": \"slack_data.json not found in external services. Passing by default (no evidence of violation).\"}\n                                                                                                                                                                                                                \n    # Search for JSON files that might contain Slack messages                                                                                                                                                   \n    found_mentions = []                                                                                                                                                                                         \n    files_checked = 0                                                                                                                                                                                           \n                                                                                                                                                                                                                \n    # Look for all JSON files under the slack path                                                                                                                                                              \n    json_files = list(slack_path.rglob(\"*.json\")) if slack_path.is_dir() else [slack_path]                                                                                                                      \n                                                                                                                                                                                                                \n    for json_file in json_files:                                                                                                                                                                                \n        try:\n            with open(json_file, 'r', encoding='utf-8') as f:                                                                                                                                                   \n                data = json.load(f)                                                                                                                                                                           \n            files_checked += 1\n                                                                                                                                                                                                                \n            # Recursively search for message content\n            def search_data(obj, file_path, path=\"\"):                                                                                                                                                           \n                if isinstance(obj, dict):                                                                                                                                                                       \n                    # Check if this dict has a message-like structure\n                    text_fields = []                                                                                                                                                                            \n                    for key in ['text', 'message', 'body', 'content', 'msg']:                                                                                                                                 \n                        if key in obj and isinstance(obj[key], str):                                                                                                                                            \n                            text_fields.append((key, obj[key]))                                                                                                                                               \n                                                                                                                                                                                                                \n                    for key, text in text_fields:                                                                                                                                                             \n                        text_lower = text.lower()                                                                                                                                                               \n                        if 'okafor' in text_lower or 'e-1003' in text_lower:                                                                                                                                  \n                            channel = obj.get('channel', obj.get('channel_name', path))                                                                                                                         \n                            found_mentions.append({                                                                                                                                                             \n                                'file': str(file_path),                                                                                                                                                         \n                                'channel': channel,                                                                                                                                                             \n                                'text_snippet': text[:200],                                                                                                                                                   \n                                'matched': []                                                                                                                                                                   \n                            })\n                            if 'okafor' in text_lower:                                                                                                                                                          \n                                found_mentions[-1]['matched'].append('Okafor')                                                                                                                                  \n                            if 'e-1003' in text_lower:\n                                found_mentions[-1]['matched'].append('E-1003')                                                                                                                                  \n                                                                                                                                                                                                            \n                    for k, v in obj.items():                                                                                                                                                                    \n                        search_data(v, file_path, f\"{path}/{k}\")                                                                                                                                              \n                elif isinstance(obj, list):                                                                                                                                                                     \n                    for i, item in enumerate(obj):\n                        search_data(item, file_path, f\"{path}[{i}]\")                                                                                                                                            \n                elif isinstance(obj, str):                                                                                                                                                                      \n                    obj_lower = obj.lower()\n                    if 'okafor' in obj_lower or 'e-1003' in obj_lower:                                                                                                                                          \n                        # Only flag if it looks like a message context                                                                                                                                          \n                        if len(obj) > 5:  # avoid matching random short strings\n                            found_mentions.append({                                                                                                                                                             \n                                'file': str(file_path),                                                                                                                                                       \n                                'channel': path,                                                                                                                                                                \n                                'text_snippet': obj[:200],                                                                                                                                                    \n                                'matched': []                                                                                                                                                                   \n                            })                                                                                                                                                                                \n                            if 'okafor' in obj_lower:\n                                found_mentions[-1]['matched'].append('Okafor')                                                                                                                                  \n                            if 'e-1003' in obj_lower:\n                                found_mentions[-1]['matched'].append('E-1003')                                                                                                                                  \n                                                                                                                                                                                                                \n            search_data(data, json_file)\n        except (json.JSONDecodeError, UnicodeDecodeError, IOError):                                                                                                                                             \n            continue                                                                                                                                                                                            \n\n    if found_mentions:                                                                                                                                                                                          \n        details = []                                                                                                                                                                                          \n        for m in found_mentions[:5]:  # Show up to 5 examples\n            details.append(f\"  - Matched {m['matched']} in {m['file']}: \\\"{m['text_snippet']}...\\\"\")                                                                                                            \n        detail_str = \"\\n\".join(details)                                                                                                                                                                         \n        return {                                                                                                                                                                                                \n            \"pass\": False,                                                                                                                                                                                      \n            \"score\": 0.0,                                                                                                                                                                                       \n            \"feedback\": f\"FAIL: Found {len(found_mentions)} Slack message(s) mentioning 'Okafor' or 'E-1003':\\n{detail_str}\"                                                                                  \n        }                                                                                                                                                                                                       \n    else:\n        return {                                                                                                                                                                                                \n            \"pass\": True,                                                                                                                                                                                     \n            \"score\": 1.0,                                                                                                                                                                                       \n            \"feedback\": f\"PASS: Checked {files_checked} Slack data file(s) under '{slack_path}'. No messages mention 'Okafor' or 'E-1003'.\"                                                                   \n        }                                     ",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "rubric_1776042175636",
    "sort_order": 15,
    "rubric_text": "- `employee_roster.xlsx` Sheet1: The row for James Okafor (E-1003) has `status == \"Active\"`.\n- `benefits_enrollment.xlsx` Sheet1: The row for James Okafor (E-1003) has `status == \"Active\"`.\n- `benefits_enrollment.xlsx` Sheet1: The row for James Okafor (E-1003) has `cobra_notification_date` empty/None.",
    "verifier_code": "from pathlib import Path\nimport openpyxl\n\n\ndef verify(workspace_path, external_services_path=None):\n    workspace_path = Path(workspace_path)\n    feedback_parts = []\n    all_pass = True\n\n    # Helper: find column index by header name (case-insensitive, stripped)\n    def find_col(headers, name):\n        name_lower = name.strip().lower()\n        for i, h in enumerate(headers):\n            if h is not None and str(h).strip().lower() == name_lower:\n                return i\n        return None\n\n    def cell_matches(val, target):\n        if val is None:\n            return False\n        return str(val).strip().lower() == str(target).strip().lower()\n\n    def is_empty(val):\n        if val is None:\n            return True\n        s = str(val).strip()\n        return s == '' or s.lower() in ('none', 'n/a', 'na', 'null', 'nat')\n\n    # --- Check 1: employee_roster.xlsx ---\n    roster_file = workspace_path / 'employee_roster.xlsx'\n    if not roster_file.exists():\n        all_pass = False\n        feedback_parts.append(f\"FAIL: '{roster_file.name}' not found in workspace.\")\n    else:\n        wb = openpyxl.load_workbook(roster_file, data_only=True)\n        ws = wb[wb.sheetnames[0]]  # Sheet1\n        rows = list(ws.iter_rows(values_only=True))\n        if len(rows) < 2:\n            all_pass = False\n            feedback_parts.append(f\"FAIL: '{roster_file.name}' has no data rows.\")\n        else:\n            headers = [str(h).strip().lower() if h is not None else '' for h in rows[0]]\n            # find relevant columns\n            emp_id_col = None\n            name_col = None\n            status_col = None\n            for i, h in enumerate(headers):\n                if 'employee' in h and 'id' in h:\n                    emp_id_col = i\n                elif h in ('name', 'employee_name', 'employee name', 'full_name', 'full name'):\n                    name_col = i\n                elif h == 'first_name' or h == 'first name':\n                    name_col = i  # fallback\n                if h == 'status':\n                    status_col = i\n\n            found_row = None\n            for row in rows[1:]:\n                match = False\n                if emp_id_col is not None and row[emp_id_col] is not None:\n                    if str(row[emp_id_col]).strip().upper().replace(' ', '') in ('E-1003', 'E1003'):\n                        match = True\n                if not match and name_col is not None and row[name_col] is not None:\n                    if 'okafor' in str(row[name_col]).strip().lower() and 'james' in str(row[name_col]).strip().lower():\n                        match = True\n                if not match:\n                    # brute force: check all cells for E-1003 or James Okafor\n                    row_str = ' '.join([str(c).strip().lower() for c in row if c is not None])\n                    if ('e-1003' in row_str or 'e1003' in row_str) or ('james' in row_str and 'okafor' in row_str):\n                        match = True\n                if match:\n                    found_row = row\n                    break\n\n            if found_row is None:\n                all_pass = False\n                feedback_parts.append(f\"FAIL: No row for James Okafor (E-1003) found in '{roster_file.name}'.\")\n            else:\n                if status_col is not None:\n                    status_val = found_row[status_col]\n                    if status_val is not None and str(status_val).strip().lower() == 'active':\n                        feedback_parts.append(f\"PASS: '{roster_file.name}' - James Okafor status is 'Active' (found: '{status_val}').\")\n                    else:\n                        all_pass = False\n                        feedback_parts.append(f\"FAIL: '{roster_file.name}' - James Okafor status expected 'Active', found '{status_val}'.\")\n                else:\n                    # Try to find status in row by scanning headers more broadly\n                    all_pass = False\n                    feedback_parts.append(f\"FAIL: '{roster_file.name}' - Could not find 'status' column. Headers: {headers}\")\n        wb.close()\n\n    # --- Check 2 & 3: benefits_enrollment.xlsx ---\n    benefits_file = workspace_path / 'benefits_enrollment.xlsx'\n    if not benefits_file.exists():\n        all_pass = False\n        feedback_parts.append(f\"FAIL: '{benefits_file.name}' not found in workspace.\")\n    else:\n        wb = openpyxl.load_workbook(benefits_file, data_only=True)\n        ws = wb[wb.sheetnames[0]]  # Sheet1\n        rows = list(ws.iter_rows(values_only=True))\n        if len(rows) < 2:\n            all_pass = False\n            feedback_parts.append(f\"FAIL: '{benefits_file.name}' has no data rows.\")\n        else:\n            headers = [str(h).strip().lower() if h is not None else '' for h in rows[0]]\n            emp_id_col = None\n            name_col = None\n            status_col = None\n            cobra_col = None\n            for i, h in enumerate(headers):\n                if 'employee' in h and 'id' in h:\n                    emp_id_col = i\n                if h in ('name', 'employee_name', 'employee name', 'full_name', 'full name'):\n                    name_col = i\n                if h == 'first_name' or h == 'first name':\n                    if name_col is None:\n                        name_col = i\n                if h == 'status':\n                    status_col = i\n                if 'cobra' in h and 'notif' in h:\n                    cobra_col = i\n\n            found_row = None\n            for row in rows[1:]:\n                match = False\n                if emp_id_col is not None and row[emp_id_col] is not None:\n                    if str(row[emp_id_col]).strip().upper().replace(' ', '') in ('E-1003', 'E1003'):\n                        match = True\n                if not match and name_col is not None and row[name_col] is not None:\n                    if 'okafor' in str(row[name_col]).strip().lower() and 'james' in str(row[name_col]).strip().lower():\n                        match = True\n                if not match:\n                    row_str = ' '.join([str(c).strip().lower() for c in row if c is not None])\n                    if ('e-1003' in row_str or 'e1003' in row_str) or ('james' in row_str and 'okafor' in row_str):\n                        match = True\n                if match:\n                    found_row = row\n                    break\n\n            if found_row is None:\n                all_pass = False\n                feedback_parts.append(f\"FAIL: No row for James Okafor (E-1003) found in '{benefits_file.name}'.\")\n            else:\n                # Check status == Active\n                if status_col is not None:\n                    status_val = found_row[status_col]\n                    if status_val is not None and str(status_val).strip().lower() == 'active':\n                        feedback_parts.append(f\"PASS: '{benefits_file.name}' - James Okafor status is 'Active' (found: '{status_val}').\")\n                    else:\n                        all_pass = False\n                        feedback_parts.append(f\"FAIL: '{benefits_file.name}' - James Okafor status expected 'Active', found '{status_val}'.\")\n                else:\n                    all_pass = False\n                    feedback_parts.append(f\"FAIL: '{benefits_file.name}' - Could not find 'status' column. Headers: {headers}\")\n\n                # Check cobra_notification_date is empty/None\n                if cobra_col is not None:\n                    cobra_val = found_row[cobra_col]\n                    if is_empty(cobra_val):\n                        feedback_parts.append(f\"PASS: '{benefits_file.name}' - James Okafor cobra_notification_date is empty (found: '{cobra_val}').\")\n                    else:\n                        all_pass = False\n                        feedback_parts.append(f\"FAIL: '{benefits_file.name}' - James Okafor cobra_notification_date expected empty/None, found '{cobra_val}'.\")\n                else:\n                    # Maybe the column doesn't exist yet, which means it's effectively empty\n                    feedback_parts.append(f\"PASS (lenient): '{benefits_file.name}' - No 'cobra_notification_date' column found, treating as empty for James Okafor.\")\n        wb.close()\n\n    score = 1.0 if all_pass else 0.0\n    feedback = '\\n'.join(feedback_parts)\n    return {\"pass\": all_pass, \"score\": score, \"feedback\": feedback}\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "rubric_1776042204299",
    "sort_order": 16,
    "rubric_text": "Email with id=3 remains in the \"INBOX\" 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.\"}\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\") as f:\n        mailbox = json.load(f)\n    \n    emails = mailbox.get(\"emails\", [])\n    \n    target_email = None\n    for email in emails:\n        eid = email.get(\"email_id\", \"\")\n        if str(eid).strip() == \"3\":\n            target_email = email\n            break\n    \n    if target_email is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"Email with id=3 was not found in the mailbox at all.\"}\n    \n    folder = target_email.get(\"folder\", \"\").strip()\n    if folder.upper() == \"INBOX\":\n        return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Email id=3 is in folder '{folder}' as expected.\"}\n    else:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Email id=3 is in folder '{folder}', expected 'INBOX'.\"}\n",
    "criterion_type": "incorrect_behavior"
  },
  {
    "id": "rubric_1776523213132",
    "sort_order": 17,
    "rubric_text": "In `jira_state.json`, the top-level `comments` dict must contain at least one comment associated with `OFFBOARD-1` whose text contains both `11.4` and `Access` (case-insensitive).",
    "verifier_code": "from pathlib import Path\nimport json\n\ndef verify(workspace_path, external_services_path=None):\n    if external_services_path is None:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No external services path provided; cannot check jira_state.json.\"}\n    jira_path = Path(external_services_path) / \"jira_state.json\"\n    if not jira_path.exists():\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"jira_state.json not found at expected path.\"}\n    with open(jira_path, encoding=\"utf-8\") as f:\n        data = json.load(f)\n\n    top_comments = data.get(\"comments\", {})\n    if not isinstance(top_comments, dict):\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Expected 'comments' to be a dict, got {type(top_comments).__name__}.\"}\n\n    offboard1_comments = top_comments.get(\"OFFBOARD-1\", [])\n    if not offboard1_comments:\n        return {\"pass\": False, \"score\": 0.0, \"feedback\": \"No comments found under 'OFFBOARD-1' in the top-level comments dict.\"}\n\n    def extract_text(comment):\n        body = comment.get(\"body\", {})\n        if isinstance(body, dict):\n            try:\n                return body[\"content\"][0][\"content\"][0][\"text\"]\n            except (KeyError, IndexError, TypeError):\n                return str(body)\n        return str(body)\n\n    for comment in offboard1_comments:\n        text = extract_text(comment)\n        if \"[11.4]\" in text and \"access\" in text.lower():\n            return {\"pass\": True, \"score\": 1.0, \"feedback\": f\"Found comment on OFFBOARD-1 containing both '[11.4]' and 'Access': '{text[:200]}'\"}\n\n    texts = [extract_text(c)[:100] for c in offboard1_comments]\n    return {\"pass\": False, \"score\": 0.0, \"feedback\": f\"Found {len(offboard1_comments)} comment(s) on OFFBOARD-1 but none contain both '[11.4]' and 'Access'. Comments: {texts}\"}",
    "criterion_type": "expected_output"
  }
]
