C12 and C13 come from one grid from the Level underside + 127 at 250 mm, up to top − 400 mm (상면 여유). The fixed count of seven rows is kept only for callers that give no top. P1 is unchanged (C12 5, C13 2); a taper of 3000 mm now gives C12 11 and C13 2, and the C13 first row is always the last C12 row plus 250. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
116 lines
5.4 KiB
TypeScript
116 lines
5.4 KiB
TypeScript
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('C12 in the taper band (h2), C13 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 C12 row is in the taper band and every C13 row in the end band', (_name, change) => {
|
||
const { m, rows } = bands({ ...loadP1(), ...change });
|
||
for (const z of rows.c12) {
|
||
expect(z).toBeGreaterThanOrEqual(m.levelBottom - 1e-9);
|
||
expect(z).toBeLessThan(m.endBottom + SIDE_COVER_M);
|
||
}
|
||
for (const z of rows.c13) {
|
||
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 C12 rows and 2 C13 rows, the top row at or below top − 0.4', () => {
|
||
const { m, rows } = rowsFor(loadP1());
|
||
expect(rows.c12).toEqual([41.288, 41.538, 41.788, 42.038, 42.288]);
|
||
expect(rows.c13).toEqual([42.538, 42.788]);
|
||
expect(Math.max(...rows.c13)).toBeLessThanOrEqual(m.copingRefTop - 0.4 + 1e-9);
|
||
});
|
||
it('taper h2 = 3000: C12 from 39.788 to 42.288 (11 rows), C13 2 rows, no row lost to a fixed count', () => {
|
||
const { rows } = rowsFor({ ...loadP1(), copingH2Mm: 3000 });
|
||
expect(rows.c12.length).toBe(11);
|
||
expect(rows.c12[0]).toBeCloseTo(39.788, 3);
|
||
expect(rows.c13).toEqual([42.538, 42.788]);
|
||
});
|
||
it('the C13 first row is the last C12 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.c12.length && rows.c13.length) {
|
||
expect(rows.c13[0] - rows.c12[rows.c12.length - 1]).toBeCloseTo(0.25, 6);
|
||
}
|
||
}
|
||
});
|
||
});
|