fix(B06): 화면 실측으로 잡은 사토장 흠 둘 — 지운 뒤 남던 값 · 가짜 경고
⚠ **시험만으로는 안 잡히던 자리임.** 실제 프로젝트에 사토장을 놓고 눈으로 본 뒤 잡음.
① **사토장을 지워도 값이 남음** — `enforce_spoil_fills` 가 **얹기만** 해서, 구조물을
지운 뒤에도 저장분의 `spoil_fill_*` 가 그대로 남아 횡단도에 계속 그려지고 면적표에도
섰음. ⇒ 사토장이 덮지 않는 측점에 **값이 남아 있으면** 사토장 없이 다시 계산해 지움.
노선 성토도 그때 원래 값으로 돌아옴(사토장 몫을 빼 두었던 것이 복구됨).
⚠ **칸이 있는 것과 값이 있는 것은 다름** — 설계 결과는 사토장이 없어도 `0.0` 을 늘
실으므로 「값이 남은」 측점만 되돌림(`_has_spoil_value`)
② **말풍선에 늘 뜨던 가짜 경고** — 「⚠ 0.0㎥ 는 못 담음」. 폭을 이분법으로 찾아
400 − 399.9937 = 0.0063 이 남는 것이라, 표시 자릿수에서 안 보이는 몫은 경고 안 함
실측(프로젝트 fa76c162) — 지운 뒤 사토장 0 · 용량 null · 노선 성토가 정확히
「지우기 전 + 옮겨 갔던 몫」으로 복구됨(100m 1.7563+4.9217=6.678 …).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,17 @@ def _spans(sections: list[dict[str, Any]], start_m: float, end_m: float) -> list
|
||||
return spans
|
||||
|
||||
|
||||
def _has_spoil_value(design: dict[str, Any]) -> bool:
|
||||
"""그 측점에 **사토장 값이 실제로 남아 있나**. 칸이 0 으로 있는 것은 값이 아니다."""
|
||||
for key in ("spoil_fill_area_m2", "spoil_fill_width_m", "spoil_fill_capacity_m3"):
|
||||
try:
|
||||
if float(design.get(key) or 0.0) > 0:
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return bool(design.get("spoil_fill_structure_id"))
|
||||
|
||||
|
||||
def _side_of(design: dict[str, Any], option: Any) -> str | None:
|
||||
"""쌓는 쪽 — 「좌·우」면 그대로, 「자동」이면 그 측점의 **성토 쪽**."""
|
||||
word = str(option or "").strip()
|
||||
@@ -169,10 +180,8 @@ def enforce_spoil_fills(
|
||||
try:
|
||||
_revision, structures = load_structures(str(project_root))
|
||||
except Exception: # noqa: BLE001 — 정본이 없으면 사토장도 없다
|
||||
return 0
|
||||
structures = []
|
||||
sites = spoil_sites(structures)
|
||||
if not sites:
|
||||
return 0
|
||||
|
||||
def recompute(section, side, width_m, slope_ratio_n, longitudinal_data, standard_spec):
|
||||
design = section.get("design")
|
||||
@@ -206,6 +215,45 @@ def enforce_spoil_fills(
|
||||
return None
|
||||
|
||||
changed = 0
|
||||
# ⚠⚠ **지운 사토장이 그림·수량에 남던 자리**(2026-09-09 화면 실측으로 잡음).
|
||||
# `enforce_spoil_fills` 는 **얹기만** 했으므로 구조물을 지워도 저장분의 `spoil_fill_*`
|
||||
# 가 그대로 남아 **횡단도에 계속 그려지고 면적표에도 섰다.**
|
||||
# ⇒ 사토장이 덮지 않는 측점에 값이 남아 있으면 **사토장 없이 다시 계산**해 지운다.
|
||||
# (노선 성토도 그때 원래 값으로 돌아온다 — 사토장 몫을 빼 두었기 때문이다.)
|
||||
covered: set[float] = set()
|
||||
for site in sites:
|
||||
for section in _sections_in(cross_sections, site["start_m"], site["end_m"]):
|
||||
covered.add(float(section["chainage_m"]))
|
||||
for section in cross_sections:
|
||||
design = section.get("design")
|
||||
if not isinstance(design, dict):
|
||||
continue
|
||||
chainage = section.get("chainage_m")
|
||||
if chainage is None or float(chainage) in covered:
|
||||
continue
|
||||
# ⚠ **칸이 있는 것과 값이 있는 것은 다르다** — 설계 결과는 사토장이 없어도
|
||||
# `spoil_fill_area_m2: 0.0` 을 늘 싣는다. 「값이 남아 있는」 측점만 되돌린다.
|
||||
if not _has_spoil_value(design):
|
||||
continue
|
||||
side = _side_of(design, None)
|
||||
plain = recompute(section, side, 0.0, None, longitudinal, standard) if side else None
|
||||
if plain is None:
|
||||
# 다시 계산할 수 없으면 **적어도 칸은 지운다** — 값이 남아 그려지는 것보다 낫다.
|
||||
for key in [k for k in design if str(k).startswith("spoil_fill_")]:
|
||||
design.pop(key, None)
|
||||
# 노선 성토는 사토장 몫을 뺀 값이라, 다시 계산 못 하면 그 몫을 되돌려 준다.
|
||||
moved = float(design.get("spoil_fill_replaced_fill_m2") or 0.0)
|
||||
if moved > 0:
|
||||
design["fill_area_m2"] = round(float(design.get("fill_area_m2") or 0.0) + moved, 4)
|
||||
changed += 1
|
||||
continue
|
||||
for key in ("status", "pavement_suggested", *USER_TOUCHED_KEYS):
|
||||
if design.get(key) is not None:
|
||||
plain[key] = design[key]
|
||||
section["design"] = plain
|
||||
changed += 1
|
||||
if not sites:
|
||||
return changed
|
||||
for site in sites:
|
||||
sections = _sections_in(cross_sections, site["start_m"], site["end_m"])
|
||||
if not sections:
|
||||
|
||||
@@ -36,7 +36,11 @@ export function spoilFillTooltip(design: SpoilFillDrawing): string {
|
||||
const placed = Number(design.spoil_fill_placed_m3 ?? 0);
|
||||
lines.push(`구간 용량 ${capacity.toFixed(1)}㎥ 중 ${placed.toFixed(1)}㎥ 담김`);
|
||||
const unplaced = Number(design.spoil_fill_unplaced_m3 ?? 0);
|
||||
if (unplaced > 0) {
|
||||
// ⚠ **적어 보이는 0 을 경고로 띄우지 않는다**(2026-09-09 화면 실측). 폭을 이분법으로
|
||||
// 찾으므로 용량과 담긴 양이 소수점 아래에서 조금 남는다(400 − 399.9937 = 0.0063).
|
||||
// 그것을 「못 담음」으로 띄우면 **늘 경고가 뜬 채**가 되어 진짜 경고가 안 보인다.
|
||||
// 표시 자릿수(0.1㎥)에서 보이지 않는 몫은 없는 것으로 본다.
|
||||
if (unplaced >= 0.05) {
|
||||
lines.push(`⚠ ${unplaced.toFixed(1)}㎥ 는 못 담음 — 지반 자료가 있는 데까지만 넓힘`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user