# frozen_string_literal: true class APIGeoService class << self def countries(locale: I18n.locale) I18nData.countries(locale) .merge(get_localized_additional_countries(locale)) .map { |(code, name)| { name:, code: } } .sort_by { I18n.transliterate(_1[:name].tr('î', 'Î')) } end def country_name(code, locale: I18n.locale) countries(locale:).find { _1[:code] == code }&.dig(:name) end def country_code(name) return if name.nil? code = I18nData.country_code(name) || I18nData.country_code(name.humanize) || I18nData.country_code(name.titleize) if code.nil? countries_index_fr[I18n.transliterate(name).upcase]&.dig(:code) else code end end def regions get_from_api_geo(:regions).sort_by { I18n.transliterate(_1[:name]) } end def region_options = regions.map { [_1[:name], _1[:code]] } def region_name(code) regions.find { _1[:code] == code }&.dig(:name) end def region_code(name) return if name.nil? regions.find { _1[:name] == name }&.dig(:code) end def region_code_by_departement(code) return if code.nil? departements.find { _1[:code] == code }&.dig(:region_code) end def departements ([{ code: '99', name: 'Etranger' }] + get_from_api_geo(:departements)).sort_by { _1[:code] } end def departement_options departements.map { ["#{_1[:code]} – #{_1[:name]}", _1[:code]] } end def departement_name(code) departements.find { _1[:code] == code }&.dig(:name) end def departement_name_by_postal_code(postal_code) APIGeoService.departement_name(postal_code[0..2]) || APIGeoService.departement_name(postal_code[0..1]) end def departement_code(name) return if name.nil? departements.find { _1[:name] == name }&.dig(:code) end def epcis(departement_code) get_from_api_geo("epcis-#{departement_code}").sort_by { I18n.transliterate(_1[:name]) } end def epci_name(departement_code, code) epcis(departement_code).find { _1[:code] == code }&.dig(:name) end def epci_code(departement_code, name) epcis(departement_code).find { _1[:name] == name }&.dig(:code) end def communes(departement_code) get_from_api_geo("communes-#{departement_code}").sort_by { I18n.transliterate([_1[:name], _1[:postal_code]].join(' ')) } end def communes_by_postal_code(postal_code) communes_by_postal_code_map.fetch(postal_code, []) .filter { !_1[:code].in?(['75056', '13055', '69123']) } .sort_by { I18n.transliterate([_1[:name], _1[:postal_code]].join(' ')) } end def commune_name(departement_code, code) communes(departement_code).find { _1[:code] == code }&.dig(:name) end def commune_by_name_or_postal_code(query) if postal_code?(query) fetch_by_postal_code(query) else fetch_by_name(query) end end def commune_code(departement_code, name) communes(departement_code).find { _1[:name] == name }&.dig(:code) end def commune_postal_codes(departement_code, code) communes(departement_code).filter { _1[:code] == code }.map { _1[:postal_code] } end def parse_ban_address(feature) return unless ban_address_schema.valid?(feature) properties = feature.fetch('properties') city_code = properties.fetch('citycode') territory = if properties['context'].present? department_code = properties.fetch('context').split(',').first region_code = region_code_by_departement(department_code) { department_name: departement_name(department_code), department_code:, region_name: region_name(region_code), region_code:, city_name: safely_normalize_city_name(department_code, city_code, properties['city']), city_code: } else { city_name: properties['city'], city_code: } end { label: properties.fetch('label'), type: properties.fetch('type'), street_address: properties.fetch('name'), postal_code: properties.fetch('postcode') { '' }, # API graphql / serializer requires non-null data street_number: properties['housenumber'], street_name: properties['street'], geometry: feature['geometry'] }.merge(territory) end def parse_rna_address(address) postal_code = address[:code_postal] city_name_fallback = address[:commune] city_code = address[:code_insee] department_code, region_code = if postal_code.present? && city_code.present? commune = communes_by_postal_code(postal_code).find { _1[:code] == city_code } if commune.present? [commune[:departement_code], commune[:region_code]] else [] end end department_name = departement_name(department_code) { street_number: address[:numero_voie], street_name: address[:libelle_voie], street_address: address[:libelle_voie].present? ? [address[:numero_voie], address[:type_voie], address[:libelle_voie]].compact.join(' ') : nil, postal_code: postal_code.presence || '', city_name: safely_normalize_city_name(department_code, city_code, city_name_fallback), city_code: city_code.presence || '', departement_code: department_code, department_code:, departement_name: department_name, department_name:, region_code:, region_name: region_name(region_code) } end def parse_rnf_address(address) postal_code = address[:postalCode] city_name_fallback = address[:cityName] city_code = address[:cityCode] department_code, region_code = if postal_code.present? && city_code.present? commune = communes_by_postal_code(postal_code).find { _1[:code] == city_code } if commune.present? [commune[:departement_code], commune[:region_code]] else [] end end department_name = departement_name(department_code) { street_number: address[:streetNumber], street_name: address[:streetName], street_address: address[:streetAddress], postal_code: postal_code.presence || '', city_name: safely_normalize_city_name(department_code, city_code, city_name_fallback), city_code: city_code.presence || '', departement_code: department_code, department_code:, departement_name: department_name, department_name:, region_code:, region_name: region_name(region_code) } end def parse_etablissement_address(etablissement) postal_code = etablissement.code_postal city_name_fallback = etablissement.localite.presence || '' city_code = etablissement.code_insee_localite department_code, region_code = if postal_code.present? && city_code.present? commune = communes_by_postal_code(postal_code).find { _1[:code] == city_code } if commune.present? [commune[:departement_code], commune[:region_code]] else [] end end department_name = departement_name(department_code) { street_number: etablissement.numero_voie, street_name: etablissement.nom_voie, street_address: etablissement.nom_voie.present? ? [etablissement.numero_voie, etablissement.type_voie, etablissement.nom_voie].compact.join(' ') : nil, postal_code: postal_code.presence || '', city_name: safely_normalize_city_name(department_code, city_code, city_name_fallback), city_code: city_code.presence || '', departement_code: department_code, department_code:, departement_name: department_name, department_name:, region_code:, region_name: region_name(region_code) } end def safely_normalize_city_name(department_code, city_code, fallback) return fallback if department_code.blank? || city_code.blank? commune_name(department_code, city_code) || fallback end def format_commune_response(results, with_combined_code) results.reject(&method(:code_metropole?)).flat_map do |result| item = { name: result[:nom].tr("'", '’'), code: result[:code], epci_code: result[:codeEpci], departement_code: result[:codeDepartement] }.compact if result[:codesPostaux].present? result[:codesPostaux].map { item.merge(postal_code: _1) } else [item] end.map do |item| if with_combined_code.present? { label: "#{item[:name]} (#{item[:postal_code]})", value: "#{item[:code]}-#{item[:postal_code]}" } else { label: "#{item[:name]} (#{item[:postal_code]})", value: item[:code], data: item[:postal_code] } end end end end def inline_service_public_address(address_data) return nil if address_data.blank? components = [ address_data['numero_voie'], address_data['complement1'], address_data['complement2'], address_data['service_distribution'], address_data['code_postal'], address_data['nom_commune'] ].compact_blank components.join(' ') end private def code_metropole?(result) result[:code].in?(['75056', '13055', '69123']) end def communes_by_postal_code_map Rails.cache.fetch('api_geo_communes', expires_in: 1.day, version: 3) do departements .filter { _1[:code] != '99' } .flat_map { communes(_1[:code]) } .group_by { _1[:postal_code] } end end def get_from_api_geo(scope) Rails.cache.fetch("api_geo_#{scope}", expires_in: 1.day, version: 3) do JSON.parse(Rails.root.join('lib', 'data', 'api_geo', "#{scope}.json").read, symbolize_names: true) end end def countries_index_fr Rails.cache.fetch('countries_index_fr', expires_in: 1.week) do countries(locale: 'FR').index_by { I18n.transliterate(_1[:name]).upcase } end end def get_localized_additional_countries(locale) additional_countries[locale.to_s.upcase] || {} end def additional_countries { 'FR' => { 'XK' => 'Kosovo' }, 'EN' => { 'XK' => 'Kosovo' } } end private def fetch_by_name(name) Typhoeus.get("#{API_GEO_URL}/communes", params: { type: 'commune-actuelle,arrondissement-municipal', nom: name, boost: 'population', limit: 100 }, timeout: 3) end def fetch_by_postal_code(postal_code) Typhoeus.get("#{API_GEO_URL}/communes", params: { type: 'commune-actuelle,arrondissement-municipal', codePostal: postal_code, boost: 'population', limit: 50 }, timeout: 3) end def postal_code?(string) string.match?(/\A[-+]?\d+\z/) ? true : false end def ban_address_schema JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json')) end end end