Files
Pier_v1/tests/bearing-pair.test.ts
T
ypshimandClaude Opus 5.5 a0b0929e13 feat(bearing): 교각 솔플레이트를 2026-11-06 결정으로 (짝 받침 S · 열 T · 거더 하면 고정)
받침방위각_솔플레이트검토.md §3과 시안(Version 110–113)을 따른다 (사용자 결정 2026-11-06: 시안 적용).
11-03 C안(받침높이 H 고정, h3 = H − T, S 입력)을 대체한다.
- S: 교량 전체(A1, P1 … Pn, A2) 거더 짝 받침의 z 차 / 수평거리. 없으면 교각 종단경사 입력 →
  다른 열 같은 순번 값(가정, WS03) → 0 %(WS03). 받침별 직접 수정(수정됨)
- 평면 L = A + 100, W = B + 100 (받침별 A × B, 기본 700 × 640 가정), h3 181
- T = 열마다 max(22, ⌈22 + L·|S|/2⌉), 열 T 직접 수정 가능(T2 < T_min → WS02).
  T1은 오르막 쪽(3D 쐐기의 두꺼운 끝도 그쪽), T2 반대쪽
- 거더 하면 고정: 블럭 상면 = z − (T − T_ref), T_ref 30
- 짝 받침은 불러오기·프로젝트 열기 때 교량 전체로 계산해 교각 생성에 넘김
- ② 카드: h3·T_ref·T_min·여유·열 T·종단경사 입력, 표에 짝 받침·S 근거·받침별 S/A/B 수정·검산 W08·검사
- P1 검증(A1과 함께): S −1.908 … −1.902 %, L 800 · W 740 · T 30 · T1 38 · T2 22, 블럭 높이 변화 없음
- 블럭·솔플레이트 테스트는 A1–P1 짝 문맥으로 실행 (짝이 없으면 S 0 · T 22 · 블럭 +8 mm)
교대(QA-15 C안)와 Pier 시트 종단경사 열 읽기는 아직 남음.

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

80 lines
4.4 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 { parseAbutments } from '../src/core/abut/parse';
import { girderPairs, pairOf, type PairStructure, soleRowThickness } from '../src/core/bearing-pair';
import { loadP1 } from './helpers';
// RAG/받침방위각_솔플레이트검토.md §2 · §6 (Q-SH1·4·8·9). A1 from the RAG workbook's Abut sheet, P1 from the same input.
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
const a1 = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
const A1: PairStructure = { name: 'A1', bearings: a1.bearings.map((b) => ({ no: b.no, x: b.pos.e, y: b.pos.n, z: b.z, azimuthDeg: b.azimuthDeg! })) };
const p1 = loadP1();
const P1: PairStructure = { name: 'P1', bearings: p1.bearings.map((b) => ({ no: b.no, x: b.x, y: b.y, z: b.z, azimuthDeg: b.azimuthDeg })) };
describe('거더 짝 받침 · 종단경사 S (Q-SH1·4·5)', () => {
const pairs = girderPairs([A1, P1]);
it('pairs A1 ①–⑤ with P1 ①–⑤ on the girder line (offset 0.000 m, azimuth difference ≤ 0.1″)', () => {
expect(pairs.map((p) => [p.from.no, p.to.no])).toEqual([[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]);
for (const p of pairs) {
expect(p.offsetM).toBeLessThan(0.0005);
expect(p.fromCheckSec).toBeLessThanOrEqual(0.5);
expect(p.distanceM).toBeCloseTo(48.791, 3);
expect(p.warnings).toEqual([]);
}
});
it('S = −1.908 / −1.906 / −1.904 / −1.904 / −1.902 % (상세도 −1.910 … −1.901, ≤ 0.002 %), A1 is the 오르막 쪽', () => {
expect(pairs.map((p) => p.slopePct.toFixed(3))).toEqual(['-1.908', '-1.906', '-1.904', '-1.904', '-1.902']);
const drawing = [-1.910, -1.907, -1.905, -1.903, -1.901];
pairs.forEach((p, i) => expect(Math.abs(p.slopePct - drawing[i])).toBeLessThanOrEqual(0.0025));
expect(pairs.every((p) => p.uphill === 'A1')).toBe(true);
});
it('P1 back row ⑥–⑩ (next span, P2 not in the workbook) has no pair', () => {
for (const no of [6, 7, 8, 9, 10]) expect(pairOf(pairs, 'P1', no)).toBeUndefined();
expect(pairOf(pairs, 'P1', 3)!.from).toEqual({ structure: 'A1', no: 3 });
});
it('WS01: P1 ③ azimuth typed as 75°59′ (CAD) is kept but flagged', () => {
const typo: PairStructure = { ...P1, bearings: P1.bearings.map((b) => (b.no === 3 ? { ...b, azimuthDeg: 90 - (75 + 59 / 60) } : b)) };
const p = girderPairs([A1, typo]).find((q) => q.to.no === 3)!;
expect(p.warnings).toContain('WS01');
expect(p.toCheckSec).toBeGreaterThan(10);
expect(girderPairs([A1, typo]).filter((q) => q.to.no !== 3).every((q) => !q.warnings.includes('WS01'))).toBe(true);
});
it('WS04: two candidates within tolerance → the closest to the girder line is used', () => {
const b1 = P1.bearings[0];
const extra = { ...b1, no: 99, x: b1.x + 0.05 * Math.cos(b1.azimuthDeg * Math.PI / 180), y: b1.y - 0.05 * Math.sin(b1.azimuthDeg * Math.PI / 180) };
const p = girderPairs([A1, { ...P1, bearings: [...P1.bearings, extra] }]).find((q) => q.from.no === 1)!;
expect(p.warnings).toContain('WS04');
expect(p.to.no).toBe(1);
});
});
describe('솔플레이트 열 두께 (Q-SH3·8·9, L 800)', () => {
it('S 1.908 % → T 30, T1 38 / T2 22 (P1 상세도)', () => {
expect(soleRowThickness([{ lengthMm: 800, slopePct: -1.908 }])).toEqual({ T: 30, ends: [{ T1: 38, T2: 22 }] });
});
it('S 3 % → T 34, T1 46 / T2 22; S 0 → T 22', () => {
expect(soleRowThickness([{ lengthMm: 800, slopePct: 3 }])).toEqual({ T: 34, ends: [{ T1: 46, T2: 22 }] });
expect(soleRowThickness([{ lengthMm: 800, slopePct: 0 }]).T).toBe(22);
});
it('one T per row: the largest requirement wins, the flatter bearing gets a thicker T2', () => {
const row = soleRowThickness([{ lengthMm: 800, slopePct: 3 }, { lengthMm: 800, slopePct: 1 }]);
expect(row.T).toBe(34);
expect(row.ends[1]).toEqual({ T1: 38, T2: 30 });
});
});
describe('교량 전체 짝 받침 (bridgePairs, 구조물 순서 A1 · P1 … · A2)', () => {
it('builds the same A1 ↔ P1 pairs from the loaded records', async () => {
const { bridgePairs } = await import('../src/core/bridge-pairs');
const pairs = bridgePairs([p1], [a1]);
expect(pairs.map((p) => `${p.from.structure}${p.from.no}-${p.to.structure}${p.to.no}`)).toEqual(['A11-P11', 'A12-P12', 'A13-P13', 'A14-P14', 'A15-P15']);
});
});