Files
Pier_v1/tests/coping-geom.test.ts
T
ypshimandClaude Opus 5.5 2b26b7c095 fix(rebar): 측면 수평 C12 = 상단 · C13 = 하단 변화구간 (엑셀 Pier B357–G358)
T형과 다주형 모두 엑셀 Pier 시트 철근표(C12 상단, C13 하단(변화구간))에 맞춰
측면 수평철근 번호를 서로 바꾼다 (사용자 결정 2026-11-06).
- 경사 하면에서 끊기는 아래 단 → C13, 끝면까지 가는 위 단 → C12
- 철근표 위치·비고 문구, 3D, 다주형 생성기가 같은 원천(sideRows)을 따른다
- C14 높이 순서를 유지하도록 측면 단을 아래(C13) → 위(C12) 순서로 넘김
- 테스트 기대값의 번호를 교환 (P1: C12 2단 8개, C13 5단 20개)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:29:50 +09:00

116 lines
5.4 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, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { P1_COPING_GEOM, SIDE_COVER_M, sideRowLevels } from '../src/core/coping-geom';
import { sideRows } from '../src/core/rebar/coping';
import { shearUHeight, shearULength } from '../src/core/rebar/coping';
import { rebarSummary } from '../src/core/rebar/summary';
import { buildRebar3D } from '../src/view/rebar3d';
import { loadP1 } from './helpers';
const L = 12000;
describe('① 코핑 끝단 높이 h1 · 테이퍼 높이 h2 · Level 폭 (1단계, 승인 2026-10-23)', () => {
it('the P1 geometry reproduces the document U heights (890.9, 2178.8, Level 2300)', () => {
expect(shearUHeight(300, L)).toBeCloseTo(890.9, 1);
expect(shearUHeight(4550, L)).toBeCloseTo(2178.8, 1);
expect(shearUHeight(6000, L)).toBeCloseTo(2300, 6);
});
it('the model defaults to the P1 coping geometry and the P1 shear lengths', () => {
const model = buildPier(loadP1());
expect(model.copingGeom).toEqual(P1_COPING_GEOM);
expect(model.endBottom).toBeCloseTo(43.411 - 1.0, 6);
expect(model.levelBottom).toBeCloseTo(43.411 - 1.0 - 1.5, 6);
});
it('a higher end height h1 lowers the coping ends and adds the same height to every U', () => {
const base = buildPier(loadP1());
const high = buildPier({ ...loadP1(), copingH1Mm: 1200 });
expect(high.endBottom).toBeCloseTo(base.endBottom - 0.2, 6);
expect(high.levelBottom).toBeCloseTo(base.levelBottom - 0.2, 6);
const geomH1 = { ...P1_COPING_GEOM, h1: 1200 };
expect(shearUHeight(300, L, geomH1)).toBeCloseTo(shearUHeight(300, L) + 200, 6);
expect(shearULength(300, L, geomH1)).toBeCloseTo(shearULength(300, L) + 400, 6);
});
it('a taper h2 changes the Level underside and the height at the Level', () => {
const high = buildPier({ ...loadP1(), copingH2Mm: 1800 });
expect(high.levelBottom).toBeCloseTo(43.411 - 1.0 - 1.8, 6);
expect(shearUHeight(6000, L, { ...P1_COPING_GEOM, h2: 1800 })).toBeCloseTo(2600, 6);
});
it('a wider Level changes the Level zone in the model and the shear-bar Level half-width', () => {
const wide = buildPier({ ...loadP1(), levelWidthMm: 2500 });
expect(wide.copingGeom.levelWidth).toBe(2500);
expect(wide.levelBottom).toBeCloseTo(buildPier(loadP1()).levelBottom, 6);
});
it('side-row levels keep their P1 values from the Level underside (assumption, 명세 확인 필요)', () => {
const levels = sideRowLevels(40.911, 42.411);
expect(levels.zBottom).toBeCloseTo(41.288, 6);
expect(levels.zBreak).toBeCloseTo(42.538, 6);
});
it('the coping rebar follows a changed geometry: the rows and the 3D build without error, the positions are unchanged', () => {
const base = rebarSummary(buildPier(loadP1())).rows;
const changed = buildPier({ ...loadP1(), copingH1Mm: 1200, copingH2Mm: 1600, levelWidthMm: 2300 });
const rows = rebarSummary(changed).rows;
expect(rows.find((r) => r.name === 'C10')!.count).toBe(base.find((r) => r.name === 'C10')!.count);
expect(rows.find((r) => r.name === 'C9')!.count).toBe(base.find((r) => r.name === 'C9')!.count);
expect(buildRebar3D(changed).byName.get('C10')!.length).toBeGreaterThan(0);
});
});
describe('C13 in the taper band (h2), C12 in the end band (h1)', () => {
const bands = (input: Parameters<typeof buildPier>[0]) => {
const m = buildPier(input);
const lv = sideRowLevels(m.levelBottom, m.endBottom);
const rows = sideRows(lv.zBottom, lv.zBreak);
return { m, rows };
};
it.each([
['P1', {}],
['h1 800', { copingH1Mm: 800 }],
['h1 1200', { copingH1Mm: 1200 }],
['h2 1800', { copingH2Mm: 1800 }],
])('%s: every C13 row is in the taper band and every C12 row in the end band', (_name, change) => {
const { m, rows } = bands({ ...loadP1(), ...change });
for (const z of rows.c13) {
expect(z).toBeGreaterThanOrEqual(m.levelBottom - 1e-9);
expect(z).toBeLessThan(m.endBottom + SIDE_COVER_M);
}
for (const z of rows.c12) {
expect(z).toBeGreaterThanOrEqual(m.endBottom + SIDE_COVER_M - 1e-9);
expect(z).toBeLessThanOrEqual(m.copingRefTop);
}
});
});
describe('side-row grid (250, 상면 여유 400, 격자 연속)', () => {
const rowsFor = (input: Parameters<typeof buildPier>[0]) => {
const m = buildPier(input);
const lv = sideRowLevels(m.levelBottom, m.endBottom, m.copingRefTop);
return { m, rows: sideRows(lv.zBottom, lv.zBreak, lv.zTop) };
};
it('P1: 5 C13 rows and 2 C12 rows, the top row at or below top − 0.4', () => {
const { m, rows } = rowsFor(loadP1());
expect(rows.c13).toEqual([41.288, 41.538, 41.788, 42.038, 42.288]);
expect(rows.c12).toEqual([42.538, 42.788]);
expect(Math.max(...rows.c12)).toBeLessThanOrEqual(m.copingRefTop - 0.4 + 1e-9);
});
it('taper h2 = 3000: C13 from 39.788 to 42.288 (11 rows), C12 2 rows, no row lost to a fixed count', () => {
const { rows } = rowsFor({ ...loadP1(), copingH2Mm: 3000 });
expect(rows.c13.length).toBe(11);
expect(rows.c13[0]).toBeCloseTo(39.788, 3);
expect(rows.c12).toEqual([42.538, 42.788]);
});
it('the C12 first row is the last C13 row plus the pitch (the grid is continuous)', () => {
for (const h2 of [1000, 1500, 1750, 2000, 3000]) {
const { rows } = rowsFor({ ...loadP1(), copingH2Mm: h2 });
if (rows.c13.length && rows.c12.length) {
expect(rows.c12[0] - rows.c13[rows.c13.length - 1]).toBeCloseTo(0.25, 6);
}
}
});
});