교각·교대 솔플레이트 상세 TABLE에 받침별로 엑셀 방위각과 짝 받침 방향의 차이(초)를 보인다. 10″ 이하 "≤ 10″", 넘으면 WS01, 짝이 없으면 "— (짝 없음)". - BearingSole.azimuthCheckSec (짝의 from/to 쪽에 맞는 값), 표 행 azimuthSec - 테스트: P1 전열 ≤ 0.5″ · 후열 없음, A1 ≤ 0.5″, P1 ③ 방위각 75°59′ 오타 → 약 87″ · WS01 그 받침만 - 브라우저 확인(운곡교 완도·광주 데이터): P1-광주 전열 짝 A1-광주 0.0–0.3″, 후열 짝 P2-광주 0.0–0.1″ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
5.1 KiB
TypeScript
100 lines
5.1 KiB
TypeScript
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 일치');
|
||
expect(rows.every((r) => r.azimuthSec !== undefined && r.azimuthSec <= 0.5)).toBe(true);
|
||
expect(abutSolplateRows(model, rule, H, { slopeI: rec.slopeI })[0].azimuthSec).toBeUndefined();
|
||
});
|
||
|
||
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);
|
||
});
|
||
});
|