Files
Pier_v1/수량산출/model-alignment.test.ts
T
ypshimandClaude Haiku 5.5 fc520ec03d feat: 프로젝트 공통 지반모델·교대 지반 통합·재료표/수량 진입·옥성교 랜딩 히어로 (11-06~11-09 작업분)
공용 파일(main.ts·index.html·style.css)에서 서로 얽혀 있어 한 커밋으로 묶음.
- 지반: ① 형상 프로젝트 지반모델 관리창(생성·JSON 불러오기·교각별 비교·선택 적용),
  프로젝트 JSON 내장 groundModel 추출, 교대 8행 통합 적용·공통지형 중복격자 제외
- 재료표·수량: 상단 ⑤ 메뉴·배근 바로가기, 결과 교각/탭 기억, 수량 워크북·정합 보정
- 랜딩: 옥성교 6교각 히어로 이미지·투영 강조점·좌→우 광선·지형 라운드/도로 연결
- 검증 스크립트(verify-project-ground/ground-abutments/okseong-user-files/quantity-*/landing-hero 등)
정본: DOC/04_실행/실행_*_2026-11-09_v0.1.00.md

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-09 18:01:02 +09:00

71 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {describe,it,expect} from 'vitest';
import {buildPier} from '../src/core/geometry';
import {solplateGeometry} from '../src/core/solplate';
import {loadP1} from '../tests/helpers';
import {calculate,withTemplateDefaults,DETAIL,type QuantityInputs} from './model';
import {exportEdits} from './export';
import {template,templateHash} from './template-data';
import {cellKey} from './formula';
import {exportRecords,finishQuantityExport} from './export-state';
import type {QuantitySnapshot} from './project-inputs';
const base=loadP1();
const input={...base,sole:{h3Mm:181,marginMm:100,tByRow:{전열:35,후열:35},slopePct:Object.fromEntries(base.bearings.map(b=>[String(b.no),0]))}};
const model=buildPier(input);
const stored:QuantityInputs={values:{r20:0,r21:2.8,r22:0,r23:0},reviewed:true};
const raw=()=>calculate(input,model,stored,1.25);
const snapshot=():QuantitySnapshot=>({project:{name:'프로젝트',bridge:'교량'},selected:input.name,piers:[{input:{...input,quantityTakeoff:stored},model,dirty:false,rebarTon:1.25}]});
describe('W64 모델 기준 수량·Excel',()=>{
it('800×740×35mm 10개는 1.62652t이며 Excel에도 같은 숫자를 전달한다',()=>{
const result=raw(),row=result.results.find(r=>r.cell==='W64')!;
expect(model.blocks).toHaveLength(10);
expect(row.value).toBeCloseTo(1.62652,10);
expect(row.sourceFormula).toBe(template[DETAIL].W64.value);
expect(row.formula).toContain('3D 모델');
const payload=exportEdits({project:{name:'',bridge:''},pier:input.name,overrides:result.overrides,specs:withTemplateDefaults(stored).values,reviewed:true,copingMode:result.copingMode,solplateMode:result.solplateMode,templateHash});
expect(payload.edits[DETAIL].W64.value).toBeCloseTo(row.value!,12);
expect(payload.edits[DETAIL].K64.value).toContain('3D 모델');
expect(template[DETAIL].W64.value).toBe('=(BG28+0.1)*(BG29+0.1)*BG34*BG30*7.85');
});
it('구형 요청은 모델 값을 원식의 캐시로 섞지 않고 기존 계산을 유지한다',()=>{
const result=raw();
const payload=exportEdits({project:{name:'',bridge:''},pier:input.name,overrides:result.overrides,specs:withTemplateDefaults(stored).values,reviewed:true,copingMode:result.copingMode,templateHash});
expect(payload.edits[DETAIL].W64.cached).toBeCloseTo(1.23088,10);
expect(payload.edits[DETAIL].W64.value).toBeUndefined();
});
it('받침별 크기·경사·두께가 달라도 개별 모델 체적으로 합산한다',()=>{
const next={...input,sole:{...input.sole,plates:{[String(base.bearings[0].no)]:{aMm:900,bMm:800}},slopePct:{[String(base.bearings[0].no)]:2.1}}};
const m=buildPier(next),r=calculate(next,m,{...stored,values:{...stored.values,r40:999,r41:999,r46:999}},1.25);
const actual=m.blocks.reduce((sum,b)=>sum+solplateGeometry(b,b.plan).volume*7.85,0);
expect(r.results.find(x=>x.cell==='W64')!.value).toBeCloseTo(actual,12);
expect(r.results.find(x=>x.cell==='W64')!.dependencies).toEqual([cellKey(DETAIL,'W64')]);
});
});
describe('Excel 비동기 생성 후 입력 보존·범위',()=>{
it('선택 출력은 해당 교각만, 전체 출력은 미생성 교각도 검사 대상으로 반환한다',()=>{
const s=snapshot();s.piers.push({...s.piers[0],input:{...input,name:'P2'},model:null});
expect(exportRecords(s,'selected').map(r=>r.pier.input.name)).toEqual([input.name]);
expect(exportRecords(s,'all').map(r=>r.pier.input.name)).toEqual([input.name,'P2']);
});
it('생성 중 수정한 입력·검토 체크를 이전 스냅샷으로 저장하지 않는다',()=>{
const before=snapshot(),live=snapshot(),saved:unknown[]=[];
live.piers[0].input={...live.piers[0].input,quantityTakeoff:{values:{...stored.values,r21:6},reviewed:false}};
expect(finishQuantityExport(before,'all',live,(...args)=>saved.push(args))).toEqual([input.name]);
expect(saved).toEqual([]);
expect(live.piers[0].input.quantityTakeoff).toMatchObject({values:{r21:6},reviewed:false});
});
it('변경이 없으면 현재 입력에 계산 서명만 기록한다',()=>{
const s=snapshot(),saved:{name:string;data:QuantityInputs}[]=[];
expect(finishQuantityExport(s,'selected',s,(name,data)=>saved.push({name,data}))).toEqual([]);
expect(saved[0].data).toMatchObject({values:stored.values,reviewed:true,lastSignature:raw().signature});
});
it('다른 프로젝트로 바뀌거나 모델이 재생성되면 이전 출력의 서명을 저장하지 않는다',()=>{
const before=snapshot(),live=snapshot();live.project={...live.project,name:'다른 프로젝트'};
expect(finishQuantityExport(before,'all',live,()=>{throw new Error('과거 입력 저장');})).toEqual([input.name]);
live.project=before.project;live.piers[0].model=buildPier(input);
expect(finishQuantityExport(before,'all',live,()=>{throw new Error('과거 입력 저장');})).toEqual([input.name]);
});
});