diff --git a/M02_MasterTemplete/M02_Labor_Inputs.py b/M02_MasterTemplete/M02_Labor_Inputs.py index 71cf43bae..fd18f0489 100644 --- a/M02_MasterTemplete/M02_Labor_Inputs.py +++ b/M02_MasterTemplete/M02_Labor_Inputs.py @@ -40,8 +40,20 @@ def _logics(codes: list[str]) -> tuple[list[dict], list[str]]: return found, missing +def _labels(whole: Any, spec: dict) -> dict[str, str]: + """고르기 원문번호 → 보이는 글(85-1) — 기계 「이름 규격」 · 직종(입력 이름에 「직종」) 「이름」 · 모르는 번호는 뺌.""" + table = "LB" if "직종" in str(spec.get("이름") or "") else "EQ" + rows = whole.numbers.get(table, {}) + out = {} + for value in spec.get("고르기") or []: + row = rows.get(str(value)) if isinstance(value, str) else None + if row: + out[value] = " ".join(str(row[k]) for k in ("이름", "규격") if row.get(k)) + return out + + def _listed(logics: list[dict]) -> list[dict[str, Any]]: - """로직 줄마다 `{로직, 이름, 결과단위, 출처, 입력: [{이름, 설명, 단위, 고르기?, 범위?, 자동값}]}`.""" + """로직 줄마다 `{로직, 이름, 결과단위, 출처, 입력: [{이름, 설명, 단위, 고르기?, 고르기이름?, 범위?, 자동값}]}`.""" whole = m01.loaded()[1] out = [] for logic in logics: @@ -51,6 +63,9 @@ def _listed(logics: list[dict]) -> list[dict[str, Any]]: one = {k: spec[k] for k in _SPEC if k in spec} one.setdefault("설명", None) one.setdefault("단위", None) + labels = _labels(whole, spec) + if labels: + one["고르기이름"] = labels specs.append({**one, "자동값": auto.get(spec["이름"])}) out.append( { diff --git a/M02_MasterTemplete/M02_Labor_Values.ts b/M02_MasterTemplete/M02_Labor_Values.ts index e70f8b978..b83422733 100644 --- a/M02_MasterTemplete/M02_Labor_Values.ts +++ b/M02_MasterTemplete/M02_Labor_Values.ts @@ -13,6 +13,7 @@ import { choicesOf, inputKey, isOutside, + labelOf, rangeOf, storedOf, type ValueDoc, @@ -65,7 +66,12 @@ export function createValueControl( if (control instanceof HTMLSelectElement) { control.append( ...(auto === null ? [el("option", { attrs: { value: "" }, text: "—" })] : []), - ...choices.map((c) => el("option", { attrs: { value: c.값 }, text: c.이름 })), + ...choices.map((c) => + el("option", { + attrs: { value: c.값, ...(c.이름 !== c.값 ? { title: c.값 } : {}) }, + text: c.이름, + }), + ), ); } const empty = value === null || value === ""; @@ -194,10 +200,15 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand }; if (opts.mode === "view") { const text = isEmpty(stored.값) ? auto : stored.값; + const label = text === null ? "—" : labelOf(input, text); const shown = el("span", { className: `m02-inputval__value${isEmpty(stored.값) ? " is-auto" : ""}`, - text: text === null ? "—" : String(text), - attrs: isEmpty(stored.값) ? { title: VALUE_TEXT.auto } : {}, + text: label, + attrs: isEmpty(stored.값) + ? { title: VALUE_TEXT.auto } + : label !== String(text) + ? { title: String(text) } + : {}, }); paintState(); return el("div", { className: "m02-inputval__cell", children: [shown, state] }); diff --git a/M02_MasterTemplete/M02_Labor_Values_Model.ts b/M02_MasterTemplete/M02_Labor_Values_Model.ts index b49ea4771..5d8c61de2 100644 --- a/M02_MasterTemplete/M02_Labor_Values_Model.ts +++ b/M02_MasterTemplete/M02_Labor_Values_Model.ts @@ -14,9 +14,14 @@ const text = (value: unknown): string => ? JSON.stringify(value) : String(value); +/** 값 → 보이는 글(85-1) — 원문번호면 「이름 규격」 · 그 밖은 값 그대로 */ +export const labelOf = (input: ItemInput, value: unknown): string => + input.고르기이름?.[text(value)] ?? text(value); + /** 고르기 · 범위 표시 — 선택지 목록이면 「a / b / c」 · 범위(배열 둘 · {최소, 최대})면 「최소 ~ 최대」 */ export function valueHint(input: ItemInput): string { - if (Array.isArray(input.고르기) && input.고르기.length) return input.고르기.map(text).join(" / "); + if (Array.isArray(input.고르기) && input.고르기.length) + return input.고르기.map((one) => labelOf(input, one)).join(" / "); const range = input.범위; if (Array.isArray(range) && range.length >= 2) return `${text(range[0])} ~ ${text(range[1])}`; if (range && typeof range === "object") { @@ -60,7 +65,11 @@ export function choicesOf(input: ItemInput): { 값: string; 이름: string; raw: const object = typeof one === "object" && one !== null ? (one as Record) : null; const raw = (object ? (object.값 ?? object.이름 ?? "") : one) as string | number; - return { 값: String(raw), 이름: object ? String(object.이름 ?? raw) : String(one), raw }; + return { + 값: String(raw), + 이름: object ? String(object.이름 ?? raw) : labelOf(input, one), + raw, + }; }); } @@ -112,7 +121,7 @@ export function valueLine( for (const input of inputs) { const value = values?.[input.이름]; if (value !== undefined && value !== "") { - parts.push(`${input.이름} ${value}${input.단위 ? input.단위 : ""}`); + parts.push(`${input.이름} ${labelOf(input, value)}${input.단위 ? input.단위 : ""}`); } else if (autoText(input) === "") empty += 1; } return { text: parts.join(" · "), empty }; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts b/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts index a76619d33..c6e60c267 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Api_Fetch.ts @@ -319,6 +319,8 @@ export interface ItemInput { 설명?: string; 단위?: string; 고르기?: unknown[]; + /** 고르기 원문번호 → 보이는 글(85-1 · 기계 「이름 규격」 · 직종 이름) */ + 고르기이름?: Record; 범위?: unknown; 자동값?: unknown; } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts index 9c2ab01b1..4b464ba3d 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts @@ -98,7 +98,7 @@ export interface LaborDiagramSource { 로직: string; 이름: string; 결과단위?: string; - 입력: { 이름: string; 단위?: string; 자동값?: unknown }[]; + 입력: { 이름: string; 단위?: string; 자동값?: unknown; 고르기이름?: Record }[]; }[]; /** calc 답 · 아직이면 null */ calc: { @@ -137,10 +137,11 @@ export function buildLaborDiagram(src: LaborDiagramSource): DiagramData { inputs: (def?.입력 ?? []).map((x) => { const mine = r.입력값[x.이름]; const has = mine !== undefined && mine !== ""; + const raw = has ? String(mine) : autoOf(x.자동값); return { name: x.이름, unit: x.단위 ?? "", - value: has ? String(mine) : autoOf(x.자동값), + value: x.고르기이름?.[raw] ?? raw, // 85-1 원문번호 → 「이름 규격」 ...(has || !autoOf(x.자동값) ? {} : { auto: true }), }; }), diff --git a/resources/tester/test_m02_labor_values.py b/resources/tester/test_m02_labor_values.py index 923e5fe8c..e800b2d63 100644 --- a/resources/tester/test_m02_labor_values.py +++ b/resources/tester/test_m02_labor_values.py @@ -70,6 +70,9 @@ const kind = { 이름: "종류", 고르기: ["막돌", "견치돌"] }; const size = { 이름: "규격", 고르기: [25, 40] }; const area = { 이름: "지역" }; const inputs = [height, kind, size, area]; +const machine = { + 이름: "기계", 고르기: ["0201-0060", "9999"], 고르기이름: { "0201-0060": "굴착기(무한궤도) 0.6㎥" }, +}; console.log(JSON.stringify({ stored: [ m.storedOf(height, ""), m.storedOf(height, "2.5"), m.storedOf(kind, "막돌"), @@ -82,6 +85,11 @@ console.log(JSON.stringify({ known: m.keepKnown(inputs, { 높이: 2, 종류: "", 없앤것: 3 }), line: m.valueLine(inputs, { 높이: 2, 규격: 40 }), lineEmpty: m.valueLine(inputs, {}), + named: [ + m.choicesOf(machine).map((c) => [c.값, c.이름]), m.valueHint(machine), + m.valueLine([machine], { 기계: "0201-0060" }).text, m.labelOf(machine, "9999"), + m.storedOf(machine, "0201-0060"), + ], })); """ @@ -100,6 +108,27 @@ def test_값_셈() -> None: assert got["known"] == {"kept": {"높이": 2}, "lost": ["없앤것"]} assert got["line"] == {"text": "높이 2m · 규격 40", "empty": 2} # 종류 · 지역 = 자동값 없음 assert got["lineEmpty"] == {"text": "", "empty": 3} # 높이는 자동값이 있어 빈 값 아님 + # 85-1 — 보이는 글 = 이름 규격 · 모르는 번호는 그대로 · 저장 값은 번호 + assert got["named"] == [ + [["0201-0060", "굴착기(무한궤도) 0.6㎥"], ["9999", "9999"]], + "굴착기(무한궤도) 0.6㎥ / 9999", + "기계 굴착기(무한궤도) 0.6㎥", + "9999", + "0201-0060", + ] + + +def test_고르기_원문번호에_이름표(client: TestClient) -> None: + """85-1 — 기계 · 집게 = 「이름 규격」 · 직종(입력 이름에 「직종」) = 직종 이름 · 고르기는 번호 그대로.""" + got = client.get("/api/m02/logic-lib/inputs", params={"keys": "GC000260"}).json() + specs = {i["이름"]: i for i in got["로직"][0]["입력"]} + assert specs["기계"]["고르기이름"]["0201-0060"] == "굴착기(무한궤도) 0.6㎥" + assert specs["집게"]["고르기이름"]["7206-0020"] == "부착용 집게 0.2㎥" + assert "0201-0060" in specs["기계"]["고르기"] + assert "고르기이름" not in specs["뒷길이"] and "고르기이름" not in specs["지역"] + whole = labor_inputs.m01.loaded()[1] + job = {"이름": "조종원직종", "고르기": ["1048", "없는번호"]} + assert labor_inputs._labels(whole, job) == {"1048": "건설기계운전사"} ROWS_SCRIPT = r"""