diff --git a/M01_MasterData/M01_MasterData_Router.py b/M01_MasterData/M01_MasterData_Router.py index 191ed674..97424138 100644 --- a/M01_MasterData/M01_MasterData_Router.py +++ b/M01_MasterData/M01_MasterData_Router.py @@ -27,6 +27,9 @@ class CalcBody(BaseModel): inputs: dict[str, Any] = {} row: dict[str, Any] | None = None # 저장 전 고친 로직 줄(없으면 저장된 파일로) file: str | None = None # 새 로직이면 더할 로직 파일 + 고름: dict[ + str, str + ] = {} # {호표 줄 차례(0부터): 자재 키} — 그 줄만 이 품목으로 셈 · 저장 안 함 class Change(BaseModel): @@ -212,6 +215,12 @@ def get_logic(key: str) -> dict: return _call(store.logic, key) +@router.get("/logic/auto") +def get_logic_auto(key: str) -> dict: + """견본 입력 — 마스터 보기용 자동값(프로젝트 기본값 아님).""" + return _call(store.auto, key) + + @router.get("/elements") def get_elements(group: str, q: str = "", limit: int = 50) -> dict: return _call(store.elements, group, q, limit) @@ -238,13 +247,15 @@ def get_materials( @router.post("/calc") def post_calc(body: CalcBody) -> dict: - return _call(store.calc, body.key, body.inputs, body.row, body.file) + with store.cm.mf.mp.choosing(body.고름): + return _call(store.calc, body.key, body.inputs, body.row, body.file) @router.post("/text") def post_text(body: CalcBody) -> dict: """읽는 식 줄 — 몸은 `/calc` 와 같음.""" - return _call(store.text, body.key, body.inputs, body.row, body.file) + with store.cm.mf.mp.choosing(body.고름): + return _call(store.text, body.key, body.inputs, body.row, body.file) @router.post("/save") diff --git a/M01_MasterData/M01_MasterData_Store.py b/M01_MasterData/M01_MasterData_Store.py index cf8d28e0..16508673 100644 --- a/M01_MasterData/M01_MasterData_Store.py +++ b/M01_MasterData/M01_MasterData_Store.py @@ -19,6 +19,7 @@ SCRIPTS = Path(__file__).resolve().parents[1] / "resources/master_data/scripts" sys.path.insert(0, str(SCRIPTS)) import check_master as cm # noqa: E402 +import master_auto as mau # noqa: E402 import master_combo as mcb # noqa: E402,F401 (`_Store_Combo` 가 `store.mcb` 로 씀) import master_copy as mcp # noqa: E402,F401 (`_Store_Make` 가 `store.mcp` 로 씀) import master_keys as mk # noqa: E402 @@ -87,10 +88,8 @@ def chapter_of(file: str, data: dict | None = None) -> str: def _hit(row: dict, q: str) -> bool: - q = q.lower() - return not q or any( - q in str(row.get(k, "")).lower() for k in ("키", "원문번호", "이름", "옛이름") - ) + q = _squash(q) # 띄어쓰기 무시 — 「골 함 석」 ↔ 「함석」 + return not q or any(q in _squash(row.get(k, "")) for k in ("키", "원문번호", "이름", "옛이름")) def _book(data: dict) -> str | None: @@ -547,6 +546,7 @@ def calc(key: str, inputs: dict, row: dict | None = None, file: str | None = Non if row is not None: key = _swap(files, str(row.get("키") or key), row, file) try: + given = cm.mf.fill_empty(cm.mf.Master(files).logic(key), given) result = cm.calc(files, key, given) except (cm.mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError) as e: return {"ok": False, "reason": str(e) or type(e).__name__} @@ -560,6 +560,15 @@ def calc(key: str, inputs: dict, row: dict | None = None, file: str | None = Non } +def auto(key: str) -> dict: + """견본 입력 — 마스터 보기용 자동값(`master_auto`) · 프로젝트 기본값 아님 · 저장 안 함.""" + whole = loaded()[1] + try: + return {"값": mau.values(whole, whole.logic(key))} + except cm.mf.FormulaError as e: + raise StoreError(404, str(e)) from e + + def text(key: str, inputs: dict, row: dict | None = None, file: str | None = None): """사람이 읽는 식 줄 — 비목 묶음마다 「이름 : 단가 * 수량식 = 금액」 과 소계(`master_text`).""" given = {k: _dec(v) for k, v in inputs.items()} @@ -567,6 +576,7 @@ def text(key: str, inputs: dict, row: dict | None = None, file: str | None = Non if row is not None: key = _swap(files, str(row.get("키") or key), row, file) try: + given = cm.mf.fill_empty(cm.mf.Master(files).logic(key), given) return mtx.lines(files, key, given) except (cm.mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError) as e: return {"ok": False, "reason": str(e) or type(e).__name__} diff --git a/M01_MasterData/M01_MasterData_UI_Combo_Preview.ts b/M01_MasterData/M01_MasterData_UI_Combo_Preview.ts index 71765bf8..27a4d46b 100644 --- a/M01_MasterData/M01_MasterData_UI_Combo_Preview.ts +++ b/M01_MasterData/M01_MasterData_UI_Combo_Preview.ts @@ -5,9 +5,10 @@ * ========================================================================== */ import { createInputField, createSelectField, el } from "@ui/ui_template_elements"; -import { fetchLogic, type LogicInput } from "./M01_MasterData_UI_Logic_Api"; +import { fetchAuto, fetchLogic, type LogicInput } from "./M01_MasterData_UI_Logic_Api"; import { previewCombo, type Combo } from "./M01_MasterData_UI_Combo_Api"; import { tc, type ComboTextKey } from "./M01_MasterData_UI_Combo_Text"; +import { formatMoney } from "./M01_MasterData_UI_Logic_Money"; const COSTS = ["노무비", "재료비", "경비"] as const; const LABEL: Record<(typeof COSTS)[number], ComboTextKey> = { @@ -17,8 +18,8 @@ const LABEL: Record<(typeof COSTS)[number], ComboTextKey> = { }; const num = (v: string): number | string => (v.trim() !== "" && !isNaN(Number(v)) ? Number(v) : v); -/** 원 단위 소수점 0 — 조합 미리 보기는 모두 돈 자리(노무비·재료비·경비·계) */ -const fmt = (n: number): string => n.toLocaleString("ko-KR", { maximumFractionDigits: 0 }); +/** 조합 미리 보기는 모두 돈 자리(노무비·재료비·경비·계) */ +const fmt = formatMoney; /** 로직 입력 정의 → 처음 값(고르기 첫째 · 범위 아래끝) */ const initial = (i: LogicInput): string => String(i.고르기?.[0] ?? i.범위?.[0] ?? ""); @@ -28,6 +29,7 @@ function inputCell( input: LogicInput, key: string, values: Record>, + onEdit: () => void, ): HTMLElement { const mine = (values[key] ??= {}); const value = mine[input.이름] ?? initial(input); @@ -39,8 +41,14 @@ function inputCell( const control = "select" in field ? field.select : field.input; control.value = value; control.setAttribute("data-input", `${key}|${input.이름}`); - control.addEventListener("input", () => (mine[input.이름] = control.value)); - control.addEventListener("change", () => (mine[input.이름] = control.value)); + const edit = (): void => { + mine[input.이름] = control.value; + }; + control.addEventListener("input", edit); + control.addEventListener("change", () => { + edit(); + onEdit(); // 바꾸면 다시 미리 보기 계산 + }); return field.root; } @@ -54,6 +62,8 @@ export function buildPreview(combo: () => Combo): PreviewHandle { const values: Record> = {}; /** 서버가 받을 꼴 — 원래 숫자·고르기 값으로 되돌림 */ const inputs = new Map(); + /** 로직마다 자동값은 한 번만 받음 */ + const asked = new Set(); const form = el("div", { className: "m01-logic__stack" }); const out = el("div", { className: "m01-master__grid-wrap", @@ -130,7 +140,17 @@ export function buildPreview(combo: () => Combo): PreviewHandle { inputs.clear(); for (const row of combo().담은로직) { try { - const one = await fetchLogic(row.로직); + const [one, auto] = await Promise.all([ + fetchLogic(row.로직), + asked.has(row.로직) ? Promise.resolve({}) : fetchAuto(row.로직), + ]); + asked.add(row.로직); + const mine = (values[row.로직] ??= {}); + for (const i of one.logic.입력) { + const got = (auto as Record)[i.이름]; + if (got === undefined || got === null || (mine[i.이름] ?? "").trim() !== "") continue; + mine[i.이름] = String(got); + } inputs.set(row.로직, one.logic.입력); blocks.push( el("div", { @@ -140,7 +160,7 @@ export function buildPreview(combo: () => Combo): PreviewHandle { className: "m01lab__cell--wide", text: `${row.로직} ${one.logic.이름}`, }), - ...one.logic.입력.map((i) => inputCell(i, row.로직, values)), + ...one.logic.입력.map((i) => inputCell(i, row.로직, values, () => void run())), ], }), ); @@ -149,7 +169,9 @@ export function buildPreview(combo: () => Combo): PreviewHandle { } } form.replaceChildren(...blocks); - out.replaceChildren(el("p", { className: "m01-logic__muted", text: tc("Prev_Wait") })); + if (blocks.length) + await run(); // 열자마자 한 번 미리 보기 계산 + else out.replaceChildren(el("p", { className: "m01-logic__muted", text: tc("Prev_Wait") })); }; const root = el("section", { diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab.ts b/M01_MasterData/M01_MasterData_UI_LogicLab.ts index 4eb22ae7..c0d63c84 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab.ts @@ -34,6 +34,7 @@ import { buildList, logicId, type ListItem, type ListMark } from "./M01_MasterDa import type { SideHandle } from "./M01_MasterData_UI_Side"; import { tx } from "./M01_MasterData_UI_Logic_Text"; import { buildLabDetail, isOwnLogic } from "./M01_MasterData_UI_LogicLab_Detail"; +import { setLogicOpener } from "./M01_MasterData_UI_LogicLab_Modal"; import { openCopyScreen } from "./M01_MasterData_UI_LogicLab_Copy"; import { tl } from "./M01_MasterData_UI_LogicLab_Text"; import { buildNewLogic } from "./M01_MasterData_UI_LogicLab_New"; @@ -58,6 +59,8 @@ interface Opened extends Draft { reasons: string[]; lines: CalcLine[] | null; text: TextAnswer | null; + /** 마지막으로 잘 받은 읽는 식 — 멈춘 뒤에도 수량 칸 조각을 이걸로 */ + lastText?: TextAnswer; ownHost: HTMLElement; values: Record; } @@ -165,10 +168,12 @@ export async function mountM01LogicLab( savedKey: current.origKey, file: current.file, row: current.row as LogicRow, + prices: current.prices, dirty: () => current.id in drafts, values: current.values, onText: (text) => { current.text = text; + if (text.ok) current.lastText = text; drawEditor(); // [계산] 전 미리보기 응답도 왼쪽 이름 식 칸에 바로 비치게 — onLines 와 같은 자리 }, onLines: (lines) => { @@ -201,6 +206,7 @@ export async function mountM01LogicLab( prices: current.prices, lines: current.lines, text: current.text, + lastText: current.lastText, values: current.values, ownHost: current.ownHost, onChange: touch, @@ -441,5 +447,6 @@ export async function mountM01LogicLab( } finally { hideLoadingOverlay(); } + setLogicOpener((key) => void openKeyOn(key)); return { openKey: openKeyOn }; } diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts index 03fbe43d..5842ec45 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts @@ -14,57 +14,45 @@ import type { LogicRow, NamedFormula, TextAnswer, + TextLine, + TextPiece, } from "./M01_MasterData_UI_Logic_Api"; -import { buildEditor, formatNumber, qtyKind } from "./M01_MasterData_UI_Logic_Edit"; +import { buildEditor, qtyKind } from "./M01_MasterData_UI_Logic_Edit"; +import { formatMoney, formatPrice } from "./M01_MasterData_UI_Logic_Money"; import { openPicker } from "./M01_MasterData_UI_Logic_Pick"; -import { tx } from "./M01_MasterData_UI_Logic_Text"; +import { safeReason, tx } from "./M01_MasterData_UI_Logic_Text"; import { buildFormula } from "./M01_MasterData_UI_LogicLab_Formula"; -import { openMaterialModal, openTableModal } from "./M01_MasterData_UI_LogicLab_Modal"; +import { + condText, + openMaterialModal, + openNoteModal, + openRefModal, + openLogic, + openTableModal, + type PickCond, +} from "./M01_MasterData_UI_LogicLab_Modal"; import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill"; import { tl, type LabTextKey } from "./M01_MasterData_UI_LogicLab_Text"; import "./M01_MasterData_UI_LogicLab_Style.css"; import "./M01_MasterData_UI_LogicLab_Pill.css"; -/** 원 단위 소수점 0 — 돈 자리(단가·금액·소계·계)만. 수량·비율은 `formatNumber` 그대로 둠. */ -const formatMoney = (value: unknown): string => - typeof value === "number" - ? value.toLocaleString("ko-KR", { maximumFractionDigits: 0 }) - : formatNumber(value); - -/** 조각 계약(`ref/_분석_알약_상세줄.md` ㉮ 표) — 수량 식 안 찾기(...) 표 호출만 알약, - * 그 밖(숫자·연산기호·중간값·설계입력 이름)은 글자. 서버가 조각 목록을 아직 안 주므로 - * 여기서 임시로 쪼갬(`Logic_Note.ts` 찾기() 정규식과 같은 규칙) — 서버가 붙으면 이 파싱을 걷어냄. */ -const FIND_RE = /찾기\(\s*([A-Za-z0-9_]+)\s*,([^)]*)\)\s*\.\s*([A-Za-z0-9_가-힣]+)/g; - -function splitFindConds(raw: string): [string, string][] { - return raw - .split(",") - .map((p) => p.trim()) - .filter(Boolean) - .map((p): [string, string] => { - const i = p.indexOf("="); - return i < 0 ? [p, p] : [p.slice(0, i).trim(), p.slice(i + 1).trim()]; - }); -} - -function qtyFragments(expr: string, values: Record): Fragment[] { - const out: Fragment[] = []; - let last = 0; - for (const m of expr.matchAll(FIND_RE)) { - const at = m.index ?? 0; - if (at > last) out.push({ text: expr.slice(last, at) }); - const table = m[1]; - const label = `${tl("Pill_Table")} ${table}`; - out.push({ - pill: { - label, - onClick: () => openTableModal({ title: label, table, conds: splitFindConds(m[2]), values }), - }, - }); - last = at + m[0].length; - } - if (last < expr.length) out.push({ text: expr.slice(last) }); - return out.length ? out : [{ text: expr }]; +/** 서버 조각(`/text` 줄의 `조각`) → 수량 칸 조각 — 요소·표·로직은 알약(글 = 조각 글) · 나머지는 글자 */ +function toFragments(pieces: TextPiece[], ctx: DetailContext): Fragment[] { + return pieces.map((piece): Fragment => { + const dim = piece.깊이 ? { dim: true } : {}; + if (piece.kind !== "요소" && piece.kind !== "표" && piece.kind !== "로직") + return { text: piece.글, ...dim }; + const ref = piece.참조; + const open = (): void => { + if (!ref) return openNoteModal(piece.글, tl("Pill_NoPick")); + if (piece.kind === "표") + openTableModal({ title: piece.글, table: ref.키, row: ref.줄 ?? ref.행, col: ref.열 }); + else if (piece.kind === "요소") + openRefModal({ title: piece.글, ref: ref.키, prices: ctx.prices }); + else openLogic(ref.키); + }; + return { pill: { label: piece.글, onClick: open }, ...dim }; + }); } export interface DetailContext { @@ -76,6 +64,8 @@ export interface DetailContext { lines: CalcLine[] | null; /** 마지막 시험 계산의 읽는 식 줄(서버가 줌) */ text: TextAnswer | null; + /** 마지막으로 잘 받은 읽는 식 — `text` 가 멈춤이면 조각을 이걸로 */ + lastText?: TextAnswer; values: Record; /** 자체 로직 편집기 자리 — 다시 그려도 같은 칸을 씀(고치던 값이 안 날아감) */ ownHost: HTMLElement; @@ -94,6 +84,8 @@ const groupOfHo = (item: HoLine): Group => OF_KIND[item.종류] ?? (item.종류 === "로직" ? OF_COST[item.비목 ?? ""] : undefined) ?? "other"; interface Entry { + /** 호표 줄 차례(덧줄은 호표 수 + 덧줄 차례) — 서버 조각을 찾는 열쇠 */ + idx: number; group: Group; name: string; spec: string; @@ -101,13 +93,29 @@ interface Entry { qty: string; qtyTag: string; qtyFrag: Fragment[]; + /** 한 줄이 비목 몫으로 갈렸을 때 이 묶음의 몫 이름(예 「노무비」) */ + share?: string; price: string; amount: number | null; /** 덧줄이면 true */ extra: boolean; - open: () => void; + open: (self: Entry) => void; + /** 서버 조각으로 그린 단가 자리 · 수량 풀이 — 줄 모달이 씀(붙기 전엔 없음) */ + unitFrag?: Fragment[]; + piecesFrag?: Fragment[]; + /** 서버 요소조각의 첫 요소 — 자리 표시 든 줄이 입력으로 풀린 키 · 글 */ + unitRef?: string; + unitText?: string; + /** 자재 단위가 줄 단위와 성격이 다름 — 「줄 m · 자재 Ton」 */ + warn?: string; } +/** 재료 고르기 줄 — `요소` 가 글 대신 {구분·상세구분·규격·대표} 객체 */ +const condOf = (item: HoLine): PickCond | null => + typeof item.요소 === "object" && item.요소 !== null ? (item.요소 as unknown as PickCond) : null; +const condLabel = (cond: PickCond): string => + [cond.구분, cond.상세구분, cond.규격].filter(Boolean).join(" · "); + const groupName = (group: Group): string => tl(`G_${group}` as LabTextKey); const section = (title: string, body: HTMLElement[], hint = ""): HTMLElement => @@ -162,7 +170,7 @@ function infoBox(ctx: DetailContext): HTMLElement { className: "m01-logic__reasons", children: [ el("strong", { text: tx("Head_Blocked") }), - ...ctx.reasons.map((r) => el("div", { text: r })), + ...ctx.reasons.map((r) => el("div", { text: safeReason(r) })), ], }), ] @@ -196,7 +204,20 @@ function infoBox(ctx: DetailContext): HTMLElement { const spread = (e: Entry, line?: CalcLine): Entry[] => { const parts = Object.entries(line?.비목 ?? {}).filter(([, v]) => v !== 0); if (!parts.length) return [e]; - return parts.map(([k, v]) => ({ ...e, group: OF_COST[k] ?? "other", amount: v })); + return parts.map(([k, v]) => ({ + ...e, + group: OF_COST[k] ?? "other", + amount: v, + ...(parts.length > 1 + ? { + share: k, + // 몫 줄 단가 = 단가 × (몫 금액 ÷ 줄 금액) — 곱하기 수량이 몫 금액과 맞게 + ...(line && line.금액 && line.단가 != null + ? { price: formatPrice((line.단가 * v) / line.금액) } + : {}), + } + : {}), + })); }; function entries(ctx: DetailContext): Entry[] { @@ -204,24 +225,31 @@ function entries(ctx: DetailContext): Entry[] { const ho = ctx.row.호표 ?? []; const out: Entry[] = ho.flatMap((item, i) => { const line = ctx.lines?.[i]; - const brief = ctx.prices[item.요소]; + const cond = condOf(item); + const brief = cond ? ctx.prices[condText(cond)] : ctx.prices[item.요소]; const price = line ? line.단가 : brief?.값; const entry: Entry = { + idx: i, group: groupOfHo(item), - name: item.이름 ?? item.요소, + name: item.이름 ?? (cond ? condLabel(cond) : item.요소), spec: item.규격 ?? brief?.규격 ?? "", unit: item.단위 ?? "", qty: item.수량, qtyTag: qtyKind(item.수량) === tx("Ho_QtyTable") ? tl("Tag_Table") : qtyKind(item.수량), - qtyFrag: qtyFragments(item.수량, ctx.values), - price: price === undefined || price === null ? "" : formatMoney(price), + qtyFrag: [{ text: item.수량 }], + price: price === undefined || price === null ? "" : formatPrice(price), amount: line ? line.금액 : null, + warn: line?.단위경고, extra: false, - open: () => + open: (self) => openMaterialModal({ - title: item.이름 ?? item.요소, + unitFrag: self.unitFrag, + unitRef: self.unitRef, + unitText: self.unitText, + qtyFrag: self.piecesFrag, + title: item.이름 ?? (cond ? condLabel(cond) : item.요소), expr: item.수량, - element: item.요소, + ...(cond ? { cond, unit: item.단위 } : { element: item.요소 }), prices: ctx.prices, middles, values: ctx.values, @@ -232,18 +260,21 @@ function entries(ctx: DetailContext): Entry[] { (ctx.row.덧줄 ?? []).forEach((extra, i) => { const line = ctx.lines?.[ho.length + i]; const entry: Entry = { + idx: ho.length + i, group: OF_COST[extra.비목 ?? ""] ?? "other", name: extra.이름, spec: "", unit: "", qty: extra.식, qtyTag: tl("Extra"), - qtyFrag: qtyFragments(extra.식, ctx.values), + qtyFrag: [{ text: extra.식 }], price: "", amount: line ? line.금액 : null, extra: true, - open: () => + open: (self) => openMaterialModal({ + unitFrag: self.unitFrag, + qtyFrag: self.piecesFrag, title: extra.이름, expr: extra.식, prices: ctx.prices, @@ -253,9 +284,49 @@ function entries(ctx: DetailContext): Entry[] { }; out.push(...spread(entry, line)); }); + attachPieces(out, ctx); return out; } +/** 서버 `/text` 줄의 조각을 호표 줄 차례(덧줄은 덧줄 차례)로 이음 — 서버는 묶음마다 호표 차례대로 줄을 주므로 + * 이름을 차례로 따라가며 줄 차례에 맞춤 · 금액 0 로직 부름 줄처럼 묶음이 달라도 찾음 · + * 끝내 못 찾으면 날식 대신 빈칸(계산 전 · 서버 실패면 식 글자 그대로) */ +function attachPieces(all: Entry[], ctx: DetailContext): void { + const source = ctx.text?.ok ? ctx.text : ctx.lastText; + if (!source?.ok) { + if (ctx.text) for (const e of all) e.qtyFrag = e.piecesFrag = []; // 멈춤 · 받은 조각 없음 — 날식 대신 빈칸 + return; + } + const ho = ctx.row.호표 ?? []; + const names = [ + ...ho.map((item) => item.이름 ?? (condOf(item) ? condLabel(condOf(item)!) : item.요소)), + ...(ctx.row.덧줄 ?? []).map((extra) => extra.이름), + ]; + const found = new Map(); + for (const g of source.groups) { + let from = 0; + for (const line of g.줄) { + const at = names.findIndex((n, i) => i >= from && n === line.이름); + if (at < 0) continue; + found.set(`${g.비목}|${at}`, line); + from = at + 1; + } + } + const costs = Object.keys(OF_COST); + for (const e of all) { + const own = costs.find((c) => OF_COST[c] === e.group); + const line = [own, ...costs].map((c) => found.get(`${c}|${e.idx}`)).find(Boolean); + const pieces = line?.조각; + e.qtyFrag = e.piecesFrag = pieces?.length ? toFragments(pieces, ctx) : []; + if (line?.요소조각?.length) { + e.unitFrag = toFragments(line.요소조각, ctx); + const first = line.요소조각.find((piece) => piece.kind === "요소"); + e.unitRef = first?.참조?.키; + e.unitText = first?.글; + } + } +} + const cellOf = (text: string, className = ""): HTMLElement => el("td", { className, text }); function lineRow(e: Entry): HTMLElement { @@ -266,7 +337,18 @@ function lineRow(e: Entry): HTMLElement { el("td", { children: [ el("div", { text: e.name }), + ...(e.share + ? [el("div", { className: "m01-logic__tag", text: `${e.share} ${tl("Share")}` })] + : []), ...(e.spec ? [el("div", { className: "m01-logic__muted", text: e.spec })] : []), + ...(e.warn + ? [ + el("div", { + className: "m01-logic__tag m01lab__warn", + text: `${tx("Calc_UnitWarn")} · ${e.warn}`, + }), + ] + : []), ], }), cellOf(e.unit), @@ -281,8 +363,8 @@ function lineRow(e: Entry): HTMLElement { cellOf(e.amount === null ? "" : formatMoney(e.amount), "m01-logic__money"), ], }); - tr.addEventListener("click", e.open); - tr.addEventListener("keydown", (ev) => ev.key === "Enter" && e.open()); + tr.addEventListener("click", () => e.open(e)); + tr.addEventListener("keydown", (ev) => ev.key === "Enter" && e.open(e)); return tr; } diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts index 332cc315..9adccc09 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts @@ -7,8 +7,8 @@ import { el } from "@ui/ui_template_elements"; import type { TextAnswer } from "./M01_MasterData_UI_Logic_Api"; -import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; -import { tx } from "./M01_MasterData_UI_Logic_Text"; +import { formatMoney } from "./M01_MasterData_UI_Logic_Money"; +import { safeReason, tx } from "./M01_MasterData_UI_Logic_Text"; import { tl } from "./M01_MasterData_UI_LogicLab_Text"; import "./M01_MasterData_UI_LogicLab_Formula.css"; @@ -31,7 +31,10 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void { host.replaceChildren( el("div", { className: "m01-logic__reasons", - children: [el("strong", { text: tx("Calc_Stopped") }), el("div", { text: text.reason })], + children: [ + el("strong", { text: tx("Calc_Stopped") }), + el("div", { text: safeReason(text.reason) }), + ], }), ); return; @@ -62,7 +65,7 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void { ? [ el("div", { text: `${line.이름} : ${line.글}` }), ...(line.까닭 - ? [el("div", { className: "m01-logic__muted", text: line.까닭 })] + ? [el("div", { className: "m01-logic__muted", text: safeReason(line.까닭) })] : []), ] : [el("div", { className: "m01-logic__muted", text: tl("Formula_Slot") })], @@ -82,7 +85,7 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void { el("p", { className: "m01lab__subtotal-text m01lab-fx__wide", attrs: { "data-text-sub": String(g.소계) }, - text: `${g.비목} ${tl("Subtotal")} ${formatNumber(g.소계)}${g.끝수 ? ` (${g.끝수})` : ""}`, + text: `${g.비목} ${tl("Subtotal")} ${formatMoney(g.소계)}${g.끝수 ? ` (${g.끝수})` : ""}`, }), ]); blocks.unshift(heads); @@ -90,7 +93,7 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void { el("p", { className: "m01lab__total m01lab-fx__wide", attrs: { "data-text-sum": String(text.계) }, - text: `${tl("Total")} ${formatNumber(text.계)}`, + text: `${tl("Total")} ${formatMoney(text.계)}`, }), ); host.replaceChildren(el("div", { className: "m01lab-fx", children: blocks })); diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts index 69288127..1d197668 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts @@ -9,17 +9,54 @@ * ========================================================================== */ import { createButton, el } from "@ui/ui_template_elements"; -import type { ElementBrief, NamedFormula } from "./M01_MasterData_UI_Logic_Api"; +import { + searchElements, + type ElementBrief, + type NamedFormula, +} from "./M01_MasterData_UI_Logic_Api"; import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; -import { explain, loadTable, matchRow, type TableRow } from "./M01_MasterData_UI_Logic_Note"; +import { formatMoney, formatPrice } from "./M01_MasterData_UI_Logic_Money"; +import { explain, loadTable, type TableRow } from "./M01_MasterData_UI_Logic_Note"; import { jumpToMaster, type MasterJump } from "./M01_MasterData_UI_Side"; +import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill"; import { tl } from "./M01_MasterData_UI_LogicLab_Text"; -/** 원 단위 소수점 0 — 돈 자리만(수량·비율은 `formatNumber` 그대로) */ -const formatMoney = (value: unknown): string => - typeof value === "number" - ? value.toLocaleString("ko-KR", { maximumFractionDigits: 0 }) - : formatNumber(value); +/** 로직 알약 → 그 로직 열기 — 로직 화면이 자기 열기를 등록 · 열려 있는 모달은 다 닫음 */ +let logicOpener: (key: string) => void = () => {}; +export const setLogicOpener = (fn: (key: string) => void): void => { + logicOpener = fn; +}; +export function openLogic(key: string): void { + document.querySelectorAll(".m01-logic__backdrop").forEach((b) => b.remove()); + logicOpener(key); +} + +/** 재료 고르기 조건(호표 줄 `요소` 가 글 대신 객체일 때) — 서버 `master_material.PICK_KEYS` 와 같은 칸 */ +export interface PickCond { + 구분?: string; + 상세구분?: string; + 이름?: string; + 규격?: string; + 대표?: string; + [extra: string]: unknown; +} + +/** 서버가 `prices` 열쇠로 쓰는 조건 글 — 「구분|상세구분|이름|규격|대표|지역|계약종별」 */ +export const condText = (cond: PickCond): string => + ["구분", "상세구분", "이름", "규격", "대표", "지역", "계약종별"] + .map((k) => String(cond[k] ?? "")) + .join("|") + (cond["검색어"] ? `|${cond["검색어"]}` : ""); + +/** 재료 모달 검색 칸 글 — 검색어 첫 낱말의 괄호 앞(「합판(내수) 12t」 → 「합판」) */ +export const headWord = (text: unknown): string => + String(text ?? "") + .trim() + .split(/\s+/)[0] + .replace(/[((].*$/, ""); + +/** 고르기 줄 후보 찾기 글 — 검색어 → 이름 → 상세구분 → 구분 차례 첫 낱말 */ +export const seedOf = (cond: PickCond): string => + [cond["검색어"], cond.이름, cond.상세구분, cond.구분].map(headWord).find(Boolean) ?? ""; export interface ModalTarget { title: string; @@ -27,40 +64,68 @@ export interface ModalTarget { expr: string; /** 호표 줄의 단가 요소 키 — 덧줄은 없음 */ element?: string; + /** 재료 고르기 줄 — `element` 대신 조건 객체 · 잡힌 품목 단가 + 후보 목록을 보임 */ + cond?: PickCond; + /** 호표 줄 단위 — 후보를 같은 단위 줄로만 거름 */ + unit?: string; prices: Record; middles: NamedFormula[]; /** 시험 계산 칸에 넣은 값 — 걸린 줄을 맞히는 데 씀 */ values: Record; + /** 서버 `요소조각` — 단가 자리(요소 · 로직 부르기)를 조각으로 · 없으면 로직 키만 알약으로 */ + unitFrag?: Fragment[]; + /** 서버가 입력으로 푼 단가 요소 키 · 글 — 자리 표시(`{…}`) 든 줄이 씀 */ + unitRef?: string; + unitText?: string; + /** 서버 수량 조각 — 한 줄 풀이를 조각으로(알약 포함) · 없으면 식 풀이 글 */ + qtyFrag?: Fragment[]; } -/** 수량 칸 표 알약 하나가 여는 자료 — 조각 계약의 「표 찾기」 자리 */ +/** 수량 칸 표 알약 하나가 여는 자료 — 서버 조각 `참조`(키 · 행 · 열)가 곧 찾은 자리 */ export interface TableTarget { title: string; table: string; - conds: [string, string][]; - values: Record; + /** 찾은 줄 — 찾기 조건(예 {구분: 합판}) · 줄의 키·원문번호 글 · 줄 차례(0부터) */ + row?: Record | string | number | null; + /** 찾은 값 칸 이름 */ + col?: string | null; } -function tableView( - table: TableRow, - conds: [string, string][], - values: Record, -): HTMLElement { - // 표 줄의 빈 칸은 「무엇이든」 — 서버 찾기()와 같게 그 칸은 조건에서 뺌(줄마다 따로 봄) +const sameCond = (have: unknown, want: unknown, range: boolean): boolean => { + if (range && Array.isArray(have)) { + const n = Number(want); + const [low, high] = have as (number | null)[]; + return (low == null || n >= low) && (high == null || n <= high); + } + return String(have) === String(want); +}; + +function tableView(table: TableRow, target: TableTarget): HTMLElement { + const lines = table.줄 ?? []; + const at = target.row; + const kinds = table.조건 ?? {}; const hit = - (table.줄 ?? []).find((line) => - matchRow( - { ...table, 줄: [line] }, - conds.filter(([col]) => line[col] !== undefined && line[col] !== null && line[col] !== ""), - { values, middle: {} }, - ), - ) ?? null; + at === undefined || at === null + ? null + : typeof at === "number" + ? (lines[at] ?? null) + : typeof at === "string" + ? (lines.find((line) => line["키"] === at || line["원문번호"] === at) ?? null) + : (lines.find((line) => + Object.entries(at).every(([c, want]) => sameCond(line[c], want, kinds[c] === "범위")), + ) ?? null); const cols = [...Object.keys(table.조건 ?? {}), ...Object.keys(table.값칸 ?? {})]; - const rows = (table.줄 ?? []).map((line) => + const rows = lines.map((line) => el("tr", { className: hit === line ? "m01lab__hit" : "", attrs: hit === line ? { "data-hit": "1" } : {}, - children: cols.map((c) => el("td", { text: formatNumber(line[c]) })), + children: cols.map((c) => + el("td", { + // 찾은 줄을 알면 그 줄의 칸만 · 줄을 못 받았으면(표 줄에 키가 없는 표) 값 칸 세로줄만 색 + className: c === target.col && (hit === line || !hit) ? "m01lab__hit-cell" : "", + text: formatNumber(line[c]), + }), + ), }), ); const use = table.용도 @@ -77,7 +142,7 @@ function tableView( }), el("p", { className: "m01-logic__muted", - text: hit ? tl("Modal_Hit") : tl("Modal_NoHit"), + text: hit ? tl("Modal_Hit") : target.col ? tl("Modal_ColOnly") : tl("Modal_NoHit"), }), goButton({ kind: "table", key: table.키 }), el("div", { @@ -111,6 +176,7 @@ function goButton(target: MasterJump): HTMLElement { function priceView(ref: string, brief: ElementBrief): HTMLElement { const cols = Object.entries(brief.값칸 ?? {}); + const slots = Object.entries(brief.값들 ?? {}).filter(([, v]) => v !== null && v !== undefined); return el("div", { className: "m01lab__data", attrs: { "data-price": ref }, @@ -123,11 +189,15 @@ function priceView(ref: string, brief: ElementBrief): HTMLElement { ? [el("dt", { text: tl("Modal_Spec") }), el("dd", { text: brief.규격 })] : []), el("dt", { text: tl("Modal_Value") }), - el("dd", { className: "m01-logic__money", text: formatMoney(brief.값) }), + el("dd", { className: "m01-logic__money", text: formatPrice(brief.값) }), ...(brief.file ? [el("dt", { text: tl("Modal_Source") }), el("dd", { text: brief.file })] : []), ...cols.flatMap(([k, v]) => [el("dt", { text: k }), el("dd", { text: String(v) })]), + ...slots.flatMap(([k, v]) => [ + el("dt", { text: k }), + el("dd", { className: "m01-logic__money", text: formatMoney(v) }), + ]), ], }), goButton({ kind: "element", group: groupOf(ref), ref: brief.ref ?? ref, file: brief.file }), @@ -162,46 +232,207 @@ function openDialog(title: string, mount: (body: HTMLElement) => void): void { dialog.focus(); } -/** 줄을 눌렀을 때 — 그 줄의 단가(재료·인력·기계 또는 다른 로직)를 바로 보임(알약 없이) */ -export function openMaterialModal(target: ModalTarget): void { - openDialog(target.title, (body) => { - const base = target.element?.split(".")[0] ?? ""; - const price = target.element - ? (target.prices[target.element] ?? target.prices[base]) - : undefined; - const logicRef = target.element?.startsWith("로직(") ? target.element : ""; - const rateParts: HTMLElement[] = []; - if (price && target.element) { - rateParts.push(priceView(target.element, price)); - } else if (logicRef) { - rateParts.push(el("p", { text: `${tl("Modal_Logic")} · ${logicRef}` })); - } else if (target.element) { - rateParts.push( - el("p", { - className: "m01-logic__muted", - text: `${target.element} — ${tl(target.element.includes("{") ? "Pill_NoPick" : "Pill_NoValue")}`, +const muted = (text: string): HTMLElement => el("p", { className: "m01-logic__muted", text }); + +/** 재료 고르기 줄 후보 목록 — 잡힌 품목(`pickedRef`)을 노란 줄로 · 후보가 100건을 넘으면 앞 100건만 옴 */ +function candidateView( + cond: PickCond, + pickedRef: string, + onPicked: (item: ElementBrief | null) => void, +): HTMLElement { + const box = el("div", { className: "m01lab__data", attrs: { "data-candidates": "1" } }); + box.append(muted("…")); + void searchElements("재료", seedOf(cond)) + .then((got) => { + onPicked(got.items.find((it) => it.ref === pickedRef) ?? null); + const rows = got.items.map((it) => + el("tr", { + className: it.ref === pickedRef ? "m01lab__hit" : "", + attrs: it.ref === pickedRef ? { "data-hit": "1" } : {}, + children: [ + el("td", { text: it.이름 ?? it.ref }), + el("td", { text: it.규격 ?? "" }), + el("td", { text: String(it.단위 ?? "") }), + el("td", { className: "m01-logic__money", text: formatPrice(it.값) }), + ], }), ); + const more = got.total > got.items.length ? ` (${got.items.length})` : ""; + box.replaceChildren( + el("strong", { text: `${tl("Modal_Candidates")} ${got.total}${more}` }), + el("div", { + className: "m01-logic__scroll", + children: [ + el("table", { + className: "m01-logic__grid m01lab__table", + children: [ + el("thead", { + children: [ + el("tr", { + children: [ + tl("Col_Name"), + tl("Modal_Spec"), + tl("Col_Unit"), + tl("Modal_Value"), + ].map((h) => el("th", { text: h })), + }), + ], + }), + el("tbody", { children: rows }), + ], + }), + ], + }), + ); + }) + .catch(() => box.replaceChildren(muted(tl("Pill_NoPick")))); + return box; +} + +/** 고르기 줄에서 시험 계산이 잡은 품목 키 — 대표가 `{입력이름}` 이면 넣은 값 · 글자 키면 그대로 · 없으면 서버 미리보기 줄 */ +function pickedOf(cond: PickCond, values: Record, fallback: string): string { + const rep = String(cond["대표"] ?? ""); + const named = /^\{(.+)\}$/.exec(rep); + return (named ? (values[named[1]] ?? "") : rep) || fallback; +} + +/** 줄을 눌렀을 때 — 그 줄의 단가(재료·인력·기계 또는 다른 로직)를 바로 보임(알약 없이) · + * 재료 고르기 줄(`cond`)은 잡힌 품목 단가 + 후보 목록 */ +export function openMaterialModal(target: ModalTarget): void { + openDialog(target.title, (body) => { + const cond = target.cond; + const element = target.element ?? ""; + const base = element.split(".")[0]; + // 고르기 줄 = 서버가 조건 글을 열쇠로 준 줄(대표가 안 정해졌으면 없음) + const priced = cond ? target.prices[condText(cond)] : undefined; + const known = cond + ? target.unitRef && priced?.ref !== target.unitRef + ? undefined // 서버가 잡은 품목이 다르면 그 품목으로 + : priced + : element + ? (target.prices[element] ?? target.prices[base]) + : undefined; + const priceRef = cond + ? target.unitRef || pickedOf(cond, target.values, known?.ref ?? "") + : element; + const logicKey = /^로직\((\w+)/.exec(element)?.[1] ?? ""; + const sum = el("p", { className: "m01lab__sum" }); + const setSum = (price: ElementBrief | null | undefined): void => { + const unit = price ? `${tl("Modal_UnitPrice")} ${formatPrice(price.값)} × ` : ""; + if (target.qtyFrag) { + sum.replaceChildren(`${target.title} = ${unit}`, fragmentRow(target.qtyFrag)); + } else sum.textContent = `${target.title} = ${unit}${explain(target.expr)}`; + }; + setSum(known); + const rate = el("div", { className: "m01lab__modal-body" }); + if (known && priceRef) { + rate.append(priceView(priceRef, known)); + } else if (logicKey) { + rate.append( + el("p", { + children: [ + `${tl("Modal_Logic")} · `, + fragmentRow( + target.unitFrag ?? [ + { pill: { label: logicKey, onClick: () => openLogic(logicKey) } }, + ], + ), + ], + }), + ); + } else if (cond && priceRef) { + rate.append(muted("…")); // 후보 목록이 오면 그 줄로 채움 — 100건 밖이면 키로 따로 찾아 옴 + } else if (cond) { + rate.append(muted(tl("Pill_NoPick"))); + } else if (element.includes("{")) { + // 자리 표시 든 요소 — 날글 대신 서버가 푼 키로 단가 · 못 풀면 입력 이름만 + const ref = target.unitRef; + if (!ref) { + const names = [...element.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).join(" · "); + rate.append(muted(`${target.unitText || names} — ${tl("Pill_Unresolved")}`)); + } else { + const show = (brief: ElementBrief | null): void => { + rate.replaceChildren( + brief + ? priceView(ref, brief) + : muted(`${target.unitText ?? ref} — ${tl("Pill_NoValue")}`), + ); + setSum(brief); + }; + rate.append(muted("…")); + const cached = target.prices[ref]; + if (cached) show(cached); + else + void searchElements(groupOf(ref), ref) + .then((got) => show(got.items.find((it) => it.ref === ref) ?? null)) + .catch(() => show(null)); + } + } else if (element) { + rate.append(muted(`${element} — ${tl("Pill_NoValue")}`)); } - const unit = price ? `${tl("Modal_UnitPrice")} ${formatMoney(price.값)} × ` : ""; body.replaceChildren( - ...rateParts, - el("p", { className: "m01-logic__muted", text: tl("Modal_Sum") }), - el("p", { - className: "m01lab__sum", - text: `${target.title} = ${unit}${explain(target.expr)}`, - }), + rate, + ...(cond + ? [ + candidateView(cond, priceRef, (item) => { + if (known || !priceRef) return; + const show = (brief: ElementBrief | null): void => { + rate.replaceChildren( + brief ? priceView(priceRef, brief) : muted(`${priceRef} — ${tl("Pill_NoValue")}`), + ); + setSum(brief); + }; + if (item) return show(item); + void searchElements("재료", priceRef) + .then((got) => show(got.items.find((it) => it.ref === priceRef) ?? null)) + .catch(() => show(null)); + }), + ] + : []), + muted(tl("Modal_Sum")), + sum, el("details", { className: "m01lab__raw", children: [ el("summary", { text: tl("Modal_Qty") }), - el("pre", { className: "m01lab__expr", text: target.expr }), + el("pre", { + className: "m01lab__expr", + text: logicKey + ? `${element} +${target.expr}` + : target.expr, + }), ], }), ); }); } +/** 수량 칸 요소 알약을 눌렀을 때 — 그 요소의 단가 자료(이미 받은 `prices` 에 없으면 키로 찾아 옴) */ +export function openRefModal(target: { + title: string; + ref: string; + prices: Record; +}): void { + openDialog(target.title, (body) => { + const show = (brief: ElementBrief | null): void => + body.replaceChildren( + brief ? priceView(target.ref, brief) : muted(`${target.ref} — ${tl("Pill_NoValue")}`), + ); + const known = target.prices[target.ref]; + if (known) return show(known); + body.replaceChildren(muted("…")); + void searchElements(groupOf(target.ref), target.ref) + .then((got) => show(got.items.find((it) => it.ref === target.ref) ?? null)) + .catch(() => show(null)); + }); +} + +/** 알약이 가리킬 자료를 못 찾을 때(참조 없음) · 다른 로직을 부르는 알약 — 한 마디만 */ +export function openNoteModal(title: string, text: string): void { + openDialog(title, (body) => body.replaceChildren(el("p", { text }))); +} + /** 수량 칸 표 알약을 눌렀을 때 — 그 표만 보임(걸린 줄 강조) */ export function openTableModal(target: TableTarget): void { openDialog(target.title, (body) => { @@ -209,7 +440,7 @@ export function openTableModal(target: TableTarget): void { void loadTable(target.table).then((table) => { body.replaceChildren( table - ? tableView(table, target.conds, target.values) + ? tableView(table, target) : el("p", { className: "m01-logic__bad", text: `${target.table} ?` }), ); }); diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.css b/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.css index 91d90601..657f6bbc 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.css +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.css @@ -33,3 +33,14 @@ .m01lab__pill-stage { min-width: 0; } + +/* 계산 안 된 만약() 가지 조각 — 흐리게 */ +.m01lab__qty-dim { + opacity: 0.45; +} + +/* 표 모달 — 찾은 칸(참조.열) 한 번 더 진하게 */ +td.m01lab__hit-cell, +.m01lab__hit td.m01lab__hit-cell { + background: color-mix(in srgb, orange 65%, transparent); +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.ts index 4e28b0ba..31904049 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Pill.ts @@ -28,17 +28,20 @@ export function pillButton(p: Pill): HTMLButtonElement { return b; } -/** 수량 칸 조각 — 알약 아니면 그냥 글자(연산기호·숫자·중간값 이름) */ -export type Fragment = { pill: Pill } | { text: string }; +/** 수량 칸 조각 — 알약 아니면 그냥 글자(연산기호·숫자·중간값 이름) · `dim` = 계산 안 된 갈래(흐리게) */ +export type Fragment = { pill: Pill; dim?: boolean } | { text: string; dim?: boolean }; /** 조각을 이어 그림 — 칸 폭을 넘으면 자동으로 줄바꿈(`flex-wrap`) */ export function fragmentRow(fragments: Fragment[]): HTMLElement { return el("span", { className: "m01lab__qty-frags", - children: fragments.map((f) => - "pill" in f - ? pillButton(f.pill) - : el("span", { className: "m01lab__qty-text", text: f.text }), - ), + children: fragments.map((f) => { + const one = + "pill" in f + ? pillButton(f.pill) + : el("span", { className: "m01lab__qty-text", text: f.text }); + if (f.dim) one.classList.add("m01lab__qty-dim"); + return one; + }), }); } diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Style.css b/M01_MasterData/M01_MasterData_UI_LogicLab_Style.css index 4e2d49a3..7be58350 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Style.css +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Style.css @@ -175,3 +175,25 @@ .m01lab__edit { margin-top: var(--spacing-12); } + +/* 시험 계산 — 재료 · 기계 고르기 표 모달 */ +.m01-logic__pick--table { + width: min(1040px, 94vw); +} + +.m01lab__pickrow { + cursor: pointer; +} + +.m01lab__pickrow:hover, +.m01lab__pickrow.is-picked { + background: var(--color-paper); +} + +.m01lab__pickrow.is-picked { + font-weight: 600; +} + +.m01lab__warn { + color: var(--color-warning, #b45309); +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts index eff054cb..3e6367ee 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts @@ -30,6 +30,7 @@ const TEXT = { G_other: ["그 밖 (인력·자재·경비 어디에도 안 맞는 줄)", "Other (fits none of the three)"], Tag_Table: ["표", "Table"], Subtotal: ["소계", "Subtotal"], + Share: ["몫", "Share"], Total: ["계", "Total"], Extra: ["덧줄", "Extra"], Col_Name: ["이름", "Name"], @@ -58,15 +59,24 @@ const TEXT = { "고르기 조건에 맞는 마스터 줄이 없음 — 단가를 못 찾음", "No master row matches the pick conditions", ], + Pill_Unresolved: ["입력이 비어 풀리지 않음", "Not resolved — input empty"], Pill_NoValue: ["관리자 값 미확보 — 마스터에 단가가 없음", "No price in master yet"], Modal_Qty: ["수량 식", "Quantity formula"], Modal_Price: ["단가 자료", "Price source"], Modal_Value: ["값", "Value"], Modal_Source: ["출처", "Source"], Modal_Spec: ["규격", "Spec"], + Modal_Candidates: [ + "후보 목록 (노란 줄 = 시험 계산이 쓰는 품목)", + "Candidates (yellow = used in the trial)", + ], Modal_Logic: ["다른 로직을 부름", "Calls another logic"], Modal_NoData: ["표나 단가 자료가 없는 줄 — 수량 식만 있음", "No table or price data"], Modal_Hit: ["노란 줄 = 지금 넣은 값으로 걸린 줄", "Highlighted row matches the trial values"], + Modal_ColOnly: [ + "진한 칸 = 찾은 값 칸 — 서버가 줄 자리는 안 줌", + "Dark cells = the value column found; the server gave no row", + ], Modal_NoHit: [ "지금 값으로는 걸린 줄이 없음 — 값을 바꿔 보거나, 이 표를 안 쓰는 갈래일 수 있음", "No row matches the current values — change them, or this table is on an unused branch", diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts index fc50d374..c61432a3 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts @@ -66,6 +66,8 @@ export interface ElementBrief { 단위?: unknown; 값?: unknown; 값칸?: Record | null; + /** 재료 후보 줄 — 값 열 다섯(지역·계약종별별 값) */ + 값들?: Record; } export interface LogicOne { @@ -86,6 +88,10 @@ export interface CalcLine { 비목: Record; /** 값 없는 요소를 다른 값으로 셈 — 「준용: <직종>」 · 「산정: …」 */ 출처?: string; + /** 자재 단위가 줄 단위와 성격이 달라 환산 못 하고 셈 — 「줄 m · 자재 Ton」 */ + 단위경고?: string; + /** 재료 줄이 잡은 품목 — 키 · 이름 · 규격 · 단위 · 값 칸 */ + 품목?: { 키: string; 이름?: string; 규격?: string; 단위?: string; 값칸?: string }; } export type CalcAnswer = | { @@ -97,11 +103,28 @@ export type CalcAnswer = } | { ok: false; reason: string }; +/** `/text` 줄의 조각 — 알약 글 = 조각 글 · `깊이` 있으면 계산 안 된 만약() 가지(흐리게) */ +export interface TextPiece { + kind: "요소" | "표" | "로직" | "값" | "기호" | "글"; + 글: string; + 참조?: { + 테이블?: string; + 키: string; + 행?: string | number | null; + 줄?: number; + 열?: string | null; + }; + 깊이?: number; +} export interface TextLine { 이름: string; 글: string; /** 같은 줄을 변수 이름으로 적은 식 — 왼쪽 칸 */ 이름글?: string; + /** 수량 식을 쪼갠 조각 — 알약(요소·표·로직) · 글자(값·기호·글) */ + 조각?: TextPiece[]; + /** 단가 자리(요소 · 로직 부르기)를 쪼갠 조각 — 줄 모달 윗줄 */ + 요소조각?: TextPiece[]; /** 줄 번호(답 전체에서 하나씩) — 두 칸 줄 맞춤 · 밝히기 */ 짝?: number; 금액: number; @@ -192,14 +215,24 @@ export const fetchMaterials = (cond: { detail?: string; spec?: string; region?: string; + /** 호표 줄 단위 — 주면 그 단위 줄만 */ + unit?: string; }): Promise<{ total: number; 기본: string | null; items: ElementBrief[] }> => request(`/materials?${query({ ...cond, limit: "100" })}`); +/** 자동값 길(`GET /logic/auto`) — 입력 이름 → 견본값 · 서버에 아직 없거나 값이 없으면 빈 채로(화면은 그냥 안 채움) */ +export const fetchAuto = (key: string): Promise> => + request<{ 값?: Record }>(`/logic/auto?${query({ key })}`) + .then((d) => d.값 ?? {}) + .catch(() => ({})); + export const runCalc = (body: { key: string; inputs: Record; row?: LogicRow; file?: string; + /** 시험 계산에서 고른 재료 — 호표 줄 차례 → 자재품목 키 · 저장 안 함 */ + 고름?: Record; }): Promise => request("/calc", body); export const runText = (body: { @@ -207,6 +240,8 @@ export const runText = (body: { inputs: Record; row?: LogicRow; file?: string; + /** 시험 계산에서 고른 재료 — 호표 줄 차례 → 자재품목 키 · 저장 안 함 */ + 고름?: Record; }): Promise => request("/text", body); export const saveFiles = ( diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts index 821c76bd..f27f458d 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts @@ -6,16 +6,26 @@ import { createButton, createSelectField, el, showToast } from "@ui/ui_template_elements"; import { + fetchAuto, runCalc, runText, searchElements, type CalcAnswer, type CalcLine, + type ElementBrief, type TextAnswer, type LogicRow, } from "./M01_MasterData_UI_Logic_Api"; +import { + isMaterialKeyInput, + machineControl, + materialRows, + pickBody, + showItems, +} from "./M01_MasterData_UI_Logic_CalcPick"; import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; -import { tx } from "./M01_MasterData_UI_Logic_Text"; +import { formatMoney } from "./M01_MasterData_UI_Logic_Money"; +import { safeReason, tx } from "./M01_MasterData_UI_Logic_Text"; /** 고르기 값이 마스터 키(자재·기계·직종)면 이름으로 바꿔 보임 — 값 칸은 키 그대로 보냄 */ const KEY_RE = /^[A-Z]{2}\d{6}$/; @@ -45,6 +55,8 @@ export interface CalcContext { /** 저장 전 새 초안이면 비움 — 서버가 임시 자리에 얹어 셈 */ file?: string; row: LogicRow; + /** 잡힌 품목을 보이는 데 씀 — 재료 줄의 단가 요소 · 조건 글 → 요소 */ + prices?: Record; dirty: () => boolean; /** 로직마다 넣은 값 — 다시 그려도 남음 */ values: Record; @@ -55,6 +67,9 @@ export interface CalcContext { const SUMS = ["노무비", "재료비", "경비", "계"]; +/** 로직마다 자동값을 한 번만 받음(다시 그려도 안 부름) */ +const autoAsked = new WeakSet>(); + /** 로직을 열 때마다 새로 부르는 미리보기 — 늦게 온 응답이 새 로직을 덮지 않게 순번으로 거름 */ let previewSeq = 0; @@ -63,46 +78,58 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void { // 지금 쓰는 LogicLab 화면은 그 칸에 안 걸려 폭이 이미 넓음(실측 730px) · 따로 안 풂 const optionText = (o: string | number): string => (typeof o === "string" && nameCache.get(o)) || String(o); - const fields = (ctx.row.입력 ?? []).map((spec) => { - const name = spec.이름; - let control: HTMLElement; - const picks = spec.고르기; - if (picks?.length) { - const pick = createSelectField({ - options: ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })), - value: ctx.values[name] ?? "", - compact: true, - onChange: (v) => (ctx.values[name] = v), - }); - control = pick.root; - const keys = picks.filter((o): o is string => typeof o === "string" && KEY_RE.test(o)); - if (keys.length) { - void Promise.all(keys.map(keyName)).then(() => { - pick.setOptions( - ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })), - ctx.values[name] ?? "", - ); + const again = (): void => host.querySelector("[data-calc-run]")?.click(); + const fields = (ctx.row.입력 ?? []) + .filter((spec) => !isMaterialKeyInput(spec)) // 재료 키 목록은 표 모달로 바뀌어 걷음 + .map((spec) => { + const name = spec.이름; + let control: HTMLElement; + const picks = spec.고르기; + const machine = machineControl(spec, ctx.values, again); + if (machine) { + control = machine; + } else if (picks?.length) { + const pick = createSelectField({ + options: ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })), + value: ctx.values[name] ?? "", + compact: true, + onChange: (v) => { + ctx.values[name] = v; + }, }); + control = pick.root; + const keys = picks.filter((o): o is string => typeof o === "string" && KEY_RE.test(o)); + if (keys.length) { + void Promise.all(keys.map(keyName)).then(() => { + pick.setOptions( + ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })), + ctx.values[name] ?? "", + ); + }); + } + } else { + const box = el("input", { + className: "m01-logic__input", + attrs: { type: "text", inputmode: "decimal" }, + }); + if (spec.범위) box.placeholder = `${spec.범위[0]} ∼ ${spec.범위[1]}`; + box.value = ctx.values[name] ?? ""; + box.addEventListener("input", () => { + ctx.values[name] = box.value; + }); + control = box; } - } else { - const box = el("input", { - className: "m01-logic__input", - attrs: { type: "text", inputmode: "decimal" }, + const label = spec.단위 ? `${name} (${spec.단위})` : name; + // 긴 이름 줄만 한 칸 다 씀 — 고르기 줄은 짧으면 그대로 2열에 맞춰 나란히 + const wide = label.length > 10; + return el("label", { + className: `m01-logic__field${wide ? " m01-logic__field--wide" : ""}`, + children: [el("span", { text: label }), control], }); - if (spec.범위) box.placeholder = `${spec.범위[0]} ∼ ${spec.범위[1]}`; - box.value = ctx.values[name] ?? ""; - box.addEventListener("input", () => (ctx.values[name] = box.value)); - control = box; - } - const label = spec.단위 ? `${name} (${spec.단위})` : name; - // 긴 이름 줄만 한 칸 다 씀 — 고르기 줄은 짧으면 그대로 2열에 맞춰 나란히 - const wide = label.length > 10; - return el("label", { - className: `m01-logic__field${wide ? " m01-logic__field--wide" : ""}`, - children: [el("span", { text: label }), control], }); - }); + const materials = materialRows(ctx.row, ctx.prices ?? {}, ctx.values, again); const out = el("div", { className: "m01-logic__calc-out" }); + const mySeqAtOpen = ctx.onText ? previewSeq + 1 : previewSeq; // 이 그리기가 받을 순번 let manualRun = false; // [계산] 이 이미 답했으면 뒤늦게 오는 미리보기로 안 덮음 if (ctx.onText) { const mySeq = ++previewSeq; @@ -118,6 +145,23 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void { ctx.onText?.(answer); }); } + if (!fields.length && !autoAsked.has(ctx.values)) { + autoAsked.add(ctx.values); // 입력이 없으면 채울 것이 없음 — 열 때 한 번 바로 계산 + queueMicrotask(() => { + if (previewSeq === mySeqAtOpen) again(); + }); + } else if (ctx.savedKey !== null && !autoAsked.has(ctx.values)) { + autoAsked.add(ctx.values); + void fetchAuto(ctx.savedKey).then((auto) => { + const names = (ctx.row.입력 ?? []) + .map((spec) => spec.이름) + .filter((n) => auto[n] !== undefined && auto[n] !== null && !(ctx.values[n] ?? "").trim()); + if (!names.length || previewSeq !== mySeqAtOpen) return; // 값이 없거나 다른 로직이 이미 열림 + for (const n of names) ctx.values[n] = String(auto[n]); + buildCalc(host, ctx); // 채운 값을 새로 그리고 바로 계산(결과 · 텍스트 수식까지) + again(); + }); + } const run = async (): Promise => { manualRun = true; const inputs: Record = {}; @@ -134,34 +178,42 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void { const body = { key: ctx.savedKey ?? ctx.row.키, inputs, + ...pickBody(ctx.values), ...(draft ? { row: ctx.row, file: ctx.file } : {}), }; const answer = await runCalc(body); if (ctx.onText) ctx.onText(await runText(body)); ctx.onLines(answer.ok ? (answer.lines ?? null) : null); - out.replaceChildren(...answerView(answer, draft)); + if (answer.ok && answer.lines) showItems(host, ctx.values, answer.lines); + out.replaceChildren(...answerView(answer, draft, (ctx.row.결과단위 ?? "").startsWith("원"))); } catch (error) { showToast(error instanceof Error ? error.message : tx("Load_Failed"), "error"); } }; + const runButton = createButton({ label: tx("Calc_Run"), onClick: () => void run() }); + runButton.setAttribute("data-calc-run", ""); host.replaceChildren( el("h3", { text: tx("Calc_Title") }), - ...(fields.length - ? [el("div", { className: "m01-logic__head", children: fields })] // 요소 표가 쓰는 auto-fill 그리드 재사용 — 2열 + ...(fields.length || materials.length + ? [el("div", { className: "m01-logic__head", children: [...fields, ...materials] })] // 요소 표가 쓰는 auto-fill 그리드 재사용 — 2열 : [el("p", { className: "m01-logic__muted", text: tx("Calc_NoInputs") })]), - createButton({ label: tx("Calc_Run"), onClick: () => void run() }), + runButton, out, ); } -function answerView(answer: CalcAnswer, draft: boolean): HTMLElement[] { +/** `money` = 결과가 돈(원) — 그때만 결과 값도 정수 · 수량·비율 결과는 소수 그대로 */ +function answerView(answer: CalcAnswer, draft: boolean, money: boolean): HTMLElement[] { const note = draft ? [el("p", { className: "m01-logic__muted", text: tx("Calc_Draft") })] : []; if (!answer.ok) { return [ ...note, el("div", { className: "m01-logic__reasons", - children: [el("strong", { text: tx("Calc_Stopped") }), el("div", { text: answer.reason })], + children: [ + el("strong", { text: tx("Calc_Stopped") }), + el("div", { text: safeReason(answer.reason) }), + ], }), ]; } @@ -170,9 +222,25 @@ function answerView(answer: CalcAnswer, draft: boolean): HTMLElement[] { const rows = answer.lines.map((line) => el("tr", { children: [ - el("td", { text: line.출처 ? `${line.이름} (${line.출처})` : line.이름 }), + el("td", { + children: [ + el("span", { + text: line.출처 + ? `${line.이름} (${line.출처.replace(/[A-Z]{2}\d{6}\./g, "")})` + : line.이름, + }), + ...(line.단위경고 + ? [ + el("span", { + className: "m01-logic__tag m01lab__warn", + text: `${tx("Calc_UnitWarn")} · ${line.단위경고}`, + }), + ] + : []), + ], + }), el("td", { text: `${formatNumber(line.수량)} ${line.단위}` }), - el("td", { className: "m01-logic__money", text: formatNumber(line.금액) }), + el("td", { className: "m01-logic__money", text: formatMoney(line.금액) }), ], }), ); @@ -200,7 +268,7 @@ function answerView(answer: CalcAnswer, draft: boolean): HTMLElement[] { className: "m01-logic__sums", children: SUMS.flatMap((k) => [ el("dt", { text: k === "계" ? tx("Calc_Sum") : k }), - el("dd", { className: "m01-logic__money", text: formatNumber(answer.sums?.[k] ?? 0) }), + el("dd", { className: "m01-logic__money", text: formatMoney(answer.sums?.[k] ?? 0) }), ]), }), ); @@ -211,12 +279,13 @@ function answerView(answer: CalcAnswer, draft: boolean): HTMLElement[] { className: "m01-logic__sums", children: [ el("dt", { text: tx("Calc_Result") }), - el("dd", { text: formatNumber(answer.result) }), + el("dd", { text: money ? formatMoney(answer.result) : formatNumber(answer.result) }), ], }), ); } - const middle = Object.entries(answer.middle ?? {}); + // 줄 묶음(객체)은 위 상세와 겹쳐 숨김 · 돈 로직의 큰 수는 정수 + const middle = Object.entries(answer.middle ?? {}).filter(([, v]) => typeof v !== "object"); if (middle.length) { parts.push( el("h4", { text: tx("Middle_Title") }), @@ -224,7 +293,12 @@ function answerView(answer: CalcAnswer, draft: boolean): HTMLElement[] { className: "m01-logic__sums", children: middle.flatMap(([k, v]) => [ el("dt", { text: k }), - el("dd", { text: formatNumber(v) }), + el("dd", { + text: + money && typeof v === "number" && Math.abs(v) >= 1000 + ? formatMoney(v) + : formatNumber(v), + }), ]), }), ); diff --git a/M01_MasterData/M01_MasterData_UI_Logic_CalcPick.ts b/M01_MasterData/M01_MasterData_UI_Logic_CalcPick.ts new file mode 100644 index 00000000..ebf5111e --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_Logic_CalcPick.ts @@ -0,0 +1,171 @@ +/* ============================================================================= + * M01_MasterData_UI_Logic_CalcPick.ts + * 시험 계산의 재료 · 기계 고르기 — 재료 줄마다 「잡힌 품목 + 품목 고르기」 · 기계 입력은 단추 하나 + * 누르면 마스터 표 모달(`PickTable`) · 고른 재료는 `고름`(호표 줄 차례 → 키)에 실어 다시 계산 · 저장 안 함 + * ========================================================================== */ + +import { createButton, el } from "@ui/ui_template_elements"; +import { fetchRows, type Row } from "./M01_MasterData_Api_Fetch"; +import type { CalcLine, ElementBrief, LogicInput, LogicRow } from "./M01_MasterData_UI_Logic_Api"; +import { openPickTable } from "./M01_MasterData_UI_Logic_PickTable"; +import { tx } from "./M01_MasterData_UI_Logic_Text"; +import { condText, headWord, type PickCond } from "./M01_MasterData_UI_LogicLab_Modal"; + +interface Chosen { + ref: string; + label: string; +} + +/** 로직마다 넣은 값(`values`)에 딸린 고른 재료 — 다시 그려도 남고 서버에는 계산 요청에만 실림 */ +const chosen = new WeakMap, Map>(); +const picksOf = (values: Record): Map => { + let mine = chosen.get(values); + if (!mine) chosen.set(values, (mine = new Map())); + return mine; +}; + +/** 계산 요청 몸의 `고름` — 고른 것이 없으면 안 실음 */ +export function pickBody(values: Record): { 고름?: Record } { + const mine = picksOf(values); + return mine.size + ? { 고름: Object.fromEntries([...mine].map(([i, c]) => [String(i), c.ref])) } + : {}; +} + +const nameOf = (row: Row): string => `${row["이름"] ?? ""} ${row["규격"] ?? ""}`.trim(); + +const MATERIAL_KEYS = /^MT\d{6}$/; +const MACHINE_CODE = /^\d{4}-\d{4}$/; +const MACHINE_KEY = /^EQ\d{6}$/; + +/** 재료 키 목록 입력 — 표 모달로 바뀌어 걷음 */ +export const isMaterialKeyInput = (spec: LogicInput): boolean => + !!spec.고르기?.length && spec.고르기.every((o) => MATERIAL_KEYS.test(String(o))); + +const isMachineInput = (spec: LogicInput): boolean => + !!spec.고르기?.length && + spec.고르기.every((o) => MACHINE_CODE.test(String(o)) || MACHINE_KEY.test(String(o))); + +const pickButton = (label: string, onClick: () => void): HTMLButtonElement => + createButton({ label, variant: "ghost", onClick }); + +const line = (label: string, current: HTMLElement, button: HTMLElement): HTMLElement => + el("div", { + className: "m01-logic__field m01-logic__field--wide", + children: [el("span", { text: label }), el("div", { children: [current, button] })], + }); + +/** 재료 줄마다 한 칸 — 지금 잡힌 품목 + 품목 고르기 */ +export function materialRows( + row: LogicRow, + prices: Record, + values: Record, + again: () => void, +): HTMLElement[] { + const mine = picksOf(values); + return (row.호표 ?? []) + .map((item, i) => ({ item, i })) + .filter((x) => x.item.종류 === "재료") + .map(({ item, i }) => { + const cond = + typeof item.요소 === "object" && item.요소 !== null + ? (item.요소 as unknown as PickCond) + : null; + const brief = (cond ? prices[condText(cond)] : prices[item.요소]) ?? null; + const named = item.이름 && !/^[A-Z]{2}\d{6}/.test(item.이름) ? item.이름 : ""; + const label = named || (brief?.이름 as string | undefined) || ""; + const held = mine.get(i); + const nowName = + held?.label ?? (brief ? `${brief.이름 ?? ""} ${brief.규격 ?? ""}`.trim() : "") ?? ""; + const seeds = [cond?.["검색어"], cond?.이름, cond?.상세구분, cond?.구분, label] + .map(headWord) + .filter((w, k, all) => w && all.indexOf(w) === k); + const current = el("span", { + className: "m01-logic__muted", + text: `${nowName || tx("Mat_NoPrice")} `, + attrs: { "data-mat-idx": String(i), "data-ref": brief?.ref ?? "" }, + }); + const button = pickButton(tx("Pick_Item"), () => + openPickTable({ + kind: "재료", + title: `${tx("Pick_MatTitle")} · ${label}`, + seed: seeds[0] ?? "", + alt: seeds.slice(1), + isCurrent: (r) => String(r["키"]) === (mine.get(i)?.ref ?? current.dataset.ref), + onPick: (r) => { + mine.set(i, { ref: String(r["키"]), label: nameOf(r) }); + current.textContent = `${nameOf(r)} `; + again(); + }, + }), + ); + return line(`${label}${item.규격 ? ` ${item.규격}` : ""}`, current, button); + }); +} + +/** 계산 답의 `품목` 을 잡힌 품목 글로 — 고른 재료가 있는 줄은 그대로 */ +export function showItems( + host: HTMLElement, + values: Record, + lines: CalcLine[], +): void { + const mine = picksOf(values); + host.querySelectorAll("[data-mat-idx]").forEach((span) => { + const i = Number(span.dataset.matIdx); + const item = lines[i]?.품목; + if (!item || mine.has(i)) return; + span.dataset.ref = item.키; + span.textContent = `${`${item.이름 ?? ""} ${item.규격 ?? ""}`.trim() || item.키} `; + }); +} + +/** 기계 입력 칸 — 표 모달로 고르면 입력값(원문 분류번호 · 키 목록이면 키) */ +const machines = new Map(); +async function machineRow(code: string): Promise { + if (!machines.has(code)) { + let found: Row | null = null; + try { + const got = await fetchRows("기계.json", 1, 5, code); + found = got.rows.find((r) => r["원문번호"] === code || r["키"] === code) ?? null; + } catch { + found = null; + } + machines.set(code, found); + } + return machines.get(code) ?? null; +} + +export function machineControl( + spec: LogicInput, + values: Record, + again: () => void, +): HTMLElement | null { + if (!isMachineInput(spec)) return null; + const current = el("span", { className: "m01-logic__muted" }); + const paint = (): void => { + const code = values[spec.이름] ?? ""; + const known = machines.get(code); + current.textContent = `${code ? `${code}${known ? ` · ${nameOf(known)}` : ""}` : tx("Mat_NoPrice")} `; + if (code && !machines.has(code)) void machineRow(code).then(paint); + }; + paint(); + const button = pickButton(tx("Pick_Machine"), () => { + const code = values[spec.이름] ?? ""; + const known = machines.get(code); + openPickTable({ + kind: "기계", + title: `${tx("Pick_Machine")} · ${spec.이름}`, + seed: String(known?.["이름"] ?? spec.이름), + isCurrent: (r) => !!code && (r["원문번호"] === code || r["키"] === code), + onPick: (r) => { + const useKey = MACHINE_KEY.test(String(spec.고르기?.[0])); + const value = String(useKey ? r["키"] : r["원문번호"]); + machines.set(value, r); + values[spec.이름] = value; + paint(); + again(); + }, + }); + }); + return el("div", { children: [current, button] }); +} diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Money.ts b/M01_MasterData/M01_MasterData_UI_Logic_Money.ts new file mode 100644 index 00000000..f89b7475 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_Logic_Money.ts @@ -0,0 +1,19 @@ +/* ============================================================================= + * M01_MasterData_UI_Logic_Money.ts + * 돈 모양 한 벌 — 원 단위 정수(반올림). 단가·금액·소계·계 자리만 씀 · 수량·비율은 `formatNumber` 그대로 + * ========================================================================== */ + +import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; + +export const formatMoney = (value: unknown): string => + typeof value === "number" + ? value.toLocaleString("ko-KR", { maximumFractionDigits: 0 }) + : formatNumber(value); + +/** 단가 모양 — 정수로 보이되 200 원 미만은 반올림 오차가 커서 소수 넷째 자리·유효숫자 4 자리 중 많은 쪽(뒤 0 뗌) */ +export const formatPrice = (value: unknown): string => + typeof value === "number" && value !== 0 && Math.abs(value) < 200 + ? value.toLocaleString("ko-KR", { + maximumFractionDigits: Math.max(4, 3 - Math.floor(Math.log10(Math.abs(value)))), + }) + : formatMoney(value); diff --git a/M01_MasterData/M01_MasterData_UI_Logic_PickTable.ts b/M01_MasterData/M01_MasterData_UI_Logic_PickTable.ts new file mode 100644 index 00000000..d093bd11 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_Logic_PickTable.ts @@ -0,0 +1,142 @@ +/* ============================================================================= + * M01_MasterData_UI_Logic_PickTable.ts + * 시험 계산의 재료 · 기계 고르기 모달 — 마스터 컨테이너와 같은 표(`GET /rows`) · 쪽 나눔 · 칸 글 + * 검색 칸을 채워 열고 · 잡힌 품목 칠함 · 한 줄 누르면 고름 · 저장 안 함(읽기만) + * ========================================================================== */ + +import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements"; +import { fetchRows, type Row } from "./M01_MasterData_Api_Fetch"; +import { buildPager, flatten, show } from "./M01_MasterData_UI_Cells"; +import { tx } from "./M01_MasterData_UI_Logic_Text"; + +export type PickTableKind = "재료" | "기계"; + +const FILES: Record = { 재료: "재료_자재품목.json", 기계: "기계.json" }; +/** 표에 보일 칸 — 컨테이너 표의 칸 이름(묶음 값은 「값›가격」) */ +const COLS: Record = { + 재료: [ + "키", + "구분", + "상세구분", + "이름", + "규격", + "단위", + "물가자료", + "유통물가", + "물가정보", + "거래가격", + ], + 기계: ["원문번호", "구분", "상세구분", "이름", "규격", "값›가격"], +}; +const SIZE = 30; + +export interface PickTableOptions { + kind: PickTableKind; + title: string; + /** 검색 칸을 채워 엶 — 결과가 없으면 첫 낱말만으로 다시 · 그래도 없으면 `alt` 를 차례로 */ + seed: string; + alt?: string[]; + /** 지금 잡힌 줄이면 true — 그 줄을 칠함 */ + isCurrent: (row: Row) => boolean; + onPick: (row: Row) => void; +} + +export function openPickTable(opt: PickTableOptions): void { + const close = (): void => back.remove(); + const searchField = createInputField({ + type: "search", + placeholder: tx("Pick_Search"), + value: opt.seed, + }); + const search = searchField.input; + const host = el("div", { className: "m01-logic__pick-list" }); + const dialog = el("div", { + className: "m01-logic__pick m01-logic__pick--table", + attrs: { role: "dialog", "aria-label": opt.title }, + children: [ + el("div", { + className: "m01-logic__section-head", + children: [ + el("h3", { text: opt.title }), + createButton({ label: tx("Pick_Close"), variant: "ghost", onClick: close }), + ], + }), + searchField.root, + host, + ], + }); + const back = el("div", { className: "m01-logic__backdrop", children: [dialog] }); + back.addEventListener("click", (ev) => ev.target === back && close()); + back.addEventListener("keydown", (ev) => ev.key === "Escape" && close()); + + let page = 1; + const tried = new Set([opt.seed]); + let seq = 0; + const load = async (fallback = false): Promise => { + const mine = ++seq; + try { + const q = search.value.trim(); + const got = await fetchRows(FILES[opt.kind], page, SIZE, q); + if (mine !== seq) return; // 최신 응답만 + if (!got.total && fallback) { + const next = [...(q.includes(" ") ? [q.split(/\s+/)[0]] : []), ...(opt.alt ?? [])].find( + (w) => w && !tried.has(w), + ); + if (next) { + tried.add(next); + search.value = next; + return load(true); + } + } + const cols = COLS[opt.kind]; + const body = got.rows.map((row) => { + const flat = flatten(row); + const tr = el("tr", { + className: `m01lab__pickrow${opt.isCurrent(row) ? " is-picked" : ""}`, + children: cols.map((c) => + el("td", { text: show(flat[c]), attrs: { title: show(flat[c]) } }), + ), + }); + tr.addEventListener("click", () => { + close(); + opt.onPick(row); + }); + return tr; + }); + host.replaceChildren( + buildPager(got.total, SIZE, got.page, (next) => { + page = next; + void load(); + }), + el("div", { + className: "m01-master__grid-wrap", + children: [ + el("table", { + className: "m01-master__grid", + children: [ + el("thead", { + children: [el("tr", { children: cols.map((c) => el("th", { text: c })) })], + }), + el("tbody", { children: body }), + ], + }), + ], + }), + ); + host.querySelector(".is-picked")?.scrollIntoView({ block: "nearest" }); + } catch (error) { + showToast(error instanceof Error ? error.message : tx("Load_Failed"), "error"); + } + }; + let timer = 0; + search.addEventListener("input", () => { + window.clearTimeout(timer); + timer = window.setTimeout(() => { + page = 1; + void load(); + }, 300); + }); + document.body.append(back); + search.focus(); + void load(true); +} diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Style.css b/M01_MasterData/M01_MasterData_UI_Logic_Style.css index 6957eb4b..5dac8ba0 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Style.css +++ b/M01_MasterData/M01_MasterData_UI_Logic_Style.css @@ -84,6 +84,13 @@ gap: var(--spacing-8); } +/* 시험 계산 입력만 2열 고정 — 요소 표 쪽(auto-fill)은 그대로 둠 + 실제 그릇은 LogicLab 이 씌우는 `.m01lab__calc`(`.m01-logic__calc` 는 옛 테스트 컨테이너 몫이라 안 걸림) */ +.m01lab__calc .m01-logic__head, +.m01-logic__calc .m01-logic__head { + grid-template-columns: repeat(2, 1fr); +} + .m01-logic__field { display: flex; flex-direction: column; diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Text.ts b/M01_MasterData/M01_MasterData_UI_Logic_Text.ts index 95c7137a..5ee23361 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Text.ts @@ -67,6 +67,11 @@ const TEXT = { Pick_Title: ["요소 찾기", "Find element"], Pick_Search: ["이름·키·원문번호", "Name · key · source no."], Pick_Total: ["{n} 개 중 앞 100 개", "first 100 of {n}"], + Pick_Item: ["품목 고르기", "Pick item"], + Pick_Machine: ["기계 고르기", "Pick machine"], + Pick_MatTitle: ["자재품목 고르기", "Pick a material item"], + Pick_Chosen: ["고름", "Picked"], + Calc_UnitWarn: ["단위 다름", "Unit differs"], Pick_Close: ["닫기", "Close"], Pick_All: ["(전체)", "(All)"], Mat_Title: ["쓰이는 재료·기계", "Materials and machines"], @@ -137,6 +142,10 @@ const TEXT = { "「{name}」 규격이 여럿이라 정하지 못함 — 위 「쓰이는 재료·기계」에서 하나를 골라 주세요", "「{name}」 has several specs — pick one in the materials list above", ], + Stop_Generic: [ + "이 조건으로는 셈하지 못함 — 입력값 · 표에 그 조합이 있는지 확인", + "Cannot be calculated with these inputs — check the values and the table", + ], Stop_NoPrice: [ "「{name}」 단가가 자료에 비어 있어 셈하지 못함 — 자료를 채워야 함", "「{name}」 has no price in the data yet", @@ -157,6 +166,12 @@ export function plainReason(reason: string): string { return reason; } +/** 화면에 찍는 까닭의 마지막 방어 — 중괄호 · 따옴표 · Decimal( 이 섞인 날글은 사람 말 한 줄로 */ +export function safeReason(reason: string): string { + const plain = plainReason(reason); + return /[{}"']|Decimal\(/.test(plain) ? tx("Stop_Generic") : plain; +} + /** 흐름 그림 머리 「이렇게 씀」 — 줄 목록 */ /** 공용 `t()` 와 같은 뜻 — `{n}`·`{v}` 는 채움 */ diff --git a/resources/master_data/_키대장.json b/resources/master_data/_키대장.json index 02ef4a5f..493235a9 100644 --- a/resources/master_data/_키대장.json +++ b/resources/master_data/_키대장.json @@ -1,5 +1,5 @@ { - "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 252, "CC": 341, "GC": 1104, "QC": 2060, "GX": 1, "UA": 2}, + "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 252, "CC": 341, "GC": 1104, "QC": 2060, "GX": 1, "UA": 26}, "폐기": {"MT000001": "자재품목 휘발유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000002": "자재품목 경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000003": "자재품목 선박용경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000004": "자재품목 중유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000005": "자재품목 등유 — 값이 비고 글뿐 · 유가 테이블로 감", "LB000399": "이름 「〃」 — 직종이 아니라 되풀이 기호 · 소요량 13-6-9 에서 윗줄 직종으로 바로잡음", "MN": "재료_나라장터자재 — old 로 옮김 · 로직은 자재품목·품셈재료로 갈아 끼움", "LB000372": "미확보 고급기능사 — 측량노임 항공사진고급기능사 옛이름으로 옮김", "LB000393": "미확보 리베팅공 — 건설노임 철골공 옛이름으로 옮김(2010 통합표 연번10)", "LB000405": "미확보 Belt Conveyor 설치공 — 품셈 13-10-1 주② 배분으로 다섯 직종에 나눔", "MT000006": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "MT000007": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "LB000368": "미확보 기술사 — 기술사(건설) 옛이름으로 옮김", "LB000369": "미확보 특급기술자 — 특급기술자(건설) 옛이름으로 옮김", "LB000370": "미확보 고급기술자 — 고급기술자(건설) 옛이름으로 옮김", "LB000361": "미확보 중급기술자 — 중급기술자(건설) 옛이름으로 옮김", "LB000364": "미확보 초급기술자 — 초급기술자(건설) 옛이름으로 옮김", "LB000365": "미확보 고급숙련기술자 — 고급숙련기술자(건설) 옛이름으로 옮김", "LB000366": "미확보 중급숙련기술자 — 중급숙련기술자(건설) 옛이름으로 옮김", "LB000367": "미확보 초급숙련기술자 — 초급숙련기술자(건설) 옛이름으로 옮김", "LB000389": "미확보 기계기사 — 초급기술자(기계·설비) 옛이름으로 옮김", "LB000388": "미확보 기계산업기사 — 초급숙련기술자(기계·설비) 옛이름으로 옮김", "MO000055": {"원문번호": "주택용저압:기본_200이하", "파일": "재료_유가전력.json"}, "MO000056": {"원문번호": "주택용저압:기본_201~400", "파일": "재료_유가전력.json"}, "MO000057": {"원문번호": "주택용저압:기본_400초과", "파일": "재료_유가전력.json"}, "MO000059": {"원문번호": "주택용저압:전력량_다음200", "파일": "재료_유가전력.json"}, "MO000060": {"원문번호": "주택용저압:전력량_400초과", "파일": "재료_유가전력.json"}, "MO000061": {"원문번호": "주택용저압:월간최저요금", "파일": "재료_유가전력.json"}, "MO000062": {"원문번호": "일반용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000063": {"원문번호": "일반용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000064": {"원문번호": "일반용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000065": {"원문번호": "일반용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000066": {"원문번호": "산업용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000067": {"원문번호": "산업용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000068": {"원문번호": "산업용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000069": {"원문번호": "산업용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000070": {"원문번호": "임시전력갑:기본요금적용", "파일": "재료_유가전력.json"}, "MO000071": {"원문번호": "임시전력갑:전력량요금적용", "파일": "재료_유가전력.json"}, "MO000072": {"원문번호": "임시전력갑:월간최저요금", "파일": "재료_유가전력.json"}}, "키": { "CC000001": {"원문번호": "공통 1-3-1 콘크리트 및 포장용 재료", "파일": "계수_건설품셈_01장_적용기준.json"}, @@ -33918,6 +33918,125 @@ "MT030597": {"원문번호": "조달청 조경용수목 다년초, 황금무늬실유카, 수관폭15~30cm, 화분18cm", "파일": "재료_자재품목.json"}, "MT030598": {"원문번호": "조달청 조경용수목 다년초, 황금무늬실유카, 수관폭20~30cm, 포트17cm", "파일": "재료_자재품목.json"}, "MT030599": {"원문번호": "조달청 조경용수목 다년초, 황금무늬실유카, 수관폭20~40cm, 화분12cm", "파일": "재료_자재품목.json"}, + "MT030600": {"원문번호": "품셈재료 MP000012", "파일": "재료_자재품목.json"}, + "MT030601": {"원문번호": "품셈재료 MP000038", "파일": "재료_자재품목.json"}, + "MT030602": {"원문번호": "품셈재료 MP000048", "파일": "재료_자재품목.json"}, + "MT030603": {"원문번호": "품셈재료 MP000052", "파일": "재료_자재품목.json"}, + "MT030604": {"원문번호": "품셈재료 MP000055", "파일": "재료_자재품목.json"}, + "MT030605": {"원문번호": "품셈재료 MP000056", "파일": "재료_자재품목.json"}, + "MT030606": {"원문번호": "품셈재료 MP000057", "파일": "재료_자재품목.json"}, + "MT030607": {"원문번호": "품셈재료 MP000060", "파일": "재료_자재품목.json"}, + "MT030608": {"원문번호": "품셈재료 MP000065", "파일": "재료_자재품목.json"}, + "MT030609": {"원문번호": "품셈재료 MP000070", "파일": "재료_자재품목.json"}, + "MT030610": {"원문번호": "품셈재료 MP000076", "파일": "재료_자재품목.json"}, + "MT030611": {"원문번호": "품셈재료 MP000077", "파일": "재료_자재품목.json"}, + "MT030612": {"원문번호": "품셈재료 MP000095", "파일": "재료_자재품목.json"}, + "MT030613": {"원문번호": "품셈재료 MP000096", "파일": "재료_자재품목.json"}, + "MT030614": {"원문번호": "품셈재료 MP000101", "파일": "재료_자재품목.json"}, + "MT030615": {"원문번호": "품셈재료 MP000113", "파일": "재료_자재품목.json"}, + "MT030616": {"원문번호": "품셈재료 MP000129", "파일": "재료_자재품목.json"}, + "MT030617": {"원문번호": "품셈재료 MP000134", "파일": "재료_자재품목.json"}, + "MT030618": {"원문번호": "품셈재료 MP000139", "파일": "재료_자재품목.json"}, + "MT030619": {"원문번호": "품셈재료 MP000144", "파일": "재료_자재품목.json"}, + "MT030620": {"원문번호": "품셈재료 MP000148", "파일": "재료_자재품목.json"}, + "MT030621": {"원문번호": "품셈재료 MP000150", "파일": "재료_자재품목.json"}, + "MT030622": {"원문번호": "품셈재료 MP000169", "파일": "재료_자재품목.json"}, + "MT030623": {"원문번호": "품셈재료 MP000172", "파일": "재료_자재품목.json"}, + "MT030624": {"원문번호": "품셈재료 MP000176", "파일": "재료_자재품목.json"}, + "MT030625": {"원문번호": "품셈재료 MP000177", "파일": "재료_자재품목.json"}, + "MT030626": {"원문번호": "품셈재료 MP000201", "파일": "재료_자재품목.json"}, + "MT030627": {"원문번호": "품셈재료 MP000210", "파일": "재료_자재품목.json"}, + "MT030628": {"원문번호": "품셈재료 MP000211", "파일": "재료_자재품목.json"}, + "MT030629": {"원문번호": "품셈재료 MP000212", "파일": "재료_자재품목.json"}, + "MT030630": {"원문번호": "품셈재료 MP000213", "파일": "재료_자재품목.json"}, + "MT030631": {"원문번호": "품셈재료 MP000216", "파일": "재료_자재품목.json"}, + "MT030632": {"원문번호": "품셈재료 MP000218", "파일": "재료_자재품목.json"}, + "MT030633": {"원문번호": "품셈재료 MP000220", "파일": "재료_자재품목.json"}, + "MT030634": {"원문번호": "품셈재료 MP000221", "파일": "재료_자재품목.json"}, + "MT030635": {"원문번호": "품셈재료 MP000223", "파일": "재료_자재품목.json"}, + "MT030636": {"원문번호": "품셈재료 MP000235", "파일": "재료_자재품목.json"}, + "MT030637": {"원문번호": "품셈재료 MP000239", "파일": "재료_자재품목.json"}, + "MT030638": {"원문번호": "품셈재료 MP000240", "파일": "재료_자재품목.json"}, + "MT030639": {"원문번호": "품셈재료 MP000241", "파일": "재료_자재품목.json"}, + "MT030640": {"원문번호": "품셈재료 MP000242", "파일": "재료_자재품목.json"}, + "MT030641": {"원문번호": "품셈재료 MP000250", "파일": "재료_자재품목.json"}, + "MT030642": {"원문번호": "품셈재료 MP000253", "파일": "재료_자재품목.json"}, + "MT030643": {"원문번호": "품셈재료 MP000254", "파일": "재료_자재품목.json"}, + "MT030644": {"원문번호": "품셈재료 MP000255", "파일": "재료_자재품목.json"}, + "MT030645": {"원문번호": "품셈재료 MP000257", "파일": "재료_자재품목.json"}, + "MT030646": {"원문번호": "품셈재료 MP000258", "파일": "재료_자재품목.json"}, + "MT030647": {"원문번호": "품셈재료 MP000259", "파일": "재료_자재품목.json"}, + "MT030648": {"원문번호": "품셈재료 MP000260", "파일": "재료_자재품목.json"}, + "MT030649": {"원문번호": "품셈재료 MP000261", "파일": "재료_자재품목.json"}, + "MT030650": {"원문번호": "품셈재료 MP000262", "파일": "재료_자재품목.json"}, + "MT030651": {"원문번호": "품셈재료 MP000265", "파일": "재료_자재품목.json"}, + "MT030652": {"원문번호": "품셈재료 MP000269", "파일": "재료_자재품목.json"}, + "MT030653": {"원문번호": "품셈재료 MP000271", "파일": "재료_자재품목.json"}, + "MT030654": {"원문번호": "품셈재료 MP000272", "파일": "재료_자재품목.json"}, + "MT030655": {"원문번호": "품셈재료 MP000273", "파일": "재료_자재품목.json"}, + "MT030656": {"원문번호": "품셈재료 MP000274", "파일": "재료_자재품목.json"}, + "MT030657": {"원문번호": "품셈재료 MP000275", "파일": "재료_자재품목.json"}, + "MT030658": {"원문번호": "품셈재료 MP000276", "파일": "재료_자재품목.json"}, + "MT030659": {"원문번호": "품셈재료 MP000280", "파일": "재료_자재품목.json"}, + "MT030660": {"원문번호": "품셈재료 MP000281", "파일": "재료_자재품목.json"}, + "MT030661": {"원문번호": "품셈재료 MP000286", "파일": "재료_자재품목.json"}, + "MT030662": {"원문번호": "품셈재료 MP000287", "파일": "재료_자재품목.json"}, + "MT030663": {"원문번호": "품셈재료 MP000288", "파일": "재료_자재품목.json"}, + "MT030664": {"원문번호": "품셈재료 MP000290", "파일": "재료_자재품목.json"}, + "MT030665": {"원문번호": "품셈재료 MP000293", "파일": "재료_자재품목.json"}, + "MT030666": {"원문번호": "품셈재료 MP000294", "파일": "재료_자재품목.json"}, + "MT030667": {"원문번호": "품셈재료 MP000295", "파일": "재료_자재품목.json"}, + "MT030668": {"원문번호": "품셈재료 MP000296", "파일": "재료_자재품목.json"}, + "MT030669": {"원문번호": "품셈재료 MP000297", "파일": "재료_자재품목.json"}, + "MT030670": {"원문번호": "품셈재료 MP000298", "파일": "재료_자재품목.json"}, + "MT030671": {"원문번호": "품셈재료 MP000299", "파일": "재료_자재품목.json"}, + "MT030672": {"원문번호": "품셈재료 MP000301", "파일": "재료_자재품목.json"}, + "MT030673": {"원문번호": "품셈재료 MP000302", "파일": "재료_자재품목.json"}, + "MT030674": {"원문번호": "품셈재료 MP000303", "파일": "재료_자재품목.json"}, + "MT030675": {"원문번호": "품셈재료 MP000304", "파일": "재료_자재품목.json"}, + "MT030676": {"원문번호": "품셈재료 MP000305", "파일": "재료_자재품목.json"}, + "MT030677": {"원문번호": "품셈재료 MP000306", "파일": "재료_자재품목.json"}, + "MT030678": {"원문번호": "품셈재료 MP000308", "파일": "재료_자재품목.json"}, + "MT030679": {"원문번호": "품셈재료 MP000309", "파일": "재료_자재품목.json"}, + "MT030680": {"원문번호": "품셈재료 MP000310", "파일": "재료_자재품목.json"}, + "MT030681": {"원문번호": "품셈재료 MP000314", "파일": "재료_자재품목.json"}, + "MT030682": {"원문번호": "품셈재료 MP000315", "파일": "재료_자재품목.json"}, + "MT030683": {"원문번호": "품셈재료 MP000319", "파일": "재료_자재품목.json"}, + "MT030684": {"원문번호": "품셈재료 MP000325", "파일": "재료_자재품목.json"}, + "MT030685": {"원문번호": "품셈재료 MP000326", "파일": "재료_자재품목.json"}, + "MT030686": {"원문번호": "품셈재료 MP000327", "파일": "재료_자재품목.json"}, + "MT030687": {"원문번호": "품셈재료 MP000328", "파일": "재료_자재품목.json"}, + "MT030688": {"원문번호": "품셈재료 MP000330", "파일": "재료_자재품목.json"}, + "MT030689": {"원문번호": "품셈재료 MP000334", "파일": "재료_자재품목.json"}, + "MT030690": {"원문번호": "품셈재료 MP000335", "파일": "재료_자재품목.json"}, + "MT030691": {"원문번호": "품셈재료 MP000336", "파일": "재료_자재품목.json"}, + "MT030692": {"원문번호": "품셈재료 MP000337", "파일": "재료_자재품목.json"}, + "MT030693": {"원문번호": "품셈재료 MP000338", "파일": "재료_자재품목.json"}, + "MT030694": {"원문번호": "품셈재료 MP000340", "파일": "재료_자재품목.json"}, + "MT030695": {"원문번호": "품셈재료 MP000341", "파일": "재료_자재품목.json"}, + "MT030696": {"원문번호": "품셈재료 MP000342", "파일": "재료_자재품목.json"}, + "MT030697": {"원문번호": "품셈재료 MP000343", "파일": "재료_자재품목.json"}, + "MT030698": {"원문번호": "품셈재료 MP000344", "파일": "재료_자재품목.json"}, + "MT030699": {"원문번호": "품셈재료 MP000345", "파일": "재료_자재품목.json"}, + "MT030700": {"원문번호": "품셈재료 MP000349", "파일": "재료_자재품목.json"}, + "MT030701": {"원문번호": "품셈재료 MP000351", "파일": "재료_자재품목.json"}, + "MT030702": {"원문번호": "품셈재료 MP000352", "파일": "재료_자재품목.json"}, + "MT030703": {"원문번호": "품셈재료 MP000353", "파일": "재료_자재품목.json"}, + "MT030704": {"원문번호": "품셈재료 MP000359", "파일": "재료_자재품목.json"}, + "MT030705": {"원문번호": "품셈재료 MP000363", "파일": "재료_자재품목.json"}, + "MT030706": {"원문번호": "품셈재료 MP000003 전국", "파일": "재료_자재품목.json"}, + "MT030707": {"원문번호": "품셈재료 MP000003 지역별", "파일": "재료_자재품목.json"}, + "MT030708": {"원문번호": "품셈재료 MP000005 전국", "파일": "재료_자재품목.json"}, + "MT030709": {"원문번호": "품셈재료 MP000005 지역별", "파일": "재료_자재품목.json"}, + "MT030710": {"원문번호": "품셈재료 MP000209", "파일": "재료_자재품목.json"}, + "MT030711": {"원문번호": "품셈재료 MP000214", "파일": "재료_자재품목.json"}, + "MT030712": {"원문번호": "품셈재료 MP000040", "파일": "재료_자재품목.json"}, + "MT030713": {"원문번호": "품셈재료 MP000094", "파일": "재료_자재품목.json"}, + "MT030714": {"원문번호": "품셈재료 MP000202", "파일": "재료_자재품목.json"}, + "MT030715": {"원문번호": "품셈재료 MP000205", "파일": "재료_자재품목.json"}, + "MT030716": {"원문번호": "품셈재료 MP000206", "파일": "재료_자재품목.json"}, + "MT030717": {"원문번호": "품셈재료 MP000207", "파일": "재료_자재품목.json"}, + "MT030718": {"원문번호": "품셈재료 MP000208", "파일": "재료_자재품목.json"}, "QC000001": {"원문번호": "공통 2-1-1 현장사무소 등의 규모(토목)", "파일": "소요량_건설품셈_02장_가설공사.json"}, "QC000002": {"원문번호": "공통 2-1-2 현장사무소 등의 규모(건축 및 기계설비)", "파일": "소요량_건설품셈_02장_가설공사.json"}, "QC000003": {"원문번호": "공통 2-1-2 시멘트 창고, 동력소 및 변전소 필요면적", "파일": "소요량_건설품셈_02장_가설공사.json"}, @@ -36473,124 +36592,29 @@ "RT000018": {"원문번호": "rate_profit", "파일": "요율_조달청제비율.json"}, "RT000019": {"원문번호": "rate_vat", "파일": "요율_조달청제비율.json"}, "UA000001": {"원문번호": "", "파일": "일위대가조합.json"}, - "MT030600": {"원문번호": "품셈재료 MP000012", "파일": "재료_자재품목.json"}, - "MT030601": {"원문번호": "품셈재료 MP000038", "파일": "재료_자재품목.json"}, - "MT030602": {"원문번호": "품셈재료 MP000048", "파일": "재료_자재품목.json"}, - "MT030603": {"원문번호": "품셈재료 MP000052", "파일": "재료_자재품목.json"}, - "MT030604": {"원문번호": "품셈재료 MP000055", "파일": "재료_자재품목.json"}, - "MT030605": {"원문번호": "품셈재료 MP000056", "파일": "재료_자재품목.json"}, - "MT030606": {"원문번호": "품셈재료 MP000057", "파일": "재료_자재품목.json"}, - "MT030607": {"원문번호": "품셈재료 MP000060", "파일": "재료_자재품목.json"}, - "MT030608": {"원문번호": "품셈재료 MP000065", "파일": "재료_자재품목.json"}, - "MT030609": {"원문번호": "품셈재료 MP000070", "파일": "재료_자재품목.json"}, - "MT030610": {"원문번호": "품셈재료 MP000076", "파일": "재료_자재품목.json"}, - "MT030611": {"원문번호": "품셈재료 MP000077", "파일": "재료_자재품목.json"}, - "MT030612": {"원문번호": "품셈재료 MP000095", "파일": "재료_자재품목.json"}, - "MT030613": {"원문번호": "품셈재료 MP000096", "파일": "재료_자재품목.json"}, - "MT030614": {"원문번호": "품셈재료 MP000101", "파일": "재료_자재품목.json"}, - "MT030615": {"원문번호": "품셈재료 MP000113", "파일": "재료_자재품목.json"}, - "MT030616": {"원문번호": "품셈재료 MP000129", "파일": "재료_자재품목.json"}, - "MT030617": {"원문번호": "품셈재료 MP000134", "파일": "재료_자재품목.json"}, - "MT030618": {"원문번호": "품셈재료 MP000139", "파일": "재료_자재품목.json"}, - "MT030619": {"원문번호": "품셈재료 MP000144", "파일": "재료_자재품목.json"}, - "MT030620": {"원문번호": "품셈재료 MP000148", "파일": "재료_자재품목.json"}, - "MT030621": {"원문번호": "품셈재료 MP000150", "파일": "재료_자재품목.json"}, - "MT030622": {"원문번호": "품셈재료 MP000169", "파일": "재료_자재품목.json"}, - "MT030623": {"원문번호": "품셈재료 MP000172", "파일": "재료_자재품목.json"}, - "MT030624": {"원문번호": "품셈재료 MP000176", "파일": "재료_자재품목.json"}, - "MT030625": {"원문번호": "품셈재료 MP000177", "파일": "재료_자재품목.json"}, - "MT030626": {"원문번호": "품셈재료 MP000201", "파일": "재료_자재품목.json"}, - "MT030627": {"원문번호": "품셈재료 MP000210", "파일": "재료_자재품목.json"}, - "MT030628": {"원문번호": "품셈재료 MP000211", "파일": "재료_자재품목.json"}, - "MT030629": {"원문번호": "품셈재료 MP000212", "파일": "재료_자재품목.json"}, - "MT030630": {"원문번호": "품셈재료 MP000213", "파일": "재료_자재품목.json"}, - "MT030631": {"원문번호": "품셈재료 MP000216", "파일": "재료_자재품목.json"}, - "MT030632": {"원문번호": "품셈재료 MP000218", "파일": "재료_자재품목.json"}, - "MT030633": {"원문번호": "품셈재료 MP000220", "파일": "재료_자재품목.json"}, - "MT030634": {"원문번호": "품셈재료 MP000221", "파일": "재료_자재품목.json"}, - "MT030635": {"원문번호": "품셈재료 MP000223", "파일": "재료_자재품목.json"}, - "MT030636": {"원문번호": "품셈재료 MP000235", "파일": "재료_자재품목.json"}, - "MT030637": {"원문번호": "품셈재료 MP000239", "파일": "재료_자재품목.json"}, - "MT030638": {"원문번호": "품셈재료 MP000240", "파일": "재료_자재품목.json"}, - "MT030639": {"원문번호": "품셈재료 MP000241", "파일": "재료_자재품목.json"}, - "MT030640": {"원문번호": "품셈재료 MP000242", "파일": "재료_자재품목.json"}, - "MT030641": {"원문번호": "품셈재료 MP000250", "파일": "재료_자재품목.json"}, - "MT030642": {"원문번호": "품셈재료 MP000253", "파일": "재료_자재품목.json"}, - "MT030643": {"원문번호": "품셈재료 MP000254", "파일": "재료_자재품목.json"}, - "MT030644": {"원문번호": "품셈재료 MP000255", "파일": "재료_자재품목.json"}, - "MT030645": {"원문번호": "품셈재료 MP000257", "파일": "재료_자재품목.json"}, - "MT030646": {"원문번호": "품셈재료 MP000258", "파일": "재료_자재품목.json"}, - "MT030647": {"원문번호": "품셈재료 MP000259", "파일": "재료_자재품목.json"}, - "MT030648": {"원문번호": "품셈재료 MP000260", "파일": "재료_자재품목.json"}, - "MT030649": {"원문번호": "품셈재료 MP000261", "파일": "재료_자재품목.json"}, - "MT030650": {"원문번호": "품셈재료 MP000262", "파일": "재료_자재품목.json"}, - "MT030651": {"원문번호": "품셈재료 MP000265", "파일": "재료_자재품목.json"}, - "MT030652": {"원문번호": "품셈재료 MP000269", "파일": "재료_자재품목.json"}, - "MT030653": {"원문번호": "품셈재료 MP000271", "파일": "재료_자재품목.json"}, - "MT030654": {"원문번호": "품셈재료 MP000272", "파일": "재료_자재품목.json"}, - "MT030655": {"원문번호": "품셈재료 MP000273", "파일": "재료_자재품목.json"}, - "MT030656": {"원문번호": "품셈재료 MP000274", "파일": "재료_자재품목.json"}, - "MT030657": {"원문번호": "품셈재료 MP000275", "파일": "재료_자재품목.json"}, - "MT030658": {"원문번호": "품셈재료 MP000276", "파일": "재료_자재품목.json"}, - "MT030659": {"원문번호": "품셈재료 MP000280", "파일": "재료_자재품목.json"}, - "MT030660": {"원문번호": "품셈재료 MP000281", "파일": "재료_자재품목.json"}, - "MT030661": {"원문번호": "품셈재료 MP000286", "파일": "재료_자재품목.json"}, - "MT030662": {"원문번호": "품셈재료 MP000287", "파일": "재료_자재품목.json"}, - "MT030663": {"원문번호": "품셈재료 MP000288", "파일": "재료_자재품목.json"}, - "MT030664": {"원문번호": "품셈재료 MP000290", "파일": "재료_자재품목.json"}, - "MT030665": {"원문번호": "품셈재료 MP000293", "파일": "재료_자재품목.json"}, - "MT030666": {"원문번호": "품셈재료 MP000294", "파일": "재료_자재품목.json"}, - "MT030667": {"원문번호": "품셈재료 MP000295", "파일": "재료_자재품목.json"}, - "MT030668": {"원문번호": "품셈재료 MP000296", "파일": "재료_자재품목.json"}, - "MT030669": {"원문번호": "품셈재료 MP000297", "파일": "재료_자재품목.json"}, - "MT030670": {"원문번호": "품셈재료 MP000298", "파일": "재료_자재품목.json"}, - "MT030671": {"원문번호": "품셈재료 MP000299", "파일": "재료_자재품목.json"}, - "MT030672": {"원문번호": "품셈재료 MP000301", "파일": "재료_자재품목.json"}, - "MT030673": {"원문번호": "품셈재료 MP000302", "파일": "재료_자재품목.json"}, - "MT030674": {"원문번호": "품셈재료 MP000303", "파일": "재료_자재품목.json"}, - "MT030675": {"원문번호": "품셈재료 MP000304", "파일": "재료_자재품목.json"}, - "MT030676": {"원문번호": "품셈재료 MP000305", "파일": "재료_자재품목.json"}, - "MT030677": {"원문번호": "품셈재료 MP000306", "파일": "재료_자재품목.json"}, - "MT030678": {"원문번호": "품셈재료 MP000308", "파일": "재료_자재품목.json"}, - "MT030679": {"원문번호": "품셈재료 MP000309", "파일": "재료_자재품목.json"}, - "MT030680": {"원문번호": "품셈재료 MP000310", "파일": "재료_자재품목.json"}, - "MT030681": {"원문번호": "품셈재료 MP000314", "파일": "재료_자재품목.json"}, - "MT030682": {"원문번호": "품셈재료 MP000315", "파일": "재료_자재품목.json"}, - "MT030683": {"원문번호": "품셈재료 MP000319", "파일": "재료_자재품목.json"}, - "MT030684": {"원문번호": "품셈재료 MP000325", "파일": "재료_자재품목.json"}, - "MT030685": {"원문번호": "품셈재료 MP000326", "파일": "재료_자재품목.json"}, - "MT030686": {"원문번호": "품셈재료 MP000327", "파일": "재료_자재품목.json"}, - "MT030687": {"원문번호": "품셈재료 MP000328", "파일": "재료_자재품목.json"}, - "MT030688": {"원문번호": "품셈재료 MP000330", "파일": "재료_자재품목.json"}, - "MT030689": {"원문번호": "품셈재료 MP000334", "파일": "재료_자재품목.json"}, - "MT030690": {"원문번호": "품셈재료 MP000335", "파일": "재료_자재품목.json"}, - "MT030691": {"원문번호": "품셈재료 MP000336", "파일": "재료_자재품목.json"}, - "MT030692": {"원문번호": "품셈재료 MP000337", "파일": "재료_자재품목.json"}, - "MT030693": {"원문번호": "품셈재료 MP000338", "파일": "재료_자재품목.json"}, - "MT030694": {"원문번호": "품셈재료 MP000340", "파일": "재료_자재품목.json"}, - "MT030695": {"원문번호": "품셈재료 MP000341", "파일": "재료_자재품목.json"}, - "MT030696": {"원문번호": "품셈재료 MP000342", "파일": "재료_자재품목.json"}, - "MT030697": {"원문번호": "품셈재료 MP000343", "파일": "재료_자재품목.json"}, - "MT030698": {"원문번호": "품셈재료 MP000344", "파일": "재료_자재품목.json"}, - "MT030699": {"원문번호": "품셈재료 MP000345", "파일": "재료_자재품목.json"}, - "MT030700": {"원문번호": "품셈재료 MP000349", "파일": "재료_자재품목.json"}, - "MT030701": {"원문번호": "품셈재료 MP000351", "파일": "재료_자재품목.json"}, - "MT030702": {"원문번호": "품셈재료 MP000352", "파일": "재료_자재품목.json"}, - "MT030703": {"원문번호": "품셈재료 MP000353", "파일": "재료_자재품목.json"}, - "MT030704": {"원문번호": "품셈재료 MP000359", "파일": "재료_자재품목.json"}, - "MT030705": {"원문번호": "품셈재료 MP000363", "파일": "재료_자재품목.json"}, - "MT030706": {"원문번호": "품셈재료 MP000003 전국", "파일": "재료_자재품목.json"}, - "MT030707": {"원문번호": "품셈재료 MP000003 지역별", "파일": "재료_자재품목.json"}, - "MT030708": {"원문번호": "품셈재료 MP000005 전국", "파일": "재료_자재품목.json"}, - "MT030709": {"원문번호": "품셈재료 MP000005 지역별", "파일": "재료_자재품목.json"}, - "MT030710": {"원문번호": "품셈재료 MP000209", "파일": "재료_자재품목.json"}, - "MT030711": {"원문번호": "품셈재료 MP000214", "파일": "재료_자재품목.json"}, - "MT030712": {"원문번호": "품셈재료 MP000040", "파일": "재료_자재품목.json"}, - "MT030713": {"원문번호": "품셈재료 MP000094", "파일": "재료_자재품목.json"}, - "MT030714": {"원문번호": "품셈재료 MP000202", "파일": "재료_자재품목.json"}, - "MT030715": {"원문번호": "품셈재료 MP000205", "파일": "재료_자재품목.json"}, - "MT030716": {"원문번호": "품셈재료 MP000206", "파일": "재료_자재품목.json"}, - "MT030717": {"원문번호": "품셈재료 MP000207", "파일": "재료_자재품목.json"}, - "MT030718": {"원문번호": "품셈재료 MP000208", "파일": "재료_자재품목.json"} + "UA000002": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000003": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000004": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000005": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000006": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000007": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000008": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000009": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000010": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000011": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000012": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000013": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000014": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000015": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000016": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000017": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000018": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000019": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000020": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000021": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000022": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000023": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000024": {"원문번호": "", "파일": "일위대가조합.json"}, + "UA000025": {"원문번호": "", "파일": "일위대가조합.json"} } } diff --git a/resources/master_data/_틀.md b/resources/master_data/_틀.md index c91af730..b8ed909b 100644 --- a/resources/master_data/_틀.md +++ b/resources/master_data/_틀.md @@ -118,7 +118,7 @@ - **원문번호 칸 없음** — 원문(ESTX)이 준 품번 `M0005250505` 는 남의 번호라 이름 몫을 못 함 · 대조는 `_키대장.json` 에만 둠(조달청에서 새로 더한 줄의 조달청 번호도 같음). 로직·연결은 키로 적음. - 유류·전력은 뗌 — 유가·전기요금은 `재료_유가전력`(유류는 값이 비고 글뿐 · 중유는 받지 않음). - `재료_유가전력` · `환율_한국은행환율` 도 열 한 벌 — 키 · 원문번호 · 구분 · 상세구분 · 이름 · 규격 · 단위 · 값 · 출처 · 비고. 유가전력 `구분` = 유류 · 전력 · `상세구분` = 유류(지역 이름) · 전력(계약종별) · `규격` = 기준일(유류 수집일 · 전력 시행일). 환율은 `규격` = 기준일 · 구분·상세구분 비움. -- 로직 호표가 자재품목 키(`MT…`)를 바로 가리키면 그 줄의 값 열 가운데 낮은 값 — **시험 계산용**. +- 재료 줄 단가 = 고른 품목(없으면 자동값 줄)의 값 열 가운데 낮은 값 — **시험 계산용**. - `재료_품셈재료`(PLAN 7-4b 뒤 절반 — `old/` 로 옮김 · 살아 있는 마스터엔 안 읽힘 · 자료로만 둠) = 품셈이 부르는 재료 목록이던 것 — `{ 키, 원문번호, 이름, 규격, 단위, 요구절: [출처 모양], 연결, 고르기 }` · 값 칸 없음. 로직 호표·덧줄의 `MP…` 직접 참조는 다 걷어 자재품목 고르기 조건(7장)이나 대표 키로 바꿈 — 아래 두 줄은 그 옮기던 때의 자취. - `연결` = 자재품목·유가전력 키 하나 · 후보 조건 `{ 이름, 규격, 지역: "입력" }`(지역이 갈리는 레미콘·골재·아스콘) · `{ 이름, 계약종별: "입력" }`(전력 — 로직 입력 `전력 계약종별`) · `null`(못 이음). 이 지역·계약종별 조건 묶음은 이제 7장의 고르기 조건(`PICK_KEYS` 의 `지역`·`계약종별`)에 그대로 들어가 호표에 바로 박힘 — 품셈재료 간접 없이 `master_material._region`·`_tariff` 가 품. - 단가 = 연결된 줄의 값 열 다섯 가운데 낮은 값 · 호표 결과 줄에 `출처`(`<키>.<열>`) — **시험 계산용**. 후보 조건 = 품명 뿌리가 `이름` 으로 끝남 · 이름에 로직 입력 `자재지역` 글자 · 단위 같음 · 규격이 둘 이상 갈리면 멈춤 「후보 여럿」 · 그 지역 줄이 없으면 가까운 시군을 고르지 않고 멈춤 「지역 값 없음」. @@ -209,7 +209,7 @@ ```json { "종류": "재료", - "요소": { "구분": "관·수로", "상세구분": "흄관", "규격": "D300", "대표": "MT012345" }, + "요소": { "구분": "관·수로", "상세구분": "흄관", "검색어": "흄관 D300" }, "이름": "흄관", "단위": "m", "수량": "1", @@ -218,17 +218,20 @@ } ``` -- 조건 칸 = `구분`(반드시 — `지역`·`계약종별` 조건은 예외) · `상세구분` · `이름` · `규격` · `대표` · `지역`("입력" 만) · `계약종별`("입력" 만) · 그 밖의 칸은 두지 않음. `종류` 는 「재료」만. +- 조건 칸 = `검색어` · `구분` · `상세구분` · `이름` · `규격` · `지역`("입력" 만) · `계약종별`("입력" 만) · 그 밖의 칸은 두지 않음. `대표` 칸은 두지 않음. `종류` 는 「재료」만. - `지역`·`계약종별` 조건(PLAN 7-4b 뒤 절반) = 구분·상세구분 없이 `이름` 하나로 자재품목(지역)·유가전력(계약종별)을 넓게 찾음 — 옛 품셈재료 `연결` 묶음과 같은 모양·같은 매칭(`master_material._region`·`_tariff`) · 로직 입력 `자재지역`·`전력 계약종별` 이 계산 때 좁힘 · 값은 `null`(지역·전력 마스터에 안 굳힘). - 구분·상세구분 = 자재품목 줄의 같은 이름 칸과 똑같이 적음(묶음 목록은 `ref/_묶음_자재품목.md`). - 규격 조건 = 원문이 규격을 밝힌 것만 좁힘 — 낱말이 **모두** 든 줄이 후보(「D300」 · 「D300 B종」) · 대소문자·빈칸·`φ` 는 안 가림. 원문이 규격을 안 밝히면 칸을 두지 않음. - 이름 조건 = 구분·상세구분·규격이 같아도 품명이 갈리는 자재품목을 가를 때만 둠(예: 파형강관 커플링밴드 vs 접속티 — 둘 다 관경만 규격에 있어 규격 조건으로는 안 갈림). 규격과 같은 방식(낱말이 **모두** 이름에 든 줄) — 대소문자·빈칸은 안 가림. -- 대표 줄 = 관리자가 고른 자재품목 키 — 시험 계산이 그 줄의 값을 씀. 조건 밖 키는 검사가 잡음. -- 후보는 **호표 줄 단위와 같은 자재품목 줄만** — 단위가 다르면 다른 물건(우드칩 ㎥ · 원목후로링 ㎡). 조건 안 줄이 모두 다른 단위면 `check_master 단위` 가 알림. +- **키를 못 박음** — 재료 줄은 늘 요소 묶음 + `검색어`(「합판(내수) 12t 서울」) · 줄에 자재품목 키를 바로 적지 않음 · 재료 입력에 키 목록(`고르기`)을 두지 않음. +- 찾기 = 검색어 낱말(띄어쓰기로 나눔)이 **모두** 이름 또는 규격에 든 자재품목 줄 · 요소에 `구분` · `상세구분` 이 있으면 그 안에서만 · 단위는 같은 성격까지(아래 환산). +- 자동값 차례 = 값 있는 줄 → 줄 단위와 똑같은 단위 → 검색어 안 지역 낱말(서울 등)과 맞는 줄 → 키 차례 · 그 첫째. +- 고른 품목 = 계산 요청에만 실음(`_화면_계약.md` 10장 `고름`) · 로직 파일에 저장하지 않음 · 폐기 품목은 지움. +- 단위 환산 = 같은 성격만 — 무게 g · kg · ton(톤 · Ton) / 길이 mm · cm · m · km / 넓이 ㎠ · ㎡ / 부피 ㎤ · ℓ · ㎥ · 단가를 줄 단위로 바꿔 계산. +- 단위 경고 = 성격이 다르면 막지 않음 — 계산은 하되 `/calc` 줄에 `단위경고` · `check_master` 가 검색어 후보 0 · 단위 경고 줄을 셈. - 조건 값에 `{입력}` 을 넣을 수 있음 — `"규격": "{관경}"` 은 계산 때 입력값으로 채워짐(검사에서는 그 칸을 뺌). -- 대표가 없으면 시험 계산은 **조건 안 값 있는 첫 줄** · 로직 입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄부터(레미콘·골재처럼 지역이 갈리는 품목). - 실제 줄 고르기는 프로젝트 몫 — 후보 목록은 `GET /api/m01/materials`(화면·테스트 컨테이너가 같이 씀). -- 옛 방식(자재품목 키를 호표에 바로 적은 `MT…`)도 그대로 돎 — 그 줄은 그 자재품목 줄 값을 바로 씀. 품셈재료 간접(`MP…`)은 PLAN 7-4b 뒤 절반에서 다 걷음(위 4장) — 남았던 22줄(색소·에폭시·버너·퓨즈 등)도 자재품목 새 줄이나 대표 키로 이음. +- 기계 = 입력값에 원문 분류번호(목록 밖 번호도 기계 마스터에 있으면 받음) · 기본값 = 원문 분류번호 목록 첫째 · 인력은 고르지 않음(지정값). - 근거가 덜 선 줄은 `비고` 칸에 적음 — 「환산 근거 미확인」. - 입력 칸 = `이름`(반드시) · `설명` · `단위` · `고르기`·`범위`(둘 중 하나까지) · `출처`(원문 절) · `비고` · `시험입력`은 줄 머리에 따로. @@ -241,7 +244,7 @@ ## 8. 변수 · 식 - 요소 — 키 `LB000123` · 값 묶음은 `.칸` — `EQ000045.주연료`. -- 입력·중간 값으로 요소를 고르면 `ID:원문번호` 에 `{이름}` 을 끼움 — `EQ:{기계}.가격` · `MO:경유:{지역}` · `LB:{조종원직종}`. 끼운 값이 키여도 됨 — `MT:{철선}` 의 고르기는 키 목록. 번호가 겹치는 표·로직은 `ID:원문번호` 로 못 부름 — 키로 적음. +- 입력·중간 값으로 요소를 고르면 `ID:원문번호` 에 `{이름}` 을 끼움 — `EQ:{기계}.가격` · `MO:경유:{지역}` · `LB:{조종원직종}`. 재료는 `MT:{…}` 로 고르지 않음 — 7장 검색어. 번호가 겹치는 표·로직은 `ID:원문번호` 로 못 부름 — 키로 적음. - 표 찾기 — `찾기(QF000041, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).석공` - 맞는 줄이 여럿이면 찾는 값 칸이 있는 줄만 봄. 값 칸·조건 이름에 괄호·빈칸이 있으면 작은따옴표 — `.'시공량(m)'` · `'규준틀 높이'=높이` - 찾기·로직 인자의 글자 값은 작은따옴표 — `구분='V(다짐속도,km/hr)'` · 따옴표 없으면 이름(입력·중간). diff --git a/resources/master_data/_화면_계약.md b/resources/master_data/_화면_계약.md index 1c40e216..a6bf8e63 100644 --- a/resources/master_data/_화면_계약.md +++ b/resources/master_data/_화면_계약.md @@ -47,9 +47,9 @@ ## 3. 시험 계산 -`POST /calc` 받음 `{key, inputs: {이름: 값}, row?, file?}` — 새 로직은 `key` "" +`POST /calc` 받음 `{key, inputs: {이름: 값}, row?, file?, 고름?}` — 새 로직은 `key` "" · `고름` 은 10장 -- 됨 — `{ok: true, lines: [{이름, 단위, 수량, 단가, 금액, 비목: {비목: 금액}, 출처?}], sums: {노무비, 재료비, 경비, 계}, middle: {이름: 값}}` · `출처` = 품셈재료 줄의 단가 칸 +- 됨 — `{ok: true, lines: [{이름, 단위, 수량, 단가, 금액, 비목: {비목: 금액}, 출처?, 단위경고?}], sums: {노무비, 재료비, 경비, 계}, middle: {이름: 값}}` · `출처` = 재료 줄의 단가 칸 · `단위경고` 는 10장 - 돈 아닌 로직 — `{ok: true, result, middle}` - 멈춤 — `{ok: false, reason}` (200). 까닭 = 엔진 글 그대로(「입력 「돌」 없음」 · 「값 없음 — 관리자가 채울 값」 …). - `row` 없음 = 저장된 파일로 계산 · `row` 있음 = 그 로직만 고친 줄로 바꿔(없던 로직이면 `file` 에 더해) 메모리에서 계산 — 파일에 안 씀. 요소 값은 늘 저장된 파일. @@ -192,3 +192,18 @@ - `줄` = `{차례, 로직, 메모, 이름, 원문번호, 결과단위, 구분, 상세구분, 출처, ok, 갈래, 노무비, 재료비, 경비, 계}` · 돈이 아닌 로직은 `갈래` 「결과」 와 `결과` 수 하나(비목이 없어 합계에 안 얹음). - 못 도는 줄은 통째로 멈추지 않고 그 줄만 `ok` false · `까닭` · `멈춤` 에 한 줄 — 나머지 줄의 합계는 그대로 냄. - 답 — 200 · 400(틀린 몸 · 이름 없음 · 모르는 소유) · 404(없는 키) · 409(낡은 판본) · 422 `{detail: {errors: […]}}`(검사 걸림 — 없는 로직 키 · 같은 로직 두 번 · 빈 조합 · 조합을 담음). + +## 10. 재료 · 기계 고르기 + +재료 줄은 키 대신 기본 검색어로 품목을 찾음 — 파일 모양 · 찾기 규칙 · 자동값 차례 · 단위 환산은 `_틀.md` 7장. + +| 무엇 | 모양 | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| 고른 품목 | `/calc` · `/text` 몸에 `"고름": {"<호표 줄 차례>": "MT000019"}` · 저장 안 함 · 기계는 그 입력값에 원문 분류번호(목록 밖 번호도 기계 마스터에 있으면 받음) | +| 단위 경고 | 성격이 다르면 계산은 하되 `/calc` 줄에 `"단위경고": "줄 m · 자재 Ton"` · 화면은 그 줄에 딱지 | +| 모달 검색 | 재료 = 자재품목 표 · 검색 칸 = 검색어 첫 낱말(예 「합판」) · 잡힌 품목 칠함 / 기계 = 기계 표 · 검색 칸 = 기계 이름(예 「크레인(타이어)」) | + +- `고름` 이 없거나 그 줄에 없으면 자동값(`_틀.md` 7장 차례의 첫째). +- 고르면 다시 계산 · 고른 품목은 파일에 저장하지 않고 계산 요청에만 실음. +- 드롭다운 고르기는 걷음 — 재료 줄 · 기계 입력의 「품목 고르기」 가 모달을 검색 칸 채워 엶(재료 컨테이너 표 재사용). +- 인력은 고르지 않음(지정값). diff --git a/resources/master_data/scripts/build_재료_검색어.py b/resources/master_data/scripts/build_재료_검색어.py new file mode 100644 index 00000000..134ddbcd --- /dev/null +++ b/resources/master_data/scripts/build_재료_검색어.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +"""로직 재료 줄의 자재품목 키를 「기본 검색어」 로 바꿈 (PLAN 8-3 · 계약 8-1). + + · 대표 키 박은 줄 · 대표가 재료 입력(`{철선}`)인 줄 · 조건만 있는 줄 — 요소를 + `{구분, 상세구분, 검색어}` 로. 검색어는 지금 잡히는 품목(자동값)이 8-1 찾기 규칙 · + 자동값 차례로 첫째가 되게 지음(이름 핵심 + 규격 핵심 + 지역 낱말 · 모자라면 낱말을 더함) + · 재료 입력 키 목록(고르기가 자재품목 키인 입력)은 걷음 — 그 입력을 넘기던 `이름=이름` 인자도 + · 지역·계약종별 조건 줄 · 유가전력 틀(`MO:경유:{지역}`)은 그대로 — 자재품목 키가 아님 + · 글 모양 그대로 — 바뀐 값 자리만 갈음 · 다시 돌려도 같은 결과 + +사용: python scripts/build_재료_검색어.py (마른 돌리기 — 파일 안 씀 · 못 맞춘 줄 목록) + python scripts/build_재료_검색어.py --쓰기 (로직 파일 갱신) +""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + +import check_master as cm # noqa: E402 +import master_auto as mau # noqa: E402 +import master_formula as mf # noqa: E402 +import master_material as mm # noqa: E402 +import master_pick as mp # noqa: E402 + +MASTER = HERE.parent +KEY = re.compile(r"MT\d{6}") + + +# ── 판정 — 서버 찾기(`master_pick.search`) 그대로 ───────────────────────── +def _has(word: str, row: dict) -> bool: + """`master_pick.search` 와 같은 맞대기 — 낱말이 이름 또는 규격에 듦.""" + w = mm.plain_spec(word) + return w in mm.plain(row.get("이름")).replace("×", "x") or w in mm.plain_spec(row.get("규격")) + + +def found(master: mf.Master, elem: dict, unit: str | None, env: dict) -> list[str]: + return mp.search(master, elem, env, unit) + + +def first(master, elem, unit, env) -> str | None: + got = found(master, elem, unit, env) + return got[0] if got else None + + +# ── 지금 잡히는 품목 ────────────────────────────────────────────────── +def material_inputs(row: dict) -> dict[str, list]: + """고르기가 자재품목 키 목록인 입력 — 걷을 것.""" + return { + s["이름"]: s["고르기"] + for s in row.get("입력", []) + if isinstance(s.get("고르기"), list) + and s["고르기"] + and all(isinstance(x, str) and KEY.fullmatch(x) for x in s["고르기"]) + } + + +def _holes(elem: dict) -> list[str]: + return sorted({h for v in elem.values() if isinstance(v, str) for h in mf._HOLE.findall(v)}) + + +def envs(master: mf.Master, row: dict, elem: dict) -> list[dict]: + """줄을 판정할 입력 벌 — 자동값 하나 · 조건에 끼운 입력은 고르기 값마다.""" + base = mau.values(master, row) + out = [base] + for hole in _holes(elem): + spec = next((s for s in row.get("입력", []) if s["이름"] == hole), {}) + if spec.get("고르기") and hole not in material_inputs(row): + out = [{**e, hole: v} for e in out for v in spec["고르기"]] + return out + + +def old_pick(master: mf.Master, elem: dict, env: dict, unit: str | None) -> str | None: + try: + cond = {k: mf.fill(v, env) if isinstance(v, str) else v for k, v in elem.items()} + return mm.pick(master, cond, env, unit) + except mf.FormulaError: + return None + + +# ── 검색어 짓기 ─────────────────────────────────────────────────────── +def _name(v) -> str: + """품명 — 띄어 쓴 한글 글자는 붙임(「골 함 석」) · 쉼표 둘레 빈칸 뗌 · 영문 낱말 사이는 둠.""" + s = re.sub(r"\s*,\s*", ",", str(v or "").strip()) + return re.sub(r"(?<=[가-힣])\s+(?=[가-힣])", "", s) + + +def _root(name: str) -> str: + """이름 핵심 — 빈칸 뗌 · 첫 괄호·쉼표 앞(「박 리 제(중유)」 → 박리제).""" + return re.split(r"[(\[,]", _name(name))[0] + + +def _pieces(spec) -> list[str]: + """규격 조각 — 빈칸·쉼표로 · 괄호 속 쉼표는 안 가름(「저유황(목재,합판용)」).""" + return [p for p in re.split(r"\s+|,(?![^()]*\))", str(spec or "")) if p] + + +def _core(row: dict) -> list[str]: + """이름 핵심(첫 쉼표 앞 · 괄호 그대로 「합판(내수)」) + 규격 핵심(첫 조각 · 「12t×1220×…」 → + 글자 든 첫 토막 12t).""" + name = _name(row.get("이름")).split(",")[0] + pieces = _pieces(row.get("규격")) + spec = "".join(pieces[:2]) if pieces and len(pieces[0]) < 2 else (pieces or [""])[0] + head = re.split(r"[×xX*]", spec)[0] + if head != spec and re.search(r"[^\d.]", head): + spec = head + return [w for w in (name, spec) if w] + + +def _tokens(row: dict) -> list[str]: + """더할 낱말 후보 — 이름 조각(쉼표로) · 이름 괄호 · 규격 조각(쉼표·빈칸·×로).""" + name = _name(row.get("이름")) + parts = [p for p in name.split(",") if p] + out = parts + re.findall(r"\([^()]*\)", name) + pieces = _pieces(row.get("규격")) + out += pieces + [q for p in pieces for q in re.split(r"[×xX*]", p) if q and q != p] + return list(dict.fromkeys(w for w in out if w)) + + +def _place(row: dict) -> str | None: + tail = _name(row.get("이름")).split(",")[-1] + return tail if tail in mp.REGIONS else None + + +def _ok(master, elem, unit, cases) -> bool: + """판정 — 입력 벌마다 서버 찾기 첫째가 지금 품목.""" + for env, want in cases: + filled = {k: mf.fill(v, env) if isinstance(v, str) else v for k, v in elem.items()} + if first(master, filled, unit, env) != want: + return False + return True + + +def words_for(master, base: dict, words: list[str], unit, cases) -> list[str] | None: + """모자라면 첫 품목의 낱말을 하나씩 더함 — 앞선 줄을 가장 많이 떨구는 낱말부터.""" + target_rows = {want for _, want in cases} + for _ in range(6): + elem = {**base, "검색어": " ".join(words)} + if _ok(master, elem, unit, cases): + return words + if len(target_rows) != 1 or None in target_rows: + return None # 입력 따라 품목이 갈리는 줄 — 끼운 낱말로만 + want = master.get(next(iter(target_rows))) + env = cases[0][0] + got = found(master, {k: mf.fill(v, env) for k, v in elem.items()}, unit, env) + ahead = got[: got.index(want["키"])] if want["키"] in got else got + pick = None + for w in _tokens(want): + if w in words or not _has(w, want): + continue + cut = sum(not _has(w, master.get(k)) for k in set(ahead)) + if cut and (pick is None or cut > pick[0]): + pick = (cut, w) + if not pick: + return None + words.append(pick[1]) + return None + + +def convert(master: mf.Master, row: dict, item: dict) -> tuple[dict | None, list]: + """재료 줄 하나 → 새 요소 · 판정 벌. 못 맞추면 (None, 벌).""" + elem, unit = item["요소"], item.get("단위") + cases = [] + for env in envs(master, row, elem): + cases.append((env, old_pick(master, elem, env, unit))) + base = {k: elem[k] for k in ("구분", "상세구분") if elem.get(k)} + # 이미 좁혀 둔 이름·규격 낱말은 그대로 검색어로(`{관경}` 같은 끼운 칸도) + words = [w for k in ("이름", "규격") for w in str(elem.get(k) or "").split()] + targets = {want for _, want in cases} + if targets == {None}: + # 지금 같은 단위 후보 0 인 빈 줄 — 후보 단위가 줄 단위와 같은 성격(g ↔ kg 등)일 때만 + # 검색어로(값이 생김 · 금액 바뀜 · 줄마다 보고) · 성격이 다르면 빈칸 그대로 + probe = {**base, "검색어": _root(item.get("이름"))} + got = mp.search(master, probe, mau.values(master, row), unit) + if not got or not mp.same_kind(master.get(got[0])["단위"], unit): + return None, cases + name = _core(master.get(got[0]))[0] + return {**base, "검색어": name}, cases + if len(targets) == 1: + want = master.get(next(iter(targets))) + name, *spec = _core(want) + if not any(mm.plain(w) in mm.plain(name) for w in words): + words.insert(0, name) # 조건 이름 낱말이 이미 품명을 가리키면 그대로 + if spec and not elem.get("규격") and spec[0] not in words: + words.append(spec[0]) + place = _place(want) + if place and place not in words: + words.append(place) + else: + roots = {_root(master.get(t)["이름"]) for t in targets if t} + if ( + len(roots) == 1 + and (root := roots.pop()) + and not any(mm.plain(root) in mm.plain(w) for w in words) + ): + words.insert(0, root) + got = words_for(master, base, list(words), unit, cases) + return ({**base, "검색어": " ".join(got)} if got else None), cases + + +# ── 돌리기 ──────────────────────────────────────────────────────────── +def _targets(item: dict) -> bool: + elem = item.get("요소") + return ( + item.get("종류") == "재료" + and isinstance(elem, dict) + and "검색어" not in elem + and elem.get("지역") != "입력" + and elem.get("계약종별") != "입력" + ) + + +def plan(master: mf.Master, files: dict[str, dict]) -> tuple[dict, list]: + """파일마다 {로직 키: {"요소": {호표 차례: 새 요소}, "걷음": [입력 이름]}} · 못 맞춘 줄.""" + out, misses = {}, [] + for name, data in files.items(): + if not name.startswith("로직_"): + continue + for row in data["줄"]: + elems = {} + for i, item in enumerate(row.get("호표", [])): + if not _targets(item): + continue + elem, cases = convert(master, row, item) + if elem is None: + misses.append((row["키"], i, item.get("이름"), item["요소"], cases)) + else: + elems[i] = elem + gone = list(material_inputs(row)) + if elems or gone: + out.setdefault(name, {})[row["키"]] = {"요소": elems, "걷음": gone} + return out, misses + + +_ARG = r"\s*,\s*{n}\s*=\s*{n}(?=\s*[,)])" +# 걷는 입력을 식에서 품목 키로 쓰던 자리(`MT:{합판}`) — 같은 값의 호표 줄 금액으로 +# (GC000211 소모자재: 줄 금액 = 찾기 수량 × 합판율 × 단가 이므로 합판율을 줄 금액이 품음) +SWAP = { + "(찾기(QC000427).합판 * MT:{합판} + 찾기(QC000427).각재 * MT:{각재}) * 합판율 * ": ( + "(줄.'합판(내수 12㎜)' + 줄.'각재') * " + ), +} + + +def _strip(text: str, names) -> str: + """부르는 식의 `, 철선=철선` 인자를 뗌 · 품목 키 자리는 줄 금액으로.""" + for n in names: + text = re.sub(_ARG.format(n=re.escape(n)), "", text) + for old, new in SWAP.items(): + text = text.replace(old, new) + return text + + +def apply(row: dict, change: dict) -> dict: + """바꿈을 줄 사본에 — 새 요소 · 재료 입력 걷음 · 그 입력을 넘기던 인자 뗌.""" + new = _copy(row) + for i, elem in change["요소"].items(): + new["호표"][i]["요소"] = elem + gone = change["걷음"] + if gone: + new["입력"] = [s for s in new["입력"] if s["이름"] not in gone] + for group in ("중간", "호표", "덧줄"): + for part in new.get(group, []): + for k in ("식", "요소", "수량"): + if isinstance(part.get(k), str): + part[k] = _strip(part[k], gone) + if "결과" in new: + new["결과"]["식"] = _strip(new["결과"]["식"], gone) + return new + + +def _copy(v): + if isinstance(v, dict): + return {k: _copy(x) for k, x in v.items()} + if isinstance(v, list): + return [_copy(x) for x in v] + return v + + +# ── 글 모양 그대로 쓰기 ────────────────────────────────────────────────── +_DEC = json.JSONDecoder() + + +def _skip(text: str, pos: int) -> int: + while text[pos] in " \t\r\n": + pos += 1 + return pos + + +def _items(text: str, pos: int) -> list[tuple[int, int]]: + """`[` 바로 뒤 `pos` 부터 목록 칸마다 (처음, 끝).""" + spans = [] + while text[pos := _skip(text, pos)] != "]": + _, end = _DEC.raw_decode(text, pos) + spans.append((pos, end)) + pos = _skip(text, end) + pos += text[pos] == "," + return spans + + +def _patch_row(text: str, row: dict, change: dict) -> str: + """줄 글 하나 — 바뀐 요소 값 자리 · 걷은 입력 칸 · 뗀 인자만 갈음.""" + spots = [m.end() for m in re.finditer(r'"요소"\s*:\s*', text)] + if len(spots) != len(row.get("호표", [])): + raise ValueError(f"{row['키']} 요소 자리 수가 호표와 다름") + for i in sorted(change["요소"], reverse=True): + at = spots[i] + _, end = _DEC.raw_decode(text, at) + text = text[:at] + json.dumps(change["요소"][i], ensure_ascii=False) + text[end:] + gone = change["걷음"] + if gone: + m = re.search(r'"입력"\s*:\s*\[', text) + spans = _items(text, m.end()) + keep = [text[s:e] for s, e in spans if json.loads(text[s:e]).get("이름") not in gone] + lead, close = text[m.end() : spans[0][0]], _skip(text, spans[-1][1]) + tail = text[spans[-1][1] : close] + body = lead + ("," + lead).join(keep) + tail if keep else "" + text = text[: m.end()] + body + text[close:] + text = re.sub(r'"(?:[^"\\]|\\.)*"', lambda s: _strip(s.group(0), gone), text) + return text + + +def patch(text: str, changes: dict) -> str: + """파일 글 — 바뀐 로직 줄만 손봄.""" + m = re.search(r'"줄"\s*:\s*\[', text) + spans = _items(text, m.end()) + parts, at = [], 0 + for s, e in spans: + row = json.loads(text[s:e]) + if row["키"] in changes: + parts += [text[at:s], _patch_row(text[s:e], row, changes[row["키"]])] + at = e + return "".join(parts) + text[at:] + + +def main(write: bool) -> int: + files = cm.load() + master = mf.Master(files) + changes, misses = plan(master, files) + lines = sum(len(c["요소"]) for rows in changes.values() for c in rows.values()) + inputs = sum(len(c["걷음"]) for rows in changes.values() for c in rows.values()) + for name, rows in changes.items(): + path = MASTER / name + text = path.read_text(encoding="utf-8") + new_text = patch(text, rows) + want = json.loads(text) + want["줄"] = [apply(r, rows[r["키"]]) if r["키"] in rows else r for r in want["줄"]] + if json.loads(new_text) != want: + raise SystemExit(f"{name} 글 갈음이 바꿈과 다름 — 안 씀") + if write: + path.write_text(new_text, encoding="utf-8", newline="\n") + print( + f"검색어로 바꾼 재료 줄 {lines} · 걷은 재료 입력 {inputs} · " + f"로직 {sum(map(len, changes.values()))} · 파일 {len(changes)}" + + ("" if write else " (마른 돌리기)") + ) + for key, i, name, elem, cases in misses: + wants = sorted({str(w) for _, w in cases}) + print(f" 못 맞춤 {key} 호표[{i}] {name} {json.dumps(elem, ensure_ascii=False)} → {wants}") + return 0 + + +if __name__ == "__main__": + sys.stdout.reconfigure(encoding="utf-8") + sys.exit(main("--쓰기" in sys.argv)) diff --git a/resources/master_data/scripts/master_auto.py b/resources/master_data/scripts/master_auto.py new file mode 100644 index 00000000..32911604 --- /dev/null +++ b/resources/master_data/scripts/master_auto.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +"""견본 입력 — 마스터 화면 보기용 자동값(프로젝트 기본값 아님 · 저장 안 함). + +고르기 = 목록 첫째 · 범위 = 최솟값 · 찾기 조건 값으로 쓰이는 수 = 그 표 그 칸 가장 낮은 줄 값 +(범위 줄이면 그 줄에 드는 아래 끝) · 나누는 자리에 오는 수 = 1 · 그 밖 0. +찾기·나누기 쓰임은 로직 식(중간 · 결과 · 호표 수량·요소 · 덧줄)을 풀어 알아냄. +""" + +from __future__ import annotations + +from decimal import Decimal + +import master_formula as mf + + +def _texts(row: dict): + for step in row.get("중간", []): + yield step["식"] + if "결과" in row: + yield row["결과"]["식"] + for item in row.get("호표", []): + yield item["수량"] + if isinstance(item.get("요소"), str): + yield item["요소"] + for extra in row.get("덧줄", []): + yield extra["식"] + + +def _names(node) -> set: + return {part[1] for part in mf.walk(node) if part[0] == "name"} + + +def _uses(row: dict) -> tuple[dict, set]: + """(찾기 조건 값으로 바로 쓰인 이름 → [(표, 조건)], 나누는 자리에 든 이름) + — 나누는 자리에 든 중간의 식에 쓰인 이름도 나누는 자리로 봄.""" + finds, divs = {}, set() + for text in _texts(row): + try: + node = mf.parse(text) + except mf.FormulaError: + continue + for part in mf.walk(node): + if part[0] == "bin" and part[1] == "/": + divs |= _names(part[3]) + if part[0] == "find": + for cond, arg in part[2]: + if arg[0] == "name": + finds.setdefault(arg[1], []).append((part[1], cond)) + for step in reversed(row.get("중간", [])): + if step["이름"] in divs: + divs |= _names(mf.parse(step["식"])) + return finds, divs + + +def _fits(master: mf.Master, ref: str, cond: str) -> list: + """표 `ref` 조건 칸 `cond` 에 드는 수 — 낮은 차례 · 범위 칸이면 줄마다 그 줄에 드는 끝 + (아래 끝이 열린 줄 「60 미만」 은 0).""" + try: + table = master.get(ref) + except mf.FormulaError: + return [] + ranged = table.get("조건", {}).get(cond) == "범위" + out = set() + for r in table.get("줄", []): + if r.get(cond) is None: # 빈 조건 칸은 무엇이든 맞음 — 값을 정하지 않음 + continue + low, high = r[cond] if ranged else (r[cond], None) + ends = [Decimal(0) if low is None else low, high] + out |= {v for v in ends if isinstance(v, Decimal) and mf._match(table, r, cond, v)} + return sorted(out) + + +def _lowest(master: mf.Master, uses: list): + """찾기 여럿이 같은 입력을 쓰면 모든 표에 드는 가장 낮은 수 · 없으면 첫 표의 가장 낮은 수.""" + sets = [_fits(master, ref, cond) for ref, cond in uses] + both = sorted(set.intersection(*map(set, sets))) if sets else [] + return (both or next((s for s in sets if s), [None]))[0] + + +def values(master: mf.Master, row: dict) -> dict: + """로직 줄 하나의 입력마다 자동값.""" + finds, divs = _uses(row) + out = {} + for spec in row.get("입력", []): + name = spec["이름"] + if spec.get("고르기"): + out[name] = spec["고르기"][0] + elif spec.get("범위"): + out[name] = spec["범위"][0] + else: + got = _lowest(master, finds.get(name, [])) + if got is None and "원" in str(spec.get("단위") or ""): + out[name] = None # 가격 입력 — 0 원처럼 보이지 않게 비워 둠 + else: + out[name] = got if got is not None else Decimal(1 if name in divs else 0) + return out diff --git a/resources/master_data/scripts/master_copy.py b/resources/master_data/scripts/master_copy.py index 355164f4..cf9148ed 100644 --- a/resources/master_data/scripts/master_copy.py +++ b/resources/master_data/scripts/master_copy.py @@ -14,9 +14,12 @@ import re import master_formula as mf -MAX_NAME = 12 # 이 글자 수를 넘는 표 이름은 억지로 줄이지 않고 표 키를 그대로 보임 +MAX_NAME = 12 # 짧은 이름의 길이 상한 +MAX_WORD = 8 # 별칭 꼬리(찾은 줄 값) 한 낱말의 길이 상한 _DROP = re.compile(r"[^\w]", re.UNICODE) # 별칭에 못 쓰는 글자(빈칸 · 괄호 · 가운뎃점 …) _SPACE = re.compile(r"\s+") +_PAREN = re.compile(r"\([^()]*\)") +_WORDS = re.compile(r"[\s·・,/:+×*~∼]+") _QUOTED = re.compile(r"'[^']*'") RESERVED = frozenset((*mf.FUNCS, *mf.COST_ITEMS, "찾기", "로직", "그리고", "또는", "계", "줄")) @@ -98,15 +101,146 @@ def plain(text) -> str: return _DROP.sub("", str(text or "")) +def _unparen(text: str, keep: bool) -> str: + """괄호 묶음을 벗김 — `keep` 이면 숫자 없는 묶음(공기식 · 건설)만 글로 남김.""" + while True: + got = _PAREN.sub( + lambda m: f" {m.group()[1:-1]} " if keep and not re.search(r"\d", m.group()) else " ", + text, + ) + if got == text: + return got + text = got + + +def _core(text: str) -> list[str]: + """앞 핵심 낱말 — 머리 번호(1급 · 5-3)는 건너뛰고, 그 뒤 숫자 든 낱말(규격)에서 끊음.""" + out: list[str] = [] + for word in _WORDS.split(text): + word = plain(word) + if not word: + continue + if re.search(r"\d", word): + if out: + break + continue + out.append(word) + return out + + +def _fit(words: list[str], cap: int) -> str: + """낱말들을 이어 이름 하나로 — 길면 첫 낱말 + 끝 낱말 · 그래도 길면 첫 낱말을 자름.""" + tries = ["".join(words), *([words[0] + words[-1], words[0][:cap]] if words else [])] + return next((one for one in tries if one and len(one) <= cap), "") + + +def short(text, cap: int = MAX_NAME) -> str: + """사람이 읽는 짧은 이름 — 규격·코드를 떼고, 길면 괄호 속 · 가운데 낱말 순으로 버림. + 늘 같은 글이면 같은 이름 · 글자 · 숫자 · 밑줄만 · 숫자로 시작하지 않음(못 지으면 빈 글).""" + text = str(text or "") + kept = "".join(_core(_unparen(text, True))) + return kept if kept and len(kept) <= cap else _fit(_core(_unparen(text, False)), cap) + + def _base(ref: str, master) -> str: - """별칭 밑이름 — 표·로직의 `이름` 칸에서 · 비거나 너무 길면 키를 그대로 보임.""" + """별칭 밑이름 — 표·로직 이름을 짧게 · 이름이 없으면 공종 · 상세구분 · 그래도 없으면 키.""" try: - name = plain(master.get(ref).get("이름")) + row = master.get(ref) except mf.FormulaError: - name = "" - if not name or len(name) > MAX_NAME or name[0].isdigit(): - return _DROP.sub("", ref) or "표" - return name + row = {} + use = row.get("용도") if isinstance(row.get("용도"), dict) else {} + for text in (row.get("이름"), use.get("공종"), row.get("상세구분")): + got = short(text) + if got: + return got + return _DROP.sub("", ref) or "표" + + +def _words(node, master) -> list[str]: + """부르기 인자 하나의 낱말 — 글은 그 값(요소 코드면 품명 + 규격) · 수 · 이름은 그대로.""" + kind = node[0] + if kind == "name": + return [node[1]] + if kind == "num": + return [plain(node[1])] + if kind != "str": # 만약(…) 따위 — 안에 적힌 글 가운데 처음 읽히는 것 + return next(filter(None, (_core(_unparen(one, False)) for one in _texts(node))), []) + for ref in (node[1], f"EQ:{node[1]}"): # 기계 입력은 원문번호(0201-0020) + try: + row = master.get(ref) + except mf.FormulaError: + continue + if row.get("이름"): + return [short(row["이름"]), *filter(None, [plain(row.get("규격"))])] + return _core(_unparen(str(node[1]), False)) or [plain(node[1])] + + +def _texts(node): + """마디 안의 글 값(작은따옴표) — 적힌 차례로.""" + if isinstance(node, tuple) and node[:1] == ("str",): + yield str(node[1]) + elif isinstance(node, (tuple, list)): + for one in node: + yield from _texts(one) + + +def _columns(signs: list[str], refs: list[str], master) -> list[dict[str, str]]: + """부르기마다 {칸: 꼬리 낱말} — 다른 표가 섞이면 표 제목도 한 칸(`""`). + 한 칸 값들이 모두 가진 낱말(`구체콘크리트`)은 떼어 서로 가르는 낱말만 남김.""" + rows: list[dict[str, list[str]]] = [{} for _ in signs] + if len(set(refs)) > 1: + for one, ref in zip(rows, refs): + try: + one[""] = _core(_unparen(str(master.get(ref).get("이름") or ""), False)) + except mf.FormulaError: + one[""] = [] + for one, sign in zip(rows, signs): + try: + args = mf.parse(sign)[2] + except (mf.FormulaError, IndexError, TypeError): + args = [] + one.update({key: _words(value, master) for key, value in args}) + out: list[dict[str, str]] = [{} for _ in signs] + for key in dict.fromkeys(key for one in rows for key in one): + lists = [one.get(key, []) for one in rows] + common = set.intersection(*map(set, lists)) + if not all(set(words) - common for words in lists): + common = set() + for got, words in zip(out, lists): + got[key] = _fit([w for w in words if w not in common], MAX_WORD) + return out + + +def _tails(signs: list[str], refs: list[str], master) -> list[str]: + """같은 밑이름의 부르기들 — 서로 가르는 인자 값만 이어 꼬리로(`철망` · `외업_고급기술자`). + 가르는 칸은 적게 — 가장 잘 가르는 칸부터 모두 갈릴 때까지 고르고, 줄마다 없어도 되는 칸은 뺌.""" + cols = _columns(signs, refs, master) + order = list(dict.fromkeys(key for one in cols for key in one)) + + def seen(one: dict, keys: list[str]) -> tuple: + return tuple(one.get(key, "") for key in keys) + + picked: list[str] = [] + left = list(order) + while len({seen(one, picked) for one in cols}) < len(cols) and left: + best = max(left, key=lambda key: len({seen(one, [*picked, key]) for one in cols})) + if len({seen(one, [*picked, best]) for one in cols}) == len( + {seen(o, picked) for o in cols} + ): + break + picked.append(best) + left.remove(best) + out = [] + for at, one in enumerate(cols): + mine = list(picked) + for key in reversed(picked): # 늦게 고른(덜 가르는) 칸부터 빼 봄 + rest = [k for k in mine if k != key] + others = (seen(o, rest) for j, o in enumerate(cols) if j != at) + if not rest or seen(one, rest) in set(others): + break + mine = rest + out.append("_".join(filter(None, seen(one, sorted(mine, key=order.index))))) + return out def _free(want: str, taken: set[str]) -> str: @@ -144,8 +278,11 @@ def aliases(row: dict, master) -> list[dict]: taken |= set(_about(kind, ref, master)["값칸"]) for base, signs in bunch.items(): gap = "_" if base[-1:].isdigit() else "" # 키를 밑이름으로 쓴 자리 — 번호가 붙지 않게 - for at, sign in enumerate(signs): - name_of[sign] = _free(f"{base}{gap}{at + 1}" if len(signs) > 1 else base, taken) + tails = _tails(signs, [seen[s][1] for s in signs], master) if len(signs) > 1 else [""] + for at, (sign, tail) in enumerate(zip(signs, tails)): + alone = len(signs) == 1 or any(tails) # 꼬리 없는 하나는 밑이름만 + want = f"{base}_{tail}" if tail else base if alone else f"{base}{gap}{at + 1}" + name_of[sign] = _free(want, taken) out = [] for sign in order: diff --git a/resources/master_data/scripts/master_formula.py b/resources/master_data/scripts/master_formula.py index 2453ff82..83cc99f5 100644 --- a/resources/master_data/scripts/master_formula.py +++ b/resources/master_data/scripts/master_formula.py @@ -32,6 +32,26 @@ class FormulaError(ValueError): pass +class EmptyInput(FormulaError): + """값을 안 넣은(null) 입력을 씀 — 그 줄만 비우고 로직은 계속(`run`).""" + + +class NoRow(EmptyInput): + """표에서 조건에 맞는 줄이 하나가 아님 — 그 찾기를 쓰는 줄만 비움(값이 비어 있는 입력과 같은 길).""" + + +class Blank: + """못 구한 중간값 — 이 값을 쓰는 줄만 까닭과 함께 비움.""" + + def __init__(self, why: str): + self.why = why + + +def _plain(v) -> str: + """사람이 읽는 값 글 — 파이썬 날글(Decimal('1000') · 따옴표) 없이.""" + return format(v.normalize(), "f") if isinstance(v, Decimal) else str(v) + + # ── 읽기 ────────────────────────────────────────────────────────────── class _Parser: def __init__(self, text: str): @@ -247,6 +267,7 @@ class Master: if number in slot: self.twice.add((key[:2], number)) slot.setdefault(number, row) + mp.EQ.set(self) # 기계 입력이 목록 밖 원문 분류번호도 받게 def get(self, ref: str) -> dict: tid, _, number = ref.partition(":") @@ -295,7 +316,9 @@ def fill(template: str, env: dict) -> str: if m.group(1) not in env: raise FormulaError(f"원문번호의 {{{m.group(1)}}} 값 없음") value = env[m.group(1)] - return format(value.normalize(), "f") if isinstance(value, Decimal) else str(value) + if isinstance(value, Blank): + raise EmptyInput(value.why) + return _plain(value) return _HOLE.sub(one, template) @@ -349,7 +372,13 @@ def find(table: dict, conds: dict, want: str | None = None) -> dict: if len(hits) > 1 and want: # 여러 줄이면 찾는 값 칸이 있는 줄만 hits = [row for row in hits if want in row] if len(hits) != 1: - raise FormulaError(f"「{_name(table)}」 {conds} 맞는 줄 {len(hits)}개") + where = f"표 {table.get('키')}({table.get('원문번호')})" + what = " · ".join(f"{c} {_plain(v)}" for c, v in conds.items()) + raise NoRow( + f"{where}에 {what} 인 줄이 없음" + if not hits + else f"{where}에 {what} 인 줄이 {len(hits)}개" + ) return hits[0] @@ -360,6 +389,10 @@ def evaluate(node, env: dict, master: Master, depth: int = 0): if kind == "name": if node[1] not in env: raise FormulaError(f"모르는 이름 「{node[1]}」") + if env[node[1]] is None: + raise EmptyInput(f"입력 「{node[1]}」 비어 있음") + if isinstance(env[node[1]], Blank): + raise EmptyInput(env[node[1]].why) return env[node[1]] if kind == "ref": value = element(master, fill(node[1], env), env)[0] @@ -443,6 +476,16 @@ def cut(row: dict, value: Decimal, on: str) -> Decimal: return _round(value, rule.get("자리") or 0, way) +def fill_empty(row: dict, given: dict) -> dict: + """화면이 안 보낸 가격 입력(단위에 「원」)은 null 로 채움 — 빈 입력과 같이 그 줄만 비움.""" + gap = { + s["이름"]: None + for s in row.get("입력", []) + if s["이름"] not in given and "원" in str(s.get("단위") or "") + } + return {**given, **gap} + + def _inputs(row: dict, given: dict) -> dict: env = {} for spec in row.get("입력", []): @@ -460,11 +503,12 @@ def _inputs(row: dict, given: dict) -> dict: pass if isinstance(value, (int, float)): value = Decimal(str(value)) - if "고르기" in spec and value not in spec["고르기"]: + value = mp.resolve(spec, value) # 기계 EQ 키 → 원문 분류번호 + if "고르기" in spec and value not in spec["고르기"] and not mp.machine(spec, value): raise FormulaError(f"입력 「{name}」={value} 고르기 밖") if "범위" in spec: low, high = spec["범위"] - if not low <= value <= high: + if value is not None and not low <= value <= high: raise FormulaError(f"입력 「{name}」={value} 범위 [{low}, {high}] 밖") env[name] = value unknown = set(given) - set(env) @@ -473,22 +517,51 @@ def _inputs(row: dict, given: dict) -> dict: return env +def _shown(env: dict) -> dict: + """돌려줄 중간값 — 못 구한 값은 null.""" + return {k: None if isinstance(v, Blank) else v for k, v in env.items()} + + +def _blank_line(item: dict, why: str) -> dict: + """비어 있는 입력·못 구한 값을 쓰는 호표 줄 — 단가·금액만 비우고 까닭.""" + return { + "이름": item.get("이름", ""), + "단위": item.get("단위", ""), + "수량": None, + "단가": None, + "금액": None, + "비목": {item["비목"]: None} if "비목" in item else {}, + "까닭": why, + } + + def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict: """로직 한 줄 계산 — 돈 로직은 호표 줄 · 비목 합 · 계, 돈 아닌 로직은 결과 수 하나.""" row = master.logic(ref) env = _inputs(row, given) for step in row.get("중간", []): - env[step["이름"]] = evaluate(parse(step["식"]), env, master, depth) + try: + env[step["이름"]] = evaluate(parse(step["식"]), env, master, depth) + except EmptyInput as e: # 못 구한 중간값 — 쓰는 줄만 비움 + env[step["이름"]] = Blank(str(e)) if "결과" in row: value = evaluate(parse(row["결과"]["식"]), env, master, depth) - return {"결과": cut(row, value, "결과"), "중간": env} + return {"결과": cut(row, value, "결과"), "중간": _shown(env)} sums = dict.fromkeys(COST_ITEMS, Decimal(0)) lines = [] - for item in row.get("호표", []): - qty = evaluate(parse(item["수량"]), env, master, depth) - source = None + for n, item in enumerate(row.get("호표", [])): + source = why = extra = None + try: + qty = evaluate(parse(item["수량"]), env, master, depth) + except EmptyInput as e: # 비어 있는 입력을 쓰는 줄만 비움 + lines.append(_blank_line(item, str(e))) + continue if item["종류"] == "로직": - bundle = evaluate(parse(item["요소"]), env, master, depth) + try: + bundle = evaluate(parse(item["요소"]), env, master, depth) + except EmptyInput as e: + lines.append(_blank_line(item, str(e))) + continue price = bundle["계"] split = ( {k: qty * bundle[k] for k in COST_ITEMS} @@ -498,42 +571,51 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict: else: # 고르기 조건 묶음은 그대로 넘김 — `element` 가 대표 줄·첫 줄로 풂 # 조건 값의 `{입력}`(관경 등)도 채움 — 규격이 입력 따라 바뀌는 관 줄 - ref = ( - {k: fill(v, env) if isinstance(v, str) else v for k, v in item["요소"].items()} - if isinstance(item["요소"], dict) - else fill(item["요소"], env) - ) try: - price, source = element(master, ref, env, item.get("단위")) - except FormulaError: - if qty != 0: - raise - price = None + ref = ( + {k: fill(v, env) if isinstance(v, str) else v for k, v in item["요소"].items()} + if isinstance(item["요소"], dict) + else fill(item["요소"], env) + ) + except EmptyInput as e: + lines.append(_blank_line(item, str(e))) + continue + # 후보 0 · 값 빈 품목은 그 줄만 단가·금액을 비우고 까닭을 닮 — 로직은 계속 · 합에서 뺌 + # (없는 키는 틀린 로직 — 지금처럼 멈춤) · 검색어 · 고름 · 단위 환산은 `master_pick` + if isinstance(ref, str) and qty != 0: + master.get(ref) + price, source, why, extra = mp.priced(master, item, ref, env, n if depth == 0 else None) if price is None and qty == 0: - price = Decimal(0) # 안 쓰는 재료 줄 — 값이 비어도 금액 0 - if price is None: - raise FormulaError(f"「{master.label(ref)}」 값 없음 — 관리자가 채울 값") - split = {item["비목"]: qty * price} + price, why = Decimal(0), None # 안 쓰는 재료 줄 — 값이 비어도 금액 0 + split = {item["비목"]: None if price is None else qty * price} for k, v in split.items(): - sums[k] += v + sums[k] += v or 0 lines.append( { "이름": item.get("이름", ""), "단위": item.get("단위", ""), "수량": qty, "단가": price, - "금액": qty * price, + "금액": None if price is None else qty * price, "비목": split, **({"출처": source} if source else {}), + **({"까닭": why} if why else {}), + **(extra or {}), } ) - # 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'` - env["줄"] = {item["이름"]: item["금액"] for item in lines if item["이름"]} + # 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'`(빈 줄은 뺌) + env["줄"] = {x["이름"]: x["금액"] for x in lines if x["이름"] and x["금액"] is not None} + blank = {x["이름"]: x["까닭"] for x in lines if x.get("까닭")} for extra in row.get("덧줄", []): env.update(sums) - value = evaluate(parse(extra["식"]), env, master, depth) - env[extra["이름"]] = value - sums[extra["비목"]] += value + try: # 덧줄도 값 빈 품목·빈 줄을 부르면 그 줄만 비움 + value, why = evaluate(parse(extra["식"]), env, master, depth), None + env[extra["이름"]] = value + except FormulaError as e: + gone = next((n for n in blank if n in extra["식"]), None) + value, why = None, f"「{gone}」 줄 비어 못 셈 — {blank[gone]}" if gone else str(e) + blank[extra["이름"]] = why + sums[extra["비목"]] += value or 0 lines.append( { "이름": extra["이름"], @@ -542,10 +624,11 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict: "단가": value, "금액": value, "비목": {extra["비목"]: value}, + **({"까닭": why} if why else {}), } ) sums = {k: cut(row, v, "계") for k, v in sums.items()} # 비목 합마다 끊고 계는 그 합 - return {"줄": lines, **sums, "계": sum(sums.values()), "중간": env} + return {"줄": lines, **sums, "계": sum(sums.values()), "중간": _shown(env)} # ── 검사 ────────────────────────────────────────────────────────────── @@ -765,3 +848,4 @@ from master_material import ( # noqa: E402, F401 element, pick, ) +import master_pick as mp # noqa: E402 — 검색어 · 고름 · 단위 diff --git a/resources/master_data/scripts/master_material.py b/resources/master_data/scripts/master_material.py index 2230825e..6d6237ac 100644 --- a/resources/master_data/scripts/master_material.py +++ b/resources/master_data/scripts/master_material.py @@ -16,7 +16,15 @@ LINKED = "MP" # 품셈재료 테이블ID MARKET = "MT" # 자재품목 테이블ID PRICED = "MO" # 유가전력 테이블ID — 값 한 칸 SLOTS = ("물가자료", "유통물가", "물가정보", "거래가격", "관급") # 자재품목 값 열 -PICK_KEYS = ("구분", "상세구분", "이름", "규격", "대표", "지역", "계약종별") # 재료 고르기 조건 칸 (`_틀.md` 7장) +PICK_KEYS = ( + "구분", + "상세구분", + "이름", + "규격", + "대표", + "지역", + "계약종별", +) # 재료 고르기 조건 칸 (`_틀.md` 7장) REGION = "자재지역" # 로직 입력 — 「지역」 은 유가 고르기가 씀 TARIFF = "전력 계약종별" # 로직 입력 — 품셈·계약예규에 계약종별 규정 없음 _UNITS = {"ea": "개", "ton": "톤", "t": "톤", "l": "ℓ", "리터": "ℓ", "set": "조", "세트": "조"} @@ -98,7 +106,9 @@ def _region(master: mf.Master, label_ref, cond: dict, env: dict, unit) -> list[s if not hits: raise mf.FormulaError(f"「{master.label(label_ref)}」 지역 값 없음 — {env.get(REGION)}") if len({s for s, _ in hits}) > 1: - raise mf.FormulaError(f"「{master.label(label_ref)}」 후보 여럿 — 규격 선택 필요 ({len(hits)}줄)") + raise mf.FormulaError( + f"「{master.label(label_ref)}」 후보 여럿 — 규격 선택 필요 ({len(hits)}줄)" + ) return [key for _, key in hits] @@ -110,7 +120,9 @@ def static(cond: dict) -> dict: def cond_text(cond: dict) -> str: """고르기 조건 한 글자 열쇠 — 「구분|상세구분|규격|대표」 · 화면 단가 미리보기가 줄을 찾는 이름.""" - return "|".join(str(cond.get(k) or "") for k in PICK_KEYS) + return "|".join(str(cond.get(k) or "") for k in PICK_KEYS) + ( + f"|{cond['검색어']}" if cond.get("검색어") else "" + ) def by_class(master: mf.Master) -> dict[str, dict]: @@ -130,7 +142,10 @@ def candidates( ) -> list[str]: """고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 이름 · 규격 낱말이 모두 든 줄 · 파일 차례대로. `unit` 을 주면 그 단위(호표 줄 단위)와 같은 줄만 — 단위가 다르면 다른 물건. - 입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부).""" + 입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부). + 조건에 `검색어` 가 있으면 기본 검색어 찾기(`master_pick`).""" + if cond.get("검색어"): + return mp.search(master, cond, env, unit) want = [plain_spec(w) for w in str(cond.get("규격") or "").split()] want_name = [plain(w) for w in str(cond.get("이름") or "").split()] detail = cond.get("상세구분") @@ -150,18 +165,23 @@ def candidates( def pick(master: mf.Master, cond: dict, env: dict, unit: str | None = None) -> str: """시험 계산이 쓸 줄 — 관리자가 정한 `대표`, 없으면 조건 안 값 있는 첫 줄(단위 같은 줄만).""" + if cond.get("검색어"): + return mp.pick(master, cond, env, unit) if cond.get("대표"): return str(cond["대표"]) keys = candidates(master, cond, env, unit) keys = [k for k in keys if _offers(master, k)] or keys if not keys: - raise mf.FormulaError(f"재료 고르기 후보 없음 — {cond}") + what = " · ".join(f"{k} {mf._plain(v)}" for k, v in cond.items()) + raise mf.FormulaError(f"재료 고르기 후보 없음 — {what}") return keys[0] def check_unit(master: mf.Master, where: str, cond: dict, unit: str | None) -> list[str]: """단위 경고 — 호표 줄·품셈재료 줄 단위와 맞는 후보가 없거나 대표 줄 단위가 다름. 틀린 데이터가 아니라 좁혀야 할 조건이라 `check_pick` 과 따로 봄(`check_master 단위`).""" + if cond.get("검색어"): + return mp.check_warn(master, where, cond, unit) cond = static(cond) rows = master.index.get(MARKET, {}) rep = str(cond.get("대표") or "") @@ -183,6 +203,8 @@ def check_pick(master: mf.Master, where: str, cond: dict, unit: str | None = Non 값에 `{입력}` 이 든 칸(관경 등)은 계산 때 채워지므로 검사에서 뺌. 지역·계약종별 조건은 구분 없이 이름 하나로 자재품목·유가전력을 넓게 찾음(_region · _tariff) — 여기서는 이름 칸만 있는지 봄(후보 0 은 계산 때 판단).""" + if cond.get("검색어") is not None and not set(cond) - {*PICK_KEYS, "검색어"}: + return mp.check_zero(master, where, cond) if set(cond) - set(PICK_KEYS): return [f"{where} · 재료 고르기 조건 모양 「{cond}」 — {' · '.join(PICK_KEYS)}"] if ("지역" in cond and cond["지역"] != "입력") or ( @@ -246,3 +268,4 @@ def element( import master_formula as mf # noqa: E402 — 서로 부름 · 이 모듈 이름이 다 선 뒤에 읽음 +import master_pick as mp # noqa: E402 — 검색어 찾기 diff --git a/resources/master_data/scripts/master_pick.py b/resources/master_data/scripts/master_pick.py new file mode 100644 index 00000000..445af9d9 --- /dev/null +++ b/resources/master_data/scripts/master_pick.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +"""재료 줄의 기본 검색어 찾기 · 고른 품목 · 단위 환산 · 단위 경고 (PLAN 8-1 계약). + +`master_formula.run` 이 재료 줄 단가를 풀 때 부름 — 요소 객체에 `검색어` 가 있으면 여기서 찾고, +없으면 옛 길(`master_material` 조건 · 대표). 서로 부르는 모듈이라 `master_formula` 는 끝에서 읽음. +""" + +from __future__ import annotations + +from contextlib import contextmanager +from contextvars import ContextVar +from decimal import Decimal + +import master_material as mm + +# 「같은 성격」 단위 — {성격: {정규 단위: 기준 단위 몇 배}} (키는 `plain_unit` 을 거친 꼴) +UNITS = { + "무게": {"g": 1, "kg": 1000, "톤": 1000000}, + "길이": {"mm": 1, "cm": 10, "m": 1000, "km": 1000000}, + "넓이": {"cm2": 1, "m2": 10000}, + "부피": {"cm3": 1, "ℓ": 1000, "m3": 1000000}, +} +_SCALE = {u: (kind, n) for kind, group in UNITS.items() for u, n in group.items()} +REGIONS = ( + "전국평균 전국 서울 경기 강원 충북 충남 전북 전남 경북 경남 부산 대구 인천 대전 울산 세종 제주 광주" + " 전남광주" +).split() + +CHOSEN: ContextVar[dict | None] = ContextVar("고름", default=None) # {호표 줄 차례(0부터): 자재 키} +EQ: ContextVar = ContextVar("기계번호", default=None) # 지금 쓰는 마스터 — 기계 줄을 찾음 + + +@contextmanager +def choosing(picks: dict | None): + """`/calc` · `/text` 몸의 `고름` — 그 요청 동안만 · 저장 안 함.""" + token = CHOSEN.set({str(k): str(v) for k, v in (picks or {}).items()}) + try: + yield + finally: + CHOSEN.reset(token) + + +def _numbers() -> dict: + return EQ.get().numbers.get("EQ", {}) if EQ.get() else {} + + +def resolve(spec: dict, value): + """기계 입력이 EQ 키로 오면 원문 분류번호로 풂 — 고르기 목록(원문번호)과 같은 꼴.""" + if not (isinstance(value, str) and value.strip()[:2] == "EQ" and EQ.get()): + return value + row = EQ.get().index.get("EQ", {}).get(value.strip()) + number = str(row.get("원문번호") or "") if row else "" + return number if number and machine(spec, number) else value + + +def machine(spec: dict, value) -> bool: + """기계 입력 — 고르기 목록 밖 원문 분류번호도 기계 마스터에 있으면 받음.""" + known = _numbers() + picks = spec.get("고르기") or [] + return bool(picks) and str(value).strip() in known and all(str(o) in known for o in picks) + + +# ── 단위 ─────────────────────────────────────────────────────────────── +def kind_of(unit) -> str | None: + got = _SCALE.get(mm.plain_unit(unit)) + return got[0] if got else None + + +def same_kind(a, b) -> bool: + """단위가 같거나 같은 성격(무게 · 길이 · 넓이 · 부피).""" + pa, pb = mm.plain_unit(a), mm.plain_unit(b) + return pa == pb or (kind_of(a) is not None and kind_of(a) == kind_of(b)) + + +def convert(price, have, want): + """`have` 단위 단가 → `want` 단위 단가 — 같은 성격이 아니면 그대로.""" + if price is None or not want or mm.plain_unit(have) == mm.plain_unit(want): + return price + if not same_kind(have, want) or kind_of(have) is None: + return price + return price * Decimal(_SCALE[mm.plain_unit(want)][1]) / Decimal(_SCALE[mm.plain_unit(have)][1]) + + +# ── 찾기 ─────────────────────────────────────────────────────────────── +def _words(term) -> tuple[list[str], list[str]]: + """(이름·규격에 든 낱말, 지역 낱말) — 지역 낱말은 고르는 차례에만 쓰임.""" + every = [mm.plain_spec(w) for w in str(term or "").split()] + region = [w for w in every if w in REGIONS] + return [w for w in every if w not in REGIONS], region + + +def search(master, cond: dict, env: dict | None = None, unit: str | None = None) -> list[str]: + """검색어 안 자재품목 키 — 낱말이 모두 이름 또는 규격에 든 줄(요소의 구분 · 상세구분 안에서) · + 단위는 같은 성격까지(하나도 없으면 다 — 경고가 붙음) · 고르는 차례: + 값 있는 줄 → 줄 단위와 똑같은 단위 → 지역 낱말(입력 `자재지역` 포함)과 맞는 줄 → 키 차례.""" + words, region = _words(cond.get("검색어")) + region += [mm.plain(env[mm.REGION])] if (env or {}).get(mm.REGION) else [] + rows = master.index.get(mm.MARKET, {}) + hits = [ + key + for _, name, spec, _, key in mm._market_index(master) + if all(w in name.replace("×", "x") or w in spec for w in words) + and (not cond.get("구분") or rows[key].get("구분") == cond["구분"]) + and (not cond.get("상세구분") or rows[key].get("상세구분") == cond["상세구분"]) + ] + if unit: + kin = [k for k in hits if same_kind(rows[k].get("단위"), unit)] + hits = kin or hits + want = mm.plain_unit(unit) if unit else None + + def rank(key): + one = rows[key] + name = mm.plain(one.get("이름")) + return ( + not mm._offers(master, key), + bool(want) and mm.plain_unit(one.get("단위")) != want, + not (region and all(r in name for r in region)), + ) + + return sorted(hits, key=rank) # 정렬은 안정 — 남는 차례는 파일 차례 + + +def pick(master, cond: dict, env: dict, unit: str | None = None) -> str: + keys = search(master, cond, env, unit) + if not keys: + raise mm.mf.FormulaError(f"재료 검색어 후보 없음 — {cond.get('검색어')}") + return keys[0] + + +def priced(master, item: dict, ref, env: dict, index: int | None): + """재료 줄 단가 — (단가, 출처, 까닭, 단위경고). 고른 품목(`고름`)이 있으면 그것 · 검색어가 있으면 찾기 · + 없으면 옛 길. 자재 단가는 줄 단위로 환산 · 성격이 다르면 그대로 두고 경고. + 넷째 = 줄에 덧붙일 칸 — `단위경고` · `품목`(잡힌 품목 키 · 이름 · 규격 · 단위 · 값 칸).""" + price, source, why, warn = _priced(master, item, ref, env, index) + extra = {"단위경고": warn} if warn else {} + key, _, slot = (source or "").partition(".") + try: + row = master.get(key) if key else None + except mm.mf.FormulaError: + row = None + if row: + extra["품목"] = { + "키": key, + "이름": row.get("이름"), + "규격": row.get("규격"), + "단위": row.get("단위"), + "값칸": slot, + } + return price, source, why, extra + + +def _priced(master, item: dict, ref, env: dict, index: int | None): + unit = item.get("단위") + picked = (CHOSEN.get() or {}).get(str(index)) if index is not None else None + try: + if picked or (isinstance(ref, dict) and ref.get("검색어")): + ref = picked or pick(master, ref, env, unit) + have = master.get(ref).get("단위") if ref[:2] == mm.MARKET else None + price, source = mm.element(master, ref, env, None) + warn = None + if have and unit and not same_kind(have, unit): + warn = f"줄 {unit} · 자재 {have}" + price = convert(price, have, unit) if have and unit else price + else: + price, source = mm.element(master, ref, env, unit) + warn = None + except mm.mf.FormulaError as e: + return None, None, str(e), None + why = None if price is not None else f"「{master.label(ref)}」 값 없음 — 관리자가 채울 값" + return price, source, why, warn + + +# ── 검사 ─────────────────────────────────────────────────────────────── +def check_zero(master, where: str, cond: dict) -> list[str]: + """검색어 후보 0 — `{입력}` 이 든 검색어는 계산 때 채워져 여기서 못 봄.""" + term = str(cond.get("검색어") or "") + if not term.strip() or "{" in term or any("{" in str(v) for v in cond.values()): + return [] if term.strip() else [f"{where} · 검색어 비어 있음"] + return [] if search(master, cond) else [f"{where} · 검색어 「{term}」 후보 0"] + + +def check_warn(master, where: str, cond: dict, unit: str | None) -> list[str]: + """단위 경고 줄 — 검색어 후보가 있으나 줄 단위와 같은 성격이 하나도 없음.""" + term = str(cond.get("검색어") or "") + if not unit or "{" in term or "{" in str(cond.get("구분") or ""): + return [] + keys = search(master, cond) + rows = master.index.get(mm.MARKET, {}) + if keys and not any(same_kind(rows[k].get("단위"), unit) for k in keys): + return [f"{where} · 검색어 「{term}」 후보 단위가 줄 단위 「{unit}」 과 성격이 다름"] + return [] + + +import master_formula as mf # noqa: E402, F401 — 서로 부름 · 이 모듈 이름이 다 선 뒤에 읽음 diff --git a/resources/master_data/scripts/master_text.py b/resources/master_data/scripts/master_text.py index 8acea2af..0d8f340e 100644 --- a/resources/master_data/scripts/master_text.py +++ b/resources/master_data/scripts/master_text.py @@ -17,10 +17,11 @@ from __future__ import annotations -from decimal import Decimal +from decimal import ROUND_HALF_UP, Decimal import master_copy as mc import master_formula as mf +import master_text_item as mti PLACES = Decimal("0.0001") # 화면 `formatNumber` 와 같음(소수 넷째 자리까지) PRICE_TAIL = {"인력": "노임", "기계": "손료"} # 단가 자리의 이름 꼬리(그 밖은 「단가」) @@ -33,10 +34,13 @@ _CALC_ERRORS = (mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueErro def fmt(value) -> str: - """화면에 보이는 자릿수 그대로 — 소수 넷째 자리까지 · 뒤 0 은 떼고 · 천 단위 쉼표.""" + """보이는 자릿수 — 소수 넷째 자리·유효숫자 4 자리 중 많은 쪽 · 뒤 0 뗌 · 천 단위 쉼표.""" if not isinstance(value, Decimal): return str(value) - cut = value.quantize(PLACES) if value.as_tuple().exponent < -4 else value + places = max(4, 3 - value.adjusted()) if value else 4 # 0.00167 → 0.001667 + cut = value + if value.as_tuple().exponent < -places: + cut = value.quantize(Decimal(1).scaleb(-places)) sign, digits = ("-", -cut) if cut < 0 else ("", cut) whole, _, rest = f"{digits.normalize():f}".partition(".") if "E" in whole or "e" in whole: # 아주 큰 수 — 지수 꼴을 펴서 씀 @@ -44,6 +48,19 @@ def fmt(value) -> str: return sign + f"{int(whole):,}" + (f".{rest}" if rest else "") +def fmt_money(value) -> str: + """돈(단가 · 금액)은 원 단위 정수로 보임 — 글만 그렇고 계산 값은 소수 그대로.""" + if isinstance(value, Decimal): + value = value.quantize(Decimal(1), rounding=ROUND_HALF_UP) + return fmt(value) + + +def fmt_price(value) -> str: + """단가 — 정수로 보이되 200 원 미만은 반올림 오차가 커서 유효숫자로.""" + small = isinstance(value, Decimal) and abs(value) < 200 + return fmt(value) if small else fmt_money(value) + + # ── 식을 글로 — 값 꼴과 이름 꼴 ──────────────────────────────────────── def render(node, env: dict, master, depth: int = 0) -> str: """식 한 마디 → 값 꼴 글. 값이 정해지는 마디(요소 · 찾기 · 로직 · 이름)는 그 수로 바뀜.""" @@ -73,7 +90,7 @@ def named(show, real, env: dict, master, depth: int = 0) -> str: if kind == "name": return show[1] if kind == "ref": - return _elem_name(master, show[1]) + return _elem_name(master, show[1], env) if kind == "field": return f"{named(show[1], real[1], env, master, depth)}.{show[2]}" if kind == "neg": @@ -82,8 +99,12 @@ def named(show, real, env: dict, master, depth: int = 0) -> str: return _bin(show, real, env, master, depth, named) if kind == "call": if show[1] == "만약": # 값 꼴과 같은 쪽만 적어 두 칸이 짝이 되게 - at = 1 if mf.evaluate(real[2][0], env, master, depth) else 2 - return named(show[2][at], real[2][at], env, master, depth) + try: + at = 1 if mf.evaluate(real[2][0], env, master, depth) else 2 + except _CALC_ERRORS: # 조건 입력이 비면 식 그대로(아래 부르기 꼴) + at = 0 + if at: + return named(show[2][at], real[2][at], env, master, depth) args = ", ".join(named(s, r, env, master, depth) for s, r in zip(show[2], real[2])) return f"{show[1]}({args})" if kind in ("num", "str"): @@ -91,17 +112,48 @@ def named(show, real, env: dict, master, depth: int = 0) -> str: return _value(real, env, master, depth) # 별칭이 안 붙은 부르기 — 값으로 -def _elem_name(master, ref: str) -> str: +def _hole(master, ref: str, env: dict | None) -> tuple[list[str], dict | None]: + """자리 표시(`XX:품목:{입력}` · `XX:{입력}`) 든 참조 → (읽는 글 조각들, 입력으로 풀린 요소 줄). + 글 = 앞 글 + 지금 입력값(「휘발유 · 전국평균」) · 구멍뿐이면 입력 이름이 앞(「장비 · 굴착기」). + 입력이 비면 입력 이름만(「휘발유 · 지역」) · 못 풀면 줄은 None.""" + env = env or {} + tail = ref[3:] + holes = mf._HOLE.findall(tail) + words = [w.strip(": ") for w in mf._HOLE.split(tail)[::2] if w.strip(": ")] + try: + row = master.get(mf.fill(ref, env)) + except mf.FormulaError: + row = None + pure = not words and len(holes) == 1 + shown = [] + for name in holes: + value = env.get(name) + if value is None: + shown.append(name) + elif pure and row is not None: # 입력 하나가 요소 자리 전부 — 그 요소 이름 + 규격 + shown.append(f"{row.get('이름') or value} {row.get('규격') or ''}".strip()) + else: + shown.append(fmt(value) if isinstance(value, Decimal) else str(value)) + head = words or holes[:1] + return head + [one for one in shown if one not in head], row + + +def _elem_name(master, ref: str, env: dict | None = None) -> str: """식 안 요소 한 줄의 이름 — 「석공노임」 꼴(같은 이름의 중간 값과 헷갈리지 않게).""" if ( str(ref)[:2] not in ELEM_KEYS ): # 별칭 이름이 표 열쇠 그대로인 것(QC…) — 밑이름을 풀어쓰지 않음 return ref + tail = ELEM_TAIL.get(str(ref)[:2], "단가") try: - name = mc.plain(master.get(ref).get("이름")) + if "{" in str(ref): # 입력으로 풀린 요소 이름 · 못 풀면 입력 이름 + parts, row = _hole(master, ref, env) + name = mc.short(row.get("이름")) if row is not None else mc.plain("".join(parts)) + else: + name = mc.short(master.get(ref).get("이름")) except mf.FormulaError: name = "" - return f"{name}{ELEM_TAIL.get(str(ref)[:2], '단가')}" if name else ref + return f"{name}{tail}" if name else ref def _value(node, env: dict, master, depth: int) -> str: @@ -121,8 +173,20 @@ def _bin(show, real, env: dict, master, depth: int, draw) -> str: ) +def _shown(show, real, env: dict, master, depth: int): + """만약() 은 걸린 갈래만 글이 됨 — 괄호를 정할 때는 그 갈래의 마디로 봄(못 가리면 그대로).""" + while show[0] == "call" and show[1] == "만약": + try: + at = 1 if mf.evaluate(real[2][0], env, master, depth) else 2 + except _CALC_ERRORS: + break + show, real = show[2][at], real[2][at] + return show + + def _side(show, real, parent, env: dict, master, depth: int, draw, right: bool) -> str: text = draw(show, real, env, master, depth) + show = _shown(show, real, env, master, depth) if show[0] != "bin": return text mine, up = _PREC[show[1]], _PREC[parent[1]] @@ -148,12 +212,17 @@ def _alias_index(marks: list[dict]) -> dict[str, dict]: return {m["이름"]: m for m in marks} -def _elem_piece(master, ref: str) -> dict: - """요소 알약 — 글은 이름(못 찾아도 이름만). 자리 표시(`{…}`) 든 참조 14파일은 - 값(참조 키)을 못 구하므로 이름만 적고 참조 자리는 비움.""" - piece = {"kind": "요소", "글": _elem_name(master, ref)} +def _elem_piece(master, ref: str, env: dict | None = None) -> dict: + """요소 알약 — 글은 이름(못 찾아도 이름만). 자리 표시 든 참조는 글 = 이름 + 지금 입력값 · + 참조 = 입력으로 풀린 실제 키(못 풀면 참조 없음).""" if "{" in str(ref): + parts, row = _hole(master, ref, env) + piece = {"kind": "요소", "글": " · ".join(parts)} + if row is not None: + key = str(row.get("키") or ref) + piece["참조"] = {"테이블": key[:2], "키": key} return piece + piece = {"kind": "요소", "글": _elem_name(master, ref)} try: row = master.get(ref) except mf.FormulaError: @@ -163,8 +232,22 @@ def _elem_piece(master, ref: str) -> dict: return piece +def _row_label(table: dict, row: dict, conds: dict) -> list[str]: + """찾은 줄의 이름 — 조건마다 그 줄의 값(범위 칸은 찾은 범위).""" + out = [] + for cond, want in conds.items(): + have = row.get(cond, want) + if table.get("조건", {}).get(cond) == "범위" and isinstance(have, list): + low, high = ("" if x is None else fmt(x) for x in have) + out.append(f"{low}~{high}") + else: + out.append(fmt(have)) + return out + + def _alias_piece(mark: dict, env: dict, master, column: str | None) -> dict: - """표 찾기·로직 부르기 알약 — 글은 별칭 이름. 참조는 다시 찾아 봐서 되면만 붙임.""" + """표 찾기·로직 부르기 알약 — 표는 글 = 「찾은 줄 · 칸」 · 참조.행 = 찾기 조건. + 다시 못 찾으면(입력이 비어서 따위) 별칭 이름만 남김.""" piece = {"kind": "표" if mark["종류"] == "찾기" else "로직", "글": mark["이름"]} try: node = mf.parse(mark["원문"]) @@ -173,32 +256,60 @@ def _alias_piece(mark: dict, env: dict, master, column: str | None) -> dict: table = master.get(ref) conds = {k: mf.evaluate(v, env, master, 0) for k, v in node[2]} row = mf.find(table, conds, column) - piece["참조"] = { - "테이블": ref[:2], - "키": ref, - "행": row.get("키") or row.get("원문번호"), - "열": column, - } + tail = column.rpartition("_")[2] if column else "" + piece["글"] = " · ".join([*_row_label(table, row, conds), *([tail] if tail else [])]) + at = next(i for i, one in enumerate(table["줄"]) if one is row) # find 가 고른 줄 + piece["참조"] = {"테이블": ref[:2], "키": ref, "행": conds, "열": column, "줄": at} else: + piece["글"] = str(master.logic(ref).get("이름") or ref) # 요소조각처럼 로직 이름 piece["참조"] = {"테이블": ref[:2], "키": ref} except _CALC_ERRORS: pass # 다시 못 찾음 — 별칭 이름만 남김(값 자리 비움) return piece -def _if_pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int) -> list[dict]: - """만약() 조각 — 계산되면 걸린 가지만 · 안 되면 양쪽 다(깊이를 적어 접을 수 있게).""" +def _ga(word: str) -> str: + """받침이 없으면 「가」 · 있으면 「이」.""" + last = word[-1:] + return "이" if "가" <= last <= "힣" and (ord(last) - 0xAC00) % 28 else "가" + + +def _easy(show, real, env: dict, master) -> str: + if show[0] == "str": + return show[1] try: - taken = mf.evaluate(real[2][0], env, master, 0) + return named(show, real, env, master) except _CALC_ERRORS: - deeper = ifdepth + 1 - left = _pieces(show[2][1], real[2][1], env, master, alias_by_name, deeper) - right = _pieces(show[2][2], real[2][2], env, master, alias_by_name, deeper) - for one in (*left, *right): - one["깊이"] = deeper - return [*left, _p_sym("/"), *right] - at = 1 if taken else 2 - return _pieces(show[2][at], real[2][at], env, master, alias_by_name, ifdepth) + return "" + + +def _cond_text(show, real, env: dict, master) -> str: + """만약 조건 → 쉬운 말 — 「폼타이 가 사용 이면」(같음) · 그 밖은 「식 이면」.""" + if show[0] == "bin" and show[1] == "==": + left = _easy(show[2], real[2], env, master) + return f"{left} {_ga(left)} {_easy(show[3], real[3], env, master)} 이면" + return f"{_easy(show, real, env, master)} 이면" + + +def _if_pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int) -> list[dict]: + """만약() 조각 — 「조건 이면 A · 아니면 B」 · 안 걸린 갈래는 흐림 · + 조건이 안 셈해지면(입력이 비어서 따위) 흐림 없이 두 갈래 다(깊이를 적어 접을 수 있게).""" + try: + taken = bool(mf.evaluate(real[2][0], env, master, 0)) + except _CALC_ERRORS: + taken = None + deeper = ifdepth if taken is not None else ifdepth + 1 + then = _pieces(show[2][1], real[2][1], env, master, alias_by_name, deeper) + other = _pieces(show[2][2], real[2][2], env, master, alias_by_name, deeper) + other.insert(0, {"kind": "글", "글": "· 아니면"}) + for one in then if taken is None else (): + one["깊이"] = deeper + for one in other if taken is None else (): + one["깊이"] = deeper + for one in then if taken is False else other if taken else (): + one["흐림"] = True + cond = {"kind": "글", "글": _cond_text(show[2][0], real[2][0], env, master)} + return [cond, *then, *other] def _side_pieces( @@ -237,15 +348,22 @@ def _pieces(show, real, env: dict, master, alias_by_name: dict, ifdepth: int = 0 if kind == "call" and show[1] == "만약": return _if_pieces(show, real, env, master, alias_by_name, ifdepth) if kind == "ref": - return [_elem_piece(master, show[1])] + return [_elem_piece(master, show[1], env)] if kind == "field" and show[1][0] == "name" and show[1][1] in alias_by_name: return [_alias_piece(alias_by_name[show[1][1]], env, master, show[2])] if kind == "name" and show[1] in alias_by_name: return [_alias_piece(alias_by_name[show[1]], env, master, None)] + piece = _p_val(show[1] if kind == "name" else "") # 중간 값은 글에 그 이름 + if kind == "field" and show[1] == ("name", "줄"): # 덧줄이 부른 호표 줄 — 비면 그 줄 이름 + piece["글"] = show[2] try: - return [_p_val(_value(real, env, master, 0))] + value = _value(real, env, master, 0) + if value == "None": # 값이 빈 줄 — 「None」 을 글로 안 찍음 + raise mf.FormulaError("값 없음") + piece.update({"값": value} if kind == "name" else {"글": value}) except _CALC_ERRORS: - return [_p_val("")] # 그 밖(값 계산 자체가 막힘) — 빈 값 조각으로 줄이 안 죽게 + pass # 값 계산 자체가 막힘 — 이름만(그 밖은 빈 글) 남겨 줄이 안 죽게 + return [piece] def _formula_pieces(text, env: dict, master, name_of: dict, alias_by_name: dict) -> list[dict]: @@ -260,10 +378,18 @@ def _formula_pieces(text, env: dict, master, name_of: dict, alias_by_name: dict) # ── 줄 세우기 ───────────────────────────────────────────────────────── def _env_of(master, row: dict, given: dict, depth: int = 0) -> dict: - """입력 + 중간 — `run` 과 같은 차례로 다시 셈(덧줄 전 상태).""" + """입력 + 중간 — `run` 과 같은 차례로 다시 셈(덧줄 전 상태). + 아직 안 고른 입력이 있으면(값 넣기 전) 받은 것만 담아 돌려줌 — 이름 식은 값 없이도 지어짐 + (고른 값이 틀린 경우(고르기·범위 밖)는 지금처럼 그대로 막힘 — `mf._inputs` 가 걸러냄).""" + specs = row.get("입력", []) + if any(spec["이름"] not in given for spec in specs): + return {spec["이름"]: given[spec["이름"]] for spec in specs if spec["이름"] in given} env = mf._inputs(row, given) for step in row.get("중간", []): - env[step["이름"]] = mf.evaluate(mf.parse(step["식"]), env, master, depth) + try: + env[step["이름"]] = mf.evaluate(mf.parse(step["식"]), env, master, depth) + except mf.EmptyInput as e: # 못 구한 중간값 — `run` 과 같이 쓰는 줄만 비움 + env[step["이름"]] = mf.Blank(str(e)) return env @@ -293,29 +419,64 @@ def _shares(line: dict) -> list[tuple[str, Decimal]]: return [(part, money) for part, money in parts if money != 0] or parts[:1] +def _loose(node, env: dict, master) -> bool: + """수량식 맨 위가 덧셈·뺄셈이면(곱 뒤에 오면 괄호가 필요함) · 만약은 걸린 갈래 기준.""" + if node[0] == "bin": + return _PREC[node[1]] < _PREC["*"] + if node[0] == "call" and node[1] == "만약": + try: + at = 1 if mf.evaluate(node[2][0], env, master, 0) else 2 + except _CALC_ERRORS: + return False # 이름 꼴은 「만약(…)」 부르기 그대로라 괄호 불필요 + return _loose(node[2][at], env, master) + return False + + +def _times(text: str, item: dict, env: dict, master) -> str: + """「단가 * 수량식」 의 수량식 — 덧셈·뺄셈 묶음이면 괄호.""" + return f"({text})" if _loose(mf.parse(item["수량"]), env, master) else text + + def _line_text(item: dict, line: dict, env: dict, master, part: str | None) -> str: - """「단가 * 수량식 = 금액」 — 비목이 나뉜 로직 줄은 그 비목 몫만.""" - qty = render(mf.parse(item["수량"]), env, master) + """「단가 * 수량식 = 금액」 — 비목이 나뉜 로직 줄은 그 비목 몫만 · 빈 줄(금액 null)은 빈 글.""" + if line["금액"] is None: + return "" + qty = _times(render(mf.parse(item["수량"]), env, master), item, env, master) money = line["비목"][part] if part else line["금액"] price = line["단가"] if part is None or line["수량"] == 0 else money / line["수량"] - return f"{fmt(price)} * {qty} = {fmt(money)}" + return f"{fmt_price(price)} * {qty} = {fmt_money(money)}" -def _line_names(item: dict, line: dict, env: dict, master, part: str | None, name_of: dict) -> str: +def _item_names(items: list) -> list[tuple[str, str]]: + """호표 줄마다 (짧은 품명(규격·코드 뗌), 겹침 꼬리) — 한 로직 안에서 겹치면 꼬리 `_2`.""" + taken: set[str] = set() + out = [] + for item in items: + name = mc.short(item.get("이름")) or "값" + out.append((name, mc._free(name, taken)[len(name) :])) + return out + + +def _line_names( + item: dict, env: dict, master, part: str | None, name_of: dict, label: tuple[str, str] +) -> str: """같은 줄을 변수 이름으로 — 「석공노임 * 메쌓기인력.석공 * (1 + 증가율) = 석공비」.""" - name = mc.plain(item.get("이름") or line.get("이름")) or "값" + name, dup = label tail = PRICE_TAIL.get(str(item.get("종류") or ""), "단가") - price = f"{name}{part or ''}{tail}" - money = f"{name}{part}" if part else f"{name}비" - return f"{price} * {_names(item['수량'], env, master, name_of)} = {money}" + price = f"{name}{part or ''}{tail}{dup}" + money = f"{name}{part}{dup}" if part else f"{name}비{dup}" + qty = _times(_names(item["수량"], env, master, name_of), item, env, master) + return f"{price} * {qty} = {money}" def _extra_text(extra: dict, line: dict, env: dict, master) -> str: - return f"{render(mf.parse(extra['식']), env, master)} = {fmt(line['금액'])}" + if line["금액"] is None: # 빈 덧줄 — 식을 다시 셈하지 않음 + return "" + return f"{render(mf.parse(extra['식']), env, master)} = {fmt_money(line['금액'])}" def _extra_names(extra: dict, env: dict, master, name_of: dict) -> str: - got = mc.plain(extra.get("이름")) or "덧줄" + got = mc.short(extra.get("이름")) or "덧줄" return f"{_names(extra['식'], env, master, name_of)} = {got}" @@ -346,12 +507,16 @@ def lines(files: dict, key: str, given: dict) -> dict: if "결과" in row: 조각 = _formula_pieces(row["결과"]["식"], env, master, name_of, alias_by_name) - got = mc.plain(row.get("이름")) or "결과" + got = mc.short(row.get("이름")) or "결과" + shown = ( + fmt_money if str(row.get("결과단위") or "").startswith("원") else fmt + ) # 돈 결과만 정수 이름글 = f"{_names(row['결과']['식'], env, master, name_of)} = {got}" if result is not None: one = { "이름": row.get("이름") or "", - "글": f"{render(mf.parse(row['결과']['식']), env, master)} = {fmt(result['결과'])}", + "글": f"{render(mf.parse(row['결과']['식']), env, master)} = " + f"{shown(result['결과'])}", "이름글": 이름글, "금액": result["결과"], "조각": 조각, @@ -377,19 +542,22 @@ def lines(files: dict, key: str, given: dict) -> dict: made: dict[str, list] = {name: [] for name in mf.COST_ITEMS} items = row.get("호표") or [] + labels = _item_names(items) if result is not None: shown = result["줄"] env["줄"] = {r["이름"]: r["금액"] for r in shown if r["이름"]} - for item, line in zip(items, shown): + for n, (item, line, label) in enumerate(zip(items, shown, labels)): 조각 = _formula_pieces(item["수량"], env, master, name_of, alias_by_name) + 요소 = _item_pieces(item, env, master, line, n) for part, money in _shares(line): split = part if len(line["비목"]) > 1 else None one = { "이름": _label(item, line), "글": _line_text(item, line, env, master, split), - "이름글": _line_names(item, line, env, master, split, name_of), + "이름글": _line_names(item, env, master, split, name_of, label), "금액": money, "조각": 조각, + "요소조각": 요소, } why = _why(item, line) made[part].append({**one, "까닭": why} if why else one) @@ -398,13 +566,14 @@ def lines(files: dict, key: str, given: dict) -> dict: 끝수 = _cut_note(row) 계 = result["계"] else: - for item in items: + for n, (item, label) in enumerate(zip(items, labels)): parts = ( list(mf.COST_ITEMS) if item.get("종류") == "로직" and "비목" not in item else [item.get("비목")] ) 조각 = _formula_pieces(item["수량"], env, master, name_of, alias_by_name) + 요소 = _item_pieces(item, env, master, None, n) for part in parts: if part not in mf.COST_ITEMS: continue @@ -413,9 +582,10 @@ def lines(files: dict, key: str, given: dict) -> dict: { "이름": _label(item, {}), "글": "", - "이름글": _line_names(item, {}, env, master, split, name_of), + "이름글": _line_names(item, env, master, split, name_of, label), "금액": None, "조각": 조각, + "요소조각": 요소, } ) for extra in row.get("덧줄") or []: @@ -425,7 +595,7 @@ def lines(files: dict, key: str, given: dict) -> dict: "글": "", "이름글": ( f"{_names(extra['식'], env, master, name_of)} = " - f"{mc.plain(extra.get('이름')) or '덧줄'}" + f"{mc.short(extra.get('이름')) or '덧줄'}" ), "금액": None, "조각": _formula_pieces(extra["식"], env, master, name_of, alias_by_name), @@ -443,6 +613,45 @@ def lines(files: dict, key: str, given: dict) -> dict: return _paired({"ok": True, "groups": groups, "계": 계, "별칭": marks}) +def _arg_text(node, env: dict, master) -> str: + """로직 부르기 인자 값을 사람이 읽는 글로 — 요소 키는 이름 · 입력 자리는 지금 값.""" + try: + value = mf.evaluate(node, env, master, 0) + except _CALC_ERRORS: # 값 없는 입력 자리 — 이름만 + return str(node[1]) if node[0] in ("name", "str") else "" + if isinstance(value, str): + for ref in (value, f"EQ:{value}"): # 기계 입력은 원문번호(0602-0025) + try: + return mc.plain(master.get(ref).get("이름")) or value + except mf.FormulaError: + pass + return value + return fmt(value) + + +def _item_pieces( + item: dict, env: dict, master, line: dict | None = None, index: int = 0 +) -> list[dict]: + """줄의 단가 자리(요소 · 로직 부르기) 조각 — PLAN 3-1 요소조각.""" + ref = item.get("요소") + if item.get("종류") != "로직": + name = str(item.get("이름") or "") + if isinstance(ref, dict): # 재료 고르기 조건 — 잡힌 품목 이름 · 참조(고름 · 검색어 · 대표) + found = mti.piece(item, line, index, env, master) + return [found or {"kind": "요소", "글": name}] + piece = _elem_piece(master, ref, env) + return [{**piece, "글": name or piece["글"]}] + try: + node = mf.parse(ref) + key = mf.fill(node[1], env) + name = str(master.logic(key).get("이름") or key) + except _CALC_ERRORS: + return [{"kind": "로직", "글": str(item.get("이름") or "")}] + args = " · ".join(f"{k} {_arg_text(v, env, master)}" for k, v in node[2]) + out = [{"kind": "로직", "글": name, "참조": {"테이블": key[:2], "키": key}}] + return out + ([{"kind": "글", "글": args}] if args else []) + + def _paired(answer: dict) -> dict: """줄마다 짝 번호 — 두 칸(이름 꼴 · 값 꼴)이 같은 차례로 놓이게 1 부터.""" at = 0 @@ -450,6 +659,7 @@ def _paired(answer: dict) -> dict: for one in group["줄"]: at += 1 one["짝"] = at + one.setdefault("요소조각", []) return answer @@ -457,7 +667,9 @@ def _extras( row: dict, shown: list, made: dict, env: dict, master, name_of: dict, alias_by_name: dict ) -> None: """덧줄 — 앞선 줄까지의 비목 합을 넣고 한 줄씩(식 안의 비목 이름은 그 수로 바뀜).""" - sums = {name: sum((one["금액"] for one in made[name]), Decimal(0)) for name in mf.COST_ITEMS} + sums = { + name: sum((one["금액"] or 0 for one in made[name]), Decimal(0)) for name in mf.COST_ITEMS + } for extra, line in zip(row.get("덧줄") or [], shown): env.update(sums) one = { @@ -468,8 +680,9 @@ def _extras( "조각": _formula_pieces(extra["식"], env, master, name_of, alias_by_name), } made[extra["비목"]].append(one) - sums[extra["비목"]] += line["금액"] - env[extra["이름"]] = line["금액"] + if line["금액"] is not None: # 빈 덧줄은 합·이름 값에 안 넣음 + sums[extra["비목"]] += line["금액"] + env[extra["이름"]] = line["금액"] def _cut_note(row: dict, on: str = "계") -> str: diff --git a/resources/master_data/scripts/master_text_item.py b/resources/master_data/scripts/master_text_item.py new file mode 100644 index 00000000..c58bc165 --- /dev/null +++ b/resources/master_data/scripts/master_text_item.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +"""재료 줄 요소조각 — 잡힌 자재품목 이름 · 참조(PLAN 8-5). `master_text._item_pieces` 가 부름.""" + +from __future__ import annotations + +import re + +import master_formula as mf +import master_pick as mp + +_ERRORS = (mf.FormulaError, KeyError, TypeError) + + +def picked(item: dict, line: dict | None, index: int, env: dict, master) -> dict | None: + """재료 줄이 잡은 자재품목 줄 — 계산한 줄의 출처(`MT000018.거래가격`) 먼저 · + 빈 줄이면 고름 · 검색어 · 대표로 다시 찾음. 못 찾으면 None.""" + got = str((line or {}).get("출처") or "")[:8] + try: + if got[:2] == "MT": + return master.get(got) + cond = {k: mf.fill(v, env) if isinstance(v, str) else v for k, v in item["요소"].items()} + key = (mp.CHOSEN.get() or {}).get(str(index)) + if not key and cond.get("검색어"): + key = mp.pick(master, cond, env, item.get("단위")) + key = key or cond.get("대표") + return master.get(str(key)) if key and str(key)[:2] == "MT" else None + except _ERRORS: + return None + + +def piece(item: dict, line: dict | None, index: int, env: dict, master) -> dict | None: + """요소조각 — 글 = 품목 이름(지역 포함) · 참조 = 테이블 MT · 그 키. 못 찾으면 None.""" + row = picked(item, line, index, env, master) + if row is None: + return None + key = str(row["키"]) + name = re.sub(r"\s+", " ", str(row.get("이름") or "")).strip() + return {"kind": "요소", "글": name, "참조": {"테이블": key[:2], "키": key}} diff --git a/resources/master_data/로직_건설품셈_01장_도로포장공사.json b/resources/master_data/로직_건설품셈_01장_도로포장공사.json index d19f2ee4..9ee10ba4 100644 --- a/resources/master_data/로직_건설품셈_01장_도로포장공사.json +++ b/resources/master_data/로직_건설품셈_01장_도로포장공사.json @@ -3173,7 +3173,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "신구접착및방청제 LDS-MP"}, "이름": "프라이머(융착식 도료)", "단위": "kg", "수량": "찾기(QC000546).프라이머 / 10", @@ -3182,7 +3182,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "일반용", "대표": "MT003175"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "프로판가스 일반용"}, "이름": "프로판가스(건설용 LPG)", "단위": "kg", "수량": "찾기(QC000546).프로판가스 / 10", @@ -4278,7 +4278,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬"}, + "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬", "검색어": "철망 Φ3.2."}, "이름": "철망(낙석방지망)", "단위": "㎡", "수량": "찾기(QC000569, 구분='철망').수량", @@ -4287,7 +4287,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "보통철선 #8 서울"}, "이름": "결속선", "단위": "m", "수량": "찾기(QC000569, 구분='결속선').수량", @@ -4296,7 +4296,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "M-Poxy I(습식)", "대표": "MT007705"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "주입용에폭시 M-Poxy I(습식)"}, "이름": "에폭시", "단위": "kg", "수량": "만약(방지망 == '포켓식', 찾기(QC000569, 구분='에폭시').수량, 0)", diff --git a/resources/master_data/로직_건설품셈_01장_배관공사.json b/resources/master_data/로직_건설품셈_01장_배관공사.json index 266dcf2a..c69f5cc7 100644 --- a/resources/master_data/로직_건설품셈_01장_배관공사.json +++ b/resources/master_data/로직_건설품셈_01장_배관공사.json @@ -106,7 +106,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "동 및 동합금용 용접봉", "규격": "BCUP3", "단위": "㎏", @@ -116,7 +116,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "플럭스", "단위": "㎏", "수량": "찾기(QC001401, '규격(㎜)'=규격).플럭스 / 1000", @@ -125,7 +125,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소가스", "단위": "ℓ", "수량": "찾기(QC001401, '규격(㎜)'=규격).산소", @@ -134,7 +134,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌가스", "단위": "㎏", "수량": "찾기(QC001401, '규격(㎜)'=규격).아세틸렌 / 1000", @@ -194,7 +194,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "TIG 용접봉", "규격": "스테인리스", "단위": "㎏", @@ -204,7 +204,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "아르곤가스", "단위": "ℓ", "수량": "찾기(QC001404, '규격(㎜)'=규격).Argon", diff --git a/resources/master_data/로직_건설품셈_01장_철골공사.json b/resources/master_data/로직_건설품셈_01장_철골공사.json index 6f7b6f88..2299768d 100644 --- a/resources/master_data/로직_건설품셈_01장_철골공사.json +++ b/resources/master_data/로직_건설품셈_01장_철골공사.json @@ -66,7 +66,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "만약(부재 == 'H형강부재', 찾기(QC001188, 재료='산소').H형강부재, 찾기(QC001188, 재료='산소').전용접부재) * 1000", @@ -76,7 +76,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "L.P.G", "단위": "kg", "수량": "만약(부재 == 'H형강부재', 찾기(QC001188, 재료='L.P.G').H형강부재, 찾기(QC001188, 재료='L.P.G').전용접부재)", @@ -86,7 +86,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "세파볼트 D형"}, "이름": "서비스볼트", "단위": "본", "수량": "만약(부재 == 'H형강부재', 찾기(QC001188, 재료='서비스볼트').H형강부재, 찾기(QC001188, 재료='서비스볼트').전용접부재)", @@ -95,7 +95,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "형강", "대표": "MT030619"}, + "요소": {"구분": "철근·강재", "상세구분": "형강", "검색어": "보조강재"}, "이름": "보조강재", "단위": "㎏", "수량": "만약(부재 == 'H형강부재', 찾기(QC001188, 재료='보조강재').H형강부재, 찾기(QC001188, 재료='보조강재').전용접부재)", @@ -175,7 +175,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "만약(용접방식 == '수용접', 환산용접길이 * 찾기(QC001190, 재료='용접봉').수용접, 0)", @@ -185,7 +185,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "CO_2 와이어", "단위": "㎏", "수량": "만약(용접방식 == '반자동용접', 환산용접길이 * 찾기(QC001190, 재료='CO_2 와이어').반자동용접, 0)", @@ -194,7 +194,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "탄산가스", "단위": "㎏", "수량": "만약(용접방식 == '반자동용접', 환산용접길이 * 찾기(QC001190, 재료='탄산가스').반자동용접, 0)", @@ -203,7 +203,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "잠호용접와이어", "단위": "㎏", "수량": "만약(용접방식 == '자동용접', 환산용접길이 * 찾기(QC001190, 재료='잠호용접와이어').자동용접, 0)", @@ -212,7 +212,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "대표": "MT030620"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "FLUX"}, "이름": "FLUX", "단위": "㎏", "수량": "만약(용접방식 == '자동용접', 환산용접길이 * 찾기(QC001190, 재료='FLUX').자동용접, 0)", @@ -332,7 +332,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "세파볼트 D형"}, "이름": "보통볼트", "단위": "본", "수량": "찾기(QC001192, 구분='보통볼트', 규격='가조임').수량", @@ -648,7 +648,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "CO_2 와이어", "단위": "㎏", "수량": "찾기(QC001197, 구분='CO_2 와이어').수량", @@ -657,7 +657,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "탄산가스", "단위": "㎏", "수량": "찾기(QC001197, 구분='탄산가스').수량", @@ -835,7 +835,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "만약(가스 == '아세틸렌', 찾기(QC001202, '판두께(㎜)'=판두께).산소, 0) * 1000", @@ -845,7 +845,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "만약(가스 == '아세틸렌', 찾기(QC001202, '판두께(㎜)'=판두께).아세틸렌, 0)", @@ -854,7 +854,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "L.P.G", "단위": "kg", "수량": "만약(가스 == 'L.P.G', 찾기(QC001202, '판두께(㎜)'=판두께).'L.P.G', 0)", @@ -1198,7 +1198,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재", "대표": "MT030621"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "질석"}, "이름": "질석", "단위": "㎏", "수량": "찾기(QC001208, 구분='질석').수량", diff --git a/resources/master_data/로직_건설품셈_02장_가설공사.json b/resources/master_data/로직_건설품셈_02장_가설공사.json index a733c7fc..f158bc4a 100644 --- a/resources/master_data/로직_건설품셈_02장_가설공사.json +++ b/resources/master_data/로직_건설품셈_02장_가설공사.json @@ -47,7 +47,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "규격": "두께 2.0㎜ 이상", "대표": "MT030612"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "BASECHANNEL 두께 2.0㎜ 이상"}, "이름": "BASECHANNEL 두께:2.0㎜이상", "단위": "m", "수량": "찾기(QC000012, 구분='BASECHANNEL', 종류=주자재종류).수량 * 손율 / 100", @@ -55,7 +55,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "규격": "두께 2.0㎜ 이상", "대표": "MT030613"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "TOPCHANNEL 두께 2.0㎜ 이상"}, "이름": "TOPCHANNEL 두께:2.0㎜이상", "단위": "m", "수량": "찾기(QC000012, 구분='TOPCHANNEL', 종류=주자재종류).수량 * 손율 / 100", @@ -63,10 +63,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "외부PANEL(벽) 1,200×2,400㎜", "단위": "매", "수량": "찾기(QC000012, 구분='외부PANEL(벽)', 종류=주자재종류).수량 * 손율 / 100", @@ -74,10 +71,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "외부PANEL (창문) 1,200×2,400㎜", "단위": "매", "수량": "찾기(QC000012, 구분='외부PANEL (창문)', 종류=주자재종류).수량 * 손율 / 100", @@ -85,10 +79,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "외부PANEL (철재문) 1,200×2,400㎜", "단위": "매", "수량": "찾기(QC000012, 구분='외부PANEL (철재문)', 종류=주자재종류).수량 * 손율 / 100", @@ -96,10 +87,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "내부PANEL(벽) 1,200×2,400㎜", "단위": "매", "수량": "만약(종류 == '사무실', 찾기(QC000012, 구분='내부PANEL(벽)', 종류=주자재종류).수량 * 손율 / 100, 0)", @@ -107,7 +95,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재", "규격": "1,200×2,400㎜", "대표": "MT030614"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "내부PANEL (목재문) 1,200×2,400㎜"}, "이름": "내부PANEL (목재문) 1,200×2,400㎜", "단위": "매", "수량": "만약(종류 == '사무실', 찾기(QC000012, 구분='내부PANEL (목재문)', 종류=주자재종류).수량 * 손율 / 100, 0)", @@ -115,10 +103,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "판넬조인트 L=2400"}, "이름": "PANEL JOINT(AL-BAR) L=2,400㎜", "단위": "조", "수량": "찾기(QC000012, 구분='PANEL JOINT(AL-BAR)', 종류=주자재종류).수량 * 손율 / 100", @@ -126,10 +111,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "CANOPY(출입구채양) 600×1,200㎜", "단위": "매", "수량": "찾기(QC000012, 구분='CANOPY(출입구채양)', 종류=주자재종류).수량 * 손율 / 100", @@ -137,10 +119,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "박공PANEL", "단위": "매", "수량": "찾기(QC000012, 구분='박공PANEL', 종류=주자재종류).수량 * 손율 / 100", @@ -148,11 +127,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재", - "대표": "MT007262" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "지붕판 (ROOF SHEET) 0.5T"}, "이름": "ROOFSHEET 0.5㎜COLORSHEET", "단위": "㎡", "수량": "찾기(QC000012, 구분='ROOFSHEET', 종류=주자재종류).수량 * 손율 / 100", @@ -160,7 +135,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "형강"}, + "요소": {"구분": "철근·강재", "상세구분": "형강", "검색어": "트러스 (TRUSS) SPAN:L=7.2"}, "이름": "트러스 L=7.2m", "단위": "개", "수량": "찾기(QC000012, 구분='트러스', 종류=주자재종류).수량 * 손율 / 100", @@ -168,7 +143,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "형강"}, + "요소": {"구분": "철근·강재", "상세구분": "형강", "검색어": "트러스 (TRUSS) SPAN:L=7.2"}, "이름": "중도리(PURIN) 두께:2.0이상", "단위": "개", "수량": "찾기(QC000012, 구분='중도리(PURIN)', 종류=주자재종류).수량 * 손율 / 100", @@ -176,7 +151,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "T챤넬(K-SPAN) 4.5×610×2440"}, "이름": "천장판 미장합판+50㎜ GLASSWOOL", "단위": "매", "수량": "만약(종류 == '사무실', 찾기(QC000012, 구분='천장판', 종류=주자재종류).수량 * 손율 / 100, 0)", @@ -184,7 +159,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, "이름": "T-BAR", "단위": "m", "수량": "만약(종류 == '사무실', 찾기(QC000012, 구분='T-BAR', 종류=주자재종류).수량 * 손율 / 100, 0)", @@ -238,7 +213,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "가설건축물"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "가설건축물", "검색어": "콘테이너(사무실) 12.0*3.0*2.6m"}, "이름": "콘테이너형 가설건축물 3.0x3.0m", "단위": "동", "수량": "만약(규모 == '3.0x3.0m', 손율 / 100, 0)", @@ -246,7 +221,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "가설건축물"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "가설건축물", "검색어": "콘테이너(사무실) 12.0*3.0*2.6m"}, "이름": "콘테이너형 가설건축물 3.0x6.0m", "단위": "동", "수량": "만약(규모 == '3.0x6.0m', 손율 / 100, 0)", @@ -254,7 +229,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "가설건축물"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "가설건축물", "검색어": "콘테이너(사무실) 12.0*3.0*2.6m"}, "이름": "콘테이너형 가설건축물 3.0x9.0m", "단위": "동", "수량": "만약(규모 == '3.0x9.0m', 손율 / 100, 0)", @@ -291,7 +266,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "대표": "MT030615"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "축중계(이동식)"}, "이름": "축중계(이동식)", "단위": "대", "수량": "찾기(QC000018, 구분='손율', 기간=기간).값 / 100", @@ -1198,7 +1173,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "목재", "단위": "㎥", "수량": "찾기(QC000038, 구분='목재', 종별=종별).수량 * 찾기(QC000019, 규준틀='목재규준틀').손율 / 100", @@ -2056,7 +2031,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "규격": "1219×1700mm", "대표": "MT007846"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "수직문형지주(조립식틀비계) 1219×1700mm"}, "이름": "비계기본틀(기둥) H1700×W1219", "단위": "개", "수량": "찾기(QC000063, 구분='비계기본틀(기둥)', 규격='H1700×W1219').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2072,7 +2047,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "수평띠장 L1829", "단위": "개", "수량": "찾기(QC000063, 구분='수평띠장', 규격='L1829').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2080,7 +2055,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "손잡이기둥", "단위": "개", "수량": "찾기(QC000063, 구분='손잡이기둥').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2088,7 +2063,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "손잡이 L1219", "단위": "개", "수량": "찾기(QC000063, 구분='손잡이', 규격='L1219').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2096,7 +2071,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "손잡이 L1829", "단위": "개", "수량": "찾기(QC000063, 구분='손잡이', 규격='L1829').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2104,7 +2079,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "바퀴", "단위": "개", "수량": "찾기(QC000063, 구분='바퀴').수량 * 찾기(QC000016, 공기=공기, 재료='받침철물 조절받침철물 비계안전발판').손율 / 100", @@ -2112,7 +2087,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "자키", "단위": "개", "수량": "찾기(QC000063, 구분='자키').수량 * 찾기(QC000016, 공기=공기, 재료='받침철물 조절받침철물 비계안전발판').손율 / 100", @@ -2120,7 +2095,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "규격": "45×200×2000"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "발판 45×200×2000"}, "이름": "발판 45×200×2000", "단위": "장", "수량": "찾기(QC000063, 구분='발판', 규격='45×200×2000').수량 * 찾기(QC000016, 공기=공기, 재료='받침철물 조절받침철물 비계안전발판').손율 / 100", @@ -2353,15 +2328,6 @@ ], "출처": "건설품셈 공통 2-2-5", "비고": "강관·부속철물 손율 기간" - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] } ], "중간": [ @@ -2398,7 +2364,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "강관비계 Φ48.6 서울"}, "이름": "강관 ø48.6㎜×2.4㎜", "단위": "m", "수량": "찾기(QC000071, 구분='강관', 규격='ø48.6㎜×2.4㎜').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2406,7 +2372,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "브라켓", "단위": "개", "수량": "찾기(QC000071, 구분='브라켓').수량 * 찾기(QC000016, 공기=공기, 재료='조임철물 이음철물').손율 / 100", @@ -2414,7 +2380,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{철선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선 #8 서울"}, "이름": "철선", "단위": "㎏", "수량": "찾기(QC000071, 구분='철선').수량", @@ -2422,7 +2388,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "데크고정클립(NS클립)"}, "이름": "클램프", "단위": "개", "수량": "찾기(QC000071, 구분='클램프').수량 * 찾기(QC000016, 공기=공기, 재료='조임철물 이음철물').손율 / 100", @@ -2430,7 +2396,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "그물망", "단위": "㎡", "수량": "찾기(QC000071, 구분='그물망').수량", @@ -2509,7 +2475,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "강관비계 Φ48.6 서울"}, "이름": "강관 ø48.6㎜×2.4㎜", "단위": "m", "수량": "찾기(QC000074, 구분='강관', 규격='ø48.6㎜×2.4㎜').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2517,7 +2483,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "브라켓", "단위": "개", "수량": "찾기(QC000074, 구분='브라켓').수량 * 찾기(QC000016, 공기=공기, 재료='조임철물 이음철물').손율 / 100", @@ -2525,7 +2491,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "강관비계 Φ48.6 서울"}, "이름": "사다리 폭 30㎝×길이 3m 기준", "단위": "m", "수량": "찾기(QC000074, 구분='사다리', 규격='폭 30㎝×길이 3m 기준').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", @@ -2533,7 +2499,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "규격": "6mm", "대표": "MT007180"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "와이어로프(철심) 6mm"}, "이름": "와이어로프 ø6", "단위": "m", "수량": "찾기(QC000074, 구분='와이어로프', 규격='ø6').수량 * 찾기(QC000016, 공기=공기, 재료='조임철물 이음철물').손율 / 100", @@ -2541,7 +2507,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "데크고정클립(NS클립)"}, "이름": "클램프", "단위": "개", "수량": "찾기(QC000074, 구분='클램프').수량 * 찾기(QC000016, 공기=공기, 재료='조임철물 이음철물').손율 / 100", @@ -2549,7 +2515,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "그물망", "단위": "㎡", "수량": "찾기(QC000074, 구분='그물망').수량", @@ -2919,14 +2885,6 @@ 0.4 ], "출처": "건설품셈 공통 2-8-7 재료량" - }, - { - "이름": "결속선", - "고르기": [ - "MT000204", - "MT000164" - ], - "비고": "해석: # 10 = Φ3.2㎜" } ], "중간": [ @@ -2958,7 +2916,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "그물망", "단위": "㎡", "수량": "찾기(QC000085, 구분='그물망').수량 / 10", @@ -2966,7 +2924,7 @@ }, { "종류": "재료", - "요소": {"구분": "안전시설", "상세구분": "낙석방지·휀스"}, + "요소": {"구분": "안전시설", "상세구분": "낙석방지·휀스", "검색어": "칼라각관(적색) 25*25*1.6t"}, "이름": "보강재", "단위": "m", "수량": "찾기(QC000085, 구분='보강재').수량 / 10", @@ -2974,7 +2932,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{결속선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선 #10 서울"}, "이름": "결속선 # 10", "단위": "kg", "수량": "결속선량 / 10", @@ -3009,7 +2967,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "보호망", "단위": "㎡", "수량": "찾기(QC000088, 구분='보호망').수량", @@ -3044,7 +3002,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "보호망", "단위": "㎡", "수량": "찾기(QC000091, 구분='보호망').수량", @@ -3954,7 +3912,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "대표": "MT030616"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "보호막"}, "이름": "보호막", "단위": "㎡", "수량": "찾기(QC000130, 구분='보호막').수량", @@ -3971,17 +3929,7 @@ "출처": "건설품셈 공통 2-10-2", "소유": "공용", "비고": "재료량 할증 포함 · 방진망 손율 100% · 해석: 철선 소모(손율 100%) · 비계 별도", - "입력": [ - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - } - ], + "입력": [], "중간": [ { "이름": "비계공", @@ -3999,10 +3947,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "소모품" - }, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC천막지 T-210"}, "이름": "방진망", "단위": "㎡", "수량": "찾기(QC000133, 구분='방진망').수량", @@ -4010,7 +3955,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{철선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선 #8 서울"}, "이름": "철선", "단위": "kg", "수량": "찾기(QC000133, 구분='철선').수량", @@ -4594,15 +4539,6 @@ "부직포 깔기", "보양지 붙이기" ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] } ], "중간": [ @@ -4634,7 +4570,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "부직포", "단위": "㎡", "수량": "만약(작업 == '부직포 깔기', 찾기(QC000144, 구분=작업, 자재='부직포').수량, 0)", @@ -4642,7 +4578,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "규격": "하드롱지", "대표": "MT008073"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "도배지(벽지) 하드롱지"}, "이름": "하드롱지", "단위": "㎡", "수량": "만약(작업 == '보양지 붙이기', 찾기(QC000144, 구분=작업, 자재='하드롱지').수량, 0)", @@ -4650,7 +4586,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "풀", "단위": "kg", "수량": "만약(작업 == '보양지 붙이기', 찾기(QC000144, 구분=작업, 자재='풀').수량, 0)", @@ -4658,7 +4594,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "목재", "단위": "㎥", "수량": "만약(작업 == '목재 붙이기', 찾기(QC000144, 구분=작업, 자재='목재').수량, 0)", @@ -4666,7 +4602,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "못", "대표": "{못}"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "못", "검색어": "철못 N25 서울"}, "이름": "못", "단위": "kg", "수량": "만약(작업 == '목재 붙이기', 찾기(QC000144, 구분=작업, 자재='못').수량, 0)", diff --git a/resources/master_data/로직_건설품셈_02장_덕트공사.json b/resources/master_data/로직_건설품셈_02장_덕트공사.json index e89e3002..a32b5eba 100644 --- a/resources/master_data/로직_건설품셈_02장_덕트공사.json +++ b/resources/master_data/로직_건설품셈_02장_덕트공사.json @@ -666,7 +666,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "검색어": "L형앵커볼트 5/8\""}, "이름": "앵커", "규격": "1/2〃", "단위": "개", @@ -676,7 +676,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "핀볼트 M12"}, "이름": "블라인드리벳", "단위": "개", "수량": "찾기(QC001430, 구분='블라인드리벳').수량", @@ -685,7 +685,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철근"}, + "요소": {"구분": "철근·강재", "상세구분": "철근", "검색어": "철근부식방지재 MT-3"}, "이름": "철물", "규격": "D22 철근", "단위": "㎏", @@ -695,7 +695,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT030626"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "실리콘"}, "이름": "실리콘", "단위": "㎏", "수량": "찾기(QC001430, 구분='실리콘').수량", diff --git a/resources/master_data/로직_건설품셈_02장_조적공사.json b/resources/master_data/로직_건설품셈_02장_조적공사.json index 232d5dad..cdb28e7f 100644 --- a/resources/master_data/로직_건설품셈_02장_조적공사.json +++ b/resources/master_data/로직_건설품셈_02장_조적공사.json @@ -122,7 +122,7 @@ }, { "종류": "재료", - "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록"}, + "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록", "검색어": "주차라인블록 1000X300X150"}, "이름": "시멘트벽돌", "단위": "매", "수량": "찾기(QC001211, 벽두께=벽두께).'벽돌(19×9×5.7㎝)'", @@ -131,7 +131,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:3"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:3 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001211, 벽두께=벽두께).모르타르", @@ -269,7 +269,7 @@ }, { "종류": "재료", - "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록"}, + "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록", "검색어": "주차라인블록 1000X300X150"}, "이름": "치장벽돌", "단위": "매", "수량": "찾기(QC001213, 벽두께=벽두께).'벽돌(19×9×5.7㎝)'", @@ -278,7 +278,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:3"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:3 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001213, 벽두께=벽두께).'모르타르 쌓기'", @@ -287,7 +287,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:1"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:1 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001213, 벽두께=벽두께).'모르타르 치장줄눈'", @@ -350,7 +350,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:2"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:2 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001216, 벽두께=벽두께).'모르타르 쌓기'", @@ -401,7 +401,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:1"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:1 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001216, 벽두께=벽두께).'모르타르 치장줄눈'", @@ -564,7 +564,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:3"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:3 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "만약(블록규격mm == '390x190x190', 찾기(QC001219, 블록규격='390×190×190mm').모르타르, 만약(블록규격mm == '390x190x150', 찾기(QC001219, 블록규격='390×190×150mm').모르타르, 찾기(QC001219, 블록규격='390×190×100mm').모르타르))", @@ -705,7 +705,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:3"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:3 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "만약(블록규격mm == '390x190x190', 찾기(QC001221, 블록규격='390×190×190mm').모르타르, 만약(블록규격mm == '390x190x150', 찾기(QC001221, 블록규격='390×190×150mm').모르타르, 찾기(QC001221, 블록규격='390×190×100mm').모르타르))", @@ -832,7 +832,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "조적용(25kg/포)", "대표": "MT005805"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "ALC 시멘트(몰탈) 조적용(25kg/포)"}, "이름": "ALC모르타르", "단위": "kg", "수량": "만약(블록규격mm == '600x400x100', 찾기(QC001223, '블록규격(mm)'='600×400×100').모르타르, 만약(블록규격mm == '600x400x125', 찾기(QC001223, '블록규격(mm)'='600×400×125').모르타르, 만약(블록규격mm == '600x300x150', 찾기(QC001223, '블록규격(mm)'='600×300×150').모르타르, 찾기(QC001223, '블록규격(mm)'='600×300×200').모르타르)))", diff --git a/resources/master_data/로직_건설품셈_02장_토목.json b/resources/master_data/로직_건설품셈_02장_토목.json index 769fbd5b..66525629 100644 --- a/resources/master_data/로직_건설품셈_02장_토목.json +++ b/resources/master_data/로직_건설품셈_02장_토목.json @@ -280,7 +280,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, "이름": "팁(날)", "단위": "개", "수량": "0.69 * 절삭깊이 / 1000", @@ -454,7 +454,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, "이름": "팁(날)", "단위": "개", "수량": "0.69 * 100 / 1000", @@ -613,7 +613,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, "이름": "팁(날)", "단위": "개", "수량": "0.69 * 만약(밀링깊이 == '100㎜', 100, 150) / 1000", @@ -3007,7 +3007,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "신구접착및방청제 LDS-MP"}, "이름": "프라이머", "단위": "kg", "수량": "찾기(QC001944, 구분='프라이머').수량 / 10", @@ -3015,7 +3015,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "프로판가스", "단위": "kg", "수량": "찾기(QC001944, 구분='프로판가스').수량 / 10", @@ -3548,7 +3548,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "찾기(QC001951, 종별=난간규격).산소 * 1000 / 10", @@ -3556,7 +3556,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "찾기(QC001951, 종별=난간규격).아세틸렌 / 10", diff --git a/resources/master_data/로직_건설품셈_03장_타일공사.json b/resources/master_data/로직_건설품셈_03장_타일공사.json index a27b62e0..848f5d4f 100644 --- a/resources/master_data/로직_건설품셈_03장_타일공사.json +++ b/resources/master_data/로직_건설품셈_03장_타일공사.json @@ -115,7 +115,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:1"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:1 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001227, 붙임방식=붙임방식).'줄눈 모르타르량'", @@ -208,7 +208,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:3"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:3 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "찾기(QC001229, '바름두께(㎜)'=바름두께).'붙임 모르타르(벽체)'", @@ -315,7 +315,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:2"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:2 서울"}, "이름": "모르타르", "단위": "㎥", "수량": "만약(시공면 == '바닥면', 찾기(QC001231, '바름두께(㎜)'=바름두께).'붙임 모르타르 바닥면', 찾기(QC001231, '바름두께(㎜)'=바름두께).'붙임 모르타르 벽면')", diff --git a/resources/master_data/로직_건설품셈_03장_터널공사.json b/resources/master_data/로직_건설품셈_03장_터널공사.json index e3378de9..ff97a30c 100644 --- a/resources/master_data/로직_건설품셈_03장_터널공사.json +++ b/resources/master_data/로직_건설품셈_03장_터널공사.json @@ -243,7 +243,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "일체식 방수시트", "단위": "㎡", "수량": "찾기(QC000635, 구분='일체식 방수시트').수량", diff --git a/resources/master_data/로직_건설품셈_03장_토공사.json b/resources/master_data/로직_건설품셈_03장_토공사.json index c75ed296..395f28ac 100644 --- a/resources/master_data/로직_건설품셈_03장_토공사.json +++ b/resources/master_data/로직_건설품셈_03장_토공사.json @@ -1089,7 +1089,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "화약"}, + "요소": {"구분": "잡재료", "상세구분": "화약", "검색어": "폭약 28mm(메가마이트I)"}, "이름": "폭약", "단위": "㎏", "수량": "찾기(QC000199).폭약", @@ -1098,7 +1098,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "화약"}, + "요소": {"구분": "잡재료", "상세구분": "화약", "검색어": "전기뇌관 3.5m"}, "이름": "뇌관", "단위": "개", "수량": "찾기(QC000199).뇌관", @@ -1107,7 +1107,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "비트", "단위": "개", "수량": "찾기(QC000199).비트", @@ -1296,7 +1296,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "화약"}, + "요소": {"구분": "잡재료", "상세구분": "화약", "검색어": "폭약 28mm(메가마이트I)"}, "이름": "폭약", "단위": "㎏", "수량": "찾기(QC000203, 발파=발파).폭약", @@ -1305,7 +1305,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "화약"}, + "요소": {"구분": "잡재료", "상세구분": "화약", "검색어": "전기뇌관 3.5m"}, "이름": "뇌관", "단위": "개", "수량": "찾기(QC000203, 발파=발파).뇌관", @@ -1314,7 +1314,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "비트", "단위": "개", "수량": "찾기(QC000203, 발파=발파).비트", @@ -3177,13 +3177,7 @@ "출처": "건설품셈 공통 3-8-3", "소유": "공용", "비고": "해석: 재료량 표(기준 칸 빈칸)를 m당으로 읽음 · 각재 수종 설계자 고름(자재품목 각재 ㎥) · 해석: 인력품 = 인력 호표 줄 금액 합(기계 조종원 제외)", - "입력": [ - { - "이름": "각재", - "고르기": ["MT000339", "MT000338"], - "출처": "자재품목 각재(㎥) — 외송 · 미송" - } - ], + "입력": [], "중간": [], "호표": [ { @@ -3204,7 +3198,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재", "대표": "{각재}"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "외송각재 30×30×3600mm 서울"}, "이름": "각재 10㎝×10㎝", "단위": "㎥", "수량": "찾기(QC000245).각재", diff --git a/resources/master_data/로직_건설품셈_04장_궤도공사.json b/resources/master_data/로직_건설품셈_04장_궤도공사.json index 0da4a948..d075ca0c 100644 --- a/resources/master_data/로직_건설품셈_04장_궤도공사.json +++ b/resources/master_data/로직_건설품셈_04장_궤도공사.json @@ -784,7 +784,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌가스", "단위": "kg", "수량": "찾기(QC000648, 품명='아세틸렌', 레일규격=레일규격).수량", @@ -793,7 +793,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소가스", "단위": "L", "수량": "찾기(QC000648, 품명='산소', 레일규격=레일규격, 규격='KSM 1101, 99.5%').수량 * 1000", @@ -802,7 +802,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "바퀴숫돌 단면용 A36m B11호 A150×8×22 KSL 6501", "단위": "개", "수량": "찾기(QC000648, 품명='바퀴숫돌', 레일규격=레일규격, 규격='단면용 A36m B11호 A150×8×22 KSL 6501').수량", @@ -811,7 +811,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "바퀴숫돌 측면용 A24 QWV1호 A205×25×25 KSL 6501", "단위": "개", "수량": "찾기(QC000648, 품명='바퀴숫돌', 레일규격=레일규격, 규격='측면용 A24 QWV1호 A205×25×25 KSL 6501').수량", @@ -820,7 +820,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "바퀴숫돌 평면용 A24 QWV1호 A205×25×25 KSL 6501", "단위": "개", "수량": "찾기(QC000648, 품명='바퀴숫돌', 레일규격=레일규격, 규격='평면용 A24 QWV1호 A205×25×25 KSL 6501').수량", @@ -829,7 +829,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "바퀴숫돌 최종용 A24 QWV 5호 A205×22×22", "단위": "개", "수량": "찾기(QC000648, 품명='바퀴숫돌', 레일규격=레일규격, 규격='최종용 A24 QWV 5호 A205×22×22').수량", @@ -838,7 +838,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "보일러·공조", "규격": "압접가열용", "대표": "MT030710"}, + "요소": {"구분": "기계·설비", "상세구분": "보일러·공조", "검색어": "버너 압접가열용"}, "이름": "버너 압접가열용", "단위": "개", "수량": "찾기(QC000648, 품명='버너', 레일규격=레일규격, 규격='압접가열용').수량", @@ -847,7 +847,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "규격": "압접버너용", "대표": "MT030627"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "노즐 압접버너용"}, "이름": "노즐 압접버너용", "단위": "개", "수량": "찾기(QC000648, 품명='노즐', 레일규격=레일규격, 규격='압접버너용').수량", @@ -914,7 +914,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "점화용", "대표": "MT030628"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "테르밋용재 점화용"}, "이름": "테르밋용재 점화용", "단위": "포", "수량": "찾기(QC000650, 품명='테르밋용재', 레일규격=레일규격, 규격='점화용').수량", @@ -923,7 +923,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "테르밋 용접용", "대표": "MT030629"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "몰드 테르밋 용접용"}, "이름": "몰드", "단위": "개", "수량": "찾기(QC000650, 품명='몰드', 레일규격=레일규격).수량", @@ -932,7 +932,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "테르밋 용접용", "대표": "MT030630"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "골무 테르밋 용접용"}, "이름": "골무", "단위": "개", "수량": "찾기(QC000650, 품명='골무', 레일규격=레일규격).수량", @@ -941,7 +941,7 @@ }, { "종류": "재료", - "요소": {"구분": "전기·통신", "상세구분": "판넬·차단기", "규격": "테르밋 용접용", "대표": "MT030711"}, + "요소": {"구분": "전기·통신", "상세구분": "판넬·차단기", "검색어": "퓨즈 테르밋 용접용"}, "이름": "퓨즈", "단위": "개", "수량": "찾기(QC000650, 품명='퓨즈', 레일규격=레일규격).수량", @@ -950,7 +950,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소가스", "단위": "L", "수량": "찾기(QC000650, 품명='산소', 레일규격=레일규격).수량 * 1000", @@ -959,7 +959,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "프로판가스(건설용 LPG)", "단위": "kg", "수량": "찾기(QC000650, 품명='프로판가스', 레일규격=레일규격).수량", diff --git a/resources/master_data/로직_건설품셈_04장_목공사.json b/resources/master_data/로직_건설품셈_04장_목공사.json index a4211782..fe17adca 100644 --- a/resources/master_data/로직_건설품셈_04장_목공사.json +++ b/resources/master_data/로직_건설품셈_04장_목공사.json @@ -409,7 +409,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001242, 구분='접착제').수량", diff --git a/resources/master_data/로직_건설품셈_04장_조경공사.json b/resources/master_data/로직_건설품셈_04장_조경공사.json index 9c08e885..0c761bf1 100644 --- a/resources/master_data/로직_건설품셈_04장_조경공사.json +++ b/resources/master_data/로직_건설품셈_04장_조경공사.json @@ -181,7 +181,7 @@ }, { "종류": "재료", - "요소": {"구분": "식생·종자", "상세구분": "종자"}, + "요소": {"구분": "식생·종자", "상세구분": "종자", "검색어": "종자 혼합종자"}, "이름": "종자", "단위": "㎏", "수량": "종자량 / 100", @@ -190,7 +190,7 @@ }, { "종류": "재료", - "요소": {"구분": "식생·종자", "상세구분": "비료·토양개량", "대표": "MT006489"}, + "요소": {"구분": "식생·종자", "상세구분": "비료·토양개량", "검색어": "복합비료"}, "이름": "비료(복합비료)", "단위": "㎏", "수량": "찾기(QC000274, 구분='비료').수량 / 100", @@ -199,7 +199,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "화이버/펄프류", "대표": "MT030610"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "피복제 화이버/펄프류"}, "이름": "피복제(화이버/펄프류)", "단위": "㎏", "수량": "찾기(QC000274, 구분='피복제').수량 / 100", @@ -208,7 +208,7 @@ }, { "종류": "재료", - "요소": {"구분": "식생·종자", "상세구분": "비료·토양개량", "규격": "합성접착제", "대표": "MT030611"}, + "요소": {"구분": "식생·종자", "상세구분": "비료·토양개량", "검색어": "침식방지안정제 합성접착제"}, "이름": "침식방지안정제(합성접착제)", "단위": "㎏", "수량": "안정제량 / 100", @@ -217,7 +217,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "규격": "색소(M-Green)", "대표": "MT006740"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "착색제 색소(M-Green)"}, "이름": "색소(착색제)", "단위": "㎏", "수량": "찾기(QC000274, 구분='색소').수량 / 100", diff --git a/resources/master_data/로직_건설품셈_05장_강구조공사.json b/resources/master_data/로직_건설품셈_05장_강구조공사.json index c363fa1a..ff4ebca2 100644 --- a/resources/master_data/로직_건설품셈_05장_강구조공사.json +++ b/resources/master_data/로직_건설품셈_05장_강구조공사.json @@ -108,7 +108,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "용접봉(강교 제작)", "단위": "㎏", "수량": "찾기(QC000664, 품명='용접봉').수량", @@ -117,7 +117,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소가스", "단위": "L", "수량": "찾기(QC000664, 품명='산소').수량 * 1000", @@ -126,7 +126,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "LPG(건설용)", "단위": "kg", "수량": "찾기(QC000664, 품명='LPG가스').수량", @@ -164,7 +164,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030631"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "철구(Shot ball)"}, "이름": "철구(Shot ball)", "단위": "㎏", "수량": "찾기(QC000665, 구분='철구(Shot ball)').수량", @@ -173,7 +173,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "무기질아연말 샵프라이머 도막두께 20μm", "단위": "ℓ", "수량": "찾기(QC000665, 구분='무기질아연말샵프라이머', 규격='도막두께20μm').수량", @@ -204,7 +204,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030632"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "철편(Grit)"}, "이름": "철편(Grit)", "단위": "㎏", "수량": "찾기(QC000666, 구분='철편(Grit)').수량", diff --git a/resources/master_data/로직_건설품셈_05장_기초공사.json b/resources/master_data/로직_건설품셈_05장_기초공사.json index 6c26e7d4..e85c4c03 100644 --- a/resources/master_data/로직_건설품셈_05장_기초공사.json +++ b/resources/master_data/로직_건설품셈_05장_기초공사.json @@ -2386,7 +2386,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "매트(토목섬유)", "단위": "㎡", "수량": "찾기(QC000334, 구분='육상부설').매트 / 100", @@ -2394,7 +2394,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "규격": "9㎜"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "와이어로프(철심) 9㎜"}, "이름": "P.P로프(9㎜)", "단위": "m", "수량": "찾기(QC000334, 구분='육상부설').'P.P로프(9㎜)' / 100", @@ -2402,7 +2402,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재", "대표": "MT030617"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래주머니"}, "이름": "모래주머니", "단위": "개", "수량": "찾기(QC000334, 구분='육상부설').모래주머니 / 100", @@ -3057,7 +3057,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "고무바 2.2cm"}, "이름": "드레인 보드", "단위": "m", "수량": "1 + 찾기(QC000356, 항목='5-2-3 재료량(주②)', 구분='드레인 보드 재료할증').비율 / 100", @@ -4443,7 +4443,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "ℓ", "수량": "찾기(QC000377, 구분='산소', 규격=규격).수량", @@ -4451,7 +4451,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "LPG", "단위": "kg", "수량": "찾기(QC000377, 구분='LPG', 규격=규격).수량", @@ -5331,7 +5331,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "부직포", "단위": "㎡", "수량": "만약(재료 == '부직포', 찾기(QC000399, 구분='부직포').수량, 0)", @@ -5339,7 +5339,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "지오콤포지트", "단위": "㎡", "수량": "만약(재료 == '지오콤포지트', 찾기(QC000399, 구분='지오콤포지트').수량, 0)", @@ -5347,7 +5347,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "매트"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "매트", "검색어": "PET 매트 10T/m"}, "이름": "벤토나이트매트", "단위": "㎡", "수량": "만약(재료 == '벤토나이트매트', 찾기(QC000399, 구분='벤토나이트매트').수량, 0)", @@ -5355,7 +5355,7 @@ }, { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PE관", "규격": "2∼2.5㎜", "대표": "MT030618"}, + "요소": {"구분": "관·이음관", "상세구분": "PE관", "검색어": "HDPE 시트 2∼2.5㎜"}, "이름": "HDPE 시트", "단위": "㎡", "수량": "만약(재료 == 'HDPE시트', 찾기(QC000399, 구분='HDPE 시트').수량, 0)", diff --git a/resources/master_data/로직_건설품셈_05장_수장공사.json b/resources/master_data/로직_건설품셈_05장_수장공사.json index 8fa00c4e..edbdabe3 100644 --- a/resources/master_data/로직_건설품셈_05장_수장공사.json +++ b/resources/master_data/로직_건설품셈_05장_수장공사.json @@ -67,7 +67,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "만약(시공방식 == '타일형', 타일접착제량, 만약(시공방식 == '시트형 (전면접착 방식)', 찾기(QC001249, 구분='바닥 시트 전면접착 방식').접착제, 찾기(QC001249, 구분='바닥 시트 부분접착 방식').접착제))", @@ -131,7 +131,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "불연천장재(마이톤) 12mm"}, "이름": "카페트", "단위": "㎡", "수량": "찾기(QC001251, 구분='카페트').수량", @@ -140,10 +140,7 @@ }, { "종류": "재료", - "요소": { - "구분": "매트·토목섬유", - "상세구분": "부직포·토목섬유" - }, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "펠트", "단위": "㎡", "수량": "찾기(QC001251, 구분='펠트').수량", @@ -152,7 +149,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001251, 구분='접착제').수량", @@ -625,7 +622,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001260, 구분='접착제').수량", @@ -851,7 +848,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "불연천장재(마이톤) 12mm"}, "이름": "흡음판", "단위": "㎡", "수량": "찾기(QC001264, 구분='흡음판', 규격='1,000×2,000×50㎜').수량", @@ -860,7 +857,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, "이름": "조이너", "단위": "m", "수량": "찾기(QC001264, 구분='조이너', 규격='P.V.C 50T').수량", @@ -869,7 +866,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001264, 구분='접착제').수량", @@ -960,7 +957,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, "이름": "테라조", "단위": "m", "수량": "만약(구분 == '석재류', 찾기(QC001266, 구분=구분).테라조, 0)", @@ -969,7 +966,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, "이름": "합성수지 걸레받이", "단위": "m", "수량": "만약(구분 == '합성수지류', 찾기(QC001266, 구분=구분).합성수지, 0)", @@ -987,7 +984,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "만약(구분 == '석재류', 0, 걸레받이접착제량)", @@ -1109,11 +1106,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재", - "대표": "MT008074" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "종이벽지(초배지)"}, "이름": "초배지", "단위": "㎡", "수량": "찾기(QC001269, 바탕면=바탕면).초배지", @@ -1122,7 +1115,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "대표": "MT030622"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "정배지"}, "이름": "정배지", "단위": "㎡", "수량": "찾기(QC001269, 바탕면=바탕면).정배지", @@ -1131,7 +1124,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "풀", "단위": "kg", "수량": "찾기(QC001269, 바탕면=바탕면).풀", @@ -1245,7 +1238,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열재", "단위": "㎡", "수량": "찾기(QC001272, 구분='단열재').수량", @@ -1254,7 +1247,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001272, 구분='접착제').수량", @@ -1334,7 +1327,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열재", "단위": "㎡", "수량": "찾기(QC001274, 구분='단열재').수량", @@ -1343,7 +1336,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "단열접착제량", @@ -1421,7 +1414,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열재", "단위": "㎡", "수량": "찾기(QC001276, 구분='단열재').수량", @@ -1484,7 +1477,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열재", "단위": "㎡", "수량": "찾기(QC001278, 구분='단열재').수량", @@ -1493,7 +1486,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001278, 구분='접착제').수량", @@ -1502,7 +1495,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "대표": "MT030623"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "알루미늄핀"}, "이름": "알루미늄핀", "단위": "개", "수량": "찾기(QC001278, 구분='알루미늄핀').수량", @@ -1633,7 +1626,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열재", "단위": "㎡", "수량": "찾기(QC001281, 구분='단열재').수량", @@ -1712,7 +1705,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열재", "단위": "㎡", "수량": "찾기(QC001283, 구분='단열재').수량", @@ -1721,7 +1714,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "만약(접착제 == '씀', 찾기(QC001283, 구분='접착제').수량, 0)", @@ -1777,7 +1770,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC천막지 T-210"}, "이름": "방습필름", "단위": "㎡", "수량": "찾기(QC001285, 구분=구분).방습필름", @@ -1846,7 +1839,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "단열판", "단위": "㎡", "수량": "찾기(QC001288, 구분='외벽단열').단열판", @@ -1855,7 +1848,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001288, 구분='외벽단열').접착제", @@ -1864,7 +1857,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "㎏", "수량": "찾기(QC001288, 구분='외벽단열').시멘트", @@ -1873,7 +1866,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬", "대표": "MT030624"}, + "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬", "검색어": "표준보강메시"}, "이름": "표준보강메시", "단위": "㎡", "수량": "찾기(QC001288, 구분='외벽단열').표준보강메시", @@ -1928,7 +1921,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "㎏", "수량": "찾기(QC001288, 구분='하부보강').접착제", @@ -1937,7 +1930,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "㎏", "수량": "찾기(QC001288, 구분='하부보강').시멘트", @@ -1946,7 +1939,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬", "대표": "MT030625"}, + "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬", "검색어": "고강도메시"}, "이름": "고강도메시", "단위": "㎡", "수량": "찾기(QC001288, 구분='하부보강').고강도메시", diff --git a/resources/master_data/로직_건설품셈_06장_관부설_및_접합공사.json b/resources/master_data/로직_건설품셈_06장_관부설_및_접합공사.json index 12a5d620..313fd3fe 100644 --- a/resources/master_data/로직_건설품셈_06장_관부설_및_접합공사.json +++ b/resources/master_data/로직_건설품셈_06장_관부설_및_접합공사.json @@ -2052,7 +2052,7 @@ }, { "종류": "재료", - "요소": {"구분": "전기·통신", "상세구분": "케이블·전선", "규격": "2.0㎜"}, + "요소": {"구분": "전기·통신", "상세구분": "케이블·전선", "검색어": "전선(인입용) 2.0㎜"}, "이름": "IV전선 2.0㎜", "단위": "m", "수량": "찾기(QC000707).IV전선", @@ -2061,7 +2061,7 @@ }, { "종류": "재료", - "요소": {"구분": "전기·통신", "상세구분": "부속·기타", "규격": "100W"}, + "요소": {"구분": "전기·통신", "상세구분": "부속·기타", "검색어": "백열전구 100W"}, "이름": "백열등 100W", "단위": "EA", "수량": "찾기(QC000707).백열등", diff --git a/resources/master_data/로직_건설품셈_06장_방수공사.json b/resources/master_data/로직_건설품셈_06장_방수공사.json index e3642e52..3f865e96 100644 --- a/resources/master_data/로직_건설품셈_06장_방수공사.json +++ b/resources/master_data/로직_건설품셈_06장_방수공사.json @@ -406,7 +406,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "방수시트", "단위": "㎡", "수량": "찾기(QC001300, 구분='시트').수량", @@ -469,7 +469,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "방수시트", "단위": "㎡", "수량": "찾기(QC001300, 구분='시트').수량", @@ -532,7 +532,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "방수시트", "단위": "㎡", "수량": "찾기(QC001300, 구분='시트').수량", @@ -949,7 +949,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "벤토나이트방수재", "단위": "㎡", "수량": "만약(종류 == '벤토나이트 매트', 찾기(QC001307, 구분='벤토나이트방수재', 종류='매트', 부위=부위).수량, 0)", @@ -958,7 +958,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "벤토나이트방수재", "단위": "㎡", "수량": "만약(종류 == '벤토나이트 시트', 찾기(QC001307, 구분='벤토나이트방수재', 종류='시트', 부위=부위).수량, 0)", @@ -967,7 +967,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "KL-지수재 급결"}, "이름": "벤토나이트씰재", "단위": "L", "수량": "찾기(QC001307, 구분='벤토나이트씰재', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -976,7 +976,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT007749"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "벤토나이트알갱이"}, "이름": "벤토나이트알갱이", "단위": "kg", "수량": "찾기(QC001307, 구분='벤토나이트알갱이', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -985,7 +985,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC천막지 T-210"}, "이름": "PE필름 0.04㎜", "단위": "㎡", "수량": "찾기(QC001307, 구분='PE필름', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -994,7 +994,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "규격": "화약", "대표": "MT007751"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "검색어": "카트리지 화약"}, "이름": "카트리지", "단위": "개", "수량": "찾기(QC001307, 구분='카트리지', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -1003,7 +1003,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "못", "규격": "32㎜"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "못", "검색어": "타정못 32㎜"}, "이름": "콘크리트못", "단위": "개", "수량": "찾기(QC001307, 구분='콘크리트못', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -1012,7 +1012,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "핀볼트 M12"}, "이름": "와셔", "단위": "개", "수량": "찾기(QC001307, 구분='와셔', 종류=만약(종류 == '벤토나이트 매트', '매트', '시트'), 부위=부위).수량", @@ -1021,7 +1021,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC코팅철선 #8"}, "이름": "조인트테이프", "단위": "m", "수량": "만약(종류 == '벤토나이트 시트', 찾기(QC001307, 구분='조인트테이프', 종류='시트', 부위=부위).수량, 0)", @@ -1064,7 +1064,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "백업재(내열성) 10mm"}, "이름": "실링재", "단위": "m", "수량": "찾기(QC001309, 구분='실링재').수량", diff --git a/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json b/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json index 0b40493b..877cf8ce 100644 --- a/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json +++ b/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json @@ -498,7 +498,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "에폭시 신구콘크리트 접착제", "단위": "kg", "수량": "만약(용도 == '신구-콘크리트 접착제바르기', 접착제량, 0)", @@ -506,7 +506,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "에폭시 콘크리트고무 접착제", "단위": "kg", "수량": "만약(용도 == '콘크리트 및 고무 기타 접착제바르기', 접착제량, 0)", @@ -514,7 +514,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "찾기(QC000412, 용도=용도, 재료명='시너').수량", @@ -725,13 +725,6 @@ 50 ], "출처": "건설품셈 공통 6-2-3 비고" - }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] } ], "중간": [ @@ -768,7 +761,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{결속선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선(결속선) #20 서울"}, "이름": "결속선", "단위": "kg", "수량": "찾기(QC000421, Type=Type).사용량", @@ -933,7 +926,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (1kg=853ℓ)", "대표": "MT003164"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (1kg=853ℓ)"}, "이름": "아세틸렌", "단위": "ℓ", "수량": "찾기(QC000423).아세틸렌", @@ -941,7 +934,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "ℓ", "수량": "찾기(QC000423).산소", @@ -1033,19 +1026,6 @@ ], "비고": "1회 합판 비율 원문 「-」 — 1회 사용 수량(QC000427) 그대로 100%" }, - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, { "이름": "높이감률", "단위": "%", @@ -1137,7 +1117,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "합판", "대표": "{합판}"}, + "요소": {"구분": "목재", "상세구분": "합판", "검색어": "합판(내수) 12t 서울"}, "이름": "합판(내수 12㎜)", "단위": "㎡", "수량": "찾기(QC000427).합판 * 합판율", @@ -1145,7 +1125,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재", "대표": "{각재}"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "외송각재 30×30×3600mm 서울"}, "이름": "각재", "단위": "㎥", "수량": "찾기(QC000427).각재 * 합판율", @@ -1180,7 +1160,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "구멍땜 시멘트", "단위": "kg", "수량": "구멍땜 * 찾기(QC000430).시멘트 / 100", @@ -1215,7 +1195,7 @@ }, { "이름": "소모자재", - "식": "(찾기(QC000427).합판 * MT:{합판} + 찾기(QC000427).각재 * MT:{각재}) * 합판율 * 찾기(QC000428, 사용횟수=사용횟수).소모자재 / 100", + "식": "(줄.'합판(내수 12㎜)' + 줄.'각재') * 찾기(QC000428, 사용횟수=사용횟수).소모자재 / 100", "비목": "재료비", "출처": "건설품셈 공통 6-3-1 소모자재" } @@ -1292,7 +1272,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "패널(K-SPAN PANEL) 0.6×305mm"}, "이름": "강재거푸집 손료", "단위": "㎡", "수량": "(1 - 10 / 100) / 사용횟수", @@ -1530,7 +1510,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "패널(K-SPAN PANEL) 0.6×305mm"}, "이름": "문양거푸집(판넬)", "단위": "㎡", "수량": "1", @@ -1603,7 +1583,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형맨홀거푸집 ø740 손료", "단위": "조", "수량": "만약(규격 == 'ø740', 1 / 10, 0)", @@ -1612,7 +1592,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형맨홀거푸집 ø900 손료", "단위": "조", "수량": "만약(규격 == 'ø900', 1 / 10, 0)", @@ -1621,7 +1601,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형맨홀거푸집 ø1200 손료", "단위": "조", "수량": "만약(규격 == 'ø1200', 1 / 10, 0)", @@ -1630,7 +1610,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형맨홀거푸집 ø1500 손료", "단위": "조", "수량": "만약(규격 == 'ø1500', 1 / 10, 0)", @@ -1639,7 +1619,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형맨홀거푸집 ø1800 손료", "단위": "조", "수량": "만약(규격 == 'ø1800', 1 / 10, 0)", @@ -2095,16 +2075,7 @@ "출처": "건설품셈 공통 6-3-9", "소유": "공용", "비고": "재료량 할증 포함 · 설계에 따라 증감", - "입력": [ - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000159" - ], - "비고": "해석: 철선 #8 = Φ4.0㎜" - } - ], + "입력": [], "중간": [ { "이름": "특별인부", @@ -2134,7 +2105,7 @@ }, { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "200×5T", "대표": "MT006544"}, + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "검색어": "PVC지수판 200×5T"}, "이름": "PVC 지수판 200×5t", "단위": "m", "수량": "찾기(QC000449, 구분='PVC 지수판').수량", @@ -2142,7 +2113,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "2.5mm", "대표": "MT003197"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC용접봉 2.5mm"}, "이름": "PVC 용접봉", "단위": "kg", "수량": "찾기(QC000449, 구분='PVC 용접봉').수량", @@ -2150,7 +2121,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{철선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선 #8 서울"}, "이름": "철선 #8", "단위": "kg", "수량": "찾기(QC000449, 구분='철선').수량", diff --git a/resources/master_data/로직_건설품셈_07장_돌공사.json b/resources/master_data/로직_건설품셈_07장_돌공사.json index 6e89ba6c..9b950e19 100644 --- a/resources/master_data/로직_건설품셈_07장_돌공사.json +++ b/resources/master_data/로직_건설품셈_07장_돌공사.json @@ -370,7 +370,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "채움콘크리트", "단위": "㎥", "수량": "찾기(QC000492).채움콘크리트", diff --git a/resources/master_data/로직_건설품셈_07장_지붕_및_홈통공사.json b/resources/master_data/로직_건설품셈_07장_지붕_및_홈통공사.json index c8d9f437..d1f64084 100644 --- a/resources/master_data/로직_건설품셈_07장_지붕_및_홈통공사.json +++ b/resources/master_data/로직_건설품셈_07장_지붕_및_홈통공사.json @@ -340,7 +340,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "규격": "336×1,000×3㎜", "대표": "MT030705"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "아스팔트싱글 336×1,000×3㎜"}, "이름": "아스팔트싱글", "단위": "매", "수량": "찾기(QC001317, 구분='아스팔트싱글', 규격='336×1.000×3㎜').수량", @@ -407,10 +407,7 @@ }, { "종류": "재료", - "요소": { - "구분": "건축마감", - "상세구분": "천장·바닥·마감재" - }, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "불연천장재(마이톤) 12mm"}, "이름": "폴리카보네이트", "단위": "㎡", "수량": "찾기(QC001319, 구분='폴리카보네이트').수량", @@ -468,7 +465,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, "이름": "후레싱", "단위": "m", "수량": "찾기(QC001321, 구분='후레싱').수량", diff --git a/resources/master_data/로직_건설품셈_08장_금속공사.json b/resources/master_data/로직_건설품셈_08장_금속공사.json index 5eac9213..b208ad08 100644 --- a/resources/master_data/로직_건설품셈_08장_금속공사.json +++ b/resources/master_data/로직_건설품셈_08장_금속공사.json @@ -117,7 +117,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬"}, + "요소": {"구분": "철근·강재", "상세구분": "철망·와이어메쉬", "검색어": "메쉬스크린 H=0.9m"}, "이름": "와이어메시", "단위": "매", "수량": "찾기(QC001331, 구분='와이어메시', 규격='1,800×1,800㎜').수량", @@ -169,7 +169,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "검색어": "L형앵커볼트 5/8\""}, "이름": "인서트", "단위": "개", "수량": "찾기(QC001333, 구분='인서트').수량", @@ -221,7 +221,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, "이름": "조이너 및 몰딩", "단위": "m", "수량": "찾기(QC001335, 구분='조이너 및 몰딩').수량", @@ -438,7 +438,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "규격": "ø 10㎜"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "앵커", "검색어": "셋트앵커(철) ø 10㎜"}, "이름": "앵커", "단위": "개", "수량": "찾기(QC001339, 구분='앵커', 규격='ø 10㎜').수량", @@ -447,7 +447,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "규격": "ø 4.2㎜"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "작은나사(스크류) ø 4.2㎜"}, "이름": "AL리벳", "단위": "개", "수량": "찾기(QC001339, 구분='AL리벳', 규격='ø 4.2㎜').수량", diff --git a/resources/master_data/로직_건설품셈_08장_지반조사.json b/resources/master_data/로직_건설품셈_08장_지반조사.json index 48c1bd46..f3d24a90 100644 --- a/resources/master_data/로직_건설품셈_08장_지반조사.json +++ b/resources/master_data/로직_건설품셈_08장_지반조사.json @@ -99,7 +99,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "싱글코아바렐", "단위": "개", "수량": "찾기(QC000776, 층=층, 종별='싱글코아바렐').수량", @@ -108,7 +108,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "메탈크라운비트", "단위": "개", "수량": "찾기(QC000776, 층=층, 종별='메탈크라운비트').수량", @@ -117,7 +117,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "쵸핑비트", "단위": "개", "수량": "만약(층 == '호박돌층', 찾기(QC000776, 층=층, 종별='쵸핑비트').수량, 0)", @@ -126,7 +126,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "드라이브파이프헤드", "단위": "개", "수량": "찾기(QC000776, 층=층, 종별='드라이브파이프헤드').수량", @@ -135,7 +135,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "드라이브파이프슈", "단위": "개", "수량": "찾기(QC000776, 층=층, 종별='드라이브파이프슈').수량", @@ -144,7 +144,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "드라이브파이프", "단위": "개", "수량": "찾기(QC000776, 층=층, 종별='드라이브파이프').수량", @@ -211,7 +211,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "더블코아바렐", "단위": "개", "수량": "찾기(QC000777, 층=층, 종별='더블코아바렐').수량", @@ -220,7 +220,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "메탈크라운비트", "단위": "개", "수량": "만약(층 == '풍화암' 또는 층 == '연암' 또는 층 == '보통암', 찾기(QC000777, 층=층, 종별='메탈크라운비트').수량, 0)", @@ -229,7 +229,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "다이아몬드비트", "단위": "개", "수량": "만약(층 == '경암' 또는 층 == '극경암', 찾기(QC000777, 층=층, 종별='다이아몬드비트').수량, 0)", @@ -238,7 +238,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "메탈리밍쉘", "단위": "개", "수량": "만약(층 == '풍화암' 또는 층 == '연암' 또는 층 == '보통암', 찾기(QC000777, 층=층, 종별='메탈리밍쉘').수량, 0)", @@ -247,7 +247,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "다이아몬드리밍쉘", "단위": "개", "수량": "만약(층 == '경암' 또는 층 == '극경암', 찾기(QC000777, 층=층, 종별='다이아몬드리밍쉘').수량, 0)", @@ -256,7 +256,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "코아리프터", "단위": "개", "수량": "찾기(QC000777, 층=층, 종별='코아리프터').수량", @@ -337,7 +337,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "표준관입시험용", "대표": "MT030636"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "슈 표준관입시험용"}, "이름": "슈", "단위": "개", "수량": "찾기(QC000779, 종별='슈').수량", @@ -346,7 +346,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "샘플러", "단위": "개", "수량": "찾기(QC000779, 종별='샘플러').수량", @@ -445,7 +445,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "신월튜브", "단위": "개", "수량": "찾기(QC000781, 종별='신월튜브').수량", @@ -522,7 +522,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "베인 블레이드", "단위": "개", "수량": "찾기(QC000780, 종별='재료비', 세목='vane blade(대형)').수량", @@ -531,7 +531,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "ø16×750", "대표": "MT030637"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "베인전용로드 ø16×750"}, "이름": "베인 전용로드", "단위": "본", "수량": "찾기(QC000780, 종별='재료비', 세목='전용로드(ø16×750)').수량", @@ -540,7 +540,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "ø40.5×1m", "대표": "MT030638"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "로드 ø40.5×1m"}, "이름": "로드", "단위": "본", "수량": "찾기(QC000780, 종별='재료비', 세목='로드(ø40.5×1m)').수량", @@ -596,7 +596,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "평판재하시험용", "대표": "MT030639"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "표준사 평판재하시험용"}, "이름": "표준사", "단위": "㎏", "수량": "찾기(QC000782, 종별='표준사').수량", @@ -685,7 +685,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "정재하시험용", "대표": "MT030640"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "단독콘 정재하시험용"}, "이름": "단독콘", "단위": "개", "수량": "찾기(QC000785, 종별='단독콘').수량", @@ -971,7 +971,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "윙비트", "단위": "개", "수량": "찾기(QC000789, 종별='윙비트').수량", @@ -980,7 +980,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "쉬트파일수팽창지수재 DPS-500"}, "이름": "벤토나이트", "단위": "㎏", "수량": "찾기(QC000789, 규격=규격, 종별='벤토나이트').수량", @@ -1084,7 +1084,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "윙비트", "단위": "개", "수량": "찾기(QC000790, 종별='윙비트').수량", @@ -1093,7 +1093,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "쉬트파일수팽창지수재 DPS-500"}, "이름": "벤토나이트", "단위": "㎏", "수량": "찾기(QC000790, 규격=규격, 종별='벤토나이트').수량", @@ -1197,7 +1197,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "윙비트", "단위": "개", "수량": "찾기(QC000791, 종별='윙비트').수량", @@ -1206,7 +1206,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "쉬트파일수팽창지수재 DPS-500"}, "이름": "벤토나이트", "단위": "㎏", "수량": "찾기(QC000791, 규격=규격, 종별='벤토나이트').수량", @@ -1310,7 +1310,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "윙비트", "단위": "개", "수량": "찾기(QC000792, 종별='윙비트').수량", @@ -1319,7 +1319,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "쉬트파일수팽창지수재 DPS-500"}, "이름": "벤토나이트", "단위": "㎏", "수량": "찾기(QC000792, 규격=규격, 종별='벤토나이트').수량", @@ -1423,7 +1423,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "윙비트", "단위": "개", "수량": "찾기(QC000793, 종별='윙비트').수량", @@ -1432,7 +1432,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "쉬트파일수팽창지수재 DPS-500"}, "이름": "벤토나이트", "단위": "㎏", "수량": "찾기(QC000793, 규격=규격, 종별='벤토나이트').수량", @@ -1536,7 +1536,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "동물성단백기포제"}, "이름": "기포제", "단위": "ℓ", "수량": "찾기(QC000794, 규격=규격, 종별='기포제').수량", @@ -1545,7 +1545,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "에어해머", "단위": "개", "수량": "찾기(QC000794, 종별='에어해머').수량", @@ -1554,7 +1554,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "버튼비트", "단위": "개", "수량": "찾기(QC000794, 종별='버튼(Button) 비트').수량", @@ -1658,7 +1658,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "동물성단백기포제"}, "이름": "기포제", "단위": "ℓ", "수량": "찾기(QC000795, 규격=규격, 종별='기포제').수량", @@ -1667,7 +1667,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "에어해머", "단위": "개", "수량": "찾기(QC000795, 종별='에어해머').수량", @@ -1676,7 +1676,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "버튼비트", "단위": "개", "수량": "찾기(QC000795, 종별='버튼(Button) 비트').수량", @@ -1780,7 +1780,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "동물성단백기포제"}, "이름": "기포제", "단위": "ℓ", "수량": "찾기(QC000796, 규격=규격, 종별='기포제').수량", @@ -1789,7 +1789,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "에어해머", "단위": "개", "수량": "찾기(QC000796, 종별='에어해머').수량", @@ -1798,7 +1798,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "버튼비트", "단위": "개", "수량": "찾기(QC000796, 종별='버튼(Button) 비트').수량", diff --git a/resources/master_data/로직_건설품셈_09장_기타공사.json b/resources/master_data/로직_건설품셈_09장_기타공사.json index 4bd7ba4f..284b4c81 100644 --- a/resources/master_data/로직_건설품셈_09장_기타공사.json +++ b/resources/master_data/로직_건설품셈_09장_기타공사.json @@ -270,7 +270,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030631"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "철구(Shot ball)"}, "이름": "steel shot", "단위": "㎏", "수량": "만약(구분 == 'Shot Blast', 강구수량, 0)", @@ -280,7 +280,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "규사", "단위": "㎥", "수량": "만약(구분 == 'Sand Blast', 0.0508, 0)", @@ -289,7 +289,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030633"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "동력 Brush"}, "이름": "동력 Brush", "단위": "개", "수량": "만약(구분 == 'Power Tool', 0.03, 0)", @@ -307,7 +307,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030634"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Wire Brush"}, "이름": "Wire Brush", "단위": "개", "수량": "만약(구분 == 'Wire Brush', 0.016, 0)", diff --git a/resources/master_data/로직_건설품셈_09장_미장공사.json b/resources/master_data/로직_건설품셈_09장_미장공사.json index cde52acd..39b1558a 100644 --- a/resources/master_data/로직_건설품셈_09장_미장공사.json +++ b/resources/master_data/로직_건설품셈_09장_미장공사.json @@ -51,7 +51,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QC001347, 배합용적비=배합용적비).시멘트", @@ -466,7 +466,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QC001356, 구분='시멘트').수량", @@ -475,7 +475,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "무수축혼화제"}, "이름": "혼화제", "단위": "g", "수량": "만약(혼화제 == '씀', 찾기(QC001356, 구분='혼화제').수량, 0)", @@ -524,7 +524,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QC001358, 구분='시멘트').수량", @@ -637,7 +637,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "단면복구용몰탈 LDS-Mortar"}, "이름": "무수축모르타르", "단위": "㎏", "수량": "찾기(QC001361, '규격(㎜)'=규격).무수축모르타르", diff --git a/resources/master_data/로직_건설품셈_11장_가스설비공사.json b/resources/master_data/로직_건설품셈_11장_가스설비공사.json index d0329a18..f3001c96 100644 --- a/resources/master_data/로직_건설품셈_11장_가스설비공사.json +++ b/resources/master_data/로직_건설품셈_11장_가스설비공사.json @@ -233,7 +233,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030635"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "컴파운드"}, "이름": "컴파운드", "단위": "g", "수량": "만약(규격 == 'ø20', 찾기(QC001578, '구경(㎜)'='ø20').컴파운드, 만약(규격 == '25', 찾기(QC001578, '구경(㎜)'='25').컴파운드, 만약(규격 == '32', 찾기(QC001578, '구경(㎜)'='30').컴파운드, 만약(규격 == '40', 찾기(QC001578, '구경(㎜)'='40').컴파운드, 찾기(QC001578, '구경(㎜)'='50').컴파운드))))", @@ -516,7 +516,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030635"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "컴파운드"}, "이름": "컴파운드", "단위": "g", "수량": "찾기(QC001583, '구경(㎜)'='컴파운드(g)', 항목=만약(구분 == 'ø15㎜', 'ø15', 'ø20∼25')).수량", diff --git a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json index 07def85b..361bfb9b 100644 --- a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json +++ b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json @@ -1868,7 +1868,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001629, '관경 (외경)(㎜)'=관경, 항목='용접봉').수량", @@ -1957,7 +1957,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "찾기(QC001631, '구경 (㎜)'=구경, 구분=만약(SCH == '20∼40', '20∼40 산소(ℓ)', 만약(SCH == '60∼80', '60∼80 산소', '100∼160 산소'))).수량", @@ -1967,7 +1967,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "L.P.G", "단위": "kg", "수량": "찾기(QC001631, '구경 (㎜)'=구경, 구분=만약(SCH == '20∼40', '20∼40 LPG(kg)', 만약(SCH == '60∼80', '60∼80 LPG(kg)', '100∼160 LPG(kg)'))).수량", @@ -2038,7 +2038,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "산소량", @@ -2047,7 +2047,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT003176"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "LPG 가스"}, "이름": "L.P.G", "단위": "kg", "수량": "LPG량", @@ -2192,7 +2192,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001635, '구경 ㎜'=구경, 'SCH No.'=SCH).용접봉", @@ -2405,7 +2405,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001638, '두께(㎜)'=두께, '자세 및 직종'=자세, 구분='용접봉사용량(㎏)').수량", @@ -2511,7 +2511,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001641, '두께(㎜)'=두께, '자세 및 직종'=자세, 구분='용접봉소비량(㎏)').수량", @@ -2625,7 +2625,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001642, '두께(㎜)'=두께, '자세 및 직종'=자세, 구분='용접봉소비량(㎏)').수량", @@ -2734,7 +2734,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001639, '자세 및 직종 두께(㎜)'=두께, 구분=만약(용접면 == '하향한면', '하향한면 용접', '하향양면 용접')).용접봉소비량", @@ -2843,7 +2843,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QC001640, '자세 및 직종 두께(㎜)'=두께, 구분=만약(용접면 == '하향한면', '하향한면 용접', '하향양면 용접')).용접봉소비량", @@ -3175,7 +3175,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "단열바 STS 프레임(120mm ⓐ120"}, "이름": "보온 성형물", "단위": "m", "수량": "만약(보온두께 == '30mm이하', 찾기(QC001650, 'Pipe Size mm'=관경, 구분='직관의물량').성형물, 만약(보온두께 == '31mm∼40mm', 찾기(QC001651, 'Pipe Size mm'=관경, 구분='직관의물량').성형물, 만약(보온두께 == '41mm∼60mm', 찾기(QC001652, 'Pipe Size mm'=관경, 구분='직관의물량').성형물, 만약(보온두께 == '61mm∼75mm', 찾기(QC001653, 'Pipe Size mm'=관경, 구분='직관의물량').성형물, 찾기(QC001654, 'Pipe Size mm'=관경, 구분='직관의물량').성형물)))) * 만약(부위 == '관(m당)', 1, 0)", @@ -3185,7 +3185,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "보통철선 #8 서울"}, "이름": "철선", "단위": "m", "수량": "만약(보온두께 == '30mm이하', 찾기(QC001650, 'Pipe Size mm'=관경, 구분='직관의물량').철선, 만약(보온두께 == '31mm∼40mm', 찾기(QC001651, 'Pipe Size mm'=관경, 구분='직관의물량').철선, 만약(보온두께 == '41mm∼60mm', 찾기(QC001652, 'Pipe Size mm'=관경, 구분='직관의물량').철선, 만약(보온두께 == '61mm∼75mm', 찾기(QC001653, 'Pipe Size mm'=관경, 구분='직관의물량').철선, 찾기(QC001654, 'Pipe Size mm'=관경, 구분='직관의물량').철선)))) * 만약(부위 == '관(m당)', 1, 0)", @@ -3195,7 +3195,7 @@ }, { "종류": "재료", - "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록", "대표": "MT030641"}, + "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록", "검색어": "Lagging Sheet"}, "이름": "Lagging Sheet", "단위": "㎡", "수량": "만약(보온두께 == '30mm이하', 찾기(QC001650, 'Pipe Size mm'=관경, 구분='직관의물량').'Lagging Sheet', 만약(보온두께 == '31mm∼40mm', 찾기(QC001651, 'Pipe Size mm'=관경, 구분='직관의물량').'Lagging Sheet', 만약(보온두께 == '41mm∼60mm', 찾기(QC001652, 'Pipe Size mm'=관경, 구분='직관의물량').'Lagging Sheet', 만약(보온두께 == '61mm∼75mm', 찾기(QC001653, 'Pipe Size mm'=관경, 구분='직관의물량').'Lagging Sheet', 찾기(QC001654, 'Pipe Size mm'=관경, 구분='직관의물량').'Lagging Sheet')))) * 만약(부위 == '관(m당)', 1, 0)", @@ -3205,10 +3205,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "소모품" - }, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "Sheet Metal Screw", "단위": "개", "수량": "만약(보온두께 == '30mm이하', 찾기(QC001650, 'Pipe Size mm'=관경, 구분='직관의물량').'Sheet Metal Screw', 만약(보온두께 == '31mm∼40mm', 찾기(QC001651, 'Pipe Size mm'=관경, 구분='직관의물량').'Sheet Metal Screw', 만약(보온두께 == '41mm∼60mm', 찾기(QC001652, 'Pipe Size mm'=관경, 구분='직관의물량').'Sheet Metal Screw', 만약(보온두께 == '61mm∼75mm', 찾기(QC001653, 'Pipe Size mm'=관경, 구분='직관의물량').'Sheet Metal Screw', 찾기(QC001654, 'Pipe Size mm'=관경, 구분='직관의물량').'Sheet Metal Screw')))) * 만약(부위 == '관(m당)', 1, 0)", @@ -3502,7 +3499,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "유압유 RANDO(200ℓ)"}, "이름": "기름", "단위": "ℓ", "수량": "0.05 / 100", @@ -7734,7 +7731,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.36 * 6000", @@ -7744,7 +7741,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.242", @@ -7753,7 +7750,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "2", @@ -7763,7 +7760,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030642"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "코크스"}, "이름": "코크스", "단위": "㎏", "수량": "9", @@ -7772,7 +7769,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "각종", "대표": "MT030643"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Sand Paper 각종"}, "이름": "Sand Paper", "단위": "Sh", "수량": "3.125", @@ -7781,7 +7778,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "14\"×14〃", "대표": "MT030644"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "여과기 14\"×14〃"}, "이름": "여과기", "단위": "Sh", "수량": "3", @@ -7790,7 +7787,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "걸레", "단위": "㎏", "수량": "2.5", @@ -7799,7 +7796,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "C-3", "대표": "MT030645"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "세유 C-3"}, "이름": "세유", "단위": "ℓ", "수량": "2.2", @@ -7817,7 +7814,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030647"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Machine oil"}, "이름": "Machine oil", "단위": "ℓ", "수량": "0.7", @@ -7826,7 +7823,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030648"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Gasoline"}, "이름": "Gasoline", "단위": "ℓ", "수량": "0.24", @@ -7835,7 +7832,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "규격": "#8∼#16", "대표": "MT030649"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "Galvanized wire #8∼#16"}, "이름": "Galvanized wire", "단위": "㎏", "수량": "0.5", @@ -7844,7 +7841,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "8\"ø×25m/m t", "대표": "MT030650"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Grinding Wheel 8\"ø×25m/m t"}, "이름": "Grinding Wheel", "단위": "EA", "수량": "0.375", @@ -7853,7 +7850,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC코팅철선 #8"}, "이름": "비닐세트", "단위": "m", "수량": "1", @@ -7862,7 +7859,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC코팅철선 #8"}, "이름": "소창직", "단위": "m", "수량": "0.86", @@ -7871,7 +7868,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030651"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "보일유"}, "이름": "보일유", "단위": "ℓ", "수량": "0.008", @@ -7880,7 +7877,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "0.012", @@ -7889,7 +7886,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "0.062", @@ -7898,7 +7895,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "조합페인트", "단위": "ℓ", "수량": "0.062", @@ -8178,7 +8175,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "0∼3", "대표": "MT030652"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "세유 0∼3"}, "이름": "세유", "단위": "ℓ", "수량": "0.73", @@ -8187,7 +8184,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030648"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Gasoline"}, "이름": "Gasoline", "단위": "ℓ", "수량": "0.73", @@ -8196,7 +8193,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030651"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "보일유"}, "이름": "보일유", "단위": "ℓ", "수량": "0.069", @@ -8205,7 +8202,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030647"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Machine oil"}, "이름": "Machine oil", "단위": "ℓ", "수량": "0.365", @@ -8223,7 +8220,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "0.138", @@ -8232,7 +8229,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "규격": "#8∼#16", "대표": "MT030649"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "Galvanized wire #8∼#16"}, "이름": "Galvanized wire", "단위": "㎏", "수량": "0.73", @@ -8241,7 +8238,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "각종 3/8∼1.6\"", "대표": "MT030653"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Wire brush 각종 3/8∼1.6\""}, "이름": "Wire brush", "단위": "EA", "수량": "0.292", @@ -8250,7 +8247,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "12\"", "대표": "MT030654"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Hack saw blade 12\""}, "이름": "Hack saw blade", "단위": "EA", "수량": "0.438", @@ -8259,7 +8256,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "규격": "1.6ø∼3.8ø", "대표": "MT030655"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "Drill 1.6ø∼3.8ø"}, "이름": "Drill", "단위": "㎏", "수량": "0.018", @@ -8268,7 +8265,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "8\"ø∼25m/m t", "대표": "MT030656"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Grinder wheel 8\"ø∼25m/m t"}, "이름": "Grinder wheel", "단위": "㎏", "수량": "0.022", @@ -8277,7 +8274,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "각종", "대표": "MT030657"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "File 각종"}, "이름": "File", "단위": "㎏", "수량": "0.218", @@ -8286,7 +8283,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "각종(황, 중, 세)", "대표": "MT030658"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Oil stone 각종(황, 중, 세)"}, "이름": "Oil stone", "단위": "Sh", "수량": "0.055", @@ -8295,7 +8292,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030642"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "코크스"}, "이름": "코크스", "단위": "㎏", "수량": "0.328", @@ -8304,7 +8301,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "목탄", "단위": "㎏", "수량": "0.82", @@ -8313,7 +8310,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.109 * 6000", @@ -8323,7 +8320,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.084", @@ -8332,7 +8329,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "0.365", @@ -8342,7 +8339,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "가스용접봉", "단위": "㎏", "수량": "0.146", @@ -8351,7 +8348,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "신주용접봉", "단위": "㎏", "수량": "0.073", @@ -8369,7 +8366,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030660"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "광목"}, "이름": "광목", "단위": "m", "수량": "0.402", @@ -8378,7 +8375,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC코팅철선 #8"}, "이름": "소창직", "단위": "m", "수량": "0.134", @@ -8387,7 +8384,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "걸레", "단위": "㎏", "수량": "0.73", @@ -8396,7 +8393,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Sand Paper 각종"}, "이름": "비닐시트", "단위": "Sh", "수량": "0.037", @@ -8405,7 +8402,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "방청페인트", "단위": "ℓ", "수량": "0.069", @@ -8414,7 +8411,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "페인트", "단위": "ℓ", "수량": "0.04", @@ -8423,7 +8420,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "규격": "Sn 30%", "대표": "MT001252"}, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "봉땜납 Sn 30%"}, "이름": "땜납", "단위": "㎏", "수량": "0.055", @@ -8432,7 +8429,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "대표": "MT030661"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "붕사"}, "이름": "붕사", "단위": "㎏", "수량": "0.016", @@ -8441,7 +8438,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "절연용", "대표": "MT030662"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Compound 절연용"}, "이름": "Compound", "단위": "㎏", "수량": "0.073", @@ -8450,7 +8447,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "밀착제 No.2", "대표": "MT030663"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "3-Bond 밀착제 No.2"}, "이름": "3-Bond", "단위": "㎏", "수량": "0.007", @@ -8694,7 +8691,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "3.76 * 6000", @@ -8704,7 +8701,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "3.23", @@ -8713,11 +8710,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "0.71", @@ -8726,7 +8719,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "24.99", @@ -8745,7 +8738,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030664"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "Nozzle"}, "이름": "Nozzle", "단위": "개", "수량": "0.5", @@ -8754,7 +8747,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "2.5", @@ -8999,7 +8992,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "3 * 6000", @@ -9009,7 +9002,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "2.58", @@ -9018,11 +9011,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "0.62", @@ -9031,7 +9020,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "20", @@ -9050,7 +9039,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030664"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "Nozzle"}, "이름": "Nozzle", "단위": "개", "수량": "0.5", @@ -9059,7 +9048,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "2.2", @@ -9344,7 +9333,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.53 * 6000", @@ -9354,7 +9343,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.45", @@ -9363,7 +9352,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "6.2", @@ -9373,7 +9362,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030642"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "코크스"}, "이름": "코크스", "단위": "㎏", "수량": "27", @@ -9382,7 +9371,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "2.5", @@ -9391,7 +9380,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "페인트", "단위": "ℓ", "수량": "5", @@ -9681,7 +9670,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.46 * 6000", @@ -9691,7 +9680,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.39", @@ -9700,7 +9689,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "5.4", @@ -9710,7 +9699,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030642"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "코크스"}, "이름": "코크스", "단위": "㎏", "수량": "27", @@ -9719,7 +9708,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "2.2", @@ -9728,7 +9717,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "페인트", "단위": "ℓ", "수량": "4.4", @@ -9935,7 +9924,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.38 * 6000", @@ -9945,7 +9934,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.33", @@ -9954,7 +9943,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "3", @@ -9964,7 +9953,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "신구접착및방청제 LDS-MP"}, "이름": "광명단", "단위": "㎏", "수량": "2.2", @@ -9973,7 +9962,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "신구접착및방청제 LDS-MP"}, "이름": "페인트", "단위": "㎏", "수량": "4.4", @@ -10247,7 +10236,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "2.3 * 6000", @@ -10257,7 +10246,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.98", @@ -10266,11 +10255,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "대", "수량": "0.53", @@ -10279,7 +10264,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "14.35", @@ -10298,7 +10283,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030664"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "Nozzle"}, "이름": "Nozzle", "단위": "개", "수량": "0.5", @@ -10307,7 +10292,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "2.2", @@ -10523,7 +10508,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.38 * 6000", @@ -10533,7 +10518,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.33", @@ -10542,7 +10527,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "3", @@ -10799,7 +10784,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "대표": "MT030666"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "전기용접"}, "이름": "전기용접", "단위": "㎏", "수량": "9.77", @@ -10808,7 +10793,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "대표": "MT030667"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "탄소봉"}, "이름": "탄소봉", "단위": "본", "수량": "3.67", @@ -10817,7 +10802,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.45 * 6000", @@ -10827,7 +10812,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.32", @@ -10836,7 +10821,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "규격": "12\"ø", "대표": "MT030668"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "Grinder 12\"ø"}, "이름": "Grinder", "단위": "개", "수량": "0.815", @@ -10845,7 +10830,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "65×305", "대표": "MT030669"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "돌 65×305"}, "이름": "돌", "단위": "매", "수량": "4.9", @@ -10854,7 +10839,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "2회", "대표": "MT030670"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "Film 2회"}, "이름": "Film", "단위": "㎏", "수량": "405", @@ -11036,7 +11021,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "1.35 * 6000", @@ -11046,7 +11031,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.57", @@ -11055,7 +11040,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "1.16", @@ -11064,7 +11049,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "8ø×350㎜", "대표": "MT030671"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "탄소봉 8ø×350㎜"}, "이름": "탄소봉", "단위": "본", "수량": "6", @@ -11073,7 +11058,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "규사", "단위": "㎥", "수량": "0.23", @@ -11082,7 +11067,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030704"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "중유"}, "이름": "중유", "단위": "ℓ", "수량": "0.023", @@ -11100,7 +11085,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "대표": "MT003383"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "징크프라이머"}, "이름": "징크프라이머", "단위": "ℓ", "수량": "0.246", @@ -11109,7 +11094,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "0.055", @@ -11118,7 +11103,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "대표": "MT030673"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "탈에폭시레신"}, "이름": "탈에폭시레신", "단위": "ℓ", "수량": "2.05", @@ -11127,7 +11112,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "0.45", @@ -11309,7 +11294,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "9.81", @@ -11318,7 +11303,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "8ø×350㎜", "대표": "MT030671"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "탄소봉 8ø×350㎜"}, "이름": "탄소봉", "단위": "본", "수량": "3.53", @@ -11327,7 +11312,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.55 * 6000", @@ -11337,7 +11322,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.39", @@ -11346,7 +11331,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "12\"ø", "대표": "MT030674"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그라인더돌 12\"ø"}, "이름": "그라인더돌", "단위": "개", "수량": "0.5", @@ -11355,7 +11340,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "규격": "65×305", "대표": "MT030675"}, + "요소": {"구분": "기계·설비", "상세구분": "측정·시험기기", "검색어": "Film 65×305"}, "이름": "Film", "단위": "매", "수량": "4.8", @@ -11364,7 +11349,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "대표": "MT030676"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Tar epoxy"}, "이름": "Tar epoxy", "단위": "ℓ", "수량": "1.81", @@ -11373,7 +11358,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "대표": "MT030677"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "마린B/T(선박도로용)"}, "이름": "마린B/T(선박도로용)", "단위": "ℓ", "수량": "0.96", @@ -11594,7 +11579,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "2.3 * 6000", @@ -11604,7 +11589,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.6", @@ -11613,11 +11598,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "1.9", @@ -11626,7 +11607,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "54.6", @@ -11635,7 +11616,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "규사", "단위": "㎥", "수량": "0.018", @@ -11644,7 +11625,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030704"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "중유"}, "이름": "중유", "단위": "D/M", "수량": "0.0018", @@ -11662,7 +11643,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "15μ", "대표": "MT030678"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Zinc primer 15μ"}, "이름": "Zinc primer", "단위": "㎏", "수량": "0.14", @@ -11671,7 +11652,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "125μ", "대표": "MT030679"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Tar Epoxy 125μ"}, "이름": "Tar Epoxy", "단위": "㎾h", "수량": "550", @@ -11680,7 +11661,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "12\"ø", "대표": "MT030674"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그라인더돌 12\"ø"}, "이름": "그라인더돌", "단위": "개", "수량": "0.3", @@ -11875,7 +11856,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.69 * 6000", @@ -11885,7 +11866,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.2", @@ -11894,7 +11875,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "31.05", @@ -11903,7 +11884,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "2회", "대표": "MT030680"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Tar Epoxy 2회"}, "이름": "Tar Epoxy", "단위": "ℓ", "수량": "0.536", @@ -12098,7 +12079,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "2.2 * 6000", @@ -12108,7 +12089,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.6", @@ -12117,11 +12098,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "1.7", @@ -12130,7 +12107,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "전기용접봉", "단위": "㎏", "수량": "22.5", @@ -12326,7 +12303,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.5 * 6000", @@ -12336,7 +12313,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.05", @@ -12345,7 +12322,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "7", @@ -12515,7 +12492,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "1.805 * 6000", @@ -12525,7 +12502,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.275", @@ -12534,7 +12511,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "20.7", @@ -12543,11 +12520,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석(Template)", "단위": "매", "수량": "0.53", @@ -12556,7 +12529,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "연마석", "단위": "개", "수량": "1.55", @@ -12565,7 +12538,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "규격": "1/4\"", "대표": "MT030681"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "drill 1/4\""}, "이름": "drill", "단위": "개", "수량": "0.96", @@ -12574,7 +12547,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "규격": "11/15\"", "대표": "MT030682"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "drill 11/15\""}, "이름": "drill", "단위": "개", "수량": "0.96", @@ -12583,10 +12556,7 @@ }, { "종류": "재료", - "요소": { - "구분": "기계·설비", - "상세구분": "기계류" - }, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "Bite", "단위": "개", "수량": "2.5", @@ -12595,7 +12565,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "톱날", "단위": "개", "수량": "2.5", @@ -12604,10 +12574,7 @@ }, { "종류": "재료", - "요소": { - "구분": "기계·설비", - "상세구분": "기계류" - }, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "Bite", "단위": "개", "수량": "3.2", @@ -12815,7 +12782,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "2.2 * 6000", @@ -12825,7 +12792,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "1.5", @@ -12834,11 +12801,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "1.2", @@ -12847,7 +12810,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "30.5", @@ -12856,7 +12819,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "규사", "단위": "㎥", "수량": "0.19", @@ -12865,7 +12828,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030704"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "중유"}, "이름": "중유", "단위": "D/M", "수량": "0.019", @@ -12883,7 +12846,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "15μ", "대표": "MT030678"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Zinc primer 15μ"}, "이름": "Zinc primer", "단위": "ℓ", "수량": "0.36", @@ -12892,7 +12855,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "125μ", "대표": "MT030679"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Tar Epoxy 125μ"}, "이름": "Tar Epoxy", "단위": "㎾h", "수량": "420", @@ -12901,7 +12864,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "12\"ø", "대표": "MT030674"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그라인더돌 12\"ø"}, "이름": "그라인더돌", "단위": "개", "수량": "0.33", @@ -13057,7 +13020,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "1.89 * 6000", @@ -13067,7 +13030,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.8", @@ -13076,7 +13039,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "8", @@ -13085,11 +13048,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "0.71", @@ -13271,7 +13230,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.029 * 6000", @@ -13281,7 +13240,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.012", @@ -13290,7 +13249,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "5.95", @@ -13299,7 +13258,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "규격": "1회도장", "대표": "MT030683"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "Tar Epoxy 1회도장"}, "이름": "Tar Epoxy", "단위": "ℓ", "수량": "7.06", @@ -13308,7 +13267,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "1.58", @@ -15488,7 +15447,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "가루비누", "단위": "㎏", "수량": "0.04", @@ -18095,7 +18054,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.2 * 6000", @@ -18105,7 +18064,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.13", @@ -18114,7 +18073,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "3.5", @@ -18124,7 +18083,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "걸레", "단위": "㎏", "수량": "2", @@ -18151,7 +18110,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030647"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Machine oil"}, "이름": "Machine oil", "단위": "ℓ", "수량": "0.7", @@ -18403,7 +18362,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "0.68 * 6000", @@ -18413,7 +18372,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "0.58", @@ -18422,7 +18381,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "산소 KS E4301", "대표": "MT003187"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소 KS E4301"}, "이름": "용접봉", "단위": "kg", "수량": "14.2", @@ -18432,7 +18391,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "광명단", "단위": "ℓ", "수량": "2.2", @@ -18441,7 +18400,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "페인트", "단위": "ℓ", "수량": "4.4", @@ -19057,7 +19016,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소", "단위": "L", "수량": "(만약(공사 == '기기', 0.109, 만약(공사 == '철골', 1.5, 만약(공사 == '배관', 5, 만약(공사 == 'Belt & Conveyor', 1.5, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.1, 만약(공사 == 'Crane 류', 0.44, 0)))))))) * 6000", @@ -19067,7 +19026,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌", "단위": "kg", "수량": "(만약(공사 == '기기', 0.084, 만약(공사 == '철골', 1.25, 만약(공사 == '배관', 3.7, 만약(공사 == 'Belt & Conveyor', 1.25, 만약(공사 == 'Heater & Tank', 0.08, 만약(공사 == 'Pump & Fan', 0.08, 만약(공사 == 'Crane 류', 0.355, 0))))))))", @@ -19076,7 +19035,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "용접봉(전기)", "단위": "㎏", "수량": "(만약(공사 == '기기', 0.365, 만약(공사 == '철골', 2.25, 만약(공사 == '배관', 30, 만약(공사 == 'Belt & Conveyor', 2.25, 만약(공사 == 'Heater & Tank', 0.36, 만약(공사 == 'Pump & Fan', 0.36, 만약(공사 == 'Crane 류', 0.85, 0))))))))", @@ -19085,7 +19044,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, "이름": "용접봉(산소)", "단위": "㎏", "수량": "(만약(공사 == '기기', 0.146, 만약(공사 == '철골', 0.22, 만약(공사 == '배관', 3, 만약(공사 == 'Belt & Conveyor', 0.22, 만약(공사 == 'Heater & Tank', 0.15, 만약(공사 == 'Pump & Fan', 0.14, 만약(공사 == 'Crane 류', 0.15, 0))))))))", @@ -19103,7 +19062,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030684"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "M/C OIL"}, "이름": "M/C OIL", "단위": "ℓ", "수량": "(만약(공사 == '기기', 0.365, 만약(공사 == '철골', 0.04, 만약(공사 == '배관', 4.6, 만약(공사 == 'Belt & Conveyor', 0.1, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.36, 만약(공사 == 'Crane 류', 0.7, 0))))))))", @@ -19112,7 +19071,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030634"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Wire Brush"}, "이름": "Wire Brush", "단위": "EA", "수량": "(만약(공사 == '기기', 0.292, 만약(공사 == '철골', 0.15, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.1, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.3, 만약(공사 == 'Crane 류', 0.1, 0))))))))", @@ -19121,7 +19080,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030685"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Grinder Wheel"}, "이름": "Grinder Wheel", "단위": "매", "수량": "(만약(공사 == '기기', 0.022, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.02, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19130,7 +19089,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030686"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Oil Stone"}, "이름": "Oil Stone", "단위": "개", "수량": "(만약(공사 == '기기', 0.055, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.15, 만약(공사 == 'Crane 류', 0.02, 0))))))))", @@ -19139,7 +19098,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030687"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "File"}, "이름": "File", "단위": "개", "수량": "(만약(공사 == '기기', 0.218, 만약(공사 == '철골', 0.2, 만약(공사 == '배관', 0.1, 만약(공사 == 'Belt & Conveyor', 0.1, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.2, 만약(공사 == 'Crane 류', 0.1, 0))))))))", @@ -19148,7 +19107,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "보통철선 #8 서울"}, "이름": "아연도철선", "단위": "㎏", "수량": "(만약(공사 == '기기', 0.73, 만약(공사 == '철골', 0.73, 만약(공사 == '배관', 0.4, 만약(공사 == 'Belt & Conveyor', 0.2, 만약(공사 == 'Heater & Tank', 0.2, 만약(공사 == 'Pump & Fan', 0.73, 만약(공사 == 'Crane 류', 0.2, 0))))))))", @@ -19175,7 +19134,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "복사용지(A4)"}, "이름": "사포", "단위": "매", "수량": "(만약(공사 == '기기', 0.11, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.01, 만약(공사 == 'Pump & Fan', 0.11, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19184,7 +19143,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, "이름": "걸레", "단위": "㎏", "수량": "(만약(공사 == '기기', 0.73, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.2, 만약(공사 == 'Belt & Conveyor', 0.3, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.73, 만약(공사 == 'Crane 류', 0.73, 0))))))))", @@ -19193,7 +19152,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC천막지 T-210"}, "이름": "비닐시드", "단위": "㎡", "수량": "(만약(공사 == '기기', 0.037, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.04, 만약(공사 == 'Crane 류', 0.2, 0))))))))", @@ -19202,7 +19161,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "(만약(공사 == '기기', 0.138, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.38, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19238,7 +19197,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030691"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "Seal Tape"}, "이름": "Seal Tape", "단위": "통", "수량": "(만약(공사 == '기기', 0.1, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.87, 만약(공사 == 'Belt & Conveyor', 0.1, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.1, 만약(공사 == 'Crane 류', 0.1, 0))))))))", @@ -19247,7 +19206,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030692"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "백묵"}, "이름": "백묵", "단위": "통", "수량": "(만약(공사 == '기기', 0.1, 만약(공사 == '철골', 0.2, 만약(공사 == '배관', 0.1, 만약(공사 == 'Belt & Conveyor', 0.15, 만약(공사 == 'Heater & Tank', 0.15, 만약(공사 == 'Pump & Fan', 0.15, 만약(공사 == 'Crane 류', 0.15, 0))))))))", @@ -19256,7 +19215,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030693"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "석필"}, "이름": "석필", "단위": "통", "수량": "(만약(공사 == '기기', 0.2, 만약(공사 == '철골', 0.3, 만약(공사 == '배관', 0.2, 만약(공사 == 'Belt & Conveyor', 0.2, 만약(공사 == 'Heater & Tank', 0.2, 만약(공사 == 'Pump & Fan', 0.2, 만약(공사 == 'Crane 류', 0.2, 0))))))))", @@ -19265,11 +19224,7 @@ }, { "종류": "재료", - "요소": { - "구분": "잡재료", - "상세구분": "고철·기타금속", - "대표": "MT003499" - }, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "골함석 0.35×914×1829"}, "이름": "함석", "단위": "매", "수량": "(만약(공사 == '기기', 0.05, 만약(공사 == '철골', 0.07, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.05, 만약(공사 == 'Crane 류', 0.07, 0))))))))", @@ -19278,7 +19233,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030694"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "흑 Welder Glass"}, "이름": "흑 Welder Glass", "단위": "연", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.01, 만약(공사 == 'Belt & Conveyor', 0.01, 만약(공사 == 'Heater & Tank', 0.01, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.01, 0))))))))", @@ -19287,7 +19242,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030695"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "백 Welder Glass"}, "이름": "백 Welder Glass", "단위": "연", "수량": "(만약(공사 == '기기', 0.1, 만약(공사 == '철골', 0.2, 만약(공사 == '배관', 0.1, 만약(공사 == 'Belt & Conveyor', 0.2, 만약(공사 == 'Heater & Tank', 0.2, 만약(공사 == 'Pump & Fan', 0.1, 만약(공사 == 'Crane 류', 0.2, 0))))))))", @@ -19296,7 +19251,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030696"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "오스터날"}, "이름": "오스터날", "단위": "SET", "수량": "(만약(공사 == '기기', 0.05, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.3, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.05, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19305,7 +19260,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030697"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "탭"}, "이름": "탭", "단위": "SET", "수량": "(만약(공사 == '기기', 0.05, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.05, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19314,7 +19269,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030698"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "다이스"}, "이름": "다이스", "단위": "개", "수량": "(만약(공사 == '기기', 0.05, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.05, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19323,7 +19278,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030699"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "정"}, "이름": "정", "단위": "개", "수량": "(만약(공사 == '기기', 0.1, 만약(공사 == '철골', 0.2, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.1, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19332,7 +19287,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, "이름": "용접면", "단위": "개", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.02, 0))))))))", @@ -19341,7 +19296,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, "이름": "용접홀다", "단위": "개", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.02, 0))))))))", @@ -19350,7 +19305,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, "이름": "용접앞치마", "단위": "개", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.05, 0))))))))", @@ -19359,7 +19314,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "대표": "MT030700"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "Center Punch"}, "이름": "Center Punch", "단위": "개", "수량": "(만약(공사 == '기기', 0.02, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.02, 만약(공사 == 'Crane 류', 0.02, 0))))))))", @@ -19368,7 +19323,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "세파볼트 D형"}, "이름": "써비스볼트", "단위": "본", "수량": "(만약(공사 == '기기', 1, 만약(공사 == '철골', 2, 만약(공사 == '배관', 1, 만약(공사 == 'Belt & Conveyor', 1, 만약(공사 == 'Heater & Tank', 1, 만약(공사 == 'Pump & Fan', 1, 만약(공사 == 'Crane 류', 1, 0))))))))", @@ -19377,7 +19332,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030701"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "대강"}, "이름": "대강", "단위": "㎏", "수량": "(만약(공사 == '기기', 0.02, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.1, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.02, 만약(공사 == 'Crane 류', 0.1, 0))))))))", @@ -19386,7 +19341,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "대표": "MT030702"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "유지"}, "이름": "유지", "단위": "ℓ", "수량": "(만약(공사 == '기기', 0.07, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.07, 만약(공사 == 'Belt & Conveyor', 0.07, 만약(공사 == 'Heater & Tank', 0.07, 만약(공사 == 'Pump & Fan', 0.07, 만약(공사 == 'Crane 류', 0.07, 0))))))))", @@ -19395,7 +19350,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "대표": "MT030703"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "Washer"}, "이름": "Washer", "단위": "매", "수량": "(만약(공사 == '기기', 0.3, 만약(공사 == '철골', 0.5, 만약(공사 == '배관', 0.3, 만약(공사 == 'Belt & Conveyor', 0.3, 만약(공사 == 'Heater & Tank', 0.3, 만약(공사 == 'Pump & Fan', 0.3, 만약(공사 == 'Crane 류', 0.3, 0))))))))", @@ -19404,7 +19359,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "페인트(표기용)", "단위": "ℓ", "수량": "(만약(공사 == '기기', 0.069, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.5, 만약(공사 == 'Belt & Conveyor', 0.1, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.07, 만약(공사 == 'Crane 류', 0.1, 0))))))))", @@ -19413,7 +19368,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, "이름": "페인트붓(표기용)", "단위": "개", "수량": "(만약(공사 == '기기', 0.05, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.05, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.05, 만약(공사 == 'Crane 류', 0.05, 0))))))))", diff --git a/resources/master_data/로직_산림품셈_06장_생육보조.json b/resources/master_data/로직_산림품셈_06장_생육보조.json index 3e095a3b..8b5413b7 100644 --- a/resources/master_data/로직_산림품셈_06장_생육보조.json +++ b/resources/master_data/로직_산림품셈_06장_생육보조.json @@ -292,7 +292,7 @@ }, { "종류": "재료", - "요소": {"구분": "식생·종자", "상세구분": "농약·약제"}, + "요소": {"구분": "식생·종자", "상세구분": "농약·약제", "검색어": "푸른솔(분산성액제) 수간주사(10㎖)"}, "이름": "농약(Fluroxypyr-meptyl+Triclypyr-TEA 미탁제)", "단위": "병", "수량": "찾기(QF000007).주재료 * (1 + 증감 / 100)", @@ -490,7 +490,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "우드칩", "단위": "㎥", "수량": "찾기(QF000177, 구분='우드칩').수량", diff --git a/resources/master_data/로직_산림품셈_08장_방제.json b/resources/master_data/로직_산림품셈_08장_방제.json index ccc7807b..e2c43628 100644 --- a/resources/master_data/로직_산림품셈_08장_방제.json +++ b/resources/master_data/로직_산림품셈_08장_방제.json @@ -287,7 +287,7 @@ { "종류": "재료", "요소": "MO:휘발유:{지역}", "이름": "휘발유(양수기)", "단위": "ℓ", "수량": "휘발유", "비목": "재료비", "출처": "산림품셈 2-1-8" }, { "종류": "재료", - "요소": {"구분": "안전시설", "상세구분": "표지판·지주", "대표": "MT030609"}, + "요소": {"구분": "안전시설", "상세구분": "표지판·지주", "검색어": "깃발"}, "이름": "깃발", "단위": "개", "수량": "찾기(QF000016, 구분='깃발').수량 / 찾기(CF000090, 구분='1일 살포면적 소형헬기').적용", diff --git a/resources/master_data/로직_산림품셈_09장_토공.json b/resources/master_data/로직_산림품셈_09장_토공.json index 5d36b33b..8abe6be1 100644 --- a/resources/master_data/로직_산림품셈_09장_토공.json +++ b/resources/master_data/로직_산림품셈_09장_토공.json @@ -328,7 +328,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "98% (용접용)", "대표": "MT003163"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98% (용접용)"}, "이름": "아세틸렌가스", "단위": "kg", "수량": "찾기(QF000260, 이름='아세틸렌').적용", @@ -336,7 +336,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "규격": "6000ℓ/병", "대표": "MT003165"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "산소 6000ℓ/병"}, "이름": "산소가스", "단위": "ℓ", "수량": "찾기(QF000260, 이름='산소').적용", @@ -344,7 +344,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "고철·기타금속"}, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "봉땜납 Sn"}, "이름": "고철공제", "단위": "kg", "수량": "-50", diff --git a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json index 7b753bc7..d114b79d 100644 --- a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json +++ b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json @@ -452,7 +452,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "소재(원목)", "단위": "㎥", "수량": "찾기(QF000323, 구분='운반대').소재", @@ -460,7 +460,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "각재·판재", "단위": "㎥", "수량": "0.2", diff --git a/resources/master_data/로직_산림품셈_11장_가설.json b/resources/master_data/로직_산림품셈_11장_가설.json index e4fa2ae4..b7d9c0d7 100644 --- a/resources/master_data/로직_산림품셈_11장_가설.json +++ b/resources/master_data/로직_산림품셈_11장_가설.json @@ -71,7 +71,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "목재 손료", "단위": "㎥", "수량": "목재량 * 찾기(QF000346).손율 / 100", @@ -116,7 +116,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "미송각재 30×30×3600mm 서울"}, "이름": "목재 손료", "단위": "㎥", "수량": "목재량 * 찾기(QF000348).손율 / 100", diff --git a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json index b974d03b..1470a2f3 100644 --- a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json +++ b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json @@ -190,7 +190,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "kg", "수량": "만약(수중 == '수중 콘크리트', 최대(만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트) * 1.3, 370), 만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트))", @@ -199,7 +199,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재", "규격": "모래"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래"}, "이름": "모래(건조상태)", "단위": "㎥", "수량": "만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).모래) / 모래밀도", @@ -209,7 +209,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재", "규격": "{골재최대치수}mm"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "자연자갈 {골재최대치수}mm"}, "이름": "자갈 또는 부순돌", "단위": "㎥", "수량": "만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).자갈_또는_부순돌) / 자갈밀도", @@ -325,7 +325,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "kg", "수량": "만약(수중 == '수중 콘크리트', 최대(만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트) * 1.3, 370), 만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).시멘트))", @@ -334,7 +334,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재", "규격": "모래"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래"}, "이름": "모래(건조상태)", "단위": "㎥", "수량": "만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).모래) / 모래밀도", @@ -344,7 +344,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재", "규격": "{골재최대치수}mm"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "자연자갈 {골재최대치수}mm"}, "이름": "자갈 또는 부순돌", "단위": "㎥", "수량": "만약(재료배합 == '계상 안 함', 0, 찾기(QF000353, 골재최대치수=골재최대치수, 배합종류=재료배합).자갈_또는_부순돌) / 자갈밀도", @@ -419,13 +419,6 @@ 40 ], "출처": "산림품셈 12-3 주③" - }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] } ], "중간": [ @@ -480,7 +473,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{결속선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선(결속선) #20 서울"}, "이름": "결속선 0.9㎜", "단위": "kg", "수량": "만약(구조별 == '간단', 5, 만약(구조별 == '보통', 6.5, 8))", @@ -524,48 +517,6 @@ "6회사용시" ] }, - { - "이름": "합판", - "설명": "거푸집에 쓸 합판 품목", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "설명": "거푸집을 받치는 나무 품목", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "설명": "거푸집을 묶는 철선 품목", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "설명": "거푸집에 쓸 못 품목", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "설명": "거푸집을 떼기 쉽게 바르는 기름 품목", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "설명": "높은 곳 작업이라 더 드는 품 비율", @@ -607,7 +558,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "합판", "대표": "{합판}"}, + "요소": {"구분": "목재", "상세구분": "합판", "검색어": "합판(내수) 12t 서울"}, "이름": "합판(내수 12㎜)", "단위": "㎡", "수량": "찾기(QF000357, 구분='합판').기준수량_1회사용 * 재료율", @@ -615,7 +566,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재", "대표": "{각재}"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "외송각재 30×30×3600mm 서울"}, "이름": "각재", "단위": "㎥", "수량": "찾기(QF000357, 구분='각재').기준수량_1회사용 * 재료율", @@ -623,7 +574,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{철선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선 #8 서울"}, "이름": "철선", "단위": "kg", "수량": "찾기(QF000357, 구분='철선').기준수량_1회사용 * 재료율", @@ -631,7 +582,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "못", "대표": "{못}"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "못", "검색어": "철못 N25 서울"}, "이름": "못", "단위": "kg", "수량": "찾기(QF000357, 구분='못').기준수량_1회사용 * 재료율", @@ -639,7 +590,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "대표": "{박리제}"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "박리제(중유) 저유황(목재,합판용)"}, "이름": "박리제", "단위": "ℓ", "수량": "찾기(QF000357, 구분='박리제').기준수량_1회사용 * 재료율", @@ -687,7 +638,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "샌드위치판넬(그라스울) T50"}, "이름": "문양 스티로폴(자재비 포함)", "단위": "㎡", "수량": "찾기(QF000359, 구분='문양 스티로폴(자재비 포함)').적용", @@ -893,7 +844,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, "이름": "커터 블레이드", "단위": "개", "수량": "0.31 / 100", @@ -902,7 +853,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용수", "대표": "MT005758"}, + "요소": {"구분": "잡재료", "상세구분": "용수", "검색어": "물(상수도)"}, "이름": "물", "단위": "ℓ", "수량": "3000 / 100", @@ -993,7 +944,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "비탈형영구거푸집(KC-FORM) 450용"}, "이름": "핀폴 손료", "단위": "개", "수량": "1 * (1 - 10 / 100) / 20", @@ -1275,43 +1226,6 @@ "이름": "비닐면적", "단위": "㎡/m" }, - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "단위": "%", @@ -1343,20 +1257,13 @@ ], "출처": "산림품셈 12-3 주⑤" }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] - }, {"이름": "자재지역", "비고": "시·군 이름 — 레미콘·골재 시중물가 후보를 고름"} ], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "콘크리트(레미콘)", "단위": "㎥", "수량": "콘크리트량", @@ -1371,14 +1278,14 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철근", "규격": "D=16mm", "대표": "MT014464"}, + "요소": {"구분": "철근·강재", "상세구분": "철근", "검색어": "이형철근(SD300) D=16mm 전국"}, "이름": "연결철근(SD300, D16)", "단위": "ton", "수량": "연결철근량", @@ -1386,7 +1293,7 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='간단', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='간단', 강선='아님', 강선가산=0)", "이름": "연결철근 가공조립(간단)의 30%", "단위": "ton", "수량": "연결철근량 * 30 / 100", @@ -1507,43 +1414,6 @@ ], "출처": "건설품셈 공통 8-1-7 5" }, - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "단위": "%", @@ -1575,13 +1445,6 @@ ], "출처": "산림품셈 12-3 주⑤" }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] - }, {"이름": "자재지역", "비고": "시·군 이름 — 레미콘·골재 시중물가 후보를 고름"} ], "중간": [], @@ -1596,14 +1459,14 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='간단', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='간단', 강선='아님', 강선가산=0)", "이름": "철근가공조립(간단)의 30%", "단위": "ton", "수량": "철근량 * 30 / 100", @@ -1716,43 +1579,6 @@ ], "출처": "건설품셈 공통 8-1-7 5" }, - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "단위": "%", @@ -1789,7 +1615,7 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" @@ -1827,7 +1653,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PE관", "규격": "∅200㎜"}, + "요소": {"구분": "관·이음관", "상세구분": "PE관", "검색어": "PE 다중벽관 ∅200㎜"}, "이름": "유공관(∅200㎜)", "단위": "m", "수량": "찾기(QF000369, 구분='유공관설치 - 유공관').적용", @@ -1835,7 +1661,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "부직포", "단위": "㎡", "수량": "부직포면적", @@ -1844,7 +1670,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "잡석 구입", "단위": "㎥", "수량": "잡석량", @@ -1992,48 +1818,6 @@ ], "출처": "건설품셈 공통 8-1-7 5" }, - { - "이름": "합판", - "설명": "거푸집에 쓸 합판 품목", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "설명": "거푸집을 받치는 나무 품목", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "설명": "거푸집을 묶는 철선 품목", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "설명": "거푸집에 쓸 못 품목", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "설명": "거푸집을 떼기 쉽게 바르는 기름 품목", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "설명": "높은 곳 작업이라 더 드는 품 비율", @@ -2081,7 +1865,7 @@ }, { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "D={관경}mm"}, + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "검색어": "VR관 D={관경}mm"}, "이름": "VR관", "단위": "본", "수량": "찾기(QF000370, 구분='VR관', 관경=관경).적용 / 2.5", @@ -2090,7 +1874,7 @@ }, { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "D={관경}mm"}, + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "검색어": "VR관고무링 D={관경}mm"}, "이름": "고무링", "단위": "개", "수량": "찾기(QF000370, 구분='고무링', 관경=관경).적용", @@ -2098,7 +1882,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT030601"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "지수활제"}, "이름": "지수활제", "단위": "g", "수량": "찾기(QF000370, 구분='지수활제', 관경=관경).적용", @@ -2114,7 +1898,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "기초콘크리트(레미콘) 무근", "단위": "㎥", "수량": "기초콘크리트량", @@ -2129,7 +1913,7 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='6회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='6회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(6회)", "단위": "㎡", "수량": "거푸집면적" @@ -2235,48 +2019,6 @@ ], "출처": "건설품셈 공통 8-1-7 5" }, - { - "이름": "합판", - "설명": "거푸집에 쓸 합판 품목", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "설명": "거푸집을 받치는 나무 품목", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "설명": "거푸집을 묶는 철선 품목", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "설명": "거푸집에 쓸 못 품목", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "설명": "거푸집을 떼기 쉽게 바르는 기름 품목", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "설명": "높은 곳 작업이라 더 드는 품 비율", @@ -2324,11 +2066,7 @@ }, { "종류": "재료", - "요소": { - "구분": "관·이음관", - "상세구분": "흄관", - "규격": "{관경}" - }, + "요소": {"구분": "관·이음관", "상세구분": "흄관", "검색어": "{관경}"}, "이름": "흄관", "단위": "m", "수량": "찾기(QF000371, 구분='흄관', 관경=관경).적용", @@ -2336,7 +2074,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "규격": "1:2"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:2 서울"}, "이름": "접합몰탈(1:2)", "단위": "㎥", "수량": "찾기(QF000371, 구분='접합몰탈', 관경=관경).적용", @@ -2352,7 +2090,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "기초콘크리트(레미콘) 무근", "단위": "㎥", "수량": "기초콘크리트량", @@ -2367,7 +2105,7 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='6회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='6회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(6회)", "단위": "㎡", "수량": "거푸집면적" @@ -2473,11 +2211,7 @@ "호표": [ { "종류": "재료", - "요소": { - "구분": "관·이음관", - "상세구분": "파형강관", - "규격": "{관경}" - }, + "요소": {"구분": "관·이음관", "상세구분": "파형강관", "검색어": "파형강관 {관경}"}, "이름": "파형강관", "단위": "m", "수량": "찾기(QF000373, 구분='파형강관', 관경=관경).적용", @@ -2485,12 +2219,7 @@ }, { "종류": "재료", - "요소": { - "구분": "관·이음관", - "상세구분": "파형강관", - "이름": "파형강관 커플링밴드(관급)", - "규격": "Φ{관경}mm" - }, + "요소": {"구분": "관·이음관", "상세구분": "파형강관", "검색어": "파형강관커플링밴드 파형강관 커플링밴드(관급) Φ{관경}mm"}, "이름": "커플링밴드", "단위": "EA", "수량": "만약(커플링밴드 == '필요', 1 / 6, 0)", @@ -2615,48 +2344,6 @@ ], "출처": "산림품셈 12-12 주" }, - { - "이름": "합판", - "설명": "거푸집에 쓸 합판 품목", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "설명": "거푸집을 받치는 나무 품목", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "설명": "거푸집을 묶는 철선 품목", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "설명": "거푸집에 쓸 못 품목", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "설명": "거푸집을 떼기 쉽게 바르는 기름 품목", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "설명": "높은 곳 작업이라 더 드는 품 비율", @@ -2692,14 +2379,6 @@ ], "출처": "산림품셈 12-3 주⑤" }, - { - "이름": "결속선", - "설명": "철근을 묶는 철선 품목", - "고르기": [ - "MT000209", - "MT000174" - ] - }, { "이름": "브레이커", "설명": "굳은 땅을 깨는 장비 규격", @@ -2750,7 +2429,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "콘크리트(레미콘)", "단위": "㎥", "수량": "콘크리트량", @@ -2776,14 +2455,14 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철근", "규격": "D=13mm", "대표": "MT014463"}, + "요소": {"구분": "철근·강재", "상세구분": "철근", "검색어": "이형철근(SD300) D=13mm 전국"}, "이름": "철근(SD300, D13)", "단위": "ton", "수량": "철근량", @@ -2791,7 +2470,7 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='간단', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='간단', 강선='아님', 강선가산=0)", "이름": "철근 가공조립(간단)", "단위": "ton", "수량": "철근량" @@ -2878,43 +2557,6 @@ "이름": "기초잡석량", "단위": "㎥" }, - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "단위": "%", @@ -2984,7 +2626,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "콘크리트(레미콘)", "단위": "㎥", "수량": "콘크리트량", @@ -3010,7 +2652,7 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" @@ -3239,43 +2881,6 @@ ], "출처": "건설품셈 공통 8-1-7 5" }, - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "단위": "%", @@ -3307,20 +2912,13 @@ ], "출처": "산림품셈 12-3 주⑤" }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] - }, {"이름": "자재지역", "비고": "시·군 이름 — 레미콘·골재 시중물가 후보를 고름"} ], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "콘크리트(레미콘) 구체 · 버림", "단위": "㎥", "수량": "구체콘크리트량 + 버림콘크리트량", @@ -3364,14 +2962,14 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철근"}, + "요소": {"구분": "철근·강재", "상세구분": "철근", "검색어": "에폭시피복철근(가공비) D25mm"}, "이름": "철근", "단위": "ton", "수량": "철근량", @@ -3379,14 +2977,14 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='보통', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='보통', 강선='아님', 강선가산=0)", "이름": "철근 가공조립(보통)", "단위": "ton", "수량": "철근량" }, { "종류": "재료", - "요소": {"구분": "맨홀·수로·그레이팅", "상세구분": "맨홀"}, + "요소": {"구분": "맨홀·수로·그레이팅", "상세구분": "맨홀", "검색어": "PE빗물받이스틸뚜껑 495×395×50T"}, "이름": "집수정 뚜껑(스틸그레이팅)", "단위": "개", "수량": "뚜껑수", @@ -3515,48 +3113,6 @@ ], "출처": "건설품셈 공통 8-1-7 5" }, - { - "이름": "합판", - "설명": "거푸집에 쓸 합판 품목", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "설명": "거푸집을 받치는 나무 품목", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "설명": "거푸집을 묶는 철선 품목", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "설명": "거푸집에 쓸 못 품목", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "설명": "거푸집을 떼기 쉽게 바르는 기름 품목", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "설명": "높은 곳 작업이라 더 드는 품 비율", @@ -3592,21 +3148,13 @@ ], "출처": "산림품셈 12-3 주⑤" }, - { - "이름": "결속선", - "설명": "철근을 묶는 철선 품목", - "고르기": [ - "MT000209", - "MT000174" - ] - }, {"이름": "자재지역", "설명": "레미콘·골재 값을 가져올 시·군", "비고": "시·군 이름 — 레미콘·골재 시중물가 후보를 고름"} ], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "콘크리트(레미콘) 구체 · 버림", "단위": "㎥", "수량": "구체콘크리트량 + 버림콘크리트량", @@ -3650,7 +3198,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형거푸집(PE 10회) 스라브 및 기초", "단위": "조", "수량": "1 / 10", @@ -3660,7 +3208,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, "이름": "원형거푸집(PE 10회) 벽체", "단위": "조", "수량": "1 / 10", @@ -3705,14 +3253,14 @@ }, { "종류": "로직", - "요소": "로직(GF000160, 횟수='4회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='4회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(4회)", "단위": "㎡", "수량": "거푸집면적" }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철근"}, + "요소": {"구분": "철근·강재", "상세구분": "철근", "검색어": "에폭시피복철근(가공비) D25mm"}, "이름": "철근", "단위": "ton", "수량": "철근량", @@ -3720,14 +3268,14 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='보통', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='보통', 강선='아님', 강선가산=0)", "이름": "철근 가공조립(보통)", "단위": "ton", "수량": "철근량" }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "규격": "∅19㎜ 아연도금 및 스테인레스", "대표": "MT030600"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "사다리 ∅19㎜ 아연도금 및 스테인레스"}, "이름": "사다리(∅19㎜ 아연도금 및 스테인레스)", "단위": "ton", "수량": "사다리량", @@ -3735,7 +3283,7 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='간단', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='간단', 강선='아님', 강선가산=0)", "이름": "사다리설치(철근가공조립(간단) 준용)", "단위": "ton", "수량": "사다리량" @@ -3936,7 +3484,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"}, + "요소": {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"}, "이름": "시멘트", "단위": "kg", "수량": "찾기(QF000385, 배합비=배합비).시멘트", @@ -4102,7 +3650,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "구체콘크리트(철근) 레미콘", "단위": "㎥", "수량": "찾기(QF000387, 구분='구체콘크리트(철근) - 레미콘').적용", @@ -4237,7 +3785,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "버림콘크리트(무근) 레미콘", "단위": "㎥", "수량": "찾기(QF000388, 구분='버림콘크리트(무근) - 콘크리트').적용", @@ -4278,20 +3826,12 @@ "출처": "산림품셈 12-18", "소유": "공용", "비고": "해석: 결속선 = 철선 Φ0.9㎜ · 철근 재료는 원문 표에 없음 — 부르는 절에서", - "입력": [ - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] - } - ], + "입력": [], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{결속선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선(결속선) #20 서울"}, "이름": "결속선(R=0.9㎜)", "단위": "kg", "수량": "찾기(QF000389, 구분='결속선(R=0.9mm)').적용", @@ -4336,7 +3876,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "규격": "Φ48.6*2.3*4.0m", "대표": "MT000481"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "강관비계 Φ48.6*2.3*4.0m 서울"}, "이름": "강관(∅48.6㎜×2.4㎜)", "단위": "m", "수량": "찾기(QF000390, 구분='자재 - 강관(∅48.6mm×2.4mm)').적용", @@ -4344,7 +3884,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "대표": "MT000493"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "이음철물 서울"}, "이름": "이음철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 이음철물').적용", @@ -4352,7 +3892,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "규격": "직교,자재", "대표": "MT000499"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "조임철물 직교,자재 서울"}, "이름": "조임철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 조임철물').적용", @@ -4360,7 +3900,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "대표": "MT000505"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "받침철물 서울"}, "이름": "받침철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 받침철물').적용", @@ -4368,7 +3908,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, "이름": "철물", "단위": "개", "수량": "찾기(QF000390, 구분='자재 - 철물').적용", @@ -4405,7 +3945,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "강관동바리 Φ48.6 서울"}, "이름": "강관동바리 내관(48.6㎜×2.4㎜)", "단위": "본", "수량": "찾기(QF000391, 구분='자재 - 강관동바리 - 내관(48.6mm×2.4mm)').적용", @@ -4413,7 +3953,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "강관동바리 Φ48.6 서울"}, "이름": "강관동바리 외관(60.6㎜×2.3㎜)", "단위": "본", "수량": "찾기(QF000391, 구분='자재 - 강관동바리 - 외관(60.6mm×2.3mm)').적용", @@ -4457,7 +3997,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "아스콘·포장", "상세구분": "아스팔트"}, + "요소": {"구분": "아스콘·포장", "상세구분": "아스팔트", "검색어": "아스팔트 AP-3(벌크)"}, "이름": "아스팔트(㏊-500)", "단위": "kg", "수량": "찾기(QF000392, 구분='자재 - 아스팔트(㏊-500)').적용", @@ -4495,7 +4035,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, "이름": "부직포", "단위": "㎡", "수량": "찾기(QF000394, 구분='자재 - 부직포').적용", @@ -4537,7 +4077,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "50㎜"}, + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "검색어": "PVC파이프(일반관) 50㎜ 서울"}, "이름": "P.V.C 파이프 50㎜", "단위": "", "수량": "수량", @@ -4792,27 +4332,12 @@ "출처": "산림품셈 12-27-1", "소유": "공용", "비고": "해석: 철선 #8 = Φ4.0㎜", - "입력": [ - { - "이름": "용접봉", - "고르기": [ - "MT003197", - "MT003187" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000159" - ] - } - ], + "입력": [], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PVC관", "규격": "200×5T", "대표": "MT006544"}, + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "검색어": "PVC지수판 200×5T"}, "이름": "PVC 지수판(200×5)", "단위": "m", "수량": "찾기(QF000399, 구분='자재 - PVC 지수판(200×5)').적용", @@ -4820,7 +4345,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "대표": "{용접봉}"}, + "요소": {"구분": "잡재료", "검색어": "PVC용접봉 2.5mm"}, "이름": "용접봉", "단위": "kg", "수량": "찾기(QF000399, 구분='자재 - 용접봉').적용", @@ -4828,7 +4353,7 @@ }, { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{철선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선 #8 서울"}, "이름": "철선(#8)", "단위": "kg", "수량": "찾기(QF000399, 구분='자재 - 철선(#8)').적용", @@ -4873,7 +4398,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT030602"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "JOINT FILLER"}, "이름": "JOINT FILLER", "단위": "㎡", "수량": "찾기(QF000400, 구분='자재 - JOINT FILLER').적용", @@ -4881,7 +4406,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "접착제", "단위": "kg", "수량": "찾기(QF000400, 구분='자재 - 접착제').적용", @@ -4910,7 +4435,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "쉬트파일수팽창지수재 DPS-500"}, "이름": "실런트", "단위": "kg", "수량": "찾기(QF000401, 구분='자재 - 실런트').적용", @@ -4942,7 +4467,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "접착·혼화", "검색어": "석고본드"}, "이름": "에폭시 접착제(콘크리트용)", "단위": "kg", "수량": "찾기(QF000402, 구분='자재 - 에폭시 접착제').적용", @@ -4950,7 +4475,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "목재전용도료 오일스테인"}, "이름": "시너", "단위": "ℓ", "수량": "찾기(QF000402, 구분='자재 - 시너').적용", @@ -4985,7 +4510,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "대표": "MT030603"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "스페이셔(몰탈블록)"}, "이름": "스페이셔(몰탈 블록)", "단위": "", "수량": "수량", @@ -5031,20 +4556,13 @@ 50 ], "출처": "산림품셈 12-3 주⑤" - }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] } ], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "형강"}, + "요소": {"구분": "철근·강재", "상세구분": "형강", "검색어": "트러스 (TRUSS) SPAN:L=7.2"}, "이름": "강봉(D32㎜, L=1000㎜)", "단위": "EA", "수량": "강봉수", @@ -5053,7 +4571,7 @@ }, { "종류": "재료", - "요소": {"구분": "관·이음관", "상세구분": "PVC관"}, + "요소": {"구분": "관·이음관", "상세구분": "PVC관", "검색어": "PVC파이프(일반관) D=30mm(VG1) 서울"}, "이름": "P.V.C 파이프(Ø35㎜)", "단위": "m", "수량": "파이프길이", @@ -5062,7 +4580,7 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='간단', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='간단', 강선='아님', 강선가산=0)", "이름": "강봉 철근가공조립(간단)", "단위": "ton", "수량": "철근량" @@ -5123,20 +4641,13 @@ 50 ], "출처": "산림품셈 12-3 주⑤" - }, - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] } ], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철근"}, + "요소": {"구분": "철근·강재", "상세구분": "철근", "검색어": "에폭시피복철근(가공비) D25mm"}, "이름": "원형철근(∅29㎜, L=500㎜)", "단위": "ton", "수량": "철근량", @@ -5145,7 +4656,7 @@ }, { "종류": "재료", - "요소": {"구분": "교량·구조부속", "상세구분": "신축이음·받침"}, + "요소": {"구분": "교량·구조부속", "상세구분": "신축이음·받침", "검색어": "익스팬션조인트휠러 t=10mm"}, "이름": "탄성고무받침(150×150×15)", "단위": "㎡", "수량": "고무받침면적", @@ -5153,7 +4664,7 @@ }, { "종류": "재료", - "요소": {"구분": "단열·보온", "상세구분": "단열재", "규격": "T=20㎜", "대표": "MT030604"}, + "요소": {"구분": "단열·보온", "상세구분": "단열재", "검색어": "스티로폴 T=20㎜"}, "이름": "스티로폴(T=20㎜)", "단위": "㎡", "수량": "스티로폴면적", @@ -5161,7 +4672,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "규격": "T=15㎜", "대표": "MT030605"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "TAR PAPER T=15㎜"}, "이름": "TAR PAPER(T=15㎜)", "단위": "㎡", "수량": "타르페이퍼면적", @@ -5169,7 +4680,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT030606"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "채움재(아스팔트)"}, "이름": "채움재(아스팔트)", "단위": "㎥", "수량": "찾기(QF000405, 구분='재료 - 채움재(아스팔트)').적용", @@ -5177,7 +4688,7 @@ }, { "종류": "재료", - "요소": {"구분": "교량·구조부속", "상세구분": "보강재·정착"}, + "요소": {"구분": "교량·구조부속", "상세구분": "보강재·정착", "검색어": "SRR 보강재 고인장보강재"}, "이름": "다웰바캡(D=50㎜)", "단위": "m", "수량": "다웰바캡길이", @@ -5185,7 +4696,7 @@ }, { "종류": "로직", - "요소": "로직(GF000159, 소형가산=소형가산, 결속선=결속선, 구조별='보통', 강선='아님', 강선가산=0)", + "요소": "로직(GF000159, 소형가산=소형가산, 구조별='보통', 강선='아님', 강선가산=0)", "이름": "원형철근 철근가공조립(보통)", "단위": "ton", "수량": "철근량" @@ -5227,7 +4738,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, "이름": "물끊기 홈(NOTCH) 재료", "단위": "", "수량": "수량", @@ -5257,7 +4768,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "전기·통신", "상세구분": "전선관·트레이", "규격": "HI-VE 54mm", "대표": "MT009019"}, + "요소": {"구분": "전기·통신", "상세구분": "전선관·트레이", "검색어": "전선관(PVC) HI-VE 54mm"}, "이름": "경질비닐전선관 54㎜", "단위": "m", "수량": "1", @@ -5286,7 +4797,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "규격": "PE 필름(0.08mm)", "대표": "MT006485"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "비닐 PE 필름(0.08mm)"}, "이름": "폴리에틸렌필름 t=0.08㎜", "단위": "㎡", "수량": "찾기(QF000408, 구분='재료').적용", @@ -5312,43 +4823,6 @@ "소유": "공용", "비고": "12-34-2 비율(46.1 · 47.1%) = 12-4 3회 사용 비율", "입력": [ - { - "이름": "합판", - "고르기": [ - "MT000018" - ] - }, - { - "이름": "각재", - "고르기": [ - "MT000339", - "MT000338" - ] - }, - { - "이름": "철선", - "고르기": [ - "MT000199", - "MT000204", - "MT000159", - "MT000164" - ] - }, - { - "이름": "못", - "고르기": [ - "MT000246", - "MT000247", - "MT000248", - "MT000249" - ] - }, - { - "이름": "박리제", - "고르기": [ - "MT000447" - ] - }, { "이름": "높이할증", "단위": "%", @@ -5376,7 +4850,7 @@ "호표": [ { "종류": "로직", - "요소": "로직(GF000160, 횟수='3회사용시', 합판=합판, 각재=각재, 철선=철선, 못=못, 박리제=박리제, 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", + "요소": "로직(GF000160, 횟수='3회사용시', 높이할증=높이할증, 소형할증=소형할증, 폼타이=폼타이)", "이름": "합판거푸집(3회)", "단위": "㎡", "수량": "1" @@ -5392,20 +4866,12 @@ "출처": "산림품셈 12-34-3", "소유": "공용", "비고": "해석: 결속선 = 철선 Φ0.9㎜ · 이형철근 · 운반 별도계상", - "입력": [ - { - "이름": "결속선", - "고르기": [ - "MT000209", - "MT000174" - ] - } - ], + "입력": [], "중간": [], "호표": [ { "종류": "재료", - "요소": {"구분": "철근·강재", "상세구분": "철선", "대표": "{결속선}"}, + "요소": {"구분": "철근·강재", "상세구분": "철선", "검색어": "어닐링철선(결속선) #20 서울"}, "이름": "결속선(R-0.9㎜)", "단위": "kg", "수량": "찾기(QF000411, 구분='자재 - 결속선(R-0.9mm)').적용", @@ -5429,7 +4895,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "고철·기타금속"}, + "요소": {"구분": "잡재료", "상세구분": "고철·기타금속", "검색어": "봉땜납 Sn"}, "이름": "고철대(감)", "단위": "kg", "수량": "-1000 * 찾기(QF000411, 구분='인력 - 고철대(감)').적용 / 100", @@ -5453,7 +4919,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "대표": "MT030602"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "JOINT FILLER"}, "이름": "JOINT FILLER(T=20㎜)", "단위": "㎡", "수량": "1", @@ -5479,7 +4945,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "대표": "MT030607"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "도료", "검색어": "콘크리트표면강화재(하드너)"}, "이름": "하드너", "단위": "", "수량": "하드너량", diff --git a/resources/master_data/로직_산림품셈_13장_구조물.json b/resources/master_data/로직_산림품셈_13장_구조물.json index 72767681..d0b90440 100644 --- a/resources/master_data/로직_산림품셈_13장_구조물.json +++ b/resources/master_data/로직_산림품셈_13장_구조물.json @@ -403,7 +403,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "잡석(채움재)", "단위": "㎥", "수량": "잡석량", @@ -517,7 +517,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "채움 콘크리트", "단위": "㎥", "수량": "찾기(QF000434, 뒷길이=뒷길이, 종별=만약(돌 == '호박돌 및 야면석', '호박돌', 돌)).소요량", @@ -526,7 +526,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:1 서울"}, "이름": "줄눈메꿈 모르타르", "단위": "㎥", "수량": "0.009", @@ -660,7 +660,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "채움 콘크리트", "단위": "㎥", "수량": "채움콘크리트량", @@ -783,7 +783,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "모르타르"}, + "요소": {"구분": "시멘트·골재", "상세구분": "모르타르", "검색어": "모르타르 1:1 서울"}, "이름": "줄눈메꿈 모르타르", "단위": "㎥", "수량": "0.009", @@ -792,7 +792,7 @@ }, { "종류": "재료", - "요소": {"구분": "석재", "상세구분": "조경석·자연석", "대표": "MT030608"}, + "요소": {"구분": "석재", "상세구분": "조경석·자연석", "검색어": "틈메우기돌"}, "이름": "틈메우기돌", "단위": "㎥", "수량": "찾기(QF000432, 뒷길이=뒷길이, 종별=만약(돌 == '조약돌 및 야면석' 또는 돌 == '호박돌 및 야면석', '야면석', 돌)).소요량 * 틈메우기율 / 100", @@ -801,7 +801,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "채움 콘크리트(찰붙임)", "단위": "㎥", "수량": "만약(붙임 == '찰붙임', 찾기(QF000434, 뒷길이=뒷길이, 종별=만약(돌 == '조약돌 및 야면석', '야면석', 만약(돌 == '호박돌 및 야면석', '호박돌', 돌))).소요량, 0)", @@ -890,7 +890,7 @@ }, { "종류": "재료", - "요소": {"구분": "시멘트·골재", "상세구분": "골재"}, + "요소": {"구분": "시멘트·골재", "상세구분": "골재", "검색어": "모래 강모래"}, "이름": "잡석(채움재)", "단위": "㎥", "수량": "잡석량", @@ -1024,7 +1024,7 @@ }, { "종류": "재료", - "요소": {"구분": "레미콘", "상세구분": "레미콘", "규격": "{굵은골재최대치수}-{강도}-{슬럼프}"}, + "요소": {"구분": "레미콘", "상세구분": "레미콘", "검색어": "레미콘 {굵은골재최대치수}-{강도}-{슬럼프}"}, "이름": "채움 콘크리트", "단위": "㎥", "수량": "채움콘크리트량", @@ -1661,7 +1661,7 @@ "호표": [ { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "돌망태·사방"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "돌망태·사방", "검색어": "기초사석 0.015~0.03㎥"}, "이름": "철망태", "단위": "㎥", "수량": "찾기(QF000460, 구분='자재', 명칭='철망태').수량", @@ -1669,7 +1669,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "돌망태·사방"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "돌망태·사방", "검색어": "기초사석 0.015~0.03㎥"}, "이름": "채움재", "단위": "㎥", "수량": "찾기(QF000460, 구분='자재', 명칭='채움재').수량", diff --git a/resources/master_data/일위대가조합.json b/resources/master_data/일위대가조합.json index 052c040c..ce05f945 100644 --- a/resources/master_data/일위대가조합.json +++ b/resources/master_data/일위대가조합.json @@ -13,6 +13,425 @@ "출처": "자체", "소유": "현장", "비고": "브레인이 넣은 견본 · 조합 화면을 바로 볼 수 있게 함 · 지워도 됨" + }, + { + "키": "UA000002", + "이름": "임도 준공표지석", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근,소형" }, + { "로직": "GC000211", "메모": "합판거푸집(간단) 간단" }, + { "로직": "GC000088", "메모": "구조물 터파기(토사) 굴삭기 0.2m3" }, + { "로직": "GF000113", "메모": "구조물 되메우기 굴삭기 0.2m3 (실무 수는 옛 판 값)" }, + { "로직": "GF000156", "메모": "콘크리트비빔 채움재 (실무 수는 옛 판 값)" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" } + ], + "출처": "자체", + "소유": "현장", + "비고": "글자새김 자당 6*6 — 우리 로직에 없음\n잔토처리 굴삭기 0.2m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00005 · 울진대흥 B01224" + }, + { + "키": "UA000003", + "이름": "횡단개거(개소)", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000088", "메모": "구조물 터파기(토사) 굴삭기 0.2m3" }, + { "로직": "GF000113", "메모": "구조물 되메우기 굴삭기 0.2m3 (실무 수는 옛 판 값)" }, + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근" }, + { "로직": "GC000211", "메모": "합판거푸집(간단) 간단" }, + { "로직": "GF000158", "메모": "표면마무리 (실무 수는 옛 판 값)" }, + { "로직": "GF000224", "메모": "깬잡석메붙임 L3=30cm내외" }, + { "로직": "GF000101", "메모": "구조물터파기(육상토사0~1m) 기계90%+인력10%(굴삭기 0.7M3) (실무 수는 옛 판 값)" }, + { "로직": "GF000218", "메모": "잡석운반및다짐 (실무 수는 옛 판 값)" }, + { "로직": "GF000124", "메모": "면고르기" } + ], + "출처": "자체", + "소유": "현장", + "비고": "깬잡석채집 L=30cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n잔토처리 굴삭기 0.7m3 — 우리 로직에 없음\n측구수로관부설 500∼700kg미만 — 우리 로직에 없음\n기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n실무 호표 — 봉화 B00008 · 봉화 B00030 · 영월 B02019 · 영월 B02052 · 영월 B02134" + }, + { + "키": "UA000004", + "이름": "기슭막이", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GF000222", "메모": "깬잡석찰쌓기 L3=45cm내외" }, + { "로직": "GF000218", "메모": "기초다짐 및 뒤채움 소형 장비사용 시공 (실무 수는 옛 판 값)" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }, + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근" }, + { "로직": "GF000156", "메모": "콘크리트믹서사용 0.45 m3 (실무 수는 옛 판 값)" }, + { "로직": "GF000220", "메모": "깬잡석메쌓기 L3=45cm내외" }, + { "로직": "GC000677", "메모": "모르터바르기 1:3" }, + { "로직": "GF000214", "메모": "야면석채집 L3=45cm내외 (실무 수는 옛 판 값)" }, + { "로직": "GF000221", "메모": "돌쌓기 찰(켜)쌓기,L3=55cm이하 (실무 수는 옛 판 값)" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" }, + { "로직": "GF000113", "메모": "구조물되메우기 굴착기0.6m3 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "깬잡석채집 L=45cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채집 기계(0.7㎥,100%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채집 기계 — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채우기 150mm내외 — 후보 「자갈고르기」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00010 · 봉화 B00011 · 봉화 B00022 · 봉화 B00047 · 울진대흥 B01212 · 울진대흥 B01213 · 울진대흥 B01214 · 울진대흥 B01215 · 울진대흥 B01216 · 울진대흥 B01225 · 울진대흥 B01226 · 울진대흥 B01227 · 울진대흥 B01231 · 울진소광 B02122" + }, + { + "키": "UA000005", + "이름": "취수장", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근,소형" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 보통" }, + { "로직": "GC000206", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000207", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000088", "메모": "구조물 터파기(토사) 굴삭기 0.2m3" }, + { "로직": "GF000113", "메모": "구조물 되메우기 굴삭기 0.2m3 (실무 수는 옛 판 값)" }, + { "로직": "GC000224", "메모": "지수판 설치(PVC 용접) 200×5t" }, + { "로직": "GC000393", "메모": "배수관부설및접합(P.V.C) Ø 50 MM - VG2 일반관" }, + { "로직": "GC000211", "메모": "합판거푸집 4회" }, + { "로직": "GC000015", "메모": "강관동바리 2.5m이하(1개월) (실무 수는 옛 판 값)" }, + { "로직": "GC000821", "메모": "잡철물제작및설치(현장) 일반철재" } + ], + "출처": "자체", + "소유": "현장", + "비고": "PE관 부설 및 접합 D50 — 후보 「부단수 할정자관 부설 및 접합」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n실무 호표 — 봉화 B00012 · 영월 B02002 · 거창 제12호표" + }, + { + "키": "UA000006", + "이름": "콘크리트포장(㎡)", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "㎡", + "담은로직": [ + { "로직": "GC000297", "메모": "콘크리트포장포설 A-Type,t=20CM이하" }, + { "로직": "GC000665", "메모": "와이어메쉬 바닥깔기" }, + { "로직": "GF000156", "메모": "콘크리트믹서사용 0.45 m3 (실무 수는 옛 판 값)" }, + { "로직": "GF000162", "메모": "콘크리트포장(B-type) t=20cm (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 봉화 B00014 · 울진대흥 B01232" + }, + { + "키": "UA000007", + "이름": "콘크리트포장(m)", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [{ "로직": "GF000162", "메모": "콘크리트포장(인력) T=20㎝" }, { "로직": "GC000211", "메모": "합판거푸집(6회) 간단" }, { "로직": "GF000163", "메모": "콘크리트포장절단공 임도 (실무 수는 옛 판 값)" }], + "출처": "자체", + "소유": "현장", + "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n실무 호표 — 영월 B02059" + }, + { + "키": "UA000008", + "이름": "관보호공(유입·유출구)", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근,소형" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 보통" }, + { "로직": "GC000206", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000207", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GF000218", "메모": "기초다짐 및 뒤채움 소형 장비사용 시공 (실무 수는 옛 판 값)" }, + { "로직": "GF000225", "메모": "깬잡석찰붙임 L3=30cm내외" }, + { "로직": "GF000156", "메모": "콘크리트(기계비빔) 무근구조물 (실무 수는 옛 판 값)" }, + { "로직": "GF000226", "메모": "큰돌메쌓기 뒷길이 0.6~0.8m" }, + { "로직": "GF000214", "메모": "야면석채집 L3=45cm내외 (실무 수는 옛 판 값)" }, + { "로직": "GF000222", "메모": "찰쌓기(켜쌓기) L3=55cm이하 (실무 수는 옛 판 값)" }, + { "로직": "GC000677", "메모": "모르터바르기 1:3" }, + { "로직": "GF000221", "메모": "돌쌓기 찰(켜)쌓기,L3=55cm이하 (실무 수는 옛 판 값)" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" }, + { "로직": "GF000113", "메모": "구조물되메우기 굴착기0.6m3 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "막자갈채집 기계(0.7㎥,100%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n깬잡석채집 L=30cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n야면석 채집 운반 (25x25x35㎝)장비 — 후보 「야면석 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집운반 4.5톤덤프(L=500) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n큰돌채집운반(뒷길이 0.6~0.8m) 장비(80%) + 인력(20%) — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n녹생토(종자배합) 암절개지 및 특수지반 녹화용 (700kg/㎥) — 우리 로직에 없음\n막자갈채집 기계 — 후보 「모래·자갈·약돌 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈(파쇄석)채집 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채우기 150mm내외 — 후보 「자갈고르기」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00017 · 봉화 B00018 · 봉화 B00029 · 봉화 B00048 · 봉화 B00049 · 봉화 B00055 · 봉화 B00056 · 영월 B01981 · 영월 B01983 · 영월 B01988 · 영월 B02045 · 영월 B02155 · 울진대흥 B01219 · 울진대흥 B01220 · 울진소광 B02120" + }, + { + "키": "UA000009", + "이름": "돌붙임", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "㎡", + "담은로직": [ + { "로직": "GF000225", "메모": "깬잡석찰붙임 L3=30cm내외" }, + { "로직": "GF000156", "메모": "콘크리트(기계비빔) 무근구조물 (실무 수는 옛 판 값)" }, + { "로직": "GF000224", "메모": "깬잡석메붙임 L3=30cm내외" }, + { "로직": "GF000101", "메모": "구조물터파기(육상토사0~1m) 기계90%+인력10%(굴삭기 0.7M3) (실무 수는 옛 판 값)" }, + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근" }, + { "로직": "GF000214", "메모": "야면석채집 L3=45cm내외 (실무 수는 옛 판 값)" }, + { "로직": "GC000677", "메모": "모르터바르기 1:3" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" } + ], + "출처": "자체", + "소유": "현장", + "비고": "깬잡석채집 L=30cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n잔토처리 굴삭기 0.7m3 — 우리 로직에 없음\n깬잡석채집 L=60cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n파쇄석 채집 L3=35cm내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 굴삭기0.2m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00020 · 봉화 B00032 · 봉화 B00036 · 봉화 B00064 · 울진대흥 B01222 · 울진대흥 B01223 · 울진대흥 B01229 · 울진대흥 B01230 · 울진소광 B02125" + }, + { + "키": "UA000010", + "이름": "임도차단기", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000821", "메모": "잡철물 제작및설치(현장) 일반철재" }, + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근,소형" }, + { "로직": "GC000206", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000207", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }, + { "로직": "GC000714", "메모": "철부도장페인트 녹막이1회조합2회 (실무 수는 옛 판 값)" }, + { "로직": "GC000712", "메모": "철부도장페인트 녹막이1회조합2회 (실무 수는 옛 판 값)" }, + { "로직": "GC000088", "메모": "구조물 터파기(토사) 굴삭기 0.2m3" }, + { "로직": "GF000113", "메모": "구조물 되메우기 굴삭기 0.2m3 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "잔토처리 굴삭기 0.2m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00025" + }, + { + "키": "UA000011", + "이름": "돌수로", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GF000225", "메모": "깬잡석찰붙임 L3=35cm내외" }, + { "로직": "GF000156", "메모": "콘크리트믹서사용 0.45 m3 (실무 수는 옛 판 값)" }, + { "로직": "GF000224", "메모": "야면석메붙임 뒷길이 35Cm" }, + { "로직": "GF000221", "메모": "돌쌓기 찰(켜)쌓기,L3=55cm이하 (실무 수는 옛 판 값)" }, + { "로직": "GC000677", "메모": "모르타르배합 1:3" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" } + ], + "출처": "자체", + "소유": "현장", + "비고": "깬잡석채집 L=35cm 내외 — 후보 「막돌 채집」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집 기계(0.7㎥,100%) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n야면석 채집 운반 (25x25x35㎝)장비 — 후보 「야면석 채집(인력)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n고임돌채집운반 4.5톤덤프(L=500) — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n야면석소운반(L=35CM) (L=20m) — 우리 로직에 없음\n콘크리트소운반 (L=20m) — 우리 로직에 없음\n고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 봉화 B00034 · 영월 B02016 · 영월 B02017 · 울진소광 B02124" + }, + { + "키": "UA000012", + "이름": "포장 난간", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근,소형" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }, + { "로직": "GC000206", "메모": "철근가공및조립(현장) 일반(Type-Ⅰ-1)" }, + { "로직": "GC000207", "메모": "철근가공및조립(현장) 일반(Type-Ⅰ-1)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 봉화 B00038" + }, + { + "키": "UA000013", + "이름": "관보호공 안전난간", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근,소형" }, + { "로직": "GC000211", "메모": "합판거푸집 3회(복잡)" }, + { "로직": "GC000206", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000207", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000712", "메모": "유성페인트칠(붓칠)-황색 콘크리트,모르타르면3회 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 봉화 B00041" + }, + { + "키": "UA000014", + "이름": "BOX 암거", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 철근" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 보통" }, + { "로직": "GC000206", "메모": "철근가공 및 조립 Type-Ⅱ-1,현장가공조립" }, + { "로직": "GC000207", "메모": "철근가공 및 조립 Type-Ⅱ-1,현장가공조립" }, + { "로직": "GC000015", "메모": "강관동바리 암거용-3개월사용 (실무 수는 옛 판 값)" }, + { "로직": "GC000022", "메모": "강관비계 3개월사용 (실무 수는 옛 판 값)" }, + { "로직": "GC000211", "메모": "합판거푸집 6회 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 봉화 B00058 · 거창 제5호표 · 거창 제6호표" + }, + { + "키": "UA000015", + "이름": "BOX 난간", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비) 무근" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }, + { "로직": "GC000206", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" }, + { "로직": "GC000207", "메모": "철근가공 및 조립 Type-Ⅰ-1,현장가공조립" } + ], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 봉화 B00062 · 거창 제9호표" + }, + { + "키": "UA000016", + "이름": "국가지점번호판", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "무근콘크리트타설 인력운반타설" }, + { "로직": "GC000211", "메모": "합판거푸집(6회) 간단" }, + { "로직": "GF000101", "메모": "구조물터파기(인력) 경질토사 (실무 수는 옛 판 값)" }, + { "로직": "GF000113", "메모": "구조물되메우기 (인력) (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B00704" + }, + { + "키": "UA000017", + "이름": "임도 안전표지판", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000308", "메모": "임도안전표지판설치 삼각,D=900 (실무 수는 옛 판 값)" }, + { "로직": "GC000198", "메모": "무근콘크리트타설 인력운반타설" }, + { "로직": "GC000211", "메모": "합판거푸집(6회) 간단" }, + { "로직": "GF000101", "메모": "구조물터파기(인력) 경질토사 (실무 수는 옛 판 값)" }, + { "로직": "GF000113", "메모": "구조물되메우기 (인력) (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B01982" + }, + { + "키": "UA000018", + "이름": "도로반사경 설치", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "콘크리트타설 임도품셈 5-5 (실무 수는 옛 판 값)" }, + { "로직": "GC000211", "메모": "합판거푸집(6회) 간단" }, + { "로직": "GF000101", "메모": "구조물터파기 인력(경질토사) (실무 수는 옛 판 값)" }, + { "로직": "GF000113", "메모": "구조물되메우기 (인력) (실무 수는 옛 판 값)" }, + { "로직": "GC000310", "메모": "도로반사경설치 D=1000(STS)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B01997" + }, + { + "키": "UA000019", + "이름": "취수장 안내판", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "무근콘크리트타설 인력운반타설" }, + { "로직": "GC000211", "메모": "합판거푸집(6회) 간단" }, + { "로직": "GF000101", "메모": "구조물터파기(인력) 경질토사 (실무 수는 옛 판 값)" }, + { "로직": "GF000113", "메모": "구조물되메우기 (인력) (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "기초잡석채집및운반부설 4.5톤(L=500) — 우리 로직에 없음\n잔토처리 인력100% — 우리 로직에 없음\n실무 호표 — 영월 B02152" + }, + { + "키": "UA000020", + "이름": "자생수종 이식", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "㎡", + "담은로직": [{ "로직": "GC000149", "메모": "관목식재(단식) H=0.3~0.7m" }, { "로직": "GC000148", "메모": "굴취(관목) H=0.3~0.7m" }], + "출처": "자체", + "소유": "현장", + "비고": "인력운반 기타 — 우리 로직에 없음\n실무 호표 — 계류보전 B01099" + }, + { + "키": "UA000021", + "이름": "기초 콘크리트(m)", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설 무근(장비)" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" }, + { "로직": "GF000113", "메모": "구조물되메우기 굴착기0.6m3 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 울진소광 B02121" + }, + { + "키": "UA000022", + "이름": "기초 콘크리트(개소)", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [{ "로직": "GC000198", "메모": "레미콘타설 무근(장비)" }, { "로직": "GC000214", "메모": "유로폼 설치 및 해체 간단" }], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 울진소광 B02129" + }, + { + "키": "UA000023", + "이름": "돌조공", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GF000220", "메모": "돌쌓기(깬돌) 메(골)쌓기,L3=55cm이하" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" }, + { "로직": "GF000113", "메모": "구조물되메우기 굴착기0.6m3 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "고임돌채집 굴삭기0.7m3 — 후보 「큰돌 채집(기계)」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n막자갈채우기 150mm내외 — 후보 「자갈고르기」 와 수가 다름(옛 판 값) · 이름이 달라 뺌\n구조물잔토처리 굴착기0.6m3 — 우리 로직에 없음\n실무 호표 — 울진소광 B02123" + }, + { + "키": "UA000024", + "이름": "L형옹벽 측구", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "m", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설 무근(장비)" }, + { "로직": "GC000214", "메모": "유로폼 설치 및 해체 보통" }, + { "로직": "GF000161", "메모": "문양 거푸집" }, + { "로직": "GC000206", "메모": "철근(현장)가공 Type-Ⅰ-----아님 (실무 수는 옛 판 값)" }, + { "로직": "GC000207", "메모": "철근(현장)조립 Type-Ⅰ-----아님" }, + { "로직": "GC000088", "메모": "구조물터파기(토사) 굴착기0.6m3" }, + { "로직": "GF000113", "메모": "구조물되메우기 굴착기0.6m3 (실무 수는 옛 판 값)" } + ], + "출처": "자체", + "소유": "현장", + "비고": "물구멍설치 D=50mm — 우리 로직에 없음\n실무 호표 — 울진소광 B02128" + }, + { + "키": "UA000025", + "이름": "날개벽", + "구분": "실무 일위대가", + "상세구분": null, + "단위": "개소", + "담은로직": [ + { "로직": "GC000198", "메모": "레미콘타설(장비사용타설) 철근구조물 (실무 수는 옛 판 값)" }, + { "로직": "GC000214", "메모": "유로폼 보통,0~7m" }, + { "로직": "GC000206", "메모": "철근가공및조립(현장) Type-Ⅱ-1" }, + { "로직": "GC000207", "메모": "철근가공및조립(현장) Type-Ⅱ-1" } + ], + "출처": "자체", + "소유": "현장", + "비고": "실무 호표 — 거창 제7호표 · 거창 제8호표" } ] } diff --git a/resources/tester/test_m01_api.py b/resources/tester/test_m01_api.py index 1cc17860..feea2deb 100644 --- a/resources/tester/test_m01_api.py +++ b/resources/tester/test_m01_api.py @@ -369,10 +369,10 @@ def test_구분_목록(client: TestClient) -> None: assert steel["details"] -def test_재료_고르기_줄_단가는_대표_자재품목의_낮은_값(client: TestClient) -> None: - """옛 「MP:시멘트」 줄이 고르기 조건+대표로 바뀜 — 값은 대표 자재품목 줄의 낮은 값.""" +def test_재료_고르기_줄_단가는_검색어_첫째_자재품목의_낮은_값(client: TestClient) -> None: + """옛 대표 키 줄이 기본 검색어로 바뀜(PLAN 8-3) — 값은 검색어 첫째(옛 대표) 줄의 낮은 값.""" one = _get(client, "/api/m01/logic", key=key_of("GC000628")) - cond = {"구분": "시멘트·골재", "상세구분": "시멘트", "대표": "MT004176"} + cond = {"구분": "시멘트·골재", "상세구분": "시멘트", "검색어": "시멘트 전국"} cement = one["prices"][store.cm.mf.cond_text(cond)] assert cement["ref"] == "MT004176" and cement["값"] > 0 and cement["후보"] >= 1 diff --git a/resources/tester/test_m01_auto.py b/resources/tester/test_m01_auto.py new file mode 100644 index 00000000..8dd35e5b --- /dev/null +++ b/resources/tester/test_m01_auto.py @@ -0,0 +1,140 @@ +"""M01 견본 입력(`GET /logic/auto`) · 값 빈 품목은 그 줄만 비움 — PLAN 3-2 · 계약 3-1 표. + +자동값 = 고르기 목록 첫째 · 범위 최솟값 · 찾기 조건 값은 그 표 가장 낮은 줄 · 나누는 수 1 · 그 밖 0. +정본 폴더를 읽기만 함(쓰지 않음). +""" + +from __future__ import annotations + +from decimal import Decimal + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from M01_MasterData import M01_MasterData_Router as router_module +from M01_MasterData import M01_MasterData_Store as store + + +@pytest.fixture(scope="module") +def client() -> TestClient: + app = FastAPI() + app.include_router(router_module.router) + return TestClient(app) + + +def _auto(client: TestClient, key: str) -> dict: + got = client.get("/api/m01/logic/auto", params={"key": key}) + assert got.status_code == 200, got.text + return got.json()["값"] + + +def _calc(client: TestClient, key: str) -> dict: + got = client.post("/api/m01/calc", json={"key": key, "inputs": _auto(client, key)}).json() + assert got["ok"] is True, got + return got + + +def test_고르기는_목록_첫째_범위는_최솟값(client) -> None: + got = _auto(client, "GF000160") # 12-4 합판거푸집 + assert got["횟수"] == "1회사용시" and got["폼타이"] == "아님" + assert "합판" not in got # 재료 입력 키 목록은 걷음 — 품목은 기본 검색어(PLAN 8-3) + assert got["소형할증"] == 0 # 범위 [0, 30] + assert got["높이할증"] == 0 # 찾기·나누기에 안 쓰임 + + +def test_찾기_조건_값은_표_가장_낮은_줄(client) -> None: + assert _auto(client, "GC000561")["층수층"] == 0 # 가장 낮은 줄 「6 미만」 — 아래 끝이 열림 + assert _auto(client, "GC000559")["강재총사용량"] == 0 # 「60 미만」 + # 두 표가 같은 입력을 씀 — 한 표는 6 부터 · 한 표는 15 부터 → 둘 다에 드는 가장 낮은 값 + assert _auto(client, "GC000724")["규격"] == 15 + + +def test_나누는_수는_1(client) -> None: + assert _auto(client, "GC000998")["수직고초과단계"] == 1 + assert _auto(client, "GC000278")["두께"] == 1 + + +def test_없는_로직은_404(client) -> None: + assert client.get("/api/m01/logic/auto", params={"key": "GF999999"}).status_code == 404 + + +def test_견본_금액은_앞과_같음(client) -> None: + assert Decimal(str(_calc(client, "GF000160")["sums"]["계"])) == Decimal("117485.212") + assert Decimal(str(_calc(client, "GF000219")["sums"]["계"])) == Decimal("79279.668") + + +def test_후보_0_값_빈_품목은_그_줄만_비움(client) -> None: + got = _calc(client, "GC000027") # 가새(후보 0) · 발판(값 없음) + blank = {one["이름"]: one for one in got["lines"] if one.get("까닭")} + assert len(blank) == 2 + for one in blank.values(): + assert one["단가"] is None and one["금액"] is None + assert "후보 없음" in blank["가새 L1518-2개"]["까닭"] + assert "값 없음" in blank["발판 45×200×2000"]["까닭"] + rest = sum(Decimal(str(one["금액"])) for one in got["lines"] if one["금액"] is not None) + assert Decimal(str(got["sums"]["계"])) == rest > 0 # 빈 줄은 빼고 더함 + + +def test_빈_줄을_부르는_덧줄도_비움(client) -> None: + got = _calc(client, "GF000185") # 레미콘 후보 0 → 레미콘 금액을 부르는 덧줄 + blank = [one for one in got["lines"] if one.get("까닭")] + assert [one["이름"] for one in blank] == ["구체콘크리트(철근) 레미콘", "콘크리트다짐"] + assert "구체콘크리트(철근) 레미콘" in blank[1]["까닭"] and blank[1]["금액"] is None + + +def test_자동값으로_로직_전부_멈춤_0() -> None: + files, whole = store.loaded() + stuck = [] + for data in files.values(): + if data.get("그룹") != "로직": + continue + for row in data.get("줄") or []: + try: + store.cm.mf.run(whole, str(row["키"]), store.mau.values(whole, row)) + except (store.cm.mf.FormulaError, ArithmeticError, KeyError, TypeError) as e: + stuck.append(f"{row['키']} {e}") + assert stuck == [] + + +def test_원_단위_입력은_자동값_null(client) -> None: + got = _auto(client, "GF000056") # 체인톱가격(원) · 체인오일단가(원/ℓ) + assert got["체인톱가격"] is None and got["체인오일단가"] is None + assert got["소작업로"] == 0 # 원 아닌 수는 그대로 0 + + +def test_빈_입력을_쓰는_줄만_비움(client) -> None: + got = _calc(client, "GF000056") + blank = {one["이름"]: one for one in got["lines"] if one.get("까닭")} + assert set(blank) == {"체인오일", "체인톱 손료"} + for one in blank.values(): + assert one["단가"] is None and one["금액"] is None and "비어 있음" in one["까닭"] + rest = sum(Decimal(str(one["금액"])) for one in got["lines"] if one["금액"] is not None) + assert Decimal(str(got["sums"]["계"])) == rest > 0 # 빈 줄은 빼고 더함 + assert got["sums"]["경비"] == 0 + + +def test_빠진_원_입력도_null_처럼_줄만_비움(client) -> None: + given = { + k: v + for k, v in _auto(client, "GF000056").items() + if k not in ("체인톱가격", "체인오일단가") + } + for path in ("calc", "text"): + got = client.post(f"/api/m01/{path}", json={"key": "GF000056", "inputs": given}).json() + assert got["ok"] is True, (path, got) + lines = client.post("/api/m01/calc", json={"key": "GF000056", "inputs": given}).json()["lines"] + assert {o["이름"] for o in lines if o.get("까닭")} == {"체인오일", "체인톱 손료"} + + +def test_표_찾기_실패는_그_줄만_비우고_까닭은_날글_없이(client) -> None: + given = {**_auto(client, "GC001000"), "시공구분": "기계시공"} # 표 1-2-3 에 그 조합 줄이 없음 + got = client.post("/api/m01/calc", json={"key": "GC001000", "inputs": given}).json() + assert got["ok"] is True, got + blank = [one for one in got["lines"] if one.get("까닭")] + assert blank and all(one["금액"] is None for one in blank) + why = blank[0]["까닭"] + assert "표 QC001869(1-2-3)에 시공구분 기계시공" in why and "인 줄이 없음" in why + assert not any(ch in why for ch in "{}'") and "Decimal" not in why + rest = sum(Decimal(str(one["금액"])) for one in got["lines"] if one["금액"] is not None) + assert Decimal(str(got["sums"]["계"])) == rest diff --git a/resources/tester/test_m01_copy.py b/resources/tester/test_m01_copy.py index edc026c9..05ee068d 100644 --- a/resources/tester/test_m01_copy.py +++ b/resources/tester/test_m01_copy.py @@ -3,7 +3,7 @@ 계약 `resources/master_data/_화면_계약.md` 7장 · 규칙 `resources/master_data/ref/_설계_식_복사.md`. 못박는 것 - 13-4-1 을 식으로 펴 아무것도 안 고치고 되돌려 저장하면 원본과 계산 결과가 같음. - - 12-4 처럼 같은 표를 다른 인자로 쓰는 로직은 별칭이 번호로 갈림. + - 12-4 처럼 같은 표를 다른 인자로 쓰는 로직은 별칭이 찾은 줄 값으로 갈림. - 로직 전부(1,354줄)가 펴고 되돌리면 원래 식 글자 그대로 돌아옴. """ @@ -83,10 +83,12 @@ def test_별칭이_식에서_이름_하나로_보이고_원문은_뒤에_남는 assert "높이에대한증가율표.증가율" in 가로["중간[0].식"] -def test_같은_표를_다른_인자로_쓰면_별칭이_번호로_갈린다(client: TestClient) -> None: +def test_같은_표를_다른_인자로_쓰면_별칭이_찾은_줄_값으로_갈린다(client: TestClient) -> None: got = _get(client, "/api/m01/logic/formula", key=FORM) 형틀 = [one for one in got["별칭"] if one["참조"] == "QF000357"] - assert [one["이름"] for one in 형틀] == [f"합판거푸집{n}" for n in range(1, 8)] + assert [one["이름"] for one in 형틀] == [ + f"합판거푸집_{n}" for n in ("합판", "각재", "철선", "못", "박리제", "형틀목공", "보통인부") + ] assert [one["원문"] for one in 형틀][:2] == [ "찾기(QF000357, 구분='합판')", "찾기(QF000357, 구분='각재')", @@ -96,8 +98,8 @@ def test_같은_표를_다른_인자로_쓰면_별칭이_번호로_갈린다(cli assert len(한벌) == 1 and 한벌[0]["자리"] == ["중간[0].식", "중간[1].식"] 가로 = {one["자리"]: one["식"] for one in got["줄"]} - assert 가로["호표[0].수량"] == "합판거푸집1.기준수량_1회사용 * 재료율" - assert 가로["호표[1].수량"] == "합판거푸집2.기준수량_1회사용 * 재료율" + assert 가로["호표[0].수량"] == "합판거푸집_합판.기준수량_1회사용 * 재료율" + assert 가로["호표[1].수량"] == "합판거푸집_각재.기준수량_1회사용 * 재료율" def test_로직_부르기도_별칭으로_보인다(client: TestClient) -> None: @@ -155,7 +157,7 @@ def test_식을_고쳐_저장하면_그_값으로_계산된다(client: TestClien view = _get(client, "/api/m01/logic/formula", key=FORM) row = view["로직"] - row["호표"][6]["수량"] = "합판거푸집6.기준수량_1회사용 * 인력율 * 2" # 형틀목공 품 두 배 + row["호표"][6]["수량"] = "합판거푸집_형틀목공.기준수량_1회사용 * 인력율 * 2" # 형틀목공 품 두 배 made = _post( client, "/api/m01/logic/formula/save", diff --git a/resources/tester/test_m01_pick.py b/resources/tester/test_m01_pick.py new file mode 100644 index 00000000..b55767e8 --- /dev/null +++ b/resources/tester/test_m01_pick.py @@ -0,0 +1,151 @@ +"""M01 재료 기본 검색어 · 고름 · 단위 환산 · 단위 경고 — PLAN 8-1 · 8-2 계약. + +검색어 든 견본 로직 줄을 시험 안에서 만들어(`POST /calc` 의 `row` — 저장 안 함) 돌림. 정본 폴더는 읽기만. +""" + +from __future__ import annotations + +from decimal import Decimal + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from M01_MasterData import M01_MasterData_Router as router_module +from M01_MasterData import M01_MasterData_Store as store + +mf = store.cm.mf +mp = mf.mp + + +@pytest.fixture(scope="module") +def client() -> TestClient: + app = FastAPI() + app.include_router(router_module.router) + return TestClient(app) + + +def _sample(term: str, unit: str, **cond) -> dict: + """재료 줄 하나짜리 견본 로직 — 검색어 줄 하나 · 수량 1.""" + return { + "이름": "검색어 견본", + "구분": "자체", + "입력": [], + "호표": [ + { + "종류": "재료", + "요소": {"검색어": term, **cond}, + "이름": "견본 재료", + "단위": unit, + "수량": "1", + "비목": "재료비", + } + ], + } + + +def _line(client: TestClient, row: dict, picks: dict | None = None, path: str = "calc") -> dict: + body = {"key": "GF999999", "inputs": {}, "row": row, **({"고름": picks} if picks else {})} + got = client.post(f"/api/m01/{path}", json=body).json() + assert got.get("ok", True) is True, got + return got["lines"][0] if path == "calc" else got + + +def test_검색어로_찾고_지역_낱말이_맞는_줄이_먼저(client) -> None: + seoul = _line(client, _sample("합판(내수) 12t 서울", "㎡")) + assert seoul["출처"].startswith("MT000018") and seoul["단가"] == 11354 # 값 열 가운데 낮은 값 + busan = _line(client, _sample("합판(내수) 12t 부산", "㎡")) + assert busan["출처"].startswith("MT000020") and busan["단가"] == 11488 + assert "단위경고" not in seoul + + +def test_고름은_그_줄만_그_품목으로_셈(client) -> None: + row = _sample("합판(내수) 12t 서울", "㎡") + got = _line(client, row, {"0": "MT000019"}) + assert got["출처"].startswith("MT000019") and got["단가"] == 11556 + assert _line(client, row)["단가"] == 11354 # 요청마다 — 저장 안 함 + text = _line(client, row, {"0": "MT000019"}, "text") + assert text["ok"] is True # 읽는 식도 같은 몸으로 돎 + + +def test_같은_성격_단위는_줄_단위로_환산(client) -> None: + got = _line(client, _sample("어닐링철선 서울 4.0mm", "톤")) # 자재 kg → 줄 톤 + assert got["단가"] == 1610000 and "단위경고" not in got + got = _line(client, _sample("어닐링철선 서울 4.0mm", "g")) + assert got["단가"] == pytest.approx(1.61) + + +def test_성격이_다르면_계산은_하고_단위경고(client) -> None: + got = _line(client, _sample("어닐링철선 서울 4.0mm", "m")) + assert got["단가"] == 1610 and got["단위경고"] == "줄 m · 자재 kg" + assert got["금액"] == 1610 + + +def test_검색어_후보가_없으면_그_줄만_비움(client) -> None: + got = client.post( + "/api/m01/calc", json={"key": "GF999999", "inputs": {}, "row": _sample("없는물건zzz", "개")} + ).json() + assert got["ok"] is True and got["lines"][0]["금액"] is None + assert "후보 없음" in got["lines"][0]["까닭"] + + +def test_기계_입력은_목록_밖_원문번호도_받음(client) -> None: + key = "GC000267" + inputs = {**store.auto(key)["값"], "기계": "0101-0007"} # 고르기 목록 밖 · 기계 마스터엔 있음 + got = client.post("/api/m01/calc", json={"key": key, "inputs": inputs}).json() + assert got["ok"] is True, got + bad = client.post("/api/m01/calc", json={"key": key, "inputs": {**inputs, "기계": "9999-9999"}}) + assert bad.json()["ok"] is False # 기계 마스터에 없는 번호는 그대로 막힘 + + +def test_단위_환산_표() -> None: + assert mp.convert(Decimal(1610), "kg", "톤") == 1610000 + assert mp.convert(Decimal(5), "Ton", "kg") == Decimal("0.005") + assert mp.convert(Decimal(100), "㎡", "㎠") == Decimal("0.01") + assert mp.convert(Decimal(7), "kg", "m") == 7 # 성격이 다르면 그대로 + assert mp.same_kind("ℓ", "㎥") and not mp.same_kind("kg", "m") + + +def test_check_master_검색어_후보0_단위경고(client) -> None: + whole = store.loaded()[1] + assert mf.check_pick(whole, "줄", {"검색어": "합판(내수) 12t 서울"}) == [] + assert "후보 0" in mf.check_pick(whole, "줄", {"검색어": "없는물건zzz"})[0] + assert mf.check_unit(whole, "줄", {"검색어": "어닐링철선 서울 4.0mm"}, "kg") == [] + assert "성격이 다름" in mf.check_unit(whole, "줄", {"검색어": "어닐링철선 서울 4.0mm"}, "m")[0] + + +def test_기계_입력은_EQ_키도_원문번호로_풀어_받음(client) -> None: + key = "GC000267" + number = "0101-0007" # 고르기 목록 밖 + eq = next(k for k, r in store.loaded()[1].index["EQ"].items() if r.get("원문번호") == number) + base = {**store.auto(key)["값"]} + by_number = client.post( + "/api/m01/calc", json={"key": key, "inputs": {**base, "기계": number}} + ).json() + by_key = client.post("/api/m01/calc", json={"key": key, "inputs": {**base, "기계": eq}}).json() + assert by_key["ok"] is True, by_key + assert by_key["sums"] == by_number["sums"] # EQ 키 = 원문번호와 같은 계산 + bad = client.post("/api/m01/calc", json={"key": key, "inputs": {**base, "기계": "EQ999999"}}) + assert bad.json()["ok"] is False + + +def test_재료_줄마다_품목_칸(client) -> None: + key = "GF000160" + body = {"key": key, "inputs": {k: str(v) for k, v in store.auto(key)["값"].items()}} + lines = client.post("/api/m01/calc", json=body).json()["lines"] + idx = next( + i for i, x in enumerate(lines) if x.get("품목", {}).get("이름", "").startswith("합판(내수)") + ) + first = lines[idx]["품목"] + assert first["이름"] == "합판(내수), 서울" and first["값칸"] + assert {"키", "이름", "규격", "단위", "값칸"} <= set(first) + lines = client.post("/api/m01/calc", json={**body, "고름": {str(idx): "MT000019"}}).json()[ + "lines" + ] + assert lines[idx]["품목"]["키"] == "MT000019" and "인천" in lines[idx]["품목"]["이름"] + + +def test_목록_검색은_띄어쓰기를_무시(client) -> None: + for q in ("함석", "골 함 석", "골함석"): + got = client.get("/api/m01/rows", params={"file": "재료_자재품목.json", "q": q}).json() + assert any(r["이름"] == "골 함 석" for r in got["rows"]), q diff --git a/resources/tester/test_m01_search_words.py b/resources/tester/test_m01_search_words.py new file mode 100644 index 00000000..187b7cce --- /dev/null +++ b/resources/tester/test_m01_search_words.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +"""PLAN 8-3 — 로직 재료 줄 키 → 기본 검색어 바꿔 넣기(`build_재료_검색어.py`).""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "resources/master_data/scripts")) + +import build_재료_검색어 as bw # noqa: E402 +import check_master as cm # noqa: E402 +import master_auto as mau # noqa: E402 +import master_formula as mf # noqa: E402 +import master_pick as mp # noqa: E402 + + +@pytest.fixture(scope="module") +def master() -> mf.Master: + return mf.Master(cm.load()) + + +def _material(master, key: str, name: str) -> dict: + return next(h for h in master.logic(key)["호표"] if h.get("이름") == name) + + +def test_결속선은_원문대로_고른_어닐링철선이_그대로_첫째(master) -> None: + item = _material(master, "GC000037", "결속선 # 10") + assert "대표" not in item["요소"] and item["요소"]["검색어"].startswith("어닐링철선") + row = master.logic("GC000037") + got = mp.search(master, item["요소"], mau.values(master, row), item["단위"]) + assert got[0] == "MT000204" # 어닐링철선 #10 서울 — 옛 입력 「결속선」 목록 첫째 + + +def test_재료_입력_키_목록은_걷히고_넘기던_인자도_없음(master) -> None: + for name, data in cm.load().items(): + if not name.startswith("로직_"): + continue + for row in data["줄"]: + assert not bw.material_inputs(row), row["키"] + call = next(h["요소"] for h in master.logic("GF000169")["호표"] if "GF000160" in h["요소"]) + assert "횟수=" in call and "철선=철선" not in call and "합판=합판" not in call + + +def test_남은_대표는_값_빈_자리표_품목만(master) -> None: + """검색어로 못 가르는 줄(규격 빈 자리표 MT030…)만 대표가 남음 — 목록으로 알린 줄.""" + reps = { + str(h["요소"]["대표"]) + for data in cm.load().values() + for row in data.get("줄", []) + if "호표" in row + for h in row["호표"] + if isinstance(h.get("요소"), dict) and h["요소"].get("대표") + } + assert reps and all(k.startswith("MT0306") for k in reps), reps + + +def test_글_갈음은_바뀐_값_자리만(master) -> None: + text = ( + '{ "키": "GX1", "입력": [\n { "이름": "철선", "고르기": ["MT000199"] },\n' + ' { "이름": "횟수", "고르기": ["1회"] }\n],\n "호표": [\n' + ' { "종류": "재료", "요소": {"구분": "철근·강재", "대표": "{철선}"}, "수량": "1" },\n' + ' { "종류": "로직", "요소": "로직(GF000160, 횟수=횟수, 철선=철선)", "수량": "1" }\n] }' + ) + row = json.loads(text) + change = {"요소": {0: {"구분": "철근·강재", "검색어": "어닐링철선 #8 서울"}}, "걷음": ["철선"]} + got = bw._patch_row(text, row, change) + assert json.loads(got) == bw.apply(row, change) + assert '"이름": "횟수"' in got and "철선=철선" not in got and "횟수=횟수" in got + assert got.count("\n") == text.count("\n") - 1 # 걷은 입력 한 줄만 빠짐 diff --git a/resources/tester/test_m01_text.py b/resources/tester/test_m01_text.py index b83e6fb3..a7472a7e 100644 --- a/resources/tester/test_m01_text.py +++ b/resources/tester/test_m01_text.py @@ -5,6 +5,7 @@ from __future__ import annotations +import re import shutil import sys from decimal import Decimal @@ -93,7 +94,10 @@ def test_펌프카는_하위로직이_비목마다_한_줄씩이고_덧줄이_ pump = [one for one in _all_lines(got) if one["이름"] == "콘크리트펌프차"] assert len(pump) == 3 # 노무비 · 재료비 · 경비 몫이 따로 extra = next(one for one in _all_lines(got) if one["이름"].startswith("공구손료")) - assert extra["글"].endswith(f"= {mt.fmt(Decimal(str(extra['금액'])))}") and "%" in extra["글"] + assert ( + extra["글"].endswith(f"= {mt.fmt_money(Decimal(str(extra['금액'])))}") + and "%" in extra["글"] + ) def test_몫이_모두_0인_장비_줄도_글에_남는다(client: TestClient) -> None: @@ -136,9 +140,20 @@ def test_이름_꼴_식은_요소_이름과_별칭으로_적힌다(client: TestC assert extra["이름글"] == "노무비 * 5 % = 공구손료및경장비" -def test_멈추는_로직은_까닭_한_줄(client: TestClient) -> None: +def test_입력_없어도_이름_식은_보임(client: TestClient) -> None: + """값을 고르기 전(PLAN 2-5) — 멈추지 않고 왼쪽 이름 식은 그대로, 글·금액만 빔.""" got = client.post("/api/m01/text", json={"key": STACK, "inputs": {}}).json() - assert got["ok"] is False and "입력" in got["reason"] + assert got["ok"] is True + one = got["groups"][0]["줄"][0] + assert one["이름글"] and "석공노임" in one["이름글"] + assert one["글"] == "" and one["금액"] is None + + +def test_고른_값이_틀리면_그대로_막힘(client: TestClient) -> None: + """값을 넣었는데 틀린 경우(고르기 밖)는 지금처럼 멈춤 — 안 고른 것과 다름.""" + bad = dict(_inputs(STACK), 뒷길이=999) + got = client.post("/api/m01/text", json={"key": STACK, "inputs": bad}).json() + assert got["ok"] is False and "고르기" in got["reason"] def test_끝수는_줄이_아니라_소계에_붙는다(client: TestClient) -> None: @@ -153,13 +168,58 @@ def test_끝수는_줄이_아니라_소계에_붙는다(client: TestClient) -> N assert not any("버림" in one["글"] for one in group["줄"]) -def test_이름_꼴_찾기_별칭이_표_열쇠_그대로면_밑이름으로_풀지_않는다(client: TestClient) -> None: - """GC001008 덧줄 — 별칭 이름이 QC001882 뿐이라 이름 식도 별칭 목록의 그 이름을 씀.""" +def test_이름_꼴_찾기_별칭은_표_열쇠_대신_짧은_표_제목(client: TestClient) -> None: + """GC001008 덧줄 — 긴 표 제목(잔디깎기 공구손료·경장비 기계경비 요율)은 첫 + 끝 낱말.""" got, _ = _both(client, "GC001008") extra = next(one for one in _all_lines(got) if one["이름"].startswith("공구손료")) - assert "QC001882.요율 %" in extra["이름글"] - assert "잔디깎기공구손료" not in extra["이름글"] - assert any(one["이름"] == "QC001882" for one in got["별칭"]) + assert "잔디깎기요율.요율 %" in extra["이름글"] + assert "QC001882" not in extra["이름글"] + assert any(one["이름"] == "잔디깎기요율" for one in got["별칭"]) + + +def test_짧은_이름은_규격_코드를_떼고_길면_줄인다() -> None: + short = mt.mc.short + assert short("바퀴숫돌 평면용 A24 QWV1호 A205×25×25 KSL 6501") == "바퀴숫돌평면용" + assert short("크롤러드릴(공기식)(17㎥/min)") == "크롤러드릴공기식" + assert short("콘크리트 피니셔(중앙분리대용) 105.9㎾") == "콘크리트피니셔" + assert short("1급 기준점 측량") == "기준점측량" # 숫자로 시작하지 않음 + assert short("잔디깎기 연간 시공횟수별 시공량 할증율") == "잔디깎기할증율" + + +def test_이름글_별칭은_찾은_줄_값으로_갈리고_표_키_번호가_없다() -> None: + """번호(가스압접1) · 표 키(QC000568) 대신 표 제목 + 찾은 줄 값 · 되돌리면 원래 부르기.""" + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + want = { + "GC000337": "낙석방지망설치재료_철망.수량", + "GC000360": "가스압접_용접공.수량", + "GC000350": "터널바닥암반청소_굴착기_02.", + "GF000185": "구체콘크리트펌프카타설_레미콘.", + } + for key, part in want.items(): + got = mt.lines(files, key, dict(store.cm.시험입력(master.logic(key)))) + names = [one["이름글"] for one in _all_lines(got)] + assert any(part in one for one in names), (key, names) + assert not any(re.search(r"\b[A-Z]{2}\d{6}", one) for one in names), (key, names) + raw_of = {m["이름"]: m["원문"] for m in got["별칭"]} + for one in names: # 이름글 속 별칭은 모두 원문 부르기로 되돌려짐 + back = mt.mc._unmask(one, raw_of) + left = [n for n in raw_of if re.search(rf"(? None: + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + for key in ("GC000325", "GC000360"): + got = mt.lines(files, key, dict(store.cm.시험입력(master.logic(key)))) + names = [one["이름글"] for one in _all_lines(got)] + assert not any(len(w) > 20 for one in names for w in re.findall(r"[^\W\d]\w*", one)) + if key == "GC000325": + assert any(one.startswith("콘크리트피니셔노무비단가 * ") for one in names) + else: + assert "보통인부노임_2 * 0 = 보통인부비_2" in names # ── PLAN 2-1 · 2-2 — 조각(알약) ───────────────────────────────────────── @@ -205,19 +265,25 @@ def test_조각_자리_표시_든_요소_참조는_이름만_있고_참조는_ assert piece["글"] # 이름 자리는 비지 않음(못 풀어도 원문 그대로) -def test_조각_만약은_계산되면_걸린_가지만() -> None: - node = mt.mf.parse("만약(1 > 0, 2, 3)") - pieces = mt._pieces(node, node, {}, mt.mf.Master({}), {}) - assert pieces == [{"kind": "값", "글": "2"}] - assert not any("깊이" in p for p in pieces) +def test_조각_만약은_쉬운_말로_안_걸린_갈래가_흐리다() -> None: + """PLAN 3-3 — 「조건 이면 A · 아니면 B」 · 안 걸린 갈래 조각에 흐림.""" + env = {"폼타이": "사용"} + node = mt.mf.parse("만약(폼타이 == '사용', 2.14 / 10, 0)") + pieces = mt._pieces(node, node, env, mt.mf.Master({}), {}) + assert [p["글"] for p in pieces] == ["폼타이 가 사용 이면", "2.14", "÷", "10", "· 아니면", "0"] + assert [bool(p.get("흐림")) for p in pieces] == [False, False, False, False, True, True] + off = mt._pieces(node, node, {"폼타이": "아님"}, mt.mf.Master({}), {}) + assert [bool(p.get("흐림")) for p in off] == [False, True, True, True, False, False] -def test_조각_만약은_계산_안_되면_양쪽_다_깊이가_붙는다() -> None: - """PLAN 2-2 ① — 계산 안 되면(모르는 이름) 양쪽 다 · 깊이를 적어 접을 수 있게.""" - node = mt.mf.parse("만약(모르는이름 > 0, 2, 3)") +def test_조각_만약은_조건_입력이_비면_두_갈래_다_흐림_없음() -> None: + """조건이 안 셈해지면(모르는 이름) 두 갈래 다 · 흐림 없음 · 깊이를 적어 접을 수 있게.""" + node = mt.mf.parse("만약(폼타이 == '사용', 2, 3)") pieces = mt._pieces(node, node, {}, mt.mf.Master({}), {}) - vals = [(p["kind"], p["글"], p.get("깊이")) for p in pieces] - assert vals == [("값", "2", 1), ("기호", "/", None), ("값", "3", 1)] + assert [p["글"] for p in pieces] == ["폼타이 가 사용 이면", "2", "· 아니면", "3"] + assert not any("흐림" in p for p in pieces) + assert [p.get("깊이") for p in pieces] == [None, 1, 1, 1] + assert mt.named(node, node, {}, mt.mf.Master({})) == "만약(폼타이 == '사용', 2, 3)" def test_조각_로직_부르는_줄_990개_세_비목에_다_걸치고_계산_전엔_금액이_빈다( @@ -240,3 +306,241 @@ def test_조각_로직_부르는_줄_990개_세_비목에_다_걸치고_계산_ assert one["금액"] is None and one["글"] == "" and "까닭" not in one assert one["조각"] # 수량 식은 깨진 요소와 무관해 구조로 그대로 남음 assert [g["소계"] for g in out["groups"]] == [None] + + +def test_자리_표시_든_참조는_이름_입력값으로_읽히고_참조는_풀린_키() -> None: + """GF000081 잡품 — 「MO:휘발유:{지역}」 날글 대신 「휘발유 · 전국평균」 · 참조 = 풀린 MO 키.""" + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + row = master.logic("GF000081") + + def pills(given: dict) -> tuple[list, str]: + got = mt.lines(files, "GF000081", given) + one = next(o for o in _all_lines(got) if o["이름"] == "잡품") + return [p for p in one["조각"] if p["kind"] == "요소"], one["이름글"] + + filled, name_text = pills(dict(store.cm.시험입력(row))) + assert [p["글"] for p in filled] == ["휘발유 · 전국평균"] + assert filled[0]["참조"]["키"] == "MO000001" and "MO:" not in name_text + blank, name_text = pills({}) + assert [p["글"] for p in blank] == ["휘발유 · 지역"] and "참조" not in blank[0] + assert "{" not in name_text and "MO:" not in name_text + + +def test_자리_표시_참조가_조각_요소조각_이름식에_날글로_남지_않는다() -> None: + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + for key in ("GC000138", "GC000211", "GC000267", "GC000559", "GF000081"): + for given in (dict(store.cm.시험입력(master.logic(key))), {}): + for one in _all_lines(mt.lines(files, key, given)): + shown = [one["이름글"], *(p["글"] for p in one["조각"] + one["요소조각"])] + assert not any(re.search(r"[A-Z]{2}:|[{}]", t) for t in shown), (key, shown) + + +def test_표_조각_참조_줄은_엔진이_찾은_줄의_차례다() -> None: + """GC000010 크레인 — 줄에 없는 조건 칸은 맞음이라 흉내 내면 틀림 · GF000160 합판도.""" + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + for key in ("GC000010", "GF000160"): + got = mt.lines(files, key, dict(store.cm.시험입력(master.logic(key)))) + tables = [p for one in _all_lines(got) for p in one["조각"] if p["kind"] == "표"] + assert tables and all("줄" in p["참조"] for p in tables), key + for p in tables: + rows = master.get(p["참조"]["키"])["줄"] + hit = rows[p["참조"]["줄"]] + assert hit is mt.mf.find(master.get(p["참조"]["키"]), p["참조"]["행"], p["참조"]["열"]) + got = mt.lines(files, "GC000010", dict(store.cm.시험입력(master.logic("GC000010")))) + pill = next( + p + for one in _all_lines(got) + for p in one["조각"] + if p["kind"] == "표" and p["참조"]["키"] == "QC000031" and "크레인" in p["글"] + ) + assert master.get("QC000031")["줄"][pill["참조"]["줄"]]["구분"] == "크레인" + + +def test_합판거푸집_표_조각_글은_줄마다_다르고_참조에_찾기_조건이_있다(client: TestClient) -> None: + """PLAN 3-3 — 글 = 「찾은 줄 · 칸」 · 참조.행 = 찾기 조건 · 중간 값은 글에 이름.""" + got, _ = _both(client, FORM) + tables = [p for one in _all_lines(got) for p in one["조각"] if p["kind"] == "표"] + assert len(tables) == 7 and len({p["글"] for p in tables}) == 7 + plywood = next(p for p in tables if p["글"] == "합판 · 1회사용") + assert plywood["참조"]["행"] == {"구분": "합판"} and plywood["참조"]["열"] == "기준수량_1회사용" + rate = next(p for p in _all_lines(got)[0]["조각"] if p["kind"] == "값") + assert rate["글"] == "인력율" and rate["값"] == "1.31" + tie = _all_lines(got)[-1]["조각"] + assert tie[0]["글"] == "폼타이 가 사용 이면" and [bool(p.get("흐림")) for p in tie][1] is True + + +def test_값_없이_열어도_텍스트가_멈추지_않는다(client: TestClient) -> None: + got = client.post("/api/m01/text", json={"key": FORM, "inputs": {}}).json() + assert got["ok"] is True + tie = _all_lines(got)[-1] + assert "만약(폼타이 == '사용'" in tie["이름글"] and not any("흐림" in p for p in tie["조각"]) + + +def test_모든_로직을_값_없이_돌려도_멈추지_않는다() -> None: + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + stops = [] + for rows in master.index.values(): + for key in rows: + if key[:2] in mt.mf.mk.GROUP_OF and mt.mf.mk.GROUP_OF[key[:2]] == "로직": + try: + mt.lines(files, key, {}) + except mt._CALC_ERRORS as e: + stops.append((key, str(e))) + assert not stops, stops[:3] + + +def test_글_안의_돈은_정수_계산_값은_소수_그대로(client: TestClient) -> None: + assert mt.fmt_money(Decimal("79482.678")) == "79,483" + assert mt.fmt_money(Decimal("0.4")) == "0" + got, _ = _both(client, FORM) + first = _all_lines(got)[0] + assert first["글"] == "275,790 * 0.22 * 1.31 = 79,483" # 수량·비율은 소수 그대로 + assert Decimal(str(first["금액"])) == Decimal("79482.678") # 값은 그대로 + + +def test_자동값으로_모든_로직을_돌려도_멈추지_않는다() -> None: + """빈 줄(후보 0 · 값 빈 품목) — 그 줄만 빈 글 · 로직은 계속(PLAN 3-2).""" + files = store.cm.load(folder=REAL) + master = store.cm.master(REAL) + stops = [] + for rows in master.index.values(): + for key in rows: + if mt.mf.mk.GROUP_OF.get(key[:2]) != "로직": + continue + given = {n: store._dec(v) for n, v in store.auto(key)["값"].items()} + try: + mt.lines(files, key, given) + except mt._CALC_ERRORS as e: + stops.append((key, str(e))) + assert not stops, stops[:3] + + +def test_빈_줄은_빈_글이고_소계는_빈_줄_빼고_더한다(client: TestClient) -> None: + key = "GC000027" + given = {n: str(v) for n, v in store.auto(key)["값"].items()} + got = client.post("/api/m01/text", json={"key": key, "inputs": given}).json() + calc = client.post("/api/m01/calc", json={"key": key, "inputs": given}).json() + assert got["ok"] is True + blank = [one for one in _all_lines(got) if one["금액"] is None] + assert blank and all(one["글"] == "" and one["까닭"] for one in blank) + assert Decimal(str(got["계"])) == Decimal(str(calc["sums"]["계"])) + + +def test_곱_뒤_덧셈_묶음은_괄호로_이름_식_값_식_모두() -> None: + """GC000467 촌락지대가 — 노임 * (아홉 항 합) · 첫 항에만 곱하는 것처럼 읽히지 않게.""" + files = store.cm.load(folder=REAL) + row = store.cm.master(REAL).logic("GC000467") + given = {n: store._dec(v) for n, v in store.auto("GC000467")["값"].items()} + got = mt.lines(files, "GC000467", given) + one = _all_lines(got)[0] + assert one["글"].startswith("295,138 * (1 + 1 + 0.5") and " = 3,984,363" in one["글"] + left = one["이름글"].split(" = ")[0] + assert left.startswith("중급기술자건설노임 * (") and left.endswith(".수량)") + assert Decimal(str(one["금액"])) == Decimal("295138") * Decimal("13.5") + blank = mt.lines(files, "GC000467", {}) + assert _all_lines(blank)[0]["이름글"].split(" = ")[0].count("(") == 1 # 값 없이도 그대로 + assert row # 로직이 있음 + + +def test_이름글_번호_별칭은_별칭표로_되돌려진다() -> None: + """이름글 속 별칭(가1~가9)은 별칭표 이름 그대로여야 식으로 되돌려짐(찾은 줄 이름 X).""" + files = store.cm.load(folder=REAL) + given = {n: store._dec(v) for n, v in store.auto("GC000467")["값"].items()} + got = mt.lines(files, "GC000467", given) + left = _all_lines(got)[0]["이름글"].split(" = ")[0].split(" * ", 1)[1].strip("()") + raw_of = {m["이름"]: m["원문"] for m in got["별칭"]} + back = mt.mc._unmask(left, raw_of) + assert back.count("찾기(") == 9 and "촌락지대가" not in back + + +def test_요소조각_로직_부르기는_이름과_인자_글_날식_없음() -> None: + """GC001093 트럭 줄 — 로직 조각(키 GC000268) + 인자 글(기계 이름 · 지역 지금 값).""" + files = store.cm.load(folder=REAL) + row = store.cm.master(REAL).logic("GC001093") + given = dict(store.cm.시험입력(row)) + for got in (mt.lines(files, "GC001093", given), mt.lines(files, "GC001093", {})): + lines = _all_lines(got) + assert all("요소조각" in one for one in lines) + truck = [one for one in lines if one["이름"].startswith("트럭")] + assert truck + for one in truck: + head, args = one["요소조각"] + assert head["kind"] == "로직" and head["참조"]["키"] == "GC000268" + assert args["kind"] == "글" and "덤프트럭" in args["글"] + assert "GC000268" not in args["글"] and "(" not in args["글"].split("덤프트럭")[0] + labor = lines[0]["요소조각"] + assert labor[0]["kind"] == "요소" and labor[0]["글"] == "배관공(수도)" + + +def test_값_식_작은_수는_유효숫자로_보인_수끼리_곱해도_금액과_맞음() -> None: + """GC001064 수량 0.001667 — 넷째 자리로 잘라 456 이 465 로 읽히던 것.""" + files = store.cm.load(folder=REAL) + row = store.cm.master(REAL).logic("GC001064") + got = mt.lines(files, "GC001064", dict(store.cm.시험입력(row))) + first = _all_lines(got)[0]["글"] + assert first == "273,308 * 0.001667 = 456" + assert mt.fmt(Decimal("0.00166667")) == "0.001667" and mt.fmt(Decimal("12.34567")) == "12.3457" + assert mt.fmt_price(Decimal("3.5")) == "3.5" and mt.fmt_price(Decimal("3500.4")) == "3,500" + + +def _text_of(key: str, **given) -> list[dict]: + auto = {k: v for k, v in (store.auto(key).get("값") or {}).items() if v is not None} + return _all_lines(store.text(key, {**auto, **given})) + + +def test_빈_줄을_부른_덧줄_값_조각은_None_대신_그_줄_이름() -> None: + line = next(x for x in _text_of("GF000185") if x["이름"] == "콘크리트다짐") + texts = [p["글"] for p in line["조각"]] + assert "None" not in texts and "구체콘크리트(철근) 레미콘" in texts + + +def test_만약이_고른_갈래가_덧셈이면_곱셈_안에서_괄호로_묶임() -> None: + line = next(x for x in _text_of("GC000376", 면="BOX 형상 내면") if x["이름"] == "도장공") + assert line["글"] == "263,017 * 0.031 * (1 + 60 %) = 13,046" + outer = next(x for x in _text_of("GC000376", 면="외면") if x["이름"] == "도장공") + assert "(" not in outer["글"] + + +def test_수량_식_안_로직_조각은_그_로직_이름() -> None: + line = next(x for x in _text_of("GC000111") if x["이름"] == "리퍼 기계경비") + pill = next(p for p in line["조각"] if p["kind"] == "로직") + assert pill["글"] == "굴착기(무한궤도) 시간당 기계경비" and pill["참조"]["키"] == "GC000267" + + +def test_표에_없는_입력_조합은_그_줄만_비우고_글은_그대로() -> None: + """/calc 와 같은 빈 줄 규칙 — 풀리지 않는 중간값은 /text 도 멈추지 않음.""" + got = store.text("GC001000", {**store.auto("GC001000")["값"], "시공구분": "기계시공"}) + lines = _all_lines(got) + blank = [x for x in lines if x["금액"] is None] + assert blank and all(x["이름글"] and x["조각"] for x in blank) + assert len(blank) < len(lines) or got["계"] is not None + calc = store.calc("GC001000", {**store.auto("GC001000")["값"], "시공구분": "기계시공"}) + assert got["계"] == calc["sums"]["계"] + + +def _mat_piece(lines: list[dict], name: str) -> dict: + return next(x for x in lines if x["이름"] == name)["요소조각"][0] + + +def test_재료_줄_요소조각은_잡힌_품목_이름과_MT_참조() -> None: + piece = _mat_piece(_text_of("GF000159"), "결속선 0.9㎜") + assert piece["글"].startswith("어닐링철선(결속선)") and piece["글"].endswith("서울") + assert piece["참조"] == {"테이블": "MT", "키": "MT000209"} + auto = {k: v for k, v in store.auto("GF000159")["값"].items() if v is not None} + assert store.text("GF000159", auto)["계"] == store.calc("GF000159", auto)["sums"]["계"] + + +def test_고름으로_바꾼_재료_줄_요소조각은_고른_품목() -> None: + import check_master as cm + import master_pick as mp + + auto = {k: v for k, v in store.auto("GF000159")["값"].items() if v is not None} + items = cm.master().logic("GF000159")["호표"] + at = next(i for i, h in enumerate(items) if h.get("이름") == "결속선 0.9㎜") + with mp.choosing({at: "MT000174"}): + got = _all_lines(store.text("GF000159", auto)) + assert _mat_piece(got, "결속선 0.9㎜")["참조"]["키"] == "MT000174" diff --git a/resources/tester/test_masterdata_check.py b/resources/tester/test_masterdata_check.py index 7574707b..9b57f371 100644 --- a/resources/tester/test_masterdata_check.py +++ b/resources/tester/test_masterdata_check.py @@ -176,11 +176,11 @@ def test_범위규칙_이하_위끝만(whole): def test_범위규칙_마지막칸_이하_포함(whole): - """13-6-1 직경 「80㎝이상∼100㎝이하」 — 100 은 맞고 101 은 막힘.""" + """13-6-1 직경 「80㎝이상∼100㎝이하」 — 100 은 맞고 101 은 표에 없는 줄이라 그 줄만 비고 까닭.""" given = {"직경": 100, "지역": "전국평균", "보정작업": "해당 없음"} assert mf.run(whole, "GF000226", given)["줄"][0]["수량"] == Decimal("0.68") - with pytest.raises(mf.FormulaError): - mf.run(whole, "GF000226", {**given, "직경": 101}) + line = mf.run(whole, "GF000226", {**given, "직경": 101})["줄"][0] + assert line["금액"] is None and line["단가"] is None and "인 줄이 없음" in line["까닭"] def test_굴착기_부착용_집게(whole): @@ -632,11 +632,11 @@ def test_미공표_준용(): got = mf.run(mf.Master(files), "GX999999", {}) assert got["줄"][0]["출처"] == "준용: 보통인부" assert got["노무비"] == 2 * Decimal(172068) - lines[0]["요소"] = "LB:절단공" # 값도 준용도 없으면 멈춤 — 준용을 골라야 값이 섬 + lines[0]["요소"] = "LB:절단공" # 값도 준용도 없으면 그 줄이 빔 — 준용을 골라야 값이 섬 alias = mf.Master(files).get("LB:절단공") alias["상태"], alias["값"], alias["준용"] = "미확보", None, None - with pytest.raises(mf.FormulaError): - mf.run(mf.Master(files), "GX999999", {}) + got = mf.run(mf.Master(files), "GX999999", {}) + assert got["줄"][0]["금액"] is None and got["줄"][0]["까닭"] and got["노무비"] == 0 alias["준용"] = common assert mf.run(mf.Master(files), "GX999999", {})["줄"][0]["출처"] == "준용: 보통인부" lines[0]["요소"] = "LB:1003" # 옛 직종 갱부를 품은 현행 줄(특별인부) — 값 그대로