fix(B08): 임의 · 실무 값 빈칸 — 관경 · 련 미입력 · 1본 프로젝트 값만 · 거창 계수 비율 미정 · 수심비 빈칸(143-2)
- 집계표 — 관경 · 련 빈칸 = 「미입력」 열(or 1000 · or 1 지움) · 관경 빈칸 흙막이 빈 높이 미입력 · 1본 = 프로젝트 층 변수만(시스템 층 폴백 지움) - 토공집계표 — 성토면다짐 · 초류종자살포 · 지장목제거 = 빈칸 + 「비율 미정」 · 구조물 쪽 늘 빈 줄 8 뺌(나머지 7 유지) - 수리 박스 — 수심비 처음 값 빈칸 · 빈칸이면 수심 · 유출량 멈춤 · 칸 옆 알림 - 좌측 패널 1본 읽기 전 빈칸 · 시험 test_69 · 98 · 122 · 73_11 고침 + 새 test_143_2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
@@ -12,7 +12,9 @@ Q · L 출처 = 저장된 유토곡선 배분(`mass_haul.haul_plan` — B07 토
|
||||
(`free_haul` = 무대 · `dozer` = 불도우저 · `dump_truck` = 덤프트럭).
|
||||
|
||||
암 갈래 — 「연암」 · 「경암」 줄 = 암 전체 × 비율(좌측 패널 · 기본 90 : 10 · PLAN 122).
|
||||
풍화암 · 보통암 · 구조물 쪽 줄(su2 시트)은 값이 없어 비움(None).
|
||||
풍화암 · 보통암 · 표토제거 · 법면보호공 · 운반성토 줄은 값이 없어 비움(None) · 구조물 쪽 줄(su2 시트
|
||||
구조물터파기 · 토사다짐 · 되메우기 · 잔토)은 뺌 · 거창 계수(성토면다짐 0.8 · 초류종자 성토 0.5 ·
|
||||
지장목 0.8)는 근거가 없어 값 빈칸 + 「비율 미정」(PLAN 143-2).
|
||||
|
||||
짝 — `rock_split` · `split_rock_columns` ↔ `B08_Quantity_Options_Calc.ts` `rockSplit` ·
|
||||
`splitRockColumns` · `splitRockSummary`(거울 시험 `test_122_b08_options_mirror`).
|
||||
@@ -99,8 +101,8 @@ def summary_rows(
|
||||
r0 = excel_round
|
||||
free_q, dump_q, dozer_q = haul["free_haul"]["q"], haul["dump_truck"]["q"], haul["dozer"]["q"]
|
||||
diverted = t["diverted_m3"]
|
||||
tree = r0(t["tree_removal_fill_area_m2"] * 0.5 + t["tree_removal_cut_area_m2"], 2)
|
||||
m3 = "㎥"
|
||||
unsure = "비율 미정" # 거창 계수 자리 — 값을 확인한 뒤 다시 잇기(143-2)
|
||||
return [
|
||||
_row("흙깍기", "토사", "기계(굴삭기)", m3, r0(t["cut_soil_volume_m3"])), # 토적표!E137
|
||||
_row("흙깍기", "풍화암", "굴삭기 + 브레카", m3, None),
|
||||
@@ -121,26 +123,10 @@ def summary_rows(
|
||||
_row("토사운반", "", "19 TON B/D", m3, dozer_q), # 평균운반거리 불도우저 ΣQ
|
||||
_row("잔토처리", "", "백호우", m3, r0(t["balance_m3"])), # 토적표!T137
|
||||
_row("층따기", "", "백호우", m3, t["benching_area_m2"]), # 토적표!W137
|
||||
_row("성토면다짐", "", "", "㎡", r0(t["grading_fill_area_m2"] * 0.8), "성토면80%반영"),
|
||||
_row("성토면다짐", "", "", "㎡", None, unsure), # 거창 = 성토면 × 0.8
|
||||
_row("표토제거", "", "", "㎡", None),
|
||||
_row(
|
||||
"초류종자살포",
|
||||
"",
|
||||
"씨드스프레이",
|
||||
"㎡",
|
||||
r0(t["grading_fill_area_m2"] * 0.5 + t["grading_cut_area_m2"]),
|
||||
"성토면50%반영+절토면",
|
||||
),
|
||||
_row("초류종자살포", "", "씨드스프레이", "㎡", None, unsure), # 거창 = 성토 × 0.5 + 절토
|
||||
_row("법면보호공", "", "", "㎡", None),
|
||||
# 토적표!AI138 = ROUND(성토 × 0.5 + 절토, 2) → × 0.8
|
||||
_row("지장목제거", "", "", "㎡", r0(tree * 0.8), "80%반영"),
|
||||
_row("구조물터파기", "토사", "기계(굴삭기)", m3, None, "구조물 수량"),
|
||||
_row("구조물터파기", "풍화암", "굴삭기 + 브레카", m3, None),
|
||||
_row("구조물터파기", "연암", "굴삭기 + 브레카", m3, None, "구조물 수량"),
|
||||
_row("구조물터파기", "보통암", "굴삭기 + 브레카", m3, None),
|
||||
_row("구조물터파기", "경암", "발파 + 굴삭기", m3, None),
|
||||
_row("토사다짐", "", "", m3, None, "구조물 수량"),
|
||||
_row("되메우기", "", "", m3, None, "구조물 수량"),
|
||||
_row("잔토", "", "", m3, None, "현장내 성토(유용토)"),
|
||||
_row("지장목제거", "", "", "㎡", None, unsure), # 거창 = (성토 × 0.5 + 절토) × 0.8
|
||||
_row("운반성토", "", "BH+DT+BH", m3, None),
|
||||
]
|
||||
|
||||
@@ -102,8 +102,8 @@ def compute(inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
props = section_props(inputs["section"], diameter_mm / 1000.0) if diameter_mm else {}
|
||||
elif sheet == "box":
|
||||
width, height = _num(inputs, "box_width_m"), _num(inputs, "box_height_m")
|
||||
ratio = _num(inputs, "depth_ratio") or SHEET_DEFAULTS["box"]["depth_ratio"]
|
||||
if width and height:
|
||||
ratio = _num(inputs, "depth_ratio") # 빈칸 = 수심 · 단면 · 유출량 멈춤(143-2)
|
||||
if width and height and ratio:
|
||||
depth = ratio * height
|
||||
props = rect_props(width, depth)
|
||||
else:
|
||||
|
||||
@@ -56,10 +56,11 @@ SHEETS = {
|
||||
"ford_pavement": ("ford", "물넘이포장"),
|
||||
}
|
||||
|
||||
#: 장별 처음 값 — 박스 수심비 0.8H · n 0.013(울진 산출서 · 조사 69-7) · 내공 · 경사는 빈칸
|
||||
#: 장별 처음 값 — 박스 n 0.013(KDS 콘크리트관) · 수심비 · 내공 · 경사는 빈칸(수심비 0.8 = 울진 산출서 —
|
||||
#: 근거 없어 안 채움 · 빈칸이면 단면 계산 멈춤 · 143-2)
|
||||
#: 세월교 · 물넘이 월류 폭 10 m(사용자 10-7) · n · 경사 = B05 세월교 높이 계산과 같은 값
|
||||
SHEET_DEFAULTS: dict[str, dict[str, Any]] = {
|
||||
"box": {"n": 0.013, "depth_ratio": 0.8, "box_width_m": None, "box_height_m": None},
|
||||
"box": {"n": 0.013, "depth_ratio": None, "box_width_m": None, "box_height_m": None},
|
||||
"ford": {
|
||||
"n": FORD_MANNING_N,
|
||||
"overflow_width_m": 10.0,
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
· 길이 = 전 + 후 · 빈 높이 = 횡단도가 그리는 관경 최소 높이
|
||||
돌붙임 › 찰붙임 · 메붙임 ㎡ 도수로/돌붙임 붙임 형식 · 면적(빈칸이면 안 셈 · 98-3)
|
||||
관 공 › Φ{관경}m/m › 관매설 m B06 관 길이(1 m 올림 그대로)
|
||||
› 커플링 ea 올림(L ÷ 1본) − 1 · 1본 = 프로젝트 층 M02 구조물집계표 변수
|
||||
› 커플링 ea 올림(L ÷ 1본) − 1 · 1본 = 프로젝트 층 M02 구조물집계표 변수만
|
||||
관경 · 련 빈칸 = 「미입력」 열(임의 값으로 안 채움 · 143-2)
|
||||
› 관보호공 모양 실제로 선 집수정 모양마다 1 개소(98-2 `inlet_basin`)
|
||||
BOX암거 › {가로×세로} › 본체 m B06 새 키 `box_length_m`(두 끝 offset 차 · 120)
|
||||
› 날개벽 › 유입 · 유출 개소
|
||||
@@ -86,6 +87,23 @@ def _num(value: Any) -> float | None:
|
||||
return None
|
||||
|
||||
|
||||
def _count(options: dict[str, Any], key: str) -> int | None:
|
||||
"""관경 · 련 수 — 빈칸이면 None(「미입력」 열 · 임의 값으로 안 채움 · 143-2)."""
|
||||
value = _num(options.get(key))
|
||||
return int(value) if value else None
|
||||
|
||||
|
||||
def _pipe_head(names: dict[str, str], diameter: int | None) -> list[str]:
|
||||
"""「관 공」 머리 — 관경 빈칸이면 「미입력」."""
|
||||
if diameter is None:
|
||||
return ["관 공", fill.EMPTY_TEXT]
|
||||
return ["관 공", f"Φ{names.get(f'pipe.diameter={diameter}', diameter)}m/m"]
|
||||
|
||||
|
||||
def _key(value: int | None) -> str:
|
||||
return fill.EMPTY_LABEL if value is None else str(value)
|
||||
|
||||
|
||||
def _combo(item: Any) -> str | None:
|
||||
"""집계 항목 → 조합 id(화면 `comboId` 와 같은 꼴) · 없으면 None."""
|
||||
if not isinstance(item, dict) or not item.get("key"):
|
||||
@@ -94,13 +112,10 @@ def _combo(item: Any) -> str | None:
|
||||
|
||||
|
||||
def one_pipe_length(project_root: str | Path) -> float | None:
|
||||
"""1본 길이(m) — 프로젝트 층 구조물집계표 변수 · 없으면 시스템 층 · 빈칸이면 None."""
|
||||
for folder in (layers.project_dir(project_root), layers.system_dir()):
|
||||
found = layers.read_template(folder, "table", TABLE_NAME)
|
||||
if found is not None:
|
||||
value = _num((found["문서"].get("변수") or {}).get(ONE_LENGTH_VAR))
|
||||
return value if value and value > 0 else None
|
||||
return None
|
||||
"""1본 길이(m) — 프로젝트 층 구조물집계표 변수만(시스템 층으로 안 채움 · 143-2) · 빈칸 None."""
|
||||
found = layers.read_template(layers.project_dir(project_root), "table", TABLE_NAME)
|
||||
value = _num((found["문서"].get("변수") or {}).get(ONE_LENGTH_VAR)) if found else None
|
||||
return value if value and value > 0 else None
|
||||
|
||||
|
||||
def couplings(length_m: float | None, one_m: float | None) -> int | None:
|
||||
@@ -344,10 +359,11 @@ def _ford(
|
||||
combos: dict[str, str | None],
|
||||
names: dict[str, str],
|
||||
) -> None:
|
||||
"""세월교 — Φ관경×n련마다 월류 폭 m · 본체 개소."""
|
||||
diameter = int(_num(base["options"].get("pipe_diameter_mm")) or 1000)
|
||||
count = int(_num(base["options"].get("pipe_count")) or 1)
|
||||
spec = f"Φ{names.get(f'ford.diameter={diameter}', diameter)}×{count}련"
|
||||
"""세월교 — Φ관경×n련마다 월류 폭 m · 본체 개소(관경 · 련 빈칸 = 「미입력」)."""
|
||||
diameter = _count(base["options"], "pipe_diameter_mm")
|
||||
count = _count(base["options"], "pipe_count")
|
||||
size = fill.EMPTY_TEXT if diameter is None else names.get(f"ford.diameter={diameter}", diameter)
|
||||
spec = f"Φ{size}×{fill.EMPTY_TEXT if count is None else count}련"
|
||||
width_name = names.get("ford.width", "월류 폭").split(" (")[0]
|
||||
cells = (
|
||||
("width", width_name, "m", _num(base["options"].get("ford_width_m"))),
|
||||
@@ -356,8 +372,8 @@ def _ford(
|
||||
for index, (key, label, unit, value) in enumerate(cells):
|
||||
sheet.add(
|
||||
at,
|
||||
f"ford|{diameter}|{count}|{key}",
|
||||
(4, 0, diameter, count, index),
|
||||
f"ford|{_key(diameter)}|{_key(count)}|{key}",
|
||||
(4, 0, diameter or math.inf, count or math.inf, index),
|
||||
[names["종류"], spec, label],
|
||||
unit,
|
||||
value,
|
||||
@@ -380,31 +396,32 @@ def _ford_pipes(
|
||||
`pipes` 에는 련마다 한 줄(좌측 패널 1본 길이로 커플링을 다시 셀 때 련 수만큼 셈) ·
|
||||
조합 = 시설 본체 + 본체 잇기(`body` · 143-1).
|
||||
"""
|
||||
diameter = int(_num(base["options"].get("pipe_diameter_mm")) or 1000)
|
||||
count = int(_num(base["options"].get("pipe_count")) or 1)
|
||||
head = ["관 공", f"Φ{names.get(f'pipe.diameter={diameter}', diameter)}m/m"]
|
||||
diameter = _count(base["options"], "pipe_diameter_mm")
|
||||
count = _count(base["options"], "pipe_count")
|
||||
head = _pipe_head(names, diameter)
|
||||
combos = [_combo(base.get("summary_item")), body]
|
||||
each = couplings(length, one_m)
|
||||
sheet.add(
|
||||
at,
|
||||
f"len|{diameter}",
|
||||
(2, diameter, 0),
|
||||
f"len|{_key(diameter)}",
|
||||
(2, diameter or math.inf, 0),
|
||||
[*head, "관매설"],
|
||||
"m",
|
||||
length * count if length else None,
|
||||
length * count if length and count else None,
|
||||
combos,
|
||||
)
|
||||
sheet.add(
|
||||
at,
|
||||
f"cp|{diameter}",
|
||||
(2, diameter, 1),
|
||||
f"cp|{_key(diameter)}",
|
||||
(2, diameter or math.inf, 1),
|
||||
[*head, "커플링"],
|
||||
"ea",
|
||||
None if each is None else each * count,
|
||||
None if each is None or count is None else each * count,
|
||||
combos,
|
||||
)
|
||||
if any(combos):
|
||||
pipes += [{"chainage_m": at, "column": f"pipe:cp|{diameter}", "length_m": length}] * count
|
||||
column = f"pipe:cp|{_key(diameter)}"
|
||||
pipes += [{"chainage_m": at, "column": column, "length_m": length}] * (count or 0)
|
||||
|
||||
|
||||
def _pavement(
|
||||
@@ -543,15 +560,15 @@ def pipe_summary(
|
||||
no_ford += counted and not ford_length
|
||||
_ford_pipes(sheet, at, base, ford_length, one_m, names, pipes, body)
|
||||
continue
|
||||
diameter = int(_num(base["options"].get("pipe_diameter_mm")) or 1000)
|
||||
pipe_head = ["관 공", f"Φ{names.get(f'pipe.diameter={diameter}', diameter)}m/m"]
|
||||
diameter = _count(base["options"], "pipe_diameter_mm")
|
||||
pipe_head = _pipe_head(names, diameter)
|
||||
pipe_combos = [_combo(base.get("summary_item")), links.get(LINKS["pipe"])]
|
||||
length = base.get("관연장")
|
||||
no_length += any(pipe_combos) and not length
|
||||
sheet.add(
|
||||
at,
|
||||
f"len|{diameter}",
|
||||
(2, diameter, 0),
|
||||
f"len|{_key(diameter)}",
|
||||
(2, diameter or math.inf, 0),
|
||||
[*pipe_head, "관매설"],
|
||||
"m",
|
||||
length,
|
||||
@@ -559,22 +576,23 @@ def pipe_summary(
|
||||
)
|
||||
sheet.add(
|
||||
at,
|
||||
f"cp|{diameter}",
|
||||
(2, diameter, 1),
|
||||
f"cp|{_key(diameter)}",
|
||||
(2, diameter or math.inf, 1),
|
||||
[*pipe_head, "커플링"],
|
||||
"ea",
|
||||
couplings(length, one_m),
|
||||
pipe_combos,
|
||||
)
|
||||
if any(pipe_combos):
|
||||
pipes.append({"chainage_m": at, "column": f"pipe:cp|{diameter}", "length_m": length})
|
||||
column = f"pipe:cp|{_key(diameter)}"
|
||||
pipes.append({"chainage_m": at, "column": column, "length_m": length})
|
||||
shape = fill._nearest(basins, at)
|
||||
no_basin += bool(links.get(LINKS["inlet"])) and shape is None
|
||||
if shape:
|
||||
sheet.add(
|
||||
at,
|
||||
f"basin|{diameter}|{shape}",
|
||||
(2, diameter, 2 + _order(SHAPES, shape), shape),
|
||||
f"basin|{_key(diameter)}|{shape}",
|
||||
(2, diameter or math.inf, 2 + _order(SHAPES, shape), shape),
|
||||
[*pipe_head, "관보호공"],
|
||||
names.get(f"inlet.form={shape}", shape),
|
||||
1.0,
|
||||
@@ -583,7 +601,9 @@ def pipe_summary(
|
||||
|
||||
sides = [side for side in ("inlet", "outlet") if options.get(f"{side}_type") == SOIL]
|
||||
sources = [(side, links.get(LINKS[side])) for side in sides]
|
||||
_revets(sheet, at, options, links, sources, pipe_wall_min_height_m(diameter / 1000), names)
|
||||
# 관경 빈칸 = 빈 높이도 「미입력」(관경 최소 높이를 모름)
|
||||
wall = pipe_wall_min_height_m(diameter / 1000) if diameter else None
|
||||
_revets(sheet, at, options, links, sources, wall, names)
|
||||
_chutes(sheet, at, options, links, names)
|
||||
|
||||
out = sheet.result()
|
||||
|
||||
@@ -28,7 +28,7 @@ const SUMMARY_COLUMNS: DataColumn[] = [
|
||||
export function renderEarthworkSummary(host: HTMLElement, table: EarthworkTable): void {
|
||||
const info = note(
|
||||
"토적표 합계 · 평균운반거리 · 종방향 무대운반거리를 실무 토공집계표 식대로 묶음 · " +
|
||||
"연암 · 경암 = 암 × 좌측 패널 비율 · 구조물 쪽 줄은 구조물 수량에서(- = 아직 값 없음)",
|
||||
"연암 · 경암 = 암 × 좌측 패널 비율 · 「비율 미정」 = 반영 비율 확인 뒤 채움(- = 아직 값 없음)",
|
||||
);
|
||||
const grid = createDataTable({
|
||||
columns: SUMMARY_COLUMNS,
|
||||
|
||||
@@ -430,7 +430,11 @@ export function buildHydroSheet(
|
||||
dims.append(
|
||||
rowF("내공 폭 B (m)", null, numberInput("box_width_m", "입력")),
|
||||
rowF("내공 높이 H (m)", null, numberInput("box_height_m", "입력")),
|
||||
rowF("수심비 h / H", hint(`기본 ${sheetDefaults.depth_ratio}`), numberInput("depth_ratio")),
|
||||
rowF(
|
||||
"수심비 h / H",
|
||||
hint("빈칸이면 계산 멈춤 — 값 넣기"),
|
||||
numberInput("depth_ratio", "입력"),
|
||||
),
|
||||
rowF("수심 h (m)", formula("h = 수심비 × H"), outCell("depth")),
|
||||
rowF("단면적", formula("A = B × h"), outCell("section_area")),
|
||||
rowF("윤변", formula("P = B + 2h"), outCell("perimeter")),
|
||||
|
||||
@@ -142,8 +142,9 @@ export function createOptionsPanel(projectId: string | null, onSaved: () => void
|
||||
rockRow.className = "b08-options__row";
|
||||
rockRow.append(soft.root, hard.root);
|
||||
|
||||
// 1본 = 프로젝트 값만(읽기 전 빈칸 · 143-2)
|
||||
let saved: { one: number | null; soft: number; surcharge: Cells } = {
|
||||
one: 8,
|
||||
one: null,
|
||||
soft: 90,
|
||||
surcharge: PANEL_SURCHARGE,
|
||||
};
|
||||
|
||||
@@ -311,8 +311,9 @@ LINKED_PARTS = (("도면", "도면.json"), ("면적", "면적.json"), ("일위
|
||||
def linked_snapshots(project_root: str | Path) -> dict[str, dict[str, Any]]:
|
||||
"""연결된 사본 — 지정 ∩ 같은 항목 사본 `{구조물 id: {항목, 폴더, 부분, 경로}}`(PLAN 143-1).
|
||||
|
||||
구조물 id = `assignments` 키(관 지점 `pipe@측점` · 옵션 그룹 `{id}__{그룹}`) · 미지정 · 사본 없음 ·
|
||||
다른 항목 사본은 안 담음(M02 `assign_items` 규칙) · 원본 층과 대조 안 함(빠른 읽기 · 쓰기 없음) ·
|
||||
구조물 id = `assignments` 키(관 지점 `pipe@측점` · 옵션 그룹 `{id}__{그룹}`) ·
|
||||
미지정 · 사본 없음 · 다른 항목 사본은 안 담음(M02 `assign_items` 규칙) ·
|
||||
원본 층과 대조 안 함(빠른 읽기 · 쓰기 없음) ·
|
||||
`경로` = manifest 원본 경로(가져온 때 M02 구분 · 이름) · B08 · B09 공용.
|
||||
"""
|
||||
base = structures_dir(project_root)
|
||||
|
||||
@@ -40,9 +40,9 @@ def root(tmp_path, monkeypatch) -> Path:
|
||||
return project
|
||||
|
||||
|
||||
def test_기본값은_8_m_와_90_10_파일에_안_씀(root):
|
||||
def test_1본_프로젝트_값_없으면_빈칸_90_10_파일에_안_씀(root):
|
||||
assert options.read_options(root) == {
|
||||
"one_pipe_length_m": 8,
|
||||
"one_pipe_length_m": None, # 143-2 — 시스템 층 8 m 로 안 채움
|
||||
"rock_ratios_pct": {"연암": 90.0, "경암": 10.0},
|
||||
"material_surcharge_pct": {"sand": 12, "gravel": 5, "cement": 3}, # 124
|
||||
}
|
||||
@@ -51,7 +51,7 @@ def test_기본값은_8_m_와_90_10_파일에_안_씀(root):
|
||||
|
||||
|
||||
def test_1본_6_m_저장_뒤_커플링을_다시_셈(root):
|
||||
assert summary.couplings(22, summary.one_pipe_length(root)) == 2 # 올림(22/8) − 1
|
||||
assert summary.couplings(22, summary.one_pipe_length(root)) is None # 프로젝트 값 없음
|
||||
out = options.save_options(root, options.OptionsSave(one_pipe_length_m=6))
|
||||
assert out["one_pipe_length_m"] == 6 == summary.one_pipe_length(root)
|
||||
assert summary.couplings(22, 6) == 3 and summary.couplings(16, 6) == 2
|
||||
@@ -63,6 +63,7 @@ def test_1본_6_m_저장_뒤_커플링을_다시_셈(root):
|
||||
# 다시 저장 = 같은 사본 판 위에 씀
|
||||
options.save_options(root, options.OptionsSave(one_pipe_length_m=8))
|
||||
assert summary.one_pipe_length(root) == 8
|
||||
assert summary.couplings(22, summary.one_pipe_length(root)) == 2 # 올림(22/8) − 1
|
||||
|
||||
|
||||
def test_비율_70_30_저장_뒤_남고_다른_칸은_그대로(root):
|
||||
@@ -79,7 +80,7 @@ def test_비율_70_30_저장_뒤_남고_다른_칸은_그대로(root):
|
||||
assert stored["rock_methods"] == {"연암": "ripping"}
|
||||
assert options.read_options(root)["rock_ratios_pct"]["연암"] == 70.0
|
||||
# 1본 길이는 안 보냈으니 그대로
|
||||
assert options.read_options(root)["one_pipe_length_m"] == 8
|
||||
assert options.read_options(root)["one_pipe_length_m"] is None
|
||||
|
||||
|
||||
def test_비율_읽기_자름_경암만_있을_때():
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""B08 구조물 집계표 — 관경 · 련 빈칸 = 「미입력」(임의 1000 · 1 로 안 채움 · PLAN 143-2).
|
||||
|
||||
견본: 100 관 관경 빈칸 L16 · 유출 흙막이 높이 빈칸 / 200 세월교 Φ1000 련 빈칸 · 관 길이 9.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from B08_Quantity import B08_Quantity_Router_Summary as summary
|
||||
from M02_MasterTemplete import M02_Item_Snapshot as snapshots
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Definition as definition
|
||||
|
||||
ITEM = {"layer": "system", "owner": None, "key": "SS000001", "name": "견본"}
|
||||
POINTS = [
|
||||
{
|
||||
"chainage_m": 100.0,
|
||||
"options": {
|
||||
"pipe_diameter_mm": "",
|
||||
"outlet_type": "기슭막이",
|
||||
"outlet_revet_form": "돌쌓기(메)",
|
||||
"outlet_revet_before_m": 5,
|
||||
"outlet_revet_after_m": 5,
|
||||
},
|
||||
"summary_item": ITEM,
|
||||
"group_items": {"g9lq9uk": ITEM},
|
||||
},
|
||||
{
|
||||
"chainage_m": 200.0,
|
||||
"facility": "ford_bridge",
|
||||
"options": {"pipe_diameter_mm": 1000, "pipe_count": "", "ford_width_m": 12},
|
||||
"summary_item": ITEM,
|
||||
},
|
||||
]
|
||||
DESIGNS = [
|
||||
{"chainage_m": 100.0, "design": {"pipe_length_m": 16}},
|
||||
{"chainage_m": 200.0, "design": {"ford_pipe_length_m": 9}},
|
||||
]
|
||||
|
||||
|
||||
def _run(tmp_path: Path) -> dict:
|
||||
root = tmp_path / "proj"
|
||||
edits = root / "B04_PreProcess/drainage/edits"
|
||||
edits.mkdir(parents=True)
|
||||
points = [{**p, "source": "user"} for p in POINTS]
|
||||
(edits / "pipe_points.json").write_text(
|
||||
json.dumps({"points": points}, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
names = definition.read()["종류"]["pipe"]["이름"]
|
||||
linked = {owner: {} for owner in snapshots.assignments(root)}
|
||||
return summary.pipe_summary(root, DESIGNS, names, 6.0, linked=linked)
|
||||
|
||||
|
||||
def _cell(out: dict, at: float, cid: str):
|
||||
row = next(r for r in out["rows"] if abs(r["chainage_m"] - at) < 0.01)
|
||||
return row["values"].get(cid)
|
||||
|
||||
|
||||
def test_관경_빈칸은_미입력_열(tmp_path):
|
||||
out = _run(tmp_path)
|
||||
cols = {c["id"]: c for c in out["columns"]}
|
||||
assert cols["pipe:len|-"]["group"] == ["관 공", "미입력", "관매설"]
|
||||
assert _cell(out, 100, "pipe:len|-") == 16
|
||||
assert _cell(out, 100, "pipe:cp|-") == 2 # 1본 6 m — 관경과 무관
|
||||
assert not any(c.startswith("pipe:len|1000") and _cell(out, 100, c) for c in cols)
|
||||
# 흙막이 빈 높이 = 관경 최소 높이를 모름 → 「미입력」
|
||||
assert cols["pipe:revet|돌쌓기(메)|-"]["group"][2] == "미입력"
|
||||
assert _cell(out, 100, "pipe:revet|돌쌓기(메)|-") == 10
|
||||
|
||||
|
||||
def test_세월교_련_빈칸은_미입력_관_몫_빈칸(tmp_path):
|
||||
out = _run(tmp_path)
|
||||
cols = {c["id"]: c for c in out["columns"]}
|
||||
assert cols["pipe:ford|1000|-|width"]["group"][1] == "Φ1000×미입력련"
|
||||
assert _cell(out, 200, "pipe:ford|1000|-|width") == 12
|
||||
assert _cell(out, 200, "pipe:len|1000") is None # 련 수를 모르면 관 몫도 빈칸
|
||||
assert _cell(out, 200, "pipe:cp|1000") is None
|
||||
assert not [p for p in out["pipes"] if p["column"] == "pipe:cp|1000"]
|
||||
@@ -195,12 +195,15 @@ def test_토공집계표_식() -> None:
|
||||
assert value[("토사운반", "", "19 TON B/D")] == 20
|
||||
assert value[("유용토 소계", "", "")] == pytest.approx(t["diverted_m3"] + 82)
|
||||
assert value[("잔토처리", "", "백호우")] == -98
|
||||
assert value[("성토면다짐", "", "")] == excel_round(101.7 * 0.8)
|
||||
assert value[("초류종자살포", "", "씨드스프레이")] == excel_round(101.7 * 0.5 + 15.3)
|
||||
tree = excel_round(133.2 * 0.5 + 82.8, 2)
|
||||
assert value[("지장목제거", "", "")] == excel_round(tree * 0.8)
|
||||
# 143-2 — 거창 계수(0.8 · 0.5) 자리는 빈칸 + 「비율 미정」 · 구조물 쪽 줄 8 뺌 · 나머지 7 빈칸
|
||||
notes = {r["group"]: r["note"] for r in rows}
|
||||
for group in ("성토면다짐", "초류종자살포", "지장목제거"):
|
||||
assert next(r["value"] for r in rows if r["group"] == group) is None
|
||||
assert notes[group] == "비율 미정"
|
||||
assert value[("표토제거", "", "")] is None
|
||||
assert len(rows) == 33 # 시트 C5:C37
|
||||
assert not {"구조물터파기", "토사다짐", "되메우기", "잔토"} & {r["group"] for r in rows}
|
||||
assert sum(r["value"] is None and r["note"] != "비율 미정" for r in rows) == 7
|
||||
assert len(rows) == 25 # 시트 C5:C37 33 − 구조물 쪽 8
|
||||
|
||||
|
||||
def test_토적표_머리_글자는_사용자_견본대로():
|
||||
|
||||
@@ -56,15 +56,27 @@ def test_장은_시설이_정함() -> None:
|
||||
|
||||
|
||||
def test_박스_울진_산출서_단면() -> None:
|
||||
r = compute(_inputs("box", box_width_m=3.0, box_height_m=1.5, slope_denominator=50))
|
||||
assert SHEET_DEFAULTS["box"]["n"] == 0.013 and SHEET_DEFAULTS["box"]["depth_ratio"] == 0.8
|
||||
# 143-2 — 수심비 처음 값 빈칸(울진 0.8 은 손으로 넣은 값으로만)
|
||||
r = compute(
|
||||
_inputs("box", box_width_m=3.0, box_height_m=1.5, slope_denominator=50, depth_ratio=0.8)
|
||||
)
|
||||
assert SHEET_DEFAULTS["box"]["n"] == 0.013 and SHEET_DEFAULTS["box"]["depth_ratio"] is None
|
||||
assert r["n"] == 0.013
|
||||
assert r["depth"] == pytest.approx(1.2)
|
||||
assert r["section_area"] == pytest.approx(3.6)
|
||||
assert r["perimeter"] == pytest.approx(5.4)
|
||||
assert r["radius"] == pytest.approx(3.6 / 5.4)
|
||||
# 실무 29.236 = 1/n 75 · R 0.67 반올림 — 같은 n 이면 R 반올림 차(0.3 %)만 남음
|
||||
r75 = compute(_inputs("box", box_width_m=3.0, box_height_m=1.5, slope_denominator=50, n=1 / 75))
|
||||
r75 = compute(
|
||||
_inputs(
|
||||
"box",
|
||||
box_width_m=3.0,
|
||||
box_height_m=1.5,
|
||||
slope_denominator=50,
|
||||
n=1 / 75,
|
||||
depth_ratio=0.8,
|
||||
)
|
||||
)
|
||||
assert r75["capacity"] == pytest.approx(29.236, rel=0.004)
|
||||
assert 75 * 0.67 ** (2 / 3) * math.sqrt(1 / 50) * 3.6 == pytest.approx(29.236, abs=0.001)
|
||||
assert r["capacity"] > r75["capacity"] # n 고치면 따라감
|
||||
@@ -79,6 +91,9 @@ def test_박스_내공_경사_빈칸이면_대시() -> None:
|
||||
r = compute(_inputs("box"))
|
||||
assert r["design_flow"] is not None
|
||||
assert r["depth"] is None and r["capacity"] is None and r["ok"] is None
|
||||
# 수심비 빈칸 = 내공 · 경사가 있어도 계산 멈춤(143-2)
|
||||
r = compute(_inputs("box", box_width_m=3.0, box_height_m=1.5, slope_denominator=50))
|
||||
assert r["depth"] is None and r["capacity"] is None and r["ok"] is None
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -215,7 +215,7 @@ def test_1본_길이는_프로젝트_층_변수(root, tmp_path, monkeypatch):
|
||||
json.dumps({"변수": {"파형강관_1본_m": 8}}), encoding="utf-8"
|
||||
)
|
||||
monkeypatch.setattr(layers, "SYSTEM_ROOT", system)
|
||||
assert summary.one_pipe_length(root) == 8
|
||||
assert summary.one_pipe_length(root) is None # 143-2 — 시스템 층으로 안 채움
|
||||
mine = layers.template_path(layers.project_dir(root), "table", "구조물집계표")
|
||||
mine.parent.mkdir(parents=True)
|
||||
mine.write_text(json.dumps({"변수": {"파형강관_1본_m": 6}}), encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user