Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_7' into sub_laptop_6
This commit is contained in:
@@ -15,7 +15,11 @@
|
||||
* (같은 풀이 · n · S 를 받음) · 거울 시험 test_73_11_b08_hydro_box_ford.
|
||||
* ========================================================================== */
|
||||
|
||||
import { FORD_PAVEMENT_DEFAULT_WIDTH_M } from "@config/config_frontend";
|
||||
import { fordSection } from "../../B05_Profile/B05_Profile_UI_Drainage_Facility_Fields";
|
||||
import type { SpecForm } from "./design_structure_form";
|
||||
import { CHUTE_ITEMS } from "./design_structure_guard";
|
||||
import type { SpecContainer, SpecItem } from "./design_structure_spec";
|
||||
|
||||
/** 월류 폭(m) → 필요 월류 높이(m) · 0.01 m 올림. 유량 · 폭이 없으면 null. */
|
||||
export function fordHeightM(designFlowM3s: number | null, widthM: number): number | null {
|
||||
@@ -29,3 +33,36 @@ export function fordHeightHint(designFlowM3s: number | null, widthM: number): st
|
||||
if (need !== null) return `필요 수심 ${need.toFixed(2)} 이상`;
|
||||
return designFlowM3s ? "월류 폭을 넣으면 셈" : "설계유량 없음 — 수기 입력";
|
||||
}
|
||||
|
||||
/** 도수로 · 돌붙임 면적(㎡ · 287-1) = 폭 × 길이 · 0.01 반올림. */
|
||||
export function chuteAreaM2(widthM: number, lengthM: number): number {
|
||||
return Math.round(widthM * lengthM * 100) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 물넘이 포장 도수로/돌붙임 길이 ↔ 면적(287-1) — 길이를 넣으면 면적 = 폭 × 길이(폭 = 띠가 도로 방향으로
|
||||
* 걸치는 월류 폭 · 비면 물넘이 기본 폭) · 길이를 비우면 면적 = 칸 기본값 · 월류 폭을 바꾸면 길이가 있을 때만
|
||||
* 다시 셈. 길이는 그림(사면 따라 그 길이) · 면적은 수량(B08 돌붙임)이 씀.
|
||||
*/
|
||||
export function syncChuteArea(
|
||||
form: Pick<SpecForm, "state" | "setValue">,
|
||||
c: SpecContainer,
|
||||
item: SpecItem,
|
||||
): void {
|
||||
const byLength = c.id === "chute" && item.id === "length_m";
|
||||
if (!byLength && !(c.id === "slope" && item.id === "width")) return;
|
||||
const chute = form.state().chute?.[0];
|
||||
if (!chute) return;
|
||||
const length = Number(chute.length_m ?? "");
|
||||
if (!(chute.length_m ?? "").trim() || !(length > 0)) {
|
||||
if (byLength)
|
||||
form.setValue("chute", "area", CHUTE_ITEMS.find((i) => i.id === "area")?.default ?? "");
|
||||
return;
|
||||
}
|
||||
const width = Number(form.state().slope?.[0]?.width ?? "");
|
||||
form.setValue(
|
||||
"chute",
|
||||
"area",
|
||||
String(chuteAreaM2(width > 0 ? width : FORD_PAVEMENT_DEFAULT_WIDTH_M, length)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ import {
|
||||
boxOptionsIn,
|
||||
} from "../A00_Common/design/design_structure_box";
|
||||
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||
import { fordHeightM } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import { fordHeightM, syncChuteArea } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import {
|
||||
pipeDrawn,
|
||||
pipeExtraCount,
|
||||
@@ -486,7 +486,9 @@ export function createFacilityOptionsForm(
|
||||
// ── 물넘이 포장 = 사용자 구조물 표 03 명세(PLAN 52-3) — 경사각도(+ 월류 폭 · 높이 · 65-5) · 도수로/돌붙임 ·
|
||||
// 옛 바닥 경사 · 두께 · 길이 칸은 뺌(지난 정의 값) ──
|
||||
const paveFlowBox = overflow(() => paveForm, "slope");
|
||||
const paveForm: SpecForm = createSpecForm(specLinks, { onChange: paveFlowBox.onChange });
|
||||
const paveForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: (c, item) => (syncChuteArea(paveForm, c, item), paveFlowBox.onChange(c, item)),
|
||||
});
|
||||
|
||||
// 10-A ⑲ 저장 규칙 글은 [?] 설명서 `구조물_<시설>.md` 로 옮김(54-2) — 병합은 `_Drainage_Facility_Merge`.
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* 287 물넘이포장 후속 헬퍼 — 진짜 TS 를 Node 로 돌려 표준출력 JSON 한 벌로.
|
||||
* 상대 import · `@config/` 는 따라 옮김.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
const load = (file, stubs = {}) => {
|
||||
if (cache.has(file)) return cache.get(file);
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(file, box.exports);
|
||||
const near = (m) => {
|
||||
if (stubs[m]) return stubs[m];
|
||||
if (m.startsWith("@config/")) return load(path.join(ROOT, "config", `${m.slice(8)}.ts`));
|
||||
if (m.startsWith(".")) return load(path.join(path.dirname(file), `${m}.ts`));
|
||||
return new Proxy({}, { get: () => () => {} });
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, near);
|
||||
return box.exports;
|
||||
};
|
||||
const D = (f) => path.join(ROOT, "A00_Common", "design", f);
|
||||
const calc = load(D("design_structure_ford_calc.ts"));
|
||||
|
||||
/** 폼 흉내 — 상태 · setValue 만. */
|
||||
const formOf = (slope, chute) => {
|
||||
const state = { slope: [{ ...slope }], chute: [{ ...chute }] };
|
||||
return { state: () => state, setValue: (c, i, v) => (state[c][0][i] = v) };
|
||||
};
|
||||
const change = (form, c, i) => {
|
||||
calc.syncChuteArea(form, { id: c }, { id: i });
|
||||
return form.state().chute[0].area;
|
||||
};
|
||||
|
||||
const out = {
|
||||
// 287-1 길이 넣음 = 월류 폭 × 길이 · 비움 = 기본값 · 폭 바꿈 = 길이 있을 때만 다시 셈 · 폭 비면 기본 폭
|
||||
areaByLength: change(formOf({ width: "6" }, { length_m: "4", area: "30" }), "chute", "length_m"),
|
||||
areaCleared: change(formOf({ width: "6" }, { length_m: "", area: "24" }), "chute", "length_m"),
|
||||
areaByWidth: change(formOf({ width: "8" }, { length_m: "2.5", area: "15" }), "slope", "width"),
|
||||
areaWidthNoLength: change(formOf({ width: "8" }, { length_m: "", area: "17" }), "slope", "width"),
|
||||
areaNoWidth: change(formOf({ width: "" }, { length_m: "3", area: "30" }), "chute", "length_m"),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,57 @@
|
||||
"""287 물넘이포장 후속(요구 183-2 · 4 · 6 · 7 · 8 · 9).
|
||||
|
||||
못박는 것
|
||||
- 287-1 도수로/돌붙임 길이 ↔ 면적 — 길이 넣음 = 월류 폭 × 길이 · 비움 = 칸 기본값 · 월류 폭을 바꾸면
|
||||
길이가 있을 때만 다시 셈 · 폭이 비면 물넘이 기본 폭.
|
||||
|
||||
진짜 TS 는 `helper_287_ford_pavement.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
return (ROOT.joinpath(*parts)).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ts() -> dict:
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_287_ford_pavement.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_도수로_길이_면적(ts):
|
||||
assert ts["areaByLength"] == "24" # 6 m × 4 m
|
||||
assert ts["areaCleared"] == "30" # 길이 비움 = 기본값
|
||||
assert ts["areaByWidth"] == "20" # 폭 8 m × 2.5 m
|
||||
assert ts["areaWidthNoLength"] == "17" # 길이 없으면 폭을 바꿔도 그대로
|
||||
assert ts["areaNoWidth"] == "30" # 폭 비움 = 기본 폭 10 m × 3 m
|
||||
|
||||
|
||||
def test_물넘이_폼이_길이_면적을_잇는다():
|
||||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||||
assert "syncChuteArea(paveForm, c, item), paveFlowBox.onChange(c, item)" in form
|
||||
Reference in New Issue
Block a user