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,113 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ class Token
7
+ extend T::Sig
8
+
9
+ sig { returns(String) }
10
+ attr_reader :value
11
+
12
+ sig { returns(T.nilable(String)) }
13
+ attr_reader :type
14
+
15
+ sig { returns(Integer) }
16
+ attr_accessor :position
17
+
18
+ sig { returns(Integer) }
19
+ attr_accessor :start_offset
20
+
21
+ sig { returns(Integer) }
22
+ attr_accessor :end_offset
23
+
24
+ sig { returns(Integer) }
25
+ attr_reader :position_length
26
+
27
+ sig do
28
+ params(
29
+ value: String,
30
+ start_offset: Integer,
31
+ end_offset: Integer,
32
+ position: Integer,
33
+ type: T.nilable(String),
34
+ position_length: Integer,
35
+ ).void
36
+ end
37
+ def initialize(value:, start_offset:, end_offset:, position:, type: nil, position_length: 1)
38
+ @value = value
39
+ @start_offset = start_offset
40
+ @end_offset = end_offset
41
+ @type = type
42
+ @position = position
43
+ @position_length = position_length
44
+ end
45
+
46
+ sig { returns(T::Range[Integer]) }
47
+ def offset_range
48
+ @offset_range ||= start_offset...end_offset
49
+ end
50
+
51
+ sig { returns(String) }
52
+ def inspect
53
+ t = " type:#{type}" if type.present?
54
+ pos_length = " pos_length:#{position_length}" if position_length > 1
55
+ id = "id:#{object_id.to_s[-4..-1]}"
56
+ details = " strt:#{start_offset} end:#{end_offset} pos:#{position}#{t}#{pos_length}"
57
+
58
+ "<tok #{id} val:\"#{value}\"#{details}/>"
59
+ end
60
+
61
+ sig { returns(String) }
62
+ def inspect_short
63
+ id = "id:#{object_id.to_s[-4..-1]}"
64
+ "<tok #{id} val:\"#{value}\"/>"
65
+ end
66
+
67
+ sig { params(other: Token).returns(T::Boolean) }
68
+ def ==(other)
69
+ value == other.value &&
70
+ start_offset == other.start_offset &&
71
+ end_offset == other.end_offset &&
72
+ type == other.type &&
73
+ position == other.position &&
74
+ position_length == other.position_length
75
+ end
76
+
77
+ sig { params(other: Token).returns(T::Boolean) }
78
+ def preceeds?(other)
79
+ position == other.position - position_length
80
+ end
81
+
82
+ class << self
83
+ extend T::Sig
84
+
85
+ sig { params(field_terms: Hash).returns(T::Array[Token]) }
86
+ def from_field_term_vector(field_terms)
87
+ field_terms["terms"].flat_map do |term, term_info|
88
+ term_info["tokens"].map do |token|
89
+ new(
90
+ value: term,
91
+ position: token["position"],
92
+ start_offset: token["start_offset"],
93
+ end_offset: token["end_offset"],
94
+ )
95
+ end
96
+ end.sort_by(&:position)
97
+ end
98
+
99
+ sig { params(token: T::Hash[String, T.untyped]).returns(Token) }
100
+ def from_analyze(token)
101
+ new(
102
+ value: token["token"],
103
+ start_offset: token["start_offset"],
104
+ end_offset: token["end_offset"],
105
+ type: token["type"],
106
+ position: token["position"],
107
+ position_length: token["positionLength"] || 1,
108
+ )
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,147 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ class Validator
7
+ include RunsValidation
8
+ extend T::Sig
9
+
10
+ sig { returns(T.nilable(String)) }
11
+ attr_reader :address1,
12
+ :address2,
13
+ :city,
14
+ :province_code,
15
+ :zip,
16
+ :country_code,
17
+ :phone
18
+
19
+ sig { returns(Result) }
20
+ attr_reader :result
21
+
22
+ sig { returns(AbstractAddress) }
23
+ attr_reader :address
24
+
25
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
26
+ attr_reader :context
27
+
28
+ sig { returns(T.nilable(FullAddressValidatorBase)) }
29
+ attr_reader :full_address_validator
30
+
31
+ FIELD_MAP = T.let(
32
+ {
33
+ country: "country_code",
34
+ province: "province_code",
35
+ zip: "zip",
36
+ city: "city",
37
+ address1: "address1",
38
+ address2: "address2",
39
+ phone: "phone",
40
+ },
41
+ T::Hash[Symbol, String],
42
+ )
43
+
44
+ sig do
45
+ params(
46
+ address: AbstractAddress,
47
+ matching_strategy: Strategies,
48
+ locale: String,
49
+ context: T::Hash[T.untyped, T.untyped],
50
+ ).void
51
+ end
52
+ def initialize(
53
+ address:,
54
+ matching_strategy:,
55
+ locale: "en",
56
+ context: {}
57
+ )
58
+ @address = T.let(address, AbstractAddress)
59
+ @address1 = T.let(address.address1, T.nilable(String))
60
+ @address2 = T.let(address.address2, T.nilable(String))
61
+ @city = T.let(address.city, T.nilable(String))
62
+ @province_code = T.let(address.province_code, T.nilable(String))
63
+ @phone = T.let(address.phone, T.nilable(String))
64
+ @zip = T.let(address.zip, T.nilable(String))
65
+ @country_code = T.let(address.country_code, T.nilable(String))
66
+ @context = T.let(context, T::Hash[T.untyped, T.untyped])
67
+ @matching_strategy = T.let(matching_strategy, Strategies)
68
+
69
+ matching_strategy_name = matching_strategy.serialize
70
+ @predicate_pipeline = T.let(PredicatePipeline.find(matching_strategy_name), PredicatePipeline)
71
+
72
+ @result = T.let(
73
+ Result.new(
74
+ client_request_id: context.dig(:client_request_id),
75
+ origin: context.dig(:origin),
76
+ locale: locale,
77
+ matching_strategy: matching_strategy_name,
78
+ ),
79
+ Result,
80
+ )
81
+
82
+ @full_address_validator = T.let(
83
+ @predicate_pipeline.full_address_validator&.new(
84
+ address: @address,
85
+ result: @result,
86
+ ),
87
+ T.nilable(FullAddressValidatorBase),
88
+ )
89
+ end
90
+
91
+ sig { override.returns(Result) }
92
+ def run
93
+ build_fields
94
+
95
+ populate_result(execute_pipeline)
96
+
97
+ validate_full_address
98
+
99
+ result
100
+ end
101
+
102
+ sig { void }
103
+ def build_fields
104
+ result.fields = address.keys.map do |field|
105
+ Field.new(name: field, value: address[field])
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ sig { returns(T::Hash[Symbol, T::Array[Concern]]) }
112
+ def execute_pipeline
113
+ pipeline_address = Address.from_address(address: address)
114
+ local_concerns = {}
115
+ cache = Validators::Predicates::Cache.new(pipeline_address)
116
+ @predicate_pipeline.pipeline.each do |config|
117
+ local_concerns[config.field] = [] if local_concerns[config.field].nil?
118
+ next if local_concerns[config.field].present?
119
+
120
+ concern = config.class_name.new(field: config.field, address: pipeline_address, cache: cache).evaluate
121
+
122
+ local_concerns[config.field] << concern if concern.present?
123
+ end
124
+ local_concerns
125
+ end
126
+
127
+ sig { params(local_concerns: T::Hash[Symbol, T::Array[Concern]]).void }
128
+ def populate_result(local_concerns)
129
+ local_concerns.keys.each do |field|
130
+ if local_concerns[field]&.empty? && [:address2, :phone].exclude?(field)
131
+ result.validation_scope << T.must(FIELD_MAP[field])
132
+ end
133
+
134
+ local_concerns[field]&.each do |concern|
135
+ result.concerns << concern
136
+ result.suggestions << T.must(concern.suggestion) if concern.suggestion.present?
137
+ end
138
+ end
139
+ end
140
+
141
+ sig { void }
142
+ def validate_full_address
143
+ @full_address_validator&.validate
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,97 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ module Validators
7
+ module FullAddress
8
+ class AddressComparison
9
+ extend T::Sig
10
+ include Comparable
11
+
12
+ attr_reader :street_comparison,
13
+ :city_comparison,
14
+ :zip_comparison,
15
+ :province_code_comparison,
16
+ :building_comparison
17
+
18
+ sig { params(address: AbstractAddress, candidate: Candidate, datastore: DatastoreBase).void }
19
+ def initialize(address:, candidate:, datastore:)
20
+ @street_comparison = ComparisonHelper.street_comparison(
21
+ datastore: datastore,
22
+ candidate: candidate,
23
+ )
24
+ @city_comparison = ComparisonHelper.city_comparison(
25
+ datastore: datastore,
26
+ candidate: candidate,
27
+ )
28
+ @zip_comparison = ComparisonHelper.zip_comparison(
29
+ address: address,
30
+ candidate: candidate,
31
+ )
32
+ @province_code_comparison = ComparisonHelper.province_code_comparison(
33
+ address: address,
34
+ candidate: candidate,
35
+ )
36
+ @building_comparison = ComparisonHelper.building_comparison(
37
+ datastore: datastore,
38
+ candidate: candidate,
39
+ )
40
+ end
41
+
42
+ sig { params(other: AddressComparison).returns(Integer) }
43
+ def <=>(other)
44
+ # prefer addresses having more matched fields, e.g. matching on street + city + zip is better than
45
+ # just matching on street + zip, or street + province
46
+ matches = comparisons.count(&:match?) <=> other.comparisons.count(&:match?)
47
+ return matches * -1 if matches.nonzero?
48
+
49
+ # merge all sequence comparisons together, erasing the individual field boundaries, and prefer
50
+ # the most favorable aggregate comparison
51
+ merged_comparison <=> other.merged_comparison
52
+ end
53
+
54
+ sig { returns(String) }
55
+ def inspect
56
+ "<addrcomp street#{comparisons.inspect}/>"
57
+ end
58
+
59
+ sig { returns(T::Boolean) }
60
+ def potential_match?
61
+ street_comparison.nil? || T.must(street_comparison).potential_match?
62
+ end
63
+
64
+ protected
65
+
66
+ sig do
67
+ returns(T::Array[T.any(AtlasEngine::AddressValidation::Token::Sequence::Comparison, NumberComparison)])
68
+ end
69
+ def comparisons
70
+ [
71
+ street_comparison,
72
+ city_comparison,
73
+ zip_comparison,
74
+ province_code_comparison,
75
+ building_comparison,
76
+ ].compact_blank
77
+ end
78
+
79
+ sig { returns(T::Array[AtlasEngine::AddressValidation::Token::Sequence::Comparison]) }
80
+ def text_comparisons
81
+ [
82
+ street_comparison,
83
+ city_comparison,
84
+ zip_comparison,
85
+ province_code_comparison,
86
+ ].compact_blank
87
+ end
88
+
89
+ sig { returns(AtlasEngine::AddressValidation::Token::Sequence::Comparison) }
90
+ def merged_comparison
91
+ @merged_comparisons ||= text_comparisons.reduce(&:merge)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,164 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ module Validators
7
+ module FullAddress
8
+ class CandidateResult < CandidateResultBase
9
+ extend T::Sig
10
+ include LogHelper
11
+
12
+ sig do
13
+ params(
14
+ candidate: AddressValidation::CandidateTuple,
15
+ session: Session,
16
+ result: Result,
17
+ )
18
+ .void
19
+ end
20
+ def initialize(candidate:, session:, result:)
21
+ super(session: session, result: result)
22
+ @candidate = candidate.candidate
23
+ @address_comparison = candidate.address_comparison
24
+ end
25
+
26
+ sig { override.void }
27
+ def update_result
28
+ result.candidate = candidate&.serialize
29
+ return if unmatched_components.empty?
30
+
31
+ update_concerns_and_suggestions
32
+ update_result_scope
33
+ end
34
+
35
+ sig { returns(T::Boolean) }
36
+ def suggestable?
37
+ ConcernBuilder.should_suggest?(session.address, unmatched_components.keys)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :candidate
43
+
44
+ sig { void }
45
+ def update_concerns_and_suggestions
46
+ if suggestable?
47
+ add_concerns_with_suggestions
48
+ else
49
+ add_concerns_without_suggestions
50
+ end
51
+ end
52
+
53
+ sig { void }
54
+ def add_concerns_without_suggestions
55
+ concern = InvalidZipConcernBuilder.for(session.address, [])
56
+ result.concerns << concern if concern
57
+
58
+ if ConcernBuilder.too_many_unmatched_components?(unmatched_components.keys)
59
+ result.concerns << UnknownAddressConcern.new(session.address)
60
+ end
61
+ end
62
+
63
+ sig { void }
64
+ def add_concerns_with_suggestions
65
+ unmatched_components.keys.each do |unmatched_component|
66
+ field_name = unmatched_field_name(unmatched_component)
67
+ if field_name.nil?
68
+ log_unknown_field_name
69
+ next
70
+ end
71
+
72
+ concern = ConcernBuilder.new(
73
+ unmatched_component: unmatched_component,
74
+ unmatched_field: field_name,
75
+ matched_components: matched_components.keys,
76
+ address: session.address,
77
+ suggestion_ids: [suggestion.id].compact,
78
+ ).build
79
+ result.concerns << concern
80
+ end
81
+ result.suggestions << suggestion
82
+ end
83
+
84
+ sig { returns(Suggestion) }
85
+ def suggestion
86
+ unmatched_fields = { street: unmatched_field_name(:street) }.compact
87
+
88
+ @suggestion ||= SuggestionBuilder.from_comparisons(
89
+ session.address.to_h,
90
+ unmatched_components,
91
+ candidate,
92
+ unmatched_fields,
93
+ )
94
+ end
95
+
96
+ sig { returns(T::Hash[Symbol, AtlasEngine::AddressValidation::Token::Sequence::Comparison]) }
97
+ def matched_components
98
+ @matched_components || split_matched_and_unmatched_components.first
99
+ end
100
+
101
+ sig { returns(T::Hash[Symbol, AtlasEngine::AddressValidation::Token::Sequence::Comparison]) }
102
+ def unmatched_components
103
+ @unmatched_components || split_matched_and_unmatched_components.second
104
+ end
105
+
106
+ sig { returns(T::Hash[Symbol, T.nilable(AtlasEngine::AddressValidation::Token::Sequence::Comparison)]) }
107
+ def matched_and_unmatched_components
108
+ components = {}
109
+ @matched_and_unmatched_components ||= begin
110
+ components_to_validate.each do |field|
111
+ components[field] = @address_comparison.send(:"#{field}_comparison")
112
+ end
113
+ components
114
+ end
115
+ end
116
+
117
+ sig { returns(T::Array[Symbol]) }
118
+ def components_to_validate
119
+ ComponentsToValidate.new(session, candidate, street_comparison).run
120
+ end
121
+
122
+ sig do
123
+ returns(T::Array[T::Hash[Symbol,
124
+ T.nilable(AtlasEngine::AddressValidation::Token::Sequence::Comparison)]])
125
+ end
126
+ def split_matched_and_unmatched_components
127
+ @matched_components, @unmatched_components = matched_and_unmatched_components.partition do |_, comparison|
128
+ comparison&.match?
129
+ end.map(&:to_h)
130
+ end
131
+
132
+ sig { returns(T.nilable(AtlasEngine::AddressValidation::Token::Sequence::Comparison)) }
133
+ def street_comparison
134
+ return @street_comparison if defined?(@street_comparison)
135
+
136
+ @street_comparison = @address_comparison.street_comparison
137
+ end
138
+
139
+ sig { params(component: Symbol).returns(T.nilable(Symbol)) }
140
+ def unmatched_field_name(component)
141
+ return component unless component == :street
142
+ return if unmatched_components[:street].nil?
143
+
144
+ original_street = T.must(unmatched_components[:street]).left_sequence.raw_value
145
+
146
+ if session.address.address1.to_s.include?(original_street)
147
+ :address1
148
+ elsif session.address.address2.to_s.include?(original_street)
149
+ :address2
150
+ end
151
+ end
152
+
153
+ sig { void }
154
+ def log_unknown_field_name
155
+ potential_streets = { potential_streets: session.parsings.potential_streets }
156
+ input_address = session.address.to_h.compact_blank.except(:phone)
157
+ log_details = input_address.merge(potential_streets)
158
+ log_info("[AddressValidation] Unable to identify unmatched field name", log_details)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,46 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ module Validators
7
+ module FullAddress
8
+ class CandidateResultBase
9
+ extend T::Helpers
10
+ extend T::Sig
11
+
12
+ abstract!
13
+
14
+ sig { params(session: Session, result: Result).void }
15
+ def initialize(session:, result:)
16
+ @session = session
17
+ @result = result
18
+ end
19
+
20
+ sig { void }
21
+ def update_result; end
22
+
23
+ private
24
+
25
+ attr_reader :session, :result
26
+
27
+ delegate :address, to: :session
28
+
29
+ sig { void }
30
+ def update_result_scope
31
+ concern_fields = result.concerns.flat_map(&:field_names).uniq
32
+ scopes_to_remove = concern_fields.flat_map { |field| contained_scopes_for(field) }
33
+ result.validation_scope.reject! { |scope| scope.in?(scopes_to_remove) }
34
+ end
35
+
36
+ sig { params(scope: Symbol).returns(T.nilable(T::Array[Symbol])) }
37
+ def contained_scopes_for(scope)
38
+ return [] unless (scope_index = Result::SORTED_VALIDATION_SCOPES.index(scope))
39
+
40
+ Result::SORTED_VALIDATION_SCOPES.slice(scope_index..)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,135 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module AtlasEngine
5
+ module AddressValidation
6
+ module Validators
7
+ module FullAddress
8
+ class ComparisonHelper
9
+ class << self
10
+ extend T::Sig
11
+
12
+ sig do
13
+ params(
14
+ datastore: DatastoreBase,
15
+ candidate: Candidate,
16
+ ).returns(T.nilable(Token::Sequence::Comparison))
17
+ end
18
+ def street_comparison(datastore:, candidate:)
19
+ street_sequences = datastore.fetch_street_sequences
20
+ candidate_sequences = T.must(candidate.component(:street)).sequences
21
+
22
+ street_sequences.map do |street_sequence|
23
+ best_comparison(
24
+ street_sequence,
25
+ candidate_sequences,
26
+ )
27
+ end.min
28
+ end
29
+
30
+ sig do
31
+ params(
32
+ datastore: DatastoreBase,
33
+ candidate: Candidate,
34
+ ).returns(T.nilable(Token::Sequence::Comparison))
35
+ end
36
+ def city_comparison(datastore:, candidate:)
37
+ best_comparison(
38
+ datastore.fetch_city_sequence,
39
+ T.must(candidate.component(:city)).sequences,
40
+ )
41
+ end
42
+
43
+ sig do
44
+ params(
45
+ address: AbstractAddress,
46
+ candidate: Candidate,
47
+ ).returns(T.nilable(Token::Sequence::Comparison))
48
+ end
49
+ def province_code_comparison(address:, candidate:)
50
+ normalized_session_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
51
+ country_code: address.country_code,
52
+ province_code: address.province_code,
53
+ )
54
+ normalized_candidate_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
55
+ country_code: T.must(candidate.component(:country_code)).value,
56
+ province_code: T.must(candidate.component(:province_code)).value,
57
+ )
58
+
59
+ best_comparison(
60
+ Token::Sequence.from_string(normalized_session_province_code),
61
+ [Token::Sequence.from_string(normalized_candidate_province_code)],
62
+ )
63
+ end
64
+
65
+ sig do
66
+ params(
67
+ address: AbstractAddress,
68
+ candidate: Candidate,
69
+ ).returns(T.nilable(Token::Sequence::Comparison))
70
+ end
71
+ def zip_comparison(address:, candidate:)
72
+ candidate.component(:zip)&.value = PostalCodeMatcher.new(
73
+ T.must(address.country_code),
74
+ T.must(address.zip),
75
+ candidate.component(:zip)&.value,
76
+ ).truncate
77
+
78
+ normalized_zip = ValidationTranscriber::ZipNormalizer.normalize(
79
+ country_code: address.country_code, zip: address.zip,
80
+ )
81
+ zip_sequence = Token::Sequence.from_string(normalized_zip)
82
+ best_comparison(
83
+ zip_sequence,
84
+ T.must(candidate.component(:zip)).sequences,
85
+ )
86
+ end
87
+
88
+ sig do
89
+ params(
90
+ datastore: DatastoreBase,
91
+ candidate: Candidate,
92
+ ).returns(NumberComparison)
93
+ end
94
+ def building_comparison(datastore:, candidate:)
95
+ NumberComparison.new(
96
+ numbers: datastore.parsings.potential_building_numbers,
97
+ candidate_ranges: building_ranges_from_candidate(candidate),
98
+ )
99
+ end
100
+
101
+ private
102
+
103
+ sig do
104
+ params(
105
+ sequence: Token::Sequence,
106
+ component_sequences: T::Array[Token::Sequence],
107
+ ).returns(T.nilable(Token::Sequence::Comparison))
108
+ end
109
+ def best_comparison(sequence, component_sequences)
110
+ component_sequences.map do |component_sequence|
111
+ Token::Sequence::Comparator.new(
112
+ left_sequence: sequence,
113
+ right_sequence: component_sequence,
114
+ ).compare
115
+ end.min_by.with_index do |comparison, index|
116
+ # ruby's `min` and `sort` methods are not stable
117
+ # so we need to prefer the leftmost comparison when two comparisons are equivalent
118
+ [comparison, index]
119
+ end
120
+ end
121
+
122
+ sig { params(candidate: Candidate).returns(T::Array[AddressNumberRange]) }
123
+ def building_ranges_from_candidate(candidate)
124
+ building_and_unit_ranges = candidate.component(:building_and_unit_ranges)&.value
125
+ return [] if building_and_unit_ranges.blank?
126
+
127
+ building_ranges = JSON.parse(building_and_unit_ranges).keys
128
+ building_ranges.map { |building_range| AddressNumberRange.new(range_string: building_range) }
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end