param( [string]$ProjectRoot = "D:\React\cm93-mysql-importer-starter", [string]$SourceRoot = "D:\Ship\CM93V2\CM93JAPAN", [int]$DatasetId = 1, [int]$ExpectedCells = 1492, [int]$Workers = 4 ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Step([string]$Text) { Write-Host "" Write-Host "======================================================================" Write-Host $Text Write-Host "======================================================================" } function Run([string]$Label, [scriptblock]$Action) { Step $Label & $Action $Code = $LASTEXITCODE if ($null -ne $Code -and $Code -ne 0) { throw "$Label FAILED (exit=$Code)" } } function StateCount($States, [string]$Name) { if ($null -eq $States) { return 0 } $P = $States.PSObject.Properties[$Name] if ($null -eq $P) { return 0 } return [int]$P.Value } Set-Location $ProjectRoot $Python = ".\.venv\Scripts\python.exe" $Cm95Pbf = ".\.venv\Scripts\cm95pbf.exe" $Mapping = ".\config\mappings\cm95_to_navsea_v1.yaml" $Disposition = ".\config\mappings\cm95_gap_disposition_japan_dataset_1_v1.yaml" $Policy = ".\config\pbf\cm95_tile_policy_japan_full_detail_v1.yaml" $GapAudit = ".\build\production\cm95_global_mapping_gap_audit_dataset_1.json" $Closure = ".\build\production\cm95_global_mapping_gap_closure_dataset_1_after_review.json" $MappingContract = ".\build\production\cm95_mapping_contract_check_japan_dataset_1.json" $Manifest = ".\build\cm95_fid_manifest_japan_dataset_1_v1.sqlite" $Plan = ".\build\production\cm95_production_plan_japan_full_detail_v1.json" $PlanAudit = ".\build\production\cm95_production_plan_audit_japan_full_detail_v1.json" foreach ($Path in @($Python,$Cm95Pbf,$Mapping,$Disposition,$Policy)) { if (-not (Test-Path $Path)) { throw "Missing required file: $Path" } } # ---------------------------------------------------------------------- # 0. Verify v1.4.5 runtime # ---------------------------------------------------------------------- Run "STEP 0 - CM95 v1.4.5 preflight" { & $Python ".\tools\cm95_v1_4_5_preflight.py" } # ---------------------------------------------------------------------- # 1. Verify fresh Japan Dataset in MySQL, no reimport # ---------------------------------------------------------------------- Step "STEP 1 - Verify existing fresh Japan Dataset" $Tmp = Join-Path $env:TEMP ("cm95_japan_continue_" + $PID) Remove-Item $Tmp -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $Tmp | Out-Null try { $Helper = Join-Path $Tmp "verify_dataset.py" @' from pathlib import Path from dotenv import load_dotenv import os, sys, json, pymysql project_root = Path(sys.argv[1]).resolve() dataset_id = int(sys.argv[2]) expected_root = str(Path(sys.argv[3]).resolve()) load_dotenv(dotenv_path=project_root / ".env") conn = pymysql.connect( host=os.getenv("CM93_DB_HOST", "127.0.0.1"), port=int(os.getenv("CM93_DB_PORT", "3306")), user=os.getenv("CM93_DB_USER", "root"), password=os.getenv("CM93_DB_PASSWORD", ""), database=os.getenv("CM93_DB_NAME", "cm93_chart"), charset=os.getenv("CM93_DB_CHARSET", "utf8mb4"), cursorclass=pymysql.cursors.DictCursor, ) try: with conn.cursor() as cur: cur.execute( """ SELECT d.id,d.name,d.source_root, COUNT(c.id) cells, SUM(c.import_status='DONE') done_cells, SUM(c.import_status='FAILED') failed_cells FROM chart_dataset d LEFT JOIN chart_cell c ON c.dataset_id=d.id WHERE d.id=%s GROUP BY d.id,d.name,d.source_root """, (dataset_id,), ) row = cur.fetchone() if not row: raise RuntimeError(f"dataset {dataset_id} not found") actual_root = str(Path(row["source_root"]).resolve()) result = { "id": int(row["id"]), "name": row["name"], "source_root": actual_root, "cells": int(row["cells"] or 0), "done_cells": int(row["done_cells"] or 0), "failed_cells": int(row["failed_cells"] or 0), } if actual_root.lower() != expected_root.lower(): raise RuntimeError( f"source_root mismatch: {actual_root} != {expected_root}" ) print(json.dumps(result, ensure_ascii=False)) finally: conn.close() '@ | Set-Content $Helper -Encoding UTF8 $DatasetText = ( & $Python $Helper $ProjectRoot $DatasetId $SourceRoot | Out-String ).Trim() if ($LASTEXITCODE -ne 0) { throw "Dataset verification failed" } $Dataset = $DatasetText | ConvertFrom-Json if ([int]$Dataset.cells -ne $ExpectedCells -or [int]$Dataset.done_cells -ne $ExpectedCells -or [int]$Dataset.failed_cells -ne 0) { throw "Dataset state mismatch: $DatasetText" } Write-Host $DatasetText } finally { Remove-Item $Tmp -Recurse -Force -ErrorAction SilentlyContinue } # ---------------------------------------------------------------------- # 2. Re-run gap audit and closure with the new dataset-specific disposition # ---------------------------------------------------------------------- Run "STEP 2A - Re-run global mapping gap audit" { & $Python ".\tools\cm95_global_mapping_gap_audit_v1.py" ` --dataset-id $DatasetId ` --mapping $Mapping ` --output $GapAudit } Run "STEP 2B - Close 25 new Japan gap variants" { & $Python ".\tools\cm95_global_mapping_gap_closure_audit_v2.py" ` --gap-audit $GapAudit ` --mapping $Mapping ` --disposition $Disposition ` --output $Closure } $C = Get-Content $Closure -Raw | ConvertFrom-Json if ([string]$C.overall -ne "PASS") { throw "Mapping gap closure is not PASS" } if (@($C.unclassified_variants).Count -ne 0) { throw "Unclassified variants remain" } if ([int]$C.observed_gap_variants -ne 165) { throw "Unexpected gap variant count: $($C.observed_gap_variants)" } if (@($C.pending_semantic_review).Count -ne 137) { throw "Unexpected pending variant count: $(@($C.pending_semantic_review).Count)" } if ([int]$C.pending_semantic_review_feature_rows -ne 90009) { throw "Unexpected pending rows: $($C.pending_semantic_review_feature_rows)" } Write-Host "" Write-Host "Expected closure confirmed:" Write-Host " observed = 165" Write-Host " unclassified = 0" Write-Host " pending = 137" Write-Host " pending rows = 90009" Write-Host " overall = PASS" # ---------------------------------------------------------------------- # 3. Static mapping/style/sprite contract # ---------------------------------------------------------------------- Run "STEP 3 - Mapping / Style / Sprite static contract" { & $Python ".\tools\cm95_mapping_contract_check_v1.py" ` --mapping $Mapping ` --style ".\config\style\style.navsea-delivery-full-semantic-unified-20260808-cm95-v1.json" ` --sprite-json ".\config\sprite\semantic-sprite-20260808.json" ` --output $MappingContract } $MC = Get-Content $MappingContract -Raw | ConvertFrom-Json if ([string]$MC.overall -ne "PASS") { throw "Mapping/style/sprite contract failed" } # ---------------------------------------------------------------------- # 4. Full-detail policy hard gate: G=7500 must be in z12 # ---------------------------------------------------------------------- Step "STEP 4 - Japan full-detail policy check" $PolicyObj = Get-Content $Policy -Raw | & $Python -c "import sys,yaml,json; print(json.dumps(yaml.safe_load(sys.stdin.read())))" | ConvertFrom-Json # No multi-line Python is passed here; only a fixed one-line expression. $Enum12 = @($PolicyObj.enumeration_scales.'12') $Ladder12 = @($PolicyObj.scale_ladders.'12') if (7500 -notin $Enum12 -or $Ladder12.Count -eq 0 -or [int]$Ladder12[0] -ne 7500) { throw "Japan full-detail policy does not prioritize G=7500 at z12" } Write-Host "z12 enumeration : $($Enum12 -join ', ')" Write-Host "z12 ladder : $($Ladder12 -join ', ')" Write-Host "G=7500 gate : PASS" # ---------------------------------------------------------------------- # 5. Reset PBF-side outputs only. MySQL remains untouched. # ---------------------------------------------------------------------- Step "STEP 5 - Reset PBF-side outputs only" Remove-Item ".\build\releases" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item $Manifest -Force -ErrorAction SilentlyContinue Remove-Item $Plan -Force -ErrorAction SilentlyContinue Remove-Item $PlanAudit -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path ".\build\releases" | Out-Null # ---------------------------------------------------------------------- # 6. Fresh deterministic FID manifest # ---------------------------------------------------------------------- Run "STEP 6 - Build fresh FID manifest" { & $Cm95Pbf build-fid-manifest ` --dataset-id $DatasetId ` --manifest $Manifest ` --rebuild-fid-manifest } # ---------------------------------------------------------------------- # 7. Fresh production plan using G=7500-aware z12 policy # ---------------------------------------------------------------------- Run "STEP 7A - plan-production z0-z12 Japan full detail" { & $Cm95Pbf plan-production ` --dataset-id $DatasetId ` --zooms 0-12 ` --profile delivery ` --mapping $Mapping ` --policy $Policy ` --manifest $Manifest ` --output $Plan } Run "STEP 7B - production plan audit" { & $Python ".\tools\cm95_production_plan_audit_v1.py" ` --plan $Plan ` --expected-zooms 0-12 ` --output $PlanAudit } $PA = Get-Content $PlanAudit -Raw | ConvertFrom-Json if ([string]$PA.overall -ne "PASS") { throw "Production plan audit failed" } $PlanObj = Get-Content $Plan -Raw | ConvertFrom-Json $ReleaseId = [string]$PlanObj.release_id Write-Host "" Write-Host "Plan ID : $($PlanObj.plan_id)" Write-Host "Release ID : $ReleaseId" Write-Host "Jobs : $($PlanObj.job_count)" # ---------------------------------------------------------------------- # 8. Full production build # ---------------------------------------------------------------------- Run "STEP 8 - Full PBF production build" { & $Cm95Pbf build-production ` --plan $Plan ` --release-root ".\build\releases" ` --workers $Workers ` --max-attempts 3 } # ---------------------------------------------------------------------- # 9. Status # ---------------------------------------------------------------------- Step "STEP 9 - Production status" $StatusText = ( & $Cm95Pbf status-production ` --plan $Plan ` --release-root ".\build\releases" | Out-String ).Trim() if ($LASTEXITCODE -ne 0) { throw "status-production failed" } Write-Host $StatusText $S = $StatusText | ConvertFrom-Json $Pass = StateCount $S.states "PASS" $Fail = StateCount $S.states "FAILED" $Pending = StateCount $S.states "PENDING" $Running = StateCount $S.states "RUNNING" if ($Pass -ne [int]$S.jobs -or $Fail -ne 0 -or $Pending -ne 0 -or $Running -ne 0) { throw "Production state gate failed" } # ---------------------------------------------------------------------- # 10. Final release gate # ---------------------------------------------------------------------- Run "STEP 10 - Finalize production release" { & $Cm95Pbf finalize-production ` --plan $Plan ` --release-root ".\build\releases" ` --style ".\config\style\style.navsea-delivery-full-semantic-unified-20260808-cm95-v1.json" ` --sprite-json ".\config\sprite\semantic-sprite-20260808.json" ` --gap-disposition $Disposition } $ReleaseManifest = ".\build\releases\$ReleaseId\release_manifest_v1.json" if (-not (Test-Path $ReleaseManifest)) { throw "Release manifest missing: $ReleaseManifest" } $Final = Get-Content $ReleaseManifest -Raw | ConvertFrom-Json if ([string]$Final.status -ne "PASS") { throw "Final release status is not PASS" } Step "JAPAN CONTINUATION COMPLETE / PASS" Write-Host "Dataset ID : $DatasetId" Write-Host "Release ID : $ReleaseId" Write-Host "Jobs : $($S.jobs)" Write-Host "PBF files : $($S.pbf_files)" Write-Host "Output features : $($S.output_features)" Write-Host "Semantic status : $($Final.semantic_completeness)" Write-Host "Pending variants : 137" Write-Host "Pending rows : 90009" Write-Host "" Write-Host "Web test:" Write-Host " cd `"$ProjectRoot`"" Write-Host " & `".\.venv\Scripts\python.exe`" .\web\cm95_web.py --release-id `"$ReleaseId`""