import { DIM, type PierModel } from './geometry'; import type { Issue, PierInput, PierType } from './types'; const EXPECTED_COLUMNS: Record = { 'T형': 1, '2주형': 2, '3주형': 3 }; /** Rules from spec §10 that are not implemented in this stage. Shown to the user, never silently skipped. */ export const NOT_IMPLEMENTED_RULES = [ 'I02 fck·fy가 기본값(가정) 그대로 (③ 배근 단계, 3단계)', 'W06 속성 복사 건너뜀 (2단계)', 'W08 솔플레이트 T1−T2 검사 (2단계)', ] as const; /** Input-level checks (spec §10 E01, E02). Returns false when the pier must not be built. */ export function validateInput(input: PierInput, issues: Issue[]): boolean { let ok = true; if (input.columnCount !== EXPECTED_COLUMNS[input.type]) { issues.push({ code: 'E01', level: 'E', pier: input.name, message: `교각형식 ${input.type}과 기둥갯수 ${input.columnCount}개가 맞지 않습니다. 기둥 ${EXPECTED_COLUMNS[input.type]}개로 맞추세요.`, }); ok = false; } const front = input.bearings.filter((b) => b.row === '전열').length; const back = input.bearings.filter((b) => b.row === '후열').length; if (front !== back) { issues.push({ code: 'E02', level: 'E', pier: input.name, message: `전열 받침 ${front}개, 후열 받침 ${back}개로 수가 다릅니다.` }); ok = false; } return ok; } /** Geometry-level checks (spec §10 W01–W05, W07, I01) on a built model. */ export function validatePier(input: PierInput, model: PierModel, issues: Issue[]): void { const ip2Gap = Math.hypot(model.ip2Input.e - model.ip2Calc.e, model.ip2Input.n - model.ip2Calc.n); if (ip2Gap > 0.001) { issues.push({ code: 'W01', level: 'W', pier: input.name, message: `IP2 입력과 재계산값이 ${(ip2Gap * 1000).toFixed(1)} mm 다릅니다. 재계산값을 사용합니다.`, }); } const minBlock = Math.min(model.derived.blockHeightFront, model.derived.blockHeightBack); if (minBlock <= 0) { issues.push({ code: 'W02', level: 'W', pier: input.name, message: '받침블럭 콘크리트 높이가 0 이하입니다. 블럭을 생략합니다.' }); } // W03: bearing centre closer than half a block width to a step boundary (interior boundaries only). const halfBlock = DIM.blockSize / 2; const stepBounds = model.segments.slice(1).map((seg) => seg.s0); for (const b of model.bearings) { const nearest = Math.min(...stepBounds.map((s) => Math.abs(b.g - s))); if (nearest < halfBlock) { issues.push({ code: 'W03', level: 'W', pier: input.name, message: `${b.row} 받침 ${b.no}번 중심이 계단 경계에서 ${(nearest * 1000).toFixed(0)} mm 떨어져 블럭 폭의 절반(${(halfBlock * 1000).toFixed(0)} mm)보다 가깝습니다.`, }); } } // W04: block footprint extends beyond the coping plan (s in [0, L], n in [−B/2, B/2]). const planHalfB = model.copingB / 2; const outside = model.blocks.some( (b) => b.g - halfBlock < 0 || b.g + halfBlock > model.copingL || b.n - halfBlock < -planHalfB || b.n + halfBlock > planHalfB, ); if (outside) { issues.push({ code: 'W04', level: 'W', pier: input.name, message: '받침블럭이 코핑 평면 밖으로 나갑니다.' }); } if (input.foundationType !== '현장타설말뚝' && model.footings.some((f) => f.top > input.groundEl)) { issues.push({ code: 'W05', level: 'W', pier: input.name, message: '확대기초 상면이 지반고보다 높습니다.' }); } if (model.derived.frontBackDiffSpread > DIM.lateralTolerance) { issues.push({ code: 'W07', level: 'I', pier: input.name, message: '전열·후열 EL 차이가 받침마다 다릅니다 (정보).' }); } // I01: block concrete below cover + bar diameter (cover 100 mm, S1·S2 H19 from spec §6.3). const cover = 0.1; const barDia = 0.019; if (minBlock < cover + barDia) { issues.push({ code: 'I01', level: 'I', pier: input.name, message: `받침블럭 높이 ${(minBlock * 1000).toFixed(0)} mm가 피복 + 철근 지름(${((cover + barDia) * 1000).toFixed(0)} mm)보다 작습니다. 블럭 철근은 코핑 안에 묻힙니다.`, }); } }