from math import isclose
def validate_lab(notebook, minimum_even_parity):
    errors = []
    if notebook.get("scenario") != "line-routed Bell experiment" or not notebook.get("source_ids"):
        errors.append("scenario_or_sources")
    route = notebook.get("route", {})
    if (type(route.get("logical_endpoints")) is not list or len(route.get("logical_endpoints", [])) != 2 or
            type(route.get("swap_count")) is not int or route.get("distance_unit") != "edges"):
        errors.append("route_schema")
    probabilities = notebook.get("ideal_probabilities", {})
    if set(probabilities) != {"00", "01", "10", "11"} or any(type(value) is not float for value in probabilities.values()):
        errors.append("probability_schema")
    elif not isclose(sum(probabilities.values()), 1.0) or probabilities["01"] + probabilities["10"] != 0.0:
        errors.append("ideal_bell_state")
    counts = notebook.get("counts", {})
    shots = notebook.get("shots", {})
    if set(counts) != {"00", "01", "10", "11"} or any(type(value) is not int for value in counts.values()):
        errors.append("counts_schema")
    if type(shots.get("value")) is not int or shots.get("unit") != "shots" or sum(counts.values()) != shots.get("value"):
        errors.append("shots_schema")
    calibration = notebook.get("calibration", {})
    if type(calibration) is not dict:
        errors.append("calibration_schema")
    compiled = route.get("swap_count", 0) * 3 + route.get("logical_entanglers", 0)
    parity = (counts.get("00", 0) + counts.get("11", 0)) / max(shots.get("value", 0), 1)
    stamped = all(calibration.get(name) for name in ("backend", "timestamp", "job_id"))
    if errors:
        decision = "invalid"
    elif parity < minimum_even_parity:
        decision = "acceptance-fail"
    else:
        decision = "hardware-accepted" if stamped else "simulation-only"
    return {"decision": decision, "errors": sorted(set(errors)),
            "compiled_two_qubit_count": compiled, "even_parity": parity, "calibration_stamped": stamped}
notebook = {"scenario": "line-routed Bell experiment", "source_ids": ["openqasm3"],
            "route": {"logical_endpoints": [0, 2], "swap_count": 1,
                      "logical_entanglers": 1, "distance_unit": "edges"},
            "ideal_probabilities": {"00": 0.5, "11": 0.5, "01": 0.0, "10": 0.0},
            "counts": {"00": 4800, "11": 4800, "01": 210, "10": 190},
            "shots": {"value": 10000, "unit": "shots"},
            "calibration": {"backend": "", "timestamp": "", "job_id": ""}}
base_result = validate_lab(notebook, 0.95)
bad_route = {**notebook["route"], "distance_unit": ""}
bad_result = validate_lab({**notebook, "route": bad_route}, 0.95)
strict_result = validate_lab(notebook, 0.97)
assert base_result["compiled_two_qubit_count"] == 4 and base_result["decision"] == "simulation-only"
assert bad_result["decision"] == "invalid" and "route_schema" in bad_result["errors"]
assert strict_result["decision"] == "acceptance-fail" and isclose(strict_result["even_parity"], 0.96)
print(f"PASS: 84 lab notebook gates={base_result['compiled_two_qubit_count']} parity={base_result['even_parity']} claim={base_result['decision']} invalid={bad_result['errors']} strict={strict_result['decision']}")
