Add split promotion preflight
This commit is contained in:
@@ -718,6 +718,18 @@ generated `background_corpus_split_summary.json` into the split-aware promotion
|
||||
report. It still uses only existing upload, detection and report paths; it does
|
||||
not fetch providers, fetch model weights or activate a default.
|
||||
|
||||
Run the same command with `--preflight-only` first when checking a fresh
|
||||
redeploy. Preflight validates the positive portfolio path, operator manifest
|
||||
presence, required `pure_empty_negative` and `sparse_building_context`
|
||||
background categories, Python/curl availability and the frontend API proxy
|
||||
envelope without starting inference:
|
||||
|
||||
```bash
|
||||
PROMOTION_POSITIVE_PORTFOLIO_PATH=/mnt/user/appdata/geointel/artifacts/detection-quality-matrix/multi-sample/aoi1024bg512r3e50-positive/multi_sample_quality_summary.json \
|
||||
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
|
||||
bash scripts/run_split_background_promotion_workflow.sh --preflight-only http://192.168.10.150:1202
|
||||
```
|
||||
|
||||
If a legacy positive evidence portfolio records `model_asset_id` at portfolio
|
||||
level but does not include per-run tile size/overlap, pass explicit tile
|
||||
defaults instead of letting the report guess:
|
||||
|
||||
@@ -10,7 +10,7 @@ Usage:
|
||||
QUALITY_TILE_SIZES="512" \
|
||||
QUALITY_TILE_OVERLAPS="64" \
|
||||
QUALITY_THRESHOLDS="0.35 0.15" \
|
||||
bash scripts/run_split_background_promotion_workflow.sh [base_url]
|
||||
bash scripts/run_split_background_promotion_workflow.sh [--preflight-only] [base_url]
|
||||
|
||||
Optional environment:
|
||||
BACKGROUND_SPLIT_OUTPUT_DIR Split matrix output directory.
|
||||
@@ -22,6 +22,7 @@ Optional environment:
|
||||
PROMOTION_MAX_BACKGROUND_DETECTIONS_PER_SAMPLE Default: 0.
|
||||
PROMOTION_DEFAULT_POSITIVE_TILE_SIZE Optional fallback for legacy positive portfolios.
|
||||
PROMOTION_DEFAULT_POSITIVE_TILE_OVERLAP Optional fallback for legacy positive portfolios.
|
||||
GE_INTEL_BASE_URL Default base URL when no argument is supplied.
|
||||
|
||||
Runs the split background matrix, then builds a split-aware promotion report.
|
||||
The pure-empty background summary is the strict default gate. Sparse-context
|
||||
@@ -35,11 +36,24 @@ EOF
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
BASE_URL="${1:-${GE_INTEL_BASE_URL:-http://localhost:1202}}"
|
||||
if [ "${BASE_URL}" = "-h" ] || [ "${BASE_URL}" = "--help" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
BASE_URL="${GE_INTEL_BASE_URL:-http://localhost:1202}"
|
||||
PREFLIGHT_ONLY=0
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--preflight-only)
|
||||
PREFLIGHT_ONLY=1
|
||||
;;
|
||||
*)
|
||||
BASE_URL="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "${PROMOTION_POSITIVE_PORTFOLIO_PATH:-}" ]; then
|
||||
echo "PROMOTION_POSITIVE_PORTFOLIO_PATH is required" >&2
|
||||
@@ -52,6 +66,17 @@ if [ ! -s "${PROMOTION_POSITIVE_PORTFOLIO_PATH}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${OPERATOR_SAMPLE_MANIFEST_PATH:-}" ]; then
|
||||
echo "OPERATOR_SAMPLE_MANIFEST_PATH is required" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -s "${OPERATOR_SAMPLE_MANIFEST_PATH}" ]; then
|
||||
echo "Operator sample manifest is not readable: ${OPERATOR_SAMPLE_MANIFEST_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "${PYTHON_BIN:-}" ]; then
|
||||
PYTHON_BIN="${PYTHON_BIN}"
|
||||
else
|
||||
@@ -69,6 +94,49 @@ if [ -z "${PYTHON_BIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "curl is required for split-background promotion preflight" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${PYTHON_BIN}" - "${OPERATOR_SAMPLE_MANIFEST_PATH}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
manifest_path = Path(sys.argv[1])
|
||||
payload = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
||||
samples = payload.get("samples") or payload.get("items") or []
|
||||
if not isinstance(samples, list) or not samples:
|
||||
raise SystemExit(f"Operator sample manifest has no samples: {manifest_path}")
|
||||
|
||||
background_categories = {
|
||||
str(sample.get("background_category") or "")
|
||||
for sample in samples
|
||||
if str(sample.get("sample_role") or "") == "background_candidate"
|
||||
}
|
||||
missing = {"pure_empty_negative", "sparse_building_context"} - background_categories
|
||||
if missing:
|
||||
raise SystemExit(
|
||||
"Operator sample manifest is missing background categories "
|
||||
f"{sorted(missing)}; found {sorted(background_categories)}"
|
||||
)
|
||||
PY
|
||||
|
||||
api_response="$(curl -fsS "${BASE_URL%/}/api/v1/projects" 2>/dev/null || true)"
|
||||
if ! printf '%s' "${api_response}" | grep -q '"data"'; then
|
||||
echo "Runtime API proxy is not reachable or did not return the canonical envelope: ${BASE_URL%/}/api/v1/projects" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${PREFLIGHT_ONLY}" = "1" ]; then
|
||||
echo "Split-background promotion preflight passed"
|
||||
echo "Base URL: ${BASE_URL}"
|
||||
echo "Positive portfolio: ${PROMOTION_POSITIVE_PORTFOLIO_PATH}"
|
||||
echo "Operator sample manifest: ${OPERATOR_SAMPLE_MANIFEST_PATH}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
BACKGROUND_SPLIT_OUTPUT_DIR="${BACKGROUND_SPLIT_OUTPUT_DIR:-artifacts/detection-hard-negatives/background-split/${stamp}}"
|
||||
PROMOTION_OUTPUT_DIR="${PROMOTION_OUTPUT_DIR:-artifacts/detection-model-promotion/split-aware/${stamp}}"
|
||||
|
||||
Reference in New Issue
Block a user