Harden demo golden QA smoke
This commit is contained in:
@@ -24,6 +24,7 @@ class DemoWorkflowService:
|
||||
AREA_NAME = "Demo AOI - Geel buildings"
|
||||
REFERENCE_FILENAME = "demo_reference_buildings.geojson"
|
||||
CANDIDATE_FILENAME = "demo_predicted_buildings.geojson"
|
||||
EXPECTED_METRICS_FILENAME = "expected_qa_metrics.json"
|
||||
|
||||
@staticmethod
|
||||
def _repo_root() -> Path:
|
||||
@@ -49,6 +50,28 @@ class DemoWorkflowService:
|
||||
raw = path.read_bytes()
|
||||
return json.loads(raw.decode("utf-8")), raw
|
||||
|
||||
@staticmethod
|
||||
def _load_expected_metrics() -> dict:
|
||||
payload, _raw = DemoWorkflowService._load_fixture(DemoWorkflowService.EXPECTED_METRICS_FILENAME)
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
def _demo_area_geometry() -> dict:
|
||||
return {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
[4.9895, 51.1595],
|
||||
[4.9930, 51.1595],
|
||||
[4.9930, 51.1615],
|
||||
[4.9895, 51.1615],
|
||||
[4.9895, 51.1595],
|
||||
]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _find_existing_project(db: Session) -> Project | None:
|
||||
projects = (
|
||||
@@ -90,20 +113,7 @@ class DemoWorkflowService:
|
||||
|
||||
@staticmethod
|
||||
def _create_area(db: Session, project_id: UUID) -> Area:
|
||||
geometry = {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
[4.30, 51.18],
|
||||
[4.45, 51.18],
|
||||
[4.45, 51.33],
|
||||
[4.30, 51.33],
|
||||
[4.30, 51.18],
|
||||
]
|
||||
]
|
||||
],
|
||||
}
|
||||
geometry = DemoWorkflowService._demo_area_geometry()
|
||||
multipolygon = normalize_to_multipolygon(geometry)
|
||||
area = Area(
|
||||
id=uuid4(),
|
||||
@@ -119,6 +129,19 @@ class DemoWorkflowService:
|
||||
db.refresh(area)
|
||||
return area
|
||||
|
||||
@staticmethod
|
||||
def _sync_demo_area(db: Session, area: Area) -> Area:
|
||||
multipolygon = normalize_to_multipolygon(DemoWorkflowService._demo_area_geometry())
|
||||
area.name = DemoWorkflowService.AREA_NAME
|
||||
area.geometry = from_shape(multipolygon, srid=4326)
|
||||
area.original_crs = "EPSG:4326"
|
||||
area.area_m2 = area_m2(multipolygon)
|
||||
area.bbox = from_shape(geometry_bbox_polygon(multipolygon), srid=4326)
|
||||
db.add(area)
|
||||
db.commit()
|
||||
db.refresh(area)
|
||||
return area
|
||||
|
||||
@staticmethod
|
||||
def _create_dataset(
|
||||
db: Session,
|
||||
@@ -232,6 +255,38 @@ class DemoWorkflowService:
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _quality_check_matches_expected(db: Session, quality_check: QualityCheck | None) -> bool:
|
||||
if not quality_check or quality_check.status != "ok":
|
||||
return False
|
||||
expected = DemoWorkflowService._load_expected_metrics()
|
||||
tolerance = float(expected.get("tolerance", 1e-9))
|
||||
if quality_check.score is None or abs(float(quality_check.score) - float(expected["f1"])) > tolerance:
|
||||
return False
|
||||
findings = quality_check.findings_json or {}
|
||||
if int(findings.get("matches", -1)) != int(expected["matches"]):
|
||||
return False
|
||||
if int(findings.get("false_positives", -1)) != int(expected["false_positive_count"]):
|
||||
return False
|
||||
if int(findings.get("false_negatives", -1)) != int(expected["false_negative_count"]):
|
||||
return False
|
||||
|
||||
metrics = db.query(Metric).filter(Metric.quality_check_id == quality_check.id).all()
|
||||
metric_values = {metric.metric_key: metric.metric_value for metric in metrics}
|
||||
required = {
|
||||
"precision": expected["precision"],
|
||||
"recall": expected["recall"],
|
||||
"f1": expected["f1"],
|
||||
"mean_iou": expected["mean_iou"],
|
||||
"false_positive_count": expected["false_positive_count"],
|
||||
"false_negative_count": expected["false_negative_count"],
|
||||
}
|
||||
for key, expected_value in required.items():
|
||||
actual = metric_values.get(key)
|
||||
if actual is None or abs(float(actual) - float(expected_value)) > tolerance:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def seed(db: Session) -> DemoWorkflowResponse:
|
||||
existing = DemoWorkflowService._find_existing_project(db)
|
||||
@@ -261,6 +316,15 @@ class DemoWorkflowService:
|
||||
.first()
|
||||
)
|
||||
if area and reference and candidate and quality_check:
|
||||
if not DemoWorkflowService._quality_check_matches_expected(db, quality_check):
|
||||
area = DemoWorkflowService._sync_demo_area(db, area)
|
||||
quality_check = DemoWorkflowService._persist_qa(
|
||||
db=db,
|
||||
project_id=existing.id,
|
||||
candidate_dataset_id=candidate.id,
|
||||
reference_dataset_id=reference.id,
|
||||
area_id=area.id,
|
||||
)
|
||||
return DemoWorkflowResponse(
|
||||
project_id=existing.id,
|
||||
area_id=area.id,
|
||||
|
||||
@@ -58,6 +58,9 @@ def test_demo_export_workflow_script_verifies_export_endpoints() -> None:
|
||||
assert "GeoJSON Polygon/MultiPolygon geometry" in content
|
||||
assert "precision" in content
|
||||
assert "false_negative_count" in content
|
||||
assert "fixtures/golden/expected_qa_metrics.json" in content
|
||||
assert "QA/QC metric {key} drifted" in content
|
||||
assert "Seeded QA/QC match count does not match golden baseline" in content
|
||||
assert "/api/v1/exports/metadata" in content
|
||||
assert "/api/v1/exports/report" in content
|
||||
assert "/api/v1/exports/geojson" in content
|
||||
|
||||
@@ -55,6 +55,9 @@ def test_demo_workflow_service_uses_explicit_golden_fixtures() -> None:
|
||||
assert DemoWorkflowService.PROJECT_NAME == "GeoIntel Demo - Building QA"
|
||||
assert DemoWorkflowService.REFERENCE_FILENAME == "demo_reference_buildings.geojson"
|
||||
assert DemoWorkflowService.CANDIDATE_FILENAME == "demo_predicted_buildings.geojson"
|
||||
assert DemoWorkflowService.EXPECTED_METRICS_FILENAME == "expected_qa_metrics.json"
|
||||
assert DemoWorkflowService._demo_area_geometry()["coordinates"][0][0][0][0] < 4.99
|
||||
assert DemoWorkflowService._demo_area_geometry()["coordinates"][0][0][2][0] > 4.992
|
||||
|
||||
|
||||
def test_demo_workflow_service_supports_container_fixture_mount() -> None:
|
||||
@@ -66,6 +69,8 @@ def test_demo_workflow_service_supports_container_fixture_mount() -> None:
|
||||
assert "project = existing" in service
|
||||
assert "if not reference:" in service
|
||||
assert "if not candidate:" in service
|
||||
assert "_sync_demo_area" in service
|
||||
assert "_quality_check_matches_expected" in service
|
||||
|
||||
|
||||
def test_demo_workflow_prefers_complete_existing_demo_project() -> None:
|
||||
|
||||
@@ -16,6 +16,9 @@ def test_demo_workflow_browser_smoke_script_checks_connected_v1_state() -> None:
|
||||
assert "/api/v1/projects/${project_id}/datasets/${candidate_dataset_id}/vector/summary" in script
|
||||
assert "GeoJSON Polygon/MultiPolygon geometry" in script
|
||||
assert "Candidate vector summary does not report persisted features" in script
|
||||
assert "fixtures/golden/expected_qa_metrics.json" in script
|
||||
assert "Seeded QA/QC score does not match the golden F1 baseline" in script
|
||||
assert "QA/QC metric {key} drifted" in script
|
||||
|
||||
|
||||
def test_frontend_demo_action_loads_candidate_dataset_details_for_map_layer() -> None:
|
||||
|
||||
Reference in New Issue
Block a user