feat(provenance): govern source snapshots and data inputs
This commit is contained in:
@@ -9,6 +9,7 @@ new provider data.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -16,6 +17,19 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
if str(SCRIPT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
|
||||
from training_dataset_eligibility import ( # noqa: E402
|
||||
TrainingEligibilityError,
|
||||
assert_frozen_manifest_training_eligible,
|
||||
)
|
||||
from training_release_manifest import ( # noqa: E402
|
||||
TrainingReleaseError,
|
||||
create_training_release_manifest,
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_MANIFEST_PATH = Path("/app/storage/operator-data/operator_samples_manifest.json")
|
||||
DEFAULT_OUTPUT_DIR = Path("/app/storage/operator-data/yolo-building-dataset")
|
||||
@@ -24,6 +38,14 @@ Transformer: Any = None
|
||||
Image: Any = None
|
||||
|
||||
|
||||
def file_sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Export operator real-data samples to a YOLO detection dataset.",
|
||||
@@ -45,6 +67,12 @@ def parse_args() -> argparse.Namespace:
|
||||
default=os.environ.get("OPERATOR_YOLO_VAL_SAMPLES", "turnhout"),
|
||||
help="Comma/space separated sample slugs assigned to validation. Defaults to turnhout.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--review-audit",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Passed corpus audit with accepted human-review evidence for this frozen source manifest.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force",
|
||||
action="store_true",
|
||||
@@ -218,12 +246,16 @@ def ensure_yolo_directories(output_dir: Path) -> None:
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
manifest = json.loads(args.manifest_path.read_text(encoding="utf-8-sig"))
|
||||
try:
|
||||
assert_frozen_manifest_training_eligible(args.manifest_path, verify_live=True)
|
||||
except TrainingEligibilityError as exc:
|
||||
raise SystemExit(str(exc)) from exc
|
||||
ensure_dependencies()
|
||||
if args.force and args.output_dir.exists():
|
||||
shutil.rmtree(args.output_dir)
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
ensure_yolo_directories(args.output_dir)
|
||||
manifest = json.loads(args.manifest_path.read_text(encoding="utf-8-sig"))
|
||||
samples = manifest.get("samples") or []
|
||||
if not samples:
|
||||
raise SystemExit("Operator sample manifest contains no samples")
|
||||
@@ -234,12 +266,26 @@ def main() -> int:
|
||||
if not any(item["split"] == "val" for item in exported):
|
||||
raise SystemExit("YOLO dataset export produced no validation samples")
|
||||
dataset_yaml = write_dataset_yaml(args.output_dir)
|
||||
try:
|
||||
release_paths = create_training_release_manifest(
|
||||
train_yaml=dataset_yaml,
|
||||
corpus_manifest=args.manifest_path,
|
||||
review_audit_path=args.review_audit,
|
||||
)
|
||||
except TrainingReleaseError as exc:
|
||||
raise SystemExit(str(exc)) from exc
|
||||
summary = {
|
||||
"status": "ok",
|
||||
"dataset_yaml": str(dataset_yaml),
|
||||
"training_release_manifest": str(release_paths["release_manifest"]),
|
||||
"training_release_manifest_sha256": file_sha256(release_paths["release_manifest"]),
|
||||
"training_release_freeze": str(release_paths["release_freeze"]),
|
||||
"training_asset_manifest": str(release_paths["asset_manifest"]),
|
||||
"output_dir": str(args.output_dir),
|
||||
"class_names": ["building"],
|
||||
"sample_count": len(exported),
|
||||
"source_manifest": str(args.manifest_path),
|
||||
"source_manifest_sha256": file_sha256(args.manifest_path),
|
||||
"train_sample_count": sum(1 for item in exported if item["split"] == "train"),
|
||||
"val_sample_count": sum(1 for item in exported if item["split"] == "val"),
|
||||
"label_count": sum(item["label_count"] for item in exported),
|
||||
|
||||
Reference in New Issue
Block a user