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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c9f1bb20d57079d96b0bf851745a047e274cc2ac91698dafb61ddc34800875d
4
- data.tar.gz: b83584cd4d641299955cb4b28666d742e17d6ac90ab0168bdf9898e3b20f7307
3
+ metadata.gz: a1a8fc6f5350111b6fc551118c17dbd22eb496e7fe961b851b3b0c818ca28074
4
+ data.tar.gz: 3b19d2df9e4d3fd80d6b66fccf864a19a94ebcfbe337bf91bb7c5a030309694d
5
5
  SHA512:
6
- metadata.gz: 7f9df40d14c5428d64a4465930a675f803eb428d1ccc0602de23e591fd3225993c8c37b9e3437e1954885cd07584d29c7d447d93e2fda4d9d94df4cae0da60a6
7
- data.tar.gz: ed423571438c677b40fde49964c6866fd63a40c763a93c2bcac563f9547c12ad64d5371a4ef705d97f35f2b7594b34dd00b0ee70d5951d29aec1ab05a3b19172
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
- + πŸ‡ Ahead of pace
6
- + 😁 On pace
7
- + 🐒 Behind pace
5
+ - πŸ‡ Ahead of pace
6
+ - 😁 On pace
7
+ - 🐒 Behind pace
8
8
 
9
9
  ## Getting started
10
10
 
11
11
  **Ruby**
12
12
 
13
- *Supports Ruby x.x.x and above*
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 = '01-22-2022' # string (mm-dd-yyyy)
59
- non_business_days = ['01-25-2022'] # array of strings (mm-dd-yyyy)
60
- paced = Pacing::Pacer.new(school_plan: school_plan, date: date, non_business_days: non_business_days)
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
- school_plan_services: [
66
- {
67
- school_plan_type: "IEP",
68
- start_date: "01-01-2022",
69
- end_date: "01-01-2023",
70
- type_of_service: "Language Therapy",
71
- frequency: 6,
72
- interval: "monthly",
73
- time_per_session_in_minutes: 30,
74
- completed_visits_for_current_interval: 7,
75
- extra_sessions_allowable: 1,
76
- interval_for_extra_sessions_allowable: "monthly",
77
- pace: 3,
78
- reset_date: "01-31-2022",
79
- remaining_visits: 0,
80
- pace_indicator: "πŸ‡"
81
- },
82
- {
83
- school_plan_type: "IEP",
84
- start_date: "01-01-2022",
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
- - `school_plan_type` is a string.
124
- - `start_date` is a string.
125
- - `end_date` is a string.
126
- - `type_of_service` is a string.
127
- - `frequency` is an integer.
128
- - `interval` is a string.
129
- - `time_per_session_in_minutes` is an integer.
130
- - `extra_sessions_allowable` is an integer.
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
- + **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).
139
- + **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.
140
- + **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.
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
- + `bundle exec rspec`
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
- + [Report bugs](https://github.com/Ambiki/pacing/issues)
151
- + Fix bugs and [submit pull requests](https://github.com/Ambiki/pacing/pulls)
152
- + Write, clarify, or fix documentation
153
- + Suggest or add new features
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 = @school_plan[:school_plan_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
- service[:pace] = pace(service[:completed_visits_for_current_interval], expected)
57
- service[:reset_date] = reset_date(start_date: service[:start_date], interval: service[:interval_for_extra_sessions_allowable])
58
- service[:remaining_visits] = remaining_visits(completed_visits: service[:completed_visits_for_current_interval], required_visits: service[:frequency])
59
- service[:pace_indicator] = pace_indicator(service[:pace])
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
- { school_plan_services: services }
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 = reset_start + interval_days(interval)
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 = business_days(reset_start, parse_date(@date)).count
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
- return ((frequency/days_between.to_f) * days_passed).round
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
- Date.strptime(date, '%m-%d-%Y')
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
- Holidays.between(start_date, end_date, @state).map { |holiday| holiday[:date] }
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 - ((date.wday - offset_from_sunday)%7)
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]-1).strftime("%m-%d-%Y")
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
- parse_date("#{parse_date(@date).month}-#{start_date.day}-#{parse_date(@date).year}")
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
- date = parse_date(@date)
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 = week_start(date)
209
- weekly_date = week_start_date + start_date.wday
210
- weekly_date = date < weekly_date ? weekly_date - 7 : weekly_date
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
+
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pacing
4
- VERSION = "0.1.3"
4
+ VERSION = "1.0.0"
5
5
  end