Download scripts/audit_l3_event_split.py from haidang2405/telen: direct link, hf CLI and curl.
- Browser
- Download file 6.86 kB
-
https://huggingface.co/haidang2405/telen/resolve/main/scripts/audit_l3_event_split.py
- Command line
-
hf download hf://haidang2405/telen/scripts/audit_l3_event_split.py
-
curl -L -o audit_l3_event_split.py https://huggingface.co/haidang2405/telen/resolve/main/scripts/audit_l3_event_split.py
6.86 kB
| #!/usr/bin/env python3 | |
| """Freeze a chronological, law-family-disjoint L3 train/development split.""" | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| import pandas as pd | |
| REPO_ROOT = Path(__file__).resolve().parents[1] | |
| def sha256_file(path: Path) -> str: | |
| digest = hashlib.sha256() | |
| with path.open("rb") as handle: | |
| for block in iter(lambda: handle.read(1024 * 1024), b""): | |
| digest.update(block) | |
| return digest.hexdigest() | |
| def resolve_recorded_path(value: str, receipt_path: Path) -> Path: | |
| candidate = Path(value) | |
| if candidate.is_absolute(): | |
| return candidate | |
| repo_candidate = REPO_ROOT / candidate | |
| return repo_candidate if repo_candidate.exists() else receipt_path.parent / candidate | |
| def audit(args: argparse.Namespace) -> dict[str, object]: | |
| annotation = json.loads(args.annotation_audit.read_text(encoding="utf-8")) | |
| inputs = annotation.get("inputs", {}) | |
| events_path = resolve_recorded_path(str(inputs.get("events", "")), args.annotation_audit) | |
| events_hash_matches = bool( | |
| events_path.is_file() | |
| and sha256_file(events_path) == inputs.get("events_sha256") | |
| ) | |
| annotation_authorized = bool( | |
| annotation.get("artifact_type") == "expert_annotation_gate_audit" | |
| and annotation.get("learned_residual_gate", {}).get("authorized") is True | |
| and annotation.get("event_gate", {}).get("authorized") is True | |
| and events_hash_matches | |
| ) | |
| assignments = pd.read_csv(args.assignments, dtype=str).fillna("") | |
| required = {"event_group_id", "split", "effective_date", "law_family"} | |
| missing = sorted(required - set(assignments.columns)) | |
| if missing: | |
| raise ValueError(f"split assignments are missing columns: {missing}") | |
| duplicate_groups = bool(assignments["event_group_id"].duplicated().any()) | |
| allowed_splits = bool(assignments["split"].isin({"train", "dev"}).all()) | |
| dates = pd.to_datetime( | |
| assignments["effective_date"], format="%Y-%m-%d", errors="coerce" | |
| ) | |
| valid_dates = bool(dates.notna().all()) | |
| nonempty_family = bool(assignments["law_family"].str.strip().ne("").all()) | |
| gold_event_groups: set[str] = set() | |
| if events_path.is_file(): | |
| event_frame = pd.read_csv(events_path, dtype=str).fillna("") | |
| if {"event_group_id", "verification_status"}.issubset(event_frame.columns): | |
| gold_event_groups = set( | |
| event_frame.loc[ | |
| event_frame["verification_status"].eq("adjudicated_gold"), | |
| "event_group_id", | |
| ] | |
| ) | |
| assigned_groups = set(assignments["event_group_id"]) | |
| exact_gold_partition = bool(gold_event_groups and assigned_groups == gold_event_groups) | |
| train = assignments.loc[assignments["split"].eq("train")].copy() | |
| dev = assignments.loc[assignments["split"].eq("dev")].copy() | |
| minimum_counts_pass = bool( | |
| train["event_group_id"].nunique() >= args.minimum_train_events | |
| and dev["event_group_id"].nunique() >= args.minimum_dev_events | |
| ) | |
| chronological = False | |
| if valid_dates and not train.empty and not dev.empty: | |
| train_dates = pd.to_datetime(train["effective_date"], format="%Y-%m-%d") | |
| dev_dates = pd.to_datetime(dev["effective_date"], format="%Y-%m-%d") | |
| chronological = bool(train_dates.max() < dev_dates.min()) | |
| train_families = set(train["law_family"]) | |
| dev_families = set(dev["law_family"]) | |
| family_overlap = sorted(train_families & dev_families) | |
| family_disjoint = not family_overlap | |
| authorized = bool( | |
| annotation_authorized | |
| and not duplicate_groups | |
| and allowed_splits | |
| and valid_dates | |
| and nonempty_family | |
| and exact_gold_partition | |
| and minimum_counts_pass | |
| and chronological | |
| and family_disjoint | |
| ) | |
| receipt: dict[str, object] = { | |
| "schema_version": 1, | |
| "artifact_type": "telen_l3_event_split_audit", | |
| "created_at": datetime.now(timezone.utc).isoformat(), | |
| "authorized": authorized, | |
| "inputs": { | |
| "annotation_audit": str(args.annotation_audit), | |
| "annotation_audit_sha256": sha256_file(args.annotation_audit), | |
| "annotation_events": str(events_path), | |
| "annotation_events_sha256": sha256_file(events_path) | |
| if events_path.is_file() | |
| else None, | |
| "assignments": str(args.assignments), | |
| "assignments_sha256": sha256_file(args.assignments), | |
| }, | |
| "configuration": { | |
| "minimum_train_events": args.minimum_train_events, | |
| "minimum_dev_events": args.minimum_dev_events, | |
| "final_test_included": False, | |
| }, | |
| "counts": { | |
| "gold_event_groups": len(gold_event_groups), | |
| "assigned_event_groups": len(assigned_groups), | |
| "train_event_groups": int(train["event_group_id"].nunique()), | |
| "development_event_groups": int(dev["event_group_id"].nunique()), | |
| }, | |
| "gates": { | |
| "expert_annotation_authorized": annotation_authorized, | |
| "unique_event_assignment": not duplicate_groups, | |
| "train_dev_only": allowed_splits, | |
| "valid_effective_dates": valid_dates, | |
| "nonempty_law_family": nonempty_family, | |
| "exact_gold_event_partition": exact_gold_partition, | |
| "minimum_event_counts": minimum_counts_pass, | |
| "strict_chronology": chronological, | |
| "law_family_disjoint": family_disjoint, | |
| "final_test_sealed": True, | |
| }, | |
| "law_family_overlap": family_overlap, | |
| "rule": ( | |
| "authorized expert events AND every gold event assigned exactly once " | |
| "to train/dev AND max(train effective_date) < min(dev effective_date) " | |
| "AND no law_family overlap AND minimum event counts pass" | |
| ), | |
| } | |
| args.output.parent.mkdir(parents=True, exist_ok=True) | |
| args.output.write_text( | |
| json.dumps(receipt, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" | |
| ) | |
| return receipt | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--annotation-audit", type=Path, required=True) | |
| parser.add_argument("--assignments", type=Path, required=True) | |
| parser.add_argument("--minimum-train-events", type=int, default=5) | |
| parser.add_argument("--minimum-dev-events", type=int, default=5) | |
| parser.add_argument("--output", type=Path, required=True) | |
| return parser.parse_args() | |
| def main() -> None: | |
| result = audit(parse_args()) | |
| print(json.dumps(result, ensure_ascii=False, indent=2)) | |
| if not result["authorized"]: | |
| raise SystemExit(2) | |
| if __name__ == "__main__": | |
| main() | |