feat(B08): 성토고를 사면표에 실어 수평 규준틀 개소를 셈
㉔. 확정 7(준비공 3줄)의 마지막 하나가 닫힘. - 사면길이 유도가 이미 성토 사면을 재고 있어 **성토 조각의 수직 낙차 합**이 곧 성토고임. `StationSlope.fill_height_m` 으로 실어 사면표까지 옮김. ⚠ 좌우가 다르면 **큰 쪽**을 씀 — 「중심점 성토고 5m 이상」 판정은 가장 높은 쪽이 기준이고 양쪽을 더하면 실제보다 두 배가 됨. - 수평 규준틀은 **성토고 5m 이상 측점마다 한 개소**(11-3 [주]①). ⚠ 비탈규준틀(「10m 이상 20m마다」)과 **기준이 다름** — 이쪽은 지점 조건이라 간격이 없음. 두 기준을 같은 식으로 쓰면 조용히 틀림. - 성토고 칸이 아예 없으면 0 으로 때우지 않고 미확보로 둠. 성토고가 다 낮으면 0 개소가 **값**임 — 「못 셈」과 다름. 셋을 시험으로 갈라 못 박음. 실노선 확인 — 비탈 규준틀 31개소(617.475m ÷ 20 + 1) · 수평 규준틀 39개소. 검증 — 준비공 18건 통과, 전체 647 passed, tsc 오류 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,13 +72,23 @@ def batter_frame_count(slope_rows: Iterable[dict[str, Any]]) -> tuple[int, list[
|
||||
return count, notes
|
||||
|
||||
|
||||
def level_frame_count(slope_rows: Iterable[dict[str, Any]]) -> tuple[int, list[str]]:
|
||||
"""수평 규준틀 — **중심점 성토 높이 5m 이상**에 설치(품셈 11-3 [주]①).
|
||||
def level_frame_count(slope_rows: Iterable[dict[str, Any]]) -> tuple[int | None, list[str]]:
|
||||
"""수평 규준틀 — **성토고 5m 이상 측점마다** 한 개소(품셈 11-3 [주]①).
|
||||
|
||||
⚠ 성토고는 사면표에 없다 — 그 값을 받기 전에는 **0 으로 때우지 않고 미확보**로 둔다.
|
||||
⚠ 「비탈길이 10m 이상 20m마다」인 비탈규준틀과 **기준이 다르다** — 이쪽은 **지점 조건**이라
|
||||
간격이 없다. 두 기준을 같은 식으로 쓰면 조용히 틀린다.
|
||||
⚠ 성토고 칸이 아예 없으면 **0 으로 때우지 않고** 미확보로 둔다 — 「없음」과 다르다.
|
||||
"""
|
||||
return 0, [
|
||||
"중심점 성토고 5m 이상 지점에 설치(품셈 11-3 [주]①)인데 성토고가 이 표에 없어 개소를 못 셈"
|
||||
rows = list(slope_rows)
|
||||
if not rows:
|
||||
return None, ["사면표가 없어 성토고를 못 봄"]
|
||||
if all("fill_height_m" not in row for row in rows):
|
||||
return None, ["성토고가 사면표에 없어 개소를 못 셈 (품셈 11-3 [주]① 「성토고 5m 이상」)"]
|
||||
tall = [
|
||||
row for row in rows if float(row.get("fill_height_m") or 0.0) >= LEVEL_MIN_FILL_HEIGHT_M
|
||||
]
|
||||
return len(tall), [
|
||||
f"성토고 {LEVEL_MIN_FILL_HEIGHT_M:g}m 이상 측점 {len(tall)}곳 (품셈 11-3 [주]①)"
|
||||
]
|
||||
|
||||
|
||||
@@ -119,15 +129,7 @@ def preparation_rows(
|
||||
"work_item_code": "FP-09-21",
|
||||
},
|
||||
_batter_frame_row(list(slope_rows)),
|
||||
{
|
||||
"group": "준비공",
|
||||
"item": "수평 규준틀",
|
||||
"unit": "개소",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": level_frame_count(())[1][0],
|
||||
"work_item_code": "FP-11-03",
|
||||
},
|
||||
_level_frame_row(list(slope_rows)),
|
||||
]
|
||||
|
||||
|
||||
@@ -179,6 +181,25 @@ def _batter_frame_row(slope_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _level_frame_row(slope_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""수평 규준틀 한 줄. 성토고를 못 보면 미확보, 보면 개소가 선다."""
|
||||
count, notes = level_frame_count(slope_rows)
|
||||
return {
|
||||
"group": "준비공",
|
||||
"item": "수평 규준틀",
|
||||
"unit": "개소",
|
||||
"amount": float(count) if count is not None else None,
|
||||
"status": STATUS_READY if count is not None else STATUS_PENDING,
|
||||
"reason": "; ".join(notes)
|
||||
+ (
|
||||
" · 재료량은 품셈 11-3 [주]④ 「설계수량에 따른다」라 미확보"
|
||||
if count is not None
|
||||
else ""
|
||||
),
|
||||
"work_item_code": "FP-11-03",
|
||||
}
|
||||
|
||||
|
||||
def erosion_rows(structures: Iterable[dict[str, Any]] = ()) -> list[dict[str, Any]]:
|
||||
"""사방공 줄 — 이 노선에 사방 시설이 **있을 때만** 값이 선다."""
|
||||
found = sorted(
|
||||
|
||||
@@ -69,6 +69,8 @@ class SlopeAreaRow:
|
||||
chainage_m: float
|
||||
distance_m: float = 0.0
|
||||
berm_width_m: float = 0.0
|
||||
# 성토고(m) — 수평 규준틀 개소 판정(품셈 11-3 [주]① 「성토고 5m 이상」)이 쓴다.
|
||||
fill_height_m: float = 0.0
|
||||
unclosed: bool = False
|
||||
lengths: dict[str, float] = field(default_factory=dict)
|
||||
areas: dict[str, float] = field(default_factory=dict)
|
||||
@@ -102,6 +104,7 @@ def build_rows(
|
||||
row = SlopeAreaRow(
|
||||
chainage_m=slope.chainage_m,
|
||||
berm_width_m=slope.berm_width_m,
|
||||
fill_height_m=slope.fill_height_m,
|
||||
unclosed=slope.unclosed,
|
||||
)
|
||||
for series, faces in SERIES:
|
||||
@@ -153,6 +156,7 @@ def build_table(
|
||||
"chainage_m": row.chainage_m,
|
||||
"distance_m": row.distance_m,
|
||||
"berm_width_m": row.berm_width_m,
|
||||
"fill_height_m": row.fill_height_m,
|
||||
"unclosed": row.unclosed,
|
||||
"lengths": row.lengths,
|
||||
"areas": row.areas,
|
||||
|
||||
@@ -59,6 +59,10 @@ class StationSlope:
|
||||
cut_length_m: float = 0.0
|
||||
fill_length_m: float = 0.0
|
||||
berm_width_m: float = 0.0
|
||||
# 성토고(m) — 성토 사면 조각들의 **수직 낙차 합**. 노면 끝에서 원지반까지 내려간 높이다.
|
||||
# ⚠ 좌우가 다르면 **큰 쪽**을 쓴다. 「중심점 성토고 5m 이상」(품셈 11-3 [주]①) 판정은
|
||||
# 가장 높은 쪽이 기준이고, 양쪽을 더하면 실제보다 두 배가 된다.
|
||||
fill_height_m: float = 0.0
|
||||
segments: tuple[SlopeSegment, ...] = ()
|
||||
# 사면이 샘플 범위 끝까지 원지반을 못 만나 **면적이 잘린** 측점.
|
||||
# 설계 엔진이 `slope_unclosed` 로 이미 경고하는 값을 그대로 물고 온다. 잘린 측점은
|
||||
@@ -204,10 +208,15 @@ def station_slope(chainage_m: float, design: dict[str, Any]) -> StationSlope:
|
||||
for side in ("left", "right"):
|
||||
segments.extend(_side_segments(design, side, ratios))
|
||||
berm = design.get("berm") or {}
|
||||
fill_by_side = {
|
||||
side: sum(abs(s.rise_m) for s in segments if s.role == "fill" and s.side == side)
|
||||
for side in ("left", "right")
|
||||
}
|
||||
return StationSlope(
|
||||
chainage_m=float(chainage_m),
|
||||
cut_length_m=sum(s.length_m for s in segments if s.role == "cut"),
|
||||
fill_length_m=sum(s.length_m for s in segments if s.role == "fill"),
|
||||
fill_height_m=max(fill_by_side.values(), default=0.0),
|
||||
berm_width_m=_num(berm.get("width_m")) or 0.0,
|
||||
segments=tuple(segments),
|
||||
unclosed=bool(design.get("slope_unclosed")),
|
||||
|
||||
Reference in New Issue
Block a user