main_laptop_1 -> main byeonghap (4 hwangyeong 585 commits) #12
@@ -45,15 +45,7 @@ export interface StandardCrossGroup {
|
||||
/** 표준 횡단면 설정 패널 그룹 키. */
|
||||
export type StandardCrossKey = "soil" | "rock" | "paved";
|
||||
|
||||
export type StandardCrossSection = Record<StandardCrossKey, StandardCrossGroup> & {
|
||||
/** 절토 경사 판정에 쓸 **암질** — `hard_rock`(경암) / `soft_rock`(연암). 기본 경암.
|
||||
*
|
||||
* ⚠ 굴착 공법(리핑암·발파암, `cut_rock_kind`)과 **다른 축**이다. 공법은 수량·단가 몫이고
|
||||
* 암질은 별표2 경사 판정의 기준이며, 한쪽에서 다른 쪽을 추론하지 않는다
|
||||
* (2026-09-07 사용자 확정 — 「리핑을 할지 발파를 할지는 설계자가 선택」).
|
||||
* 표준단면 설정과 함께 저장된다. 없으면 기본값(경암). */
|
||||
rock_quality?: string;
|
||||
};
|
||||
export type StandardCrossSection = Record<StandardCrossKey, StandardCrossGroup>;
|
||||
|
||||
// 지반유형·토량환산계수·운반장비 한계거리는 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<string, number[]>;
|
||||
/** 절토 경사 판정에 쓸 **암질** 기본값(`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;
|
||||
|
||||
@@ -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<string, CutSlopeLimit>;
|
||||
/** 절토 경사 판정에 쓸 **암질** — `hard_rock`(경암) / `soft_rock`(연암). 설계자가 고른다.
|
||||
* ⚠ 굴착 공법(리핑암·발파암)과 다른 축이라 공법에서 추론하지 않는다(2026-09-07 사용자 확정). */
|
||||
rockQuality: string;
|
||||
/** 절토 기울기 규정이 없는 등급(작업임도). */
|
||||
exemptGrades: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
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<string, CutSlopeLimit> = {};
|
||||
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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<void> {
|
||||
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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user