Merge remote-tracking branch 'origin/dev' into sub_laptop_7

This commit is contained in:
2026-10-06 14:03:46 +09:00
9 changed files with 381 additions and 42 deletions
@@ -0,0 +1,152 @@
/* 54-32 고른 구조물에서 군 바꿈 = 새로 놓기 · 같은 군 종류 바꿈 = 새 종류 기본값 + 종류별 캐시
* 시작 화면: B06 횡단설계(#/b06-section) · 검증 프로젝트(sub4) · 로그인
* 부르는 법: 단계마다 `window.__m5432 = "단계";` 뒤 이 파일 본문을 eval
* a (B06) 돌쌓기(찰) 1: 칸 고침 → 다른 종류 → 돌아옴 = 고친 값 · 군 바꿈 = 고름 0
* 4+5 배관 1: 관경 고침 → BOX(칸 고침) → 배관 = 고친 관경 · 군 바꿈 = 고름 0 · 캐시 있음
* b (B05) 배관 → BOX = B06 에서 고친 BOX 값(캐시 공유) → 배관 · 관경 · 돌쌓기 칸 원래 값으로
* c [저장] → 캐시 비움 · 배관 → BOX = BOX 기본 · → 배관 = 저장 관경 · 다시 [저장]
* 결과는 sessionStorage `__5432_out_{단계}` · 기대: fails 0
*/
(async () => {
const MODE = window.__m5432 || "a";
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const pid = localStorage.getItem("frd_current_project_id");
const t = (e) => e.innerText.replace(/\s+/g, " ").trim();
const fails = [];
const out = {};
const fail = (m) => fails.push(m);
const sec = () => document.querySelector(".b05-structure-section");
const grid = () => sec().querySelectorAll(".b05-structure__grid select");
const rows = () => [...document.querySelectorAll(".b05-route__irregular-list li")];
const row = (head) => rows().find((li) => t(li).startsWith(head));
const picked = () => rows().filter((li) => li.classList.contains("is-selected")).length;
const read = (name) => {
const raw = sessionStorage.getItem(`aislo:draft:${name}:${pid}`);
const v = raw ? JSON.parse(raw) : null;
return v?.value ?? v;
};
const pipe = () => (read("pipes") ?? []).find((p) => Math.abs(p.chainage_m - 85.05) < 0.51);
const fire = (el, value) => {
el.value = value;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
};
const pick = async (head) => {
for (let k = 0; k < 40 && !row(head); k++) await w(300);
if (!row(head)?.classList.contains("is-selected")) row(head)?.click();
await w(2000);
};
const setType = async (id) => (fire(grid()[1], id), await w(2500));
const setGroup = async (g) => (fire(grid()[0], g), await w(2000));
/** 종류 칸 아래 첫 고를 수 있는 선택 칸(관경 · BOX 폭 등) */
const firstSelect = () =>
[...sec().querySelectorAll("select")]
.slice(2)
.find(
(s) => s.offsetParent && !s.disabled && [...s.options].filter((o) => o.value).length > 1,
);
const other = (s) => [...s.options].map((o) => o.value).find((v) => v && v !== s.value);
const firstNumber = () =>
[...sec().querySelectorAll("input")].find(
(i) =>
i.offsetParent &&
!i.disabled &&
i.value !== "" &&
!isNaN(Number(i.value)) &&
!/측점/.test(t(i.closest("label") ?? i)),
);
const S = "4+0~4+10 돌쌓기(찰)";
const P = "4+5 배관";
const ck = () => sessionStorage.getItem(`aislo:draft:structure-type-cache:${pid}`);
const memo = JSON.parse(sessionStorage.getItem("__5432_memo") || "{}");
if (MODE === "a") {
await pick(S);
const t0 = grid()[1].value;
const num = firstNumber();
memo.sLabel = t(num.closest("label"));
memo.s0 = num.value;
const s1 = String(Number(num.value) + 0.5);
fire(num, s1);
await w(1200);
const t1 = [...grid()[1].options].map((o) => o.value).find((v) => v && v !== t0);
await setType(t1);
out.afterSwitch = {
type: grid()[1].value,
picked: picked(),
primary: t(sec().querySelector("button")),
};
if (picked() !== 1) fail(`종류 바꿈 뒤 고름 ${picked()}`);
await setType(t0);
const back = firstNumber();
out.structBack = back?.value;
if (Number(back?.value) !== Number(s1)) fail(`돌쌓기 되살림 ${back?.value} ≠ ${s1}`);
fire(back, memo.s0);
await w(1200);
await setGroup("B");
out.structGroup = { picked: picked(), type: grid()[1].value, row: !!row(S) };
if (picked() !== 0 || grid()[1].value !== "")
fail(`돌쌓기 군 바꿈 ${JSON.stringify(out.structGroup)}`);
await pick(P);
const d = firstSelect();
memo.p0 = d.value;
memo.p1 = other(d);
fire(d, memo.p1);
await w(2500);
await setType("box_culvert");
out.boxType = pipe()?.facility;
if (pipe()?.facility !== "box_culvert") fail(`BOX 초안 ${pipe()?.facility}`);
const b = firstSelect();
memo.bDefault = b.value;
memo.b1 = other(b);
fire(b, memo.b1);
await w(2500);
await setType("pipe");
out.pipeBack = firstSelect()?.value;
if (firstSelect()?.value !== memo.p1) fail(`배관 되살림 ${firstSelect()?.value} ≠ ${memo.p1}`);
await setGroup("C");
out.pipeGroup = { picked: picked(), type: grid()[1].value, facility: pipe()?.facility };
if (picked() !== 0 || pipe()?.facility !== "pipe")
fail(`배관 군 바꿈 ${JSON.stringify(out.pipeGroup)}`);
out.cache = !!ck();
if (!ck()) fail("캐시 없음");
}
if (MODE === "b") {
await pick(P);
await setType("box_culvert");
out.boxFromCache = firstSelect()?.value;
if (firstSelect()?.value !== memo.b1) fail(`B05 BOX 캐시 ${firstSelect()?.value} ≠ ${memo.b1}`);
await setType("pipe");
out.pipeFromCache = firstSelect()?.value;
if (firstSelect()?.value !== memo.p1)
fail(`B05 배관 캐시 ${firstSelect()?.value} ≠ ${memo.p1}`);
fire(firstSelect(), memo.p0);
await w(2500);
out.pipeRestored = pipe()?.options?.diameter_mm ?? firstSelect()?.value;
}
if (MODE === "c") {
const save = async () => {
[...document.querySelectorAll("button")].find((x) => t(x) === "저장").click();
await w(15000);
};
await save();
out.cacheAfterSave = ck();
if (ck()) fail("저장 뒤 캐시 남음");
await pick(P);
await setType("box_culvert");
out.boxAfterSave = firstSelect()?.value;
if (firstSelect()?.value !== memo.bDefault)
fail(`저장 뒤 BOX ${firstSelect()?.value} ≠ 기본 ${memo.bDefault}`);
await setType("pipe");
out.pipeAfterSave = firstSelect()?.value;
if (firstSelect()?.value !== memo.p0) fail(`저장 뒤 배관 ${firstSelect()?.value} ≠ ${memo.p0}`);
await save();
out.cacheEnd = ck();
}
sessionStorage.setItem("__5432_memo", JSON.stringify(memo));
sessionStorage.setItem(`__5432_out_${MODE}`, JSON.stringify({ fails, out, memo }));
})();