fix(b06,b08): 높이 안 적은 다단 벽은 성토를 안 깎음 — 면적은 첫 미확정 단부터 아래를 없는 것으로 봄 · B08 도 그 아랫단을 「윗단(N단)이 미확정」 으로 같은 선에서 끊음(㉠)

258.12 실측 — 1단만 확정: 성토 감소 −60.36 → −52.50㎥(2단 몫 7.86㎥ 되살아남, +35,534원) · 1단 미확정: 성토·본공사비 다단 없을 때와 같음

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
2026-09-14 14:08:49 +09:00
co-authored by Claude Opus 5
parent d352774ce2
commit 8dfa68df32
3 changed files with 69 additions and 18 deletions
@@ -634,7 +634,8 @@ export function computeCulvertLayout(
});
// 성토부선 + 다단 기슭막이(2026-08-22 — 보호공 삭제, 윗면 선만 성토부선으로).
// 유출측과 **집수정 계류측**이 같은 체계를 쓴다: 5m 넘으면 다단을 둘 수 있다.
const extras = buildExtrasAt(outletWall ? pipeCorners.outlet.bottom : null, {
const outletStart = outletWall ? pipeCorners.outlet.bottom : null;
const outletInput = {
startBottomElevation: outletWall?.bottomBack.elevation ?? 0,
outward: outletInfo.outward,
groundAt,
@@ -643,24 +644,46 @@ export function computeCulvertLayout(
ownerForm: outletWallSpec.form,
equalize: equalizeExtras === true,
leanFor: extraLean,
});
};
const extras = buildExtrasAt(outletStart, outletInput);
// 독립 기슭막이(관 숨김)는 집수정이 없어 이 채널이 **유입측 벽의 성토부선·다단**이다 —
// 유출측(`extras`)과 같은 시작점(관 하단 꼭짓점)·같은 `bextra` 키(2026-08-29 사용자).
const basinExtras = buildExtrasAt(
culvert.hidden_pipe ? (inletWall ? pipeCorners.inlet.bottom : null) : basinFillStart,
{
startBottomElevation: culvert.hidden_pipe
? (inletWall?.bottomBack.elevation ?? 0)
: basinFillBottom,
outward: inletInfo.outward,
groundAt,
limitOffset: inletInfo.limit,
adjusts: (revetShift?.basinExtras ?? []).map(adjustOf),
ownerForm: inletWallSpec.form,
const basinStart = culvert.hidden_pipe
? inletWall
? pipeCorners.inlet.bottom
: null
: basinFillStart;
const basinInput = {
startBottomElevation: culvert.hidden_pipe
? (inletWall?.bottomBack.elevation ?? 0)
: basinFillBottom,
outward: inletInfo.outward,
groundAt,
limitOffset: inletInfo.limit,
adjusts: (revetShift?.basinExtras ?? []).map(adjustOf),
ownerForm: inletWallSpec.form,
equalize: false,
leanFor: extraLean,
};
const basinExtras = buildExtrasAt(basinStart, basinInput);
/** ⭐ 면적용 다단 — **미확정 단(높이 안 적음)부터 아래는 없는 것**으로 봄(2026-09-14 브레인 판정
* 「미확정 벽은 성토도 안 깎음 · 벽 금액과 성토 감소가 함께」). 그림은 그대로 다 그림.
* B08 도 첫 미확정 단 아래는 미확정으로 셈(`facility_structures`) — 두 곳이 같은 선에서 끊김. */
const confirmedOnly = (
built: OutletExtrasResult,
start: OffsetPoint | null,
input: typeof outletInput,
): OutletExtrasResult => {
const firstUnset = built.appliedAdjusts.findIndex((applied) => applied.h == null);
if (firstUnset < 0) return built;
return buildExtrasAt(start, {
...input,
adjusts: input.adjusts.slice(0, firstUnset),
equalize: false,
leanFor: extraLean,
},
);
});
};
const extrasForArea = confirmedOnly(extras, outletStart, outletInput);
const basinExtrasForArea = confirmedOnly(basinExtras, basinStart, basinInput);
// ── 성토 사면 구간 확정. 벽 자리가 굳은 뒤에 물매를 역산해야 관 길이 맞춤(벽 이동)이
// 접점을 다시 깨뜨리지 않는다 — 종전 어긋남의 직접 원인이 이 순서였다.
@@ -700,8 +723,8 @@ export function computeCulvertLayout(
trimMinSlope = { points: [...(trimMinSlope?.points ?? []), ...points] };
}
};
extendTrimSlope(drawnFillPoints(extras), outletInfo.outward);
extendTrimSlope(drawnFillPoints(basinExtras), inletInfo.outward);
extendTrimSlope(drawnFillPoints(extrasForArea), outletInfo.outward);
extendTrimSlope(drawnFillPoints(basinExtrasForArea), inletInfo.outward);
const outletWallFinal = walls.find((wall) => wall.role === "outlet") ?? null;
const outletSlope = outletWallFinal ? slopeOf(outletWallFinal) : null;
+7
View File
@@ -326,6 +326,8 @@ UNCONFIRMED_EXTRA_HEIGHT = (
"다단 높이를 안 적음 — 기본 1.5m 를 지반에 맞춰 그린 {height}m 로 섰음 · 적으면 금액이 섬"
)
UNCONFIRMED_EXTRA_FORM = "다단 형태를 안 정해 기준벽의 기본 형태 「{form}」로 섰음"
#: 첫 미확정 단 아래는 횡단도 면적도 없는 것으로 봄(B06 `confirmedOnly`) — 같은 선에서 끊음.
UNCONFIRMED_EXTRA_ABOVE = "윗단({above}단)이 미확정이라 이 단도 미확정 — 윗단 높이를 적으면 섬"
def extra_walls_from_designs(designs: list[dict[str, Any]] | None) -> dict[float, list[dict]]:
@@ -448,9 +450,12 @@ def facility_structures(
# 다단 — 기준벽의 기초·(독립이면) 제원 칸을 물려받고, 형태·높이·구간은 선 벽 목록 값.
parents = {role: (values, filled) for role, _label, values, _notes, _w, filled in walls}
counts = {"outlet": 0, "basin": 0}
first_unset: dict[str, int] = {}
for tier in _extra_walls_at(extra_walls, chainage):
side = "basin" if str(tier.get("side")) == "basin" else "outlet"
counts[side] += 1
if not tier.get("height_set"):
first_unset.setdefault(side, counts[side])
parent_values, parent_filled = parents.get(
EXTRA_SIDE_ROLES[side], next(iter(parents.values()), ({}, []))
)
@@ -460,6 +465,8 @@ def facility_structures(
reasons = []
if not tier.get("height_set"):
reasons.append(UNCONFIRMED_EXTRA_HEIGHT.format(height=f"{height:g}"))
elif first_unset.get(side, counts[side]) < counts[side]:
reasons.append(UNCONFIRMED_EXTRA_ABOVE.format(above=first_unset[side]))
if not tier.get("form_set") and any(str(f).startswith("형태") for f in parent_filled):
reasons.append(UNCONFIRMED_EXTRA_FORM.format(form=form))
label = f"{EXTRA_SIDE_LABELS[side]} 다단 기슭막이 {counts[side]}"
+21
View File
@@ -104,6 +104,27 @@ def test_다단_줄이_원단위와_금액_합에_든다() -> None:
assert tiers[1]["unconfirmed"]
def test_윗단이_미확정이면_아랫단도_미확정() -> None:
"""면적은 첫 미확정 단부터 아래를 없는 것으로 봄(B06) — B08 도 같은 선에서 끊어야 성토·벽이 함께 움직임."""
walls = [
dict(WALLS[1], key="extra0"),
dict(WALLS[0], key="extra1"),
] # 1단 미확정 · 2단 높이 적음
tiers = _tiers(facility_structures([POINT], {85.0: walls}))
assert "다단 높이를 안 적음" in tiers[0]["unconfirmed"]
assert "윗단(1단)이 미확정" in tiers[1]["unconfirmed"]
def test_미확정_단은_성토를_안_깎는다() -> None:
"""2026-09-14 브레인 판정 — 미확정 벽이 성토만 깎으면 「벽도 성토도 없는 빈 자리」가 생김."""
geom = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Geom.ts").read_text(
encoding="utf-8"
)
assert "extendTrimSlope(drawnFillPoints(extrasForArea)" in geom
assert "extendTrimSlope(drawnFillPoints(basinExtrasForArea)" in geom
assert "applied.h == null" in geom
def test_목록이_없으면_종전과_같다() -> None:
assert not _tiers(facility_structures([POINT]))
assert not _tiers(facility_structures([POINT], {}))