Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
This commit is contained in:
@@ -30,8 +30,8 @@ import { solveSplit, specKeys, type SplitRole } from "../A00_Common/design/desig
|
||||
export const hidesTypeSummary = (type: StructureType): boolean =>
|
||||
!!STRUCTURE_SPECS[type.type_id]?.containers.length || !!type.groups?.length || type.group === "B";
|
||||
|
||||
/** 길이 = 전 + 후 를 배관 맞물림(`solveSplit` · 마지막에 고친 둘을 지킴)으로 푸는 종류(표 07 가드레일). */
|
||||
const SOLVE_SPLIT = new Set(["guardrail"]);
|
||||
/** 길이 = 전 + 후 를 배관 맞물림(`solveSplit` · 마지막에 고친 둘을 지킴)으로 푸는 종류(표 07 가드레일 · 표 09 흙막이 · 옹벽). */
|
||||
const SOLVE_SPLIT = new Set(["guardrail", "soil_guard", "retaining_wall"]);
|
||||
const SPLIT_ROLE: Record<string, SplitRole> = {
|
||||
length_m: "length",
|
||||
before_m: "before",
|
||||
@@ -159,6 +159,22 @@ export function renderOptionFields(
|
||||
input.addEventListener("change", () => refresh(true));
|
||||
}
|
||||
});
|
||||
// 숨긴 길이 · 전 · 후(표 09 소단) — 칸은 안 그리고 값만 쥠: 범위 계산 · 저장이 옛 값 · 기본값을 이어 씀(54-26)
|
||||
for (const option of type.options) {
|
||||
if (!SPLIT_ROLE[option.key] || option.visible !== false) continue;
|
||||
const input = document.createElement("input");
|
||||
input.value = values[option.key] === undefined ? "" : String(values[option.key]);
|
||||
all.push({
|
||||
wrap: document.createElement("div"),
|
||||
entry: {
|
||||
key: option.key,
|
||||
required: false,
|
||||
input,
|
||||
read: () => Number(input.value) || 0,
|
||||
isEmpty: () => input.value.trim() === "",
|
||||
},
|
||||
});
|
||||
}
|
||||
/** 길이 · 전 · 후 맞물림 — 고친 차례(최근이 앞)를 쥐고 나머지 하나를 셈. */
|
||||
const recent: SplitRole[] = [];
|
||||
function solveInto(key: string): void {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"판": 14,
|
||||
"판": 15,
|
||||
"schema_version": 1,
|
||||
"comment": "B05 구조물 타입 레지스트리 정본. 근거·옵션 상세는 PLAN.md 구조물 리스트와 resources/knowledge/technical_info/01_임도 참조. 2026-08-17 선택지 재편: B군(종단배수)·F군(생태/녹화)·G군(노면공)은 enabled:false 보존 — B05 선택지에서 빠지고 B06 개별 횡단도 옵션으로 재사용한다. 바닥막이(bed_sill)·생태연못(eco_pond)은 임도 미사용 확정으로 삭제. 배관·BOX암거·물넘이·세월교는 pipe_points.json 정본 관리(managed_by) — structures.json에 저장하지 않으며, 배관 부속(유형/집수정/기슭막이/돌붙임)·날개벽 옵션 정의는 B06/B07 필수 승격의 선언 근거다. ★기본값 원칙(2026-08-16 크로스체크 2차): default를 두는 것은 ①법정이 등급과 무관하게 못박은 단일 수치 ②사용자가 개발 단계에서 확정한 값 ③도메인 수치가 아닌 표시용 문자열, 이 셋뿐이다. 재료·형식 같은 설계자 판단 선택지와 미확정 제원은 default 없이 required로 두어 사용자가 직접 고르게 한다 (pipe_diameter_mm 1000 = 별표2 원칙값+사용자 확정 — 화이트리스트 등재). BOX암거 본체 2.0×2.0·날개벽 유입/유출 있음·짧은쪽 높이 1m·길이 2m·각도 45°는 2026-08-17 사용자 확정값이다 — 본체 규격은 실무 관측 규격(2.0×2.0·3.0×3.0)을 폼에서 프리셋 select로 받고 필요 시 사용자 지정으로 자유 입력하되, 저장 키는 body_width_m·body_height_m 숫자 그대로다(프리셋 전용 키를 만들지 않는다). 이 값들은 B05 서브폼이 실제로 받으므로 phase를 detail로 두지 않는다. ★보호공 개편(2026-08-17 사용자 지시): 구 `*_pitching`(있음/없음) + `*_pitching_finish`(찰/메) 두 축을 `*_protection` 한 축(돌붙임(찰)·돌붙임(메)·도수로)으로 합쳤다 — 도수로·산비탈수로(B4)는 유출부에서 돌붙임 대신 쓰는 공종이라 별도 구조물이 아니라 보호공 선택지로 받는다. \"없음\"은 삭제(물이 흐르는 자리라 보호공 필수). 치수는 배타 — 돌붙임이면 `*_protection_area_m2`(10㎡), 도수로면 `*_protection_width_m`(1m, 지식DB 수치 근거 없음·사용자 확정, 실무 확인 후 개선). 구 저장분은 `common_util_drainage_pipes.parse_pipe_points`가 새 키로 옮긴다.",
|
||||
"구조물군": {
|
||||
@@ -975,15 +975,15 @@
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"key": "height_m",
|
||||
"label": "높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2,
|
||||
"default_basis": "기본값 · 소광리 도면 H=2.0 · 바꿀 수 있음",
|
||||
"required": false,
|
||||
"phase": "b05",
|
||||
"empty_means": "비워도 놓임 — 줄만 서고 미확정이라 금액에 안 들어감(높이를 적으면 섬 · 2026-09-14 브레인 판정 ①)"
|
||||
"key": "wall_form",
|
||||
"label": "형태",
|
||||
"input": "select",
|
||||
"choices": [
|
||||
"콘크리트"
|
||||
],
|
||||
"default": "콘크리트",
|
||||
"default_basis": "표 09 — 선택지 하나(콘크리트)",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "length_m",
|
||||
@@ -994,6 +994,17 @@
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "height_m",
|
||||
"label": "높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2,
|
||||
"default_basis": "기본값 · 소광리 도면 H=2.0 · 바꿀 수 있음",
|
||||
"required": false,
|
||||
"phase": "b05",
|
||||
"empty_means": "비워도 놓임 — 줄만 서고 미확정이라 금액에 안 들어감(높이를 적으면 섬 · 2026-09-14 브레인 판정 ①)"
|
||||
},
|
||||
{
|
||||
"key": "before_m",
|
||||
"label": "기준측점 전",
|
||||
@@ -1525,13 +1536,20 @@
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"key": "height_m",
|
||||
"label": "높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2.5,
|
||||
"key": "form",
|
||||
"label": "형태",
|
||||
"input": "select",
|
||||
"choices": [
|
||||
"돌쌓기(찰)",
|
||||
"돌쌓기(메)",
|
||||
"콘크리트",
|
||||
"돌망태",
|
||||
"통나무·목재틀",
|
||||
"바자"
|
||||
],
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
"empty_means": "비우면 통나무·목재틀로 그림(횡단 기본 형태) — B05 로 옮긴 칸이라 필수로 안 막음(옛 흙막이 저장 보호)"
|
||||
},
|
||||
{
|
||||
"key": "length_m",
|
||||
@@ -1542,6 +1560,15 @@
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "height_m",
|
||||
"label": "높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2.5,
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "before_m",
|
||||
"label": "기준측점 전",
|
||||
@@ -1560,24 +1587,6 @@
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "form",
|
||||
"label": "형식",
|
||||
"input": "select",
|
||||
"choices": [
|
||||
"콘크리트",
|
||||
"돌(찰)",
|
||||
"돌(메)",
|
||||
"블록쌓기",
|
||||
"돌망태",
|
||||
"흙포대",
|
||||
"통나무쌓기",
|
||||
"떼"
|
||||
],
|
||||
"default": null,
|
||||
"required": true,
|
||||
"phase": "detail"
|
||||
},
|
||||
{
|
||||
"key": "side",
|
||||
"label": "설치 측",
|
||||
@@ -1969,6 +1978,24 @@
|
||||
"quantity"
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"key": "width_m",
|
||||
"label": "소단 폭",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 0.5,
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "height_m",
|
||||
"label": "소단 높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": null,
|
||||
"required": false,
|
||||
"empty_means": "비워도 놓임 — 아직 계산(소단 간격 · 횡단)에 안 쓰임(표 09 새 칸)"
|
||||
},
|
||||
{
|
||||
"key": "length_m",
|
||||
"label": "길이",
|
||||
@@ -1996,15 +2023,6 @@
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "width_m",
|
||||
"label": "폭",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 0.5,
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
},
|
||||
{
|
||||
"key": "interval_m",
|
||||
"label": "간격(사면길이)",
|
||||
@@ -3396,11 +3414,51 @@
|
||||
"동작": [
|
||||
"포장_물넘이_겹침"
|
||||
]
|
||||
},
|
||||
"soil_guard": {
|
||||
"이름": {
|
||||
"form=돌쌓기(찰)": "돌 쌓기(찰)",
|
||||
"form=돌쌓기(메)": "돌 쌓기(메)",
|
||||
"form=콘크리트": "콘크리트",
|
||||
"form=돌망태": "돌망태",
|
||||
"form=통나무·목재틀": "통나무/목재틀",
|
||||
"form=바자": "바자"
|
||||
}
|
||||
}
|
||||
},
|
||||
"표시": {
|
||||
"pipe": {
|
||||
"pipe_kind": false
|
||||
},
|
||||
"soil_guard": {
|
||||
"side": false,
|
||||
"tiers": false,
|
||||
"lift_m": false,
|
||||
"shift_m": false,
|
||||
"thickness_top_m": false,
|
||||
"thickness_bottom_m": false,
|
||||
"back_len_cm": false,
|
||||
"stone_kind": false,
|
||||
"stone_supply": false,
|
||||
"stone_coeff_basis": false,
|
||||
"fill_concrete_mpa": false,
|
||||
"face_slope_ratio": false,
|
||||
"foundation": false,
|
||||
"weep_hole_diameter_mm": false,
|
||||
"weep_hole_area_m2": false
|
||||
},
|
||||
"retaining_wall": {
|
||||
"form": false,
|
||||
"side": false,
|
||||
"weep_hole_diameter_mm": false,
|
||||
"weep_hole_area_m2": false
|
||||
},
|
||||
"berm": {
|
||||
"length_m": false,
|
||||
"before_m": false,
|
||||
"after_m": false,
|
||||
"interval_m": false,
|
||||
"slope_deg": false
|
||||
}
|
||||
},
|
||||
"그룹": {
|
||||
@@ -3521,16 +3579,13 @@
|
||||
"retaining_wall": [
|
||||
{
|
||||
"id": "gretaindetail",
|
||||
"이름": "상세 내용",
|
||||
"이름": "옹벽",
|
||||
"옵션": [
|
||||
"height_m",
|
||||
"wall_form",
|
||||
"length_m",
|
||||
"height_m",
|
||||
"before_m",
|
||||
"after_m",
|
||||
"form",
|
||||
"side",
|
||||
"weep_hole_diameter_mm",
|
||||
"weep_hole_area_m2"
|
||||
"after_m"
|
||||
],
|
||||
"형상": {}
|
||||
}
|
||||
@@ -3592,28 +3647,13 @@
|
||||
"soil_guard": [
|
||||
{
|
||||
"id": "gsoilgdetail",
|
||||
"이름": "상세 내용",
|
||||
"이름": "흙막이",
|
||||
"옵션": [
|
||||
"height_m",
|
||||
"length_m",
|
||||
"before_m",
|
||||
"after_m",
|
||||
"form",
|
||||
"side",
|
||||
"tiers",
|
||||
"lift_m",
|
||||
"shift_m",
|
||||
"thickness_top_m",
|
||||
"thickness_bottom_m",
|
||||
"back_len_cm",
|
||||
"stone_kind",
|
||||
"stone_supply",
|
||||
"stone_coeff_basis",
|
||||
"fill_concrete_mpa",
|
||||
"face_slope_ratio",
|
||||
"foundation",
|
||||
"weep_hole_diameter_mm",
|
||||
"weep_hole_area_m2"
|
||||
"length_m",
|
||||
"height_m",
|
||||
"before_m",
|
||||
"after_m"
|
||||
],
|
||||
"형상": {}
|
||||
}
|
||||
@@ -3647,12 +3687,8 @@
|
||||
"id": "gbermdetail",
|
||||
"이름": "상세 내용",
|
||||
"옵션": [
|
||||
"length_m",
|
||||
"before_m",
|
||||
"after_m",
|
||||
"width_m",
|
||||
"interval_m",
|
||||
"slope_deg"
|
||||
"height_m"
|
||||
],
|
||||
"형상": {}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
"""PLAN 54-26 — 사용자 구조물 표 09 사면안정 › 흙막이 · 옹벽 · 소단.
|
||||
|
||||
· 컨테이너 = 흙막이 「흙막이」 · 옹벽 「옹벽」 · 소단 「상세 내용」 — 칸은 표대로만 · 표 밖 옛 칸은 숨김(값 · 기본값 그대로)
|
||||
· 흙막이 형태 = 배관 흙막이 6 · 옹벽 형태 = 콘크리트 하나(새 칸 `wall_form` · 옛 형식 `form` 값은 그대로 통과)
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Repository import _validate_types
|
||||
from B05_Profile.B05_Profile_Structures_Schema import (
|
||||
DEFINITION_PATH,
|
||||
StructureInstance,
|
||||
load_structure_types,
|
||||
)
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Definition
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Groups import groups_of
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Options import hidden_keys
|
||||
|
||||
TABLE = {
|
||||
"soil_guard": (
|
||||
"gsoilgdetail",
|
||||
"흙막이",
|
||||
["form", "length_m", "height_m", "before_m", "after_m"],
|
||||
),
|
||||
"retaining_wall": (
|
||||
"gretaindetail",
|
||||
"옹벽",
|
||||
["wall_form", "length_m", "height_m", "before_m", "after_m"],
|
||||
),
|
||||
"berm": ("gbermdetail", "상세 내용", ["width_m", "height_m"]),
|
||||
}
|
||||
SOIL_FORMS = ["돌쌓기(찰)", "돌쌓기(메)", "콘크리트", "돌망태", "통나무·목재틀", "바자"]
|
||||
|
||||
|
||||
def _types():
|
||||
return {t.type_id: t for t in load_structure_types()}
|
||||
|
||||
|
||||
def test_컨테이너_하나_칸은_표대로_차례대로():
|
||||
groups = groups_of()
|
||||
types = _types()
|
||||
hidden = hidden_keys()
|
||||
for type_id, (gid, name, keys) in TABLE.items():
|
||||
assert groups[type_id] == [{"id": gid, "name": name, "options": keys}]
|
||||
options = [o.key for o in types[type_id].options]
|
||||
assert options[: len(keys)] == keys # 칸 차례 = 표 차례
|
||||
assert hidden[type_id] == set(options) - set(keys) # 표 밖 = 숨김(값은 남음)
|
||||
|
||||
|
||||
def test_형태_선택지():
|
||||
types = _types()
|
||||
soil = {o.key: o for o in types["soil_guard"].options}
|
||||
pipe = {o.key: o for o in types["pipe"].options}
|
||||
assert soil["form"].choices == SOIL_FORMS == pipe["outlet_revet_form"].choices
|
||||
assert soil["form"].phase == "b05" # B05 = B06
|
||||
wall = {o.key: o for o in types["retaining_wall"].options}
|
||||
assert wall["wall_form"].choices == ["콘크리트"]
|
||||
assert "반중력식" in wall["form"].choices # 옛 형식 값 그대로 통과
|
||||
doc = json.loads(Path(DEFINITION_PATH).read_text("utf-8"))
|
||||
assert doc["종류"]["soil_guard"]["이름"]["form=통나무·목재틀"] == "통나무/목재틀"
|
||||
|
||||
|
||||
def test_옛_값_저장_막지_않음():
|
||||
def item(type_id, **options):
|
||||
return StructureInstance(
|
||||
type_id=type_id,
|
||||
placement="interval",
|
||||
chainage_m=20,
|
||||
start_m=20,
|
||||
end_m=30,
|
||||
options={"length_m": 10, "before_m": 0, "after_m": 10, **options},
|
||||
)
|
||||
|
||||
_validate_types(
|
||||
[
|
||||
item("soil_guard", height_m=2), # 형태 없는 옛 흙막이
|
||||
item("retaining_wall", form="반중력식", height_m=2),
|
||||
item("berm", width_m=0.5, interval_m=3, height_m=5),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_정의_검사_통과_판_이력():
|
||||
doc = json.loads(Path(DEFINITION_PATH).read_text("utf-8"))
|
||||
assert M02_SO_Definition.check(doc) == []
|
||||
assert M02_SO_Definition.history_path(doc["판"] - 1).is_file()
|
||||
@@ -20,6 +20,7 @@ KINDS = {
|
||||
"boulder_masonry": "gboulderdetail",
|
||||
"berm": "gbermdetail",
|
||||
}
|
||||
TABLE_09 = {"soil_guard", "retaining_wall", "berm"}
|
||||
|
||||
|
||||
def test_C군_종류마다_컨테이너_하나_옵션_전부():
|
||||
@@ -27,6 +28,8 @@ def test_C군_종류마다_컨테이너_하나_옵션_전부():
|
||||
assert sorted(t.type_id for t in types) == sorted(KINDS)
|
||||
groups = groups_of()
|
||||
for item in types:
|
||||
if item.type_id in TABLE_09: # 표 09 로 바뀜 — test_54_26_slope_table09
|
||||
continue
|
||||
assert groups[item.type_id] == [
|
||||
{
|
||||
"id": KINDS[item.type_id],
|
||||
@@ -55,4 +58,4 @@ def test_잇기는_컨테이너마다_집계_한_줄():
|
||||
]
|
||||
)
|
||||
assert [r["복사본"] for r in rows] == ["s1", "s1__gretaindetail"]
|
||||
assert rows[1]["summary_item"] == link and rows[1]["그룹"] == "상세 내용"
|
||||
assert rows[1]["summary_item"] == link and rows[1]["그룹"] == "옹벽" # 표 09 컨테이너 이름
|
||||
|
||||
@@ -270,7 +270,9 @@ def test_slope_group_computes_range_from_length():
|
||||
assert options[key].default == default, f"{type_id}.{key} 기본값"
|
||||
assert options[key].input == "number" and options[key].unit == "m", key
|
||||
keys = [option.key for option in item.options if option.phase == "b05"]
|
||||
assert keys[:3] == ["height_m", "length_m", "before_m"], f"{type_id} 옵션 순서"
|
||||
# 표 09 흙막이 · 옹벽 = 형태 → 길이 → 높이 → 전 → 후(54-26 · test_54_26_slope_table09)
|
||||
if type_id not in ("soil_guard", "retaining_wall"):
|
||||
assert keys[:3] == ["height_m", "length_m", "before_m"], f"{type_id} 옵션 순서"
|
||||
|
||||
|
||||
def test_retaining_wall_renamed_plain():
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/* 54-26 — B05 · B06 좌측 「구조물 배치」 사면안정 표 09(흙막이 · 옹벽 · 소단)
|
||||
* 시작 화면: B05 종단설계(#/b05-profile) · 검증 프로젝트(랩탑_보조)
|
||||
* 부르는 법: 단계마다 한 번씩 — 앞에 `window.__m5426 = "단계";` 를 붙여
|
||||
* seed → 원래 목록을 sessionStorage 에 두고 흙막이(20+0 · 잇기 하나) · 소단을 심음(PUT) · 그 뒤 orca reload
|
||||
* check → 세 종류 컨테이너 1(이름) · 칸 = 표대로 · 밖 칸 0 · 잇기 1 · 램프 · 형태 선택지 6/1
|
||||
* B05 에서 한 번(값을 sessionStorage 에 둠) · B06(#/b06-section) 에서 한 번(B05 = B06)
|
||||
* split → 흙막이 길이 10 · 전 3 → 후 7(B05 · 실시간 반영 · 저장 안 함)
|
||||
* height → B06 — 좌측 높이 1.8 · 2.3 → 20+0 횡단 벽 그림 높이(H=) 같은 값
|
||||
* save → [저장] — 파일 = 형태 · 길이 10 · 전 3 · 후 7 · 높이 2.3 · 그 뒤 orca reload
|
||||
* verify → 다시 연 흙막이 칸 = 파일 값(B05 · B06 각각)
|
||||
* restore → 원래 목록으로 되돌림(PUT) · 초안 비움
|
||||
* 쓰는 것: 검증 프로젝트 structures.json(되돌림 단계가 원래 값으로 다시 씀)
|
||||
*/
|
||||
(async () => {
|
||||
const MODE = window.__m5426 || "check";
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const until = async (fn, ms = 20000) => {
|
||||
for (let t = 0; t < ms; t += 200) {
|
||||
if (fn()) return true;
|
||||
await w(200);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const projectId = localStorage.getItem("frd_current_project_id");
|
||||
const modUrl = (part) =>
|
||||
performance
|
||||
.getEntriesByType("resource")
|
||||
.map((e) => e.name)
|
||||
.filter((n) => n.includes(part))
|
||||
.pop();
|
||||
const api = await import(modUrl("/B05_Profile/B05_Profile_Api_Structures.ts"));
|
||||
const LINK = { layer: "system", owner: null, key: "SS000001", name: "54-26 잇기" };
|
||||
const KINDS = {
|
||||
soil_guard: ["gsoilgdetail", "흙막이", /흙막이$/, 6], // 심은 것 하나 · 전 3 이면 19+17~20+7
|
||||
retaining_wall: ["gretaindetail", "옹벽", /^10\+0~10\+10옹벽/, 1],
|
||||
berm: ["gbermdetail", "상세 내용", /^28\+0~28\+10소단/, 0],
|
||||
};
|
||||
const TABLE = {
|
||||
soil_guard: ["form", "length_m", "height_m", "before_m", "after_m"],
|
||||
retaining_wall: ["wall_form", "length_m", "height_m", "before_m", "after_m"],
|
||||
berm: ["width_m", "height_m"],
|
||||
};
|
||||
const section = () => document.querySelector(".b05-structure-section");
|
||||
const selects = () => section().querySelectorAll(".b05-structure__grid select");
|
||||
const visible = (n) => n.offsetParent !== null;
|
||||
const listItem = (re) =>
|
||||
[...document.querySelectorAll(".b05-route__irregular-list li")].find((l) =>
|
||||
re.test(l.textContent.trim()),
|
||||
);
|
||||
const open = async (re) => {
|
||||
await until(() => listItem(re));
|
||||
const li = listItem(re);
|
||||
(li.querySelector("button") ?? li).click();
|
||||
await w(800);
|
||||
if (!selects()[1].value) (li.querySelector("button") ?? li).click(); // 재선택 = 해제 — 한 번 더
|
||||
await until(() => !!selects()[1].value);
|
||||
await w(400);
|
||||
};
|
||||
const reset = async () => {
|
||||
section().querySelector(".b05-route__irregular-actions button:last-child").click();
|
||||
await w(400);
|
||||
};
|
||||
const field = (key) =>
|
||||
section().querySelector(
|
||||
`[data-option-keys="${key}"] input, [data-option-keys="${key}"] select`,
|
||||
);
|
||||
const set = async (key, value) => {
|
||||
const input = field(key);
|
||||
input.value = String(value);
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await w(500);
|
||||
};
|
||||
const shape = (type) => {
|
||||
const boxes = [...section().querySelectorAll("[data-b05-group]")].filter(visible);
|
||||
const box = boxes[0];
|
||||
const form = box?.querySelector(
|
||||
'[data-option-keys="form"] select, [data-option-keys="wall_form"] select',
|
||||
);
|
||||
return {
|
||||
boxes: boxes.map((b) => `${b.dataset.b05Group}:${b.querySelector("legend")?.textContent}`),
|
||||
fields: box
|
||||
? [...box.querySelectorAll("[data-option-keys]")].map((n) => n.dataset.optionKeys)
|
||||
: [],
|
||||
loose: [...section().querySelectorAll("[data-option-keys]")].filter(
|
||||
(n) => !n.closest("[data-b05-group]") && visible(n),
|
||||
).length,
|
||||
links: box ? box.querySelectorAll(".b05-structure__summary").length : 0,
|
||||
twoRow: box ? box.querySelectorAll("[data-b05-summary=row]").length : 0,
|
||||
link: box?.querySelector(".b05-structure__summary-name")?.value.trim(),
|
||||
choices: form ? [...form.options].filter((o) => o.value).map((o) => o.textContent) : [],
|
||||
lamps: [...(listItem(KINDS[type][2])?.querySelectorAll(".b05-route__lamps > *") ?? [])].map(
|
||||
(d) => d.className,
|
||||
),
|
||||
};
|
||||
};
|
||||
const ok = (type, s) =>
|
||||
s.boxes.length === 1 &&
|
||||
s.boxes[0] === `${KINDS[type][0]}:${KINDS[type][1]}` &&
|
||||
JSON.stringify(s.fields) === JSON.stringify(TABLE[type]) &&
|
||||
s.loose === 0 &&
|
||||
s.links === 1 &&
|
||||
s.twoRow === 1 &&
|
||||
s.choices.length === KINDS[type][3] &&
|
||||
s.lamps.length === 1;
|
||||
const save = async () => {
|
||||
document.querySelector('[data-dock-action="save"]').click();
|
||||
await w(500);
|
||||
return until(() => !document.querySelector(".ui-overlay.is-active"), 60000);
|
||||
};
|
||||
const soilFile = async () =>
|
||||
(await api.fetchStructures(projectId)).structures.find((s) => s.type_id === "soil_guard");
|
||||
const out = { mode: MODE, page: location.hash };
|
||||
|
||||
if (MODE === "seed") {
|
||||
const now = await api.fetchStructures(projectId);
|
||||
sessionStorage.setItem("__5426_original", JSON.stringify(now.structures));
|
||||
const add = (type_id, at, options, extra = {}) => ({
|
||||
structure_id: null,
|
||||
type_id,
|
||||
placement: "interval",
|
||||
chainage_m: at,
|
||||
start_m: at,
|
||||
end_m: at + 10,
|
||||
options: { length_m: 10, before_m: 0, after_m: 10, ...options },
|
||||
memo: "54-26",
|
||||
...extra,
|
||||
});
|
||||
const next = [
|
||||
...now.structures,
|
||||
add(
|
||||
"soil_guard",
|
||||
400,
|
||||
{ form: "돌쌓기(찰)", height_m: 1.5 },
|
||||
{ group_items: { gsoilgdetail: LINK } },
|
||||
),
|
||||
add("berm", 560, { width_m: 1, height_m: 5 }),
|
||||
];
|
||||
await api.saveStructures(projectId, now.revision, next);
|
||||
for (const k of Object.keys(sessionStorage))
|
||||
if (k.includes("draft:structures") || k.includes("result:section-detail"))
|
||||
sessionStorage.removeItem(k);
|
||||
out.seeded = (await api.fetchStructures(projectId)).structures
|
||||
.filter((s) => s.type_id in KINDS)
|
||||
.map((s) => s.type_id);
|
||||
}
|
||||
|
||||
if (MODE === "check") {
|
||||
await until(() => document.querySelectorAll(".b05-route__irregular-list li").length > 0);
|
||||
const r = {};
|
||||
for (const [type, [, , re]] of Object.entries(KINDS)) {
|
||||
await open(re);
|
||||
r[type] = shape(type);
|
||||
r[type].ok = ok(type, r[type]);
|
||||
}
|
||||
await reset();
|
||||
out.allOk = Object.values(r).every((s) => s.ok);
|
||||
out.read = r;
|
||||
if (!location.hash.includes("b06")) sessionStorage.setItem("__5426_b05", JSON.stringify(r));
|
||||
else out.sameAsB05 = sessionStorage.getItem("__5426_b05") === JSON.stringify(r);
|
||||
}
|
||||
|
||||
if (MODE === "split") {
|
||||
await open(KINDS.soil_guard[2]);
|
||||
await set("length_m", 10);
|
||||
await set("before_m", 3);
|
||||
out.after = field("after_m").value;
|
||||
out.ok = Number(out.after) === 7;
|
||||
}
|
||||
|
||||
if (MODE === "height") {
|
||||
// 독립 벽 조정창은 위치 전용(8-29 지시) — 높이 = 좌측 칸 하나 · 횡단 그림이 그 값을 따라감
|
||||
await open(KINDS.soil_guard[2]);
|
||||
await w(1500);
|
||||
const card = () =>
|
||||
[...document.querySelectorAll(".b06-cross-card")].find((c) =>
|
||||
/^\s*20\+0400\.0m/.test(c.textContent),
|
||||
);
|
||||
const drawn = () => {
|
||||
const tip = card()?.querySelector(".b06-chart__culvert-revet title")?.textContent ?? "";
|
||||
return Number(tip.match(/H=([\d.]+)m/)?.[1]);
|
||||
};
|
||||
const near = (a, b) => Math.abs(a - b) < 0.01;
|
||||
out.steps = [];
|
||||
for (const h of [1.8, 2.3]) {
|
||||
await set("height_m", h);
|
||||
await until(() => near(drawn(), h), 8000);
|
||||
out.steps.push({ left: Number(field("height_m").value), drawn: drawn() });
|
||||
}
|
||||
out.ok = out.steps.every((s) => near(s.left, s.drawn));
|
||||
}
|
||||
|
||||
if (MODE === "save") {
|
||||
out.saved = await save();
|
||||
const soil = await soilFile();
|
||||
out.file = soil.options;
|
||||
out.ok =
|
||||
soil.options.form === "돌쌓기(찰)" &&
|
||||
soil.options.length_m === 10 &&
|
||||
soil.options.before_m === 3 &&
|
||||
soil.options.after_m === 7 &&
|
||||
soil.options.height_m === 2.3 &&
|
||||
JSON.stringify(soil.group_items) === JSON.stringify({ gsoilgdetail: LINK });
|
||||
}
|
||||
|
||||
if (MODE === "verify") {
|
||||
await open(KINDS.soil_guard[2]);
|
||||
const soil = await soilFile();
|
||||
out.form = TABLE.soil_guard.map((k) => field(k).value);
|
||||
out.file = TABLE.soil_guard.map((k) => String(soil.options[k]));
|
||||
out.ok = out.form.every((v, i) => Number(v) === Number(out.file[i]) || v === out.file[i]);
|
||||
await reset();
|
||||
}
|
||||
|
||||
if (MODE === "restore") {
|
||||
const original = JSON.parse(sessionStorage.getItem("__5426_original") || "null");
|
||||
if (!original) return JSON.stringify({ error: "원래 값 없음" });
|
||||
const now = await api.fetchStructures(projectId);
|
||||
await api.saveStructures(projectId, now.revision, original);
|
||||
for (const k of Object.keys(sessionStorage))
|
||||
if (
|
||||
k.includes("draft:structures") ||
|
||||
k.includes("result:section-detail") ||
|
||||
k.startsWith("__5426_")
|
||||
)
|
||||
sessionStorage.removeItem(k);
|
||||
const after = await api.fetchStructures(projectId);
|
||||
const key = (list) =>
|
||||
JSON.stringify(
|
||||
list.map((s) => [
|
||||
s.type_id,
|
||||
s.start_m,
|
||||
s.chainage_m,
|
||||
s.end_m,
|
||||
s.options,
|
||||
s.group_items ?? null,
|
||||
]),
|
||||
);
|
||||
out.restored = key(after.structures) === key(original);
|
||||
}
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user