Files
Pier_v1/src/core/ground.ts
T

173 lines
8.6 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 type { PierModel } from './geometry';
import type { Issue, PierInput } from './types';
/** 위치 choices of the 지반·지층 card (시안 2026-10-26). */
export const GROUND_POSITIONS = ['육상', '하상', '해상'] as const;
/** 지층 names, in the order of the RAG strata list (지반모델링계획). */
export const STRATA_NAMES = ['매립토', '퇴적토', '풍화토', '풍화암', '연암', '경암'] as const;
/** The 지지층 for the pile tip check (지반 모델 G-7). */
export const SUPPORT_STRATUM = '연암';
/** 근입 = 이 값 × 말뚝 지름 (지반 모델 G-7, embed_d 1.0). */
export const PILE_EMBED_D = 1.0;
/** 해상 datum (승인 2026-10-26): mean sea level. */
export const SEA_DATUM = 'MSL';
/**
* 부식 여유 (승인 2026-10-26: 기준값으로 고정). PLACEHOLDER: the value is not in the RAG documents yet,
* so 1 mm is a reference to confirm against the design standard before use.
*/
export const CORROSION_ALLOWANCE_MM = 1;
/**
* 지반·지층 입력 검사 (승인 2026-10-26).
* G01: 층 하단 EL은 위 경계보다 낮아야 함. G02: 지하수위는 지반고와 가장 아래 층 사이에 있어야 함.
* G03: 층 이름은 지층 목록에 있어야 함. 하상: G07 DHWL 없음, G08 DHWL이 지반고보다 낮음, G09 세굴선 확인.
* Ground values are inputs only; they do not change the 3D model.
*/
export function validateGround(input: PierInput, issues: Issue[]): void {
const rows = input.strataRows ?? [];
let upper = input.groundEl;
rows.forEach((row, i) => {
const label = `지층 ${i + 1}행 (${row.name})`;
if (!(STRATA_NAMES as readonly string[]).includes(row.name)) {
issues.push({ code: 'G03', level: 'W', pier: input.name, message: `${label}: 층 이름이 지층 목록에 없습니다.` });
}
if (!(row.bottomEl < upper)) {
issues.push({ code: 'G01', level: 'W', pier: input.name, message: `${label}: 하단 EL ${row.bottomEl.toFixed(3)}이 위 경계 ${upper.toFixed(3)}보다 낮아야 합니다.` });
}
upper = row.bottomEl;
});
const position = input.groundPosition ?? '육상';
if (position === '하상') {
// 하상 (승인 2026-10-26): the 설계홍수위 DHWL replaces the groundwater level.
const f = input.floodLevelEl;
if (f === undefined || !Number.isFinite(f)) {
issues.push({ code: 'G07', level: 'W', pier: input.name, message: '하상입니다. 설계홍수위(DHWL) EL을 입력하세요.' });
} else if (f < input.groundEl) {
issues.push({ code: 'G08', level: 'W', pier: input.name, message: `설계홍수위 ${f.toFixed(3)}이 지반고 ${input.groundEl.toFixed(3)}보다 낮습니다.` });
}
validateScour(input, issues);
} else if (position === '해상') {
validateSea(input, issues);
validateScour(input, issues);
} else if (input.groundWaterOn) {
const w = input.groundWaterEl;
const lowest = rows.length ? rows[rows.length - 1].bottomEl : undefined;
if (w === undefined || !Number.isFinite(w)) {
issues.push({ code: 'G02', level: 'W', pier: input.name, message: '지하수위를 고려하지만 지하수위 EL이 없습니다.' });
} else if (w > input.groundEl || (lowest !== undefined && w < lowest)) {
issues.push({ code: 'G02', level: 'W', pier: input.name, message: `지하수위 EL ${w.toFixed(3)}이 지반고와 가장 아래 층 사이에 있지 않습니다.` });
}
}
}
/**
* 말뚝 선단 근입 (승인 2026-10-26): every pile tip must be at least PILE_EMBED_D × D below the 연암 상면.
* G04: a tip above that limit. G05 (info): piles but no 연암 row, so the embedment is not checked.
* The 연암 상면 is the bottom of the row above the 연암 row (ground EL for the first row).
*/
export function validatePileEmbedment(input: PierInput, model: PierModel, issues: Issue[]): void {
if (model.piles.length === 0) return;
const rows = input.strataRows ?? [];
const k = rows.findIndex((r) => r.name === SUPPORT_STRATUM);
if (k < 0) {
// No layers entered yet: nothing to say. Layers entered but no 연암 row: say so.
if (rows.length > 0) {
issues.push({ code: 'G05', level: 'I', pier: input.name, message: '연암 행이 없어 말뚝 선단 근입을 확인하지 않았습니다.' });
}
return;
}
const top = k === 0 ? input.groundEl : rows[k - 1].bottomEl;
const deepest = Math.max(...model.piles.map((p) => p.bottom));
const diameter = Math.max(...model.piles.map((p) => p.diameter));
const limit = top - PILE_EMBED_D * diameter;
if (deepest > limit + 1e-9) {
issues.push({
code: 'G04',
level: 'W',
pier: input.name,
message: `말뚝 선단 EL ${deepest.toFixed(3)}이 연암 상면 ${top.toFixed(3)} − 근입 ${(PILE_EMBED_D * diameter).toFixed(3)} = ${limit.toFixed(3)}보다 높습니다.`,
});
}
}
/** N값 as read from the text: a blow count, or a refusal "타수/관입량" with the penetration in mm. */
export type NValue = { kind: 'empty' } | { kind: 'n'; n: number } | { kind: 'refusal'; blows: number; mm: number } | { kind: 'invalid' };
/** Largest blow count and penetration accepted (표준관입시험 의 일반 범위; 연타 한계 50 blows / 300 mm). */
export const N_MAX = 100;
export const PENETRATION_MAX_MM = 300;
/** Reads an N value. "" is empty; "23" is 23 blows; "50/5" is 50 blows for 5 mm (관입량 mm). */
export function parseNValue(text: string | undefined): NValue {
const v = (text ?? '').trim();
if (v === '') return { kind: 'empty' };
if (/^\d+$/.test(v)) {
const n = Number(v);
return n <= N_MAX ? { kind: 'n', n } : { kind: 'invalid' };
}
const m = /^(\d+)\/(\d+)$/.exec(v);
if (m) {
const blows = Number(m[1]);
const mm = Number(m[2]);
if (blows >= 1 && blows <= N_MAX && mm >= 1 && mm <= PENETRATION_MAX_MM) return { kind: 'refusal', blows, mm };
}
return { kind: 'invalid' };
}
/**
* G06 (경고): an N value that is not a blow count or a refusal in the accepted range. Empty is allowed.
* The values are not used in the calculation yet; they will feed the 지지력 and 액상화 checks, so the format is strict.
*/
export function validateStrataNValues(input: PierInput, issues: Issue[]): void {
(input.strataRows ?? []).forEach((row, i) => {
if (parseNValue(row.nValue).kind === 'invalid') {
issues.push({
code: 'G06',
level: 'W',
pier: input.name,
message: `지층 ${i + 1}행 (${row.name}) N값 "${row.nValue}"의 형식이 맞지 않습니다. 타수(0–${N_MAX}) 또는 "타수/관입량 mm"(예: 50/5)으로 적으세요.`,
});
}
});
}
/** G09: the 세굴 깊이 is not negative, and the scour line stays above the footing bottom (하상·해상). */
function validateScour(input: PierInput, issues: Issue[]): void {
const scour = input.scourDepthM;
if (scour === undefined) return;
if (!(scour >= 0)) {
issues.push({ code: 'G09', level: 'W', pier: input.name, message: '세굴 깊이는 0 이상이어야 합니다.' });
} else if (input.groundEl - scour < input.footingBottomEl) {
issues.push({ code: 'G09', level: 'W', pier: input.name, message: `세굴선 EL ${(input.groundEl - scour).toFixed(3)}이 확대기초 하면 ${input.footingBottomEl.toFixed(3)}보다 낮습니다 (기초 노출).` });
}
}
/**
* 해상 (승인 2026-10-26, MSL 기준): 고조위와 저조위 are required, the 해저면 (groundEl) lies below the 고조위, and
* 저조위 is below 고조위. G13 missing; G10 해저면이 고조위보다 낮지 않음; G11 저조위가 고조위보다 낮지 않음;
* G14 강관 두께가 부식 여유 기준 이하. 파랑과 선박 하중은 이번 범위에서 제외.
*/
function validateSea(input: PierInput, issues: Issue[]): void {
const hw = input.highWaterEl;
const lw = input.lowWaterEl;
if (hw === undefined || lw === undefined || !Number.isFinite(hw) || !Number.isFinite(lw)) {
issues.push({ code: 'G13', level: 'W', pier: input.name, message: `해상입니다. 고조위(HWL)와 저조위(LWL)를 ${SEA_DATUM} 기준으로 입력하세요.` });
} else {
if (hw <= input.groundEl) {
issues.push({ code: 'G10', level: 'W', pier: input.name, message: `고조위 ${hw.toFixed(3)}이 해저면 ${input.groundEl.toFixed(3)}보다 높아야 합니다 (수심 확인).` });
}
if (lw >= hw) {
issues.push({ code: 'G11', level: 'W', pier: input.name, message: `저조위 ${lw.toFixed(3)}이 고조위 ${hw.toFixed(3)}보다 낮아야 합니다.` });
}
}
const thickness = input.pileThicknessMm ?? 12;
if (thickness <= CORROSION_ALLOWANCE_MM) {
issues.push({ code: 'G14', level: 'W', pier: input.name, message: `말뚝 두께 ${thickness} mm가 부식 여유 기준 ${CORROSION_ALLOWANCE_MM} mm 이하입니다 (기준값 확인 필요).` });
}
}