module KfcStores
Constants
- SUBURB_COMPONENT_KEYS
Public Instance Methods
Source
# File lib/kfc_stores.rb, line 120 def component_value(components, key) value = components[key] || components[key.to_sym] text = value.to_s.strip text.empty? ? nil : text end
Source
# File lib/kfc_stores.rb, line 113 def contact_value(contacts, key) list = Array(contacts) match = list.find { |entry| entry["key"].to_s == key } text = match&.dig("value").to_s.strip text.empty? ? nil : text end
Source
# File lib/kfc_stores.rb, line 39 def extract_store_list(data) return [] unless data.is_a?(Hash) nested = data["data"] if nested.is_a?(Hash) nested["stores"] || [] elsif nested.is_a?(Array) nested else data["stores"] || [] end end
Source
# File lib/kfc_stores.rb, line 126 def find_store(location, stores:, fallback_nearest: false) query = normalize_query(location) return nil if query.empty? scored = stores.filter_map do |store| score = score_store(store, query) score.positive? ? [store, score] : nil end best = scored.max_by { |(_store, score)| score }&.first return best if best # No textual match. For trusted geocoded queries the API has already # sorted by proximity, so the nearest (first) result is the right store # even when its address text doesn't literally echo the search term. fallback_nearest ? Array(stores).first : nil end
Source
# File lib/kfc_stores.rb, line 101 def localized_value(value) if value.is_a?(Array) english = value.find { |entry| entry["lang"].to_s.downcase.start_with?("en") } candidate = english || value.first return candidate["value"].to_s.strip if candidate.is_a?(Hash) return candidate.to_s.strip end value.to_s.strip end
Source
# File lib/kfc_stores.rb, line 144 def normalize_query(location) location.to_s.downcase.gsub(/[^a-z0-9\s]/, " ").squeeze(" ").strip end
Source
# File lib/kfc_stores.rb, line 14 def parse_location(text) value = text.to_s.strip return { error: usage_message } if value.empty? if value.match?(%r{\A["']}) unless (match = value.match(%r{\A["'](.+?)["']\z})) return { error: usage_message } end location = match[1].strip return { error: usage_message } if location.empty? return { location: location } end { location: value } end
Source
# File lib/kfc_stores.rb, line 52 def parse_store(entry) address_entry = entry.dig("addresses", 0) || {} address = address_entry["address"].is_a?(Hash) ? address_entry["address"] : address_entry lines = Array(address["addressLines"]).map(&:to_s).map(&:strip).reject(&:empty?) suburb = suburb_from_address(address) state = component_value(address, "state") postcode = component_value(address, "pinCode") || component_value(address, "postcode") { id: entry["id"].to_s.strip, name: localized_value(entry["name"]), suburb: suburb, state: state, postcode: postcode, address: lines.join(", "), phone: contact_value(entry["contacts"], "phoneNumber"), latitude: entry.dig("location", "latitude")&.to_f, longitude: entry.dig("location", "longitude")&.to_f, amenities: Array(entry["amenities"]).map(&:to_s).reject(&:empty?) } rescue StandardError nil end
Source
# File lib/kfc_stores.rb, line 32 def parse_stores(payload) data = payload.is_a?(String) ? JSON.parse(payload) : payload Array(extract_store_list(data)).filter_map { |entry| parse_store(entry) } rescue StandardError [] end
Source
# File lib/kfc_stores.rb, line 92 def parse_suburb_text(text) fragments = text.to_s.split(",").map(&:strip) candidate = (fragments.length > 1 ? fragments.last : text).to_s candidate = candidate.gsub(/\b(QLD|NSW|VIC|SA|WA|TAS|NT|ACT)\b/i, "") candidate = candidate.gsub(/\b\d{4}\b/, "") candidate = candidate.gsub(/[^a-zA-Z\s]/, " ").squeeze(" ").strip candidate.empty? ? nil : candidate end
Source
# File lib/kfc_stores.rb, line 148 def score_store(store, query) score = 0 tokens = query.split suburb = store[:suburb].to_s.downcase name = store[:name].to_s.downcase postcode = store[:postcode].to_s state = store[:state].to_s.downcase id = store[:id].to_s.downcase address = store[:address].to_s.downcase fields = [suburb, name, state, address] return 1000 if query == suburb return 950 if query == id return 900 if query == postcode return 850 if query == name score += 500 if suburb == query || id == query score += 450 if suburb.start_with?(query) || name.start_with?(query) score += 400 if query.include?(suburb) && !suburb.empty? score += 380 if query.include?(name) && !name.empty? score += 360 if query.include?(address) && !address.empty? score += 300 if tokens.all? { |token| fields.any? { |field| field.include?(token) } } score += 250 if tokens.any? { |token| postcode == token } score += 200 if tokens.any? { |token| token.length >= 3 && suburb.include?(token) } score += 180 if tokens.any? { |token| token.length >= 3 && name.include?(token) } score += 160 if tokens.any? { |token| token.length >= 3 && address.include?(token) } score end
Source
# File lib/kfc_stores.rb, line 76 def suburb_from_address(address) SUBURB_COMPONENT_KEYS.each do |key| value = component_value(address, key) return value if value end suburb_from_address_lines(Array(address["addressLines"])) end
Source
# File lib/kfc_stores.rb, line 85 def suburb_from_address_lines(lines) cleaned = lines.map(&:to_s).map(&:strip).reject(&:empty?) return nil if cleaned.empty? parse_suburb_text(cleaned.last) end
Source
# File lib/kfc_stores.rb, line 10 def usage_message 'Usage: !kfc <location> — quote multi-word locations, e.g. !kfc "mount isa qld"' end