Merge remote-tracking branch 'origin/sub_laptop_1' into sub_desktop_1

This commit is contained in:
2026-09-15 06:00:39 +09:00
4 changed files with 80 additions and 5 deletions
@@ -166,19 +166,28 @@ def _side_segments(
berm = design.get("berm") or {}
berm_width = _num(berm.get("width_m")) or 0.0
# 소단 안쪽 기울기(°) 만큼은 평탄부로 봄 — 기울인 소단이 비탈로 읽혀 끊기지 않게.
berm_grade = math.tan(math.radians(_num(berm.get("slope_deg")) or 0.0))
# 설계선 표고는 넷째 자리 반올림 — 기울인 소단 조각이 그만큼 더 가파르게 읽힘(0.0069/0.1953).
berm_slack = 2e-4 if berm_width > 0 else 0.0
segments: list[SlopeSegment] = []
started = False
# ⚠ 설계선이 격자(0.5m)로 찍혀 소단 평탄부가 **여러 조각**으로 쪼개짐 — 이어진 평탄 조각을 모아
# 합이 소단 폭일 때만 소단으로 넘김(2026-09-15 · 한 조각만 보다가 첫 소단에서 사면이 끊겼음).
flat_run = 0.0
for start, end, run, rise in _outward(line, float(edge), side):
if run <= 1e-9:
continue
if abs(rise) <= _FLAT_RISE_M:
# 평탄부 — 측구 바닥·소단. 사면이 시작된 뒤라면 소단으로 보고 이어 간다.
if started and berm_width > 0 and abs(run - berm_width) < 0.05:
continue
if abs(rise) <= _FLAT_RISE_M + run * berm_grade + berm_slack:
# 평탄부 — 측구 바닥·소단. 사면이 시작된 뒤라면 모아 두고 다음 조각에서 판정.
if started:
break # 사면이 끝나고 평지를 만난 것이다
flat_run += run
continue
if flat_run > 0:
if not (berm_width > 0 and abs(flat_run - berm_width) < 0.05):
break # 사면이 끝나고 평지를 만난 것이다
flat_run = 0.0
ratio = run / abs(rise)
# 절토는 바깥으로 갈수록 오르고, 성토는 내려간다.
role = "cut" if rise > 0 else "fill"
@@ -54,6 +54,11 @@ from common_util.common_util_structure_lengths import structure_lengths
from config.config_db import run_with_connection
logger = logging.getLogger(__name__)
#: 소단 — 원단위 줄을 안 세우는 까닭(2026-09-15 조사).
BERM_COUNTED = (
"횡단 설계의 계단으로 토공(절·성토)·사면 면적(면고르기·파종·녹화)에 이미 들어감"
" — 따로 줄 안 세움"
)
router = APIRouter(prefix="/api/projects", tags=["B08 Quantity"])
@@ -95,6 +100,11 @@ def _collect_structures(
f"{definition.name}: {definition.design_owner} 가 이미 셈 — 중복 계상 방지"
)
continue
if type_id == "berm":
# 소단 계단은 횡단 설계가 절·성토 면적·사면길이에 이미 넣음 — 줄을 세우면
# 「산출식 없음」 거짓 막힘이 내역에 섬(2026-09-15 조사 · 임도 소단 품셈 공종 없음).
skipped.append(f"{definition.name}: {BERM_COUNTED}")
continue
if definition.reference_only:
skipped.append(f"{definition.name}: 전문 상세설계 대상 — 배치까지만")
continue
+5
View File
@@ -61,5 +61,10 @@
- [ ] 선떼붙이기(길이형) — 표 2-2-6 급별 m당 매수 · 단끊기 연장 = 직고 ÷ 단 직고 × 연장(5-16-1 [주]②)
- [ ] 조공·씨뿌리기·편책·녹생토·나무심기
- [ ] 소단 — 임도 조문(사면길이 2~3m 마다 폭 50~100㎝) · 사방 기준 아님
- 12장 D 표(PLAN.md) 고칠 것 — 소단 근거 「사방사업 세부기준 제3조1호(절·성토고 3~5m · 폭 0.5m 이상)」 → 임도 「시행규칙 별표2 Ⅰ.2.차.(5) 절토·성토한 경사면이 붕괴 또는 밀려 내려갈 우려가 있는 지역에는 사면길이 2~3m 마다 폭 50~100㎝」(조건부 · 우려 지역)
- 지금 서는 자리(조사) — 등록부 「소단」(C군 구간 · 폭 0.5 · 간격 3 · 안쪽 0° — 별표2 범위 안 가장 적게 파는 조합) → B06 계단 기하(`common_util_cross_berm`) → 절·성토 면적(토공)·사면길이에 들어감 · 자동 배치 없음(설계자가 놓음) · 품셈에 임도 소단 공종 없음
- [X] 흠 둘 고침 — ① 설계선 0.5m 격자에 소단 평탄부가 쪼개져 사면길이가 첫 소단에서 끊김(측점 20 성토 8.91 → 3.0m) → 이어진 평탄 조각을 모아 소단 폭과 대조 · 안쪽 기울기 반영 ② 소단 구조물이 원단위에 「산출식 없음」 거짓 막힘으로 섬 → 건너뜀 + 까닭. 검증완료(시험 · 936be972 읽기만)
- 936be972 전 측점 소단(0.5·3·0°) 가정 전후(읽기만) — 성토 18,247.12 → 41,179.33㎥(+125.7%) · 절토 6,686.30 → 6,719.03㎥ · 면고르기·파종·지장목 성토면 15,919.95 → 16,979.02㎡(+6.7% · 고치기 전엔 3,136.23 80%) · 절토면 5,588.88 → 6,258.46㎡(+12.0%) · 미폐합 측점 2 → 26
- ⚠ 녹화 화면 확인 보류 — 구조물 저장이 B06 이후 단계를 STALE 로 돌림(`_invalidate_downstream`) · 지워도 단계 상태는 안 돌아옴
- [ ] 개소형 — 대피소·작업장·차돌림곳·노출형 횡단수로
- [ ] 부대 — 가드레일·반사경·표지(그림 11-6~11-8 표준도 확인 · 번호판 500m 제안)
@@ -0,0 +1,51 @@
"""소단이 선 사면의 사면길이 — 첫 소단에서 끊기지 않을 것 (2026-09-15 소단 착수 전 조사).
(936be972 읽기만) 측점에 소단(0.5m · 3m · 0°) 놓자 성토 사면길이가 측점 20 에서
8.91 3.0m 끊겨 면고르기·파종 성토면 15,919.95 3,136.23(80%) 줄었음.
까닭 설계선이 0.5m 격자로 찍혀 소단 평탄부 0.5m 0.195 + 0.305 조각으로 쪼개짐
평탄부 조각 = 소단 판정이 맞아 거기서 사면이 끝난 것으로 . 안쪽 기울기(°) 주면
평탄부가 아예 평탄으로 읽혀 같은 자리에서 끊김.
"""
from __future__ import annotations
import pytest
from B06_Section.B06_Section_Engine_Design import compute_cross_design
from B08_Quantity.B08_Quantity_Engine_SlopeLength import station_slope
from common_util.common_util_cross_berm import BermSpec
# 노면보다 8m 낮은 평지 — 양쪽 성토 사면이 길게 섬.
SAMPLES = [{"offset_m": x / 2, "elevation_m": -8.0} for x in range(-160, 161)]
def _design(berm: BermSpec | None) -> dict:
return compute_cross_design(
SAMPLES, 0.0, ground_type="soil", section_mode="both_fill", berm=berm
)
@pytest.mark.parametrize("slope_deg", [0.0, 2.0])
def test_소단이_있어도_사면길이가_끝까지_이어진다(slope_deg: float) -> None:
plain = station_slope(0.0, _design(None))
stepped_design = _design(BermSpec(0.5, 3.0, slope_deg))
stepped = station_slope(0.0, stepped_design)
assert plain.fill_length_m > 15 # 양쪽 합 — 시험이 뜻을 가지는 크기
# 소단 평탄부는 사면길이에 안 들고, 비탈 조각은 끝까지 셈 — 같은 낙차라 거의 같음.
assert stepped.fill_length_m == pytest.approx(plain.fill_length_m, rel=0.05)
assert stepped.fill_height_m == pytest.approx(plain.fill_height_m, abs=0.2)
def test_소단_구조물은_원단위_줄을_안_세우고_까닭을_남긴다(monkeypatch, tmp_path) -> None:
"""소단 계단은 횡단 설계가 절·성토 면적·사면길이에 이미 넣음 — 따로 줄을 세우면 「산출식 없음」
거짓 막힘(formula_missing) 내역에 (2026-09-15 조사)."""
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance
from B08_Quantity import B08_Quantity_Router_Material as material
berm = StructureInstance(
structure_id="b", type_id="berm", placement="interval", start_m=100.0, end_m=120.0
)
monkeypatch.setattr(material, "load_structures", lambda root: (1, [berm]))
targets, _names, skipped = material._collect_structures(str(tmp_path))
assert targets == []
assert any("소단" in note and "토공" in note and "사면" in note for note in skipped)