Files
Aislo/A00_Common/master_unit_swap.ts
T
eomsangdonandClaude Opus 5.5 c201a1269d feat(728-1): SK 단위 바꿈 환산 순수 함수 · 파이썬 · TS 짝 · 거울 시험
- master_unit.swap · split — 같은 성격 = 배 자동 · 부피 ↔ 무게 = 단위중량 · 그 밖 = 사용자 계수 · 계수 없으면 배 없음 + 까닭 글
- A00_Common/master_unit_swap.ts swapUnit — 같은 답(단위 표 · 별칭 · 분모 읽기 옮김)
- 지킴 시험 test_728_1_unit_swap.py(거울 20 사례)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:08:02 +09:00

133 lines
4.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.
/* =============================================================================
* master_unit_swap.ts
* SK 단위 바꿈 환산(728-1) — 짝(①): `resources/master_data/scripts/master_unit.py` `swap` · `split`.
* 거울 시험 `resources/tester/test_728_1_unit_swap.py`. 단위 표 · 별칭 · 분모 읽기는 그 모듈 그대로 옮김
* (한 벌 — 고칠 때 두 파일 함께). 화면 · fetch · 저장 없음.
* ========================================================================== */
const ALIAS: Record<string, string> = {
ea: "개",
ton: "톤",
t: "톤",
l: "ℓ",
리터: "ℓ",
set: "조",
세트: "조",
};
const UNITS: Record<string, Record<string, number>> = {
무게: { g: 1, kg: 1000, 톤: 1000000 },
길이: { mm: 1, cm: 10, m: 1000, km: 1000000 },
넓이: { cm2: 1, m2: 10000 },
부피: { cm3: 1, ℓ: 1000, m3: 1000000 },
개수: { 개: 1, 본: 1, 매: 1 },
};
const SCALE: Record<string, [string, number]> = {};
for (const [kind, group] of Object.entries(UNITS)) {
for (const [u, n] of Object.entries(group)) SCALE[u] = [kind, n];
}
const WEIGHT_TABLE = "CC000337";
const PER = /원\s*\/\s*(\d[\d,]*\.?\d*|\.\d+)?\s*([^\s·(\d,.][^\s·(]*)/;
export type UnitNeed = null | "단위중량" | "사용자";
export interface UnitSwap {
가: [number, string];
나: [number, string];
성격: [string | null, string | null];
/** have 1 개가 want 몇 개 — 1 단위 당 값은 ÷배 · 수량은 ×배 · 계수 없으면 null(셈 막음) */
배: number | null;
계수: UnitNeed;
근거: string;
}
export function plainUnit(unit: unknown): string {
const s = String(unit ?? "")
.normalize("NFKC")
.toLowerCase()
.replace(/\s+/g, "")
.replace(/ℓ/g, "l");
return ALIAS[s] ?? s;
}
export function unitKind(unit: unknown): string | null {
return SCALE[plainUnit(unit)]?.[0] ?? null;
}
function sameKind(a: unknown, b: unknown): boolean {
const k = unitKind(a);
return plainUnit(a) === plainUnit(b) || (k !== null && k === unitKind(b));
}
function weighs(a: unknown, b: unknown): boolean {
const ks = new Set([unitKind(a), unitKind(b)]);
return ks.size === 2 && ks.has("무게") && ks.has("부피");
}
/** `have` 1 단위가 `want` 몇 단위 — 부피 ↔ 무게는 단위중량(㎏/㎥) 있을 때만 · 못 하면 null */
function factor(have: string, want: string, density?: number | null): number | null {
const a = plainUnit(have);
const b = plainUnit(want);
if (a === b) return 1;
if (sameKind(a, b)) return SCALE[a][1] / SCALE[b][1];
if (density && weighs(a, b)) {
const gramsPerCm3 = density / 1000;
const base = SCALE[a][1] * (unitKind(a) === "부피" ? gramsPerCm3 : 1 / gramsPerCm3);
return base / SCALE[b][1];
}
return null;
}
/** 열두 자리로 다듬음 — 파이썬 Decimal 셈과 같은 값(뜬수 끝 흔들림 없앰) */
function tidy(x: number): number {
return Number(x.toPrecision(12));
}
function num(x: number): string {
return Number.isInteger(x) ? String(x) : String(x).slice(0, 10);
}
/** 기준단위 앞 수 떼기 — 「100본」 → [100, 본] · 「㎥」 → [1, ㎥] */
export function splitUnit(unit: unknown): [number, string] {
const text = String(unit ?? "").trim();
const got = text ? PER.exec(`원/${text}`) : null;
if (!got) return [1, text];
return [got[1] ? Number(got[1].replace(/,/g, "")) : 1, got[2]];
}
/** SK 단위 `have` → `want` 환산 — 같은 성격 = 배 자동 · 부피 ↔ 무게 = 단위중량 · 그 밖 = 사용자 계수
* (have 앞 수 뗀 1 단위 = want 앞 수 뗀 몇 단위 · 0 보다 커야). */
export function swapUnit(
have: string,
want: string,
density?: number | null,
coef?: number | null,
): UnitSwap {
const [na, ua] = splitUnit(have);
const [nb, ub] = splitUnit(want);
const base = { 가: [na, ua], 나: [nb, ub], 성격: [unitKind(ua), unitKind(ub)] } as Pick<
UnitSwap,
"가" | "나" | "성격"
>;
if (!ua || !ub) return { ...base, 배: 1, 계수: null, 근거: "" };
const need: UnitNeed = sameKind(ua, ub) ? null : weighs(ua, ub) ? "단위중량" : "사용자";
const f = need === "사용자" ? (coef && coef > 0 ? coef : null) : factor(ua, ub, density);
if (f === null) {
const why =
need === "단위중량"
? `단위중량(${WEIGHT_TABLE}) 골라야 셈`
: "성격 다름 · 환산 계수 넣어야 셈";
return { ...base, 배: null, 계수: need, 근거: `${have} ↔ ${want} — ${why}` };
}
const ratio = tidy((na * f) / nb);
const how =
need === "단위중량"
? ` · 단위중량 ${density}㎏/㎥`
: need === "사용자"
? ` · 계수 1 ${ua} = ${coef} ${ub}`
: "";
let step = "";
if (ratio === 1) step = plainUnit(have) === plainUnit(want) ? "" : `${have} = ${want}`;
else
step = `${have} 당 → ${want} 당 ${ratio > 1 ? `÷${num(ratio)}` : `×${num(tidy(1 / ratio))}`}`;
return { ...base, 배: ratio, 계수: need, 근거: step + how };
}