Skip to content
This repository was archived by the owner on Jul 23, 2024. It is now read-only.

Commit 36a19c8

Browse files
pidekjuniorjirutka
andcommitted
Implement planning of timetable slots with specified weeks
Co-Authored-By: Jakub Jirutka <[email protected]>
1 parent 7213208 commit 36a19c8

File tree

9 files changed

+134
-24
lines changed

9 files changed

+134
-24
lines changed

app/roles/planned_semester_period.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class PlannedSemesterPeriod < RolePlaying::Role
99

1010
def plan(teaching_time)
1111
scheduling_start = combine_date_with_time(
12-
schedule_start_day(teaching_time.parity),
12+
schedule_start_day(teaching_time),
1313
teaching_time.start_time
1414
)
1515

@@ -33,8 +33,11 @@ def combine_date_with_time(date, time)
3333
Time.new(date.year, date.month, date.day, time.hour, time.min, time.sec)
3434
end
3535

36-
def schedule_start_day(teaching_time_parity)
37-
if teaching_time_parity == 'both' || teaching_time_parity == first_week_parity
36+
def schedule_start_day(teaching_time)
37+
if teaching_time.start_date
38+
# Teaching time is defined only in some time interval.
39+
[starts_at, teaching_time.start_date].max
40+
elsif teaching_time.parity == 'both' || teaching_time.parity == first_week_parity
3841
starts_at
3942
else
4043
starts_at.next_week(:monday)

app/roles/planned_timetable_slot.rb

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ def initialize(obj, time_converter)
88
@time_converter = time_converter
99
end
1010

11-
def generate_events(faculty_semester, semester_period)
12-
teaching_time = generate_teaching_time
13-
event_periods = semester_period.plan(teaching_time)
11+
def generate_events(faculty_semester, semester_period, weeks_dates)
12+
teaching_times = generate_teaching_times(weeks_dates)
13+
# Count event periods in current semester period in all week intervals.
14+
event_periods = teaching_times.flat_map do |teaching_time|
15+
semester_period.plan(teaching_time)
16+
end
1417
create_events(event_periods, faculty_semester).tap do |events|
1518
events.map { |e| e.deleted = !!deleted_at }
1619
end
@@ -32,21 +35,41 @@ def day
3235
end
3336

3437
private
38+
3539
attr_reader :time_converter
3640

3741
def filter_extra_events(all_events, planned_events)
3842
planned_event_ids = planned_events.map(&:id)
3943
all_events.find_all { |evt| !planned_event_ids.include?(evt.id) }
4044
end
4145

42-
def generate_teaching_time
43-
teaching_period = if start_time && end_time
44-
# Timetable slot has start time and end time already specified.
45-
Period.new(start_time, end_time)
46-
else
47-
time_converter.convert_time(first_hour, duration)
46+
def generate_teaching_times(weeks_dates)
47+
teaching_period = generate_teaching_period
48+
49+
if weeks.blank?
50+
# Timetable slot with even/odd week parity.
51+
[Sirius::TeachingTime.new(teaching_period: teaching_period, day: day, parity: parity)]
52+
else
53+
# Timetable slot with specified weeks.
54+
weeks_intervals.map do |first, last|
55+
start_date = weeks_dates[first - 1].first
56+
end_date = weeks_dates[last - 1].last
57+
Sirius::TeachingTime.new(teaching_period: teaching_period, day: day, start_date: start_date, end_date: end_date)
4858
end
49-
Sirius::TeachingTime.new(teaching_period: teaching_period, day: day, parity: parity)
59+
end
60+
end
61+
62+
def generate_teaching_period
63+
if start_time && end_time
64+
# Timetable slot has start time and end time already specified.
65+
Period.new(start_time, end_time)
66+
else
67+
time_converter.convert_time(first_hour, duration)
68+
end
69+
end
70+
71+
def weeks_intervals
72+
weeks.slice_when { |i, j| i + 1 != j }.map(&:minmax)
5073
end
5174

5275
def create_events(event_periods, faculty_semester)

lib/actors/teacher_timetable_slot_transformer.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'sirius/time_converter'
55
require 'roles/planned_timetable_slot'
66
require 'day'
7+
require 'sirius/faculty_semester_weeks_generator'
78

89
# A convertor which receives TeacherTimetableSlots loaded from KOSapi and plans them into
910
# Events according to semester parameters and semester periods.
@@ -16,6 +17,7 @@ def initialize(input, output, semester)
1617
self.input = input
1718
self.output = output
1819
@semester = semester
20+
@semester_weeks_dates = Sirius::FacultySemesterWeeksGenerator.generate_semester_weeks_dates(@semester)
1921
@events = nil
2022
end
2123

@@ -58,7 +60,7 @@ def plan_events(slot, teacher)
5860
end
5961

6062
events = periods.flat_map do |period|
61-
PlannedTimetableSlot.new(slot, time_converter).generate_events(@semester, period)
63+
PlannedTimetableSlot.new(slot, time_converter).generate_events(@semester, period, @semester_weeks_dates)
6264
end
6365

6466
events.each_with_index do |e, i|

lib/sirius/event_planner.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'models/parallel'
44
require 'sirius/time_converter'
55
require 'roles/planned_semester_period'
6+
require 'sirius/faculty_semester_weeks_generator'
67

78
module Sirius
89
class EventPlanner
@@ -14,10 +15,13 @@ def initialize
1415

1516
def plan_semester(semester)
1617
time_converter, semester_periods = create_converters(semester)
18+
19+
weeks_dates = Sirius::FacultySemesterWeeksGenerator.generate_semester_weeks_dates(semester)
20+
1721
slots_dataset(semester).flat_map do |sl|
1822
slot = PlannedTimetableSlot.new(sl, time_converter)
1923
events = semester_periods.flat_map do |semester_period|
20-
slot.generate_events(semester, semester_period)
24+
slot.generate_events(semester, semester_period, weeks_dates)
2125
end
2226
number_events(events)
2327
apply_exceptions(events)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
require 'sirius_api/semester_week'
2+
3+
module Sirius
4+
module FacultySemesterWeeksGenerator
5+
6+
module_function
7+
8+
def generate_semester_weeks_dates(semester)
9+
SiriusApi::SemesterWeek
10+
.resolve_weeks(semester, from: semester.starts_at, to: semester.teaching_ends_at)
11+
.select(&:teaching_week)
12+
.map { |week| [week.start_date, week.end_date]}
13+
end
14+
end
15+
end

lib/sirius/teaching_time.rb

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
module Sirius
44
class TeachingTime
55

6-
attr_reader :teaching_period, :day
6+
attr_reader :teaching_period, :day, :start_date, :end_date
77
attr_accessor :parity
88

9-
def initialize(teaching_period:, day:, parity:)
9+
def initialize(teaching_period:, day:, parity: nil, start_date: nil, end_date: nil)
1010
@teaching_period = teaching_period
1111
@day = day
1212
@parity = parity
13+
@start_date = start_date
14+
@end_date = end_date
1315
end
1416

1517
def start_time
@@ -25,7 +27,11 @@ def duration
2527
end
2628

2729
def ==(other)
28-
@teaching_period == other.teaching_period && @day == other.day && @parity == other.parity
30+
@teaching_period == other.teaching_period &&
31+
@day == other.day &&
32+
@parity == other.parity &&
33+
@start_date == other.start_date &&
34+
@end_date == other.end_date
2935
end
3036

3137
def numeric_day
@@ -34,7 +40,15 @@ def numeric_day
3440

3541
def to_recurrence_rule(day_offset, schedule_ends_at)
3642
teaching_day = (numeric_day + day_offset) % 7
37-
IceCube::Rule.weekly(week_frequency, :monday).day(teaching_day).until(schedule_ends_at)
43+
until_date = if @end_date && @end_date < schedule_ends_at
44+
# The current teaching time is defined in some time interval.
45+
@end_date
46+
else
47+
# The current teaching time is defined in whole semester -> whole scheduling period.
48+
schedule_ends_at
49+
end
50+
51+
IceCube::Rule.weekly(week_frequency, :monday).day(teaching_day).until(until_date)
3852
end
3953

4054
def week_frequency

spec/fabricators/timetable_slot_fabricator.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@
1717
room
1818
parallel
1919
end
20+
21+
Fabricator(:timetable_slot_with_weeks, from: :timetable_slot_with_times) do
22+
parity nil
23+
weeks [1, 3]
24+
end

spec/lib/sirius/teaching_time_spec.rb

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88

99
subject(:teaching_time) do
1010
described_class.new(teaching_period: Period.parse('14:30', '16:00'),
11-
parity: parity, day: input_day)
11+
parity: parity, day: input_day, start_date: start_date, end_date: end_date)
1212
end
1313
let(:parity) { 'odd' }
1414
let(:input_day) { :tuesday }
15+
let(:start_date) { nil }
16+
let(:end_date) { nil }
1517

1618
describe '#week_frequency' do
1719
where :parity, :week_frequency do
@@ -34,7 +36,7 @@
3436
end
3537

3638
describe '#to_recurrence_rule' do
37-
let(:ends_at) { DateTime.parse('2015-11-11') }
39+
let(:ends_at) { DateTime.parse('2020-03-11') }
3840

3941
it 'returns weekly recurrence rule' do
4042
expect(subject.to_recurrence_rule(0, ends_at)).to be_an_instance_of IceCube::WeeklyRule
@@ -61,5 +63,25 @@
6163
expect(rule.validations[:day].first.day).to eq result_day
6264
end
6365
end
66+
67+
context 'when start/end dates were not provided' do
68+
it 'returns recurrence rule ending at the given ends_at date' do
69+
expect(
70+
subject.to_recurrence_rule(0, ends_at).until_time.strftime("%F")
71+
).to eq ends_at.strftime("%F")
72+
end
73+
end
74+
75+
context 'when start/end dates were provided' do
76+
let(:period) { nil }
77+
let(:start_date) { Date.parse('2020-02-20') }
78+
let(:end_date) { Date.parse('2020-02-26') }
79+
80+
it 'returns recurrence rule ending at the end_date' do
81+
expect(
82+
subject.to_recurrence_rule(0, ends_at).until_time.strftime("%F")
83+
).to eq end_date.strftime("%F")
84+
end
85+
end
6486
end
6587
end

spec/roles/planned_timetable_slot_spec.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,32 @@
1010
let(:converter) { Sirius::TimeConverter.new(hour_starts: faculty_semester.hour_starts, hour_length: faculty_semester.hour_duration) }
1111
let(:semester_calendar) { PlannedSemesterPeriod.new(Fabricate(:teaching_semester_period)) }
1212
let(:faculty_semester) { Fabricate.build(:faculty_semester) }
13+
let(:weeks_dates) {[
14+
[Date.parse('2014-09-22'), Date.parse('2014-09-28')],
15+
[Date.parse('2014-09-29'), Date.parse('2014-10-05')],
16+
[Date.parse('2014-10-06'), Date.parse('2014-10-12')],
17+
]}
1318
subject(:planned_slot) { described_class.new(slot, converter) }
1419

1520
describe '#generate_events' do
1621

1722
it 'converts TimetableSlot to events' do
18-
events = planned_slot.generate_events(faculty_semester, semester_calendar)
23+
events = planned_slot.generate_events(faculty_semester, semester_calendar, weeks_dates)
1924
expect(events.size).to eq 13
2025
expect(events.first).to be_an_instance_of(Event)
2126
end
2227

2328
it 'sets deleted flag for deleted timetable slots' do
2429
slot.deleted_at = Time.now
25-
events = planned_slot.generate_events(faculty_semester, semester_calendar)
30+
events = planned_slot.generate_events(faculty_semester, semester_calendar, weeks_dates)
2631
expect(events.first.deleted).to be_truthy
2732
end
2833

2934
context 'TimetableSlot with start_time and end_time' do
3035
let(:slot) { Fabricate(:timetable_slot_with_times, parity: 'even') }
3136

3237
it 'converts TimetableSlot to events with the specified times' do
33-
events = planned_slot.generate_events(faculty_semester, semester_calendar)
38+
events = planned_slot.generate_events(faculty_semester, semester_calendar, weeks_dates)
3439

3540
expect(events.size).to eq 6
3641
expect(events.first).to be_an_instance_of(Event)
@@ -39,6 +44,23 @@
3944
end
4045
end
4146

47+
context 'TimetableSlot with weeks' do
48+
let(:slot) { Fabricate(:timetable_slot_with_weeks, weeks: weeks) }
49+
let(:weeks) { [1, 3] }
50+
51+
it 'converts TimetableSlot to events in the specified weeks only' do
52+
events = planned_slot.generate_events(faculty_semester, semester_calendar, weeks_dates)
53+
54+
expect(events.size).to eq weeks.size
55+
expect(events[0]).to be_an_instance_of(Event)
56+
57+
events.each_with_index do |event, i|
58+
expect(event.starts_at.strftime('%F')).to eq weeks_dates[weeks[i] - 1].first.strftime('%F')
59+
expect(event.starts_at.strftime('%H:%M')).to eq slot.start_time.strftime('%H:%M')
60+
end
61+
end
62+
end
63+
4264
end
4365

4466
describe '#clear_extra_events' do

0 commit comments

Comments
 (0)