Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/0Crazy-0/ClinicFlow/llms.txt

Use this file to discover all available pages before exploring further.

Scheduling in ClinicFlow is handled by the AppointmentSchedulingService domain service. The service exposes three static entry points — ScheduleByPatient, ScheduleByDoctor, and ScheduleByStaff — each enforcing a distinct set of invariants before delegating to the Appointment.Schedule(...) internal factory. Every entry point requires a valid SchedulingClearance token obtained from IRegionalSchedulingService, proving that regional regulations have been evaluated.

Actor-Specific Rules

When a user with the Patient role initiates scheduling, the following checks run in order:
  1. Relationship access checkPatientAccessService.EnsureCanActOnBehalfOf(initiatorPatient, targetPatient) verifies the initiator and target share the same UserId. A patient may only schedule on behalf of their own family members.
  2. Phone must be verifiedargs.IsInitiatorPhoneVerified must be true. Throws PHONE_NOT_VERIFIED if not.
  3. Complete patient profiletargetPatient.EnsureCompleteProfile() checks that BloodType and EmergencyContact are populated. Throws PATIENT_PROFILE_INCOMPLETE if not.
  4. Active penalty checknew PenaltyHistory(penalties).EnsureNotBlocked(scheduledDate) throws PatientBlockedException if the patient has an unexpired, non-removed TemporaryBlock.
  5. Age eligibilityappointmentType.ValidatePatientEligibility(patientAge, isGuardianScheduling) checks AgeEligibilityPolicy. Guardian consent is inferred automatically: when the initiator is Self and the target is not Self (i.e., a family member), isGuardianScheduling is set to true.
  6. Doctor availabilityschedule.CoversTimeRange(timeRange) must return true. Patients cannot overbook.

SchedulingClearance

SchedulingClearance is a token value object with no data properties. It can only be instantiated by calling SchedulingClearance.Granted(), which is the exclusive output of IRegionalSchedulingService.EnforceSchedulingRegulations(...).
// ClinicFlow.Domain/ValueObjects/SchedulingClearance.cs
public record SchedulingClearance
{
    private SchedulingClearance() { }

    /// <summary>
    /// Grants the clearance token. This should only be called by authorized scheduling policies.
    /// </summary>
    public static SchedulingClearance Granted() => new();
}
Every scheduling service method (ScheduleByPatient, ScheduleByDoctor, ScheduleByStaff) has a required SchedulingClearance clearance parameter and throws immediately if it is null. This design makes it structurally impossible to call the scheduling service without first having evaluated regional regulations.

IRegionalSchedulingService

// ClinicFlow.Domain/Interfaces/Services/IRegionalSchedulingService.cs
public interface IRegionalSchedulingService
{
    SchedulingClearance EnforceSchedulingRegulations(
        Doctor targetDoctor,
        Patient targetPatient,
        AppointmentTypeDefinition appointmentType
    );
}
The concrete implementation is registered in the infrastructure layer and may enforce jurisdiction-specific rules (e.g., specialty licensing requirements, maximum patient age for certain procedures). If all regulations pass, it returns SchedulingClearance.Granted().

Doctor Availability: Schedule.CoversTimeRange

// ClinicFlow.Domain/Entities/Schedule.cs
internal bool CoversTimeRange(TimeRange requestedRange) =>
    IsActive && TimeRange.Covers(requestedRange);
TimeRange.Covers returns true only when the schedule’s window fully encloses the requested window:
// ClinicFlow.Domain/ValueObjects/TimeRange.cs
public bool Covers(TimeRange other) =>
    Start <= other.Start && End >= other.End;
This means a slot from 09:00–12:00 covers a requested 10:00–10:30 window but does not cover 11:45–12:15. An inactive schedule always fails the check regardless of the time.

Age Eligibility: AgeEligibilityPolicy

AppointmentTypeDefinition stores an AgeEligibilityPolicy with three optional constraints:
FieldDescription
MinimumAgePatient must be at least this age. null = no lower bound.
MaximumAgePatient must not exceed this age. null = no upper bound.
RequiresLegalGuardianIf true, a patient under 18 requires guardian consent.
// ClinicFlow.Domain/ValueObjects/AgeEligibilityPolicy.cs
public void ValidatePatientEligibility(int patientAgeInYears, bool hasGuardianConsent = false)
{
    if (patientAgeInYears < MinimumAllowedAge)
        throw new DomainValidationException(DomainErrors.Validation.ValueCannotBeNegative);

    if (patientAgeInYears > MaximumAllowedAge)
        throw new DomainValidationException(DomainErrors.Validation.ValueExceedsMaximum);

    if (MinimumAge.HasValue && patientAgeInYears < MinimumAge.Value)
        throw new DomainValidationException(DomainErrors.AppointmentType.MinimumAgeNotMet);

    if (MaximumAge.HasValue && patientAgeInYears > MaximumAge.Value)
        throw new DomainValidationException(DomainErrors.AppointmentType.MaximumAgeExceeded);

    if (RequiresLegalGuardian && patientAgeInYears < LegalAdultAge && !hasGuardianConsent)
        throw new DomainValidationException(DomainErrors.AppointmentType.LegalGuardianRequired);
}
AgeEligibilityPolicy.NoRestriction is a static shortcut that sets all three fields to their permissive defaults and is used when a new AppointmentTypeDefinition is created without an explicit policy.

Family Member Scheduling

Patients can schedule appointments on behalf of their dependents (children, spouses, parents, siblings). The gate for this is PatientAccessService.EnsureCanActOnBehalfOf:
// ClinicFlow.Domain/Services/PatientAccessService.cs
public static void EnsureCanActOnBehalfOf(Patient initiator, Patient target)
{
    // Both patients must belong to the same user account
    if (initiator.UserId != target.UserId)
        throw new PatientAccessUnauthorizedException(DomainErrors.Patient.UnauthorizedAccess);

    // A family-member profile (non-Self) may only act for itself, not for other family members
    if (initiator.RelationshipToUser is not PatientRelationship.Self
        && initiator.Id != target.Id)
        throw new PatientAccessUnauthorizedException(DomainErrors.Patient.UnauthorizedAccess);
}
Only the primary (Self) patient profile is allowed to schedule for other family members on the same account. A Child profile cannot schedule for its Sibling.

Appointment Status Lifecycle

The diagram below shows every valid state transition in the appointment lifecycle.
                   ┌─────────────────────────────────┐
                   │                                 │
         ┌─────────▼──────────┐             ┌────────┴───────────┐
         │     Scheduled      │────────────►│ RequiresReassignment│
         └────────┬───────────┘             └────────┬───────────┘
                  │                                  │
       ┌──────────┼──────────────┐          Reassign │ or
       │          │              │                   │ CancelDueToSystemTimeout
       ▼          ▼              ▼                   ▼
  Cancelled  LateCancellation  NoShow          Scheduled (new doctor)
                                               or Cancelled


  CheckedIn


  InProgress


  Completed
Normal path: Scheduled → CheckedIn → InProgress → Completed Cancellation paths:
  • Scheduled → Cancelled — cancelled within the specialty’s CancellationLimit notice window
  • Scheduled → LateCancellation — cancelled after the notice deadline, triggers a penalty
  • Scheduled → NoShow — patient did not arrive; marked by staff or the owning doctor
Disruption path:
  • Scheduled → RequiresReassignment — the doctor was suspended
  • RequiresReassignment → Scheduled — a new doctor and timeslot have been assigned (Reassign)
  • RequiresReassignment → Cancelled — the system timeout expired without reassignment (CancelDueToSystemTimeout), no patient penalty applied
Each appointment may be rescheduled at most once. The domain enforces RescheduleCount >= 1 as a hard stop — any subsequent reschedule attempt on the same appointment throws AppointmentReschedulingNotAllowedException. This applies to all actor roles including staff and doctors.

Rescheduling Rules

Rescheduling follows the same actor-specific rule structure as scheduling:
RulePatientDoctorStaff
Phone must be verified
Penalty block check
Doctor must own the appointment
Can overbook
Requires SchedulingClearance
Max 1 reschedule per appointment

Cancellation Rules

Cancellation is handled by AppointmentCancellationService:
  • Patient — must be the owner of the patient profile (targetPatient.UserId == initiatorUserId). Cannot cancel Procedure appointments. Emergency appointments can only be cancelled if the patient is Self or is a Child under 18 years old. The service checks MedicalSpecialty.IsCancellationAllowed(...) to decide whether to call Cancel (no penalty) or CancelLate (penalty).
  • Doctor — must be the assigned doctor (initiatorDoctorId == appointment.DoctorId). Always results in a regular Cancelled status.
  • Staff — can cancel any appointment but must provide a non-empty cancellation reason. Always results in a regular Cancelled status.

Build docs developers (and LLMs) love