"""Analyze local or Hugging Face privacy-safe trace JSONL files.""" from __future__ import annotations import argparse import json import sys from collections import Counter from pathlib import Path from tempfile import TemporaryDirectory from typing import Any ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from traces.runtime import DATASET_REPO, validate_trace def load_records(paths: list[Path]) -> tuple[list[dict[str, Any]], list[str]]: records: list[dict[str, Any]] = [] errors: list[str] = [] trace_ids: set[str] = set() for path in paths: for line_number, line in enumerate( path.read_text(encoding="utf-8").splitlines(), start=1, ): if not line.strip(): continue try: record = json.loads(line) except json.JSONDecodeError as exc: errors.append(f"{path}:{line_number}: invalid JSON: {exc}") continue if not isinstance(record, dict): errors.append(f"{path}:{line_number}: Trace must be an object.") continue trace_id = record.get("trace_id") if trace_id in trace_ids: errors.append(f"{path}:{line_number}: Duplicate trace ID: {trace_id}") elif isinstance(trace_id, str): trace_ids.add(trace_id) errors.extend( f"{path}:{line_number}: {error}" for error in validate_trace(record) ) records.append(record) return records, errors def summarize(records: list[dict[str, Any]], file_count: int) -> dict[str, Any]: inputs = Counter( record.get("input") for record in records if isinstance(record.get("input"), str) ) duplicate_rows = sum(count - 1 for count in inputs.values() if count > 1) return { "files": file_count, "rows": len(records), "unique_inputs": len(inputs), "duplicate_input_rows": duplicate_rows, "duplicate_input_rate": ( round(duplicate_rows / len(records), 4) if records else 0 ), "unclassified_images": sum( record.get("input") == "image: Unclassified content with no mapped signals" for record in records ), "unavailable_image_assessments": sum( record.get("input") == "image: Assessment unavailable" for record in records ), "incomplete_assessments": sum( record.get("risk_label") == "none" for record in records ), "risk_labels": dict(Counter( str(record.get("risk_label", "[missing]")) for record in records ).most_common()), "input_categories": dict(Counter( str(record.get("input_category", "[missing]")) for record in records ).most_common()), "input_types": dict(Counter( record["input"].split(":", 1)[0] for record in records if isinstance(record.get("input"), str) ).most_common()), "top_repeated_inputs": [ {"count": count, "input": input_value} for input_value, count in inputs.most_common(10) if count > 1 ], } def hub_paths(repo_id: str, directory: Path) -> list[Path]: try: from huggingface_hub import HfApi, hf_hub_download except ImportError as exc: raise RuntimeError( "Install huggingface_hub to analyze a Hub dataset." ) from exc api = HfApi() filenames = [ name for name in api.list_repo_files(repo_id, repo_type="dataset") if name.endswith(".jsonl") ] return [ Path(hf_hub_download( repo_id, filename, repo_type="dataset", local_dir=directory, )) for filename in filenames ] def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("paths", nargs="*", type=Path) parser.add_argument("--repo-id", default=DATASET_REPO) parser.add_argument("--json", action="store_true", dest="as_json") args = parser.parse_args() with TemporaryDirectory() as directory: paths = args.paths or hub_paths(args.repo_id, Path(directory)) records, errors = load_records(paths) report = summarize(records, len(paths)) report["validation_errors"] = len(errors) if args.as_json: print(json.dumps(report, ensure_ascii=False, indent=2)) else: print(f"Files: {report['files']}") print(f"Rows: {report['rows']}") print(f"Validation errors: {report['validation_errors']}") print(f"Unique inputs: {report['unique_inputs']}") print( "Repeated-input rows: " f"{report['duplicate_input_rows']} " f"({report['duplicate_input_rate']:.1%})" ) print(f"Unclassified images: {report['unclassified_images']}") print( "Unavailable image assessments: " f"{report['unavailable_image_assessments']}" ) print(f"Incomplete assessments: {report['incomplete_assessments']}") print(f"Risk labels: {report['risk_labels']}") print(f"Input categories: {report['input_categories']}") if errors: print("\n".join(errors), file=sys.stderr) return 1 if errors else 0 if __name__ == "__main__": raise SystemExit(main())