from pathlib import Path import json import py_compile import sys from PIL import Image ROOT = Path(__file__).resolve().parent REQUIRED = [ 'app.py', 'requirements.txt', 'packages.txt', 'README.md', 'template.jpg', 'template_lossless.png', 'template.json', 'marker_config.json', 'template_A4_print.pdf', 'marker_reference.png', 'samples/sample_phone_perspective.jpg', 'samples/sample_phone_shadow_rescue.jpg', 'samples/sample_phone_missing_tl.jpg', ] errors = [] for filename in REQUIRED: if not (ROOT / filename).is_file(): errors.append(f'Missing: {filename}') try: py_compile.compile(str(ROOT / 'app.py'), doraise=True) except Exception as error: errors.append(f'app.py syntax validation failed: {error}') for image_name in ('template.jpg', 'template_lossless.png'): try: image = Image.open(ROOT / image_name) if image.size != (3093, 4374): errors.append(f'{image_name} size is {image.size}, expected (3093, 4374)') except Exception as error: errors.append(f'{image_name} cannot be read: {error}') try: template = json.loads((ROOT / 'template.json').read_text(encoding='utf-8')) markers = json.loads((ROOT / 'marker_config.json').read_text(encoding='utf-8')) if template.get('pageDimensions') != [3093, 4374]: errors.append('template.json pageDimensions mismatch') if markers.get('pageDimensions') != [3093, 4374]: errors.append('marker_config.json pageDimensions mismatch') if markers.get('largeMarkerOuterSize') != 144: errors.append('V3.1 largeMarkerOuterSize must remain 144') if markers.get('strictMarkerMode') is not True: errors.append('strictMarkerMode must default to true') if 'smallGroupMarkers' in markers or 'smallMarkerOuterSize' in markers: errors.append('Small group marker settings must not exist in this variant') rescue = markers.get('localRescue') or {} if rescue.get('enabled') is not True: errors.append('V3.1 localRescue.enabled must be true') if int(rescue.get('minimumGlobalCandidates', 0)) < 2: errors.append('minimumGlobalCandidates must be at least 2') if int(rescue.get('maxRescueCorners', 99)) > 2: errors.append('maxRescueCorners must not exceed 2') threshold = float(rescue.get('templateMatchThreshold', 0)) if not 0.35 <= threshold <= 0.65: errors.append('templateMatchThreshold outside expected safe range') except Exception as error: errors.append(f'JSON validation failed: {error}') if errors: print('PROJECT VALIDATION FAILED') for error in errors: print('-', error) sys.exit(1) print('PROJECT VALIDATION PASSED') print('V3.1 four-large-marker-only template, local-shadow rescue settings and required files are present.')