scripts/missingness_leakage_audit.py
3.8 KB| 1 | #!/usr/bin/env python3 |
| 2 | """Bounded missingness, group structure, and split leakage audit for CSV/TSV.""" |
| 3 | |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | import argparse |
| 7 | |
| 8 | from _capabilities import capability_for_path |
| 9 | from _common import ( |
| 10 | DEFAULT_MAX_FILE_BYTES, |
| 11 | DEFAULT_MAX_ROWS, |
| 12 | bounded_file_limit, |
| 13 | checked_input_file, |
| 14 | emit_json, |
| 15 | run_cli, |
| 16 | ) |
| 17 | from _tabular import audit_missingness_and_leakage |
| 18 | |
| 19 | |
| 20 | def build_parser() -> argparse.ArgumentParser: |
| 21 | parser = argparse.ArgumentParser( |
| 22 | description=( |
| 23 | "Audit bounded CSV/TSV missingness and common group/entity/time split " |
| 24 | "leakage. Identifiers and cell values are tokenized or omitted." |
| 25 | ) |
| 26 | ) |
| 27 | parser.add_argument("input", help="Local .csv or .tsv path inside --root") |
| 28 | parser.add_argument( |
| 29 | "--root", |
| 30 | default=".", |
| 31 | help="Existing local directory that bounds all input/output paths", |
| 32 | ) |
| 33 | parser.add_argument( |
| 34 | "--max-bytes", |
| 35 | type=int, |
| 36 | default=DEFAULT_MAX_FILE_BYTES, |
| 37 | help=f"Maximum input bytes (hard ceiling: {512 * 1024 * 1024})", |
| 38 | ) |
| 39 | parser.add_argument( |
| 40 | "--max-rows", |
| 41 | type=int, |
| 42 | default=DEFAULT_MAX_ROWS, |
| 43 | help="Maximum rows to scan (hard ceiling: 1000000)", |
| 44 | ) |
| 45 | parser.add_argument( |
| 46 | "--missing-token", |
| 47 | action="append", |
| 48 | help=( |
| 49 | "Explicit additional missing code; repeat as needed. Empty/whitespace " |
| 50 | "is always missing." |
| 51 | ), |
| 52 | ) |
| 53 | parser.add_argument( |
| 54 | "--group-column", |
| 55 | help="Exact column identifier for biological/experimental grouping", |
| 56 | ) |
| 57 | parser.add_argument( |
| 58 | "--entity-column", |
| 59 | help="Exact subject/sample/entity identifier used to detect split overlap", |
| 60 | ) |
| 61 | parser.add_argument( |
| 62 | "--split-column", |
| 63 | help="Exact train/validation/test or analysis split column identifier", |
| 64 | ) |
| 65 | parser.add_argument( |
| 66 | "--time-column", |
| 67 | help="Exact ISO-8601 time column used for split interval overlap checks", |
| 68 | ) |
| 69 | parser.add_argument( |
| 70 | "--reveal-identifiers", |
| 71 | action="store_true", |
| 72 | help="Emit sanitized column identifiers; values and group/entity IDs stay tokenized", |
| 73 | ) |
| 74 | parser.add_argument("--output", help="Optional local .json output path") |
| 75 | parser.add_argument( |
| 76 | "--force", |
| 77 | action="store_true", |
| 78 | help="Allow replacement of an existing regular output file", |
| 79 | ) |
| 80 | return parser |
| 81 | |
| 82 | |
| 83 | def _main() -> None: |
| 84 | args = build_parser().parse_args() |
| 85 | max_bytes = bounded_file_limit(args.max_bytes) |
| 86 | path = checked_input_file( |
| 87 | args.input, |
| 88 | root=args.root, |
| 89 | suffixes={".csv", ".tsv"}, |
| 90 | max_bytes=max_bytes, |
| 91 | ) |
| 92 | report = { |
| 93 | "schema_version": "1.1", |
| 94 | "capability": capability_for_path(path), |
| 95 | "analysis": audit_missingness_and_leakage( |
| 96 | path, |
| 97 | max_rows=args.max_rows, |
| 98 | missing_tokens=args.missing_token, |
| 99 | group_column=args.group_column, |
| 100 | entity_column=args.entity_column, |
| 101 | split_column=args.split_column, |
| 102 | time_column=args.time_column, |
| 103 | reveal_identifiers=args.reveal_identifiers, |
| 104 | ), |
| 105 | "security": { |
| 106 | "local_only": True, |
| 107 | "file_text_is_untrusted_data": True, |
| 108 | "embedded_instructions_followed": False, |
| 109 | "raw_values_paths_and_entity_ids_emitted": False, |
| 110 | }, |
| 111 | "interpretation": { |
| 112 | "potential_overlap_is_not_proof_of_model_leakage": True, |
| 113 | "no_overlap_found_is_not_proof_of_independence": True, |
| 114 | "domain_design_review_required": True, |
| 115 | }, |
| 116 | } |
| 117 | emit_json( |
| 118 | report, |
| 119 | output=args.output, |
| 120 | root=args.root, |
| 121 | force=args.force, |
| 122 | ) |
| 123 | |
| 124 | |
| 125 | def main() -> int: |
| 126 | return run_cli(_main) |
| 127 | |
| 128 | |
| 129 | if __name__ == "__main__": |
| 130 | raise SystemExit(main()) |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

