refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)

B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음).
화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음.
B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry
로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠.
B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져
부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음.
B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함.
B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
2026-09-22 12:27:45 +09:00
co-authored by Claude Opus 5
parent 185f800d23
commit 8472fc9f40
231 changed files with 1617 additions and 1216 deletions
@@ -0,0 +1,171 @@
"""콘크리트 포장 전개식 — **면적 × 두께 · 거푸집 양면 · 수축줄눈** (2026-09-15 브레인 포장 둘).
근거
① 임도기술교본 3-2 기본설계 — 「포장 폭의 3.0m, 두께 0.2m를 기준으로 곡선부에는 확폭」
② 임도기술교본 부록 4-7 7-1 3.5.1 — 「수축줄눈의 간격은 4-6m를 기준」
③ 산림품셈 12-6 콘크리트 포장(인력) — 비닐·철망깔기·포설·양생 포함 · 거푸집·줄눈 제외 ·
[주]② 비닐·양생재·철망 재료 별도 · 12-7-1 포장절단 · 12-8 콘크리트 포장 거푸집(연장 m)
④ 실무 울진소광 `02-1-공종수량산출` 「포장및난간개거」 — 면적 = B × L · 거푸집(1면기준) = 2L ·
수축줄눈 = 내림(L÷6) × B · 「포장확폭」 면적 = 거리 × 확폭 · 신축줄눈 = 면적 ÷ 6
⭐ 폭·확폭·두께는 **B06 이 이미 앎** — 빈 칸만 B06 에서 잇고 칸이 이김(판정 ①④ · 한 값 원칙).
"""
from __future__ import annotations
import math
from typing import Any
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Base import Component, _num, routed
SOURCES = (
"산식 = 임도기술교본 3-2(폭·두께·곡선부 확폭) · 부록 4-7 7-1(수축줄눈 4-6m) · 실무 울진소광"
" 「포장및난간개거」·「포장확폭」(면적 B×L · 거푸집 2L · 줄눈 내림(L÷간격)×B"
" · 확폭 줄눈 면적÷간격)"
)
NOTE_JOINT = (
"수축줄눈 = 12-7-1 포장절단(m)으로 이음 — 12-7-2 줄눈설치는 안 씀 — 소광 실무가 컷팅"
"(「수축줄눈(컷팅)」) · 줄눈재를 넣는 공법이면 12-7-2 로 물을 것"
)
NOTE_FORM = (
"포장거푸집 = 양쪽 가장자리 2 × 연장(소광 「1면기준」) · 12-8 이 강재 3m·핀폴·20회를 품에 둠"
)
SHEET_UNCHOSEN = (
"비닐(깔기·양생)을 안 고름 — 품셈 12-6 [주]② 「양생에 필요한 재료비(비닐, 양생재 등)는 별도"
" 계상」이라 설계자가 고를 것(기본값 없음)"
)
MESH_UNCHOSEN = (
"철망을 안 고름 — 품셈 12-6 [주]② 「철망재료비는 별도 계상」 · 교본 부록 7-1 3.4 「설계도서에"
" 따라」 · 실무 소광은 「철망포함」 관측 — 설계자가 고를 것(기본값 없음)"
)
MISSING = "콘크리트 포장 {what} — 칸이 비고 B06 에서도 못 읽어 수량이 안 섬 · 칸에 적을 것"
JOINT = "joint_spacing_m"
def _stations(designs: list[dict[str, Any]] | None) -> list[tuple[float, dict[str, Any]]]:
rows = [
(_num(item.get("chainage_m")), item.get("design"))
for item in designs or ()
if isinstance(item, dict) and isinstance(item.get("design"), dict)
]
return sorted(rows, key=lambda row: row[0])
def _widening(design: dict[str, Any]) -> float:
return _num(design.get("widening_left_m")) + _num(design.get("widening_right_m"))
def _widening_at(stations: list[tuple[float, dict[str, Any]]], at: float) -> float:
"""그 자리의 확폭 — 앞뒤 측점 사이 직선 보간 · 끝 밖이면 가장 가까운 측점."""
if at <= stations[0][0]:
return _widening(stations[0][1])
for (s0, d0), (s1, d1) in zip(stations, stations[1:]):
if s0 <= at <= s1:
t = 0.0 if s1 == s0 else (at - s0) / (s1 - s0)
return _widening(d0) + (_widening(d1) - _widening(d0)) * t
return _widening(stations[-1][1])
def pavement_from_designs(
structure: dict[str, Any], designs: list[dict[str, Any]] | None
) -> dict[str, Any]:
"""빈 칸(폭·확폭·두께)을 B06 구간 측점에서 채운 **새** 구조물 — 칸이 차 있으면 그대로."""
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
options = dict(structure.get("options") or {})
notes = list(structure.get("notes") or [])
# 상세 칸은 배치 폼에 없어 제안값이 저장에 안 들어감 — 등록부 한 벌에서 채움(값 두 벌 금지).
spacing = next(o for o in structure_type_map()["pavement_concrete"].options if o.key == JOINT)
if options.get(JOINT) in (None, "") and spacing.default is not None:
options[JOINT] = spacing.default
notes.append(f"수축줄눈 간격 {spacing.default:g}m — {spacing.default_basis}")
stations = _stations(designs)
start, end = sorted((_num(structure.get("start_m")), _num(structure.get("end_m"))))
inside = [design for chainage, design in stations if start <= chainage <= end]
if stations and not inside:
inside = [min(stations, key=lambda row: abs(row[0] - (start + end) / 2))[1]]
if options.get("width_m") in (None, "") and inside:
widths = {round(_num(d.get("carriageway_standard_width_m")), 3) for d in inside} - {0.0}
if len(widths) == 1:
options["width_m"] = widths.pop()
notes.append(f"포장 폭 {options['width_m']:g}m — B06 노폭(구간 측점 {len(inside)}곳)")
elif widths:
notes.append(f"포장 폭 — 구간 측점의 B06 노폭이 갈림({sorted(widths)}) · 칸에 적을 것")
if options.get("thickness_cm") in (None, "") and inside:
thick = {round(_num(d.get("pavement_thickness_m")), 4) for d in inside} - {0.0}
if len(thick) == 1:
options["thickness_cm"] = round(thick.pop() * 100, 2)
notes.append(f"포장 두께 {options['thickness_cm']:g}㎝ — B06 포장층 두께")
if options.get("widening_area_m2") in (None, "") and stations and end > start:
cuts = [start, *[s for s, _d in stations if start < s < end], end]
area = sum(
(_widening_at(stations, a) + _widening_at(stations, b)) / 2 * (b - a)
for a, b in zip(cuts, cuts[1:])
)
options["widening_area_m2"] = round(area, 4)
notes.append(f"확폭 면적 {area:.2f}㎡ — B06 곡선부 확폭 × 측점 사이 거리(평균)")
return {**structure, "options": options, "notes": notes}
def concrete_pavement(length: float, options: dict[str, Any]) -> tuple[list[Component], list[str]]:
"""콘크리트 포장 한 구간 — (성분, 사유). 면적 밑수가 없으면 성분 없이 사유만."""
width = _num(options.get("width_m"))
thickness = _num(options.get("thickness_cm")) / 100
widening = options.get("widening_area_m2")
missing = [
what
for what, value in (
("연장", length),
("폭", width),
("두께", thickness),
("확폭 면적", 1.0 if isinstance(widening, (int, float)) else 0.0),
)
if value <= 0
]
if missing:
return [], [MISSING.format(what="·".join(missing))]
spacing = _num(options.get("joint_spacing_m"))
widening = float(widening)
area = width * length + widening
rows: list[tuple[str, str, float, str]] = [
(
"콘크리트",
"㎥",
area * thickness,
f"(폭 {width:g} × 연장 {length:g} + 확폭 {widening:g}) × 두께 {thickness:g}",
),
("포장거푸집", "m", 2 * length, f"양쪽 2 × 연장 {length:g}"),
]
notes = [SOURCES, NOTE_FORM]
if spacing > 0:
joints = math.floor(length / spacing) * width + widening / spacing
rows.append(
(
"수축줄눈",
"m",
joints,
f"내림({length:g} ÷ {spacing:g}) × 폭 {width:g} + 확폭 {widening:g} ÷ {spacing:g}",
)
)
notes.append(NOTE_JOINT)
for key, name, unchosen in (
("separation_sheet", "비닐", SHEET_UNCHOSEN),
("wire_mesh", "철망", MESH_UNCHOSEN),
):
choice = options.get(key)
if choice == "있음":
rows.append((name, "㎡", area, f"포장 면적 {area:.4g}"))
elif choice != "없음":
notes.append(unchosen)
components = [
Component(name, unit, amount, destination, basis)
for name, unit, amount, basis in rows
if (destination := routed(name, notes))
]
return components, notes
def billing(length: float, options: dict[str, Any]) -> tuple[str, float]:
"""내역 줄 — 포장 면적 ㎡(소광 「콘크리트포장 T=20 ㎡」)."""
widening = options.get("widening_area_m2")
extra = float(widening) if isinstance(widening, (int, float)) else 0.0
return "㎡", _num(options.get("width_m")) * length + extra