diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff.py b/B08_Quantity/B08_Quantity_Engine_Handoff.py index 26c8353d..e6d72e12 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff.py @@ -104,6 +104,7 @@ def build_handoff( ground_classes: list[str] | None = None, ground_methods: dict[str, str | None] | None = None, concrete_placing_method: str | None = None, + bench_cut_depth_m: float | None = None, ) -> dict[str, Any]: """B09 가 그대로 받는 모양. 없는 표는 건너뛰되 **빈 표와 구별해 적는다**.""" table = mapping or load_mapping() @@ -112,7 +113,7 @@ def build_handoff( methods = {key: value for key, value in (ground_methods or {}).items() if value} if summary_table: - rows, misses = _earthwork_rows(summary_table, table, methods) + rows, misses = _earthwork_rows(summary_table, table, methods, bench_cut_depth_m) work_items.extend(rows) unmatched.extend(misses) if haul_table: diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py b/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py index 0b19c353..4f0d1e42 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py @@ -89,10 +89,17 @@ def _basis_mismatch(entry: dict[str, Any] | None, unit: str) -> tuple[str, str, ) +#: 면적(㎡)으로 서지만 **길이를 곱해 ㎥ 로 내보내는** 공종 — 지금은 층따기뿐이다. +#: ⭐ 2026-09-09 사용자 확정 2차 ① — 「면적이 정본이고 부피는 사용자가 지정한 길이를 곱해 쓴다」. +#: ⚠ 면적을 없애지 않는다(횡단도 하단 표가 면적을 쓴다) — **㎥ 를 덧붙이는 것**이다. +AREA_TIMES_DEPTH_GROUPS = {"층따기"} + + def _earthwork_rows( summary_table: dict[str, Any], mapping: WorkItemMapping, methods: dict[str, str | None], + bench_cut_depth_m: float | None = None, ) -> tuple[list[dict[str, Any]], list[str]]: """토공집계표 줄을 내역 줄로 옮긴다. @@ -117,7 +124,12 @@ def _earthwork_rows( amount = float(row.get("amount") or 0.0) mismatch = _basis_mismatch(entry, unit) if code else None spec_detail = "" - if mismatch is not None: + # 층따기 — 길이가 들어오면 **면적 × 길이**로 ㎥ 를 내 품셈 단가를 그대로 쓴다. + depth = float(bench_cut_depth_m or 0.0) + if group in AREA_TIMES_DEPTH_GROUPS and unit == "㎡" and depth > 0: + spec_detail = f"면적 {amount:,.2f}㎡ × 길이 {depth:g}m" + unit, amount, mismatch = "㎥", amount * depth, None + elif mismatch is not None: spec_detail = f"집계 {amount:,.2f} {unit} (품셈 밑수 {mismatch[0]})" unit, amount = mismatch[0], 0.0 if code is None and not is_subtotal: diff --git a/B08_Quantity/B08_Quantity_Router_Earthwork.py b/B08_Quantity/B08_Quantity_Router_Earthwork.py index 5ccf4c94..442ae047 100644 --- a/B08_Quantity/B08_Quantity_Router_Earthwork.py +++ b/B08_Quantity/B08_Quantity_Router_Earthwork.py @@ -217,11 +217,13 @@ class QuantitySettingsBody(BaseModel): # ⚠ 산식(연장÷500)으로 만들지 않는다 — 임도규정이 「필요시 거리를 조정」이라 하고 # 기점 포함·갈림길 중복을 원문이 정하지 않는다. **설계자가 넣는 값**이다. ancillary_counts: dict[str, float] | None = None + # 층따기 길이(깊이, m). 면적 × 이 값 = ㎥ (확정 2차 ①). + bench_cut_depth_m: float | None = None #: `None` 이 「안 정함」을 뜻하는 칸 — 저장에서 **버리지 않고 그대로 덮어쓴다**. #: 빈 문자열로 되돌리는 칸(시공법·타설 방식)과 달리 숫자 칸은 되돌릴 값이 `None` 뿐이다. -NULLABLE_SETTING_KEYS = ("topsoil_thickness_m",) +NULLABLE_SETTING_KEYS = ("topsoil_thickness_m", "bench_cut_depth_m") @router.put("/{project_id}/quantity/settings") diff --git a/B08_Quantity/B08_Quantity_Router_Material.py b/B08_Quantity/B08_Quantity_Router_Material.py index 9a3058f1..339fde83 100644 --- a/B08_Quantity/B08_Quantity_Router_Material.py +++ b/B08_Quantity/B08_Quantity_Router_Material.py @@ -203,6 +203,8 @@ async def get_handoff(project_id: UUID) -> JSONResponse: ground_methods={name: rock_method(settings, name) for name in rock_classes(settings)}, # 타설 방식 — 안 정했으면 기본값으로 서되 그 사실을 `placing_notes` 가 알린다. concrete_placing_method=concrete_placing_method(settings)[0], + # 층따기 길이 — 면적 × 이 값으로 ㎥ 를 낸다(확정 2차 ①). 안 넣었으면 막히고 사유가 감. + bench_cut_depth_m=settings.get("bench_cut_depth_m"), ) handoff["summary"] = summarize(handoff) handoff["skipped_structures"] = skipped diff --git a/common_util/common_util_project_settings.py b/common_util/common_util_project_settings.py index 8ab40ad4..921d9d8e 100644 --- a/common_util/common_util_project_settings.py +++ b/common_util/common_util_project_settings.py @@ -109,6 +109,12 @@ def default_settings() -> dict[str, Any]: # ⇒ `ceil(연장÷500)` 을 확정 산식으로 쓰지 않고 **개소를 받는다.** # 비워 두면 물량을 안 낸다(0 으로 때우지 않음). "ancillary_counts": {}, + # 층따기 길이(깊이, m) — ⭐ 2026-09-09 사용자 확정 2차 ①. + # ⚠ **면적이 정본**이고 부피는 **면적 × 이 길이**로 낸다. 그러면 품셈 ㎥ 단가를 + # 그대로 쓸 수 있다(단위 불일치가 풀림). + # ⚠ 기본값을 두지 않는다 — 안 넣으면 물량을 안 낸다(0 으로 때우지 않음). + # 교본이 「층따기 높이·폭은 **설계도서에 명시**」라 해 설계 입력이다. + "bench_cut_depth_m": None, "dataset_versions": {}, }, "estimation": { diff --git a/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json b/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json index a8b4156f..bb8f95fe 100644 --- a/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json +++ b/resources/data_work_item_mapping/work_item_mapping_2026-01-01.json @@ -75,9 +75,10 @@ "group": "층따기", "work_item_code": "FP-09-18", "master_name": "층따기", - "basis_unit": "㎡", - "basis_source": "⭐ 2026-09-09 사용자 확정 ⑦ — 층따기 수량 단위는 **㎡**(실무 관행). 밑수는 **원지반 표면의 경사길이**(B06 `design.bench_cut_length_m`)를 평균단면적법으로 면적화한 값이다. ⚠ 품셈 9-18 [주]는 `Q1 = 3600×q×K×f×E/㎝ = ㎥/시간` 이라 **공식은 체적 기준**이다 — 단의 높이·폭이 설계도서 값이라 체적을 지어낼 수 없어 면적으로 간다. ⇒ **받는 쪽(B09)이 ㎡ 단가를 세워야 한다**(㎥ 단가를 그대로 곱하면 금액이 틀림).", - "unit_note": "앞서 2026-09-08 에는 이 어긋남을 「막힘」으로 두어 금액이 안 섰다(410만원 자리). 사용자 확정으로 ㎡ 가 정본이 되었으므로 막지 않는다." + "basis_unit": "㎥", + "basis_source": "품셈 9-18 [주] 「Q1 = 3600×q×K×f×E/㎝ = ㎥/시간」 — 공식이 체적 기준이다. ⭐ 2026-09-09 사용자 확정 2차 ① 로 **면적(㎡)을 정본으로 두고 사용자가 넣은 길이를 곱해 ㎥ 를 낸다** — 그러면 품셈 ㎥ 단가를 그대로 쓸 수 있다(단위 불일치가 풀림).", + "mismatch_reason": "층따기 길이(깊이)가 아직 입력되지 않았습니다 — 산출 조건에서 넣으면 면적 × 길이로 물량이 섭니다(면적은 이미 섰습니다).", + "mismatch_kind": "input_missing" }, { "group": "면고르기",