// Convert buildings rejected only by library asset-fit into exact-outline massing fallback. // This preserves the surveyed footprint instead of stretching an incompatible asset. import { readFileSync, writeFileSync } from 'node:fs' const root = 'tools/city-massing/city/cad-bim/' const batches = [ 'lod-pilot', 'lod-batch-urban8000', 'lod-batch-urban8000b', 'lod-batch-urban8000c', 'lod-batch-urban-final', 'lod-batch-urban-tail', 'lod-batch-rural1', 'lod-batch-rural2', 'lod-batch-rural3', ] const ids = new Set() const placed = new Set() for (const batch of batches) { const manifest = JSON.parse(readFileSync(`${root}${batch}/manifest.json`, 'utf8')) for (const cell of manifest.cells || []) for (const id of cell.buildingIds || []) placed.add(id) for (const id of manifest.excludedUnfittedIds || []) ids.add(id) } for (const id of placed) ids.delete(id) const seated = JSON.parse(readFileSync(`${root}seated-plan.json`, 'utf8')) const byId = new Map(seated.buildings.map((b) => [b.id, b])) const buildings = [] const missing = [] for (const id of ids) { const source = byId.get(id) if (!source) { missing.push(id) continue } buildings.push({ ...source, kind: 'massing', archetype: null, preferredAssets: null, massingFallbackFrom: source.kind, }) } const plan = { ...seated, buildings, batch: { name: 'cad-bim-unfitted-exact-outline-fallback', sourceBatches: batches, selected: buildings.length, missing, note: 'Buildings rejected only by asset-fit are preserved as exact surveyed-outline massing.', }, } writeFileSync(`${root}seated-unfitted-fallback.json`, JSON.stringify(plan, null, 2)) console.log(JSON.stringify({ rejectedIds: ids.size, selected: buildings.length, missing: missing.length }))