Files
Aislo/B05_Profile/B05_Profile_UI_Profile_Balance.ts
T
eomsangdonandClaude Fable 5 8812657022 feat(B05/B02): 표시 정보 정비 + 설계속도 축 도입
- 종단 상단 표시줄을 법정 판정 3항목으로 축소: 최대 기울기/상한(별표2 Ⅰ.2.라),
  절·성토 불균형(Ⅰ.1.나.(4)(다)), 종단곡선 필요 n곳(Ⅰ.2.마 — 대수차 5% 초과인데
  곡선이 빠진 변화점). 변화점·종단곡선 개수·기본 곡선길이 L·중복 경고칩은 제거.
- [초기선 복원] → [편집 되돌리기] — 실제 동작은 화면 편집 델타 삭제이지 저장 지점
  복귀가 아니다. 툴팁에 동작과 [초기화]와의 차이를 적었다.
- 3D 뷰포트 안내 라벨 삭제(로딩 완료 후 남던 조작법 문구).
- 배수유역 버튼 정비: 유역 다시 나누기 / 선택한 관 삭제 / 배관 배치 초기화 +
  각 툴팁에 실제 동작 명시(선택 삭제는 툴팁 자체가 없었다).
- 유토곡선 요약 12칩 → 5칩(절토·성토·잉여|부족·운반·검산). 토질 3종 내역·다짐
  환산·블록 수·장거리 운반·사토는 해당 칩 툴팁으로. B05·B06 공용 함수라 동시 반영.
- 설계속도 축 도입(임도는 속도를 낼 수 없는 노선 — 기본 20km/h):
  · 임도 종류는 프로젝트 등록값(projects.road_type)을 읽어 B05가 읽기 전용 표시
    (main→간선임도, fire→산불진화임도, 그 외→작업임도). 화면에서 고치지 않는다.
  · 설계속도 선택(간선·산불진화 20/30/40, 작업 20 고정) 신설 — 종단기울기 상한과
    종단곡선 반경이 등급이 아니라 이 값으로 정해진다(지식DB 설계제원_총괄 §6·§7).
  · B02 임도 종류 선택지를 현행 3종으로 정리(지선 폐지·계류보전은 사방 구분).
  · 백엔드: ROUTE_GRADE_CLASSES 3종+branch 호환, selectable_design_speeds 신설,
    resolve_design_speed 신설, legal_grade_limit_pct·resolve_grade_options에
    설계속도 인자, sections/context가 road_type 제공(B06도 저장값 승계).
  · 계획선 정책은 그릴 때마다 현재 기준으로 동기화 — 저장분의 옛 상한이 남지 않는다.
- 검증: pytest 125 통과(설계속도 12건 신규), tsc, 헤드 브라우저 — 진입 시 상한 9%,
  40km/h 전환 시 7% 즉시 반영, 원복 9%, 안내문 '간선임도 · 설계속도 20km/h ·
  일반지형', 유토곡선 5칩, 유역 버튼 새 이름·툴팁, 3D 라벨 빈 문자열 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:46:03 +09:00

120 lines
6.0 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.
/* =============================================================================
* B05_Profile_UI_Profile_Balance.ts
* 종단 패널 상단 표시줄 — **법정 판정에 쓰이는 값만** 남긴다(2026-08-19 재편).
*
* 표시 항목(별표2 근거):
* 최대 기울기 x.x% (상한 y%) — Ⅰ.2.라: 설계속도·지형별 상한 준수 판정
* 불균형 z% / 허용 w% — .1.나.(4)(다): 시공계획고 절·성토 균형
* 곡선 필요 n곳 — Ⅰ.2.마: 대수차 5% 초과 변화점의 종단곡선 삽입
* 절·성토 면적은 하단 유토곡선이 부피로 더 정확히 보여 주고, 변화점·종단곡선 개수와
* 기본 곡선길이 L은 판단 기준이 없어 뺐다(2026-08-19 사용자 지적 + 지식DB 대조).
* [저장선 복원]은 상단에서 내려 그래프 조작부로 옮겼다.
*
* 패널 본체(B05_Profile_UI_Profile_Panel)가 700줄 한계에 닿아 분리했다.
* 표시줄은 상태를 갖지 않는다 — 그릴 때마다 현재 선형·편집 상태를 인자로 받는다.
* ========================================================================== */
import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment";
export interface BalanceBarParams {
/** 표시줄 컨테이너. 그릴 때마다 통째로 갈아 끼운다. */
balanceBar: HTMLElement;
/** 현재 계획선(없으면 안내만 띄운다). */
alignment: ProfileAlignment | null;
/** 계획선이 없고 저장분이 구버전 형식일 때 재계산을 안내한다. */
legacyAlignment: boolean;
/** 편집이 있었는지(초기선 복원 버튼 노출 조건). */
edited: boolean;
/** 비정규 측점이 있는지(초기선 복원 버튼 노출 조건). */
hasIrregularStations: boolean;
/** 저장되지 않은 편집이 있는지. */
dirty: boolean;
/** [초기선 복원] — 편집·비정규 측점을 모두 지운다. */
onResetAll: () => void;
}
export function renderBalanceBar(params: BalanceBarParams): void {
params.balanceBar.replaceChildren();
if (!params.alignment) {
if (params.legacyAlignment) {
const note = document.createElement("span");
note.className = "b05-route-profile__balance-warning";
note.textContent =
"⚠ 계획선 데이터가 구버전 형식입니다 — [최적 경로 계산]을 다시 실행하세요.";
params.balanceBar.append(note);
}
return;
}
const { alignment } = params;
const { balance, policy, violations } = alignment;
// 최대 종단기울기 — 법정 상한 대비가 이 화면의 첫 판정 항목이다(별표2 Ⅰ.2.라).
const steepest = alignment.segments.reduce(
(worst, segment) => Math.max(worst, Math.abs(segment.grade_percent)),
0,
);
// 종단곡선이 필요한데 빠진 변화점 — 생략은 비포장 & 대수차 5% 이하만 허용된다
// (별표2 Ⅰ.2.마 / 지식DB 종단선형 §3). skip_allowed가 그 판정 결과다.
const curvesNeeded = alignment.curves.filter(
(curve) => curve.omitted && !curve.skip_allowed,
).length;
const entries: Array<[string, string, string?]> = [
[
"최대 기울기",
`${steepest.toFixed(1)} % / 상한 ${policy.max_grade_pct.toFixed(1)} %`,
violations.length ? "over" : undefined,
],
[
"절·성토 불균형",
`${balance.imbalance_percent.toFixed(1)} % / 허용 ${balance.tolerance_percent.toFixed(0)} %`,
balance.within_tolerance ? undefined : "over",
],
];
// 필요한 곳이 없으면 적지 않는다 — "0곳"은 화면 폭만 먹는다.
if (curvesNeeded) entries.push(["종단곡선 필요", `${curvesNeeded} 곳`, "over"]);
const editedCount = Object.keys(alignment.edits.station_offsets).length;
if (editedCount) entries.push(["편집 측점", `${editedCount} 개`, "edited"]);
entries.forEach(([label, value, tone]) => {
const item = document.createElement("span");
item.className = `b05-route-profile__balance-item${tone ? ` is-${tone}` : ""}`;
const caption = document.createElement("em");
caption.textContent = label;
item.append(caption, document.createTextNode(value));
// 상한 초과 구간의 내역은 별도 경고 칩 대신 이 항목의 툴팁으로 붙인다 —
// 같은 사실을 두 번 적지 않는다(2026-08-19 재편).
if (label === "최대 기울기" && violations.length) {
item.title = violations
.map(
(entry) =>
`구간 ${entry.segment_index + 1}: ${entry.value.toFixed(2)}% > ${entry.limit}%`,
)
.join("\n");
}
params.balanceBar.append(item);
});
if (params.edited || params.hasIrregularStations) {
const reset = document.createElement("button");
reset.type = "button";
reset.className = "b05-route-profile__balance-reset";
// 이 버튼이 지우는 것은 **화면에 쌓인 편집 델타**다(계획선 변화점 이동·비정규
// 측점). 임시저장을 거치면 그 편집이 정본에 반영되므로 결과적으로 "마지막 저장
// 상태"가 되는 것뿐, 저장 지점으로 되돌아가는 기능이 아니다. 최초 자동 계산
// 상태로의 복귀는 좌측 하단 [초기화](B05·B06 재계산)가 맡는다(2026-08-19 정정).
// 자리도 값 칩들 뒤 — 편집 상태(미저장 배지) 옆이 맥락에 맞다.
reset.textContent = "편집 되돌리기";
reset.title =
"화면에서 수정한 계획선 변화점과 추가한 비정규 측점을 지웁니다.\n" +
"이미 임시저장한 내용은 정본에 반영돼 있어 그대로 남습니다.\n" +
"최초 자동 계산 상태로 되돌리려면 좌측 하단 [초기화]를 쓰세요.";
reset.addEventListener("click", () => {
params.onResetAll();
});
params.balanceBar.append(reset);
}
if (params.dirty) {
const badge = document.createElement("span");
badge.className = "b05-route-profile__balance-item is-unsaved";
badge.textContent = "미저장 (확정 시 반영)";
params.balanceBar.append(badge);
}
}