Support browser calibration summaries in evidence export
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-08 13:41:39 +02:00
parent 4c38b7712a
commit 673f6c6160
6 changed files with 141 additions and 4 deletions
+6
View File
@@ -442,6 +442,12 @@ CALIBRATION_SUMMARY_PATH=/mnt/user/appdata/geointel/artifacts/detection-calibrat
bash scripts/export_detection_calibration_evidence.sh http://192.168.10.150:1202
```
Browser Detection Lab calibration summary exports are supported too:
```bash
bash scripts/export_detection_calibration_evidence.sh http://192.168.10.150:1202 ./detection-calibration-summary.json
```
The evidence export reads each persisted `quality_check_id`, calls the existing
QA evidence GeoJSON endpoint, writes `calibration_evidence.geojson`,
`calibration_evidence_summary.json` and a standalone
@@ -10,8 +10,12 @@ Usage:
or:
bash scripts/export_detection_calibration_evidence.sh [base_url] /path/to/calibration_summary.json
or with the browser Detection Lab export:
bash scripts/export_detection_calibration_evidence.sh [base_url] /path/to/detection-calibration-summary.json
Required input:
CALIBRATION_SUMMARY_PATH calibration_summary.json produced by run_detection_calibration_sweep.sh.
CALIBRATION_SUMMARY_PATH calibration_summary.json produced by run_detection_calibration_sweep.sh
or detection-calibration-summary.json from the Detection Lab.
Optional environment:
CALIBRATION_EVIDENCE_MODE all or best, default: all.
@@ -86,9 +90,42 @@ summary_path, manifest_path, mode = sys.argv[1:4]
with open(summary_path, "r", encoding="utf-8") as handle:
summary = json.load(handle)
items = summary.get("items") or []
def normalize_calibration_items(summary):
root_project_id = summary.get("project_id")
if summary.get("export_type") == "detection_calibration_summary":
rows = summary.get("rows") or []
quality_check_ids = summary.get("quality_check_ids") or []
items = []
for row in rows:
if not isinstance(row, dict):
continue
quality_check_id = row.get("quality_check_id")
if not quality_check_id:
continue
items.append(
{
"project_id": row.get("project_id") or root_project_id,
"analysis_run_id": row.get("analysis_run_id"),
"job_id": row.get("job_id"),
"quality_check_id": quality_check_id,
"threshold": row.get("threshold"),
"detection_count": row.get("detection_count"),
"quality_score": row.get("quality_score") or row.get("f1_score"),
"precision": row.get("precision"),
"recall": row.get("recall"),
"f1_score": row.get("f1_score"),
}
)
if quality_check_ids and not items:
raise SystemExit("detection-calibration-summary.json has quality_check_ids but no export rows")
return items
return summary.get("items") or []
items = normalize_calibration_items(summary)
if mode == "best":
best = summary.get("best_by_score")
if best is None and items:
best = max(items, key=lambda item: item.get("quality_score") if isinstance(item.get("quality_score"), (int, float)) else float("-inf"))
if not isinstance(best, dict):
raise SystemExit("calibration_summary.json has no best_by_score object")
items = [best]
@@ -133,9 +170,46 @@ summary_path, output_dir, mode = sys.argv[1:4]
with open(summary_path, "r", encoding="utf-8") as handle:
calibration_summary = json.load(handle)
selected_items = calibration_summary.get("items") or []
def normalize_calibration_items(summary):
root_project_id = summary.get("project_id")
if summary.get("export_type") == "detection_calibration_summary":
rows = summary.get("rows") or []
quality_check_ids = summary.get("quality_check_ids") or []
items = []
for row in rows:
if not isinstance(row, dict):
continue
quality_check_id = row.get("quality_check_id")
if not quality_check_id:
continue
items.append(
{
"project_id": row.get("project_id") or root_project_id,
"analysis_run_id": row.get("analysis_run_id"),
"job_id": row.get("job_id"),
"quality_check_id": quality_check_id,
"threshold": row.get("threshold"),
"detection_count": row.get("detection_count"),
"quality_score": row.get("quality_score") or row.get("f1_score"),
"precision": row.get("precision"),
"recall": row.get("recall"),
"f1_score": row.get("f1_score"),
}
)
if quality_check_ids and not items:
raise SystemExit("detection-calibration-summary.json has quality_check_ids but no export rows")
return items
return summary.get("items") or []
selected_items = normalize_calibration_items(calibration_summary)
if mode == "best":
selected_items = [calibration_summary["best_by_score"]]
best = calibration_summary.get("best_by_score")
if best is None and selected_items:
best = max(
selected_items,
key=lambda item: item.get("quality_score") if isinstance(item.get("quality_score"), (int, float)) else float("-inf"),
)
selected_items = [best]
items_by_quality_check = {str(item["quality_check_id"]): item for item in selected_items}
combined_features = []