From 229bef75aec447d9bb6f86abfce628c2c69d28f6 Mon Sep 17 00:00:00 2001 From: kpcode11 Date: Sun, 25 Jan 2026 11:48:27 +0530 Subject: [PATCH 1/2] create scheme and other changes for patient scheduling --- prisma/schema.prisma | 62 +++++ src/app/api/appointments/route.ts | 6 + src/app/api/appointments/smart/route.ts | 188 +++++++++++++++ src/app/api/doctor-shifts/route.ts | 218 +++++++++++++++++ src/services/scheduling.service.ts | 303 ++++++++++++++++++++++++ src/types/scheduling.ts | 147 ++++++++++++ 6 files changed, 924 insertions(+) create mode 100644 src/app/api/appointments/smart/route.ts create mode 100644 src/app/api/doctor-shifts/route.ts create mode 100644 src/services/scheduling.service.ts create mode 100644 src/types/scheduling.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cfe6627..e618a27 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -56,9 +56,11 @@ model Doctor { userId String @unique specialization String licenseNumber String @unique + seniority SeniorityLevel @default(JUNIOR) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt appointments Appointment[] + shifts DoctorShift[] user User @relation(fields: [userId], references: [id], onDelete: Cascade) prescriptions Prescription[] referralsSent Referral[] @relation("ReferralsFrom") @@ -66,6 +68,24 @@ model Doctor { surgeries Surgery[] } +model DoctorShift { + id String @id @default(cuid()) + doctorId String + shiftType ShiftType + dayOfWeek Int // 0 = Sunday, 1 = Monday, ..., 6 = Saturday + startTime String // Format: "HH:mm" (e.g., "09:00") + endTime String // Format: "HH:mm" (e.g., "17:00") + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + doctor Doctor @relation(fields: [doctorId], references: [id], onDelete: Cascade) + + @@unique([doctorId, shiftType, dayOfWeek]) + @@index([doctorId]) + @@index([shiftType]) + @@index([dayOfWeek]) +} + model Nurse { id String @id @default(cuid()) userId String @unique @@ -132,6 +152,11 @@ model Appointment { reason String status AppointmentStatus @default(SCHEDULED) notes String? + // Smart Scheduling Fields + severity SeverityLevel? + problemCategory ProblemCategory? + symptoms String[] + wasAutoAssigned Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt referralId String? @unique @@ -461,3 +486,40 @@ enum ReferralUrgency { URGENT EMERGENCY } + +// Smart Scheduling Enums +enum SeverityLevel { + LOW + MODERATE + HIGH +} + +enum ProblemCategory { + HEART + BONE_JOINT + EAR_NOSE_THROAT + EYE + SKIN + DIGESTIVE + RESPIRATORY + NEUROLOGICAL + DENTAL + GENERAL + PEDIATRIC + GYNECOLOGY + UROLOGY + MENTAL_HEALTH +} + +enum SeniorityLevel { + HOD + SENIOR + JUNIOR +} + +enum ShiftType { + MORNING + AFTERNOON + EVENING + NIGHT +} diff --git a/src/app/api/appointments/route.ts b/src/app/api/appointments/route.ts index 66cf5bd..6ae3c35 100644 --- a/src/app/api/appointments/route.ts +++ b/src/app/api/appointments/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from "next/server"; import { requirePermission } from "@/lib/authorization"; import { prisma } from "@/lib/prisma"; import { createAudit } from "@/services/audit.service"; +import { SeverityLevel, ProblemCategory } from "@/types/scheduling"; export async function GET(req: Request) { try { @@ -82,6 +83,11 @@ export async function POST(req: Request) { reason: body.reason, notes: body.notes || null, status: "SCHEDULED", + // Smart scheduling fields (optional) + severity: body.severity as SeverityLevel | undefined, + problemCategory: body.problemCategory as ProblemCategory | undefined, + symptoms: body.symptoms || [], + wasAutoAssigned: body.wasAutoAssigned || false, }, include: { patient: true, diff --git a/src/app/api/appointments/smart/route.ts b/src/app/api/appointments/smart/route.ts new file mode 100644 index 0000000..98ccf8e --- /dev/null +++ b/src/app/api/appointments/smart/route.ts @@ -0,0 +1,188 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/lib/auth'; +import { prisma } from '@/lib/prisma'; +import { findBestAvailableDoctor } from '@/services/scheduling.service'; +import { + SeverityLevel, + ProblemCategory, +} from '@/types/scheduling'; + +/** + * POST /api/appointments/smart + * Create an appointment with automatic doctor assignment based on severity and problem category + */ +export async function POST(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const body = await request.json(); + const { + patientId, + severity, + problemCategory, + symptoms, + preferredDate, + preferredTime, + reason, + notes, + } = body; + + // Validate required fields + if (!patientId || !severity || !problemCategory || !preferredDate) { + return NextResponse.json( + { error: 'Missing required fields: patientId, severity, problemCategory, preferredDate' }, + { status: 400 }, + ); + } + + // Validate severity + if (!['LOW', 'MODERATE', 'HIGH'].includes(severity)) { + return NextResponse.json( + { error: 'Invalid severity. Must be LOW, MODERATE, or HIGH' }, + { status: 400 }, + ); + } + + // Build requested datetime + const dateTime = new Date(`${preferredDate}T${preferredTime || '09:00'}:00`); + if (isNaN(dateTime.getTime())) { + return NextResponse.json( + { error: 'Invalid date/time format' }, + { status: 400 }, + ); + } + + // Find best available doctor + const recommendation = await findBestAvailableDoctor( + problemCategory as ProblemCategory, + severity as SeverityLevel, + dateTime, + ); + + if (!recommendation.success || !recommendation.doctor) { + return NextResponse.json( + { + error: 'No suitable doctor available', + details: recommendation.reason, + alternatives: recommendation.alternativeDoctors, + }, + { status: 404 }, + ); + } + + // Get receptionist ID if user is a receptionist + let receptionistId: string | null = null; + if (session.user.role === 'RECEPTIONIST') { + const receptionist = await prisma.receptionist.findUnique({ + where: { userId: session.user.id }, + }); + receptionistId = receptionist?.id || null; + } + + // Create the appointment with smart scheduling data + const appointment = await prisma.appointment.create({ + data: { + patientId, + doctorId: recommendation.doctor.id, + receptionistId, + dateTime, + reason: reason || `${problemCategory.replace('_', ' ')} - ${severity} severity`, + notes, + severity: severity as SeverityLevel, + problemCategory: problemCategory as ProblemCategory, + symptoms: symptoms || [], + wasAutoAssigned: true, + status: 'SCHEDULED', + }, + include: { + patient: { + select: { + id: true, + firstName: true, + lastName: true, + phone: true, + }, + }, + doctor: { + include: { + user: { + select: { + id: true, + name: true, + email: true, + }, + }, + }, + }, + }, + }); + + return NextResponse.json({ + success: true, + appointment, + assignmentDetails: { + doctor: recommendation.doctor, + reason: recommendation.reason, + alternatives: recommendation.alternativeDoctors, + }, + }); + } catch (error) { + console.error('Smart appointment creation error:', error); + return NextResponse.json( + { error: 'Failed to create appointment' }, + { status: 500 }, + ); + } +} + +/** + * GET /api/appointments/smart + * Get doctor recommendations without creating an appointment + */ +export async function GET(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const severity = searchParams.get('severity') as SeverityLevel; + const problemCategory = searchParams.get('problemCategory') as ProblemCategory; + const date = searchParams.get('date'); + const time = searchParams.get('time') || '09:00'; + + if (!severity || !problemCategory || !date) { + return NextResponse.json( + { error: 'Missing required params: severity, problemCategory, date' }, + { status: 400 }, + ); + } + + const dateTime = new Date(`${date}T${time}:00`); + if (isNaN(dateTime.getTime())) { + return NextResponse.json( + { error: 'Invalid date/time format' }, + { status: 400 }, + ); + } + + const recommendation = await findBestAvailableDoctor( + problemCategory, + severity, + dateTime, + ); + + return NextResponse.json(recommendation); + } catch (error) { + console.error('Doctor recommendation error:', error); + return NextResponse.json( + { error: 'Failed to get recommendations' }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/doctor-shifts/route.ts b/src/app/api/doctor-shifts/route.ts new file mode 100644 index 0000000..17f610c --- /dev/null +++ b/src/app/api/doctor-shifts/route.ts @@ -0,0 +1,218 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/lib/auth'; +import { prisma } from '@/lib/prisma'; +import { + upsertDoctorShift, + getDoctorShifts, + updateDoctorSeniority, +} from '@/services/scheduling.service'; +import { ShiftType, SeniorityLevel, SHIFT_TIMES } from '@/types/scheduling'; + +/** + * GET /api/doctor-shifts + * Get shift schedules for doctors + */ +export async function GET(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const doctorId = searchParams.get('doctorId'); + + if (doctorId) { + // Get shifts for specific doctor + const shifts = await getDoctorShifts(doctorId); + const doctor = await prisma.doctor.findUnique({ + where: { id: doctorId }, + select: { + id: true, + seniority: true, + specialization: true, + user: { + select: { name: true, email: true }, + }, + }, + }); + + return NextResponse.json({ doctor, shifts }); + } + + // Get all doctors with their shifts + const doctors = await prisma.doctor.findMany({ + include: { + user: { + select: { id: true, name: true, email: true }, + }, + shifts: { + where: { isActive: true }, + orderBy: [{ dayOfWeek: 'asc' }, { startTime: 'asc' }], + }, + }, + }); + + return NextResponse.json({ doctors }); + } catch (error) { + console.error('Get doctor shifts error:', error); + return NextResponse.json( + { error: 'Failed to get doctor shifts' }, + { status: 500 }, + ); + } +} + +/** + * POST /api/doctor-shifts + * Create or update a doctor's shift schedule + */ +export async function POST(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + // Only admins can manage shifts + if (session.user.role !== 'ADMIN') { + return NextResponse.json( + { error: 'Only admins can manage doctor shifts' }, + { status: 403 }, + ); + } + + const body = await request.json(); + const { doctorId, shiftType, dayOfWeek, startTime, endTime } = body; + + if (!doctorId || !shiftType || dayOfWeek === undefined) { + return NextResponse.json( + { error: 'Missing required fields: doctorId, shiftType, dayOfWeek' }, + { status: 400 }, + ); + } + + // Validate shift type + if (!['MORNING', 'AFTERNOON', 'EVENING', 'NIGHT'].includes(shiftType)) { + return NextResponse.json( + { error: 'Invalid shiftType' }, + { status: 400 }, + ); + } + + // Use default times if not provided + const defaultTimes = SHIFT_TIMES[shiftType as ShiftType]; + const shift = await upsertDoctorShift( + doctorId, + shiftType as ShiftType, + dayOfWeek, + startTime || defaultTimes.start, + endTime || defaultTimes.end, + ); + + return NextResponse.json({ success: true, shift }); + } catch (error) { + console.error('Create doctor shift error:', error); + return NextResponse.json( + { error: 'Failed to create doctor shift' }, + { status: 500 }, + ); + } +} + +/** + * PUT /api/doctor-shifts + * Update doctor's seniority level + */ +export async function PUT(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + // Only admins can update seniority + if (session.user.role !== 'ADMIN') { + return NextResponse.json( + { error: 'Only admins can update doctor seniority' }, + { status: 403 }, + ); + } + + const body = await request.json(); + const { doctorId, seniority } = body; + + if (!doctorId || !seniority) { + return NextResponse.json( + { error: 'Missing required fields: doctorId, seniority' }, + { status: 400 }, + ); + } + + // Validate seniority + if (!['HOD', 'SENIOR', 'JUNIOR'].includes(seniority)) { + return NextResponse.json( + { error: 'Invalid seniority. Must be HOD, SENIOR, or JUNIOR' }, + { status: 400 }, + ); + } + + const doctor = await updateDoctorSeniority( + doctorId, + seniority as SeniorityLevel, + ); + + return NextResponse.json({ success: true, doctor }); + } catch (error) { + console.error('Update doctor seniority error:', error); + return NextResponse.json( + { error: 'Failed to update doctor seniority' }, + { status: 500 }, + ); + } +} + +/** + * DELETE /api/doctor-shifts + * Deactivate a doctor's shift + */ +export async function DELETE(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + // Only admins can delete shifts + if (session.user.role !== 'ADMIN') { + return NextResponse.json( + { error: 'Only admins can delete doctor shifts' }, + { status: 403 }, + ); + } + + const { searchParams } = new URL(request.url); + const shiftId = searchParams.get('shiftId'); + + if (!shiftId) { + return NextResponse.json( + { error: 'Missing shiftId parameter' }, + { status: 400 }, + ); + } + + await prisma.doctorShift.update({ + where: { id: shiftId }, + data: { isActive: false }, + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Delete doctor shift error:', error); + return NextResponse.json( + { error: 'Failed to delete doctor shift' }, + { status: 500 }, + ); + } +} diff --git a/src/services/scheduling.service.ts b/src/services/scheduling.service.ts new file mode 100644 index 0000000..7b3a888 --- /dev/null +++ b/src/services/scheduling.service.ts @@ -0,0 +1,303 @@ +import { prisma } from '@/lib/prisma'; +import { + SeverityLevel, + ProblemCategory, + SeniorityLevel, + ShiftType, + PROBLEM_TO_SPECIALIZATION, + SENIORITY_PRIORITY, + DoctorRecommendation, + getShiftTypeForTime, +} from '@/types/scheduling'; + +interface DoctorWithDetails { + id: string; + specialization: string; + seniority: SeniorityLevel; + user: { + id: string; + name: string; + email: string; + }; + shifts: Array<{ + id: string; + shiftType: ShiftType; + dayOfWeek: number; + startTime: string; + endTime: string; + isActive: boolean; + }>; + appointments: Array<{ + id: string; + dateTime: Date; + status: string; + }>; +} + +/** + * Find the best available doctor based on problem category, severity, and shift availability + */ +export async function findBestAvailableDoctor( + problemCategory: ProblemCategory, + severity: SeverityLevel, + requestedDateTime: Date, +): Promise { + // Get required specializations for the problem + const requiredSpecs = PROBLEM_TO_SPECIALIZATION[problemCategory]; + + // Get the day of week and time + const dayOfWeek = requestedDateTime.getDay(); + const timeStr = requestedDateTime.toTimeString().slice(0, 5); // "HH:mm" + const shiftType = getShiftTypeForTime(timeStr); + + // Find doctors with matching specialization + const doctors = await prisma.doctor.findMany({ + where: { + specialization: { + in: requiredSpecs, + mode: 'insensitive', + }, + }, + include: { + user: { + select: { + id: true, + name: true, + email: true, + }, + }, + shifts: { + where: { + isActive: true, + dayOfWeek: dayOfWeek, + }, + }, + appointments: { + where: { + dateTime: { + gte: new Date(requestedDateTime.setHours(0, 0, 0, 0)), + lt: new Date(requestedDateTime.setHours(23, 59, 59, 999)), + }, + status: { + in: ['SCHEDULED'], + }, + }, + }, + }, + }) as unknown as DoctorWithDetails[]; + + if (doctors.length === 0) { + return { + success: false, + reason: `No doctors found with specialization for ${problemCategory.replace('_', ' ')}`, + }; + } + + // Filter doctors who are available on the requested shift + const availableDoctors = doctors.filter((doctor) => { + // Check if doctor has a shift on this day and time + const hasShift = doctor.shifts.some((shift) => { + if (shift.shiftType === shiftType) return true; + // Check if time falls within any of their shifts + return timeStr >= shift.startTime && timeStr < shift.endTime; + }); + + // If no shifts defined, assume available (legacy doctors) + if (doctor.shifts.length === 0) return true; + + return hasShift; + }); + + if (availableDoctors.length === 0) { + return { + success: false, + reason: `No doctors with ${problemCategory.replace('_', ' ')} specialty available during ${shiftType.toLowerCase()} shift`, + alternativeDoctors: doctors.slice(0, 3).map((d) => ({ + id: d.id, + name: d.user.name, + specialization: d.specialization, + seniority: d.seniority, + })), + }; + } + + // Sort by seniority priority based on severity + const seniorityOrder = SENIORITY_PRIORITY[severity]; + const sortedDoctors = [...availableDoctors].sort((a, b) => { + const aIndex = seniorityOrder.indexOf(a.seniority); + const bIndex = seniorityOrder.indexOf(b.seniority); + return aIndex - bIndex; + }); + + // Check appointment conflicts and find the best available doctor + for (const doctor of sortedDoctors) { + // Check if doctor has conflicting appointment at the exact time + const hasConflict = doctor.appointments.some((apt) => { + const aptTime = new Date(apt.dateTime); + const timeDiff = Math.abs(aptTime.getTime() - requestedDateTime.getTime()); + // Consider conflict if within 30 minutes + return timeDiff < 30 * 60 * 1000; + }); + + if (!hasConflict) { + return { + success: true, + doctor: { + id: doctor.id, + name: doctor.user.name, + specialization: doctor.specialization, + seniority: doctor.seniority, + }, + availableSlot: { + date: requestedDateTime.toISOString().split('T')[0], + time: timeStr, + shiftType: shiftType, + }, + reason: `${doctor.seniority} doctor matched for ${severity} severity ${problemCategory.replace('_', ' ')} case`, + alternativeDoctors: sortedDoctors + .filter((d) => d.id !== doctor.id) + .slice(0, 2) + .map((d) => ({ + id: d.id, + name: d.user.name, + specialization: d.specialization, + seniority: d.seniority, + })), + }; + } + } + + // All doctors have conflicts, return first available with alternatives + return { + success: false, + reason: 'All matching doctors have scheduling conflicts at the requested time', + alternativeDoctors: sortedDoctors.slice(0, 3).map((d) => ({ + id: d.id, + name: d.user.name, + specialization: d.specialization, + seniority: d.seniority, + })), + }; +} + +/** + * Get available time slots for a specific doctor on a given date + */ +export async function getDoctorAvailableSlots( + doctorId: string, + date: Date, +): Promise { + const dayOfWeek = date.getDay(); + + // Get doctor's shifts for this day + const shifts = await prisma.doctorShift.findMany({ + where: { + doctorId, + dayOfWeek, + isActive: true, + }, + }); + + // Get existing appointments for this day + const startOfDay = new Date(date); + startOfDay.setHours(0, 0, 0, 0); + const endOfDay = new Date(date); + endOfDay.setHours(23, 59, 59, 999); + + const appointments = await prisma.appointment.findMany({ + where: { + doctorId, + dateTime: { + gte: startOfDay, + lte: endOfDay, + }, + status: 'SCHEDULED', + }, + select: { + dateTime: true, + }, + }); + + const bookedTimes = new Set( + appointments.map((a) => a.dateTime.toTimeString().slice(0, 5)), + ); + + // Generate available slots based on shifts + const availableSlots: string[] = []; + + for (const shift of shifts) { + let currentTime = shift.startTime; + while (currentTime < shift.endTime) { + if (!bookedTimes.has(currentTime)) { + availableSlots.push(currentTime); + } + // Increment by 30 minutes + const [hours, minutes] = currentTime.split(':').map(Number); + const newMinutes = minutes + 30; + if (newMinutes >= 60) { + currentTime = `${String(hours + 1).padStart(2, '0')}:${String(newMinutes - 60).padStart(2, '0')}`; + } else { + currentTime = `${String(hours).padStart(2, '0')}:${String(newMinutes).padStart(2, '0')}`; + } + } + } + + return availableSlots.sort(); +} + +/** + * Create or update doctor shift schedule + */ +export async function upsertDoctorShift( + doctorId: string, + shiftType: ShiftType, + dayOfWeek: number, + startTime: string, + endTime: string, +) { + return prisma.doctorShift.upsert({ + where: { + doctorId_shiftType_dayOfWeek: { + doctorId, + shiftType, + dayOfWeek, + }, + }, + update: { + startTime, + endTime, + isActive: true, + }, + create: { + doctorId, + shiftType, + dayOfWeek, + startTime, + endTime, + isActive: true, + }, + }); +} + +/** + * Get all shifts for a doctor + */ +export async function getDoctorShifts(doctorId: string) { + return prisma.doctorShift.findMany({ + where: { doctorId }, + orderBy: [{ dayOfWeek: 'asc' }, { startTime: 'asc' }], + }); +} + +/** + * Update doctor seniority level + */ +export async function updateDoctorSeniority( + doctorId: string, + seniority: SeniorityLevel, +) { + return prisma.doctor.update({ + where: { id: doctorId }, + data: { seniority }, + }); +} diff --git a/src/types/scheduling.ts b/src/types/scheduling.ts new file mode 100644 index 0000000..3af6ea1 --- /dev/null +++ b/src/types/scheduling.ts @@ -0,0 +1,147 @@ +// Types matching Prisma schema enums for smart scheduling + +export type SeverityLevel = 'LOW' | 'MODERATE' | 'HIGH'; + +export type ProblemCategory = + | 'HEART' + | 'BONE_JOINT' + | 'EAR_NOSE_THROAT' + | 'EYE' + | 'SKIN' + | 'DIGESTIVE' + | 'RESPIRATORY' + | 'NEUROLOGICAL' + | 'DENTAL' + | 'GENERAL' + | 'PEDIATRIC' + | 'GYNECOLOGY' + | 'UROLOGY' + | 'MENTAL_HEALTH'; + +export type SeniorityLevel = 'HOD' | 'SENIOR' | 'JUNIOR'; + +export type ShiftType = 'MORNING' | 'AFTERNOON' | 'EVENING' | 'NIGHT'; + +// Shift time ranges +export const SHIFT_TIMES: Record = { + MORNING: { start: '06:00', end: '12:00' }, + AFTERNOON: { start: '12:00', end: '18:00' }, + EVENING: { start: '18:00', end: '22:00' }, + NIGHT: { start: '22:00', end: '06:00' }, +}; + +// Problem category to required specialization mapping +export const PROBLEM_TO_SPECIALIZATION: Record = { + HEART: ['Cardiology', 'Cardiologist', 'Internal Medicine'], + BONE_JOINT: ['Orthopedics', 'Orthopedic Surgery', 'Sports Medicine'], + EAR_NOSE_THROAT: ['ENT', 'Otolaryngology', 'Otorhinolaryngology'], + EYE: ['Ophthalmology', 'Optometry'], + SKIN: ['Dermatology', 'Dermatologist'], + DIGESTIVE: ['Gastroenterology', 'Internal Medicine', 'Hepatology'], + RESPIRATORY: ['Pulmonology', 'Pulmonary Medicine', 'Internal Medicine'], + NEUROLOGICAL: ['Neurology', 'Neurosurgery', 'Neurologist'], + DENTAL: ['Dentistry', 'Dental Surgery', 'Oral Surgery'], + GENERAL: ['General Medicine', 'Internal Medicine', 'Family Medicine', 'General Practice'], + PEDIATRIC: ['Pediatrics', 'Pediatric Medicine', 'Child Health'], + GYNECOLOGY: ['Gynecology', 'Obstetrics', 'OB/GYN', 'OBGYN'], + UROLOGY: ['Urology', 'Urologist', 'Nephrology'], + MENTAL_HEALTH: ['Psychiatry', 'Psychology', 'Mental Health'], +}; + +// Seniority priority for severity matching +export const SENIORITY_PRIORITY: Record = { + HIGH: ['HOD', 'SENIOR', 'JUNIOR'], // High severity prefers HOD first + MODERATE: ['SENIOR', 'HOD', 'JUNIOR'], // Moderate prefers Senior + LOW: ['JUNIOR', 'SENIOR', 'HOD'], // Low severity can be handled by Junior +}; + +// Doctor shift schedule interface +export interface DoctorShift { + id: string; + doctorId: string; + shiftType: ShiftType; + dayOfWeek: number; // 0 = Sunday, 6 = Saturday + startTime: string; + endTime: string; + isActive: boolean; +} + +// Doctor with scheduling info +export interface DoctorWithScheduling { + id: string; + userId: string; + specialization: string; + seniority: SeniorityLevel; + shifts: DoctorShift[]; + user: { + id: string; + name: string; + email: string; + }; +} + +// Smart appointment request +export interface SmartAppointmentRequest { + patientId: string; + severity: SeverityLevel; + problemCategory: ProblemCategory; + symptoms?: string[]; + preferredDate: string; // ISO date string + preferredTime?: string; // Optional preferred time + notes?: string; +} + +// Doctor recommendation result +export interface DoctorRecommendation { + success: boolean; + doctor?: { + id: string; + name: string; + specialization: string; + seniority: SeniorityLevel; + }; + availableSlot?: { + date: string; + time: string; + shiftType: ShiftType; + }; + reason: string; + alternativeDoctors?: Array<{ + id: string; + name: string; + specialization: string; + seniority: SeniorityLevel; + }>; +} + +// Utility function to get current shift type based on time +export function getCurrentShiftType(time: Date = new Date()): ShiftType { + const hours = time.getHours(); + const minutes = time.getMinutes(); + const timeStr = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; + + if (timeStr >= '06:00' && timeStr < '12:00') return 'MORNING'; + if (timeStr >= '12:00' && timeStr < '18:00') return 'AFTERNOON'; + if (timeStr >= '18:00' && timeStr < '22:00') return 'EVENING'; + return 'NIGHT'; +} + +// Check if a time falls within a shift +export function isTimeInShift(time: string, shiftType: ShiftType): boolean { + const shift = SHIFT_TIMES[shiftType]; + + // Handle night shift spanning midnight + if (shiftType === 'NIGHT') { + return time >= shift.start || time < shift.end; + } + + return time >= shift.start && time < shift.end; +} + +// Get shift type for a given time string +export function getShiftTypeForTime(time: string): ShiftType { + if (time >= '06:00' && time < '12:00') return 'MORNING'; + if (time >= '12:00' && time < '18:00') return 'AFTERNOON'; + if (time >= '18:00' && time < '22:00') return 'EVENING'; + return 'NIGHT'; +} From a8c03555cc69ea235b7b18ca1301cbfff2bebd2b Mon Sep 17 00:00:00 2001 From: kpcode11 Date: Sun, 25 Jan 2026 12:16:41 +0530 Subject: [PATCH 2/2] developement branch --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c8b1dbf..3d4956f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A comprehensive Hospital Information System (HIS) built with **Next.js 15**, **React 19**, and **TypeScript**, featuring real-time analytics, role-based access control, and modern Apple HCI-inspired UI/UX. + + ## ✨ Current Features ### Core Capabilities