NeoScan_Physician/app/modules/AIPrediction/redux/aiPredictionSlice.ts
2025-08-20 20:42:33 +05:30

622 lines
18 KiB
TypeScript

/*
* File: aiPredictionSlice.ts
* Description: Redux slice for AI Prediction state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import {
AIPredictionCase,
AIPredictionState,
AIPredictionStats,
AIPredictionAPIResponse
} from '../types';
import { aiPredictionAPI } from '../services';
// ============================================================================
// ASYNC THUNKS
// ============================================================================
/**
* Fetch AI Predictions Async Thunk
*
* Purpose: Fetch AI prediction results from API
*
* @param token - Authentication token
* @param params - Optional query parameters for filtering
* @returns Promise with AI prediction data or error
*/
export const fetchAIPredictions = createAsyncThunk(
'aiPrediction/fetchAIPredictions',
async (payload: {
token: string;
params?: {
page?: number;
limit?: number;
urgency?: string;
severity?: string;
category?: string;
search?: string;
}
}, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getAllPredictions(payload.token, payload.params);
console.log('AI predictions response:', response);
if (response.ok && response.data && response.data.success) {
// Add additional metadata to each case for UI purposes
const enhancedCases = response.data.data.map((aiCase: AIPredictionCase) => ({
...aiCase,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
review_status: 'pending' as const,
priority: getPriorityFromPrediction(aiCase.prediction)
}));
console.log('Enhanced AI prediction cases:', enhancedCases);
return {
cases: enhancedCases as AIPredictionCase[],
total: response.data.total || enhancedCases.length,
page: response.data.page || 1,
limit: response.data.limit || 20
};
} else {
// Fallback to mock data for development
const mockData = generateMockAIPredictions();
return {
cases: mockData,
total: mockData.length,
page: 1,
limit: 20
};
}
} catch (error: any) {
console.error('Fetch AI predictions error:', error);
return rejectWithValue(error.message || 'Failed to fetch AI predictions.');
}
}
);
/**
* Fetch AI Prediction Case Details Async Thunk
*
* Purpose: Fetch detailed information for a specific AI prediction case
*
* @param caseId - AI prediction case ID
* @param token - Authentication token
* @returns Promise with case details or error
*/
export const fetchAIPredictionDetails = createAsyncThunk(
'aiPrediction/fetchAIPredictionDetails',
async (payload: { caseId: string; token: string }, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getCaseDetails(payload.caseId, payload.token);
if (response.ok && response.data) {
return response.data as AIPredictionCase;
} else {
// Fallback to mock data
const mockCase = generateMockAIPredictions().find(c => c.patid === payload.caseId);
if (mockCase) {
return mockCase;
}
throw new Error('Case not found');
}
} catch (error: any) {
console.error('Fetch AI prediction details error:', error);
return rejectWithValue(error.message || 'Failed to fetch case details.');
}
}
);
/**
* Update Case Review Async Thunk
*
* Purpose: Update review status of an AI prediction case
*
* @param caseId - Case ID to update
* @param reviewData - Review data
* @param token - Authentication token
* @returns Promise with updated case or error
*/
export const updateCaseReview = createAsyncThunk(
'aiPrediction/updateCaseReview',
async (payload: {
caseId: string;
reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
priority?: 'critical' | 'high' | 'medium' | 'low';
};
token: string;
}, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.updateCaseReview(
payload.caseId,
payload.reviewData,
payload.token
);
if (response.ok && response.data) {
return {
caseId: payload.caseId,
...payload.reviewData,
updated_at: new Date().toISOString()
};
} else {
throw new Error('Failed to update case review');
}
} catch (error: any) {
console.error('Update case review error:', error);
return rejectWithValue(error.message || 'Failed to update case review.');
}
}
);
/**
* Fetch AI Prediction Statistics Async Thunk
*
* Purpose: Fetch statistics for AI predictions dashboard
*
* @param token - Authentication token
* @param timeRange - Time range filter
* @returns Promise with statistics data or error
*/
export const fetchAIPredictionStats = createAsyncThunk(
'aiPrediction/fetchAIPredictionStats',
async (payload: { token: string; timeRange?: 'today' | 'week' | 'month' }, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getPredictionStats(payload.token, payload.timeRange);
if (response.ok && response.data) {
return response.data as AIPredictionStats;
} else {
// Fallback to mock stats
return generateMockStats();
}
} catch (error: any) {
console.error('Fetch AI prediction stats error:', error);
return rejectWithValue(error.message || 'Failed to fetch statistics.');
}
}
);
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Priority from AI Prediction
*
* Purpose: Determine case priority based on AI prediction results
*/
function getPriorityFromPrediction(prediction: any): 'critical' | 'high' | 'medium' | 'low' {
if (prediction.clinical_urgency === 'emergency' || prediction.primary_severity === 'high') {
return 'critical';
}
if (prediction.clinical_urgency === 'urgent' || prediction.primary_severity === 'medium') {
return 'high';
}
if (prediction.clinical_urgency === 'moderate' || prediction.primary_severity === 'low') {
return 'medium';
}
return 'low';
}
/**
* Generate Mock AI Predictions
*
* Purpose: Generate mock data for development and testing
*/
function generateMockAIPredictions(): AIPredictionCase[] {
return [
{
patid: "demogw05-08-2017",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "midline shift",
finding_type: "pathology",
clinical_urgency: "urgent",
confidence_score: 0.996,
finding_category: "abnormal",
primary_severity: "high",
anatomical_location: "brain"
},
created_at: "2024-01-15T10:30:00Z",
updated_at: "2024-01-15T10:30:00Z",
review_status: "pending",
priority: "critical"
},
{
patid: "demo-patient-002",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "normal brain",
finding_type: "no_pathology",
clinical_urgency: "routine",
confidence_score: 0.892,
finding_category: "normal",
primary_severity: "none",
anatomical_location: "not_applicable"
},
created_at: "2024-01-15T09:15:00Z",
updated_at: "2024-01-15T09:15:00Z",
review_status: "reviewed",
priority: "low"
},
{
patid: "demo-patient-003",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "hemorrhage",
finding_type: "pathology",
clinical_urgency: "emergency",
confidence_score: 0.945,
finding_category: "critical",
primary_severity: "high",
anatomical_location: "temporal lobe"
},
created_at: "2024-01-15T11:45:00Z",
updated_at: "2024-01-15T11:45:00Z",
review_status: "confirmed",
priority: "critical"
}
];
}
/**
* Generate Mock Statistics
*
* Purpose: Generate mock statistics for development
*/
function generateMockStats(): AIPredictionStats {
return {
totalCases: 156,
criticalCases: 23,
urgentCases: 45,
reviewedCases: 89,
pendingCases: 67,
averageConfidence: 0.887,
todaysCases: 12,
weeklyTrend: 15.4
};
}
// ============================================================================
// INITIAL STATE
// ============================================================================
/**
* Initial AI Prediction State
*
* Purpose: Define the initial state for AI predictions
*
* Features:
* - Prediction cases list and management
* - Current case details
* - Loading states for async operations
* - Error handling and messages
* - Search and filtering
* - Pagination support
* - Cache management
*/
const initialState: AIPredictionState = {
// Prediction data
predictionCases: [],
currentCase: null,
// Loading states
isLoading: false,
isRefreshing: false,
isLoadingCaseDetails: false,
// Error handling
error: null,
// Search and filtering
searchQuery: '',
selectedUrgencyFilter: 'all',
selectedSeverityFilter: 'all',
selectedCategoryFilter: 'all',
sortBy: 'date',
sortOrder: 'desc',
// Pagination
currentPage: 1,
itemsPerPage: 20,
totalItems: 0,
// Cache management
lastUpdated: null,
cacheExpiry: null,
// UI state
showFilters: false,
selectedCaseIds: [],
};
// ============================================================================
// AI PREDICTION SLICE
// ============================================================================
/**
* AI Prediction Slice
*
* Purpose: Redux slice for AI prediction state management
*
* Features:
* - AI prediction data management
* - Search and filtering
* - Case review management
* - Pagination
* - Caching
* - Error handling
* - Loading states
*/
const aiPredictionSlice = createSlice({
name: 'aiPrediction',
initialState,
reducers: {
/**
* Clear Error Action
*
* Purpose: Clear AI prediction errors
*/
clearError: (state) => {
state.error = null;
},
/**
* Set Search Query Action
*
* Purpose: Set search query for AI predictions
*/
setSearchQuery: (state, action: PayloadAction<string>) => {
state.searchQuery = action.payload;
state.currentPage = 1; // Reset to first page when searching
},
/**
* Set Urgency Filter Action
*
* Purpose: Set urgency filter for AI predictions
*/
setUrgencyFilter: (state, action: PayloadAction<AIPredictionState['selectedUrgencyFilter']>) => {
state.selectedUrgencyFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Severity Filter Action
*
* Purpose: Set severity filter for AI predictions
*/
setSeverityFilter: (state, action: PayloadAction<AIPredictionState['selectedSeverityFilter']>) => {
state.selectedSeverityFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Category Filter Action
*
* Purpose: Set category filter for AI predictions
*/
setCategoryFilter: (state, action: PayloadAction<AIPredictionState['selectedCategoryFilter']>) => {
state.selectedCategoryFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Sort Action
*
* Purpose: Set sort options for AI predictions
*/
setSort: (state, action: PayloadAction<{ by: 'date' | 'urgency' | 'confidence' | 'severity'; order: 'asc' | 'desc' }>) => {
state.sortBy = action.payload.by;
state.sortOrder = action.payload.order;
},
/**
* Set Current Page Action
*
* Purpose: Set current page for pagination
*/
setCurrentPage: (state, action: PayloadAction<number>) => {
state.currentPage = action.payload;
},
/**
* Set Items Per Page Action
*
* Purpose: Set items per page for pagination
*/
setItemsPerPage: (state, action: PayloadAction<number>) => {
state.itemsPerPage = action.payload;
state.currentPage = 1; // Reset to first page when changing items per page
},
/**
* Set Current Case Action
*
* Purpose: Set the currently selected AI prediction case
*/
setCurrentCase: (state, action: PayloadAction<AIPredictionCase | null>) => {
state.currentCase = action.payload;
},
/**
* Update Case in List Action
*
* Purpose: Update an AI prediction case in the list
*/
updateCaseInList: (state, action: PayloadAction<AIPredictionCase>) => {
const index = state.predictionCases.findIndex(case_ => case_.patid === action.payload.patid);
if (index !== -1) {
state.predictionCases[index] = action.payload;
}
// Update current case if it's the same case
if (state.currentCase && state.currentCase.patid === action.payload.patid) {
state.currentCase = action.payload;
}
},
/**
* Toggle Show Filters Action
*
* Purpose: Toggle the display of filter options
*/
toggleShowFilters: (state) => {
state.showFilters = !state.showFilters;
},
/**
* Clear All Filters Action
*
* Purpose: Reset all filters to default values
*/
clearAllFilters: (state) => {
state.searchQuery = '';
state.selectedUrgencyFilter = 'all';
state.selectedSeverityFilter = 'all';
state.selectedCategoryFilter = 'all';
state.currentPage = 1;
},
/**
* Select Case Action
*
* Purpose: Add/remove case from selected cases
*/
toggleCaseSelection: (state, action: PayloadAction<string>) => {
const caseId = action.payload;
const index = state.selectedCaseIds.indexOf(caseId);
if (index === -1) {
state.selectedCaseIds.push(caseId);
} else {
state.selectedCaseIds.splice(index, 1);
}
},
/**
* Clear Selected Cases Action
*
* Purpose: Clear all selected cases
*/
clearSelectedCases: (state) => {
state.selectedCaseIds = [];
},
/**
* Clear Cache Action
*
* Purpose: Clear AI prediction data cache
*/
clearCache: (state) => {
state.predictionCases = [];
state.currentCase = null;
state.lastUpdated = null;
state.cacheExpiry = null;
},
},
extraReducers: (builder) => {
// Fetch AI Predictions
builder
.addCase(fetchAIPredictions.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchAIPredictions.fulfilled, (state, action) => {
state.isLoading = false;
state.predictionCases = action.payload.cases;
state.totalItems = action.payload.total;
state.lastUpdated = new Date().toLocaleString();
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000).toLocaleString(); // 5 minutes
state.error = null;
})
.addCase(fetchAIPredictions.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
// Fetch AI Prediction Details
builder
.addCase(fetchAIPredictionDetails.pending, (state) => {
state.isLoadingCaseDetails = true;
state.error = null;
})
.addCase(fetchAIPredictionDetails.fulfilled, (state, action) => {
state.isLoadingCaseDetails = false;
state.currentCase = action.payload;
state.error = null;
})
.addCase(fetchAIPredictionDetails.rejected, (state, action) => {
state.isLoadingCaseDetails = false;
state.error = action.payload as string;
});
// Update Case Review
builder
.addCase(updateCaseReview.fulfilled, (state, action) => {
// Update case in list
const index = state.predictionCases.findIndex(case_ => case_.patid === action.payload.caseId);
if (index !== -1) {
state.predictionCases[index] = {
...state.predictionCases[index],
review_status: action.payload.review_status,
reviewed_by: action.payload.reviewed_by,
priority: action.payload.priority,
updated_at: action.payload.updated_at
};
}
// Update current case if it's the same case
if (state.currentCase && state.currentCase.patid === action.payload.caseId) {
state.currentCase = {
...state.currentCase,
review_status: action.payload.review_status,
reviewed_by: action.payload.reviewed_by,
priority: action.payload.priority,
updated_at: action.payload.updated_at
};
}
})
.addCase(updateCaseReview.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
// ============================================================================
// EXPORTS
// ============================================================================
export const {
clearError,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
setSort,
setCurrentPage,
setItemsPerPage,
setCurrentCase,
updateCaseInList,
toggleShowFilters,
clearAllFilters,
toggleCaseSelection,
clearSelectedCases,
clearCache,
} = aiPredictionSlice.actions;
export default aiPredictionSlice.reducer;
/*
* End of File: aiPredictionSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/