feat(demo): add export and silhouette visualization outputs

Add preprocess-only silhouette export and configurable result exporters so demo runs can be persisted for offline analysis and reproducible evaluation. Include optional parquet support and CLI visualization dumps while updating tests and tracking notes for the verified pipeline/debug workflow.
This commit is contained in:
2026-02-27 17:16:20 +08:00
parent 3496a1beb7
commit f501119d43
10 changed files with 1101 additions and 217 deletions
+14 -15
View File
@@ -91,7 +91,7 @@ def _start_nats_container(port: int) -> bool:
"--name",
CONTAINER_NAME,
"-p",
f"{port}:{port}",
f"{port}:4222",
"nats:latest",
],
capture_output=True,
@@ -152,7 +152,7 @@ def _validate_result_schema(data: dict[str, object]) -> tuple[bool, str]:
"track_id": int,
"label": str (one of: "negative", "neutral", "positive"),
"confidence": float in [0, 1],
"window": list[int] (start, end),
"window": int (non-negative),
"timestamp_ns": int
}
"""
@@ -190,14 +190,13 @@ def _validate_result_schema(data: dict[str, object]) -> tuple[bool, str]:
return False, f"confidence must be numeric, got {type(confidence)}"
if not 0.0 <= float(confidence) <= 1.0:
return False, f"confidence must be in [0, 1], got {confidence}"
# Validate window (list of 2 ints)
# Validate window (int, non-negative)
window = data["window"]
if not isinstance(window, list) or len(cast(list[object], window)) != 2:
return False, f"window must be list of 2 ints, got {window}"
window_list = cast(list[object], window)
if not all(isinstance(x, int) for x in window_list):
return False, f"window elements must be ints, got {window}"
if not isinstance(window, int):
return False, f"window must be int, got {type(window)}"
if window < 0:
return False, f"window must be non-negative, got {window}"
# Validate timestamp_ns (int)
timestamp_ns = data["timestamp_ns"]
@@ -356,7 +355,7 @@ class TestNatsPublisherIntegration:
"track_id": 1,
"label": "positive",
"confidence": 0.85,
"window": [0, 30],
"window": 30,
"timestamp_ns": 1234567890,
}
@@ -431,7 +430,7 @@ class TestNatsSchemaValidation:
"track_id": 42,
"label": "positive",
"confidence": 0.85,
"window": [1200, 1230],
"window": 1230,
"timestamp_ns": 1234567890000,
}
@@ -445,7 +444,7 @@ class TestNatsSchemaValidation:
"track_id": 42,
"label": "invalid_label",
"confidence": 0.85,
"window": [1200, 1230],
"window": 1230,
"timestamp_ns": 1234567890000,
}
@@ -460,7 +459,7 @@ class TestNatsSchemaValidation:
"track_id": 42,
"label": "positive",
"confidence": 1.5,
"window": [1200, 1230],
"window": 1230,
"timestamp_ns": 1234567890000,
}
@@ -486,7 +485,7 @@ class TestNatsSchemaValidation:
"track_id": 42,
"label": "positive",
"confidence": 0.85,
"window": [1200, 1230],
"window": 1230,
"timestamp_ns": 1234567890000,
}
@@ -502,7 +501,7 @@ class TestNatsSchemaValidation:
"track_id": 1,
"label": label_str,
"confidence": 0.5,
"window": [70, 100],
"window": 100,
"timestamp_ns": 1234567890,
}
is_valid, error = _validate_result_schema(data)