/** * filekorolheader: AI决策看板状态管理 - 决策数据状态管理核心 * 功能:决策数据管理、统计计算、状态更新、本地存储同步 * 路径:/ai-crop-model/support/dashboard/components/aiDecisionDashboardReducer * 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理模式 */ import { safeLocalStorage } from '@/utils/storage'; // 决策类型 export type DecisionType = 'irrigation' | 'fertilizer' | 'pesticide' | 'harvest' | 'soil' | 'weather'; // 决策状态 export type DecisionStatus = 'generated' | 'executing' | 'completed' | 'expired'; // 决策优先级 export type DecisionPriority = 'urgent' | 'high' | 'medium' | 'low'; // 决策记录 export interface DecisionRecord { id: string; type: DecisionType; title: string; description: string; status: DecisionStatus; priority: DecisionPriority; fieldId: string; fieldName: string; fieldArea: number; cropType: string; confidence: number; createdAt: string; updatedAt: string; dueDate: string; location: { lat: number; lng: number; }; modelVersion?: string; ruleCount?: number; executedAt?: string; executedBy?: string; completedAt?: string; } // 地块决策信息 export interface FieldDecisionInfo { fieldId: string; fieldName: string; location: { lat: number; lng: number; }; area: number; cropType: string; decisions: DecisionRecord[]; urgentCount: number; generatedCount: number; executingCount: number; completedCount: number; } // 统计数据 export interface DecisionStats { total: number; generated: number; executing: number; completed: number; urgent: number; avgConfidence: number; } // 趋势数据 export interface TrendData { date: string; generated: number; completed: number; } // 状态接口 export interface AIDecisionDashboardState { decisions: DecisionRecord[]; fieldDecisions: FieldDecisionInfo[]; stats: DecisionStats; trendData: TrendData[]; latestDecisions: DecisionRecord[]; lastUpdated: string; } // Action类型 export type AIDecisionDashboardAction = | { type: 'SET_DECISIONS'; payload: DecisionRecord[] } | { type: 'ADD_DECISION'; payload: DecisionRecord } | { type: 'UPDATE_DECISION'; payload: { id: string; updates: Partial } } | { type: 'DELETE_DECISION'; payload: string } | { type: 'REFRESH_DATA' } | { type: 'LOAD_FROM_STORAGE'; payload: Partial }; // 初始决策数据 const initialDecisions: DecisionRecord[] = [ { id: 'dec_001', type: 'irrigation', title: '1号大棚番茄开花期灌溉', description: '土壤湿度35%,低于最佳湿度,建议立即灌溉120升', status: 'generated', priority: 'urgent', fieldId: 'field_001', fieldName: '1号大棚', fieldArea: 2.5, cropType: '番茄', confidence: 0.89, createdAt: '2024-10-23 14:30', updatedAt: '2024-10-23 14:30', dueDate: '2024-10-23 18:00', location: { lat: 39.9042, lng: 116.4074 }, modelVersion: 'v2.1.3', ruleCount: 2, }, { id: 'dec_002', type: 'pesticide', title: '2号大棚早疫病防治', description: '检测到早疫病轻度感染,建议使用生物防治剂', status: 'executing', priority: 'high', fieldId: 'field_002', fieldName: '2号大棚', fieldArea: 3.0, cropType: '番茄', confidence: 0.87, createdAt: '2024-10-23 10:15', updatedAt: '2024-10-23 11:00', dueDate: '2024-10-23 17:00', executedAt: '2024-10-23 11:00', executedBy: '王五', location: { lat: 39.9142, lng: 116.4174 }, modelVersion: 'v3.2.1', ruleCount: 1, }, { id: 'dec_003', type: 'fertilizer', title: '3号地块小麦追肥', description: '结果期营养需求增加,建议施用复合肥', status: 'executing', priority: 'medium', fieldId: 'field_003', fieldName: '3号地块', fieldArea: 5.0, cropType: '小麦', confidence: 0.92, createdAt: '2024-10-23 09:30', updatedAt: '2024-10-23 10:00', dueDate: '2024-10-24 12:00', executedAt: '2024-10-23 10:00', executedBy: '李四', location: { lat: 39.8942, lng: 116.3974 }, modelVersion: 'v2.0.5', ruleCount: 3, }, { id: 'dec_004', type: 'soil', title: '4号地块土壤改良', description: 'pH值偏低,建议施用石灰调节土壤酸碱度', status: 'completed', priority: 'low', fieldId: 'field_004', fieldName: '4号地块', fieldArea: 4.2, cropType: '玉米', confidence: 0.85, createdAt: '2024-10-22 15:00', updatedAt: '2024-10-22 16:30', dueDate: '2024-10-25 12:00', executedAt: '2024-10-22 15:30', executedBy: '张三', completedAt: '2024-10-22 16:30', location: { lat: 39.8842, lng: 116.3874 }, modelVersion: 'v1.8.2', ruleCount: 1, }, { id: 'dec_005', type: 'weather', title: '5号大棚温度调控', description: '预计晚间温度降至12℃,建议提前加温', status: 'generated', priority: 'high', fieldId: 'field_005', fieldName: '5号大棚', fieldArea: 2.8, cropType: '黄瓜', confidence: 0.91, createdAt: '2024-10-23 16:00', updatedAt: '2024-10-23 16:00', dueDate: '2024-10-23 20:00', location: { lat: 39.9242, lng: 116.4274 }, modelVersion: 'v2.3.1', ruleCount: 2, }, { id: 'dec_006', type: 'harvest', title: '6号地块水稻收获', description: '水稻已达成熟期,建议3天内完成收获', status: 'generated', priority: 'urgent', fieldId: 'field_006', fieldName: '6号地块', fieldArea: 8.0, cropType: '水稻', confidence: 0.94, createdAt: '2024-10-23 08:00', updatedAt: '2024-10-23 08:00', dueDate: '2024-10-26 18:00', location: { lat: 39.9342, lng: 116.3874 }, modelVersion: 'v2.5.0', ruleCount: 3, }, { id: 'dec_007', type: 'irrigation', title: '7号大棚茄子补水', description: '连续3天无降雨,土壤湿度偏低', status: 'completed', priority: 'medium', fieldId: 'field_007', fieldName: '7号大棚', fieldArea: 2.2, cropType: '茄子', confidence: 0.86, createdAt: '2024-10-22 18:00', updatedAt: '2024-10-23 09:00', dueDate: '2024-10-23 12:00', executedAt: '2024-10-22 19:00', executedBy: '赵六', completedAt: '2024-10-23 09:00', location: { lat: 39.8742, lng: 116.4174 }, modelVersion: 'v2.1.3', ruleCount: 2, }, ]; // 初始趋势数据 const initialTrendData: TrendData[] = [ { date: '10-17', generated: 8, completed: 5 }, { date: '10-18', generated: 12, completed: 8 }, { date: '10-19', generated: 10, completed: 9 }, { date: '10-20', generated: 15, completed: 11 }, { date: '10-21', generated: 13, completed: 10 }, { date: '10-22', generated: 11, completed: 9 }, { date: '10-23', generated: 14, completed: 7 }, ]; // 计算统计数据 const calculateStats = (decisions: DecisionRecord[]): DecisionStats => { return { total: decisions.length, generated: decisions.filter(d => d.status === 'generated').length, executing: decisions.filter(d => d.status === 'executing').length, completed: decisions.filter(d => d.status === 'completed').length, urgent: decisions.filter(d => d.priority === 'urgent').length, avgConfidence: decisions.reduce((sum, d) => sum + d.confidence, 0) / decisions.length, }; }; // 计算地块决策信息 const calculateFieldDecisions = (decisions: DecisionRecord[]): FieldDecisionInfo[] => { const fieldDecisionMap = new Map(); decisions.forEach(decision => { if (!fieldDecisionMap.has(decision.fieldId)) { fieldDecisionMap.set(decision.fieldId, { fieldId: decision.fieldId, fieldName: decision.fieldName, location: decision.location, area: decision.fieldArea, cropType: decision.cropType, decisions: [], urgentCount: 0, generatedCount: 0, executingCount: 0, completedCount: 0, }); } const fieldInfo = fieldDecisionMap.get(decision.fieldId)!; fieldInfo.decisions.push(decision); if (decision.priority === 'urgent') fieldInfo.urgentCount++; if (decision.status === 'generated') fieldInfo.generatedCount++; if (decision.status === 'executing') fieldInfo.executingCount++; if (decision.status === 'completed') fieldInfo.completedCount++; }); return Array.from(fieldDecisionMap.values()); }; // 计算最新决策 const calculateLatestDecisions = (decisions: DecisionRecord[]): DecisionRecord[] => { return [...decisions] .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, 5); }; // 保存到本地存储 const saveToStorage = (state: AIDecisionDashboardState) => { safeLocalStorage.setItem('ai-decision-dashboard', JSON.stringify({ decisions: state.decisions, lastUpdated: state.lastUpdated, })); }; // 从本地存储加载 const loadFromStorage = () => { const stored = safeLocalStorage.getItem('ai-decision-dashboard'); if (stored) { try { const data = JSON.parse(stored); return { decisions: data.decisions || initialDecisions, lastUpdated: data.lastUpdated || new Date().toISOString(), }; } catch (error) { console.warn('Failed to parse stored data:', error); } } return null; }; // 计算派生状态 const calculateDerivedState = (decisions: DecisionRecord[]) => { const stats = calculateStats(decisions); const fieldDecisions = calculateFieldDecisions(decisions); const latestDecisions = calculateLatestDecisions(decisions); return { stats, fieldDecisions, latestDecisions, trendData: initialTrendData, }; }; // 初始状态 export const initialState: AIDecisionDashboardState = (() => { const stored = loadFromStorage(); const decisions = stored?.decisions || initialDecisions; const derivedState = calculateDerivedState(decisions); return { decisions, ...derivedState, lastUpdated: stored?.lastUpdated || new Date().toISOString(), }; })(); // Reducer export function AIDecisionDashboardReducer( state: AIDecisionDashboardState, action: AIDecisionDashboardAction ): AIDecisionDashboardState { switch (action.type) { case 'SET_DECISIONS': { const derivedState = calculateDerivedState(action.payload); const newState = { ...state, decisions: action.payload, ...derivedState, lastUpdated: new Date().toISOString(), }; saveToStorage(newState); return newState; } case 'ADD_DECISION': { const newDecisions = [...state.decisions, action.payload]; const derivedState = calculateDerivedState(newDecisions); const newState = { ...state, decisions: newDecisions, ...derivedState, lastUpdated: new Date().toISOString(), }; saveToStorage(newState); return newState; } case 'UPDATE_DECISION': { const newDecisions = state.decisions.map(decision => decision.id === action.payload.id ? { ...decision, ...action.payload.updates } : decision ); const derivedState = calculateDerivedState(newDecisions); const newState = { ...state, decisions: newDecisions, ...derivedState, lastUpdated: new Date().toISOString(), }; saveToStorage(newState); return newState; } case 'DELETE_DECISION': { const newDecisions = state.decisions.filter(decision => decision.id !== action.payload); const derivedState = calculateDerivedState(newDecisions); const newState = { ...state, decisions: newDecisions, ...derivedState, lastUpdated: new Date().toISOString(), }; saveToStorage(newState); return newState; } case 'REFRESH_DATA': { const derivedState = calculateDerivedState(state.decisions); const newState = { ...state, ...derivedState, lastUpdated: new Date().toISOString(), }; saveToStorage(newState); return newState; } case 'LOAD_FROM_STORAGE': { const decisions = action.payload.decisions || state.decisions; const derivedState = calculateDerivedState(decisions); return { ...state, decisions, ...derivedState, lastUpdated: action.payload.lastUpdated || state.lastUpdated, }; } default: return state; } }