Files
Pier_v1/tests/rebar-coping-main.test.ts
T
ypshimandClaude Haiku 5.5 496e0ea4e5 feat(rebar): 상면 주철근(C1–C6) 이음 무시/반영, 위치 기둥 중심/단부
- 기본 '이음 무시' (사양 결정). '반영'이면 정척(12000) 초과 철근을 l_s 겹침 2조각으로 나눔
- 위치: 기둥 중심 = 전체의 절반, 단부 = 정척 안에서 가장 끝 쪽 (짧은 조각 = 전체 + l_s − 12000, 긴 조각 = 12000)
- 철근표 조각 목록과 비고, 3D 두 조각(l_s 겹침)에 반영
- STANDARD_BAR를 common.ts로 옮겨 순환 import 방지

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-26 16:14:23 +09:00

126 lines
6.2 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 { developmentLength, hookLength, NOMINAL_DIAMETER, spliceLength } from '../src/core/rebar/common';
import { bottomPiece, topBarLength, topRowEl, topSplicePieces, widthLayout } from '../src/core/rebar/coping-main';
import { buildPier } from '../src/core/geometry';
import { rebarSummary } from '../src/core/rebar/summary';
import { DEFAULT_COPING_REBAR } from '../src/core/rebar/coping-settings';
import { loadP1 } from './helpers';
describe('C1–C8 상·하면 주철근 (코핑철근.md §5, plan C3)', () => {
it('nominal diameters: H29 28.6, S32 31.8 (documents use these, not the round numbers)', () => {
expect(NOMINAL_DIAMETER.H29).toBe(28.6);
expect(NOMINAL_DIAMETER.S32).toBe(31.8);
});
it('width layout for B 3100: edge 127, ends 110.5, 21@125, 24 bars (12 + 12)', () => {
const w = widthLayout(3100);
expect(w.yE).toBe(127);
expect(w.S).toBe(2846);
expect(w.n).toBe(21);
expect(w.g).toBe(110.5);
expect(w.count).toBe(24);
expect(w.cyclePerKind).toBe(12);
expect(w.text).toBe('127 · 110.5 · 21@125 · 110.5 · 127');
});
it('주철근 기본간격 input drives the layout: 140 → 19@140 (22 bars, even), 120 → 22@120 (25, odd)', () => {
const w140 = widthLayout(3100, { ...DEFAULT_COPING_REBAR, mainSpacing: 140 });
expect(w140.spacing).toBe(140);
expect(w140.n).toBe(19);
expect(w140.count).toBe(22);
expect(w140.text).toBe(`${w140.yE} · ${w140.g} · 19@140 · ${w140.g} · ${w140.yE}`);
const w120 = widthLayout(3100, { ...DEFAULT_COPING_REBAR, mainSpacing: 120 });
expect(w120.n).toBe(22);
expect(w120.count).toBe(25);
});
it('철근표 간격: P1 기본값은 250 / 125 · 기본값 근거, 기본간격 140이면 280 / 140 · 입력 근거', () => {
const base = rebarSummary(buildPier(loadP1())).rows;
const c1 = base.find((r) => r.name === 'C1')!;
expect([c1.spacing, c1.actual, c1.basis]).toEqual(['250', '125', 'default']);
const c12 = base.find((r) => r.name === 'C12')!;
expect(c12.basis).toBe('default');
const c15 = base.find((r) => r.name === 'C15')!;
expect([c15.spacing, c15.actual, c15.basis]).toEqual(['250', '250', 'default']);
const m = buildPier({ ...loadP1(), copingRebar: { mainSpacing: 140 }, blockRebar: { spacingS1: 130 } });
const rows = rebarSummary(m).rows;
const c8 = rows.find((r) => r.name === 'C8')!;
expect([c8.spacing, c8.actual, c8.basis]).toEqual(['280', '140', 'input']);
expect(rows.find((r) => r.name === 'C12')!.basis).toBe('default');
const c16 = rows.find((r) => r.name === 'C16')!;
expect([c16.spacing, c16.actual, c16.basis]).toEqual(['280', '280', 'input']);
const s1 = rows.find((r) => r.name === 'S1')!;
expect([s1.spacing, s1.actual, s1.basis]).toEqual(['130', '130', 'input']);
});
it('상면 이음 (승인 2026-10-26): 기둥 중심 → total/2 + l_s/2 each; 단부 → total + l_s − 12000 and 12000', () => {
const center = topSplicePieces(12000, 1, 'center')!;
expect(center.lap).toBeGreaterThan(1700);
expect(center.pieces[0]).toBeCloseTo(12744 / 2 + center.lap / 2, 3);
expect(center.pieces[1]).toBeCloseTo(center.pieces[0], 3);
expect(center.pieces[0] + center.pieces[1]).toBeCloseTo(12744 + center.lap, 3);
const end = topSplicePieces(12000, 1, 'end')!;
expect(end.pieces[0]).toBeCloseTo(12744 + end.lap - 12000, 3);
expect(end.pieces[1]).toBeCloseTo(12000, 3);
});
it('상면 이음: a bar within the standard length is not split', () => {
expect(topSplicePieces(10000, 1, 'center')).toBeNull();
});
it('철근표: 이음 무시 → 단일 길이 12744 · 반영(기둥 중심) → 2조각, 각 조각 개수 = 행 개수', () => {
const ignore = rebarSummary(buildPier(loadP1())).rows.find((r) => r.name === 'C1')!;
expect(ignore.length).toBeCloseTo(12744, 0);
expect(ignore.pieces).toBeUndefined();
const applied = rebarSummary(buildPier({ ...loadP1(), topSplice: { mode: 'apply', at: 'center' } })).rows;
const c1 = applied.find((r) => r.name === 'C1')!;
const lap = topSplicePieces(12000, 1, 'center')!.lap;
expect(c1.length).toBeNull();
expect(c1.pieces).toHaveLength(2);
expect(c1.pieces![0].count).toBe(c1.count);
expect(c1.pieces![0].length).toBeCloseTo(12744 / 2 + lap / 2, 3);
expect(c1.pieces![1].length).toBeCloseTo(12744 / 2 + lap / 2, 3);
expect(c1.note).toContain('기둥 중심');
const end = rebarSummary(buildPier({ ...loadP1(), topSplice: { mode: 'apply', at: 'end' } })).rows.find((r) => r.name === 'C1')!;
expect(end.pieces![1].length).toBeCloseTo(12000, 3);
expect(end.note).toContain('단부');
});
it('CP04: the default P1 has an even count (no warning); 기본간격 120 gives an odd count and a CP04 warning', () => {
const base = rebarSummary(buildPier(loadP1())).warnings.filter((w) => w.code === 'CP04');
expect(base).toEqual([]);
const odd = buildPier({ ...loadP1(), copingRebar: { mainSpacing: 120 } });
const warnings = rebarSummary(odd).warnings.filter((w) => w.code === 'CP04');
expect(warnings).toHaveLength(1);
expect(warnings[0].target).toBe('coping');
});
it('heights: 43.284 / 43.184 / 43.084 for the three top rows (상면 단 간격 100)', () => {
expect(topRowEl(43.284, 1)).toBe(43.284);
expect(topRowEl(43.284, 2)).toBe(43.184);
expect(topRowEl(43.284, 3)).toBe(43.084);
});
it('top bar lengths 12744 / 12686.8 / 12629.6 (path + 2 × 472 hook, 가로 단 간격 d = 28.6)', () => {
expect(topBarLength(12000, 1)).toBeCloseTo(12744, 0);
expect(topBarLength(12000, 2)).toBeCloseTo(12686.8, 1);
expect(topBarLength(12000, 3)).toBeCloseTo(12629.6, 1);
});
it('bottom piece ≈ 7448 with l_s = 1717.6 (1.3 × l_d of H29 at fck 27) — the document shows 1717', () => {
const b = bottomPiece();
expect(b.path).toBeCloseTo(12235.6, 6);
expect(b.ls).toBeCloseTo(1717.6, 0);
expect(b.piece).toBeCloseTo(7448, 0);
});
it('hook 472 (16.5 × 28.6) and splice from the same formula as C1', () => {
expect(hookLength('main90', NOMINAL_DIAMETER.H29)).toBeCloseTo(472, 0);
expect(spliceLength(NOMINAL_DIAMETER.H29, 400, 27)).toBeCloseTo(1.3 * developmentLength(NOMINAL_DIAMETER.H29, 400, 27), 9);
});
});