NeoScan_Radiologist/app/modules/Dashboard/components/BrainPredictionsOverview.tsx
2025-08-12 18:50:19 +05:30

613 lines
18 KiB
TypeScript

/*
* File: BrainPredictionsOverview.tsx
* Description: Component to display patient overview statistics based on different AI prediction scenarios for brain conditions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
Dimensions,
ScrollView,
} from 'react-native';
import { PieChart } from 'react-native-chart-kit';
import { theme } from '../../../theme/theme';
import { ERDashboard } from '../../../shared/types';
/**
* BrainPredictionsOverviewProps Interface
*
* Purpose: Defines the props required by the BrainPredictionsOverview component
*
* Props:
* - dashboard: ERDashboard object containing brain prediction statistics
*/
interface BrainPredictionsOverviewProps {
dashboard: ERDashboard;
}
/**
* BrainPredictionsOverview Component
*
* Purpose: Display patient overview statistics based on different AI prediction scenarios
*
* Features:
* 1. Shows distribution of patients by brain condition predictions
* 2. Displays AI confidence levels for each prediction type
* 3. Shows critical vs non-critical brain conditions
* 4. Provides summary statistics for quick overview
* 5. Visualizes data using react-native-chart-kit pie chart
*
* Prediction Categories:
* - Hemorrhagic Conditions (IPH, IVH, SAH, SDH, EDH)
* - Ischemic Conditions (Stroke)
* - Structural Changes (Mass effect, Midline shift)
* - Normal Findings
*/
export const BrainPredictionsOverview: React.FC<BrainPredictionsOverviewProps> = ({
dashboard,
}) => {
// ============================================================================
// MOCK PREDICTION DATA
// ============================================================================
/**
* Mock prediction statistics based on brain conditions
* In a real app, this would come from the AI analysis API
*/
const predictionStats = {
// Hemorrhagic Conditions
intraparenchymal: { count: 2, confidence: 94, critical: true },
intraventricular: { count: 1, confidence: 87, critical: true },
subarachnoid: { count: 1, confidence: 87, critical: true },
subdural: { count: 1, confidence: 89, critical: true },
epidural: { count: 1, confidence: 96, critical: true },
// Ischemic Conditions
ischemic_stroke: { count: 1, confidence: 92, critical: true },
// Structural Changes
mass_effect: { count: 2, confidence: 91, critical: true },
midline_shift: { count: 1, confidence: 96, critical: true },
// Normal Findings
normal_brain: { count: 3, confidence: 98, critical: false },
// Pending Analysis
pending_analysis: { count: 6, confidence: 0, critical: false },
};
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* getPredictionLabel Function
*
* Purpose: Get human-readable label for prediction type
*
* @param predictionType - Type of brain prediction
* @returns Human-readable label
*/
const getPredictionLabel = (predictionType: string) => {
if (!predictionType) {
return 'Unknown'; // Default label for undefined types
}
const labels: { [key: string]: string } = {
intraparenchymal: 'IPH',
intraventricular: 'IVH',
subarachnoid: 'SAH',
subdural: 'SDH',
epidural: 'EDH',
ischemic_stroke: 'Stroke',
mass_effect: 'Mass Effect',
midline_shift: 'Midline Shift',
normal_brain: 'Normal',
pending_analysis: 'Pending',
};
return labels[predictionType] || predictionType;
};
/**
* getPredictionColor Function
*
* Purpose: Get color based on prediction type and criticality
*
* @param predictionType - Type of brain prediction
* @param isCritical - Whether the condition is critical
* @returns Color string for styling
*/
const getPredictionColor = (predictionType: string, isCritical: boolean) => {
if (!predictionType) {
return theme.colors.textSecondary; // Default color for undefined types
}
// Define distinct colors for each prediction class
const predictionColors: { [key: string]: string } = {
intraparenchymal: '#E53E3E', // Red for IPH
intraventricular: '#C53030', // Dark Red for IVH
subarachnoid: '#F56565', // Light Red for SAH
subdural: '#FC8181', // Pink Red for SDH
epidural: '#E53E3E', // Red for EDH
ischemic_stroke: '#3182CE', // Blue for Stroke
mass_effect: '#805AD5', // Purple for Mass Effect
midline_shift: '#553C9A', // Dark Purple for Midline Shift
normal_brain: '#38A169', // Green for Normal
pending_analysis: '#D69E2E', // Yellow for Pending
};
return predictionColors[predictionType] || theme.colors.textSecondary;
};
// ============================================================================
// COMPUTED VALUES
// ============================================================================
/**
* Calculate total patients
*/
const totalPatients = Object.values(predictionStats).reduce((sum, stats) => sum + stats.count, 0);
/**
* Calculate total critical conditions
*/
const totalCritical = Object.values(predictionStats)
.filter(stats => stats.critical)
.reduce((sum, stats) => sum + stats.count, 0);
/**
* Calculate total normal findings
*/
const totalNormal = predictionStats.normal_brain.count;
/**
* Calculate total pending analysis
*/
const totalPending = predictionStats.pending_analysis.count;
/**
* Calculate average confidence for critical conditions
*/
const criticalConditions = Object.values(predictionStats).filter(stats => stats.critical && stats.confidence > 0);
const averageConfidence = criticalConditions.length > 0
? Math.round(criticalConditions.reduce((sum, stats) => sum + stats.confidence, 0) / criticalConditions.length)
: 0;
/**
* Get all prediction classes by count
*/
const allPredictions = Object.entries(predictionStats)
.filter(([_, stats]) => stats.count > 0)
.sort((a, b) => b[1].count - a[1].count);
// ============================================================================
// CHART DATA PREPARATION
// ============================================================================
/**
* Prepare chart data for react-native-chart-kit PieChart
*/
const chartData = Object.entries(predictionStats)
.filter(([_, stats]) => stats && stats.count > 0) // Filter out undefined or zero count entries
.map(([predictionType, stats]) => ({
name: getPredictionLabel(predictionType),
population: stats.count, // react-native-chart-kit uses 'population' property
color: getPredictionColor(predictionType, stats.critical),
legendFontColor: theme.colors.textPrimary,
legendFontSize: 12,
confidence: stats.confidence,
isCritical: stats.critical,
}))
.filter(item => item.population > 0) // Additional filter to ensure no zero count items
.sort((a, b) => b.population - a.population); // Sort by count descending - removed limit to show all classes
/**
* Chart configuration for react-native-chart-kit
*/
const chartConfig = {
backgroundColor: theme.colors.background,
backgroundGradientFrom: theme.colors.background,
backgroundGradientTo: theme.colors.background,
decimalPlaces: 0,
color: (opacity = 1) => `rgba(33, 150, 243, ${opacity})`,
labelColor: (opacity = 1) => `rgba(33, 33, 33, ${opacity})`,
style: {
borderRadius: 16,
},
propsForDots: {
r: '6',
strokeWidth: '2',
stroke: theme.colors.primary,
},
useShadowColorFromDataset: false,
};
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* renderLegend Function
*
* Purpose: Render chart legend with confidence levels in grid layout
*
* @returns Legend component
*/
const renderLegend = () => (
<View style={styles.legendContainer}>
<Text style={styles.legendTitle}>Prediction Classes Breakdown</Text>
<View style={styles.legendGrid}>
{chartData.map((item, index) => (
<View key={index} style={styles.legendGridItem}>
<View style={styles.legendItemHeader}>
<View style={[styles.legendColor, { backgroundColor: item.color }]} />
<Text style={styles.legendLabel}>{item.name}</Text>
</View>
<Text style={styles.legendCount}>{item.population} patients</Text>
{item.confidence > 0 && (
<Text style={styles.legendConfidence}>{item.confidence}%</Text>
)}
</View>
))}
</View>
</View>
);
// ============================================================================
// MAIN RENDER
// ============================================================================
return (
<View style={styles.container}>
{/* Section Header */}
<View style={styles.header}>
<Text style={styles.title}>Brain AI Predictions Overview</Text>
<Text style={styles.subtitle}>
Patient distribution by AI-detected conditions
</Text>
</View>
{/* Pie Chart Section */}
<View style={styles.chartSection}>
<ScrollView
style={styles.chartScrollView}
showsVerticalScrollIndicator={false}
>
{chartData.length > 0 ? (
<View style={styles.chartContent}>
<View style={styles.pieChartContainer}>
{(() => {
try {
return (
<PieChart
data={chartData}
width={Dimensions.get('window').width}
height={250}
accessor="population"
backgroundColor="transparent"
paddingLeft="0"
absolute
hasLegend={false}
chartConfig={chartConfig}
center={[Dimensions.get('window').width/4, 0]}
/>
);
} catch (error) {
console.warn('PieChart error:', error);
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Chart temporarily unavailable</Text>
</View>
);
}
})()}
</View>
{renderLegend()}
</View>
) : (
<View style={styles.noDataContainer}>
<Text style={styles.noDataText}>No data available for chart</Text>
</View>
)}
</ScrollView>
</View>
{/* Summary Statistics */}
<View style={styles.summaryContainer}>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Total Patients</Text>
<Text style={styles.summaryValue}>{totalPatients}</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Critical Cases</Text>
<Text style={[styles.summaryValue, { color: theme.colors.error }]}>
{totalCritical}
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Pending Analysis</Text>
<Text style={[styles.summaryValue, { color: theme.colors.warning }]}>
{totalPending}
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Avg Confidence</Text>
<Text style={[styles.summaryValue, { color: theme.colors.primary }]}>
{averageConfidence}%
</Text>
</View>
</View>
</View>
);
};
// ============================================================================
// STYLES SECTION
// ============================================================================
const styles = StyleSheet.create({
// Main container
container: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.md,
marginVertical: theme.spacing.sm,
borderWidth: 1,
borderColor: theme.colors.border,
},
// Header section
header: {
// marginBottom: theme.spacing.lg,
},
// Main title
title: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
// Subtitle
subtitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
// Statistics grid
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: theme.spacing.xs,
marginBottom: theme.spacing.lg,
},
// Individual stat card
statCard: {
flex: 1,
minWidth: '30%',
maxWidth: '32%',
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.sm,
borderWidth: 1,
borderColor: theme.colors.border,
borderLeftWidth: 3,
alignItems: 'center',
},
// Stat value
statValue: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
// Stat title
statTitle: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.semibold,
color: theme.colors.textPrimary,
textAlign: 'center',
marginBottom: theme.spacing.xs,
},
// Stat subtitle
statSubtitle: {
fontSize: theme.typography.fontSize.caption,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
textAlign: 'center',
},
// Chart section
chartSection: {
marginBottom: theme.spacing.lg,
},
// Chart title
chartTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.semibold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
// Chart subtitle
chartSubtitle: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.md,
},
// Chart scroll view
chartScrollView: {
// Removed maxHeight to allow full content to be visible
},
// Chart content
chartContent: {
alignItems: 'center',
paddingHorizontal: theme.spacing.sm,
},
// Pie chart container
pieChartContainer: {
alignItems: 'center',
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
},
// Legend container
legendContainer: {
width: '100%',
paddingHorizontal: theme.spacing.sm,
backgroundColor: theme.colors.backgroundAlt,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.md,
},
// Legend title
legendTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.semibold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.md,
textAlign: 'center',
},
// Legend grid container
legendGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: theme.spacing.sm,
},
// Legend grid item
legendGridItem: {
width: '48%',
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.small,
padding: theme.spacing.sm,
borderWidth: 1,
borderColor: theme.colors.border,
marginBottom: theme.spacing.xs,
},
// Legend item header
legendItemHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.xs,
},
// Legend color
legendColor: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: theme.spacing.xs,
},
// Legend label
legendLabel: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.semibold,
color: theme.colors.textPrimary,
flex: 1,
},
// Legend count
legendCount: {
fontSize: theme.typography.fontSize.caption,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
// Legend confidence
legendConfidence: {
fontSize: theme.typography.fontSize.caption,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
// No data container
noDataContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.md,
},
// No data text
noDataText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
// Summary container
summaryContainer: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingTop: theme.spacing.md,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
// Summary item
summaryItem: {
alignItems: 'center',
},
// Summary label
summaryLabel: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.xs,
},
// Summary value
summaryValue: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
},
// Error container
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing.md,
backgroundColor: theme.colors.backgroundAlt,
borderRadius: theme.borderRadius.medium,
},
// Error text
errorText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
textAlign: 'center',
},
});
/*
* End of File: BrainPredictionsOverview.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/