feat(b08): 운반 사이클시간 짝 · 로직 입력 거리 채움 (253-2)
- B08_Quantity_Haul_Cycle.py · .ts 짝 — 건설품셈 8-2-8 덤프트럭 · 거창 25 단가산출 1308~1347행 재현(t1 5.33 · t2 101.23 · Cm 109.78) - 로직 입력 이름 채움 Cm · 사이클시간 · t2 · 운반거리 · 덮은 칸(own) 그대로 - B09 셈(hand_sources) 때 거리이정표 거리로 다시 채움 · 덮개 ref 에 cycle · own 남김 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"""B08 운반 사이클시간(PLAN 253-2) — 건설품셈 8-2-8 덤프트럭 · 거리이정표 거리 → 운반 로직 입력.
|
||||
|
||||
⚠ 짝 = `B08_Quantity_Haul_Cycle.ts`(같은 식) — 한쪽을 고치면 다른 쪽도 ·
|
||||
거울 시험 `resources/tester/test_253_2_haul_cycle.py`.
|
||||
조작 중은 TS 짝(「운반 단가」 모달)이 바로 셈 · B09 셈 때 서버가 이 파일로 다시 채움(정본).
|
||||
|
||||
식(거창 25 설계서 단가산출 1308~1347행 꼴 · 끝수 = 소수 둘째 자리 반올림)
|
||||
q = T / rt × f · n = q / (qs × k) · t1 = Cms × n / (60 × Es)
|
||||
t2 = (L1/V1 + L1/V2 + L2/V3 + L2/V4) × 60 · Cm = t1 + t2 + t3 + t4 + t5 + t6
|
||||
L1 = 거리이정표 그 행 포장 계(2차 + 1차 + 포장) · L2 = 비포장 · 속도 0 = 그 항 0.
|
||||
로직 입력 채움(이름으로) — `Cm` · `사이클시간` = Cm · `t2` = t2 · `운반거리` = L1 + L2 ·
|
||||
`own` = 사용자가 덮은 칸(L1 · L2 · t1 · 로직 입력 이름) — 그 값이 이김.
|
||||
기본값 = 거창 설계서(혼합골재 덤프 15톤) · 속도 · t 표 = master_data CC000054~058(화면이 고름).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
DEFAULTS: dict[str, float] = {
|
||||
"L1": 0.0,
|
||||
"L2": 0.0,
|
||||
"V1": 30.0,
|
||||
"V2": 35.0,
|
||||
"V3": 10.0,
|
||||
"V4": 15.0,
|
||||
"T": 15.0,
|
||||
"rt": 1.7,
|
||||
"f": 1.0,
|
||||
"qs": 1.72,
|
||||
"k": 1.0,
|
||||
"Es": 0.6,
|
||||
"Cms": 37.4,
|
||||
"t3": 0.8,
|
||||
"t4": 0.42,
|
||||
"t5": 0.5,
|
||||
"t6": 1.5,
|
||||
}
|
||||
#: 로직 입력 이름 → 셈 결과 칸
|
||||
FILL = {"Cm": "cm", "사이클시간": "cm", "t2": "t2", "운반거리": "L"}
|
||||
|
||||
|
||||
def r2(value: float) -> float:
|
||||
"""소수 둘째 자리 반올림(0.5 올림 · TS `Math.round` 와 같음)."""
|
||||
return math.floor(value * 100 + 0.5) / 100
|
||||
|
||||
|
||||
def _div(a: float, b: float) -> float:
|
||||
return a / b if b else 0.0
|
||||
|
||||
|
||||
def _num(value: Any) -> float | None:
|
||||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||||
return None
|
||||
return float(value) if math.isfinite(value) else None
|
||||
|
||||
|
||||
def cycle_time(inputs: dict[str, Any] | None) -> dict[str, float]:
|
||||
"""사이클 입력(빈 칸 = 기본값 · `t1` 있으면 그 값) → {q, n, t1, t2, cm, L}."""
|
||||
given = {k: _num(v) for k, v in (inputs or {}).items()}
|
||||
v = {k: given[k] if given.get(k) is not None else d for k, d in DEFAULTS.items()}
|
||||
q = r2(_div(v["T"], v["rt"]) * v["f"])
|
||||
n = r2(_div(q, v["qs"] * v["k"]))
|
||||
t1 = given.get("t1")
|
||||
t1 = r2(_div(v["Cms"] * n, 60 * v["Es"])) if t1 is None else t1
|
||||
l1, l2 = v["L1"], v["L2"]
|
||||
t2 = r2((_div(l1, v["V1"]) + _div(l1, v["V2"]) + _div(l2, v["V3"]) + _div(l2, v["V4"])) * 60)
|
||||
cm = r2(t1 + t2 + v["t3"] + v["t4"] + v["t5"] + v["t6"])
|
||||
return {
|
||||
"q": q,
|
||||
"n": n,
|
||||
"t1": t1,
|
||||
"t2": t2,
|
||||
"cm": cm,
|
||||
"L": math.floor((l1 + l2) * 1000 + 0.5) / 1000,
|
||||
}
|
||||
|
||||
|
||||
def cycle_inputs(ref: dict[str, Any] | None, dist: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""연결 `ref.cycle` + 거리이정표 거리(`dist{L1, L2}`) — 덮은 L1 · L2(`ref.own`)는 그대로."""
|
||||
cycle = dict((ref or {}).get("cycle") or {})
|
||||
own = set((ref or {}).get("own") or [])
|
||||
for name in ("L1", "L2"):
|
||||
if name not in own and _num((dist or {}).get(name)) is not None:
|
||||
cycle[name] = dist[name]
|
||||
if "t1" not in own:
|
||||
cycle.pop("t1", None)
|
||||
return cycle
|
||||
|
||||
|
||||
def fill_given(
|
||||
ref: dict[str, Any], names: list[str], dist: dict[str, Any] | None
|
||||
) -> dict[str, Any]:
|
||||
"""로직 입력값 다시 채움 — `names` = 그 로직 입력 이름 · 덮은 칸(`ref.own`)은 그대로."""
|
||||
own = set(ref.get("own") or [])
|
||||
got = cycle_time(cycle_inputs(ref, dist))
|
||||
given = dict(ref.get("given") or {})
|
||||
for name, slot in FILL.items():
|
||||
if name in names and name not in own:
|
||||
given[name] = got[slot]
|
||||
return given
|
||||
@@ -0,0 +1,93 @@
|
||||
/* =============================================================================
|
||||
* B08_Quantity_Haul_Cycle.ts
|
||||
* B08 운반 사이클시간(PLAN 253-2) — 건설품셈 8-2-8 덤프트럭 · 거리이정표 거리 → 운반 로직 입력.
|
||||
*
|
||||
* ⚠ 짝 = `B08_Quantity_Haul_Cycle.py`(같은 식) — 한쪽을 고치면 다른 쪽도 ·
|
||||
* 거울 시험 `resources/tester/test_253_2_haul_cycle.py`.
|
||||
* 조작 중은 이 파일(「운반 단가」 모달)이 바로 셈 · B09 셈 때 서버가 짝으로 다시 채움(정본).
|
||||
*
|
||||
* 식 = q = T / rt × f · n = q / (qs × k) · t1 = Cms × n / (60 × Es) ·
|
||||
* t2 = (L1/V1 + L1/V2 + L2/V3 + L2/V4) × 60 · Cm = t1 + … + t6 · 끝수 = 소수 둘째 자리 ·
|
||||
* 속도 0 = 그 항 0 · 로직 입력 채움 = Cm · 사이클시간 = Cm · t2 · 운반거리 = L1 + L2 ·
|
||||
* `own` = 사용자가 덮은 칸(그 값이 이김).
|
||||
* ========================================================================== */
|
||||
|
||||
export const CYCLE_DEFAULTS: Record<string, number> = {
|
||||
L1: 0,
|
||||
L2: 0,
|
||||
V1: 30,
|
||||
V2: 35,
|
||||
V3: 10,
|
||||
V4: 15,
|
||||
T: 15,
|
||||
rt: 1.7,
|
||||
f: 1,
|
||||
qs: 1.72,
|
||||
k: 1,
|
||||
Es: 0.6,
|
||||
Cms: 37.4,
|
||||
t3: 0.8,
|
||||
t4: 0.42,
|
||||
t5: 0.5,
|
||||
t6: 1.5,
|
||||
};
|
||||
/** 로직 입력 이름 → 셈 결과 칸 */
|
||||
export const CYCLE_FILL: Record<string, "cm" | "t2" | "L"> = {
|
||||
Cm: "cm",
|
||||
사이클시간: "cm",
|
||||
t2: "t2",
|
||||
운반거리: "L",
|
||||
};
|
||||
|
||||
export interface CycleResult {
|
||||
q: number;
|
||||
n: number;
|
||||
t1: number;
|
||||
t2: number;
|
||||
cm: number;
|
||||
L: number;
|
||||
}
|
||||
export interface CycleRef {
|
||||
given?: Record<string, unknown> | null;
|
||||
cycle?: Record<string, unknown> | null;
|
||||
own?: string[] | null;
|
||||
}
|
||||
export type Dist = { L1?: number | null; L2?: number | null } | null | undefined;
|
||||
|
||||
/** 소수 둘째 자리 반올림(0.5 올림 · 파이썬 짝 `r2`) */
|
||||
export const r2 = (value: number): number => Math.floor(value * 100 + 0.5) / 100;
|
||||
const div = (a: number, b: number): number => (b ? a / b : 0);
|
||||
const num = (value: unknown): number | null =>
|
||||
typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||
|
||||
/** 사이클 입력(빈 칸 = 기본값 · t1 있으면 그 값) → {q, n, t1, t2, cm, L} */
|
||||
export function cycleTime(inputs: Record<string, unknown> | null | undefined): CycleResult {
|
||||
const v: Record<string, number> = {};
|
||||
for (const [k, d] of Object.entries(CYCLE_DEFAULTS)) v[k] = num(inputs?.[k]) ?? d;
|
||||
const q = r2(div(v.T, v.rt) * v.f);
|
||||
const n = r2(div(q, v.qs * v.k));
|
||||
const t1 = num(inputs?.t1) ?? r2(div(v.Cms * n, 60 * v.Es));
|
||||
const t2 = r2((div(v.L1, v.V1) + div(v.L1, v.V2) + div(v.L2, v.V3) + div(v.L2, v.V4)) * 60);
|
||||
const cm = r2(t1 + t2 + v.t3 + v.t4 + v.t5 + v.t6);
|
||||
return { q, n, t1, t2, cm, L: Math.floor((v.L1 + v.L2) * 1000 + 0.5) / 1000 };
|
||||
}
|
||||
|
||||
/** 연결 `ref.cycle` + 거리이정표 거리 — 덮은 L1 · L2(`ref.own`)는 그대로 */
|
||||
export function cycleInputs(ref: CycleRef | null | undefined, dist: Dist): Record<string, unknown> {
|
||||
const cycle: Record<string, unknown> = { ...(ref?.cycle || {}) };
|
||||
const own = new Set(ref?.own || []);
|
||||
for (const name of ["L1", "L2"] as const)
|
||||
if (!own.has(name) && num(dist?.[name]) !== null) cycle[name] = dist?.[name];
|
||||
if (!own.has("t1")) delete cycle.t1;
|
||||
return cycle;
|
||||
}
|
||||
|
||||
/** 로직 입력값 다시 채움 — names = 그 로직 입력 이름 · 덮은 칸은 그대로 */
|
||||
export function fillGiven(ref: CycleRef, names: string[], dist: Dist): Record<string, unknown> {
|
||||
const own = new Set(ref.own || []);
|
||||
const got = cycleTime(cycleInputs(ref, dist));
|
||||
const given: Record<string, unknown> = { ...(ref.given || {}) };
|
||||
for (const [name, slot] of Object.entries(CYCLE_FILL))
|
||||
if (names.includes(name) && !own.has(name)) given[name] = got[slot];
|
||||
return given;
|
||||
}
|
||||
Reference in New Issue
Block a user