Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -1137,13 +1137,13 @@ def _준비공표() -> dict:
|
||||
"tree_removal_cut": 6726.9,
|
||||
"bench_cut_fill": 9000.0,
|
||||
}
|
||||
return build_prep(slope, [], [], 0.15)
|
||||
return build_prep(slope, [], [], 0.15, road_surface_area_m2=8000.0)
|
||||
|
||||
|
||||
def test_준비공_값이_서는_줄은_내역에_선다() -> None:
|
||||
handoff = build_handoff(preparation_table=_준비공표())
|
||||
표토 = next(r for r in handoff["work_items"] if r["name"] == "표토제거")
|
||||
assert 표토["work_item_code"] == "FP-09-15"
|
||||
assert 표토["work_item_code"] == "FP-09-15-02"
|
||||
assert 표토["in_bill"] is True
|
||||
assert 표토["quantity"] > 0
|
||||
assert 표토["blocked_kind"] is None
|
||||
|
||||
@@ -63,7 +63,7 @@ def test_사유가_비어_있지_않을것() -> None:
|
||||
|
||||
def test_값이_없어도_공종코드는_알려줄것() -> None:
|
||||
"""무엇으로 서게 될 줄인지 알아야 나중에 잇는다."""
|
||||
assert 줄(build_table(), "표토제거")["work_item_code"] == "FP-09-15"
|
||||
assert 줄(build_table(), "표토제거")["work_item_code"] == "FP-09-15-02"
|
||||
|
||||
|
||||
# ── 사방공 ──────────────────────────────────────────────────────────
|
||||
@@ -192,30 +192,29 @@ def test_성토고가_다_낮으면_0개소() -> None:
|
||||
# ⚠ 안 넣으면 0 으로 때우지 않고 물량을 안 낸다.
|
||||
|
||||
|
||||
def test_두께가_없으면_물량을_안_낼것() -> None:
|
||||
row = 줄(build_table({"face_dressing_fill": 1000.0, "face_dressing_cut": 500.0}), "표토제거")
|
||||
assert row["amount"] is None
|
||||
# ⚠ 사유가 정정됐다(2026-09-09) — 종전에 「품셈이 정하는 값이 아닙니다」라고만 적었는데
|
||||
# 품셈 9-15-2(답외구간) 원문에 **T=0.2m · L=20m 가 적용값으로 박혀 있다.** 두께를
|
||||
# 자동으로 넣지는 않되 **원문에 있는 값을 없다고 말하지도 않는다.**
|
||||
assert "두께는 설계가 정하는 값" in row["reason"]
|
||||
assert "T=0.2m" in row["reason"] and "9-15-2" in row["reason"]
|
||||
# 대상 면적은 참고로 보인다 — 무엇에 곱할지 사용자가 알아야 한다.
|
||||
assert row["reference_amount"] == pytest.approx(1500.0)
|
||||
|
||||
|
||||
def test_두께를_넣으면_물량이_설것() -> None:
|
||||
def test_표토제거는_노면과_절토대상지_면적이고_두께를_안_곱한다() -> None:
|
||||
"""⭐ 2026-09-13 판정 「실무대로」 — 밑수 ㎡ · 대상 노면+절토대상지 · 성토 사면은 안 셈."""
|
||||
table = build_table(
|
||||
{"face_dressing_fill": 1000.0, "face_dressing_cut": 500.0}, topsoil_thickness_m=0.15
|
||||
{"face_dressing_fill": 1000.0, "face_dressing_cut": 500.0},
|
||||
topsoil_thickness_m=0.15,
|
||||
road_surface_area_m2=4000.0,
|
||||
)
|
||||
row = 줄(table, "표토제거")
|
||||
assert row["amount"] == pytest.approx(225.0) # 1,500㎡ × 0.15m
|
||||
assert row["status"] == "값 있음"
|
||||
assert (row["unit"], row["amount"], row["status"]) == ("㎡", pytest.approx(4500.0), "값 있음")
|
||||
assert row["work_item_code"] == "FP-09-15-02" # 답외구간
|
||||
assert "노면·절토대상지" in row["reason"] and "20m 압토" in row["reason"]
|
||||
|
||||
|
||||
def test_두께가_0이면_안_낼것() -> None:
|
||||
"""0 은 「안 넣음」과 같게 본다 — 0㎥ 를 내면 「없다」로 오해된다."""
|
||||
row = 줄(build_table({"face_dressing_fill": 1000.0}, topsoil_thickness_m=0), "표토제거")
|
||||
def test_노면_면적을_못_세면_물량을_안_낼것() -> None:
|
||||
"""노체 끝이 없는 측점이 있으면 절토 사면만으로 세우지 않는다 — 합계가 그럴듯하게 틀림."""
|
||||
row = 줄(build_table({"face_dressing_cut": 500.0}), "표토제거")
|
||||
assert row["amount"] is None and "노면 면적" in row["reason"]
|
||||
assert row["reference_amount"] == pytest.approx(500.0)
|
||||
|
||||
|
||||
def test_면적이_0이면_안_낼것() -> None:
|
||||
"""0 은 「없음」과 구별이 안 된다 — 0㎡ 를 내면 「표토가 없는 노선」으로 읽힌다."""
|
||||
row = 줄(build_table({}, road_surface_area_m2=0.0), "표토제거")
|
||||
assert row["amount"] is None
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"""표토 운반·적치 — 법이 요구하는데 제거만 세고 있던 줄 (2026-09-09).
|
||||
"""표토 운반·적치 — 법이 요구하는데 제거만 세고 있던 줄 (2026-09-09 · 2026-09-13 판정 「실무대로」).
|
||||
|
||||
⚠⚠ 시행규칙 별표2 Ⅰ.2.차.(6)·Ⅰ.3.카.(6):
|
||||
「노면·절토대상지에 있는 입목…과 그 뿌리, **표토는 전량 제거한 후** 강우 시 유실되거나
|
||||
경관에 저해되지 않도록 **최고 홍수위보다 높은 장소로 운반하고 쌓아두어야 한다**」
|
||||
⇒ 제거(9-15)만 세면 **운반이 빠진다.**
|
||||
|
||||
⚠ 겨누는 것 다섯
|
||||
⚠ 겨누는 것
|
||||
① 줄이 **선다** — 값이 없어도 목록에 남는다
|
||||
② 물량은 **제거 물량 그대로** — 다시 세지 않는다
|
||||
③ 거리는 **설계 입력** — 「최고 홍수위보다 높은 장소」는 현장이 정한다. 지어내지 않는다
|
||||
④ ⚠ 제거가 안 서면(두께 미입력) 운반도 안 선다 — 밑수가 그 줄이다
|
||||
② 부피는 **제거 면적(㎡) × 두께** — 제거는 ㎡ 로 서고 두께는 운반 부피에만 쓴다
|
||||
③ 거리는 **설계 입력** · 제거 품(9-15-2)이 **20m 압토**를 품어 **넘는 몫만** 운반으로 셈
|
||||
④ 제거가 안 서거나 두께가 비면 운반도 안 선다 — 0 으로 때우지 않는다
|
||||
⑤ 법 문구가 **사유에 적힌다** — 왜 세는지 화면에서 보여야 한다
|
||||
"""
|
||||
|
||||
@@ -22,16 +22,19 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import ( # noqa: E402
|
||||
STATUS_COUNTED_ELSEWHERE,
|
||||
STATUS_PENDING,
|
||||
STATUS_READY,
|
||||
build_table,
|
||||
preparation_rows,
|
||||
)
|
||||
|
||||
사면 = {"face_dressing_fill": 600.0, "face_dressing_cut": 400.0}
|
||||
노면 = 600.0 # 노면 600 + 절토 사면 400 = 제거 1,000㎡
|
||||
|
||||
|
||||
def 줄(thickness: float | None, distance: float | None) -> dict:
|
||||
rows = preparation_rows(사면, [], thickness, distance)
|
||||
def 줄(thickness: float | None, distance: float | None, road: float | None = 노면) -> dict:
|
||||
rows = preparation_rows(사면, [], thickness, distance, None, road)
|
||||
return {row["item"]: row for row in rows}
|
||||
|
||||
|
||||
@@ -39,13 +42,20 @@ def test_줄이_선다() -> None:
|
||||
assert "표토 운반·적치" in 줄(None, None)
|
||||
|
||||
|
||||
def test_물량은_제거_물량_그대로() -> None:
|
||||
"""⚠ 다시 세면 두 값이 갈린다 — 제거 줄이 밑수다."""
|
||||
def test_부피는_제거_면적_곱하기_두께_거리는_20m_넘는_몫() -> None:
|
||||
rows = 줄(0.2, 300.0)
|
||||
assert rows["표토제거"]["amount"] == 1000.0 * 0.2
|
||||
assert rows["표토 운반·적치"]["amount"] == rows["표토제거"]["amount"]
|
||||
assert rows["표토 운반·적치"]["status"] == STATUS_READY
|
||||
assert rows["표토 운반·적치"]["work_item_code"] == "FP-10-12"
|
||||
assert rows["표토제거"]["amount"] == 1000.0
|
||||
운반 = rows["표토 운반·적치"]
|
||||
assert 운반["amount"] == 1000.0 * 0.2 and 운반["status"] == STATUS_READY
|
||||
assert 운반["work_item_code"] == "FP-10-12"
|
||||
assert 운반["haul_distance_m"] == 280.0 # 300 − 압토 20
|
||||
assert "20m 를 넘는 몫" in 운반["reason"]
|
||||
|
||||
|
||||
def test_20m_이내면_제거_품에_들어_따로_안_셈() -> None:
|
||||
운반 = 줄(0.2, 15.0)["표토 운반·적치"]
|
||||
assert 운반["amount"] is None and 운반["status"] == STATUS_COUNTED_ELSEWHERE
|
||||
assert 운반["reference_amount"] == 200.0
|
||||
|
||||
|
||||
def test_거리를_안_넣으면_막고_사유() -> None:
|
||||
@@ -53,18 +63,18 @@ def test_거리를_안_넣으면_막고_사유() -> None:
|
||||
row = 줄(0.2, None)["표토 운반·적치"]
|
||||
assert row["amount"] is None and row["status"] == STATUS_PENDING
|
||||
assert "운반거리가 아직 입력되지 않았습니다" in row["reason"]
|
||||
# 값을 버리지 않는다 — 운반할 물량은 참고로 보인다.
|
||||
assert row["reference_amount"] == 200.0
|
||||
assert row["reference_amount"] == 200.0 # 값을 버리지 않는다
|
||||
|
||||
|
||||
def test_제거가_안_서면_운반도_안_선다() -> None:
|
||||
row = 줄(None, 300.0)["표토 운반·적치"]
|
||||
assert row["amount"] is None
|
||||
assert "두께 먼저" in row["reason"]
|
||||
def test_두께가_비거나_제거가_안_서면_운반도_안_선다() -> None:
|
||||
no_thickness = 줄(None, 300.0)["표토 운반·적치"]
|
||||
assert no_thickness["amount"] is None and "T=0.2m" in no_thickness["reason"]
|
||||
no_removal = 줄(0.2, 300.0, road=None)["표토 운반·적치"]
|
||||
assert no_removal["amount"] is None and "제거 면적이 아직 안 서서" in no_removal["reason"]
|
||||
|
||||
|
||||
def test_법_문구가_사유에_적힌다() -> None:
|
||||
for thickness, distance in ((None, None), (0.2, None), (0.2, 300.0)):
|
||||
for thickness, distance in ((None, None), (0.2, None), (0.2, 300.0), (0.2, 10.0)):
|
||||
reason = 줄(thickness, distance)["표토 운반·적치"]["reason"]
|
||||
assert "별표2" in reason and "운반하고 쌓아두어야" in reason
|
||||
|
||||
@@ -73,21 +83,12 @@ def test_법_문구가_사유에_적힌다() -> None:
|
||||
|
||||
|
||||
def test_뿌리_뒤_단계_두_줄이_선다() -> None:
|
||||
"""⚠⚠ 「벌개·제근 → 뿌리다듬기 → **적재** → **운반**」인데 우리는 한 줄만 세고 있었다.
|
||||
|
||||
표토와 같은 병이다 — 법·품셈이 두 동작을 묶어 두는데 앞 동작만 세던 자리.
|
||||
"""
|
||||
rows = 줄(0.2, 300.0)
|
||||
assert "뿌리 적재" in rows and "뿌리 운반" in rows
|
||||
|
||||
|
||||
def test_적재는_9_20_2_운반은_덤프() -> None:
|
||||
"""⭐ 2026-09-09 확정 5차 4번 — 「뿌리 운반은 **덤프**」로 답이 왔다.
|
||||
|
||||
⚠ 그 전에는 「9-20 장에 운반 공종이 없어 코드를 못 붙임」이었다. 지어낸 것이 아니라
|
||||
**사용자가 고른 것**이라 코드가 붙는다.
|
||||
⚠ 다만 물량은 여전히 안 선다 — 운반 밑수는 **부피(㎥)** 인데 뿌리 부피를 든 곳이 없다.
|
||||
"""
|
||||
"""⭐ 2026-09-09 확정 5차 4번 — 「뿌리 운반은 **덤프**」. 물량은 부피가 없어 안 선다."""
|
||||
rows = 줄(0.2, 300.0)
|
||||
assert rows["뿌리 적재"]["work_item_code"] == "FP-09-20-02"
|
||||
assert rows["뿌리 운반"]["work_item_code"] == "FP-10-12"
|
||||
@@ -96,97 +97,51 @@ def test_적재는_9_20_2_운반은_덤프() -> None:
|
||||
|
||||
|
||||
def test_적재는_면적으로_서고_운반은_못_선다() -> None:
|
||||
"""⭐ 확정 5차 6번으로 **면적 축**이 정해져 적재는 선다(제근과 같은 밑수).
|
||||
|
||||
⚠ 운반만 못 선다 — 축이 **부피**라서다. 「같은 확정인데 한 줄만 서는」 자리라 갈라 적는다.
|
||||
⚠ 대상 면적은 **벌개제근 연동**이라 `tree_removal_*` 에서 온다(표토 계열 `face_dressing_*`
|
||||
과 **다른 면적**이다 — 2026-09-09 실측: 17,873.8 ↔ 15,726.9).
|
||||
"""
|
||||
사면 = {**{"tree_removal_fill": 600.0, "tree_removal_cut": 400.0}, **{}}
|
||||
rows = {row["item"]: row for row in preparation_rows(사면, [], 0.2, 300.0)}
|
||||
사면_ = {"tree_removal_fill": 600.0, "tree_removal_cut": 400.0}
|
||||
rows = {row["item"]: row for row in preparation_rows(사면_, [], 0.2, 300.0)}
|
||||
assert rows["뿌리 적재"]["amount"] == 1000.0
|
||||
assert rows["제근·뿌리다듬기"]["amount"] == 1000.0 # 같은 밑수로 함께 섬
|
||||
assert rows["뿌리 운반"]["amount"] is None
|
||||
assert rows["뿌리 운반"]["reference_amount"] == 1000.0
|
||||
|
||||
|
||||
# ── 2026-09-09 준비공 축 감사에서 잡은 둘 ──────────────────────────
|
||||
|
||||
|
||||
def test_면적이_0이면_물량을_안_낸다() -> None:
|
||||
"""⚠ 0 은 **「없음」과 구별이 안 된다** — 받는 쪽이 「표토가 없는 노선」으로 읽는다.
|
||||
|
||||
절·성토 사면적이 0 인 임도는 성립하지 않으므로, 이 자리는 사실상 **사면표가 아직
|
||||
안 선 것**이다. 두께 미입력은 이미 막고 있었는데 면적 0 은 그냥 통과하고 있었다.
|
||||
"""
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import build_table
|
||||
|
||||
rows = {r["item"]: r for r in build_table(slope_totals={}, topsoil_thickness_m=0.2)["rows"]}
|
||||
제거 = rows["표토제거"]
|
||||
assert 제거["amount"] is None
|
||||
assert "사면표" in 제거["reason"]
|
||||
# 운반은 제거를 밑수로 삼으므로 함께 막힌다 — 0 ㎥ 를 실어 나르는 줄이 안 생긴다.
|
||||
assert rows["표토 운반·적치"]["amount"] is None
|
||||
|
||||
|
||||
def test_대상_면적이_법_문언과_다르다는_사실이_적힌다() -> None:
|
||||
"""⚠ 값을 고치는 것이 아니라 **드러내는** 시험이다(임의 확정 금지).
|
||||
|
||||
별표2 Ⅰ.2.차.(6) 은 대상을 「**노면·절토대상지**」로 못박고, 성토대상지는 (7) 에서
|
||||
「표토 등은 **제거·정리**한다」로 따로 두어 운반·적치 의무를 안 건다. 우리는
|
||||
절토·성토 **사면적을 다 더하고 노면은 안 센다** — 두 방향으로 어긋난다.
|
||||
실무가 어느 쪽인지는 사용자·실무자 확인 사항이라 **사유만** 낸다.
|
||||
"""
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import build_table
|
||||
|
||||
rows = {
|
||||
r["item"]: r
|
||||
for r in build_table(
|
||||
slope_totals={"face_dressing_cut": 9000.0, "face_dressing_fill": 6726.9},
|
||||
topsoil_thickness_m=0.2,
|
||||
)["rows"]
|
||||
}
|
||||
reason = rows["표토제거"]["reason"]
|
||||
assert rows["표토제거"]["amount"] is not None # 값은 그대로 선다
|
||||
assert "노면·절토대상지" in reason and "노면은 안 셈" in reason
|
||||
|
||||
|
||||
def test_운반이_갈래를_실어_보낸다() -> None:
|
||||
"""⚠ `FP-10-12` 는 **부모**다 — 품이 붙은 것은 잎(10-12-1 토사 · -2 암절취 · -3 발파암)뿐.
|
||||
|
||||
갈래를 안 보내면 B09 가 「이 공종엔 일위대가가 없고 한 층 아래에 있습니다 — 후보 3건」
|
||||
에서 멈추고 **금액이 안 선다.** 표토가 토사인 것은 다툼이 없으므로 갈래를 실어 보낸다.
|
||||
"""
|
||||
"""⚠ `FP-10-12` 는 **부모**다 — 갈래(토사)를 안 보내면 B09 가 「후보 3건」에서 멈춘다."""
|
||||
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import build_table
|
||||
|
||||
table = build_table(
|
||||
slope_totals={"face_dressing_cut": 9000.0, "face_dressing_fill": 6726.9},
|
||||
topsoil_thickness_m=0.2,
|
||||
topsoil_haul_distance_m=500.0,
|
||||
road_surface_area_m2=12000.0,
|
||||
)
|
||||
handed = {r["name"]: r for r in build_handoff(preparation_table=table)["work_items"]}
|
||||
운반 = handed["표토 운반·적치"]
|
||||
assert 운반["in_bill"] is True
|
||||
assert (운반["variant_axis"], 운반["variant_value"]) == ("ground_class", "토사")
|
||||
제거 = handed["표토제거"]
|
||||
assert (제거["unit"], 제거["quantity"], 제거["in_bill"]) == ("㎡", 21000.0, True)
|
||||
|
||||
|
||||
def test_제거_품에_20m_압토가_들어_있다는_사실이_적힌다() -> None:
|
||||
"""⚠ 품셈 9-15-2 원문이 `L(운반거리) 20m` 를 적용값으로 박아 뒀다.
|
||||
def test_노면_면적은_노체_폭의_평균단면적() -> None:
|
||||
from types import SimpleNamespace as S
|
||||
|
||||
실무 내역서(영월 2024)도 「표토제거 답외구간 / M2 · 도자 19Ton · D=20」 **한 줄뿐이고
|
||||
운반 줄이 따로 없다.** 우리 운반 줄은 그래서 **20m 를 넘는 몫**일 때만 새 줄인데 그
|
||||
가름을 아직 안 했다 — 값을 지어내지 않고 **그 사실을 사유에 남긴다**(이중계상 후보).
|
||||
"""
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import build_table
|
||||
from B08_Quantity.B08_Quantity_Engine_SlopeLength import _roadbed_width, road_surface_area
|
||||
|
||||
rows = {
|
||||
r["item"]: r
|
||||
for r in build_table(
|
||||
slope_totals={"face_dressing_cut": 9000.0, "face_dressing_fill": 6726.9},
|
||||
topsoil_thickness_m=0.2,
|
||||
topsoil_haul_distance_m=500.0,
|
||||
)["rows"]
|
||||
}
|
||||
reason = rows["표토 운반·적치"]["reason"]
|
||||
assert "L=20m 압토가 들어 있음" in reason and "20m 를 넘는" in reason
|
||||
assert (
|
||||
_roadbed_width({"road_edges": {"left": {"offset_m": 2.5}, "right": {"offset_m": -2.5}}})
|
||||
== 5.0
|
||||
)
|
||||
assert (
|
||||
road_surface_area(
|
||||
[S(chainage_m=20.0, roadbed_width_m=6.0), S(chainage_m=0.0, roadbed_width_m=4.0)]
|
||||
)
|
||||
== 100.0
|
||||
)
|
||||
assert (
|
||||
road_surface_area(
|
||||
[S(chainage_m=0.0, roadbed_width_m=4.0), S(chainage_m=20.0, roadbed_width_m=None)]
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert road_surface_area([]) is None
|
||||
|
||||
@@ -32,7 +32,8 @@ SLOPE = {
|
||||
|
||||
|
||||
def _handoff_and_bill():
|
||||
handoff = build_handoff(preparation_table=build_prep(SLOPE, [], [], 0.15))
|
||||
table = build_prep(SLOPE, [], [], 0.15, road_surface_area_m2=8000.0)
|
||||
handoff = build_handoff(preparation_table=table)
|
||||
return handoff, build_bill(handoff)
|
||||
|
||||
|
||||
@@ -50,7 +51,8 @@ def test_준비공_줄은_내역_아니면_제외로_반드시_선다() -> None:
|
||||
def test_금액이_서는_줄은_수량째_본체에_선다() -> None:
|
||||
handoff, bill = _handoff_and_bill()
|
||||
topsoil = next(r for r in bill.rows if not r.is_group and r.name == "표토제거")
|
||||
assert Decimal(str(topsoil.quantity)).quantize(Decimal("0.01")) == Decimal("2359.04")
|
||||
# ⭐ 2026-09-13 「실무대로」 — 노면 8,000 + 절토 사면 6,726.9 ㎡ (두께를 안 곱함)
|
||||
assert Decimal(str(topsoil.quantity)).quantize(Decimal("0.01")) == Decimal("14726.90")
|
||||
in_bill = [
|
||||
r["name"] for r in handoff["work_items"] if r["origin"] == "preparation" and r["in_bill"]
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user