feat(B08): 거푸집 사용횟수 + 공종코드 잇기
- 거푸집 사용횟수는 **관측값이 아니라 법**임. 품셈 1-7-1 이 구조물 종류별로 정해 둠(옹벽 3회 · 보호공 기초 6회). 원문 문구를 데이터에 싣고 우리 구조물이 어느 예시에 걸리는지 적음. 걸리는 예시가 없으면 「사용횟수 미확보」. - ⚠ 횟수별 재료 환산(품셈 12-4 합판 3회 46.1 %)은 **하지 않음**. 그 비율은 일위대가 재료비에 걸리는 값이라 B08 이 곱하면 B09 와 겹쳐 두 번 줌. B08 이 내는 것은 접촉 면적 그대로 + 몇 회짜리인가까지. 시험으로 못 박음. - 동바리는 슬래브를 떠받칠 때 쓰는 것이라 지금 서는 구조물(옹벽·집수정)은 대상이 아님. 0 으로 적지 않고 「대상 없음 + 사유」로 냄. - 거푸집 이름은 정확 일치로만 봄 — 부분일치면 「거푸집씻기」(공사용수)가 걸림. 공종코드 잇기 - 집수정 → FP-12-15, 물넘이포장 → FP-12-06 로 이음. - 옹벽은 **품셈 12장에 그 이름의 공종이 없음**. 빈 코드로 두면 「매핑을 못 찾은 줄」과 구별이 안 되므로 `composite` 로 묶음(타설+거푸집+철근+기초잡석)을 적음. 일위대가 조립은 B09 몫이고 B08 은 물량과 묶음만 넘김. 검증 — 거푸집 8건 · 인계 45건 통과, 전체 558 passed. tsc 오류 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""거푸집 사용횟수 — 접촉 면적에 **몇 회 쓰는 거푸집인지**를 붙인다 (B08 일감 ⑩).
|
||||
|
||||
⚠ 사용횟수는 **관측값이 아니라 법이다**
|
||||
품셈 1-7-1 이 구조물 종류별로 정해 둔다 — 「3회 … 옹벽, 파라펫트, 날개벽 등 약간 복잡한
|
||||
구조」. 그래서 실무 관측값으로 갈음하지 않고 **원문 문구를 그대로 데이터에 싣고** 우리
|
||||
구조물이 그 줄의 어느 예시에 걸리는지를 적는다. 걸리는 예시가 없으면 지어내지 않는다.
|
||||
|
||||
⚠⚠ **횟수별 재료 환산은 여기서 하지 않는다** (이중계상)
|
||||
품셈 12-4 의 「사용횟수별 기준수량에 대한 비율(%)」(합판 3회 46.1 % 등)은 **일위대가
|
||||
재료비**에 걸리는 값이다. B08 이 면적에 그 비율을 곱해 넘기면 B09 가 또 곱해 두 번 준다.
|
||||
**B08 이 내는 것은 「접촉 면적 + 몇 회짜리인가」까지다.** 비율표는 참고로만 싣는다.
|
||||
|
||||
⚠ 동바리는 지금 대상이 없다
|
||||
강관동바리(12-20)는 **슬래브를 떠받칠 때** 쓴다. 지금 서는 구조물(옹벽·집수정)은 벽체
|
||||
거푸집뿐이라 대상이 아니고, 대상이 될 BOX암거·세월교는 치수·원단위가 미확보라
|
||||
슬래브 면적 자체가 안 나온다. **없는 것을 0 으로 적지 않고 「대상 없음」이라고 말한다.**
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
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_"
|
||||
|
||||
#: 거푸집으로 보는 성분 이름. 정확히 같은 이름으로만 본다 — 부분일치면 「거푸집씻기」가 걸린다.
|
||||
FORMWORK_NAMES = frozenset({"합판거푸집", "유로폼", "문양거푸집", "거푸집"})
|
||||
|
||||
NOTE_REUSE_MISSING = "사용횟수 미확보"
|
||||
NOTE_NOT_APPLICABLE = "거푸집 대상 아님"
|
||||
|
||||
|
||||
def _latest_dataset_path(directory: Path | None = None) -> Path | None:
|
||||
folder = directory or DATASET_DIR
|
||||
if not folder.is_dir():
|
||||
return None
|
||||
files = sorted(folder.glob(DATASET_PREFIX + "*.json"))
|
||||
return files[-1] if files else None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FormworkTable:
|
||||
"""사용횟수표 한 판."""
|
||||
|
||||
effective_date: str = ""
|
||||
source: dict[str, Any] = field(default_factory=dict)
|
||||
type_map: list[dict[str, Any]] = field(default_factory=list)
|
||||
reuse_by_class: list[dict[str, Any]] = field(default_factory=list)
|
||||
reuse_ratio_pct: dict[str, Any] = field(default_factory=dict)
|
||||
shoring: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def for_type(self, type_id: str) -> dict[str, Any] | None:
|
||||
for row in self.type_map:
|
||||
if row.get("type_id") == type_id:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def load_formwork_table(path: Path | None = None) -> FormworkTable:
|
||||
"""사용횟수표를 읽는다. 파일이 없으면 **빈 표** — 전부 「미확보」로 드러난다."""
|
||||
target = path or _latest_dataset_path()
|
||||
if target is None or not target.is_file():
|
||||
return FormworkTable()
|
||||
payload = json.loads(target.read_text(encoding="utf-8"))
|
||||
return FormworkTable(
|
||||
effective_date=str(payload.get("effective_date") or ""),
|
||||
source=payload.get("source") or {},
|
||||
type_map=list(payload.get("type_map") or []),
|
||||
reuse_by_class=list(payload.get("reuse_by_class") or []),
|
||||
reuse_ratio_pct=payload.get("reuse_ratio_pct") or {},
|
||||
shoring=payload.get("shoring") or {},
|
||||
)
|
||||
|
||||
|
||||
def annotate(
|
||||
structures: list[dict[str, Any]], table: FormworkTable | None = None
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""산출물의 거푸집 성분에 사용횟수를 달아 준다. (알림, 미확보 종류) 를 돌려준다.
|
||||
|
||||
성분 딕셔너리를 **그 자리에서** 고친다 — 거푸집 줄만 손대고 나머지는 건드리지 않는다.
|
||||
"""
|
||||
found = table or load_formwork_table()
|
||||
notes: list[str] = []
|
||||
missing: list[str] = []
|
||||
for structure in structures:
|
||||
type_id = str(structure.get("type_id") or "")
|
||||
entry = found.for_type(type_id)
|
||||
targets = [
|
||||
component
|
||||
for component in structure.get("components") or []
|
||||
if str(component.get("name") or "").strip() in FORMWORK_NAMES
|
||||
]
|
||||
if not targets:
|
||||
continue
|
||||
if entry is None:
|
||||
missing.append(type_id)
|
||||
for component in targets:
|
||||
component["reuse_count"] = None
|
||||
component["reuse_note"] = NOTE_REUSE_MISSING
|
||||
continue
|
||||
count = entry.get("reuse_count")
|
||||
for component in targets:
|
||||
component["reuse_count"] = count
|
||||
component["reuse_note"] = (
|
||||
f"품셈 1-7-1 {count}회 — 「{entry.get('matched_example')}」"
|
||||
if count
|
||||
else NOTE_NOT_APPLICABLE
|
||||
)
|
||||
if count:
|
||||
notes.append(f"{structure.get('name') or type_id} 거푸집 {count}회 (품셈 1-7-1)")
|
||||
else:
|
||||
missing.append(type_id)
|
||||
return notes, sorted(set(missing))
|
||||
|
||||
|
||||
def shoring_status(table: FormworkTable | None = None) -> dict[str, Any]:
|
||||
"""동바리 — **대상이 없으면 없다고 말한다.** 0 으로 적으면 「없음」과 구별이 안 된다."""
|
||||
found = table or load_formwork_table()
|
||||
shoring = found.shoring or {}
|
||||
return {
|
||||
"applicable": False,
|
||||
"reason": str(shoring.get("note") or "슬래브 구조물이 없어 동바리 대상이 아님"),
|
||||
"pending_types": list(shoring.get("targets_pending") or []),
|
||||
}
|
||||
@@ -85,6 +85,7 @@ class WorkItemMapping:
|
||||
haul: list[dict[str, Any]] = field(default_factory=list)
|
||||
structure: list[dict[str, Any]] = field(default_factory=list)
|
||||
pending_user: dict[str, Any] = field(default_factory=dict)
|
||||
composite: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def for_earthwork(self, group: str, ground: str | None) -> dict[str, Any] | None: # noqa: D401
|
||||
"""공종+지반유형으로 찾는다. 지반이 갈리지 않는 공종은 `ground` 없는 줄이 받는다."""
|
||||
@@ -111,6 +112,16 @@ class WorkItemMapping:
|
||||
return row
|
||||
return None
|
||||
|
||||
def composite_for(self, type_id: str) -> dict[str, Any] | None:
|
||||
"""품셈에 그 이름의 공종이 없어 **여러 공종을 묶는** 자리인가.
|
||||
|
||||
빈 코드로 두면 「매핑을 못 찾은 줄」과 구별이 안 된다 — 묶음을 적어 구별한다.
|
||||
"""
|
||||
for row in self.composite.get("items") or []:
|
||||
if row.get("type_id") == type_id:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def load_mapping(path: Path | None = None) -> WorkItemMapping:
|
||||
"""매핑표를 읽는다. 파일이 없으면 **빈 표** — 전 줄이 `unmatched` 로 드러난다."""
|
||||
@@ -124,6 +135,7 @@ def load_mapping(path: Path | None = None) -> WorkItemMapping:
|
||||
haul=list(payload.get("haul") or []),
|
||||
structure=list(payload.get("structure") or []),
|
||||
pending_user=payload.get("pending_user") or {},
|
||||
composite=payload.get("composite") or {},
|
||||
)
|
||||
|
||||
|
||||
@@ -263,7 +275,8 @@ def _structure_rows(
|
||||
type_id = str(structure.get("type_id") or "")
|
||||
entry = mapping.for_structure(type_id) or {}
|
||||
code = entry.get("work_item_code")
|
||||
if code is None:
|
||||
composite = mapping.composite_for(type_id) if code is None else None
|
||||
if code is None and composite is None:
|
||||
unmatched.append(f"구조물({type_id})")
|
||||
length = float(structure.get("length_m") or 0.0)
|
||||
rows.append(
|
||||
@@ -283,8 +296,10 @@ def _structure_rows(
|
||||
"station_from": structure.get("start_m"),
|
||||
"station_to": structure.get("end_m"),
|
||||
"spec_detail": _spec_detail(structure),
|
||||
# 품셈에 그 이름의 공종이 없어 여러 공종을 묶는 자리 — 빈 코드와 구별한다.
|
||||
"composite_parts": (composite or {}).get("parts"),
|
||||
"in_bill": True,
|
||||
"in_bill_reason": "",
|
||||
"in_bill_reason": (composite or {}).get("why", ""),
|
||||
"origin": ORIGIN_STRUCTURE,
|
||||
}
|
||||
)
|
||||
@@ -422,8 +437,12 @@ def verify_bill_flags(handoff: dict[str, Any]) -> list[str]:
|
||||
"""
|
||||
found: list[str] = []
|
||||
for row in handoff.get("work_items") or []:
|
||||
if row.get("in_bill") and not row.get("work_item_code"):
|
||||
found.append(str(row.get("name")))
|
||||
if not row.get("in_bill") or row.get("work_item_code"):
|
||||
continue
|
||||
# 묶음으로 서는 줄은 코드가 없어도 정상이다 — 무엇으로 묶이는지 적혀 있다.
|
||||
if row.get("composite_parts"):
|
||||
continue
|
||||
found.append(str(row.get("name")))
|
||||
return found
|
||||
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ from B08_Quantity.B08_Quantity_Engine_ObservedUnit import (
|
||||
expand_observed,
|
||||
load_observed_table,
|
||||
)
|
||||
from B08_Quantity.B08_Quantity_Engine_Formwork import annotate as annotate_formwork
|
||||
from B08_Quantity.B08_Quantity_Engine_Formwork import shoring_status
|
||||
from common_util.common_util_quantity_spread import spread_by_unit
|
||||
|
||||
# ── 계수표 — 식에 박지 않고 여기서 고른다 ─────────────────────────────
|
||||
@@ -406,32 +408,40 @@ def build_table(
|
||||
)
|
||||
entry["amount"] += component.amount
|
||||
|
||||
payload_structures = [
|
||||
{
|
||||
"structure_id": item.structure_id,
|
||||
"type_id": item.type_id,
|
||||
"name": item.name,
|
||||
"length_m": item.length_m,
|
||||
"height_m": item.height_m,
|
||||
"start_m": item.start_m,
|
||||
"end_m": item.end_m,
|
||||
"notes": item.notes,
|
||||
"components": [
|
||||
{
|
||||
"name": component.name,
|
||||
"unit": component.unit,
|
||||
"amount": component.amount,
|
||||
"destination": component.destination,
|
||||
"basis": component.basis,
|
||||
"basis_kind": component.basis_kind,
|
||||
"source": component.source,
|
||||
}
|
||||
for component in item.components
|
||||
],
|
||||
}
|
||||
for item in quantities
|
||||
]
|
||||
# 거푸집 줄에 **몇 회짜리인지**를 달아 준다. 횟수별 재료 환산은 하지 않는다(B09 몫).
|
||||
formwork_notes, formwork_missing = annotate_formwork(payload_structures)
|
||||
|
||||
return {
|
||||
"structures": [
|
||||
{
|
||||
"structure_id": item.structure_id,
|
||||
"type_id": item.type_id,
|
||||
"name": item.name,
|
||||
"length_m": item.length_m,
|
||||
"height_m": item.height_m,
|
||||
"start_m": item.start_m,
|
||||
"end_m": item.end_m,
|
||||
"notes": item.notes,
|
||||
"components": [
|
||||
{
|
||||
"name": component.name,
|
||||
"unit": component.unit,
|
||||
"amount": component.amount,
|
||||
"destination": component.destination,
|
||||
"basis": component.basis,
|
||||
"basis_kind": component.basis_kind,
|
||||
"source": component.source,
|
||||
}
|
||||
for component in item.components
|
||||
],
|
||||
}
|
||||
for item in quantities
|
||||
],
|
||||
"structures": payload_structures,
|
||||
"formwork_notes": formwork_notes,
|
||||
"formwork_reuse_missing": formwork_missing,
|
||||
# 동바리 — 대상이 없으면 0 이 아니라 「없음」이라고 말한다.
|
||||
"shoring": shoring_status(),
|
||||
"totals": sorted(totals.values(), key=lambda entry: entry["name"]),
|
||||
# 할증 전 값임을 응답에 못 박는다 — 자재총괄이 한 번만 붙인다(㉠).
|
||||
"surcharge_applied": False,
|
||||
|
||||
@@ -55,6 +55,9 @@ export interface UnitQuantityStructure {
|
||||
/** 값이 어디서 왔나 — `derived`(치수에서 식으로) / `observed`(실무 관측 원단위표). */
|
||||
basis_kind?: string;
|
||||
source?: string;
|
||||
/** 거푸집 줄만 — 몇 회짜리인가(품셈 1-7-1). 횟수별 재료 환산은 B09 몫이다. */
|
||||
reuse_count?: number | null;
|
||||
reuse_note?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
@@ -71,6 +74,13 @@ export interface MaterialResponse {
|
||||
structure_count: number;
|
||||
}
|
||||
|
||||
/** 거푸집·동바리 안내에 쓰는 값. */
|
||||
export interface FormworkInfo {
|
||||
formwork_notes?: string[];
|
||||
formwork_reuse_missing?: string[];
|
||||
shoring?: { applicable: boolean; reason: string; pending_types: string[] };
|
||||
}
|
||||
|
||||
/** 성분이 어디로 가는지 — 화면에서도 보이게 한다. 규칙이 코드에만 있으면 잊힌다. */
|
||||
const DESTINATION_LABELS: Record<string, string> = {
|
||||
earthwork: "토공 합산",
|
||||
@@ -297,6 +307,20 @@ export function renderUnitQuantityGrid(response: MaterialResponse): HTMLElement
|
||||
const skipped = warning("건너뛴 구조물", response.skipped_structures);
|
||||
if (skipped) wrap.append(skipped);
|
||||
|
||||
// 거푸집 사용횟수 — 값이 아니라 **몇 회짜리인지**를 알려 주는 자리(품셈 1-7-1).
|
||||
const info = unit as unknown as FormworkInfo;
|
||||
const reuse = warning("거푸집 사용횟수", info.formwork_notes ?? []);
|
||||
if (reuse) wrap.append(reuse);
|
||||
const reuseMissing = warning("사용횟수 미확보", info.formwork_reuse_missing ?? []);
|
||||
if (reuseMissing) wrap.append(reuseMissing);
|
||||
if (info.shoring && !info.shoring.applicable) {
|
||||
// 「없음」을 0 으로 적지 않는다 — 대상이 없는 것과 값이 0 인 것은 다르다.
|
||||
const line = document.createElement("p");
|
||||
line.className = "b08-grid__caption";
|
||||
line.textContent = `동바리: 대상 없음 — ${info.shoring.reason.replace(/\*\*/g, "")}`;
|
||||
wrap.append(line);
|
||||
}
|
||||
|
||||
if (!unit.structures.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b08-quantity__message";
|
||||
@@ -338,7 +362,8 @@ export function renderUnitQuantityGrid(response: MaterialResponse): HTMLElement
|
||||
tr.append(textCell(component.basis, "b08-grid__note"));
|
||||
// ⚠ 식에서 나온 값과 실무 관측값이 한 표에 섞인다 — 어느 쪽인지 화면에서 보여야
|
||||
// 나중에 「이 값이 왜 이런가」를 되짚을 수 있다.
|
||||
tr.append(textCell(component.basis_kind === "observed" ? "실무 관측" : "치수 전개"));
|
||||
const kind = component.basis_kind === "observed" ? "실무 관측" : "치수 전개";
|
||||
tr.append(textCell(component.reuse_count ? `${kind} · ${component.reuse_count}회` : kind));
|
||||
body.append(tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "formwork_reuse",
|
||||
"effective_date": "2026-01-01",
|
||||
"note": "거푸집 사용횟수 — **품셈 1-7-1 원문**이 구조물 종류별로 정해 둔 값이다. 관측값이 아니라 법이므로 실무값으로 갈음하지 않는다.",
|
||||
"source": {
|
||||
"doc": "산림사업 표준품셈 1-7-1 거푸집 사용",
|
||||
"table_id": "F0040",
|
||||
"quote": "2회 T형보, 난간, 특히 복잡한 구조의 교각, 교대, 수문관의 본체 등 복잡한 구조 / 3회 슬래브, 교대, 교각, 옹벽, 파라펫트, 날개벽 등 약간 복잡한 구조 / 4회 측구, 수로, 확대기초, 우물통 등 비교적 간단한 구조 / 6회 수문 또는 관의 기초, 호안 및 보호공의 기초 등 극히 간단한 구조"
|
||||
},
|
||||
"policy": {
|
||||
"b08_delivers": "접촉 면적(㎡) + 사용횟수. **횟수별 재료 환산은 하지 않는다**.",
|
||||
"b09_applies": "품셈 12-4 의 「사용횟수별 기준수량에 대한 비율(%)」은 일위대가 재료비에 걸린다. B08 이 여기서 곱하면 B09 와 겹쳐 두 번 준다.",
|
||||
"unlisted_is_flagged": true
|
||||
},
|
||||
"reuse_by_class": [
|
||||
{
|
||||
"reuse_count": 2,
|
||||
"class": "복잡한 구조",
|
||||
"examples": ["T형보", "난간", "복잡한 교각", "교대", "수문관 본체"]
|
||||
},
|
||||
{
|
||||
"reuse_count": 3,
|
||||
"class": "약간 복잡한 구조",
|
||||
"examples": ["슬래브", "교대", "교각", "옹벽", "파라펫트", "날개벽"]
|
||||
},
|
||||
{
|
||||
"reuse_count": 4,
|
||||
"class": "비교적 간단한 구조",
|
||||
"examples": ["측구", "수로", "확대기초", "우물통"]
|
||||
},
|
||||
{
|
||||
"reuse_count": 6,
|
||||
"class": "극히 간단한 구조",
|
||||
"examples": ["수문 기초", "관의 기초", "호안 기초", "보호공 기초"]
|
||||
}
|
||||
],
|
||||
"type_map": [
|
||||
{
|
||||
"type_id": "retaining_wall",
|
||||
"reuse_count": 3,
|
||||
"matched_example": "옹벽",
|
||||
"note": "원문 3회 줄에 「옹벽」이 그대로 있음"
|
||||
},
|
||||
{
|
||||
"type_id": "pipe_inlet_basin",
|
||||
"reuse_count": 6,
|
||||
"matched_example": "보호공 기초",
|
||||
"note": "관보호공 집수정 — 원문 6회 줄의 「호안 및 보호공의 기초」에 해당. ⚠ 벽체까지 6회로 볼지는 확인 필요"
|
||||
},
|
||||
{
|
||||
"type_id": "ford_pavement",
|
||||
"reuse_count": null,
|
||||
"note": "물넘이포장은 거푸집이 서지 않는 구조(면 포장) — 대상 아님"
|
||||
}
|
||||
],
|
||||
"reuse_ratio_pct": {
|
||||
"note": "품셈 12-4 「사용횟수별 기준수량에 대한 비율(%)」. **B09 일위대가가 쓰는 값**이며 B08 은 참고로만 싣는다 — 여기서 곱하면 이중계상.",
|
||||
"table_id": "F0336",
|
||||
"plywood": { "1": 100.0, "2": 57.0, "3": 46.1, "4": 40.1, "5": 37.1, "6": 34.7 },
|
||||
"timber": { "1": 100.0, "2": 60.0, "3": 47.1, "4": 40.0, "5": 34.2, "6": 32.0 }
|
||||
},
|
||||
"shoring": {
|
||||
"note": "강관동바리(품셈 12-20)는 **슬래브를 떠받칠 때** 필요하다. 지금 서는 구조물(옹벽·집수정)은 벽체 거푸집만이라 대상이 아니다.",
|
||||
"targets_pending": ["box_culvert", "ford_bridge"],
|
||||
"why": "그 둘은 원단위·치수가 미확보라 슬래브 면적 자체가 안 나온다 — 동바리도 함께 미확보"
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,16 @@
|
||||
"work_item_code": "FP-13-04-02",
|
||||
"master_name": "돌쌓기 > 메쌓기(장비)",
|
||||
"note": "인력 시공이면 FP-13-04-01"
|
||||
},
|
||||
{
|
||||
"type_id": "pipe_inlet_basin",
|
||||
"work_item_code": "FP-12-15",
|
||||
"master_name": "집수정"
|
||||
},
|
||||
{
|
||||
"type_id": "ford_pavement",
|
||||
"work_item_code": "FP-12-06",
|
||||
"master_name": "콘크리트 포장(인력시공)"
|
||||
}
|
||||
],
|
||||
"pending_user": {
|
||||
@@ -132,14 +142,37 @@
|
||||
"items": [
|
||||
{
|
||||
"group": "지장목제거",
|
||||
"candidates": ["FP-04-01 수확베기", "FP-04-02 단목베기", "FP-04-03 위험목 베기"],
|
||||
"candidates": [
|
||||
"FP-04-01 수확베기",
|
||||
"FP-04-02 단목베기",
|
||||
"FP-04-03 위험목 베기"
|
||||
],
|
||||
"why": "품셈 4장은 벌목을 목적별로 가르는데 임도 지장목이 어느 쪽인지 원본이 말하지 않음"
|
||||
},
|
||||
{
|
||||
"group": "흙깎기/측구터파기 암",
|
||||
"candidates": ["FP-09-04 암절취(리핑)", "FP-09-05 발파암"],
|
||||
"candidates": [
|
||||
"FP-09-04 암절취(리핑)",
|
||||
"FP-09-05 발파암"
|
||||
],
|
||||
"why": "설계자가 넣는 암 갈래 이름(풍화암·연암·보통암·경암)이 리핑이냐 발파냐를 말하지 않음. 갈래마다 시공법을 지정하는 칸이 필요함"
|
||||
}
|
||||
]
|
||||
},
|
||||
"composite": {
|
||||
"note": "품셈에 **그 이름의 공종이 없어** 여러 공종을 묶어 일위대가로 세우는 자리. 코드 하나로 못 적으므로 묶음을 적어 둔다 — 빈 코드로 두면 「매핑을 못 찾은 줄」과 구별이 안 된다.",
|
||||
"items": [
|
||||
{
|
||||
"type_id": "retaining_wall",
|
||||
"parts": [
|
||||
"FP-12-01 콘크리트 타설",
|
||||
"FP-12-04 합판거푸집",
|
||||
"FP-12-03 철근 현장가공 및 조림",
|
||||
"FP-12-25 기초잡석"
|
||||
],
|
||||
"why": "품셈 12장에 「옹벽」 공종이 없음. 실무 내역은 「반중력식옹벽 H=2.0」 한 줄이고 그 일위대가가 위 공종을 묶음.",
|
||||
"needs": "일위대가 조립은 B09 몫 — B08 은 물량과 묶음만 넘김"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user