Files
Aislo/resources/tester/helper_150_detail_pick.cjs
T
eomsangdonandClaude Opus 5.5 c9b4f0bede fix(B06 십자선): 십자선 = 형상과 한 높이(측점 설계 높이) · 원지반 십자선 · 평면 초안 계획선 표본도 초안 종단 (413-1)
- 카드 · B05 오른쪽 판 · [상세] 창 십자선 · 계획 상자 = section.design.design_elevation_m · 설계 없을 때만 종단 표본
- 원지반 십자선(center_z · 원지반 색 · 다른 무리) 먼저 그림 · 겹치면 보라 위 · 확대 보정 둘 다
- [상세] 표 원지반고 = 원지반 십자 · 절성토고 = 둘 다
- 시험 318 · 150 · 164 고쳐 씀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-18 12:19:39 +09:00

154 lines
5.5 KiB
JavaScript
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.
/* PLAN 150-4 단위검증 헬퍼 — [상세] 수량표 칸 → 그림 복제 강조(`B06_Section_UI_Cross_Detail_Pick.ts`).
* 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. 짝 = `test_150_detail_pick.py`.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const cache = new Map();
// 조정창 공용 부품(Design_Pick → Cross_Panel_Base · 151-3)이 불러올 때 문서에 귀를 닮
globalThis.document = { addEventListener: () => {}, removeEventListener: () => {} };
const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base);
function loadTs(full) {
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (request.endsWith(".css")) return {};
if (request.startsWith("@util/"))
return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6))));
return loadTs(resolve(path.join(path.dirname(full), request)));
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const pick = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Detail_Pick.ts"));
// ── 그림 복제 흉내 — 평평한 요소 목록 · 「tag.class」 · 「.class」 · 「g.class line」 · 「:not(.class)」 만 ──
function node(tag, classes, attrs = {}, parent = null) {
const set = new Set(classes);
return {
tag,
parent,
set,
getAttribute: (name) => attrs[name] ?? null,
classList: {
add: (c) => set.add(c),
remove: (c) => set.delete(c),
contains: (c) => set.has(c),
},
};
}
const one = (el, part) => {
const nots = [...part.matchAll(/:not\(\.([\w-]+)\)/g)].map((m) => m[1]); // 370-3 암 판정 선 뺌
const [tag, ...cls] = part.replace(/:not\([^)]*\)/g, "").split(".");
return (
(!tag || el.tag === tag) && cls.every((c) => el.set.has(c)) && !nots.some((c) => el.set.has(c))
);
};
const matches = (el, selector) => {
const parts = selector.split(" ");
if (!one(el, parts.at(-1))) return false;
return parts.length === 1 || (el.parent !== null && one(el.parent, parts[0]));
};
function fakeSvg(ppm, x0) {
const center = node("g", ["b06-chart__center"]);
const ground = node("g", ["b06-chart__ground-center"]); // 원지반 십자(413)
const els = [
ground,
center,
node("line", [], { x1: String(x0) }, center),
node("g", ["b06-chart__area", "b06-chart__area--cut_soil"]),
node("g", ["b06-chart__area", "b06-chart__area--cut_rock", "b06-chart__area--cut_rr"]),
node("g", ["b06-chart__area", "b06-chart__area--fill", "is-active"]), // 카드에서 묻어 온 강조
node("polyline", ["b06-chart__spoil-fill"]),
];
// 판정 선: offset 구간(m) → 화면 x = x0 − offset × ppm
const hit = (a, b) =>
node("polyline", ["b06-chart__design-hit"], {
points: `${x0 - a * ppm},10 ${x0 - b * ppm},20`,
});
const hits = {
road: hit(-2, 2),
ditch: hit(2.5, 3.0),
cutSlope: hit(3.0, 6.0),
fillSlope: hit(-2.5, -6.0),
};
els.push(...Object.values(hits));
return {
els,
hits,
getAttribute: (name) => (name === "data-px-per-m" ? String(ppm) : null),
querySelectorAll: (sel) =>
els.filter((el) => sel.split(",").some((s) => matches(el, s.trim()))),
querySelector: (sel) => els.find((el) => matches(el, sel)) ?? null,
};
}
const design = {
road_edges: {
left: { offset_m: 2.5, elevation_m: 99.95 },
right: { offset_m: -2.5, elevation_m: 99.95 },
},
ditch: { type: "standard", top_width_m: 0.5, bottom_width_m: 0.2, depth_m: 0.3 },
ditch_side: "left",
ditch_enabled: true,
section_mode: "left_cut",
};
const activeOf = (svg) => {
const names = [];
for (const [name, el] of Object.entries(svg.hits)) if (el.set.has("is-active")) names.push(name);
for (const el of svg.els)
if (el.set.has("is-active") && !el.set.has("b06-chart__design-hit"))
names.push(
[...el.set].find(
(c) =>
c.includes("--") ||
c === "b06-chart__center" ||
c === "b06-chart__ground-center" ||
c === "b06-chart__spoil-fill",
),
);
return names;
};
const out = { targets: Object.keys(pick.DETAIL_TARGETS), zone: {}, active: {} };
out.zone = {
road: pick.lineZone(design, 0),
ditch: pick.lineZone(design, 2.8),
cutSlope: pick.lineZone(design, 4),
fillSlope: pick.lineZone(design, -4),
};
for (const key of [
null,
"cut_soil",
"cut_rock",
"embankment",
"spoil_fill",
"ground",
"planned",
"cut",
"ditch_soil",
"grading_cut",
"grading_fill",
"road_compaction",
"fence",
]) {
const svg = fakeSvg(20, 300);
pick.highlightDetail(svg, design, key);
out.active[String(key)] = activeOf(svg);
}
// 164-2 — 그 카드에 대상이 있는 키(암석 띠 · 사토장 없는 카드 = 그 칸 빠짐)
out.present = [...pick.presentKeys(fakeSvg(20, 300), design)].sort();
const bare = fakeSvg(20, 300);
const kept = bare.els.filter(
(el) => !el.set.has("b06-chart__area--cut_rock") && !el.set.has("b06-chart__spoil-fill"),
);
bare.els.splice(0, bare.els.length, ...kept);
out.presentBare = [...pick.presentKeys(bare, design)].sort();
process.stdout.write(JSON.stringify(out));