From cc428d7e12fc05387397611c4e33c6dfdf011024 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 7 Sep 2026 16:56:26 +0900 Subject: [PATCH] =?UTF-8?q?revert(B06):=20=EC=A0=88=ED=86=A0=20=EA=B2=BD?= =?UTF-8?q?=EC=82=AC=20=EB=B2=95=EC=A0=95=20=EA=B2=80=EC=82=AC=20=ED=8F=90?= =?UTF-8?q?=EA=B8=B0=20=E2=80=94=20=EC=95=94=EC=A7=88=EC=9D=84=20=ED=9A=A1?= =?UTF-8?q?=EB=8B=A8=EB=8F=84=EC=97=90=EC=84=9C=20=EA=B3=A0=EB=A5=B4?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 확정(2026-09-07): 「연암과 경암 / 발파암과 리핑암 선택은 횡단도에서 선택 안함. 사유는 향후 설계내역에서 설계자가 직접 비율로 지정하기로 함. 암반 지정과 범위 애매모호한 경우가 있어. 실무자는 그렇게 하기로 판단함. 대신 경고부분은 삭제해주고 대신 각도를 사용자가 넣을수 있게 반영.」 **왜 폐기인가** — 판정이 서려면 측점마다 암질(연암·경암)을 못 박아야 하는데, 실무에서 암반 지정·범위가 애매해 그 못 박음 자체가 틀린 전제였음. 암 비율은 **설계내역 단계에서 설계자가 비율로** 넣음. 기준이 없으니 경고도 없음. 되돌린 것 — 1e596846(암질 선택) + 973128f7(별표2 경고). 카드 경고 배지 · 판정 모듈 · config 범위표 · context 필드 · 표준 패널 암질 칸 전부 사라짐. ⚠ **남긴 것** — `cut_slope_segments`(구간별 경사)는 지우지 않음. 소단 기하가 그 위에 서 있고 구간 경사를 아는 값이라 뒤에 쓸 자리가 있음. 4e7a4bf7(사면 구간 재료를 그린 경사비로 가름 — 무릎 위 라벨 13구간 오류 수정)도 남김. 검사와 무관하게 옳은 고침임. 시험 4건 — 되살아나지 않게 막는 쪽으로 다시 씀(판정 모듈 없음 · 카드 경고 없음 · 암질 고르는 자리 없음 · **구간별 경사값은 남아 있음**). Co-Authored-By: Claude Opus 5 (1M context) --- B06_Section/B06_Section_Api_Types.ts | 17 +-- B06_Section/B06_Section_Cut_Slope_Check.ts | 137 ------------------ B06_Section/B06_Section_Router.py | 8 - B06_Section/B06_Section_Schema.py | 5 - .../B06_Section_UI_Cross_Card_Chrome.ts | 21 --- B06_Section/B06_Section_UI_Page.ts | 3 - B06_Section/B06_Section_UI_Standard_Panel.ts | 35 +---- config/config_system_design.py | 23 --- 8 files changed, 2 insertions(+), 247 deletions(-) delete mode 100644 B06_Section/B06_Section_Cut_Slope_Check.ts diff --git a/B06_Section/B06_Section_Api_Types.ts b/B06_Section/B06_Section_Api_Types.ts index c2493a9a..42dacd1b 100644 --- a/B06_Section/B06_Section_Api_Types.ts +++ b/B06_Section/B06_Section_Api_Types.ts @@ -45,15 +45,7 @@ export interface StandardCrossGroup { /** 표준 횡단면 설정 패널 그룹 키. */ export type StandardCrossKey = "soil" | "rock" | "paved"; -export type StandardCrossSection = Record & { - /** 절토 경사 판정에 쓸 **암질** — `hard_rock`(경암) / `soft_rock`(연암). 기본 경암. - * - * ⚠ 굴착 공법(리핑암·발파암, `cut_rock_kind`)과 **다른 축**이다. 공법은 수량·단가 몫이고 - * 암질은 별표2 경사 판정의 기준이며, 한쪽에서 다른 쪽을 추론하지 않는다 - * (2026-09-07 사용자 확정 — 「리핑을 할지 발파를 할지는 설계자가 선택」). - * 표준단면 설정과 함께 저장된다. 없으면 기본값(경암). */ - rock_quality?: string; -}; +export type StandardCrossSection = Record; // 지반유형·토량환산계수·운반장비 한계거리는 B05 계획 유토곡선과 공유하므로 정의처를 // `@util/common_util_mass_haul_types` 한 곳에 두고 여기서는 재수출만 한다(사본 금지). @@ -85,13 +77,6 @@ export interface SectionContextResponse { /** 이 프로젝트에 **저장된** 표준 횡단면(사용자가 고쳐 확정한 값). 없으면 null. * 위 `standard_cross_section` 은 config 기본값이라 둘은 다른 것이다(2026-09-07). */ stored_standard_cross_section?: StandardCrossSection | null; - /** 절토 비탈 법정 기울기 범위(별표2) — `hard_rock`·`soft_rock`·`soil` → [최소, 최대]. - * 상수를 화면에 복제하지 않으려고 **서버가 준 값을 그대로** 쓴다(2026-09-07). */ - cut_slope_limits?: Record; - /** 절토 경사 판정에 쓸 **암질** 기본값(`hard_rock`/`soft_rock`). 설계자가 고른다. */ - cut_slope_rock_quality_default?: string; - /** 절토 기울기 규정이 없는 등급(작업임도). */ - cut_slope_exempt_grades?: string[]; /** 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m). */ rock_boundary_default_offset_m: number; rock_boundary_step_m: number; diff --git a/B06_Section/B06_Section_Cut_Slope_Check.ts b/B06_Section/B06_Section_Cut_Slope_Check.ts deleted file mode 100644 index a3371c42..00000000 --- a/B06_Section/B06_Section_Cut_Slope_Check.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* ============================================================================= - * B06_Section_Cut_Slope_Check.ts - * 절토 비탈 **법정 기울기 검사**(별표2) — 순수 판정만 둔다(그리기는 카드 몫). - * - * 왜 필요한가 — 평면 곡선반경도, 성토사면 길이(5m·기슭막이 의무)도 위반 표시가 있는데 - * **절토 경사만 검사가 없었다**(2026-09-07). 절토 경사비는 표준 횡단면 설정에서 오는 - * 입력값일 뿐이라 별표2 범위를 벗어나도 아무 표시가 없었다. - * - * ⚠ **실효 경사로 보면 안 된다.** 소단이 서면 사면 전체를 하나로 잰 경사가 완만해져 - * **위반이 사라진 것처럼** 보인다(실측: 폭 1.0·간격 2 이면 설계 1:1 이 실효 1:1.71). - * 그래서 `design.cut_slope_segments`(소단을 뺀 **구간별** 경사)를 읽는다. - * - * 기준값·매핑은 **서버가 준 값**을 쓴다(config 상수를 화면에 복제하지 않는다). - * ========================================================================== */ - -import type { CrossSection, SectionContextResponse } from "./B06_Section_Api_Fetch"; - -/** 별표2 한 줄 — 수직 1 에 대한 수평(1:n 의 n) 범위. */ -export interface CutSlopeLimit { - min: number; - max: number; -} - -export interface CutSlopeCriteria { - /** 별표2 범위표 — `hard_rock`·`soft_rock`·`soil`. */ - limits: Record; - /** 절토 경사 판정에 쓸 **암질** — `hard_rock`(경암) / `soft_rock`(연암). 설계자가 고른다. - * ⚠ 굴착 공법(리핑암·발파암)과 다른 축이라 공법에서 추론하지 않는다(2026-09-07 사용자 확정). */ - rockQuality: string; - /** 절토 기울기 규정이 없는 등급(작업임도). */ - exemptGrades: ReadonlyArray; -} - -export interface CutSlopeViolation { - side: string; - ratio: number; - /** 어느 별표2 줄로 판정했나 — 툴팁에 적는다. */ - limitKey: string; - limit: CutSlopeLimit; - /** 급한 쪽 위반인지(`steep`) 완만한 쪽 위반인지(`gentle`). */ - kind: "steep" | "gentle"; - startOffsetM: number; - endOffsetM: number; -} - -/** 서버 컨텍스트에서 기준을 꺼낸다. 값이 안 왔으면 null — 검사를 하지 않는다. */ -export function criteriaFrom(context: SectionContextResponse | null): CutSlopeCriteria | null { - const limits = context?.cut_slope_limits; - if (!limits || !Object.keys(limits).length) return null; - const table: Record = {}; - for (const [key, pair] of Object.entries(limits)) { - if (Array.isArray(pair) && pair.length >= 2) table[key] = { min: pair[0], max: pair[1] }; - } - return { - limits: table, - rockQuality: context?.cut_slope_rock_quality_default ?? "hard_rock", - exemptGrades: context?.cut_slope_exempt_grades ?? [], - }; -} - -/** - * 이 측점의 절토 구간이 별표2 범위를 벗어났는지. - * - * · 등급이 면제(작업임도)면 빈 목록. - * · 구간이 없으면(성토 측점 등) 빈 목록. - * · 암 구간은 **설계자가 고른 암질**로 판정한다 — 굴착 공법(`cut_rock_kind`)은 보지 않는다. - */ -export function cutSlopeViolations( - section: CrossSection, - criteria: CutSlopeCriteria | null, - gradeClass: string | null, -): CutSlopeViolation[] { - if (!criteria) return []; - if (gradeClass && criteria.exemptGrades.includes(gradeClass)) return []; - const design = section.design as { cut_slope_segments?: unknown } | undefined; - const segments = design?.cut_slope_segments; - if (!Array.isArray(segments) || !segments.length) return []; - - const rockKey = criteria.rockQuality || "hard_rock"; - const out: CutSlopeViolation[] = []; - for (const raw of segments) { - const segment = raw as { - side?: string; - ratio?: number; - material?: string | null; - start_offset_m?: number; - end_offset_m?: number; - }; - const ratio = segment.ratio; - if (typeof ratio !== "number" || !Number.isFinite(ratio)) continue; - const limitKey = segment.material === "rock" ? rockKey : "soil"; - const limit = criteria.limits[limitKey]; - if (!limit) continue; - // 경계값은 통과 — 1:0.8 은 「0.3~0.8」 안이다. - if (ratio >= limit.min - 1e-9 && ratio <= limit.max + 1e-9) continue; - out.push({ - side: segment.side ?? "", - ratio, - limitKey, - limit, - kind: ratio < limit.min ? "steep" : "gentle", - startOffsetM: segment.start_offset_m ?? 0, - endOffsetM: segment.end_offset_m ?? 0, - }); - } - return out; -} - -/** 별표2 줄 이름 — 화면·툴팁에 적는 말. */ -export function limitLabel(key: string): string { - if (key === "hard_rock") return "경암"; - if (key === "soft_rock") return "연암"; - return "토사"; -} - -/* ── 기억해 둔 기준 ────────────────────────────────────────────────────────── - * 카드는 그리는 자리마다 컨텍스트를 들고 있지 않다. 표준단면 기본값을 기억해 두는 것과 - * 같은 방식으로(`rememberStandardDefaults`) 여기 한 번 담아 두고 카드가 읽는다. - * ------------------------------------------------------------------------ */ -let remembered: CutSlopeCriteria | null = null; -let rememberedGrade: string | null = null; - -export function rememberCutSlopeCriteria(context: SectionContextResponse | null): void { - remembered = criteriaFrom(context); - rememberedGrade = context?.road_type ?? null; -} - -/** 설계자가 고른 암질을 얹는다. 없으면 기본값(경암) 그대로. */ -export function applyRockQualityChoice(quality: string | null | undefined): void { - if (!remembered || !quality) return; - remembered = { ...remembered, rockQuality: quality }; -} - -/** 지금 측점의 위반 목록 — 기준이 없거나 면제 등급이면 빈 목록. */ -export function violationsOf(section: CrossSection): CutSlopeViolation[] { - return cutSlopeViolations(section, remembered, rememberedGrade); -} diff --git a/B06_Section/B06_Section_Router.py b/B06_Section/B06_Section_Router.py index 57cb8470..006ad692 100644 --- a/B06_Section/B06_Section_Router.py +++ b/B06_Section/B06_Section_Router.py @@ -80,9 +80,6 @@ from config.config_db import get_db_pool, run_with_connection from config.config_system import ( EARTHWORK_CONVERSION_FACTORS, EARTHWORK_HAUL_EQUIPMENT_LIMITS_M, - FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES, - FOREST_ROAD_CUT_SLOPE_LIMITS, - FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT, FOREST_ROAD_MIN_WIDTH_M, NATURAL_SPOIL_MIN_GROUND_SLOPE, SECTION_VERTICAL_EXAGGERATION, @@ -144,11 +141,6 @@ async def get_section_context(project_id: UUID) -> SectionContextResponse | JSON ), standard_cross_section=STANDARD_CROSS_SECTION, stored_standard_cross_section=stored_standard, - cut_slope_limits={ - key: list(value) for key, value in FOREST_ROAD_CUT_SLOPE_LIMITS.items() - }, - cut_slope_rock_quality_default=FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT, - cut_slope_exempt_grades=list(FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES), rock_boundary_default_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M, rock_boundary_step_m=STANDARD_ROCK_BOUNDARY_STEP_M, earthwork_conversion=EARTHWORK_CONVERSION_FACTORS, diff --git a/B06_Section/B06_Section_Schema.py b/B06_Section/B06_Section_Schema.py index 427fb1c3..9b8dac61 100644 --- a/B06_Section/B06_Section_Schema.py +++ b/B06_Section/B06_Section_Schema.py @@ -243,11 +243,6 @@ class SectionContextResponse(BaseModel): # **화면은 config 기본값으로, 서버는 저장분으로** 계산해 같은 측점이 갈렸다. # 기존 `standard_cross_section`(기본값)은 그대로 두고 **한 칸만 더한다**. stored_standard_cross_section: dict[str, Any] | None = None - # 절토 비탈 법정 기울기 범위(별표2)와 지반유형 → 별표2 줄 기본 매핑, 검사 제외 등급. - # 브라우저가 상수를 복제하지 않게 **서버가 준 값을 그대로 기억**한다(표준단면과 같은 방식). - cut_slope_limits: dict[str, list[float]] = Field(default_factory=dict) - cut_slope_rock_quality_default: str = "hard_rock" - cut_slope_exempt_grades: list[str] = Field(default_factory=list) # 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m). rock_boundary_default_offset_m: float = -0.5 rock_boundary_step_m: float = 0.1 diff --git a/B06_Section/B06_Section_UI_Cross_Card_Chrome.ts b/B06_Section/B06_Section_UI_Cross_Card_Chrome.ts index f716468a..d7cd8c5c 100644 --- a/B06_Section/B06_Section_UI_Cross_Card_Chrome.ts +++ b/B06_Section/B06_Section_UI_Cross_Card_Chrome.ts @@ -13,7 +13,6 @@ import { buildRockBoundaryControl, sectionModeLabel, } from "./B06_Section_UI_Cross_Design"; -import { limitLabel, violationsOf } from "./B06_Section_Cut_Slope_Check"; import { type FillSlopeLength, fillSlopeLengths } from "./B06_Section_UI_Cross_Fit"; import { type DesignChangeHandler, @@ -86,26 +85,6 @@ export function appendCardHeader( openSlope.title = L("B06_Cross_SlopeUnclosed_Tip"); meta.append(openSlope); } - // 절토 비탈 법정 기울기(별표2) 위반 — 성토사면·미폐합 경고와 같은 자리·같은 모양이다. - // 소단이 서면 실효 경사가 완만해져 위반이 사라진 것처럼 보이므로, 판정은 **구간별** - // 경사(`cut_slope_segments`)로 한다(2026-09-07). - const cutViolations = violationsOf(section); - if (cutViolations.length) { - const worst = cutViolations[0]; - const badge = document.createElement("span"); - badge.className = "b06-cross-card__warning"; - badge.textContent = `⚠ 절토 1:${worst.ratio.toFixed(2)}`; - badge.title = cutViolations - .map( - (item) => - `${item.side === "left" ? "좌" : "우"} 1:${item.ratio.toFixed(2)}` + - ` — ${limitLabel(item.limitKey)} 법정 1:${item.limit.min}~${item.limit.max}` + - ` 범위 밖(${item.kind === "steep" ? "너무 급함" : "너무 완만함"})` + - ` · ${item.startOffsetM.toFixed(2)}~${item.endOffsetM.toFixed(2)}m`, - ) - .join("\n"); - meta.append(badge); - } const structureName = section.structure; if (structureName) { const structure = document.createElement("span"); diff --git a/B06_Section/B06_Section_UI_Page.ts b/B06_Section/B06_Section_UI_Page.ts index 890019f0..3877af0a 100644 --- a/B06_Section/B06_Section_UI_Page.ts +++ b/B06_Section/B06_Section_UI_Page.ts @@ -2,7 +2,6 @@ import { writeCrossDesignChoice } from "./B06_Section_Cross_Design_Session"; import { CURRENT_PROJECT_ID_KEY, ROUTES } from "@config/config_frontend"; import { leaveForDashboard } from "../A00_Common/b_missing_data_guard"; import { readByKey, stateKey, writeByKey } from "../A00_Common/b_page_state"; -import { rememberCutSlopeCriteria } from "./B06_Section_Cut_Slope_Check"; import { navigateTo } from "../A00_Common/router"; import { createButton, createInputField, showToast } from "@ui/ui_template_elements"; import type { StructureInstance, StructureType } from "../B05_Profile/B05_Profile_Api_Structures"; @@ -656,8 +655,6 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { standardPanel = createStandardPanel(projectId, context.standard_cross_section, applyPanelToAll); // 브라우저 횡단 계산이 옛 암 측점을 서버와 같은 기본값으로 다시 계산하게 기억해 둔다. rememberRockBoundaryDefault(projectId, context.rock_boundary_default_offset_m); - // 절토 비탈 법정 기울기 기준(별표2)도 같은 방식으로 기억해 둔다 — 카드가 읽는다. - rememberCutSlopeCriteria(context); standardPanelSlot.append(standardPanel.root); if (context.route_id === null) { diff --git a/B06_Section/B06_Section_UI_Standard_Panel.ts b/B06_Section/B06_Section_UI_Standard_Panel.ts index f9a81493..9279ab3b 100644 --- a/B06_Section/B06_Section_UI_Standard_Panel.ts +++ b/B06_Section/B06_Section_UI_Standard_Panel.ts @@ -26,7 +26,6 @@ import { } from "../A00_Common/b_page_state"; import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; import { createButton, createInputField, createSelectField } from "@ui/ui_template_elements"; -import { applyRockQualityChoice } from "./B06_Section_Cut_Slope_Check"; import { buildStandardDiagram } from "./B06_Section_UI_Standard_Diagram"; import { getCompanyStandard, @@ -397,40 +396,8 @@ export function createStandardPanel( } actions.append(resetButton); - // ── 절토 법정 기울기 판정 기준: **암질** (2026-09-07 사용자 확정) ──────────── - // 「연암과 경암도 별도의 버튼으로 선택하게 하고 경암을 기본값으로. 리핑을 할지 발파를 - // 할지는 설계자가 선택 필요함」 — 축이 둘이다. **암질**(연암·경암)은 별표2 경사 판정의 - // 기준이고, **굴착 공법**(리핑암·발파암)은 수량·단가 몫이다. 한쪽에서 다른 쪽을 추론하지 - // 않는다. 절토 경사비가 오는 자리 옆이라 여기 둔다. - const rockQualityWrap = document.createElement("div"); - rockQualityWrap.className = "b06-std__cutclass"; - const rockQualityTitle = document.createElement("p"); - rockQualityTitle.className = "b06-std__cutclass-title"; - rockQualityTitle.textContent = "절토 법정 기울기 판정 암질 (별표2)"; - rockQualityTitle.title = - "별표2: 경암 1:0.3~0.8 · 연암 1:0.5~1.2 · 토사 1:0.8~1.5. 작업임도는 규정 없음.\n" + - "굴착 공법(리핑·발파)과는 다른 값이다 — 공법은 수량·단가에서 따로 고른다."; - rockQualityWrap.append(rockQualityTitle); - const rockQualityField = createSelectField({ - label: "암질", - options: [ - { value: "hard_rock", text: "경암 (1:0.3~0.8)" }, - { value: "soft_rock", text: "연암 (1:0.5~1.2)" }, - ], - value: state.rock_quality ?? "hard_rock", - onChange: (value) => { - state.rock_quality = value; - persist(); - applyRockQualityChoice(value); - onApplyAll?.(); - }, - }); - rockQualityWrap.append(rockQualityField.root); - // 처음 세울 때도 고른 값을 검사에 반영한다(세션에 남아 있던 선택 포함). - applyRockQualityChoice(state.rock_quality ?? "hard_rock"); - // 횡단 반폭은 사이드의 **별도 컨테이너**로 뺐다(2026-08-23) — 여기는 표준단면 설정만. - root.append(body, rockQualityWrap, loader, actions); + root.append(body, loader, actions); return { root, diff --git a/config/config_system_design.py b/config/config_system_design.py index d430961c..c239ab33 100644 --- a/config/config_system_design.py +++ b/config/config_system_design.py @@ -46,29 +46,6 @@ ROUTE_GRADE_CLASSES = ("trunk", "fire", "work", "branch") FOREST_ROAD_MAX_GRADE = {"trunk": 0.26, "fire": 0.26, "branch": 0.28, "work": 0.40} FOREST_ROAD_MIN_CURVE_R_M = {"trunk": 12.0, "fire": 12.0, "branch": 10.0, "work": 6.0} -# ───────────────────────────────────────────────────────────────────────── -# 절토 비탈 법정 기울기 (별표2) -# -# 근거 — 지식DB `resources/knowledge/technical_info/01_임도/02_상세설계/절토_비탈면.md` §1. -# 그 문서의 [구현] 줄대로 **판정은 별표2 범위로** 한다(실무 관행값은 후보일 뿐 기준이 아님). -# 값은 수직 1 에 대한 수평(1:n 의 n). -# ───────────────────────────────────────────────────────────────────────── -FOREST_ROAD_CUT_SLOPE_LIMITS = { - "hard_rock": (0.3, 0.8), # 암석지 — 경암 - "soft_rock": (0.5, 1.2), # 암석지 — 연암 - "soil": (0.8, 1.5), # 토사지역 -} -# 절토 경사 판정에 쓸 **암질** 기본값 (2026-09-07 사용자 확정). -# -# ⚠ 축이 둘이다 — 섞지 말 것. -# · **암질**(연암·경암) = 별표2 경사 판정의 기준. 설계자가 고른다. 기본값 **경암**. -# · **굴착 공법**(리핑암·발파암, `cut_rock_kind`) = 어떻게 파는가. 수량·단가 몫이다. -# 한쪽에서 다른 쪽을 **추론하지 않는다**(사용자: 「리핑을 할지 발파를 할지는 설계자가 선택」). -# 처음에는 공법에서 암질을 유추하려 했으나 그것이 잘못 세운 문제였다. -FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT = "hard_rock" -# 절토 기울기 규정이 없는 등급 — 작업임도(별표2 §1 「작업임도: 절토 기울기 표 규정 없음」). -FOREST_ROAD_CUT_SLOPE_EXEMPT_GRADES = ("work",) - # 대안(정속경사) 파라미터 ROUTE_ALT_MIN_GRADE = float(os.getenv("ROUTE_ALT_MIN_GRADE", "0.08")) ROUTE_ALT_MAX_GRADE = float(os.getenv("ROUTE_ALT_MAX_GRADE", "0.14"))