Traditional hospital appointment systems rely on fixed schedules and fail to handle:
- Variable consultation times
- Patient no-shows
- Late arrivals
- Real-time delays
π This leads to:
- Long waiting times
- Inefficient doctor utilization
To build an intelligent appointment scheduling system that:
- Predicts consultation time
- Predicts patient no-show probability
- Dynamically updates queue in real-time
- Reduces waiting time
- Improves hospital efficiency
π Machine Learning = Prediction
π Backend = Decision making
π Real-time = Event-driven updates
We use the Kaggle dataset:
./KaggleV2-May-2016.csv
- Total records: 110,527 medical appointments
- Total features: 14 variables
- Goal: Predict whether patient will show up or not and time
| Feature | Description |
|---|---|
| PatientId | Unique patient ID |
| AppointmentID | Appointment ID |
| Gender | Male / Female |
| DataMarcacaoConsulta | Appointment date |
| DataAgendamento | Booking date |
| Age | Patient age |
| Neighbourhood | Location |
| Scholarship | Govt. welfare program |
| Hipertension | True/False |
| Diabetes | True/False |
| Alcoholism | True/False |
| Handcap | True/False |
| SMS_received | Reminder sent |
| No-show | Target (Yes/No) |
βWhat if it was possible to predict whether a patient will not show up?β
Frontend (React) β Backend (Node.js / Express) β ML Service (Python - Flask) β Models: β’ No-show Prediction Model β’ Time Prediction Model
-
Patient enters details:
- Age
- Health conditions
- Other info
-
Backend sends data to ML API
-
ML returns: { "no_show_probability": 0.3, "estimated_time": 15 }
-
Backend:
- Assigns appointment slot
- Stores in database
- Patient checks in via:
- App OR reception
status = "arrived"
- App OR reception
- If patient does not arrive within threshold (e.g., 10 mins):
status = "no-show"
- Remove patient from queue
- Recalculate waiting times
- If patient arrives late:
status = "late"
-
Reinsert into queue: π Insert after current patient
-
Update queue dynamically
Doctor dashboard actions:
- Start Consultation
- End Consultation
System calculates:
actual_time = end_time - start_time
After each consultation:
- Calculate delay:
delay = actual_time - predicted_time
- Update dynamic average:
new_avg = recent_patient_times
- Recalculate queue:
wait_time = sum(previous patients time) + delay
- Notify all patients
Users see:
- Estimated wait time
- Queue position
- Real-time updates
-
Type: Classification
-
Output:
- 0 β No-show
- 1 β Show
-
Features:
- Age
- SMS_received
- Diabetes
- Hypertension
-
Type: Regression
-
Output:
- Time in minutes
-
Features:
- Age
- Health condition
- Visit type
- Models are trained using Kaggle dataset
- Training happens once (offline)
- Models are saved as
.pklfiles - No real-time retraining required
- ML service built using Flask
- Backend sends request to
/predictAPI - ML returns both predictions in one response
- Event-driven updates (not continuous tracking)
- Hybrid system (automation + manual confirmation)
- Fair queue handling
- Modular architecture
| Scenario | Solution |
|---|---|
| No-show | Remove from queue |
| Late arrival | Reinsert after current patient |
| Delay | Update wait time |
| Faster consultation | Reduce waiting time |
β Predicts appointment behavior
β Tracks real-time events
β Dynamically updates queue
β Minimizes waiting time
- Patient interface
- Doctor dashboard
- Queue display
- Real-time UI updates
- API development
- Queue logic
- Event handling (start/end/no-show)
- Database management
- ML API integration
- Data preprocessing
- Train classification model
- Feature engineering
- Export model (
no_show.pkl)
- Train regression model
- Predict consultation time
- Optimize performance
- Export model (
time.pkl)
βOur system combines machine learning-based predictions with event-driven real-time updates to dynamically optimize patient scheduling and minimize waiting time.β
This system is:
- Predictive (ML-based)
- Real-time adaptive
- Efficient and scalable
- Practical for real-world healthcare systems
The backend is the core brain of the system responsible for:
- Managing patient queue
- Integrating ML predictions
- Handling real-time updates
- Processing events (start, end, no-show, late, walk-in)
- Serving data to frontend
π Store only what is:
- Required for ML
- Required for backend logic
- Required for UI
- Node.js
- Express.js
- Axios (for ML API calls)
- MongoDB
- Mongoose (ODM)
backend/
βββ models/
β βββ Patient.js
βββ config/
β βββ db.js
βββ server.js
npm install mongooseπ config/db.js
const mongoose = require("mongoose");
const connectDB = async () => {
try {
await mongoose.connect("mongodb://127.0.0.1:27017/smart-appointment");
console.log("MongoDB Connected β
");
} catch (err) {
console.error(err);
process.exit(1);
}
};
module.exports = connectDB;const connectDB = require("./config/db");
connectDB();π models/Patient.js
const mongoose = require("mongoose");
const patientSchema = new mongoose.Schema({
// π§ ML INPUT FEATURES
age: { type: Number, required: true },
gender: { type: Number, default: 0 }, // 0 = female, 1 = male
hypertension: { type: Number, default: 0 },
diabetes: { type: Number, default: 0 },
scholarship: { type: Number, default: 0 },
sms: { type: Number, default: 0 }, // 0 = not sent, 1 = sent
// π€ ML OUTPUT
predictedTime: { type: Number },
noShowProb: { type: Number },
// π QUEUE STATUS
status: {
type: String,
enum: ["waiting", "arrived", "in-progress", "done", "no-show"],
default: "waiting"
},
// π€ TYPE
type: {
type: String,
enum: ["booked", "walk-in"],
default: "booked"
},
// β±οΈ TIME TRACKING
startTime: { type: Date },
endTime: { type: Date },
actualTime: { type: Number },
// π
META
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model("Patient", patientSchema);| Field | Reason |
|---|---|
| age, diabetes, etc. | ML input |
| predictedTime | Queue calculation |
| noShowProb | SMS + logic |
| status | Queue control |
| type | Walk-in support |
| createdAt | Queue ordering |
π§Ύ 1. Booking
Backend receives data, calls ML, stores record:
await Patient.create({
age,
gender,
hypertension,
diabetes,
scholarship,
sms: 0,
predictedTime,
noShowProb,
status: "waiting",
type: "booked"
});π₯ 2. Arrival
await Patient.updateOne(
{ _id: id },
{ status: "arrived" }
);π¨ββοΈ 3. Start Consultation
await Patient.updateOne(
{ _id: id },
{
status: "in-progress",
startTime: Date.now()
}
);βΉ 4. End Consultation
await Patient.updateOne(
{ _id: id },
{
status: "done",
endTime: Date.now(),
actualTime: calculatedTime
}
);β 5. No-Show
await Patient.updateOne(
{ _id: id },
{ status: "no-show" }
);β° 6. Late Arrival
await Patient.updateOne(
{ _id: id },
{ status: "waiting" }
);π Queue is NOT stored
π It is dynamically generated
const queue = await Patient.find({
status: { $in: ["waiting", "arrived", "in-progress"] }
}).sort({ createdAt: 1 });function calculateWaitTime(queue, index) {
let time = 0;
for (let i = 0; i < index; i++) {
if (queue[i].status !== "no-show") {
time += queue[i].predictedTime;
}
}
return time;
}π Backend and DB must agree on:
- Field Names:
predictedTime,noShowProb,sms,status - Status Values:
waiting,arrived,in-progress,done,no-show
| Scenario | DB Action |
|---|---|
| No-show | Update status |
| Late arrival | Update + reorder |
| Walk-in | Insert new record |
| Delay | Update actualTime |
| SMS sent | Update sms |
- Do NOT store queue separately
- Always fetch fresh data
- Keep schema consistent
- Do not change field names randomly
This database system:
β Stores all patient data
β Supports ML integration
β Enables dynamic queue
β Handles real-time updates
β Ensures system consistency
π Give this to your database teammate
π Start MongoDB setup
π Create model β test insert
SMS_received is a feature in the dataset that tells us:
0= Patient has NOT received an SMS reminder yet1= Patient HAS received an SMS reminder
Studies show patients who receive an SMS reminder are more likely to show up. So this feature directly improves our No-Show prediction accuracy.
We use SMS_received only in the No-Show model (not in the Time model).
| Model | Uses SMS_received? | Reason |
|---|---|---|
| No-Show Prediction | β YES | SMS affects whether patient shows up |
| Time Prediction | β NO | SMS has no effect on consultation duration |
- Backend calls ML with
SMS_received = 0(SMS not sent yet) - ML returns
no_show_probabilityandsms_strategy
| ML says | Risk Level | What Backend Does |
|---|---|---|
sms_strategy = "high_risk" |
π΄ prob > 0.4 | Send SMS 24 hours before + 2 hours before |
sms_strategy = "medium_risk" |
π‘ prob 0.2β0.4 | Send SMS 24 hours before only |
sms_strategy = "low_risk" |
π’ prob β€ 0.2 | No SMS needed |
- Backend marks
sms_received = 1in patient record - Backend calls ML again with
SMS_received = 1 - ML returns updated (lower) no_show_probability
- Queue wait times are recalculated
Request (on booking):
{
"Age": 35,
"Gender": 0,
"Hipertension": 1,
"Diabetes": 0,
"Alcoholism": 0,
"Handcap": 0,
"Scholarship": 0,
"SMS_received": 0
}Response:
{
"no_show_probability": 0.35,
"estimated_time": 18.0,
"sms_strategy": "medium_risk",
"status": "success"
}Request (after SMS is sent):
{
"SMS_received": 1,
...same fields...
}Response (updated):
{
"no_show_probability": 0.18,
"estimated_time": 18.0,
"sms_strategy": "low_risk",
"status": "success"
}Patient books
β
ML predicts risk (SMS_received = 0)
β
High/Medium risk? β Schedule SMS reminder
β
SMS sent β Update patient record (SMS_received = 1)
β
ML re-predicts updated risk (lower now)
β
Queue recalculated with new probability
The Patient Schema we designed earlier is strictly for Queue Management and ML Predictions (it essentially represents an Appointment in the queue).
Do NOT mix login credentials into the Patient queue schema. A patient might visit multiple times (multiple queue records over time), but they should only have one permanent login account.
Therefore, authentication must be completely independent from the queue logic!
Create a separate User model to handle registration and login for both Receptionists and Patients.
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true }, // β οΈ Store HASHED (use bcrypt)
// Role string determines permissions across the app
role: {
type: String,
enum: ["patient", "receptionist", "doctor"],
default: "patient"
},
phone: { type: String } // Useful to link to the ML SMS feature
});
module.exports = mongoose.model("User", userSchema);-
Registration:
- Patient registers on frontend β creates a
Userdocument in database. - Receptionists/Doctors are pre-created manually by admins.
- Patient registers on frontend β creates a
-
Login / JWT:
- User logs in via
/api/auth/login. - Backend verifies password (bcrypt) and returns a JWT token containing their
userIdandrole.
- User logs in via
-
Booking an Appointment (Connecting them):
- An authenticated patient hits the
/bookqueue endpoint. - The backend securely reads their
userIdfrom the JWT token and attaches it to the newly generatedPatient(queue) document.
- An authenticated patient hits the
- Never store plain-text passwords: Always use
bcryptto hash the password before saving to MongoDB. - Secure endpoints via Routes Guarding: Use JWT to secure your Backend APIs. Receptionists should have access to
/start,/end, and/late, while Patients can only access/bookand view the/queue. - Database Separation:
Collection 1: Users(For login / auth / profiles)Collection 2: Patients(For daily appointments / queues / ML)