| """Dataset auditing and portable manifest construction.""" |
|
|
| from __future__ import annotations |
|
|
| from collections import Counter, defaultdict |
| from datetime import datetime, timezone |
| from pathlib import Path |
| from typing import Any, Iterable, Mapping |
|
|
| from .audio import AudioPayloadError, inspect_audio |
| from .grouping import GroupingConfig, attach_group_ids, derive_group_keys, group_sizes |
| from .manifest import MANIFEST_SCHEMA_VERSION |
| from .schema import ValidationIssue, normalize_record_with_issues |
|
|
|
|
| def _category(value: Any) -> str: |
| if value is None: |
| return "<null>" |
| if isinstance(value, bool): |
| return "true" if value else "false" |
| text = str(value).strip() |
| return text or "<empty>" |
|
|
|
|
| def _source_base_dir(source_file: str | None) -> Path | None: |
| if source_file is None or source_file.startswith("hf://"): |
| return None |
| path = Path(source_file) |
| return path.parent if path.suffix else path |
|
|
|
|
| def build_manifest_row( |
| raw_record: Mapping[str, Any], |
| *, |
| grouping_config: GroupingConfig | None = None, |
| ) -> dict[str, Any]: |
| """Validate, fingerprint and reduce a raw row to a lightweight manifest row.""" |
|
|
| result = normalize_record_with_issues(raw_record) |
| record = result.record |
| issues = list(result.issues) |
| audio_fields: dict[str, Any] = { |
| "audio_sha256": None, |
| "audio_num_bytes": None, |
| "audio_format": None, |
| "sample_rate": None, |
| "num_channels": None, |
| "num_frames": None, |
| "bits_per_sample": None, |
| "duration_seconds": None, |
| "audio_path": None, |
| } |
| if record.audio is not None: |
| try: |
| audio_fields.update( |
| inspect_audio(record.audio, base_dir=_source_base_dir(record.source_file)).to_dict() |
| ) |
| except AudioPayloadError as exc: |
| issues.append(ValidationIssue("error", "invalid_audio", str(exc), "audio")) |
|
|
| group_source = dict(raw_record) |
| group_source.setdefault("record_id", record.record_id) |
| group_source.setdefault("dataset", record.dataset) |
| group_source.setdefault("spoken_text", record.spoken_text) |
| group_source["audio_path"] = audio_fields["audio_path"] |
| group_keys = derive_group_keys( |
| group_source, |
| audio_sha256=audio_fields["audio_sha256"], |
| config=grouping_config, |
| ) |
|
|
| return { |
| "record_id": record.record_id, |
| "source_file": record.source_file, |
| "source_row": record.source_row, |
| "language": record.language, |
| "endpoint": record.endpoint, |
| "midfiller": record.midfiller, |
| "endfiller": record.endfiller, |
| "synthetic": record.synthetic, |
| "dataset": record.dataset, |
| "spoken_text": record.spoken_text, |
| **audio_fields, |
| "group_keys": group_keys, |
| "group_id": None, |
| "validation_errors": [item.to_dict() for item in issues if item.severity == "error"], |
| "validation_warnings": [item.to_dict() for item in issues if item.severity == "warning"], |
| } |
|
|
|
|
| def _append_duplicate_conflict_warnings(rows: list[dict[str, Any]]) -> None: |
| labels_by_hash: dict[str, set[bool]] = defaultdict(set) |
| indices_by_hash: dict[str, list[int]] = defaultdict(list) |
| for index, row in enumerate(rows): |
| digest = row.get("audio_sha256") |
| endpoint = row.get("endpoint") |
| if isinstance(digest, str): |
| indices_by_hash[digest].append(index) |
| if isinstance(endpoint, bool): |
| labels_by_hash[digest].add(endpoint) |
| for digest, labels in labels_by_hash.items(): |
| if len(labels) <= 1: |
| continue |
| issue = ValidationIssue( |
| "warning", |
| "conflicting_duplicate_label", |
| f"exact audio hash {digest[:12]}… has conflicting endpoint labels", |
| "endpoint", |
| ).to_dict() |
| for index in indices_by_hash[digest]: |
| rows[index]["validation_warnings"].append(issue) |
|
|
|
|
| def build_audit_report(rows: Iterable[Mapping[str, Any]]) -> dict[str, Any]: |
| """Aggregate manifest rows into a JSON-serializable data quality report.""" |
|
|
| materialized = list(rows) |
| distributions: dict[str, Counter[str]] = { |
| field: Counter(_category(row.get(field)) for row in materialized) |
| for field in ("endpoint", "language", "dataset", "synthetic", "midfiller", "endfiller", "audio_format") |
| } |
| errors = Counter( |
| str(issue.get("code", "unknown")) |
| for row in materialized |
| for issue in row.get("validation_errors", []) |
| if isinstance(issue, Mapping) |
| ) |
| warnings = Counter( |
| str(issue.get("code", "unknown")) |
| for row in materialized |
| for issue in row.get("validation_warnings", []) |
| if isinstance(issue, Mapping) |
| ) |
| invalid_rows = sum(bool(row.get("validation_errors")) for row in materialized) |
|
|
| audio_hash_counts = Counter( |
| str(row["audio_sha256"]) for row in materialized if row.get("audio_sha256") |
| ) |
| labels_by_hash: dict[str, set[Any]] = defaultdict(set) |
| for row in materialized: |
| if row.get("audio_sha256"): |
| labels_by_hash[str(row["audio_sha256"])].add(row.get("endpoint")) |
| group_counts = group_sizes(materialized) |
| duration_values = [ |
| float(row["duration_seconds"]) |
| for row in materialized |
| if isinstance(row.get("duration_seconds"), (int, float)) |
| ] |
| total_bytes = sum( |
| int(row["audio_num_bytes"]) |
| for row in materialized |
| if isinstance(row.get("audio_num_bytes"), int) |
| ) |
| duplicate_record_ids = Counter( |
| str(row["record_id"]) for row in materialized if row.get("record_id") |
| ) |
|
|
| return { |
| "manifest_schema_version": MANIFEST_SCHEMA_VERSION, |
| "generated_at": datetime.now(timezone.utc).isoformat(), |
| "records": { |
| "total": len(materialized), |
| "valid": len(materialized) - invalid_rows, |
| "invalid": invalid_rows, |
| }, |
| "distributions": { |
| field: dict(sorted(counts.items())) for field, counts in distributions.items() |
| }, |
| "audio": { |
| "total_encoded_bytes": total_bytes, |
| "duration_observed_records": len(duration_values), |
| "total_duration_seconds": sum(duration_values), |
| "minimum_duration_seconds": min(duration_values) if duration_values else None, |
| "maximum_duration_seconds": max(duration_values) if duration_values else None, |
| }, |
| "duplicates": { |
| "unique_audio_hashes": len(audio_hash_counts), |
| "duplicate_audio_hashes": sum(count > 1 for count in audio_hash_counts.values()), |
| "duplicate_audio_rows_beyond_first": sum(count - 1 for count in audio_hash_counts.values() if count > 1), |
| "conflicting_label_audio_hashes": sum( |
| len({value for value in labels if isinstance(value, bool)}) > 1 |
| for labels in labels_by_hash.values() |
| ), |
| "duplicate_record_ids": sum(count > 1 for count in duplicate_record_ids.values()), |
| }, |
| "groups": { |
| "total": len(group_counts), |
| "multirow": sum(count > 1 for count in group_counts.values()), |
| "largest_rows": max(group_counts.values(), default=0), |
| }, |
| "validation": { |
| "errors": dict(sorted(errors.items())), |
| "warnings": dict(sorted(warnings.items())), |
| }, |
| } |
|
|
|
|
| def audit_records( |
| records: Iterable[Mapping[str, Any]], |
| *, |
| grouping_config: GroupingConfig | None = None, |
| limit: int | None = None, |
| ) -> tuple[list[dict[str, Any]], dict[str, Any]]: |
| """Build a leakage-grouped manifest and aggregate report from raw rows.""" |
|
|
| if limit is not None and limit < 0: |
| raise ValueError("limit cannot be negative") |
| rows: list[dict[str, Any]] = [] |
| for index, raw_record in enumerate(records): |
| if limit is not None and index >= limit: |
| break |
| rows.append(build_manifest_row(raw_record, grouping_config=grouping_config)) |
| attach_group_ids(rows) |
| _append_duplicate_conflict_warnings(rows) |
| return rows, build_audit_report(rows) |
|
|