diff --git a/B08_Quantity/B08_Quantity_Router_Material.py b/B08_Quantity/B08_Quantity_Router_Material.py index 93999bf5..09edd1f7 100644 --- a/B08_Quantity/B08_Quantity_Router_Material.py +++ b/B08_Quantity/B08_Quantity_Router_Material.py @@ -146,6 +146,33 @@ async def _section_modes(project_id: UUID) -> dict[float, str]: return section_modes_from_designs(designs) +def material_table_for( + unit_table: dict[str, Any], + settings: dict[str, Any], + preparation_table: dict[str, Any] | None, +) -> dict[str, Any]: + """자재총괄 **한 벌** — 화면(`material-summary`)·인계(`handoff`)가 같이 부름(브레인 판정). + + ⚠ 규준틀 재료는 **준비공 개소가 선 뒤에야** 서므로 준비공 표를 받아 여기서 얹음. 종전엔 인계만 + 얹어 화면 6줄 · 인계 9줄로 갈림 → 화면에서 규준틀 재료의 관급구분·할증을 못 만짐. + ⚠ 화면 쪽에 따로 복사하지 말 것 — 두 벌이 되면 또 갈림. + ⚠ 값은 **제안값(실무 관측)**이고 산출 조건에서 고칠 수 있음 — 그 사실이 줄 사유에 적힘. + """ + frame_rows = [ + row + for row in ((preparation_table or {}).get("rows") or []) + if str(row.get("item") or "").endswith("규준틀") + ] + return build_material_table( + unit_table, + supply_map=settings.get("material_supply") or {}, + surcharge_overrides=settings.get("material_surcharge") or {}, + # 콘크리트 할증은 **레미콘일 때만** 붙는다 — 방식이 이름을 가른다(확정 3차 ⑥). + concrete_placing_method=settings.get("concrete_placing_method"), + extra_materials=frame_material_rows(frame_rows, settings.get("frame_material") or {}), + ) + + @router.get("/{project_id}/quantity/material-summary") async def get_material_summary(project_id: UUID) -> JSONResponse: """구조물 원단위와 자재총괄을 **한 응답**으로 낸다. @@ -183,13 +210,9 @@ async def get_material_summary(project_id: UUID) -> JSONResponse: # 프로젝트에 박힌 양식(PLAN 4장) — 같은 까닭. structure_templates=project_templates(project_root), ) - material_table = build_material_table( - unit_table, - supply_map=settings.get("material_supply") or {}, - surcharge_overrides=settings.get("material_surcharge") or {}, - # 콘크리트 할증은 **레미콘일 때만** 붙는다 — 방식이 이름을 가른다(확정 3차 ⑥). - concrete_placing_method=settings.get("concrete_placing_method"), - ) + # 인계와 같은 한 벌 — 규준틀 재료가 준비공 표를 따라 서므로 토공 표를 받음. + earthwork = await _earthwork_tables(project_id) + material_table = material_table_for(unit_table, settings, earthwork.get("preparation")) # 묶음으로 서는 구조물의 조각을 화면에도 보인다 — 코드만으로는 사람이 검증 못 한다. handoff = build_handoff(unit_quantity_table=unit_table) composite = [ @@ -437,30 +460,10 @@ async def get_handoff(project_id: UUID) -> JSONResponse: structure_formulas=settings.get("structure_formula_overrides"), structure_templates=project_templates(project_root), ) - material_table = build_material_table( - unit_table, - supply_map=settings.get("material_supply") or {}, - surcharge_overrides=settings.get("material_surcharge") or {}, - concrete_placing_method=settings.get("concrete_placing_method"), - ) - # 토공·운반 표는 토적표 라우터의 것을 그대로 쓴다 — 여기서 다시 만들지 않는다. earthwork = await _earthwork_tables(project_id) - - # 규준틀 재료 — **개소가 선 뒤에야 설 수 있어** 준비공 표를 받은 다음 자재 축에 얹는다. - # ⚠ 값은 **제안값(실무 관측)**이고 산출 조건에서 고칠 수 있다 — 그 사실이 줄 사유에 적힌다. - frame_rows = [ - row - for row in ((earthwork.get("preparation") or {}).get("rows") or []) - if str(row.get("item") or "").endswith("규준틀") - ] - material_table = build_material_table( - unit_table, - supply_map=settings.get("material_supply") or {}, - surcharge_overrides=settings.get("material_surcharge") or {}, - concrete_placing_method=settings.get("concrete_placing_method"), - extra_materials=frame_material_rows(frame_rows, settings.get("frame_material") or {}), - ) + # 자재총괄 — 화면과 같은 한 벌(규준틀 재료 포함). + material_table = material_table_for(unit_table, settings, earthwork.get("preparation")) handoff = build_handoff( summary_table=earthwork.get("summary"), diff --git a/resources/tester/test_b08_frame_material.py b/resources/tester/test_b08_frame_material.py index 1a1e5736..b28fe237 100644 --- a/resources/tester/test_b08_frame_material.py +++ b/resources/tester/test_b08_frame_material.py @@ -85,6 +85,28 @@ def test_자재_축으로_간다() -> None: assert all(row["destination"] == "material" for row in frame_material_rows(규준틀)) +def test_화면과_인계가_같은_한_벌을_부른다() -> None: + """자재총괄 화면(6줄)·인계(9줄)가 갈리던 자리 — 규준틀 재료를 한 곳에서 얹음(2026-09-14). + + ⚠ 두 라우터가 이 함수를 부르는지까지 본다 — 한쪽이 옛 길로 돌아가면 다시 갈림. + """ + import inspect + + from B08_Quantity import B08_Quantity_Router_Material as router + + table = router.material_table_for( + {"structures": []}, + {"material_supply": {"각재 50×50": "owner_supplied"}}, + {"rows": 규준틀}, + ) + keys = {row["supply_key"]: row for row in table["rows"]} + assert set(keys) == {"각재 50×50", "판재 T12", "못"} + assert keys["각재 50×50"]["supply"] == "owner_supplied" # 화면에서 고른 값이 먹음 + for route in (router.get_material_summary, router.get_handoff): + source = inspect.getsource(route) + assert "material_table_for(" in source and "build_material_table(" not in source + + def test_제안값이_실무_관측값과_같다() -> None: """울진 소광 §8 — 각재 50×50 0.0044㎥ · 판재 T12 0.0029㎥ · 못 0.03㎏(개소당).""" assert FRAME_MATERIAL_SUGGESTED["각재 50×50"][0] == 0.0044