diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Entry.css b/M02_MasterTemplete/DataFlow/M02_DF_Entry.css index 05228f379..9058ccdb2 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Entry.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_Entry.css @@ -70,7 +70,7 @@ /* 시작 화면 */ .m02-df__start { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr); gap: var(--spacing-24); } diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts b/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts index e6a60b0e8..0cd240ba0 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Entry.ts @@ -47,12 +47,11 @@ const loadMods = () => import("./M02_DF_FuncPanel"), import("./M02_DF_OutputPanel"), import("./M02_DF_Summary"), - import("./M02_DF_Sample"), import("./M02_DF_InputPanel"), import("./M02_DF_PillPanel"), import("./M02_DF_LinkPanel"), import("./M02_DF_Standards"), - ]).then(([model, ev, api, diagram, func, output, summary, sample, input, pill, line, std]) => ({ + ]).then(([model, ev, api, diagram, func, output, summary, input, pill, line, std]) => ({ model, ev, api, @@ -60,7 +59,6 @@ const loadMods = () => func, output, summary, - sample, input, pill, line, @@ -78,7 +76,6 @@ const TEXT = { Start_New: ["새로 만들기", "New"], Start_Mine: ["내 로직", "My logics"], Start_MineNone: ["아직 없음", "None yet"], - Start_Samples: ["견본 — 복사해서 열기", "Samples — open a copy"], Name: ["로직 이름", "Logic name"], NamePlaceholder: ["예: 메쌓기 내 방식", "e.g. my dry stone wall"], Unit: ["결과 단위", "Result unit"], @@ -217,7 +214,6 @@ function openDataFlow(): void { paper = null; strip.replaceChildren(); const mineHost = el("div", { className: "m02-df__list", children: [muted(te("Loading"))] }); - const sampleHost = el("div", { className: "m02-df__list" }); body.replaceChildren( el("div", { className: "m02-df__start", @@ -232,28 +228,10 @@ function openDataFlow(): void { mineHost, ], }), - el("section", { - className: "m02-df__start-col", - children: [el("h3", { text: te("Start_Samples") }), sampleHost], - }), ], }), ); const M = await mods; - sampleHost.replaceChildren( - ...M.sample.sampleList().map((s) => - listRow( - s.name, - s.resultUnit, - () => - run(async (M2) => { - const lg = M2.sample.sampleLoad(s.id); - if (lg) await showPaper(M2, lg, false); - }), - s.note, - ), - ), - ); try { const mine = (await M.api.cfList()).filter((x: CfListItem) => !x.sample); mineHost.replaceChildren( @@ -379,7 +357,7 @@ function openDataFlow(): void { if (sig === logicSig) return; logicSig = sig; const get = async (key: string): Promise => - key.startsWith("DF-") ? M.api.cfGet(key) : M.sample.sampleLoad(key); + key.startsWith("DF-") ? M.api.cfGet(key) : Promise.resolve(null); void loadLogics(lg, get).then((got) => { if (sig !== logicSig) return; logics = got.logics; diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Sample.ts b/M02_MasterTemplete/DataFlow/M02_DF_Sample.ts deleted file mode 100644 index 96bda504e..000000000 --- a/M02_MasterTemplete/DataFlow/M02_DF_Sample.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* ============================================================================= - * M02_DF_Sample.ts - * 단가산출 흐름도 견본 — 처음 열었을 때 바로 보고 따라 만들 견본(`_sample/*.json` · 손으로 짬) - * ① 돌쌓기 메쌓기(인력) — 변수 · 품표 · 증가율표 · 인력 → 표 찾기 · 높이 할증(직렬) · 품 × 노임(병렬) → 노무비 - * ② 지반 가르기 — 같은 굴착량이 「조건 통과」 선 둘(지반 = 토사 / ≠ 토사)로 굴삭기 · 브레이커에 감 → 경비 - * ③ 흄관 관경 조건 찾기 — 재료 자리(조건 찾기 · 규격에 관경 포함) → 본수 × 단가 → 재료비 - * ④ 다른 로직 잇기 — 작업량 로직(⑤) 출력 → 장비 시간 → 경비 - * ⑤ 덤프트럭 시간당 작업량(값) — ④ 가 부르는 다른 로직 - * 견본은 읽기만 — `sampleLoad` 는 「복사해서 열기」 사본(key 없음 · 저장하면 `_data` 에 새 번호). - * 입력 값(노임 · 표 · 재료)은 견본에 안 들고 열 때 · 저장 때 마스터에서 받음. - * 모양 = `M02_DF_Model` CfLogic(선 규칙 · 재료 방식 · 다른 로직 입력 포함) · 알약 이름 규칙 — 정함 - * `docs/raw/M01_쉬운단가/흐름도_정함.md` 1 · 4~7 · 12. 이 폴더째 지워도 됨. - * ========================================================================== */ - -import { currentLanguageIndex } from "@ui/ui_template_locale"; -import type { CfLogic } from "./M02_DF_Model"; - -const FILES = import.meta.glob("./_sample/*.json", { eager: true, import: "default" }); - -/** 견본마다 한 줄 설명 — [한, 영] */ -const NOTE: Record = { - masonry: [ - "변수 · 품표 · 증가율표 · 인력 → 표 찾기 · 높이 할증 → 품 × 노임 → 노무비", - "Variables, tables and labor → lookup, height surcharge → crew × wage → labor cost", - ], - ground: [ - "지반이 토사면 굴삭기로 · 아니면 브레이커로 — 조건 통과 선 둘 → 경비", - "Soil goes to the excavator, rock to the breaker — two gate lines → expense", - ], - hume: [ - "관경으로 흄관 품목을 찾음(재료 자리 · 규격에 관경 포함) → 본수 × 단가 → 재료비", - "Pipe size picks the hume pipe item (spec contains size) → count × price → material cost", - ], - linked: [ - "작업량 로직의 출력을 받아 장비 시간 → 경비", - "Takes the output of a productivity logic → machine hours → expense", - ], - value: [ - "q · f · E · Cm → 60 × q × f × E ÷ Cm → 값(시간당 작업량)", - "q, f, E, Cm → 60 × q × f × E ÷ Cm → value (output per hour)", - ], -}; - -/** 보여 줄 차례 */ -const ORDER = ["masonry", "ground", "hume", "linked", "value"]; - -export interface CfSampleItem { - /** 파일 이름(확장자 뺌) */ - id: string; - name: string; - resultUnit: string; - /** 쉬운 말 한 줄 */ - note: string; -} - -const idOf = (path: string): string => path.replace(/^.*\//, "").replace(/\.json$/, ""); -const byId = (): Map => - new Map(Object.entries(FILES).map(([path, lg]) => [idOf(path), lg])); - -/** 견본 목록 — 정한 차례 먼저 · 나머지는 이름 차례 */ -export function sampleList(): CfSampleItem[] { - const all = byId(); - const rank = (id: string): number => (ORDER.includes(id) ? ORDER.indexOf(id) : ORDER.length); - return [...all.entries()] - .sort(([a, x], [b, y]) => rank(a) - rank(b) || x.name.localeCompare(y.name)) - .map(([id, lg]) => ({ - id, - name: lg.name, - resultUnit: lg.resultUnit, - note: NOTE[id]?.[currentLanguageIndex === 1 ? 1 : 0] ?? "", - })); -} - -/** 견본 하나 「복사해서 열기」 — 깊은 사본 · key 없음(저장하면 서버가 새 번호) · 없으면 null */ -export function sampleLoad(id: string): CfLogic | null { - const lg = byId().get(id); - return lg ? { ...(JSON.parse(JSON.stringify(lg)) as CfLogic), key: null } : null; -} diff --git a/M02_MasterTemplete/CostFlow/_sample/masonry.json b/resources/tester/fixtures_df/costflow/masonry.json similarity index 100% rename from M02_MasterTemplete/CostFlow/_sample/masonry.json rename to resources/tester/fixtures_df/costflow/masonry.json diff --git a/M02_MasterTemplete/CostFlow/_sample/rate.json b/resources/tester/fixtures_df/costflow/rate.json similarity index 100% rename from M02_MasterTemplete/CostFlow/_sample/rate.json rename to resources/tester/fixtures_df/costflow/rate.json diff --git a/M02_MasterTemplete/CostFlow/_sample/value.json b/resources/tester/fixtures_df/costflow/value.json similarity index 100% rename from M02_MasterTemplete/CostFlow/_sample/value.json rename to resources/tester/fixtures_df/costflow/value.json diff --git a/M02_MasterTemplete/DataFlow/_sample/ground.json b/resources/tester/fixtures_df/ground.json similarity index 100% rename from M02_MasterTemplete/DataFlow/_sample/ground.json rename to resources/tester/fixtures_df/ground.json diff --git a/M02_MasterTemplete/DataFlow/_sample/hume.json b/resources/tester/fixtures_df/hume.json similarity index 100% rename from M02_MasterTemplete/DataFlow/_sample/hume.json rename to resources/tester/fixtures_df/hume.json diff --git a/M02_MasterTemplete/DataFlow/_sample/linked.json b/resources/tester/fixtures_df/linked.json similarity index 100% rename from M02_MasterTemplete/DataFlow/_sample/linked.json rename to resources/tester/fixtures_df/linked.json diff --git a/M02_MasterTemplete/DataFlow/_sample/masonry.json b/resources/tester/fixtures_df/masonry.json similarity index 100% rename from M02_MasterTemplete/DataFlow/_sample/masonry.json rename to resources/tester/fixtures_df/masonry.json diff --git a/M02_MasterTemplete/DataFlow/_sample/value.json b/resources/tester/fixtures_df/value.json similarity index 100% rename from M02_MasterTemplete/DataFlow/_sample/value.json rename to resources/tester/fixtures_df/value.json diff --git a/resources/tester/test_m02_cf_sample.py b/resources/tester/test_m02_cf_sample.py index 2acb47268..00d6fb661 100644 --- a/resources/tester/test_m02_cf_sample.py +++ b/resources/tester/test_m02_cf_sample.py @@ -1,4 +1,4 @@ -"""M02 단가산출 다이어그램 견본(35-7) 시험 — `M02_MasterTemplete/CostFlow/_sample/*.json` · `M02_CF_Sample.ts`. +"""M02 단가산출 다이어그램 견본(35-7) 시험 — 시험 견본 `resources/tester/fixtures_df/costflow/*.json` · `M02_CF_Sample.ts`. · 견본 셋 모양 — 상자(입력 · 함수 · 출력) · 알약 이름 규칙 · 선(받는 자리마다 하나 · 출력에서 안 나감 · 순환 없음) · 견본 ① 원가 = 지금 `/calc`(GF000219 · 같은 예시값 · 끝수 원 미만) — 함수 상자 시트를 우리 스프레드시트 엔진 @@ -21,7 +21,7 @@ import pytest ROOT = Path(__file__).resolve().parents[2] FOLDER = ROOT / "M02_MasterTemplete" / "CostFlow" SAMPLE_TS = FOLDER / "M02_CF_Sample.ts" -SAMPLES = {p.stem: json.loads(p.read_text(encoding="utf-8")) for p in sorted((FOLDER / "_sample").glob("*.json"))} # fmt: skip +SAMPLES = {p.stem: json.loads(p.read_text(encoding="utf-8")) for p in sorted((ROOT / "resources" / "tester" / "fixtures_df" / "costflow").glob("*.json"))} # fmt: skip HOOKS = r""" import { readFileSync } from "node:fs"; diff --git a/resources/tester/test_m02_df_model.py b/resources/tester/test_m02_df_model.py index 4cfbee4d8..f2e45f3c9 100644 --- a/resources/tester/test_m02_df_model.py +++ b/resources/tester/test_m02_df_model.py @@ -646,7 +646,9 @@ def test_쉬는_갈래와_문제_무게() -> None: def _ground() -> dict: - return json.loads((CF / "_sample" / "ground.json").read_text(encoding="utf-8")) + return json.loads( + (ROOT / "resources" / "tester" / "fixtures_df" / "ground.json").read_text(encoding="utf-8") + ) def _server(lg: dict, values: dict) -> dict: diff --git a/resources/tester/test_m02_df_sample.py b/resources/tester/test_m02_df_sample.py index 11bc2f9af..342436f79 100644 --- a/resources/tester/test_m02_df_sample.py +++ b/resources/tester/test_m02_df_sample.py @@ -1,4 +1,4 @@ -"""M02 단가산출 흐름도 견본(35-9) 시험 — `M02_MasterTemplete/DataFlow/_sample/*.json` · `M02_DF_Sample.ts`. +"""M02 단가산출 흐름도 견본(35-9) 시험 — 시험 견본 `resources/tester/fixtures_df/*.json`(36-7 · 화면 견본 목록은 지움). 견본 넷(메쌓기 · 지반 가르기 · 흄관 조건 찾기 · 다른 로직 잇기) + 넷째가 부르는 작업량 로직(value) · 모양 — 상자(입력 · 함수 · 출력) · 알약 이름 규칙 · 기본 색 · 선(받는 자리마다 하나 · 출력에서 안 나감 · 순환 없음) @@ -6,7 +6,7 @@ · 흐름 — 출력에서 거꾸로 따라가면 닿는 출처(선 · 선 규칙의 조건 알약 · 재료 조건 알약 포함)가 견본 뜻과 같음 · 값(내부 확인) — 함수 상자 시트를 우리 스프레드시트 엔진(Node 번들)으로 차례대로 셈 · 선 규칙 · 끝수를 지남 · 입력 값(노임 · 품셈 표 · 재료 단가)은 마스터에서 읽기만 · 메쌓기 = /calc GF000219 · value = /calc GF000133 -· `sampleList` · `sampleLoad`(사본 · key 없음) — Node 로 바로 돌림 · 파일 안 씀 +· 파일 안 씀 """ from __future__ import annotations @@ -14,7 +14,6 @@ from __future__ import annotations import json import math import re -import subprocess from decimal import Decimal from pathlib import Path @@ -22,8 +21,7 @@ import pytest ROOT = Path(__file__).resolve().parents[2] FOLDER = ROOT / "M02_MasterTemplete" / "DataFlow" -SAMPLE_TS = FOLDER / "M02_DF_Sample.ts" -SAMPLES = {p.stem: json.loads(p.read_text(encoding="utf-8")) for p in sorted((FOLDER / "_sample").glob("*.json"))} # fmt: skip +SAMPLES = {p.stem: json.loads(p.read_text(encoding="utf-8")) for p in sorted((ROOT / "resources" / "tester" / "fixtures_df").glob("*.json"))} # fmt: skip NAMES = ["masonry", "ground", "hume", "linked", "value"] BOX_COLOR = dict( re.findall( @@ -34,40 +32,6 @@ BOX_COLOR = dict( ) ) -HOOKS = r""" -import { readFileSync } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { stripTypeScriptTypes } from "node:module"; -export async function load(url, ctx, next) { - if (!url.endsWith(".ts")) return next(url, ctx); - let src = readFileSync(fileURLToPath(url), "utf8"); - src = src.replace(/import\.meta\.glob(?:<[\s\S]*?>)?\([^)]*\)/g, "globalThis.__samples"); - src = src.replace(/import\s*\{([^}]*)\}\s*from\s*"([^"]+)";/g, (m, names) => { - const list = names.split(",").map((x) => x.trim()).filter((x) => x && !x.startsWith("type ")); - return list.length ? `const { ${list.join(", ")} } = globalThis.__stub;` : ""; - }); - return { format: "module", source: stripTypeScriptTypes(src, { mode: "transform" }), shortCircuit: true }; -} -""" - - -def _node(body: str) -> object: - files = {f"./_sample/{k}.json": v for k, v in SAMPLES.items()} - script = ( - "import { register } from 'node:module';\n" - f"register('data:text/javascript,' + encodeURIComponent({json.dumps(HOOKS)}));\n" - "globalThis.__stub = { currentLanguageIndex: 0 };\n" - f"globalThis.__samples = {json.dumps(files, ensure_ascii=False)};\n" - f"const S = await import({json.dumps(SAMPLE_TS.as_uri())});\n" - "const out = (x) => process.stdout.write(JSON.stringify(x));\n" + body - ) - result = subprocess.run( - ["node", "--no-warnings", "--input-type=module", "-e", script], - capture_output=True, text=True, encoding="utf-8", timeout=120, cwd=ROOT, - ) # fmt: skip - assert result.returncode == 0, result.stderr[-2000:] - return json.loads(result.stdout) - # ── 알약 · 선 ───────────────────────────────────────────────────────────── def _ports_out(box: dict) -> list[str]: @@ -215,16 +179,6 @@ def test_견본_흐름_규칙_방식() -> None: } -def test_견본_목록_열기() -> None: - got = _node( - "out({ list: S.sampleList(), one: S.sampleLoad('ground'), none: S.sampleLoad('없음') });" - ) - assert [x["id"] for x in got["list"]] == NAMES - assert all(x["note"] for x in got["list"]) - assert got["one"]["key"] is None and got["one"]["links"] == SAMPLES["ground"]["links"] - assert got["none"] is None - - # ── 값(내부 확인) ───────────────────────────────────────────────────────── @pytest.fixture(scope="module") def master():