import json from pathlib import Path stats=json.loads(Path('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/roof-imagery-quality/roof-imagery-statistics.json').read_text()) thresholds={'manualReviewDarkFraction':0.30,'manualReviewSeamMeanRgbRms':10.0,'manualReviewSeamP95RgbRms':30.0} rows=[] for x in stats['roofCrops']: d=x['roofCrop']['darkPixelFractionLumaUnder55']; rows.append({'id':x['id'],'darkFraction':d,'darkRisk':d>=thresholds['manualReviewDarkFraction'],'sourceTileCount':x['sourceTileCount']}) for x in stats['adjacentTileSeams']: x['manualReviewRisk']=x['meanRgbRmsDelta']>=thresholds['manualReviewSeamMeanRgbRms'] or x['p95RgbRmsDelta']>=thresholds['manualReviewSeamP95RgbRms'] report={'generatedAt':'2026-10-15','decision':'manual-review-required','thresholds':thresholds,'roofCropRows':rows,'seamRows':stats['adjacentTileSeams'],'automaticIntegrationAllowed':False,'reason':['Statistics cover crop rectangles, not only roof UV pixels.','Darkness can be shadow, roof material, occlusion or non-roof pixels.','Tile edge deltas can cross different land cover.','Only maintainer cockpit review can accept visual naturalness.'],'candidateUnchanged':True,'runtimeConfigChanged':False} Path('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/roof-imagery-quality/manual-review-gate.json').write_text(json.dumps(report,indent=2)) print(json.dumps({'passed':True,'decision':report['decision'],'roofCropsRequiringReview':sum(x['darkRisk'] for x in rows),'seamsRequiringReview':sum(x['manualReviewRisk'] for x in report['seamRows']),'automaticIntegrationAllowed':False}))