feat(ground): pile-tip embedment check and soil layers from the ground model

- ground.ts: G04 when a pile tip is above the 연암 상면 minus 1.0 × D; G05
  (info) when layers are entered but there is no 연암 row. No message while
  no layers are entered at all.
- ground-model.ts: reads the Python soil.layers as strata rows; the project
  file keeps and checks them.
- ② card: a button fills the layer rows from the ground model for the pier.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-26 10:35:41 +09:00
1 parent 16ab317094
commit 325c841733
6 files changed
+152 -5

No files matched your search

+33 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { validateGround } from '../src/core/ground';
import { validateGround, validatePileEmbedment } from '../src/core/ground';
import { changedKeys } from '../src/core/changes';
import { COPY_ITEMS, applyCopy, planCopy } from '../src/core/copy';
import type { Issue, PierInput } from '../src/core/types';
@@ -103,3 +103,35 @@ describe('단면 그림 (삽도)', () => {
expect(svg).toContain('지반고 28.931');
});
});
describe('말뚝 선단 근입 (G04·G05)', () => {
// P1 model: pile tips at about EL 2.2 m, D 0.6096 m, so the limit is the 연암 top minus 0.6096.
const model = buildPier(base);
const deepest = Math.max(...model.piles.map((p) => p.bottom));
function embed(input: PierInput): string[] {
const issues: Issue[] = [];
validatePileEmbedment(input, model, issues);
return issues.map((i) => i.code);
}
it('no check and no message when no layers are entered yet', () => {
expect(embed(withGround({}))).toEqual([]);
});
it('G04: a pile tip above the 연암 top minus the embedment', () => {
// 연암 top = the 매립토 bottom 2.5 m, so the limit is 2.5 − 0.6096 = 1.89 m, above the tips at 2.2 m.
expect(deepest).toBeGreaterThan(2.5 - 0.6096);
const rows = [{ name: '매립토', bottomEl: 2.5 }, { name: '연암', bottomEl: 1 }];
expect(embed(withGround({ strataRows: rows }))).toEqual(['G04']);
});
it('no G04 when the 연암 top is high enough above the tips', () => {
const rows = [{ name: '매립토', bottomEl: 20 }, { name: '연암', bottomEl: 10 }];
expect(embed(withGround({ strataRows: rows }))).toEqual([]);
});
it('G05 (info): layers entered but no 연암 row, so the embedment is not checked', () => {
expect(embed(withGround({ strataRows: [{ name: '풍화토', bottomEl: 20 }] }))).toEqual(['G05']);
});
});
+43 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { groundElFor, parseGroundModel } from '../src/core/ground-model';
import { groundElFor, parseGroundModel, strataRowsFor } from '../src/core/ground-model';
import { changedKeys, overrideKeys, revertDraft, type PierValues } from '../src/core/changes';
import { parseProject, serializeProject, SCHEMA_VERSION, type ProjectFile } from '../src/core/project';
import { loadP1 } from './helpers';
@@ -151,3 +151,45 @@ describe('프로젝트 파일의 지반 모델', () => {
expect(parseProject(serializeProject(project({}))).groundModel).toBeUndefined();
});
});
describe('지반 모델의 지층 기둥 (soil.layers)', () => {
const WITH_SOIL = JSON.stringify({
schema: 2,
piers: [{
id: 'P1',
ground: { center: 29.355, excel: 28.931 },
soil: { layers: [{ name: '매립토', top_el: 29.355, bottom_el: 27.5 }, { name: '연암', top_el: 27.5, bottom_el: 10 }] },
}],
});
it('reads the layers as strata rows (name and bottom EL, top to bottom)', () => {
const m = parseGroundModel(WITH_SOIL);
expect(strataRowsFor(m, 'P1')).toEqual([{ name: '매립토', bottomEl: 27.5 }, { name: '연암', bottomEl: 10 }]);
expect(strataRowsFor(m, 'P9')).toBeUndefined();
});
it('a pier without a soil column has no layers', () => {
expect(strataRowsFor(parseGroundModel(SAMPLE), 'P1')).toBeUndefined();
});
it('refuses a soil layers entry that is not a list', () => {
const data = JSON.parse(WITH_SOIL);
data.piers[0].soil.layers = { x: 1 };
expect(() => parseGroundModel(JSON.stringify(data))).toThrow('지층(soil.layers) 형식');
});
it('keeps the layers in a saved project and refuses a damaged layer', () => {
const m = parseGroundModel(WITH_SOIL);
const text = serializeProject({
schemaVersion: SCHEMA_VERSION,
project: { name: 'x', bridge: '운곡교', client: '', author: '', crs: 'KATEC', memo: '' },
source: { fileName: 'x.xlsx', sheets: [] },
piers: [{ id: 'P1', input: loadP1() }],
history: [],
groundModel: m,
});
expect(strataRowsFor(parseProject(text).groundModel!, 'P1')).toEqual(strataRowsFor(m, 'P1'));
const bad = text.replace('"bottomEl": 10', '"bottomEl": "10"');
expect(() => parseProject(bad)).toThrow('지층 값이 맞지 않습니다');
});
});