From 4b1eeec52478b9bcf3f84fb133ecd79a0125fd16 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 20 Sep 2026 20:03:59 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(M01):=20=EC=A4=80=EC=9A=A9=20=EA=B3=A0?= =?UTF-8?q?=EB=A5=B4=EB=A9=B4=20=EA=B0=92=20=EB=94=B0=EB=9D=BC=EC=98=B4=20?= =?UTF-8?q?=C2=B7=20=EC=9D=B8=EB=A0=A5=20=EC=83=81=ED=83=9C=20=EA=B1=B0?= =?UTF-8?q?=EB=A6=84=20=C2=B7=20=EC=9C=A0=EA=B0=80=EC=A0=84=EB=A0=A5=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=C2=B7=EC=83=81=EC=84=B8=EA=B5=AC=EB=B6=84=20?= =?UTF-8?q?=EA=B1=B0=EB=A6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인력 준용 고르기 — 값 칸이 고른 직종 값으로 바뀌어 노랑 · 비우면 원래 값 - 저장 때 서버가 준용 직종 값을 다시 읽어 값 칸에 채움(화면 값 불신) - 인력 왼쪽 패널 「상태」 드롭다운(목록은 서버) · 재료 유가전력 2단 거름 - 시험 2개 더함 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT --- M01_MasterData/M01_MasterData_Api_Fetch.ts | 12 ++- M01_MasterData/M01_MasterData_Router.py | 3 +- M01_MasterData/M01_MasterData_Store.py | 35 ++++++- M01_MasterData/M01_MasterData_UI_Pick.ts | 4 +- M01_MasterData/M01_MasterData_UI_Rows.ts | 9 +- M01_MasterData/M01_MasterData_UI_Side.ts | 107 +++++++++++++++------ resources/tester/test_m01_api.py | 38 ++++++++ ui_template/ui_template_locale_m1.ts | 3 + 8 files changed, 171 insertions(+), 40 deletions(-) diff --git a/M01_MasterData/M01_MasterData_Api_Fetch.ts b/M01_MasterData/M01_MasterData_Api_Fetch.ts index df3e436e..9ce08993 100644 --- a/M01_MasterData/M01_MasterData_Api_Fetch.ts +++ b/M01_MasterData/M01_MasterData_Api_Fetch.ts @@ -112,13 +112,19 @@ export const fetchRows = ( unlinked = false, sub = "", detail = "", + state = "", ): Promise => - get("/rows", { file, page, size, q, unlinked: unlinked ? 1 : 0, sub, detail }); + get("/rows", { file, page, size, q, unlinked: unlinked ? 1 : 0, sub, detail, state }); /** 한 테이블 파일은 `file` · 장 그룹(소요량 · 계수 · 로직)은 `group` 으로 부름. */ +export const fetchSubsAll = (where: { + file?: string; + group?: string; +}): Promise<{ subs: SubInfo[]; states?: string[] }> => + get("/subs", { file: where.file ?? "", group: where.group ?? "" }); + export const fetchSubs = async (where: { file?: string; group?: string }): Promise => - (await get<{ subs: SubInfo[] }>("/subs", { file: where.file ?? "", group: where.group ?? "" })) - .subs; + (await fetchSubsAll(where)).subs; export const fetchTables = ( group: string, diff --git a/M01_MasterData/M01_MasterData_Router.py b/M01_MasterData/M01_MasterData_Router.py index 7880fab9..86e59128 100644 --- a/M01_MasterData/M01_MasterData_Router.py +++ b/M01_MasterData/M01_MasterData_Router.py @@ -81,8 +81,9 @@ def get_rows( unlinked: bool = False, sub: str = "", detail: str = "", + state: str = "", ) -> dict: - return _call(store.rows, file, page, size, q, unlinked, sub, detail) + return _call(store.rows, file, page, size, q, unlinked, sub, detail, state) @router.get("/tables") diff --git a/M01_MasterData/M01_MasterData_Store.py b/M01_MasterData/M01_MasterData_Store.py index 3df73036..eacfe9f7 100644 --- a/M01_MasterData/M01_MasterData_Store.py +++ b/M01_MasterData/M01_MasterData_Store.py @@ -126,6 +126,9 @@ def refs_of(rows_: list[dict]) -> dict[str, str]: return out +_STATES = ("공표", "산정", "미공표", "미확보", "추정") # 인력 「상태」 거름 차례 + + def subs(file: str = "", group: str = "") -> dict: """하위 거름 목록 — 한 테이블 파일은 머리 묶음(인력 「구분」 · 기계 「세부분류」) · 장 그룹(소요량 · 계수 · 로직)은 장 파일에서 모음(구분 = 원문 + 부문 · 상세구분 = 장 · 차례대로).""" @@ -144,12 +147,26 @@ def subs(file: str = "", group: str = "") -> dict: return {"file": "", "version": "", "slot": "구분", "subs": list(found.values())} data, version = read(file) slot = next((s for s in ("구분", "세부분류") if isinstance(data.get(s), dict)), "") + lines = data.get(items_key(data)) or [] + has_state = any("상태" in r for r in lines) + + def details_of(name: str, head: dict) -> list: + """머리에 상세구분이 없으면 줄에서 모음(재료_유가전력 — 지역 · 계약종별).""" + if head.get("상세구분"): + return head["상세구분"] + return list( + dict.fromkeys( + r["상세구분"] for r in lines if r.get("구분") == name and r.get("상세구분") + ) + ) + return { "file": file, "version": version, "slot": slot, + "states": list(_STATES) if has_state else [], "subs": [ - {"name": name, "book": head.get("원문"), "details": head.get("상세구분") or []} + {"name": name, "book": head.get("원문"), "details": details_of(name, head)} for name, head in (data.get(slot) or {}).items() ], } @@ -163,6 +180,7 @@ def rows( unlinked: bool = False, sub: str = "", detail: str = "", + state: str = "", ) -> dict: data, version = read(file) hits = [r for r in data.get(items_key(data)) or [] if _hit(r, q)] @@ -170,6 +188,8 @@ def rows( hits = [r for r in hits if sub in (r.get("구분"), r.get("세부분류"))] if detail: # 인력 상세구분 hits = [r for r in hits if r.get("상세구분") == detail] + if state: # 인력 상태 + hits = [r for r in hits if r.get("상태") == state] if unlinked: # 품셈재료 — 아직 못 이은 줄 hits = [r for r in hits if not r.get("연결")] page, size = max(page, 1), min(max(size, 1), 500) @@ -452,6 +472,17 @@ def _chapter_cols(file: str, data: dict) -> dict: } +def _follow(row: dict, items: list[dict], file: str) -> dict: + """인력 줄의 준용 = 그 직종의 값을 다시 읽어 값 칸에 채움(화면이 보낸 값을 믿지 않음).""" + ref = row.get("준용") + if file != "인력.json" or not isinstance(ref, str) or not _REF_KEY.match(ref): + return row + src = next((r for r in items if r.get("키") == ref), None) + if src is None or src.get("값") is None: + return row + return {**row, "값": src["값"]} + + def _apply(data: dict, changes: list[dict], file: str, book: dict) -> None: """고침 · 더함(키는 대장의 다음 번호 · 원문번호 없으면 빈 글) · 지움 — 키는 바꾸지 않음.""" items = data.setdefault(items_key(data), []) @@ -466,6 +497,8 @@ def _apply(data: dict, changes: list[dict], file: str, book: dict) -> None: raise StoreError(404, f"{file} · 없는 키 「{key}」") if op != "delete" and not isinstance(row, dict): raise StoreError(400, f"{file} · row 없음") + if op != "delete": + row = _follow(row, items, file) if op == "add": number = str(row.get("원문번호") or "") cols = _chapter_cols(file, data) diff --git a/M01_MasterData/M01_MasterData_UI_Pick.ts b/M01_MasterData/M01_MasterData_UI_Pick.ts index d41767e3..c3934e04 100644 --- a/M01_MasterData/M01_MasterData_UI_Pick.ts +++ b/M01_MasterData/M01_MasterData_UI_Pick.ts @@ -23,7 +23,7 @@ export interface PickOptions { /** 처음 찾을 글 — 그 재료 이름·규격 (없는 결과면 첫 낱말만으로 다시). */ seed: string; /** 고르면 ref · 연결 끊기 = null. */ - onPick: (ref: string | null, name?: string) => void; + onPick: (ref: string | null, name?: string, value?: unknown) => void; /** 있으면 「후보 조건으로 연결」 칸이 뜸. */ cond?: { 이름: string; 규격: string; onCond: (c: Cond) => void }; } @@ -93,7 +93,7 @@ export function openPickModal(opt: PickOptions): void { ], }); b.addEventListener("click", () => { - opt.onPick(it.ref, `${it.이름} ${it.규격}`.trim()); + opt.onPick(it.ref, `${it.이름} ${it.규격}`.trim(), it.값); close(); }); return b; diff --git a/M01_MasterData/M01_MasterData_UI_Rows.ts b/M01_MasterData/M01_MasterData_UI_Rows.ts index 858a6fcf..439f8f64 100644 --- a/M01_MasterData/M01_MasterData_UI_Rows.ts +++ b/M01_MasterData/M01_MasterData_UI_Rows.ts @@ -90,7 +90,7 @@ const remember = (ref: string | null, name?: string): void => { /** 돌려받은 함수 = 이 화면을 걷을 때 부를 해제. */ export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void { - const { sub, detail } = pick; + const { sub, detail, state } = pick; const file = pick.file.file; let page = 1; let data: RowsPage | null = null; @@ -233,7 +233,7 @@ export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void const load = async (): Promise => { try { - data = await fetchRows(file, page, SIZE, q, unlinked, sub, detail); + data = await fetchRows(file, page, SIZE, q, unlinked, sub, detail, state); for (const [key, name] of Object.entries(data.refs ?? {})) names.set(key, name); paint(); } catch (error) { @@ -295,9 +295,10 @@ function wageCell(row: Row, cur: Row, put?: Put): HTMLTableCellElement { title: `${L("M01_JobTitle")} · ${cur["이름"] ?? ""}`, current: text, seed: "", - onPick: (ref, name) => { + onPick: (ref, name, value) => { remember(ref, name); - put(withCell(cur, "준용", ref)); + // 준용을 고르면 값도 그 직종 값으로 · 비우면 원래 값 (저장 때 서버가 다시 읽음) + put(withCell(withCell(cur, "준용", ref), "값", ref ? value : row["값"])); }, }), ); diff --git a/M01_MasterData/M01_MasterData_UI_Side.ts b/M01_MasterData/M01_MasterData_UI_Side.ts index b3db4eba..ebf58894 100644 --- a/M01_MasterData/M01_MasterData_UI_Side.ts +++ b/M01_MasterData/M01_MasterData_UI_Side.ts @@ -8,7 +8,7 @@ import { createSelectField, el, showToast } from "@ui/ui_template_elements"; import { attachCollapsible } from "@ui/ui_template_collapsible"; import { t as L } from "@ui/ui_template_locale"; -import { fetchFiles, fetchSubs, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch"; +import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch"; import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree"; /** 요소 화면에서 열 것 — 파일 · 하위 거름(구분·세부분류) · 상세구분 · 제목 */ @@ -17,6 +17,8 @@ export interface Pick { file: FileInfo; sub: string; detail: string; + /** 인력 상태 거름 · 비면 전체 */ + state?: string; label: string; } @@ -39,13 +41,19 @@ export interface FilterArgs { subs: SubInfo[]; total: number; subLabel: string; + /** 「전체」 줄 글 · 없으면 「전체」 */ + title?: string; /** 없으면 한 단 */ detailLabel?: string; - onPick: (sub: string, detail: string, label: string) => void; + /** 있으면 「상태」 드롭다운 한 단 더 — 목록은 서버가 줌 */ + states?: string[]; + stateLabel?: string; + onPick: (sub: string, detail: string, label: string, state: string) => void; } const MATERIAL_NAMES: Record = { 유가전력: "유가·전력" }; /** 재료 컨테이너 차례 — 여기 없는 파일은 뒤에 */ +const FUEL = "유가전력"; const MATERIAL_ORDER = ["자재품목", "유가전력", "품셈재료"]; /** 파일 이름 → 목록 글자 — 「소요량_건설품셈_10장_창호…」 → 「건설품셈_10장_창호…」 */ @@ -72,6 +80,7 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si const buttons = new Map(); // 하위 거름은 서버가 준 목록(파일 머리에 등록된 구분·세부분류) — 새 조사가 늘어도 화면은 그대로 const kinds = new Map(); + const states = new Map(); const setActive = (id: string | null): void => { for (const [key, button] of buttons) button.classList.toggle("is-active", key === id); @@ -136,10 +145,19 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si disabled: !pickSub?.details.length, }) : null; + const state = a.states?.length + ? createSelectField({ + options: opts(a.states), + value: a.states.includes(saved[2]) ? saved[2] : "", + compact: true, + label: a.stateLabel ?? "", + }) + : null; const run = (): void => { const d = detail?.select.value ?? ""; + const s = state?.select.value ?? ""; try { - sessionStorage.setItem(a.store, JSON.stringify([sub.select.value, d])); + sessionStorage.setItem(a.store, JSON.stringify([sub.select.value, d, s])); } catch { /* 기억 못 해도 거름은 됨 */ } @@ -147,7 +165,8 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si a.onPick( sub.select.value, d, - [sub.select.value, d].filter(Boolean).join(" · ") || L("M01_All"), + [sub.select.value, d, s].filter(Boolean).join(" · ") || L("M01_All"), + s, ); }; sub.select.addEventListener("change", () => { @@ -159,16 +178,23 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si run(); }); detail?.select.addEventListener("change", run); - const all = make({ id: a.id, label: L("M01_All"), count: a.total, run }, "", 0, () => { - sub.select.value = ""; - sub.select.dispatchEvent(new Event("change")); - }); + state?.select.addEventListener("change", run); + const all = make( + { id: a.id, label: a.title ?? L("M01_All"), count: a.total, run }, + "", + 0, + () => { + if (state) state.select.value = ""; + sub.select.value = ""; + sub.select.dispatchEvent(new Event("change")); + }, + ); return el("div", { children: [ all, el("div", { className: "m01-side__filter", - children: [sub.root, ...(detail ? [detail.root] : [])], + children: [sub.root, ...(detail ? [detail.root] : []), ...(state ? [state.root] : [])], }), ], }); @@ -183,8 +209,8 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si const files = lists.get(group) ?? []; const body = bodies.get(group); if (!body) return; - const open = (file: FileInfo, sub: string, label: string, detail = ""): void => - onOpen({ group, file, sub, detail, label }); + const open = (file: FileInfo, sub: string, label: string, detail = "", state = ""): void => + onOpen({ group, file, sub, detail, state, label }); const one = files[0]; const dropped: Partial> = { 인력: { label: L("M01_LaborSub"), detail: L("M01_LaborDetail") }, @@ -202,36 +228,59 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si total: files.reduce((n, f) => n + f.rows, 0), subLabel: drop.label, detailLabel: drop.detail, - onPick: (sub, detail, label) => open(one, sub, label, detail), + states: states.get(group), + stateLabel: L("M01_LaborState"), + onPick: (sub, detail, label, state) => open(one, sub, label, detail, state), }), ); return; } body.replaceChildren( - ...renderTree( - [...files] - .sort((a, b) => order(a.file) - order(b.file)) - .map((f) => { - const name = fileLabel(f.file); - const label = group === "재료" ? (MATERIAL_NAMES[name] ?? name) : name; - return { - id: `${group}\n${f.file}`, - label, - count: f.rows, - run: () => open(f, "", label), - }; - }), - make, - ), + ...[...files] + .sort((a, b) => order(a.file) - order(b.file)) + .flatMap((f) => { + const name = fileLabel(f.file); + const label = group === "재료" ? (MATERIAL_NAMES[name] ?? name) : name; + if (group === "재료" && name === FUEL) + return [ + filter({ + id: `${group}\n${f.file}`, + store: `m01.filter.${group}.${name}`, + subs: kinds.get(group) ?? [], + total: f.rows, + title: label, + subLabel: L("M01_FuelSub"), + detailLabel: L("M01_FuelDetail"), + onPick: (sub, detail, tail) => + open(f, sub, sub || detail ? `${label} · ${tail}` : label, detail), + }), + ]; + return renderTree( + [ + { + id: `${group}\n${f.file}`, + label, + count: f.rows, + run: () => open(f, "", label), + }, + ], + make, + ); + }), ); }; const refresh = async (group: string): Promise => { const list = await fetchFiles(group); if (list[0] && (group === "인력" || group === "기계")) { - kinds.set(group, await fetchSubs({ file: list[0].file })); + const got = await fetchSubsAll({ file: list[0].file }); + kinds.set(group, got.subs); + states.set(group as Group, got.states ?? []); + } else if (group === "재료") { + const fuel = list.find((f) => fileLabel(f.file) === FUEL); + if (fuel) kinds.set(group, (await fetchSubsAll({ file: fuel.file })).subs); } else if (group === "소요량" || group === "계수") { - kinds.set(group, await fetchSubs({ group })); + kinds.set(group, (await fetchSubsAll({ group })).subs); } lists.set(group as Group, list); drawGroup(group as Group); diff --git a/resources/tester/test_m01_api.py b/resources/tester/test_m01_api.py index 44d953bc..e41f8511 100644 --- a/resources/tester/test_m01_api.py +++ b/resources/tester/test_m01_api.py @@ -389,3 +389,41 @@ def test_자재품목_저장은_띄어쓰기를_안_바꿈(client: TestClient) - assert diff[0][1].replace('"물가자료": 1,', '"물가자료": X,').count("{ ") == diff[0][0].count( "{ " ) + + +def test_준용_저장은_직종_값을_다시_읽고_상태_거름(client: TestClient) -> None: + subs = _get(client, "/api/m01/subs", file=LABOR) + assert subs["states"] == ["공표", "산정", "미공표", "미확보", "추정"] + guess = _get(client, "/api/m01/rows", file=LABOR, size=500, state="추정") + assert 0 < guess["total"] < 30 and {r["상태"] for r in guess["rows"]} == {"추정"} + row = next(r for r in guess["rows"] if r["이름"] == "제관공") + other, version = _labor_row(client, "1016") # 화약취급공 + edited = {**row, "준용": other["키"], "값": 1} # 화면이 보낸 값은 믿지 않음 + body = { + "files": [ + { + "file": LABOR, + "version": guess["version"], + "changes": [{"op": "edit", "key": row["키"], "row": edited}], + } + ] + } + assert client.post("/api/m01/save", json=body).status_code == 200 + saved = next(r for r in _get(client, "/api/m01/rows", file=LABOR, q=row["키"])["rows"]) + assert saved["준용"] == other["키"] and saved["값"] == other["값"] + + +def test_유가전력_구분_상세구분_거름(client: TestClient) -> None: + subs = _get(client, "/api/m01/subs", file="재료_유가전력.json")["subs"] + assert {s["name"] for s in subs} == {"유류", "전력"} + power = next(s for s in subs if s["name"] == "전력") + assert "주택용 저압" in power["details"] + hit = _get( + client, + "/api/m01/rows", + file="재료_유가전력.json", + size=500, + sub="전력", + detail="주택용 저압", + ) + assert hit["total"] > 0 and {r["상세구분"] for r in hit["rows"]} == {"주택용 저압"} diff --git a/ui_template/ui_template_locale_m1.ts b/ui_template/ui_template_locale_m1.ts index c00c325e..33c82a98 100644 --- a/ui_template/ui_template_locale_m1.ts +++ b/ui_template/ui_template_locale_m1.ts @@ -55,6 +55,9 @@ export const ui_locales_m1 = { M01_All: ["전체", "All"], M01_LaborSub: ["구분", "Category"], M01_LaborDetail: ["상세구분", "Detail"], + M01_FuelSub: ["구분", "Category"], + M01_FuelDetail: ["상세구분", "Detail"], + M01_LaborState: ["상태", "Status"], M01_MachineSub: ["세부분류", "Sub-category"], M01_GroupLabor: ["인력", "Labor"], M01_GroupMaterial: ["재료", "Materials"], From e013168b4089bbae234a9a94923f1a0a50fe0a16 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 20 Sep 2026 20:17:57 +0900 Subject: [PATCH 2/2] =?UTF-8?q?knowledge(=EC=9C=A0=EA=B0=80=EC=A0=84?= =?UTF-8?q?=EB=A0=A5):=20=EC=A0=84=EB=A0=A5=20=EC=A4=84=20=EB=91=90=20?= =?UTF-8?q?=EC=A4=84=EB=A1=9C=20=EC=A4=84=EC=9E=84(=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=ED=99=95=EC=A0=95)=20=E2=80=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=E3=80=8C=EC=A0=84=EB=A0=A5=20=EA=B3=84=EC=95=BD?= =?UTF-8?q?=EC=A2=85=EB=B3=84=E3=80=8D=20=EA=B3=A0=EB=A5=B4=EA=B8=B0?= =?UTF-8?q?=EB=8F=84=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 재료_유가전력.json 전력 18줄 가운데 두 줄만 남김: ① 주택용 저압 전력량요금 120.0원/kWh(처음 200kWh까지, 옛 키 MO000058 그대로) ② 일반용(갑)Ⅰ 저압 전력량요금 한 줄(여름 132.4·봄가을 91.9·겨울 119.0 세 계절 단순평균 114.4원/kWh, 새 키 MO000073) — 비고에 계절 평균 값을 남김. 기본요금·최저요금·산업용(갑)Ⅰ·임시전력(갑) 16줄은 마스터에서 뺌(md 원문에는 그대로) — _키대장.json 「폐기」로 옮김(build 스크립트가 다시 돌아도 같은 결과, 번호 재사용 없음). 로직_건설품셈_13장_플랜트설비공사.json의 「전력 계약종별」 입력 고르기 8곳 모두 ["주택용 저압", "일반용(갑)Ⅰ 저압", "산업용(갑)Ⅰ 저압"] → 두 가지로 맞춤(산업용 뺌). 품셈재료 「전력」(MP000358) 연결은 {"이름": "전력량요금", "계약종별": "입력"} 구조라 바뀐 줄에도 그대로 이어짐 — master_material.py 코드 변경 불요(구분·상세구분·이름 앞머리로 찾는 방식이라 데이터만 바뀌면 됨). 검증: check_master 틀·로직 0건(전체) · test_masterdata_check·test_m01_api 72건 전부 통과(test_전력은_계약종별로_고름이 MO000058.값=120 그대로 확인). 계산 결과 전후 동일. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WaS48Nvs26o3aFUpcfJxVv --- resources/master_data/_키대장.json | 22 +--- .../scripts/build_재료_유가전력.py | 111 ++++-------------- .../로직_건설품셈_13장_플랜트설비공사.json | 24 ++-- resources/master_data/재료_유가전력.json | 22 +--- 4 files changed, 34 insertions(+), 145 deletions(-) diff --git a/resources/master_data/_키대장.json b/resources/master_data/_키대장.json index 58b2e33e..ce8b83af 100644 --- a/resources/master_data/_키대장.json +++ b/resources/master_data/_키대장.json @@ -1,6 +1,6 @@ { - "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 24361, "MO": 73, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 252, "CC": 337, "GC": 1104, "QC": 2060}, - "폐기": {"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": "미확보 기계산업기사 — 초급숙련기술자(기계·설비) 옛이름으로 옮김"}, + "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 24361, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 252, "CC": 337, "GC": 1104, "QC": 2060}, + "폐기": {"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"}, "CC000002": {"원문번호": "공통 1-3-1 노상 및 노반재료", "파일": "계수_건설품셈_01장_적용기준.json"}, @@ -2957,24 +2957,8 @@ "MO000052": {"원문번호": "실내등유:울산", "파일": "재료_유가전력.json"}, "MO000053": {"원문번호": "실내등유:세종", "파일": "재료_유가전력.json"}, "MO000054": {"원문번호": "실내등유:전남광주", "파일": "재료_유가전력.json"}, - "MO000055": {"원문번호": "주택용저압:기본_200이하", "파일": "재료_유가전력.json"}, - "MO000056": {"원문번호": "주택용저압:기본_201~400", "파일": "재료_유가전력.json"}, - "MO000057": {"원문번호": "주택용저압:기본_400초과", "파일": "재료_유가전력.json"}, "MO000058": {"원문번호": "주택용저압:전력량_처음200", "파일": "재료_유가전력.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"}, + "MO000073": {"원문번호": "일반용갑I저압:전력량_평균", "파일": "재료_유가전력.json"}, "MP000001": {"원문번호": "레미콘", "파일": "재료_품셈재료.json"}, "MP000002": {"원문번호": "시멘트", "파일": "재료_품셈재료.json"}, "MP000003": {"원문번호": "모래(중량)", "파일": "재료_품셈재료.json"}, diff --git a/resources/master_data/scripts/build_재료_유가전력.py b/resources/master_data/scripts/build_재료_유가전력.py index 09e3564a..b26021a5 100644 --- a/resources/master_data/scripts/build_재료_유가전력.py +++ b/resources/master_data/scripts/build_재료_유가전력.py @@ -5,7 +5,9 @@ (`master_keys.issue`). 휘발유·경유 36줄은 옛 판(전국평균 2026-08-14 · 시도별 2026-09-09) 값을 그대로 지님 — 여기서 새로 안 받음(로직 계산 결과가 이 작업 전후로 갈리지 않게). 새로 받는 것은 ① 실내등유(C004, `유가_오피넷/` 최신 전국평균·시도별) ② 전력(한전 전기요금표 별표1(C), -`전기요금_한국전력/20250101/`, 저압 위주 주요 계약종별만 — 나머지는 md 원문만 참조). +`전기요금_한국전력/20250101/`). 전력은 사용자 확정으로 전력량요금 두 줄만(주택용 저압 · +일반용(갑)Ⅰ 저압 계절 평균) — 기본요금·최저요금·산업용·임시전력 등은 md 원문만 참조, 옛 +키는 _키대장.json 「폐기」로 옮김(다시 빌드해도 같은 결과). 쓰기: ./venv/Scripts/python.exe resources/master_data/scripts/build_재료_유가전력.py """ @@ -104,100 +106,18 @@ def 실내등유_rows(national: Path, sido: Path) -> list[dict]: POWER_SOURCE = "유가전력 한전 전기요금표 2025-01-01" -# 별표1(C) 저압 위주 주요 계약종별만(원문 § 1.가 · 2.가(1) · 2.다(1) · 6.가) — 그 밖은 md 참조. +# 사용자 확정 — 전력량요금 두 줄만(2026-09-20). 기본요금·최저요금·산업용·임시전력 등 +# 나머지 계약종별은 md 원문(전기요금_한국전력/20250101/)만 참조 · 옛 키는 대장 「폐기」. POWER_ROWS = [ - # 주택용 저압(원문 1.가) - ("주택용저압:기본_200이하", "주택용 저압", "기본요금(200kWh 이하)", "원/호", 910, None), - ("주택용저압:기본_201~400", "주택용 저압", "기본요금(201~400kWh)", "원/호", 1600, None), - ("주택용저압:기본_400초과", "주택용 저압", "기본요금(400kWh 초과)", "원/호", 7300, None), + ("주택용저압:전력량_처음200", "주택용 저압", "전력량요금", "원/kWh", 120.0, "처음 200kWh까지"), ( - "주택용저압:전력량_처음200", - "주택용 저압", - "전력량요금(처음 200kWh까지)", - "원/kWh", - 120.0, - None, - ), - ( - "주택용저압:전력량_다음200", - "주택용 저압", - "전력량요금(다음 200kWh까지)", - "원/kWh", - 214.6, - None, - ), - ("주택용저압:전력량_400초과", "주택용 저압", "전력량요금(400kWh 초과)", "원/kWh", 307.3, None), - ("주택용저압:월간최저요금", "주택용 저압", "월간 최저요금", "원/월", 1000, None), - # 일반용(갑)Ⅰ 저압(원문 2.가(1)) - ("일반용갑I저압:기본", "일반용(갑)Ⅰ 저압", "기본요금", "원/kW", 6160, None), - ( - "일반용갑I저압:전력량_여름철", + "일반용갑I저압:전력량_평균", "일반용(갑)Ⅰ 저압", - "전력량요금(여름철)", - "원/kWh", - 132.4, - None, - ), - ( - "일반용갑I저압:전력량_봄가을철", - "일반용(갑)Ⅰ 저압", - "전력량요금(봄·가을철)", - "원/kWh", - 91.9, - None, - ), - ( - "일반용갑I저압:전력량_겨울철", - "일반용(갑)Ⅰ 저압", - "전력량요금(겨울철)", - "원/kWh", - 119.0, - None, - ), - # 산업용(갑)Ⅰ 저압(원문 2.다(1)) - ("산업용갑I저압:기본", "산업용(갑)Ⅰ 저압", "기본요금", "원/kW", 5550, None), - ( - "산업용갑I저압:전력량_여름철", - "산업용(갑)Ⅰ 저압", - "전력량요금(여름철)", - "원/kWh", - 107.7, - None, - ), - ( - "산업용갑I저압:전력량_봄가을철", - "산업용(갑)Ⅰ 저압", - "전력량요금(봄·가을철)", - "원/kWh", - 85.9, - None, - ), - ( - "산업용갑I저압:전력량_겨울철", - "산업용(갑)Ⅰ 저압", - "전력량요금(겨울철)", - "원/kWh", - 106.0, - None, - ), - # 임시전력(갑)(원문 6.가) — 자체 요율 없이 주택용전력 요금을 그대로 적용 - ( - "임시전력갑:기본요금적용", - "임시전력(갑)", - "기본요금", - "-", - None, - "주택용 저압 요금 그대로 적용(원문 6.가.(1))", - ), - ( - "임시전력갑:전력량요금적용", - "임시전력(갑)", "전력량요금", - "-", - None, - "주택용 저압 요금 그대로 적용(원문 6.가.(2))", + "원/kWh", + 114.4, + "계절 평균: 여름 132.4 · 봄가을 91.9 · 겨울 119.0", ), - ("임시전력갑:월간최저요금", "임시전력(갑)", "월간 최저요금", "원/월", 1000, None), ] @@ -229,6 +149,15 @@ def main() -> None: book = mk.load_book() file = OUT.name + new_numbers = {r["원문번호"] for r in rows} + retired = book.setdefault("폐기", {}) + for key in [ + k + for k, v in book["키"].items() + if k[:2] == "MO" and v.get("파일") == file and v["원문번호"] not in new_numbers + ]: + retired[key] = book["키"].pop(key) + out_rows = [] for r in rows: key = mk.issue(book, "MO", r["원문번호"], file) @@ -287,7 +216,7 @@ def main() -> None: "규격 = 기준일(유류: 수집일 · 전력: 시행일) · 구분 = 유류/전력 · 상세구분 = 유류(지역 이름)·전력(계약종별)", "휘발유·경유는 옛 판(전국평균 2026-08-14 · 시도별 2026-09-09) 값 그대로 — 이번엔 실내등유만 새로 받음(로직 계산 결과가 갈리지 않게). 다음에 유류 전체를 갱신할 땐 이 셋을 한 수집일로 맞출 것.", "전력비 계상 기준 조문 없음 — 계약예규 「예정가격작성기준」 §19③1·§34③1, 건설품셈 1-5-2호, 산림품셈 1-7-2호 모두 「전력비는 실비를 계상」만 정하고 특정 계약종별 지정 없음.", - "전력 — 별표1(C) 저압 위주 주요 계약종별(주택용·일반용(갑)Ⅰ·산업용(갑)Ⅰ·임시전력(갑))만 올림. 고압·을종·교육용·농사용·가로등 등 나머지 계약종별과 슈퍼유저요금·하계 특례는 md 원문(전기요금_한국전력/20250101/)만 참조.", + "전력 — 사용자 확정(2026-09-20)으로 두 줄만 남김: 주택용 저압 전력량요금(처음 200kWh까지, 120.0원/kWh) · 일반용(갑)Ⅰ 저압 전력량요금(여름·봄가을·겨울 단순평균, 114.4원/kWh). 기본요금·최저요금·산업용·임시전력 등 나머지 계약종별은 md 원문(전기요금_한국전력/20250101/)만 참조 — 옛 키는 _키대장.json 「폐기」로 옮김.", ], } dump_master(OUT, head, out_rows) diff --git a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json index 49945d3c..7d135584 100644 --- a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json +++ b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json @@ -2328,8 +2328,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -2440,8 +2439,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -2547,8 +2545,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -2662,8 +2659,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -2772,8 +2768,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -8475,8 +8470,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -8784,8 +8778,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" @@ -9990,8 +9983,7 @@ "이름": "전력 계약종별", "고르기": [ "주택용 저압", - "일반용(갑)Ⅰ 저압", - "산업용(갑)Ⅰ 저압" + "일반용(갑)Ⅰ 저압" ], "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "품셈·계약예규에 계약종별 규정 없음(전력비는 실비 계상) · 임시전력(갑)은 주택용 저압 요금 그대로(요금표 6.가)" diff --git a/resources/master_data/재료_유가전력.json b/resources/master_data/재료_유가전력.json index 2bff8c94..4e75baf8 100644 --- a/resources/master_data/재료_유가전력.json +++ b/resources/master_data/재료_유가전력.json @@ -48,7 +48,7 @@ "규격 = 기준일(유류: 수집일 · 전력: 시행일) · 구분 = 유류/전력 · 상세구분 = 유류(지역 이름)·전력(계약종별)", "휘발유·경유는 옛 판(전국평균 2026-08-14 · 시도별 2026-09-09) 값 그대로 — 이번엔 실내등유만 새로 받음(로직 계산 결과가 갈리지 않게). 다음에 유류 전체를 갱신할 땐 이 셋을 한 수집일로 맞출 것.", "전력비 계상 기준 조문 없음 — 계약예규 「예정가격작성기준」 §19③1·§34③1, 건설품셈 1-5-2호, 산림품셈 1-7-2호 모두 「전력비는 실비를 계상」만 정하고 특정 계약종별 지정 없음.", - "전력 — 별표1(C) 저압 위주 주요 계약종별(주택용·일반용(갑)Ⅰ·산업용(갑)Ⅰ·임시전력(갑))만 올림. 고압·을종·교육용·농사용·가로등 등 나머지 계약종별과 슈퍼유저요금·하계 특례는 md 원문(전기요금_한국전력/20250101/)만 참조." + "전력 — 사용자 확정(2026-09-20)으로 두 줄만 남김: 주택용 저압 전력량요금(처음 200kWh까지, 120.0원/kWh) · 일반용(갑)Ⅰ 저압 전력량요금(여름·봄가을·겨울 단순평균, 114.4원/kWh). 기본요금·최저요금·산업용·임시전력 등 나머지 계약종별은 md 원문(전기요금_한국전력/20250101/)만 참조 — 옛 키는 _키대장.json 「폐기」로 옮김." ], "줄": [ { "키": "MO000001", "원문번호": "휘발유:전국평균", "구분": "유류", "상세구분": "전국평균", "이름": "휘발유", "규격": "2026-08-14", "단위": "원/L", "값": 1863.25, "출처": "유가전력 오피넷유가 B027", "비고": null }, @@ -105,23 +105,7 @@ { "키": "MO000052", "원문번호": "실내등유:울산", "구분": "유류", "상세구분": "울산", "이름": "실내등유", "규격": "2026-09-20", "단위": "원/L", "값": 1594.43, "출처": "유가전력 오피넷유가 C004", "비고": null }, { "키": "MO000053", "원문번호": "실내등유:세종", "구분": "유류", "상세구분": "세종", "이름": "실내등유", "규격": "2026-09-20", "단위": "원/L", "값": 1536.15, "출처": "유가전력 오피넷유가 C004", "비고": null }, { "키": "MO000054", "원문번호": "실내등유:전남광주", "구분": "유류", "상세구분": "전남광주", "이름": "실내등유", "규격": "2026-09-20", "단위": "원/L", "값": 1563.45, "출처": "유가전력 오피넷유가 C004", "비고": null }, - { "키": "MO000055", "원문번호": "주택용저압:기본_200이하", "구분": "전력", "상세구분": "주택용 저압", "이름": "기본요금(200kWh 이하)", "규격": "2025-01-01", "단위": "원/호", "값": 910, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000056", "원문번호": "주택용저압:기본_201~400", "구분": "전력", "상세구분": "주택용 저압", "이름": "기본요금(201~400kWh)", "규격": "2025-01-01", "단위": "원/호", "값": 1600, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000057", "원문번호": "주택용저압:기본_400초과", "구분": "전력", "상세구분": "주택용 저압", "이름": "기본요금(400kWh 초과)", "규격": "2025-01-01", "단위": "원/호", "값": 7300, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000058", "원문번호": "주택용저압:전력량_처음200", "구분": "전력", "상세구분": "주택용 저압", "이름": "전력량요금(처음 200kWh까지)", "규격": "2025-01-01", "단위": "원/kWh", "값": 120.0, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000059", "원문번호": "주택용저압:전력량_다음200", "구분": "전력", "상세구분": "주택용 저압", "이름": "전력량요금(다음 200kWh까지)", "규격": "2025-01-01", "단위": "원/kWh", "값": 214.6, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000060", "원문번호": "주택용저압:전력량_400초과", "구분": "전력", "상세구분": "주택용 저압", "이름": "전력량요금(400kWh 초과)", "규격": "2025-01-01", "단위": "원/kWh", "값": 307.3, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000061", "원문번호": "주택용저압:월간최저요금", "구분": "전력", "상세구분": "주택용 저압", "이름": "월간 최저요금", "규격": "2025-01-01", "단위": "원/월", "값": 1000, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000062", "원문번호": "일반용갑I저압:기본", "구분": "전력", "상세구분": "일반용(갑)Ⅰ 저압", "이름": "기본요금", "규격": "2025-01-01", "단위": "원/kW", "값": 6160, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000063", "원문번호": "일반용갑I저압:전력량_여름철", "구분": "전력", "상세구분": "일반용(갑)Ⅰ 저압", "이름": "전력량요금(여름철)", "규격": "2025-01-01", "단위": "원/kWh", "값": 132.4, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000064", "원문번호": "일반용갑I저압:전력량_봄가을철", "구분": "전력", "상세구분": "일반용(갑)Ⅰ 저압", "이름": "전력량요금(봄·가을철)", "규격": "2025-01-01", "단위": "원/kWh", "값": 91.9, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000065", "원문번호": "일반용갑I저압:전력량_겨울철", "구분": "전력", "상세구분": "일반용(갑)Ⅰ 저압", "이름": "전력량요금(겨울철)", "규격": "2025-01-01", "단위": "원/kWh", "값": 119.0, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000066", "원문번호": "산업용갑I저압:기본", "구분": "전력", "상세구분": "산업용(갑)Ⅰ 저압", "이름": "기본요금", "규격": "2025-01-01", "단위": "원/kW", "값": 5550, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000067", "원문번호": "산업용갑I저압:전력량_여름철", "구분": "전력", "상세구분": "산업용(갑)Ⅰ 저압", "이름": "전력량요금(여름철)", "규격": "2025-01-01", "단위": "원/kWh", "값": 107.7, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000068", "원문번호": "산업용갑I저압:전력량_봄가을철", "구분": "전력", "상세구분": "산업용(갑)Ⅰ 저압", "이름": "전력량요금(봄·가을철)", "규격": "2025-01-01", "단위": "원/kWh", "값": 85.9, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000069", "원문번호": "산업용갑I저압:전력량_겨울철", "구분": "전력", "상세구분": "산업용(갑)Ⅰ 저압", "이름": "전력량요금(겨울철)", "규격": "2025-01-01", "단위": "원/kWh", "값": 106.0, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null }, - { "키": "MO000070", "원문번호": "임시전력갑:기본요금적용", "구분": "전력", "상세구분": "임시전력(갑)", "이름": "기본요금", "규격": "2025-01-01", "단위": "-", "값": null, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "주택용 저압 요금 그대로 적용(원문 6.가.(1))" }, - { "키": "MO000071", "원문번호": "임시전력갑:전력량요금적용", "구분": "전력", "상세구분": "임시전력(갑)", "이름": "전력량요금", "규격": "2025-01-01", "단위": "-", "값": null, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "주택용 저압 요금 그대로 적용(원문 6.가.(2))" }, - { "키": "MO000072", "원문번호": "임시전력갑:월간최저요금", "구분": "전력", "상세구분": "임시전력(갑)", "이름": "월간 최저요금", "규격": "2025-01-01", "단위": "원/월", "값": 1000, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": null } + { "키": "MO000058", "원문번호": "주택용저압:전력량_처음200", "구분": "전력", "상세구분": "주택용 저압", "이름": "전력량요금", "규격": "2025-01-01", "단위": "원/kWh", "값": 120.0, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "처음 200kWh까지" }, + { "키": "MO000073", "원문번호": "일반용갑I저압:전력량_평균", "구분": "전력", "상세구분": "일반용(갑)Ⅰ 저압", "이름": "전력량요금", "규격": "2025-01-01", "단위": "원/kWh", "값": 114.4, "출처": "유가전력 한전 전기요금표 2025-01-01", "비고": "계절 평균: 여름 132.4 · 봄가을 91.9 · 겨울 119.0" } ] }