[ { "id": "149b0ac0-9db2-47d0-b674-c453f7c2d37d", "sort_order": 0, "rubric_text": "The inbound_receiving_log.xlsx file must contain a data row where: the Expected Pallets and Actual Pallets column both contain 24; the Actual Cases column contains 474; the three Temp Reading columns collectively contain −4.5, −6.0 (alternatively −6), and −7.5 in any order; the Variance (Cases) column contains −6; the Lot Codes Received column contains 'A-20260519-01'; the Disposition column contains both 'quarantine' (or 'quarantined') and 'H-Q'; and the Status column contains exactly 'P2 – Compound event.'", "verifier_code": "from pathlib import Path\nimport re\n\ndef verify(workspace_path, external_services_path=None):\n ws = Path(workspace_path)\n\n # Find the receiving log xlsx file\n xlsx_files = list(ws.rglob('*.xlsx'))\n log_file = None\n for f in xlsx_files:\n if 'receiv' in f.name.lower() or 'log' in f.name.lower():\n log_file = f\n break\n if log_file is None and xlsx_files:\n log_file = xlsx_files[0]\n\n if log_file is None:\n return {'pass': False, 'score': 0.0, 'feedback': 'No .xlsx file found in workspace.'}\n\n try:\n import openpyxl\n wb = openpyxl.load_workbook(log_file)\n except Exception as e:\n return {'pass': False, 'score': 0.0, 'feedback': f'Failed to open {log_file.name}: {e}'}\n\n best_result = None\n best_score = -1\n\n for ws_sheet in wb.worksheets:\n rows = list(ws_sheet.iter_rows(values_only=True))\n if len(rows) < 2:\n continue\n\n # Find header row\n header_row_idx = None\n headers = []\n for i, row in enumerate(rows):\n non_none = [c for c in row if c is not None]\n if len(non_none) >= 3:\n header_row_idx = i\n headers = [str(c).strip().lower() if c is not None else '' for c in row]\n break\n\n if header_row_idx is None:\n continue\n\n def find_col(patterns):\n results = []\n for idx, h in enumerate(headers):\n for p in patterns:\n if p in h:\n results.append(idx)\n break\n return results\n\n expected_pallet_cols = find_col(['expected pallet'])\n actual_pallet_cols = find_col(['actual pallet'])\n pallet_cols = find_col(['pallet'])\n actual_case_cols = find_col(['actual case'])\n case_cols = find_col(['case'])\n temp_cols = find_col(['temp'])\n variance_cols = find_col(['variance'])\n lot_cols = find_col(['lot'])\n disposition_cols = find_col(['disposition'])\n status_cols = find_col(['status'])\n\n data_rows = rows[header_row_idx + 1:]\n for row in data_rows:\n row_str_list = [str(c).strip() if c is not None else '' for c in row]\n row_lower = ' '.join(row_str_list).lower()\n\n non_empty = [c for c in row if c is not None and str(c).strip() != '']\n if len(non_empty) < 3:\n continue\n\n def get_vals(col_indices):\n vals = []\n for ci in col_indices:\n if ci < len(row) and row[ci] is not None:\n vals.append(row[ci])\n return vals\n\n def num_eq(val, target, tol=0.5):\n try:\n return abs(float(val) - target) < tol\n except (ValueError, TypeError):\n s = str(val).replace('\\u2212', '-').replace('\\u2013', '-').replace('\\u2014', '-')\n m = re.search(r'-?\\d+\\.?\\d*', s)\n if m:\n try:\n return abs(float(m.group()) - target) < tol\n except:\n pass\n return False\n\n # Check Expected Pallets = 24\n expected_pallet_ok = False\n for v in get_vals(expected_pallet_cols):\n if num_eq(v, 24, 0.5):\n expected_pallet_ok = True\n if not expected_pallet_ok:\n for v in get_vals(pallet_cols):\n if num_eq(v, 24, 0.5):\n expected_pallet_ok = True\n break\n if not expected_pallet_ok:\n for v in row:\n if v is not None and num_eq(v, 24, 0.5):\n expected_pallet_ok = True\n break\n\n # Check Actual Pallets = 24\n actual_pallet_ok = False\n for v in get_vals(actual_pallet_cols):\n if num_eq(v, 24, 0.5):\n actual_pallet_ok = True\n if not actual_pallet_ok:\n for v in get_vals(pallet_cols):\n if num_eq(v, 24, 0.5):\n actual_pallet_ok = True\n break\n if not actual_pallet_ok:\n for v in row:\n if v is not None and num_eq(v, 24, 0.5):\n actual_pallet_ok = True\n break\n\n pallets_ok = expected_pallet_ok and actual_pallet_ok\n\n # Check Actual Cases = 474\n cases_ok = False\n for v in get_vals(actual_case_cols):\n if num_eq(v, 474, 0.5):\n cases_ok = True\n if not cases_ok:\n for v in get_vals(case_cols):\n if num_eq(v, 474, 0.5):\n cases_ok = True\n break\n if not cases_ok:\n for v in row:\n if v is not None and num_eq(v, 474, 0.5):\n cases_ok = True\n break\n\n # Check temps: need -4.5, -6.0 (or -6), -7.5\n temp_vals_raw = get_vals(temp_cols)\n if not temp_vals_raw:\n temp_vals_raw = [v for v in row if v is not None]\n\n temp_nums = []\n for v in temp_vals_raw:\n try:\n temp_nums.append(float(v))\n except (ValueError, TypeError):\n s = str(v).replace('\\u2212', '-').replace('\\u2013', '-').replace('\\u2014', '-')\n matches = re.findall(r'-?\\d+\\.?\\d*', s)\n for m in matches:\n try:\n temp_nums.append(float(m))\n except:\n pass\n\n has_neg4_5 = any(abs(t - (-4.5)) < 0.15 for t in temp_nums)\n has_neg6 = any(abs(t - (-6.0)) < 0.15 for t in temp_nums)\n has_neg7_5 = any(abs(t - (-7.5)) < 0.15 for t in temp_nums)\n temps_ok = has_neg4_5 and has_neg6 and has_neg7_5\n\n # Check variance = -6\n variance_ok = False\n for v in get_vals(variance_cols):\n if num_eq(v, -6, 0.5):\n variance_ok = True\n if not variance_ok:\n for v in get_vals(variance_cols):\n s = str(v).replace('\\u2212', '-').replace('\\u2013', '-')\n if re.search(r'-\\s*6(\\.0)?\\s*$', s.strip()):\n variance_ok = True\n\n # Check lot code A-20260519-01\n lot_ok = False\n for v in get_vals(lot_cols):\n if 'a-20260519-01' in str(v).lower():\n lot_ok = True\n if not lot_ok:\n if 'a-20260519-01' in row_lower:\n lot_ok = True\n\n # Check disposition: quarantine/quarantined AND H-Q\n disp_vals = get_vals(disposition_cols)\n disp_text = ' '.join(str(v) for v in disp_vals).lower() if disp_vals else row_lower\n disp_text_norm = disp_text.replace('\\u2212', '-').replace('\\u2013', '-')\n quarantine_ok = bool(re.search(r'quarantin', disp_text_norm))\n hq_ok = bool(re.search(r'h[\\-\\s]?q', disp_text_norm))\n\n # Check status: P2 - Compound event (with flexible dash)\n status_vals = get_vals(status_cols)\n status_text = ' '.join(str(v) for v in status_vals).lower() if status_vals else row_lower\n status_text_norm = status_text.replace('\\u2212', '-').replace('\\u2013', '-').replace('\\u2014', '-')\n has_p2 = bool(re.search(r'p\\s*2', status_text_norm))\n has_compound = bool(re.search(r'compound\\s*event', status_text_norm))\n status_ok = has_p2 and has_compound\n\n checks = [\n ('Expected Pallets = 24', expected_pallet_ok),\n ('Actual Pallets = 24', actual_pallet_ok),\n ('Actual Cases = 474', cases_ok),\n ('Temp -4.5', has_neg4_5),\n ('Temp -6.0/-6', has_neg6),\n ('Temp -7.5', has_neg7_5),\n ('Variance (Cases) = -6', variance_ok),\n ('Lot Code A-20260519-01', lot_ok),\n ('Disposition: quarantine', quarantine_ok),\n ('Disposition: H-Q', hq_ok),\n ('Status: P2', has_p2),\n ('Status: Compound event', has_compound),\n ]\n\n passed_checks = [name for name, ok in checks if ok]\n failed_checks = [name for name, ok in checks if not ok]\n score = len(passed_checks) / len(checks)\n\n if score > best_score:\n best_score = score\n if not failed_checks:\n best_result = {\n 'pass': True,\n 'score': 1.0,\n 'feedback': f'All required fields found in {log_file.name}: {\", \".join(passed_checks)}.'\n }\n else:\n best_result = {\n 'pass': False,\n 'score': score,\n 'feedback': f'In {log_file.name}, found: {\", \".join(passed_checks)}. Missing: {\", \".join(failed_checks)}. Row values: {row_str_list}'\n }\n\n if best_result is not None and best_score >= 0.4:\n return best_result\n\n # Fallback: read all text from all files and do loose matching\n candidates = set()\n for pattern in ['*.txt', '*.md', '*.csv', '*.json', '*.log']:\n candidates.update(ws.glob(pattern))\n candidates.update(ws.rglob('*receiv*'))\n candidates.update(ws.rglob('*log*'))\n candidates.update(ws.rglob('*.xlsx'))\n candidates.update(ws.rglob('*.csv'))\n candidates.update(ws.rglob('*.txt'))\n candidates.update(ws.rglob('*.md'))\n\n all_text = ''\n files_read = []\n\n for f in candidates:\n try:\n if f.is_dir():\n continue\n if f.suffix.lower() == '.xlsx':\n import openpyxl as oxl\n wb2 = oxl.load_workbook(f)\n for s in wb2.worksheets:\n for row in s.iter_rows(values_only=True):\n for cell in row:\n if cell is not None:\n all_text += str(cell) + ' '\n files_read.append(str(f.name))\n elif f.suffix.lower() == '.docx':\n from docx import Document\n doc = Document(f)\n content = ' '.join([p.text for p in doc.paragraphs])\n all_text += content + ' '\n files_read.append(str(f.name))\n else:\n content = f.read_text(encoding='utf-8', errors='ignore')\n all_text += content + ' '\n files_read.append(str(f.name))\n except Exception:\n continue\n\n if not all_text.strip():\n return {'pass': False, 'score': 0.0, 'feedback': 'No readable content found in workspace.'}\n\n normalized = all_text.replace('\\u2212', '-').replace('\\u2013', '-').replace('\\u2014', '-').replace('\\u00b0', ' ')\n text_lower = normalized.lower()\n\n checks = []\n\n # 1. pallets 24\n checks.append(('Pallets 24', '24' in text_lower))\n\n # 2. cases 474\n checks.append(('Actual Cases 474', '474' in text_lower))\n\n # 3. temps\n checks.append(('Temp -4.5', bool(re.search(r'-\\s*4\\.5', normalized))))\n checks.append(('Temp -6.0/-6', bool(re.search(r'-\\s*6\\.0', normalized) or re.search(r'-\\s*6(?![\\d.])', normalized))))\n checks.append(('Temp -7.5', bool(re.search(r'-\\s*7\\.5', normalized))))\n\n # 4. variance -6\n checks.append(('Variance -6', bool(re.search(r'-\\s*6', normalized))))\n\n # 5. lot code\n checks.append(('Lot Code A-20260519-01', 'a-20260519-01' in text_lower))\n\n # 6. quarantine\n checks.append(('Disposition: quarantine', bool(re.search(r'quarantin', text_lower))))\n\n # 7. H-Q\n checks.append(('Disposition: H-Q', bool(re.search(r'h[\\-\\s]?q', text_lower))))\n\n # 8. P2\n checks.append(('Status: P2', bool(re.search(r'p[\\-\\s]?2', text_lower))))\n\n # 9. Compound event\n checks.append(('Status: Compound event', bool(re.search(r'compound\\s*event', text_lower))))\n\n passed_checks = [name for name, ok in checks if ok]\n failed_checks = [name for name, ok in checks if not ok]\n score = len(passed_checks) / len(checks) if checks else 0.0\n\n if not failed_checks:\n return {'pass': True, 'score': 1.0, 'feedback': f'All required fields found (fallback text search): {\", \".join(passed_checks)}. Files: {\", \".join(files_read[:10])}'}\n else:\n return {'pass': False, 'score': score, 'feedback': f'Missing: {\", \".join(failed_checks)}. Found: {\", \".join(passed_checks)}. Files: {\", \".join(files_read[:10])}'}\n", "criterion_type": "expected_output" }, { "id": "a20b335b-2dbd-4e0c-8f52-1500a16fd68f", "sort_order": 1, "rubric_text": "A message posted to the #dc-escalations Slack channel must begin with 'P2 escalation. Compound.' [exact string, 'compound event' not acceptable], contain manifest number '2026-05-20-001', contain the exact phrase 'Escalated to Director of Supply Chain.', and contain the exact phrase 'Incident report: awaiting Director decision.' The Detail and Action required fields may contain any text.", "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\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 external_services_path'}\n\n with open(slack_path, 'r', encoding='utf-8') as f:\n data = json.load(f)\n\n # Find the #dc-escalations channel\n channels = data.get('channels', {})\n escalation_channel_id = None\n for cid, cinfo in channels.items():\n cname = str(cinfo.get('name', '')).lower().strip()\n if cname == 'dc-escalations':\n escalation_channel_id = cid\n break\n\n if escalation_channel_id is None:\n return {'pass': False, 'score': 0.0, 'feedback': 'Could not find #dc-escalations channel in slack_data.json. Channels found: ' + str([c.get('name', '') for c in channels.values()])}\n\n # Get messages in that channel\n messages_by_channel = data.get('messages', {})\n channel_messages = messages_by_channel.get(escalation_channel_id, [])\n\n if not channel_messages:\n return {'pass': False, 'score': 0.0, 'feedback': f'No messages found in #dc-escalations channel (channel ID: {escalation_channel_id})'}\n\n # Score each message to find the best candidate\n best_msg = None\n best_score = -1\n\n for msg in channel_messages:\n text = str(msg.get('text', ''))\n text_lower = text.lower()\n score = 0\n if text_lower.lstrip().startswith('p2 escalation'):\n score += 5\n if '2026-05-20-001' in text:\n score += 5\n if 'compound' in text_lower:\n score += 3\n if 'director of supply chain' in text_lower:\n score += 3\n if 'awaiting director decision' in text_lower:\n score += 3\n if score > best_score:\n best_score = score\n best_msg = msg\n\n if best_msg is None or best_score <= 0:\n texts_preview = [str(m.get('text', ''))[:80] for m in channel_messages[:5]]\n return {'pass': False, 'score': 0.0, 'feedback': f'No plausible escalation message found in #dc-escalations among {len(channel_messages)} messages. Previews: {texts_preview}'}\n\n text = str(best_msg.get('text', ''))\n text_lower = text.lower()\n\n failures = []\n details = []\n\n # (1) Must begin with 'P2 escalation. Compound.' [exact string, 'compound event' not acceptable]\n # The rubric says exact string 'P2 escalation. Compound.' at the start.\n # We check case-insensitively for leniency, and allow minor whitespace variations,\n # but 'compound' must appear as a standalone word (not 'compound event' replacing it).\n # The key distinction: must say 'Compound' (with period/punctuation after), NOT 'Compound event'.\n start_pattern = re.compile(r'(?i)^\\s*p2\\s+escalation\\.\\s*compound\\s*\\.', re.DOTALL)\n if start_pattern.search(text):\n # Additionally verify it doesn't say 'compound event' right at the start\n bad_pattern = re.compile(r'(?i)^\\s*p2\\s+escalation\\.\\s*compound\\s+event', re.DOTALL)\n if bad_pattern.search(text):\n failures.append(f\"(1) Message begins with 'P2 escalation. Compound event...' but rubric requires exactly 'P2 escalation. Compound.' (not 'compound event'). Start: '{text[:80]}...'\")\n else:\n details.append(\"(1) Message begins with 'P2 escalation. Compound.' \\u2713\")\n else:\n failures.append(f\"(1) Message must begin with 'P2 escalation. Compound.' but starts with: '{text[:80]}...'\")\n\n # (2) Must contain manifest number '2026-05-20-001'\n if '2026-05-20-001' in text:\n details.append(\"(2) Manifest number '2026-05-20-001' found \\u2713\")\n else:\n failures.append(\"(2) Manifest number '2026-05-20-001' not found in message\")\n\n # (3) Must contain the exact phrase 'Escalated to Director of Supply Chain.'\n # Be loose: case-insensitive, allow with or without trailing period\n if re.search(r'(?i)escalated\\s+to\\s+director\\s+of\\s+supply\\s+chain\\.?', text):\n details.append(\"(3) 'Escalated to Director of Supply Chain.' found \\u2713\")\n else:\n failures.append(\"(3) Exact phrase 'Escalated to Director of Supply Chain.' not found in message\")\n\n # (4) Must contain the exact phrase 'Incident report: awaiting Director decision.'\n # Be loose: case-insensitive, allow with or without trailing period, flexible colon/space\n if re.search(r'(?i)incident\\s+report\\s*[:\\-]\\s*awaiting\\s+director\\s+decision\\.?', text):\n details.append(\"(4) 'Incident report: awaiting Director decision.' found \\u2713\")\n else:\n failures.append(\"(4) Exact phrase 'Incident report: awaiting Director decision.' not found in message\")\n\n if failures:\n feedback = 'Escalation message verification failures: ' + '; '.join(failures)\n if details:\n feedback += ' | Passed: ' + '; '.join(details)\n feedback += f' | Message preview: {text[:400]}'\n return {'pass': False, 'score': 0.0, 'feedback': feedback}\n\n return {'pass': True, 'score': 1.0, 'feedback': 'All #dc-escalations message criteria satisfied. ' + '; '.join(details)}\n", "criterion_type": "expected_output" }, { "id": "98ee41bc-57a2-4c54-a5c1-57bf70a8aee9", "sort_order": 2, "rubric_text": "A sent email in mailbox.json must be addressed To: daniel.reyes@prairiestar.com only, CC: carlos.mendoza@prairiestar.com and jacob.patel@prairiestar.com with no additional recipients, have a subject containing 'P2', 'Compound Event', '2026-05-20-001', and '2026-05-20', and have a body containing all three temperature readings −4.5, −6.0 (alternatively −6), and −7.5, 'Tier 3', variance of −6 cases and 1.25%, 'Moderate' as the variance classification, 'quarantined to H-Q', and 'Director assessment required' [exact string].", "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\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\n with open(mailbox_path, 'r', encoding='utf-8') as f:\n data = json.load(f)\n\n sent_emails = [e for e in data.get('emails', []) if e.get('folder', '').lower() == 'sent']\n\n if not sent_emails:\n return {'pass': False, 'score': 0.0, 'feedback': 'No sent emails found in mailbox.json'}\n\n # Find the escalation email - look for one addressed to daniel.reyes\n matching_email = None\n for email in sent_emails:\n to_addr = (email.get('to_addr') or email.get('to') or '').lower()\n if 'daniel.reyes@prairiestar.com' in to_addr:\n matching_email = email\n break\n\n if matching_email is None:\n # Fallback: look for email with P2 in subject or relevant content\n for email in sent_emails:\n subject = (email.get('subject') or '').lower()\n body = (email.get('body_text') or email.get('body') or '').lower()\n if 'p2' in subject or ('quarantine' in body and 'h-q' in body):\n matching_email = email\n break\n\n if matching_email is None:\n return {'pass': False, 'score': 0.0, 'feedback': 'No escalation email found in sent folder addressed to daniel.reyes@prairiestar.com'}\n\n failures = []\n\n # --- Check TO field ---\n to_addr = (matching_email.get('to_addr') or matching_email.get('to') or '').lower().strip()\n if 'daniel.reyes@prairiestar.com' not in to_addr:\n failures.append('To field does not contain daniel.reyes@prairiestar.com')\n else:\n to_emails = re.findall(r'[\\w.+-]+@[\\w.-]+', to_addr)\n to_emails_filtered = [e for e in to_emails if e != 'daniel.reyes@prairiestar.com']\n if to_emails_filtered:\n failures.append(f'To field contains additional recipients beyond daniel.reyes@prairiestar.com: {to_emails_filtered}')\n\n # --- Check CC field ---\n cc_addr = (matching_email.get('cc_addr') or matching_email.get('cc') or '').lower().strip()\n cc_emails = re.findall(r'[\\w.+-]+@[\\w.-]+', cc_addr)\n required_cc = {'carlos.mendoza@prairiestar.com', 'jacob.patel@prairiestar.com'}\n cc_set = set(cc_emails)\n missing_cc = required_cc - cc_set\n extra_cc = cc_set - required_cc\n if missing_cc:\n failures.append(f'CC field missing required recipients: {missing_cc}')\n if extra_cc:\n failures.append(f'CC field contains extra recipients: {extra_cc}')\n\n # --- Check Subject ---\n subject = matching_email.get('subject') or ''\n subject_lower = subject.lower()\n if 'p2' not in subject_lower:\n failures.append('Subject does not contain P2')\n if 'compound event' not in subject_lower:\n failures.append('Subject does not contain Compound Event')\n if '2026-05-20-001' not in subject:\n failures.append('Subject does not contain 2026-05-20-001')\n if '2026-05-20' not in subject:\n failures.append('Subject does not contain 2026-05-20')\n\n # --- Check Body ---\n body = matching_email.get('body_text') or matching_email.get('body') or ''\n # Normalize unicode minus signs and en-dashes to regular minus/hyphen\n normalized_body = body.replace('\\u2212', '-').replace('\\u2013', '-').replace('\\u2014', '-').replace('\\u00b0', ' ')\n normalized_lower = normalized_body.lower()\n\n # Temperature readings: -4.5, -6.0 (or -6), -7.5\n if not re.search(r'[-]\\s*4\\.5', normalized_lower):\n failures.append('Body does not contain temperature reading -4.5 (or \\u22124.5)')\n\n # -6.0 or -6 (but distinguish from -6 in other contexts like variance)\n has_minus6 = bool(re.search(r'[-]\\s*6\\.0', normalized_lower)) or bool(re.search(r'[-]\\s*6(?:\\b|\\s|f|$|,|\\)|\\*|\\xb0)', normalized_lower))\n if not has_minus6:\n failures.append('Body does not contain temperature reading -6.0 or -6 (or \\u22126.0/\\u22126)')\n\n if not re.search(r'[-]\\s*7\\.5', normalized_lower):\n failures.append('Body does not contain temperature reading -7.5 (or \\u22127.5)')\n\n # Tier 3\n if not re.search(r'tier[\\s\\-_]*3', normalized_lower):\n failures.append('Body does not contain Tier 3')\n\n # Variance: -6 cases and 1.25%\n has_variance_cases = bool(re.search(r'[-]\\s*6\\s*case', normalized_lower)) or \\\n bool(re.search(r'varianc.{0,80}[-]\\s*6', normalized_lower)) or \\\n bool(re.search(r'[-]\\s*6.{0,30}case', normalized_lower)) or \\\n bool(re.search(r'shortage.{0,30}6', normalized_lower)) or \\\n bool(re.search(r'short.{0,30}6\\s*case', normalized_lower)) or \\\n bool(re.search(r'6\\s*case.{0,30}short', normalized_lower))\n if not has_variance_cases:\n failures.append('Body does not state variance of -6 cases')\n\n if '1.25' not in normalized_body:\n failures.append('Body does not state variance percentage 1.25%')\n else:\n # Check if 1.25 is near a percent sign or the word percent\n if not re.search(r'1\\.25\\s*(%|percent)', normalized_lower):\n failures.append('Body contains 1.25 but not clearly as 1.25%')\n\n # 'Moderate'\n if 'moderate' not in normalized_lower:\n failures.append('Body does not contain Moderate')\n\n # 'quarantined to H-Q' - check that quarantined and H-Q are present and near each other\n if 'h-q' not in normalized_lower and 'h\\u2011q' not in normalized_lower:\n failures.append('Body does not contain H-Q')\n if 'quarantine' not in normalized_lower and 'quarantined' not in normalized_lower:\n failures.append('Body does not contain quarantine or quarantined')\n\n # 'Director assessment required' - exact string check (case-insensitive)\n if not re.search(r'director\\s+assessment\\s+required', normalized_lower):\n failures.append('Body does not contain exact string \"Director assessment required\"')\n\n if failures:\n return {'pass': False, 'score': 0.0, 'feedback': 'Email issues: ' + '; '.join(failures)}\n\n return {'pass': True, 'score': 1.0, 'feedback': 'Email correctly addressed To: daniel.reyes@prairiestar.com, CC: carlos.mendoza and jacob.patel, subject contains P2/Compound Event/2026-05-20-001/2026-05-20, body contains all required content: temps -4.5/-6.0/-7.5, Tier 3, variance -6 cases/1.25%, Moderate, quarantined to H-Q, Director assessment required.'}\n", "criterion_type": "expected_output" } ]