expand AI-assisted review to full building corpus
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-08-09 18:37:08 +02:00
parent f9a466b221
commit 7e07fe8486
7 changed files with 112 additions and 50 deletions
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"ledger_id": "reviewedexp6-ai-assisted-review-r1",
"reviewed_at": "2026-08-09T14:00:00+02:00",
"ledger_id": "reviewedexp6-ai-assisted-review-r2-full-corpus",
"reviewed_at": "2026-08-09T15:00:00+02:00",
"reviewer": {
"id": "openai-codex",
"type": "ai_assistant",
@@ -10,16 +10,21 @@
"status": "complete_ai_assisted_experimental_only",
"explicit_limitations": [
"This ledger is not human review and must never satisfy a human-review release gate.",
"Decisions cover the 64 rendered samples on the contact sheet, not every label instance in the corpus.",
"Decisions cover all 252 rendered tiles, but visual review still does not adjudicate every individual label as human ground truth.",
"Visible roof geometry can be displaced from authoritative GRB ground-level footprints.",
"Accepted samples are eligible only for experimental training and non-protected validation."
],
"artifacts": {
"contact_sheet_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/visual-review/contact_sheet_001.png",
"contact_sheet_sha256": "411ee541dcd53b59abc1c12f733f164b28478a1398ab2d50be8c728b11c8fdc4",
"review_summary_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/visual-review/operator_yolo_label_qa_summary.json",
"rendered_tile_count": 64,
"rendered_label_count": 21611,
"review_summary_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/full-ai-review-r1/operator_yolo_label_qa_summary.json",
"review_summary_sha256": "467e5c3c489c42e42872a63fbb0bba7c613824631598d86ea6324b22c007f75a",
"contact_sheets": [
{"path":"/app/storage/operator-data/model-review/reviewedexp6-corpus/full-ai-review-r1/contact_sheet_001.png","sha256":"688b97a6e82e6b906b7966ed3e2b43685786e89d50549188bfdcc3837d042eb2","tile_count":64},
{"path":"/app/storage/operator-data/model-review/reviewedexp6-corpus/full-ai-review-r1/contact_sheet_002.png","sha256":"87f1945df3324dc100e08febd3e45f291a2d709ddebd9c627b082f30fd8f2d26","tile_count":64},
{"path":"/app/storage/operator-data/model-review/reviewedexp6-corpus/full-ai-review-r1/contact_sheet_003.png","sha256":"1873ab25b8ee843abe28d00329fe28998a7b39f9fdbcd8f8acfb58cc576cdf10","tile_count":64},
{"path":"/app/storage/operator-data/model-review/reviewedexp6-corpus/full-ai-review-r1/contact_sheet_004.png","sha256":"e64c252aac08792a76a62fe734728e7c81ba6a649ad2a1cd2051b4df1f95be98","tile_count":60}
],
"rendered_tile_count": 252,
"rendered_label_count": 79192,
"selected_sample_count": 28
},
"visual_criteria": [
@@ -30,34 +35,34 @@
"dense roof-versus-ground-footprint disagreement is retained as an explicit limitation"
],
"decisions": [
{"sample_slug":"arendonk_center","sampled_tiles":2,"sampled_labels":1163,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"balen","sampled_tiles":2,"sampled_labels":794,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"beerse_center","sampled_tiles":2,"sampled_labels":1158,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"dessel_center","sampled_tiles":2,"sampled_labels":841,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"dessel_heide","sampled_tiles":2,"sampled_labels":68,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"geel","sampled_tiles":2,"sampled_labels":1244,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"geel_bel","sampled_tiles":2,"sampled_labels":62,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"grobbendonk_center","sampled_tiles":2,"sampled_labels":1008,"split":"val","category":"reference_aoi","decision":"non_protected_validation_only"},
{"sample_slug":"herentals","sampled_tiles":2,"sampled_labels":1512,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"herenthout_bos","sampled_tiles":2,"sampled_labels":199,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"hoogstraten_center","sampled_tiles":2,"sampled_labels":1063,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"hulshout_center","sampled_tiles":2,"sampled_labels":895,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"kasterlee_bos","sampled_tiles":2,"sampled_labels":72,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"kasterlee_center","sampled_tiles":2,"sampled_labels":983,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"laakdal_center","sampled_tiles":2,"sampled_labels":156,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible_with_dense_alignment_limitation"},
{"sample_slug":"lille_center","sampled_tiles":2,"sampled_labels":961,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"lommel_heide","sampled_tiles":6,"sampled_labels":0,"split":"train","category":"pure_empty_negative","decision":"experimental_training_eligible"},
{"sample_slug":"meerhout_bos","sampled_tiles":2,"sampled_labels":111,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"meerhout_center","sampled_tiles":2,"sampled_labels":1016,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"mol","sampled_tiles":2,"sampled_labels":951,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"nijlen_center","sampled_tiles":2,"sampled_labels":1309,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"olen_center","sampled_tiles":2,"sampled_labels":971,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"oud_turnhout_center","sampled_tiles":2,"sampled_labels":1535,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible_with_dense_alignment_limitation"},
{"sample_slug":"postel_bos","sampled_tiles":6,"sampled_labels":0,"split":"train","category":"pure_empty_negative","decision":"experimental_training_eligible"},
{"sample_slug":"ravels_bos","sampled_tiles":2,"sampled_labels":22,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"rijkevorsel_center","sampled_tiles":2,"sampled_labels":1153,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"vorselaar_center","sampled_tiles":2,"sampled_labels":930,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"vosselaar_center","sampled_tiles":2,"sampled_labels":1434,"split":"val","category":"reference_aoi","decision":"non_protected_validation_only"}
{"sample_slug":"arendonk_center","sampled_tiles":9,"sampled_labels":4172,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"balen","sampled_tiles":9,"sampled_labels":2658,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"beerse_center","sampled_tiles":9,"sampled_labels":4303,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"dessel_center","sampled_tiles":9,"sampled_labels":3405,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"dessel_heide","sampled_tiles":9,"sampled_labels":192,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"geel","sampled_tiles":9,"sampled_labels":4615,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"geel_bel","sampled_tiles":9,"sampled_labels":168,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"grobbendonk_center","sampled_tiles":9,"sampled_labels":2793,"split":"val","category":"reference_aoi","decision":"non_protected_validation_only"},
{"sample_slug":"herentals","sampled_tiles":9,"sampled_labels":5087,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"herenthout_bos","sampled_tiles":9,"sampled_labels":520,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"hoogstraten_center","sampled_tiles":9,"sampled_labels":3761,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"hulshout_center","sampled_tiles":9,"sampled_labels":3606,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"kasterlee_bos","sampled_tiles":9,"sampled_labels":137,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"kasterlee_center","sampled_tiles":9,"sampled_labels":4022,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"laakdal_center","sampled_tiles":9,"sampled_labels":287,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible_with_dense_alignment_limitation"},
{"sample_slug":"lille_center","sampled_tiles":9,"sampled_labels":3949,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"lommel_heide","sampled_tiles":9,"sampled_labels":0,"split":"train","category":"pure_empty_negative","decision":"experimental_training_eligible"},
{"sample_slug":"meerhout_bos","sampled_tiles":9,"sampled_labels":206,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"meerhout_center","sampled_tiles":9,"sampled_labels":4002,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"mol","sampled_tiles":9,"sampled_labels":3666,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"nijlen_center","sampled_tiles":9,"sampled_labels":4900,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"olen_center","sampled_tiles":9,"sampled_labels":3955,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"oud_turnhout_center","sampled_tiles":9,"sampled_labels":5492,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible_with_dense_alignment_limitation"},
{"sample_slug":"postel_bos","sampled_tiles":9,"sampled_labels":0,"split":"train","category":"pure_empty_negative","decision":"experimental_training_eligible"},
{"sample_slug":"ravels_bos","sampled_tiles":9,"sampled_labels":55,"split":"train","category":"sparse_building_context","decision":"experimental_training_eligible"},
{"sample_slug":"rijkevorsel_center","sampled_tiles":9,"sampled_labels":4231,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"vorselaar_center","sampled_tiles":9,"sampled_labels":3858,"split":"train","category":"reference_aoi","decision":"experimental_training_eligible"},
{"sample_slug":"vosselaar_center","sampled_tiles":9,"sampled_labels":5152,"split":"val","category":"reference_aoi","decision":"non_protected_validation_only"}
],
"accounting": {
"accepted_experimental_training_samples": 26,
@@ -67,5 +72,13 @@
"sparse_context_samples": 6,
"human_review_complete": false,
"production_release_eligible": false
},
"nested_box_audit": {
"exact_duplicate_pairs": 0,
"near_duplicate_pairs_iou_at_least_0_90": 0,
"possible_nested_pairs_containment_at_least_0_98": 43,
"flagged_tile_count": 36,
"share_of_rendered_labels": 0.000543,
"action": "requires_human_adjudication_before_release; no automatic rewrite or exclusion"
}
}
+13
View File
@@ -12549,3 +12549,16 @@ Open:
review and therefore does not accept this AI ledger as release evidence.
- Did not add an override, fixture claim or synthetic reviewer identity. No
model training, activation or production redeploy followed from this review.
### Full-corpus extension
- Added deterministic contact-sheet pagination and rendered all 252 retained
tiles across four pages. Accounting covers all 79,192 labels with zero
missing images/files, invalid rows or low-variance tiles.
- Inspected every page at original resolution. Pure-empty tiles remain visibly
empty; sparse contexts remain distinct; dense tiles retain the documented
roof-versus-ground-footprint limitation.
- A read-only same-class box probe found zero exact duplicates and zero pairs
at IoU >= 0.90. It flagged 43 possible containment pairs across 36 tiles for
human adjudication. No source label was modified or silently excluded.
- Targeted renderer tests passed: 6 tests.
+5
View File
@@ -1150,6 +1150,11 @@ This file now starts with the current implementation status. Older preparation/b
- [x] Complete an explicitly AI-assisted per-AOI review of the 64-tile
reviewedexp6 contact sheet, retaining reviewer type, artifact hash, split
role, sample accounting and roof/ground-footprint limitations.
- [x] Extend AI-assisted review to all 252 tiles and 79,192 labels through
deterministic four-page contact-sheet rendering with complete accounting.
- [ ] Human-adjudicate the 43 possible nested same-class box pairs in 36 tiles;
do not rewrite or exclude them automatically because GRB objects can overlap
legitimately.
- [ ] Convert the AI-assisted ledger into no stronger claim than experimental
triage; a real human must independently review and sign the frozen artifacts
before the governed training wrapper may unlock.
@@ -103,3 +103,11 @@ It explicitly cannot satisfy the human-review release gate. The production
training wrapper reproduced that boundary and no bypass was added. The exact
ledger is retained in
`artifacts/evidence/accuracy/model-training/20260809-reviewedexp6-ai-assisted-review-ledger.json`.
The renderer was subsequently paginated and the review expanded from the
64-tile sample to all 252 retained tiles. Four immutable pages now account for
all 79,192 labels with zero missing images, missing label files, invalid rows
or low-variance tiles. No exact or IoU>=0.90 duplicate box pair was found. A
separate containment probe identified 43 potentially nested pairs across 36
tiles (0.0543% relative to rendered labels); these remain human-adjudication
candidates and were neither rewritten nor automatically excluded.
+5
View File
@@ -6,6 +6,11 @@ model and dataset hashes, standard Ultralytics detection metrics and a separate
pure-background detection count. The output claim is validation ranking only;
the script neither reads protected test data nor promotes a model.
`render_operator_yolo_label_qa_contact_sheets.py` paginates complete visual
reviews with `--tiles-per-sheet` (default `64`). This keeps large corpora
inspectable while preserving deterministic tile selection, ordering, label
accounting and stable `contact_sheet_001.png` naming for the first page.
Setup-, import-, demo- en maintenance-scripts voor GeoIntel.
## WALOUS source provisioning
@@ -19,7 +19,7 @@ from PIL import Image, ImageDraw, ImageFont
JSON_NAME = "operator_yolo_label_qa_summary.json"
MARKDOWN_NAME = "operator_yolo_label_qa_contact_sheet.md"
CONTACT_SHEET_NAME = "contact_sheet_001.png"
CONTACT_SHEET_NAME_TEMPLATE = "contact_sheet_{index:03d}.png"
def parse_args() -> argparse.Namespace:
@@ -37,6 +37,12 @@ def parse_args() -> argparse.Namespace:
)
parser.add_argument("--columns", type=int, default=4, help="Contact-sheet columns")
parser.add_argument("--thumb-size", type=int, default=256, help="Rendered tile thumbnail size in pixels")
parser.add_argument(
"--tiles-per-sheet",
type=int,
default=64,
help="Maximum rendered cards per contact-sheet page. Default: 64.",
)
parser.add_argument(
"--blank-range-threshold",
type=int,
@@ -329,16 +335,17 @@ def build_report(summary: dict[str, Any], summary_path: Path, args: argparse.Nam
}
)
contact_sheets = []
if rendered_cards:
contact_sheets.append(
{
"path": CONTACT_SHEET_NAME,
"tile_count": len(rendered_cards),
"columns": args.columns,
"thumb_size": args.thumb_size,
}
)
if args.tiles_per_sheet <= 0:
raise ValueError("tiles_per_sheet must be positive")
contact_sheets = [
{
"path": CONTACT_SHEET_NAME_TEMPLATE.format(index=(start // args.tiles_per_sheet) + 1),
"tile_count": len(rendered_cards[start : start + args.tiles_per_sheet]),
"columns": args.columns,
"thumb_size": args.thumb_size,
}
for start in range(0, len(rendered_cards), args.tiles_per_sheet)
]
return (
{
@@ -350,6 +357,7 @@ def build_report(summary: dict[str, Any], summary_path: Path, args: argparse.Nam
"requested_sample_slugs": sorted(set(args.sample_slug)),
"columns": args.columns,
"thumb_size": args.thumb_size,
"tiles_per_sheet": args.tiles_per_sheet,
"selected_tile_count": len(selected_tiles),
"selected_sample_count": len(
{str(tile.get("sample_slug") or "unknown") for tile in selected_tiles}
@@ -424,8 +432,10 @@ def main() -> int:
summary = load_json(summary_path)
report, cards = build_report(summary, summary_path, args)
if cards:
build_contact_sheet(cards, args.columns, output_dir / CONTACT_SHEET_NAME)
for page_index, start in enumerate(range(0, len(cards), args.tiles_per_sheet), start=1):
page_cards = cards[start : start + args.tiles_per_sheet]
page_name = CONTACT_SHEET_NAME_TEMPLATE.format(index=page_index)
build_contact_sheet(page_cards, args.columns, output_dir / page_name)
(output_dir / JSON_NAME).write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
write_markdown(report, output_dir)
@@ -1,6 +1,9 @@
import pytest
from scripts.render_operator_yolo_label_qa_contact_sheets import filter_tiles_by_samples
from scripts.render_operator_yolo_label_qa_contact_sheets import (
CONTACT_SHEET_NAME_TEMPLATE,
filter_tiles_by_samples,
)
TILES = [
@@ -24,3 +27,8 @@ def test_filter_tiles_by_samples_rejects_missing_sample() -> None:
def test_filter_tiles_by_samples_without_filter_preserves_tiles() -> None:
assert filter_tiles_by_samples(TILES, []) is TILES
def test_contact_sheet_name_template_is_stable_and_one_indexed() -> None:
assert CONTACT_SHEET_NAME_TEMPLATE.format(index=1) == "contact_sheet_001.png"
assert CONTACT_SHEET_NAME_TEMPLATE.format(index=12) == "contact_sheet_012.png"