feat(B06): 절토 경사 판정 암질을 설계자가 고르게 — 기본 경암
사용자 확정(2026-09-07): 「연암과 경암도 별도의 버튼으로 선택하게 하고 경암을 기본값으로 선택. 리핑을 할지 발파를 할지는 설계자가 선택 필요함.」 **축이 둘이었음 — 섞지 말 것.** · **암질**(연암·경암) = 별표2 경사 판정의 기준. 설계자가 고름. 기본 **경암**. · **굴착 공법**(리핑암·발파암, `cut_rock_kind`) = 어떻게 파는가. 수량·단가 몫. 처음에는 공법에서 암질을 유추하려 했는데 **그것이 잘못 세운 문제**였음. 매핑을 걷어내고 암질 선택 하나로 바꿈 — 표준 횡단면 설정 안, 자리는 그대로. - config 매핑 상수 → `FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT = "hard_rock"`. - 판정에서 `cut_rock_kind` 를 뗌. 수량 쪽 쓰임은 그대로 둠. - 실측(용화 63측점·구간 124개) — 기본값 경암에서 **위반 0건**. config 기본 절토비 1:0.4 가 경암 범위(0.3~0.8) 안임. 토사 63구간도 0건. **기존 설계·수량 안 바뀜.** (연암으로 고르면 61건 — 1:0.4 가 연암 하한 0.5 밖이라 그때는 설계 검토가 필요함.) 시험 8건 — 별표2 값 · 작업임도 제외 · **암질은 설계자가 고르고 기본은 경암** · **공법으로 암질을 추론하지 않음** · 구간별 판정 · 소단이 있어도 위반이 안 사라짐 · 경계값 통과 · 카드에 같은 방식으로 붙는지. 7-3 회귀에 소단 한 줄 더함 — 「소단은 값이 아니라 **기하 입력**이라 계산 뒤에 베껴 붙이면 `berm` 값만 남고 계단이 안 그려진다」. 저장분 소단을 **계산 전에** 읽어 넣는 순서를 지킴. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,12 +46,13 @@ export interface StandardCrossGroup {
|
|||||||
export type StandardCrossKey = "soil" | "rock" | "paved";
|
export type StandardCrossKey = "soil" | "rock" | "paved";
|
||||||
|
|
||||||
export type StandardCrossSection = Record<StandardCrossKey, StandardCrossGroup> & {
|
export type StandardCrossSection = Record<StandardCrossKey, StandardCrossGroup> & {
|
||||||
/** 절토 법정 기울기 판정에 쓸 별표2 줄 — 지반유형(리핑암·발파암) → `soft_rock`/`hard_rock`.
|
/** 절토 경사 판정에 쓸 **암질** — `hard_rock`(경암) / `soft_rock`(연암). 기본 경암.
|
||||||
*
|
*
|
||||||
* 별표2 는 암을 **경암·연암**으로 가르고 프로그램은 **리핑암·발파암**으로 가르는데 둘을 잇는
|
* ⚠ 굴착 공법(리핑암·발파암, `cut_rock_kind`)과 **다른 축**이다. 공법은 수량·단가 몫이고
|
||||||
* 문장이 법령·교본에 없다. 그래서 법정 근거가 아니라 **사용자가 고르는 설정**으로 두고
|
* 암질은 별표2 경사 판정의 기준이며, 한쪽에서 다른 쪽을 추론하지 않는다
|
||||||
* 표준단면 설정과 함께 저장한다(2026-09-07 사용자 확정). 없으면 기본값을 쓴다. */
|
* (2026-09-07 사용자 확정 — 「리핑을 할지 발파를 할지는 설계자가 선택」).
|
||||||
cut_slope_class?: Record<string, string>;
|
* 표준단면 설정과 함께 저장된다. 없으면 기본값(경암). */
|
||||||
|
rock_quality?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 지반유형·토량환산계수·운반장비 한계거리는 B05 계획 유토곡선과 공유하므로 정의처를
|
// 지반유형·토량환산계수·운반장비 한계거리는 B05 계획 유토곡선과 공유하므로 정의처를
|
||||||
@@ -87,8 +88,8 @@ export interface SectionContextResponse {
|
|||||||
/** 절토 비탈 법정 기울기 범위(별표2) — `hard_rock`·`soft_rock`·`soil` → [최소, 최대].
|
/** 절토 비탈 법정 기울기 범위(별표2) — `hard_rock`·`soft_rock`·`soil` → [최소, 최대].
|
||||||
* 상수를 화면에 복제하지 않으려고 **서버가 준 값을 그대로** 쓴다(2026-09-07). */
|
* 상수를 화면에 복제하지 않으려고 **서버가 준 값을 그대로** 쓴다(2026-09-07). */
|
||||||
cut_slope_limits?: Record<string, number[]>;
|
cut_slope_limits?: Record<string, number[]>;
|
||||||
/** 지반유형(리핑암·발파암) → 별표2 줄. **사용자가 바꿀 수 있는 설정값**의 기본값이다. */
|
/** 절토 경사 판정에 쓸 **암질** 기본값(`hard_rock`/`soft_rock`). 설계자가 고른다. */
|
||||||
cut_slope_class_default?: Record<string, string>;
|
cut_slope_rock_quality_default?: string;
|
||||||
/** 절토 기울기 규정이 없는 등급(작업임도). */
|
/** 절토 기울기 규정이 없는 등급(작업임도). */
|
||||||
cut_slope_exempt_grades?: string[];
|
cut_slope_exempt_grades?: string[];
|
||||||
/** 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m). */
|
/** 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m). */
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ export interface CutSlopeLimit {
|
|||||||
export interface CutSlopeCriteria {
|
export interface CutSlopeCriteria {
|
||||||
/** 별표2 범위표 — `hard_rock`·`soft_rock`·`soil`. */
|
/** 별표2 범위표 — `hard_rock`·`soft_rock`·`soil`. */
|
||||||
limits: Record<string, CutSlopeLimit>;
|
limits: Record<string, CutSlopeLimit>;
|
||||||
/** 지반유형(리핑암·발파암) → 별표2 줄. 사용자가 바꿀 수 있는 **설정값**이다. */
|
/** 절토 경사 판정에 쓸 **암질** — `hard_rock`(경암) / `soft_rock`(연암). 설계자가 고른다.
|
||||||
classOf: Record<string, string>;
|
* ⚠ 굴착 공법(리핑암·발파암)과 다른 축이라 공법에서 추론하지 않는다(2026-09-07 사용자 확정). */
|
||||||
|
rockQuality: string;
|
||||||
/** 절토 기울기 규정이 없는 등급(작업임도). */
|
/** 절토 기울기 규정이 없는 등급(작업임도). */
|
||||||
exemptGrades: ReadonlyArray<string>;
|
exemptGrades: ReadonlyArray<string>;
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ export function criteriaFrom(context: SectionContextResponse | null): CutSlopeCr
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
limits: table,
|
limits: table,
|
||||||
classOf: context?.cut_slope_class_default ?? {},
|
rockQuality: context?.cut_slope_rock_quality_default ?? "hard_rock",
|
||||||
exemptGrades: context?.cut_slope_exempt_grades ?? [],
|
exemptGrades: context?.cut_slope_exempt_grades ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -62,7 +63,7 @@ export function criteriaFrom(context: SectionContextResponse | null): CutSlopeCr
|
|||||||
*
|
*
|
||||||
* · 등급이 면제(작업임도)면 빈 목록.
|
* · 등급이 면제(작업임도)면 빈 목록.
|
||||||
* · 구간이 없으면(성토 측점 등) 빈 목록.
|
* · 구간이 없으면(성토 측점 등) 빈 목록.
|
||||||
* · 암 구간은 측점의 `cut_rock_kind` 로 별표2 줄을 고른다 — 구간에는 `rock`/`soil` 만 있다.
|
* · 암 구간은 **설계자가 고른 암질**로 판정한다 — 굴착 공법(`cut_rock_kind`)은 보지 않는다.
|
||||||
*/
|
*/
|
||||||
export function cutSlopeViolations(
|
export function cutSlopeViolations(
|
||||||
section: CrossSection,
|
section: CrossSection,
|
||||||
@@ -71,12 +72,11 @@ export function cutSlopeViolations(
|
|||||||
): CutSlopeViolation[] {
|
): CutSlopeViolation[] {
|
||||||
if (!criteria) return [];
|
if (!criteria) return [];
|
||||||
if (gradeClass && criteria.exemptGrades.includes(gradeClass)) return [];
|
if (gradeClass && criteria.exemptGrades.includes(gradeClass)) return [];
|
||||||
const design = section.design as
|
const design = section.design as { cut_slope_segments?: unknown } | undefined;
|
||||||
{ cut_slope_segments?: unknown; cut_rock_kind?: string | null } | undefined;
|
|
||||||
const segments = design?.cut_slope_segments;
|
const segments = design?.cut_slope_segments;
|
||||||
if (!Array.isArray(segments) || !segments.length) return [];
|
if (!Array.isArray(segments) || !segments.length) return [];
|
||||||
|
|
||||||
const rockKey = criteria.classOf[design?.cut_rock_kind ?? ""] ?? "hard_rock";
|
const rockKey = criteria.rockQuality || "hard_rock";
|
||||||
const out: CutSlopeViolation[] = [];
|
const out: CutSlopeViolation[] = [];
|
||||||
for (const raw of segments) {
|
for (const raw of segments) {
|
||||||
const segment = raw as {
|
const segment = raw as {
|
||||||
@@ -125,10 +125,10 @@ export function rememberCutSlopeCriteria(context: SectionContextResponse | null)
|
|||||||
rememberedGrade = context?.road_type ?? null;
|
rememberedGrade = context?.road_type ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 사용자가 고른 매핑(리핑암·발파암 → 별표2 줄)을 얹는다. 없으면 기본값 그대로. */
|
/** 설계자가 고른 암질을 얹는다. 없으면 기본값(경암) 그대로. */
|
||||||
export function applyCutSlopeClassChoice(choice: Record<string, string> | null | undefined): void {
|
export function applyRockQualityChoice(quality: string | null | undefined): void {
|
||||||
if (!remembered || !choice) return;
|
if (!remembered || !quality) return;
|
||||||
remembered = { ...remembered, classOf: { ...remembered.classOf, ...choice } };
|
remembered = { ...remembered, rockQuality: quality };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 지금 측점의 위반 목록 — 기준이 없거나 면제 등급이면 빈 목록. */
|
/** 지금 측점의 위반 목록 — 기준이 없거나 면제 등급이면 빈 목록. */
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ from config.config_db import get_db_pool, run_with_connection
|
|||||||
from config.config_system import (
|
from config.config_system import (
|
||||||
EARTHWORK_CONVERSION_FACTORS,
|
EARTHWORK_CONVERSION_FACTORS,
|
||||||
EARTHWORK_HAUL_EQUIPMENT_LIMITS_M,
|
EARTHWORK_HAUL_EQUIPMENT_LIMITS_M,
|
||||||
FOREST_ROAD_CUT_SLOPE_CLASS_DEFAULT,
|
|
||||||
FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES,
|
FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES,
|
||||||
FOREST_ROAD_CUT_SLOPE_LIMITS,
|
FOREST_ROAD_CUT_SLOPE_LIMITS,
|
||||||
|
FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT,
|
||||||
FOREST_ROAD_MIN_WIDTH_M,
|
FOREST_ROAD_MIN_WIDTH_M,
|
||||||
NATURAL_SPOIL_MIN_GROUND_SLOPE,
|
NATURAL_SPOIL_MIN_GROUND_SLOPE,
|
||||||
SECTION_VERTICAL_EXAGGERATION,
|
SECTION_VERTICAL_EXAGGERATION,
|
||||||
@@ -147,7 +147,7 @@ async def get_section_context(project_id: UUID) -> SectionContextResponse | JSON
|
|||||||
cut_slope_limits={
|
cut_slope_limits={
|
||||||
key: list(value) for key, value in FOREST_ROAD_CUT_SLOPE_LIMITS.items()
|
key: list(value) for key, value in FOREST_ROAD_CUT_SLOPE_LIMITS.items()
|
||||||
},
|
},
|
||||||
cut_slope_class_default=dict(FOREST_ROAD_CUT_SLOPE_CLASS_DEFAULT),
|
cut_slope_rock_quality_default=FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT,
|
||||||
cut_slope_exempt_grades=list(FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES),
|
cut_slope_exempt_grades=list(FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES),
|
||||||
rock_boundary_default_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
|
rock_boundary_default_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
|
||||||
rock_boundary_step_m=STANDARD_ROCK_BOUNDARY_STEP_M,
|
rock_boundary_step_m=STANDARD_ROCK_BOUNDARY_STEP_M,
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class SectionContextResponse(BaseModel):
|
|||||||
# 절토 비탈 법정 기울기 범위(별표2)와 지반유형 → 별표2 줄 기본 매핑, 검사 제외 등급.
|
# 절토 비탈 법정 기울기 범위(별표2)와 지반유형 → 별표2 줄 기본 매핑, 검사 제외 등급.
|
||||||
# 브라우저가 상수를 복제하지 않게 **서버가 준 값을 그대로 기억**한다(표준단면과 같은 방식).
|
# 브라우저가 상수를 복제하지 않게 **서버가 준 값을 그대로 기억**한다(표준단면과 같은 방식).
|
||||||
cut_slope_limits: dict[str, list[float]] = Field(default_factory=dict)
|
cut_slope_limits: dict[str, list[float]] = Field(default_factory=dict)
|
||||||
cut_slope_class_default: dict[str, str] = Field(default_factory=dict)
|
cut_slope_rock_quality_default: str = "hard_rock"
|
||||||
cut_slope_exempt_grades: list[str] = Field(default_factory=list)
|
cut_slope_exempt_grades: list[str] = Field(default_factory=list)
|
||||||
# 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m).
|
# 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m).
|
||||||
rock_boundary_default_offset_m: float = -0.5
|
rock_boundary_default_offset_m: float = -0.5
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
} from "../A00_Common/b_page_state";
|
} from "../A00_Common/b_page_state";
|
||||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||||
import { createButton, createInputField, createSelectField } from "@ui/ui_template_elements";
|
import { createButton, createInputField, createSelectField } from "@ui/ui_template_elements";
|
||||||
import { applyCutSlopeClassChoice } from "./B06_Section_Cut_Slope_Check";
|
import { applyRockQualityChoice } from "./B06_Section_Cut_Slope_Check";
|
||||||
import { buildStandardDiagram } from "./B06_Section_UI_Standard_Diagram";
|
import { buildStandardDiagram } from "./B06_Section_UI_Standard_Diagram";
|
||||||
import {
|
import {
|
||||||
getCompanyStandard,
|
getCompanyStandard,
|
||||||
@@ -397,46 +397,40 @@ export function createStandardPanel(
|
|||||||
}
|
}
|
||||||
actions.append(resetButton);
|
actions.append(resetButton);
|
||||||
|
|
||||||
// ── 절토 법정 기울기 판정 기준 (2026-09-07 사용자 확정) ──────────────────
|
// ── 절토 법정 기울기 판정 기준: **암질** (2026-09-07 사용자 확정) ────────────
|
||||||
// 별표2 는 암을 **경암·연암**으로 가르고 프로그램은 **리핑암·발파암**으로 가른다. 둘을
|
// 「연암과 경암도 별도의 버튼으로 선택하게 하고 경암을 기본값으로. 리핑을 할지 발파를
|
||||||
// 잇는 문장이 법령·교본에 없어 **프로그램 설정**으로 두고 여기서 고르게 한다. 기본값은
|
// 할지는 설계자가 선택 필요함」 — 축이 둘이다. **암질**(연암·경암)은 별표2 경사 판정의
|
||||||
// 리핑암 → 연암 · 발파암 → 경암(자연스러운 읽기). 절토 경사비가 오는 자리 옆이라 여기 둔다.
|
// 기준이고, **굴착 공법**(리핑암·발파암)은 수량·단가 몫이다. 한쪽에서 다른 쪽을 추론하지
|
||||||
const cutClassWrap = document.createElement("div");
|
// 않는다. 절토 경사비가 오는 자리 옆이라 여기 둔다.
|
||||||
cutClassWrap.className = "b06-std__cutclass";
|
const rockQualityWrap = document.createElement("div");
|
||||||
const cutClassTitle = document.createElement("p");
|
rockQualityWrap.className = "b06-std__cutclass";
|
||||||
cutClassTitle.className = "b06-std__cutclass-title";
|
const rockQualityTitle = document.createElement("p");
|
||||||
cutClassTitle.textContent = "절토 법정 기울기 판정 기준 (별표2)";
|
rockQualityTitle.className = "b06-std__cutclass-title";
|
||||||
cutClassTitle.title =
|
rockQualityTitle.textContent = "절토 법정 기울기 판정 암질 (별표2)";
|
||||||
"별표2: 경암 1:0.3~0.8 · 연암 1:0.5~1.2 · 토사 1:0.8~1.5. 작업임도는 규정 없음.";
|
rockQualityTitle.title =
|
||||||
cutClassWrap.append(cutClassTitle);
|
"별표2: 경암 1:0.3~0.8 · 연암 1:0.5~1.2 · 토사 1:0.8~1.5. 작업임도는 규정 없음.\n" +
|
||||||
const CUT_CLASS_OPTIONS = [
|
"굴착 공법(리핑·발파)과는 다른 값이다 — 공법은 수량·단가에서 따로 고른다.";
|
||||||
{ value: "soft_rock", text: "연암 (1:0.5~1.2)" },
|
rockQualityWrap.append(rockQualityTitle);
|
||||||
{ value: "hard_rock", text: "경암 (1:0.3~0.8)" },
|
const rockQualityField = createSelectField({
|
||||||
];
|
label: "암질",
|
||||||
for (const [kind, label] of [
|
options: [
|
||||||
["ripping_rock", "리핑암"],
|
{ value: "hard_rock", text: "경암 (1:0.3~0.8)" },
|
||||||
["blasting_rock", "발파암"],
|
{ value: "soft_rock", text: "연암 (1:0.5~1.2)" },
|
||||||
] as const) {
|
],
|
||||||
const current =
|
value: state.rock_quality ?? "hard_rock",
|
||||||
state.cut_slope_class?.[kind] ?? (kind === "ripping_rock" ? "soft_rock" : "hard_rock");
|
onChange: (value) => {
|
||||||
const field = createSelectField({
|
state.rock_quality = value;
|
||||||
label,
|
persist();
|
||||||
options: CUT_CLASS_OPTIONS,
|
applyRockQualityChoice(value);
|
||||||
value: current,
|
onApplyAll?.();
|
||||||
onChange: (value) => {
|
},
|
||||||
state.cut_slope_class = { ...(state.cut_slope_class ?? {}), [kind]: value };
|
});
|
||||||
persist();
|
rockQualityWrap.append(rockQualityField.root);
|
||||||
applyCutSlopeClassChoice(state.cut_slope_class);
|
|
||||||
onApplyAll?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cutClassWrap.append(field.root);
|
|
||||||
}
|
|
||||||
// 처음 세울 때도 고른 값을 검사에 반영한다(세션에 남아 있던 선택 포함).
|
// 처음 세울 때도 고른 값을 검사에 반영한다(세션에 남아 있던 선택 포함).
|
||||||
applyCutSlopeClassChoice(state.cut_slope_class);
|
applyRockQualityChoice(state.rock_quality ?? "hard_rock");
|
||||||
|
|
||||||
// 횡단 반폭은 사이드의 **별도 컨테이너**로 뺐다(2026-08-23) — 여기는 표준단면 설정만.
|
// 횡단 반폭은 사이드의 **별도 컨테이너**로 뺐다(2026-08-23) — 여기는 표준단면 설정만.
|
||||||
root.append(body, cutClassWrap, loader, actions);
|
root.append(body, rockQualityWrap, loader, actions);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root,
|
root,
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ FOREST_ROAD_CUT_SLOPE_LIMITS = {
|
|||||||
"soft_rock": (0.5, 1.2), # 암석지 — 연암
|
"soft_rock": (0.5, 1.2), # 암석지 — 연암
|
||||||
"soil": (0.8, 1.5), # 토사지역
|
"soil": (0.8, 1.5), # 토사지역
|
||||||
}
|
}
|
||||||
# 프로그램 지반유형(리핑암·발파암) → 별표2 줄(연암·경암).
|
# 절토 경사 판정에 쓸 **암질** 기본값 (2026-09-07 사용자 확정).
|
||||||
# **기본값이며 사용자가 바꿀 수 있다**(2026-09-07 사용자 확정 — 「사용자가 제어할 수 있게」).
|
#
|
||||||
# 별표2 는 경암·연암으로 가르고 프로그램은 리핑암·발파암으로 가르는데, 둘을 잇는 문장이
|
# ⚠ 축이 둘이다 — 섞지 말 것.
|
||||||
# 법령·교본에 없다. 그래서 법정 근거가 아니라 **프로그램 설정**으로 두고 화면에서 고르게 한다.
|
# · **암질**(연암·경암) = 별표2 경사 판정의 기준. 설계자가 고른다. 기본값 **경암**.
|
||||||
FOREST_ROAD_CUT_SLOPE_CLASS_DEFAULT = {
|
# · **굴착 공법**(리핑암·발파암, `cut_rock_kind`) = 어떻게 파는가. 수량·단가 몫이다.
|
||||||
"ripping_rock": "soft_rock",
|
# 한쪽에서 다른 쪽을 **추론하지 않는다**(사용자: 「리핑을 할지 발파를 할지는 설계자가 선택」).
|
||||||
"blasting_rock": "hard_rock",
|
# 처음에는 공법에서 암질을 유추하려 했으나 그것이 잘못 세운 문제였다.
|
||||||
}
|
FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT = "hard_rock"
|
||||||
# 절토 기울기 규정이 없는 등급 — 작업임도(별표2 §1 「작업임도: 절토 기울기 표 규정 없음」).
|
# 절토 기울기 규정이 없는 등급 — 작업임도(별표2 §1 「작업임도: 절토 기울기 표 규정 없음」).
|
||||||
FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES = ("work",)
|
FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES = ("work",)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user