"use client"; import React, { useMemo, useState } from "react"; import { Box, Button, Card, CardContent, Checkbox, Chip, Collapse, FormControlLabel, IconButton, Tooltip, Typography, } from "@mui/material"; import { InfoOutlined as InfoIcon } from "@mui/icons-material"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs, { Dayjs } from "dayjs"; import "dayjs/locale/zh-cn"; import { useNotification } from "@refinedev/core"; import { api } from "@/lib/api"; import { NETWORK_NAME } from "@config/config"; import { useControllableObjectState } from "@components/olmap/core/useControllableState"; import { SchemeQueryEmptyState } from "@components/olmap/common/PanelEmptyState"; import { BurstDetectionResult, BurstDetectionSchemeDetail, BurstDetectionSchemeRecord, } from "./types"; interface Props { onViewResult: (result: BurstDetectionResult) => void; schemes?: BurstDetectionSchemeRecord[]; onSchemesChange?: (schemes: BurstDetectionSchemeRecord[]) => void; state?: BurstDetectionSchemeQueryState; onStateChange?: (state: BurstDetectionSchemeQueryState) => void; } export interface BurstDetectionSchemeQueryState { queryAll: boolean; queryDate: Dayjs | null; expandedId: number | null; hasQueried: boolean; } export const createBurstDetectionSchemeQueryState = (): BurstDetectionSchemeQueryState => ({ queryAll: true, queryDate: dayjs(), expandedId: null, hasQueried: false, }); const SchemeQuery: React.FC = ({ onViewResult, schemes: externalSchemes, onSchemesChange, state, onStateChange, }) => { const { open } = useNotification(); const [queryState, , setQueryField] = useControllableObjectState( state, onStateChange, createBurstDetectionSchemeQueryState(), ); const { queryAll, queryDate, expandedId, hasQueried } = queryState; const [internalSchemes, setInternalSchemes] = useState([]); const [loading, setLoading] = useState(false); const schemes = externalSchemes !== undefined ? externalSchemes : internalSchemes; const setSchemes = onSchemesChange || setInternalSchemes; const sortedSchemes = useMemo( () => schemes .slice() .sort( (a, b) => dayjs(b.create_time).valueOf() - dayjs(a.create_time).valueOf(), ), [schemes], ); const buildDisplayResult = ( scheme: Pick, detail?: BurstDetectionSchemeDetail, ): BurstDetectionResult | null => { const payload = detail?.result_payload; const summary = detail?.result_summary; const fallbackLatestDay = summary?.latest_day; if (!payload && !summary) return null; return { network: payload?.network ?? detail?.network ?? NETWORK_NAME, sensor_nodes: payload?.sensor_nodes ?? detail?.sensor_nodes ?? [], observed_source: payload?.observed_source ?? detail?.observed_source ?? "stored_scheme", sample_count: payload?.sample_count ?? 0, points_per_day: payload?.points_per_day ?? detail?.algorithm_params?.points_per_day ?? 1440, day_count: payload?.day_count ?? payload?.rows?.length ?? 0, rows: payload?.rows ?? (fallbackLatestDay ? [fallbackLatestDay] : []), summary: payload?.summary ?? (summary ? summary : { burst_detected: false, latest_day: fallbackLatestDay ?? { Day: 0, Score: 0, Prediction: 1, IsBurst: false }, most_anomalous_day: 0, anomaly_days: [], anomaly_day_count: 0, latest_sensor_rankings: [], }), scada_window: payload?.scada_window ?? detail?.scada_window, scheme_name: payload?.scheme_name ?? scheme.scheme_name, username: payload?.username ?? scheme.username, create_time: payload?.create_time ?? scheme.create_time, algorithm_params: payload?.algorithm_params ?? detail?.algorithm_params, requested_target_time: payload?.requested_target_time, target_time: payload?.target_time, reference_window: payload?.reference_window, sampling_interval_minutes: payload?.sampling_interval_minutes, daily_scores: payload?.daily_scores, data_quality: payload?.data_quality, }; }; const handleQuery = async () => { setLoading(true); try { const params: Record = { scheme_type: "burst_detection", }; if (!queryAll && queryDate) { params.query_date = queryDate.startOf("day").toISOString(); } const response = await api.get("/api/v1/schemes", { params }); const nextSchemes = response.data as BurstDetectionSchemeRecord[]; setSchemes(nextSchemes); setQueryField("hasQueried", true); open?.({ type: "success", message: "查询成功", description: `共找到 ${nextSchemes.length} 条侦测记录。`, }); } catch (error: any) { open?.({ type: "error", message: "查询失败", description: error?.response?.data?.detail ?? "无法获取侦测方案列表", }); } finally { setLoading(false); } }; const handleViewSchemeResult = async (schemeName: string) => { try { const response = await api.get( `/api/v1/schemes/${encodeURIComponent(schemeName)}`, { params: { scheme_type: "burst_detection" } }, ); const schemeRecord = response.data as BurstDetectionSchemeRecord & { result_payload?: BurstDetectionResult; }; const normalizedResult = schemeRecord.result_payload ?? buildDisplayResult( { scheme_name: schemeRecord.scheme_name, username: schemeRecord.username, create_time: schemeRecord.create_time, }, schemeRecord.scheme_detail, ); if (!normalizedResult) { throw new Error("方案详情缺少侦测结果数据"); } onViewResult(normalizedResult); open?.({ type: "success", message: "方案加载成功", description: `已加载方案:${schemeName}`, }); } catch (error: any) { open?.({ type: "error", message: "查看详情失败", description: error?.response?.data?.detail ?? error?.message ?? "无法获取方案详情", }); } }; return ( { setQueryField("queryAll", event.target.checked); setQueryField("hasQueried", false); }} /> } label={查询全部} className="m-0" /> { setQueryField("queryDate", value); setQueryField("hasQueried", false); }} disabled={queryAll} format="YYYY-MM-DD" slotProps={{ textField: { size: "small", sx: { width: 180 } } }} /> {sortedSchemes.length === 0 ? ( ) : ( 共 {sortedSchemes.length} 条记录 {sortedSchemes.map((scheme) => { const summary = scheme.scheme_detail?.result_summary; const payload = scheme.scheme_detail?.result_payload; const isBurst = payload?.summary?.burst_detected ?? summary?.burst_detected ?? false; const anomalyDayCount = payload?.summary?.anomaly_day_count ?? summary?.anomaly_day_count ?? 0; const mostAnomalousDay = payload?.summary?.most_anomalous_day ?? summary?.most_anomalous_day ?? "-"; const sensorCount = payload?.sensor_nodes?.length ?? scheme.scheme_detail?.sensor_nodes?.length ?? 0; const targetTime = payload?.target_time ?? payload?.summary?.target_time; const targetScore = payload?.summary?.target_score ?? summary?.target_score; const targetRank = payload?.summary?.target_rank ?? summary?.target_rank; return ( {scheme.scheme_name} 创建时间:{dayjs(scheme.create_time).format("YYYY-MM-DD HH:mm")} setQueryField( "expandedId", expandedId === scheme.scheme_id ? null : scheme.scheme_id, ) } color="primary" className="p-1" > {targetTime ? "目标时刻" : "异常天数"} {targetTime ? dayjs(targetTime).format("MM-DD HH:mm") : anomalyDayCount} {targetTime ? "目标分数" : "最异常日"} {targetTime ? typeof targetScore === "number" ? targetScore.toFixed(4) : "-" : isBurst ? typeof mostAnomalousDay === "number" ? `第 ${mostAnomalousDay} 天` : mostAnomalousDay : "无"} {targetTime ? "异常排名" : "测点数"} {targetTime && targetRank ? `${targetRank} / ${payload?.day_count ?? 15}` : sensorCount} 数据来源: {(() => { const ds = payload?.data_source; const os = payload?.observed_source ?? scheme.scheme_detail?.observed_source; if (ds === "simulation") return "模拟数据"; if (ds === "monitoring") return "监测数据"; if (os === "simulation_scheme_timerange") return "模拟数据"; if (os === "backend_timerange") return "监测数据"; if (os === "latest_monitoring") return "最新监测数据"; if (os === "historical_monitoring") return "历史监测回放"; return os || "-"; })()} 时间窗口: {payload?.scada_window?.start ? `${dayjs(payload.scada_window.start).format("MM-DD HH:mm")} ~ ${dayjs( payload.scada_window.end, ).format("MM-DD HH:mm")}` : "-"} 侦测口径: 频域系数:{scheme.scheme_detail?.algorithm_params?.mu ?? payload?.algorithm_params?.mu ?? "-"} ,每日采样: {scheme.scheme_detail?.algorithm_params?.points_per_day ?? payload?.algorithm_params?.points_per_day ?? "-"} 点,阈值: {payload?.summary?.score_threshold ?? "-"} ); })} )} ); }; export default SchemeQuery;