Files
Pier_v1/tests/abut-bearing-table.test.ts
T
ypshimandClaude Opus 5.5 87b445965b feat(abut): 교대 솔플레이트도 2026-11-06 결정으로 (짝 받침 S · T 하나 · 거더 하면 고정)
교대 받침 카드(spec_abut §5, QA-15)를 교각과 같은 계산(sole-plate.ts)으로 바꾸고 C안 모듈을 정리한다.
- S: 짝 받침(A1 ↔ P1 전열, A2 ↔ Pn) 계산, 없으면 교대 종단경사 i(엑셀 Abut). 직접 수정 가능
- L = A + 100, W = B + 100 (A × B 기본 700 × 640, P1 전열 값 가정), 받침 1열이라 T 하나
- h3 기본값 = 받침 시트 받침높이 − T_ref (A1 275 − 30 = 245): 엑셀 받침높이의 거더 하면을 지킴.
  시안은 P1의 h3 181을 가정했으나 교대 시트에 받침높이가 있어 이렇게 둔다 (입력으로 바꿀 수 있음)
- 블럭 콘크리트 = 블럭 높이 − 패드 − (T − T_ref), E는 그대로
- 입력 칸: h3 · T_ref · T_min · 여유 · A · B · T · S (h3·T·S는 비우면 자동). C안 값(lengthMm 등)은 무시
- A1 검증: S −1.908 … −1.902 % (짝 P1 전열), L 800 · W 740 · T 30 · T1 38 · T2 22, E 400
- src/core/solplate-rule.ts(C안)와 그 테스트 삭제, 솔플레이트 기하 테스트는 새 계획으로

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

98 lines
4.9 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 { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as XLSX from 'xlsx';
import { describe, expect, it } from 'vitest';
import { buildAbut } from '../src/core/abut/geometry';
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
import { buildMembers } from '../src/core/abut/members';
import {
ABUT_BEARING_RULE_DEFAULT,
abutBearingChecks,
abutElevationRows,
abutSolplateRows,
resolveBearingRule,
} from '../src/core/abut/bearing-table';
import { applyBearingRule } from '../src/core/abut/edit';
import { checkStoredAbutments } from '../src/core/project';
import { p1Pairs } from './helpers';
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
const input = toAbutInput(rec);
const model = buildAbut(input);
const members = buildMembers(model, { bottomEl: input.foundationBottomEl, pileLengthMm: input.pileLengthMm });
const rule = resolveBearingRule();
const H = rec.bearingHeightMm;
// Bridge context as in the app: A1 ①–⑤ pair with P1 전열 ①–⑤ (2026-11-06), 교대 종단경사 i from the sheet.
const ctx = { pairs: p1Pairs(), slopeI: rec.slopeI };
describe('교대 받침 카드 (솔플레이트 결정 2026-11-06, h3 = 받침높이 − T_ref)', () => {
it('defaults: A 700 × B 640 + 100, T_ref 30, T_min 22; H 275 from the 교대 sheet → h3 245', () => {
expect(ABUT_BEARING_RULE_DEFAULT).toMatchObject({ tRefMm: 30, tMinMm: 22, marginMm: 100, aMm: 700, bMm: 640 });
expect(H).toBe(275);
const [r] = abutSolplateRows(model, rule, H, ctx);
expect([r.L, r.W, r.T, r.T1, r.T2]).toEqual([800, 740, 30, 38, 22]);
expect(abutElevationRows(model, rule, H, ctx)[0].h3).toBe(245);
});
it('ELEVATION: block 125 = 75 + 50, E = 75 + 50 + 245 + 30 = 400 (the girder underside of the sheet)', () => {
const rows = abutElevationRows(model, rule, H, ctx);
expect(rows).toHaveLength(5);
for (const r of rows) {
expect([r.h1, r.h2, r.h3, r.h4, r.E]).toEqual([75, 50, 245, 30, 400]);
expect(r.solplateTopEl).toBeCloseTo(r.seatEl + 0.4, 6);
}
const first = rows.find((r) => r.no === 1)!;
expect(first.seg).toBe('①');
expect(first.seatEl).toBeCloseTo(44.367, 3);
expect(first.z).toBeCloseTo(44.492, 3);
});
it('솔플레이트 TABLE: S from the P1 pair (−1.908 %, 계산), T1 38 on the 오르막 (A1) side; W08 within 1 mm', () => {
const rows = abutSolplateRows(model, rule, H, ctx);
expect(rows.map((r) => r.S.toFixed(3))).toEqual(['-1.908', '-1.906', '-1.904', '-1.904', '-1.902']);
expect(rows.every((r) => r.source === '계산' && r.pair?.startsWith('P1-'))).toBe(true);
expect(rows[0].check).toBe('16 ≈ 15.3 일치');
});
it('without a pair the 교대 종단경사 i (−0.0191) is used: S −1.91 % (입력), T 30', () => {
const [r] = abutSolplateRows(model, rule, H, { slopeI: rec.slopeI });
expect(r.source).toBe('입력');
expect(r.S).toBeCloseTo(-1.91, 9);
expect(r.T).toBe(30);
});
it('S typed −3 % → T 34; the block concrete loses 4 mm and E stays 400 (거더 하면 고정)', () => {
const steep = resolveBearingRule({ slopePct: -3 });
expect(abutSolplateRows(model, steep, H, ctx)[0]).toMatchObject({ T: 34, T1: 46, T2: 22, source: '수정됨' });
const r = abutElevationRows(model, steep, H, ctx)[0];
expect([r.h1, r.h4, r.E]).toEqual([71, 34, 400]);
});
it('checks: W02, W04 (흉벽 ≈ 145 · 전면 255), W03 and I01 all clear for A1', () => {
const lines = abutBearingChecks(model, members, rule, H, ctx);
expect(lines).toHaveLength(4);
expect(lines[0]).toContain('W02 없음');
// 시안 145 is bearing ① (lateral 0.745); the smallest gap over the five bearings is 144 (lateral 0.744).
expect(lines[1]).toMatch(/흉벽 14[45]/);
expect(lines[1]).toContain('전면 255');
expect(lines[1]).toContain('W04 없음');
expect(lines[2]).toContain('W03 없음');
expect(lines[3]).toContain('I01 없음');
});
it('the edit keeps only changed values, clears optional ones, refuses non-positive sizes and h3 ≤ 0, and the project file accepts it', () => {
const edited = applyBearingRule(applyBearingRule(rec, 'aMm', 650), 'tMm', 32);
expect(edited.bearingRule).toEqual({ aMm: 650, tMm: 32 });
expect(applyBearingRule(edited, 'tMm', undefined).bearingRule).toEqual({ aMm: 650 });
expect(() => applyBearingRule(rec, 'aMm', 0)).toThrow('0보다');
expect(() => applyBearingRule(rec, 'tRefMm', 300)).toThrow('h3');
expect(() => checkStoredAbutments([edited])).not.toThrow();
expect(() => checkStoredAbutments([{ ...edited, bearingRule: { aMm: 'x' } }])).toThrow('받침 규칙');
});
it('values stored by the 2026-11-03 C안 (lengthMm, widthMm) are ignored', () => {
expect(resolveBearingRule({ lengthMm: 900, widthMm: 1000 } as Record<string, unknown>)).toEqual(ABUT_BEARING_RULE_DEFAULT);
});
});