pacing 0.1.3 β 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +79 -61
- data/lib/pacing/pacer.rb +260 -27
- data/lib/pacing/version.rb +1 -1
- data/spec/pacing_spec.rb +4389 -336
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1a8fc6f5350111b6fc551118c17dbd22eb496e7fe961b851b3b0c818ca28074
|
4
|
+
data.tar.gz: 3b19d2df9e4d3fd80d6b66fccf864a19a94ebcfbe337bf91bb7c5a030309694d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e5a7500a4873e8f7748052785fb914ad6b9b3a59c9f80df84a7a836346fe757da050fc81c369bd904184485c06da68757462874a8e25fcfac53b602f2db0f5b
|
7
|
+
data.tar.gz: 30f8e30fee65de76a98bc99b5e14ede8d94801647c3927e4aae2e43b73cc559797cd4f434f0d1c05b3bc4ac42b49d4021386fdbf791916d95eeba962c8123eea
|
data/README.md
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
Pacing is a tool that enables therapists to better manage and track their caseload. It is built for cases where there are therapy frequency limitations that need to be adhered to. For example, in the case of an [IEP (Individualized Education Program)](https://ambiki.com/glossary-concepts/iep), 504 plan, or a Services plan. This gem helps to calculate remaining visits as well as a therapist's current pace to meet visit mandates.
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
- π Ahead of pace
|
6
|
+
- π On pace
|
7
|
+
- π’ Behind pace
|
8
8
|
|
9
9
|
## Getting started
|
10
10
|
|
11
11
|
**Ruby**
|
12
12
|
|
13
|
-
|
13
|
+
_Supports Ruby x.x.x and above_
|
14
|
+
|
14
15
|
```
|
15
16
|
gem install pacing
|
16
17
|
```
|
@@ -18,6 +19,7 @@ gem install pacing
|
|
18
19
|
**Ruby on Rails**
|
19
20
|
|
20
21
|
Add this line to your applicationβs Gemfile:
|
22
|
+
|
21
23
|
```ruby
|
22
24
|
gem 'pacing'
|
23
25
|
```
|
@@ -55,52 +57,69 @@ school_plan = {
|
|
55
57
|
]
|
56
58
|
}
|
57
59
|
|
58
|
-
date = '
|
59
|
-
|
60
|
-
|
60
|
+
date = '04-22-2022' # string (mm-dd-yyyy)
|
61
|
+
state = :us_tn
|
62
|
+
# default is `:us_tn` possible options :us_fl, :us_la, :us_ct, :us_de, :us_gu, :us_hi, :us_in, :us_ky, :us_nj, :us_nc, :us_nd, :us_pr, :us_tn, :us_ms, :us_id, :us_ar, :us_tx, :us_dc, :us_md, :us_va, :us_vt, :us_ak, :us_ca, :us_me, :us_ma, :us_al, :us_ga, :us_ne, :us_mo, :us_sc, :us_wv, :us_vi, :us_ut, :us_ri, :us_az, :us_co, :us_il, :us_mt, :us_nm, :us_ny, :us_oh, :us_pa, :us_mi, :us_mn, :us_nv, :us_or, :us_sd, :us_wa, :us_wi, :us_wy, :us_ia, :us_ks, :us_nh, :us_ok
|
63
|
+
non_business_days = ['04-25-2022'] # array of strings (mm-dd-yyyy)
|
64
|
+
summer_holidays = ["05-31-2022", "08-01-2022"] # [start-of-holiday-date, end-of-holiday-date] array of strings (mm-dd-yyyy)
|
65
|
+
mode = :liberal # default is :liberal, two possible options :strict, :liberal
|
66
|
+
paced = Pacing::Pacer.new(school_plan: school_plan, date: date, non_business_days: non_business_days, mode: :liberal, summer_holidays: summer_holidays, state: state)
|
61
67
|
paced.calculate
|
62
68
|
|
69
|
+
# Below is the result you will get when in liberal mode
|
63
70
|
=begin
|
64
|
-
=>
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end_date: "01-01-2023",
|
86
|
-
type_of_service: "Physical Therapy",
|
87
|
-
frequency: 6,
|
88
|
-
interval: "monthly",
|
89
|
-
time_per_session_in_minutes: 30,
|
90
|
-
completed_visits_for_current_interval: 7,
|
91
|
-
extra_sessions_allowable: 1,
|
92
|
-
interval_for_extra_sessions_allowable: "monthly",
|
93
|
-
pace: 3,
|
94
|
-
reset_date: "01-31-2022",
|
95
|
-
remaining_visits: 0,
|
96
|
-
pace_indicator: "π"
|
97
|
-
}
|
98
|
-
]
|
99
|
-
}
|
71
|
+
=> [
|
72
|
+
{
|
73
|
+
discipline: 'Speech Therapy',
|
74
|
+
remaining_visits: 0,
|
75
|
+
used_visits: 7,
|
76
|
+
expected_visits_at_date: 5,
|
77
|
+
reset_date: '05-01-2022',
|
78
|
+
pace: 2,
|
79
|
+
pace_indicator: "π",
|
80
|
+
pace_suggestion: "less than once per week"
|
81
|
+
}, {
|
82
|
+
discipline: 'Physical Therapy',
|
83
|
+
remaining_visits: 0,
|
84
|
+
expected_visits_at_date: 5,
|
85
|
+
used_visits: 7,
|
86
|
+
reset_date: '05-01-2022',
|
87
|
+
pace: 2,
|
88
|
+
pace_indicator: "π",
|
89
|
+
pace_suggestion: "less than once per week"
|
90
|
+
}
|
91
|
+
]
|
100
92
|
=end
|
101
93
|
|
94
|
+
# Below is the result you will get when in strict mode
|
95
|
+
# in strict mode the reset date for intervals of month and week is determined by the start date of the school plan service. e.g the reset date in the 6th month for a start date of "05-11-2022" for an interva of `month` will be "06-11-2022" whereas for `:liberal` mode it will be "06-01-2022"
|
96
|
+
=begin
|
97
|
+
=> [
|
98
|
+
{
|
99
|
+
discipline: 'Speech Therapy',
|
100
|
+
remaining_visits: 0,
|
101
|
+
used_visits: 7,
|
102
|
+
expected_visits_at_date: 3,
|
103
|
+
reset_date: '05-11-2022',
|
104
|
+
pace: 4,
|
105
|
+
pace_indicator: "π",
|
106
|
+
pace_suggestion: "less than once per week"
|
107
|
+
}, {
|
108
|
+
discipline: 'Physical Therapy',
|
109
|
+
remaining_visits: 0,
|
110
|
+
expected_visits_at_date: 3,
|
111
|
+
used_visits: 7,
|
112
|
+
reset_date: '05-11-2022',
|
113
|
+
pace: 4,
|
114
|
+
pace_indicator: "π",
|
115
|
+
pace_suggestion: "less than once per week"
|
116
|
+
}
|
117
|
+
]
|
118
|
+
=end
|
102
119
|
```
|
103
120
|
|
121
|
+
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
|
+
|
104
123
|
## Data Types
|
105
124
|
|
106
125
|
Pacing accepts input which consists of a school_plan, a date and a non_business_day variable. The school_plan variable is a hash that includes the various school plan services that the client received, the date is a string and the non_business_days variable is an array of dates.
|
@@ -120,37 +139,36 @@ The following list shows the various variables and what they consist of:
|
|
120
139
|
- `extra_sessions_allowable` is an integer.
|
121
140
|
- `interval_for_extra_sessions_allowable` is a string.
|
122
141
|
2. Output
|
123
|
-
- `
|
124
|
-
- `
|
125
|
-
- `
|
126
|
-
- `
|
127
|
-
- `
|
128
|
-
- `
|
129
|
-
- `
|
130
|
-
- `
|
131
|
-
- `interval_for_extra_sessions_allowable` is a string.
|
142
|
+
- `discipline`: is a string.
|
143
|
+
- `remaining_visits`: is an integer.
|
144
|
+
- `used_visits`: is an integer.
|
145
|
+
- `expected_visits_at_date`: is an integer.
|
146
|
+
- `reset_date`: is a string.
|
147
|
+
- `pace`: is an integer.
|
148
|
+
- `pace_indicator`: is an emoji string.
|
149
|
+
- `pace_suggestion`: is a string.
|
132
150
|
- `reset_date` is a string.
|
133
151
|
- `pace` is an integer.
|
134
152
|
- `pace_indicator` is a string.
|
135
153
|
|
136
154
|
## Terminology
|
137
155
|
|
138
|
-
|
139
|
-
|
140
|
-
|
156
|
+
- **IEP (Individualized Education Program)**: Individualized Education Programs (IEPs) are required by law for every student who receives special education services and are developed on an annual basis. The IEP is an educational document that the school generates. The therapist is bound to the frequency on the IEP, and the insurance companies will not pay for anything above or beyond what is on the IEP. It is a blueprint for a studentβs special education experience in a public school. The plan must ensure that the child receives a free appropriate public education, (FAPE).
|
157
|
+
- **504 Plan**: 504 plans are formal plans that schools develop to give kids with disabilities the support they need. That covers any condition that limits daily activities in a major way. These plans prevent discrimination and they protect the rights of kids with disabilities in school. They are covered under Section 504 of the Rehabilitation Act, a civil rights law.
|
158
|
+
- **Services Plan**: A plan paid for by the local school district for students with disabilities who attend private schools. A services plan does not have to ensure a child is provided with FAPE (free appropriate public education). A services plan spells out the special education and related services the LEA will make available to a child. These services are provided at no cost to parents. But the student may not be able to receive these services at the private school. Instead, the LEA can require him to go to a public school for services like speech therapy sessions. [Β§34 CFR 300.130 through Β§300.144 of IDEA](https://sites.ed.gov/idea/files/CWD_Enrolled_by_Their_Parents_in_Private_Schools_11-16-06.pdf) is a specific section that describes how services are provided to kids in private school.
|
141
159
|
|
142
160
|
## Testing
|
143
161
|
|
144
|
-
|
162
|
+
- `bundle exec rspec`
|
145
163
|
|
146
164
|
## Contributing
|
147
165
|
|
148
166
|
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
149
167
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
168
|
+
- [Report bugs](https://github.com/Ambiki/pacing/issues)
|
169
|
+
- Fix bugs and [submit pull requests](https://github.com/Ambiki/pacing/pulls)
|
170
|
+
- Write, clarify, or fix documentation
|
171
|
+
- Suggest or add new features
|
154
172
|
|
155
173
|
To get started with development:
|
156
174
|
|
@@ -188,4 +206,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
188
206
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
189
207
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
190
208
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
191
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
209
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/pacing/pacer.rb
CHANGED
@@ -2,21 +2,24 @@ require 'date'
|
|
2
2
|
require 'holidays'
|
3
3
|
|
4
4
|
module Pacing
|
5
|
+
# two modes(strict: use start dates strictly in calculating pacing)
|
5
6
|
class Pacer
|
6
7
|
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
7
|
-
attr_reader :school_plan, :date, :non_business_days, :state
|
8
|
+
attr_reader :school_plan, :date, :non_business_days, :state, :mode, :interval, :summer_holidays
|
8
9
|
|
9
|
-
def initialize(school_plan:, date:, non_business_days:, state: :us_tn)
|
10
|
+
def initialize(school_plan:, date:, non_business_days:, state: :us_tn, mode: :liberal, summer_holidays: [])
|
10
11
|
@school_plan = school_plan
|
11
12
|
@non_business_days = non_business_days
|
12
13
|
@date = date
|
13
14
|
@state = state
|
15
|
+
@mode = [:strict, :liberal].include?(mode) ? mode : :liberal
|
14
16
|
|
15
17
|
raise ArgumentError.new("You must pass in at least one school plan") if @school_plan.nil?
|
16
18
|
raise TypeError.new("School plan must be a hash") if @school_plan.class != Hash
|
17
19
|
|
18
20
|
raise ArgumentError.new('You must pass in a date') if @date.nil?
|
19
21
|
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)
|
22
|
+
raise ArgumentError.new('Date must be within the interval range of the school plan') if !date_within_range
|
20
23
|
|
21
24
|
@non_business_days.each do |non_business_day|
|
22
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)
|
@@ -45,40 +48,71 @@ module Pacing
|
|
45
48
|
|
46
49
|
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?
|
47
50
|
end
|
51
|
+
|
52
|
+
@summer_holidays = summer_holidays.empty? ? parse_summer_holiday_dates : [parse_date(summer_holidays[0]), parse_date(summer_holidays[1])]
|
48
53
|
end
|
49
54
|
|
50
55
|
def calculate
|
51
|
-
services
|
56
|
+
# filter out services that haven't started or whose time is passed
|
57
|
+
services = @school_plan[:school_plan_services].filter do |school_plan_service|
|
58
|
+
within = true
|
59
|
+
if !(parse_date(school_plan_service[:start_date]) <= parse_date(@date) && parse_date(@date) <= parse_date(school_plan_service[:end_date]))
|
60
|
+
within = false
|
61
|
+
end
|
62
|
+
|
63
|
+
within
|
64
|
+
end
|
52
65
|
|
53
66
|
services = services.map do |service|
|
67
|
+
discipline = {}
|
68
|
+
|
54
69
|
expected = expected_visits(start_date: parse_date(service[:start_date]), end_date: parse_date(service[:end_date]), frequency: service[:frequency], interval: service[:interval])
|
55
70
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
71
|
+
discipline[:pace] = pace(service[:completed_visits_for_current_interval], expected)
|
72
|
+
|
73
|
+
discipline[:reset_date] = reset_date(start_date: service[:start_date], interval: service[:interval])
|
74
|
+
|
75
|
+
discipline[:reset_date] = parse_date(discipline[:reset_date]) > parse_date(service[:end_date]) && service[:interval] == "yearly" ? service[:end_date] : discipline[:reset_date]
|
76
|
+
|
77
|
+
discipline[:remaining_visits] = remaining_visits(completed_visits: service[:completed_visits_for_current_interval], required_visits: service[:frequency] + service[:extra_sessions_allowable])
|
78
|
+
|
79
|
+
discipline[:pace_indicator] = pace_indicator(discipline[:pace])
|
80
|
+
|
81
|
+
discipline[:used_visits] = service[:completed_visits_for_current_interval]
|
82
|
+
|
83
|
+
discipline[:suggested_rate] = suggested_rate(remaining_visits: discipline[:remaining_visits], start_date: parse_date(service[:start_date]), interval: service[:interval])
|
84
|
+
|
85
|
+
discipline[:expected_visits_at_date] = expected
|
60
86
|
|
61
|
-
service
|
87
|
+
discipline[:type_of_service] = service[:type_of_service]
|
88
|
+
|
89
|
+
discipline
|
62
90
|
end
|
63
91
|
|
64
|
-
|
92
|
+
disciplines_cleaner ([speech_discipline(services), occupational_discipline(services), physical_discipline(services), feeding_discipline(services)])
|
65
93
|
end
|
66
94
|
|
67
95
|
# get a spreadout of visit dates over an interval by using simple proportion.
|
68
96
|
def expected_visits(start_date:, end_date:, frequency:, interval:)
|
69
97
|
reset_start = start_of_treatment_date(start_date, interval)
|
70
98
|
|
71
|
-
reset_end =
|
99
|
+
reset_end = end_of_treatment_date(reset_start, interval)
|
72
100
|
|
73
101
|
days_between = business_days(reset_start, reset_end).count
|
74
102
|
|
75
|
-
days_passed =
|
103
|
+
days_passed = 0
|
104
|
+
|
105
|
+
visits = 0
|
106
|
+
|
107
|
+
if parse_date(@date) > reset_start
|
108
|
+
days_passed = business_days(reset_start, parse_date(@date)).count
|
109
|
+
end
|
76
110
|
|
77
111
|
if days_between != 0
|
78
|
-
|
79
|
-
else
|
80
|
-
return 0
|
112
|
+
visits = ((frequency/days_between.to_f) * days_passed).round
|
81
113
|
end
|
114
|
+
|
115
|
+
return visits
|
82
116
|
end
|
83
117
|
|
84
118
|
def interval_days(interval)
|
@@ -108,7 +142,10 @@ module Pacing
|
|
108
142
|
end
|
109
143
|
|
110
144
|
def parse_date(date)
|
111
|
-
|
145
|
+
begin
|
146
|
+
Date.strptime(date, '%m-%d-%Y')
|
147
|
+
rescue => exception
|
148
|
+
end
|
112
149
|
end
|
113
150
|
|
114
151
|
def parsed_non_business_days
|
@@ -123,11 +160,13 @@ module Pacing
|
|
123
160
|
# remove holidays(array from Ambiki)
|
124
161
|
# remove school holidays/non business days
|
125
162
|
# should we remove today?(datetime call?)
|
163
|
+
summer = get_working_days(summer_holidays[0], summer_holidays[1])
|
164
|
+
|
126
165
|
working_days = get_working_days(start_date, end_date)
|
127
166
|
|
128
167
|
holidays = get_holidays(start_date, end_date)
|
129
|
-
|
130
|
-
working_days.sort - parsed_non_business_days.sort - holidays.sort
|
168
|
+
|
169
|
+
working_days.sort - parsed_non_business_days.sort - holidays.sort - summer.sort
|
131
170
|
end
|
132
171
|
|
133
172
|
# scoped to the interval
|
@@ -168,12 +207,16 @@ module Pacing
|
|
168
207
|
end
|
169
208
|
|
170
209
|
def get_holidays(start_date, end_date)
|
171
|
-
|
210
|
+
begin
|
211
|
+
Holidays.between(start_date, end_date, @state).map { |holiday| holiday[:date] }
|
212
|
+
rescue => exception
|
213
|
+
end
|
172
214
|
end
|
173
215
|
|
174
216
|
# get actual date of the first day of the week where date falls
|
175
217
|
def week_start(date, offset_from_sunday=0)
|
176
|
-
date
|
218
|
+
return date if date.monday?
|
219
|
+
date - ((date.wday - offset_from_sunday) % 7)
|
177
220
|
end
|
178
221
|
|
179
222
|
# reset date for the yearly interval
|
@@ -183,7 +226,7 @@ module Pacing
|
|
183
226
|
|
184
227
|
# reset date for the monthly interval
|
185
228
|
def reset_date_monthly(start_date, interval)
|
186
|
-
(start_of_treatment_date(parse_date(start_date), interval) + COMMON_YEAR_DAYS_IN_MONTH[(parse_date(@date)).month]
|
229
|
+
(start_of_treatment_date(parse_date(start_date), interval) + COMMON_YEAR_DAYS_IN_MONTH[(parse_date(@date)).month]).strftime("%m-%d-%Y")
|
187
230
|
end
|
188
231
|
|
189
232
|
# reset date for the weekly interval
|
@@ -193,23 +236,213 @@ module Pacing
|
|
193
236
|
|
194
237
|
# start of treatment for the yearly interval
|
195
238
|
def start_of_treatment_date_yearly(start_date)
|
196
|
-
parse_date("#{start_date.month}-#{start_date.day}-#{parse_date(@date).year}")
|
239
|
+
start = parse_date("#{start_date.month}-#{start_date.day}-#{parse_date(@date).year}")
|
240
|
+
if start > parse_date(date)
|
241
|
+
start = start_date
|
242
|
+
end
|
243
|
+
|
244
|
+
start
|
245
|
+
# start_date
|
197
246
|
end
|
198
247
|
|
199
248
|
# start of treatment for the montly interval
|
200
249
|
def start_of_treatment_date_monthly(start_date)
|
201
|
-
|
250
|
+
if @mode == :strict
|
251
|
+
return parse_date("#{parse_date(@date).month}-#{start_date.day}-#{parse_date(@date).year}")
|
252
|
+
else
|
253
|
+
return parse_date("#{parse_date(@date).month}-01-#{parse_date(@date).year}")
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def end_of_treatment_date(reset_start, interval)
|
258
|
+
reset_start + interval_days(interval)
|
202
259
|
end
|
203
260
|
|
204
261
|
# start of treatment for the weekly interval
|
205
262
|
def start_of_treatment_date_weekly(start_date)
|
206
|
-
|
263
|
+
parsed_date = parse_date(@date)
|
264
|
+
week_start_date = week_start(parsed_date)
|
265
|
+
weekly_date = week_start_date
|
207
266
|
|
208
|
-
week_start_date
|
209
|
-
|
210
|
-
|
267
|
+
if week_start_date != parsed_date && @mode == :strict
|
268
|
+
weekly_date = week_start_date + start_date.wday #unless start_date.wday == 1
|
269
|
+
weekly_date = parsed_date < weekly_date ? weekly_date - 7 : weekly_date
|
270
|
+
end
|
211
271
|
|
212
272
|
weekly_date
|
213
273
|
end
|
274
|
+
|
275
|
+
def date_within_range
|
276
|
+
valid_range_or_exceptions = false
|
277
|
+
|
278
|
+
begin
|
279
|
+
@school_plan[:school_plan_services].each do |school_plan_service|
|
280
|
+
if (parse_date(school_plan_service[:start_date]) <= parse_date(@date) && parse_date(@date) <= parse_date(school_plan_service[:end_date]))
|
281
|
+
valid_range_or_exceptions = true
|
282
|
+
end
|
283
|
+
end
|
284
|
+
rescue => exception
|
285
|
+
valid_range_or_exceptions = true
|
286
|
+
end
|
287
|
+
|
288
|
+
valid_range_or_exceptions
|
289
|
+
end
|
290
|
+
|
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
|
+
["Language Therapy", "Speech Therapy", "Speech and Language Therapy", "Speech Language Therapy"].include? service[:type_of_service]
|
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]
|
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
|
+
def readable_suggestion(rate:)
|
398
|
+
# rate = suggested_rate(remaining_visits: remaining_visits, start_date: start_date, interval: interval)
|
399
|
+
|
400
|
+
if rate < 0.2
|
401
|
+
'less than once per week'
|
402
|
+
elsif rate >= 0.2 && rate < 0.25
|
403
|
+
'once a week'
|
404
|
+
elsif rate >= 0.25 && rate < 0.33
|
405
|
+
'once every 4 days'
|
406
|
+
elsif rate >= 0.33 && rate < 0.5
|
407
|
+
'once every 3 days'
|
408
|
+
elsif rate == 0.5
|
409
|
+
'once every other day'
|
410
|
+
elsif rate > 0.5 && rate < 1
|
411
|
+
'about once every other day'
|
412
|
+
elsif rate >= 1.00
|
413
|
+
'once a day'
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def suggested_rate(remaining_visits:, start_date:, interval:)
|
418
|
+
days_left = remaining_days(start_date: start_date, interval: interval).to_f
|
419
|
+
days_left = 1 if days_left == 0
|
420
|
+
(remaining_visits / days_left.to_f).round(2)
|
421
|
+
end
|
422
|
+
|
423
|
+
def remaining_days(start_date:, interval:)
|
424
|
+
reset_start = start_of_treatment_date(start_date, interval)
|
425
|
+
reset_end = end_of_treatment_date(reset_start, interval)
|
426
|
+
|
427
|
+
days_left = business_days(parse_date(@date), reset_end).count
|
428
|
+
|
429
|
+
days_left
|
430
|
+
end
|
431
|
+
|
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
|
+
def parse_summer_holiday_dates
|
438
|
+
holidays_start = parse_date("05-13-#{parse_date(@date).year}")
|
439
|
+
holidays_start += 1 until holidays_start.wday == 5
|
440
|
+
|
441
|
+
holidays_end = parse_date("08-01-#{parse_date(@date).year}")
|
442
|
+
holidays_start += 1 until holidays_start.wday == 1
|
443
|
+
|
444
|
+
[holidays_start, holidays_end]
|
445
|
+
end
|
214
446
|
end
|
215
|
-
end
|
447
|
+
end
|
448
|
+
|
data/lib/pacing/version.rb
CHANGED