Skip to content

Add scheduling algorithm #3062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@
"@tuturuuu/supabase": "workspace:*",
"@tuturuuu/utils": "workspace:*",
"ai": "^4.3.16",
"dayjs": "^1.11.13",
"eslint": "^9.28.0",
"next": "15.3.3",
"react": "^19.1.0",
Expand Down
1 change: 1 addition & 0 deletions packages/ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@tuturuuu/supabase": "workspace:*",
"@tuturuuu/utils": "workspace:*",
"ai": "^4.3.16",
"dayjs": "^1.11.13",
"eslint": "^9.28.0",
"next": "15.3.3",
"react": "^19.1.0",
Expand Down
184 changes: 184 additions & 0 deletions packages/ai/src/scheduling/algorithm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { defaultActiveHours, defaultTasks } from './algorithm';
import dayjs from 'dayjs';
import { describe, expect, it } from 'vitest';

describe('Scheduling Algorithm', () => {
describe('DateRange interface', () => {
it('should create valid date ranges', () => {
const start = dayjs('2024-01-01T09:00:00');
const end = dayjs('2024-01-01T17:00:00');

const range = { start, end };

expect(range.start.isValid()).toBe(true);
expect(range.end.isValid()).toBe(true);
expect(range.end.isAfter(range.start)).toBe(true);
});
});

describe('Event interface', () => {
it('should create valid events', () => {
const event = {
id: 'event-1',
name: 'Team Meeting',
range: {
start: dayjs('2024-01-01T10:00:00'),
end: dayjs('2024-01-01T11:00:00'),
},
};

expect(event.id).toBe('event-1');
expect(event.name).toBe('Team Meeting');
expect(event.range.start.isValid()).toBe(true);
expect(event.range.end.isValid()).toBe(true);
});
});

describe('Task interface', () => {
it('should create valid tasks', () => {
const task = {
id: 'task-1',
name: 'Complete project',
duration: 120, // 2 hours in minutes
events: [],
};

expect(task.id).toBe('task-1');
expect(task.name).toBe('Complete project');
expect(task.duration).toBe(120);
expect(Array.isArray(task.events)).toBe(true);
});

it('should create tasks with events', () => {
const task = {
id: 'task-2',
name: 'Review code',
duration: 60,
events: [
{
id: 'event-1',
name: 'Code review session',
range: {
start: dayjs('2024-01-01T14:00:00'),
end: dayjs('2024-01-01T15:00:00'),
},
},
],
};

expect(task.events).toHaveLength(1);
expect(task.events[0]?.name).toBe('Code review session');
});
});

describe('ActiveHours interface', () => {
it('should have valid structure for active hours', () => {
const activeHours = {
personal: [
{
start: dayjs().hour(7).minute(0).second(0).millisecond(0),
end: dayjs().hour(23).minute(0).second(0).millisecond(0),
},
],
work: [
{
start: dayjs().hour(9).minute(0).second(0).millisecond(0),
end: dayjs().hour(17).minute(0).second(0).millisecond(0),
},
],
meeting: [
{
start: dayjs().hour(9).minute(0).second(0).millisecond(0),
end: dayjs().hour(17).minute(0).second(0).millisecond(0),
},
],
};

expect(Array.isArray(activeHours.personal)).toBe(true);
expect(Array.isArray(activeHours.work)).toBe(true);
expect(Array.isArray(activeHours.meeting)).toBe(true);

expect(activeHours.personal[0]?.start.isValid()).toBe(true);
expect(activeHours.work[0]?.end.isAfter(activeHours.work[0]?.start)).toBe(
true
);
});
});

describe('DefaultActiveHours', () => {
it('should have correct default active hours configuration', () => {
expect(defaultActiveHours).toBeDefined();
expect(defaultActiveHours.personal).toHaveLength(1);
expect(defaultActiveHours.work).toHaveLength(1);
expect(defaultActiveHours.meeting).toHaveLength(1);

// Test personal hours (7:00 - 23:00)
expect(defaultActiveHours.personal[0]?.start.format('HH:mm')).toBe(
'07:00'
);
expect(defaultActiveHours.personal[0]?.end.format('HH:mm')).toBe('23:00');

// Test work hours (9:00 - 17:00)
expect(defaultActiveHours.work[0]?.start.format('HH:mm')).toBe('09:00');
expect(defaultActiveHours.work[0]?.end.format('HH:mm')).toBe('17:00');

// Test meeting hours (9:00 - 17:00)
expect(defaultActiveHours.meeting[0]?.start.format('HH:mm')).toBe(
'09:00'
);
expect(defaultActiveHours.meeting[0]?.end.format('HH:mm')).toBe('17:00');
});
});

describe('DefaultTasks', () => {
it('should have correct default tasks configuration', () => {
expect(defaultTasks).toBeDefined();
expect(Array.isArray(defaultTasks)).toBe(true);
expect(defaultTasks).toHaveLength(1);
});

it('should have valid task structure', () => {
const task = defaultTasks[0];

expect(task).toBeDefined();
expect(task?.id).toBe('task-1');
expect(task?.name).toBe('Task 1');
expect(task?.duration).toBe(1);
expect(Array.isArray(task?.events)).toBe(true);
expect(task?.events).toHaveLength(0);
});

it('should have valid task properties types', () => {
const task = defaultTasks[0];

expect(typeof task?.id).toBe('string');
expect(typeof task?.name).toBe('string');
expect(typeof task?.duration).toBe('number');
expect(Array.isArray(task?.events)).toBe(true);
});

it('should have positive duration', () => {
const task = defaultTasks[0];

expect(task?.duration).toBeGreaterThan(0);
});

it('should have non-empty id and name', () => {
const task = defaultTasks[0];

expect(task?.id).toBeTruthy();
expect(task?.name).toBeTruthy();
expect(task?.id.length).toBeGreaterThan(0);
expect(task?.name.length).toBeGreaterThan(0);
});
});

// TODO: Add tests for the schedule function once it's implemented

Check notice on line 176 in packages/ai/src/scheduling/algorithm.test.ts

View check run for this annotation

codefactor.io / CodeFactor

packages/ai/src/scheduling/algorithm.test.ts#L176

Unresolved 'todo' comment. (eslint/no-warning-comments)
describe('schedule function', () => {
it.todo('should schedule tasks without conflicts');
it.todo('should respect active hours constraints');
it.todo('should handle overlapping events');
it.todo('should return empty schedule for no tasks');
it.todo('should prioritize tasks based on duration');
});
});
57 changes: 57 additions & 0 deletions packages/ai/src/scheduling/algorithm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import dayjs from 'dayjs';

interface DateRange {
start: dayjs.Dayjs;
end: dayjs.Dayjs;
}

interface Event {
id: string;
name: string;
range: DateRange;
}

interface Task {
id: string;
name: string;
duration: number;
events: Event[];
}

interface ActiveHours {
personal: DateRange[];
work: DateRange[];
meeting: DateRange[];
}

export const defaultActiveHours: ActiveHours = {
personal: [
{
start: dayjs().hour(7).minute(0).second(0).millisecond(0),
end: dayjs().hour(23).minute(0).second(0).millisecond(0),
},
],
work: [
{
start: dayjs().hour(9).minute(0).second(0).millisecond(0),
end: dayjs().hour(17).minute(0).second(0).millisecond(0),
},
],
meeting: [
{
start: dayjs().hour(9).minute(0).second(0).millisecond(0),
end: dayjs().hour(17).minute(0).second(0).millisecond(0),
},
],
};

export const defaultTasks: Task[] = [
{
id: 'task-1',
name: 'Task 1',
duration: 1,
events: [],
},
];

// export const schedule = (events: Event[], tasks: Task[]) => {};