feat(history): align units and history data
This commit is contained in:
+41
-6
@@ -1,4 +1,11 @@
|
||||
import { FLOW_DISPLAY_UNIT, isLpsFlowProperty, toM3h } from "./units";
|
||||
import {
|
||||
FLOW_DISPLAY_UNIT,
|
||||
metricForResultProperty,
|
||||
networkResultUnitsFromOptions,
|
||||
toDisplayValue,
|
||||
toM3h,
|
||||
toModelDisplayValue,
|
||||
} from "./units";
|
||||
|
||||
describe("flow display units", () => {
|
||||
it("uses cubic meters per hour as the flow display unit", () => {
|
||||
@@ -10,10 +17,38 @@ describe("flow display units", () => {
|
||||
expect(toM3h(10, "L/s")).toBe(36);
|
||||
});
|
||||
|
||||
it("recognizes computed properties that arrive from the backend in L/s", () => {
|
||||
expect(isLpsFlowProperty("flow")).toBe(true);
|
||||
expect(isLpsFlowProperty("actual_demand")).toBe(true);
|
||||
expect(isLpsFlowProperty("actualdemand")).toBe(true);
|
||||
expect(isLpsFlowProperty("pressure")).toBe(false);
|
||||
it("centralizes model and SCADA conversion for all UI consumers", () => {
|
||||
expect(toDisplayValue(2, "flow", "MLD")).toBeCloseTo(83.3333333334);
|
||||
expect(toDisplayValue(10, "pressure", "KPA")).toBeCloseTo(1.019716213);
|
||||
expect(toDisplayValue(2, "velocity", "ft/s")).toBeCloseTo(0.6096);
|
||||
expect(toDisplayValue(36, "flow", "m3/h")).toBe(36);
|
||||
});
|
||||
|
||||
it("maps result properties to their physical metrics", () => {
|
||||
expect(metricForResultProperty("flow")).toBe("flow");
|
||||
expect(metricForResultProperty("base_demand")).toBe("flow");
|
||||
expect(metricForResultProperty("actual_demand")).toBe("flow");
|
||||
expect(metricForResultProperty("pressure")).toBe("pressure");
|
||||
expect(metricForResultProperty("velocity")).toBe("velocity");
|
||||
expect(metricForResultProperty("headloss")).toBeNull();
|
||||
});
|
||||
|
||||
it("resolves legacy and v3 model options in one place", () => {
|
||||
expect(
|
||||
networkResultUnitsFromOptions({
|
||||
FLOW_UNITS: "MLD",
|
||||
PRESSURE_UNITS: "METERS",
|
||||
}),
|
||||
).toEqual({ flow: "MLD", pressure: "METERS", velocity: "m/s" });
|
||||
expect(networkResultUnitsFromOptions({ UNITS: "GPM", PRESSURE: "PSI" }))
|
||||
.toEqual({ flow: "GPM", pressure: "PSI", velocity: "ft/s" });
|
||||
});
|
||||
|
||||
it("converts model properties using project result units", () => {
|
||||
const units = { flow: "MLD", pressure: "KPA", velocity: "ft/s" };
|
||||
expect(toModelDisplayValue(2, "flow", units)).toBeCloseTo(83.3333333334);
|
||||
expect(toModelDisplayValue(10, "pressure", units)).toBeCloseTo(1.019716213);
|
||||
expect(toModelDisplayValue(2, "velocity", units)).toBeCloseTo(0.6096);
|
||||
expect(toModelDisplayValue(3, "headloss", units)).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
+124
-9
@@ -1,17 +1,132 @@
|
||||
export const FLOW_DISPLAY_UNIT = "m³/h";
|
||||
const M3H_FACTOR = 3600;
|
||||
const LPS_FLOW_PROPERTIES = new Set(["flow", "actual_demand", "actualdemand"]);
|
||||
export const PRESSURE_DISPLAY_UNIT = "m";
|
||||
export const VELOCITY_DISPLAY_UNIT = "m/s";
|
||||
export type MeasurementMetric = "flow" | "pressure" | "velocity";
|
||||
export interface NetworkResultUnits {
|
||||
flow: string;
|
||||
pressure: string;
|
||||
velocity: string;
|
||||
}
|
||||
|
||||
export const isLpsFlowProperty = (property: string) =>
|
||||
LPS_FLOW_PROPERTIES.has(property);
|
||||
export const DEFAULT_NETWORK_RESULT_UNITS: NetworkResultUnits = {
|
||||
flow: "LPS",
|
||||
pressure: "MTR",
|
||||
velocity: "m/s",
|
||||
};
|
||||
|
||||
const M3H_FACTOR = 3600;
|
||||
const FLOW_PROPERTIES = new Set([
|
||||
"flow",
|
||||
"demand",
|
||||
"base_demand",
|
||||
"actual_demand",
|
||||
"actualdemand",
|
||||
]);
|
||||
const PRESSURE_PROPERTIES = new Set(["pressure"]);
|
||||
const VELOCITY_PROPERTIES = new Set(["velocity"]);
|
||||
const IMPERIAL_FLOW_UNITS = new Set(["CFS", "GPM", "MGD", "IMGD", "AFD"]);
|
||||
|
||||
export const metricForResultProperty = (
|
||||
property: string,
|
||||
): MeasurementMetric | null => {
|
||||
const normalizedProperty = property.trim().toLowerCase();
|
||||
if (FLOW_PROPERTIES.has(normalizedProperty)) return "flow";
|
||||
if (PRESSURE_PROPERTIES.has(normalizedProperty)) return "pressure";
|
||||
if (VELOCITY_PROPERTIES.has(normalizedProperty)) return "velocity";
|
||||
return null;
|
||||
};
|
||||
|
||||
export const networkResultUnitsFromOptions = (
|
||||
options: Record<string, string>,
|
||||
): NetworkResultUnits => {
|
||||
const flow = options.FLOW_UNITS || options.UNITS || "LPS";
|
||||
const pressure = options.PRESSURE_UNITS || options.PRESSURE || "MTR";
|
||||
return {
|
||||
flow,
|
||||
pressure,
|
||||
velocity: IMPERIAL_FLOW_UNITS.has(flow.trim().toUpperCase())
|
||||
? "ft/s"
|
||||
: "m/s",
|
||||
};
|
||||
};
|
||||
|
||||
export const toM3h = (value: number, sourceUnit: string = "m³/s") => {
|
||||
if (!Number.isFinite(value)) return Number.NaN;
|
||||
const normalizedUnit = sourceUnit.trim().toLowerCase();
|
||||
if (normalizedUnit === "m³/h") return value;
|
||||
if (normalizedUnit === "lps" || normalizedUnit === "l/s") return value * 3.6;
|
||||
if (normalizedUnit === "m³/s") return value * M3H_FACTOR;
|
||||
return value * M3H_FACTOR;
|
||||
const normalizedUnit = sourceUnit.trim().toUpperCase().replace("³", "3");
|
||||
const factors: Record<string, number> = {
|
||||
CFS: 101.9406477312,
|
||||
GPM: 0.22712470704,
|
||||
MGD: 157.725491,
|
||||
IMGD: 189.4204167,
|
||||
AFD: 51.39507656,
|
||||
LPS: 3.6,
|
||||
"L/S": 3.6,
|
||||
LPM: 0.06,
|
||||
MLD: 41.6666666667,
|
||||
CMH: 1,
|
||||
"M3/H": 1,
|
||||
CMD: 1 / 24,
|
||||
"M3/D": 1 / 24,
|
||||
"M3/S": M3H_FACTOR,
|
||||
};
|
||||
const factor = factors[normalizedUnit];
|
||||
if (factor === undefined) throw new Error(`不支持的流量单位: ${sourceUnit}`);
|
||||
return value * factor;
|
||||
};
|
||||
|
||||
export const toMeters = (value: number, sourceUnit: string = "m") => {
|
||||
if (!Number.isFinite(value)) return Number.NaN;
|
||||
const normalizedUnit = sourceUnit.trim().toUpperCase();
|
||||
const factors: Record<string, number> = {
|
||||
METERS: 1,
|
||||
METRES: 1,
|
||||
MTR: 1,
|
||||
M: 1,
|
||||
MH2O: 1,
|
||||
"M H2O": 1,
|
||||
KPA: 0.1019716213,
|
||||
PSI: 0.7032496149,
|
||||
};
|
||||
const factor = factors[normalizedUnit];
|
||||
if (factor === undefined) throw new Error(`不支持的压力单位: ${sourceUnit}`);
|
||||
return value * factor;
|
||||
};
|
||||
|
||||
export const toMetersPerSecond = (
|
||||
value: number,
|
||||
sourceUnit: string = "m/s",
|
||||
) => {
|
||||
if (!Number.isFinite(value)) return Number.NaN;
|
||||
const normalizedUnit = sourceUnit.trim().toUpperCase();
|
||||
if (["M/S", "MPS"].includes(normalizedUnit)) return value;
|
||||
if (["FT/S", "FPS"].includes(normalizedUnit)) return value * 0.3048;
|
||||
throw new Error(`不支持的流速单位: ${sourceUnit}`);
|
||||
};
|
||||
|
||||
export const toDisplayValue = (
|
||||
value: number | null | undefined,
|
||||
metric: MeasurementMetric,
|
||||
sourceUnit: string,
|
||||
) => {
|
||||
if (value === null || value === undefined) return null;
|
||||
switch (metric) {
|
||||
case "flow":
|
||||
return toM3h(value, sourceUnit);
|
||||
case "pressure":
|
||||
return toMeters(value, sourceUnit);
|
||||
case "velocity":
|
||||
return toMetersPerSecond(value, sourceUnit);
|
||||
}
|
||||
};
|
||||
|
||||
export const toModelDisplayValue = (
|
||||
value: number,
|
||||
property: string,
|
||||
units: NetworkResultUnits,
|
||||
) => {
|
||||
const metric = metricForResultProperty(property);
|
||||
if (!metric) return value;
|
||||
return toDisplayValue(value, metric, units[metric])!;
|
||||
};
|
||||
|
||||
export const toM3s = (value: number, sourceUnit: string = "m³/h") => {
|
||||
|
||||
Reference in New Issue
Block a user