From 1b7e6a322b4e3510535a443788e1666e74b80e5d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 20 Sep 2026 20:12:34 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(M01):=20=EA=B1=B0=EB=A6=84=20=EC=BB=A8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=84=88=20=ED=8E=BC=EC=B9=98=EB=A9=B4=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=C2=B7=20=EB=8B=AB=EC=95=84=EB=8F=84=20?= =?UTF-8?q?=EA=B1=B0=EB=A6=84=C2=B7=EC=B0=BE=EA=B8=B0=C2=B7=EC=AA=BD=20?= =?UTF-8?q?=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인력·기계·소요량·계수·로직 펼치면 전체(또는 닫기 전 거름)가 바로 열림 - 닫을 때는 화면 그대로 · 컨테이너마다 찾기 글·쪽을 sessionStorage 에 따로 기억 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT --- .../M01_MasterData_UI_Logic_Page.ts | 1 + M01_MasterData/M01_MasterData_UI_Page.ts | 10 +++- M01_MasterData/M01_MasterData_UI_Rows.ts | 5 +- M01_MasterData/M01_MasterData_UI_Side.ts | 59 ++++++++++++++++++- M01_MasterData/M01_MasterData_UI_Tables.ts | 5 +- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Page.ts b/M01_MasterData/M01_MasterData_UI_Logic_Page.ts index a6d98903..92589788 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Page.ts @@ -237,6 +237,7 @@ export async function mountM01Logic(host: HTMLElement, side: SideHandle): Promis total: list.total(), subLabel: L("M01_LaborSub"), detailLabel: L("M01_LaborDetail"), + restore: true, onPick: (sub, detail) => { list.setPick(sub, detail); if (opened) show(null); diff --git a/M01_MasterData/M01_MasterData_UI_Page.ts b/M01_MasterData/M01_MasterData_UI_Page.ts index 08799466..79e47e04 100644 --- a/M01_MasterData/M01_MasterData_UI_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Page.ts @@ -23,7 +23,7 @@ import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { saveFiles } from "./M01_MasterData_Api_Fetch"; import { discard, isStale, onDraftChange, payload, totalCount } from "./M01_MasterData_Draft"; import { renderRows } from "./M01_MasterData_UI_Rows"; -import { buildSide, fileLabel, type Pick } from "./M01_MasterData_UI_Side"; +import { buildSide, fileLabel, loadView, saveView, type Pick } from "./M01_MasterData_UI_Side"; import { renderTables } from "./M01_MasterData_UI_Tables"; import "./M01_MasterData_UI_Style.css"; @@ -85,7 +85,11 @@ function buildPage(): HTMLElement { const openFile = (pick: Pick): void => { dispose(); showMode(false); - current = pick; + if (pick.view) { + query = pick.view.q; + search.value = query; + } else saveView(pick.group, { q: query, page: 1 }); + current = { ...pick, view: undefined }; const groupTitle = pick.group === pick.label ? [pick.group] : [pick.group, pick.label]; title.textContent = groupTitle.join(" · "); const view = TABLE_GROUPS.includes(pick.group) ? renderTables : renderRows; @@ -114,7 +118,7 @@ function buildPage(): HTMLElement { const pick = current; try { const again = (await side.refresh(pick.group)).find((f) => f.file === pick.file.file); - if (again) openFile({ ...pick, file: again }); + if (again) openFile({ ...pick, file: again, view: loadView(pick.group) }); } catch (error) { showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"); } diff --git a/M01_MasterData/M01_MasterData_UI_Rows.ts b/M01_MasterData/M01_MasterData_UI_Rows.ts index 439f8f64..07f5b564 100644 --- a/M01_MasterData/M01_MasterData_UI_Rows.ts +++ b/M01_MasterData/M01_MasterData_UI_Rows.ts @@ -6,7 +6,7 @@ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; import { openPickModal } from "./M01_MasterData_UI_Pick"; -import type { Pick } from "./M01_MasterData_UI_Side"; +import { saveView, type Pick } from "./M01_MasterData_UI_Side"; import { fetchRows, type Row, type RowsPage } from "./M01_MasterData_Api_Fetch"; import { addRow, @@ -92,7 +92,7 @@ const remember = (ref: string | null, name?: string): void => { export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void { const { sub, detail, state } = pick; const file = pick.file.file; - let page = 1; + let page = pick.view?.page ?? 1; let data: RowsPage | null = null; let unlinked = false; @@ -235,6 +235,7 @@ export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void try { data = await fetchRows(file, page, SIZE, q, unlinked, sub, detail, state); for (const [key, name] of Object.entries(data.refs ?? {})) names.set(key, name); + saveView(pick.group, { page: data.page }); paint(); } catch (error) { showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"); diff --git a/M01_MasterData/M01_MasterData_UI_Side.ts b/M01_MasterData/M01_MasterData_UI_Side.ts index ebf58894..e9ec3b65 100644 --- a/M01_MasterData/M01_MasterData_UI_Side.ts +++ b/M01_MasterData/M01_MasterData_UI_Side.ts @@ -11,6 +11,30 @@ import { t as L } from "@ui/ui_template_locale"; import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch"; import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree"; +/** 컨테이너마다 기억하는 화면 상태 — 찾기 글 · 쪽 (거름은 `m01.filter.*`) */ +export interface View { + q: string; + page: number; +} + +const viewKey = (group: string): string => `m01.view.${group}`; + +export function loadView(group: string): View { + try { + return { q: "", page: 1, ...JSON.parse(sessionStorage.getItem(viewKey(group)) ?? "{}") }; + } catch { + return { q: "", page: 1 }; + } +} + +export function saveView(group: string, patch: Partial): void { + try { + sessionStorage.setItem(viewKey(group), JSON.stringify({ ...loadView(group), ...patch })); + } catch { + /* 기억 못 해도 화면은 됨 */ + } +} + /** 요소 화면에서 열 것 — 파일 · 하위 거름(구분·세부분류) · 상세구분 · 제목 */ export interface Pick { group: string; @@ -20,6 +44,8 @@ export interface Pick { /** 인력 상태 거름 · 비면 전체 */ state?: string; label: string; + /** 다시 펼칠 때만 — 닫기 전 찾기 글 · 쪽 */ + view?: View; } export interface SideHandle { @@ -49,6 +75,8 @@ export interface FilterArgs { states?: string[]; stateLabel?: string; onPick: (sub: string, detail: string, label: string, state: string) => void; + /** 만들자마자 고른 거름으로 한 번 부름 */ + restore?: boolean; } const MATERIAL_NAMES: Record = { 유가전력: "유가·전력" }; @@ -73,6 +101,9 @@ const GROUP_TITLE = { type Group = keyof typeof GROUP_TITLE; const GROUPS = Object.keys(GROUP_TITLE) as Group[]; const LEAF: Group[] = ["환율", "요율"]; +/** 거름이 있는 컨테이너 — 펼치면 「전체」(또는 닫기 전 거름) 이 바로 열림 */ +const FILTERED: Group[] = ["인력", "기계", "소요량", "계수"]; +const LOGIC_ID = "로직|"; export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): SideHandle { const lists = new Map(); @@ -81,6 +112,8 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si // 하위 거름은 서버가 준 목록(파일 머리에 등록된 구분·세부분류) — 새 조사가 늘어도 화면은 그대로 const kinds = new Map(); const states = new Map(); + const runs = new Map void>(); + let restoring = false; const setActive = (id: string | null): void => { for (const [key, button] of buttons) button.classList.toggle("is-active", key === id); @@ -169,6 +202,7 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si s, ); }; + runs.set(a.id, run); sub.select.addEventListener("change", () => { if (detail) { const details = a.subs.find((k) => k.name === sub.select.value)?.details ?? []; @@ -189,6 +223,7 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si sub.select.dispatchEvent(new Event("change")); }, ); + if (a.restore) run(); return el("div", { children: [ all, @@ -210,7 +245,15 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si const body = bodies.get(group); if (!body) return; const open = (file: FileInfo, sub: string, label: string, detail = "", state = ""): void => - onOpen({ group, file, sub, detail, state, label }); + onOpen({ + group, + file, + sub, + detail, + state, + label, + view: restoring ? loadView(group) : undefined, + }); const one = files[0]; const dropped: Partial> = { 인력: { label: L("M01_LaborSub"), detail: L("M01_LaborDetail") }, @@ -310,13 +353,25 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si onOpen({ group, file: one, sub: "", detail: "", label: L(GROUP_TITLE[group]) }); }); } + if (FILTERED.includes(group)) { + root.querySelector(".ui-collapsible__title")?.addEventListener("click", () => { + if (!root.classList.contains("is-collapsed")) return; // 닫을 때는 그대로 둠 + restoring = true; + try { + runs.get(`${group}|`)?.(); + } finally { + restoring = false; + } + }); + } return root; }); const logicHost = el("div", { className: "m01-side__items" }); const logic = section(L("M01_GroupLogic"), logicHost); logic.querySelector(".ui-collapsible__title")?.addEventListener("click", () => { - setActive(null); + if (!logic.classList.contains("is-collapsed")) return; // 닫을 때는 그대로 둠 + setActive(buttons.has(LOGIC_ID) ? LOGIC_ID : null); onLogic(); }); diff --git a/M01_MasterData/M01_MasterData_UI_Tables.ts b/M01_MasterData/M01_MasterData_UI_Tables.ts index 2e675a0a..394899a4 100644 --- a/M01_MasterData/M01_MasterData_UI_Tables.ts +++ b/M01_MasterData/M01_MasterData_UI_Tables.ts @@ -7,7 +7,7 @@ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; import { fetchTable, fetchTables, type Row, type TableHead } from "./M01_MasterData_Api_Fetch"; -import type { Pick } from "./M01_MasterData_UI_Side"; +import { saveView, type Pick } from "./M01_MasterData_UI_Side"; import { onDraftChange, peek, @@ -31,7 +31,7 @@ const LIST_COLS = ["키", "원문번호", "구분", "상세구분", "이름", " /** 돌려받은 함수 = 이 화면을 걷을 때 부를 해제. */ export function renderTables(host: HTMLElement, pick: Pick, q: string): () => void { let stop = (): void => {}; - const list = (page = 1): void => { + const list = (page = pick.view?.page ?? 1): void => { stop(); stop = (): void => {}; void showList(host, pick, q, page, (file, key) => { @@ -68,6 +68,7 @@ async function showList( showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"); return; } + saveView(pick.group, { page }); const rows = got.tables.map((t) => { const tr = el("tr", { className: "m01-master__table-row", 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" } ] }