pueri 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pueri.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pueri/age'
4
+ require 'pueri/neuro'
5
+ require 'pueri/vax'
6
+ require 'pueri/version'
7
+
8
+ require 'pastel'
9
+ require 'tty-table'
10
+
11
+ # This module is a support for physicians and nurses who work on Primary Care
12
+ # with children age 12 and younger. It calculates the child's age and outputs
13
+ # an optimal vaccination scheme for such age, based on Brazil's National Program
14
+ # of Immunization (PNI), using the 2019 uptade.
15
+ module Pueri; end
data/lib/pueri/age.rb ADDED
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pueri
4
+ # Parses a given date string into and Array of age. The array contains the
5
+ # years, months and days of life of the one who was/would be born on the given
6
+ # day.
7
+ class Age
8
+ attr_reader :days_month, :age
9
+
10
+ # Creates an Age instance.
11
+ #
12
+ # @param date [String] A date current or on the past - NOT on the future.
13
+ def initialize(date)
14
+ @days_month = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
15
+ @age = parse_age(date)
16
+ end
17
+
18
+ # Retrieves the age on a readable format.
19
+ #
20
+ # @param short [Booleann] Whether to output a short-format age or not.
21
+ # Defaults to long format.
22
+ # @return [String] The age in a readable format, either short or long.
23
+ def to_s(short = false)
24
+ if short
25
+ short_string
26
+ else
27
+ long_string
28
+ end
29
+ end
30
+
31
+ # Retrieves the age in days.
32
+ #
33
+ # @return [Float] The age in days, rounded to the second decimal place.
34
+ def to_f
35
+ a = age[0].to_f * 365.25
36
+ a += age[1].to_f * 30.0
37
+ a += age[2].to_f
38
+ a.round(2)
39
+ end
40
+
41
+ private
42
+
43
+ def short_string
44
+ p = Pastel.new
45
+ "#{p.bold(age[0])}a#{p.bold(age[1])}m#{p.bold(age[2])}d"
46
+ end
47
+
48
+ def long_string
49
+ p = Pastel.new
50
+ "Idade: #{p.cyan(age[0])} anos, #{p.cyan(age[1])} meses, "\
51
+ "#{p.cyan(age[2])} dias."
52
+ end
53
+
54
+ # This method takes a date in the string format "DD/MM/YYYY" and returns
55
+ # a Time object.
56
+ def birthday_timestamp_time(date_read)
57
+ date = date_read.chomp
58
+ if date =~ %r{/}
59
+ birthday = date.split('/')
60
+ birthday[2] = year(birthday[2])
61
+ else
62
+ birthday = parse_numonly_date(date)
63
+ end
64
+ Time.new(birthday[2], birthday[1], birthday[0])
65
+ end
66
+
67
+ def parse_numonly_date(date)
68
+ if date.size == 6
69
+ [date[0..1], date[2..3], year(date[4..5])]
70
+ else
71
+ [date[0..1], date[2..3], date[4..7]]
72
+ end
73
+ end
74
+
75
+ def year(year)
76
+ if year.chomp.size == 2
77
+ if year.to_i < 30
78
+ year.prepend('20')
79
+ else
80
+ year.prepend('19')
81
+ end
82
+ end
83
+
84
+ year
85
+ end
86
+
87
+ def future_day?(day, month, birth_date, current_date, borrowed_month)
88
+ if day.negative?
89
+ # subtract month, get positive # for day
90
+ day = @days_month[birth_date.month] - birth_date.day + current_date.day
91
+ month -= 1
92
+ borrowed_month = true
93
+ end
94
+
95
+ [day, month, borrowed_month]
96
+ end
97
+
98
+ def future_month?(month, year, birth_date, current_date, borrowed_month)
99
+ if month.negative?
100
+ # subtract year, get positive # for month
101
+ month = 12 - birth_date.month + current_date.month
102
+ month -= 1 if borrowed_month == true
103
+ year -= 1
104
+ end
105
+
106
+ [month, year]
107
+ end
108
+
109
+ def future_year?(year, month, day)
110
+ # Error-handling for future date
111
+ if year.negative?
112
+ year = 0
113
+ month = 0
114
+ day = 0
115
+ end
116
+
117
+ [year, month, day]
118
+ end
119
+
120
+ def check_future(day, month, year, dates, borrowed_month)
121
+ birth_date, current_date = dates
122
+ day, month, borrowed_month = future_day?(
123
+ day, month, birth_date, current_date, borrowed_month
124
+ )
125
+ month, year = future_month?(
126
+ month, year, birth_date, current_date, borrowed_month
127
+ )
128
+ year, month, day = future_year?(year, month, day)
129
+
130
+ [day, month, year]
131
+ end
132
+
133
+ def check_present(current_date, birth_date)
134
+ day = current_date.day - birth_date.day
135
+ month = current_date.month - birth_date.month
136
+ year = current_date.year - birth_date.year
137
+
138
+ [day, month, year]
139
+ end
140
+
141
+ # This method takes a birthday in the string format "DD/MM/YYYY" and returns
142
+ # the person's age.
143
+ def parse_age(date)
144
+ borrowed_month = false
145
+
146
+ current_date = Time.new
147
+ birth_date = birthday_timestamp_time(date)
148
+
149
+ # Get days for this year
150
+ @days_month[2] = 29 if current_date.to_date.leap?
151
+
152
+ day, month, year = check_present(current_date, birth_date)
153
+
154
+ day, month, year = check_future(
155
+ day, month, year, [birth_date, current_date], borrowed_month
156
+ )
157
+
158
+ [year, month, day]
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pueri
4
+ # Class description here
5
+ class Neuro
6
+ attr_reader :range, :header, :social, :motor, :lang, :table
7
+
8
+ def initialize(norm_age)
9
+ @range = calc_range(norm_age)
10
+ init_tables
11
+ mount_table
12
+ end
13
+
14
+ private
15
+
16
+ def mount_table
17
+ adjust_length
18
+ table_obj = TTY::Table.new @header, make_lines
19
+ @table = table_obj.render :unicode
20
+ end
21
+
22
+ def adjust_length
23
+ max_length = num_lines
24
+ adjust_length_social(max_length)
25
+ adjust_length_motor(max_length)
26
+ adjust_length_lang(max_length)
27
+ end
28
+
29
+ def adjust_length_social(max_length)
30
+ return unless @social.size < max_length
31
+
32
+ (max_length - @social.size).times do
33
+ @social.append ''
34
+ end
35
+ end
36
+
37
+ def adjust_length_motor(max_length)
38
+ return unless @motor.size < max_length
39
+
40
+ (max_length - @motor.size).times do
41
+ @motor.append ''
42
+ end
43
+ end
44
+
45
+ def adjust_length_lang(max_length)
46
+ return unless @lang.size < max_length
47
+
48
+ (max_length - @lang.size).times do
49
+ @lang.append ''
50
+ end
51
+ end
52
+
53
+ def make_lines
54
+ [@social, @motor, @lang].transpose
55
+ end
56
+
57
+ def num_lines
58
+ n = @social.size
59
+ n = @motor.size if @motor.size > n
60
+ n = @lang.size if @lang.size > n
61
+ n
62
+ end
63
+
64
+ def init_tables
65
+ init_header
66
+ init_social
67
+ init_motor
68
+ init_lang
69
+ end
70
+
71
+ def init_header
72
+ @header = %w[
73
+ Social
74
+ Motor
75
+ Linguagem
76
+ ]
77
+ end
78
+
79
+ def init_social
80
+ social = [
81
+ 'Mamar, evacuar e dormir.',
82
+ 'Fixa o olhar com o examinador',
83
+ 'Sorriso social',
84
+ 'Leva a mão a objetos', '', '',
85
+ 'Estranhamento social', '',
86
+ 'Dá tchau e bate palma',
87
+ 'Imita atividades da casa', '', ''
88
+ ]
89
+ @social = social[0..@range].reject(&:empty?)
90
+ end
91
+
92
+ def init_motor
93
+ motor = [
94
+ '', '', '',
95
+ 'Fixa polo cefálico',
96
+ 'Senta com apoio',
97
+ 'Senta sem apoio, pinça superior',
98
+ 'Fica em pé com ajuda', '', '',
99
+ 'Primeiros passos', '', ''
100
+ ]
101
+ @motor = motor[0..@range].reject(&:empty?)
102
+ end
103
+
104
+ def init_lang
105
+ lang = [
106
+ '', '', '', '',
107
+ 'Lalação', '', '',
108
+ 'Primeiras palavras', '',
109
+ 'Palavra frase',
110
+ 'Junta duas palavras',
111
+ 'Fases gramaticais'
112
+ ]
113
+ @lang = lang[0..@range].reject(&:empty?)
114
+ end
115
+
116
+ def out_of_range
117
+ m = 'The given age is out of reality bounds'
118
+ raise ArgumentError, m
119
+ end
120
+
121
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
122
+ # Disabled rubocop for this method for breaking this switch apart will make
123
+ # it less readable than keeping it as a whole logic.
124
+ def calc_range(age)
125
+ r = case age
126
+ when 0.01..59.99 then 0
127
+ when 60.0..89.99 then 1
128
+ when 90.0..119.99 then 2
129
+ when 120.0..179.99 then 3
130
+ when 180.0..269.99 then 4
131
+ when 270.0..299.99 then 5
132
+ when 300.0..365.24 then 6
133
+ when 365.25..455.24 then 7
134
+ when 455.25..545.24 then 8
135
+ when 545.25..730.49 then 9
136
+ when 730.5..1095.74 then 10
137
+ when 1095.75..50_000.0 then 11
138
+ else out_of_range
139
+ end
140
+ r
141
+ end
142
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
143
+ end
144
+ end
data/lib/pueri/vax.rb ADDED
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pueri
4
+ # Gets input from child vaccination calendar for children (PNI) and an age in
5
+ # days. With this, process which vaccines are due by that age.
6
+ class Vax
7
+ attr_reader :header, :calendar, :notes
8
+ attr_reader :range, :table
9
+
10
+ def initialize(norm_age)
11
+ init_base
12
+ @range = calc_range(norm_age)
13
+ parse_calendar
14
+ end
15
+
16
+ # Retrieves the notes for the vaccination calendar, prettified.
17
+ #
18
+ # @return [String] The notes for the vaccines' calendar.
19
+ def parse_notes
20
+ r = ''
21
+ max = @notes.size
22
+ p = Pastel.new
23
+
24
+ @notes.each_with_index do |line, k|
25
+ pre = ' ' * (max - (k + 1))
26
+ pre += '*' * (k.to_i + 1)
27
+ r += "\n#{p.cyan(pre)} #{line}"
28
+ end
29
+
30
+ r
31
+ end
32
+
33
+ private
34
+
35
+ def init_base
36
+ init_header
37
+ init_calendar
38
+ init_notes
39
+ end
40
+
41
+ def init_header
42
+ @header = ['BCB', 'HepB', 'Penta', 'Polio', 'Pneumo 10V', 'Rota',
43
+ 'MeningoC', 'FA', 'HepA', 'SCR', 'Tetra*', 'Varicela',
44
+ 'HPV', 'Influenza']
45
+ end
46
+
47
+ def init_calendar
48
+ part1 = init_calendar1
49
+ part2 = init_calendar2
50
+ @calendar = part1.concat part2
51
+ end
52
+
53
+ def init_calendar1
54
+ [
55
+ ['DU', '1ª', '', '', '', '', '', '', '', '', '', '', '', ''],
56
+ ['', '', '1ª', '1ª VIP', '1ª', '1ª', '', '', '', '', '', '', '', ''],
57
+ ['', '', '', '', '', '', '1ª', '', '', '', '', '', '', ''],
58
+ ['', '', '2ª', '2ª VIP', '2ª', '2ª', '', '', '', '', '', '', '', ''],
59
+ ['', '', '', '', '', '', '2ª', '', '', '', '', '', '', ''],
60
+ ['', '', '3ª', '3ª VIP', '', '', '', '', '', '', '', '', '', '**']
61
+ ]
62
+ end
63
+
64
+ def init_calendar2
65
+ [
66
+ ['', '', '', '', '', '', '', 'DU', '', '', '', '', '', '**'],
67
+ ['', '', '', '', 'Ref', '', 'Ref', '', '', '1ª', '', '', '', '**'],
68
+ ['', '', 'DTP', '1ª VOP', '', '', '', '', 'DU', '', 'DU', '', '', '**'],
69
+ ['', '', 'DTP', '2ª VOP', '', '', '', '', '', '', '', 'DU', '', '**'],
70
+ ['', '', '', '', '', '', '', '', '', '', '', '', '***', '']
71
+ ]
72
+ end
73
+
74
+ def init_notes
75
+ @notes = [
76
+ 'A vacina tetra viral corresponde à segunda dose de SCR e a uma dose '\
77
+ 'de Varicela.',
78
+ 'A partir dos 6 meses aos 6 anos incompletos, uma dose por ano, exceto'\
79
+ ' no primeiro ano de vida, quase se oferta duas doses.',
80
+ 'Meninas de 9 a 14 anos. Meninos de 11 a 14 anos. Duas doses com '\
81
+ 'intervalo de 6 meses.'
82
+ ]
83
+ end
84
+
85
+ def parse_calendar
86
+ table_obj = TTY::Table.new(header: @header) do |table_line|
87
+ @calendar[0..@range].each do |line|
88
+ table_line << line
89
+ end
90
+ end
91
+ @table = table_obj.render(:unicode, alignment: :center)
92
+ end
93
+
94
+ def out_of_range
95
+ m = 'The given age is out of reality bounds'
96
+ raise ArgumentError, m
97
+ end
98
+
99
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
100
+ # Disabled rubocop for this method for breaking this switch apart will make
101
+ # it less readable than keeping it as a whole logic.
102
+ def calc_range(norm_age)
103
+ r = case norm_age
104
+ when 0.01..59.99 then 0
105
+ when 60.0..89.99 then 1
106
+ when 90.0..119.99 then 2
107
+ when 120.0..149.99 then 3
108
+ when 150.0..179.99 then 4
109
+ when 180.0..269.99 then 5
110
+ when 270.0..365.24 then 6
111
+ when 365.25..455.24 then 7
112
+ when 455.25..1460.99 then 8
113
+ when 1461.0..3287.24 then 9
114
+ when 3287.25..50_000.0 then 10
115
+ else out_of_range
116
+ end
117
+ r
118
+ end
119
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module is a support for physicians and nurses who work on Primary Care
4
+ # with children age 12 and younger. It calculates the child's age and outputs
5
+ # an optimal vaccination scheme for such age, based on Brazil's National Program
6
+ # of Immunization (PNI), using the 2019 uptade.
7
+ module Pueri
8
+ # Sets the current version of the gem
9
+ @version = '0.7.0'
10
+
11
+ # Returns the Gem's current version.
12
+ #
13
+ # @return [String] The Gem's current version.
14
+ def self.version
15
+ @version
16
+ end
17
+ end