feat(core): parse P1 workbook and build T-type pier shape

- parse Excel input by header strings (pier, IP, bearing tables)
- DMS azimuth parser (E03), input validation (E01, E02)
- pure geometry: axis, bearings, stepped coping, column, footing, piles, blinding
- warnings W01, W02, W05, W07, I01
- regression tests against spec.md section 12 (P1)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-21 17:26:16 +09:00
commit d963d8ed3c
15 files changed
+2527

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { validateInput, validatePier } from '../src/core/validate';
import type { Issue, PierInput } from '../src/core/types';
import { loadP1 } from './helpers';
function run(input: PierInput): Issue[] {
const issues: Issue[] = [];
if (validateInput(input, issues)) validatePier(input, buildPier(input), issues);
return issues;
}
describe('validation (spec §10)', () => {
it('P1 raises I01 (block 100 mm < cover + bar 119 mm) and no E/W', () => {
const issues = run(loadP1());
expect(issues.map((i) => i.code)).toContain('I01');
expect(issues.filter((i) => i.level !== 'I')).toEqual([]);
});
it('E01 when the pier type and column count disagree', () => {
const issues = run({ ...loadP1(), type: '2주형' });
expect(issues.map((i) => i.code)).toContain('E01');
});
it('E02 when front and back bearing counts differ', () => {
const p1 = loadP1();
const issues: Issue[] = [];
const bad = { ...p1, bearings: p1.bearings.filter((b) => !(b.row === '후열' && b.no === 10)) };
expect(validateInput(bad, issues)).toBe(false);
expect(issues.map((i) => i.code)).toContain('E02');
});
it('W01 when IP2 disagrees with the 1:9 recalculation by more than 1 mm', () => {
const p1 = loadP1();
const ips = p1.ips.map((p) => (p.name === 'IP2' ? { ...p, x: p.x + 0.002 } : p));
expect(run({ ...p1, ips }).map((i) => i.code)).toContain('W01');
});
});
describe('foundation types', () => {
it('직접기초: footing, no piles', () => {
const m = buildPier({ ...loadP1(), foundationType: '직접기초' });
expect(m.footings).toHaveLength(1);
expect(m.piles).toHaveLength(0);
});
it('현장타설말뚝: no footing, one pile of column diameter per column, tip = bottom EL − length', () => {
const m = buildPier({ ...loadP1(), foundationType: '현장타설말뚝' });
expect(m.footings).toHaveLength(0);
expect(m.piles).toHaveLength(1);
expect(m.piles[0].diameter).toBeCloseTo(2.0, 6);
expect(m.piles[0].bottom).toBeCloseTo(26.208 - 24, 6);
});
});