pvdgm_services 0.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.
Files changed (256) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +29 -0
  3. data/Rakefile +40 -0
  4. data/app/contexts/services/accept_hl7_message_context.rb +32 -0
  5. data/app/contexts/services/application_mds_context.rb +21 -0
  6. data/app/contexts/services/complete_sltc_registration_process_context.rb +23 -0
  7. data/app/contexts/services/create_account_mapping_context.rb +23 -0
  8. data/app/contexts/services/create_configured_account_context.rb +26 -0
  9. data/app/contexts/services/create_configured_facility_context.rb +26 -0
  10. data/app/contexts/services/create_credential_context.rb +23 -0
  11. data/app/contexts/services/create_facility_mapping_context.rb +23 -0
  12. data/app/contexts/services/create_public_key_context.rb +22 -0
  13. data/app/contexts/services/create_service_context.rb +22 -0
  14. data/app/contexts/services/create_service_definition_context.rb +26 -0
  15. data/app/contexts/services/create_sltc_registration_context.rb +24 -0
  16. data/app/contexts/services/create_third_party_context.rb +22 -0
  17. data/app/contexts/services/hl7_message_trimming_context.rb +19 -0
  18. data/app/contexts/services/invoke_service_context.rb +21 -0
  19. data/app/contexts/services/list_sltc_providers_context.rb +22 -0
  20. data/app/contexts/services/mds_file_processing_context.rb +27 -0
  21. data/app/contexts/services/mds_file_upload_context.rb +25 -0
  22. data/app/contexts/services/mds_pull_context.rb +21 -0
  23. data/app/contexts/services/monthly_service_table_cleanup_context.rb +19 -0
  24. data/app/contexts/services/notify_new_registration_context.rb +22 -0
  25. data/app/contexts/services/notify_sltc_provider_change_context.rb +19 -0
  26. data/app/contexts/services/request_sltc_baseline_context.rb +23 -0
  27. data/app/contexts/services/update_account_mapping_context.rb +23 -0
  28. data/app/contexts/services/update_configured_account_context.rb +26 -0
  29. data/app/contexts/services/update_configured_facility_context.rb +26 -0
  30. data/app/contexts/services/update_credential_context.rb +23 -0
  31. data/app/contexts/services/update_facility_mapping_context.rb +23 -0
  32. data/app/contexts/services/update_public_key_context.rb +23 -0
  33. data/app/contexts/services/update_service_context.rb +23 -0
  34. data/app/contexts/services/update_service_definition_context.rb +26 -0
  35. data/app/contexts/services/update_third_party_context.rb +23 -0
  36. data/app/controllers/services/account_mappings_controller.rb +54 -0
  37. data/app/controllers/services/adts_controller.rb +35 -0
  38. data/app/controllers/services/application_controller.rb +17 -0
  39. data/app/controllers/services/assessment_requests_controller.rb +48 -0
  40. data/app/controllers/services/available_files_controller.rb +19 -0
  41. data/app/controllers/services/configured_account_base_controller.rb +17 -0
  42. data/app/controllers/services/configured_accounts_controller.rb +59 -0
  43. data/app/controllers/services/configured_facilities_controller.rb +65 -0
  44. data/app/controllers/services/facility_mappings_controller.rb +58 -0
  45. data/app/controllers/services/mds_files_controller.rb +75 -0
  46. data/app/controllers/services/mds_pull_accounts_controller.rb +57 -0
  47. data/app/controllers/services/public_keys_controller.rb +50 -0
  48. data/app/controllers/services/service_base_controller.rb +18 -0
  49. data/app/controllers/services/service_definition_base_controller.rb +31 -0
  50. data/app/controllers/services/service_definitions_controller.rb +59 -0
  51. data/app/controllers/services/services_controller.rb +56 -0
  52. data/app/controllers/services/sltc_providers_controller.rb +15 -0
  53. data/app/controllers/services/sltc_registrations_controller.rb +74 -0
  54. data/app/controllers/services/status_masking.rb +17 -0
  55. data/app/controllers/services/third_parties_controller.rb +47 -0
  56. data/app/controllers/services/third_party_base_controller.rb +17 -0
  57. data/app/controllers/services/validation_controller.rb +17 -0
  58. data/app/helpers/services/application_helper.rb +8 -0
  59. data/app/helpers/services/assessment_request_helper.rb +19 -0
  60. data/app/helpers/services/available_files_helper.rb +27 -0
  61. data/app/helpers/services/mds_pull_accounts_helper.rb +27 -0
  62. data/app/helpers/services/sltc_registrations_helper.rb +15 -0
  63. data/app/mailers/services_mailer.rb +57 -0
  64. data/app/models/services/abaqis_mds_push.rb +51 -0
  65. data/app/models/services/account_mapping.rb +10 -0
  66. data/app/models/services/application_api.rb +19 -0
  67. data/app/models/services/assessment_request.rb +18 -0
  68. data/app/models/services/available_file.rb +73 -0
  69. data/app/models/services/configured_account.rb +26 -0
  70. data/app/models/services/configured_facility.rb +16 -0
  71. data/app/models/services/credential.rb +9 -0
  72. data/app/models/services/facility_mapping.rb +12 -0
  73. data/app/models/services/ftp_server.rb +69 -0
  74. data/app/models/services/hl7_inbound_service.rb +47 -0
  75. data/app/models/services/hl7_message.rb +27 -0
  76. data/app/models/services/isc_code.rb +15 -0
  77. data/app/models/services/isc_code_lookup.rb +75 -0
  78. data/app/models/services/mds_assessment.rb +371 -0
  79. data/app/models/services/mds_content.rb +55 -0
  80. data/app/models/services/mds_pull.rb +41 -0
  81. data/app/models/services/mds_pull_account.rb +192 -0
  82. data/app/models/services/mds_push.rb +24 -0
  83. data/app/models/services/mds_upload.rb +64 -0
  84. data/app/models/services/mds_upload_content.rb +148 -0
  85. data/app/models/services/mds_ws_response.rb +21 -0
  86. data/app/models/services/mds_ws_response_handler.rb +31 -0
  87. data/app/models/services/pcc_mds_pull.rb +77 -0
  88. data/app/models/services/provider_change.rb +26 -0
  89. data/app/models/services/public_key.rb +11 -0
  90. data/app/models/services/service.rb +20 -0
  91. data/app/models/services/service_definition.rb +37 -0
  92. data/app/models/services/service_implementation.rb +29 -0
  93. data/app/models/services/sltc_api.rb +179 -0
  94. data/app/models/services/sltc_api_exception.rb +54 -0
  95. data/app/models/services/sltc_mds_pull.rb +230 -0
  96. data/app/models/services/sltc_registration.rb +23 -0
  97. data/app/models/services/third_party.rb +18 -0
  98. data/app/roles/services/account_mapping_creator.rb +13 -0
  99. data/app/roles/services/account_mapping_updator.rb +12 -0
  100. data/app/roles/services/configured_account_creator.rb +13 -0
  101. data/app/roles/services/configured_account_updater.rb +12 -0
  102. data/app/roles/services/configured_facility_creator.rb +13 -0
  103. data/app/roles/services/configured_facility_updater.rb +12 -0
  104. data/app/roles/services/credential_creator.rb +11 -0
  105. data/app/roles/services/credential_updater.rb +20 -0
  106. data/app/roles/services/facility_mapping_creator.rb +13 -0
  107. data/app/roles/services/facility_mapping_updater.rb +11 -0
  108. data/app/roles/services/hl7_adt_message_saver.rb +15 -0
  109. data/app/roles/services/hl7_message_trimmer.rb +14 -0
  110. data/app/roles/services/mds_assessment_categorizer.rb +71 -0
  111. data/app/roles/services/mds_file_processor.rb +86 -0
  112. data/app/roles/services/mds_notifications.rb +44 -0
  113. data/app/roles/services/monthly_service_table_cleaner.rb +19 -0
  114. data/app/roles/services/new_registration_notifier.rb +11 -0
  115. data/app/roles/services/public_key_creator.rb +14 -0
  116. data/app/roles/services/public_key_updater.rb +12 -0
  117. data/app/roles/services/service_creator.rb +13 -0
  118. data/app/roles/services/service_definition_creator.rb +13 -0
  119. data/app/roles/services/service_definition_updater.rb +12 -0
  120. data/app/roles/services/service_invoker.rb +25 -0
  121. data/app/roles/services/service_updater.rb +11 -0
  122. data/app/roles/services/sltc_baseline_requestor.rb +19 -0
  123. data/app/roles/services/sltc_provider_change_notifier.rb +25 -0
  124. data/app/roles/services/sltc_provider_lister.rb +13 -0
  125. data/app/roles/services/sltc_registration_completer.rb +81 -0
  126. data/app/roles/services/sltc_registration_saver.rb +13 -0
  127. data/app/roles/services/submit_mds_file_for_processing.rb +28 -0
  128. data/app/roles/services/third_party_creator.rb +13 -0
  129. data/app/roles/services/third_party_updater.rb +12 -0
  130. data/app/utils/services/file_upload_handler.rb +33 -0
  131. data/app/utils/services/mds_upload_filters.rb +26 -0
  132. data/app/utils/services/mds_xml_file_parser.rb +104 -0
  133. data/app/utils/services/upload_file.rb +13 -0
  134. data/app/validators/isc_code_validator.rb +42 -0
  135. data/app/validators/mds_birthdate_validator.rb +37 -0
  136. data/app/validators/mds_date_validator.rb +8 -0
  137. data/app/validators/mds_integer_validator.rb +15 -0
  138. data/app/validators/mds_version_validator.rb +16 -0
  139. data/app/validators/state_code_validator.rb +9 -0
  140. data/app/views/layouts/services/application.html.erb +14 -0
  141. data/app/views/services/account_mappings/index.json.jbuilder +9 -0
  142. data/app/views/services/account_mappings/show.json.jbuilder +7 -0
  143. data/app/views/services/assessment_requests/index.json.jbuilder +11 -0
  144. data/app/views/services/available_files/index.json.jbuilder +13 -0
  145. data/app/views/services/configured_accounts/index.json.jbuilder +13 -0
  146. data/app/views/services/configured_accounts/show.json.jbuilder +11 -0
  147. data/app/views/services/configured_facilities/index.json.jbuilder +13 -0
  148. data/app/views/services/configured_facilities/show.json.jbuilder +11 -0
  149. data/app/views/services/facility_mappings/index.json.jbuilder +10 -0
  150. data/app/views/services/facility_mappings/show.json.jbuilder +8 -0
  151. data/app/views/services/mds_files/create.builder +10 -0
  152. data/app/views/services/mds_pull_accounts/index.json.jbuilder +11 -0
  153. data/app/views/services/public_keys/index.json.jbuilder +8 -0
  154. data/app/views/services/public_keys/show.json.jbuilder +6 -0
  155. data/app/views/services/service_definitions/index.json.jbuilder +17 -0
  156. data/app/views/services/service_definitions/show.json.jbuilder +16 -0
  157. data/app/views/services/services/index.json.jbuilder +6 -0
  158. data/app/views/services/services/show.json.jbuilder +4 -0
  159. data/app/views/services/sltc_providers/index.json.jbuilder +10 -0
  160. data/app/views/services/sltc_registrations/index.json.jbuilder +10 -0
  161. data/app/views/services/third_parties/index.json.jbuilder +7 -0
  162. data/app/views/services/third_parties/show.json.jbuilder +5 -0
  163. data/app/views/services_mailer/burying_job.html.erb +20 -0
  164. data/app/views/services_mailer/delaying_job.html.erb +20 -0
  165. data/app/views/services_mailer/notify_sltc_client_registration.html.erb +39 -0
  166. data/app/views/services_mailer/notify_sltc_client_registration_invalid_request_type.html.erb +8 -0
  167. data/app/views/services_mailer/notify_sltc_client_registration_request_invalid.html.erb +10 -0
  168. data/app/views/services_mailer/notify_sltc_client_registration_validation_errors.html.erb +13 -0
  169. data/app/views/services_mailer/notify_sltc_provider_changes.html.erb +26 -0
  170. data/app/views/services_mailer/notify_support_about_no_mds_pull.html.erb +6 -0
  171. data/config/routes.rb +48 -0
  172. data/db/migrate/20140102000000_create_services_engine_tables.rb +131 -0
  173. data/db/migrate/20140517184450_new_services_columns.rb +24 -0
  174. data/db/migrate/20140525142842_new_configured_provider_table.rb +18 -0
  175. data/db/migrate/20140714172442_add_error_column_to_hl7_messages.rb +5 -0
  176. data/db/migrate/20140730164152_mds_upload_tables.rb +78 -0
  177. data/db/sql_data/service_data_setup.sql +24 -0
  178. data/lib/services/engine.rb +40 -0
  179. data/lib/services/version.rb +3 -0
  180. data/lib/services.rb +4 -0
  181. data/lib/tasks/services_tasks.rake +40 -0
  182. data/spec/dummy/README.rdoc +261 -0
  183. data/spec/dummy/Rakefile +7 -0
  184. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  185. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  186. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  187. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  188. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  189. data/spec/dummy/config/application.rb +60 -0
  190. data/spec/dummy/config/boot.rb +10 -0
  191. data/spec/dummy/config/database.yml +29 -0
  192. data/spec/dummy/config/environment.rb +5 -0
  193. data/spec/dummy/config/environments/development.rb +35 -0
  194. data/spec/dummy/config/environments/production.rb +68 -0
  195. data/spec/dummy/config/environments/test.rb +32 -0
  196. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  197. data/spec/dummy/config/initializers/email.rb +1 -0
  198. data/spec/dummy/config/initializers/inflections.rb +15 -0
  199. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  200. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  201. data/spec/dummy/config/initializers/session_store.rb +8 -0
  202. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  203. data/spec/dummy/config/locales/en.yml +5 -0
  204. data/spec/dummy/config/routes.rb +4 -0
  205. data/spec/dummy/config.ru +4 -0
  206. data/spec/dummy/db/schema.rb +237 -0
  207. data/spec/dummy/db/sql_data/services_isc_codes.sql +878 -0
  208. data/spec/dummy/log/test.log +3498 -0
  209. data/spec/dummy/public/404.html +26 -0
  210. data/spec/dummy/public/422.html +26 -0
  211. data/spec/dummy/public/500.html +25 -0
  212. data/spec/dummy/public/favicon.ico +0 -0
  213. data/spec/dummy/script/rails +6 -0
  214. data/spec/models/abaqis_mds_push_spec.rb +120 -0
  215. data/spec/models/available_file_spec.rb +234 -0
  216. data/spec/models/configured_account_spec.rb +39 -0
  217. data/spec/models/ftp_server_spec.rb +221 -0
  218. data/spec/models/isc_code_lookup_spec.rb +125 -0
  219. data/spec/models/isc_code_spec.rb +5 -0
  220. data/spec/models/mds_assessment_spec.rb +1070 -0
  221. data/spec/models/mds_pull_account_spec.rb +468 -0
  222. data/spec/models/mds_pull_spec.rb +48 -0
  223. data/spec/models/mds_push_spec.rb +43 -0
  224. data/spec/models/mds_ws_response_spec.rb +54 -0
  225. data/spec/models/pcc_mds_pull_spec.rb +273 -0
  226. data/spec/models/service_implementation_spec.rb +88 -0
  227. data/spec/models/sltc_api_exception_spec.rb +136 -0
  228. data/spec/models/sltc_api_spec.rb +192 -0
  229. data/spec/models/sltc_mds_pull_spec.rb +776 -0
  230. data/spec/roles/account_mapping_creator_spec.rb +40 -0
  231. data/spec/roles/account_mapping_updator_spec.rb +16 -0
  232. data/spec/roles/configured_account_creator_spec.rb +40 -0
  233. data/spec/roles/configured_account_updater_spec.rb +16 -0
  234. data/spec/roles/configured_facility_creator_spec.rb +40 -0
  235. data/spec/roles/configured_facility_updater_spec.rb +16 -0
  236. data/spec/roles/credential_creator_spec.rb +23 -0
  237. data/spec/roles/credential_updater_spec.rb +38 -0
  238. data/spec/roles/facility_mapping_creator_spec.rb +40 -0
  239. data/spec/roles/facility_mapping_updater_spec.rb +16 -0
  240. data/spec/roles/hl7_adt_message_saver_spec.rb +35 -0
  241. data/spec/roles/hl7_message_trimmer_spec.rb +31 -0
  242. data/spec/roles/monthly_service_table_cleaner_spec.rb +27 -0
  243. data/spec/roles/new_registration_notifier_spec.rb +18 -0
  244. data/spec/roles/service_ceator_spec.rb +34 -0
  245. data/spec/roles/service_definition_creator_spec.rb +40 -0
  246. data/spec/roles/service_definition_updater_spec.rb +16 -0
  247. data/spec/roles/service_invoker_spec.rb +22 -0
  248. data/spec/roles/service_updater_spec.rb +17 -0
  249. data/spec/roles/sltc_baseline_requestor_spec.rb +30 -0
  250. data/spec/roles/sltc_provider_lister_spec.rb +27 -0
  251. data/spec/roles/sltc_registration_completer_spec.rb +187 -0
  252. data/spec/roles/sltc_registration_saver_spec.rb +34 -0
  253. data/spec/roles/third_party_creator_spec.rb +34 -0
  254. data/spec/roles/third_party_updater_spec.rb +17 -0
  255. data/spec/spec_helper.rb +72 -0
  256. metadata +581 -0
@@ -0,0 +1,371 @@
1
+ module Services
2
+
3
+ class MdsAssessment < ActiveRecord::Base
4
+ include MdsAssessmentCategorizer
5
+
6
+ belongs_to :mds_upload
7
+ belongs_to :facility
8
+ belongs_to :resident
9
+ belongs_to :stay
10
+
11
+ attr_encrypted :responses, type: 'json'
12
+
13
+ VERSION1_0 = "1.00"
14
+ VERSION1_1 = "1.10"
15
+ VERSION1_1_DATE = "20120401"
16
+ CORRECTED = 1
17
+
18
+ MATCH_MAP = {
19
+ 'x0150' => 'a0200',
20
+ 'x0600a' => 'a0310a',
21
+ 'x0600b' => 'a0310b',
22
+ 'x0600c' => 'a0310c',
23
+ 'x0600d' => 'a0310d',
24
+ 'x0600f' => 'a0310f',
25
+ 'x0700a' => 'a2300',
26
+ 'x0700b' => 'a2000',
27
+ 'x0700c' => 'a1600'
28
+ }
29
+
30
+ MISSING_ATTRIBUTE_VALUES = %w{ ^ - }
31
+ CLEAN_ATTR_METHOD_REX = /^clean_(\w+)$/
32
+
33
+ # Record types
34
+ ADD_NEW_RECORD = 1
35
+ MODIFY_EXISTING_RECORD = 2
36
+ INACTIVATE_EXISTING_RECORD = 3
37
+
38
+ # These are fields that must be treated as if they are
39
+ # full-fledged attributes of the assessment. If they are
40
+ # not listed here, any access to the field will raise an
41
+ # exception from #method_missing.
42
+ VALIDATED_FIELDS = [ :itm_sbst_cd,
43
+ :fac_id,
44
+ :corrected,
45
+ :prior_isc_cd,
46
+ :state_cd,
47
+ :a0050,
48
+ :a0100b,
49
+ :a0200,
50
+ :a1600,
51
+ :a1800,
52
+ :a2000,
53
+ :a2200,
54
+ :a2300,
55
+ :a0310a,
56
+ :a0310b,
57
+ :a0310c,
58
+ :a0310d,
59
+ :a0310f,
60
+ :a0900,
61
+ :k0200a,
62
+ :k0200b,
63
+ :x0100]
64
+
65
+ CREATED = 0
66
+ PROCESSED = 1
67
+
68
+ alias_attribute :assessment_date, :reference_date
69
+
70
+ validates :responses,
71
+ presence: true
72
+
73
+ validates :fac_id,
74
+ presence: {
75
+ :message => "FAC_ID (CMS Assigned facility submission id) cannot be blank.",
76
+ if: :has_responses? }
77
+
78
+ validates :itm_sbst_cd,
79
+ presence: {
80
+ message: "ITM_SBST_CD (ISC) cannot be blank.",
81
+ if: :has_responses? },
82
+ isc_code: {
83
+ if: :has_responses? }
84
+
85
+ validates :state_cd,
86
+ presence: {
87
+ message: "Invalid state code entered in state_cd.",
88
+ if: :has_responses? },
89
+ state_code: {
90
+ if: :has_responses? }
91
+
92
+ validates :a1600,
93
+ mds_date: {
94
+ unless: :inactivation_assessment?,
95
+ if: :has_responses? }
96
+
97
+ validates :a2000,
98
+ mds_date: { if: :discharge_or_death_assessment? }
99
+
100
+ validates :a2300,
101
+ mds_date: { if: :a0310f_99? }
102
+
103
+ validates :a0900,
104
+ mds_birthdate: {
105
+ msg_invalid_characters: "Invalid birth date entered in a0900. Birth date may contain only numbers.",
106
+ msg_invalid_format: "Invalid birth date entered in a0900. Birth date must be formatted as YYYYMMDD, YYYYMM or YYYY.",
107
+ msg_invalid: "Invalid birth date entered in a0900",
108
+ if: :has_responses? }
109
+
110
+ validates :k0200a,
111
+ mds_integer: {
112
+ minimum: 0,
113
+ maximum: 99,
114
+ message: "Invalid height entered in k0200a. Height must be between 0 and 99.",
115
+ if: :has_responses? }
116
+
117
+ validates :k0200b,
118
+ mds_integer: {
119
+ minimum: 0,
120
+ maximum: 999,
121
+ message: "Invalid weight entered in k0200b. Weight must be between 0 and 999.",
122
+ if: :has_responses? }
123
+
124
+ validates :version,
125
+ mds_version: {
126
+ if: :has_responses? }
127
+
128
+ validate :twisted_discharge_dates,
129
+ if: :discharge_or_death_assessment?
130
+
131
+ before_validation :determine_reference_date, unless: :has_reference_date?
132
+ before_validation :determine_version, if: :has_reference_date?
133
+ before_validation :determine_matching_fields, if: :has_reference_date?
134
+ before_create :set_inact_corr_fields
135
+
136
+ scope :noncorrected, -> { where("corrected is null OR corrected = 0") }
137
+
138
+ scope :correctable_assessments, ->(mds_id, resident_id) {
139
+ where(resident_id: resident_id).
140
+ where("id != ?", mds_id).
141
+ where("corrected != ? or corrected IS NULL", CORRECTED) }
142
+
143
+ scope :correction_assessments_for_target, ->(mds) {
144
+ where(resident_id: mds.resident_id).
145
+ where(type_of_record: 2).
146
+ where("
147
+ (previous_reference_date = '#{mds.a2300}' AND item_subset_code = '#{mds.item_subset_code}') OR
148
+ (inact_corr_match = '#{mds.inact_corr_match}')
149
+ ".compact).
150
+ order("reference_date DESC")
151
+ }
152
+
153
+ scope :inactivation_assessments_for_target, ->(mds) {
154
+ where(resident_id: mds.resident_id).
155
+ where(item_subset_code: IscCode::INACTIVATION_CODE).
156
+ where("
157
+ (previous_reference_date = '#{mds.a2300}' AND item_subset_code = '#{mds.item_subset_code}') OR
158
+ (inact_corr_match = '#{mds.inact_corr_match}')
159
+ ".compact).
160
+ order("reference_date DESC")
161
+ }
162
+
163
+ # Paired with method_missing
164
+ def respond_to_missing?(sym, include_private=false)
165
+ VALIDATED_FIELDS.include?(sym) || (responses.present? && responses.keys.include?(sym.to_s)) || super
166
+ end
167
+
168
+ def method_missing(sym, *args)
169
+ # If an attribute is referenced as mds.clean_* (e.g. mds.clean_k0200b), the value
170
+ # we will return the value of the attribute as nil if it is one of the special
171
+ # mds values (^|-). If we do it this way, we don't have to splatter the special
172
+ # value junk all over the code base.
173
+ if sym.to_s =~ CLEAN_ATTR_METHOD_REX
174
+ return filter_value(responses.present? ? responses[$1] : nil)
175
+ end
176
+ return responses.present? ? responses[sym.to_s] : nil if VALIDATED_FIELDS.include?(sym)
177
+ return responses[sym.to_s] if responses.present? && responses.keys.include?(sym.to_s)
178
+ super
179
+ end
180
+
181
+ def unit_id
182
+ MDS_MODULE_ID
183
+ end
184
+
185
+ def has_responses?
186
+ responses.present?
187
+ end
188
+
189
+ def version1_0?
190
+ reference_date < Date.parse("20120401")
191
+ end
192
+
193
+ def version1_1?
194
+ reference_date >= Date.parse("20120401")
195
+ end
196
+
197
+ def entry_type
198
+ (clean_a1700 == "2") ? Stay::REENTRY : Stay::ADMISSION
199
+ end
200
+
201
+ def entry_date
202
+ a1600
203
+ end
204
+
205
+ def discharge_date
206
+ a2000
207
+ end
208
+
209
+ def discharge_type
210
+ clean_a2100.blank? ? nil : clean_a2100.to_i
211
+ end
212
+
213
+ def ssn
214
+ clean_a0600a || clean_x0500 || ""
215
+ end
216
+
217
+ def first_name
218
+ clean_a0500a || clean_x0200a || ""
219
+ end
220
+
221
+ def last_name
222
+ clean_a0500c || clean_x0200c || ""
223
+ end
224
+
225
+ def gender
226
+ clean_a0800 || clean_x0300 || ""
227
+ end
228
+
229
+ def comatose?
230
+ clean_b0100 == "1"
231
+ end
232
+
233
+ def inact_corr_match_fields(entry_discharge_reporting)
234
+ fields = %w{ x0150 x0600a x0600b x0600c x0600d x0600f }
235
+ case entry_discharge_reporting
236
+ when 99
237
+ fields << 'x0700a'
238
+ when 01
239
+ fields << 'x0700c'
240
+ when 10, 11, 12
241
+ fields << 'x0700b'
242
+ end
243
+ fields
244
+ end
245
+
246
+ def hash_field_values(fields, target=true)
247
+ field_values = fields.map do |f|
248
+ value_field = target ? f : MATCH_MAP[f]
249
+ "#{f}=#{responses[value_field]}"
250
+ end
251
+
252
+ # binding.pry
253
+
254
+ Digest::MD5.hexdigest(field_values.join(','))
255
+ end
256
+
257
+ private
258
+
259
+ def filter_value(value)
260
+ MISSING_ATTRIBUTE_VALUES.include?(value) ? nil : value
261
+ end
262
+
263
+ def twisted_discharge_dates
264
+ e_date = Date.safe_parse(entry_date)
265
+ d_date = Date.safe_parse(discharge_date)
266
+
267
+ return if e_date.nil? || d_date.nil?
268
+
269
+ errors.add('entry_discharge_dates', "Discharge date (a2000) must be later than or equal to entry date (a1600)") if e_date > d_date
270
+ end
271
+
272
+ def self.disp_abbr
273
+ "MDS 3.0"
274
+ end
275
+
276
+ def self.find_assessment_data(facility, filter, options={})
277
+ facility.find_matching_qcli_results(filter, options)
278
+ end
279
+
280
+ def self.find_non_interviewed_data(facility, filter, options={})
281
+ return nil
282
+ end
283
+
284
+
285
+ def self.find_flagged_results_for_qcli_or_subgroup(facility, filter, qcli, subgroup)
286
+ qcli_ids = Unit.find(7).qclis.collect(&:id).join(",")
287
+ qcli_results = facility.find_matching_qcli_results(filter, {:qcli_ids => qcli_ids})
288
+ (subgroup) ? qcli_results.select{|result| result.qcli_id == qcli.id && result.flags_subgroup_numerator?(qcli, subgroup.columns)} : qcli_results.select{|result| result.flags_numerator?(qcli)}
289
+ end
290
+
291
+ def self.initials; self.table_name.split('_')[1..-1].map{|i| i.slice(0,1)}.join; end
292
+
293
+ def self.select_latest_assessment_sql(filter, resident_ids_override=nil)
294
+ resident_ids = resident_ids_override || Resident.resident_ids_in_group_or_sample(filter)
295
+ resident_sql = (resident_ids.present?) ? "resident_id in (#{resident_ids.join(",")})" : "facility_id = #{filter.facility.id}"
296
+
297
+
298
+ sql=<<-SQL
299
+ SELECT *
300
+ FROM (#{order_latest_assessments_for_use(filter.facility.id, filter)}) mds_assmts
301
+ WHERE #{resident_sql}
302
+ SQL
303
+ sql.compact
304
+ end
305
+
306
+ def self.order_latest_assessments_for_use(facility_id, filter)
307
+
308
+ date_sql = ""
309
+ if filter[:mds_date_s].present?
310
+ date_sql = "AND date_e >= '#{filter['mds_date_s']}' AND date_e <= '#{filter.date_e}'"
311
+ elsif filter.date_s.present?
312
+ date_sql = "AND date_e >= '#{filter.date_s}' AND date_e <= '#{filter.date_e}'"
313
+ end
314
+
315
+ #date_sql = (filter.date_s.present?) ? "AND date_e >= '#{filter.date_s}' AND date_e <= '#{filter.date_e}'" : ""
316
+ #date_sql = (filter[:mds_date_s].present?) ? "AND date_e >= '#{filter['mds_date_s']}' AND date_e <= '#{filter.date_e}'" : ""
317
+ exclusion_sql = (filter[:mds_exclude_empty_pos_neg].present?) ? " AND NOT(positives LIKE '%[]%' and negatives LIKE '%[]%') " : ""
318
+ sql=<<-SQL
319
+ SELECT *
320
+ FROM
321
+ (SELECT * FROM qcli_results
322
+ WHERE facility_id = #{facility_id} #{date_sql} #{exclusion_sql}
323
+ ORDER BY date_s desc, id desc) mds_assessment_results
324
+ GROUP BY resident_id, qcli_id
325
+ SQL
326
+ sql.compact
327
+ end
328
+
329
+ def has_reference_date?
330
+ self.reference_date.present?
331
+ end
332
+
333
+ def set_inact_corr_fields
334
+ self.obra_reason = clean_a0310a
335
+ self.pps_reason = clean_a0310b
336
+ self.entry_disch_reporting = [ INACTIVATE_EXISTING_RECORD, MODIFY_EXISTING_RECORD ].include?(type_of_record) ? x0600f.to_i : a0310f
337
+ self.inact_corr_match = hash_field_values(inact_corr_match_fields(entry_disch_reporting), [ INACTIVATE_EXISTING_RECORD, MODIFY_EXISTING_RECORD ].include?(type_of_record) )
338
+ end
339
+
340
+ def determine_matching_fields
341
+ self.item_subset_code = itm_sbst_cd
342
+ self.type_of_record = (version == VERSION1_0 ? x0100 : a0050).to_i
343
+ self.previous_reference_date = a2200 if type_of_record == MODIFY_EXISTING_RECORD
344
+ end
345
+
346
+ def determine_reference_date
347
+ ref_date = entry_assessment? ? self.clean_a1600 : discharge_or_death_assessment? ? self.clean_a2000 : nil
348
+
349
+ if inactivation_assessment?
350
+ ref_date =
351
+ case self.x0600f
352
+ when "99" then self.clean_x0700a
353
+ when "01" then self.clean_x0700c
354
+ when "10", "11", "12" then self.clean_x0700b
355
+ else self.clean_x1100e
356
+ end
357
+ end
358
+
359
+ ref_date ||= self.clean_a2300
360
+ ref_date ||= self.clean_z0500b
361
+
362
+ self.reference_date = Date.safe_parse(ref_date)
363
+ end
364
+
365
+ def determine_version
366
+ self.version = self.reference_date >= Date.parse(VERSION1_1_DATE) ? VERSION1_1 : VERSION1_0
367
+ end
368
+
369
+ end
370
+
371
+ end
@@ -0,0 +1,55 @@
1
+ module Services
2
+
3
+ class MdsContent < ActiveRecord::Base
4
+ BASE_EXTRACTION_PATH = Rails.root.join('tmp').to_path
5
+
6
+ attr_encrypted :content, type: 'binary'
7
+ belongs_to :uploadable, polymorphic: true
8
+
9
+ def each_entry(&block)
10
+ each_zipped_file('**/*', &block)
11
+ end
12
+
13
+ def each_zipped_file(glob_pattern)
14
+ write_content_to_file
15
+ extract_zip_contents
16
+ Dir.glob(File.join(extracted_files_path, glob_pattern), File::FNM_CASEFOLD).sort.each do |zip_file_path|
17
+ yield zip_file_path, zip_file_path[extracted_files_path.length + 1..-1]
18
+ end
19
+
20
+ ensure
21
+ remove_content_file
22
+ remove_extracted_files
23
+ end
24
+
25
+ def content_path
26
+ @content_path ||= File.join(BASE_EXTRACTION_PATH, content_file_name)
27
+ end
28
+
29
+ def extracted_files_path
30
+ @extracted_files_path ||= File.join(BASE_EXTRACTION_PATH, "extracted_files_#{id}")
31
+ end
32
+
33
+ def extract_zip_contents
34
+ FileUtils.mkdir_p(extracted_files_path) unless File.exists?(extracted_files_path)
35
+ RecursiveUnzipper.new(content_path).extract_to(extracted_files_path)
36
+ end
37
+
38
+ def write_content_to_file
39
+ File.open(content_path, "wb") { |f| f.write(content) }
40
+ end
41
+
42
+ def remove_content_file
43
+ File.unlink(content_path) if File.exists?(content_path)
44
+ end
45
+
46
+ def remove_extracted_files
47
+ FileUtils.rm_rf(extracted_files_path) if File.exists?(extracted_files_path)
48
+ end
49
+
50
+ def content_file_name
51
+ @content_file_name ||= "composite_file_#{id}.zip"
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,41 @@
1
+ module Services
2
+
3
+ class MdsPull < ServiceImplementation
4
+
5
+ #
6
+ # The async configuration options
7
+ #
8
+ ASYNC_SEND_OPTS = {
9
+ :priority => 100, # Pretty high priority; higher than MDS for sure
10
+ :time_to_run => 3600 # GIve it an hour
11
+ }
12
+
13
+ #
14
+ # Main (only public) entry point for the MDS pull service.
15
+ #
16
+ def invoke
17
+
18
+ service_definition.configured_accounts.enabled.each do | configured_account |
19
+
20
+ mds_pull_account = MdsPullAccount.create!(configured_account: configured_account)
21
+
22
+ mds_pull_account.ayl_send_opts(:process, ASYNC_SEND_OPTS)
23
+
24
+ end
25
+ end
26
+
27
+ def request_baseline(configured_account, provider_id, uploaded_after, uploaded_before)
28
+ # Do nothing by default; the derived class will handle the necessary details.
29
+ end
30
+
31
+ def identify_providers(configured_account)
32
+ # Do nothing by default. Only some service providers will provide this feature.
33
+ end
34
+
35
+ def request_assessments(configured_account)
36
+ # Do nothing by default. Only some service providers will provide this feature.
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,192 @@
1
+ module Services
2
+
3
+ class MdsPullAccount < ActiveRecord::Base
4
+ include FileUtils
5
+ belongs_to :configured_account
6
+
7
+ DECAY_TIME = 1200 # 20 minutes
8
+
9
+ #
10
+ # Define all the statuses allowed for this object
11
+ #
12
+ STATUS_NEW = 0
13
+ STATUS_PROVIDERS_IDENTIFIED = 1
14
+ STATUS_ASSESSMENTS_REQUESTED = 2
15
+ STATUS_FILES_IDENTIFIED = 3
16
+ STATUS_FILES_DOWNLOADED = 4
17
+ STATUS_SUBMITTED_TO_ABAQIS = 5
18
+ STATUS_FILE_SYSTEM_CLEAN = 6
19
+ STATUS_REMOTE_CLEAN = 7
20
+
21
+ #
22
+ # Define the order of statuses for this object. The statuses
23
+ # can proceed ONLY in this order
24
+ #
25
+ PROCESSING_STATES = [ STATUS_NEW,
26
+ STATUS_PROVIDERS_IDENTIFIED,
27
+ STATUS_ASSESSMENTS_REQUESTED,
28
+ STATUS_FILES_IDENTIFIED,
29
+ STATUS_FILES_DOWNLOADED,
30
+ STATUS_SUBMITTED_TO_ABAQIS,
31
+ STATUS_FILE_SYSTEM_CLEAN,
32
+ STATUS_REMOTE_CLEAN ]
33
+
34
+ #
35
+ # Define the task to perform for each of the valid status, so if
36
+ # the current status of the object is STATUS_FILES_IDENTIFIED, then
37
+ # the :download_files method should be invoked. If the method is
38
+ # sucessful (doesn't raise an exception), the status of the object is automatically
39
+ # moved to the next valid status (from the list above).
40
+ #
41
+ STATE_MACHINE = {
42
+ STATUS_NEW => :identify_providers, #=> :identify_files_to_pull,
43
+ STATUS_PROVIDERS_IDENTIFIED => :request_assessments_to_process,
44
+ STATUS_ASSESSMENTS_REQUESTED => :identify_files_to_pull,
45
+ STATUS_FILES_IDENTIFIED => :download_files,
46
+ STATUS_FILES_DOWNLOADED => :submit_files_to_abaqis,
47
+ STATUS_SUBMITTED_TO_ABAQIS => :clean_up_file_system,
48
+ STATUS_FILE_SYSTEM_CLEAN => :clean_up_remote_server
49
+ }
50
+
51
+ has_one :service_definition, :through => :configured_account
52
+
53
+ validates :status, presence: true, inclusion: { in: PROCESSING_STATES }
54
+ validates :attempt, presence: true
55
+
56
+ def process
57
+ logger.info "Processing configured account: #{configured_account.to_rrepr}"
58
+
59
+ # Update the attempt count for the MDS Pull Account record. (how many times have we tried
60
+ # to process this record?)
61
+ update_attribute :attempt, attempt + 1
62
+
63
+ PROCESSING_STATES.each do | p_status |
64
+ perform_task p_status
65
+ end
66
+
67
+ rescue Exception => ex
68
+ logger.error "Error processing configured account: #{self.to_rrepr}: #{ex.message}\n#{ex.backtrace.join("\n")}"
69
+ if attempt > 1 && (attempt % 3 == 0)
70
+ ServicesMailer.burying_job(self).deliver
71
+ raise Ayl::Beanstalk::RequiresJobBury
72
+ else
73
+ ServicesMailer.delaying_job(self).deliver
74
+ raise Ayl::Beanstalk::RequiresJobDecay.new(DECAY_TIME)
75
+ end
76
+ end
77
+
78
+ def self.for_service_within_time_frame(service_key, from, to)
79
+ svc = Service.where(key: service_key).first
80
+ configured_account_ids = ConfiguredAccount.for_service(svc.id).enabled.pluck(:id)
81
+
82
+ self.where("configured_account_id IN (?) AND created_at >= ? AND created_at <= ?",
83
+ configured_account_ids, from, to)
84
+ end
85
+
86
+ private
87
+
88
+ def identify_providers
89
+ logger.debug "Identifying configured providers for: #{configured_account.to_rrepr}"
90
+ service_impl.identify_providers(configured_account)
91
+ end
92
+
93
+ def request_assessments_to_process
94
+ logger.debug "Requesting assessment to be processed from the third party"
95
+ service_impl.request_assessments(configured_account)
96
+ end
97
+
98
+ def identify_files_to_pull
99
+ logger.debug "Identifying files to pull for: #{configured_account.to_rrepr}"
100
+ list_available_files.each do | file_info |
101
+ logger.debug "File: #{file_info}"
102
+ configured_account.available_files.create(request_id: file_info[0],
103
+ filename: file_info[1],
104
+ status: AvailableFile::STATUS_AVAILABLE)
105
+ end
106
+ end
107
+
108
+ def list_available_files
109
+ service_impl.list_available_files(configured_account)
110
+ end
111
+
112
+ def download_files
113
+ logger.debug "Downloading files for: #{configured_account.to_rrepr}"
114
+
115
+ # Get the list of available_files that are ready for download
116
+ files_to_download = configured_account.available_files.available
117
+
118
+ # Actually perform the download of the files
119
+ downloaded_files = perform_file_downloads files_to_download
120
+
121
+ # Update the records
122
+ if downloaded_files.present?
123
+ fid_list = downloaded_files.join(",")
124
+ # Update only the files that were actually downloaded, but do it in a
125
+ # single db call.
126
+ AvailableFile.where("id IN (#{fid_list})").update_all("status = '#{AvailableFile::STATUS_DOWNLOADED}', downloaded_at = '#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}'")
127
+ end
128
+ end
129
+
130
+ def perform_file_downloads(files_to_download)
131
+ service_impl.perform_file_downloads(configured_account, files_to_download)
132
+ end
133
+
134
+ def submit_files_to_abaqis
135
+ logger.debug "Submitting files to abaqis for: #{configured_account.to_rrepr}"
136
+ mds_push = AbaqisMdsPush.service_impl
137
+ configured_account.available_files.downloaded.each do | available_file |
138
+ begin
139
+ mds_push.push_mds_file(available_file)
140
+ rescue Exception => ex
141
+ logger.error "Error pushing file (#{available_file.to_rrepr})) to abaqis: #{ex.message}\n#{ex.backtrace.join("\n")}"
142
+ available_file.update_attribute :error, "Error pushing file to abaqis: #{ex.message}"
143
+ end
144
+ end
145
+ end
146
+
147
+ def clean_up_file_system
148
+ logger.debug "Cleaning up file system for: #{configured_account.to_rrepr}"
149
+ files_to_remove = configured_account.available_files.uploaded
150
+ files_to_remove.each do | available_file |
151
+ rm_rf available_file.download_path
152
+ available_file.update_attribute :status, AvailableFile::STATUS_DELETED
153
+ end
154
+
155
+ end
156
+
157
+ def clean_up_remote_server
158
+ logger.debug "Removing files from remote server for: #{configured_account.to_rrepr}"
159
+
160
+ # Actually remove the files from the server
161
+ removed_files = perform_cleanup_on_remote_server
162
+
163
+ # Update the records
164
+ if removed_files.present?
165
+ fid_list = removed_files.join(',')
166
+ # Update only the files that were actually removed, but do it in a
167
+ # single db call.
168
+ AvailableFile.where("id IN (#{fid_list})").update_all("status = '#{AvailableFile::STATUS_CLEARED}'")
169
+ end
170
+ end
171
+
172
+ def perform_cleanup_on_remote_server
173
+ service_impl.perform_cleanup_on_remote_server(configured_account)
174
+ end
175
+
176
+ def perform_task(p_status)
177
+ return unless self.status == p_status && STATE_MACHINE[p_status]
178
+
179
+ # Invoke the appropriate method for this status
180
+ send STATE_MACHINE[status]
181
+
182
+ # If the processing completes without a hitch, update the status attribute
183
+ # to the next appropriate status.
184
+ update_attribute :status, PROCESSING_STATES[PROCESSING_STATES.index(p_status) + 1]
185
+ end
186
+
187
+ def service_impl
188
+ @service_impl ||= service_definition.service_implementation
189
+ end
190
+ end
191
+
192
+ end
@@ -0,0 +1,24 @@
1
+ module Services
2
+
3
+ class MdsPush < ServiceImplementation
4
+
5
+ def invoke
6
+ end
7
+
8
+ def push_mds_file(available_file)
9
+ raise "Downloaded file does not exist: #{available_file.downloaded_file_path}" unless available_file.downloaded_file_exists?
10
+ raise "Downloaded file is not valid MDS: #{available_file.downloaded_file_path}" unless available_file.valid_mds_file?
11
+ submit_to_service(available_file)
12
+ end
13
+
14
+ def self.service_impl
15
+ nil
16
+ end
17
+
18
+ def submit_to_service(available_file)
19
+ puts "## Submitting MDS file to service"
20
+ end
21
+
22
+ end
23
+
24
+ end