/* * File: aiPredictionSelectors.ts * Description: Redux selectors for AI Prediction state * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import { createSelector } from '@reduxjs/toolkit'; import type { RootState } from '../../../store'; import { AIPredictionCase } from '../types'; // ============================================================================ // BASE SELECTORS // ============================================================================ /** * Select AI Prediction State * * Purpose: Get the entire AI prediction state */ export const selectAIPredictionState = (state: RootState) => state.aiPrediction; /** * Select Prediction Cases * * Purpose: Get all AI prediction cases */ export const selectPredictionCases = (state: RootState) => state.aiPrediction.predictionCases; /** * Select Current Case * * Purpose: Get the currently selected AI prediction case */ export const selectCurrentCase = (state: RootState) => state.aiPrediction.currentCase; /** * Select Loading State * * Purpose: Get the loading state for AI predictions */ export const selectIsLoading = (state: RootState) => state.aiPrediction.isLoading; /** * Select Loading Case Details State * * Purpose: Get the loading state for case details */ export const selectIsLoadingCaseDetails = (state: RootState) => state.aiPrediction.isLoadingCaseDetails; /** * Select Error * * Purpose: Get the current error message */ export const selectError = (state: RootState) => state.aiPrediction.error; /** * Select Search Query * * Purpose: Get the current search query */ export const selectSearchQuery = (state: RootState) => state.aiPrediction.searchQuery; /** * Select Filter States * * Purpose: Get all filter states */ export const selectUrgencyFilter = (state: RootState) => state.aiPrediction.selectedUrgencyFilter; export const selectSeverityFilter = (state: RootState) => state.aiPrediction.selectedSeverityFilter; export const selectCategoryFilter = (state: RootState) => state.aiPrediction.selectedCategoryFilter; /** * Select Sort Options * * Purpose: Get current sort configuration */ export const selectSortBy = (state: RootState) => state.aiPrediction.sortBy; export const selectSortOrder = (state: RootState) => state.aiPrediction.sortOrder; /** * Select Pagination * * Purpose: Get pagination configuration */ export const selectCurrentPage = (state: RootState) => state.aiPrediction.currentPage; export const selectItemsPerPage = (state: RootState) => state.aiPrediction.itemsPerPage; export const selectTotalItems = (state: RootState) => state.aiPrediction.totalItems; /** * Select UI State * * Purpose: Get UI state flags */ export const selectShowFilters = (state: RootState) => state.aiPrediction.showFilters; export const selectSelectedCaseIds = (state: RootState) => state.aiPrediction.selectedCaseIds; // ============================================================================ // COMPUTED SELECTORS // ============================================================================ /** * Select Filtered and Sorted Cases * * Purpose: Get AI prediction cases filtered and sorted based on current settings */ export const selectFilteredAndSortedCases = createSelector( [ selectPredictionCases, selectSearchQuery, selectUrgencyFilter, selectSeverityFilter, selectCategoryFilter, selectSortBy, selectSortOrder, ], (cases, searchQuery, urgencyFilter, severityFilter, categoryFilter, sortBy, sortOrder) => { let filteredCases = [...cases]; // Apply search filter if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filteredCases = filteredCases.filter(case_ => case_.patid.toLowerCase().includes(query) || case_.prediction.label.toLowerCase().includes(query) || case_.prediction.anatomical_location.toLowerCase().includes(query) ); } // Apply urgency filter if (urgencyFilter !== 'all') { filteredCases = filteredCases.filter(case_ => case_.prediction.clinical_urgency === urgencyFilter ); } // Apply severity filter if (severityFilter !== 'all') { filteredCases = filteredCases.filter(case_ => case_.prediction.primary_severity === severityFilter ); } // Apply category filter if (categoryFilter !== 'all') { filteredCases = filteredCases.filter(case_ => case_.prediction.finding_category === categoryFilter ); } // Apply sorting filteredCases.sort((a, b) => { let comparison = 0; switch (sortBy) { case 'date': comparison = new Date(a.created_at || '').getTime() - new Date(b.created_at || '').getTime(); break; case 'urgency': const urgencyOrder = { emergency: 5, urgent: 4, moderate: 3, low: 2, routine: 1 }; comparison = (urgencyOrder[a.prediction.clinical_urgency as keyof typeof urgencyOrder] || 0) - (urgencyOrder[b.prediction.clinical_urgency as keyof typeof urgencyOrder] || 0); break; case 'confidence': comparison = a.prediction.confidence_score - b.prediction.confidence_score; break; case 'severity': const severityOrder = { high: 4, medium: 3, low: 2, none: 1 }; comparison = (severityOrder[a.prediction.primary_severity as keyof typeof severityOrder] || 0) - (severityOrder[b.prediction.primary_severity as keyof typeof severityOrder] || 0); break; default: break; } return sortOrder === 'desc' ? -comparison : comparison; }); return filteredCases; } ); /** * Select Paginated Cases * * Purpose: Get the current page of filtered and sorted cases */ export const selectPaginatedCases = createSelector( [selectFilteredAndSortedCases, selectCurrentPage, selectItemsPerPage], (filteredCases, currentPage, itemsPerPage) => { const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; return filteredCases.slice(startIndex, endIndex); } ); /** * Select Critical Cases * * Purpose: Get cases marked as critical or emergency */ export const selectCriticalCases = createSelector( [selectPredictionCases], (cases) => cases.filter(case_ => case_.prediction.clinical_urgency === 'emergency' || case_.prediction.clinical_urgency === 'urgent' || case_.prediction.primary_severity === 'high' || case_.priority === 'critical' ) ); /** * Select Pending Cases * * Purpose: Get cases pending review */ export const selectPendingCases = createSelector( [selectPredictionCases], (cases) => cases.filter(case_ => case_.review_status === 'pending') ); /** * Select Reviewed Cases * * Purpose: Get cases that have been reviewed */ export const selectReviewedCases = createSelector( [selectPredictionCases], (cases) => cases.filter(case_ => case_.review_status === 'reviewed' || case_.review_status === 'confirmed' || case_.review_status === 'disputed' ) ); /** * Select Cases by Urgency * * Purpose: Group cases by urgency level */ export const selectCasesByUrgency = createSelector( [selectPredictionCases], (cases) => { const grouped = { emergency: [] as AIPredictionCase[], urgent: [] as AIPredictionCase[], moderate: [] as AIPredictionCase[], low: [] as AIPredictionCase[], routine: [] as AIPredictionCase[], }; cases.forEach(case_ => { const urgency = case_.prediction.clinical_urgency as keyof typeof grouped; if (grouped[urgency]) { grouped[urgency].push(case_); } }); return grouped; } ); /** * Select Cases Statistics * * Purpose: Get statistical overview of cases */ export const selectCasesStatistics = createSelector( [selectPredictionCases], (cases) => { const total = cases.length; const critical = cases.filter(c => c.prediction.clinical_urgency === 'emergency' || c.prediction.clinical_urgency === 'urgent' ).length; const pending = cases.filter(c => c.review_status === 'pending').length; const reviewed = cases.filter(c => c.review_status === 'reviewed' || c.review_status === 'confirmed' ).length; const averageConfidence = total > 0 ? cases.reduce((sum, c) => sum + c.prediction.confidence_score, 0) / total : 0; return { total, critical, pending, reviewed, averageConfidence: Math.round(averageConfidence * 1000) / 1000, // Round to 3 decimal places reviewProgress: total > 0 ? Math.round((reviewed / total) * 100) : 0, }; } ); /** * Select Filter Counts * * Purpose: Get counts for each filter option */ export const selectFilterCounts = createSelector( [selectPredictionCases], (cases) => { const urgencyCounts = { all: cases.length, emergency: 0, urgent: 0, moderate: 0, low: 0, routine: 0, }; const severityCounts = { all: cases.length, high: 0, medium: 0, low: 0, none: 0, }; const categoryCounts = { all: cases.length, normal: 0, abnormal: 0, critical: 0, warning: 0, unknown: 0, }; cases.forEach(case_ => { // Count urgency const urgency = case_.prediction.clinical_urgency as keyof typeof urgencyCounts; if (urgencyCounts[urgency] !== undefined) { urgencyCounts[urgency]++; } // Count severity const severity = case_.prediction.primary_severity as keyof typeof severityCounts; if (severityCounts[severity] !== undefined) { severityCounts[severity]++; } // Count category const category = case_.prediction.finding_category as keyof typeof categoryCounts; if (categoryCounts[category] !== undefined) { categoryCounts[category]++; } }); return { urgency: urgencyCounts, severity: severityCounts, category: categoryCounts, }; } ); /** * Select Total Pages * * Purpose: Calculate total number of pages based on filtered results */ export const selectTotalPages = createSelector( [selectFilteredAndSortedCases, selectItemsPerPage], (filteredCases, itemsPerPage) => Math.ceil(filteredCases.length / itemsPerPage) ); /** * Select Has Previous Page * * Purpose: Check if there's a previous page available */ export const selectHasPreviousPage = createSelector( [selectCurrentPage], (currentPage) => currentPage > 1 ); /** * Select Has Next Page * * Purpose: Check if there's a next page available */ export const selectHasNextPage = createSelector( [selectCurrentPage, selectTotalPages], (currentPage, totalPages) => currentPage < totalPages ); /** * Select Active Filters Count * * Purpose: Count how many filters are currently active */ export const selectActiveFiltersCount = createSelector( [selectSearchQuery, selectUrgencyFilter, selectSeverityFilter, selectCategoryFilter], (searchQuery, urgencyFilter, severityFilter, categoryFilter) => { let count = 0; if (searchQuery.trim()) count++; if (urgencyFilter !== 'all') count++; if (severityFilter !== 'all') count++; if (categoryFilter !== 'all') count++; return count; } ); /* * End of File: aiPredictionSelectors.ts * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */