From c7b9956bf31ea41596bc21f03375c6dd2e38297c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 15:02:43 +0900 Subject: [PATCH] =?UTF-8?q?feat(b06,b08):=20=EA=B4=80=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EB=B2=BD=EB=8F=84=20=EB=B2=BD=20=EB=AA=B8=20=E2=88=A9=20?= =?UTF-8?q?=EC=84=B1=ED=86=A0=20=ED=8F=90=ED=9A=8C=EB=A1=9C=20=EA=B2=B9?= =?UTF-8?q?=EC=B9=A8=EC=9D=84=20=EC=A4=84=20=EC=82=AC=EC=9C=A0=EB=A1=9C=20?= =?UTF-8?q?=E2=80=94=20=EB=8B=A4=EB=8B=A8=EC=97=90=EB=A7=8C=20=EB=9C=A8?= =?UTF-8?q?=EB=A9=B4=20=E3=80=8C=EB=8B=A4=EB=8B=A8=EB=A7=8C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=E3=80=8D=EB=A1=9C=20=EC=9D=BD=ED=9E=98(=E3=89=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 서버 Node 가 기준벽 역할별 넓이를 design.wall_fill_overlap 에 남기고 B08 이 다단 목록 통(base 표시 칸)으로 읽어 기준벽 줄 비고에 붙임 · 호출부(Router_Material) 안 늘림 · 936be972 빈 저장: 관 9 곳 유출 벽 258.12 1.417 · 720 1.403 · 804.18 1.166 · 982.64 0.807 · 620 0.136 · 440 0.046 · 533.98 0.004 · 85.05/900 0㎡ · 이 프로젝트 기준벽은 모두 미확정(금액 밖)이라 비고 안 뜸 · 벽 칸을 적으면 「1.42㎡ × 10m ≈ 14.2㎥」 · 금액 변화 0 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- .../B06_Section_Server_Calc_Prebuild.py | 9 +++- B06_Section/B06_Section_Structure_Layouts.ts | 22 +++++++--- B08_Quantity/B08_Quantity_Engine_Pipe.py | 43 ++++++++++++++++--- resources/tester/test_b08_extra_walls.py | 20 +++++++++ 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/B06_Section/B06_Section_Server_Calc_Prebuild.py b/B06_Section/B06_Section_Server_Calc_Prebuild.py index 02c08f8e..d032de2d 100644 --- a/B06_Section/B06_Section_Server_Calc_Prebuild.py +++ b/B06_Section/B06_Section_Server_Calc_Prebuild.py @@ -246,8 +246,15 @@ async def _recompute(project_id: UUID | str, route_id: int) -> int: rows = output.get("areas") mass_haul = output.get("mass_haul") # 선 다단 벽 목록(④) — 목록째 얹음(수가 아니라 `_AREA_KEYS` 로는 못 거름). 주인 측점엔 빈 목록도. + # 관 기준벽 벽 몸 겹침(㉡) — 역할별 ㎡ 한 벌. 빈 dict 도 실어 옛 값을 지움. extra_walls = [ - (float(row["chainage_m"]), {"extra_walls": row["extra_walls"]}) + ( + float(row["chainage_m"]), + { + "extra_walls": row["extra_walls"], + "wall_fill_overlap": row.get("wall_fill_overlap") or {}, + }, + ) for row in output.get("extra_walls") or [] if isinstance(row, dict) and isinstance(row.get("extra_walls"), list) ] diff --git a/B06_Section/B06_Section_Structure_Layouts.ts b/B06_Section/B06_Section_Structure_Layouts.ts index e954d9ef..726ef730 100644 --- a/B06_Section/B06_Section_Structure_Layouts.ts +++ b/B06_Section/B06_Section_Structure_Layouts.ts @@ -251,16 +251,21 @@ export interface BuiltExtraWall { fill_overlap_m2: number | null; } +export interface ExtraWallRow { + chainage_m: number; + extra_walls: BuiltExtraWall[]; + wall_fill_overlap: Record; +} + /** * 관(·독립 기슭막이) 주인 측점마다 **실제로 선** 다단 벽 목록(④, 2026-09-14). * * 단 수·높이는 지형이 정해 요청보다 적게 설 수 있음 — 그래서 요청 칸(`extra_wall_counts`)이 아니라 * 기하가 세운 결과를 남김(관 연장 `pipe_length_m` 과 같은 길). 주인 측점에는 빈 목록도 실어 옛 값을 지움. + * `wall_fill_overlap` — 관 기준벽 역할별 벽 몸 ∩ 성토 폐회로(㎡). B08 기준벽 줄 비고. */ -export function extraWallRows( - sections: readonly CrossSection[], -): Array<{ chainage_m: number; extra_walls: BuiltExtraWall[] }> { - const rows: Array<{ chainage_m: number; extra_walls: BuiltExtraWall[] }> = []; +export function extraWallRows(sections: readonly CrossSection[]): ExtraWallRow[] { + const rows: ExtraWallRow[] = []; for (const section of sections) { if (!section.culvert) continue; const owner = pipeOwnerChainage(section, sections); @@ -293,7 +298,14 @@ export function extraWallRows( }); }); } - rows.push({ chainage_m: section.chainage_m, extra_walls: walls }); + // 관 기준벽(유입·유출)도 같은 겹침 — 다단에만 사유가 뜨면 「다단만 문제」로 읽힘(㉡ 2026-09-14). + const wall_fill_overlap: Record = {}; + for (const wall of culvert.walls) { + if (areaInput && (wall.role === "inlet" || wall.role === "outlet")) { + wall_fill_overlap[wall.role] = Number(wallBodyInFillM2(wall.points, areaInput).toFixed(3)); + } + } + rows.push({ chainage_m: section.chainage_m, extra_walls: walls, wall_fill_overlap }); } return rows; } diff --git a/B08_Quantity/B08_Quantity_Engine_Pipe.py b/B08_Quantity/B08_Quantity_Engine_Pipe.py index b9a150fd..ded2e7b5 100644 --- a/B08_Quantity/B08_Quantity_Engine_Pipe.py +++ b/B08_Quantity/B08_Quantity_Engine_Pipe.py @@ -337,14 +337,27 @@ NOTE_FILL_OVERLAP = ( ) +#: 관 기준벽(유입·유출) 벽 몸 겹침 ㎡ — `design.wall_fill_overlap` {inlet, outlet}(서버 Node 가 잼, ㉡ 2026-09-14). +#: 호출부(`Router_Material`)를 안 늘리려고 다단 목록 통에 `base` 표시 칸으로 실어 옴 — 다단 줄은 거름. +WALL_OVERLAP_KEY = "wall_fill_overlap" + + def extra_walls_from_designs(designs: list[dict[str, Any]] | None) -> dict[float, list[dict]]: - """측점별 「선 다단 벽 목록」 — 목록이 있는 측점만(빈 목록도 담음 — 「다단 없음」).""" + """측점별 「선 다단 벽 목록」 — 목록이 있는 측점만(빈 목록도 담음 — 「다단 없음」). + 기준벽 겹침 값이 있으면 `{"base": 역할, "fill_overlap_m2": ㎡}` 칸을 뒤에 붙임.""" found: dict[float, list[dict]] = {} for row in designs or []: design = row.get("design") if isinstance(row, dict) else None - walls = design.get(EXTRA_WALLS_KEY) if isinstance(design, dict) else None - if isinstance(walls, list): - found[round(float(row.get("chainage_m") or 0.0), 3)] = walls + if not isinstance(design, dict): + continue + walls = design.get(EXTRA_WALLS_KEY) + overlaps = design.get(WALL_OVERLAP_KEY) + if not isinstance(walls, list) and not isinstance(overlaps, dict): + continue + entries = list(walls) if isinstance(walls, list) else [] + if isinstance(overlaps, dict): + entries += [{"base": role, "fill_overlap_m2": area} for role, area in overlaps.items()] + found[round(float(row.get("chainage_m") or 0.0), 3)] = entries return found @@ -438,6 +451,12 @@ def facility_structures( walls[0][3].append(NOTE_SIDE_UNKNOWN.format(side=side)) foundation = options.get("foundation" if legacy else "revet_foundation") kept = {k: v for k, v in options.items() if not k.startswith(("inlet_", "outlet_"))} + station_walls = _extra_walls_at(extra_walls, chainage) + base_overlap = { + str(entry["base"]): _num_or_zero(entry.get("fill_overlap_m2")) + for entry in station_walls + if entry.get("base") + } for role, label, values, notes, withheld, filled in walls: before, after = _num_or_zero(values["before_m"]), _num_or_zero(values["after_m"]) row = child_row(base, "revetment", label, f"{role}_revet") @@ -445,6 +464,18 @@ def facility_structures( if legacy and _blank(values["height_m"]): # 높이는 설계자 입력(계획홍수위 + 여유고) — 없으면 줄만 서고 금액 밖. reasons.append(own_height_unconfirmed()) + # 좌·우 한 벽으로 합친 독립 기슭막이는 그 측점에 선 벽 몸 전부. + overlap = ( + sum(base_overlap.values()) + if legacy and len(walls) == 1 + else base_overlap.get(role, 0.0) + ) + if overlap > 0 and not reasons: # 미확정 벽은 금액에 안 듦 — 두 번 셀 금액이 없음 + notes.append( + NOTE_FILL_OVERLAP.format( + area=overlap, length=before + after, volume=overlap * (before + after) + ) + ) row.update( start_m=chainage - before, end_m=chainage + after, @@ -458,7 +489,9 @@ 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): + for tier in station_walls: + if tier.get("base"): + continue side = "basin" if str(tier.get("side")) == "basin" else "outlet" counts[side] += 1 if not tier.get("height_set"): diff --git a/resources/tester/test_b08_extra_walls.py b/resources/tester/test_b08_extra_walls.py index 661dd5c9..6cd5c457 100644 --- a/resources/tester/test_b08_extra_walls.py +++ b/resources/tester/test_b08_extra_walls.py @@ -139,6 +139,26 @@ def test_서버가_벽_몸_겹침을_잰다() -> None: assert "wallBodyInFillM2(" in LAYOUTS and "fill_overlap_m2" in LAYOUTS +def test_관_기준벽도_겹침_사유가_뜬다() -> None: + """2026-09-14 브레인 ㉡ — 같은 겹침이 관 기준벽에도 있음. 다단에만 뜨면 「다단만 문제」로 읽힘. + 258.12 실측 유출 벽 몸 1.417㎡ 가 성토 폐회로 안.""" + designs = [ + {"chainage_m": 85.0, "design": {"extra_walls": [], "wall_fill_overlap": {"outlet": 1.417}}} + ] + rows = facility_structures([POINT], extra_walls_from_designs(designs)) + by_label = { + row["attachment_label"]: " ".join(row["notes"]) + for row in rows + if row.get("notes") is not None + } + outlet = next(text for label, text in by_label.items() if "유출" in label) + inlet = next(text for label, text in by_label.items() if "유입" in label) + assert "성토 면적에도" in outlet and "1.42㎡" in outlet and "14.2㎥" in outlet + assert "성토 면적에도" not in inlet # 잰 값이 없는 벽엔 지어내지 않음 + assert not _tiers(rows) # 기준벽 값이 다단 줄로 새지 않음 + assert "wall_fill_overlap" in LAYOUTS and '"wall_fill_overlap"' in PREBUILD + + def test_목록이_없으면_종전과_같다() -> None: assert not _tiers(facility_structures([POINT])) assert not _tiers(facility_structures([POINT], {}))