Scheduling Profile
Overview
A Scheduling Profile is a declarative configuration that defines which rounds from which events should be scheduled on specific dates and at specific venues. It serves as the blueprint for automated multi-day tournament scheduling, allowing the Garman scheduling algorithm to process rounds across multiple events in a coordinated manner.
Key Benefits:
- Multi-Event Coordination: Schedule rounds from singles, doubles, and team events together
- Venue-Specific Planning: Assign different events/rounds to different venues
- Day-by-Day Control: Define exactly what gets scheduled each day
- Automated Execution: Use
scheduleProfileRounds()to automatically schedule all configured rounds - Persistent Configuration: Stored as tournament extension, survives save/load cycles
Data Structure
A scheduling profile is an array of date-specific configurations, each containing venue-specific round assignments.
Profile Structure
type SchedulingProfile = ScheduleDateProfile[];
type ScheduleDateProfile = {
scheduleDate: string; // ISO date string 'YYYY-MM-DD'
venues: VenueProfile[]; // Venue assignments for this date
};
type VenueProfile = {
venueId: string; // UUID of venue
rounds: RoundProfile[]; // Rounds to schedule at this venue
};
type RoundProfile = {
// Required identifiers
tournamentId: string; // Tournament UUID
eventId: string; // Event UUID
drawId: string; // Draw UUID
structureId: string; // Structure UUID
roundNumber: number; // Round number (1-based)
// Optional scheduling controls
notBeforeTime?: string; // 'HH:MM' - earliest start time for this round
roundSegment?: {
segmentNumber: number; // Which segment (1-based)
segmentsCount: number; // Total segments in round
};
// Optional display attributes (not used by scheduling engine)
eventName?: string; // Display name
roundName?: string; // Display name (e.g., 'R16', 'QF', 'SF')
sortOrder?: number; // Display order
};
Creating a Scheduling Profile
Manual Creation
Build the profile structure manually for complete control:
import { tournamentEngine } from 'tods-competition-factory';
// Define profile for 3-day tournament
const schedulingProfile = [
// Day 1: First rounds of multiple events
{
scheduleDate: '2024-06-15',
venues: [
{
venueId: 'venue-main-courts',
rounds: [
{
tournamentId: 'tournament-123',
eventId: 'event-singles-u18',
drawId: 'draw-singles-u18',
structureId: 'structure-main',
roundNumber: 1,
notBeforeTime: '09:00'
},
{
tournamentId: 'tournament-123',
eventId: 'event-doubles-u18',
drawId: 'draw-doubles-u18',
structureId: 'structure-main',
roundNumber: 1,
notBeforeTime: '11:00'
}
]
}
]
},
// Day 2: Later rounds
{
scheduleDate: '2024-06-16',
venues: [
{
venueId: 'venue-main-courts',
rounds: [
{
tournamentId: 'tournament-123',
eventId: 'event-singles-u18',
drawId: 'draw-singles-u18',
structureId: 'structure-main',
roundNumber: 2 // R16
},
{
tournamentId: 'tournament-123',
eventId: 'event-singles-u18',
drawId: 'draw-singles-u18',
structureId: 'structure-main',
roundNumber: 3 // QF
}
]
}
]
},
// Day 3: Finals
{
scheduleDate: '2024-06-17',
venues: [
{
venueId: 'venue-center-court',
rounds: [
{
tournamentId: 'tournament-123',
eventId: 'event-singles-u18',
drawId: 'draw-singles-u18',
structureId: 'structure-main',
roundNumber: 4, // SF
notBeforeTime: '10:00'
},
{
tournamentId: 'tournament-123',
eventId: 'event-singles-u18',
drawId: 'draw-singles-u18',
structureId: 'structure-main',
roundNumber: 5, // Final
notBeforeTime: '14:00'
}
]
}
]
}
];
// Attach to tournament
tournamentEngine.setState(tournamentRecord);
const result = tournamentEngine.setSchedulingProfile({ schedulingProfile });
if (result.success) {
console.log('Scheduling profile configured');
}
Programmatic Generation
Build profiles dynamically based on tournament structure:
// Get all events and their draw structures
const { events } = tournamentEngine.getEvents();
const schedulingProfile = [];
const scheduleDates = ['2024-06-15', '2024-06-16', '2024-06-17'];
const venueId = 'venue-main';
// Assign rounds to dates
scheduleDates.forEach((scheduleDate, dayIndex) => {
const rounds = [];
events.forEach(event => {
event.drawDefinitions?.forEach(draw => {
draw.structures?.forEach(structure => {
const maxRound = structure.matchUps?.reduce((max, mu) =>
Math.max(max, mu.roundNumber), 0
);
// Schedule specific rounds on specific days
if (dayIndex === 0 && [1].includes(structure.roundNumber)) {
// Day 1: Round 1
rounds.push({
tournamentId: event.tournamentId,
eventId: event.eventId,
drawId: draw.drawId,
structureId: structure.structureId,
roundNumber: 1,
eventName: event.eventName
});
} else if (dayIndex === 1 && maxRound >= 2) {
// Day 2: Round 2
rounds.push({
tournamentId: event.tournamentId,
eventId: event.eventId,
drawId: draw.drawId,
structureId: structure.structureId,
roundNumber: 2,
eventName: event.eventName
});
}
// ... more logic
});
});
});
if (rounds.length > 0) {
schedulingProfile.push({
scheduleDate,
venues: [{ venueId, rounds }]
});
}
});
tournamentEngine.setSchedulingProfile({ schedulingProfile });
Using Scheduling Profiles
Automated Scheduling
Once a profile is configured, use scheduleProfileRounds() to automatically schedule all defined rounds:
// Schedule all rounds defined in the profile
const result = tournamentEngine.scheduleProfileRounds({
periodLength: 30, // Scheduling block size in minutes (see note below)
checkPotentialRequestConflicts: true // Consider potential matchUp conflicts
});
// Handle results
const {
scheduledDates, // Array of dates where matches were scheduled
scheduledMatchUpIds, // All successfully scheduled matchUpIds
noTimeMatchUpIds, // MatchUps that couldn't be fit into available time
overLimitMatchUpIds, // MatchUps blocked by daily limits
requestConflicts // Array of { date, conflicts } for participant conflicts
} = result;
console.log(`Scheduled ${scheduledMatchUpIds.length} matches`);
console.log(`${noTimeMatchUpIds.length} matches need more court time`);
console.log(`${overLimitMatchUpIds.length} matches blocked by daily limits`);
periodLength defines the scheduling block size in minutes (default: 30). It controls:
- Time slot granularity: Creates time blocks at regular intervals (e.g., 30 minutes = 9:00, 9:30, 10:00, ...)
- Match grouping: Groups matches by duration relative to block size for efficient scheduling
- Court utilization: Smaller blocks (15 min) = finer control; larger blocks (60 min) = simpler but may waste time
Recommended values:
- 15 minutes: Short format tournaments (Fast4, no-ad)
- 30 minutes: Standard tournaments (best of 3 sets) ← Most common
- 60 minutes: Long format events (best of 5 sets)
See Period Length documentation for detailed explanation.
Partial Scheduling
Schedule specific dates from the profile:
// Schedule only specific dates
const result = tournamentEngine.scheduleProfileRounds({
scheduleDates: ['2024-06-15', '2024-06-16'], // Only these dates
periodLength: 30
});
Clear and Reschedule
Clear existing schedules before applying profile:
// Clear specific dates
const result = tournamentEngine.scheduleProfileRounds({
clearScheduleDates: ['2024-06-15'], // Clear these dates first
scheduleDates: ['2024-06-15'], // Then reschedule
periodLength: 30
});
// Clear ALL dates in profile
const result = tournamentEngine.scheduleProfileRounds({
clearScheduleDates: true, // Clear all dates in profile
periodLength: 30
});
Dry Run
Preview scheduling without making changes:
const result = tournamentEngine.scheduleProfileRounds({
dryRun: true, // Don't actually schedule, just report what would happen
periodLength: 30
});
console.log('Would schedule:', result.scheduledMatchUpIds.length);
console.log('Would have conflicts:', result.requestConflicts.length);
Validation
Validate a profile before applying it:
const { valid, error } = tournamentEngine.validateSchedulingProfile({
schedulingProfile
});
if (!valid) {
console.error('Invalid profile:', error);
} else {
console.log('Profile is valid');
tournamentEngine.setSchedulingProfile({ schedulingProfile });
}
Validation Checks:
- All required fields present (
scheduleDate,venueId,tournamentId,eventId,drawId,structureId,roundNumber) - Valid date formats
- Referenced IDs exist in tournament
- Round numbers valid for their structures
- No duplicate round assignments on same date
Round Segmentation
For large rounds, split scheduling across multiple sessions:
{
scheduleDate: '2024-06-15',
venues: [{
venueId: 'venue-main',
rounds: [
// Morning session: First half of R32
{
eventId: 'event-singles',
drawId: 'draw-main',
structureId: 'structure-main',
roundNumber: 1,
notBeforeTime: '09:00',
roundSegment: {
segmentNumber: 1, // First segment
segmentsCount: 2 // Split into 2 segments
}
},
// Afternoon session: Second half of R32
{
eventId: 'event-singles',
drawId: 'draw-main',
structureId: 'structure-main',
roundNumber: 1,
notBeforeTime: '14:00',
roundSegment: {
segmentNumber: 2, // Second segment
segmentsCount: 2
}
}
]
}]
}
Multi-Venue Scheduling
Assign different events to different venues on the same day:
{
scheduleDate: '2024-06-15',
venues: [
{
venueId: 'venue-center-court',
rounds: [
// Feature matches on center court
{
eventId: 'event-singles-championship',
drawId: 'draw-main',
structureId: 'structure-main',
roundNumber: 4, // Semifinals
notBeforeTime: '13:00'
}
]
},
{
venueId: 'venue-outside-courts',
rounds: [
// Earlier rounds on outside courts
{
eventId: 'event-singles-u16',
drawId: 'draw-main',
structureId: 'structure-main',
roundNumber: 1
},
{
eventId: 'event-doubles-u16',
drawId: 'draw-main',
structureId: 'structure-main',
roundNumber: 1
}
]
}
]
}
Best Practices
1. Logical Progression
Order rounds logically within each day:
// Good: Logical progression
rounds: [
{ roundNumber: 1, eventName: 'U18 Singles' },
{ roundNumber: 2, eventName: 'U18 Singles' },
{ roundNumber: 1, eventName: 'U18 Doubles' }
]
// Avoid: Random ordering
rounds: [
{ roundNumber: 2, eventName: 'U18 Singles' },
{ roundNumber: 1, eventName: 'U18 Doubles' },
{ roundNumber: 1, eventName: 'U18 Singles' }
]
2. Use notBeforeTime
Prevent early rounds from delaying featured matches:
{
scheduleDate: '2024-06-17',
venues: [{
venueId: 'venue-center-court',
rounds: [
{
roundNumber: 4, // Semifinals
notBeforeTime: '11:00' // Morning session
},
{
roundNumber: 5, // Final
notBeforeTime: '15:00' // Afternoon feature
}
]
}]
}
3. Balance Court Load
Distribute matches across available venues:
// Check court availability before assigning
const { venues } = tournamentEngine.getVenues();
venues.forEach(venue => {
const courtCount = venue.courts?.length || 0;
const availableHours = 8; // Court availability hours
const matchesPerCourt = 6; // Estimate
const capacity = courtCount * matchesPerCourt;
console.log(`${venue.venueName}: ${capacity} matches/day capacity`);
});
4. Consider Dependencies
Schedule rounds in order that respects match dependencies:
// Day 1: Round 1 must complete before Round 2 can start
{
scheduleDate: '2024-06-15',
venues: [{
venueId: 'venue-main',
rounds: [
{ roundNumber: 1 } // R32
]
}]
},
// Day 2: Round 2 only after Round 1 winners determined
{
scheduleDate: '2024-06-16',
venues: [{
venueId: 'venue-main',
rounds: [
{ roundNumber: 2 } // R16 - depends on R32 completion
]
}]
}
Common Scenarios
Weekend Tournament
Two-day local tournament:
const schedulingProfile = [
{
scheduleDate: '2024-06-15', // Saturday
venues: [{
venueId: 'local-club',
rounds: [
{ eventId: 'singles-open', roundNumber: 1 },
{ eventId: 'singles-open', roundNumber: 2 },
{ eventId: 'doubles-open', roundNumber: 1 }
]
}]
},
{
scheduleDate: '2024-06-16', // Sunday
venues: [{
venueId: 'local-club',
rounds: [
{ eventId: 'singles-open', roundNumber: 3 }, // SF
{ eventId: 'singles-open', roundNumber: 4, notBeforeTime: '13:00' }, // Final
{ eventId: 'doubles-open', roundNumber: 2 }, // SF
{ eventId: 'doubles-open', roundNumber: 3, notBeforeTime: '15:00' } // Final
]
}]
}
];
Championship Week
Multi-venue, multi-event championship:
const schedulingProfile = [
// Days 1-3: Qualifying and early rounds
...['2024-07-01', '2024-07-02', '2024-07-03'].map(date => ({
scheduleDate: date,
venues: [
{
venueId: 'qualifying-venue',
rounds: qualifyingRounds
},
{
venueId: 'main-venue',
rounds: earlyMainDrawRounds
}
]
})),
// Days 4-5: Later rounds at main venue only
...['2024-07-04', '2024-07-05'].map(date => ({
scheduleDate: date,
venues: [{
venueId: 'main-venue',
rounds: laterRounds
}]
})),
// Day 6: Finals on center court
{
scheduleDate: '2024-07-06',
venues: [{
venueId: 'center-court',
rounds: finalsRounds
}]
}
];
Multi-Event Youth Tournament
Age-group tournament with staggered schedules:
const ageGroups = ['U12', 'U14', 'U16', 'U18'];
const schedulingProfile = [];
// Spread age groups across 4 days
ageGroups.forEach((ageGroup, dayIndex) => {
const date = addDays('2024-08-01', dayIndex);
schedulingProfile.push({
scheduleDate: date,
venues: [{
venueId: 'youth-center',
rounds: [
{
eventId: `singles-${ageGroup}`,
drawId: `draw-singles-${ageGroup}`,
structureId: 'main',
roundNumber: 1,
eventName: `${ageGroup} Singles Round 1`
},
{
eventId: `doubles-${ageGroup}`,
drawId: `draw-doubles-${ageGroup}`,
structureId: 'main',
roundNumber: 1,
eventName: `${ageGroup} Doubles Round 1`
}
]
}]
});
});
Retrieving Scheduling Profile
Get the current profile from tournament record:
// Get as extension
const { extension } = tournamentEngine.findExtension({
name: 'schedulingProfile'
});
const schedulingProfile = extension?.value;
// Or query specific information
schedulingProfile.forEach(({ scheduleDate, venues }) => {
console.log(`Date: ${scheduleDate}`);
venues.forEach(({ venueId, rounds }) => {
console.log(` Venue: ${venueId}`);
console.log(` Rounds: ${rounds.length}`);
});
});
Integration with Other Scheduling Features
Combined with Scheduling Policy
Profiles work alongside scheduling policies:
// Set policy for recovery times and daily limits
tournamentEngine.attachPolicies({
policyDefinitions: POLICY_SCHEDULING_DEFAULT
});
tournamentEngine.setMatchUpDailyLimits({
dailyLimits: { SINGLES: 2, DOUBLES: 1, total: 3 }
});
// Then use profile for round-by-round scheduling
tournamentEngine.setSchedulingProfile({ schedulingProfile });
tournamentEngine.scheduleProfileRounds({ periodLength: 30 });
// Garman scheduling will respect policy limits
Pro Scheduling Mode
For professional tournaments with grid scheduling:
const result = tournamentEngine.scheduleProfileRounds({
pro: true, // Use grid scheduling instead of Garman
periodLength: 30
});
// Uses court-specific grid placement
Related Documentation
- Scheduling Overview - Understanding scheduling workflows
- Automated Scheduling - Garman formula details
- Scheduling Policy - Recovery times and daily limits
- Venues and Courts - Venue configuration
- Schedule Governor - API reference for
scheduleProfileRounds()