HFU Digital Docs
CourseKitBackend

ConflictService

Constraint pipeline for detecting scheduling conflicts

Overview

ConflictService runs all registered constraints against materialized schedule occurrences to detect conflicts. It supports both live checks and dry-run previews.

import { ConflictService } from '@hfu.digital/coursekit-nestjs';

Methods

check

Check a single event against all registered constraints within a date range.

async check(event: TimetableEvent, dateRange: DateRange): Promise<ConflictCheckResult>
ParameterTypeDescription
eventTimetableEventThe event to check
dateRangeDateRangeThe date range to evaluate

How it works:

  1. Fetches all events in the date range from storage
  2. Materializes all events (expanding RRULE + exceptions) into occurrences
  3. Builds a ConstraintContext with lookup helpers
  4. Evaluates every registered ScheduleConstraint
  5. Collects all Conflict results
  6. Emits DOMAIN_EVENTS.CONFLICT_DETECTED if any conflicts are found
const result = await conflicts.check(event, {
    start: new Date('2026-03-01'),
    end: new Date('2026-03-31'),
});

if (result.hasErrors) {
    console.log('Blocking conflicts:', result.conflicts.filter(c => c.severity === 'error'));
}

dryRun

Preview what conflicts a proposed event would create without persisting it.

async dryRun(
    proposedEvent: Omit<TimetableEvent, 'id' | 'createdAt' | 'updatedAt' | 'version'>,
    dateRange: DateRange,
): Promise<ConflictCheckResult>
ParameterTypeDescription
proposedEventpartial TimetableEventThe proposed event data
dateRangeDateRangeThe date range to evaluate

The proposed event is assigned a temporary ID and added to the existing schedule for evaluation. No data is written to storage and no domain events are emitted.

const result = await conflicts.dryRun(
    {
        title: 'Math 101',
        startTime: new Date('2026-03-02T09:00:00'),
        durationMin: 90,
        recurrenceRule: 'FREQ=WEEKLY;BYDAY=MO',
        roomId: 'room-a1',
        courseId: null,
        periodId: null,
        metadata: null,
    },
    { start: new Date('2026-03-01'), end: new Date('2026-06-30') },
);

ConflictCheckResult

interface ConflictCheckResult {
    hasErrors: boolean;     // true if any conflict has severity 'error'
    hasWarnings: boolean;   // true if any conflict has severity 'warning'
    conflicts: Conflict[];  // all detected violations
}

Constraint Pipeline Flow

MaterializedOccurrence[]

  ├── OverlapConstraint.evaluate()    → Conflict[] (instructor/room overlaps)
  ├── CapacityConstraint.evaluate()   → Conflict[] (room over-capacity)
  ├── AvailabilityConstraint.evaluate() → Conflict[] (blocked time windows)
  └── ...custom constraints...


ConflictCheckResult { hasErrors, hasWarnings, conflicts }

On this page