KeepersEye-1 / training /tools /prepare_android_runtime_assets.py
AvalonskyAfar's picture
Upload 16 files
e58b840 verified
Raw
History Blame Contribute Delete
6.5 kB
#!/usr/bin/env python3
"""Stage the frozen Day4.3 runtime assets into the Android alpha project."""
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
from datetime import datetime, timezone
from pathlib import Path
EXPECTED = {
"models/text_classifier_int8.bin":
"5239754a0431241b805e805f3e1b603deb48380ebc1f0ea62a3f3a4c4d372cfd",
"config/rule_schema.json":
"2b5811a5cd8f95eb3283bed12f9cd56ed6fef47b20be787daa1567c80a4e2beb",
}
EXPECTED_CANDIDATE_ID = "day43-fusion-only-frozen-visual-v1"
EXPECTED_VISUAL_CHECKPOINT_SHA256 = (
"63780ae3f1716bc2f0d36995a3d780dd47a2a1f9ca98c7ec9be6c69c935afabd"
)
MAX_PARITY_ERROR = 1e-4
def 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 copy_verified(source: Path, target: Path, expected: str | None = None) -> dict:
actual = sha256(source)
if expected is not None and actual != expected:
raise RuntimeError(
f"hash mismatch for {source}: expected {expected}, got {actual}"
)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
if sha256(target) != actual:
raise RuntimeError(f"copy verification failed: {target}")
return {
"path": target.as_posix(),
"bytes": target.stat().st_size,
"sha256": actual,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--workspace",
type=Path,
default=Path(__file__).resolve().parents[2],
)
parser.add_argument("--visual-model", type=Path)
parser.add_argument("--visual-report", type=Path)
args = parser.parse_args()
workspace = args.workspace.resolve()
project = workspace / "training_workspace"
frozen = project / "mobile_deployment" / "candidate_day43_v1"
portable = project / "mobile_deployment" / "runtime_day43_v1" / "fusion"
assets = (
project
/ "android"
/ "Gravekeeper"
/ "app"
/ "src"
/ "main"
/ "assets"
)
sources = {
"models/text_classifier_int8.bin": frozen / "text" / "text_classifier_int8.bin",
"models/text_classifier_int8.json": frozen / "text" / "text_classifier_int8.json",
"models/fusion_model.json": portable / "fusion_model.json",
"models/fusion_test_vectors.json": portable / "fusion_test_vectors.json",
"config/fusion_schema.json": portable / "fusion_schema.json",
"config/rule_schema.json": frozen / "rules" / "rule_schema.json",
"config/rule_test_vectors.json": frozen / "rules" / "rule_test_vectors.json",
}
records = []
for relative, source in sources.items():
if "locked" in source.as_posix().lower():
raise RuntimeError("locked-test paths are forbidden")
records.append(copy_verified(
source,
assets / relative,
EXPECTED.get(relative),
))
visual = None
if args.visual_model:
model_path = args.visual_model.resolve()
if model_path.suffix.lower() != ".tflite":
raise RuntimeError("visual model must be a .tflite file")
if "locked" in model_path.as_posix().lower():
raise RuntimeError("locked-test paths are forbidden")
if not args.visual_report:
raise RuntimeError("--visual-report is required with --visual-model")
report_path = args.visual_report.resolve()
report = json.loads(report_path.read_text(encoding="utf-8"))
if report.get("status") != "PASS":
raise RuntimeError("visual export report is not PASS")
if report.get("candidate_id") != EXPECTED_CANDIDATE_ID:
raise RuntimeError("visual export report has the wrong candidate_id")
source = report.get("source", {})
if source.get("checkpoint_sha256") != EXPECTED_VISUAL_CHECKPOINT_SHA256:
raise RuntimeError("visual export report has the wrong checkpoint hash")
parity = report.get("parity", {})
if parity.get("status") != "PASS":
raise RuntimeError("visual export parity did not pass")
maximum_error = parity.get("maximum_absolute_error")
if not isinstance(maximum_error, (int, float)) or maximum_error > MAX_PARITY_ERROR:
raise RuntimeError("visual export parity exceeds the 1e-4 tolerance")
if report.get("locked_test_read_or_used") is not False:
raise RuntimeError("visual report does not attest locked-test non-use")
artifact = report.get("artifact", {})
if artifact.get("sha256") != sha256(model_path):
raise RuntimeError("visual model hash does not match the export report")
if artifact.get("bytes") != model_path.stat().st_size:
raise RuntimeError("visual model size does not match the export report")
destination = assets / "models" / "gravekeeper_visual.tflite"
visual = copy_verified(model_path, destination)
records.append(visual)
records.append(copy_verified(
report_path,
assets / "models" / "visual_export_report.json",
))
manifest = {
"format": "health_marketing_android_runtime_assets",
"version": 1,
"generated_at": datetime.now(timezone.utc).isoformat(),
"candidate_id": "day43-fusion-only-frozen-visual-v1",
"fusion_threshold": 0.48764924527277753,
"visual_model_present": visual is not None,
"human_labels_frozen": True,
"semantic_relabeling_performed": False,
"locked_test_read_or_used": False,
"quality_status": "QUALITY_GATE_FAILED_PENDING_FRESH_BLIND_ACCEPTANCE",
"day5_allowed": False,
"files": records,
}
manifest_path = assets / "config" / "runtime_asset_manifest.json"
manifest_path.parent.mkdir(parents=True, exist_ok=True)
manifest_path.write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(json.dumps({
"status": "STAGED",
"assets": str(assets),
"visual_model_present": visual is not None,
"file_count": len(records) + 1,
"manifest_sha256": sha256(manifest_path),
}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())