fix(burst-location): clarify simulation baseline requirement

This commit is contained in:
2026-07-30 20:55:01 +08:00
parent 782363cfb6
commit 81cc5dcae1
3 changed files with 114 additions and 9 deletions
@@ -28,6 +28,7 @@ import { NETWORK_NAME, config } from "@config/config";
import { useControllableObjectState } from "@components/olmap/core/useControllableState"; import { useControllableObjectState } from "@components/olmap/core/useControllableState";
import { FLOW_DISPLAY_UNIT, toM3s } from "@utils/units"; import { FLOW_DISPLAY_UNIT, toM3s } from "@utils/units";
import { BurstLocationResult } from "./types"; import { BurstLocationResult } from "./types";
import { getBurstLocationErrorNotice } from "./burstLocationError";
interface Props { interface Props {
onResult: (result: BurstLocationResult) => void; onResult: (result: BurstLocationResult) => void;
@@ -262,11 +263,12 @@ const AnalysisParameters: React.FC<Props> = ({
description: `定位到管段: ${(response.data as BurstLocationResult).located_pipe}`, description: `定位到管段: ${(response.data as BurstLocationResult).located_pipe}`,
}); });
} catch (error: any) { } catch (error: any) {
const notice = getBurstLocationErrorNotice(error);
open?.({ open?.({
key: "burst-location-analysis-error", key: "burst-location-analysis-error",
type: "error", type: "error",
message: "提交分析失败", message: notice.message,
description: error?.response?.data?.detail ?? error?.message ?? "请求失败", description: notice.description,
}); });
} finally { } finally {
setRunning(false); setRunning(false);
@@ -303,22 +305,30 @@ const AnalysisParameters: React.FC<Props> = ({
</Select> </Select>
</FormControl> </FormControl>
<Alert <Alert
severity="info" severity={isSimulationMode ? "warning" : "info"}
variant="outlined" variant="outlined"
sx={{ sx={{
mt: 1, mt: 1,
alignItems: "flex-start", alignItems: "flex-start",
borderColor: "info.light", borderColor: isSimulationMode ? "warning.light" : "info.light",
backgroundColor: "#eff6ff", backgroundColor: isSimulationMode ? "#fff8e1" : "#eff6ff",
"& .MuiAlert-message": { width: "100%" }, "& .MuiAlert-message": { width: "100%" },
}} }}
> >
<Typography variant="caption" className="block font-semibold text-blue-900"> <Typography
variant="caption"
className="block font-semibold"
sx={{ color: isSimulationMode ? "warning.dark" : "info.dark" }}
>
{isSimulationMode ? "模拟方案分析前置条件" : "数据选择规则"}
</Typography> </Typography>
<Typography variant="caption" className="block leading-5 text-blue-800"> <Typography
variant="caption"
className="block leading-5"
sx={{ color: isSimulationMode ? "warning.dark" : "info.dark" }}
>
{isSimulationMode {isSimulationMode
? "当前为模拟方案:爆管数据取所选方案模拟结果,正常数据取实时模拟结果,两者使用同一爆管时间窗。" ? "爆管数据取所选方案,正常数据取同一时间窗的实时模拟结果。所选时间窗必须已有实时模拟正常基线,否则无法分析;系统不会自动补数据。"
: "当前为监测数据:爆管数据取所选监测时间窗,正常数据默认取前一天同一时段的监测数据。"} : "当前为监测数据:爆管数据取所选监测时间窗,正常数据默认取前一天同一时段的监测数据。"}
</Typography> </Typography>
</Alert> </Alert>
@@ -0,0 +1,45 @@
import { getBurstLocationErrorNotice } from "./burstLocationError";
describe("getBurstLocationErrorNotice", () => {
it("explains a missing realtime normal-pressure baseline", () => {
const notice = getBurstLocationErrorNotice({
response: {
data: {
detail: "正常压力数据 在时间窗内无有效模拟数据: 10186",
},
},
});
expect(notice.message).toBe("缺少正常模拟基线");
expect(notice.description).toContain("实时模拟正常压力数据");
expect(notice.description).toContain("10186");
expect(notice.description).toContain("系统不会自动补数据");
});
it("distinguishes incomplete burst-scheme data", () => {
const notice = getBurstLocationErrorNotice({
response: {
data: {
detail: "爆管压力数据 在时间窗内无有效模拟数据: 10186",
},
},
});
expect(notice).toEqual({
message: "所选方案数据不完整",
description:
"所选方案时间窗内没有可用的爆管压力数据,缺失点位:10186。请选择数据完整的模拟方案。",
});
});
it("preserves unrelated backend details", () => {
expect(
getBurstLocationErrorNotice({
response: { data: { detail: "分析参数不合法" } },
}),
).toEqual({
message: "提交分析失败",
description: "分析参数不合法",
});
});
});
@@ -0,0 +1,50 @@
export interface BurstLocationErrorNotice {
message: string;
description: string;
}
const DATA_GAP_PATTERN =
/^(爆管压力数据|正常压力数据|爆管流量数据|正常流量数据) 在时间窗内无有效模拟数据: (.+)$/;
const extractErrorDetail = (error: unknown): string => {
const candidate = error as {
message?: string;
response?: { data?: { detail?: unknown } };
};
const detail = candidate?.response?.data?.detail;
if (typeof detail === "string" && detail.trim()) {
return detail.trim();
}
if (typeof candidate?.message === "string" && candidate.message.trim()) {
return candidate.message.trim();
}
return "请求失败";
};
export const getBurstLocationErrorNotice = (
error: unknown,
): BurstLocationErrorNotice => {
const detail = extractErrorDetail(error);
const match = detail.match(DATA_GAP_PATTERN);
if (!match) {
return {
message: "提交分析失败",
description: detail,
};
}
const [, dataLabel, missingIds] = match;
if (dataLabel.startsWith("正常")) {
return {
message: "缺少正常模拟基线",
description: `所选方案时间窗内没有可用的实时模拟${dataLabel},缺失点位:${missingIds}。请选择已有正常基线覆盖的方案或时间窗,系统不会自动补数据。`,
};
}
return {
message: "所选方案数据不完整",
description: `所选方案时间窗内没有可用的${dataLabel},缺失点位:${missingIds}。请选择数据完整的模拟方案。`,
};
};