atlas_engine 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 (298) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +123 -0
  3. data/Rakefile +20 -0
  4. data/app/assets/config/atlas_engine_manifest.js +3 -0
  5. data/app/assets/stylesheets/atlas_engine/application.css +15 -0
  6. data/app/concerns/atlas_engine/handles_blob.rb +26 -0
  7. data/app/concerns/atlas_engine/handles_interruption.rb +22 -0
  8. data/app/controllers/atlas_engine/application_controller.rb +7 -0
  9. data/app/controllers/atlas_engine/connectivity_controller.rb +21 -0
  10. data/app/controllers/atlas_engine/country_imports_controller.rb +73 -0
  11. data/app/controllers/atlas_engine/graphql_controller.rb +59 -0
  12. data/app/countries/atlas_engine/ar/country_profile.yml +9 -0
  13. data/app/countries/atlas_engine/at/address_importer/corrections/open_address/city_corrector.rb +23 -0
  14. data/app/countries/atlas_engine/at/country_profile.yml +24 -0
  15. data/app/countries/atlas_engine/at/index_configuration.yml +63 -0
  16. data/app/countries/atlas_engine/at/synonyms.yml +6 -0
  17. data/app/countries/atlas_engine/at/validation_transcriber/address_parser.rb +58 -0
  18. data/app/countries/atlas_engine/au/address_importer/open_address/filter.rb +26 -0
  19. data/app/countries/atlas_engine/au/address_importer/open_address/mapper.rb +41 -0
  20. data/app/countries/atlas_engine/au/country_profile.yml +13 -0
  21. data/app/countries/atlas_engine/au/synonyms.yml +209 -0
  22. data/app/countries/atlas_engine/au/validation_transcriber/address_parser.rb +121 -0
  23. data/app/countries/atlas_engine/be/country_profile.yml +12 -0
  24. data/app/countries/atlas_engine/bm/address_importer/corrections/open_address/city_alias_corrector.rb +38 -0
  25. data/app/countries/atlas_engine/bm/address_importer/open_address/mapper.rb +40 -0
  26. data/app/countries/atlas_engine/bm/country_profile.yml +12 -0
  27. data/app/countries/atlas_engine/br/country_profile.yml +4 -0
  28. data/app/countries/atlas_engine/ca/country_profile.yml +7 -0
  29. data/app/countries/atlas_engine/ca/synonyms.yml +1615 -0
  30. data/app/countries/atlas_engine/ch/address_importer/corrections/open_address/city_corrector.rb +29 -0
  31. data/app/countries/atlas_engine/ch/address_importer/corrections/open_address/locale_corrector.rb +74 -0
  32. data/app/countries/atlas_engine/ch/address_importer/open_address/mapper.rb +40 -0
  33. data/app/countries/atlas_engine/ch/country_profile.yml +15 -0
  34. data/app/countries/atlas_engine/ch/locales/de/country_profile.yml +15 -0
  35. data/app/countries/atlas_engine/ch/locales/de/index_configuration.yml +63 -0
  36. data/app/countries/atlas_engine/ch/locales/de/synonyms.yml +7 -0
  37. data/app/countries/atlas_engine/ch/locales/fr/synonyms.yml +21 -0
  38. data/app/countries/atlas_engine/cz/country_profile.yml +6 -0
  39. data/app/countries/atlas_engine/de/country_profile.yml +19 -0
  40. data/app/countries/atlas_engine/de/index_configuration.yml +64 -0
  41. data/app/countries/atlas_engine/de/synonyms.yml +2 -0
  42. data/app/countries/atlas_engine/de/validation_transcriber/address_parser.rb +19 -0
  43. data/app/countries/atlas_engine/dk/country_profile.yml +6 -0
  44. data/app/countries/atlas_engine/dk/synonyms.yml +3 -0
  45. data/app/countries/atlas_engine/dk/validation_transcriber/address_parser.rb +21 -0
  46. data/app/countries/atlas_engine/fo/country_profile.yml +5 -0
  47. data/app/countries/atlas_engine/fr/address_importer/corrections/open_address/city_corrector.rb +28 -0
  48. data/app/countries/atlas_engine/fr/country_profile.yml +13 -0
  49. data/app/countries/atlas_engine/fr/synonyms.yml +21 -0
  50. data/app/countries/atlas_engine/fr/validation_transcriber/address_parser.rb +34 -0
  51. data/app/countries/atlas_engine/gb/address_validation/es/query_builder.rb +98 -0
  52. data/app/countries/atlas_engine/gb/country_profile.yml +10 -0
  53. data/app/countries/atlas_engine/gb/validation_transcriber/full_address_parser.rb +164 -0
  54. data/app/countries/atlas_engine/gb/validation_transcriber/parsed_address.rb +120 -0
  55. data/app/countries/atlas_engine/gg/address_validation/validators/full_address/restrictions/unsupported_city.rb +39 -0
  56. data/app/countries/atlas_engine/gg/country_profile.yml +7 -0
  57. data/app/countries/atlas_engine/ie/country_profile.yml +3 -0
  58. data/app/countries/atlas_engine/it/address_importer/corrections/open_address/city_corrector.rb +27 -0
  59. data/app/countries/atlas_engine/it/address_importer/corrections/open_address/province_corrector.rb +29 -0
  60. data/app/countries/atlas_engine/it/address_importer/open_address/mapper.rb +42 -0
  61. data/app/countries/atlas_engine/it/country_profile.yml +11 -0
  62. data/app/countries/atlas_engine/jp/address_validation/es/data_mapper.rb +63 -0
  63. data/app/countries/atlas_engine/jp/country_profile.yml +6 -0
  64. data/app/countries/atlas_engine/kr/address_importer/open_address/mapper.rb +41 -0
  65. data/app/countries/atlas_engine/kr/country_profile.yml +11 -0
  66. data/app/countries/atlas_engine/li/address_importer/corrections/open_address/city_corrector.rb +25 -0
  67. data/app/countries/atlas_engine/li/country_profile.yml +21 -0
  68. data/app/countries/atlas_engine/li/index_configuration.yml +63 -0
  69. data/app/countries/atlas_engine/li/synonyms.yml +6 -0
  70. data/app/countries/atlas_engine/lt/country_profile.yml +6 -0
  71. data/app/countries/atlas_engine/lt/synonyms.yml +7 -0
  72. data/app/countries/atlas_engine/lt/validation_transcriber/address_parser.rb +24 -0
  73. data/app/countries/atlas_engine/lu/address_importer/corrections/open_address/locale_corrector.rb +54 -0
  74. data/app/countries/atlas_engine/lu/country_profile.yml +12 -0
  75. data/app/countries/atlas_engine/nl/address_importer/corrections/open_address/city_corrector.rb +25 -0
  76. data/app/countries/atlas_engine/nl/country_profile.yml +18 -0
  77. data/app/countries/atlas_engine/nl/index_configuration.yml +52 -0
  78. data/app/countries/atlas_engine/nl/synonyms.yml +92 -0
  79. data/app/countries/atlas_engine/nl/validation_transcriber/address_parser.rb +85 -0
  80. data/app/countries/atlas_engine/no/country_profile.yml +5 -0
  81. data/app/countries/atlas_engine/nz/country_profile.yml +3 -0
  82. data/app/countries/atlas_engine/pl/country_profile.yml +5 -0
  83. data/app/countries/atlas_engine/pl/validation_transcriber/address_parser.rb +19 -0
  84. data/app/countries/atlas_engine/pt/address_importer/corrections/open_address/city_corrector.rb +32 -0
  85. data/app/countries/atlas_engine/pt/address_importer/open_address/mapper.rb +39 -0
  86. data/app/countries/atlas_engine/pt/country_profile.yml +10 -0
  87. data/app/countries/atlas_engine/pt/synonyms.yml +7 -0
  88. data/app/countries/atlas_engine/sa/country_profile.yml +10 -0
  89. data/app/countries/atlas_engine/se/country_profile.yml +5 -0
  90. data/app/countries/atlas_engine/tt/address_importer/open_address/mapper.rb +38 -0
  91. data/app/countries/atlas_engine/tt/country_profile.yml +7 -0
  92. data/app/countries/atlas_engine/us/country_profile.yml +12 -0
  93. data/app/countries/atlas_engine/us/synonyms.yml +350 -0
  94. data/app/graphql/atlas_engine/errors/locale_unsupported_error.rb +17 -0
  95. data/app/graphql/atlas_engine/schema.graphql +1293 -0
  96. data/app/graphql/atlas_engine/schema.rb +23 -0
  97. data/app/graphql/atlas_engine/types/address_validation/address_input.rb +51 -0
  98. data/app/graphql/atlas_engine/types/address_validation/concern_type.rb +20 -0
  99. data/app/graphql/atlas_engine/types/address_validation/enums/concern_enum.rb +15 -0
  100. data/app/graphql/atlas_engine/types/address_validation/field_type.rb +15 -0
  101. data/app/graphql/atlas_engine/types/address_validation/suggestion_type.rb +21 -0
  102. data/app/graphql/atlas_engine/types/base_argument.rb +9 -0
  103. data/app/graphql/atlas_engine/types/base_enum.rb +9 -0
  104. data/app/graphql/atlas_engine/types/base_field.rb +10 -0
  105. data/app/graphql/atlas_engine/types/base_input_object.rb +9 -0
  106. data/app/graphql/atlas_engine/types/base_interface.rb +10 -0
  107. data/app/graphql/atlas_engine/types/base_object.rb +9 -0
  108. data/app/graphql/atlas_engine/types/base_scalar.rb +9 -0
  109. data/app/graphql/atlas_engine/types/base_union.rb +9 -0
  110. data/app/graphql/atlas_engine/types/matching_strategy_type.rb +12 -0
  111. data/app/graphql/atlas_engine/types/mutation_type.rb +9 -0
  112. data/app/graphql/atlas_engine/types/query_type.rb +61 -0
  113. data/app/graphql/atlas_engine/types/validation_supported_country.rb +12 -0
  114. data/app/graphql/atlas_engine/types/validation_type.rb +22 -0
  115. data/app/helpers/atlas_engine/address_importer/import_log_helper.rb +66 -0
  116. data/app/helpers/atlas_engine/application_helper.rb +7 -0
  117. data/app/helpers/atlas_engine/locale_format_helper.rb +40 -0
  118. data/app/helpers/atlas_engine/log_base.rb +32 -0
  119. data/app/helpers/atlas_engine/log_helper.rb +24 -0
  120. data/app/helpers/atlas_engine/metrics_helper.rb +25 -0
  121. data/app/jobs/atlas_engine/address_importer/clear_records_job.rb +39 -0
  122. data/app/jobs/atlas_engine/address_importer/open_address/geo_json_import_job.rb +212 -0
  123. data/app/jobs/atlas_engine/address_importer/open_address/geo_json_import_launcher_job.rb +67 -0
  124. data/app/jobs/atlas_engine/address_importer/open_address/prepares_geo_json_file.rb +41 -0
  125. data/app/jobs/atlas_engine/address_importer/resumable_import_job.rb +49 -0
  126. data/app/jobs/atlas_engine/address_importer/street_backfill_job.rb +63 -0
  127. data/app/jobs/atlas_engine/application_job.rb +10 -0
  128. data/app/jobs/atlas_engine/concerns/address_importer/handles_errors.rb +43 -0
  129. data/app/lib/atlas_engine/concern_formatter.rb +40 -0
  130. data/app/lib/atlas_engine/restrictions/base.rb +20 -0
  131. data/app/lib/atlas_engine/restrictions/unsupported_script.rb +31 -0
  132. data/app/lib/atlas_engine/validation_transcriber/address_parser_base.rb +201 -0
  133. data/app/lib/atlas_engine/validation_transcriber/address_parser_factory.rb +27 -0
  134. data/app/lib/atlas_engine/validation_transcriber/address_parser_north_america.rb +39 -0
  135. data/app/lib/atlas_engine/validation_transcriber/address_parser_oceanic.rb +17 -0
  136. data/app/lib/atlas_engine/validation_transcriber/address_parser_preprocessor.rb +132 -0
  137. data/app/lib/atlas_engine/validation_transcriber/address_parsing_helper.rb +38 -0
  138. data/app/lib/atlas_engine/validation_transcriber/address_parsings.rb +54 -0
  139. data/app/lib/atlas_engine/validation_transcriber/constants.rb +50 -0
  140. data/app/lib/atlas_engine/validation_transcriber/english_street_parser.rb +59 -0
  141. data/app/lib/atlas_engine/validation_transcriber/formatter.rb +46 -0
  142. data/app/lib/atlas_engine/validation_transcriber/french_street_parser.rb +50 -0
  143. data/app/lib/atlas_engine/validation_transcriber/province_code_normalizer.rb +45 -0
  144. data/app/lib/atlas_engine/validation_transcriber/street_parser.rb +18 -0
  145. data/app/lib/atlas_engine/validation_transcriber/zip_normalizer.rb +23 -0
  146. data/app/mailers/atlas_engine/application_mailer.rb +9 -0
  147. data/app/models/atlas_engine/address_importer/corrections/corrector.rb +33 -0
  148. data/app/models/atlas_engine/address_importer/import_events_notifier/base.rb +35 -0
  149. data/app/models/atlas_engine/address_importer/import_events_notifier/notifier.rb +26 -0
  150. data/app/models/atlas_engine/address_importer/open_address/default_mapper.rb +46 -0
  151. data/app/models/atlas_engine/address_importer/open_address/feature_helper.rb +110 -0
  152. data/app/models/atlas_engine/address_importer/open_address/filter.rb +17 -0
  153. data/app/models/atlas_engine/address_importer/open_address/loader.rb +27 -0
  154. data/app/models/atlas_engine/address_importer/open_address/transformer.rb +39 -0
  155. data/app/models/atlas_engine/address_importer/open_address.rb +10 -0
  156. data/app/models/atlas_engine/address_importer/validation/base_validator.rb +86 -0
  157. data/app/models/atlas_engine/address_importer/validation/default_validator.rb +27 -0
  158. data/app/models/atlas_engine/address_importer/validation/field_validations/city.rb +47 -0
  159. data/app/models/atlas_engine/address_importer/validation/field_validations/interface.rb +29 -0
  160. data/app/models/atlas_engine/address_importer/validation/field_validations/province.rb +73 -0
  161. data/app/models/atlas_engine/address_importer/validation/field_validations/zip.rb +84 -0
  162. data/app/models/atlas_engine/address_importer/validation/validator.rb +17 -0
  163. data/app/models/atlas_engine/address_importer/validation/wrapper.rb +70 -0
  164. data/app/models/atlas_engine/address_number.rb +36 -0
  165. data/app/models/atlas_engine/address_number_range.rb +200 -0
  166. data/app/models/atlas_engine/address_validation/abstract_address.rb +49 -0
  167. data/app/models/atlas_engine/address_validation/address.rb +47 -0
  168. data/app/models/atlas_engine/address_validation/candidate.rb +109 -0
  169. data/app/models/atlas_engine/address_validation/candidate_tuple.rb +15 -0
  170. data/app/models/atlas_engine/address_validation/concern.rb +74 -0
  171. data/app/models/atlas_engine/address_validation/concern_producer.rb +19 -0
  172. data/app/models/atlas_engine/address_validation/concern_queue.rb +20 -0
  173. data/app/models/atlas_engine/address_validation/concern_record.rb +122 -0
  174. data/app/models/atlas_engine/address_validation/datastore_base.rb +27 -0
  175. data/app/models/atlas_engine/address_validation/errors.rb +13 -0
  176. data/app/models/atlas_engine/address_validation/es/candidate_selector.rb +70 -0
  177. data/app/models/atlas_engine/address_validation/es/data_mappers/decompounding_data_mapper.rb +39 -0
  178. data/app/models/atlas_engine/address_validation/es/data_mappers/default_data_mapper.rb +110 -0
  179. data/app/models/atlas_engine/address_validation/es/datastore.rb +229 -0
  180. data/app/models/atlas_engine/address_validation/es/default_query_builder.rb +30 -0
  181. data/app/models/atlas_engine/address_validation/es/query_builder.rb +160 -0
  182. data/app/models/atlas_engine/address_validation/es/term_vectors.rb +78 -0
  183. data/app/models/atlas_engine/address_validation/es/validators/full_address.rb +123 -0
  184. data/app/models/atlas_engine/address_validation/es/validators/full_address_street.rb +18 -0
  185. data/app/models/atlas_engine/address_validation/es/validators/restriction_evaluator.rb +37 -0
  186. data/app/models/atlas_engine/address_validation/field.rb +30 -0
  187. data/app/models/atlas_engine/address_validation/full_address_validator_base.rb +27 -0
  188. data/app/models/atlas_engine/address_validation/log_emitter.rb +66 -0
  189. data/app/models/atlas_engine/address_validation/matching_strategies.rb +16 -0
  190. data/app/models/atlas_engine/address_validation/normalizer.rb +38 -0
  191. data/app/models/atlas_engine/address_validation/predicate_pipeline.rb +80 -0
  192. data/app/models/atlas_engine/address_validation/request.rb +12 -0
  193. data/app/models/atlas_engine/address_validation/result.rb +154 -0
  194. data/app/models/atlas_engine/address_validation/runs_validation.rb +16 -0
  195. data/app/models/atlas_engine/address_validation/session.rb +47 -0
  196. data/app/models/atlas_engine/address_validation/statsd_emitter.rb +72 -0
  197. data/app/models/atlas_engine/address_validation/strategies.rb +10 -0
  198. data/app/models/atlas_engine/address_validation/suggestion.rb +97 -0
  199. data/app/models/atlas_engine/address_validation/token/comparator.rb +44 -0
  200. data/app/models/atlas_engine/address_validation/token/comparison.rb +76 -0
  201. data/app/models/atlas_engine/address_validation/token/sequence/comparator.rb +158 -0
  202. data/app/models/atlas_engine/address_validation/token/sequence/comparison.rb +166 -0
  203. data/app/models/atlas_engine/address_validation/token/sequence.rb +147 -0
  204. data/app/models/atlas_engine/address_validation/token/synonyms.rb +77 -0
  205. data/app/models/atlas_engine/address_validation/token.rb +113 -0
  206. data/app/models/atlas_engine/address_validation/validator.rb +147 -0
  207. data/app/models/atlas_engine/address_validation/validators/full_address/address_comparison.rb +97 -0
  208. data/app/models/atlas_engine/address_validation/validators/full_address/candidate_result.rb +164 -0
  209. data/app/models/atlas_engine/address_validation/validators/full_address/candidate_result_base.rb +46 -0
  210. data/app/models/atlas_engine/address_validation/validators/full_address/comparison_helper.rb +135 -0
  211. data/app/models/atlas_engine/address_validation/validators/full_address/components_to_validate.rb +88 -0
  212. data/app/models/atlas_engine/address_validation/validators/full_address/concern_builder.rb +127 -0
  213. data/app/models/atlas_engine/address_validation/validators/full_address/exclusions/exclusion_base.rb +23 -0
  214. data/app/models/atlas_engine/address_validation/validators/full_address/invalid_zip_concern_builder.rb +42 -0
  215. data/app/models/atlas_engine/address_validation/validators/full_address/invalid_zip_for_country_concern.rb +37 -0
  216. data/app/models/atlas_engine/address_validation/validators/full_address/invalid_zip_for_province_concern.rb +37 -0
  217. data/app/models/atlas_engine/address_validation/validators/full_address/no_candidate_result.rb +26 -0
  218. data/app/models/atlas_engine/address_validation/validators/full_address/number_comparison.rb +31 -0
  219. data/app/models/atlas_engine/address_validation/validators/full_address/postal_code_matcher.rb +60 -0
  220. data/app/models/atlas_engine/address_validation/validators/full_address/result_updater.rb +42 -0
  221. data/app/models/atlas_engine/address_validation/validators/full_address/suggestion_builder.rb +140 -0
  222. data/app/models/atlas_engine/address_validation/validators/full_address/unknown_address_concern.rb +30 -0
  223. data/app/models/atlas_engine/address_validation/validators/full_address/unknown_province_concern.rb +38 -0
  224. data/app/models/atlas_engine/address_validation/validators/full_address/unknown_zip_for_address_concern.rb +32 -0
  225. data/app/models/atlas_engine/address_validation/validators/full_address/unmatched_field_concern.rb +84 -0
  226. data/app/models/atlas_engine/address_validation/validators/full_address/unsupported_script_result.rb +22 -0
  227. data/app/models/atlas_engine/address_validation/validators/predicates/cache.rb +38 -0
  228. data/app/models/atlas_engine/address_validation/validators/predicates/city/present.rb +36 -0
  229. data/app/models/atlas_engine/address_validation/validators/predicates/country/exists.rb +34 -0
  230. data/app/models/atlas_engine/address_validation/validators/predicates/country/valid_for_zip.rb +60 -0
  231. data/app/models/atlas_engine/address_validation/validators/predicates/no_emojis.rb +38 -0
  232. data/app/models/atlas_engine/address_validation/validators/predicates/no_html_tags.rb +39 -0
  233. data/app/models/atlas_engine/address_validation/validators/predicates/no_url.rb +38 -0
  234. data/app/models/atlas_engine/address_validation/validators/predicates/not_exceed_max_length.rb +34 -0
  235. data/app/models/atlas_engine/address_validation/validators/predicates/not_exceed_max_token_count.rb +63 -0
  236. data/app/models/atlas_engine/address_validation/validators/predicates/phone/valid.rb +41 -0
  237. data/app/models/atlas_engine/address_validation/validators/predicates/predicate.rb +37 -0
  238. data/app/models/atlas_engine/address_validation/validators/predicates/province/exists.rb +43 -0
  239. data/app/models/atlas_engine/address_validation/validators/predicates/province/valid_for_country.rb +48 -0
  240. data/app/models/atlas_engine/address_validation/validators/predicates/street/building_number_in_address1.rb +45 -0
  241. data/app/models/atlas_engine/address_validation/validators/predicates/street/building_number_in_address1_or_address2.rb +43 -0
  242. data/app/models/atlas_engine/address_validation/validators/predicates/street/present.rb +35 -0
  243. data/app/models/atlas_engine/address_validation/validators/predicates/zip/present.rb +58 -0
  244. data/app/models/atlas_engine/address_validation/validators/predicates/zip/valid_for_country.rb +45 -0
  245. data/app/models/atlas_engine/address_validation/validators/predicates/zip/valid_for_province.rb +55 -0
  246. data/app/models/atlas_engine/address_validation/validators/predicates/zip/zip_base.rb +25 -0
  247. data/app/models/atlas_engine/address_validation/zip_truncator.rb +32 -0
  248. data/app/models/atlas_engine/application_record.rb +8 -0
  249. data/app/models/atlas_engine/coded_error.rb +18 -0
  250. data/app/models/atlas_engine/coded_errors.rb +17 -0
  251. data/app/models/atlas_engine/country_import.rb +44 -0
  252. data/app/models/atlas_engine/country_profile.rb +270 -0
  253. data/app/models/atlas_engine/country_profile_ingestion_subset.rb +42 -0
  254. data/app/models/atlas_engine/country_profile_subset_base.rb +22 -0
  255. data/app/models/atlas_engine/country_profile_validation_subset.rb +48 -0
  256. data/app/models/atlas_engine/country_repository.rb +110 -0
  257. data/app/models/atlas_engine/elasticsearch/client.rb +116 -0
  258. data/app/models/atlas_engine/elasticsearch/client_interface.rb +89 -0
  259. data/app/models/atlas_engine/elasticsearch/repository.rb +246 -0
  260. data/app/models/atlas_engine/elasticsearch/repository_interface.rb +82 -0
  261. data/app/models/atlas_engine/elasticsearch/response.rb +20 -0
  262. data/app/models/atlas_engine/event.rb +12 -0
  263. data/app/models/atlas_engine/field_decompounder.rb +36 -0
  264. data/app/models/atlas_engine/index_configuration_factory.rb +188 -0
  265. data/app/models/atlas_engine/post_address.rb +114 -0
  266. data/app/models/atlas_engine/post_address_importer.rb +34 -0
  267. data/app/models/atlas_engine/services/service_helper.rb +21 -0
  268. data/app/models/atlas_engine/services/validation.rb +65 -0
  269. data/app/models/atlas_engine/services/validation_eligibility.rb +18 -0
  270. data/app/models/atlas_engine/street.rb +34 -0
  271. data/app/tasks/maintenance/atlas_engine/elasticsearch_index_create_task.rb +106 -0
  272. data/app/tasks/maintenance/atlas_engine/geo_json_import_task.rb +29 -0
  273. data/app/views/atlas_engine/connectivity/index.html.erb +50 -0
  274. data/app/views/atlas_engine/country_imports/index.html.erb +49 -0
  275. data/app/views/atlas_engine/country_imports/show.html.erb +73 -0
  276. data/app/views/layouts/atlas_engine/application.html.erb +15 -0
  277. data/config/initializers/1.ruby_patches.rb +18 -0
  278. data/config/initializers/sorbet.rb +5 -0
  279. data/config/initializers/worldwide.rb +5 -0
  280. data/config/locales/internal/en.yml +14 -0
  281. data/config/routes.rb +17 -0
  282. data/db/data/address_synonyms/index_configurations/default.yml +141 -0
  283. data/db/data/country_profiles/default.yml +23 -0
  284. data/db/data/transcriber.yml +760 -0
  285. data/db/data/validation_pipelines/es.yml +58 -0
  286. data/db/data/validation_pipelines/es_street.yml +58 -0
  287. data/db/data/validation_pipelines/local.yml +60 -0
  288. data/db/migrate/20230919173037_create_atlas_engine_post_addresses.rb +25 -0
  289. data/db/migrate/20231117142735_add_building_and_unit_ranges_column.rb +7 -0
  290. data/db/migrate/20231117143536_create_atlas_engine_country_imports.rb +11 -0
  291. data/db/migrate/20231117145844_create_atlas_engine_events_table.rb +13 -0
  292. data/db/migrate/20231123153554_add_unique_index_to_atlas_engine_post_addresses.rb +14 -0
  293. data/db/migrate/20231123154658_add_index_to_post_addresses_on_source_id_locale_country_code.rb +12 -0
  294. data/lib/atlas_engine/engine.rb +10 -0
  295. data/lib/atlas_engine/version.rb +6 -0
  296. data/lib/atlas_engine.rb +66 -0
  297. data/lib/tasks/atlas_engine/address_importer.rake +20 -0
  298. metadata +553 -0
@@ -0,0 +1,73 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ module FieldValidations
8
+ class Province
9
+ extend T::Sig
10
+
11
+ extend T::Helpers
12
+ include Interface
13
+
14
+ sig { returns(String) }
15
+ attr_reader :country_code
16
+
17
+ sig { returns(T.nilable(String)) }
18
+ attr_reader :province_code
19
+
20
+ sig { returns(T::Array[String]) }
21
+ attr_reader :errors
22
+
23
+ sig do
24
+ override.params(
25
+ address: AddressImporter::Validation::Wrapper::AddressStruct,
26
+ allow_partial_zip: T::Boolean,
27
+ ).void
28
+ end
29
+ def initialize(address:, allow_partial_zip: false)
30
+ @country_code = address.country_code
31
+ @province_code = address.province_code
32
+ @errors = []
33
+ end
34
+
35
+ sig { override.returns(T::Array[String]) }
36
+ def validation_errors
37
+ validate_country
38
+ validate_province if errors.empty?
39
+ errors
40
+ end
41
+
42
+ private
43
+
44
+ sig { void }
45
+ def validate_country
46
+ errors << "Country '#{country_code}' is invalid" unless country.country?
47
+ end
48
+
49
+ sig { void }
50
+ def validate_province
51
+ return unless country_has_provinces?
52
+
53
+ if province_code.blank?
54
+ errors << "Province is required for country '#{country_code}'" unless country.province_optional?
55
+ elsif !country.zone(code: province_code).province?
56
+ errors << "Province '#{province_code}' is invalid for country '#{country_code}'"
57
+ end
58
+ end
59
+
60
+ sig { returns(Worldwide::Region) }
61
+ def country
62
+ @country ||= Worldwide.region(code: country_code)
63
+ end
64
+
65
+ sig { returns(T::Boolean) }
66
+ def country_has_provinces?
67
+ country.zones.present? && !country.hide_provinces_from_addresses
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,84 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ module FieldValidations
8
+ class Zip
9
+ extend T::Sig
10
+ extend T::Helpers
11
+ include Interface
12
+
13
+ attr_reader :errors
14
+
15
+ sig do
16
+ override.params(
17
+ address: AddressImporter::Validation::Wrapper::AddressStruct,
18
+ allow_partial_zip: T::Boolean,
19
+ ).void
20
+ end
21
+ def initialize(address:, allow_partial_zip: false)
22
+ @country_code = address.country_code
23
+ @province_code = address.province_code
24
+ @zip = address.zip
25
+ @allow_partial_zip = allow_partial_zip
26
+ @errors = []
27
+ end
28
+
29
+ sig { override.returns(T::Array[String]) }
30
+ def validation_errors
31
+ validate
32
+ errors
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :country_code, :province_code, :zip
38
+
39
+ def validate
40
+ return unless country.country?
41
+
42
+ zip_must_match_country
43
+ return if errors.any?
44
+
45
+ zip_must_match_province
46
+ end
47
+
48
+ def zip_must_match_country
49
+ return unless country.has_zip?
50
+
51
+ if zip.blank? && country.zip_required?
52
+ errors << "Zip is required for country '#{country_code}'"
53
+ elsif !zip_valid_for_country?
54
+ errors << "Zip '#{zip}' is invalid for country '#{country_code}'"
55
+ end
56
+ end
57
+
58
+ def zip_must_match_province
59
+ province = country.zone(code: province_code) unless country.province_optional?
60
+
61
+ return unless province&.province?
62
+ return if province.valid_zip?(zip)
63
+ return if @allow_partial_zip && province.valid_zip?(zip, partial_match: true)
64
+
65
+ errors << "Zip '#{zip}' is invalid for province '#{province_code}'"
66
+ end
67
+
68
+ def zip_valid_for_country?
69
+ country.valid_zip?(zip) ||
70
+ (@allow_partial_zip && country.valid_zip?(zip, partial_match: true))
71
+ end
72
+
73
+ def partial_postal_code_valid_for_province?
74
+ country.zone(code: province_code).valid_zip?(zip, partial_match: true)
75
+ end
76
+
77
+ def country
78
+ @country ||= Worldwide.region(code: country_code)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,17 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ module Validator
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ interface!
11
+
12
+ sig { abstract.params(address: T.untyped).returns(T::Boolean) }
13
+ def valid?(address); end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,70 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ class Wrapper
8
+ extend T::Sig
9
+
10
+ include Validator
11
+ include ImportLogHelper
12
+
13
+ attr_reader :country_import, :validator
14
+
15
+ AddressStruct = Struct.new(:country_code, :province_code, :zip, :city, keyword_init: true)
16
+
17
+ sig do
18
+ params(
19
+ country_import: CountryImport,
20
+ validator: T.nilable(AddressImporter::Validation::BaseValidator),
21
+ log_invalid_records: T.nilable(T::Boolean),
22
+ ).void
23
+ end
24
+ def initialize(country_import:, validator: nil, log_invalid_records: true)
25
+ @country_import = country_import
26
+ @validator = validator || DefaultValidator.new(country_code: country_import.country_code)
27
+ @log_invalid_records = log_invalid_records
28
+ end
29
+
30
+ sig { override.params(address: T.nilable(Hash)).returns(T::Boolean) }
31
+ def valid?(address)
32
+ return false unless address
33
+
34
+ errors = validation_errors(address)
35
+
36
+ return true if errors.empty?
37
+
38
+ log_invalid_address(address, errors) if @log_invalid_records
39
+ false
40
+ end
41
+
42
+ private
43
+
44
+ sig { params(address: Hash).returns(T::Array[String]) }
45
+ def validation_errors(address)
46
+ validator.validation_errors(
47
+ address: address_from_hash(address),
48
+ ).values.flatten
49
+ end
50
+
51
+ sig { params(address_hash: Hash).returns(AddressStruct) }
52
+ def address_from_hash(address_hash)
53
+ AddressStruct.new(**address_hash.slice(:country_code, :province_code, :zip, :city))
54
+ end
55
+
56
+ sig { params(address: Hash, errors: T::Array[String]).void }
57
+ def log_invalid_address(address, errors)
58
+ errors.each do |error|
59
+ import_log_info(
60
+ country_import: country_import,
61
+ message: "Invalid address; #{error}",
62
+ category: :invalid_address,
63
+ additional_params: { address: address },
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ class AddressNumber
6
+ extend T::Sig
7
+
8
+ sig { returns(String) }
9
+ attr_reader :raw
10
+
11
+ NUMBERS = /\d+/
12
+ NUMBERS_AND_NON_NUMBERS = %r{([A-Za-z]+|\d+\s+\d+/\d+|\d+/\d+|\d+|-)}
13
+
14
+ sig { params(value: String).void }
15
+ def initialize(value:)
16
+ @raw = value
17
+ end
18
+
19
+ sig { returns(T.nilable(Integer)) }
20
+ def to_i
21
+ numbers = @raw.scan(NUMBERS).flatten
22
+ numbers.length == 1 ? numbers[0].to_i : nil
23
+ end
24
+
25
+ sig { returns(T.any(Integer, Rational)) }
26
+ def to_r
27
+ fractions = @raw.scan(NUMBERS_AND_NON_NUMBERS).flatten
28
+ fractions[0].to_s.split.sum(&:to_r)
29
+ end
30
+
31
+ sig { returns(T::Array[String]) }
32
+ def segments
33
+ Array(@raw.scan(NUMBERS_AND_NON_NUMBERS).flatten)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,200 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ class AddressNumberRange
6
+ extend T::Sig
7
+
8
+ RANGE_FORMAT = %r{^\((?<min>[^\)]+)\.\.(?<max>[^\)]+)\)/(?<step>[12])$} # ex. (A1..A9)/2
9
+
10
+ class RangeError < ArgumentError; end
11
+
12
+ class << self
13
+ extend T::Sig
14
+
15
+ sig { params(overlapping_ranges: T::Array[T::Range[Integer]]).returns(T::Array[T::Range[Integer]]) }
16
+ def merge_overlapping_ranges(overlapping_ranges)
17
+ overlapping_ranges.sort_by(&:min).inject([]) do |ranges, range|
18
+ if !ranges.empty? && (ranges.last.overlaps?(range) || consecutive_ranges?(ranges.last, range))
19
+ ranges[0...-1] + [merge_ranges(ranges.last, range)]
20
+ else
21
+ ranges + [range]
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ sig { params(a: T::Range[Integer], b: T::Range[Integer]).returns(T::Range[Integer]) }
29
+ def merge_ranges(a, b)
30
+ [a.min, b.min].min..[a.max, b.max].max
31
+ end
32
+
33
+ sig { params(a: T::Range[Integer], b: T::Range[Integer]).returns(T::Boolean) }
34
+ def consecutive_ranges?(a, b)
35
+ b.min - a.max == 1
36
+ end
37
+ end
38
+
39
+ sig { params(range_string: String).void }
40
+ def initialize(range_string:)
41
+ range = RANGE_FORMAT.match(range_string)
42
+ if range
43
+ @min = AddressNumber.new(value: T.must(range[:min]))
44
+ @max = AddressNumber.new(value: T.must(range[:max]))
45
+
46
+ if @min.segments.length != @max.segments.length
47
+ raise RangeError, "min and max of range are not compatible, range_string: #{range_string}"
48
+ end
49
+
50
+ @step = range[:step].to_i
51
+ else
52
+ @min = AddressNumber.new(value: range_string)
53
+ @max = AddressNumber.new(value: range_string)
54
+ @step = 1
55
+ end
56
+ end
57
+
58
+ sig { params(value: String, exact_match: T::Boolean).returns(T::Boolean) }
59
+ def include?(value, exact_match = true)
60
+ value_number = AddressNumber.new(value: value)
61
+ value_segments = value_number.segments
62
+
63
+ if value_segments.length != format.length
64
+ return false if exact_match || format.length > 1
65
+
66
+ numeric_only_value = value_number.to_i
67
+ return false unless numeric_only_value
68
+
69
+ value_segments = [numeric_only_value.to_s]
70
+ end
71
+
72
+ format.each_with_index do |range_segment, i|
73
+ value_segment = value_segments[i]
74
+
75
+ return false unless value_segment == range_segment || range_segment.include?(value_segment)
76
+ end
77
+
78
+ true
79
+ end
80
+
81
+ sig { returns(T.nilable(T::Range[Integer])) }
82
+ def approx_numeric_range
83
+ min_numeric = @min.to_i
84
+ max_numeric = @max.to_i
85
+
86
+ return unless min_numeric && max_numeric
87
+
88
+ (min_numeric..max_numeric)
89
+ end
90
+
91
+ private
92
+
93
+ sig { returns(T::Array[T::Array[String]]) }
94
+ def format
95
+ @format ||= if include_fractions?(@min.raw) || include_fractions?(@max.raw)
96
+ fraction_format
97
+ else
98
+
99
+ min_segments = @min.segments
100
+ max_segments = @max.segments
101
+
102
+ range_value_format = []
103
+ min_segments.each_with_index do |min_segment, i|
104
+ max_segment = max_segments[i]
105
+ range_value_format << values_in_range(min_segment, max_segment)
106
+ end
107
+ range_value_format
108
+ end
109
+ end
110
+
111
+ sig { params(range_min: String, range_max: String).returns(T::Array[String]) }
112
+ def values_in_range(range_min, range_max)
113
+ (range_min..range_max).step(@step).to_a
114
+ end
115
+
116
+ sig { returns [T::Array[String]] }
117
+ def fraction_format
118
+ min_whole = whole_frac_value(@min.raw)
119
+ max_whole = whole_frac_value(@max.raw)
120
+
121
+ range_value_format = []
122
+
123
+ fractions = [
124
+ "",
125
+ "1/2",
126
+ "1/3",
127
+ "1/4",
128
+ "1/5",
129
+ "1/6",
130
+ "1/7",
131
+ "1/8",
132
+ "2/3",
133
+ "2/4",
134
+ "2/5",
135
+ "2/6",
136
+ "2/7",
137
+ "2/8",
138
+ "3/4",
139
+ "3/5",
140
+ "3/6",
141
+ "3/7",
142
+ "3/8",
143
+ "4/5",
144
+ "4/6",
145
+ "4/7",
146
+ "4/8",
147
+ "5/6",
148
+ "5/7",
149
+ "5/8",
150
+ "6/7",
151
+ "6/8",
152
+ "7/8",
153
+ ]
154
+
155
+ (min_whole..max_whole).step(@step).each do |whole|
156
+ fractions.each do |fraction|
157
+ number = build_mixed_fraction(whole, fraction)
158
+ address_num = AddressNumber.new(value: number)
159
+ range_value_format << number if address_num.to_r.between?(@min.to_r, @max.to_r)
160
+ end
161
+ end
162
+ [range_value_format]
163
+ end
164
+
165
+ sig { params(value: String).returns(T::Boolean) }
166
+ def include_fractions?(value)
167
+ %r{^([0-9]+ )?([0-9]+/[0-9]+)?$}.match?(value)
168
+ end
169
+
170
+ sig { params(str: String).returns(String) }
171
+ def whole_frac_value(str)
172
+ pieces = str.split(" ")
173
+ first_piece = T.must(pieces.first)
174
+ if pieces.length == 2
175
+ first_piece
176
+ elsif pieces.length == 1
177
+ if first_piece.include?("/")
178
+ "0"
179
+ else
180
+ first_piece
181
+ end
182
+ else
183
+ "0"
184
+ end
185
+ end
186
+
187
+ sig { params(whole_number: String, proper_fraction: String).returns(String) }
188
+ def build_mixed_fraction(whole_number, proper_fraction)
189
+ if whole_number == "0"
190
+ if proper_fraction == ""
191
+ whole_number
192
+ else
193
+ proper_fraction
194
+ end
195
+ else
196
+ [whole_number, proper_fraction].join(" ").strip
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ module AbstractAddress
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ include Kernel
10
+ abstract!
11
+ ComponentType = T.type_alias { T.nilable(String) }
12
+
13
+ sig { abstract.returns(ComponentType) }
14
+ def address1; end
15
+
16
+ sig { abstract.returns(ComponentType) }
17
+ def address2; end
18
+
19
+ sig { abstract.returns(ComponentType) }
20
+ def city; end
21
+
22
+ sig { abstract.returns(ComponentType) }
23
+ def province_code; end
24
+
25
+ sig { abstract.returns(ComponentType) }
26
+ def phone; end
27
+
28
+ sig { abstract.returns(ComponentType) }
29
+ def country_code; end
30
+
31
+ sig { abstract.returns(ComponentType) }
32
+ def zip; end
33
+
34
+ sig { abstract.returns(T::Hash[Symbol, String]) }
35
+ def to_h; end
36
+
37
+ sig { abstract.returns(T.untyped) }
38
+ def context; end
39
+
40
+ sig { overridable.returns(T::Enumerable[Symbol]) }
41
+ def keys = to_h.keys # rubocop:disable Rails/Delegate
42
+
43
+ sig { overridable.params(key: Symbol).returns(T.untyped) }
44
+ def [](key) = to_h[key] # rubocop:disable Rails/Delegate
45
+ end
46
+
47
+ TAddress = T.type_alias { AbstractAddress }
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ class Address < T::Struct
7
+ extend T::Sig
8
+ include LogHelper
9
+ include AbstractAddress
10
+
11
+ ComponentType = T.type_alias { T.nilable(String) }
12
+ CountryType = T.type_alias { T.nilable(T.any(String, Symbol)) }
13
+ AddressInput = T.type_alias { Types::AddressValidation::AddressInput }
14
+
15
+ const :address1, ComponentType
16
+ const :address2, ComponentType
17
+ const :city, ComponentType
18
+ const :province_code, ComponentType
19
+ const :phone, ComponentType
20
+ const :country_code, CountryType
21
+ const :zip, ComponentType
22
+
23
+ sig { override.returns(T::Hash[Symbol, T.untyped]) }
24
+ def context = {}
25
+
26
+ sig { override.returns(T::Hash[Symbol, String]) }
27
+ def to_h = serialize.transform_keys(&:to_sym)
28
+
29
+ class << self
30
+ extend T::Sig
31
+
32
+ sig { params(address: TAddress).returns(Address) }
33
+ def from_address(address:)
34
+ new(
35
+ address1: address.address1,
36
+ address2: address.address2,
37
+ city: address.city,
38
+ country_code: address.country_code,
39
+ province_code: address.province_code,
40
+ zip: address.zip,
41
+ phone: address.phone,
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,109 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ class Candidate
7
+ extend T::Sig
8
+ attr_reader :id, :index
9
+
10
+ sig { params(id: String, source: Hash, index: T.nilable(String)).void }
11
+ def initialize(id:, source:, index: nil)
12
+ components_hash = Hash.new { |hash, key| hash[key] = Component.new(key, nil) }
13
+
14
+ @components = source.each_with_object(components_hash) do |(key, value), hash|
15
+ hash[key.to_sym] = Component.new(key.to_sym, value)
16
+ end
17
+
18
+ @id = id
19
+ @components[:id] = Component.new(:id, id)
20
+ @index = index
21
+ end
22
+
23
+ sig { params(name: Symbol).returns(T.nilable(Component)) }
24
+ def component(name)
25
+ @components[name]
26
+ end
27
+
28
+ sig { params(names: Symbol).returns(T::Hash[Symbol, Component]) }
29
+ def components(*names)
30
+ return @components.reject { |_, component| component.value.nil? } if names.empty?
31
+
32
+ names.index_with do |name|
33
+ component(name)
34
+ end
35
+ end
36
+
37
+ sig { returns(String) }
38
+ def serialize
39
+ components(:locale, :province_code, :region2, :region3, :region4, :zip, :city, :suburb, :street)
40
+ .values.map(&:serialize).join(",")
41
+ end
42
+
43
+ sig { returns(T::Boolean) }
44
+ def describes_po_box?
45
+ component(:street)&.value&.casecmp("po box") == 0
46
+ end
47
+
48
+ sig { returns(T::Boolean) }
49
+ def describes_general_delivery?
50
+ component(:street)&.value&.casecmp("general delivery") == 0
51
+ end
52
+
53
+ class << self
54
+ extend T::Sig
55
+
56
+ sig { params(hit: Hash).returns(Candidate) }
57
+ def from(hit)
58
+ id = hit.dig("_id")
59
+ source = hit.dig("_source")
60
+ source["city"] = source["city_aliases"].map(&:values).flatten if source["city_aliases"]
61
+ index = hit.dig("_index")
62
+ new(id: id, source: source, index: index)
63
+ end
64
+ end
65
+
66
+ class Component
67
+ extend T::Sig
68
+
69
+ attr_reader :name
70
+ attr_writer :sequences
71
+ attr_accessor :value
72
+
73
+ sig do
74
+ params(
75
+ name: Symbol,
76
+ value: T.nilable(T.any(String, Integer, Float, T::Boolean, Array, Hash, BigDecimal)),
77
+ ).void
78
+ end
79
+ def initialize(name, value)
80
+ @name = name
81
+ @value = value
82
+ end
83
+
84
+ def first_value
85
+ values.first
86
+ end
87
+
88
+ sig { returns(T::Array[AtlasEngine::AddressValidation::Token::Sequence]) }
89
+ def sequences
90
+ @sequences ||= values.map { |val| AtlasEngine::AddressValidation::Token::Sequence.from_string(val) }
91
+ end
92
+
93
+ sig { returns(String) }
94
+ def serialize
95
+ if value.is_a?(Array)
96
+ "[#{value.map(&:to_s).join(",")}]"
97
+ else
98
+ value.to_s
99
+ end
100
+ end
101
+
102
+ sig { returns(T::Array[String]) }
103
+ def values
104
+ Array(value)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,15 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ CandidateTuple = Struct.new(:address_comparison, :position, :candidate) do
7
+ extend T::Sig
8
+
9
+ sig { params(other: CandidateTuple).returns(Integer) }
10
+ def <=>(other)
11
+ to_a[0..1] <=> other.to_a[0..1] # only consider address_comparison and position
12
+ end
13
+ end
14
+ end
15
+ end