feat(B08): 암 환산계수 C = 연암 · 경암 비율 가중(연암 리핑 · 경암 발파 고정) — 토적표 조작 중 바로 · B06 유토곡선 다음 서버 계산 때(125-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
* rockSplit · splitRockColumns · splitRockSummary
|
||||
* ↔ `B08_Quantity_Engine_Earthwork_Summary.py` rock_split ·
|
||||
* split_rock_columns · summary_rows(연암 · 경암 네 줄)
|
||||
* mixedRockC ↔ `common_util_project_settings.py` mixed_rock_c(125-1)
|
||||
* recountRockC · recountAdjustedSummary
|
||||
* ↔ `B08_Quantity_Engine_Earthwork.py` build_rows · totals ·
|
||||
* summary_rows(보정량계 · 유용토 · 잔토처리 줄)
|
||||
* [저장] 뒤에는 서버가 정본으로 다시 셈 — 여기 값을 받아 적지 않음.
|
||||
* import 없음 — 시험이 이 파일만 옮겨 돌림.
|
||||
* ========================================================================== */
|
||||
@@ -106,3 +110,68 @@ export function splitRockSummary<R extends { group: string; kind: string; value:
|
||||
return { ...row, value: Math.round(row.kind === "연암" ? soft : hard) };
|
||||
});
|
||||
}
|
||||
|
||||
/** 암 C = 연암 % × 리핑암 C + 경암 % × 발파암 C(0~100 자름) — 연암 = 리핑 · 경암 = 발파 고정(125-1). */
|
||||
export function mixedRockC(softPct: number, rippingC: number, blastingC: number): number {
|
||||
const soft = Math.min(Math.max(softPct, 0), 100) / 100;
|
||||
return soft * rippingC + (1 - soft) * blastingC;
|
||||
}
|
||||
|
||||
const num = (value: unknown): number => (typeof value === "number" ? value : 0);
|
||||
const RECOUNT_TOTALS = [
|
||||
"cut_rock_adjusted_m3",
|
||||
"ditch_rock_adjusted_m3",
|
||||
"adjusted_total_m3",
|
||||
"diverted_m3",
|
||||
"balance_m3",
|
||||
] as const;
|
||||
|
||||
/** 토적표를 새 암 C 로 — 암 보정량 · 보정량계 · 유용토 · 차인 · 누가 · 합계(줄 · 합계 덮어씀). */
|
||||
export function recountRockC(
|
||||
rows: Array<Record<string, unknown>>,
|
||||
totals: Record<string, number>,
|
||||
c: number,
|
||||
): void {
|
||||
let cumulative = 0;
|
||||
for (const row of rows) {
|
||||
const cutRock = num(row.cut_rock_volume_m3) * c;
|
||||
const adjusted = num(row.cut_soil_adjusted_m3) + cutRock;
|
||||
const fill = num(row.fill_volume_m3);
|
||||
cumulative += adjusted - fill;
|
||||
Object.assign(row, {
|
||||
cut_rock_adjusted_m3: cutRock,
|
||||
ditch_rock_adjusted_m3: num(row.ditch_rock_volume_m3) * c,
|
||||
adjusted_total_m3: adjusted,
|
||||
diverted_m3: Math.min(adjusted, fill),
|
||||
balance_m3: adjusted - fill,
|
||||
cumulative_m3: cumulative,
|
||||
});
|
||||
}
|
||||
const sum = (key: string, picked = rows): number =>
|
||||
picked.reduce((total, row) => total + num(row[key]), 0);
|
||||
for (const key of RECOUNT_TOTALS) totals[key] = sum(key);
|
||||
for (const kind of ["ripping_rock", "blasting_rock"])
|
||||
totals[`cut_${kind}_adjusted_m3`] = sum(
|
||||
"cut_rock_adjusted_m3",
|
||||
rows.filter((row) => row.cut_rock_kind === kind),
|
||||
);
|
||||
}
|
||||
|
||||
/** 엑셀 ROUND(정수) — 반은 0 에서 먼 쪽. */
|
||||
const excelRound = (value: number): number => Math.sign(value) * Math.round(Math.abs(value));
|
||||
|
||||
/** 토공집계표 보정량계 · 유용토 소계 · 횡방향유용토 · 잔토처리 줄 — 합계에서 다시(종방향 무대 Q 는 그대로). */
|
||||
export function recountAdjustedSummary<R extends { group: string; value: number | null }>(
|
||||
rows: readonly R[],
|
||||
totals: Record<string, number>,
|
||||
freeQ: number,
|
||||
): R[] {
|
||||
const diverted = totals.diverted_m3 ?? 0;
|
||||
const values: Record<string, number> = {
|
||||
보정량계: excelRound(totals.adjusted_total_m3 ?? 0),
|
||||
"유용토 소계": diverted + freeQ,
|
||||
횡방향유용토: diverted,
|
||||
잔토처리: excelRound(totals.balance_m3 ?? 0),
|
||||
};
|
||||
return rows.map((row) => (row.group in values ? { ...row, value: values[row.group] } : row));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
운반 Q · L = 종단 레코드에 저장된 유토곡선 배분(`mass_haul.haul_plan`) — B07 토적도와 같은 정본.
|
||||
|
||||
단면적 = B06 횡단 설계 저장값(`cross_sections.data.design`) · 노선 = 확정 노선(없으면 최신 노선) ·
|
||||
토량환산계수 = 유토곡선과 같은 `mixed_conversion_factors(quantity_settings)`.
|
||||
토량환산계수 = 유토곡선과 같은 `mixed_conversion_factors(quantity_settings)` · 암 = 연암 · 경암 비율 가중 C ·
|
||||
`rock_c` = 가중 전 리핑암 · 발파암 C(조작 중 브라우저가 다시 가중 · 125-1).
|
||||
암 → 연암 · 경암 = 암 × 연암 %(`rock_ratios_pct` · 기본 90 · PLAN 122) — 토적표 줄 · 합계 · 토공집계표.
|
||||
"""
|
||||
|
||||
@@ -32,6 +33,7 @@ from B08_Quantity.B08_Quantity_Engine_Earthwork_Summary import (
|
||||
)
|
||||
from B08_Quantity.B08_Quantity_Router_Hydro import _project_root
|
||||
from common_util.common_util_project_settings import (
|
||||
earthwork_conversion_factors,
|
||||
mixed_conversion_factors,
|
||||
quantity_settings,
|
||||
soft_rock_pct,
|
||||
@@ -65,6 +67,11 @@ async def earthwork_table(project_id: UUID) -> dict[str, Any]:
|
||||
**table,
|
||||
"summary": summary_rows(table["totals"], haul, soft),
|
||||
"rock_ratios_pct": {"연암": soft, "경암": 100.0 - soft},
|
||||
"rock_c": {
|
||||
kind: entry["compacted"]
|
||||
for kind, entry in earthwork_conversion_factors(settings).items()
|
||||
if kind != "soil"
|
||||
},
|
||||
"haul": haul,
|
||||
"haul_saved": bool(mass_haul.get("haul_plan")),
|
||||
"route_id": route_id,
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* 보정량 · 보정량계 · 성토 · 유용토 · 차인토량 · 누가토량 · 면고르기 · 지장목제거 · 제근 · 노면정리 각
|
||||
* 거리 · 면적) · 맨 아래 합계 줄. 첫 측점은 앞이 없어 입적 · 면적이 빈 값(—).
|
||||
* 표 = 공용 많은 자료 표(`ui_template_data_table` · PLAN 70) · 카드 안 · 나머지 셋은 `…_Earthwork_Sheets`.
|
||||
* 절토 연암 · 경암 = 암 × 좌측 패널 비율 — 조작 중 같은 식으로 바로 다시 셈(`…_Options_Calc` · PLAN 122).
|
||||
* 절토 연암 · 경암 = 암 × 좌측 패널 비율 · 암 C = 비율 가중 — 조작 중 같은 식으로 바로 다시 셈
|
||||
* (`…_Options_Calc` · PLAN 122 · 125-1).
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -20,7 +21,13 @@ import {
|
||||
renderFreeHaul,
|
||||
renderHaulDistance,
|
||||
} from "./B08_Quantity_UI_Earthwork_Sheets";
|
||||
import { splitRockColumns, splitRockSummary } from "./B08_Quantity_Options_Calc";
|
||||
import {
|
||||
mixedRockC,
|
||||
recountAdjustedSummary,
|
||||
recountRockC,
|
||||
splitRockColumns,
|
||||
splitRockSummary,
|
||||
} from "./B08_Quantity_Options_Calc";
|
||||
import { liveSoftRock, onOptionsChange } from "./B08_Quantity_UI_Options";
|
||||
|
||||
type Face = { length_m: number | null; area_m2: number | null };
|
||||
@@ -85,6 +92,8 @@ export interface EarthworkTable {
|
||||
haul_saved: boolean;
|
||||
/** 저장된 연암 · 경암 %(암 전체 기준) */
|
||||
rock_ratios_pct: { 연암: number; 경암: number };
|
||||
/** 가중 전 리핑암(연암) · 발파암(경암) C — 비율로 다시 가중(125-1) */
|
||||
rock_c: { ripping_rock: number; blasting_rock: number };
|
||||
}
|
||||
|
||||
/** 칸 묶음 — [머리 묶음, 키 앞머리, 풍선 글] · 단면적 · 입적 · 보정량 셋 · 머리 글자는 사용자 견본대로 */
|
||||
@@ -212,8 +221,9 @@ function renderSheet(host: HTMLElement, table: EarthworkTable, interval: number)
|
||||
const info = [
|
||||
note(
|
||||
`평균단면적법 · 노선 ${table.route_id}${table.confirmed ? "(확정)" : "(확정 전 최신 노선)"} · ` +
|
||||
`측점 ${table.station_count} · 다짐 환산계수 토사 ${c("soil")} · 리핑암 ${c("ripping_rock")} · ` +
|
||||
`발파암 ${c("blasting_rock")} · 보정량계 = 절토 보정량 · 면 거리 = B07 횡단도 수량 · ` +
|
||||
`측점 ${table.station_count} · 다짐 환산계수 토사 ${c("soil")} · 암 ` +
|
||||
`${table.conversion_factors.ripping_rock?.compacted.toFixed(3) ?? ""}(연암 리핑 ` +
|
||||
`${table.rock_c.ripping_rock} · 경암 발파 ${table.rock_c.blasting_rock} 비율 가중) · 보정량계 = 절토 보정량 · 면 거리 = B07 횡단도 수량 · ` +
|
||||
`단위 m · ㎡ · ㎥`,
|
||||
),
|
||||
...(flagged.length ? [note(`* ${flagged[0].notes[0]} — ${flagged.length}곳`)] : []),
|
||||
@@ -260,13 +270,21 @@ export async function renderEarthwork(host: HTMLElement, projectId: string): Pro
|
||||
return;
|
||||
}
|
||||
|
||||
// 연암 · 경암 칸 · 집계표 줄 = 암 × 지금 비율(캐시 → 저장본)
|
||||
// 연암 · 경암 칸 · 집계표 줄 = 암 × 지금 비율 · 암 C = 비율 가중(캐시 → 저장본 · 125-1)
|
||||
const summary = table.summary;
|
||||
const applyRock = (): void => {
|
||||
const soft = liveSoftRock(projectId, table.rock_ratios_pct?.연암 ?? 90);
|
||||
const c = mixedRockC(soft, table.rock_c.ripping_rock, table.rock_c.blasting_rock);
|
||||
for (const kind of ["ripping_rock", "blasting_rock"])
|
||||
table.conversion_factors[kind].compacted = c;
|
||||
recountRockC(table.rows as unknown as Array<Record<string, unknown>>, table.totals, c);
|
||||
for (const record of [...table.rows, table.totals])
|
||||
splitRockColumns(record as unknown as Record<string, unknown>, soft);
|
||||
table.summary = splitRockSummary(summary, table.totals, soft);
|
||||
table.summary = recountAdjustedSummary(
|
||||
splitRockSummary(summary, table.totals, soft),
|
||||
table.totals,
|
||||
table.haul.free_haul.q,
|
||||
);
|
||||
};
|
||||
applyRock();
|
||||
|
||||
|
||||
@@ -345,27 +345,29 @@ def earthwork_conversion_factors(settings: dict[str, Any]) -> dict[str, dict[str
|
||||
ROCK_METHOD_KINDS = {ROCK_METHOD_RIPPING: "ripping_rock", ROCK_METHOD_BLASTING: "blasting_rock"}
|
||||
|
||||
|
||||
def mixed_conversion_factors(settings: dict[str, Any]) -> dict[str, dict[str, float]]:
|
||||
"""토적표·유토곡선·운반표가 **계산에** 쓸 계수 — 암은 구성비 가중 C (㉱ (나) · 2026-09-14).
|
||||
def mixed_rock_c(soft_pct: float, ripping_c: float, blasting_c: float) -> float:
|
||||
"""암 C = 연암 % × 리핑암 C + 경암 % × 발파암 C(0~100 자름 · 125-1).
|
||||
|
||||
B06 의 암은 한 종류 **자리표시**(`ripping_rock` · 옛 자료 `blasting_rock`)이고 암질은 설계내역에서
|
||||
구성비로 정함(8-1 사용자 확정). 그래서 구성비와 갈래별 시공법이 **다 서면** 암 두 칸을
|
||||
Σ몫 × C(시공법)로 갈음 — 몫은 흙깎기와 같은 안분(준 비율끼리 · 합이 100 이 아니어도 안분).
|
||||
⚠ 구성비가 비거나 시공법이 빠진 갈래가 있으면 **종전 값** — 인계가 막힘 사유를 내고, 지어낸 계수를 안 씀.
|
||||
짝 — `B08_Quantity_Options_Calc.ts` `mixedRockC`(거울 시험 `test_122_b08_options_mirror`).
|
||||
"""
|
||||
soft = min(max(soft_pct, 0.0), 100.0) / 100.0
|
||||
return soft * ripping_c + (1.0 - soft) * blasting_c
|
||||
|
||||
|
||||
def mixed_conversion_factors(settings: dict[str, Any]) -> dict[str, dict[str, float]]:
|
||||
"""토적표·유토곡선·운반표가 **계산에** 쓸 계수 — 암은 연암 · 경암 비율 가중 C (125-1).
|
||||
|
||||
B06 의 암은 한 종류 **자리표시**(`ripping_rock` · 옛 자료 `blasting_rock`)라 암 두 칸을
|
||||
`mixed_rock_c(연암 %, 리핑암 C, 발파암 C)` 하나로 갈음 — 비율 = `soft_rock_pct`(기본 90 : 10 · 122).
|
||||
시공법은 연암 = 리핑암 · 경암 = 발파암 고정(브레인 정함) — `rock_methods` 를 안 읽음.
|
||||
⚠ 화면 「무엇을 골랐나」(`earthwork_conversion_choices`)는 갈래별 값 그대로 — 여기 값을 보이면 안 됨.
|
||||
"""
|
||||
resolved = earthwork_conversion_factors(settings)
|
||||
ratios = settings.get("rock_ratios_pct") or {}
|
||||
shares = {
|
||||
name: float(ratios.get(name) or 0)
|
||||
for name in rock_classes(settings)
|
||||
if name != "토사" and float(ratios.get(name) or 0) > 0
|
||||
}
|
||||
kinds = {name: ROCK_METHOD_KINDS.get(rock_method(settings, name) or "") for name in shares}
|
||||
if not shares or not all(kinds.values()):
|
||||
return resolved
|
||||
total = sum(shares.values())
|
||||
mixed = sum(shares[name] / total * resolved[kind]["compacted"] for name, kind in kinds.items())
|
||||
mixed = mixed_rock_c(
|
||||
soft_rock_pct(settings),
|
||||
resolved["ripping_rock"]["compacted"],
|
||||
resolved["blasting_rock"]["compacted"],
|
||||
)
|
||||
for kind in ROCK_METHOD_KINDS.values():
|
||||
resolved[kind]["compacted"] = mixed
|
||||
return resolved
|
||||
|
||||
@@ -14,6 +14,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from B08_Quantity.B08_Quantity_Engine_Earthwork import StationArea, build_table
|
||||
from B08_Quantity.B08_Quantity_Engine_Earthwork_Summary import (
|
||||
haul_lists,
|
||||
rock_split,
|
||||
@@ -21,6 +22,7 @@ from B08_Quantity.B08_Quantity_Engine_Earthwork_Summary import (
|
||||
summary_rows,
|
||||
)
|
||||
from B08_Quantity.B08_Quantity_Router_Summary import couplings
|
||||
from common_util.common_util_project_settings import mixed_conversion_factors, mixed_rock_c
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
||||
@@ -85,7 +87,7 @@ writeFileSync(process.argv[2], JSON.stringify(out));
|
||||
"""
|
||||
|
||||
|
||||
def _ts(tmp_path: Path, payload: dict) -> dict:
|
||||
def _ts(tmp_path: Path, payload: dict, runner: str = _RUNNER) -> dict:
|
||||
out = tmp_path / "js"
|
||||
subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
[
|
||||
@@ -106,7 +108,7 @@ def _ts(tmp_path: Path, payload: dict) -> dict:
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||||
(out / "runner.mjs").write_text(runner, encoding="utf-8")
|
||||
result = tmp_path / "out.json"
|
||||
subprocess.run( # noqa: S603
|
||||
["node", str(out / "runner.mjs"), str(result), json.dumps(payload)],
|
||||
@@ -162,6 +164,68 @@ def test_두_쪽이_같은_값을_낸다(tmp_path: Path) -> None:
|
||||
assert got == _py_recount(one), one
|
||||
|
||||
|
||||
# 125-1 — 암 C = 비율 가중 · 토적표(90 : 10 으로 선 서버 값)를 새 비율로 다시 센 TS = 서버가 그 비율로 센 값
|
||||
STATIONS = [
|
||||
StationArea(0.0, cut_soil_area_m2=1.5, cut_rock_area_m2=2.0, fill_area_m2=0.5),
|
||||
StationArea(20.0, cut_soil_area_m2=0.7, cut_rock_area_m2=3.3, ditch_area_m2=0.4),
|
||||
StationArea(40.0, cut_rock_area_m2=1.1, fill_area_m2=9.0, cut_rock_kind="blasting_rock"),
|
||||
StationArea(52.5, cut_soil_area_m2=2.2, fill_area_m2=1.3),
|
||||
]
|
||||
HAUL_PLAN = {
|
||||
"blocks": [{"bands": [{"equipment": "free_haul", "volume_m3": 12.5, "haul_distance_m": 8}]}]
|
||||
}
|
||||
|
||||
_RUNNER_C = """
|
||||
import { writeFileSync } from "node:fs";
|
||||
import * as calc from "./B08_Quantity_Options_Calc.js";
|
||||
const input = JSON.parse(process.argv[3]);
|
||||
const out = {
|
||||
c: input.pcts.map((p) => calc.mixedRockC(p, 1.15, 1.3)),
|
||||
tables: input.pcts.map((p) => {
|
||||
const t = structuredClone(input.table);
|
||||
calc.recountRockC(t.rows, t.totals, calc.mixedRockC(p, 1.15, 1.3));
|
||||
for (const r of [...t.rows, t.totals]) calc.splitRockColumns(r, p);
|
||||
const summary = calc.recountAdjustedSummary(
|
||||
calc.splitRockSummary(t.summary, t.totals, p), t.totals, input.freeQ);
|
||||
return { rows: t.rows, totals: t.totals, summary };
|
||||
}),
|
||||
};
|
||||
writeFileSync(process.argv[2], JSON.stringify(out));
|
||||
"""
|
||||
|
||||
|
||||
def _py_table(pct: float) -> dict:
|
||||
"""서버 길(`Router_Earthwork`) 그대로 — 가중 C 로 표 · 연암 · 경암 칸 · 집계표."""
|
||||
table = build_table(STATIONS, mixed_conversion_factors({"rock_ratios_pct": {"연암": pct}}))
|
||||
for record in [*table["rows"], table["totals"]]:
|
||||
split_rock_columns(record, pct)
|
||||
summary = summary_rows(table["totals"], haul_lists(HAUL_PLAN), pct)
|
||||
return {"rows": table["rows"], "totals": table["totals"], "summary": summary}
|
||||
|
||||
|
||||
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
||||
def test_암_C_가중_두_쪽이_같은_값을_낸다(tmp_path: Path) -> None:
|
||||
pcts = [0, 10, 33.3, 50, 90, 100, 130, -5]
|
||||
ts = _ts(
|
||||
tmp_path,
|
||||
{"pcts": pcts, "table": _py_table(90), "freeQ": haul_lists(HAUL_PLAN)["free_haul"]["q"]},
|
||||
_RUNNER_C,
|
||||
)
|
||||
assert ts["c"] == [mixed_rock_c(p, 1.15, 1.3) for p in pcts]
|
||||
for pct, got in zip(pcts, ts["tables"]):
|
||||
want = json.loads(json.dumps(_py_table(pct)))
|
||||
assert got["rows"] == want["rows"] and got["summary"] == want["summary"], pct
|
||||
# 합계 — 파이썬 3.12 sum() 은 보정 합산이라 끝자리(1 ulp)만 다를 수 있음
|
||||
assert got["totals"] == pytest.approx(want["totals"], rel=1e-12), pct
|
||||
|
||||
|
||||
def test_50_50_이면_보정량이_바뀜() -> None:
|
||||
ninety, half = _py_table(90)["totals"], _py_table(50)["totals"]
|
||||
assert half["cut_rock_volume_m3"] == ninety["cut_rock_volume_m3"]
|
||||
assert half["cut_rock_adjusted_m3"] == pytest.approx(ninety["cut_rock_volume_m3"] * 1.225)
|
||||
assert half["adjusted_total_m3"] > ninety["adjusted_total_m3"]
|
||||
|
||||
|
||||
def test_1본_6_m_면_커플링이_바뀜(tmp_path: Path) -> None:
|
||||
six, eight = _py_recount(6), _py_recount(8)
|
||||
assert six["total"]["pipe:cp|800"] == 2 + 1 + 2 and eight["total"]["pipe:cp|800"] == 2 + 1 + 1
|
||||
@@ -174,4 +238,8 @@ def test_식_모양이_두_쪽_같다() -> None:
|
||||
assert "Math.max(Math.ceil(lengthM / oneM - 1e-9) - 1, 0)" in ts
|
||||
assert "const soft = (value * Math.min(Math.max(softPct, 0), 100)) / 100;" in ts
|
||||
assert "soft = value * min(max(soft_pct, 0.0), 100.0) / 100.0" in py
|
||||
assert "return soft * rippingC + (1 - soft) * blastingC;" in ts
|
||||
assert "return soft * ripping_c + (1.0 - soft) * blasting_c" in (
|
||||
ROOT / "common_util" / "common_util_project_settings.py"
|
||||
).read_text("utf-8")
|
||||
assert not re.search(r"^import ", ts, re.M) # 시험이 이 파일만 옮겨 돌림
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""125-1 연암 · 경암 비율 → 암 환산계수 C 가중(연암 = 리핑암 · 경암 = 발파암 고정).
|
||||
|
||||
C = 연암 % × 리핑암 C + 경암 % × 발파암 C · 비율 = 122 `rock_ratios_pct`(기본 90 : 10) ·
|
||||
B08 토적표 보정량 · B06 유토곡선이 같은 `mixed_conversion_factors` 를 씀.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from B06_Section import B06_Section_Server_Calc_Prebuild as prebuild
|
||||
from B08_Quantity.B08_Quantity_Engine_Earthwork import StationArea, build_table
|
||||
from common_util.common_util_project_settings import (
|
||||
mixed_conversion_factors,
|
||||
quantity_settings,
|
||||
settings_path,
|
||||
)
|
||||
|
||||
RIP, BLAST = 1.15, 1.30 # 품셈 토량환산계수 기본(config) — 표 그대로
|
||||
|
||||
|
||||
def _c(settings: dict) -> float:
|
||||
factors = mixed_conversion_factors(settings)
|
||||
assert factors["ripping_rock"]["compacted"] == factors["blasting_rock"]["compacted"]
|
||||
return factors["ripping_rock"]["compacted"]
|
||||
|
||||
|
||||
def test_기본_90_10_이면_가중_C() -> None:
|
||||
assert _c({}) == pytest.approx(0.9 * RIP + 0.1 * BLAST) # 1.165
|
||||
assert _c({"rock_ratios_pct": {"연암": 90.0, "경암": 10.0}}) == pytest.approx(1.165)
|
||||
|
||||
|
||||
def test_50_50_이면_C_가_바뀜() -> None:
|
||||
assert _c({"rock_ratios_pct": {"연암": 50.0, "경암": 50.0}}) == pytest.approx(1.225)
|
||||
assert _c({"rock_ratios_pct": {"연암": 100.0}}) == pytest.approx(RIP)
|
||||
assert _c({"rock_ratios_pct": {"연암": 0.0}}) == pytest.approx(BLAST)
|
||||
|
||||
|
||||
def test_시공법_칸은_안_읽음() -> None:
|
||||
ratios = {"rock_ratios_pct": {"연암": 50.0}}
|
||||
assert _c({**ratios, "rock_methods": {"연암": "blasting", "경암": "ripping"}}) == _c(ratios)
|
||||
|
||||
|
||||
def test_토사_C_는_그대로() -> None:
|
||||
assert mixed_conversion_factors({"rock_ratios_pct": {"연암": 50.0}})["soil"]["compacted"] == 0.9
|
||||
|
||||
|
||||
def test_B08_토적표_보정량이_가중_C_로() -> None:
|
||||
stations = [
|
||||
StationArea(chainage_m=0.0, cut_rock_area_m2=2.0),
|
||||
StationArea(chainage_m=20.0, cut_rock_area_m2=4.0, cut_rock_kind="blasting_rock"),
|
||||
]
|
||||
for pct, c in ((90.0, 1.165), (50.0, 1.225)):
|
||||
settings = {"rock_ratios_pct": {"연암": pct}}
|
||||
table = build_table(stations, mixed_conversion_factors(settings))
|
||||
assert table["totals"]["cut_rock_volume_m3"] == pytest.approx(60.0)
|
||||
assert table["totals"]["cut_rock_adjusted_m3"] == pytest.approx(60.0 * c)
|
||||
|
||||
|
||||
def test_B06_유토곡선이_같은_C(tmp_path: Path, monkeypatch) -> None:
|
||||
settings_path(tmp_path).write_text(
|
||||
json.dumps({"quantity": {"rock_ratios_pct": {"연암": 50.0, "경암": 50.0}}}), "utf-8"
|
||||
)
|
||||
|
||||
async def fake_run(_fn, _project_id):
|
||||
return "x"
|
||||
|
||||
monkeypatch.setattr(prebuild, "run_with_connection", fake_run)
|
||||
monkeypatch.setattr(prebuild, "resolve_stored_project_path", lambda _p: tmp_path)
|
||||
factors = asyncio.run(prebuild.conversion_factors_for("p"))
|
||||
assert factors["ripping_rock"]["compacted"] == pytest.approx(1.225)
|
||||
assert factors == mixed_conversion_factors(quantity_settings(tmp_path))
|
||||
context = prebuild._mass_haul_context(None, factors)
|
||||
assert context["earthwork_conversion"]["ripping_rock"]["compacted"] == pytest.approx(1.225)
|
||||
@@ -44,14 +44,15 @@ def test_고른_계수_위에서_가중() -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"settings",
|
||||
("settings", "soft"),
|
||||
[
|
||||
{"rock_class_set": "geochang5"},
|
||||
{**MIX, "rock_methods": {"연암": "ripping"}},
|
||||
({"rock_class_set": "geochang5"}, 0.9), # 비율 비면 기본 90 : 10(122)
|
||||
({**MIX, "rock_methods": {"연암": "ripping"}}, 0.6), # 시공법 안 봄(125-1)
|
||||
],
|
||||
)
|
||||
def test_구성비나_시공법이_비면_종전_값(settings: dict) -> None:
|
||||
assert mixed_conversion_factors(settings) == earthwork_conversion_factors(settings)
|
||||
def test_비율이_비면_기본_시공법은_연암_리핑_경암_발파_고정(settings: dict, soft: float) -> None:
|
||||
mixed = mixed_conversion_factors(settings)["ripping_rock"]["compacted"]
|
||||
assert mixed == pytest.approx(soft * 1.15 + (1 - soft) * 1.30)
|
||||
|
||||
|
||||
def test_화면_고른_값_표시는_갈래별_그대로() -> None:
|
||||
|
||||
Reference in New Issue
Block a user