From c18dfb5e249f68f450f764c1e30373cff8b37d1c Mon Sep 17 00:00:00 2001 From: Emeka Date: Wed, 4 Jun 2025 12:01:50 +0100 Subject: [PATCH] feat: Health-Optimization-Performance --- .../entities/appointment.entity.ts | 43 ++++++ .../entities/medical-record.entity.ts | 37 +++++ .../entities/patient.entity.ts | 53 +++++++ .../modules/analytics/analytics.service.ts | 140 ++++++++++++++++++ .../device-integration.service.ts | 102 +++++++++++++ .../load-balancer/load-balancer.service.ts | 70 +++++++++ .../modules/patient/patient.controller.ts | 31 ++++ .../modules/patient/patient.module.ts | 13 ++ .../modules/patient/patient.service.ts | 76 ++++++++++ .../modules/real-time/real-time.gateway.ts | 64 ++++++++ .../modules/workflow/workflow.service.ts | 73 +++++++++ 11 files changed, 702 insertions(+) create mode 100644 src/Hospital-Performance-Optimization/entities/appointment.entity.ts create mode 100644 src/Hospital-Performance-Optimization/entities/medical-record.entity.ts create mode 100644 src/Hospital-Performance-Optimization/entities/patient.entity.ts create mode 100644 src/Hospital-Performance-Optimization/modules/analytics/analytics.service.ts create mode 100644 src/Hospital-Performance-Optimization/modules/device-integration/device-integration.service.ts create mode 100644 src/Hospital-Performance-Optimization/modules/load-balancer/load-balancer.service.ts create mode 100644 src/Hospital-Performance-Optimization/modules/patient/patient.controller.ts create mode 100644 src/Hospital-Performance-Optimization/modules/patient/patient.module.ts create mode 100644 src/Hospital-Performance-Optimization/modules/patient/patient.service.ts create mode 100644 src/Hospital-Performance-Optimization/modules/real-time/real-time.gateway.ts create mode 100644 src/Hospital-Performance-Optimization/modules/workflow/workflow.service.ts diff --git a/src/Hospital-Performance-Optimization/entities/appointment.entity.ts b/src/Hospital-Performance-Optimization/entities/appointment.entity.ts new file mode 100644 index 0000000..ec97a9c --- /dev/null +++ b/src/Hospital-Performance-Optimization/entities/appointment.entity.ts @@ -0,0 +1,43 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm'; +import { Patient } from './patient.entity'; + +@Entity('appointments') +@Index(['appointmentDate', 'status']) // Optimize appointment scheduling +@Index(['doctorId', 'appointmentDate']) +export class Appointment { + @PrimaryGeneratedColumn() + id: number; + + @Column() + patientId: number; + + @Column() + doctorId: number; + + @Column() + appointmentDate: Date; + + @Column() + duration: number; // in minutes + + @Column() + type: string; // 'consultation', 'follow_up', 'surgery', 'test' + + @Column({ default: 'scheduled' }) + status: string; // 'scheduled', 'in_progress', 'completed', 'cancelled' + + @Column('text', { nullable: true }) + notes: string; + + @Column('json', { nullable: true }) + vitals: object; + + @ManyToOne(() => Patient, patient => patient.appointments) + patient: Patient; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} diff --git a/src/Hospital-Performance-Optimization/entities/medical-record.entity.ts b/src/Hospital-Performance-Optimization/entities/medical-record.entity.ts new file mode 100644 index 0000000..9ed77f3 --- /dev/null +++ b/src/Hospital-Performance-Optimization/entities/medical-record.entity.ts @@ -0,0 +1,37 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, Index, CreateDateColumn } from 'typeorm'; +import { Patient } from './patient.entity'; + +@Entity('medical_records') +@Index(['patientId', 'recordDate']) // Optimize medical record queries +@Index(['recordType']) +export class MedicalRecord { + @PrimaryGeneratedColumn() + id: number; + + @Column() + patientId: number; + + @Column() + recordType: string; // 'diagnosis', 'treatment', 'lab_result', 'vital_signs' + + @Column('json') + data: object; + + @Column() + recordDate: Date; + + @Column({ nullable: true }) + doctorId: number; + + @Column('text', { nullable: true }) + notes: string; + + @Column({ default: 'active' }) + status: string; + + @ManyToOne(() => Patient, patient => patient.medicalRecords) + patient: Patient; + + @CreateDateColumn() + createdAt: Date; +} diff --git a/src/Hospital-Performance-Optimization/entities/patient.entity.ts b/src/Hospital-Performance-Optimization/entities/patient.entity.ts new file mode 100644 index 0000000..e5ffb09 --- /dev/null +++ b/src/Hospital-Performance-Optimization/entities/patient.entity.ts @@ -0,0 +1,53 @@ +import { Entity, PrimaryGeneratedColumn, Column, OneToMany, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm'; +import { MedicalRecord } from './medical-record.entity'; +import { Appointment } from './appointment.entity'; + +@Entity('patients') +@Index(['medicalRecordNumber'], { unique: true }) +@Index(['lastName', 'firstName']) // Optimize patient searches +export class Patient { + @PrimaryGeneratedColumn() + id: number; + + @Column({ unique: true }) + medicalRecordNumber: string; + + @Column() + firstName: string; + + @Column() + lastName: string; + + @Column() + dateOfBirth: Date; + + @Column() + phoneNumber: string; + + @Column() + email: string; + + @Column('text', { nullable: true }) + address: string; + + @Column({ default: 'active' }) + status: string; + + @Column('json', { nullable: true }) + emergencyContact: object; + + @Column('json', { nullable: true }) + insuranceInfo: object; + + @OneToMany(() => MedicalRecord, record => record.patient, { lazy: true }) + medicalRecords: MedicalRecord[]; + + @OneToMany(() => Appointment, appointment => appointment.patient, { lazy: true }) + appointments: Appointment[]; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} \ No newline at end of file diff --git a/src/Hospital-Performance-Optimization/modules/analytics/analytics.service.ts b/src/Hospital-Performance-Optimization/modules/analytics/analytics.service.ts new file mode 100644 index 0000000..2fc22dd --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/analytics/analytics.service.ts @@ -0,0 +1,140 @@ +import { Injectable, Inject, CACHE_MANAGER } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Cache } from 'cache-manager'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { Patient } from '../../entities/patient.entity'; +import { Appointment } from '../../entities/appointment.entity'; +import { MedicalRecord } from '../../entities/medical-record.entity'; + +@Injectable() +export class AnalyticsService { + constructor( + @InjectRepository(Patient) + private patientRepository: Repository, + @InjectRepository(Appointment) + private appointmentRepository: Repository, + @InjectRepository(MedicalRecord) + private medicalRecordRepository: Repository, + @Inject(CACHE_MANAGER) + private cacheManager: Cache, + ) {} + + async getDashboardMetrics(): Promise { + const cacheKey = 'dashboard_metrics'; + let metrics = await this.cacheManager.get(cacheKey); + + if (!metrics) { + const [ + totalPatients, + todayAppointments, + pendingAppointments, + recentMedicalRecords, + ] = await Promise.all([ + this.patientRepository.count(), + this.getTodayAppointmentsCount(), + this.getPendingAppointmentsCount(), + this.getRecentMedicalRecordsCount(), + ]); + + metrics = { + totalPatients, + todayAppointments, + pendingAppointments, + recentMedicalRecords, + lastUpdated: new Date(), + }; + + await this.cacheManager.set(cacheKey, metrics, 300); // Cache for 5 minutes + } + + return metrics; + } + + async getPatientFlowAnalytics(): Promise { + const cacheKey = 'patient_flow_analytics'; + let analytics = await this.cacheManager.get(cacheKey); + + if (!analytics) { + const query = ` + SELECT + DATE(appointment_date) as date, + COUNT(*) as appointment_count, + AVG(duration) as avg_duration, + COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_count, + COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled_count + FROM appointments + WHERE appointment_date >= DATE_SUB(NOW(), INTERVAL 7 DAY) + GROUP BY DATE(appointment_date) + ORDER BY date DESC + `; + + analytics = await this.appointmentRepository.query(query); + await this.cacheManager.set(cacheKey, analytics, 900); // Cache for 15 minutes + } + + return analytics; + } + + async getResourceUtilization(): Promise { + const cacheKey = 'resource_utilization'; + let utilization = await this.cacheManager.get(cacheKey); + + if (!utilization) { + // Calculate room/doctor utilization + const query = ` + SELECT + doctor_id, + COUNT(*) as total_appointments, + SUM(duration) as total_minutes, + AVG(duration) as avg_appointment_duration + FROM appointments + WHERE appointment_date >= DATE_SUB(NOW(), INTERVAL 1 WEEK) + GROUP BY doctor_id + ORDER BY total_appointments DESC + `; + + utilization = await this.appointmentRepository.query(query); + await this.cacheManager.set(cacheKey, utilization, 600); // Cache for 10 minutes + } + + return utilization; + } + + // Scheduled task to pre-compute analytics + @Cron(CronExpression.EVERY_5_MINUTES) + async preComputeAnalytics(): Promise { + console.log('Pre-computing analytics...'); + + // Pre-compute and cache frequently accessed analytics + await Promise.all([ + this.getDashboardMetrics(), + this.getPatientFlowAnalytics(), + this.getResourceUtilization(), + ]); + + console.log('Analytics pre-computation completed'); + } + + private async getTodayAppointmentsCount(): Promise { + return this.appointmentRepository + .createQueryBuilder('appointment') + .where('DATE(appointment.appointmentDate) = CURDATE()') + .getCount(); + } + + private async getPendingAppointmentsCount(): Promise { + return this.appointmentRepository + .createQueryBuilder('appointment') + .where('appointment.status = :status', { status: 'scheduled' }) + .andWhere('appointment.appointmentDate > NOW()') + .getCount(); + } + + private async getRecentMedicalRecordsCount(): Promise { + return this.medicalRecordRepository + .createQueryBuilder('record') + .where('DATE(record.createdAt) = CURDATE()') + .getCount(); + } +} diff --git a/src/Hospital-Performance-Optimization/modules/device-integration/device-integration.service.ts b/src/Hospital-Performance-Optimization/modules/device-integration/device-integration.service.ts new file mode 100644 index 0000000..b5b5bb6 --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/device-integration/device-integration.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { RealTimeGateway } from '../real-time/real-time.gateway'; + +@Injectable() +export class DeviceIntegrationService { + constructor( + private eventEmitter: EventEmitter2, + private realTimeGateway: RealTimeGateway, + ) {} + + async processDeviceData(deviceId: string, data: any): Promise { + try { + // Validate and process device data + const processedData = await this.validateDeviceData(data); + + // Determine data type and route accordingly + switch (processedData.type) { + case 'vital_signs': + await this.processVitalSigns(processedData); + break; + case 'lab_result': + await this.processLabResult(processedData); + break; + case 'imaging': + await this.processImagingData(processedData); + break; + default: + console.warn(`Unknown device data type: ${processedData.type}`); + } + + } catch (error) { + console.error(`Error processing device data from ${deviceId}:`, error); + this.eventEmitter.emit('device.error', { deviceId, error: error.message }); + } + } + + private async validateDeviceData(data: any): Promise { + // Implement data validation logic + if (!data.patientId || !data.type || !data.values) { + throw new Error('Invalid device data format'); + } + + return { + ...data, + timestamp: data.timestamp || new Date(), + validated: true, + }; + } + + private async processVitalSigns(data: any): Promise { + // Check for critical values + const criticalValues = this.checkCriticalValues(data.values); + + if (criticalValues.length > 0) { + this.realTimeGateway.broadcastEmergencyAlert({ + type: 'critical_vitals', + patientId: data.patientId, + criticalValues, + severity: 'high', + }); + } + + // Broadcast real-time vital signs + this.realTimeGateway.broadcastVitalSigns(data.patientId, data.values); + + // Emit event for storage + this.eventEmitter.emit('vitals.recorded', data); + } + + private async processLabResult(data: any): Promise { + // Process lab results + this.eventEmitter.emit('lab.result', data); + } + + private async processImagingData(data: any): Promise { + // Process imaging data + this.eventEmitter.emit('imaging.received', data); + } + + private checkCriticalValues(vitals: any): string[] { + const critical = []; + + // Example critical value checks + if (vitals.heartRate && (vitals.heartRate < 60 || vitals.heartRate > 100)) { + critical.push(`Heart rate: ${vitals.heartRate} BPM`); + } + + if (vitals.bloodPressure) { + const [systolic, diastolic] = vitals.bloodPressure.split('/').map(Number); + if (systolic > 180 || diastolic > 110) { + critical.push(`Blood pressure: ${vitals.bloodPressure}`); + } + } + + if (vitals.temperature && (vitals.temperature < 95 || vitals.temperature > 102)) { + critical.push(`Temperature: ${vitals.temperature}°F`); + } + + return critical; + } +} diff --git a/src/Hospital-Performance-Optimization/modules/load-balancer/load-balancer.service.ts b/src/Hospital-Performance-Optimization/modules/load-balancer/load-balancer.service.ts new file mode 100644 index 0000000..e3ec30d --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/load-balancer/load-balancer.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +@Injectable() +export class LoadBalancerService { + private healthChecks: Map = new Map(); + private serverLoad: Map = new Map(); + + constructor() { + // Initialize server monitoring + this.initializeServers(); + } + + private initializeServers(): void { + const servers = ['server-1', 'server-2', 'server-3']; + servers.forEach(server => { + this.healthChecks.set(server, true); + this.serverLoad.set(server, 0); + }); + } + + @Cron(CronExpression.EVERY_30_SECONDS) + async performHealthChecks(): Promise { + for (const [server] of this.healthChecks) { + try { + // Simulate health check + const isHealthy = await this.checkServerHealth(server); + this.healthChecks.set(server, isHealthy); + + if (!isHealthy) { + console.warn(`Server ${server} is unhealthy`); + await this.initiateFailover(server); + } + } catch (error) { + console.error(`Health check failed for ${server}:`, error); + this.healthChecks.set(server, false); + } + } + } + + async getOptimalServer(): Promise { + // Get healthy servers with lowest load + const healthyServers = Array.from(this.healthChecks.entries()) + .filter(([_, isHealthy]) => isHealthy) + .map(([server]) => server); + + if (healthyServers.length === 0) { + throw new Error('No healthy servers available'); + } + + // Return server with lowest load + return healthyServers.reduce((optimal, current) => { + const currentLoad = this.serverLoad.get(current) || 0; + const optimalLoad = this.serverLoad.get(optimal) || 0; + return currentLoad < optimalLoad ? current : optimal; + }); + } + + private async checkServerHealth(server: string): Promise { + // Simulate health check - in production, make actual HTTP calls + const load = Math.random() * 100; + this.serverLoad.set(server, load); + + // Consider server unhealthy if load > 90% + return load < 90; + } + + private async initiateFailover(failedServer: string): Promise { + console.log(`Initiating failover for ${failedServer}`); + \ No newline at end of file diff --git a/src/Hospital-Performance-Optimization/modules/patient/patient.controller.ts b/src/Hospital-Performance-Optimization/modules/patient/patient.controller.ts new file mode 100644 index 0000000..a039565 --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/patient/patient.controller.ts @@ -0,0 +1,31 @@ +import { Controller, Get, Post, Put, Param, Body, Query } from '@nestjs/common'; +import { PatientService } from '../../patient/patient.service'; +import { Patient } from '../../entities/patient.entity'; + +@Controller('patients') +export class PatientController { + constructor(private readonly patientService: PatientService) {} + + @Get('search') + async searchPatients(@Query('q') query: string): Promise { + return this.patientService.searchPatients(query); + } + + @Get(':id') + async getPatient(@Param('id') id: number): Promise { + return this.patientService.findById(id); + } + + @Post() + async createPatient(@Body() patientData: Partial): Promise { + return this.patientService.create(patientData); + } + + @Put(':id') + async updatePatient( + @Param('id') id: number, + @Body() updateData: Partial, + ): Promise { + return this.patientService.update(id, updateData); + } +} diff --git a/src/Hospital-Performance-Optimization/modules/patient/patient.module.ts b/src/Hospital-Performance-Optimization/modules/patient/patient.module.ts new file mode 100644 index 0000000..5a0ba7e --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/patient/patient.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Patient } from '../../entities/patient.entity'; +import { PatientService } from '../../patient/patient.service'; +import { PatientController } from './patient.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([Patient])], + providers: [PatientService], + controllers: [PatientController], + exports: [PatientService], +}) +export class PatientModule {} diff --git a/src/Hospital-Performance-Optimization/modules/patient/patient.service.ts b/src/Hospital-Performance-Optimization/modules/patient/patient.service.ts new file mode 100644 index 0000000..e6f752d --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/patient/patient.service.ts @@ -0,0 +1,76 @@ +import { Injectable, Inject, CACHE_MANAGER } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Cache } from 'cache-manager'; +import { Patient } from '../entities/patient.entity'; + +@Injectable() +export class PatientService { + constructor( + @InjectRepository(Patient) + private patientRepository: Repository, + @Inject(CACHE_MANAGER) + private cacheManager: Cache, + ) {} + + async findById(id: number): Promise { + const cacheKey = `patient:${id}`; + let patient = await this.cacheManager.get(cacheKey); + + if (!patient) { + patient = await this.patientRepository.findOne({ + where: { id }, + relations: ['medicalRecords', 'appointments'], + }); + + if (patient) { + await this.cacheManager.set(cacheKey, patient, 300); // Cache for 5 minutes + } + } + + return patient; + } + + async searchPatients(query: string): Promise { + const cacheKey = `patient_search:${query}`; + let patients = await this.cacheManager.get(cacheKey); + + if (!patients) { + patients = await this.patientRepository + .createQueryBuilder('patient') + .where('patient.firstName LIKE :query', { query: `%${query}%` }) + .orWhere('patient.lastName LIKE :query', { query: `%${query}%` }) + .orWhere('patient.medicalRecordNumber LIKE :query', { query: `%${query}%` }) + .limit(50) // Prevent large result sets + .getMany(); + + await this.cacheManager.set(cacheKey, patients, 60); // Cache for 1 minute + } + + return patients; + } + + async create(patientData: Partial): Promise { + const patient = this.patientRepository.create(patientData); + const savedPatient = await this.patientRepository.save(patient); + + // Clear related caches + await this.clearPatientCaches(savedPatient.id); + + return savedPatient; + } + + async update(id: number, updateData: Partial): Promise { + await this.patientRepository.update(id, updateData); + await this.clearPatientCaches(id); + + return this.findById(id); + } + + private async clearPatientCaches(patientId: number): Promise { + await this.cacheManager.del(`patient:${patientId}`); + // Clear search caches - in production, use more sophisticated cache invalidation + const keys = await this.cacheManager.store.keys('patient_search:*'); + await Promise.all(keys.map(key => this.cacheManager.del(key))); + } +} \ No newline at end of file diff --git a/src/Hospital-Performance-Optimization/modules/real-time/real-time.gateway.ts b/src/Hospital-Performance-Optimization/modules/real-time/real-time.gateway.ts new file mode 100644 index 0000000..4faefd6 --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/real-time/real-time.gateway.ts @@ -0,0 +1,64 @@ +import { + WebSocketGateway, + WebSocketServer, + SubscribeMessage, + MessageBody, + ConnectedSocket, + } from '@nestjs/websockets'; + import { Server, Socket } from 'socket.io'; + import { Injectable } from '@nestjs/common'; + + @WebSocketGateway({ + cors: { + origin: '*', + }, + }) + @Injectable() + export class RealTimeGateway { + @WebSocketServer() + server: Server; + + @SubscribeMessage('join-room') + handleJoinRoom( + @MessageBody() data: { room: string }, + @ConnectedSocket() client: Socket, + ): void { + client.join(data.room); + client.emit('joined-room', { room: data.room }); + } + + // Broadcast vital signs updates + broadcastVitalSigns(patientId: number, vitals: any): void { + this.server.to(`patient-${patientId}`).emit('vital-signs-update', { + patientId, + vitals, + timestamp: new Date(), + }); + } + + // Broadcast emergency alerts + broadcastEmergencyAlert(alert: any): void { + this.server.emit('emergency-alert', { + ...alert, + timestamp: new Date(), + }); + } + + // Broadcast appointment updates + broadcastAppointmentUpdate(appointmentId: number, update: any): void { + this.server.to('appointments').emit('appointment-update', { + appointmentId, + update, + timestamp: new Date(), + }); + } + + // Broadcast system status + broadcastSystemStatus(status: any): void { + this.server.emit('system-status', { + ...status, + timestamp: new Date(), + }); + } + } + \ No newline at end of file diff --git a/src/Hospital-Performance-Optimization/modules/workflow/workflow.service.ts b/src/Hospital-Performance-Optimization/modules/workflow/workflow.service.ts new file mode 100644 index 0000000..bf94d23 --- /dev/null +++ b/src/Hospital-Performance-Optimization/modules/workflow/workflow.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bull'; +import { Queue } from 'bull'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +@Injectable() +export class WorkflowService { + constructor( + @InjectQueue('appointment-workflow') + private appointmentQueue: Queue, + @InjectQueue('medical-workflow') + private medicalQueue: Queue, + private eventEmitter: EventEmitter2, + ) {} + + async processAppointmentWorkflow(appointmentId: number): Promise { + // Add jobs to queue for appointment processing + await this.appointmentQueue.add('prepare-appointment', { + appointmentId, + priority: 1, + }); + + await this.appointmentQueue.add('notify-patient', { + appointmentId, + priority: 2, + }, { + delay: 24 * 60 * 60 * 1000, // 24 hours before appointment + }); + + await this.appointmentQueue.add('notify-doctor', { + appointmentId, + priority: 2, + }, { + delay: 2 * 60 * 60 * 1000, // 2 hours before appointment + }); + } + + async processLabResultWorkflow(recordId: number): Promise { + await this.medicalQueue.add('process-lab-result', { + recordId, + priority: 1, + }); + + await this.medicalQueue.add('alert-critical-values', { + recordId, + priority: 0, // Highest priority + }); + + await this.medicalQueue.add('update-patient-record', { + recordId, + priority: 2, + }); + } + + async optimizeScheduling(): Promise { + // Implement scheduling optimization algorithm + const optimizationData = await this.calculateOptimalSchedule(); + + this.eventEmitter.emit('schedule.optimized', optimizationData); + + return optimizationData; + } + + private async calculateOptimalSchedule(): Promise { + // Simplified scheduling optimization + return { + recommendedSlots: [], + utilizationRate: 85, + waitTimeReduction: 15, + timestamp: new Date(), + }; + } +}