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>
This commit is contained in:
1 parent
a0b0929e13
commit
87b445965b
7 files changed
+184
-205
No files matched your search
@@ -9,12 +9,12 @@ import {
|
||||
ABUT_BEARING_RULE_DEFAULT,
|
||||
abutBearingChecks,
|
||||
abutElevationRows,
|
||||
abutSolplatePlan,
|
||||
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];
|
||||
@@ -23,20 +23,23 @@ 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('교대 받침 카드 (교각 솔플레이트 규칙, 받침높이 H = 교대 시트 값)', () => {
|
||||
it('the rule defaults are the pier rule: 1000 × 1100 · −0.5 %, H 275 from the 교대 sheet', () => {
|
||||
expect(ABUT_BEARING_RULE_DEFAULT).toMatchObject({ lengthMm: 1000, widthMm: 1100, slopePct: -0.5 });
|
||||
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);
|
||||
// T = ⌈22 + 1000·0.5/200⌉ = 25, h3 = 275 − 25 = 250, T1/T2 = 25 ± 2.5
|
||||
expect(abutSolplatePlan(rule, H)).toMatchObject({ T: 25, h3: 250, T1: 27.5, T2: 22.5 });
|
||||
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 + 250 + 25 = 400', () => {
|
||||
const rows = abutElevationRows(model, rule, H);
|
||||
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, 250, 25, 400]);
|
||||
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)!;
|
||||
@@ -45,21 +48,29 @@ describe('교대 받침 카드 (교각 솔플레이트 규칙, 받침높이 H =
|
||||
expect(first.z).toBeCloseTo(44.492, 3);
|
||||
});
|
||||
|
||||
it('솔플레이트 TABLE: L 1000, W 1100, S −0.5 %, T 25, T1 27.5 / T2 22.5, check 5.0 = 5.0', () => {
|
||||
const [r] = abutSolplateRows(model, rule, H);
|
||||
expect([r.L, r.W, r.S, r.T, r.T1, r.T2]).toEqual([1000, 1100, -0.5, 25, 27.5, 22.5]);
|
||||
expect(r.check).toBe('5.0 = 5.0');
|
||||
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('the rule inputs change the plan: S −1.91 gives T 32 and h3 243', () => {
|
||||
const steep = resolveBearingRule({ slopePct: -1.91 });
|
||||
// T = ⌈22 + 1000·1.91/200⌉ = ⌈31.55⌉ = 32
|
||||
expect(abutSolplatePlan(steep, H)).toMatchObject({ T: 32, h3: 243 });
|
||||
expect(abutElevationRows(model, steep, H)[0].E).toBe(75 + 50 + 275);
|
||||
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);
|
||||
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).
|
||||
@@ -70,12 +81,17 @@ describe('교대 받침 카드 (교각 솔플레이트 규칙, 받침높이 H =
|
||||
expect(lines[3]).toContain('I01 없음');
|
||||
});
|
||||
|
||||
it('the rule edit keeps only changed values, refuses L 0 and a rule thicker than H, and the project file accepts it', () => {
|
||||
const edited = applyBearingRule(applyBearingRule(rec, 'lengthMm', 900), 'widthMm', 1000);
|
||||
expect(edited.bearingRule).toEqual({ lengthMm: 900, widthMm: 1000 });
|
||||
expect(() => applyBearingRule(rec, 'lengthMm', 0)).toThrow('0보다');
|
||||
expect(() => applyBearingRule(rec, 'slopePct', 60)).toThrow('받침높이');
|
||||
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: { lengthMm: 'x' } }])).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);
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SOLPLATE_RULE_DEFAULT, solplateCenterT, solplatePlan, solplateRuleIssues } from '../src/core/solplate-rule';
|
||||
|
||||
describe('솔플레이트 규칙 (C안, 2026-11-03)', () => {
|
||||
it('default 211 · 1000 × 1100 · −0.5 %: T = 25, T1 = 27.5, T2 = 22.5, h3 = 211 − 25 = 186', () => {
|
||||
const p = solplatePlan(SOLPLATE_RULE_DEFAULT);
|
||||
expect(p.T).toBe(25);
|
||||
expect(p.T1).toBeCloseTo(27.5, 9);
|
||||
expect(p.T2).toBeCloseTo(22.5, 9);
|
||||
expect(p.h3).toBe(186);
|
||||
expect(p.L).toBe(1000);
|
||||
expect(p.W).toBe(1100);
|
||||
});
|
||||
|
||||
it('the centre thickness is the smallest whole mm that keeps T2 at 22 mm or more', () => {
|
||||
for (const [L, S] of [[1000, -0.5], [1100, -0.5], [1000, -2], [800, -1.91], [1200, -3]] as const) {
|
||||
const T = solplateCenterT(L, S);
|
||||
expect(T - (L * Math.abs(S)) / 200).toBeGreaterThanOrEqual(22);
|
||||
expect(solplateCenterT(L, S) - 1 - (L * Math.abs(S)) / 200).toBeLessThan(22);
|
||||
}
|
||||
});
|
||||
|
||||
it('T1 + T2 = 2T for any rule', () => {
|
||||
const p = solplatePlan({ heightMm: 220, lengthMm: 900, widthMm: 800, slopePct: -1.2 });
|
||||
expect(p.T1 + p.T2).toBeCloseTo(2 * p.T, 9);
|
||||
});
|
||||
|
||||
it('the default rule has no input issues', () => {
|
||||
expect(solplateRuleIssues(SOLPLATE_RULE_DEFAULT)).toEqual([]);
|
||||
});
|
||||
|
||||
it('reports a bearing height that does not exceed the solplate centre thickness', () => {
|
||||
expect(solplateRuleIssues({ ...SOLPLATE_RULE_DEFAULT, heightMm: 25 })).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('reports non-positive plan sizes and a non-numeric slope', () => {
|
||||
expect(solplateRuleIssues({ ...SOLPLATE_RULE_DEFAULT, lengthMm: 0 }).length).toBeGreaterThan(0);
|
||||
expect(solplateRuleIssues({ ...SOLPLATE_RULE_DEFAULT, widthMm: -1 }).length).toBeGreaterThan(0);
|
||||
expect(solplateRuleIssues({ ...SOLPLATE_RULE_DEFAULT, slopePct: NaN }).length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
+22
-24
@@ -1,42 +1,40 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildPier } from '../src/core/geometry';
|
||||
import { solplateGeometry } from '../src/core/solplate';
|
||||
import { SOLPLATE_RULE_DEFAULT, solplatePlan } from '../src/core/solplate-rule';
|
||||
import { loadP1 } from './helpers';
|
||||
import { loadP1, p1Pairs } from './helpers';
|
||||
|
||||
const model = buildPier(loadP1());
|
||||
// P1 with its A1 girder pairs (2026-11-06): S −1.908 %, L 800 × W 740, T 30, T1 38 / T2 22, h3 181.
|
||||
const model = buildPier(loadP1(), { pairs: p1Pairs() });
|
||||
const front1 = model.blocks.filter((b) => b.row === '전열')[0];
|
||||
const plan = solplatePlan(SOLPLATE_RULE_DEFAULT);
|
||||
|
||||
describe('솔플레이트 기하 (C안, 2026-11-03)', () => {
|
||||
it('L = 교직 1000 across the 교각축 직각 (n) and W = 교축 1100 along the 교축 (s)', () => {
|
||||
const g = solplateGeometry(front1, plan);
|
||||
expect(g.L).toBeCloseTo(1.0, 6);
|
||||
expect(g.W).toBeCloseTo(1.1, 6);
|
||||
describe('솔플레이트 기하 (결정 2026-11-06)', () => {
|
||||
it('L = A + 100 = 800 along the girder and W = B + 100 = 740 across it', () => {
|
||||
const g = solplateGeometry(front1, front1.plan);
|
||||
expect(g.L).toBeCloseTo(0.8, 6);
|
||||
expect(g.W).toBeCloseTo(0.74, 6);
|
||||
});
|
||||
|
||||
it('the thick end T1 is on the (−) side: T1 = 27.5 mm, T2 = 22.5 mm, and T1 + T2 = 2T', () => {
|
||||
const g = solplateGeometry(front1, plan);
|
||||
expect(g.T1 * 1000).toBeCloseTo(27.5, 6);
|
||||
expect(g.T2 * 1000).toBeCloseTo(22.5, 6);
|
||||
expect((g.T1 + g.T2) * 1000).toBeCloseTo(50, 6);
|
||||
// The top at the (−) corners is the thick side.
|
||||
const topAtStart = g.corners.filter((c) => Math.abs(c[1] - (g.bottom + g.T1)) < 1e-9);
|
||||
expect(topAtStart.length).toBe(2);
|
||||
it('T1 = 38 mm on one end, T2 = 22 mm on the other, and T1 + T2 = 2T', () => {
|
||||
const g = solplateGeometry(front1, front1.plan);
|
||||
expect(g.T1 * 1000).toBeCloseTo(38, 6);
|
||||
expect(g.T2 * 1000).toBeCloseTo(22, 6);
|
||||
expect((g.T1 + g.T2) * 1000).toBeCloseTo(60, 6);
|
||||
const thick = g.corners.filter((c) => Math.abs(c[1] - (g.bottom + g.T1)) < 1e-9);
|
||||
expect(thick.length).toBe(2);
|
||||
});
|
||||
|
||||
it('the underside is the bearing top: block top (EL.X) + h3 (186 mm)', () => {
|
||||
const g = solplateGeometry(front1, plan);
|
||||
expect(g.bottom).toBeCloseTo(front1.padTop + 0.186, 6);
|
||||
it('the underside is the bearing top: pad top (EL.X) + h3 (181 mm)', () => {
|
||||
const g = solplateGeometry(front1, front1.plan);
|
||||
expect(g.bottom).toBeCloseTo(front1.padTop + 0.181, 6);
|
||||
});
|
||||
|
||||
it('volume = L·W·T (the taper averages to the centre thickness)', () => {
|
||||
const g = solplateGeometry(front1, plan);
|
||||
expect(g.volume).toBeCloseTo(g.L * g.W * 0.025, 9);
|
||||
it('volume = L·W·T (the taper averages to the row thickness)', () => {
|
||||
const g = solplateGeometry(front1, front1.plan);
|
||||
expect(g.volume).toBeCloseTo(g.L * g.W * 0.03, 9);
|
||||
});
|
||||
|
||||
it('the solplate is centred on the block and follows the row direction', () => {
|
||||
const g = solplateGeometry(front1, plan);
|
||||
const g = solplateGeometry(front1, front1.plan);
|
||||
const c = g.corners.reduce((acc, p) => [acc[0] + p[0] / 8, acc[1] + p[2] / 8] as [number, number], [0, 0]);
|
||||
expect(c[0]).toBeCloseTo(front1.s, 6);
|
||||
expect(c[1]).toBeCloseTo(front1.n, 6);
|
||||
|
||||
Reference in new issue
Block a user