411 lines
11 KiB
TypeScript
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.
|
|
*/
|