Files
Aislo/Z01_MasterData/Z01_MasterData_BasePrices_Tables.py
T

415 lines
14 KiB
Python

"""Z01 기초데이터 아홉 — 품셈 근거 8 + 돌쌓기 갈래 1(2026-09-16 사용자 지시 · 브레인 승인).
사용자 잣대: 기초데이터 = **로직으로 계산되는 값이 아니라 값만으로 정의되는 데이터셋.**
태움 — 토량환산 · 자재 할증 · 거푸집 전용 · 철근 갈래 · 목재 갈래 · 돌쌓기 경사·뒷길이·돌 종류·갈래
안 태움 — 이음표(`type_map`·`form_map`·`bond.codes`) · 품셈 원문 표(coef `surcharge_*`) · 글·방침 ·
구조물 실측(울진 한 현장 관찰값) · 기슭막이 교본값(값과 글이 한 줄에 섞임)
⚠ 축 칸(`@axis`)은 못 고침 — 그 칸이 `@id` 를 이루므로 바꾸면 덮개가 줄을 못 찾음.
⚠ 원문 중복(coef 「암괴…점토」 두 줄)은 **값이 같을 때만** 한 줄로 합침 — 갈리면 `DuplicateRowError`(브레인 조건).
"""
from __future__ import annotations
from typing import Any
_ROW = dict[str, Any]
def _brackets(steps: list[float]) -> list[str]:
"""직고 구간 이름 — 원문 표기(`∼1.5 · ∼3 · ∼5 · ∼7 · 7이상`)를 그대로 씀."""
return [f"~{s:g}" for s in steps] + [f"{steps[-1]:g}이상"]
def _row(row_id: str, source: str, axis: tuple[str, ...], **cells: Any) -> _ROW:
return {"@id": row_id, "@source": source, "@axis": axis, **cells}
def _coef(doc: dict[str, Any], name: str) -> list[_ROW]:
"""토량환산계수 L·C — 품셈 체적변화율. 원문 표(`surcharge_*`)·공구손료율은 여기 아님."""
rows = []
for table in ("coef_soil_L", "coef_soil_C"):
for record in doc["variables"][table]["records"]:
rows.append(
_row(
f"{table}/{record['soil_type']}",
name,
("table", "soil_type"),
table=table,
soil_type=record["soil_type"],
min=record.get("min"),
max=record.get("max"),
rule=record.get("rule"),
selection=record.get("selection"),
)
)
return rows
def _material_surcharge(doc: dict[str, Any], name: str) -> list[_ROW]:
return [
_row(
f"rates_pct/{r['material']}",
name,
("material",),
material=r["material"],
rate=r.get("rate"),
condition=r.get("condition"),
alt_rate=r.get("alt_rate"),
alt_condition=r.get("alt_condition"),
pumsem=r.get("pumsem"),
)
for r in doc["rates_pct"]
]
def _formwork_reuse(doc: dict[str, Any], name: str) -> list[_ROW]:
rows = []
for r in doc["reuse_by_class"]:
rows.append(
_row(
f"reuse_by_class/{r['class']}",
name,
("table", "class"),
table="reuse_by_class",
**{"class": r["class"]},
reuse_count=r.get("reuse_count"),
examples=r.get("examples"),
)
)
ratios = doc["reuse_ratio_pct"]
for material in ("plywood", "timber"):
for count, percent in (ratios.get(material) or {}).items():
rows.append(
_row(
f"reuse_ratio_pct/{material}/{count}",
name,
("table", "material", "reuse_count"),
table="reuse_ratio_pct",
material=material,
reuse_count=int(count),
ratio_pct=percent,
)
)
for r in doc["euroform_type"]["classes"]:
rows.append(
_row(
f"euroform_type/classes/{r['key']}",
name,
("table", "class"),
table="euroform_type/classes",
**{"class": r["key"]},
daily_area_m2=r.get("daily_area_m2"),
examples=r.get("examples"),
)
)
return rows
def _rebar_complexity(doc: dict[str, Any], name: str) -> list[_ROW]:
rows = [
_row(
f"classes/{r['key']}",
name,
("table", "class"),
table="classes",
**{"class": r["key"]},
examples=r.get("examples"),
)
for r in doc["classes"]
]
for key, price in (doc["price_hint_krw_per_ton"].get("values") or {}).items():
rows.append(
_row(
f"price_hint_krw_per_ton/{key}",
name,
("table", "class"),
table="price_hint_krw_per_ton",
**{"class": key},
price_krw_per_ton=price,
)
)
return rows
def _timber_structure_class(doc: dict[str, Any], name: str) -> list[_ROW]:
return [
_row(
f"classes/{r['key']}",
name,
("class",),
**{"class": r["key"]},
carpenter=r.get("carpenter"),
laborer=r.get("laborer"),
examples=r.get("examples"),
)
for r in doc["classes"]
]
def _masonry_slope(doc: dict[str, Any], name: str) -> list[_ROW]:
brackets = _brackets(doc["steps_m"])
rows = []
for bond, faces in doc["table"].items():
for face, values in faces.items():
for bracket, ratio in zip(brackets, values):
rows.append(
_row(
f"table/{bond}/{face}/{bracket}",
name,
("bond", "face", "height_bracket"),
bond=bond,
face=face,
height_bracket=bracket,
face_slope_ratio=ratio,
)
)
return rows
def _masonry_back_length(doc: dict[str, Any], name: str) -> list[_ROW]:
brackets = _brackets(doc["steps_m"])
rows = []
for bond, pairs in doc["table_cm"].items():
for bracket, pair in zip(brackets, pairs):
rows.append(
_row(
f"table_cm/{bond}/{bracket}",
name,
("bond", "height_bracket"),
bond=bond,
height_bracket=bracket,
min_cm=pair[0],
max_cm=pair[1], # 원문 「-」 는 그대로 빈 칸
)
)
return rows
def _stone_kind(doc: dict[str, Any], name: str) -> list[_ROW]:
rows = []
def unit_rows(table: str, holder: dict[str, Any]) -> None:
for kind, lengths in holder.items():
if not isinstance(lengths, dict):
continue
for cm, value in lengths.items():
rows.append(
_row(
f"{table}/{kind}/{cm}",
name,
("table", "stone_kind", "back_length_cm"),
table=table,
stone_kind=kind,
back_length_cm=int(cm),
m3_per_m2=value,
)
)
for table in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"):
unit_rows(table, doc[table])
for kind, ratio in doc["backfill_ratio_of_back_length"].items():
if isinstance(ratio, (int, float)):
rows.append(
_row(
f"backfill_ratio_of_back_length/{kind}",
name,
("table", "stone_kind"),
table="backfill_ratio_of_back_length",
stone_kind=kind,
ratio=ratio,
)
)
fallback = doc["fallback"] # 돌 종류 미지정일 때 쓰는 한 벌(건설품셈 참고자료)
for table in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"):
for cm, value in (fallback.get(table) or {}).items():
rows.append(
_row(
f"fallback/{table}/{cm}",
name,
("table", "back_length_cm"),
table=f"fallback/{table}",
back_length_cm=int(cm),
m3_per_m2=value,
)
)
if isinstance(fallback.get("backfill_ratio_of_back_length"), (int, float)):
rows.append(
_row(
"fallback/backfill_ratio_of_back_length",
name,
("table",),
table="fallback/backfill_ratio_of_back_length",
ratio=fallback["backfill_ratio_of_back_length"],
)
)
return rows
def _masonry_class(doc: dict[str, Any], name: str) -> list[_ROW]:
rows = [
_row(
f"back_length/classes/{r['key']}",
name,
("table", "class"),
table="back_length/classes",
**{"class": r["key"]},
max_cm=r.get("max_cm"),
)
for r in doc["back_length"]["classes"]
]
for value in doc["boulder_diameter"]["classes"]:
rows.append(
_row(
f"boulder_diameter/{value}",
name,
("table", "class"),
table="boulder_diameter",
**{"class": value},
)
)
for type_id, ratio in doc["face_slope"]["by_type"].items():
rows.append(
_row(
f"face_slope/{type_id}",
name,
("table", "type_id"),
table="face_slope",
type_id=type_id,
face_slope_ratio=ratio,
)
)
return rows
def _machine_productivity(doc: dict[str, Any], name: str) -> list[_ROW]:
"""기계 작업량 밑값 — 코드에 박혀 있던 값을 꺼낸 자리(불도저 속도·삽날 · 덤프 운반·적재 계수).
⚠ 식(`Q = n·q·f·E` 따위)은 값이 아니라 로직이라 계산 모듈에 남는다 — 여기는 계수만.
"""
rows = []
for r in doc["variables"]["dozer_speed"]["records"]:
rows.append(
_row(
f"dozer_speed/{r['track']}/{r['tonnage_ton']}/{r['gear']}",
name,
("table", "track", "tonnage_ton", "gear"),
table="dozer_speed",
track=r["track"],
tonnage_ton=float(r["tonnage_ton"]),
gear=int(r["gear"]),
forward_m_per_min=float(r["forward_m_per_min"]),
reverse_m_per_min=float(r["reverse_m_per_min"]),
)
)
for r in doc["variables"]["dozer_blade"]["records"]:
rows.append(
_row(
f"dozer_blade/{r['track']}/{r['tonnage_ton']}",
name,
("table", "track", "tonnage_ton"),
table="dozer_blade",
track=r["track"],
tonnage_ton=float(r["tonnage_ton"]),
blade_m3=float(r["blade_m3"]),
)
)
for r in doc["variables"]["dump_haul"]["records"]:
rows.append(
_row(
f"dump_haul/{r['key']}",
name,
("table", "key"),
table="dump_haul",
key=r["key"],
value=float(r["value"]),
unit=r.get("unit"),
meaning=r.get("meaning"),
)
)
for r in doc["variables"]["dump_material"]["records"]:
rows.append(
_row(
f"dump_material/{r['work_item_code']}",
name,
("table", "work_item_code"),
table="dump_material",
work_item_code=r["work_item_code"],
label=r["label"],
unit_weight_ton_per_m3=float(r["unit_weight_ton_per_m3"]),
loose_factor=float(r["loose_factor"]),
bucket_factor=float(r["bucket_factor"]),
loader_efficiency=float(r["loader_efficiency"]),
truck_efficiency=float(r["truck_efficiency"]),
loading_efficiency=float(r["loading_efficiency"]),
notes=r.get("notes"),
loading_note=r.get("loading_note"),
)
)
return rows
#: kind → 원본 파일 id · 줄 만드는 길 · 고칠 칸. 이름은 **자료 이름 그대로**(브레인).
SPEC: dict[str, dict[str, Any]] = {
"coef": {"file": "coef", "build": _coef, "editable": ["min", "max"]},
"material_surcharge": {
"file": "material_surcharge",
"build": _material_surcharge,
"editable": ["rate", "alt_rate"],
},
"formwork_reuse": {
"file": "formwork_reuse",
"build": _formwork_reuse,
"editable": ["reuse_count", "ratio_pct", "daily_area_m2"],
},
"rebar_complexity": {
# ⚠ 단가 참고값은 **B09 가 자재 단가로 셈한 값**이라 고칠 칸이 아님(코덱스 검증 1 · 2026-09-16).
# 사용자 잣대 「마스터 = 계산으로 나오는 값이 아니라 값 그 자체」와 어긋나 editable 에서 뺌.
"file": "rebar_complexity",
"build": _rebar_complexity,
"editable": [],
"locked": {
"price_krw_per_ton": "계산값 — B09 가 자재 단가로 셈(표시 전용 · 밑값을 고쳐야 바뀜)"
},
"formula": {
"price_krw_per_ton": "갈래별 이형철근 단가 참고값 = B09 가 자재 단가에서 셈 — 표시 전용(B08 계산에 안 듦)"
},
},
"timber_structure_class": {
"file": "timber_structure_class",
"build": _timber_structure_class,
"editable": ["carpenter", "laborer"],
},
"masonry_slope": {
"file": "masonry_slope",
"build": _masonry_slope,
"editable": ["face_slope_ratio"],
},
"masonry_back_length": {
"file": "masonry_back_length",
"build": _masonry_back_length,
"editable": ["min_cm", "max_cm"],
},
"stone_kind": {"file": "stone_kind", "build": _stone_kind, "editable": ["m3_per_m2", "ratio"]},
"machine_productivity": {
"file": "machine_productivity",
"build": _machine_productivity,
"editable": [
"forward_m_per_min",
"reverse_m_per_min",
"blade_m3",
"value",
"unit_weight_ton_per_m3",
"loose_factor",
"bucket_factor",
"loader_efficiency",
"truck_efficiency",
"loading_efficiency",
],
},
"masonry_class": {
"file": "masonry_class",
"build": _masonry_class,
"editable": ["max_cm", "face_slope_ratio"],
},
}