feat(846-2): 기계 줄 시간당 작업량 화면 — 산출근거 「시간당 작업량」 줄 먼저 · 비목 ÷ 시간당 작업량 · 제어 구역 일당 / 시간당 식 고르기 · 변수 칸 · 기계 보기 작업량 식
- 산출근거: 일당 「시간당 작업량 = 시공량 560 ㎥/일 ÷ (1 대 × 8 시간) = 70 ㎥/시간」 · 시간당 식 = q · K · f · E · cm 값 줄 + 한 층 분수 Q 식 · 비목 줄 「59,020 원/시간 ÷ 70 ㎥/시간 = 843.1 원」(금액 그대로) - 제어 구역: 「시간당 작업량」 묶음 맨 위 — 방식 고르기(식 없는 기계 = 잠김 + 까닭) · 시간당 = 작업량 틀 변수 칸(고침 · 원래 값) · 덮음 = 기계덮음.<기계>.작업량 - 기계 보기: 왼쪽 작업량 카드(기계 정본 변수) · 오른쪽 풀이 · 서버가 작업량 틀 연결을 실음(M02_MyMachine_Sheet._work) - 시험: test_846_2_work_view 새로 · test_845_2 GC000088 비목 줄 기대값을 새 꼴로 고침(846-2 표시 변경) · 헬퍼 가짜 DOM 이 진짜 DOM 처럼 요소를 옮김(같은 요소 두 줄 재사용 흠을 잡게) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/* =============================================================================
|
||||
* common_util_line_stack_work.ts
|
||||
* 시간당 작업량 그리기 자료(PLAN 846-2 · 요구 881 · 882 · 883) — 산출근거 · 기계 제어 구역 · 기계 보기가 같이 씀
|
||||
* 셈 답 기계 줄 `줄[i].작업량`(846-1 계산기 `common_util_formula_doc_work`) → 그림 자료
|
||||
* ㉠ 일당 풀이 = 율 식 「D ÷ (8 × 대수…)」 → 시공량 D · 나눔 항(8 = 시간 · 그 밖 = 대수)
|
||||
* ㉡ 비목 줄 = 수량 식의 작업량 자리를 바꾼 글(`swap` — 「나머지 ÷ Q_NEW」) → 나머지(1 이면 없음) ·
|
||||
* 꼴이 다르면 null(지금 꼴 그대로)
|
||||
* ㉢ 단위 — 작업량 「㎥/hr」 → 「㎥/시간」 · 시공량 = 「㎥/일」
|
||||
* ㉣ 시간당 식 풀이 = 작업량 틀 결과 식이 읽는 기호(q · K · f · E · cm — 품셈 기호 그대로)
|
||||
* 그리기만 — 식 · 값 · 금액 불변 · 화면 없음(시험이 Node 로 돌림)
|
||||
* ========================================================================== */
|
||||
|
||||
import { names, type FormulaDoc, type Node } from "./common_util_formula_doc";
|
||||
import type { DocAnswer } from "./common_util_formula_doc_calc";
|
||||
import {
|
||||
Q_NEW,
|
||||
specsOf,
|
||||
swap,
|
||||
WORK,
|
||||
type WorkInfo,
|
||||
type WorkSpec,
|
||||
} from "./common_util_formula_doc_work";
|
||||
import { nodeOf } from "./common_util_line_stack";
|
||||
import { unitText } from "./common_util_line_stack_unit";
|
||||
|
||||
/** 셈 답 i 번째 금액 줄의 작업량(없으면 null) */
|
||||
export function workAt(a: DocAnswer | null | undefined, i: number): WorkInfo | null {
|
||||
const got = (a?.줄?.[i] as Record<string, unknown> | undefined)?.[WORK];
|
||||
return got && typeof got === "object" ? (got as WorkInfo) : null;
|
||||
}
|
||||
|
||||
/** 그 기계의 첫 작업량 줄(같은 기계 두 줄 = 덮음 하나) */
|
||||
export function workOfMachine(a: DocAnswer | null | undefined, machine: string): WorkInfo | null {
|
||||
for (let i = 0; i < (a?.줄?.length ?? 0); i++) {
|
||||
const w = workAt(a, i);
|
||||
if (w && w.기계 === machine) return w;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 기계 작업량 줄(기계 값 칸 `작업량` — 캐시 `links.element`) · 없음 · 못 읽음 = 빈 목록 */
|
||||
export function machineWork(
|
||||
element: (p: Record<string, unknown>) => unknown,
|
||||
machine: string,
|
||||
): WorkSpec[] {
|
||||
try {
|
||||
return specsOf(element({ 키: `EQ:${machine}`, 칸: WORK }));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** 작업량 단위 → 화면(「/hr」 = 「/시간」) */
|
||||
export const workUnit = (u: unknown): string =>
|
||||
unitText(u).replace(/\/\s*(hr|h|Hr|HR|시간)$/, "/시간");
|
||||
|
||||
/** 시공량(하루) 단위 — 작업량 단위 분자 + 「/일」 */
|
||||
export const dayUnit = (u: unknown): string => {
|
||||
const top = workUnit(u).replace(/\/시간$/, "");
|
||||
return top ? `${top}/일` : "";
|
||||
};
|
||||
|
||||
const factors = (n: Node): Node[] =>
|
||||
n[0] === "bin" && n[1] === "*" ? [...factors(n[2]), ...factors(n[3])] : [n];
|
||||
|
||||
export interface DailyParts {
|
||||
/** 하루 시공량 식 */
|
||||
day: Node;
|
||||
/** 나눔 항 — 8 = 시간 · 그 밖 = 대수 */
|
||||
per: { node: Node; hours: boolean }[];
|
||||
}
|
||||
|
||||
/** ㉠ 일당 율 식 「D ÷ (8 × …)」 → 시공량 · 나눔 항 · 꼴이 다르면 null */
|
||||
export function dailyParts(rate: unknown): DailyParts | null {
|
||||
const n = nodeOf(rate);
|
||||
if (!n || n[0] !== "bin" || n[1] !== "/") return null;
|
||||
const per = factors(n[3]).map((x) => ({ node: x, hours: x[0] === "num" && x[1] === 8 }));
|
||||
if (!per.some((p) => p.hours)) return null;
|
||||
// 대수 먼저 · 8 시간 끝(「1 대 × 8 시간」 — 경쟁사 「대수 × 8hr」 꼴)
|
||||
return { day: n[2], per: [...per.filter((p) => !p.hours), ...per.filter((p) => p.hours)] };
|
||||
}
|
||||
|
||||
/** ㉡ 수량 식 → 「나머지 ÷ Q」 의 나머지(없으면 rest null) · 꼴이 다르면 null */
|
||||
export function restOfQty(doc: FormulaDoc, qty: unknown): { rest: Node | null } | null {
|
||||
let text: string;
|
||||
try {
|
||||
text = swap(doc, qty);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const n = nodeOf(text);
|
||||
if (!n || n[0] !== "bin" || n[1] !== "/" || n[3][0] !== "var" || n[3][1] !== Q_NEW) return null;
|
||||
const rest = n[2];
|
||||
return { rest: rest[0] === "num" && rest[1] === 1 ? null : rest };
|
||||
}
|
||||
|
||||
/** ㉣ 작업량 틀 결과 식 · 그 식이 읽는 기호(차례 그대로 · 겹침 뺌) */
|
||||
export function hourlyParts(tpl: FormulaDoc): { id: string; node: Node; uses: string[] } | null {
|
||||
const id = String(tpl.결과 ?? "");
|
||||
const line = (tpl.식 ?? []).find((l) => String(l.기호 ?? "") === id);
|
||||
const node = line ? nodeOf(line.식) : null;
|
||||
return node ? { id, node, uses: [...new Set(names(node))] } : null;
|
||||
}
|
||||
Reference in New Issue
Block a user