pacing 1.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +54 -0
- data/lib/pacing/error.rb +75 -0
- data/lib/pacing/normalizer.rb +210 -0
- data/lib/pacing/pacer.rb +57 -152
- data/lib/pacing/version.rb +1 -1
- data/lib/pacing.rb +2 -0
- data/spec/pacing_spec.rb +377 -135
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbc2189e33671c8a34eccd7aa71ec4f93d48fe0228e4931655bda84c519cc1d8
|
4
|
+
data.tar.gz: a1808e5be13be267c73b2720fabafbfe9e379029a209a86f5b19921c515497f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f320cddb2b0a54b1f834c4a8a98fb496f6d7348c848525c26fdd52e22eaec8312f083df2b9378ca0bb2794480f6d6c3e8b65239221bdd27cf5463ac5bb8f2080
|
7
|
+
data.tar.gz: 7a6d01438e8ab61f2bec040cd2ddbdd14fda5b832d93324678ce0852fb10e34cd9a8d608b4c58fc89987d974c40a9251abbc4b9a25bb6c50b0c46a68e6688055
|
data/README.md
CHANGED
@@ -116,8 +116,62 @@ paced.calculate
|
|
116
116
|
}
|
117
117
|
]
|
118
118
|
=end
|
119
|
+
|
120
|
+
# Optionally if we want to include the frequency of the discipline in the pacing output we can
|
121
|
+
# pass in the optional param `show_frequency`. This value defaults to false.
|
122
|
+
|
123
|
+
paced = Pacing::Pacer.new(school_plan: school_plan, date: date, non_business_days: non_business_days, mode: :liberal, summer_holidays: summer_holidays, state: state, show_frequency: true)
|
124
|
+
paced.calculate
|
125
|
+
|
126
|
+
# Below is the output we will get when in strict mode and also showing the frequency in the pacing output.
|
127
|
+
=begin
|
128
|
+
=> [
|
129
|
+
{
|
130
|
+
discipline: 'Speech Therapy',
|
131
|
+
remaining_visits: 0,
|
132
|
+
used_visits: 7,
|
133
|
+
expected_visits_at_date: 3,
|
134
|
+
reset_date: '05-11-2022',
|
135
|
+
pace: 4,
|
136
|
+
pace_indicator: "🐇",
|
137
|
+
pace_suggestion: "less than once per week".
|
138
|
+
frequency: "6Mx30ST"
|
139
|
+
}, {
|
140
|
+
discipline: 'Physical Therapy',
|
141
|
+
remaining_visits: 0,
|
142
|
+
expected_visits_at_date: 3,
|
143
|
+
used_visits: 7,
|
144
|
+
reset_date: '05-11-2022',
|
145
|
+
pace: 4,
|
146
|
+
pace_indicator: "🐇",
|
147
|
+
pace_suggestion: "less than once per week",
|
148
|
+
frequency: "6Mx30PT"
|
149
|
+
}
|
150
|
+
]
|
151
|
+
=end
|
152
|
+
|
153
|
+
paced.interval # Return current interval start and end dates
|
154
|
+
|
155
|
+
# Below is the result you will get
|
156
|
+
=begin
|
157
|
+
=> [
|
158
|
+
{
|
159
|
+
discipline: 'Speech Therapy',
|
160
|
+
start_date: '04-01-2022',
|
161
|
+
reset_date: '05-01-2022'
|
162
|
+
},
|
163
|
+
{
|
164
|
+
discipline: 'Physical Therapy',
|
165
|
+
start_date: '04-01-2022',
|
166
|
+
reset_date: '05-01-2022'
|
167
|
+
}
|
168
|
+
]
|
169
|
+
# =>
|
170
|
+
=end
|
119
171
|
```
|
120
172
|
|
173
|
+
Interval `start_date` and `end_date`'s are different from start and end dates for the service. Here they define the start and end dates under evaluation in a specific interval, be it `month`, `week`, or `year`. They represent when the bounds of an interval.
|
174
|
+
|
121
175
|
It is important to note that the `pace` is hugely influenced by the `summer_holidays` period and the `mode` in which it is calculated.
|
122
176
|
|
123
177
|
## Data Types
|
data/lib/pacing/error.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'holidays'
|
3
|
+
|
4
|
+
module Pacing
|
5
|
+
class Error
|
6
|
+
attr_reader :school_plan, :date, :non_business_days, :state, :mode, :interval, :summer_holidays
|
7
|
+
|
8
|
+
def initialize(school_plan:, date:, non_business_days:, state: :us_tn, mode: :liberal, summer_holidays: [])
|
9
|
+
@school_plan = school_plan
|
10
|
+
@non_business_days = non_business_days
|
11
|
+
@date = date
|
12
|
+
@state = state
|
13
|
+
@mode = [:strict, :liberal].include?(mode) ? mode : :liberal
|
14
|
+
|
15
|
+
raise ArgumentError.new("You must pass in at least one school plan") if school_plan.nil?
|
16
|
+
raise TypeError.new("School plan must be a hash") if school_plan.class != Hash
|
17
|
+
|
18
|
+
raise ArgumentError.new('You must pass in a date') if date.nil?
|
19
|
+
raise TypeError.new("The date should be formatted as a string in the format mm-dd-yyyy") if date.class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(date)
|
20
|
+
raise ArgumentError.new('Date must be within the interval range of the school plan') if !date_within_range
|
21
|
+
|
22
|
+
non_business_days.each do |non_business_day|
|
23
|
+
raise TypeError.new('"Non business days" dates should be formatted as a string in the format mm-dd-yyyy') if non_business_day.class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(non_business_day)
|
24
|
+
end
|
25
|
+
|
26
|
+
school_plan[:school_plan_services].each do |school_plan_service|
|
27
|
+
raise TypeError.new("School plan type must be a string and cannot be nil") if school_plan_service[:school_plan_type].class != String || school_plan_service[:school_plan_type].nil?
|
28
|
+
|
29
|
+
raise ArgumentError.new("School plan services start and end dates can not be nil") if school_plan_service[:start_date].nil? || school_plan_service[:end_date].nil?
|
30
|
+
|
31
|
+
raise TypeError.new("School plan services start and end dates should be formatted as a string in the format mm-dd-yyyy") if school_plan_service[:start_date].class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(school_plan_service[:start_date])
|
32
|
+
|
33
|
+
raise TypeError.new("School plan services start and end dates should be formatted as a string in the format mm-dd-yyyy") if school_plan_service[:end_date].class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(school_plan_service[:end_date])
|
34
|
+
|
35
|
+
raise TypeError.new("Type of service must be a string and cannot be nil") if school_plan_service[:type_of_service].class != String || school_plan_service[:type_of_service].nil?
|
36
|
+
|
37
|
+
raise TypeError.new("Frequency must be an integer and cannot be nil") if school_plan_service[:frequency].class != Integer || school_plan_service[:frequency].nil?
|
38
|
+
|
39
|
+
raise TypeError.new("Interval must be a string and cannot be nil") if school_plan_service[:interval].class != String || school_plan_service[:interval].nil?
|
40
|
+
|
41
|
+
raise TypeError.new("Time per session in minutes must be an integer and cannot be nil") if school_plan_service[:time_per_session_in_minutes].class != Integer || school_plan_service[:time_per_session_in_minutes].nil?
|
42
|
+
|
43
|
+
raise TypeError.new("Completed visits for current interval must be an integer and cannot be nil") if school_plan_service[:completed_visits_for_current_interval].class != Integer || school_plan_service[:completed_visits_for_current_interval].nil?
|
44
|
+
|
45
|
+
raise TypeError.new("Extra sessions allowable must be an integer and cannot be nil") if school_plan_service[:extra_sessions_allowable].class != Integer || school_plan_service[:extra_sessions_allowable].nil?
|
46
|
+
|
47
|
+
raise TypeError.new("Interval for extra sessions allowable must be a string and cannot be nil") if school_plan_service[:interval_for_extra_sessions_allowable].class != String || school_plan_service[:interval_for_extra_sessions_allowable].nil?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def date_within_range
|
52
|
+
valid_range_or_exceptions = false
|
53
|
+
|
54
|
+
begin
|
55
|
+
@school_plan[:school_plan_services].each do |school_plan_service|
|
56
|
+
if (parse_date(school_plan_service[:start_date]) < parse_date(@date) && parse_date(@date) < parse_date(school_plan_service[:end_date]))
|
57
|
+
valid_range_or_exceptions = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue => exception
|
61
|
+
valid_range_or_exceptions = true
|
62
|
+
end
|
63
|
+
|
64
|
+
valid_range_or_exceptions
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_date(date)
|
68
|
+
begin
|
69
|
+
Date.strptime(date, '%m-%d-%Y')
|
70
|
+
rescue => exception
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'holidays'
|
3
|
+
|
4
|
+
module Pacing
|
5
|
+
class Normalizer
|
6
|
+
|
7
|
+
attr_accessor :services, :date
|
8
|
+
|
9
|
+
def initialize(services, date)
|
10
|
+
@date = date
|
11
|
+
@services = active_services(services)
|
12
|
+
end
|
13
|
+
|
14
|
+
def normalize
|
15
|
+
{ school_plan_services: disciplines_cleaner([speech_discipline, occupational_discipline, physical_discipline, feeding_discipline]) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def speech_discipline
|
19
|
+
discipline = {
|
20
|
+
:school_plan_type => 'IEP',
|
21
|
+
:start_date => "01-01-2100", # some arbitrary start date
|
22
|
+
:end_date => "01-01-2000", # some arbitrary end date
|
23
|
+
:type_of_service => 'Speech Therapy',
|
24
|
+
:frequency => 0,
|
25
|
+
:interval => '',
|
26
|
+
:time_per_session_in_minutes => 0,
|
27
|
+
:completed_visits_for_current_interval => 0,
|
28
|
+
:extra_sessions_allowable => 0,
|
29
|
+
:interval_for_extra_sessions_allowable => ''
|
30
|
+
}
|
31
|
+
|
32
|
+
discipline_services = services.filter do |service|
|
33
|
+
["pragmatic language", "speech and language", "language", "speech", "language therapy", "speech therapy", "speech and language therapy", "speech language therapy"].include?(service[:type_of_service].downcase)
|
34
|
+
end
|
35
|
+
|
36
|
+
return {} if discipline_services.empty?
|
37
|
+
|
38
|
+
discipline_services = normalize_to_monthly_frequency(discipline_services)
|
39
|
+
|
40
|
+
discipline_data(discipline_services, discipline)
|
41
|
+
end
|
42
|
+
|
43
|
+
def occupational_discipline
|
44
|
+
discipline = {
|
45
|
+
:school_plan_type => 'IEP',
|
46
|
+
:start_date => "01-01-2100", # some arbitrary start date
|
47
|
+
:end_date => "01-01-2000", # some arbitrary end date
|
48
|
+
:type_of_service => 'Occupational Therapy',
|
49
|
+
:frequency => 0,
|
50
|
+
:interval => '',
|
51
|
+
:time_per_session_in_minutes => 0,
|
52
|
+
:completed_visits_for_current_interval => 0,
|
53
|
+
:extra_sessions_allowable => 0,
|
54
|
+
:interval_for_extra_sessions_allowable => ''
|
55
|
+
}
|
56
|
+
|
57
|
+
discipline_services = services.filter do |service|
|
58
|
+
["occupation therapy", "occupational therapy", "occupation"].include?(service[:type_of_service].downcase)
|
59
|
+
end
|
60
|
+
|
61
|
+
return {} if discipline_services.empty?
|
62
|
+
|
63
|
+
discipline_services = normalize_to_monthly_frequency(discipline_services)
|
64
|
+
|
65
|
+
discipline_data(discipline_services, discipline)
|
66
|
+
end
|
67
|
+
|
68
|
+
def physical_discipline
|
69
|
+
discipline = {
|
70
|
+
:school_plan_type => 'IEP',
|
71
|
+
:start_date => "01-01-2100", # some arbitrary start date
|
72
|
+
:end_date => "01-01-2000", # some arbitrary end date
|
73
|
+
:type_of_service => 'Physical Therapy',
|
74
|
+
:frequency => 0,
|
75
|
+
:interval => '',
|
76
|
+
:time_per_session_in_minutes => 0,
|
77
|
+
:completed_visits_for_current_interval => 0,
|
78
|
+
:extra_sessions_allowable => 0,
|
79
|
+
:interval_for_extra_sessions_allowable => ''
|
80
|
+
}
|
81
|
+
|
82
|
+
discipline_services = services.filter do |service|
|
83
|
+
["physical therapy", "physical"].include?(service[:type_of_service].downcase)
|
84
|
+
end
|
85
|
+
|
86
|
+
return {} if discipline_services.empty?
|
87
|
+
|
88
|
+
discipline_services = normalize_to_monthly_frequency(discipline_services)
|
89
|
+
|
90
|
+
discipline_data(discipline_services, discipline)
|
91
|
+
end
|
92
|
+
|
93
|
+
def feeding_discipline
|
94
|
+
discipline = {
|
95
|
+
:school_plan_type => 'IEP',
|
96
|
+
:start_date => "01-01-2100", # some arbitrary start date
|
97
|
+
:end_date => "01-01-2000", # some arbitrary end date
|
98
|
+
:type_of_service => 'Feeding Therapy',
|
99
|
+
:frequency => 0,
|
100
|
+
:interval => '',
|
101
|
+
:time_per_session_in_minutes => 0,
|
102
|
+
:completed_visits_for_current_interval => 0,
|
103
|
+
:extra_sessions_allowable => 0,
|
104
|
+
:interval_for_extra_sessions_allowable => ''
|
105
|
+
}
|
106
|
+
|
107
|
+
discipline_services = services.filter do |service|
|
108
|
+
["feeding therapy", "feeding"].include?(service[:type_of_service].downcase)
|
109
|
+
end
|
110
|
+
|
111
|
+
return {} if discipline_services.empty?
|
112
|
+
|
113
|
+
discipline_services = normalize_to_monthly_frequency(discipline_services)
|
114
|
+
|
115
|
+
discipline_data(discipline_services, discipline)
|
116
|
+
end
|
117
|
+
|
118
|
+
def discipline_data(services, discipline)
|
119
|
+
services.each do |service|
|
120
|
+
discipline[:start_date] = parse_date(service[:start_date]) < parse_date(discipline[:start_date]) ? service[:start_date] : discipline[:start_date]
|
121
|
+
|
122
|
+
discipline[:end_date] = parse_date(service[:end_date]) > parse_date(discipline[:end_date]) ? service[:end_date] : discipline[:end_date]
|
123
|
+
|
124
|
+
discipline[:frequency] += service[:frequency].to_i
|
125
|
+
|
126
|
+
discipline[:completed_visits_for_current_interval] = service[:completed_visits_for_current_interval] if service[:completed_visits_for_current_interval] > discipline[:completed_visits_for_current_interval]
|
127
|
+
|
128
|
+
discipline[:time_per_session_in_minutes] = service[:time_per_session_in_minutes] > discipline[:time_per_session_in_minutes] ? service[:time_per_session_in_minutes] : discipline[:time_per_session_in_minutes]
|
129
|
+
|
130
|
+
discipline[:interval] = service[:interval]
|
131
|
+
|
132
|
+
discipline[:extra_sessions_allowable] += service[:extra_sessions_allowable].to_i
|
133
|
+
|
134
|
+
discipline[:interval_for_extra_sessions_allowable] = service[:interval_for_extra_sessions_allowable]
|
135
|
+
end
|
136
|
+
|
137
|
+
discipline
|
138
|
+
end
|
139
|
+
|
140
|
+
def same_interval(services)
|
141
|
+
interval = services[0].nil? ? "" : services[0][:interval]
|
142
|
+
same = true
|
143
|
+
|
144
|
+
services.each do |service|
|
145
|
+
if interval != service[:interval]
|
146
|
+
# puts "this happened for real? interval #{interval} and service interval #{service[:interval]} #{services}"
|
147
|
+
same = false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
same
|
152
|
+
end
|
153
|
+
|
154
|
+
def normalize_to_monthly_frequency(services)
|
155
|
+
# average business days for each interval
|
156
|
+
interval_average_days = {
|
157
|
+
"weekly" => 5,
|
158
|
+
"monthly" => 22,
|
159
|
+
"yearly" => 210 # take away average holidays period with is 2.5 months
|
160
|
+
}
|
161
|
+
|
162
|
+
return services if same_interval(services)
|
163
|
+
|
164
|
+
services.map do |service|
|
165
|
+
if !(service[:interval] == "monthly")
|
166
|
+
# weekly(5 days) = frequency # weekly
|
167
|
+
# monthly(20 days) = frequency * monthly
|
168
|
+
# yearly(200 days)
|
169
|
+
|
170
|
+
f = service[:frequency]
|
171
|
+
|
172
|
+
service[:frequency] = ((service[:frequency] * interval_average_days["monthly"].to_f) / interval_average_days[service[:interval]]).round
|
173
|
+
|
174
|
+
service[:interval] = "monthly"
|
175
|
+
end
|
176
|
+
|
177
|
+
service
|
178
|
+
end
|
179
|
+
|
180
|
+
services
|
181
|
+
end
|
182
|
+
|
183
|
+
def parse_date(date)
|
184
|
+
begin
|
185
|
+
Date.strptime(date, '%m-%d-%Y')
|
186
|
+
rescue => exception
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def disciplines_cleaner(disciplines)
|
191
|
+
# use the fake arbitrary reset date to remove unrequired disciplines
|
192
|
+
disciplines.filter { |discipline| !discipline.empty? }
|
193
|
+
end
|
194
|
+
|
195
|
+
def active_services(services)
|
196
|
+
services.filter do |school_plan_service|
|
197
|
+
within = true
|
198
|
+
begin
|
199
|
+
if !(parse_date(school_plan_service[:start_date]) <= parse_date(date) && parse_date(date) <= parse_date(school_plan_service[:end_date]))
|
200
|
+
within = false
|
201
|
+
end
|
202
|
+
rescue => exception
|
203
|
+
end
|
204
|
+
|
205
|
+
within
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
data/lib/pacing/pacer.rb
CHANGED
@@ -2,59 +2,57 @@ require 'date'
|
|
2
2
|
require 'holidays'
|
3
3
|
|
4
4
|
module Pacing
|
5
|
-
# two modes(strict: use start dates strictly in calculating pacing)
|
6
5
|
class Pacer
|
7
6
|
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
8
|
-
attr_reader :school_plan, :date, :non_business_days, :state, :mode, :interval, :summer_holidays
|
7
|
+
attr_reader :school_plan, :date, :non_business_days, :state, :mode, :interval, :summer_holidays, :show_frequency
|
9
8
|
|
10
|
-
def initialize(school_plan:, date:, non_business_days:, state: :us_tn, mode: :liberal, summer_holidays: [])
|
9
|
+
def initialize(school_plan:, date:, non_business_days:, state: :us_tn, mode: :liberal, summer_holidays: [], show_frequency: false)
|
11
10
|
@school_plan = school_plan
|
12
11
|
@non_business_days = non_business_days
|
13
12
|
@date = date
|
14
13
|
@state = state
|
14
|
+
@show_frequency = show_frequency
|
15
15
|
@mode = [:strict, :liberal].include?(mode) ? mode : :liberal
|
16
16
|
|
17
|
-
|
18
|
-
raise TypeError.new("School plan must be a hash") if @school_plan.class != Hash
|
17
|
+
Pacing::Error.new(school_plan: school_plan, date: date, non_business_days: non_business_days, state: state, mode: mode, summer_holidays: summer_holidays)
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
raise ArgumentError.new('Date must be within the interval range of the school plan') if !date_within_range
|
23
|
-
|
24
|
-
@non_business_days.each do |non_business_day|
|
25
|
-
raise TypeError.new('"Non business days" dates should be formatted as a string in the format mm-dd-yyyy') if non_business_day.class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(non_business_day)
|
26
|
-
end
|
27
|
-
|
28
|
-
@school_plan[:school_plan_services].each do |school_plan_service|
|
29
|
-
raise TypeError.new("School plan type must be a string and cannot be nil") if school_plan_service[:school_plan_type].class != String || school_plan_service[:school_plan_type].nil?
|
30
|
-
|
31
|
-
raise ArgumentError.new("School plan services start and end dates can not be nil") if school_plan_service[:start_date].nil? || school_plan_service[:end_date].nil?
|
32
|
-
|
33
|
-
raise TypeError.new("School plan services start and end dates should be formatted as a string in the format mm-dd-yyyy") if school_plan_service[:start_date].class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(school_plan_service[:start_date])
|
34
|
-
|
35
|
-
raise TypeError.new("School plan services start and end dates should be formatted as a string in the format mm-dd-yyyy") if school_plan_service[:end_date].class != String || !/(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])-(19|20)\d\d/.match?(school_plan_service[:end_date])
|
36
|
-
|
37
|
-
raise TypeError.new("Type of service must be a string and cannot be nil") if school_plan_service[:type_of_service].class != String || school_plan_service[:type_of_service].nil?
|
38
|
-
|
39
|
-
raise TypeError.new("Frequency must be an integer and cannot be nil") if school_plan_service[:frequency].class != Integer || school_plan_service[:frequency].nil?
|
40
|
-
|
41
|
-
raise TypeError.new("Interval must be a string and cannot be nil") if school_plan_service[:interval].class != String || school_plan_service[:interval].nil?
|
19
|
+
@summer_holidays = summer_holidays.empty? ? parse_summer_holiday_dates : [parse_date(summer_holidays[0]), parse_date(summer_holidays[1])]
|
20
|
+
end
|
42
21
|
|
43
|
-
|
22
|
+
def interval
|
23
|
+
# filter out services that haven't started or whose time is passed
|
24
|
+
services = @school_plan[:school_plan_services].filter do |school_plan_service|
|
25
|
+
within = true
|
26
|
+
if !(parse_date(school_plan_service[:start_date]) <= parse_date(@date) && parse_date(@date) <= parse_date(school_plan_service[:end_date]))
|
27
|
+
within = false
|
28
|
+
end
|
44
29
|
|
45
|
-
|
30
|
+
within
|
31
|
+
end
|
46
32
|
|
47
|
-
|
33
|
+
services = services.map do |service|
|
34
|
+
if ["pragmatic language", "speech and language", "language", "speech", "language therapy", "speech therapy", "speech and language therapy", "speech language therapy"].include?(service[:type_of_service].downcase)
|
35
|
+
discipline_name = "Speech Therapy"
|
36
|
+
elsif ["occupation therapy", "occupational therapy"].include?(service[:type_of_service].downcase)
|
37
|
+
discipline_name = "Occupational Therapy"
|
38
|
+
elsif ["physical therapy"].include?(service[:type_of_service].downcase)
|
39
|
+
discipline_name = "Physical Therapy"
|
40
|
+
elsif ["feeding therapy"].include?(service[:type_of_service].downcase)
|
41
|
+
discipline_name = "Feeding Therapy"
|
42
|
+
end
|
48
43
|
|
49
|
-
|
44
|
+
discipline = {}
|
45
|
+
discipline[:discipline] = discipline_name
|
46
|
+
discipline[:reset_date] = reset_date(start_date: service[:start_date], interval: service[:interval])
|
47
|
+
discipline[:start_date] = start_of_treatment_date(parse_date(service[:start_date]), service[:interval]).strftime("%m-%d-%Y")
|
48
|
+
discipline
|
50
49
|
end
|
51
|
-
|
52
|
-
@summer_holidays = summer_holidays.empty? ? parse_summer_holiday_dates : [parse_date(summer_holidays[0]), parse_date(summer_holidays[1])]
|
53
50
|
end
|
54
51
|
|
55
52
|
def calculate
|
56
53
|
# filter out services that haven't started or whose time is passed
|
57
|
-
|
54
|
+
school_plan_services = Pacing::Normalizer.new(@school_plan[:school_plan_services], @date).normalize
|
55
|
+
services = school_plan_services[:school_plan_services].filter do |school_plan_service|
|
58
56
|
within = true
|
59
57
|
if !(parse_date(school_plan_service[:start_date]) <= parse_date(@date) && parse_date(@date) <= parse_date(school_plan_service[:end_date]))
|
60
58
|
within = false
|
@@ -84,12 +82,26 @@ module Pacing
|
|
84
82
|
|
85
83
|
discipline[:expected_visits_at_date] = expected
|
86
84
|
|
87
|
-
discipline[:
|
85
|
+
discipline[:discipline] = service[:type_of_service]
|
86
|
+
|
87
|
+
discipline[:pace_indicator] = pace_indicator(discipline[:pace])
|
88
|
+
discipline[:pace_suggestion] = readable_suggestion(rate: discipline[:suggested_rate])
|
89
|
+
|
90
|
+
if show_frequency
|
91
|
+
discipline[:frequency] = discipline_frequency(service)
|
92
|
+
end
|
93
|
+
|
94
|
+
discipline.delete(:suggested_rate)
|
88
95
|
|
89
96
|
discipline
|
90
97
|
end
|
91
98
|
|
92
|
-
|
99
|
+
services
|
100
|
+
end
|
101
|
+
|
102
|
+
# discipline iep frequency
|
103
|
+
def discipline_frequency(service)
|
104
|
+
service[:frequency].to_s + service[:interval][0].gsub(/(per)|p/i, "").strip.upcase + "x" + service[:time_per_session_in_minutes].to_s + service[:type_of_service][0].upcase + "T"
|
93
105
|
end
|
94
106
|
|
95
107
|
# get a spreadout of visit dates over an interval by using simple proportion.
|
@@ -215,6 +227,7 @@ module Pacing
|
|
215
227
|
|
216
228
|
# get actual date of the first day of the week where date falls
|
217
229
|
def week_start(date, offset_from_sunday=0)
|
230
|
+
offset_from_sunday = @mode == :liberal ? 1 : 0
|
218
231
|
return date if date.monday?
|
219
232
|
date - ((date.wday - offset_from_sunday) % 7)
|
220
233
|
end
|
@@ -226,7 +239,9 @@ module Pacing
|
|
226
239
|
|
227
240
|
# reset date for the monthly interval
|
228
241
|
def reset_date_monthly(start_date, interval)
|
229
|
-
|
242
|
+
month = (parse_date(@date)).month
|
243
|
+
|
244
|
+
(start_of_treatment_date(parse_date(start_date), interval) + COMMON_YEAR_DAYS_IN_MONTH[month]).strftime("%m-%d-%Y")
|
230
245
|
end
|
231
246
|
|
232
247
|
# reset date for the weekly interval
|
@@ -245,7 +260,7 @@ module Pacing
|
|
245
260
|
# start_date
|
246
261
|
end
|
247
262
|
|
248
|
-
# start of treatment for the
|
263
|
+
# start of treatment for the monthly interval
|
249
264
|
def start_of_treatment_date_monthly(start_date)
|
250
265
|
if @mode == :strict
|
251
266
|
return parse_date("#{parse_date(@date).month}-#{start_date.day}-#{parse_date(@date).year}")
|
@@ -260,12 +275,14 @@ module Pacing
|
|
260
275
|
|
261
276
|
# start of treatment for the weekly interval
|
262
277
|
def start_of_treatment_date_weekly(start_date)
|
278
|
+
# TODO: Update with assumption that Monday is start of week
|
279
|
+
# Future TODO: allow user to pass in configuration for start of week
|
263
280
|
parsed_date = parse_date(@date)
|
264
281
|
week_start_date = week_start(parsed_date)
|
265
282
|
weekly_date = week_start_date
|
266
283
|
|
267
284
|
if week_start_date != parsed_date && @mode == :strict
|
268
|
-
weekly_date = week_start_date + start_date.wday #unless start_date.wday == 1
|
285
|
+
weekly_date = week_start_date + start_date.wday # unless start_date.wday == 1
|
269
286
|
weekly_date = parsed_date < weekly_date ? weekly_date - 7 : weekly_date
|
270
287
|
end
|
271
288
|
|
@@ -288,112 +305,6 @@ module Pacing
|
|
288
305
|
valid_range_or_exceptions
|
289
306
|
end
|
290
307
|
|
291
|
-
def speech_discipline(services)
|
292
|
-
discipline = {
|
293
|
-
:discipline => "Speech Therapy",
|
294
|
-
:remaining_visits => 0,
|
295
|
-
:used_visits => 0,
|
296
|
-
:pace => 0,
|
297
|
-
:pace_indicator => "🐢",
|
298
|
-
:pace_suggestion => "once a day",
|
299
|
-
:suggested_rate => 0,
|
300
|
-
:expected_visits_at_date => 0,
|
301
|
-
:reset_date => nil } # some arbitrarity date in the past
|
302
|
-
|
303
|
-
discipline_services = services.filter do |service|
|
304
|
-
["pragmatic language", "speech and language", "language", "speech", "language therapy", "speech therapy", "speech and language therapy", "speech language therapy"].include?(service[:type_of_service].downcase)
|
305
|
-
end
|
306
|
-
|
307
|
-
return {} if discipline_services.empty?
|
308
|
-
|
309
|
-
discipline_data(discipline_services, discipline)
|
310
|
-
end
|
311
|
-
|
312
|
-
def occupational_discipline(services)
|
313
|
-
discipline = {
|
314
|
-
:discipline=>"Occupational Therapy",
|
315
|
-
:remaining_visits=>0,
|
316
|
-
:used_visits=>0,
|
317
|
-
:pace=>0,
|
318
|
-
:pace_indicator=>"🐢",
|
319
|
-
:pace_suggestion=>"once a day",
|
320
|
-
:suggested_rate => 0,
|
321
|
-
:expected_visits_at_date=>0,
|
322
|
-
:reset_date=> nil } # some arbitrarity date in the past
|
323
|
-
|
324
|
-
discipline_services = services.filter do |service|
|
325
|
-
["occupation therapy", "occupational therapy"].include?(service[:type_of_service].downcase)
|
326
|
-
end
|
327
|
-
|
328
|
-
return {} if discipline_services.empty?
|
329
|
-
|
330
|
-
discipline_data(discipline_services, discipline)
|
331
|
-
end
|
332
|
-
|
333
|
-
def physical_discipline(services)
|
334
|
-
discipline = {
|
335
|
-
:discipline=>"Physical Therapy",
|
336
|
-
:remaining_visits=>0,
|
337
|
-
:used_visits=>0,
|
338
|
-
:pace=>0,
|
339
|
-
:pace_indicator=>"🐢",
|
340
|
-
:pace_suggestion=>"once a day",
|
341
|
-
:suggested_rate => 0,
|
342
|
-
:expected_visits_at_date=>0,
|
343
|
-
:reset_date=> nil } # some arbitrarity date in the past
|
344
|
-
|
345
|
-
discipline_services = services.filter do |service|
|
346
|
-
["physical therapy"].include?(service[:type_of_service].downcase)
|
347
|
-
end
|
348
|
-
|
349
|
-
return {} if discipline_services.empty?
|
350
|
-
|
351
|
-
discipline_data(discipline_services, discipline)
|
352
|
-
end
|
353
|
-
|
354
|
-
def feeding_discipline(services)
|
355
|
-
discipline = {
|
356
|
-
:discipline=>"Feeding Therapy",
|
357
|
-
:remaining_visits=>0,
|
358
|
-
:used_visits=>0,
|
359
|
-
:pace=>0,
|
360
|
-
:pace_indicator=>"🐢",
|
361
|
-
:pace_suggestion=>"once a day",
|
362
|
-
:suggested_rate => 0,
|
363
|
-
:expected_visits_at_date=>0,
|
364
|
-
:reset_date=> nil } # some arbitrarity date in the past
|
365
|
-
|
366
|
-
discipline_services = services.filter do |service|
|
367
|
-
["Feeding Therapy"].include? service[:type_of_service]
|
368
|
-
end
|
369
|
-
|
370
|
-
return {} if discipline_services.empty?
|
371
|
-
|
372
|
-
discipline_data(discipline_services, discipline)
|
373
|
-
end
|
374
|
-
|
375
|
-
def discipline_data(services, discipline)
|
376
|
-
services.each do |service|
|
377
|
-
discipline[:pace] = discipline[:pace] ? discipline[:pace].to_i + service[:pace].to_i : service[:pace]
|
378
|
-
|
379
|
-
discipline[:remaining_visits] = discipline[:remaining_visits] ? discipline[:remaining_visits].to_i + service[:remaining_visits].to_i : service[:remaining_visits]
|
380
|
-
|
381
|
-
discipline[:used_visits] = discipline[:used_visits] ? discipline[:used_visits].to_i + service[:used_visits].to_i : service[:used_visits]
|
382
|
-
|
383
|
-
discipline[:expected_visits_at_date] = discipline[:expected_visits_at_date] ? discipline[:expected_visits_at_date].to_i + service[:expected_visits_at_date].to_i : service[:expected_visits_at_date]
|
384
|
-
|
385
|
-
discipline[:suggested_rate] = discipline[:suggested_rate] ? discipline[:suggested_rate].to_f + service[:suggested_rate].to_f : service[:suggested_rate].to_f
|
386
|
-
|
387
|
-
discipline[:reset_date] = (!discipline[:reset_date].nil? && parse_date(service[:reset_date]) < parse_date(discipline[:reset_date])) ? discipline[:reset_date] : service[:reset_date]
|
388
|
-
end
|
389
|
-
|
390
|
-
discipline[:pace_indicator] = pace_indicator(discipline[:pace])
|
391
|
-
discipline[:pace_suggestion] = readable_suggestion(rate: discipline[:suggested_rate])
|
392
|
-
|
393
|
-
discipline.delete(:suggested_rate)
|
394
|
-
discipline
|
395
|
-
end
|
396
|
-
|
397
308
|
def readable_suggestion(rate:)
|
398
309
|
# rate = suggested_rate(remaining_visits: remaining_visits, start_date: start_date, interval: interval)
|
399
310
|
|
@@ -429,11 +340,6 @@ module Pacing
|
|
429
340
|
days_left
|
430
341
|
end
|
431
342
|
|
432
|
-
def disciplines_cleaner(disciplines)
|
433
|
-
# use the fake arbitrary reset date to remove unrequired disciplines
|
434
|
-
disciplines.filter { |discipline| !discipline.empty? }
|
435
|
-
end
|
436
|
-
|
437
343
|
def parse_summer_holiday_dates
|
438
344
|
holidays_start = parse_date("05-13-#{parse_date(@date).year}")
|
439
345
|
holidays_start += 1 until holidays_start.wday == 5
|
@@ -442,7 +348,6 @@ module Pacing
|
|
442
348
|
holidays_start += 1 until holidays_start.wday == 1
|
443
349
|
|
444
350
|
[holidays_start, holidays_end]
|
445
|
-
end
|
351
|
+
end
|
446
352
|
end
|
447
353
|
end
|
448
|
-
|
data/lib/pacing/version.rb
CHANGED
data/lib/pacing.rb
CHANGED