import type { SteadyDataView } from '@/api/steady/steadyDataView/interface' export const CHECKSQUARE_STAT_TYPES: SteadyDataView.SteadyTrendStatType[] = ['AVG', 'MAX', 'MIN', 'CP95'] export const CHECKSQUARE_HARMONIC_ORDER_MIN = 2 export const CHECKSQUARE_HARMONIC_ORDER_MAX = 50 export const CHECKSQUARE_HARMONIC_ORDERS = Array.from( { length: CHECKSQUARE_HARMONIC_ORDER_MAX - CHECKSQUARE_HARMONIC_ORDER_MIN + 1 }, (_item, index) => index + CHECKSQUARE_HARMONIC_ORDER_MIN ) const CHECKSQUARE_STAT_LABEL_MAP: Record = { AVG: '平均值', MAX: '最大值', MIN: '最小值', CP95: 'CP95' } export const formatChecksquareStatType = (statType: SteadyDataView.SteadyTrendStatType | string) => { return CHECKSQUARE_STAT_LABEL_MAP[statType as SteadyDataView.SteadyTrendStatType] || statType } export const formatBooleanText = (value?: boolean | null) => { if (value === null || value === undefined) return '-' return value ? '是' : '否' } export const formatDataIntegrity = (value?: number | null, text?: string | null) => { if (text) return text const integrityValue = value === null || value === undefined || !Number.isFinite(Number(value)) ? null : Number(value) if (integrityValue === null) return '-' return `${(integrityValue * 100).toFixed(2)}%` } export const findStatSummary = ( item: SteadyDataView.SteadyChecksquareItem, statType: SteadyDataView.SteadyTrendStatType ) => { return item.statSummaries?.find(summary => summary.statType === statType) } export const formatStatMissingRate = ( item: SteadyDataView.SteadyChecksquareItem, statType: SteadyDataView.SteadyTrendStatType ) => { const summary = findStatSummary(item, statType) if (!summary || summary.supported === false) return '-' return formatDataIntegrity(summary.dataIntegrity, summary.dataIntegrityText) } export const resolveChecksquareRowName = (item: SteadyDataView.SteadyChecksquareItem) => { const progressText = getHarmonicProgressText(item) if (progressText) return `${item.indicatorName || item.indicatorCode}(${progressText})` if (!item.harmonicOrder) return item.indicatorName || item.indicatorCode return `${item.harmonicOrder}次` } export const getHarmonicProgressText = (item: SteadyDataView.SteadyChecksquareItem) => { const children = item.children || [] if (!children.length || !children.some(child => child.harmonicOrder)) return '' const totalCount = children.length const resolvedCount = children.filter(child => isResolvedChecksquareItem(child)).length if (resolvedCount >= totalCount) return '' return `已完成 ${resolvedCount}/${totalCount}` } export const collectMissingSegments = (item: SteadyDataView.SteadyChecksquareItem | null) => { if (!item) return [] return (item.statDetails || []).flatMap(detail => (detail.segments || []) .filter(segment => segment.status === 'MISSING') .map(segment => ({ ...segment, statType: detail.statType })) ) } export const hasChecksquareDetail = (item: SteadyDataView.SteadyChecksquareItem) => { return ( Boolean(item.itemId) || Boolean(item.abnormalPointCount) || Boolean(item.harmonicParityAbnormalPointCount) || Boolean(item.missingPointCount) || (item.statDetails || []).some(detail => (detail.segments || []).length) ) } const hasChecksquareHarmonicOrderRange = (indicator: SteadyDataView.SteadyIndicatorNode) => { const start = Number(indicator.harmonicOrderStart) const end = Number(indicator.harmonicOrderEnd) if (!Number.isFinite(start) || !Number.isFinite(end)) return false const rangeStart = Math.min(start, end) const rangeEnd = Math.max(start, end) return rangeStart <= CHECKSQUARE_HARMONIC_ORDER_MAX && rangeEnd >= CHECKSQUARE_HARMONIC_ORDER_MIN } export const isChecksquareHarmonicIndicator = (indicator: SteadyDataView.SteadyIndicatorNode) => { // 只有指标目录明确包含 2-50 次谐波范围时,才预建谐波子行并执行逐次合并。 return hasChecksquareHarmonicOrderRange(indicator) } export const buildPendingChecksquareItem = ( indicator: SteadyDataView.SteadyIndicatorNode ): SteadyDataView.SteadyChecksquareItem => { const indicatorCode = indicator.indicatorCode || indicator.id || indicator.treeKey || indicator.name return { itemKey: `pending|${indicatorCode}`, indicatorCode, indicatorName: indicator.name || indicatorCode, children: isChecksquareHarmonicIndicator(indicator) ? buildPendingChecksquareHarmonicItems(indicator) : undefined, statSummaries: [], statDetails: [] } } export const buildPendingChecksquareHarmonicItems = ( indicator: SteadyDataView.SteadyIndicatorNode ): SteadyDataView.SteadyChecksquareItem[] => { const indicatorCode = indicator.indicatorCode || indicator.id || indicator.treeKey || indicator.name return CHECKSQUARE_HARMONIC_ORDERS.map(harmonicOrder => ({ itemKey: `pending|${indicatorCode}|${harmonicOrder}`, indicatorCode, indicatorName: indicator.name || indicatorCode, harmonicOrder, statSummaries: [], statDetails: [] })) } export const buildPendingChecksquareResult = ( indicators: SteadyDataView.SteadyIndicatorNode[], formState: { timeRange: string[] } ): SteadyDataView.SteadyChecksquareQueryResult => { return { lineId: '', timeStart: formState.timeRange[0] || '', timeEnd: formState.timeRange[1] || '', items: indicators.map(buildPendingChecksquareItem) } } const isResolvedChecksquareItem = (item: SteadyDataView.SteadyChecksquareItem) => { return item.hasData !== undefined || item.expectedPointCount !== undefined || item.actualPointCount !== undefined } const normalizeChecksquareResultItemKey = ( item: SteadyDataView.SteadyChecksquareItem, itemKey: string ): SteadyDataView.SteadyChecksquareItem => ({ ...item, itemKey }) const isValidChecksquareHarmonicOrder = (value: number) => { return Number.isInteger(value) && CHECKSQUARE_HARMONIC_ORDERS.includes(value) } const parseChecksquareHarmonicOrder = (value?: string | number | null) => { const order = Number(value) return isValidChecksquareHarmonicOrder(order) ? order : null } const parseChecksquareHarmonicOrderFromText = (value?: string | null) => { if (!value) return null const orderText = value.match(/(?:^|[^\d])([2-9]|[1-4]\d|50)(?:次|[^\d]|$)/)?.[1] return parseChecksquareHarmonicOrder(orderText) } const resolveChecksquareHarmonicOrder = (item: SteadyDataView.SteadyChecksquareItem) => { return ( parseChecksquareHarmonicOrder(item.harmonicOrder) || parseChecksquareHarmonicOrderFromText(item.itemKey) || parseChecksquareHarmonicOrderFromText(item.indicatorName) || parseChecksquareHarmonicOrderFromText(item.indicatorCode) ) } const sumNumber = ( items: SteadyDataView.SteadyChecksquareItem[], getter: (item: SteadyDataView.SteadyChecksquareItem) => unknown ) => { return items.reduce((total, item) => { const value = Number(getter(item)) return Number.isFinite(value) ? total + value : total }, 0) } const summarizeStatType = ( items: SteadyDataView.SteadyChecksquareItem[], statType: SteadyDataView.SteadyTrendStatType ): SteadyDataView.SteadyChecksquareStatSummary => { const summaries = items .map(item => findStatSummary(item, statType)) .filter((summary): summary is SteadyDataView.SteadyChecksquareStatSummary => Boolean(summary)) const supportedSummaries = summaries.filter(summary => summary.supported !== false) const expectedPointCount = supportedSummaries.reduce((total, summary) => total + (summary.expectedPointCount || 0), 0) const actualPointCount = supportedSummaries.reduce((total, summary) => total + (summary.actualPointCount || 0), 0) const missingPointCount = supportedSummaries.reduce((total, summary) => total + (summary.missingPointCount || 0), 0) return { statType, supported: supportedSummaries.length > 0, hasData: supportedSummaries.length > 0 && supportedSummaries.every(summary => summary.hasData === true), expectedPointCount, actualPointCount, missingPointCount, dataIntegrity: expectedPointCount ? actualPointCount / expectedPointCount : null, dataIntegrityText: expectedPointCount ? undefined : '-' } } export const buildHarmonicParentSummary = ( parentItem: SteadyDataView.SteadyChecksquareItem, children: SteadyDataView.SteadyChecksquareItem[] ): SteadyDataView.SteadyChecksquareItem => { if (!children.length || !children.every(item => isResolvedChecksquareItem(item))) { return { itemKey: parentItem.itemKey, indicatorCode: parentItem.indicatorCode, indicatorName: parentItem.indicatorName, statSummaries: [], statDetails: [], children } } const expectedPointCount = sumNumber(children, item => item.expectedPointCount) const actualPointCount = sumNumber(children, item => item.actualPointCount) const missingPointCount = sumNumber(children, item => item.missingPointCount) const statSummaries = CHECKSQUARE_STAT_TYPES.map(statType => summarizeStatType(children, statType)) return { ...parentItem, hasData: children.every(item => item.hasData === true), expectedPointCount, actualPointCount, missingPointCount, dataIntegrity: expectedPointCount ? actualPointCount / expectedPointCount : null, dataIntegrityText: expectedPointCount ? undefined : '-', statSummaries, statDetails: [], children } } export const mergeChecksquareIndicatorResult = ( currentResult: SteadyDataView.SteadyChecksquareQueryResult | null, indicator: SteadyDataView.SteadyIndicatorNode, indicatorResult: SteadyDataView.SteadyChecksquareQueryResult ): SteadyDataView.SteadyChecksquareQueryResult => { const pendingResult = currentResult || { lineId: indicatorResult.lineId || '', lineName: indicatorResult.lineName, timeStart: indicatorResult.timeStart || '', timeEnd: indicatorResult.timeEnd || '', intervalMinutes: indicatorResult.intervalMinutes, items: [buildPendingChecksquareItem(indicator)] } const indicatorCode = indicator.indicatorCode const resultItems = indicatorResult.items || [] const shouldMergeHarmonicItems = isChecksquareHarmonicIndicator(indicator) const normalItems = shouldMergeHarmonicItems ? resultItems.filter(item => !resolveChecksquareHarmonicOrder(item)) : resultItems const harmonicItems = shouldMergeHarmonicItems ? resultItems.filter(item => resolveChecksquareHarmonicOrder(item)) : [] const currentItem = pendingResult.items.find(item => item.indicatorCode === indicatorCode) const mergedItem = normalItems[0] || currentItem || buildPendingChecksquareItem(indicator) const currentChildren = currentItem?.children || mergedItem.children || [] const mergedChildren = currentChildren.length ? currentChildren.map(child => { const replacement = harmonicItems.find(item => resolveChecksquareHarmonicOrder(item) === child.harmonicOrder) return replacement ? normalizeChecksquareResultItemKey( { ...replacement, harmonicOrder: child.harmonicOrder }, child.itemKey ) : child }) : harmonicItems const replacement = { ...mergedItem, itemKey: currentItem?.itemKey || mergedItem.itemKey, indicatorName: indicator.name || mergedItem.indicatorName || mergedItem.indicatorCode, children: mergedChildren.length ? mergedChildren : undefined } as SteadyDataView.SteadyChecksquareItem const finalReplacement = replacement.children?.length ? buildHarmonicParentSummary(replacement, replacement.children) : replacement return { ...pendingResult, lineId: indicatorResult.lineId || pendingResult.lineId, lineName: indicatorResult.lineName || pendingResult.lineName, timeStart: indicatorResult.timeStart || pendingResult.timeStart, timeEnd: indicatorResult.timeEnd || pendingResult.timeEnd, intervalMinutes: indicatorResult.intervalMinutes ?? pendingResult.intervalMinutes, items: pendingResult.items.map(item => (item.indicatorCode === indicatorCode ? finalReplacement : item)) } }