현장타설말뚝철근.md §5–§6 (사용자 결정 2026-11-06: 11-06 겹침 결정이 11-05 커플러 회신을 대체). - 희생강관 기본 적용, 상단 지반고 + 2500 (그림 값, Q-PL4 가정). 암반 근입부 지름 입력 추가(기본 D − 150, 강관 없으면 D) - 말뚝 철근망 반지름을 근입부 기준으로 (D 3000: r 1300 / 1268.2) - 두 철근 원이 다르면 자동 겹침이음: 기둥 주철근이 지반고 아래 l_s(1.3 l_d, S32 2387)까지 내려감, 지반고 커플러 없음. 원이 같으면(근입부 없음) 지반고 커플러. 저장된 방식은 그대로 둠 - 겹침 구간의 말뚝 띠철근은 바깥 기둥 주철근을 감싸는 반지름 - 겹침 모드에서도 주기 커플러(기둥 지반고 + @3000, 말뚝 지반고 − @3000, 내측 + 150) - RB-PL07: 두 원 간격 > min(l_s/5, 150)이면 경고 - 이전 '겹침부 미계산' 표시 제거. 기존 커플러 테스트는 희생강관 미적용 입력으로 유지 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
5.1 KiB
TypeScript
77 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { castShape, castShapeIssues, castLowerZone } from '../src/core/cast-in-place-shape';
|
||
import { castRebarDefaults } from '../src/core/rebar/cast-in-place';
|
||
import { buildPier } from '../src/core/geometry';
|
||
import { changedKeys } from '../src/core/changes';
|
||
import { parseProject, serializeProject, SCHEMA_VERSION, type ProjectFile } from '../src/core/project';
|
||
import { loadP1 } from './helpers';
|
||
|
||
describe('2026-11-05 cast-in-place drawing and user decisions', () => {
|
||
const input = { ...loadP1(), foundationType: '현장타설말뚝' as const, groundEl: 30, footingBottomEl: 10, pileLengthMm: 24000 };
|
||
it('uses ground level for the pile head and ground minus length for its tip', () => {
|
||
const model = buildPier(input);
|
||
expect(model.piles[0].top).toBe(30);
|
||
expect(model.piles[0].bottom).toBe(6);
|
||
expect(model.columns[0].bottom).toBe(30);
|
||
expect(model.footings).toEqual([]);
|
||
expect(model.derived.soilCover).toBe(0);
|
||
});
|
||
it('uses the clarified positive rock offsets, 9000 mm beta and casing defaults', () => {
|
||
expect(castShape(input)).toMatchObject({ pileBottomEl: 6, betaLengthMm: 9000, betaEl: 21, weatheredRockEl: 11, bedrockEl: 7, casingOn: true, casingTopEl: 32.5, casingBottomEl: 7, clayBottomEl: undefined, socketDiameterMm: 1850 });
|
||
// Without a casing there is no rock socket: D.
|
||
expect(castShape({ ...input, castInPlaceShape: { casingOn: false } }).socketDiameterMm).toBe(2000);
|
||
});
|
||
it('rejects a pile head above the coping instead of generating an inverted column', () => {
|
||
expect(() => buildPier({ ...input, groundEl: 100 })).toThrow('지반고');
|
||
});
|
||
it('rejects zero or negative cast-in-place pile length', () => {
|
||
for (const pileLengthMm of [0, -1]) expect(() => buildPier({ ...input, pileLengthMm })).toThrow('말뚝 길이');
|
||
});
|
||
it('keeps automatic values linked to geometry changes and manual overrides fixed', () => {
|
||
const auto = castShape({ ...input, groundEl: 40 });
|
||
expect(auto.bedrockEl).toBe(17);
|
||
expect(auto.casingBottomEl).toBe(17);
|
||
expect(auto.casingTopEl).toBe(42.5);
|
||
const manual = castShape({ ...input, groundEl: 40, castInPlaceShape: { bedrockEl: -2, casingTopEl: 35, weatheredRockEl: 3 } });
|
||
expect(manual.casingBottomEl).toBe(-2);
|
||
expect(manual.casingTopEl).toBe(35);
|
||
expect(manual.weatheredRockEl).toBe(3);
|
||
});
|
||
it('requires independent clay EL and reports reversed lower zone instead of silently swapping it', () => {
|
||
expect(castLowerZone(input)).toBeNull();
|
||
expect(castLowerZone({ ...input, castInPlaceShape: { clayBottomEl: 16 } })).toEqual({ bottomEl: 15, topEl: 18, valid: true });
|
||
expect(castLowerZone({ ...input, castInPlaceShape: { clayBottomEl: 4 } })?.valid).toBe(false);
|
||
});
|
||
it('inherits both round-column main layers, hoop spacing, cover convention and couplers', () => {
|
||
const s = castRebarDefaults(input);
|
||
expect(s.column).toMatchObject({ mainDiameterMm: 31.8, innerMainDiameterMm: 31.8, mainCount: 46, innerMainCount: 46, coverMm: 125, coverReference: 'main-center', tieSpacingMm: 300, endZoneSpacingMm: 120, tieKind: 'hoop' });
|
||
expect(s.pile?.endCoverMm).toBe(250);
|
||
// Rock socket D − 150 → the cages differ → lap of l_s = 1.3·l_d (S32, fy 500, fck 27) = 2387 (Q-PL3·6).
|
||
expect(s.connection).toEqual({ mode: 'lap', couplerLengthMm: 150, lapLengthMm: 2387 });
|
||
expect(castRebarDefaults({ ...input, castInPlaceShape: { casingOn: false } }).connection).toEqual({ mode: 'coupler', couplerLengthMm: 150 });
|
||
});
|
||
it('preserves old explicit clear cover and independent edits', () => {
|
||
const s = castRebarDefaults({ ...input, columnRebar: { nDiv: 48, c: 140 }, castInPlaceRebar: { column: { coverMm: 100, mainCount: 44 }, pile: { endCoverMm: 300 } } });
|
||
expect(s.column?.coverReference).toBe('clear');
|
||
expect(s.column?.coverMm).toBe(100);
|
||
expect(s.column?.mainCount).toBe(44);
|
||
expect(s.pile?.coverMm).toBe(140);
|
||
expect(s.pile?.innerMainCount).toBe(48);
|
||
expect(s.pile?.endCoverMm).toBe(300);
|
||
});
|
||
it('persists explicit shape values without turning auto defaults into fixed values', () => {
|
||
const next = { ...input, castInPlaceShape: { casingOn: true, clayBottomEl: 16, betaLengthMm: 10000 } };
|
||
const p: ProjectFile = { schemaVersion: SCHEMA_VERSION, project: { name: '', bridge: '', client: '', author: '', crs: '', memo: '' }, source: { fileName: '', sheets: [] }, history: [], piers: [{ id: 'P1', input: next, defaults: input, draft: next }] };
|
||
expect(parseProject(serializeProject(p))).toEqual(p);
|
||
expect(changedKeys(input, next)).toEqual(['castInPlaceShape']);
|
||
const invalid = JSON.parse(serializeProject(p));
|
||
invalid.piers[0].draft.castInPlaceShape.betaLengthMm = 0;
|
||
expect(() => parseProject(JSON.stringify(invalid))).toThrow('현장타설');
|
||
});
|
||
it('permits negative elevations but rejects nonfinite elevations and invalid switches', () => {
|
||
expect(castShapeIssues({ bedrockEl: -20, casingBottomEl: -30 })).toEqual([]);
|
||
expect(castShapeIssues({ casingOn: 1 })).toHaveLength(1);
|
||
expect(castShapeIssues({ clayBottomEl: Infinity })).toHaveLength(1);
|
||
});
|
||
});
|