Case Studies

Hearts in Scrubs: Deep Dive

Hearts in Scrubs is South Africa's premier healthcare staffing platform, connecting qualified medical professionals with healthcare facilities in need. The platform processes thousands of shift assignments monthly, managing complex scheduling, credentialing, and real-time communication.

This case study explores the technical architecture that enables Hearts in Scrubs to operate at scale while maintaining code quality and development velocity through LEGO Builder principles.

The Challenge

Healthcare staffing involves unique challenges that most platforms don't face:

Technical Architecture

Database Schema

The platform uses PostgreSQL with a normalized schema optimized for complex queries:

// Database models follow single-responsibility principle

// models/professional.js (27 lines)
export const ProfessionalModel = {
    tableName: 'professionals',
    fields: {
        id: 'uuid',
        userId: 'uuid',
        profession: 'string', // Nurse, Doctor, Paramedic
        experienceYears: 'integer',
        licenses: 'jsonb', // Array of license objects
        availability: 'jsonb', // Weekly availability pattern
        rating: 'decimal',
        verificationStatus: 'enum'
    }
};

Matching Algorithm

The heart of the platform is the matching algorithm that pairs professionals with shifts:

// controllers/shift-matcher.js (29 lines)
export async function findMatchingProfessionals(shift) {
    const candidates = await getProfessionalsInRadius(
        shift.facilityLocation,
        shift.radiusKm
    );

    const qualified = filterByQualifications(candidates, shift.requirements);
    const available = filterByAvailability(qualified, shift.startTime, shift.endTime);
    const scored = scoreMatches(available, shift);

    return scored.sort((a, b) => b.score - a.score);
}

// utilities/match-scorer.js (24 lines)
export function scoreMatches(professionals, shift) {
    return professionals.map(pro => ({
        ...pro,
        score: calculateMatchScore(pro, shift)
    }));
}

function calculateMatchScore(professional, shift) {
    let score = 0;
    score += professional.rating * 10;
    score += professional.completedShifts * 0.5;
    score += getExperienceBonus(professional.experienceYears);
    score += getDistanceBonus(professional.location, shift.facilityLocation);
    return score;
}

Real-Time Notifications

The platform uses WebSockets for instant updates when shifts become available or are filled:

// bridges/notification-bridge.js (28 lines)
export async function notifyMatchingProfessionals(shift, professionals) {
    const notifications = professionals.map(pro => ({
        userId: pro.userId,
        type: 'shift_available',
        data: {
            shiftId: shift.id,
            facility: shift.facilityName,
            startTime: shift.startTime,
            rate: shift.hourlyRate
        }
    }));

    await Promise.all([
        sendPushNotifications(notifications),
        sendEmailNotifications(notifications),
        broadcastWebSocket(notifications)
    ]);
}

LEGO Builder Implementation

Block Organization

The codebase is organized into clear categories:

src/
├── utilities/          // Pure functions (date formatting, scoring, validation)
├── builders/           // Object constructors (shift builder, notification builder)
├── guards/            // Auth and authorization checks
├── processors/        // Business logic (payment processing, credential verification)
├── bridges/           // External service integrations (Twilio, SendGrid, Stripe)
└── controllers/       // Request handlers and orchestration

Example: Credential Verification Flow

Here's how LEGO blocks compose to verify a professional's credentials:

// controllers/credential-controller.js (26 lines)
export async function verifyProfessionalCredentials(professionalId) {
    const professional = await getProfessional(professionalId);

    const licenseValid = await verifyLicense(professional.licenseNumber);
    const backgroundClear = await checkBackgroundStatus(professionalId);
    const certificatesValid = verifyCertificates(professional.certificates);

    const verificationResult = {
        licenseValid,
        backgroundClear,
        certificatesValid,
        overallStatus: licenseValid && backgroundClear && certificatesValid
    };

    await updateVerificationStatus(professionalId, verificationResult);
    await notifyProfessional(professionalId, verificationResult);

    return verificationResult;
}

// Each function is a separate <30 line block

Performance Optimizations

Query Optimization

// utilities/geo-query-builder.js (22 lines)
export function buildRadiusQuery(center, radiusKm) {
    // Use PostGIS for efficient geographic queries
    return `
        SELECT *
        FROM professionals
        WHERE ST_DWithin(
            location::geography,
            ST_MakePoint(${center.lng}, ${center.lat})::geography,
            ${radiusKm * 1000}
        )
        AND verification_status = 'approved'
        AND availability IS NOT NULL
    `;
}

Caching Strategy

Frequently accessed data is cached to reduce database load:

Results and Impact

The LEGO Builder approach delivered measurable results:

Lessons Learned

1. Start With Utilities

Pure utility functions are the easiest blocks to extract and provide immediate value. They're also the most reusable across projects.

2. Controllers Should Orchestrate, Not Implement

Controllers that just call other blocks (no implementation logic) are easier to understand and test. They read like a recipe.

3. Break When Blocks Get Too Big

When a block approaches 30 lines, it's time to extract helper functions. Don't wait until it's already over the limit.

4. Document Block Contracts

Clear input/output documentation makes blocks easier to reuse and prevents misuse. TypeScript helps enforce this at compile time.

Conclusion

Hearts in Scrubs demonstrates that LEGO Builder architecture scales from small utilities to complex, multi-faceted platforms. The key is consistent application of the principles: atomic blocks, single responsibility, and clear boundaries.

By treating every piece of code as a reusable LEGO block, we built a platform that's fast to develop, easy to maintain, and straightforward to scale.

← Back to Blog