[ { "id": "fd5fea82-8bfe-48e1-81d0-be8b87856e35", "sort_order": 0, "rubric_text": "In `jira_state.json`, CLM_LIFE-1 must NOT have CLM-910 (Identity Verification Hold) or CLM-920 (Complex Beneficiary Hold) set.", "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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n # --- Locate CLM_LIFE-1 ---\n issue = None\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n\n if isinstance(issues, dict):\n issue = issues.get(\"CLM_LIFE-1\")\n if issue is None:\n for key, val in issues.items():\n if key.upper() == \"CLM_LIFE-1\":\n issue = val\n break\n\n if issue is None and isinstance(data, list):\n for item in data:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == \"CLM_LIFE-1\":\n issue = item\n break\n\n if issue is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-1 not found in jira_state.json\"}\n\n # --- Extract status name (handles nested dict or plain string) ---\n def get_status_name(issue):\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n status_field = fields.get(\"status\")\n if isinstance(status_field, dict):\n return str(status_field.get(\"name\", \"\")).strip()\n elif isinstance(status_field, str):\n return status_field.strip()\n top_status = issue.get(\"status\")\n if isinstance(top_status, dict):\n return str(top_status.get(\"name\", \"\")).strip()\n elif isinstance(top_status, str):\n return top_status.strip()\n return \"\"\n\n status_name = get_status_name(issue)\n status_lower = status_name.lower()\n\n # --- Extract labels ---\n labels = []\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n raw_labels = fields.get(\"labels\", [])\n if isinstance(raw_labels, list):\n labels = [str(l).lower() for l in raw_labels]\n\n # --- Check CLM-910 NOT set (status and labels only, NOT comment text) ---\n has_910 = (\n \"clm-910\" in status_lower\n or \"identity verification hold\" in status_lower\n or any(\"clm-910\" in lbl for lbl in labels)\n or any(\"identity-verification-hold\" in lbl or \"identity verification hold\" in lbl for lbl in labels)\n )\n\n # --- Check CLM-920 NOT set (status and labels only) ---\n has_920 = (\n \"clm-920\" in status_lower\n or \"complex beneficiary hold\" in status_lower\n or any(\"clm-920\" in lbl for lbl in labels)\n or any(\"complex-beneficiary-hold\" in lbl or \"complex beneficiary hold\" in lbl for lbl in labels)\n )\n\n # --- Check status is \"Route to AP for Payout\" ---\n routed = \"route to ap for payout\" in status_lower\n\n if not routed:\n transitions = issue.get(\"transitions\", [])\n if isinstance(transitions, list):\n for t in transitions:\n t_name = \"\"\n if isinstance(t, dict):\n t_name = str(t.get(\"name\", t.get(\"to\", \"\"))).lower()\n elif isinstance(t, str):\n t_name = t.lower()\n if \"route to ap for payout\" in t_name:\n routed = True\n break\n\n # --- Build result ---\n issues_found = []\n if has_910:\n issues_found.append(f\"CLM-910 Identity Verification Hold is set (status: '{status_name}')\")\n if has_920:\n issues_found.append(f\"CLM-920 Complex Beneficiary Hold is set (status: '{status_name}')\")\n if issues_found:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-1: \" + \"; \".join(issues_found)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"CLM_LIFE-1: No CLM-910, no CLM-920\",\n }", "criterion_type": "incorrect_behavior" }, { "id": "3328f333-48c7-45b0-a940-a7e9e52f2b5f", "sort_order": 1, "rubric_text": "In `jira_state.json`, CLM_LIFE-1 must have a structured beneficiary allocation comment containing Policy LC-2019-08844, Face Amount $400,000.00, Maria L. Martinez (Primary, 60%, $240,000.00), David R. Martinez (Primary, 40%, $160,000.00), with a header line and Analyst/Date attribution (\"analyst\" and the date must appear within 80 characters of each other), and must NOT include Elena Martinez (mention of her exclusion is acceptable).", "verifier_code": "from pathlib import Path\nimport json\nimport re\n\n\ndef extract_text_from_adf(node):\n \"\"\"Recursively extract plain text from Atlassian Document Format.\"\"\"\n text = \"\"\n if isinstance(node, dict):\n if node.get(\"type\") == \"text\":\n text += node.get(\"text\", \"\")\n for child in node.get(\"content\", []):\n text += extract_text_from_adf(child)\n elif isinstance(node, list):\n for item in node:\n text += extract_text_from_adf(item)\n return text\n\n\ndef get_all_comment_text(data, issue_key):\n \"\"\"Get all comment text for an issue from both top-level and inline locations.\"\"\"\n text = \"\"\n # 1. Top-level comments dict\n for comment in data.get(\"comments\", {}).get(issue_key, []):\n body = comment.get(\"body\", {})\n text += extract_text_from_adf(body) + \"\\n\"\n # 2. Inline comments on the issue (fields.comment.comments)\n issues = data.get(\"issues\", {})\n issue = issues.get(issue_key, {}) if isinstance(issues, dict) else {}\n fields = issue.get(\"fields\", {}) if isinstance(issue, dict) else {}\n if isinstance(fields, dict):\n comment_field = fields.get(\"comment\", {})\n if isinstance(comment_field, dict):\n for c in comment_field.get(\"comments\", []):\n body = c.get(\"body\", {})\n text += extract_text_from_adf(body) + \"\\n\"\n return text\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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n # --- Locate CLM_LIFE-1 ---\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n issue = None\n if isinstance(issues, dict):\n issue = issues.get(\"CLM_LIFE-1\")\n if issue is None:\n for key, val in issues.items():\n if key.upper() == \"CLM_LIFE-1\":\n issue = val\n break\n if issue is None and isinstance(data, list):\n for item in data:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == \"CLM_LIFE-1\":\n issue = item\n break\n\n if issue is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-1 not found in jira_state.json\"}\n\n # --- Get all comment text (ADF-aware, checks both top-level and inline) ---\n comments_text = get_all_comment_text(data, \"CLM_LIFE-1\")\n ct_lower = comments_text.lower()\n\n failures = []\n\n # Check header line: \"beneficiary allocation\" somewhere in the comment\n if \"beneficiary allocation\" not in ct_lower:\n failures.append(\"Missing 'Beneficiary Allocation' header line\")\n\n # Check policy LC-2019-08844\n if \"lc-2019-08844\" not in ct_lower:\n failures.append(\"Missing Policy LC-2019-08844\")\n\n # Check face amount $400,000.00\n if \"400,000\" not in comments_text and \"400000\" not in comments_text:\n failures.append(\"Missing Face Amount $400,000.00\")\n\n # Check Maria L. Martinez (Primary, 60%, $240,000.00)\n if \"maria\" not in ct_lower or \"martinez\" not in ct_lower:\n failures.append(\"Missing beneficiary Maria L. Martinez\")\n else:\n if \"240,000\" not in comments_text and \"240000\" not in comments_text:\n failures.append(\"Missing Maria Martinez amount $240,000.00\")\n if \"60%\" not in comments_text and \"60 %\" not in comments_text:\n failures.append(\"Missing Maria Martinez 60% split\")\n\n # Check David R. Martinez (Primary, 40%, $160,000.00)\n if \"david\" not in ct_lower:\n failures.append(\"Missing beneficiary David R. Martinez\")\n else:\n if \"160,000\" not in comments_text and \"160000\" not in comments_text:\n failures.append(\"Missing David Martinez amount $160,000.00\")\n if \"40%\" not in comments_text and \"40 %\" not in comments_text:\n failures.append(\"Missing David Martinez 40% split\")\n\n # Check Elena Martinez NOT included as a beneficiary in the allocation\n # Mention of her exclusion (e.g., \"Elena Martinez excluded\" or \"not included\") is acceptable.\n # We look for 'elena' in the allocation section and check if it's in the context of exclusion.\n alloc_start = ct_lower.find(\"beneficiary allocation\")\n if alloc_start != -1:\n alloc_section = ct_lower[alloc_start:]\n if \"elena\" in alloc_section:\n # Check if Elena is mentioned only in context of exclusion\n # Look for exclusion-related keywords near 'elena'\n elena_idx = alloc_section.find(\"elena\")\n surrounding = alloc_section[max(0, elena_idx - 80):elena_idx + 80]\n exclusion_keywords = [\"exclud\", \"not included\", \"contingent\", \"not eligible\", \"removed\", \"n/a\", \"omit\", \"not applicable\", \"not payable\", \"skipped\", \"does not\"]\n is_exclusion_mention = any(kw in surrounding for kw in exclusion_keywords)\n if not is_exclusion_mention:\n failures.append(\"Elena Martinez (contingent) must NOT be included in allocation (mention of her exclusion is acceptable)\")\n\n # Check Analyst/Date attribution: \"analyst\" and a date must appear within 80 characters of each other\n analyst_match = re.search(r'analyst', ct_lower)\n # Look for date patterns: 2026-04-10, April 10, 2026, 04/10/2026, 2026, etc.\n date_matches = list(re.finditer(r'(?:202[0-9][-/]\\d{1,2}[-/]\\d{1,2}|\\d{1,2}[-/]\\d{1,2}[-/]202[0-9]|(?:january|february|march|april|may|june|july|august|september|october|november|december)\\s+\\d{1,2},?\\s*202[0-9]|\\b202[0-9]\\b)', ct_lower))\n \n analyst_date_found = False\n if analyst_match and date_matches:\n analyst_pos = analyst_match.start()\n for dm in date_matches:\n date_pos = dm.start()\n if abs(analyst_pos - date_pos) <= 80:\n analyst_date_found = True\n break\n\n if not analyst_date_found:\n failures.append(\"Missing Analyst/Date attribution (\\\"analyst\\\" and a date must appear within 80 characters of each other)\")\n\n if failures:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-1 allocation comment issues: \" + \"; \".join(failures)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"CLM_LIFE-1 allocation comment is correct: Policy LC-2019-08844, $400,000, Maria 60%/$240k, David 40%/$160k, no Elena as beneficiary, has analyst/date attribution within 80 chars\",\n }\n", "criterion_type": "expected_output" }, { "id": "819c155d-7a13-4695-978e-f567c5be3cc9", "sort_order": 2, "rubric_text": "In `jira_state.json`, CLM_LIFE-2 must NOT have CLM-910 or CLM-920 set, and must have a structured beneficiary allocation comment containing Policy LC-2021-15290, Face Amount $250,000.00, Thomas P. Whitfield (Primary, 50%, $125,000.00), Emily J. Whitfield (Primary, 50%, $125,000.00), without contingent beneficiary James R. Whitfield (mention of his exclusion is acceptable).", "verifier_code": "from pathlib import Path\nimport json\nimport re\n\n\ndef extract_text_from_adf(node):\n \"\"\"Recursively extract plain text from Atlassian Document Format.\"\"\"\n text = \"\"\n if isinstance(node, dict):\n if node.get(\"type\") == \"text\":\n text += node.get(\"text\", \"\")\n for child in node.get(\"content\", []):\n text += extract_text_from_adf(child)\n elif isinstance(node, list):\n for item in node:\n text += extract_text_from_adf(item)\n return text\n\n\ndef get_all_comment_text(data, issue_key):\n \"\"\"Get all comment text for an issue from both top-level and inline locations.\"\"\"\n text = \"\"\n issue_key_upper = issue_key.upper()\n # Top-level comments dict\n for ck, cv in data.get(\"comments\", {}).items():\n if ck.upper() == issue_key_upper:\n if isinstance(cv, list):\n for comment in cv:\n body = comment.get(\"body\", {}) if isinstance(comment, dict) else comment\n if isinstance(body, str):\n text += body + \"\\n\"\n else:\n text += extract_text_from_adf(body) + \"\\n\"\n # Inline in issue fields\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n issue = None\n if isinstance(issues, dict):\n for k, v in issues.items():\n if k.upper() == issue_key_upper:\n issue = v\n break\n if isinstance(issues, list):\n for item in issues:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == issue_key_upper:\n issue = item\n break\n if issue and isinstance(issue, dict):\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n comment_field = fields.get(\"comment\", {})\n if isinstance(comment_field, dict):\n for c in comment_field.get(\"comments\", []):\n body = c.get(\"body\", {}) if isinstance(c, dict) else c\n if isinstance(body, str):\n text += body + \"\\n\"\n else:\n text += extract_text_from_adf(body) + \"\\n\"\n return text\n\n\ndef find_issue(data, issue_key):\n \"\"\"Find an issue by key (case-insensitive) in various data shapes.\"\"\"\n issue_key_upper = issue_key.upper()\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n if isinstance(issues, dict):\n for key, val in issues.items():\n if key.upper() == issue_key_upper:\n return val\n if isinstance(issues, list):\n for item in issues:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == issue_key_upper:\n return item\n if isinstance(data, list):\n for item in data:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == issue_key_upper:\n return item\n return None\n\n\ndef get_status_name(issue):\n if not isinstance(issue, dict):\n return \"\"\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n sf = fields.get(\"status\")\n if isinstance(sf, dict):\n return str(sf.get(\"name\", \"\")).strip()\n elif isinstance(sf, str):\n return sf.strip()\n ts = issue.get(\"status\")\n if isinstance(ts, dict):\n return str(ts.get(\"name\", \"\")).strip()\n elif isinstance(ts, str):\n return ts.strip()\n return \"\"\n\n\ndef check_routed(issue, status_lower):\n \"\"\"Check if the issue has been transitioned to 'Route to AP for Payout'.\"\"\"\n if \"route to ap for payout\" in status_lower:\n return True\n # Check transitions history\n transitions = issue.get(\"transitions\", [])\n if isinstance(transitions, list):\n for t in transitions:\n t_name = \"\"\n if isinstance(t, dict):\n t_name = str(t.get(\"name\", \"\")).lower()\n to_field = t.get(\"to\", {})\n if isinstance(to_field, dict):\n t_name += \" \" + str(to_field.get(\"name\", \"\")).lower()\n elif isinstance(to_field, str):\n t_name += \" \" + to_field.lower()\n elif isinstance(t, str):\n t_name = t.lower()\n if \"route to ap for payout\" in t_name:\n return True\n # Check changelog\n changelog = issue.get(\"changelog\", {})\n if isinstance(changelog, dict):\n histories = changelog.get(\"histories\", [])\n if isinstance(histories, list):\n for h in histories:\n for item in (h.get(\"items\", []) if isinstance(h, dict) else []):\n if isinstance(item, dict) and str(item.get(\"field\", \"\")).lower() == \"status\":\n to_str = str(item.get(\"toString\", \"\")).lower()\n if \"route to ap for payout\" in to_str:\n return True\n return False\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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n issue = find_issue(data, \"CLM_LIFE-2\")\n if issue is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-2 not found in jira_state.json\"}\n\n status_name = get_status_name(issue)\n status_lower = status_name.lower()\n\n # --- Extract labels ---\n labels = []\n fields = issue.get(\"fields\", {}) if isinstance(issue, dict) else {}\n if isinstance(fields, dict):\n raw_labels = fields.get(\"labels\", [])\n if isinstance(raw_labels, list):\n labels = [str(l).lower() for l in raw_labels]\n\n # Serialize all field values for broad matching\n all_field_values_str = json.dumps(fields).lower() if isinstance(fields, dict) else \"\"\n # Also serialize whole issue for broader check\n all_issue_str = json.dumps(issue).lower() if isinstance(issue, dict) else \"\"\n\n # --- Check CLM-910 NOT set ---\n has_910 = (\n \"clm-910\" in status_lower\n or \"identity verification hold\" in status_lower\n or any(\"clm-910\" in lbl for lbl in labels)\n or any(\"identity-verification-hold\" in lbl or \"identity verification hold\" in lbl for lbl in labels)\n or \"clm-910\" in all_field_values_str\n )\n\n # --- Check CLM-920 NOT set ---\n has_920 = (\n \"clm-920\" in status_lower\n or \"complex beneficiary hold\" in status_lower\n or any(\"clm-920\" in lbl for lbl in labels)\n or any(\"complex-beneficiary-hold\" in lbl or \"complex beneficiary hold\" in lbl for lbl in labels)\n or \"clm-920\" in all_field_values_str\n )\n\n # --- Check status is \"Route to AP for Payout\" ---\n routed = check_routed(issue, status_lower)\n\n # --- Get all comment text (ADF-aware) ---\n comments_text = get_all_comment_text(data, \"CLM_LIFE-2\")\n ct_lower = comments_text.lower()\n\n failures = []\n\n # Hold checks\n if has_910:\n failures.append(f\"CLM-910 Identity Verification Hold is set (status: '{status_name}')\")\n if has_920:\n failures.append(f\"CLM-920 Complex Beneficiary Hold is set (status: '{status_name}')\")\n # Allocation comment checks - look for beneficiary allocation or similar structured comment\n has_alloc_header = (\n \"beneficiary\" in ct_lower\n or \"allocation\" in ct_lower\n or \"payout\" in ct_lower\n )\n if not has_alloc_header:\n failures.append(\"Missing structured beneficiary allocation comment\")\n\n # Policy number\n if \"lc-2021-15290\" not in ct_lower and \"lc202115290\" not in ct_lower and \"lc 2021 15290\" not in ct_lower:\n failures.append(\"Missing Policy LC-2021-15290 in comments\")\n\n # Face amount $250,000.00\n if \"250,000\" not in comments_text and \"250000\" not in comments_text and \"250,000.00\" not in comments_text:\n failures.append(\"Missing Face Amount $250,000.00 in comments\")\n\n # Thomas P. Whitfield\n has_thomas = \"thomas\" in ct_lower and \"whitfield\" in ct_lower\n if not has_thomas:\n failures.append(\"Missing beneficiary Thomas P. Whitfield in comments\")\n\n # Emily J. Whitfield\n has_emily = \"emily\" in ct_lower and \"whitfield\" in ct_lower\n if not has_emily:\n failures.append(\"Missing beneficiary Emily J. Whitfield in comments\")\n\n # Check 50% split - accept various representations\n has_50 = bool(re.search(r'50\\s*%', comments_text) or '50%' in comments_text or '50.00%' in comments_text or '50.0%' in comments_text)\n if not has_50:\n failures.append(\"Missing 50% split for beneficiaries\")\n\n # Check $125,000.00 amount\n if \"125,000\" not in comments_text and \"125000\" not in comments_text:\n failures.append(\"Missing $125,000.00 allocation amount\")\n\n # Check Primary designation\n if \"primary\" not in ct_lower:\n failures.append(\"Missing 'Primary' designation for beneficiaries\")\n\n # James R. Whitfield must NOT be included in the allocation as a payee\n # Mention of his exclusion is acceptable per rubric\n if \"james\" in ct_lower and \"whitfield\" in ct_lower:\n james_as_payee = False\n lines = comments_text.split('\\n')\n for line in lines:\n line_lower = line.lower()\n if 'james' in line_lower and 'whitfield' in line_lower:\n exclusion_words = ['exclud', 'contingent', 'not eligible', 'not included', 'n/a', 'skip',\n 'omit', 'no payout', '$0', '0.00', '0%', 'not applicable',\n 'does not', 'do not', 'not payable', 'no allocation',\n 'waiv', 'none', 'excluded', 'ineligible']\n has_exclusion = any(ew in line_lower for ew in exclusion_words)\n # Check if line has a real dollar allocation for James\n has_dollar = bool(re.search(r'\\$[1-9][0-9,]+', line))\n if has_dollar and not has_exclusion:\n james_as_payee = True\n\n if james_as_payee:\n failures.append(\"James R. Whitfield (contingent) must NOT be included in allocation as a payee\")\n\n if failures:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-2 issues: \" + \"; \".join(failures)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"CLM_LIFE-2: No CLM-910/CLM-920 holds, correct structured beneficiary allocation comment with Policy LC-2021-15290, Face Amount $250,000.00, Thomas P. Whitfield (Primary, 50%, $125,000.00), Emily J. Whitfield (Primary, 50%, $125,000.00), James R. Whitfield contingent properly excluded.\",\n }\n", "criterion_type": "expected_output" }, { "id": "3c204513-4ad4-418b-b9a7-a5efa2ced421", "sort_order": 3, "rubric_text": "In `jira_state.json`, CLM_LIFE-3 must NOT have CLM-910 or CLM-920 set, and must have a structured beneficiary allocation comment with pro-rata redistribution: Mark H. Drummond (63.64% [allow 63.6%], $381,818.18) and Susan E. Drummond (36.36% [allow 36.3%], $218,181.82), excluding deceased Linda M. Drummond and contingent George T. Drummond. Linda and George mentioned as excluded or not receiving payout is acceptable.", "verifier_code": "from pathlib import Path\nimport json\nimport re\n\n\ndef extract_text_from_adf(node):\n \"\"\"Recursively extract plain text from Atlassian Document Format.\n\n Block-level nodes (paragraph, heading, list item, etc.) are separated with\n newlines, and hardBreak nodes emit a newline, so the per-line payout checks\n below see the same line boundaries the comment author intended (ADF\n otherwise drops them, collapsing the whole comment onto one line).\n \"\"\"\n block_types = {\"paragraph\", \"heading\", \"blockquote\", \"listItem\",\n \"bulletList\", \"orderedList\", \"codeBlock\", \"rule\", \"tableRow\"}\n text = \"\"\n if isinstance(node, dict):\n node_type = node.get(\"type\")\n if node_type == \"text\":\n text += node.get(\"text\", \"\")\n elif node_type == \"hardBreak\":\n text += \"\\n\"\n for child in node.get(\"content\", []):\n text += extract_text_from_adf(child)\n if node_type in block_types:\n text += \"\\n\"\n elif isinstance(node, list):\n for item in node:\n text += extract_text_from_adf(item)\n return text\n\n\ndef get_all_comment_text(data, issue_key):\n \"\"\"Get all comment text for an issue from both top-level and inline locations.\"\"\"\n text = \"\"\n # Top-level comments keyed by issue\n for comment in data.get(\"comments\", {}).get(issue_key, []):\n body = comment.get(\"body\", {})\n if isinstance(body, str):\n text += body + \"\\n\"\n else:\n text += extract_text_from_adf(body) + \"\\n\"\n # Inline comments inside issues -> fields -> comment\n issues = data.get(\"issues\", {})\n issue = issues.get(issue_key, {}) if isinstance(issues, dict) else {}\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n comment_field = fields.get(\"comment\", {})\n if isinstance(comment_field, dict):\n for c in comment_field.get(\"comments\", []):\n body = c.get(\"body\", {})\n if isinstance(body, str):\n text += body + \"\\n\"\n else:\n text += extract_text_from_adf(body) + \"\\n\"\n return text\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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n # --- Locate CLM_LIFE-3 ---\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n issue = None\n if isinstance(issues, dict):\n issue = issues.get(\"CLM_LIFE-3\")\n if issue is None:\n for key, val in issues.items():\n if key.upper() == \"CLM_LIFE-3\":\n issue = val\n break\n if issue is None and isinstance(data, list):\n for item in data:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == \"CLM_LIFE-3\":\n issue = item\n break\n\n if issue is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-3 not found in jira_state.json\"}\n\n # --- Extract status name ---\n def get_status_name(iss):\n fields = iss.get(\"fields\", {})\n if isinstance(fields, dict):\n sf = fields.get(\"status\")\n if isinstance(sf, dict):\n return str(sf.get(\"name\", \"\")).strip()\n elif isinstance(sf, str):\n return sf.strip()\n ts = iss.get(\"status\")\n if isinstance(ts, dict):\n return str(ts.get(\"name\", \"\")).strip()\n elif isinstance(ts, str):\n return ts.strip()\n return \"\"\n\n status_name = get_status_name(issue)\n status_lower = status_name.lower()\n\n # --- Extract labels ---\n labels = []\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n raw_labels = fields.get(\"labels\", [])\n if isinstance(raw_labels, list):\n labels = [str(l).lower() for l in raw_labels]\n\n # --- Check CLM-910 NOT set (status and labels) ---\n has_910 = (\n \"clm-910\" in status_lower\n or \"identity verification hold\" in status_lower\n or any(\"clm-910\" in lbl for lbl in labels)\n or any(\"identity-verification-hold\" in lbl or \"identity verification hold\" in lbl for lbl in labels)\n )\n\n # --- Check CLM-920 NOT set (status and labels) ---\n has_920 = (\n \"clm-920\" in status_lower\n or \"complex beneficiary hold\" in status_lower\n or any(\"clm-920\" in lbl for lbl in labels)\n or any(\"complex-beneficiary-hold\" in lbl or \"complex beneficiary hold\" in lbl for lbl in labels)\n )\n\n # --- Check status is \"Route to AP for Payout\" ---\n routed = \"route to ap for payout\" in status_lower\n if not routed:\n transitions = issue.get(\"transitions\", [])\n if isinstance(transitions, list):\n for t in transitions:\n t_name = \"\"\n if isinstance(t, dict):\n t_name = str(t.get(\"name\", t.get(\"to\", \"\"))).lower()\n elif isinstance(t, str):\n t_name = t.lower()\n if \"route to ap for payout\" in t_name:\n routed = True\n break\n\n # --- Get all comment text (ADF-aware) ---\n comments_text = get_all_comment_text(data, \"CLM_LIFE-3\")\n ct_lower = comments_text.lower()\n\n # --- Extract allocation block only (text from the allocation header onward) ---\n # This scopes George/Linda payout checks to the actual allocation table,\n # preventing false positives from policy schedule documentation in Step 1 review.\n alloc_header_pos = ct_lower.find(\"beneficiary allocation\")\n if alloc_header_pos == -1:\n # Fallback: search for any structured payout block marker\n alloc_header_pos = ct_lower.find(\"beneficiary 1\")\n alloc_block = ct_lower[alloc_header_pos:] if alloc_header_pos != -1 else \"\"\n\n failures = []\n\n # Hold checks\n if has_910:\n failures.append(f\"CLM-910 Identity Verification Hold is set (status: '{status_name}')\")\n if has_920:\n failures.append(f\"CLM-920 Complex Beneficiary Hold is set (status: '{status_name}')\")\n # --- Beneficiary allocation comment checks ---\n has_alloc_header = (\"beneficiary allocation\" in ct_lower or\n (\"beneficiary\" in ct_lower and \"allocation\" in ct_lower) or\n (\"beneficiary\" in ct_lower and (\"redistribution\" in ct_lower or \"pro-rata\" in ct_lower or \"pro rata\" in ct_lower)))\n if not has_alloc_header:\n failures.append(\"Missing 'Beneficiary Allocation' or similar structured header in comments\")\n\n # Mark H. Drummond — redistributed to ~63.64%, ~$381,818.18\n has_mark = \"mark\" in ct_lower and \"drummond\" in ct_lower\n if not has_mark:\n failures.append(\"Missing Mark H. Drummond in comments\")\n else:\n mark_amount_ok = bool(re.search(r\"381[,.]?818\", comments_text))\n if not mark_amount_ok:\n failures.append(\"Missing Mark Drummond redistributed amount ~$381,818.18\")\n mark_pct_ok = bool(re.search(r\"63\\.6[0-9]*\", comments_text))\n if not mark_pct_ok:\n failures.append(\"Missing Mark Drummond redistributed percentage ~63.64%\")\n\n # Susan E. Drummond — redistributed to ~36.36%, ~$218,181.82\n has_susan = \"susan\" in ct_lower and \"drummond\" in ct_lower\n if not has_susan:\n failures.append(\"Missing Susan E. Drummond in comments\")\n else:\n susan_amount_ok = bool(re.search(r\"218[,.]?181\", comments_text))\n if not susan_amount_ok:\n failures.append(\"Missing Susan Drummond redistributed amount ~$218,181.82\")\n susan_pct_ok = bool(re.search(r\"36\\.3[0-9]*\", comments_text))\n if not susan_pct_ok:\n failures.append(\"Missing Susan Drummond redistributed percentage ~36.36%\")\n\n # --- Forbidden payees: George T. Drummond (contingent) and Linda M. Drummond\n # (deceased) must NOT appear as a payee RECEIVING a payout in the allocation. ---\n # Mentioning either as excluded / not listed / deceased / redistributed is fine.\n # We scan line by line (extract_text_from_adf newline-separates block nodes) and\n # only fail when the name shares a line with a NON-ZERO dollar amount and that line\n # carries no exclusion language -- i.e. an actual payout row, not a free-text note\n # or the grand total. The non-zero requirement ($[1-9]...) ignores \"$0.00\" lines.\n # alloc_block is already lower-cased, so name/exclusion tokens are compared as-is.\n exclusion_words = [\n \"exclud\", \"not listed\", \"not included\", \"not receiving\", \"not eligible\",\n \"ineligible\", \"redistribut\", \"deceased\", \"predecease\", \"n/a\",\n \"not applicable\", \"not payable\", \"no payout\", \"no allocation\",\n ]\n\n def receives_payout(first_name):\n for line in alloc_block.split(\"\\n\"):\n if first_name in line and \"drummond\" in line:\n has_exclusion = any(w in line for w in exclusion_words)\n has_nonzero_amount = bool(re.search(r\"\\$\\s*[1-9][\\d,]*\", line))\n if has_nonzero_amount and not has_exclusion:\n return True\n return False\n\n if receives_payout(\"george\"):\n failures.append(\"George T. Drummond (contingent) must NOT be included in allocation with a payout amount\")\n if receives_payout(\"linda\"):\n failures.append(\"Linda M. Drummond (deceased) must NOT be included in allocation with a payout amount\")\n\n if failures:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-3 issues: \" + \"; \".join(failures)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": (\n \"CLM_LIFE-3: No CLM-910/CLM-920 holds, transitioned to 'Route to AP for Payout', \"\n \"correct pro-rata redistribution comment — Mark H. Drummond ~63.64%/$381,818.18, \"\n \"Susan E. Drummond ~36.36%/$218,181.82, deceased Linda M. Drummond and contingent \"\n \"George T. Drummond excluded from allocation. All checks passed.\"\n ),\n }", "criterion_type": "expected_output" }, { "id": "d2411716-ed63-4513-8756-51f430561e46", "sort_order": 4, "rubric_text": "In `jira_state.json`, CLM_LIFE-4 must NOT have CLM-910 or CLM-920 set, and must have a structured beneficiary allocation comment containing Policy LC-2020-11467, Face Amount $350,000.00, Emeka A. Okafor (Primary, 70%, $245,000.00), Chidi E. Okafor (Primary, 30%, $105,000.00), without contingent beneficiary Ngozi F. Okafor (mention of her exclusion is acceptable).", "verifier_code": "from pathlib import Path\nimport json\nimport re\n\n\ndef extract_text_from_adf(node):\n \"\"\"Recursively extract plain text from Atlassian Document Format.\"\"\"\n text = \"\"\n if isinstance(node, dict):\n if node.get(\"type\") == \"text\":\n text += node.get(\"text\", \"\")\n for child in node.get(\"content\", []):\n text += extract_text_from_adf(child)\n elif isinstance(node, list):\n for item in node:\n text += extract_text_from_adf(item)\n return text\n\n\ndef get_all_comment_text(data, issue_key):\n \"\"\"Get all comment text for an issue from both top-level and inline locations.\"\"\"\n text = \"\"\n # Top-level comments dict\n for comment in data.get(\"comments\", {}).get(issue_key, []):\n body = comment.get(\"body\", {})\n if isinstance(body, str):\n text += body + \"\\n\"\n else:\n text += extract_text_from_adf(body) + \"\\n\"\n # Inline comments in issue fields\n issues = data.get(\"issues\", {})\n issue = issues.get(issue_key, {}) if isinstance(issues, dict) else {}\n fields = issue.get(\"fields\", {}) if isinstance(issue, dict) else {}\n if isinstance(fields, dict):\n comment_field = fields.get(\"comment\", {})\n if isinstance(comment_field, dict):\n for c in comment_field.get(\"comments\", []):\n body = c.get(\"body\", {})\n if isinstance(body, str):\n text += body + \"\\n\"\n else:\n text += extract_text_from_adf(body) + \"\\n\"\n return text\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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n # --- Locate CLM_LIFE-4 ---\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n issue = None\n if isinstance(issues, dict):\n issue = issues.get(\"CLM_LIFE-4\")\n if issue is None:\n for key, val in issues.items():\n if key.upper() == \"CLM_LIFE-4\":\n issue = val\n break\n if issue is None and isinstance(data, list):\n for item in data:\n if isinstance(item, dict) and item.get(\"key\", \"\").upper() == \"CLM_LIFE-4\":\n issue = item\n break\n\n if issue is None:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-4 not found in jira_state.json\"}\n\n # --- Extract status name ---\n def get_status_name(iss):\n fields = iss.get(\"fields\", {})\n if isinstance(fields, dict):\n sf = fields.get(\"status\")\n if isinstance(sf, dict):\n return str(sf.get(\"name\", \"\")).strip()\n elif isinstance(sf, str):\n return sf.strip()\n ts = iss.get(\"status\")\n if isinstance(ts, dict):\n return str(ts.get(\"name\", \"\")).strip()\n elif isinstance(ts, str):\n return ts.strip()\n return \"\"\n\n status_name = get_status_name(issue)\n status_lower = status_name.lower()\n\n # --- Extract labels ---\n labels = []\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n raw_labels = fields.get(\"labels\", [])\n if isinstance(raw_labels, list):\n labels = [str(l).lower() for l in raw_labels]\n\n # --- Collect all text fields from the issue for hold detection ---\n def all_field_values_str(iss):\n \"\"\"Gather string representations of all field values for broad searching.\"\"\"\n parts = []\n flds = iss.get(\"fields\", {})\n if isinstance(flds, dict):\n for k, v in flds.items():\n if k == \"comment\":\n continue # skip comments for hold detection\n if isinstance(v, str):\n parts.append(v.lower())\n elif isinstance(v, dict):\n parts.append(json.dumps(v).lower())\n elif isinstance(v, list):\n parts.append(json.dumps(v).lower())\n return \" \".join(parts)\n\n all_fields_text = all_field_values_str(issue)\n\n # --- Check CLM-910 NOT set ---\n has_910 = (\n \"clm-910\" in status_lower\n or \"identity verification hold\" in status_lower\n or any(\"clm-910\" in lbl for lbl in labels)\n or any(\"identity-verification-hold\" in lbl or \"identity verification hold\" in lbl for lbl in labels)\n or \"clm-910\" in all_fields_text\n )\n\n # --- Check CLM-920 NOT set ---\n has_920 = (\n \"clm-920\" in status_lower\n or \"complex beneficiary hold\" in status_lower\n or any(\"clm-920\" in lbl for lbl in labels)\n or any(\"complex-beneficiary-hold\" in lbl or \"complex beneficiary hold\" in lbl for lbl in labels)\n or \"clm-920\" in all_fields_text\n )\n\n # --- Check status is \"Route to AP for Payout\" ---\n routed = \"route to ap for payout\" in status_lower\n if not routed:\n # Also check transitions history\n transitions = issue.get(\"transitions\", [])\n if isinstance(transitions, list):\n for t in transitions:\n t_name = \"\"\n if isinstance(t, dict):\n t_name = str(t.get(\"name\", t.get(\"to\", \"\"))).lower()\n elif isinstance(t, str):\n t_name = t.lower()\n if \"route to ap for payout\" in t_name:\n routed = True\n break\n\n # --- Get all comment text (ADF-aware) ---\n comments_text = get_all_comment_text(data, \"CLM_LIFE-4\")\n ct_lower = comments_text.lower()\n\n failures = []\n\n # Hold checks\n if has_910:\n failures.append(f\"CLM-910 Identity Verification Hold is set (status: '{status_name}')\")\n if has_920:\n failures.append(f\"CLM-920 Complex Beneficiary Hold is set (status: '{status_name}')\")\n # Allocation comment checks - be loose about the header\n has_alloc_header = (\n \"beneficiary allocation\" in ct_lower\n or \"allocation\" in ct_lower\n or \"beneficiary\" in ct_lower\n )\n if not has_alloc_header:\n failures.append(\"Missing 'Beneficiary Allocation' or similar header in comments\")\n\n if \"lc-2020-11467\" not in ct_lower and \"lc202011467\" not in ct_lower:\n failures.append(\"Missing Policy LC-2020-11467\")\n\n if \"350,000\" not in comments_text and \"350000\" not in comments_text and \"350,000.00\" not in comments_text:\n failures.append(\"Missing Face Amount $350,000.00\")\n\n # Emeka A. Okafor\n if \"emeka\" not in ct_lower or \"okafor\" not in ct_lower:\n failures.append(\"Missing beneficiary Emeka A. Okafor\")\n else:\n if \"245,000\" not in comments_text and \"245000\" not in comments_text and \"245,000.00\" not in comments_text:\n failures.append(\"Missing Emeka Okafor amount $245,000.00\")\n if \"70%\" not in comments_text and \"70 %\" not in comments_text and \"70.0%\" not in comments_text:\n failures.append(\"Missing Emeka Okafor 70% split\")\n\n # Chidi E. Okafor\n if \"chidi\" not in ct_lower:\n failures.append(\"Missing beneficiary Chidi E. Okafor\")\n else:\n if \"105,000\" not in comments_text and \"105000\" not in comments_text and \"105,000.00\" not in comments_text:\n failures.append(\"Missing Chidi Okafor amount $105,000.00\")\n if \"30%\" not in comments_text and \"30 %\" not in comments_text and \"30.0%\" not in comments_text:\n failures.append(\"Missing Chidi Okafor 30% split\")\n\n # Ngozi F. Okafor must NOT be included as an allocated beneficiary.\n # Mention of her exclusion (e.g., \"Ngozi excluded as contingent\") is acceptable.\n # We check if Ngozi appears with a dollar amount or percentage that looks like an allocation.\n if \"ngozi\" in ct_lower:\n # Look for Ngozi paired with allocation indicators (dollar amounts or percentages)\n # but allow exclusion language\n ngozi_sections = [m.start() for m in re.finditer(r\"ngozi\", ct_lower)]\n for pos in ngozi_sections:\n # Get surrounding context (200 chars)\n context_start = max(0, pos - 100)\n context_end = min(len(ct_lower), pos + 200)\n context = ct_lower[context_start:context_end]\n # Check if this mention has allocation numbers AND no exclusion language\n has_exclusion_language = any(word in context for word in [\n \"exclud\", \"not included\", \"not eligible\", \"contingent\",\n \"skip\", \"omit\", \"n/a\", \"not applicable\", \"no payout\",\n \"does not receive\", \"not a primary\", \"not primary\"\n ])\n has_alloc_numbers = bool(re.search(r\"\\$[\\d,]+\\.?\\d*\", context)) and not has_exclusion_language\n has_pct = bool(re.search(r\"\\d+\\.?\\d*\\s*%\", context)) and not has_exclusion_language\n if (has_alloc_numbers or has_pct) and not has_exclusion_language:\n failures.append(\"Ngozi F. Okafor (contingent) must NOT be included in beneficiary allocation with amounts/percentages\")\n break\n\n if failures:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-4 issues: \" + \"; \".join(failures)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"CLM_LIFE-4: No CLM-910/CLM-920 holds, correct structured beneficiary allocation with Policy LC-2020-11467, Face Amount $350,000.00, Emeka A. Okafor (Primary, 70%, $245,000.00), Chidi E. Okafor (Primary, 30%, $105,000.00), contingent Ngozi F. Okafor properly excluded from allocation.\",\n }\n", "criterion_type": "expected_output" }, { "id": "rubric_1775973366733", "sort_order": 5, "rubric_text": "CLM_LIFE-1 must NOT be transitioned to CLM_LEGAL or routed to Claims Supervisor (CLM-Identity).", "verifier_code": "from pathlib import Path\nimport json\nimport re\n\n\ndef extract_text_from_adf(node):\n \"\"\"Recursively extract plain text from Atlassian Document Format.\"\"\"\n text = \"\"\n if isinstance(node, dict):\n if node.get(\"type\") == \"text\":\n text += node.get(\"text\", \"\")\n for child in node.get(\"content\", []):\n text += extract_text_from_adf(child)\n elif isinstance(node, list):\n for item in node:\n text += extract_text_from_adf(item)\n return text\n\n\ndef get_all_comment_text(data, issue_key):\n \"\"\"Get all comment text for an issue from both top-level and inline locations.\"\"\"\n text = \"\"\n for comment in data.get(\"comments\", {}).get(issue_key, []):\n body = comment.get(\"body\", {})\n text += extract_text_from_adf(body) + \"\\n\"\n issues = data.get(\"issues\", {})\n issue = issues.get(issue_key, {}) if isinstance(issues, dict) else {}\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n comment_field = fields.get(\"comment\", {})\n if isinstance(comment_field, dict):\n for c in comment_field.get(\"comments\", []):\n body = c.get(\"body\", {})\n text += extract_text_from_adf(body) + \"\\n\"\n return text\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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n failures = []\n\n # --- Check 1: Were any new CLM_LEGAL issues created that reference CLM_LIFE-1? ---\n # CLM_LEGAL-1 is pre-existing (Queue Roles & Assignees), so skip it\n for key, val in issues.items():\n if \"legal\" in key.lower() and key != \"CLM_LEGAL-1\":\n issue_str = json.dumps(val)\n if \"CLM_LIFE-1\" in issue_str or \"Martinez\" in issue_str or \"LC-2019-08844\" in issue_str:\n summary = \"\"\n if isinstance(val, dict):\n fields = val.get(\"fields\", {})\n if isinstance(fields, dict):\n summary = str(fields.get(\"summary\", \"\"))\n failures.append(f\"New CLM_LEGAL issue '{key}' was created referencing CLM_LIFE-1 (summary: '{summary}')\")\n\n # --- Check 2: Did the model's comments on CLM_LIFE-1 indicate routing to Legal? ---\n comments_text = get_all_comment_text(data, \"CLM_LIFE-1\")\n ct_lower = comments_text.lower()\n\n routed_to_legal = False\n if \"clm-legal\" in ct_lower:\n legal_patterns = [\n r\"rout\\w*\\s+to\\s+clm[- ]?legal\",\n r\"set\\s+clm[- ]?legal\",\n r\"created?\\s+.*clm[- ]?legal\",\n r\"assign\\w*\\s+.*clm[- ]?legal\",\n r\"forward\\w*\\s+.*clm[- ]?legal\",\n r\"escala\\w*\\s+.*clm[- ]?legal\",\n ]\n for pat in legal_patterns:\n if re.search(pat, ct_lower):\n routed_to_legal = True\n break\n\n if routed_to_legal:\n failures.append(\"CLM_LIFE-1 comments indicate routing to CLM_LEGAL\")\n\n # --- Check 3: Is the status itself set to a Legal queue? ---\n issue = issues.get(\"CLM_LIFE-1\")\n if issue is not None:\n fields = issue.get(\"fields\", {})\n status_name = \"\"\n if isinstance(fields, dict):\n sf = fields.get(\"status\")\n if isinstance(sf, dict):\n status_name = str(sf.get(\"name\", \"\")).strip()\n elif isinstance(sf, str):\n status_name = sf.strip()\n\n if \"legal\" in status_name.lower():\n failures.append(f\"CLM_LIFE-1 status is set to a Legal queue ('{status_name}')\")\n\n if failures:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-1 incorrectly routed to Legal: \" + \"; \".join(failures)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"CLM_LIFE-1: Not routed to CLM_LEGAL, no Legal issues created for this claim\",\n }", "criterion_type": "incorrect_behavior" }, { "id": "rubric_1775973378903", "sort_order": 6, "rubric_text": "CLM_LIFE-4 must NOT be transitioned to CLM_LEGAL or have any hold status applied.", "verifier_code": "from pathlib import Path\nimport json\nimport re\n\n\ndef extract_text_from_adf(node):\n \"\"\"Recursively extract plain text from Atlassian Document Format.\"\"\"\n text = \"\"\n if isinstance(node, dict):\n if node.get(\"type\") == \"text\":\n text += node.get(\"text\", \"\")\n for child in node.get(\"content\", []):\n text += extract_text_from_adf(child)\n elif isinstance(node, list):\n for item in node:\n text += extract_text_from_adf(item)\n return text\n\n\ndef get_all_comment_text(data, issue_key):\n \"\"\"Get all comment text for an issue from both top-level and inline locations.\"\"\"\n text = \"\"\n for comment in data.get(\"comments\", {}).get(issue_key, []):\n body = comment.get(\"body\", {})\n text += extract_text_from_adf(body) + \"\\n\"\n issues = data.get(\"issues\", {})\n issue = issues.get(issue_key, {}) if isinstance(issues, dict) else {}\n fields = issue.get(\"fields\", {})\n if isinstance(fields, dict):\n comment_field = fields.get(\"comment\", {})\n if isinstance(comment_field, dict):\n for c in comment_field.get(\"comments\", []):\n body = c.get(\"body\", {})\n text += extract_text_from_adf(body) + \"\\n\"\n return text\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\": \"external_services_path is None; cannot check jira_state.json\"}\n\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\"}\n\n with open(jira_path) as f:\n data = json.load(f)\n\n issues = data.get(\"issues\", {}) if isinstance(data, dict) else {}\n failures = []\n\n # --- Check 1: Were any new CLM_LEGAL issues created that reference CLM_LIFE-4? ---\n for key, val in issues.items():\n if \"legal\" in key.lower() and key != \"CLM_LEGAL-1\":\n issue_str = json.dumps(val)\n if \"CLM_LIFE-4\" in issue_str or \"Okafor\" in issue_str or \"LC-2020-11467\" in issue_str:\n summary = \"\"\n if isinstance(val, dict):\n fields = val.get(\"fields\", {})\n if isinstance(fields, dict):\n summary = str(fields.get(\"summary\", \"\"))\n failures.append(f\"New CLM_LEGAL issue '{key}' was created referencing CLM_LIFE-4 (summary: '{summary}')\")\n\n # --- Check 2: Did the model's comments on CLM_LIFE-4 indicate routing to Legal? ---\n comments_text = get_all_comment_text(data, \"CLM_LIFE-4\")\n ct_lower = comments_text.lower()\n\n routed_to_legal = False\n if \"clm-legal\" in ct_lower:\n legal_patterns = [\n r\"rout\\w*\\s+to\\s+clm[- ]?legal\",\n r\"set\\s+clm[- ]?legal\",\n r\"created?\\s+.*clm[- ]?legal\",\n r\"assign\\w*\\s+.*clm[- ]?legal\",\n r\"forward\\w*\\s+.*clm[- ]?legal\",\n r\"escala\\w*\\s+.*clm[- ]?legal\",\n ]\n for pat in legal_patterns:\n if re.search(pat, ct_lower):\n routed_to_legal = True\n break\n\n if routed_to_legal:\n failures.append(\"CLM_LIFE-4 comments indicate routing to CLM_LEGAL\")\n\n # --- Check 3: Is the status itself set to a Legal queue? ---\n issue = issues.get(\"CLM_LIFE-4\")\n if issue is not None:\n fields = issue.get(\"fields\", {})\n status_name = \"\"\n if isinstance(fields, dict):\n sf = fields.get(\"status\")\n if isinstance(sf, dict):\n status_name = str(sf.get(\"name\", \"\")).strip()\n elif isinstance(sf, str):\n status_name = sf.strip()\n\n if \"legal\" in status_name.lower():\n failures.append(f\"CLM_LIFE-4 status is set to a Legal queue ('{status_name}')\")\n\n if failures:\n return {\"pass\": False, \"score\": 0.0, \"feedback\": \"CLM_LIFE-4 incorrectly routed to Legal: \" + \"; \".join(failures)}\n\n return {\n \"pass\": True,\n \"score\": 1.0,\n \"feedback\": \"CLM_LIFE-4: Not routed to CLM_LEGAL, no Legal issues created for this claim\",\n }", "criterion_type": "incorrect_behavior" } ]