pacing 1.0.0 → 2.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: a1a8fc6f5350111b6fc551118c17dbd22eb496e7fe961b851b3b0c818ca28074
4
- data.tar.gz: 3b19d2df9e4d3fd80d6b66fccf864a19a94ebcfbe337bf91bb7c5a030309694d
3
+ metadata.gz: a71bff11bdcc21f74452c5217d348302f8e3680e3c2ed9d753ba1acd156f4d4a
4
+ data.tar.gz: b78da81e0108e399aba1b422f56ce5509948d2708e8b6c5a011a0f82d91086fb
5
5
  SHA512:
6
- metadata.gz: 3e5a7500a4873e8f7748052785fb914ad6b9b3a59c9f80df84a7a836346fe757da050fc81c369bd904184485c06da68757462874a8e25fcfac53b602f2db0f5b
7
- data.tar.gz: 30f8e30fee65de76a98bc99b5e14ede8d94801647c3927e4aae2e43b73cc559797cd4f434f0d1c05b3bc4ac42b49d4021386fdbf791916d95eeba962c8123eea
6
+ metadata.gz: 87c3abd50e088736eae74610469c86972c2ec09c0e8da94d3c7e27dd02e34d62aef25ddab2acf602d90d46e8aefd515d3b1d6102fac37dd813bcbd24ef71f507
7
+ data.tar.gz: 98a2d9a5b3fc8bd62630273ead673bce8a2c2ec3667dd706b3e3ab8efdbdd6db95df39501d8ac0a08170db4b40d6c66439fad08f6937a8c1b61bc97051377c7e
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Pacing
2
2
 
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.
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 Service plan. This gem helps to calculate remaining visits as well as a therapist's current pace to meet visit mandates.
4
4
 
5
5
  - 🐇 Ahead of pace
6
6
  - 😁 On pace
@@ -31,7 +31,7 @@ gem 'pacing'
31
31
  school_plan = {
32
32
  school_plan_services: [
33
33
  {
34
- school_plan_type: "IEP", # string ('IEP', '504 Plan', 'Services Plan' )
34
+ school_plan_type: "IEP", # string ('IEP', '504 Plan', 'Service Plan' )
35
35
  start_date: "01-01-2022", # string (mm-dd-yyyy)
36
36
  end_date: "01-01-2023", # string (mm-dd-yyyy)
37
37
  type_of_service: "Language Therapy", # string ('Language Therapy', 'Speech Therapy', 'Occupation Therapy', 'Physical Therapy', 'Feeding Therapy', 'Speech and Language Therapy')
@@ -43,7 +43,7 @@ school_plan = {
43
43
  interval_for_extra_sessions_allowable: "monthly", # string ('weekly', 'monthly', 'yearly')
44
44
  },
45
45
  {
46
- school_plan_type: "IEP", # string ('IEP', '504 Plan', 'Services Plan' )
46
+ school_plan_type: "IEP", # string ('IEP', '504 Plan', 'Service Plan' )
47
47
  start_date: "01-01-2022", # string (mm-dd-yyyy)
48
48
  end_date: "01-01-2023", # string (mm-dd-yyyy)
49
49
  type_of_service: "Physical Therapy", # string ('Language Therapy', 'Speech Therapy', 'Occupation Therapy', 'Physical Therapy', 'Feeding Therapy', 'Speech and Language Therapy')
@@ -116,8 +116,29 @@ paced.calculate
116
116
  }
117
117
  ]
118
118
  =end
119
+
120
+ paced.interval # Return current interval start and end dates
121
+
122
+ # Below is the result you will get
123
+ =begin
124
+ => [
125
+ {
126
+ discipline: 'Speech Therapy',
127
+ start_date: '04-01-2022',
128
+ reset_date: '05-01-2022'
129
+ },
130
+ {
131
+ discipline: 'Physical Therapy',
132
+ start_date: '04-01-2022',
133
+ reset_date: '05-01-2022'
134
+ }
135
+ ]
136
+ # =>
137
+ =end
119
138
  ```
120
139
 
140
+ 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.
141
+
121
142
  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
143
 
123
144
  ## Data Types
@@ -155,7 +176,7 @@ The following list shows the various variables and what they consist of:
155
176
 
156
177
  - **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
178
  - **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.
179
+ - **Service Plan**: A plan paid for by the local school district for students with disabilities who attend private schools. A Service plan does not have to ensure a child is provided with FAPE (free appropriate public education). A Service 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.
159
180
 
160
181
  ## Testing
161
182
 
@@ -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
+