Merge remote-tracking branch 'origin/dev' into main_laptop_1
This commit is contained in:
@@ -76,6 +76,12 @@
|
||||
"type_id": "ford_pavement",
|
||||
"reuse_count": null,
|
||||
"note": "물넘이포장은 거푸집이 서지 않는 구조(면 포장) — 대상 아님"
|
||||
},
|
||||
{
|
||||
"type_id": "box_culvert",
|
||||
"reuse_count": 6,
|
||||
"matched_example": "관의 기초",
|
||||
"note": "BOX암거 합판은 **버림 옆면뿐**(본체는 유로폼 — 12-38 유형 「박스」 보통) — 원문 6회 줄 「관의 기초 등 극히 간단한 구조」 · 실무 울진 2×2 「합판6회」 와 같음(2026-09-15)"
|
||||
}
|
||||
],
|
||||
"reuse_ratio_pct": {
|
||||
@@ -99,12 +105,11 @@
|
||||
}
|
||||
},
|
||||
"shoring": {
|
||||
"note": "강관동바리(품셈 12-20)는 **슬래브를 떠받칠 때** 필요하다. 지금 서는 구조물(옹벽·집수정)은 벽체 거푸집만이라 대상이 아니다.",
|
||||
"note": "강관동바리(품셈 12-20)는 **슬래브를 떠받칠 때** 필요하다. 옹벽·집수정은 벽체 거푸집만이라 대상이 아니고, BOX암거는 전개식이 동바리 줄을 낸다(2026-09-15).",
|
||||
"targets_pending": [
|
||||
"box_culvert",
|
||||
"ford_bridge"
|
||||
],
|
||||
"why": "그 둘은 원단위·치수가 미확보라 슬래브 면적 자체가 안 나온다 — 동바리도 함께 미확보"
|
||||
"why": "세월교는 수량 근거가 없어 슬래브 면적 자체가 안 나온다 — 동바리도 함께 미확보"
|
||||
},
|
||||
"euroform_type": {
|
||||
"note": "유로폼 설치·해체 유형(간단/보통/복잡) — **품셈 12-38-3 [주]④ 원문**이 시설 예시로 갈라 둔다. 거푸집 사용횟수(1-7-1)·철근 갈래(12-3 [주]①)와 같은 자리로, 사람이 고르는 값이 아니다.",
|
||||
|
||||
@@ -560,15 +560,10 @@
|
||||
"not_found": {
|
||||
"note": "규격은 우리 모델에 있으나 **관측 원단위가 어디에도 없는** 것. 지어내지 않는다.",
|
||||
"items": [
|
||||
{
|
||||
"type_id": "box_culvert",
|
||||
"why": "울진 2공구에 BOX암거가 실재하나 원단위 라이브러리에 탭이 없음. 게다가 structures.json 의 BOX 제원은 body_width_m·body_height_m 와 날개벽뿐이라 **벽·저판·상판 두께가 없어 전개식도 못 세움**.",
|
||||
"needs": "표준 단면(벽·저판·상판 두께) 확보 — 사용자 확정 대기"
|
||||
},
|
||||
{
|
||||
"type_id": "ford_bridge",
|
||||
"why": "세월교 본체(날개벽 포함) 원단위 없음. 관 부분은 pipe 로 따로 섬.",
|
||||
"needs": "표준도 물량 또는 실무 관측"
|
||||
"why": "세월교 수량 근거 없음 — 법령·임도기술교본은 설치 조건만(그림 3-18 은 배치 모식도) · 실무 원본(울진 4공구·오솔길 전수)에 세월교 시트·도면 없음. 관 부분은 pipe 로 따로 섬(2026-09-15 브레인 판정 ⑥ — 사유로 닫음).",
|
||||
"needs": "세월교 표준도 물량 또는 실무 관측 — 나오면 다시 엶"
|
||||
},
|
||||
{
|
||||
"type_id": "retaining_wall",
|
||||
|
||||
@@ -435,6 +435,62 @@
|
||||
"needs": "일위대가 조립은 B09 몫 — B08 은 물량과 묶음만 넘김",
|
||||
"placing_note": "타설 코드는 프로젝트 설정의 타설 방식으로 갈림(기본 레디믹스트). 철근구조물 판정은 원단위의 D13·D16 에서 자동으로 나옴.",
|
||||
"parts_note": "조각마다 **어느 성분에서 나오는지**(`from_components`)를 적는다. 이름을 바꾸면 물량이 조용히 0 이 되므로 원단위 성분 이름과 정확히 같아야 하고, 못 찾으면 드러낸다. `unit_from` 이 있으면 그 단위에서 목표 단위로 환산한다 — **철근은 ㎏ → ton (÷1000)**. ⚠ 단위를 안 맞추면 1000배 틀린다."
|
||||
},
|
||||
{
|
||||
"type_id": "box_culvert",
|
||||
"parts": [
|
||||
{
|
||||
"code": "FP-12-01-01",
|
||||
"name": "콘크리트 타설",
|
||||
"unit": "㎥",
|
||||
"from_components": ["콘크리트", "버림콘크리트"],
|
||||
"kind_suffix": "concrete_placing"
|
||||
},
|
||||
{
|
||||
"code": "FP-12-38",
|
||||
"name": "유로폼",
|
||||
"unit": "㎡",
|
||||
"from_components": ["유로폼"],
|
||||
"kind_suffix": "euroform_type"
|
||||
},
|
||||
{
|
||||
"code": "FP-12-04",
|
||||
"name": "합판거푸집",
|
||||
"unit": "㎡",
|
||||
"from_components": ["합판거푸집"],
|
||||
"kind_suffix": "formwork_reuse"
|
||||
},
|
||||
{
|
||||
"code": "FP-12-03",
|
||||
"name": "철근 현장가공 및 조립",
|
||||
"unit": "ton",
|
||||
"from_components": ["이형철근 D13", "이형철근 D16"],
|
||||
"unit_from": "kg",
|
||||
"kind_suffix": "rebar_complexity",
|
||||
"why": "철근은 크기별 식이 없어 울진 기번3 구조도 2×2 관측값만 섬 — 다른 크기는 이 조각이 비어 묶음이 막힘(2026-09-15 브레인 판정 ②)"
|
||||
},
|
||||
{
|
||||
"code": "FP-12-20",
|
||||
"name": "강관동바리",
|
||||
"unit": "㎥",
|
||||
"from_components": ["동바리"]
|
||||
},
|
||||
{
|
||||
"code": "FP-12-19",
|
||||
"name": "강관비계",
|
||||
"unit": "㎡",
|
||||
"from_components": ["비계"]
|
||||
},
|
||||
{
|
||||
"code": "FP-12-25",
|
||||
"name": "기초잡석",
|
||||
"unit": "㎥",
|
||||
"from_components": ["기초잡석"],
|
||||
"why": "BOX 근거 둘(예제·울진)에는 잡석 줄이 없으나 **버림이 선 구조물엔 기초잡석이 함께 섬**(사용자 확정 3차 ② · 옹벽과 같은 규칙) — 원단위에 선 줄을 묶음에서 빼면 수량만 보이고 금액이 빠짐"
|
||||
}
|
||||
],
|
||||
"why": "품셈 12장에 「BOX암거」 공종이 없음 — 산림과임업기술(임도) 5장 수량산출 예제가 구체콘크리트·기초·거푸집·동바리·철근으로 가르고, 실무 울진 기번3 「암거수량집계표(2×2)」가 레미콘·버림·유로폼·합판·철근·동바리·비계로 셈(2026-09-15 브레인 12장 D).",
|
||||
"needs": "일위대가 조립은 B09 몫 — B08 은 물량과 묶음만 넘김"
|
||||
}
|
||||
],
|
||||
"unit_conversion": {
|
||||
|
||||
@@ -19,31 +19,29 @@ if str(ROOT) not in sys.path:
|
||||
|
||||
from B06_Section.B06_Section_Engine_Culvert import ( # noqa: E402
|
||||
BOX_COVER_M,
|
||||
FORD_SLAB_THICKNESS_M,
|
||||
FORD_WALL_THICKNESS_M,
|
||||
_box_set,
|
||||
attach_culvert_sets,
|
||||
)
|
||||
|
||||
|
||||
def test_box_set_defaults() -> None:
|
||||
"""저장 옵션이 없으면 레지스트리 기본 2.0×2.0에 세월교 두께를 얹는다."""
|
||||
"""옵션이 없으면 등록부 기본 2.0×2.0 · 두께도 등록부 제안값(2026-09-15 · 세월교 승계 걷음)."""
|
||||
spec = _box_set(None)
|
||||
assert spec["type"] == "box"
|
||||
assert spec["inner_width_m"] == 2.0
|
||||
assert spec["inner_height_m"] == 2.0
|
||||
assert spec["wall_thickness_m"] == FORD_WALL_THICKNESS_M
|
||||
assert spec["slab_thickness_m"] == FORD_SLAB_THICKNESS_M
|
||||
assert spec["top_thickness_m"] == FORD_SLAB_THICKNESS_M
|
||||
assert spec["wall_thickness_m"] == 0.3
|
||||
assert spec["slab_thickness_m"] == 0.25
|
||||
assert spec["top_thickness_m"] == 0.25
|
||||
assert spec["cover_m"] == BOX_COVER_M
|
||||
# 도로 방향 길이 = 내공 폭 + 측벽 두 장.
|
||||
assert spec["span_m"] == 2.0 + 2 * FORD_WALL_THICKNESS_M
|
||||
assert spec["span_m"] == 2.0 + 2 * 0.3
|
||||
|
||||
|
||||
def test_box_wing_extends_slab() -> None:
|
||||
"""저판 편측 연장 = 날개벽 길이 × cos(각도). 세월교와 같은 산식이다."""
|
||||
spec = _box_set({"body_width_m": 3, "body_height_m": 3, "wing_in_length_m": 2})
|
||||
assert spec["span_m"] == 3 + 2 * FORD_WALL_THICKNESS_M
|
||||
assert spec["span_m"] == 3 + 2 * 0.3
|
||||
assert spec["wing_in"]["slab_extend_m"] == round(2 * math.cos(math.radians(45)), 3)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""BOX암거 두께·연장 칸 — 등록부가 정본, B06 그림이 그 칸을 읽음 (2026-09-15 브레인 12장 D ①③).
|
||||
|
||||
① 제안값 = 산림과임업기술(임도) 5장 수량산출 예제 — 벽 0.3 · 판 0.25 · 헌치 0.2 · 버림 0.1
|
||||
근거에 KDS 44 90 00 4.5.2 「부재 최소두께 300㎜」(도로 기준)를 나란히 — 판 0.25 는 못 미침
|
||||
② 종전 B06 상수(벽 0.2 · 판 0.3 — 세월교 값 승계)는 근거 없는 값이라 걷음
|
||||
③ 연장 칸 — 기본값 없음
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from B06_Section.B06_Section_Engine_Culvert import _box_set
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _box_options() -> dict[str, dict]:
|
||||
registry = json.loads(
|
||||
(ROOT / "B05_Profile" / "B05_Profile_Structure_Types.json").read_text(encoding="utf-8")
|
||||
)
|
||||
types = registry if isinstance(registry, list) else registry.get("types") or []
|
||||
box = next(t for t in types if t["type_id"] == "box_culvert")
|
||||
return {option["key"]: option for option in box["options"]}
|
||||
|
||||
|
||||
def test_등록부에_두께_헌치_버림_연장_칸이_선다() -> None:
|
||||
options = _box_options()
|
||||
expected = {
|
||||
"wall_thickness_m": 0.3,
|
||||
"slab_thickness_m": 0.25,
|
||||
"haunch_m": 0.2,
|
||||
"blinding_thickness_m": 0.1,
|
||||
}
|
||||
for key, value in expected.items():
|
||||
assert options[key]["default"] == value, key
|
||||
assert "산림과임업기술" in options[key]["default_basis"], key
|
||||
# 판 0.25 는 KDS 도로 최소 300㎜ 에 못 미침 — 나란히 적어 설계자가 고르게.
|
||||
assert "KDS" in options["slab_thickness_m"]["default_basis"]
|
||||
assert "300" in options["slab_thickness_m"]["default_basis"]
|
||||
assert "KDS" in options["wall_thickness_m"]["default_basis"]
|
||||
# 연장은 기본값 없음.
|
||||
assert options["length_m"]["default"] is None
|
||||
|
||||
|
||||
def test_B06_세트가_등록부_칸을_읽는다() -> None:
|
||||
spec = _box_set(None)
|
||||
assert (spec["wall_thickness_m"], spec["slab_thickness_m"], spec["top_thickness_m"]) == (
|
||||
0.3,
|
||||
0.25,
|
||||
0.25,
|
||||
)
|
||||
assert spec["span_m"] == 2.0 + 2 * 0.3
|
||||
stored = _box_set({"wall_thickness_m": 0.35, "slab_thickness_m": 0.3})
|
||||
assert (stored["wall_thickness_m"], stored["slab_thickness_m"], stored["top_thickness_m"]) == (
|
||||
0.35,
|
||||
0.3,
|
||||
0.3,
|
||||
)
|
||||
assert stored["span_m"] == 2.0 + 2 * 0.35
|
||||
@@ -0,0 +1,102 @@
|
||||
"""BOX암거 전개식 — m당 × 연장 (2026-09-15 브레인 12장 D 판정 ①~⑤).
|
||||
|
||||
근거 둘(구조 같음)
|
||||
산림과임업기술(임도) 5장 「구조물도에 의한 수량산출(예)」 — 구체 = (외곽 − 유수구 + 헌치) ·
|
||||
기초 폭×두께 · 거푸집 외벽·유수구·받침판·귀면 · 동바리 유수구 단면
|
||||
실무 울진 기번3 구조도 「암거수량집계표(2×2)」 m당 — 레미콘 2.84 · 버림 0.28 · 유로폼 11.132 ·
|
||||
합판 0.2 · 철근 0.336t(H13 0.179 · H16 0.157) · 동바리 3.92 · 비계 벽체 5.2 + 시종점 13.52
|
||||
⚠ 철근은 크기별 식이 없어 **2×2 관측값만** · 날개벽·토공은 사유.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||||
from B08_Quantity.B08_Quantity_Engine_Pipe import facility_structures
|
||||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table, expand
|
||||
|
||||
ULJIN_2X2 = {
|
||||
"body_width_m": 2.0,
|
||||
"body_height_m": 2.0,
|
||||
"wall_thickness_m": 0.3,
|
||||
"slab_thickness_m": 0.3,
|
||||
"haunch_m": 0.2,
|
||||
"blinding_thickness_m": 0.1,
|
||||
"length_m": 10.0,
|
||||
}
|
||||
|
||||
|
||||
def _amounts(options: dict) -> tuple[dict, list[str], object]:
|
||||
result = expand({"type_id": "box_culvert", "options": options}, {"box_culvert": "BOX암거"})
|
||||
return (
|
||||
{(c.name, c.spec): c.amount for c in result.components},
|
||||
result.notes,
|
||||
result,
|
||||
)
|
||||
|
||||
|
||||
def test_울진_2x2_m당_값을_연장에_곱한다() -> None:
|
||||
got, notes, result = _amounts(ULJIN_2X2)
|
||||
assert got[("콘크리트", "")] == pytest.approx(2.84 * 10)
|
||||
assert got[("버림콘크리트", "")] == pytest.approx(0.28 * 10)
|
||||
assert got[("유로폼", "")] == pytest.approx(
|
||||
11.1314 * 10, abs=0.01
|
||||
) # 원문 11.132(귀면 0.566 반올림)
|
||||
assert got[("합판거푸집", "")] == pytest.approx(0.2 * 10)
|
||||
assert got[("이형철근", "D13")] == pytest.approx(179 * 10)
|
||||
assert got[("이형철근", "D16")] == pytest.approx(157 * 10)
|
||||
assert got[("동바리", "")] == pytest.approx(3.92 * 10)
|
||||
# 비계 — 벽체 m당 5.2 × 연장 + 시종점 두 면 13.52 는 한 번(원문은 m당에 넣었음 · 사유).
|
||||
assert got[("비계", "")] == pytest.approx(5.2 * 10 + 13.52)
|
||||
assert (result.billing_unit, result.billing_quantity) == ("m", 10.0)
|
||||
text = " ".join(notes)
|
||||
assert "관측" in text and "날개벽" in text
|
||||
|
||||
|
||||
def test_예제_두께_기본값으로_구체가_줄어든다() -> None:
|
||||
got, _notes, _r = _amounts({**ULJIN_2X2, "slab_thickness_m": 0.25})
|
||||
# (2.6 × 2.5 − 2 × 2 + 0.2² ÷ 2 × 4) = 2.58㎥/m
|
||||
assert got[("콘크리트", "")] == pytest.approx(2.58 * 10)
|
||||
|
||||
|
||||
def test_2x2_가_아니면_철근은_사유() -> None:
|
||||
got, notes, _r = _amounts({**ULJIN_2X2, "body_width_m": 3.0})
|
||||
assert not [key for key in got if key[0] == "이형철근"]
|
||||
assert any("크기별 식 없음" in note and "2×2" in note for note in notes)
|
||||
assert got[("콘크리트", "")] > 0 # 나머지는 섬
|
||||
|
||||
|
||||
def test_연장이_없으면_안_선다() -> None:
|
||||
got, notes, _r = _amounts({k: v for k, v in ULJIN_2X2.items() if k != "length_m"})
|
||||
assert got == {}
|
||||
assert any("연장" in note for note in notes)
|
||||
|
||||
|
||||
def test_치수를_안_적으면_기본값으로_서되_미확정() -> None:
|
||||
rows = facility_structures(
|
||||
[{"chainage_m": 50.0, "facility": "box_culvert", "options": {"length_m": 8.0}}]
|
||||
)
|
||||
box = next(row for row in rows if row["type_id"] == "box_culvert")
|
||||
assert box["unconfirmed"] and "측벽 두께" in box["unconfirmed"]
|
||||
assert box["options"]["wall_thickness_m"] == 0.3 and box["options"]["slab_thickness_m"] == 0.25
|
||||
stored = facility_structures(
|
||||
[{"chainage_m": 50.0, "facility": "box_culvert", "options": ULJIN_2X2}]
|
||||
)
|
||||
assert not next(r for r in stored if r["type_id"] == "box_culvert").get("unconfirmed")
|
||||
|
||||
|
||||
def test_인계_묶음이_조각을_세운다() -> None:
|
||||
table = build_table(
|
||||
facility_structures(
|
||||
[{"chainage_m": 50.0, "facility": "box_culvert", "options": ULJIN_2X2}]
|
||||
),
|
||||
{"box_culvert": "BOX암거"},
|
||||
)
|
||||
row = next(
|
||||
r for r in build_handoff(unit_quantity_table=table)["work_items"] if "BOX" in r["name"]
|
||||
)
|
||||
parts = {part["code"].split("#")[0]: part for part in row["composite_parts"] or []}
|
||||
assert {"FP-12-01-01", "FP-12-04", "FP-12-38", "FP-12-03", "FP-12-20", "FP-12-19"} <= set(parts)
|
||||
assert parts["FP-12-03"]["quantity"] == pytest.approx((179 + 157) * 10 / 1000)
|
||||
assert parts["FP-12-20"]["quantity"] == pytest.approx(3.92 * 10)
|
||||
@@ -116,7 +116,8 @@ def test_독립_기슭막이_높이는_설계자_입력_없으면_미확정과_
|
||||
|
||||
def test_산출식_없는_세월교는_사유로_서고_터파기_빠짐_줄을_안_만든다() -> None:
|
||||
table = _table([{"facility": "ford_bridge", "chainage_m": 30.0, "options": {}}])
|
||||
assert "산출식이 아직 없습니다" in table["structures"][0]["notes"][0]
|
||||
# 2026-09-15 브레인 판정 ⑥ — 「아직 없음」이 아니라 「근거 없음」으로 닫음.
|
||||
assert "세월교 수량 근거 없음" in table["structures"][0]["notes"][0]
|
||||
rows = build_handoff(unit_quantity_table=table)["work_items"]
|
||||
assert not any("터파기가 안 선 구조물" in str(row.get("spec_detail")) for row in rows)
|
||||
|
||||
|
||||
@@ -114,8 +114,22 @@ def test_동바리는_대상이_없으면_없다고_말할것() -> None:
|
||||
status = shoring_status()
|
||||
assert status["applicable"] is False
|
||||
assert status["reason"]
|
||||
assert set(status["pending_types"]) == {"box_culvert", "ford_bridge"}
|
||||
# BOX암거는 2026-09-15 전개식이 서서 대상에서 빠짐 — 세월교만 남음(근거 없음 사유).
|
||||
assert set(status["pending_types"]) == {"ford_bridge"}
|
||||
|
||||
|
||||
def test_표에도_동바리_상태가_실림() -> None:
|
||||
assert build_table([옹벽()])["shoring"]["applicable"] is False
|
||||
box = {
|
||||
"type_id": "box_culvert",
|
||||
"options": {
|
||||
"body_width_m": 2.0,
|
||||
"body_height_m": 2.0,
|
||||
"wall_thickness_m": 0.3,
|
||||
"slab_thickness_m": 0.3,
|
||||
"haunch_m": 0.2,
|
||||
"blinding_thickness_m": 0.1,
|
||||
"length_m": 5.0,
|
||||
},
|
||||
}
|
||||
assert build_table([box])["shoring"]["applicable"] is True
|
||||
|
||||
@@ -169,13 +169,16 @@ def test_숫자와_글자_규격을_같이_본다() -> None:
|
||||
assert table.find("retaining_wall", {"form": "반중력식", "height_m": 1.9999}) is None
|
||||
|
||||
|
||||
def test_BOX암거는_미확보로_남음() -> None:
|
||||
"""⚠ 두께가 저장돼 있지 않아 식도 못 세우고 관측값도 없다 — 지어내면 콘크리트·거푸집·
|
||||
철근으로 번져 나간다. 사용자 확정 대기."""
|
||||
def test_BOX암거는_관측표가_아니라_전개식으로_섬() -> None:
|
||||
"""2026-09-15 — 두께 칸이 등록부에 서고 전개식(`_UnitQuantity_Box`)이 생겨 관측표 몫이 아님.
|
||||
|
||||
관측값은 여전히 없음(울진 2×2 철근만 전개식 안에 관측값으로 듦) · 세월교는 사유로 닫힘.
|
||||
"""
|
||||
table = load_observed_table()
|
||||
assert table.find("box_culvert", {"body_width_m": 3.0, "body_height_m": 1.2}) is None
|
||||
reasons = [item["type_id"] for item in table.not_found["items"]]
|
||||
assert "box_culvert" in reasons
|
||||
reasons = {item["type_id"]: item for item in table.not_found["items"]}
|
||||
assert "box_culvert" not in reasons
|
||||
assert "세월교 수량 근거 없음" in reasons["ford_bridge"]["why"]
|
||||
|
||||
|
||||
def test_파일이_없으면_전부_미확보() -> None:
|
||||
|
||||
Reference in New Issue
Block a user