SensorflowStudio / tests /test_labeleval /test_e2e_acceptance.py
whassan's picture
Deploy Sensorflow Studio
729628a verified
Raw History Blame Contribute Delete
10.1 kB
"""End-to-end acceptance test through the UI-backed HTTP APIs (spec §49).
Dataset -> precheck -> ingest synthetic sensor data -> generate labels ->
queue -> anomaly detection -> rare events -> regression -> grader comparison ->
strict geometric validation -> precision/recall -> tracking -> consensus ->
triage -> auto-release valid -> flag invalid -> HITL tasks -> correct a label ->
re-validate -> verify -> training dataset -> train -> stream logs -> evaluate
new model -> regression/improvement -> complete flywheel.
"""
import time
import pytest
from fastapi.testclient import TestClient
from sensorflow.evaluation.pipeline import reset_pipeline
from sensorflow.evaluation.records import reset_store
@pytest.fixture(scope="module")
def client(tmp_path_factory):
store = reset_store(tmp_path_factory.mktemp("e2e"))
reset_pipeline(store)
from app_backend import app
with TestClient(app) as c:
yield c
def _wait_pipeline(client, timeout=90):
for _ in range(timeout * 4):
state = client.get("/api/labeleval/pipeline").json()
if not state["running"] and state["stage"] in ("complete", "failed", "idle"):
return state
time.sleep(0.25)
raise AssertionError("pipeline did not finish in time")
def test_full_flywheel_through_apis(client):
# 1. generate synthetic dataset (raw sensor data + reference GT)
ds = client.post("/api/labeleval/datasets/generate",
json={"name": "e2e", "num_sequences": 3, "frames_per_sequence": 20, "seed": 42}).json()
dataset_id = ds["dataset_id"]
assert ds["num_frames"] == 60
assert ds["gt_availability"]["gt_type"] == "VENDOR_GROUND_TRUTH"
# 2. precheck
pre = client.post("/api/dataset/precheck", json={"dataset_id": dataset_id}).json()
assert pre["status"] == "success", pre
assert all(c["passed"] for c in pre["checks"] if c["applicable"])
# 3. run the evaluation pipeline (queue -> workers -> triage)
run = client.post("/api/labeleval/run", json={"dataset_id": dataset_id}).json()
assert run["status"] == "started"
state = _wait_pipeline(client)
assert state["stage"] == "complete", state["stage"]
# queue drained and services healthy
q = client.get("/api/queue/status").json()
assert q["pending"] == 0 and q["completed"] > 0 and q["failed"] == 0
services = {s["service"]: s for s in state["services"]}
for name in ("label-generator", "anomaly-detector", "grader", "quality-validator",
"rare-event-detector", "triage-engine", "review-service", "metrics-service"):
assert services[name]["state"] in ("HEALTHY", "DEGRADED"), name
# 4. overview counters populated
ov = client.get("/api/labeleval/overview").json()
c = ov["counters"]
assert c["auto_labeled"] > 100
assert c["verified"] > 0 and c["flagged"] > 0
assert c["rare_events"] > 0
assert ov["process_units_total"] > 0
# 5. quality metrics: precision/recall only because reference GT exists
m = client.get("/api/quality/metrics", params={"dataset_id": dataset_id}).json()
assert m["gt_available"] and m["gt_type"] == "VENDOR_GROUND_TRUTH"
g = m["global"]
assert 0.5 < g["precision"] <= 1.0 and 0.5 < g["recall"] <= 1.0
assert g["safety_critical_recall"] is not None
assert g["grader_consensus"] is not None
assert len(m["per_class"]) >= 4 and len(m["per_scenario"]) >= 3
# 6. verified vs non-verified groups
groups = client.get("/api/quality/groups", params={"dataset_id": dataset_id}).json()
names = {x["name"]: x for x in groups["groups"]}
assert set(names) == {"verified", "non_verified", "hitl", "rejected"}
assert groups["total"] == c["auto_labeled"]
gd = client.get(f"/api/quality/groups/{dataset_id}:hitl").json()
assert gd["count"] == names["hitl"]["count"]
assert gd["failure_reason_counts"]
# 7. haystack renders all categories and points open evidence
hs = client.get("/api/labeleval/haystack", params={"dataset_id": dataset_id}).json()["points"]
cats = {p["category"] for p in hs}
assert {"normal", "anomaly", "rare_event", "false_negative"} <= cats
ann_point = next(p for p in hs if p["kind"] == "annotation")
ev = client.get(f"/api/labeleval/evaluations/{ann_point['id']}").json()
assert ev["anomaly"]["detector_scores"] and ev["decision"] is not None
# 8. rare events with evidence
events = client.get("/api/labeleval/rare-events").json()["events"]
assert events and all(e["evidence_frames"] for e in events)
# 9. regression endpoint has an entry for this run
reg = client.get("/api/regression").json()
assert reg["entries"]
# 10. HITL: find a position-error task, correct it with the reference box
tasks = client.get("/api/review/tasks").json()["tasks"]
assert tasks
target = None
for t in tasks:
if t["status"] != "open":
continue
full = client.get(f"/api/review/tasks/{t['task_id']}").json()
rec = full["evidence"]
if rec and "WRONG_POSITION" in rec["injected_errors"]:
target = full
break
assert target is not None, "expected an open WRONG_POSITION review task"
frame = client.get(f"/api/labeleval/frames/{target['frame_id']}").json()["frame"]
gt = next(g for g in frame["gt_boxes"] if g["gt_id"] == target["evidence"]["ground_truth_id"])
res = client.post(f"/api/review/tasks/{target['task_id']}",
json={"action": "correct",
"corrected_bbox_3d": gt["bbox_3d"],
"corrected_class": gt["class_name"]}).json()
assert res["task"]["status"] == "resolved"
assert res["task"]["resolution"]["revalidation_passed"] is True, res["message"]
assert res["task"]["resolution"]["final_status"] == "VERIFIED"
# Corrected label was re-validated against a human-verified reference.
reval = res["revalidation"]
assert reval["ground_truth_type"] == "HUMAN_VERIFIED_GROUND_TRUTH"
assert reval["geometry"]["iou_3d"] >= 0.99
assert reval["decision"]["status"] == "AUTO_GRADED"
# funnel now shows the HITL side branch flowing back to verified
funnel = client.get("/api/labeleval/funnel").json()
side = {s["stage"]: s["count"] for s in funnel["side_path"]}
assert side["RE-LABELING"] >= 1 and side["VERIFIED (post-HITL)"] >= 1
# 11. training on the verified pool; logs must stream (grow over time)
tr = client.post("/api/train", json={"dataset_version": dataset_id,
"training_parameters": {"epochs": 3}}).json()
job_id = tr["job_id"]
assert tr["model_version"].startswith("model-v")
seen_lens = []
status = None
for _ in range(120):
status = client.get(f"/api/train/jobs/{job_id}").json()
seen_lens.append(len(status["logs"]))
if status["status"] == "completed":
break
time.sleep(0.3)
assert status["status"] == "completed", status
assert status["epoch"] == 3
assert status["rare_recall"] > 0 and status["safety_recall"] > 0
assert status["process_units"] > 0
assert max(seen_lens) > min(seen_lens), "training logs should stream incrementally"
assert status["lineage"]["parent_dataset"] == dataset_id
assert status["lineage"]["validated_by_policy"]
# 12. new model evaluated and registered with regression status
models = client.get("/api/models").json()["models"]
trained = [mm for mm in models if mm["model_id"] == tr["model_id"]]
assert trained and trained[0]["metrics"]["f1"] is not None
assert trained[0]["regression_status"] in ("baseline", "improved", "regressed")
# 13. process units accounted at every stage with unit economics
pu = client.get("/api/labeleval/process-units").json()
for stage in ("ingestion", "label_generation", "anomaly_detection", "validation",
"grading", "rare_event_detection", "hitl", "training"):
assert pu["by_stage"].get(stage, 0) > 0, stage
assert pu["unit_economics"]["per_verified_event"] is not None
# 14. audit trail captured the flywheel
audit = client.get("/api/labeleval/audit").json()["events"]
actions = {e["action"] for e in audit}
assert {"dataset_generated", "labels_generated", "pipeline_completed",
"review_correct", "training_started", "training_completed"} <= actions
def test_benchmark_and_copilot_through_apis(client):
bench = client.post("/api/benchmark/techniques", json={}).json()
assert len(bench["rows"]) >= 10
techniques = {r["technique"] for r in bench["rows"]}
assert {"knn", "lof", "isolation_forest", "ocsvm", "dbscan",
"autoencoder", "vae", "gan", "few_shot"} <= techniques
assert {"best_rare_recall", "best_safety_recall", "lowest_fp_rate",
"lowest_process_units", "lowest_tracking_error"} <= set(bench["highlights"])
# Copilot on a flagged annotation: advisory, graceful without Ollama.
tasks = client.get("/api/review/tasks").json()["tasks"]
open_tasks = [t for t in tasks if t["status"] == "open"]
assert open_tasks
cp = client.post("/api/copilot/explain",
json={"context_type": "anomaly",
"annotation_id": open_tasks[0]["annotation_id"]}).json()
assert cp["status"] == "ok"
assert cp["analysis"]
assert cp["structured"]["hypothesis"].startswith("HYPOTHESIS")
assert cp["structured"]["observed_evidence"]
assert 0 <= cp["structured"]["confidence"] <= 1
def test_sse_stream_emits_state(client):
# Bounded tick count: TestClient buffers the response, so the stream must
# terminate; real clients consume the long-lived stream incrementally.
res = client.get("/api/events/stream", params={"ticks": 2})
assert res.headers["content-type"].startswith("text/event-stream")
lines = [l for l in res.text.splitlines() if l.startswith("data: ")]
assert len(lines) == 2
import json
payload = json.loads(lines[0][6:])
assert "pipeline" in payload and "training" in payload and "ts" in payload