NeoScan_Radiologist/app/modules/AIPrediction/redux/aiPredictionSelectors.ts

411 lines
11 KiB
TypeScript

/*
* 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.
*/