Files
Pier_v1/src/core/validate.ts
T

113 lines
5.8 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 { DIM, type PierModel } from './geometry';
import type { Issue, PierInput, PierType } from './types';
import { validateGround, validatePileEmbedment, validateStrataNValues } from './ground';
import { integratedCheck } from './bearing-layout';
import { checkBlockRebar } from './rebar/block-check';
const EXPECTED_COLUMNS: Record<PierType, number> = { '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 === 0 || back === 0) {
issues.push({ code: 'E02', level: 'E', pier: input.name, message: '전열과 후열에 각각 받침이 1개 이상 필요합니다.' });
ok = false;
} else if (front !== back) {
issues.push({ code: 'W09', level: 'W', pier: input.name, message: `전열 받침 ${front}개, 후열 받침 ${back}개입니다. 각 열의 좌표와 표고로 분리 생성합니다.` });
}
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;
for (const b of model.bearings) {
const stepBounds = (b.row === '후열' ? model.backSegments ?? model.segments : model.segments).slice(1).map((seg) => seg.s0);
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)보다 작습니다. 블럭 철근은 코핑 안에 묻힙니다.`,
});
}
validateGround(input, issues);
validatePileEmbedment(input, model, issues);
validateStrataNValues(input, issues);
issues.push(...checkBlockRebar(input, model));
// 일체 (승인 2026-10-26): the model builds one stepped block per pair when allowed; otherwise it keeps 분리.
if (input.bearingLayout === '일체') {
const check = integratedCheck(model.blocks);
if (!check.allowed) {
const worst = check.overLimit.map((p) => `전열 ${p.frontNo}·후열 ${p.backNo} ${p.eccentricityMm.toFixed(0)} mm`).join(', ');
const unequal = model.blocks.filter(b => b.row === '전열').length !== model.blocks.filter(b => b.row === '후열').length;
issues.push({ code: 'B02', level: 'W', pier: input.name, message: unequal ? '전열·후열 받침 수가 달라 모든 받침블럭을 분리로 계산합니다.' : `일체를 골랐지만 편심이 100 mm를 넘는 쌍이 있어 분리로 계산합니다 (${worst || '쌍 없음'}).` });
} else {
issues.push({ code: 'B01', level: 'I', pier: input.name, message: '일체 배치: 전열·후열 쌍마다 단이 있는 블록 (2500 × 1200)으로 계산합니다.' });
}
}
}