Files
NODEDC_MISSION_CORE/simulation/ai-polygon/navigation/replay.py
T

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()