Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -690,6 +690,11 @@ def build() -> dict[str, Any]:
|
|||||||
node["tables"].append(entry)
|
node["tables"].append(entry)
|
||||||
attached += 1
|
attached += 1
|
||||||
|
|
||||||
|
# 부모 모양(명세 2장) — 기본 choose_one, 합산형은 사람이 적은 것만.
|
||||||
|
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Parents import apply_parent_modes
|
||||||
|
|
||||||
|
apply_parent_modes(nodes)
|
||||||
|
|
||||||
src_meta = {
|
src_meta = {
|
||||||
"dataset_id": data["dataset_id"],
|
"dataset_id": data["dataset_id"],
|
||||||
"effective_date": data["effective_date"],
|
"effective_date": data["effective_date"],
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
"""공종 마스터 — 부모 공종의 모양 `parent_mode` (명세 2장 · 2026-09-13 축 C Ⓐ).
|
||||||
|
|
||||||
|
아래 절이 있는 공종(부모)은 두 모양이고 규칙이 다르다.
|
||||||
|
|
||||||
|
choose_one 갈래 고르기형 — 잎 하나를 고른다. **부모에 금액을 붙이지 않는다.** (기본)
|
||||||
|
sum_steps 단계 합산형 — 내역 한 줄이 곧 부모이고, 일위대가는 **잎들의 합**으로 조립한다.
|
||||||
|
|
||||||
|
⚠ **자동 판정 금지** — 표 모양으로 짐작하면 조용히 틀린다. 합산형은 아래에 **사람이 적은 것**만.
|
||||||
|
⚠ 가중치(`weight`)도 사람이 적는다 — 원문 절 제목이 준 비율만(「발파 10%」·「깎기(90%)」).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
PARENT_CHOOSE_ONE = "choose_one"
|
||||||
|
PARENT_SUM_STEPS = "sum_steps"
|
||||||
|
|
||||||
|
#: 단계 합산형 부모 — (잎 코드, 가중치) 차례와 근거. ⚠ 여기 없는 부모는 전부 `choose_one`.
|
||||||
|
SUM_STEPS: dict[str, dict[str, Any]] = {
|
||||||
|
"FP-09-04": {
|
||||||
|
"steps": (("FP-09-04-01", "1"), ("FP-09-04-02", "1")),
|
||||||
|
"basis": (
|
||||||
|
"품셈 9-4 암절취 = 9-4-1 암파쇄 + 9-4-2 집토 — 같은 ㎥ 를 깨고 모음"
|
||||||
|
"(지식DB 토공_수량 「암절취 (암파쇄ㆍ집토) | 9-4」)"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"FP-09-05": {
|
||||||
|
"steps": (("FP-09-05-01", "0.1"), ("FP-09-05-02", "0.9"), ("FP-09-05-03", "1")),
|
||||||
|
"basis": (
|
||||||
|
"품셈 9-5 발파암 — 절 제목 「9-5-1 육상, 암석 절취(발파 10%)」·「9-5-2 깎기(90%)」·"
|
||||||
|
"「9-5-3 집토」(지식DB 토공_수량 「발파암 (절취 10 %ㆍ깎기 90 %ㆍ집토) | 9-5」)"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"FP-12-38": {
|
||||||
|
"steps": (("FP-12-38-02", "1"), ("FP-12-38-03", "1")),
|
||||||
|
"basis": (
|
||||||
|
"품셈 12-38 유로폼 = 12-38-2 사용수량(자재) + 12-38-3 설치 및 해체(품) — "
|
||||||
|
"12-38-1 사용횟수는 금액 단계가 아니라 사용수량의 잔존율 조건"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_parent_modes(nodes: list[dict[str, Any]]) -> None:
|
||||||
|
"""부모 공종에 `parent_mode`(합산형이면 `steps`·`steps_basis` 까지)를 단다 — 자리에서.
|
||||||
|
|
||||||
|
⚠ 선언한 잎이 그 부모의 자식이 아니면 멈춘다 — 판이 바뀌어 절 번호가 옮겨 간 자리다.
|
||||||
|
"""
|
||||||
|
children: dict[str, list[str]] = {}
|
||||||
|
for node in nodes:
|
||||||
|
if node.get("parent_code"):
|
||||||
|
children.setdefault(node["parent_code"], []).append(node["work_item_code"])
|
||||||
|
for node in nodes:
|
||||||
|
code = node["work_item_code"]
|
||||||
|
if code not in children:
|
||||||
|
continue
|
||||||
|
declared = SUM_STEPS.get(code)
|
||||||
|
if declared is None:
|
||||||
|
node["parent_mode"] = PARENT_CHOOSE_ONE
|
||||||
|
continue
|
||||||
|
strays = [step for step, _ in declared["steps"] if step not in children[code]]
|
||||||
|
if strays:
|
||||||
|
raise ValueError(f"{code} 합산 단계가 자식이 아닙니다: {strays}")
|
||||||
|
node["parent_mode"] = PARENT_SUM_STEPS
|
||||||
|
node["steps"] = [{"code": step, "weight": weight} for step, weight in declared["steps"]]
|
||||||
|
node["steps_basis"] = declared["basis"]
|
||||||
|
missing = [code for code in SUM_STEPS if code not in children]
|
||||||
|
if missing:
|
||||||
|
raise ValueError(f"합산형으로 적은 부모가 마스터에 없습니다: {missing}")
|
||||||
@@ -177,8 +177,11 @@ def _earthwork_rows(
|
|||||||
# 토공 줄은 대개 갈래 축이 없다 — 그래도 **칸은 둔다**(계약이 한 모양).
|
# 토공 줄은 대개 갈래 축이 없다 — 그래도 **칸은 둔다**(계약이 한 모양).
|
||||||
# 매핑이 갈래를 적은 작업 갈래(잡관목제거 → 단목베기 「5m 미만」)만 값을 싣는다.
|
# 매핑이 갈래를 적은 작업 갈래(잡관목제거 → 단목베기 「5m 미만」)만 값을 싣는다.
|
||||||
"variant_axis": (entry or {}).get("variant_axis"),
|
"variant_axis": (entry or {}).get("variant_axis"),
|
||||||
|
# 암 갈래(연암·보통암·경암)는 줄 자신의 갈래를 넘김 — 9-4·9-5 단계 합산형(축 C Ⓐ).
|
||||||
"variant_value": (entry or {}).get("variant_value")
|
"variant_value": (entry or {}).get("variant_value")
|
||||||
or (variant_inputs or {}).get(str((entry or {}).get("variant_from") or ""))
|
or {**(variant_inputs or {}), "ground_class": ground}.get(
|
||||||
|
str((entry or {}).get("variant_from") or "")
|
||||||
|
)
|
||||||
or None,
|
or None,
|
||||||
"secondary_axes": None,
|
"secondary_axes": None,
|
||||||
"spec_class": None,
|
"spec_class": None,
|
||||||
|
|||||||
@@ -203,13 +203,28 @@ def _leaf_row(
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
price_code = f"B-{node.code}"
|
price_code = f"B-{node.code}"
|
||||||
if item.variant_value and price_code not in unit_prices.book.titles:
|
if price_code not in unit_prices.book.titles:
|
||||||
# B08 은 **의미**(어느 공종·어느 제원)만 보내고 갈래 키는 우리가 만든다.
|
# B08 은 **의미**(어느 공종·어느 제원)만 보내고 갈래 키는 우리가 만든다.
|
||||||
# 못 맞추면 후보를 보이는 길로 내려간다 — 가까운 갈래를 임의로 고르지 않는다.
|
# 못 맞추면 후보를 보이는 길로 내려간다 — 가까운 갈래를 임의로 고르지 않는다.
|
||||||
picked = find_variant_code(node.code, item.variant_value, unit_prices)
|
picked = (
|
||||||
|
find_variant_code(node.code, item.variant_value, unit_prices)
|
||||||
|
if item.variant_value
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
default = unit_prices.default_variants.get(node.code)
|
||||||
if picked is not None:
|
if picked is not None:
|
||||||
price_code = picked
|
price_code = picked
|
||||||
row.spec = f"{row.spec} {item.variant_value}".strip()
|
row.spec = f"{row.spec} {item.variant_value}".strip()
|
||||||
|
elif default is not None:
|
||||||
|
# 표에 없거나 안 준 암질(풍화암·암) — **원문이 정한 갈래**로만 선다(9-4-1 [주]① 평균).
|
||||||
|
price_code = f"{price_code}#{default[0]}"
|
||||||
|
row.spec = f"{row.spec} {default[0]}".strip()
|
||||||
|
given = (
|
||||||
|
f"「{item.variant_value}」은 표에 없는 갈래"
|
||||||
|
if item.variant_value
|
||||||
|
else "갈래 미지정"
|
||||||
|
)
|
||||||
|
row.add_note("spec", f"ⓘ {given} — {default[1]}")
|
||||||
|
|
||||||
if price_code not in unit_prices.book.titles:
|
if price_code not in unit_prices.book.titles:
|
||||||
# 한 층 아래에 일위대가가 있으면 **후보로 보여준다** — 임의로 고르지 않는다
|
# 한 층 아래에 일위대가가 있으면 **후보로 보여준다** — 임의로 고르지 않는다
|
||||||
@@ -225,16 +240,35 @@ def _leaf_row(
|
|||||||
)
|
)
|
||||||
or code.startswith(f"{price_code}#")
|
or code.startswith(f"{price_code}#")
|
||||||
)
|
)
|
||||||
if children:
|
mode = unit_prices.parent_modes.get(node.code)
|
||||||
|
if mode == "sum_steps" and node.code in unit_prices.component_gaps:
|
||||||
|
# 단계 합산형 — 부모가 곧 내역 줄인데 **어느 단계가 못 서서** 조립이 안 됐다.
|
||||||
|
gap = unit_prices.component_gaps[node.code]
|
||||||
|
row.add_note("unit_price_krw", f"단계 합산 단가를 못 세웠습니다 — {gap}")
|
||||||
|
reason = f"단계 합산 미완 — {gap}"
|
||||||
|
elif children and mode == "sum_steps":
|
||||||
|
names = ", ".join(c[2:] for c in children if c.startswith(f"{price_code}#"))
|
||||||
|
row.add_note(
|
||||||
|
"unit_price_krw",
|
||||||
|
f"갈래 「{item.variant_value or '미지정'}」에 맞는 단계 합산 단가가 없습니다"
|
||||||
|
f" — 있는 갈래: {names}",
|
||||||
|
)
|
||||||
|
reason = f"단계 합산 갈래 미일치 — {item.variant_value or '미지정'}"
|
||||||
|
elif mode == "choose_one":
|
||||||
|
# ⚠ 갈래 고르기형 부모 — **부모에 금액을 붙이지 않는다**(명세 2장). 조용히 0 원이 되지 않게 막는다.
|
||||||
|
names = ", ".join(f"{c[2:]} {unit_prices.book.title(c).name}" for c in children)
|
||||||
|
row.add_note(
|
||||||
|
"unit_price_krw",
|
||||||
|
"갈래 고르기형 부모 공종이라 잎 하나를 골라야 합니다 — "
|
||||||
|
+ (f"후보: {names}" if names else "잎 공종 일위대가도 아직 없습니다"),
|
||||||
|
)
|
||||||
|
reason = f"갈래 고르기형 부모 — 잎 미선택(후보 {len(children)}건)"
|
||||||
|
elif children:
|
||||||
names = ", ".join(f"{c[2:]} {unit_prices.book.title(c).name}" for c in children)
|
names = ", ".join(f"{c[2:]} {unit_prices.book.title(c).name}" for c in children)
|
||||||
row.add_note(
|
row.add_note(
|
||||||
"unit_price_krw",
|
"unit_price_krw",
|
||||||
f"이 공종엔 일위대가가 없고 한 층 아래에 있습니다 — 후보: {names}",
|
f"이 공종엔 일위대가가 없고 한 층 아래에 있습니다 — 후보: {names}",
|
||||||
)
|
)
|
||||||
# 관경이 표 밖이면 **무엇을 정해야 하는지**까지 가리킨다.
|
|
||||||
diameter_note = pipe_diameter_note(node.code, item.variant_value)
|
|
||||||
if diameter_note:
|
|
||||||
row.add_note("spec", diameter_note)
|
|
||||||
reason = f"일위대가가 하위 공종에 있음(후보 {len(children)}건)"
|
reason = f"일위대가가 하위 공종에 있음(후보 {len(children)}건)"
|
||||||
else:
|
else:
|
||||||
# ⚠ 「아직 안 만든 것」과 「성분이 빠져 못 세운 것」은 **할 일이 다르다**.
|
# ⚠ 「아직 안 만든 것」과 「성분이 빠져 못 세운 것」은 **할 일이 다르다**.
|
||||||
@@ -249,6 +283,10 @@ def _leaf_row(
|
|||||||
"unit_price_krw", "일위대가가 아직 없습니다 — 금액을 0 으로 때우지 않습니다."
|
"unit_price_krw", "일위대가가 아직 없습니다 — 금액을 0 으로 때우지 않습니다."
|
||||||
)
|
)
|
||||||
reason = "일위대가 없음"
|
reason = "일위대가 없음"
|
||||||
|
# 관경이 표 밖이면 **무엇을 정해야 하는지**까지 가리킨다.
|
||||||
|
diameter_note = pipe_diameter_note(node.code, item.variant_value) if children else ""
|
||||||
|
if diameter_note:
|
||||||
|
row.add_note("spec", diameter_note)
|
||||||
result.missing.append(
|
result.missing.append(
|
||||||
{
|
{
|
||||||
"name": row.name,
|
"name": row.name,
|
||||||
|
|||||||
@@ -219,6 +219,25 @@ MACHINE_CHOICES: dict[str, dict[str, Any]] = {
|
|||||||
"(2026-09-09 밤에 드러남).",
|
"(2026-09-09 밤에 드러남).",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
# 암절취·발파암의 집토 — 흙깎기와 같이 기종이 [주] 에만 있다(2026-09-13 축 C Ⓐ, 단계 합산형 부모의 잎).
|
||||||
|
"FP-09-04-02": {
|
||||||
|
"work_item_name": "암절취 집토",
|
||||||
|
"default_code": "0201-0070",
|
||||||
|
"source": "note",
|
||||||
|
"basis": [
|
||||||
|
"산림사업 표준품셈 9-4-2 [주]① 「장비는 무한궤도 굴착기(0.7㎥)를 적용한다」 — "
|
||||||
|
"표가 아니라 [주] 에 있어 공종 마스터가 못 싣는 값입니다.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"FP-09-05-03": {
|
||||||
|
"work_item_name": "발파암 집토",
|
||||||
|
"default_code": "0201-0070",
|
||||||
|
"source": "note",
|
||||||
|
"basis": [
|
||||||
|
"산림사업 표준품셈 9-5-3 [주]① 「장비는 무한궤도 굴착기(0.7㎥)를 적용한다」 — "
|
||||||
|
"표가 아니라 [주] 에 있어 공종 마스터가 못 싣는 값입니다.",
|
||||||
|
],
|
||||||
|
},
|
||||||
"FP-09-18": {
|
"FP-09-18": {
|
||||||
"work_item_name": "층따기",
|
"work_item_name": "층따기",
|
||||||
"default_code": "0201-0070",
|
"default_code": "0201-0070",
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
"""B09 원가계산 — **단계 합산형 부모**를 잎들의 합으로 조립한다 (명세 2장 · 2026-09-13 축 C Ⓐ).
|
||||||
|
|
||||||
|
공종 마스터가 부모마다 `parent_mode` 를 단다(`B08_Quantity_Build_WorkItemMaster_Parents`).
|
||||||
|
|
||||||
|
choose_one 갈래 고르기형 — **여기서 안 세운다.** 내역 줄이 부모를 가리키면 오류로 드러냄
|
||||||
|
sum_steps 단계 합산형 — B-<부모>[#갈래] = Σ B-<잎>[#갈래] × 가중치
|
||||||
|
|
||||||
|
⭐ 흙깎기(리핑암) = 9-4 암절취 = 9-4-1 암파쇄 + 9-4-2 집토 가 첫 사례다 — 부모에 일위대가 제목이
|
||||||
|
아예 없어 금액 0 이던 자리(명세 2장 실측).
|
||||||
|
|
||||||
|
⚠ **암질 갈래** — 암파쇄(9-4-1)·깎기(9-5-2) 표는 연암·보통암·경암마다 작업능력 `Q`(㎥/hr)를 준다.
|
||||||
|
갈래마다 따로 세우고, 부모도 그 갈래로 선다. 내역 줄의 암질이 표에 없으면(풍화암·암 등)
|
||||||
|
**원문이 평균을 정한 잎만** 평균으로 선다(9-4-1 [주]①). 9-5-2 는 그 [주]가 없어 막는다.
|
||||||
|
⚠ 모르는 값을 지어내지 않는다 — 단계 하나라도 못 서면 부모를 안 세우고 **어느 단계가 왜**를 남긴다.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from B09_Estimation.B09_Estimation_PriceBook import PriceDetail, PriceKind, PriceTitle
|
||||||
|
|
||||||
|
ROCK_CLASSES = ("연암", "보통암", "경암")
|
||||||
|
|
||||||
|
#: 암질을 표에 없는 이름으로 준 줄이 쓸 갈래 — **원문이 평균을 정한 잎만**.
|
||||||
|
AVERAGE_VARIANT = "평균"
|
||||||
|
AVERAGE_BASIS: dict[str, str] = {
|
||||||
|
"FP-09-04-01": "품셈 9-4-1 [주]① 「Q(시간당 작업량 ㎥/hr) =(5.0+3.4+2.6)/3」 — 세 암질 평균",
|
||||||
|
}
|
||||||
|
|
||||||
|
#: 표에 기계 이름이 없는 잎 — [주]가 기종을 적은 자리(마스터가 [주]를 못 싣는다). 원문 그대로.
|
||||||
|
#: ⚠ 부모(9-5)에서 물려받을 기계는 없다 — 부모엔 표가 없고, 잎의 [주]가 스스로 적었다.
|
||||||
|
NOTE_MACHINES: dict[str, tuple[tuple[str, ...], str]] = {
|
||||||
|
"FP-09-05-02": (
|
||||||
|
("0201-0070", "0230-0007"),
|
||||||
|
"품셈 9-5-2 [주] 「장비는 대형브레이커와 무한궤도 굴착기(0.7㎥)를 적용한다」",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _tight(text: Any) -> str:
|
||||||
|
return re.sub(r"\s", "", str(text or ""))
|
||||||
|
|
||||||
|
|
||||||
|
def rock_rows(node: dict[str, Any]) -> list[tuple[str, Decimal, Decimal | None]]:
|
||||||
|
"""(암질, 작업능력 Q, 치즐소모량) — 암질 이름 칸 **바로 뒤** 두 칸을 읽는다."""
|
||||||
|
from B09_Estimation.B09_Estimation_MachineProductivity import parse_measure
|
||||||
|
|
||||||
|
found: list[tuple[str, Decimal, Decimal | None]] = []
|
||||||
|
for table in node.get("tables", []):
|
||||||
|
for row in table.get("raw_row") or []:
|
||||||
|
cells = [str(cell) for cell in row]
|
||||||
|
index = next((i for i, c in enumerate(cells) if _tight(c) in ROCK_CLASSES), None)
|
||||||
|
if index is None:
|
||||||
|
continue
|
||||||
|
tail = [parse_measure(c) for c in cells[index + 1 : index + 3]] + [None, None]
|
||||||
|
if tail[0] is not None and tail[0] > 0:
|
||||||
|
found.append((_tight(cells[index]), tail[0], tail[1]))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def leaf_machines(node: dict[str, Any]) -> tuple[list[tuple[str, str]], str]:
|
||||||
|
"""(기종 코드·이름 목록, 출처) — 표의 조합 칸(「대형브레이커+ 유압식백호우 …」)이 먼저."""
|
||||||
|
from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
|
||||||
|
from B09_Estimation.B09_Estimation_MachineProductivity import resolve_machine
|
||||||
|
from B09_Estimation.B09_Estimation_MachineProductivity_Reference import _machine_by_name
|
||||||
|
|
||||||
|
catalog = load_machine_catalog()
|
||||||
|
for table in node.get("tables", []):
|
||||||
|
for row in table.get("raw_row") or []:
|
||||||
|
for cell in row:
|
||||||
|
parts = [part.strip() for part in str(cell).split("+")]
|
||||||
|
if len(parts) < 2:
|
||||||
|
continue
|
||||||
|
bodies = [resolve_machine(part) for part in parts]
|
||||||
|
body = next((b for b in bodies if b is not None), None)
|
||||||
|
if body is None:
|
||||||
|
continue
|
||||||
|
spec = str(catalog.machines[body[0]].specification)
|
||||||
|
picked = [b or _machine_by_name(p, spec) for b, p in zip(bodies, parts)]
|
||||||
|
if all(picked):
|
||||||
|
return [
|
||||||
|
p for p in picked if p
|
||||||
|
], f"표의 조합 칸 「{' '.join(str(cell).split())}」"
|
||||||
|
code = str(node.get("work_item_code") or "")
|
||||||
|
if code in NOTE_MACHINES:
|
||||||
|
codes, source = NOTE_MACHINES[code]
|
||||||
|
return [(c, catalog.machines[c].name) for c in codes if c in catalog.machines], source
|
||||||
|
return [], ""
|
||||||
|
|
||||||
|
|
||||||
|
def _add_rock_leaf(build: Any, node: dict[str, Any]) -> None:
|
||||||
|
"""암질 갈래마다 잎 제목을 세운다 — 기계마다 1/Q 시간."""
|
||||||
|
code = str(node["work_item_code"])
|
||||||
|
rows = rock_rows(node)
|
||||||
|
if not rows or any(title.startswith(f"B-{code}#") for title in build.book.titles):
|
||||||
|
return
|
||||||
|
machines, source = leaf_machines(node)
|
||||||
|
missing = [f"X-{m}" for m, _ in machines if f"X-{m}" not in build.book.titles]
|
||||||
|
if not machines or missing:
|
||||||
|
build.component_gaps[code] = (
|
||||||
|
f"작업능력 표의 기종을 못 골랐습니다 — {source or '표·[주]에 기종 없음'}"
|
||||||
|
+ (f" (기계 단가 층 없음: {', '.join(missing)})" if missing else "")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
variants = [(rock, q, f"작업능력 Q = {q} ㎥/hr ({rock})") for rock, q, _ in rows]
|
||||||
|
if code in AVERAGE_BASIS:
|
||||||
|
average = sum((q for _, q, _ in rows), Decimal(0)) / Decimal(len(rows))
|
||||||
|
variants.append(
|
||||||
|
(AVERAGE_VARIANT, average, f"Q = {average:.4f} ㎥/hr — {AVERAGE_BASIS[code]}")
|
||||||
|
)
|
||||||
|
for variant, capacity, note in variants:
|
||||||
|
title_code = f"B-{code}#{variant}"
|
||||||
|
build.book.add_title(
|
||||||
|
PriceTitle(
|
||||||
|
code=title_code,
|
||||||
|
kind=PriceKind.UNIT_PRICE,
|
||||||
|
name=f"{node.get('name', code)} ({variant})",
|
||||||
|
spec=variant,
|
||||||
|
unit="㎥",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for machine_code, _name in machines:
|
||||||
|
build.book.add_detail(
|
||||||
|
PriceDetail(
|
||||||
|
title_code,
|
||||||
|
f"X-{machine_code}",
|
||||||
|
Decimal(1) / capacity,
|
||||||
|
note=f"{note} · {source}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
build.variants.setdefault(code, []).append(variant)
|
||||||
|
# 표 줄은 다 읽었다 — 남는 것은 치즐뿐(자재 카탈로그가 없어 금액에 안 붙는 알려진 미결).
|
||||||
|
build.unattached[code] = ["치즐소모량(본/hr) — 자재 단가 층 없음"] if rows[0][2] else []
|
||||||
|
|
||||||
|
|
||||||
|
def _step_titles(build: Any, step: str) -> dict[str, str]:
|
||||||
|
"""갈래 → 그 단계의 제목 코드. 갈래 없는 제목은 빈 문자열 키."""
|
||||||
|
titles = {"": f"B-{step}"} if f"B-{step}" in build.book.titles else {}
|
||||||
|
for code in build.book.titles:
|
||||||
|
if code.startswith(f"B-{step}#"):
|
||||||
|
titles[code.split("#", 1)[1]] = code
|
||||||
|
return titles
|
||||||
|
|
||||||
|
|
||||||
|
def _assemble(build: Any, node: dict[str, Any], names: dict[str, str]) -> None:
|
||||||
|
parent = str(node["work_item_code"])
|
||||||
|
steps = [(str(s["code"]), Decimal(str(s["weight"]))) for s in node.get("steps") or []]
|
||||||
|
per_step: list[tuple[str, Decimal, dict[str, str]]] = []
|
||||||
|
for step, weight in steps:
|
||||||
|
label = f"단계 {step} {names.get(step, '')}".strip()
|
||||||
|
titles = _step_titles(build, step)
|
||||||
|
if not titles:
|
||||||
|
why = build.component_gaps.get(step) or "일위대가가 없습니다"
|
||||||
|
build.component_gaps[parent] = f"{label} 이 안 섰습니다 — {why}"
|
||||||
|
return
|
||||||
|
if step in build.partial_ratio or step in build.basis_missing:
|
||||||
|
why = build.component_gaps.get(step) or "일부 몫만 섰습니다"
|
||||||
|
build.component_gaps[parent] = f"{label} 이 일부만 섰습니다 — {why}"
|
||||||
|
return
|
||||||
|
per_step.append((step, weight, titles))
|
||||||
|
varying = [item for item in per_step if set(item[2]) - {""}]
|
||||||
|
if len(varying) > 1:
|
||||||
|
build.component_gaps[parent] = "갈래가 두 단계 이상에 걸쳐 조립하지 않았습니다"
|
||||||
|
return
|
||||||
|
units = {build.book.titles[t].unit for _, _, ts in per_step for t in ts.values()} - {""}
|
||||||
|
if len(units) > 1:
|
||||||
|
build.component_gaps[parent] = f"단계 단위가 서로 다릅니다 — {', '.join(sorted(units))}"
|
||||||
|
return
|
||||||
|
variants = [v for v in varying[0][2] if v] if varying else [""]
|
||||||
|
for variant in variants:
|
||||||
|
title_code = f"B-{parent}" + (f"#{variant}" if variant else "")
|
||||||
|
build.book.add_title(
|
||||||
|
PriceTitle(
|
||||||
|
code=title_code,
|
||||||
|
kind=PriceKind.UNIT_PRICE,
|
||||||
|
name=f"{names.get(parent, parent)} ({variant})"
|
||||||
|
if variant
|
||||||
|
else names.get(parent, parent),
|
||||||
|
spec=variant or parent,
|
||||||
|
unit=next(iter(units), ""),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for step, weight, titles in per_step:
|
||||||
|
ref = titles.get(variant) or titles[""]
|
||||||
|
build.book.add_detail(
|
||||||
|
PriceDetail(
|
||||||
|
title_code,
|
||||||
|
ref,
|
||||||
|
weight,
|
||||||
|
note=f"단계 합산 × {weight} — {node.get('steps_basis', '')}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if variant:
|
||||||
|
build.variants.setdefault(parent, []).append(variant)
|
||||||
|
if varying and AVERAGE_VARIANT in varying[0][2] and varying[0][0] in AVERAGE_BASIS:
|
||||||
|
build.default_variants[parent] = (AVERAGE_VARIANT, AVERAGE_BASIS[varying[0][0]])
|
||||||
|
build.component_gaps.pop(parent, None)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_parent_steps(build: Any, master: dict[str, Any]) -> None:
|
||||||
|
"""부모 모양을 싣고, 합산형 부모를 조립한다 — `build_unit_prices` 끝(조합 16% 바꿔 달기 전)."""
|
||||||
|
nodes = master.get("work_items", [])
|
||||||
|
names = {str(n.get("work_item_code")): str(n.get("name", "")) for n in nodes}
|
||||||
|
by_code = {str(n.get("work_item_code")): n for n in nodes}
|
||||||
|
for node in nodes:
|
||||||
|
mode = node.get("parent_mode")
|
||||||
|
if mode:
|
||||||
|
build.parent_modes[str(node["work_item_code"])] = str(mode)
|
||||||
|
if mode != "sum_steps":
|
||||||
|
continue
|
||||||
|
for step in node.get("steps") or []:
|
||||||
|
leaf = by_code.get(str(step["code"]))
|
||||||
|
if leaf is not None:
|
||||||
|
_add_rock_leaf(build, leaf)
|
||||||
|
_assemble(build, node, names)
|
||||||
@@ -129,6 +129,10 @@ class UnitPriceBuild:
|
|||||||
#: ⚠ 금액을 막지 않는다. 「표본이 얇다」는 사실을 화면이 말하게 하는 통로다
|
#: ⚠ 금액을 막지 않는다. 「표본이 얇다」는 사실을 화면이 말하게 하는 통로다
|
||||||
#: (지식DB `노임단가_적용 §2-3` 「단가 채택 시 플래그 유지 필요」, 2026-09-09에 이음).
|
#: (지식DB `노임단가_적용 §2-3` 「단가 채택 시 플래그 유지 필요」, 2026-09-09에 이음).
|
||||||
labor_reliability: dict[str, tuple[str, str, str]] = field(default_factory=dict)
|
labor_reliability: dict[str, tuple[str, str, str]] = field(default_factory=dict)
|
||||||
|
#: 부모 공종 → 모양(`choose_one`·`sum_steps`) — 공종 마스터가 단 그대로(명세 2장).
|
||||||
|
parent_modes: dict[str, str] = field(default_factory=dict)
|
||||||
|
#: 합산형 부모 → (갈래, 근거) — 내역 줄의 갈래가 표에 없을 때 **원문이 정한** 갈래(9-4-1 [주]① 평균).
|
||||||
|
default_variants: dict[str, tuple[str, str]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
def _add_labor_titles(book: PriceBook, wages: dict[str, Decimal]) -> None:
|
def _add_labor_titles(book: PriceBook, wages: dict[str, Decimal]) -> None:
|
||||||
@@ -921,6 +925,11 @@ def build_unit_prices(
|
|||||||
covered += machine_share
|
covered += machine_share
|
||||||
if covered < Decimal(100):
|
if covered < Decimal(100):
|
||||||
build.partial_ratio[work_item_code] = covered
|
build.partial_ratio[work_item_code] = covered
|
||||||
|
# 단계 합산형 부모(9-4 암절취 = 암파쇄 + 집토) — 잎이 다 선 뒤, 조합 16% 바꿔 달기 전(명세 2장).
|
||||||
|
from B09_Estimation.B09_Estimation_ParentSteps import attach_parent_steps
|
||||||
|
|
||||||
|
attach_parent_steps(build, master)
|
||||||
|
|
||||||
# 기계 수송비 — 기계경비의 셋째 몫(손료·운전경비·**수송비**). 거리가 있어야 선다.
|
# 기계 수송비 — 기계경비의 셋째 몫(손료·운전경비·**수송비**). 거리가 있어야 선다.
|
||||||
from B09_Estimation.B09_Estimation_Transport import attach_transport
|
from B09_Estimation.B09_Estimation_Transport import attach_transport
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@
|
|||||||
"work_item_code": "FP-09-04",
|
"work_item_code": "FP-09-04",
|
||||||
"basis_unit": "㎥",
|
"basis_unit": "㎥",
|
||||||
"basis_source": "산림사업 표준품셈(고시 2025-82) 원문 L4725·L4738 「Q(시간당 작업량 ㎥/hr)」·「Q=…= ㎥/시간」 — 절 머리에 「(단위: …)」가 없어 마스터가 못 채운 자리(2026-09-08 원문 대조).",
|
"basis_source": "산림사업 표준품셈(고시 2025-82) 원문 L4725·L4738 「Q(시간당 작업량 ㎥/hr)」·「Q=…= ㎥/시간」 — 절 머리에 「(단위: …)」가 없어 마스터가 못 채운 자리(2026-09-08 원문 대조).",
|
||||||
"master_name": "암절취"
|
"master_name": "암절취",
|
||||||
|
"variant_axis": "ground_class",
|
||||||
|
"variant_from": "ground_class",
|
||||||
|
"variant_note": "2026-09-13 축 C Ⓐ — 단계 합산형 부모(9-4·9-5). 암파쇄·깎기 표가 연암·보통암·경암마다 작업능력을 줘 암 갈래 이름을 갈래 값으로 넘김(표에 없는 이름의 처리는 B09 몫)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "흙깎기",
|
"group": "흙깎기",
|
||||||
@@ -36,7 +39,10 @@
|
|||||||
"work_item_code": "FP-09-05",
|
"work_item_code": "FP-09-05",
|
||||||
"basis_unit": "㎥",
|
"basis_unit": "㎥",
|
||||||
"basis_source": "산림사업 표준품셈(고시 2025-82) 원문 L4744 「(단위: ㎥당)」 · L4777 「Q=…= ㎥/시간」 — 절 머리에 「(단위: …)」가 없어 마스터가 못 채운 자리(2026-09-08 원문 대조).",
|
"basis_source": "산림사업 표준품셈(고시 2025-82) 원문 L4744 「(단위: ㎥당)」 · L4777 「Q=…= ㎥/시간」 — 절 머리에 「(단위: …)」가 없어 마스터가 못 채운 자리(2026-09-08 원문 대조).",
|
||||||
"master_name": "발파암"
|
"master_name": "발파암",
|
||||||
|
"variant_axis": "ground_class",
|
||||||
|
"variant_from": "ground_class",
|
||||||
|
"variant_note": "2026-09-13 축 C Ⓐ — 단계 합산형 부모(9-4·9-5). 암파쇄·깎기 표가 연암·보통암·경암마다 작업능력을 줘 암 갈래 이름을 갈래 값으로 넘김(표에 없는 이름의 처리는 B09 몫)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "측구터파기",
|
"group": "측구터파기",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schema_version": "1.0",
|
"schema_version": "1.0",
|
||||||
"dataset_id": "data_work_item_master_manifest",
|
"dataset_id": "data_work_item_master_manifest",
|
||||||
"generated_at": "2026-09-13T17:55:11+09:00",
|
"generated_at": "2026-09-13T18:54:39+09:00",
|
||||||
"built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py",
|
"built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py",
|
||||||
"source": {
|
"source": {
|
||||||
"dataset_id": "pum_forest",
|
"dataset_id": "pum_forest",
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"file": "work_item_master_2026-01-01.json",
|
"file": "work_item_master_2026-01-01.json",
|
||||||
"sha256": "43b6d48175847e1d4011aad4a0597313008f791ec7eba0429d39d279e3e1fcb3",
|
"sha256": "11c49ecde0a611307a82fd98b3625ee6577c01c2f77db6e1f515b41bf83ce685",
|
||||||
"size_bytes": 856831
|
"size_bytes": 861050
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "form_undetermined_2026-01-01.json",
|
"file": "form_undetermined_2026-01-01.json",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"schema_version": "1.0",
|
"schema_version": "1.0",
|
||||||
"dataset_id": "work_item_master_forest",
|
"dataset_id": "work_item_master_forest",
|
||||||
"effective_date": "2026-01-01",
|
"effective_date": "2026-01-01",
|
||||||
"generated_at": "2026-09-13T17:55:11+09:00",
|
"generated_at": "2026-09-13T18:54:39+09:00",
|
||||||
"dataset_version": {
|
"dataset_version": {
|
||||||
"dataset_id": "pum_forest",
|
"dataset_id": "pum_forest",
|
||||||
"effective_date": "2026-01-01",
|
"effective_date": "2026-01-01",
|
||||||
@@ -111,7 +111,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 256,
|
"sort_order": 256,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-01-01",
|
"work_item_code": "FP-01-01",
|
||||||
@@ -120,7 +121,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-01",
|
"parent_code": "FP-01",
|
||||||
"sort_order": 512,
|
"sort_order": 512,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-01-01-01",
|
"work_item_code": "FP-01-01-01",
|
||||||
@@ -233,7 +235,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-01",
|
"parent_code": "FP-01",
|
||||||
"sort_order": 1536,
|
"sort_order": 1536,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-01-02-01",
|
"work_item_code": "FP-01-02-01",
|
||||||
@@ -1790,7 +1793,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-01",
|
"parent_code": "FP-01",
|
||||||
"sort_order": 3840,
|
"sort_order": 3840,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-01-03-01",
|
"work_item_code": "FP-01-03-01",
|
||||||
@@ -2167,7 +2171,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-01",
|
"parent_code": "FP-01",
|
||||||
"sort_order": 4608,
|
"sort_order": 4608,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-01-04-01",
|
"work_item_code": "FP-01-04-01",
|
||||||
@@ -3985,7 +3990,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-01",
|
"parent_code": "FP-01",
|
||||||
"sort_order": 12032,
|
"sort_order": 12032,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-01-07-01",
|
"work_item_code": "FP-01-07-01",
|
||||||
@@ -4143,7 +4149,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 13312,
|
"sort_order": 13312,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-02-01",
|
"work_item_code": "FP-02-01",
|
||||||
@@ -4519,7 +4526,8 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-02-01-01",
|
"work_item_code": "FP-02-01-01",
|
||||||
@@ -6086,7 +6094,8 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-02-02-01",
|
"work_item_code": "FP-02-02-01",
|
||||||
@@ -6747,7 +6756,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 20224,
|
"sort_order": 20224,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-03-01",
|
"work_item_code": "FP-03-01",
|
||||||
@@ -9812,7 +9822,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-03",
|
"parent_code": "FP-03",
|
||||||
"sort_order": 21248,
|
"sort_order": 21248,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-03-04-01",
|
"work_item_code": "FP-03-04-01",
|
||||||
@@ -10231,7 +10242,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 23296,
|
"sort_order": 23296,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-04-01",
|
"work_item_code": "FP-04-01",
|
||||||
@@ -11718,7 +11730,8 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-04-01-01",
|
"work_item_code": "FP-04-01-01",
|
||||||
@@ -12684,7 +12697,8 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-04-02-01",
|
"work_item_code": "FP-04-02-01",
|
||||||
@@ -13144,7 +13158,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 26112,
|
"sort_order": 26112,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-01",
|
"work_item_code": "FP-05-01",
|
||||||
@@ -13153,7 +13168,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 26368,
|
"sort_order": 26368,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-01-01",
|
"work_item_code": "FP-05-01-01",
|
||||||
@@ -13860,7 +13876,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 28160,
|
"sort_order": 28160,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-03-01",
|
"work_item_code": "FP-05-03-01",
|
||||||
@@ -15328,7 +15345,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 32768,
|
"sort_order": 32768,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-16-01",
|
"work_item_code": "FP-05-16-01",
|
||||||
@@ -15488,7 +15506,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 33536,
|
"sort_order": 33536,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-17-01",
|
"work_item_code": "FP-05-17-01",
|
||||||
@@ -15716,7 +15735,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 34560,
|
"sort_order": 34560,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-19-01",
|
"work_item_code": "FP-05-19-01",
|
||||||
@@ -15964,7 +15984,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 35840,
|
"sort_order": 35840,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-22-01",
|
"work_item_code": "FP-05-22-01",
|
||||||
@@ -16142,7 +16163,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 37120,
|
"sort_order": 37120,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-23-01",
|
"work_item_code": "FP-05-23-01",
|
||||||
@@ -16257,7 +16279,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 38144,
|
"sort_order": 38144,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-24-01",
|
"work_item_code": "FP-05-24-01",
|
||||||
@@ -16789,7 +16812,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 39168,
|
"sort_order": 39168,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-26-01",
|
"work_item_code": "FP-05-26-01",
|
||||||
@@ -17050,7 +17074,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-05",
|
"parent_code": "FP-05",
|
||||||
"sort_order": 40448,
|
"sort_order": 40448,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-05-28-01",
|
"work_item_code": "FP-05-28-01",
|
||||||
@@ -17475,7 +17500,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 42752,
|
"sort_order": 42752,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-06-01",
|
"work_item_code": "FP-06-01",
|
||||||
@@ -17539,7 +17565,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-06",
|
"parent_code": "FP-06",
|
||||||
"sort_order": 43264,
|
"sort_order": 43264,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-06-02-01",
|
"work_item_code": "FP-06-02-01",
|
||||||
@@ -17788,7 +17815,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-06",
|
"parent_code": "FP-06",
|
||||||
"sort_order": 44544,
|
"sort_order": 44544,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-06-04-01",
|
"work_item_code": "FP-06-04-01",
|
||||||
@@ -18286,7 +18314,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-06",
|
"parent_code": "FP-06",
|
||||||
"sort_order": 46336,
|
"sort_order": 46336,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-06-07-01",
|
"work_item_code": "FP-06-07-01",
|
||||||
@@ -18529,7 +18558,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 47616,
|
"sort_order": 47616,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-01",
|
"work_item_code": "FP-07-01",
|
||||||
@@ -18538,7 +18568,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 47872,
|
"sort_order": 47872,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-01-01",
|
"work_item_code": "FP-07-01-01",
|
||||||
@@ -19103,7 +19134,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 49152,
|
"sort_order": 49152,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-04-01",
|
"work_item_code": "FP-07-04-01",
|
||||||
@@ -19305,7 +19337,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 49920,
|
"sort_order": 49920,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-05-01",
|
"work_item_code": "FP-07-05-01",
|
||||||
@@ -19585,7 +19618,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 50944,
|
"sort_order": 50944,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-07-01",
|
"work_item_code": "FP-07-07-01",
|
||||||
@@ -19817,7 +19851,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 51712,
|
"sort_order": 51712,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-08-01",
|
"work_item_code": "FP-07-08-01",
|
||||||
@@ -20102,7 +20137,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 52736,
|
"sort_order": 52736,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-09-01",
|
"work_item_code": "FP-07-09-01",
|
||||||
@@ -20945,7 +20981,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-07",
|
"parent_code": "FP-07",
|
||||||
"sort_order": 54784,
|
"sort_order": 54784,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-07-15-01",
|
"work_item_code": "FP-07-15-01",
|
||||||
@@ -21370,7 +21407,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 56320,
|
"sort_order": 56320,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-08-01",
|
"work_item_code": "FP-08-01",
|
||||||
@@ -21379,7 +21417,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-08",
|
"parent_code": "FP-08",
|
||||||
"sort_order": 56576,
|
"sort_order": 56576,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-08-01-01",
|
"work_item_code": "FP-08-01-01",
|
||||||
@@ -21563,7 +21602,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-08",
|
"parent_code": "FP-08",
|
||||||
"sort_order": 57344,
|
"sort_order": 57344,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-08-02-01",
|
"work_item_code": "FP-08-02-01",
|
||||||
@@ -26590,7 +26630,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-08",
|
"parent_code": "FP-08",
|
||||||
"sort_order": 59648,
|
"sort_order": 59648,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-08-06-01",
|
"work_item_code": "FP-08-06-01",
|
||||||
@@ -27436,7 +27477,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-08",
|
"parent_code": "FP-08",
|
||||||
"sort_order": 60928,
|
"sort_order": 60928,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-08-08-01",
|
"work_item_code": "FP-08-08-01",
|
||||||
@@ -27762,7 +27804,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 62464,
|
"sort_order": 62464,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-01",
|
"work_item_code": "FP-09-01",
|
||||||
@@ -27823,7 +27866,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 63232,
|
"sort_order": 63232,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-03-01",
|
"work_item_code": "FP-09-03-01",
|
||||||
@@ -27949,7 +27993,19 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 64000,
|
"sort_order": 64000,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "sum_steps",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"code": "FP-09-04-01",
|
||||||
|
"weight": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FP-09-04-02",
|
||||||
|
"weight": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"steps_basis": "품셈 9-4 암절취 = 9-4-1 암파쇄 + 9-4-2 집토 — 같은 ㎥ 를 깨고 모음(지식DB 토공_수량 「암절취 (암파쇄ㆍ집토) | 9-4」)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-04-01",
|
"work_item_code": "FP-09-04-01",
|
||||||
@@ -28089,7 +28145,23 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 64768,
|
"sort_order": 64768,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "sum_steps",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"code": "FP-09-05-01",
|
||||||
|
"weight": "0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FP-09-05-02",
|
||||||
|
"weight": "0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FP-09-05-03",
|
||||||
|
"weight": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"steps_basis": "품셈 9-5 발파암 — 절 제목 「9-5-1 육상, 암석 절취(발파 10%)」·「9-5-2 깎기(90%)」·「9-5-3 집토」(지식DB 토공_수량 「발파암 (절취 10 %ㆍ깎기 90 %ㆍ집토) | 9-5」)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-05-01",
|
"work_item_code": "FP-09-05-01",
|
||||||
@@ -28340,7 +28412,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 65792,
|
"sort_order": 65792,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-06-01",
|
"work_item_code": "FP-09-06-01",
|
||||||
@@ -28394,7 +28467,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 66304,
|
"sort_order": 66304,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-07-01",
|
"work_item_code": "FP-09-07-01",
|
||||||
@@ -28501,7 +28575,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 67072,
|
"sort_order": 67072,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-08-01",
|
"work_item_code": "FP-09-08-01",
|
||||||
@@ -28731,7 +28806,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 68096,
|
"sort_order": 68096,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-10-01",
|
"work_item_code": "FP-09-10-01",
|
||||||
@@ -28832,7 +28908,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 68864,
|
"sort_order": 68864,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-11-01",
|
"work_item_code": "FP-09-11-01",
|
||||||
@@ -28936,7 +29013,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 69376,
|
"sort_order": 69376,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-12-01",
|
"work_item_code": "FP-09-12-01",
|
||||||
@@ -29242,7 +29320,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 70400,
|
"sort_order": 70400,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-13-01",
|
"work_item_code": "FP-09-13-01",
|
||||||
@@ -30742,7 +30821,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 75264,
|
"sort_order": 75264,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-14-01",
|
"work_item_code": "FP-09-14-01",
|
||||||
@@ -30913,7 +30993,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 76032,
|
"sort_order": 76032,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-15-01",
|
"work_item_code": "FP-09-15-01",
|
||||||
@@ -31086,7 +31167,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 76800,
|
"sort_order": 76800,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-16-01",
|
"work_item_code": "FP-09-16-01",
|
||||||
@@ -31303,7 +31385,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 77824,
|
"sort_order": 77824,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-17-01",
|
"work_item_code": "FP-09-17-01",
|
||||||
@@ -31483,7 +31566,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 78848,
|
"sort_order": 78848,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-19-01",
|
"work_item_code": "FP-09-19-01",
|
||||||
@@ -31767,7 +31851,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-09",
|
"parent_code": "FP-09",
|
||||||
"sort_order": 79872,
|
"sort_order": 79872,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-09-20-01",
|
"work_item_code": "FP-09-20-01",
|
||||||
@@ -32032,7 +32117,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 81152,
|
"sort_order": 81152,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-01",
|
"work_item_code": "FP-10-01",
|
||||||
@@ -32139,7 +32225,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 81920,
|
"sort_order": 81920,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-03-01",
|
"work_item_code": "FP-10-03-01",
|
||||||
@@ -32452,7 +32539,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 83456,
|
"sort_order": 83456,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-06-01",
|
"work_item_code": "FP-10-06-01",
|
||||||
@@ -32703,7 +32791,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 84480,
|
"sort_order": 84480,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-07-01",
|
"work_item_code": "FP-10-07-01",
|
||||||
@@ -33141,7 +33230,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 85760,
|
"sort_order": 85760,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-08-01",
|
"work_item_code": "FP-10-08-01",
|
||||||
@@ -33351,7 +33441,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 86784,
|
"sort_order": 86784,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-09-01",
|
"work_item_code": "FP-10-09-01",
|
||||||
@@ -33471,7 +33562,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 87296,
|
"sort_order": 87296,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-10-01",
|
"work_item_code": "FP-10-10-01",
|
||||||
@@ -33727,7 +33819,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 88320,
|
"sort_order": 88320,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-12-01",
|
"work_item_code": "FP-10-12-01",
|
||||||
@@ -33890,7 +33983,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-10",
|
"parent_code": "FP-10",
|
||||||
"sort_order": 89344,
|
"sort_order": 89344,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-10-13-01",
|
"work_item_code": "FP-10-13-01",
|
||||||
@@ -34054,7 +34148,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 90112,
|
"sort_order": 90112,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-11-01",
|
"work_item_code": "FP-11-01",
|
||||||
@@ -34310,7 +34405,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 91392,
|
"sort_order": 91392,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-01",
|
"work_item_code": "FP-12-01",
|
||||||
@@ -34319,7 +34415,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 91648,
|
"sort_order": 91648,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-01-01",
|
"work_item_code": "FP-12-01-01",
|
||||||
@@ -35377,7 +35474,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 93952,
|
"sort_order": 93952,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-07-01",
|
"work_item_code": "FP-12-07-01",
|
||||||
@@ -35571,7 +35669,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 94976,
|
"sort_order": 94976,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-09-01",
|
"work_item_code": "FP-12-09-01",
|
||||||
@@ -35938,7 +36037,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 96256,
|
"sort_order": 96256,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-11-01",
|
"work_item_code": "FP-12-11-01",
|
||||||
@@ -36919,7 +37019,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 98560,
|
"sort_order": 98560,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-17-01",
|
"work_item_code": "FP-12-17-01",
|
||||||
@@ -37523,7 +37624,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 101120,
|
"sort_order": 101120,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-24-01",
|
"work_item_code": "FP-12-24-01",
|
||||||
@@ -37845,7 +37947,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 102400,
|
"sort_order": 102400,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-24-01",
|
"work_item_code": "FP-12-24-01",
|
||||||
@@ -38211,7 +38314,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 103936,
|
"sort_order": 103936,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-30-01",
|
"work_item_code": "FP-12-30-01",
|
||||||
@@ -38574,7 +38678,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 105472,
|
"sort_order": 105472,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-34-01",
|
"work_item_code": "FP-12-34-01",
|
||||||
@@ -39018,7 +39123,19 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-12",
|
"parent_code": "FP-12",
|
||||||
"sort_order": 107520,
|
"sort_order": 107520,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "sum_steps",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"code": "FP-12-38-02",
|
||||||
|
"weight": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FP-12-38-03",
|
||||||
|
"weight": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"steps_basis": "품셈 12-38 유로폼 = 12-38-2 사용수량(자재) + 12-38-3 설치 및 해체(품) — 12-38-1 사용횟수는 금액 단계가 아니라 사용수량의 잔존율 조건"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-12-38-01",
|
"work_item_code": "FP-12-38-01",
|
||||||
@@ -39280,7 +39397,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 108544,
|
"sort_order": 108544,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-01",
|
"work_item_code": "FP-13-01",
|
||||||
@@ -39298,7 +39416,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 109056,
|
"sort_order": 109056,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-02-01",
|
"work_item_code": "FP-13-02-01",
|
||||||
@@ -39785,7 +39904,8 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-03-01",
|
"work_item_code": "FP-13-03-01",
|
||||||
@@ -39936,7 +40056,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 111104,
|
"sort_order": 111104,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-04-01",
|
"work_item_code": "FP-13-04-01",
|
||||||
@@ -40800,7 +40921,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 112896,
|
"sort_order": 112896,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-05-01",
|
"work_item_code": "FP-13-05-01",
|
||||||
@@ -40959,7 +41081,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 113664,
|
"sort_order": 113664,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-06-01",
|
"work_item_code": "FP-13-06-01",
|
||||||
@@ -41252,7 +41375,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 114688,
|
"sort_order": 114688,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-07-01",
|
"work_item_code": "FP-13-07-01",
|
||||||
@@ -41564,7 +41688,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 115968,
|
"sort_order": 115968,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-10-01",
|
"work_item_code": "FP-13-10-01",
|
||||||
@@ -41921,7 +42046,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 116736,
|
"sort_order": 116736,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-11-01",
|
"work_item_code": "FP-13-11-01",
|
||||||
@@ -42399,7 +42525,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 118016,
|
"sort_order": 118016,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-12-01",
|
"work_item_code": "FP-13-12-01",
|
||||||
@@ -42587,7 +42714,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 118784,
|
"sort_order": 118784,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-13-01",
|
"work_item_code": "FP-13-13-01",
|
||||||
@@ -42922,7 +43050,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 119808,
|
"sort_order": 119808,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-15-01",
|
"work_item_code": "FP-13-15-01",
|
||||||
@@ -43092,7 +43221,8 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"parent_code": "FP-13",
|
"parent_code": "FP-13",
|
||||||
"sort_order": 120832,
|
"sort_order": 120832,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-13-16-01",
|
"work_item_code": "FP-13-16-01",
|
||||||
@@ -43273,7 +43403,8 @@
|
|||||||
"level": 1,
|
"level": 1,
|
||||||
"parent_code": null,
|
"parent_code": null,
|
||||||
"sort_order": 121600,
|
"sort_order": 121600,
|
||||||
"tables": []
|
"tables": [],
|
||||||
|
"parent_mode": "choose_one"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"work_item_code": "FP-14-01",
|
"work_item_code": "FP-14-01",
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
"""부모 공종 모양 `parent_mode` (명세 2장 · 2026-09-13 축 C Ⓐ).
|
||||||
|
|
||||||
|
지키는 것
|
||||||
|
① 마스터 — 부모는 기본 choose_one, 합산형(9-4·9-5·12-38)만 사람이 적은 단계·가중치를 든다
|
||||||
|
② 흙깎기 리핑암 = 9-4 암절취 = 암파쇄(암질별 1/Q) + 집토 — 부모 제목이 잎들의 합으로 선다
|
||||||
|
③ 내역 줄의 암 갈래가 그대로 갈래 값으로 가고, 표에 없는 암질은 9-4-1 [주]① 평균으로만 선다
|
||||||
|
④ 단계 하나라도 못 서면 부모를 안 세우고 사유 · 갈래 고르기형 부모를 가리키면 오류로 막는다
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||||||
|
from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill
|
||||||
|
from B09_Estimation.B09_Estimation_ResourceAxis_Sources import load_work_item_master
|
||||||
|
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _build():
|
||||||
|
return build_unit_prices()
|
||||||
|
|
||||||
|
|
||||||
|
def _node(code: str) -> dict:
|
||||||
|
return next(n for n in load_work_item_master()["work_items"] if n["work_item_code"] == code)
|
||||||
|
|
||||||
|
|
||||||
|
def test_마스터_부모_모양은_기본_고르기_합산형만_단계를_든다():
|
||||||
|
assert _node("FP-10-12")["parent_mode"] == "choose_one"
|
||||||
|
rock = _node("FP-09-05")
|
||||||
|
assert rock["parent_mode"] == "sum_steps"
|
||||||
|
assert [(s["code"], s["weight"]) for s in rock["steps"]] == [
|
||||||
|
("FP-09-05-01", "0.1"),
|
||||||
|
("FP-09-05-02", "0.9"),
|
||||||
|
("FP-09-05-03", "1"),
|
||||||
|
]
|
||||||
|
assert "parent_mode" not in _node("FP-09-04-01") # 잎
|
||||||
|
|
||||||
|
|
||||||
|
def test_암절취는_암파쇄와_집토의_합으로_선다():
|
||||||
|
book = _build().book
|
||||||
|
leaf = book.details["B-FP-09-04-01#연암"]
|
||||||
|
assert {d.ref_code for d in leaf} == {"X-0201-0070#조합", "X-0230-0007"} # 조합 16 %
|
||||||
|
assert all(d.quantity == Decimal(1) / Decimal("5.0") for d in leaf)
|
||||||
|
whole = book.resolve("B-FP-09-04#연암").total
|
||||||
|
parts = book.resolve("B-FP-09-04-01#연암").total + book.resolve("B-FP-09-04-02").total
|
||||||
|
assert whole == parts > 0
|
||||||
|
assert "B-FP-09-04#평균" in book.titles # [주]① 평균
|
||||||
|
|
||||||
|
|
||||||
|
def test_단계가_못_서면_부모를_안_세우고_사유를_남긴다():
|
||||||
|
build = _build()
|
||||||
|
assert not any(code.startswith("B-FP-09-05#") for code in build.book.titles)
|
||||||
|
assert "FP-09-05-01" in build.component_gaps["FP-09-05"] # 발파 착암기 층 없음
|
||||||
|
assert "FP-12-38-02" in build.component_gaps["FP-12-38"] # 사용수량 미구현
|
||||||
|
# 깎기 표엔 기계 이름이 없음 — [주] 원문의 기종으로 섬(부모에서 물려받지 않음)
|
||||||
|
assert "[주]" in build.book.details["B-FP-09-05-02#경암"][0].note
|
||||||
|
|
||||||
|
|
||||||
|
def _bill(rock: str, method: str = "ripping"):
|
||||||
|
handoff = build_handoff(
|
||||||
|
summary_table={
|
||||||
|
"rows": [{"group": "흙깎기", "item": rock, "spec": "", "unit": "㎥", "amount": 10.0}]
|
||||||
|
},
|
||||||
|
ground_methods={rock: method},
|
||||||
|
)
|
||||||
|
return handoff["work_items"][0], build_bill(handoff, build=_build())
|
||||||
|
|
||||||
|
|
||||||
|
def test_암_갈래가_그대로_단가를_고른다():
|
||||||
|
row, bill = _bill("보통암")
|
||||||
|
assert (row["work_item_code"], row["variant_value"]) == ("FP-09-04", "보통암")
|
||||||
|
priced = next(r for r in bill.rows if not r.is_group and r.name == "흙깎기")
|
||||||
|
assert priced.unit_price_krw and "보통암" in priced.spec
|
||||||
|
|
||||||
|
|
||||||
|
def test_표에_없는_암질은_주1_평균으로만_선다():
|
||||||
|
_row, bill = _bill("풍화암")
|
||||||
|
priced = next(r for r in bill.rows if not r.is_group and r.name == "흙깎기")
|
||||||
|
assert priced.unit_price_krw and "[주]①" in priced.note
|
||||||
|
|
||||||
|
|
||||||
|
def test_발파암은_단계_사유로_막히고_고르기형_부모는_오류():
|
||||||
|
_row, bill = _bill("경암", "blasting")
|
||||||
|
assert any("단계 합산 미완" in m["reason"] for m in bill.missing)
|
||||||
|
handoff = {
|
||||||
|
"work_items": [{**_row, "work_item_code": "FP-10-12", "variant_value": None}],
|
||||||
|
"materials": [],
|
||||||
|
}
|
||||||
|
blocked = build_bill(handoff, build=_build())
|
||||||
|
assert any("갈래 고르기형 부모" in m["reason"] for m in blocked.missing)
|
||||||
Reference in New Issue
Block a user