Files
Pier_v1/tests/abut-wings.test.ts
ypshimandClaude Haiku 5.5 5e64a4f2ed feat(abut): 벽체 높이 H_w = 받침 ① 교좌면 ~ 흉벽 상단 연직 (기본 3326)
- 사용자 도면(2026-11-04): 받침 ① 위치에서 받침 ① 교좌면부터 흉벽 상단까지 연직 거리. 모든 교대 기본값 3326 (QA-4 가정 2500 대체)
- 입력 이름 "벽체 높이 · 받침 ① 기준 연직", ② 정면도에 받침 ① 기준선과 H_w 치수
- ③ 흉벽 철근표 대표 단면 = 받침 ① 위치 (P1–P3는 그 단면에서 정확히 계산)
- 테스트: 흉벽 상단 IP1 48.006 / IP3 47.661 등 + 0.826 반영, 교대흉벽철근.md §11 재현은 2500을 넣어 유지, 3326 기본 확인 (P1 3945)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-04 17:20:05 +09:00

168 lines
7.3 KiB
TypeScript
Raw Permalink 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 { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as XLSX from 'xlsx';
import { describe, expect, it } from 'vitest';
import { buildAbut, type AbutInput } from '../src/core/abut/geometry';
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
import { buildMembers } from '../src/core/abut/members';
import { buildWings, WING_LENGTH_M } from '../src/core/abut/wings';
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
const a1Input = toAbutInput(parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0]);
const SKEW = 95 + 2 / 60 + 52 / 3600;
const GIRDER = 75 + 56 / 60 + 55.9 / 3600;
// A2 합성 예제 (spec §10.2): ① 쪽이 IP1 끝.
const a2Input: AbutInput = {
name: 'A2',
type: 'A2',
seatEl: 44.367,
ip1: { e: 179161.0318, n: 259932.3259 },
ip2: { e: 179161.0417, n: 259932.3276 },
ip3: { e: 179173.0573, n: 259930.2859 },
bearings: [
{ no: 1, pos: { e: 179161.9111, n: 259931.422 }, z: 44.492 },
{ no: 2, pos: { e: 179164.3854, n: 259931.0021 }, z: 44.563 },
{ no: 3, pos: { e: 179166.8597, n: 259930.5821 }, z: 44.634 },
{ no: 4, pos: { e: 179169.334, n: 259930.1621 }, z: 44.705 },
{ no: 5, pos: { e: 179171.8084, n: 259929.7421 }, z: 44.776 },
],
skewDeg: SKEW,
slopeI: -0.0191,
girderAzimuthDeg: GIRDER,
foundationBottomEl: 40.895,
pileLengthMm: 21600,
};
describe('교대 A2 합성 · 날개벽 (AA3b, spec_abut §10.2)', () => {
const model = buildAbut(a2Input);
const members = buildMembers(model, { bottomEl: 40.895, pileLengthMm: 21600 });
const [left, right] = buildWings(model, members);
it('좌 날개벽 (① 쪽, IP1 끝): top 47.664 at the start, 47.540 at the end, bottom end 46.040', () => {
expect(left.side).toBe('left');
expect(left.sEnd).toBeCloseTo(0, 9);
expect(left.topStartEl).toBeCloseTo(47.664, 2);
expect(left.topEndEl).toBeCloseTo(47.540, 2);
expect(left.bottomEndEl).toBeCloseTo(46.040, 2);
});
it('우 날개벽 (⑤ 쪽, IP3 끝): top 48.009 → 47.885, bottom end 46.385', () => {
expect(right.side).toBe('right');
expect(right.sEnd).toBeCloseTo(model.length, 9);
expect(right.topStartEl).toBeCloseTo(48.009, 2);
expect(right.topEndEl).toBeCloseTo(47.885, 2);
expect(right.bottomEndEl).toBeCloseTo(46.385, 2);
});
it('the wing is 6.5 m long along the wing line and 0.45 m thick inward', () => {
const [p0, p1] = left.corners;
expect(Math.hypot(p1.e - p0.e, p1.n - p0.n)).toBeCloseTo(WING_LENGTH_M, 6);
const [, , q2, q3] = left.corners;
expect(Math.hypot(q2.e - p1.e, q2.n - p1.n)).toBeCloseTo(0.45, 6);
expect(Math.hypot(q3.e - p0.e, q3.n - p0.n)).toBeCloseTo(0.45, 6);
});
it('the wing starts at the axis end with the wing direction on the 배면 side (AA2 line 75.32°)', () => {
const [p0, p1] = left.corners;
const deg = (Math.atan2(p1.n - p0.n, p1.e - p0.e) * 180) / Math.PI;
const cad = ((deg % 360) + 360) % 360;
expect(cad).toBeCloseTo(75.32, 1);
});
it('a 단축형 wing bottom sits at the footing top (41.995)', () => {
const short = buildWings(model, members, { left: 'short', right: 'none' });
expect(short).toHaveLength(1);
expect(short[0].bottomEndEl).toBeCloseTo(41.995, 3);
});
it('no wing when the side is 없음', () => {
expect(buildWings(model, members, { left: 'none', right: 'none' })).toEqual([]);
});
});
describe('교대 A1 날개벽 (운곡교P1입력.xlsx)', () => {
const model = buildAbut(a1Input);
const members = buildMembers(model, { bottomEl: a1Input.foundationBottomEl, pileLengthMm: a1Input.pileLengthMm });
const wings = buildWings(model, members);
it('A1 has two general wings; ① 쪽 wing starts at the IP3 end (the bearing ① is near IP3)', () => {
expect(wings.map((w) => w.side)).toEqual(['left', 'right']);
const left = wings[0];
expect(left.sEnd).toBeCloseTo(model.length, 9);
expect(left.topStartEl).toBeCloseTo(47.661, 2);
// A1 날개벽 경사는 +1.91 % (벽체에서 멀어질수록 높아짐): 끝이 0.124 m 높다.
expect(left.topEndEl - left.topStartEl).toBeCloseTo(0.124, 2);
});
});
describe('교대 날개벽 하단 선 · 헌치 (AA3c, spec_abut §4.7)', () => {
const a1 = buildAbut(a1Input);
const a1m = buildMembers(a1, { bottomEl: a1Input.foundationBottomEl, pileLengthMm: a1Input.pileLengthMm });
const [a1Left] = buildWings(a1, a1m);
const a2 = buildAbut(a2Input);
const a2m = buildMembers(a2, { bottomEl: 40.895, pileLengthMm: 21600 });
const [a2Left] = buildWings(a2, a2m);
it('뒷굽 끝까지의 날개벽 거리 1.807 m (단축형 표와 같다)', () => {
expect(a1Left.heelDistanceM).toBeCloseTo(1.807, 2);
expect(a2Left.heelDistanceM).toBeCloseTo(1.807, 2);
});
it('하단선: 뒷굽 끝까지 저판 상면 41.995, 그 밖은 단부 하단까지 직선 (A2 좌: 46.040)', () => {
const line = a2Left.bottomLine;
expect(line[0].el).toBeCloseTo(41.995, 3);
expect(line[1].dM).toBeCloseTo(1.807, 2);
expect(line[1].el).toBeCloseTo(41.995, 3);
expect(line[2].el).toBeCloseTo(46.040, 2);
});
it('two vertical 300 × 300 haunches at the 내측면 corners: upper at the 흉벽 배면, lower at the 벽체 배면', () => {
expect(a2Left.haunches.map((h) => h.kind)).toEqual(['upper', 'lower']);
for (const h of a2Left.haunches) {
const [c0, c1, c2] = h.corners;
expect(Math.hypot(c1.e - c0.e, c1.n - c0.n)).toBeCloseTo(0.3, 6);
expect(Math.hypot(c2.e - c0.e, c2.n - c0.n)).toBeCloseTo(0.3, 6);
}
});
it('the 저판 상면 haunch runs from the 벽체 배면 to the 뒷굽 on the 저판 상면; the 뒷굽 haunch is gone', () => {
const f = a2Left.footingHaunch!;
expect(f.el).toBeCloseTo(41.995, 3);
expect(f.dMTo).toBeGreaterThan(f.dMFrom);
expect((a2Left as { heelHaunch?: unknown }).heelHaunch).toBeUndefined();
});
it('upper haunch runs from the bracket top (흉벽 상단 − 0.85) to the wing top', () => {
const [up, low] = a2Left.haunches;
// The upper haunch rests on the highest point of the 브라켓 top over its footprint (the top falls along s), never below it.
expect(up.zBottomEl).toBeGreaterThanOrEqual(47.664 - 0.85 - 1e-6);
expect(up.zBottomEl).toBeLessThan(47.664 - 0.85 + 0.05);
expect(up.zTopEl).toBeGreaterThan(up.zBottomEl);
expect(low.zBottomEl).toBeCloseTo(41.995, 3);
expect(low.zTopEl).toBeCloseTo(47.664 - 0.85, 2);
});
});
describe('날개벽 수평 단 (QA-12 초안 적용, spec_abut §4.7)', () => {
const a2 = buildAbut(a2Input);
const a2m = buildMembers(a2, { bottomEl: 40.895, pileLengthMm: 21600 });
const [a2Left] = buildWings(a2, a2m);
it('등단면 down to 1.5 m below the wing end, then 변화단면1 to the seat EL and 변화단면2 to the footing top', () => {
const [equal, v1, v2] = a2Left.bands;
expect(equal.name).toBe('등단면');
expect(equal.zBottomEl).toBeCloseTo(46.040, 2);
expect(v1.name).toBe('변화단면1');
expect(v1.zBottomEl).toBeCloseTo(44.367, 3);
expect(v2.name).toBe('변화단면2');
expect(v2.zBottomEl).toBeCloseTo(41.995, 3);
});
it('bands are contiguous from the wing top to the footing top', () => {
const b = buildWings(a2, a2m)[0].bands;
for (let i = 1; i < b.length; i++) expect(b[i].zTopEl).toBeCloseTo(b[i - 1].zBottomEl, 6);
expect(b[b.length - 1].zBottomEl).toBeCloseTo(41.995, 3);
});
});