/* ============================================================================= * 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 = { 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 = { 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 | null; cycle?: Record | 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 | null | undefined): CycleResult { const v: Record = {}; 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 { const cycle: Record = { ...(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 { const own = new Set(ref.own || []); const got = cycleTime(cycleInputs(ref, dist)); const given: Record = { ...(ref.given || {}) }; for (const [name, slot] of Object.entries(CYCLE_FILL)) if (names.includes(name) && !own.has(name)) given[name] = got[slot]; return given; }