feat(history): align units and history data

This commit is contained in:
2026-09-14 12:30:57 +08:00
parent 35b6819459
commit 08ddb4453d
21 changed files with 1849 additions and 3618 deletions
@@ -76,7 +76,7 @@ describe("buildSceneFrame", () => {
});
expect(frame.payload?.links["P-1"]).toEqual({
velocity: 0.82,
flow: -18.4,
flow: -66.24,
direction: -1,
status: "open",
});
@@ -196,7 +196,12 @@ describe("buildSceneFrame", () => {
const devices = await fetchPressureDevices(new AbortController().signal);
expect(devices).toEqual([
{ device_id: "S-1", device_type: "pressure", node_id: "J-1" },
{
device_id: "S-1",
device_type: "pressure",
node_id: "J-1",
measurement_unit: "m",
},
]);
expect(mockApiFetch).toHaveBeenCalledWith(
expect.stringContaining("/api/v1/scada-devices?limit=1000&offset=0"),
@@ -228,6 +233,10 @@ describe("buildSceneFrame", () => {
},
],
})
.mockResolvedValueOnce({
ok: true,
json: async () => ({ FLOW_UNITS: "LPS", PRESSURE_UNITS: "METERS" }),
})
.mockResolvedValueOnce({
ok: false,
status: 503,
+44 -11
View File
@@ -1,5 +1,11 @@
import { apiFetch } from "@/lib/apiFetch";
import { fetchNetworkResultUnits } from "@/hooks/useNetworkResultUnits";
import { config } from "@config/config";
import {
DEFAULT_NETWORK_RESULT_UNITS,
type NetworkResultUnits,
toDisplayValue,
} from "@/utils/units";
export const ZJB_PROJECT_CODE = "zjb";
export const ZJB_SCENE_MODEL_ID = "zjb-water-network-v23";
@@ -32,8 +38,8 @@ export type SceneResultsPayload = {
modelId: string;
units: {
velocity: "m/s";
pressure: "mH2O";
flow: "L/s";
pressure: "m";
flow: "m³/h";
};
timestamp: string;
nodes: Record<string, SceneNodeResult>;
@@ -61,6 +67,7 @@ export type PressureDevice = {
device_id: string;
device_type: string;
node_id: string;
measurement_unit?: string;
};
type RawPressureDevice = Omit<PressureDevice, "node_id"> & {
@@ -192,6 +199,7 @@ export const buildSceneFrame = ({
linkRows,
pressureDevices,
scadaRows,
simulationUnits = DEFAULT_NETWORK_RESULT_UNITS,
}: {
queryTime: Date;
model: SceneModelIndex;
@@ -199,6 +207,7 @@ export const buildSceneFrame = ({
linkRows: RealtimeLinkRow[];
pressureDevices: PressureDevice[];
scadaRows: ScadaReadingRow[];
simulationUnits?: NetworkResultUnits;
}): SceneFrame => {
const selectedTime = queryTime.toISOString();
const frameTime = resolveCommonFrameTime(queryTime, nodeRows, linkRows);
@@ -223,7 +232,14 @@ export const buildSceneFrame = ({
return;
}
if (isFiniteNumber(row.pressure)) {
nodes[id] = { pressure: row.pressure, source: "simulation" };
nodes[id] = {
pressure: toDisplayValue(
row.pressure,
"pressure",
simulationUnits.pressure,
)!,
source: "simulation",
};
}
});
@@ -234,9 +250,15 @@ export const buildSceneFrame = ({
return;
}
const result: SceneLinkResult = {};
if (isFiniteNumber(row.velocity)) result.velocity = row.velocity;
if (isFiniteNumber(row.velocity)) {
result.velocity = toDisplayValue(
row.velocity,
"velocity",
simulationUnits.velocity,
)!;
}
if (isFiniteNumber(row.flow)) {
result.flow = row.flow;
result.flow = toDisplayValue(row.flow, "flow", simulationUnits.flow)!;
result.direction = Math.sign(row.flow) as -1 | 0 | 1;
}
const status = normalizeLinkStatus(row.status);
@@ -260,7 +282,11 @@ export const buildSceneFrame = ({
if (!isFiniteNumber(value)) return;
const simulationPressure = nodes[device.node_id]?.pressure;
nodes[device.node_id] = {
pressure: value,
pressure: toDisplayValue(
value,
"pressure",
device.measurement_unit || "m",
)!,
source: "scada",
deviceId: device.device_id,
...(simulationPressure === undefined ? {} : { simulationPressure }),
@@ -274,7 +300,7 @@ export const buildSceneFrame = ({
resultTime,
payload: {
modelId: model.modelId,
units: { velocity: "m/s", pressure: "mH2O", flow: "L/s" },
units: { velocity: "m/s", pressure: "m", flow: "m³/h" },
timestamp: resultTime,
nodes,
links,
@@ -301,18 +327,23 @@ const readJson = async <T>(url: string, signal: AbortSignal): Promise<T> => {
};
export const fetchPressureDevices = async (signal: AbortSignal) => {
const page = await readJson<Page<RawPressureDevice>>(
const response = await readJson<Page<RawPressureDevice> | RawPressureDevice[]>(
`${config.BACKEND_URL}/api/v1/scada-devices?limit=1000&offset=0`,
signal,
);
return page.items
const items = Array.isArray(response) ? response : response.items;
return items
.filter(
(device): device is PressureDevice =>
device.device_type?.trim().toLowerCase() === "pressure" &&
typeof device.node_id === "string" &&
device.node_id.trim().length > 0,
)
.map((device) => ({ ...device, node_id: device.node_id.trim() }));
.map((device) => ({
...device,
node_id: device.node_id.trim(),
measurement_unit: device.measurement_unit?.trim() || "m",
}));
};
export const fetchSceneFrame = async ({
@@ -337,7 +368,7 @@ export const fetchSceneFrame = async ({
pressureDevices.map((device) => device.device_id).join(","),
);
const [nodeRows, linkRows] = await Promise.all([
const [nodeRows, linkRows, networkOptions] = await Promise.all([
readJson<RealtimeNodeRow[]>(
`${config.BACKEND_URL}/api/v1/timeseries/realtime/nodes?${range}`,
signal,
@@ -346,6 +377,7 @@ export const fetchSceneFrame = async ({
`${config.BACKEND_URL}/api/v1/timeseries/realtime/links?${range}`,
signal,
),
fetchNetworkResultUnits(ZJB_PROJECT_CODE, signal),
]);
let scadaRows: ScadaReadingRow[] = [];
@@ -370,6 +402,7 @@ export const fetchSceneFrame = async ({
linkRows: Array.isArray(linkRows) ? linkRows : [],
pressureDevices,
scadaRows,
simulationUnits: networkOptions,
});
return { ...frame, warnings };
};