Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1

This commit is contained in:
2026-10-08 12:16:13 +09:00
11 changed files with 444 additions and 29 deletions
@@ -20,6 +20,9 @@ export function setPipeSectionsSource(source: () => readonly CrossSection[] | nu
sectionsSource = source;
}
/** 횡단 측점 목록 읽기 — 노면공 확폭 구간 상자(87-3)가 확폭을 읽음 · 자료 없으면 null. */
export const pipeSections = (): readonly CrossSection[] | null => sectionsSource?.() ?? null;
/** 유출 단 수를 대는 곳 — 횡단 보기의 세션 값 우선(`countFor`) · 없으면 정본 patch. */
let extraCountSource: ((section: CrossSection) => number) | null = null;
export function setPipeExtraCountSource(source: (section: CrossSection) => number): void {
@@ -0,0 +1,114 @@
/* =============================================================================
* design_structure_widening.ts
* 노면공 › 확폭 구간(87-3) — 범위 안에서 확폭(좌 + 우) > 0 인 횡단 측점을 이어진 구간으로 묶고,
* 끈 구간을 `options.widening_off = "시작-끝,시작-끝"`(측점 m · 빈 글 = 모두 켬)으로 읽고 씀.
* 켬 = 확폭 포함 · 끔 = 규격 폭만. 노선이 바뀌어 구간 끝이 안 맞는 끈 값은 무시(= 켬).
* 횡단 형상 쪽(88장)도 이 키를 같은 글로 읽음 — 측점이 범위 ±0.02 m 안.
* import 없음 — 시험이 이 파일만 옮겨 돌림.
* ========================================================================== */
/** 구조물 범위 · 구간 끝 맞춤 허용치(m). */
export const WIDENING_TOL_M = 0.02;
/** 횡단 측점에서 읽는 몫만 — 횡단 설계 확폭(좌 · 우) · 설계 없으면 저장된 곡선 확폭(바깥쪽 한 방향). */
export interface WideningStation {
chainage_m: number;
curve_widening_m?: number | null;
curve_outer_side?: "left" | "right" | null;
design?: { widening_left_m?: number; widening_right_m?: number } | null;
}
export interface WideningRun {
start_m: number;
end_m: number;
/** 구간 안 확폭(좌 + 우) 최댓값(m). */
widening_m: number;
}
/** 한 측점의 확폭(좌 + 우, m) — 없으면 0. */
export function wideningOf(station: WideningStation): number {
const sum = station.design
? (station.design.widening_left_m ?? 0) + (station.design.widening_right_m ?? 0)
: station.curve_outer_side
? (station.curve_widening_m ?? 0)
: 0;
return Number.isFinite(sum) && sum > 0 ? sum : 0;
}
/** 범위 `[start, end]` 안 확폭 > 0 측점을 이어진 구간(범위 안 측점 목록에서 이웃)으로 묶음. */
export function wideningRuns(
stations: readonly WideningStation[],
start: number,
end: number,
): WideningRun[] {
const inside = stations
.filter((s) => s.chainage_m >= start - WIDENING_TOL_M && s.chainage_m <= end + WIDENING_TOL_M)
.sort((a, b) => a.chainage_m - b.chainage_m);
const runs: WideningRun[] = [];
let open: WideningRun | null = null;
for (const station of inside) {
const width = wideningOf(station);
if (width <= 0) {
open = null;
continue;
}
if (open) {
open.end_m = station.chainage_m;
open.widening_m = Math.max(open.widening_m, width);
} else {
open = { start_m: station.chainage_m, end_m: station.chainage_m, widening_m: width };
runs.push(open);
}
}
return runs;
}
const round = (value: number): number => Number(value.toFixed(2));
/** 저장 글 → 끈 구간 쌍. 틀린 조각은 버림. */
export function parseOff(text: unknown): Array<[number, number]> {
if (typeof text !== "string") return [];
const pairs: Array<[number, number]> = [];
for (const part of text.split(",")) {
const [a, b] = part.split("-").map((token) => Number(token.trim()));
if (part.includes("-") && Number.isFinite(a) && Number.isFinite(b) && b >= a)
pairs.push([a, b]);
}
return pairs;
}
export const formatOff = (pairs: ReadonlyArray<readonly [number, number]>): string =>
pairs.map(([a, b]) => `${round(a)}-${round(b)}`).join(",");
/** 끈 쌍이 이 구간과 맞는가(시작 · 끝 각각 허용치 안). */
export const pairMatches = (pair: readonly [number, number], run: WideningRun): boolean =>
Math.abs(pair[0] - run.start_m) <= WIDENING_TOL_M &&
Math.abs(pair[1] - run.end_m) <= WIDENING_TOL_M;
/** 지금 구간들 중 끈 것만 남긴 글(안 맞는 옛 값은 빠짐). */
export function offForRuns(runs: readonly WideningRun[], text: unknown): string {
const pairs = parseOff(text);
return formatOff(
runs
.filter((run) => pairs.some((p) => pairMatches(p, run)))
.map((run) => [run.start_m, run.end_m] as const),
);
}
/** 구간 하나의 켬/끔을 뒤집은 새 글(맞는 구간 쌍만 남김). */
export function toggleRun(
runs: readonly WideningRun[],
text: unknown,
target: WideningRun,
): string {
const pairs = parseOff(text);
const isOff = (run: WideningRun): boolean => pairs.some((p) => pairMatches(p, run));
return formatOff(
runs
.filter((run) => (run === target ? !isOff(run) : isOff(run)))
.map((run) => [run.start_m, run.end_m] as const),
);
}
export const isRunOff = (run: WideningRun, text: unknown): boolean =>
parseOff(text).some((p) => pairMatches(p, run));
+20 -7
View File
@@ -29,6 +29,7 @@ import {
type FacilityDefinition,
} from "./B05_Profile_UI_Structures_Define_Form";
import { createStructureSpecForm } from "./B05_Profile_UI_Structures_Spec";
import { createWideningBox } from "./B05_Profile_UI_Structures_Widening";
export interface StructuresFormElements {
root: HTMLElement;
@@ -156,6 +157,16 @@ export function buildStructuresForm(options: {
set: groupBoxes.setOne,
});
groupBoxes.watch(facilityOptions.paintLinks);
// 노면공 시작~종료 읽기(87-1 길이 출력 · 87-3 확폭 구간 상자가 같이 씀) — 못 읽으면 null
const rangeNow = (): { start: number | null; end: number | null } => {
const step = options.getInterval();
return { start: startFields.read(step, false), end: endFields.read(step, false) };
};
const widening = createWideningBox({
range: rangeNow,
interval: options.getInterval,
onChange: options.onFacilityChange,
});
// 일반 구조물 명세 컨테이너(PLAN 52-10 · 노면공 등) — 잇기 · 반영 길은 배관 명세와 같음
const structureSpec = createStructureSpecForm(
optionRow,
@@ -163,19 +174,20 @@ export function buildStructuresForm(options: {
options.onFacilityChange,
// 노면공 길이 칸(87-1) = 시작~종료 출력 — 못 셈이면 빈 글(「—」)
(_c, item) => {
if (item.id !== "length") return "";
const step = options.getInterval();
const start = startFields.read(step, false);
const end = endFields.read(step, false);
return start !== null && end !== null && end > start
const { start, end } = rangeNow();
return item.id === "length" && start !== null && end !== null && end > start
? String(Number((end - start).toFixed(2)))
: "";
},
widening,
);
// 시작 · 종료를 고치면 길이 출력을 다시 그림
// 시작 · 종료를 고치면 길이 출력 · 확폭 구간을 다시 그림
[startFields, endFields].forEach((fields) =>
[fields.station, fields.remainder].forEach((input) =>
input.addEventListener("change", () => structureSpec.refresh()),
input.addEventListener("change", () => {
structureSpec.refresh();
widening.refresh();
}),
),
);
groupBoxes.watch(structureSpec.paintLinks);
@@ -190,6 +202,7 @@ export function buildStructuresForm(options: {
positionDivider,
optionRow,
structureSpec.root,
widening.root, // 노면공 「확폭 구간」 — 상세 내용 아래(87-3)
facilityOptions.root,
staleBox,
groupBoxes.root,
@@ -54,7 +54,7 @@ export async function splitPavementRange(
await showConfirmDialog(
`지정한 포장 구간(${format(start)}~${format(end)})이 물넘이포장` +
`(${format(partial.start)}~${format(partial.end)})의 끝에 걸칩니다.\n` +
"물넘이는 그 자체가 포장이라 겹칠 수 없습니다 — 기준 측점·길이·전후 값을 고쳐 주세요.",
"물넘이는 그 자체가 포장이라 겹칠 수 없습니다 — 시작·종료 측점을 고쳐 주세요.",
"확인",
);
return null;
+25 -5
View File
@@ -18,8 +18,11 @@ import {
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
import { specKeys, type StructureSpec } from "../A00_Common/design/design_structure_spec";
import { optionDefaults } from "./B05_Profile_UI_Structures_Fields";
import type { WideningBox } from "./B05_Profile_UI_Structures_Widening";
const forms = new WeakMap<HTMLElement, SpecForm>();
/** 범위형 명세(노면공 · 87-3)의 「확폭 구간」 상자 — 옵션 칸 짝. */
const widenings = new WeakMap<HTMLElement, WideningBox>();
/** 일반 구조물 명세 — 계곡 통과 시설(관 지점 정본)은 시설 폼 몫이라 뺌. */
export const structureSpecOf = (type: StructureType | null): StructureSpec | null =>
@@ -31,14 +34,19 @@ export function createStructureSpecForm(
links: SpecLinks,
onChange: () => void,
auto?: SpecFormHooks["auto"],
widening?: WideningBox,
): SpecForm {
const form = createSpecForm(links, { onChange, auto });
form.root.hidden = true;
forms.set(optionRow, form);
if (widening) widenings.set(optionRow, widening);
const lock = (): void => {
const locked = optionRow.classList.contains("is-locked");
form.root.inert = locked;
form.root.classList.toggle("is-locked", locked);
for (const root of [form.root, widening?.root]) {
if (!root) continue;
root.inert = locked;
root.classList.toggle("is-locked", locked);
}
};
if (typeof MutationObserver !== "undefined")
new MutationObserver(lock).observe(optionRow, {
@@ -51,7 +59,16 @@ export function createStructureSpecForm(
/** 정의 옵션 칸으로 안 그리는 키 — 명세 키 · 명세가 뺀 옛 키. */
export function specOwnedKeys(type: StructureType | null): Set<string> {
const spec = structureSpecOf(type);
return new Set(spec ? [...specKeys(spec).keys, ...spec.retired, ...(spec.dropped ?? [])] : []);
return new Set(
spec
? [
...specKeys(spec).keys,
...spec.retired,
...(spec.dropped ?? []),
...(spec.byRange ? ["widening_off"] : []),
]
: [],
);
}
/** 종류 · 값으로 명세 폼을 다시 그림(명세 없는 종류면 가림). */
@@ -62,6 +79,7 @@ export function paintStructureSpec(
): void {
const form = forms.get(optionRow);
const spec = structureSpecOf(type);
widenings.get(optionRow)?.paint(!!spec?.byRange, values.widening_off);
if (!form) return;
form.root.hidden = !spec;
if (!spec) return;
@@ -75,7 +93,9 @@ export function readStructureSpec(
type: StructureType | null,
): Record<string, string | number> {
const form = forms.get(optionRow);
return form && structureSpecOf(type) ? form.read() : {};
const spec = structureSpecOf(type);
if (!form || !spec) return {};
return { ...form.read(), ...(spec.byRange ? widenings.get(optionRow)?.read() : {}) };
}
/** [수정] 병합에서 폼이 다시 쓰는 키 — 명세 키 + 옛 값 중 선택 컨테이너 머리 키(빼면 지워져야 함). */
@@ -87,5 +107,5 @@ export function specRenderedKeys(
if (!spec) return [];
const { keys, prefixes } = specKeys(spec);
const own = Object.keys(previous ?? {}).filter((key) => prefixes.some((p) => key.startsWith(p)));
return [...keys, ...own, ...(spec.dropped ?? [])];
return [...keys, ...own, ...(spec.dropped ?? []), ...(spec.byRange ? ["widening_off"] : [])];
}
@@ -0,0 +1,102 @@
/* =============================================================================
* B05_Profile_UI_Structures_Widening.ts
* 노면공 「확폭 구간」 상자(PLAN 87-3) — 상세 내용 아래. 범위 안 확폭 > 0 측점을 이어진 구간으로 묶어
* 구간마다 단추(켬 = 확폭 포함 · 끔 = 규격 폭만 · 기본 켬). 끈 구간 = `options.widening_off`.
* 규칙(구간 묶기 · 글 읽고 쓰기)은 `design_structure_widening` · 횡단 측점 읽기는 `pipeSections`.
* ========================================================================== */
import { pipeSections } from "../A00_Common/design/design_structure_pipe_calc";
import {
isRunOff,
offForRuns,
toggleRun,
wideningRuns,
type WideningRun,
} from "../A00_Common/design/design_structure_widening";
import { stationLabel } from "../common_util/common_util_station";
import { group } from "./B05_Profile_UI_Drainage_Facility_Fields";
export interface WideningBox {
root: HTMLElement;
/** 종류 · 저장 값으로 다시 그림(범위형이 아니면 가림). */
paint: (visible: boolean, stored: string | number | undefined) => void;
/** 저장할 값 — 끈 구간이 없으면 빈 묶음. 횡단 자료가 없으면 저장 값을 그대로 지킴. */
read: () => Record<string, string>;
/** 범위 · 횡단 자료가 바뀌었을 때 다시 그림. */
refresh: () => void;
}
export function createWideningBox(hooks: {
range: () => { start: number | null; end: number | null };
interval: () => number;
onChange: () => void;
}): WideningBox {
const box = group("확폭 구간");
box.root.classList.add("b05-structure__widening");
box.root.hidden = true;
let off = "";
/** 지금 범위의 구간들 — 횡단 자료 · 범위가 없으면 null(+ 안내 글). */
const runs = (): { list: WideningRun[] | null; note: string } => {
const sections = pipeSections();
if (!sections?.length) return { list: null, note: "횡단 자료 없음" };
const { start, end } = hooks.range();
if (start === null || end === null || end <= start)
return { list: null, note: "시작 · 종료 측점을 넣으면 보임" };
const list = wideningRuns(sections, start, end);
return { list, note: list.length ? "" : "범위 안 확폭 없음" };
};
const paintBody = (): void => {
const { list, note } = runs();
if (!list?.length) {
const text = document.createElement("span");
text.className = "b05-structure__widening-note";
text.textContent = note;
box.body.replaceChildren(text);
return;
}
const step = hooks.interval();
box.body.replaceChildren(
...list.map((run) => {
const on = !isRunOff(run, off);
const button = document.createElement("button");
button.type = "button";
button.className = `b05-route__irregular-btn${on ? " is-primary" : ""}`;
button.setAttribute("aria-pressed", String(on));
button.dataset.wideningRun = `${run.start_m}-${run.end_m}`;
button.textContent = `${stationLabel(run.start_m, step)} ~ ${stationLabel(run.end_m, step)} · 확폭 ${Number(run.widening_m.toFixed(2))} m`;
button.title = on ? "확폭 포함 — 누르면 규격 폭만" : "규격 폭만 — 누르면 확폭 포함";
button.addEventListener("click", () => {
off = toggleRun(list, off, run);
paintBody();
hooks.onChange();
});
return button;
}),
);
};
// 횡단 자료가 늦게 오면 상자 위에 올 때 다시 그림
box.root.addEventListener("pointerenter", () => {
if (!box.root.hidden) paintBody();
});
return {
root: box.root,
paint(visible, stored) {
box.root.hidden = !visible;
off = typeof stored === "string" ? stored : "";
if (visible) paintBody();
},
read() {
const { list } = runs();
const text = list ? offForRuns(list, off) : off;
const out: Record<string, string> = text ? { widening_off: text } : {};
return out;
},
refresh() {
if (!box.root.hidden) paintBody();
},
};
}
@@ -570,3 +570,25 @@
font-size: var(--text-caption);
line-height: 1;
}
/* 노면공 「확폭 구간」 상자(PLAN 87-3) — 구간마다 단추 한 줄 */
.b05-structure__widening[hidden] {
display: none;
}
.b05-structure__widening .b05-structure__group-body {
display: flex;
flex-direction: column;
gap: 4px;
}
.b05-structure__widening .b05-structure__group-body button {
align-self: stretch;
text-align: left;
white-space: normal;
}
.b05-structure__widening-note {
color: var(--text-muted, inherit);
font-size: var(--text-caption);
}
+54
View File
@@ -0,0 +1,54 @@
/* 노면공 확폭 구간 규칙(PLAN 87-3) — `design_structure_widening.ts` 를 그 자리에서 트랜스파일해 Node 로 돌림. */
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const file = path.join(
__dirname,
"..",
"..",
"A00_Common",
"design",
"design_structure_widening.ts",
);
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", js)(box.exports, box);
const w = box.exports;
// 곡선 둘(앞 No.1+0~No.3+0 · 뒤 No.5+0~No.6+0) 사이에 직선 측점 하나 — 확폭 좌우 합
const at = (chainage_m, left = 0, right = 0) => ({
chainage_m,
design: { widening_left_m: left, widening_right_m: right },
});
const stations = [
at(0),
at(20, 0.3, 0),
at(40, 0.75, 0),
at(60, 0.4, 0),
at(80),
at(100, 0, 0.5),
at(120, 0, 0.6),
at(140),
];
const runs = w.wideningRuns(stations, 0, 140);
const inner = w.wideningRuns(stations, 30, 110); // 범위가 곡선을 가로지름
const off = w.formatOff([[20, 60]]);
const out = {
runs,
inner,
none: w.wideningRuns(stations, 0, 10),
fallback: w.wideningOf({ chainage_m: 1, curve_widening_m: 0.9, curve_outer_side: "left" }),
fallbackStraight: w.wideningOf({ chainage_m: 1, curve_widening_m: 0.9, curve_outer_side: null }),
off,
parsed: w.parseOff("20-60, 100-120,bad,5"),
isOff: runs.map((r) => w.isRunOff(r, off)),
toggledOff: w.toggleRun(runs, "", runs[1]),
toggledBoth: w.toggleRun(runs, off, runs[1]),
toggledOn: w.toggleRun(runs, off, runs[0]),
stale: w.offForRuns(runs, "20-60,0-5,100-130"),
empty: w.offForRuns([], "20-60"),
};
process.stdout.write(JSON.stringify(out));
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""노면공 확폭 구간(PLAN 87-3) — 구간 묶기 · `widening_off` 글 읽고 쓰기 · 안 맞는 값 무시.
규칙은 Node 헬퍼(`helper_87_widening.cjs`)가 `design_structure_widening.ts` 를 그대로 돌림.
"""
import json
import os
import subprocess
import pytest
HERE = os.path.dirname(os.path.abspath(__file__))
@pytest.fixture(scope="module")
def out():
proc = subprocess.run(
["node", os.path.join(HERE, "helper_87_widening.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_곡선_둘은_구간_둘_확폭은_구간_최대(out):
assert out["runs"] == [
{"start_m": 20, "end_m": 60, "widening_m": 0.75},
{"start_m": 100, "end_m": 120, "widening_m": 0.6},
]
def test_범위_안_측점만_묶음(out):
assert out["inner"] == [
{"start_m": 40, "end_m": 60, "widening_m": 0.75},
{"start_m": 100, "end_m": 100, "widening_m": 0.5},
]
assert out["none"] == []
def test_설계_없으면_곡선_확폭_바깥쪽만(out):
assert out["fallback"] == 0.9
assert out["fallbackStraight"] == 0
def test_끈_구간_글_왕복(out):
assert out["off"] == "20-60"
assert out["parsed"] == [[20, 60], [100, 120]] # 틀린 조각은 버림
assert out["isOff"] == [True, False]
assert out["toggledOff"] == "100-120"
assert out["toggledBoth"] == "20-60,100-120"
assert out["toggledOn"] == ""
def test_안_맞는_끈_값은_무시_저장_때_빠짐(out):
assert out["stale"] == "20-60"
assert out["empty"] == ""
@@ -8,8 +8,9 @@
* orca eval --expression "window.__m5210='restore'; $(cat 이 파일)" → 추가한 둘 삭제 · [저장]
* 쓰는 것: 검증 프로젝트 structures.json(되돌림 단계가 지움)
* 기대 수치:
* 포장 — containers ["detail"] · 상세 내용 · 포장사양 선택지 ["콘크리트"] · 높이 0.35 · 잇기 지정 ·
* 종류 단위 잇기 줄 가림 · 옛 상세 칸(두께 · 줄눈 …) 안 보임 · 길이 · 전 · 후 칸 보임
* 포장 — containers ["detail"] · 상세 내용 · 포장사양 선택지 [콘크리트 · 아스콘 · 쇄석] · 칸 차례 포장사양 → 길이(출력 20) →
* 높이 0.35 · 잇기 지정 · 종류 단위 잇기 줄 가림 · 옛 상세 칸(두께 · 줄눈 …) 안 보임 ·
* 기준 측점 칸 없음 · 시작 · 종료 칸 보임 · 길이 · 전 · 후 입력 칸 없음
* 기타 — 「상세 종류 이름」 글 칸 = 「52-10 시험 기타」 · 목록 이름도 그 글 ·
* 선택 컨테이너 「상세 내용」(52-14) 하나만(52-17) — 추가 → [추가] 없음 · 빼기 → 다시 보임 ·
* 다시 추가 · 비고 「52-14 비고」(길이 상한 100) · 잇기 · 하나 남음 · 종류 단위 잇기 줄 가림
@@ -37,7 +38,7 @@
};
const listItems = () => [...document.querySelectorAll(".b05-route__irregular-list li")];
const ITEMS = [
{ typeId: "pavement_concrete", group: "G", at: [4, 6], list: /콘크리트 포장/ },
{ typeId: "pavement_concrete", group: "G", at: [4, 6], to: [5, 6], list: /콘크리트 포장/ },
{ typeId: "etc", group: "호환", at: [5, 2], list: new RegExp(NAME) },
];
const typeSummary = () =>
@@ -76,6 +77,16 @@
: null,
paveSpec: specCtl("spec")?.value ?? null,
height: specCtl("height")?.value ?? null,
itemOrder: boxes[0]
? [...boxes[0].querySelectorAll("[data-spec-item]")].map((n) => n.dataset.specItem)
: [],
lengthOut:
spec()?.querySelector('[data-spec-item="detail.length"] output')?.textContent ?? null,
anchorHidden: !!sec().querySelectorAll(".b05-structure__position-row > label")[2]?.hidden,
startEndShown: [1, 3].every(
(n) => !sec().querySelectorAll(".b05-structure__position-row > label")[n]?.hidden,
),
wideningShown: vis(sec().querySelector(".b05-structure__widening")),
containerLink: !!boxes[0]?.querySelector(".b05-structure__summary-name.is-assigned"),
containerLinks: boxes.map(
(b) => !!b.querySelector(".b05-structure__summary-name.is-assigned"),
@@ -139,11 +150,20 @@
for (const item of ITEMS) {
await pickType(item.group, item.typeId);
const row = { lockedBeforePosition: shape(item.typeId).locked };
const [no, dist] = sec()
.querySelectorAll(".b05-structure__position-row > label")[2]
.querySelectorAll("input");
fire(no, String(item.at[0]));
fire(dist, String(item.at[1]));
const labels = sec().querySelectorAll(".b05-structure__position-row > label");
if (item.to) {
// 노면공 = 시작 · 종료 측점만(87-1) — 기준 측점 칸 없음
const [s0, s1] = labels[1].querySelectorAll("input");
const [e0, e1] = labels[3].querySelectorAll("input");
fire(s0, String(item.at[0]));
fire(s1, String(item.at[1]));
fire(e0, String(item.to[0]));
fire(e1, String(item.to[1]));
} else {
const [no, dist] = labels[2].querySelectorAll("input");
fire(no, String(item.at[0]));
fire(dist, String(item.at[1]));
}
await w(300);
if (item.typeId === "pavement_concrete") {
fire(specCtl("spec"), "콘크리트");
@@ -226,13 +246,17 @@
!pave.missing &&
JSON.stringify(pave.containers) === '["detail"]' &&
pave.legend[0] === "상세 내용" &&
JSON.stringify(pave.paveChoices) === '["콘크리트"]' &&
JSON.stringify(pave.paveChoices) === '["콘크리트","아스콘","쇄석"]' &&
JSON.stringify(pave.itemOrder) === '["detail.spec","detail.length","detail.height"]' &&
Number(pave.lengthOut) === 20 &&
pave.anchorHidden &&
pave.startEndShown &&
pave.paveSpec === "콘크리트" &&
Number(pave.height) === 0.35 &&
pave.containerLink &&
!pave.typeSummaryVisible &&
!pave.options.some((l) => /두께|줄눈|철망|비닐|포장 폭|확폭/.test(l)) &&
["길이", "기준측점 전", "기준측점 후"].every((l) => pave.options.some((o) => o.startsWith(l)));
!pave.options.some((l) => /^(길이|기준측점 전|기준측점 후)/.test(l)); // 87-1 입력 칸 없음
out.etcOk =
!!etc &&
!etc.missing &&
@@ -13,7 +13,7 @@
(async () => {
const MODE = window.__m5215 || "check";
const PIPE = /^22\+0 배관/;
const PAVE = /^21\+15~22\+5 콘크리트 포장/; // 기준 22+0 · 길이 10 m
const PAVE = /^21\+15~22\+5 콘크리트 포장/; // 시작 21+15 · 종료 22+5 (10 m)
const OTHER = /^4\+5 배관/;
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const until = async (fn, ms = 15000) => {
@@ -85,13 +85,16 @@
await w(300);
fire(t, "pavement_concrete");
await w(1500);
const anchor = () =>
sec().querySelectorAll(".b05-structure__position-row > label")[2].querySelectorAll("input");
fire(anchor()[0], "22");
// 노면공 = 시작 · 종료 측점만(87-1) — 시작 21+15 · 종료 22+5(10 m)
const pos = (n) =>
sec().querySelectorAll(".b05-structure__position-row > label")[n].querySelectorAll("input");
fire(pos(1)[0], "21");
fire(pos(1)[1], "15");
fire(pos(3)[0], "22");
await w(300);
fire(anchor()[1], "0");
fire(pos(3)[1], "5");
await w(500);
out.anchor = [...anchor()].map((i) => i.value);
out.anchor = [...pos(1), ...pos(3)].map((i) => i.value);
fire(specCtl("spec"), "콘크리트");
await w(200);
fire(specCtl("height"), "0.35");