fix(b05): 도수로/돌붙임 면적 = 늘 손 입력 — 길이 · 월류 폭으로 면적 자동 셈(syncChuteArea · chuteAreaM2) 걷음 · 물넘이 · 세월교 폼 onChange 원래 흐름 상자로 · 길이 = 그림 전용 (301-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
@@ -15,10 +15,8 @@
|
||||
* (같은 풀이 · 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. */
|
||||
@@ -81,39 +79,3 @@ 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 · 294-4) — 길이를 넣으면 면적 = 폭 × 길이(폭 = 띠가
|
||||
* 도로 방향으로 걸치는 월류 폭 · 비면 물넘이 기본 폭 · 배관 · 박스는 폭 정의 없어 자동 안 함) · 길이를 비우면 면적 = 칸 기본값 · 월류 폭을 바꾸면 길이가 있을 때만
|
||||
* 다시 셈. 길이는 그림(사면 따라 그 길이) · 면적은 수량(B08 돌붙임)이 씀.
|
||||
*/
|
||||
export function syncChuteArea(
|
||||
form: Pick<SpecForm, "state" | "setValue">,
|
||||
c: SpecContainer,
|
||||
item: SpecItem,
|
||||
/** 도수로 상자 · 월류 폭 상자 — 물넘이 `chute` · `slope` · 세월교 `chute_add` · `ford`(294-4). */
|
||||
chuteBox = "chute",
|
||||
widthBox = "slope",
|
||||
): void {
|
||||
const byLength = c.id === chuteBox && item.id === "length_m";
|
||||
if (!byLength && !(c.id === widthBox && item.id === "width")) return;
|
||||
const chute = form.state()[chuteBox]?.[0];
|
||||
if (!chute) return;
|
||||
const length = Number(chute.length_m ?? "");
|
||||
if (!(chute.length_m ?? "").trim() || !(length > 0)) {
|
||||
if (byLength)
|
||||
form.setValue(chuteBox, "area", CHUTE_ITEMS.find((i) => i.id === "area")?.default ?? "");
|
||||
return;
|
||||
}
|
||||
const width = Number(form.state()[widthBox]?.[0]?.width ?? "");
|
||||
form.setValue(
|
||||
chuteBox,
|
||||
"area",
|
||||
String(chuteAreaM2(width > 0 ? width : FORD_PAVEMENT_DEFAULT_WIDTH_M, length)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ import {
|
||||
boxOptionsIn,
|
||||
} from "../A00_Common/design/design_structure_box";
|
||||
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||
import { overflowSync, syncChuteArea } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import { overflowSync } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import {
|
||||
pipeDrawn,
|
||||
pipeExtraCount,
|
||||
@@ -384,10 +384,7 @@ export function createFacilityOptionsForm(
|
||||
);
|
||||
// 유출 흙막이 단 수 = 흙막이 상자 수(관보호공과 같은 방향 · 「+ 흙막이」 없음 · 90-1)
|
||||
const fordForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: (c, item) => (
|
||||
syncChuteArea(fordForm, c, item, "chute_add", "ford"),
|
||||
fordFlowBox.onChange(c, item)
|
||||
),
|
||||
onChange: fordFlowBox.onChange,
|
||||
fields: (c, values) => [...guardFields(c, values), ...fordFlowBox.fields(c)],
|
||||
count: guardSoilCount(
|
||||
() => chainage,
|
||||
@@ -477,7 +474,7 @@ export function createFacilityOptionsForm(
|
||||
emit,
|
||||
);
|
||||
const paveForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: (c, item) => (syncChuteArea(paveForm, c, item), paveFlowBox.onChange(c, item)),
|
||||
onChange: paveFlowBox.onChange,
|
||||
fields: paveFlowBox.fields,
|
||||
});
|
||||
|
||||
|
||||
@@ -26,16 +26,6 @@ const load = (file, stubs = {}) => {
|
||||
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;
|
||||
};
|
||||
|
||||
// 287-2 · 287-4 그림 — DOM 흉내(속성 · 자식만)
|
||||
class Node {
|
||||
constructor(tag) {
|
||||
@@ -209,6 +199,32 @@ const overflowRun = (flowValue) => {
|
||||
};
|
||||
};
|
||||
|
||||
// 301-1 폼 훅 그대로(B05 물넘이 paveForm · 세월교 fordForm onChange = overflowSync onChange)
|
||||
const areaRun = (widthBox, chuteBox) => {
|
||||
const state = { [widthBox]: [{ width: "6" }], [chuteBox]: [{ length_m: "", area: "17" }] };
|
||||
const form = {
|
||||
state: () => state,
|
||||
setValue: (c, i, v) => (state[c][0][i] = v),
|
||||
refresh: () => undefined,
|
||||
};
|
||||
const sync = calc.overflowSync(
|
||||
() => form,
|
||||
widthBox,
|
||||
() => 14.5776,
|
||||
() => undefined,
|
||||
);
|
||||
const edit = (box, item, value) => {
|
||||
state[box][0][item] = value;
|
||||
sync.onChange({ id: box }, { id: item });
|
||||
return state[chuteBox][0].area;
|
||||
};
|
||||
return [
|
||||
edit(chuteBox, "length_m", "4"),
|
||||
edit(widthBox, "width", "8"),
|
||||
edit(chuteBox, "length_m", ""),
|
||||
];
|
||||
};
|
||||
|
||||
const out = {
|
||||
overflowFlow: overflowRun(14.5776),
|
||||
overflowNoFlow: overflowRun(null),
|
||||
@@ -235,11 +251,8 @@ const out = {
|
||||
paveSlopeItems: load(D("design_structure_ford_pavement.ts"))
|
||||
.FORD_PAVEMENT_SPEC.containers.find((c) => c.id === "slope")
|
||||
.items.map((i) => i.id),
|
||||
// 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"),
|
||||
// 301-1 면적 = 늘 손 입력 — 길이 · 월류 폭을 바꾸거나 길이를 비워도 면적 그대로(물넘이 · 세월교 폼 훅)
|
||||
areaKeptPave: areaRun("slope", "chute"),
|
||||
areaKeptFord: areaRun("ford", "chute_add"),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -64,10 +64,6 @@ const pts = (lines) =>
|
||||
lines.map((l) => l.points.map((p) => [+p.offset.toFixed(4), +p.elevation.toFixed(4)]));
|
||||
const dipGround = hatch.groundLine(input.dipSamples);
|
||||
const fillGround = hatch.groundLine(input.fillSamples);
|
||||
const calc = load(path.join(ROOT, "A00_Common", "design", "design_structure_ford_calc.ts"));
|
||||
const formOf = (state) => ({ state: () => state, setValue: (c, i, v) => (state[c][0][i] = v) });
|
||||
const fordState = { ford: [{ width: "8" }], chute_add: [{ length_m: "2.5", area: "30" }] };
|
||||
calc.syncChuteArea(formOf(fordState), { id: "chute_add" }, { id: "length_m" }, "chute_add", "ford");
|
||||
const guard = load(path.join(ROOT, "A00_Common", "design", "design_structure_guard.ts"));
|
||||
const fordPave = load(path.join(ROOT, "A00_Common", "design", "design_structure_ford_pavement.ts"));
|
||||
|
||||
@@ -85,7 +81,6 @@ const out = {
|
||||
fillLine: pave.outletFillLine(input.fillDesign, fillGround),
|
||||
fillLineOld: pave.outletFillLine(input.fillDesign),
|
||||
fillChuteLength: pave.fordChuteLine(input.fillDesign, { length_m: 1 }, fillGround),
|
||||
fordArea: fordState.chute_add[0].area,
|
||||
chuteItems: guard.CHUTE_ITEMS.map((i) => i.id),
|
||||
fordPaveChuteSame:
|
||||
fordPave.FORD_PAVEMENT_SPEC.containers.find((c) => c.id === "chute").items ===
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""287 물넘이포장 후속(요구 183-2 · 4 · 6 · 7 · 8 · 9).
|
||||
|
||||
못박는 것
|
||||
- 287-1 도수로/돌붙임 길이 ↔ 면적 — 길이 넣음 = 월류 폭 × 길이 · 비움 = 칸 기본값 · 월류 폭을 바꾸면
|
||||
길이가 있을 때만 다시 셈 · 폭이 비면 물넘이 기본 폭.
|
||||
- 301-1 도수로/돌붙임 면적 = 늘 손 입력(요구 192-2 · 287-1 자동 셈 걷음) — 길이 · 월류 폭을 바꾸거나
|
||||
길이를 비워도 면적 그대로 · 길이 = 그림 전용(294-4).
|
||||
- 287-3 월류 폭 → 높이 자동 = 세월교 한 벌(`fordHeightM` · 폼 `overflow(…, "slope")`) · 유량 없으면 손 입력.
|
||||
- 287-5 물넘이포장 형상 누름 = 다른 시설과 같은 고름 창구(`toggleRevet("pipe")` → 폼 · 알약 · 측점선) ·
|
||||
판정면은 보라 손잡이 아래(요구 185 ①) · 끌기(3 px 넘게 움직임) = 고름 아님(②) · 노면공 포장층
|
||||
@@ -51,17 +51,19 @@ def ts() -> dict:
|
||||
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_도수로_면적은_손_입력_그대로(ts):
|
||||
# 길이 4 넣음 · 월류 폭 8 · 길이 비움 — 면적은 손 입력 17 그대로
|
||||
assert ts["areaKeptPave"] == ["17", "17", "17"]
|
||||
assert ts["areaKeptFord"] == ["17", "17", "17"]
|
||||
|
||||
|
||||
def test_물넘이_폼이_길이_면적을_잇는다():
|
||||
def test_폼이_길이_면적을_잇지_않는다():
|
||||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||||
assert "syncChuteArea(paveForm, c, item), paveFlowBox.onChange(c, item)" in form
|
||||
assert "syncChuteArea" not in form
|
||||
assert "onChange: fordFlowBox.onChange," in form
|
||||
assert "onChange: paveFlowBox.onChange," in form
|
||||
calc = _read("A00_Common", "design", "design_structure_ford_calc.ts")
|
||||
assert "syncChuteArea" not in calc and "chuteAreaM2" not in calc
|
||||
|
||||
|
||||
def _pts(text: str) -> list[float]:
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
연장」) · 그림 = 콘크리트(포장층)가 늘인 끝까지.
|
||||
- 294-4 도수로/돌붙임 띠 = 낮은 쪽 끝단(늘인 끝 · 아니면 유출 쪽 노견 끝)에서 성토 사면 따라 · 길이 비움 =
|
||||
원지반 닿으면 끝 · 양측 절토 물넘이 = 띠 없음 · 길이 = 그 길이(사면보다 길면 원지반 따라) · 다른 구조물
|
||||
(배관 · 박스 · 세월교) 같은 규칙(`chuteRun` · 원지반 `layout.ground`) + 같은 「길이 (m)」 칸 · 면적 = 세월교만
|
||||
월류 폭 × 길이(배관 · 박스 = 폭 정의 없어 자동 안 함) · 길이는 집계 · B08 수량에 안 실림.
|
||||
(배관 · 박스 · 세월교) 같은 규칙(`chuteRun` · 원지반 `layout.ground`) + 같은 「길이 (m)」 칸 · 면적 = 늘
|
||||
손 입력(301-1 · 길이 · 폭으로 안 셈) · 길이는 집계 · B08 수량에 안 실림.
|
||||
|
||||
진짜 TS 는 `helper_294_3_ford_extend_chute.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
@@ -155,7 +155,6 @@ def test_성토_쪽_띠는_원지반_닿으면_끝(ts):
|
||||
def test_다른_구조물_같은_규칙과_길이_칸(ts):
|
||||
assert ts["chuteItems"][-1] == "length_m"
|
||||
assert ts["fordPaveChuteSame"] is True
|
||||
assert ts["fordArea"] == "20" # 세월교 월류 폭 8 × 길이 2.5
|
||||
hatch = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Wall_Hatch.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
@@ -168,7 +167,7 @@ def test_다른_구조물_같은_규칙과_길이_칸(ts):
|
||||
form = (ROOT / "B05_Profile" / "B05_Profile_UI_Drainage_Facility.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert form.count("syncChuteArea(") == 2 # 물넘이 · 세월교만(배관 · 박스 = 폭 정의 없음)
|
||||
assert "syncChuteArea" not in form # 면적 = 손 입력(301-1)
|
||||
|
||||
|
||||
def test_길이는_집계_수량에_안_실림():
|
||||
|
||||
Reference in New Issue
Block a user