My App
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