"""유토곡선에 넘길 구조물 몫 — 채집석 공제 · 구조물 잔토 (2026-09-08). ⚠ 겨누는 것 다섯 ① 둘 다 **양수**로 낸다 — 빼고 더하는 것은 받는 쪽(B06) 몫 ② ⚠ 「아직 안 옴(None)」과 「없음(0)」을 가른다 ③ **측점별로도** 낸다 — 총량만 주면 잔량 비례로 흩어져 운반거리가 틀어진다 ④ 구조물이 구간이라 **가운데 측점**을 자리로 본다 ⑤ 칸 이름이 받는 쪽과 같은 낱말이다 """ from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Engine_HaulInputs import ( # noqa: E402 STRUCTURE_SPOIL_KEY, STRUCTURE_SPOIL_POINTS_KEY, haul_inputs, ) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402 COLLECTED_STONE_KEY, build_table, ) def 표(*ranges: tuple[float, float]) -> dict: return build_table( [ { "structure_id": f"s{index}", "type_id": "masonry_wet", "start_m": start, "end_m": end, "options": { "height_m": 2.5, "length_m": end - start, "back_len_cm": 45, "foundation": "기초유", }, } for index, (start, end) in enumerate(ranges) ] ) def test_둘_다_양수로_낸다() -> None: got = haul_inputs(표((0.0, 10.0))) assert got[COLLECTED_STONE_KEY] > 0 assert got[STRUCTURE_SPOIL_KEY] > 0 def test_아직_안_옴과_없음을_가른다() -> None: """⚠ 0 으로 눅이면 받는 쪽이 「없다」와 「못 받았다」를 못 가른다.""" 빈값 = haul_inputs(None) assert 빈값[STRUCTURE_SPOIL_KEY] is None assert 빈값[COLLECTED_STONE_KEY] is None assert 빈값[STRUCTURE_SPOIL_POINTS_KEY] == [] def test_측점별로도_낸다() -> None: got = haul_inputs(표((0.0, 10.0), (100.0, 110.0))) points = got[STRUCTURE_SPOIL_POINTS_KEY] assert [row["chainage_m"] for row in points] == [5.0, 105.0] assert abs(sum(row["spoil_m3"] for row in points) - got[STRUCTURE_SPOIL_KEY]) < 0.01 def test_측점_차례로_선다() -> None: """⚠ 받는 쪽이 잔량에 얹을 때 차례가 어긋나면 운반거리가 틀어진다.""" points = haul_inputs(표((100.0, 110.0), (0.0, 10.0)))[STRUCTURE_SPOIL_POINTS_KEY] assert [row["chainage_m"] for row in points] == [5.0, 105.0] def test_칸_이름이_받는_쪽과_같다() -> None: assert STRUCTURE_SPOIL_KEY == "structure_spoil_m3" assert COLLECTED_STONE_KEY == "collected_stone_deduction_m3" def test_지반_갈래를_함께_낸다() -> None: """⚠ 새 판정이 아니라 **구조물터파기가 쓰는 그 값**이다 — 두 벌로 짜면 갈린다.""" unit = build_table( [ { "structure_id": "s1", "type_id": "masonry_wet", "start_m": 0.0, "end_m": 10.0, "options": { "height_m": 2.5, "length_m": 10.0, "back_len_cm": 45, "foundation": "기초유", }, } ], None, None, {0.0: "ripping_rock", 10.0: "ripping_rock"}, ) point = haul_inputs(unit)[STRUCTURE_SPOIL_POINTS_KEY][0] assert point["ground_type"] == "ripping_rock" assert point["ground_label"] == "암절취" assert "암절취" in point["ground_basis"] def test_섞인_구조물은_모름으로_보낸다() -> None: """⚠ 터파기에서 다수결로 안 고른 그 규칙 그대로 — 받는 쪽이 「모르는 몫」으로 드러낸다.""" unit = build_table( [ { "structure_id": "s1", "type_id": "masonry_wet", "start_m": 0.0, "end_m": 10.0, "options": { "height_m": 2.5, "length_m": 10.0, "back_len_cm": 45, "foundation": "기초유", }, } ], None, None, {0.0: "soil", 10.0: "blasting_rock"}, ) point = haul_inputs(unit)[STRUCTURE_SPOIL_POINTS_KEY][0] assert point["ground_type"] is None assert "섞여" in point["ground_basis"] def test_상태를_값으로_적어_보낸다() -> None: """⚠⚠ 유토곡선 잔량은 **다짐상태**인데 이 값은 **자연상태**다 — 그냥 더하면 섞인다. 말로만 두면 다음 사람이 못 본다. 총량에도, 측점마다도 상태를 적는다. """ from B08_Quantity.B08_Quantity_Engine_HaulInputs import ( VOLUME_BASIS_KEY, VOLUME_BASIS_NATURAL, ) got = haul_inputs(표((0.0, 10.0))) assert got[VOLUME_BASIS_KEY] == VOLUME_BASIS_NATURAL == "natural" assert all(row[VOLUME_BASIS_KEY] == "natural" for row in got[STRUCTURE_SPOIL_POINTS_KEY]) # 빈 값일 때도 칸은 있다 — 받는 쪽이 「상태를 모른다」와 「값이 없다」를 안 헷갈리게. assert haul_inputs(None)[VOLUME_BASIS_KEY] == "natural" def test_채집석을_갈래별로_낸다() -> None: """⚠ 축을 맞추려면 갈래가 있어야 한다 — 받는 쪽이 ×C 로 다짐 축에 맞춰 뺀다.""" from B08_Quantity.B08_Quantity_Engine_HaulInputs import ( COLLECTED_STONE_BY_GROUND_KEY, COLLECTED_STONE_UNKNOWN_KEY, ) unit = build_table( [ { "structure_id": "s1", "type_id": "masonry_wet", "start_m": 0.0, "end_m": 10.0, "options": {"height_m": 2.5, "length_m": 10.0, "back_len_cm": 45}, } ], None, None, {0.0: "ripping_rock", 10.0: "ripping_rock"}, ) got = haul_inputs(unit) assert got[COLLECTED_STONE_BY_GROUND_KEY]["ripping_rock"] > 0 assert got[COLLECTED_STONE_UNKNOWN_KEY] == 0.0 # 갈래별 합이 총량과 같다 — 어느 쪽으로도 새지 않는다. assert abs(sum(got[COLLECTED_STONE_BY_GROUND_KEY].values()) - got[COLLECTED_STONE_KEY]) < 0.01 def test_갈래를_못_가른_몫은_따로_낸다() -> None: """⚠ 계수가 없으므로 환산하지 않는다 — 받는 쪽이 그 사실을 알아야 한다.""" from B08_Quantity.B08_Quantity_Engine_HaulInputs import ( COLLECTED_STONE_BY_GROUND_KEY, COLLECTED_STONE_UNKNOWN_KEY, ) unit = build_table( [ { "structure_id": "s1", "type_id": "masonry_wet", "start_m": 0.0, "end_m": 10.0, "options": {"height_m": 2.5, "length_m": 10.0, "back_len_cm": 45}, } ], None, None, {0.0: "soil", 10.0: "blasting_rock"}, ) got = haul_inputs(unit) assert got[COLLECTED_STONE_BY_GROUND_KEY] == {} assert got[COLLECTED_STONE_UNKNOWN_KEY] > 0 def test_원문이_없다는_사실을_값과_함께_보낸다() -> None: """⚠ 벽 입적 ↔ 원바닥 암 관계를 정한 원문이 없다 — 받는 쪽이 그 한계를 알아야 한다.""" basis = haul_inputs(표((0.0, 10.0)))["collected_stone_basis"] assert "원문이 없음" in basis and "실무 시트는 그냥 뺌" in basis