Merge remote-tracking branch 'origin/sub_laptop_3' into main_desktop_1

This commit is contained in:
2026-09-19 19:10:18 +09:00
37 changed files with 109 additions and 84 deletions
@@ -9,7 +9,7 @@
* ========================================================================== */
import { FORD_MANNING_N, FORD_SLOPE } from "@config/config_frontend";
import revetmentSabang from "../resources/data_masonry/revetment_sabang_2026-01-01.json";
import revetmentSabang from "../resources/master_data/ref/수량_사방기슭막이치수_사방기술교본_2026-01-01.json";
/* ── 유입구 구조 ──────────────────────────────────────────────────────────── */
+2 -2
View File
@@ -1,6 +1,6 @@
/* =============================================================================
* B06_Section_UI_Cross_Lean.ts
* 횡단도 벽 전면 기울기 — 품셈 13-4-4 [주]⑪ 표준경사 표(`resources/data_masonry/masonry_slope_*`)를
* 횡단도 벽 전면 기울기 — 품셈 13-4-4 [주]⑪ 표준경사 표(`resources/master_data/old/3_품셈_산림_돌쌓기경사_*`)를
* 읽는 자리 하나(2026-09-14 B5 · B08 과 한 벌). 표 읽기 식은 `common_util_masonry_slope.ts`(파이썬 짝).
*
* 따로 뗀 까닭 — 표(JSON)를 가져오는 모듈을 상수 파일에 두면 그 상수를 쓰는 기하 조각을 Node 로
@@ -10,7 +10,7 @@
* ========================================================================== */
import { type WallLeanInput, wallLeanRatio } from "../common_util/common_util_masonry_slope";
import masonrySlope from "../resources/data_masonry/masonry_slope_2026-01-01.json";
import masonrySlope from "../resources/master_data/old/3_품셈_산림_돌쌓기경사_13-4-4_2026-01-01.json";
/** 벽 전면 기울기 n — 표를 못 고르면 종전 0.3. */
export function leanOf(input: WallLeanInput): number {
@@ -56,8 +56,8 @@ from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Table import ( # noqa: F401
)
ROOT = Path(__file__).resolve().parent.parent
SOURCE = ROOT / "resources" / "data_cost_input_value" / "pum_forest_2026.json"
OUT_DIR = ROOT / "resources" / "data_work_item_master"
SOURCE = ROOT / "resources" / "master_data" / "old" / "3_품셈_산림_표전체_2026-01-01.json"
OUT_DIR = ROOT / "resources" / "master_data" / "old"
#: 불변 열쇠 장부 — **판이 붙지 않는다.** 판마다 새로 짓는 산출물이 아니라 대대로 잇는 장부다.
KEY_REGISTRY = OUT_DIR / "work_item_keys.json"
@@ -25,9 +25,9 @@ from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parent.parent
SOURCE = ROOT / "resources" / "data_cost_input_value" / "pum_const_2026.json"
SOURCE = ROOT / "resources" / "master_data" / "old" / "3_품셈_건설_표전체_2026-01-01.json"
#: 건설 열쇠 장부 — 산림 장부(`work_item_keys.json`)와 일련번호를 안 섞음
CONST_KEY_REGISTRY = ROOT / "resources" / "data_work_item_master" / "work_item_keys_const.json"
CONST_KEY_REGISTRY = ROOT / "resources" / "master_data" / "old" / "work_item_keys_const.json"
KEY_PREFIX = "CW" # Construction Work item
BOOK_NAME = "2026년_건설공사_표준품셈.md"
CODE_PREFIX = "CP" # Construction Pumsem — 목차 코드
@@ -33,7 +33,8 @@ PATH_SEP = " "
AXIS_POLICY = (
Path(__file__).resolve().parent.parent
/ "resources"
/ "data_work_item_master"
/ "master_data"
/ "old"
/ "axis_policy.json"
)
@@ -20,8 +20,8 @@ import json
from pathlib import Path
from typing import Any, Iterable
MASTER_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_work_item_master"
MASTER_PREFIX = "work_item_master_"
MASTER_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "old"
MASTER_PREFIX = "2_공종_산림_마스터_"
#: 같은 뜻인데 표기만 다른 단위 — **환산이 아니라 표기 흡수**다.
UNIT_ALIASES = {
+2 -2
View File
@@ -23,8 +23,8 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_formwork"
DATASET_PREFIX = "formwork_reuse_"
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "old"
DATASET_PREFIX = "3_품셈_산림_거푸집사용횟수_"
#: 거푸집으로 보는 성분 이름. 정확히 같은 이름으로만 본다 — 부분일치면 「거푸집씻기」가 걸린다.
FORMWORK_NAMES = frozenset({"합판거푸집", "유로폼", "문양거푸집", "거푸집"})
@@ -15,22 +15,22 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_work_item_mapping"
DATASET_PREFIX = "work_item_mapping_"
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "ref"
DATASET_PREFIX = "공종_수량연결_실무_"
#: 철근 갈래표 — **품셈 12-3 [주]① 원문**이 구조물 예시로 갈라 둔 것이라 사람이 고르는 값이
#: 아니다(거푸집 사용횟수 1-7-1 과 같은 자리). 원문 예시에 안 걸리면 지어내지 않는다.
REBAR_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_rebar"
REBAR_PREFIX = "rebar_complexity_"
REBAR_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "old"
REBAR_PREFIX = "3_품셈_산림_철근가공갈래_"
#: 돌쌓기 규격 갈래표 — 저장 제원 값으로 자동 판정한다(사람이 고르는 값이 아니다).
MASONRY_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_masonry"
MASONRY_PREFIX = "masonry_class_"
MASONRY_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "old"
MASONRY_PREFIX = "3_품셈_산림_돌쌓기규격갈래_"
#: 목재공작물 구조 갈래표 — 품셈 13-13-1 [주]③ 이 **재료 구성**으로 가른다.
TIMBER_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_timber"
TIMBER_PREFIX = "timber_structure_class_"
TIMBER_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "old"
TIMBER_PREFIX = "3_품셈_산림_목재구조갈래_"
def load_timber_table(path: Path | None = None) -> dict[str, Any]:
@@ -53,8 +53,8 @@ from typing import Any, Iterable
from common_util.common_util_quantity_spread import spread_by_unit
# ── 데이터 자리 ──────────────────────────────────────────────────────
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_material_surcharge"
DATASET_PREFIX = "material_surcharge_"
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "ref"
DATASET_PREFIX = "품셈_산림_재료할증_울진_"
#: 이 표가 받는 성분 갈래. 나머지는 각자 다른 표로 간다.
ACCEPTED_DESTINATION = "material"
@@ -32,8 +32,8 @@ from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_structure_unit"
DATASET_PREFIX = "structure_unit_observed_"
DATASET_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "ref"
DATASET_PREFIX = "수량_구조물원단위_울진_"
#: 값이 어디서 왔나 — 한 표에 섞이므로 줄마다 단다.
BASIS_DERIVED = "derived" # 저장된 치수에서 식으로
+2 -2
View File
@@ -222,8 +222,8 @@ NOTE_WALL_HEIGHT_PIPE = " · 높이는 횡단도와 같은 관경 기준 최소
#: 독립 기슭막이 치수 **한 벌**(`resources/data_masonry/revetment_sabang_*.json`) — B05 칸 밑 근거와
#: 이 사유가 같은 파일을 읽음(2026-09-14 브레인 판정). 높이는 **설계자 입력** — 등록부 기본 2.5 걷음.
REVET_SABANG_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_masonry"
REVET_SABANG_PREFIX = "revetment_sabang_"
REVET_SABANG_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "ref"
REVET_SABANG_PREFIX = "수량_사방기슭막이치수_사방기술교본_"
UNCONFIRMED_OWN_HEIGHT = "높이를 안 적음 — {rule}({source}) 설계자 입력 · 적으면 금액이 섬"
@@ -131,8 +131,8 @@ STONE_PILE_BASIS = "⚠ 정본에 없는 줄 — 소광리 시트에만 있음(
#: ⚠ 지금까지 **건설품셈 참고자료 한 벌**(돌 종류로 안 갈리는 표)로만 돌고 있었다.
#: 그 값이 「깬돌」 계열이라, **자재로는 야면석을 내면서 계수는 깬돌**을 쓰는 어긋남이
#: 있었다(2026-09-08 지식DB 대조). 랩탑 창이 `stone_kind` 칸을 만들어 축이 생겼다.
STONE_KIND_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_masonry"
STONE_KIND_PREFIX = "stone_kind_"
STONE_KIND_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "ref"
STONE_KIND_PREFIX = "품셈_산림_돌종류계수_울진_"
STONE_KIND_OPTION = "stone_kind"
@@ -336,8 +336,8 @@ BLINDING_OPTION_KEYS = ("blinding_concrete", "base_blinding")
#: 표준경사표 — 품셈 13-4-4 [주]⑪. 파일이 없으면 종전 기본값(0.3)으로 돈다.
SLOPE_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_masonry"
SLOPE_PREFIX = "masonry_slope_"
SLOPE_DIR = Path(__file__).resolve().parents[1] / "resources" / "master_data" / "old"
SLOPE_PREFIX = "3_품셈_산림_돌쌓기경사_"
#: 사용자가 덮어쓰는 칸 — 값이 있으면 **그 값이 이긴다**(확정 ⑨).
FACE_SLOPE_KEYS = ("face_slope_ratio",)
#: 성토/절토는 **측점 단면유형(`design.section_mode`) + 설치 측**으로 판정한다
@@ -469,7 +469,7 @@ def dry_masonry_height_note(height_m: float) -> str | None:
#: 뒷길이 표준표 — 품셈 13-4-4 [주]⑩. 안 고른 돌쌓기는 붙박이 45 가 아니라 이 표의 하한
#: (2026-09-14 브레인 판정 ㉮ 「범위 안에 드는 규격 중 가장 작은 것」). 파일이 없으면 종전 45.
BACK_LENGTH_PREFIX = "masonry_back_length_"
BACK_LENGTH_PREFIX = "3_품셈_산림_돌쌓기뒷길이_"
@lru_cache(maxsize=1)
+2 -2
View File
@@ -24,8 +24,8 @@ from typing import Any
from B09_Estimation.B09_Estimation_PriceBook import PriceKind
from B09_Estimation.B09_Estimation_ResourceAxis_UnitBasis import similar_occupation_note
_MANIFEST = ("resources", "data_cost_input_value", "_manifest.json")
_MASTER_DIR = ("resources", "data_work_item_master")
_MANIFEST = ("resources", "master_data", "old", "_manifest.json")
_MASTER_DIR = ("resources", "master_data", "old")
SHEET_NOTE = (
"산출기초는 새로 세는 장이 아니라 **줄에 달린 근거를 한 장으로 모은 것**입니다 —"
@@ -131,6 +131,6 @@ class CostInput:
enabled_items: tuple[str, ...] | str = DEFAULT_ITEMS
#: 요율 데이터 파일명. **연도를 갈아끼우는 자리.**
rate_file_name: str = "rates_2026.json"
rate_file_name: str = "1_기초_제비율_토목_조달청_2026-04-13.json"
#: 매니페스트 밖 요율 파일(옛 연도 재현 검산 전용). 주면 이쪽이 우선.
rate_file_path: str | None = None
@@ -32,7 +32,7 @@ from decimal import Decimal
from functools import lru_cache
from typing import Any
_DATASET = ("resources", "data_cost_input_value", "coef_2026.json")
_DATASET = ("resources", "master_data", "old", "3_품셈_산림_계수_2026-01-01.json")
_FOREST_SPEC = (
"resources",
"knowledge",
+4 -2
View File
@@ -34,7 +34,7 @@ from B09_Estimation.B09_Estimation_Guards import (
)
from B09_Estimation.B09_Estimation_PriceBook import Money3
_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
_CATALOG_SUBPATH = ("resources", "master_data", "old")
_THOUSAND = Decimal(1000)
@@ -224,7 +224,9 @@ class MachineCatalog:
raise MachineCostError(f"기종 카탈로그에 없는 코드입니다: {machine_code}") from exc
def load_machine_catalog(file_name: str = "mach_base_2026.json") -> MachineCatalog:
def load_machine_catalog(
file_name: str = "3_품셈_건설_기계경비기준_8장_2026-01-01.json",
) -> MachineCatalog:
"""취득가 613건에 손료계수 387건을 붙여 카탈로그 한 벌을 만든다."""
variables = _read_json(file_name)["variables"]
coefficients = {
@@ -33,7 +33,7 @@ from B09_Estimation.B09_Estimation_PriceBook import PriceKind
#: 조합 사용(굴착기+부착장비)일 때의 본체 잡재료비율 — 품셈 제8장 [주]⑤.
COMBINED_MISC_PERCENT = 16
_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
_CATALOG_SUBPATH = ("resources", "master_data", "old")
_THOUSAND = Decimal(1000)
_ZERO = Decimal(0)
@@ -66,7 +66,9 @@ def _project_root() -> str:
@lru_cache(maxsize=1)
def _loss_records() -> dict[str, dict[str, Any]]:
"""기종코드 → 손료계수 원문 줄(상각·정비·관리 계수까지)."""
path = os.path.join(_project_root(), *_CATALOG_SUBPATH, "mach_base_2026.json")
path = os.path.join(
_project_root(), *_CATALOG_SUBPATH, "3_품셈_건설_기계경비기준_8장_2026-01-01.json"
)
with open(path, encoding="utf-8") as handle:
payload = json.load(handle)
records = payload["variables"]["mach_loss_coef"]["records"]
@@ -27,8 +27,8 @@ from dataclasses import dataclass, field
from decimal import Decimal
from typing import Any
_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
_OUTPUT_SUBPATH = ("resources", "data_cost_machine_operating")
_CATALOG_SUBPATH = ("resources", "master_data", "old")
_OUTPUT_SUBPATH = ("resources", "master_data", "old")
#: 품셈 8-4 운전경비 표의 머리글 — 이 여섯이 다 있어야 그 표로 본다.
_REQUIRED_HEADERS = ("분류번호", "기계명", "규격", "주연료", "잡재료", "조종원")
@@ -359,8 +359,8 @@ def enrich_with_catalog(
def load_operating_records(
pum_file: str = "pum_const_2026.json",
labor_file: str = "labor_const_2026-01-01.json",
pum_file: str = "3_품셈_건설_표전체_2026-01-01.json",
labor_file: str = "1_기초_노임_건설업_대한건설협회_2026-01-01.json",
) -> OperatingParseResult:
from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
@@ -394,7 +394,7 @@ def write_operating_records(
"""파생 파일로 낸다. **어느 기준자료 판에서 파생됐는지**를 함께 적는다."""
directory = output_dir or os.path.join(_project_root(), *_OUTPUT_SUBPATH)
os.makedirs(directory, exist_ok=True)
path = os.path.join(directory, "machine_operating_2026.json")
path = os.path.join(directory, "3_품셈_건설_기계운전경비_8-4_2026-01-01.json")
payload = {
"schema_version": "1.0",
@@ -419,7 +419,7 @@ def write_operating_records(
#: 시도별 유가 판 — 품셈 8-1-7 5호 「유류가격은 **해당지역의 가격**으로 한다」.
#: ⚠ **파일이 있을 때만 지역을 고를 수 있다** — 없으면 전국평균 한 벌로 돈다(코드로 막지 않음).
REGIONAL_OIL_FILE = "oil_regional_2026-09-09.json"
REGIONAL_OIL_FILE = "1_기초_유가_시도별_오피넷_2026-09-09.json"
#: 운전경비표 연료 종류 → 유가 판 변수. ⚠ 종류를 안 읽고 경유로 때우면 휘발유 기계가 싸게 섬
#: (2026-09-14 661 뒤 ② — 플레이트 콤팩터·진동기·믹서·래머·커터가 경유값이었음).
FUEL_VARIABLES = {"경유": "oil_diesel", "휘발유": "oil_gasoline"}
@@ -455,7 +455,9 @@ def load_regional_fuel_table(
def load_fuel_price(
oil_file: str = "oil_2026-08-14.json", region: str | None = None, kind: str = "경유"
oil_file: str = "1_기초_유가_전국평균_오피넷_2026-08-14.json",
region: str | None = None,
kind: str = "경유",
) -> tuple[Decimal, dict[str, str]]:
"""경유 단가와 그 판의 신원. `region`(시도코드)을 주면 **그 지역 값**으로 선다.
@@ -490,7 +492,9 @@ def load_fuel_price(
}
def load_operator_wages(labor_file: str = "labor_const_2026-01-01.json") -> dict[str, Decimal]:
def load_operator_wages(
labor_file: str = "1_기초_노임_건설업_대한건설협회_2026-01-01.json",
) -> dict[str, Decimal]:
"""직종코드 → 일당."""
records = _read_json(*_CATALOG_SUBPATH, labor_file)["variables"]["labor_rate"]["records"]
return {
@@ -513,7 +517,9 @@ LABOR_RELIABILITY_LABEL = {
}
def load_labor_reliability(labor_file: str = "labor_const_2026-01-01.json") -> dict[str, str]:
def load_labor_reliability(
labor_file: str = "1_기초_노임_건설업_대한건설협회_2026-01-01.json",
) -> dict[str, str]:
"""직종코드 → 신뢰도 기호(`*`·`**`). 정상 공표 직종은 아예 안 담는다."""
records = _read_json(*_CATALOG_SUBPATH, labor_file)["variables"]["labor_rate"]["records"]
return {
@@ -17,8 +17,8 @@ from functools import lru_cache
from pathlib import Path
from typing import Any
DATA_DIR = Path(__file__).resolve().parent.parent / "resources" / "data_machine_productivity"
PREFIX = "machine_productivity_"
DATA_DIR = Path(__file__).resolve().parent.parent / "resources" / "master_data" / "old"
PREFIX = "3_품셈_산림건설_기계작업량_"
class MachineProductivityDataError(FileNotFoundError):
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field
from decimal import Decimal
from typing import Any
_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
_CATALOG_SUBPATH = ("resources", "master_data", "old")
#: 두 창이 맞춘 구분 이름 — 값을 바꾸면 B08 자재총괄과 안 맞는다.
SUPPLY_OWNER = "owner_supplied"
@@ -108,7 +108,7 @@ class MaterialCatalog:
def load_material_catalog(
public_file: str = "mat_price_public_2026-08-14.json",
public_file: str = "1_기초_자재_시설공통_나라장터_2026-08-14.json",
) -> MaterialCatalog:
"""관급 자재를 읽고, 사급은 **없다는 사실을 목록으로** 남긴다."""
payload = _read_json(public_file)
@@ -119,7 +119,7 @@ def material_catalog_rows() -> dict[str, dict[str, str]]:
from B09_Estimation.B09_Estimation_ResourceAxis_Join import EXT_CATALOG_FILE
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
path = os.path.join(root, "resources", "data_resource_catalog", EXT_CATALOG_FILE)
path = os.path.join(root, "resources", "master_data", "old", EXT_CATALOG_FILE)
if not os.path.isfile(path):
return {}
with open(path, encoding="utf-8") as handle:
+2 -2
View File
@@ -27,7 +27,7 @@ _UNIT_MULTIPLIER: dict[str, int] = {
"billion": 1_000_000_000,
}
_RESOURCE_SUBPATH = ("resources", "data_cost_input_value")
_RESOURCE_SUBPATH = ("resources", "master_data", "old")
# 라벨 문법 — 숫자 구간만 해석한다. 그 밖(`turnkey_or_alternative` 등)은 명시 선택자로 고른다.
_RE_LT = re.compile(r"^lt_(\d+(?:\.\d+)?)_(million|billion)$")
@@ -89,7 +89,7 @@ def _manifest_entry(file_name: str) -> dict[str, Any]:
@lru_cache(maxsize=8)
def load_rate_dataset(file_name: str = "rates_2026.json") -> RateDataset:
def load_rate_dataset(file_name: str = "1_기초_제비율_토목_조달청_2026-04-13.json") -> RateDataset:
"""요율 파일 한 벌을 읽는다. 매니페스트의 지문·적용일을 함께 실어 재현성을 남긴다."""
entry = _manifest_entry(file_name)
with open(os.path.join(_dataset_dir(), file_name), encoding="utf-8") as handle:
@@ -44,8 +44,8 @@ NON_WORK_ITEM_FORMS = frozenset({"coefficient", "reference"})
#: 형태 판정이 안 된 표 — 값을 쓰지 않는다.
UNUSABLE_FORMS = frozenset({"undetermined"})
_MASTER_SUBPATH = ("resources", "data_work_item_master")
_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
_MASTER_SUBPATH = ("resources", "master_data", "old")
_CATALOG_SUBPATH = ("resources", "master_data", "old")
#: 규격이 이름 안에 붙어 있는 흔한 모양 — 「굴착기(0.7㎥)」·「덤프트럭 15톤」.
_RE_SPEC = re.compile(r"[(]([^)]+)[)]|(\d+(?:\.\d+)?\s*(?:톤|ton|㎥|m3|㎡|㎜|mm|HP|kW))")
@@ -22,7 +22,7 @@ from B09_Estimation.B09_Estimation_ResourceAxis import (
#: 자원 축 산출물이 나가는 자리 — **메인의 `data_work_item_master/` 안에 넣지 않는다.**
#: 메인이 품셈을 다시 돌리면 그 폴더가 덮이므로 섞으면 사라진다.
OUTPUT_SUBPATH = ("resources", "data_cost_resource_axis")
OUTPUT_SUBPATH = ("resources", "master_data", "old")
def _master_file_fingerprint(master: dict[str, Any]) -> dict[str, str]:
@@ -36,8 +36,8 @@ from B09_Estimation.B09_Estimation_ResourceAxis_Sources import (
)
from common_util.common_util_aliases import AliasError, in_scope, load_aliases, parse_aliases
_EXT_SUBPATH = ("resources", "data_resource_catalog")
EXT_CATALOG_FILE = "resource_catalog_ext_2026-01-01.json"
_EXT_SUBPATH = ("resources", "master_data", "old")
EXT_CATALOG_FILE = "3_품셈_산림_자원목록보충_2026-01-01.json"
#: 명세 §2 ③ — `AR-<M|L|X>-<8자리 소문자 16진>`. 종류 글자는 PriceKind 글자 그대로.
_RE_AR_CODE = re.compile(r"^AR-([MLX])-[0-9a-f]{8}$")
@@ -29,11 +29,13 @@ from B09_Estimation.B09_Estimation_ResourceAxis import (
_RE_ALTERNATIVE,
)
_CATALOG_SUBPATH = ("resources", "data_cost_input_value")
_MASTER_SUBPATH = ("resources", "data_work_item_master")
_CATALOG_SUBPATH = ("resources", "master_data", "old")
_MASTER_SUBPATH = ("resources", "master_data", "old")
def load_labor_catalog(file_name: str = "labor_const_2026-01-01.json") -> ResourceCatalog:
def load_labor_catalog(
file_name: str = "1_기초_노임_건설업_대한건설협회_2026-01-01.json",
) -> ResourceCatalog:
"""노임 카탈로그 132직종 + `aliases`. 코드는 `occupation_code`."""
payload = _read_json(*_CATALOG_SUBPATH, file_name)
variables = payload["variables"]
@@ -45,7 +47,9 @@ def load_labor_catalog(file_name: str = "labor_const_2026-01-01.json") -> Resour
return ResourceCatalog(entries=entries, aliases=dict(variables.get("aliases", {})))
def load_machine_catalog_entries(file_name: str = "mach_base_2026.json") -> list[CatalogEntry]:
def load_machine_catalog_entries(
file_name: str = "3_품셈_건설_기계경비기준_8장_2026-01-01.json",
) -> list[CatalogEntry]:
"""기종 카탈로그 613건을 매칭용 항목으로 편다.
**규격이 매칭의 일부** 굴착기(무한궤도) 23 규격이라 이름만으로는
@@ -61,7 +65,7 @@ def load_machine_catalog_entries(file_name: str = "mach_base_2026.json") -> list
def load_material_catalog_entries(
file_name: str = "mat_price_public_2026-08-14.json",
file_name: str = "1_기초_자재_시설공통_나라장터_2026-08-14.json",
) -> list[CatalogEntry]:
"""관급 자재 6,999건을 매칭용 항목으로 편다.
@@ -102,7 +106,7 @@ def load_combined_catalog() -> ResourceCatalog:
def load_work_item_master(
file_name: str = "work_item_master_2026-01-01.json",
file_name: str = "2_공종_산림_마스터_2026-01-01.json",
) -> dict[str, Any]:
"""메인 창 산출물 — **읽기 전용**."""
return _read_json(*_MASTER_SUBPATH, file_name)
+1 -1
View File
@@ -38,7 +38,7 @@ def _sources() -> tuple[dict[str, dict[str, Any]], dict[str, int]]:
"""(기계 코드 → 손료 성분 레코드, 규칙 이름 → 암석 가산 %)."""
from B09_Estimation.B09_Estimation_MachineCost import _read_json
variables = _read_json("mach_base_2026.json")["variables"]
variables = _read_json("3_품셈_건설_기계경비기준_8장_2026-01-01.json")["variables"]
records = {r["machine_code"]: r for r in variables["mach_loss_coef"]["records"]}
rules = {r["machine_group"]: int(r["rock_work"]) for r in variables["mach_rock_adj"]["rules"]}
return records, rules
+1 -1
View File
@@ -92,7 +92,7 @@ class CostRequest(BaseModel):
equipment_guarantee_work_type: str = "civil_general"
subcontract_guarantee_variant: str = "integrated_civil_or_industrial"
rate_file_name: str = "rates_2026.json"
rate_file_name: str = "1_기초_제비율_토목_조달청_2026-04-13.json"
#: 공종별 수량 `{공종코드: 수량}`. 주면 **직접비 3분할을 여기서 만들어** 쓴다.
#: ⚠ 일위대가 합계를 뭉쳐 넣지 않는다 — 밑수가 항목마다 갈린다(PLAN 8-9 규칙 2).
+2 -2
View File
@@ -500,7 +500,7 @@ def _add_machine_layers(
@lru_cache(maxsize=1)
def load_basis_missing(
file_name: str = "basis_missing_2026-01-01.json",
file_name: str = "2_공종_산림_밑수없음_2026-01-01.json",
) -> dict[str, str]:
"""B08 이 낸 **밑수 못 찾은 표** 목록 — `{표 번호: 절 이름}`.
@@ -512,7 +512,7 @@ def load_basis_missing(
import os
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
path = os.path.join(root, "resources", "data_work_item_master", file_name)
path = os.path.join(root, "resources", "master_data", "old", file_name)
if not os.path.exists(path):
return {}
with open(path, encoding="utf-8") as handle:
@@ -57,7 +57,9 @@ def operating_record(node: dict[str, Any] | None = None):
values = _table_values(node)
if values is None:
return None
aliases = _read_json(*_CATALOG_SUBPATH, "labor_const_2026-01-01.json")["variables"]["aliases"]
aliases = _read_json(*_CATALOG_SUBPATH, "1_기초_노임_건설업_대한건설협회_2026-01-01.json")[
"variables"
]["aliases"]
return OperatingRecord(
machine_code=MACHINE,
machine_name="이동식 임목파쇄기",
@@ -23,7 +23,11 @@ from B09_Estimation.B09_Estimation_MachineOperating import FUEL_VARIABLES, load_
from Z01_MasterData.Z01_MasterData_Tables import RESOURCES
#: B09 셈이 여는 원본 셋 — 바뀌면 캐시를 새로 읽음
_INPUT_FILES = ("mach_base_2026.json", "pum_const_2026.json", "labor_const_2026-01-01.json")
_INPUT_FILES = (
"3_품셈_건설_기계경비기준_8장_2026-01-01.json",
"3_품셈_건설_표전체_2026-01-01.json",
"1_기초_노임_건설업_대한건설협회_2026-01-01.json",
)
EDITABLE = ["price_thousand_krw", "loss_coefficient_per_hour"]
COMPUTED = ("hourly_loss_krw", "hourly_fuel_krw", "hourly_operator_krw", "hourly_total_krw")
#: 조종원 식 글자 — B09 식 문자열(좌→우 차례)을 사람이 읽게만 바꿈
@@ -90,7 +94,7 @@ def _cached(_mtimes: tuple[int, ...]):
def _inputs():
folder = RESOURCES / "data_cost_input_value"
folder = RESOURCES / "master_data" / "old"
return _cached(tuple((folder / name).stat().st_mtime_ns for name in _INPUT_FILES))
+2 -2
View File
@@ -19,7 +19,7 @@ from typing import Any
ROOT = Path(__file__).resolve().parent.parent
RESOURCES = ROOT / "resources"
LABEL_DIR = RESOURCES / "data_master_labels"
LABEL_DIR = RESOURCES / "master_data" / "old"
DEFAULT_PAGE_SIZE = 50
MAX_PAGE_SIZE = 500
ROW_KEY = "@key"
@@ -132,7 +132,7 @@ def _labels_at(path: str, _mtime_ns: int) -> dict[str, Any]:
def load_labels() -> dict[str, Any]:
"""이름표 끝 판(이름 차례) — 없으면 빈 이름표(파일도 안 보임)."""
found = sorted(LABEL_DIR.glob("labels_*.json")) if LABEL_DIR.is_dir() else []
found = sorted(LABEL_DIR.glob("4_화면_이름표_*.json")) if LABEL_DIR.is_dir() else []
if not found:
return {"kinds": {}, "files": [], "columns": {}, "column_overrides": {}}
return _labels_at(str(found[-1]), found[-1].stat().st_mtime_ns)
+3 -3
View File
@@ -24,7 +24,7 @@ SOURCE_OF = {
"work_item_forest": "pum_forest",
"work_item_const": "pum_const",
} # 이름표 `work_item_axis` 와 같은 이름
FOLDER = tables.RESOURCES / "data_work_item_master"
FOLDER = tables.RESOURCES / "master_data" / "old"
#: 불변 열쇠 칸 — ⚠ 데스크탑 서브 자료가 오면 칸 이름을 맞출 것(여기 한 곳)
KEY_FIELD = "work_item_key"
PATH_JOIN = " "
@@ -127,7 +127,7 @@ def base_rows(kind: str) -> list[dict[str, Any]]:
return rows
LINK_FOLDER = tables.RESOURCES / "data_work_item_link"
LINK_FOLDER = tables.RESOURCES / "master_data" / "old"
LINKED = "연결됨"
@@ -136,7 +136,7 @@ def linked_keys() -> frozenset[str]:
목록을 코드에 판정은 · 미판정은 이음(표의 `unconfirmed_action: do_not_link`).
"""
found = sorted(LINK_FOLDER.glob("work_item_link_*.json")) if LINK_FOLDER.is_dir() else []
found = sorted(LINK_FOLDER.glob("2_공종_산림건설_연결_*.json")) if LINK_FOLDER.is_dir() else []
if not found:
return frozenset()
links = _read(str(found[-1]), found[-1].stat().st_mtime_ns).get("links") or []
+5 -1
View File
@@ -22,7 +22,11 @@ from pathlib import Path
from typing import Any
ALIAS_FILE = (
Path(__file__).resolve().parents[1] / "resources" / "data_aliases" / "aliases_2026-01-01.json"
Path(__file__).resolve().parents[1]
/ "resources"
/ "master_data"
/ "ref"
/ "공종_자원별칭_실무_2026-01-01.json"
)
AXES = frozenset({"resource", "variant"})
_FIELDS = ("axis", "from", "to", "scope", "pum_edition")
+1 -1
View File
@@ -22,7 +22,7 @@ ROOT = Path(__file__).resolve().parents[1]
# 번들이 낡았는지 재는 대상 — 기하 계통이 걸쳐 있는 폴더 + 구조물도 식 풀이(B08, 2026-09-13).
SOURCE_DIRS = ("B05_Profile", "B06_Section", "B08_Quantity", "common_util")
# 번들에 박히는 자료(JSON) — 벽 전면 기울기 표준경사 표(2026-09-14 B5). 표만 바뀌어도 다시 빌드.
SOURCE_DATA_DIRS = ("resources/data_masonry",)
SOURCE_DATA_DIRS = ("resources/master_data/old",)
# 번들 만들기·실행 상한(초). 실측 번들 실행 0.1초, 빌드 3초 수준이라 넉넉하다.
BUILD_TIMEOUT_S = 300
RUN_TIMEOUT_S = 600
+2 -2
View File
@@ -20,7 +20,7 @@ from functools import lru_cache
from pathlib import Path
from typing import Any, Iterable
MASTER_DIR = Path(__file__).resolve().parent.parent / "resources" / "data_work_item_master"
MASTER_DIR = Path(__file__).resolve().parent.parent / "resources" / "master_data" / "old"
#: 우리가 세운 공종·구조물 — 난수 코드가 곧 불변 열쇠
_STABLE_CODE = re.compile(r"^AX-(?:WK|ST)-[0-9a-f]{8}$")
@@ -38,7 +38,7 @@ def _read(path: str, _mtime_ns: int) -> dict[tuple[str, str], str | None]:
def _keys() -> dict[tuple[str, str], str | None]:
out: dict[tuple[str, str], str | None] = {}
for path in sorted(MASTER_DIR.glob("*work_item_master_*.json")): # 산림 + 건설(`const_`)
for path in sorted(MASTER_DIR.glob("2_공종_*_마스터_*.json")): # 산림 + 건설(`const_`)
out.update(_read(str(path), path.stat().st_mtime_ns))
return out
+2 -2
View File
@@ -19,7 +19,7 @@ from functools import lru_cache
from pathlib import Path
from typing import Any
FOLDER = Path(__file__).resolve().parent.parent / "resources" / "data_work_item_link"
FOLDER = Path(__file__).resolve().parent.parent / "resources" / "master_data" / "old"
CONFIRMED = "확정"
#: 표 `policy.both_precedence` 낱말 → 그 행의 열쇠 칸
_KEY_COLUMN = {"forest": "forest_key", "construction": "const_key"}
@@ -39,7 +39,7 @@ def _read(path: str, _mtime_ns: int) -> dict[str, Any]:
def _doc() -> dict[str, Any]:
found = sorted(FOLDER.glob("work_item_link_*.json")) if FOLDER.is_dir() else []
found = sorted(FOLDER.glob("2_공종_산림건설_연결_*.json")) if FOLDER.is_dir() else []
return _read(str(found[-1]), found[-1].stat().st_mtime_ns) if found else {}