Make dataset audit mandatory for training loop
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-27 03:04:06 +02:00
parent 70897a3265
commit a31dce325f
5 changed files with 63 additions and 1 deletions
@@ -1,6 +1,9 @@
from __future__ import annotations
import importlib.util
import json
import subprocess
import sys
from pathlib import Path
@@ -29,3 +32,35 @@ def test_training_command_is_cuda_deterministic_and_bound_to_frozen_inputs(tmp_p
assert "seed=42" in command
assert "epochs=160" in command
assert f"data={tmp_path / 'dataset.yaml'}" in command
def test_loop_refuses_failed_dataset_audit(tmp_path: Path) -> None:
audit = tmp_path / "audit.json"
audit.write_text(json.dumps({"status": "needs_attention", "low_variance_positive_tile_count": 4}))
result = subprocess.run(
[
sys.executable,
str(SCRIPT),
"--initial-model",
str(tmp_path / "base.pt"),
"--train-yaml",
str(tmp_path / "dataset.yaml"),
"--dataset-audit",
str(audit),
"--calibration-summary",
str(tmp_path / "cal.json"),
"--test-summary",
str(tmp_path / "test.json"),
"--background-summary",
str(tmp_path / "background.json"),
"--corpus-manifest",
str(tmp_path / "manifest.json"),
"--output-dir",
str(tmp_path / "output"),
],
capture_output=True,
text=True,
check=False,
)
assert result.returncode != 0
assert "Dataset audit is not ok" in result.stderr
+2
View File
@@ -79,3 +79,5 @@ The active production model remains unchanged while any gate fails.
Every failed assessment returns `continue_training_loop`. Only a report with
`training_complete` may proceed to final human review and guarded activation.
The orchestrator refuses to start unless the frozen dataset audit is `ok` and
contains zero blank/low-variance positive tiles.
+17
View File
@@ -11570,3 +11570,20 @@ Next gate:
became Brussels `0.566`, Wallonia `0.427`, Flanders `0.222`. The generic-base
iteration did not beat it. Both remained rejected, and iteration 4 started
from the stronger candidate on the dated 75-AOI corpus.
## 2026-07-27 - Positive-imagery gate and complete SPW campaign
- Visual inspection caught 20 positive Walloon tiles with labels over white
no-data imagery. SPW 2024 is an official but partial campaign, so that
training run was stopped and its candidate is invalid.
- Dataset QA now fails whenever any positive tile is blank/low-variance. The
checkpointed training orchestrator also refuses to start unless this audit
is `ok` with zero affected positive tiles.
- Replaced the partial product with the official, territory-complete SPW summer
2023 campaign (27 May through 25 June, 25 cm). The frozen replacement corpus
`building-be-v5-dated-20260727-r1` retains 75 AOIs and 13,765 accepted labels.
Manifest SHA-256 is
`eade90d3ba22b426b72a300fcaa9a01f6eb1d18567c4a53413b72b8048f5f021`.
- The v5 tile audit passed with zero blank positive tiles. The automated CUDA
loop started from the strongest prior candidate and will checkpoint every
train/calibrate/test/background assessment without promoting failed models.
+2 -1
View File
@@ -953,4 +953,5 @@ This file now starts with the current implementation status. Older preparation/b
- [x] Freeze the objective train/evaluate/error-analysis loop and regional exit gates.
- [x] Expand the second corpus wave to 60 independent AOIs and 10,262 accepted labels.
- [ ] Resolve the v3 Flanders and Wallonia generalisation failures through additional training-only evidence and retraining.
- [x] Replace rolling-mosaic training inputs with governed dated 2025 Flanders/Brussels and 2024 SPW imagery; retain exact flight-day limitations.
- [x] Replace rolling-mosaic training inputs with governed dated 2025 Flanders/Brussels and complete 2023 SPW imagery; retain exact flight-day limitations.
- [x] Reject positive labels over blank/no-data imagery and replace partial SPW 2024 coverage with the complete dated SPW 2023 campaign.
@@ -78,6 +78,7 @@ def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--initial-model", type=Path, required=True)
parser.add_argument("--train-yaml", type=Path, required=True)
parser.add_argument("--dataset-audit", type=Path, required=True)
parser.add_argument("--calibration-summary", type=Path, required=True)
parser.add_argument("--test-summary", type=Path, required=True)
parser.add_argument("--background-summary", type=Path, required=True)
@@ -93,6 +94,11 @@ def main() -> int:
args = parser.parse_args()
if args.iterations < 1:
raise SystemExit("--iterations must be positive")
dataset_audit = json.loads(args.dataset_audit.read_text(encoding="utf-8"))
if dataset_audit.get("status") != "ok":
raise SystemExit(f"Dataset audit is not ok: {args.dataset_audit}")
if int(dataset_audit.get("low_variance_positive_tile_count") or 0) != 0:
raise SystemExit("Dataset audit contains blank/low-variance positive tiles")
state_path = args.output_dir / "training-loop-state.json"
state: dict[str, Any] = {
@@ -101,6 +107,7 @@ def main() -> int:
"started_at": datetime.now(UTC).isoformat(),
"initial_model": str(args.initial_model),
"train_yaml": str(args.train_yaml),
"dataset_audit": str(args.dataset_audit),
"corpus_manifest": str(args.corpus_manifest),
"iterations": [],
}