- 한눈 표(M02_SO_Def_Table): 군 · 종류 ▲▼ · 이름 · 목록 · 자리꼴 · 정본(고정) · 횡단도 · 카드 이름 · 형상(+ 벽 기본 형태 · 물빼기) · 알약 색 · 3D · 검사 · 처음 값 = 흐림 「기본」 · 바꿈 = 진함 · [처음 값으로] - 종류별: 반영 칸 + 명세 이름 나무 줄마다 보임(컨테이너 = 그 안 칸) · 못 숨김 · 미리보기 숨김 따름 · 「안 씀」 = 목록 B06 만 - 부품 탭(M02_SO_Def_Parts): 부품 · 읽는 칸 · 자리꼴 · 정본 · 쓰는 종류 · 켬/끔(정의 부품 = 끈 것만) - 편집 손질(M02_SO_Def_Reflect): 처음 값이면 반영에서 지움 · [저장] 때 enabled = 목록 both 일 때만 참 - 검사 짝 M02_SO_Def_Check.ts ↔ M02_SO_Check.py(글까지 같음): 반영 · 부품 꼴 · 형상 none → 횡단도 none · 부품 자리꼴 · 정본 · 기본값 없는 읽는 칸 새로 숨김 금지(앞 판 숨김 그대로) · 종류 지우기 · 자리꼴 · 정본 · 군 글자 · 선택지 값 고정 · 서버 check(doc, 지금 판) · 앞 판 없으면 꼴만 - 명세 폼: 쓰는 옵션 키가 모두 숨긴 칸 · 상자는 안 그림(값 그대로) · B05 · B06 시설 · 구조물 명세 폼이 정의 「표시」를 따름 - 판 알림: GET 구조물 목록에 definition_version · structures.json 판 ≠ 지금 판이면 「구조물 정의가 판 N 으로 바뀜 — 저장하면 반영」 한 번 - 시험: test_322_2_check_mirror(거울 · 규칙 · 반영 손질 · 서버 저장 · 422) · 판 알림 · 옛 정의 화면 시험 = 선택지 값 대신 이름 표 · 못 숨김 아닌 칸 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
110 lines
5.0 KiB
JavaScript
110 lines
5.0 KiB
JavaScript
/* PLAN 322-2 구조물 표 검사 · 편집 헬퍼 — 진짜 `M02_SO_Def_Check.ts` · `M02_SO_Def_Reflect.ts` ·
|
|
* `design_structure_spec.ts` 를 그 자리에서 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌.
|
|
* 입력 = argv[2] JSON `{cases: {이름: [정의, 앞 판|null]}, definition}`. 짝 = `test_322_2_check_mirror.py`.
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
|
|
|
|
const cache = new Map();
|
|
function load(file) {
|
|
const full = path.resolve(file);
|
|
if (cache.has(full)) return cache.get(full);
|
|
if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8"));
|
|
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.CommonJS,
|
|
target: ts.ScriptTarget.ES2020,
|
|
esModuleInterop: true,
|
|
},
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
cache.set(full, box.exports);
|
|
const need = (spec) => {
|
|
const base = path.join(path.dirname(full), spec);
|
|
const hit = [base, `${base}.ts`, base.replace(/\.js$/, ".ts")].find(
|
|
(p) => fs.existsSync(p) && fs.statSync(p).isFile(),
|
|
);
|
|
if (!hit) throw new Error(`모름: ${spec} (${full})`);
|
|
return load(hit);
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, need);
|
|
return box.exports;
|
|
}
|
|
|
|
const SO = (name) => path.join(ROOT, "M02_MasterTemplete", "StructOptions", `${name}.ts`);
|
|
const check = load(SO("M02_SO_Def_Check"));
|
|
const reflect = load(SO("M02_SO_Def_Reflect"));
|
|
const spec = load(path.join(ROOT, "A00_Common", "design", "design_structure_spec.ts"));
|
|
const { STRUCTURE_SPECS } = load(
|
|
path.join(ROOT, "A00_Common", "design", "design_structure_pipe.ts"),
|
|
);
|
|
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
const clone = (v) => JSON.parse(JSON.stringify(v));
|
|
const out = { cases: {} };
|
|
|
|
for (const [name, [doc, before]] of Object.entries(input.cases))
|
|
out.cases[name] = check.catalogProblems(doc, before);
|
|
|
|
// 반영 손질 — 목록 + enabled 짝 · 처음 값이면 지움 · 형상칸 · 보임 · 부품 쓰는 종류
|
|
const def = clone(input.definition);
|
|
const enabled = (id) => def.types.find((t) => t.type_id === id).enabled;
|
|
const own = (id) => clone(reflect.reflectOf(def)[id] ?? null);
|
|
reflect.setReflect(def, "wildlife_path", "목록", "both");
|
|
reflect.setReflect(def, "retaining_wall", "목록", "b06");
|
|
reflect.setReflect(def, "open_ditch", "횡단도", "none");
|
|
reflect.setReflect(def, "cross_drain_exposed", "횡단도", "anchor");
|
|
const setBefore = reflect.isSet(def, "cross_drain_exposed", "횡단도");
|
|
reflect.setShapeOpt(def, "soil_guard", "weep", true);
|
|
const weepOn = own("soil_guard");
|
|
reflect.setShapeOpt(def, "soil_guard", "weep", false);
|
|
reflect.setShown(def, "pipe", ["inlet_type", "없는키"], false);
|
|
out.reflect = {
|
|
wildlife: [own("wildlife_path"), enabled("wildlife_path")],
|
|
wall: [own("retaining_wall"), enabled("retaining_wall")],
|
|
openDitch: own("open_ditch"),
|
|
exposed: [own("cross_drain_exposed"), setBefore],
|
|
weepOn,
|
|
weepOff: own("soil_guard"),
|
|
shown: clone(def.표시.pipe),
|
|
readPipe: [...reflect.readKeys(def, "pipe")].sort(),
|
|
wallUsers: reflect.usersOf(def, "형상", "wall").map((row) => row.typeId),
|
|
};
|
|
reflect.resetRow(def, "cross_drain_exposed");
|
|
reflect.resetRow(def, "retaining_wall");
|
|
out.reflect.reset = [own("cross_drain_exposed"), own("retaining_wall"), enabled("retaining_wall")];
|
|
// 저장 전 목록 + enabled 함께 — 목록은 그대로 · enabled = both 일 때만 참
|
|
const plain = clone(input.definition);
|
|
const lists = (d) => [...reflect.rowsOf(d).values()].map((row) => `${row.typeId}:${row.list}`);
|
|
const listBefore = lists(plain);
|
|
reflect.syncEnabled(plain);
|
|
out.sync = {
|
|
same: JSON.stringify(lists(plain)) === JSON.stringify(listBefore),
|
|
enabled: plain.types.filter((t) => t.enabled).map((t) => t.type_id),
|
|
};
|
|
reflect.setPartOn(plain, "형상.wall", false);
|
|
out.partOff = [clone(plain.부품), reflect.rowsOf(plain).get("retaining_wall").shape];
|
|
reflect.setPartOn(plain, "형상.wall", true);
|
|
out.partOn = clone(plain.부품);
|
|
|
|
// 명세 폼 숨김 — 저장 · 갈래 키가 모두 숨김인 칸 · 키 있는 칸이 모두 숨긴 상자
|
|
out.spec = {};
|
|
for (const [tid, sp] of Object.entries(STRUCTURE_SPECS)) {
|
|
const keys = sp.containers.flatMap((c) => c.items.flatMap((i) => spec.specItemKeys(i)));
|
|
const hidden = new Set(keys.slice(0, 1));
|
|
const all = new Set(keys);
|
|
out.spec[tid] = {
|
|
first: keys[0] ?? null,
|
|
hiddenItems: sp.containers.flatMap((c) =>
|
|
c.items.filter((i) => spec.specItemHidden(i, hidden)).map((i) => `${c.id}.${i.id}`),
|
|
),
|
|
allBoxes: sp.containers.filter((c) => spec.specContainerHidden(c, all)).map((c) => c.id),
|
|
keyedBoxes: sp.containers
|
|
.filter((c) => c.items.some((i) => spec.specItemKeys(i).length))
|
|
.map((c) => c.id),
|
|
noneHidden: sp.containers.filter((c) => spec.specContainerHidden(c, new Set())).length,
|
|
};
|
|
}
|
|
process.stdout.write(JSON.stringify(out));
|