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,50 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module ValidationTranscriber
6
+ class FrenchStreetParser
7
+ include AddressParsingHelper
8
+
9
+ def initialize
10
+ super
11
+ end
12
+
13
+ def parse(street:)
14
+ return {} if street.blank?
15
+
16
+ # Expected format: [suffix, pre_directional, name, post_directional]
17
+ # Note that the directionals may be absent.
18
+ # Suffix is actually a prefix in French, but we keep the existing terminology for cross-language consistency.
19
+
20
+ suffix = nil
21
+ pre_directional = nil
22
+ post_directional = nil
23
+
24
+ tokens = street.split(" ")
25
+
26
+ if street_suffix?(tokens[0])
27
+ suffix = tokens[0]
28
+ tokens = tokens[1..-1]
29
+ end
30
+
31
+ if directional?(tokens[0])
32
+ pre_directional = tokens[0]
33
+ tokens = tokens[1..-1]
34
+ end
35
+
36
+ if directional?(tokens[-1])
37
+ post_directional = tokens[-1]
38
+ tokens = tokens[0..-2]
39
+ end
40
+
41
+ {
42
+ pre_directional: pre_directional,
43
+ name: tokens.join(" "),
44
+ suffix: suffix,
45
+ post_directional: post_directional,
46
+ }.compact_blank.to_h
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module ValidationTranscriber
6
+ class ProvinceCodeNormalizer
7
+ class << self
8
+ extend T::Sig
9
+ sig { params(country_code: T.nilable(String), province_code: T.nilable(String)).returns(T.nilable(String)) }
10
+ def normalize(country_code:, province_code:)
11
+ return if province_code.blank?
12
+ return province_code if country_code.blank?
13
+
14
+ iso_code(country_code, province_code) ||
15
+ iso_from_cldr(country_code, province_code) ||
16
+ province_code
17
+ end
18
+
19
+ private
20
+
21
+ sig { params(country_code: String, province_code: String).returns(T.nilable(String)) }
22
+ def iso_code(country_code, province_code)
23
+ zone = Worldwide.region(code: country_code)&.zone(code: province_code)
24
+ zone.province? ? zone.iso_code : nil
25
+ end
26
+
27
+ sig { params(country_code: String, province_code: String).returns(T.nilable(String)) }
28
+ def iso_from_cldr(country_code, province_code)
29
+ zone = Worldwide.region(code: province_code)
30
+ if zone_valid?(zone, country_code)
31
+ return zone.iso_code
32
+ end
33
+
34
+ zone = Worldwide.region(cldr: province_code)
35
+ zone_valid?(zone, country_code) ? zone.iso_code : nil
36
+ end
37
+
38
+ sig { params(zone: Worldwide::Region, country_code: String).returns(T::Boolean) }
39
+ def zone_valid?(zone, country_code)
40
+ zone.province? && zone.associated_country.iso_code.casecmp(country_code) == 0
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module ValidationTranscriber
6
+ class StreetParser
7
+ def parse(street:, locale: nil)
8
+ lang = Worldwide.locale(code: locale || I18n.locale).language_subtag.to_s.downcase
9
+
10
+ if "fr" == lang
11
+ FrenchStreetParser.new.parse(street: street)
12
+ else
13
+ EnglishStreetParser.new.parse(street: street)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module ValidationTranscriber
6
+ class ZipNormalizer
7
+ class << self
8
+ extend T::Sig
9
+
10
+ sig { params(country_code: T.nilable(String), zip: T.nilable(String)).returns(T.nilable(String)) }
11
+ def normalize(country_code:, zip:)
12
+ if country_code.present? && Worldwide.region(code: country_code).valid_zip?(zip)
13
+ AddressValidation::ZipTruncator.new(country_code: country_code).truncate(
14
+ zip: Worldwide::Zip.normalize(country_code: country_code, zip: zip, strip_extraneous_characters: true),
15
+ )
16
+ else
17
+ Worldwide::Zip.normalize(country_code: country_code, zip: zip)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ class ApplicationMailer < ActionMailer::Base
6
+ default from: "from@example.com"
7
+ layout "mailer"
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Corrections
7
+ class Corrector
8
+ extend T::Sig
9
+ attr_reader :correctors
10
+
11
+ sig { params(country_code: String, source: String).void }
12
+ def initialize(country_code:, source:)
13
+ @country_code = country_code.upcase
14
+ @correctors ||= correctors_for_country(@country_code, source)
15
+ end
16
+
17
+ sig { params(address: Hash).void }
18
+ def apply(address)
19
+ correctors.each do |corrector|
20
+ corrector.apply(address)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ sig { params(country_code: String, source: String).returns(T::Array[Class]) }
27
+ def correctors_for_country(country_code, source)
28
+ CountryProfile.for(country_code).ingestion.correctors(source: source)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module ImportEventsNotifier
7
+ class Base
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ abstract!
11
+
12
+ class << self
13
+ extend T::Sig
14
+
15
+ sig { returns(T.untyped) }
16
+ def instance
17
+ T.unsafe(self).new
18
+ end
19
+ end
20
+
21
+ sig { params(client: T.untyped).void }
22
+ def initialize(client: nil)
23
+ @client = client
24
+ end
25
+
26
+ sig do
27
+ abstract.params(
28
+ event: Event,
29
+ ).void
30
+ end
31
+ def notify(event:); end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module ImportEventsNotifier
7
+ class Notifier < Base
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ include Singleton
12
+
13
+ sig do
14
+ override.params(
15
+ event: Event,
16
+ ).void
17
+ end
18
+ def notify(event:)
19
+ # do nothing,
20
+ # The Host application can define its own notifier by configuring
21
+ # AtlasEngine.address_importer_notifier = MyCustomNotifier
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module OpenAddress
7
+ class DefaultMapper
8
+ extend T::Sig
9
+ include FeatureHelper
10
+ sig { params(country_code: String, locale: T.nilable(String)).void }
11
+ def initialize(country_code:, locale: nil)
12
+ @country_code = country_code
13
+ @locale = locale
14
+ end
15
+
16
+ sig { params(feature: Feature).returns(T::Hash[Symbol, T.untyped]) }
17
+ def map(feature)
18
+ region, city, street, number, unit, postcode = feature["properties"].values_at(
19
+ "region",
20
+ "city",
21
+ "street",
22
+ "number",
23
+ "unit",
24
+ "postcode",
25
+ )
26
+ {
27
+ source_id: openaddress_source_id(feature),
28
+ locale: @locale,
29
+ country_code: @country_code,
30
+ province_code: nil,
31
+ region1: region,
32
+ # Don't titleize. The sources have proper capitalization, and it's a problem for cities like
33
+ # 's-Graveland, which would get titleized to "'S Graveland" which is wrong.
34
+ city: [city],
35
+ suburb: nil,
36
+ zip: normalize_zip(postcode),
37
+ street: street,
38
+ building_and_unit_ranges: housenumber_and_unit(number, unit),
39
+ latitude: geometry(feature)&.at(1),
40
+ longitude: geometry(feature)&.at(0),
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,110 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module OpenAddress
7
+ module FeatureHelper
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ sig { params(feature: T::Hash[String, T.untyped]).returns(T.nilable(String)) }
12
+ def openaddress_source_id(feature)
13
+ objid, hash = feature["properties"].values_at("id", "hash")
14
+ if objid.present?
15
+ # OA indicates an OpenAddresses-provided ID
16
+ "OA-#{objid}"
17
+ elsif hash.present?
18
+ # Which may come from a different field, hence the hash sign
19
+ "OA##{hash}"
20
+ else
21
+ # AT signifies an Atlas-calculated hash
22
+ "AT-#{signature(feature)}"
23
+ end
24
+ end
25
+
26
+ sig do
27
+ params(number: T.nilable(String), unit: T.nilable(String))
28
+ .returns(T.nilable(T::Hash[T.untyped, T.untyped]))
29
+ end
30
+ def housenumber_and_unit(number, unit)
31
+ return {} if number.blank?
32
+
33
+ { number => unit.present? ? { unit => {} } : {} }
34
+ end
35
+
36
+ sig { params(feature: Feature).returns(T.nilable([Numeric, Numeric])) }
37
+ def geometry(feature)
38
+ geom = feature["geometry"]
39
+ if geom.present? && geom["type"] == "Point"
40
+ geom["coordinates"]
41
+ end
42
+ end
43
+
44
+ sig { params(zip: String).returns(String) }
45
+ def normalize_zip(zip)
46
+ Worldwide::Zip.normalize(
47
+ country_code: @country_code,
48
+ zip: zip,
49
+ strip_extraneous_characters: true,
50
+ )
51
+ end
52
+
53
+ sig { params(district: String).returns(T.nilable(String)) }
54
+ def province_code_from_name(district) = zones_mapping[district.downcase]
55
+
56
+ sig { params(region: String).returns(T.nilable(String)) }
57
+ def province_from_code(region) = zone_codes.include?(region) ? region : nil
58
+
59
+ sig { params(zip: String).returns(T.nilable(String)) }
60
+ def province_code_from_zip(zip)
61
+ @zip_to_province_mapping ||= {}
62
+ unless @zip_to_province_mapping.key?(zip)
63
+ province = Worldwide.region(code: @country_code).zone(zip: zip)
64
+ @zip_to_province_mapping[zip] = province.province? ? province.iso_code : nil
65
+ end
66
+ @zip_to_province_mapping[zip]
67
+ end
68
+
69
+ private
70
+
71
+ sig { params(feature: T::Hash[String, T.untyped]).returns(String) }
72
+ def signature(feature)
73
+ Digest::MD5.hexdigest(
74
+ feature["properties"].map { "#{_1}=#{_2}" }.join("\n"),
75
+ )
76
+ end
77
+
78
+ sig { returns(T::Hash[String, String]) }
79
+ def zones_mapping
80
+ @zones_mapping ||= legacy_zone_names.merge(full_zone_names).merge(name_alternates)
81
+ end
82
+
83
+ sig { returns(T::Array[Worldwide::Region]) }
84
+ def zones = @zones ||= Worldwide.region(code: @country_code).zones
85
+
86
+ sig { returns(T::Hash[String, String]) }
87
+ def legacy_zone_names
88
+ zones.to_h { |z| [z.legacy_name.downcase, z.legacy_code] }
89
+ end
90
+
91
+ sig { returns(T::Hash[String, String]) }
92
+ def full_zone_names
93
+ zones.to_h { |z| [z.full_name.downcase, z.legacy_code] }
94
+ end
95
+
96
+ sig { returns(T::Hash[String, String]) }
97
+ def name_alternates
98
+ zones_with_alternates = zones.select { |z| z.name_alternates.present? }
99
+ zones_with_alternates.each_with_object({}) do |zone, hash|
100
+ zone.name_alternates.each { |name| hash[name.downcase] = zone.legacy_code }
101
+ end
102
+ end
103
+
104
+ def zone_codes
105
+ @zone_codes ||= Worldwide.region(code: @country_code).zones.to_set(&:legacy_code)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,17 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module OpenAddress
7
+ module Filter
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ interface!
11
+
12
+ sig { abstract.params(feature: T::Hash[String, T.untyped]).returns(T::Boolean) }
13
+ def filter(feature); end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module OpenAddress
7
+ class Loader
8
+ extend T::Sig
9
+
10
+ sig { params(addresses: T::Array[Hash]).void }
11
+ def load(addresses)
12
+ PostAddress.upsert_all( # rubocop:disable Rails/SkipsModelValidations
13
+ addresses,
14
+ on_duplicate: merge_building_ranges_clause,
15
+ )
16
+ end
17
+
18
+ private
19
+
20
+ def merge_building_ranges_clause
21
+ Arel.sql("building_and_unit_ranges =
22
+ JSON_MERGE(#{PostAddress.table_name}.building_and_unit_ranges, VALUES(building_and_unit_ranges))")
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module OpenAddress
7
+ class Transformer
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ def initialize(country_import:, locale: nil)
12
+ @country_code = country_import.country_code
13
+ @locale = locale
14
+ @mapper = CountryProfile.for(@country_code).ingestion.open_address_feature_mapper.new(
15
+ country_code: @country_code, locale: @locale,
16
+ )
17
+ @corrector = AddressImporter::Corrections::Corrector.new(country_code: @country_code, source: "open_address")
18
+ @validator = AddressImporter::Validation::Wrapper.new(
19
+ country_import: country_import,
20
+ log_invalid_records: false,
21
+ )
22
+ end
23
+
24
+ sig do
25
+ params(feature: Feature)
26
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
27
+ end
28
+ def transform(feature)
29
+ address_hash = @mapper.map(feature)
30
+
31
+ @corrector.apply(address_hash)
32
+ return if address_hash.blank?
33
+
34
+ address_hash if @validator.valid?(address_hash)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module OpenAddress
7
+ Feature = T.type_alias { T::Hash[String, T.untyped] }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,86 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ class BaseValidator
8
+ extend T::Sig
9
+
10
+ Errors = T.type_alias { T::Hash[Symbol, T.untyped] }
11
+
12
+ attr_reader :allow_partial_zip, :field_validations, :additional_field_validations
13
+
14
+ sig do
15
+ params(
16
+ country_code: String,
17
+ field_validations: T::Hash[Symbol, T::Array[FieldValidations::Interface]],
18
+ additional_field_validations: T::Hash[Symbol, T::Array[FieldValidations::Interface]],
19
+ ).void
20
+ end
21
+ def initialize(country_code:, field_validations:, additional_field_validations: {})
22
+ @allow_partial_zip = T.let(CountryProfile.partial_zip_allowed_countries.include?(country_code), T::Boolean)
23
+ @field_validations = field_validations
24
+ @additional_field_validations = additional_field_validations.transform_values do |validator_classes|
25
+ validator_classes.map do |validator_class|
26
+ if validator_class.is_a?(Class)
27
+ validator_class
28
+ else
29
+ validator_class.to_s.constantize
30
+ end
31
+ end
32
+ end
33
+
34
+ @caches = {}
35
+ merged_field_validations.map do |field, _validator_class|
36
+ @caches[field] = Set.new
37
+ end
38
+
39
+ @errors = T.let(Hash.new([]), Errors)
40
+ end
41
+
42
+ sig { params(address: AddressImporter::Validation::Wrapper::AddressStruct).returns(Errors) }
43
+ def validation_errors(address:)
44
+ validate(address: address)
45
+ @errors
46
+ end
47
+
48
+ private
49
+
50
+ sig { returns(T::Hash[Symbol, T::Array[FieldValidations::Interface]]) }
51
+ def merged_field_validations
52
+ @merged_field_validations ||= @field_validations.merge(@additional_field_validations) do
53
+ |_field, validator_class, additional_validator_class|
54
+
55
+ [validator_class, additional_validator_class].flatten.compact.uniq
56
+ end
57
+ end
58
+
59
+ sig { params(address: AddressImporter::Validation::Wrapper::AddressStruct).void }
60
+ def validate(address:)
61
+ clear_errors
62
+
63
+ merged_field_validations.each do |field, validators|
64
+ if @caches[field].exclude?(address[field])
65
+ @errors[field] = [].tap do |error_msgs|
66
+ validators.each do |validator_class|
67
+ validator = T.unsafe(validator_class).new(address: address, allow_partial_zip: allow_partial_zip)
68
+ error_msgs << validator.validation_errors
69
+ end
70
+ end.flatten
71
+ end
72
+
73
+ break if @errors[field].present?
74
+
75
+ @caches[field].add(address[field])
76
+ end
77
+ end
78
+
79
+ sig { void }
80
+ def clear_errors
81
+ @errors = Hash.new([])
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ class DefaultValidator < BaseValidator
8
+ extend T::Sig
9
+
10
+ sig { params(country_code: String).void }
11
+ def initialize(country_code:)
12
+ field_validations = {
13
+ city: [FieldValidations::City],
14
+ province_code: [FieldValidations::Province],
15
+ zip: [FieldValidations::Zip],
16
+ }
17
+
18
+ super(
19
+ country_code: country_code,
20
+ field_validations: field_validations,
21
+ additional_field_validations: AtlasEngine.address_importer_additional_field_validations,
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ module FieldValidations
8
+ class City
9
+ extend T::Sig
10
+ extend T::Helpers
11
+ include Interface
12
+
13
+ sig do
14
+ override.params(
15
+ address: AddressImporter::Validation::Wrapper::AddressStruct,
16
+ allow_partial_zip: T::Boolean,
17
+ ).void
18
+ end
19
+ def initialize(address:, allow_partial_zip: false)
20
+ @city = address.city
21
+ @errors = []
22
+ end
23
+
24
+ sig { override.returns(T::Array[String]) }
25
+ def validation_errors
26
+ clear_errors
27
+ validate
28
+ errors
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :city
34
+ attr_accessor :errors
35
+
36
+ def clear_errors
37
+ self.errors = []
38
+ end
39
+
40
+ def validate
41
+ errors << "City is required" unless city&.any?(&:present?)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressImporter
6
+ module Validation
7
+ module FieldValidations
8
+ module Interface
9
+ extend T::Sig
10
+ extend T::Helpers
11
+ include Kernel
12
+
13
+ interface!
14
+
15
+ sig do
16
+ abstract.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); end
22
+
23
+ sig { abstract.returns(T::Array[String]) }
24
+ def validation_errors; end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end