Files
Aislo/common_util/common_util_line_stack_unit.ts
T
eomsangdonandClaude Opus 5.5 3a8ba840f4 fix(845-2): sub2 단위 받은 뒤 표시 셋 — 「줄 따름」 = 고른 표 줄 단위 · 단위 TeX 글 안전(%) · 노임 원/일 × 인 = 원/인
- 「줄 따름」 표 값 = 지금 조건으로 고른 표 줄의 「단위」 칸(rowUnit · 화면 캐시 표 읽기) — 오른쪽 · 왼쪽 값 칸 · 수량 꼬리표
- 식 줄 결과 단위 = TeX 글 안전(「%」 가 주석으로 먹혀 「가산 = 0 %」 가 안 보이던 것)
- 단가 분모가 수량 단위와 같은 뜻이면 수량 글로(원/일 × 인 → 원/인 · ㎏ ↔ kg · ℓ ↔ L) · kWh = ㎾h · 기계 칸 전력 = ㎾h/hr(845-1 문서 단위와 같게)
- 왼쪽 만약이 수 하나로 접힌 식 줄 = 단위 남김
- 전수(845-1 단위 채움 뒤) 표시 탓 0 · 시험 test_845_2_unit_show 줄 따름 · % · 원/인 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 23:01:15 +09:00

124 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* common_util_line_stack_unit.ts
* 단가산출 단위 그리는 규칙 한 벌(PLAN 845-2 · 요구 880) — 왼쪽 편집 · 오른쪽 산출근거 · 기계 제어 구역 · 기계 보기가 같이 씀
* ㉠ 값 줄 = 값 뒤 단위(`varUnit`) ㉡ 식 줄 = 결과 값 뒤만(식 가운데 숫자 뒤 없음)
* ㉢ 비목 · 금액 줄 = 「단가 원/단위 × (수량 식) 단위 = 금액 원」(`moneyUnits` — 수량이 식이면 괄호로 식 전체에 단위) ·
* 소계 · 합계 = 원(머리 [원/㎥] = 결과 하나당)
* ㉣ 무차원 = 빈 칸 ㉤ 율 = %(「%(인력품 대비)」 등 설명 괄호 뗌) ㉥ 왼쪽 값 칸 = 칸 바로 뒤 한 자리
* 메타 단위(「구분 따름」 · 「줄 따름」 · 「수(…)」 · 「원문 단위 없음」) = 단위 아님 → 안 그림 · 「10^-7」 = 10⁻⁷ · kWh = ㎾h
* 「줄 따름」 표 값 = 지금 조건으로 고른 표 줄의 「단위」 칸(`rowUnit`) · 단가 분모 = 같은 뜻이면 수량 단위 글(원/일 × 인 → 원/인)
* 기계 칸 연결(요소 `EQ:` 칸) 변수 = 변수 단위가 빈 칸이면 기계 표 칸 단위(`MACHINE_UNIT` — 기계 표 빌더 · 내 기계와 같은 글)
* 그리기만 — 자료 · 식 · 금액 불변 · 화면 없음(시험이 Node 로 돌림)
* ========================================================================== */
import type { DocVar, MoneyLine, Node } from "./common_util_formula_doc";
import { evalNode, tryParse, type Values } from "./common_util_formula_doc_view";
/** 기계 표 칸 단위(`build_기계_건설품셈.py` UNIT · `M02_MyMachine.py` UNITS · 845-1 문서 단위와 같은 글) */
export const MACHINE_UNIT: Record<string, string> = {
가격: "천원",
내용시간: "시간",
연간표준가동시간: "시간",
상각비계수: "10^-7/시간",
정비비계수: "10^-7/시간",
관리비계수: "10^-7/시간",
손료계수: "10^-7/시간",
주연료: "ℓ/hr",
전력: "㎾h/hr",
잡재료율: "%",
잡품: "%",
조종원: "인/일",
인력: "인/일",
노무비: "원/시간",
재료비: "원/시간",
경비: "원/시간",
};
/** 금액 · 소계 · 합계 단위 */
export const AMOUNT_UNIT = "원";
/** 자료 단위 글 → 화면 단위(메타 = 빈 글) */
export function unitText(u: unknown): string {
const s = String(u ?? "").trim();
if (!s || /따름|원문 단위 없음/.test(s) || /^수(\(| 또는|$)/.test(s)) return "";
return s
.replace(/^%\(.*\)$/, "%")
.replace(/10\^-7/g, "10⁻⁷")
.replace(/kWh/g, "㎾h");
}
/** 같은 뜻 다른 표기(단가 단위 분모 ↔ 수량 단위) — 노임 원/일 ↔ 인(인·일) · ㎏ ↔ kg · ℓ ↔ L */
const SAME: [string, string][] = [["일", "인"], ["㎏", "kg"], ["ℓ", "L"]]; // prettier-ignore
const same = (a: string, b: string): boolean =>
a === b || SAME.some(([x, y]) => (a === x && b === y) || (a === y && b === x));
/** 기계 칸 이름의 화면 단위 */
export const machineUnit = (col: string): string => unitText(MACHINE_UNIT[col]);
/** 변수 화면 단위 — 변수 단위 › 기계 칸 연결이면 기계 표 칸 단위 */
export function varUnit(v: DocVar | undefined): string {
const own = unitText(v?.단위);
if (own) return own;
const k = v?.연결;
return k?.갈래 === "요소" && /^EQ:/.test(String(k.키 ?? "")) && k.칸 ? machineUnit(k.칸) : "";
}
/** 표 한 칸 읽기(화면 캐시 `links.table`) */
export type TableLook = (p: Record<string, unknown>) => unknown;
/** 「줄 따름」(줄마다 다른 단위) 표 값 = 지금 조건으로 고른 표 줄의 「단위」 칸 · 못 읽으면 빈 글 */
export function rowUnit(v: DocVar | undefined, vals: Values, look?: TableLook): string {
const k = v?.연결;
if (!look || !k?.표 || !/따름/.test(String(v?.단위 ?? ""))) return "";
const 조건: Record<string, unknown> = {};
for (const [c, t] of Object.entries(k.조건 ?? {})) {
const n = tryParse(t);
if (n) 조건[c] = evalNode(n, vals);
}
try {
return unitText(look({ 표: k.표, 칸: "단위", 조건 }));
} catch {
return "";
}
}
/** 값 줄 단위 한 벌 — 변수 단위 › 기계 칸 › 고른 표 줄 단위 */
export const shownUnitOf = (v: DocVar | undefined, vals: Values, look?: TableLook): string =>
varUnit(v) || rowUnit(v, vals, look);
const HIDE = new Set(["반올림", "올림", "버림"]);
/** 한 덩이(기호 · 수 · 칸 — 감싸개 안 한 덩이 포함) */
export function isAtom(n: Node | null | undefined): boolean {
if (!n) return false;
if (n[0] === "call" && HIDE.has(n[1]) && n[2].length >= 1 && n[2].length <= 2)
return isAtom(n[2][0]);
return n[0] === "var" || n[0] === "num" || n[0] === "field" || n[0] === "str";
}
export interface MoneyUnits {
/** 단가 뒤(원/시간 · 원/인 …) — 단가가 한 덩이이고 수량이 있을 때만 */
price: string;
/** 수량 식 뒤 */
qty: string;
/** 수량 식을 괄호로 감쌈(식이고 단위가 있을 때) */
wrap: boolean;
}
/** 금액 줄 단위 자리(㉢) — `priceVar` = 단가 기호 변수 · `sub` = 묶음 · 단가산출 줄이 부르는 문서 */
export function moneyUnits(
line: MoneyLine,
price: Node | null,
qty: Node | null,
priceVar?: DocVar,
sub?: { 결과단위?: string } | null,
): MoneyUnits {
const has = line.수량 !== undefined && line.수량 !== null && line.수량 !== "";
const q = has ? unitText(line.단위) : "";
const wrap = !!q && !!qty && !isAtom(qty);
const own = [sub?.결과단위, priceVar?.단위].map(unitText).find((u) => u.startsWith("원"));
// 단가 분모가 수량 단위와 같은 뜻이면 수량 단위 글로(「원/일 × 인」 대신 「원/인 × 인」)
const mine = own && q && same(own.slice(2), q) ? `${AMOUNT_UNIT}/${q}` : own;
const p = has && isAtom(price) ? (mine ?? (q ? `${AMOUNT_UNIT}/${q}` : "")) : "";
return { price: p, qty: q, wrap };
}