71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { UNIT_INFO, type Unit, type UnitKind } from "@app/shared-types";
|
||
|
||
export interface IngredientUnitInfo {
|
||
/** g per ml – krävs för volym → massa. */
|
||
densityGPerMl?: number | null;
|
||
/** g per styck – krävs för antal → massa. */
|
||
gramsPerPiece?: number | null;
|
||
}
|
||
|
||
export function unitKind(unit: Unit): UnitKind {
|
||
return UNIT_INFO[unit].kind;
|
||
}
|
||
|
||
/** Konvertera till basenhet inom samma slag (g, ml eller st). */
|
||
export function toBase(quantity: number, unit: Unit): { kind: UnitKind; amount: number } {
|
||
const info = UNIT_INFO[unit];
|
||
return { kind: info.kind, amount: quantity * info.toBase };
|
||
}
|
||
|
||
/**
|
||
* Konvertera valfri mängd till gram. Returnerar null när konvertering inte är
|
||
* möjlig utan mer information – hellre ärlig osäkerhet än gissning (spec §61.4).
|
||
*/
|
||
export function toGrams(
|
||
quantity: number,
|
||
unit: Unit,
|
||
info: IngredientUnitInfo = {},
|
||
): number | null {
|
||
const base = toBase(quantity, unit);
|
||
switch (base.kind) {
|
||
case "mass":
|
||
return base.amount;
|
||
case "volume": {
|
||
const density = info.densityGPerMl;
|
||
if (density == null || density <= 0) return null;
|
||
return base.amount * density;
|
||
}
|
||
case "count": {
|
||
const perPiece = info.gramsPerPiece;
|
||
if (perPiece == null || perPiece <= 0) return null;
|
||
return base.amount * perPiece;
|
||
}
|
||
}
|
||
}
|
||
|
||
/** Konvertera en mängd mellan enheter (samma slag, eller via densitet/styckvikt). */
|
||
export function convert(
|
||
quantity: number,
|
||
from: Unit,
|
||
to: Unit,
|
||
info: IngredientUnitInfo = {},
|
||
): number | null {
|
||
const fromInfo = UNIT_INFO[from];
|
||
const toInfo = UNIT_INFO[to];
|
||
if (fromInfo.kind === toInfo.kind) {
|
||
return (quantity * fromInfo.toBase) / toInfo.toBase;
|
||
}
|
||
// Olika slag: gå via gram.
|
||
const grams = toGrams(quantity, from, info);
|
||
if (grams == null) return null;
|
||
if (toInfo.kind === "mass") return grams / toInfo.toBase;
|
||
if (toInfo.kind === "volume") {
|
||
const density = info.densityGPerMl;
|
||
if (density == null || density <= 0) return null;
|
||
return grams / density / toInfo.toBase;
|
||
}
|
||
const perPiece = info.gramsPerPiece;
|
||
if (perPiece == null || perPiece <= 0) return null;
|
||
return grams / perPiece / toInfo.toBase;
|
||
}
|