122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
"""Replay retained sensor evidence against the shipped CMU adapter on Worker.
|
|
|
|
Owns exactly one bounded CPU container. Never starts rendering or model GPU jobs.
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import http.client
|
|
import json
|
|
import subprocess
|
|
import time
|
|
from collections import Counter
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import numpy as np
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("--adapter", type=Path, required=True)
|
|
p.add_argument("--run", type=Path, required=True)
|
|
p.add_argument("--output", type=Path, required=True)
|
|
p.add_argument("--limit", type=int, default=180)
|
|
p.add_argument("--terrain", action="store_true")
|
|
args = p.parse_args()
|
|
profile = json.loads((args.adapter / "models.worker-006.json").read_text())
|
|
identity = subprocess.check_output(
|
|
[
|
|
"docker",
|
|
"run",
|
|
"-d",
|
|
"--name",
|
|
"ndc-ai-polygon-navigation-replay-" + uuid4().hex[:8],
|
|
"--cpus",
|
|
"3",
|
|
"--memory",
|
|
"2g",
|
|
"--label",
|
|
"com.nodedc.product=mission-core",
|
|
"--label",
|
|
"com.nodedc.stack=ai-polygon-qualification",
|
|
"--label",
|
|
"com.nodedc.role=qualification",
|
|
"--label",
|
|
"com.nodedc.managed-by=ai-polygon-qualification",
|
|
"-p",
|
|
"127.0.0.1:18193:8010",
|
|
"--mount",
|
|
f"type=bind,source={args.adapter},target=/adapter,readonly",
|
|
profile["navigation"]["image"],
|
|
],
|
|
text=True,
|
|
).strip()
|
|
connection = http.client.HTTPConnection("127.0.0.1", 18193, timeout=3)
|
|
source = args.run / "camera/decisions.jsonl"
|
|
rows = [json.loads(line) for line in source.read_text().splitlines()]
|
|
report = dict(
|
|
utc=datetime.now(UTC).isoformat(),
|
|
monotonic=time.monotonic(),
|
|
input_sha256=hashlib.sha256(source.read_bytes()).hexdigest(),
|
|
samples=[],
|
|
)
|
|
try:
|
|
deadline = time.monotonic() + 20
|
|
while True:
|
|
try:
|
|
connection.request("GET", "/ready")
|
|
response = connection.getresponse()
|
|
response.read()
|
|
if response.status == 200:
|
|
break
|
|
except (OSError, http.client.HTTPException):
|
|
connection.close()
|
|
if time.monotonic() > deadline:
|
|
raise TimeoutError("Replay navigation unavailable")
|
|
time.sleep(0.2)
|
|
for row in rows[: args.limit]:
|
|
if row["goal"] is None:
|
|
continue
|
|
observation = np.load(args.run / "camera" / f"{row['frame_id']:08d}.range.npz")
|
|
payload = dict(
|
|
points=observation["points"].tolist(),
|
|
pose=observation["pose"].tolist(),
|
|
goal=row["goal"],
|
|
max_speed_mps=0.15,
|
|
allow_reverse=row.get("mission", {}).get("state") == "reversing",
|
|
include_terrain=args.terrain,
|
|
)
|
|
connection.request("POST", "/plan", body=json.dumps(payload))
|
|
response = connection.getresponse()
|
|
result = json.loads(response.read())
|
|
if response.status != 200:
|
|
raise RuntimeError(result)
|
|
report["samples"].append(
|
|
dict(
|
|
frame=row["frame_id"],
|
|
pose=payload["pose"],
|
|
original=row["decision"],
|
|
result=result,
|
|
)
|
|
)
|
|
time.sleep(0.18)
|
|
finally:
|
|
connection.close()
|
|
subprocess.run(["docker", "rm", "-f", identity], check=True, capture_output=True)
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(report, indent=2), encoding="utf-8")
|
|
failures = Counter(
|
|
v["result"].get("diagnostic", {}).get("failure", v["result"]["status"])
|
|
for v in report["samples"]
|
|
)
|
|
blocked = [v for v in report["samples"] if v["result"]["status"] == "blocked"]
|
|
print(
|
|
json.dumps(dict(failures=failures, first_blocked=blocked[:1], last=report["samples"][-1:]))
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|