import {readFileSync,writeFileSync} from 'node:fs' const dir='.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/large-building-audit/',plan=JSON.parse(readFileSync(dir+'factory-dem-plan.json')),dem=JSON.parse(readFileSync(dir+'factory-dem-samples.json')) function fit(points,values){const rows=points.map((p,i)=>[1,p.u,p.v,values[i]]);let a=[[0,0,0,0],[0,0,0,0],[0,0,0,0]];for(const r of rows){const x=[r[0],r[1],r[2]],y=r[3];for(let i=0;i<3;i++){a[i][3]+=x[i]*y;for(let j=0;j<3;j++)a[i][j]+=x[i]*x[j]}}for(let i=0;i<3;i++){let k=i;for(let j=i+1;j<3;j++)if(Math.abs(a[j][i])>Math.abs(a[k][i]))k=j;[a[i],a[k]]=[a[k],a[i]];let q=a[i][i];for(let j=i;j<4;j++)a[i][j]/=q;for(let j=0;j<3;j++)if(j!==i){q=a[j][i];for(let c=i;c<4;c++)a[j][c]-=q*a[i][c]}}return a.map(r=>r[3])} function quantile(a,q){const s=[...a].sort((x,y)=>x-y);return s[Math.floor((s.length-1)*q)]} function summary(values){return{min:Math.min(...values),max:Math.max(...values),relief:Math.max(...values)-Math.min(...values),p95toP05:quantile(values,.95)-quantile(values,.05)}} const results=plan.buildings.map(b=>{const ps=b.sampleIndices.map(i=>plan.samples[i]),zs=b.sampleIndices.map(i=>dem.heights[i]),coef=fit(ps,zs),res=zs.map((z,i)=>z-(coef[0]+coef[1]*ps[i].u+coef[2]*ps[i].v)),edge=[],interior=[];ps.forEach((p,i)=>(p.edge?edge:interior).push(res[i]));const headings={};for(const sector of [0,1,2,3]){const t=ps.map((p,i)=>({p,z:zs[i]})).filter(({p})=>sector===0?p.u<.15:sector===1?p.u>.85:sector===2?p.v<.15:p.v>.85).map(x=>x.z);headings[['u-min','u-max','v-min','v-max'][sector]]=summary(t)}return{id:b.id,measured:summary(zs),leastSquaresPlane:{heightAt00:coef[0],deltaAlongU:coef[1],deltaAlongV:coef[2]},slopeMagnitude:Math.hypot(coef[1],coef[2]),planeResidualPeakToPeak:summary(res).relief,residualP95ToP05:summary(res).p95toP05,perimeterResidual:summary(edge),interiorResidual:summary(interior),edgeBands:headings,flatPatchLayoutEstimate:{minimum3x3Patches:[2,2],patchSurveyGridInterval:[.5,.5],stillRequiresSharedContinuousShell:true}}}) const report={source:dem.source,layerJsonSha256:dem.layerJsonSha256,samplesSha256:dem.samplesSha256,points:dem.heights.length,method:'least-squares plane over 21x9 bilinear quadrilateral grid; edge bands u/v <=0.15 or >=0.85; residuals subtract plane only',results,decision:'No threshold changes or model geometry. Plane trend is not permission to remove geology/site slope; review actual foundations and fit residual/edge measurements before any segment proposal.'} writeFileSync(dir+'factory-slope-segmentation.json',JSON.stringify(report,null,2));console.log(JSON.stringify({candidates:results.map(r=>({id:r.id,slope:r.slopeMagnitude,relief:r.measured.relief,planeResidual:r.planeResidualPeakToPeak,residual95:r.residualP95ToP05,edgeResidual:r.perimeterResidual.relief}))}))