export function getRelevantLocations()

in src/app/api/v1/location-autocomplete/getRelevantLocations.ts [8:105]


export function getRelevantLocations(
  searchQuery: string,
  knownLocations: RelevantLocation[],
): RelevantLocation[] {
  /**
   * Fully spelled-out names of locations in the US
   *
   * This array includes a string containing the name, state, and country of
   * locations in the US, e.g. `St Louis, MO, USA`.
   *
   * Some places also have alternate names. Those *also* get added to this
   * array. For example, `Saint Louis, MO, USA`.
   */
  const locationNamesAndAlternateNames: string[] = [];
  /**
   * Track the original location data for `locationNamesAndAlternateNames`
   */
  const nameIndexToLocationMap = new Map<number, RelevantLocation>();
  knownLocations.forEach((location: RelevantLocation) => {
    const { n, s, a } = location;
    const countryCode = "USA";
    nameIndexToLocationMap.set(locationNamesAndAlternateNames.length, location);
    locationNamesAndAlternateNames.push(`${n} ${s} ${countryCode}`);
    a?.forEach((alternateName) => {
      nameIndexToLocationMap.set(
        locationNamesAndAlternateNames.length,
        location,
      );
      locationNamesAndAlternateNames.push(
        `${alternateName} ${s} ${countryCode}`,
      );
    });
  });

  // For search options see: https://github.com/leeoniya/uFuzzy#options
  const fuzzySearch = new uFuzzy({
    intraMode: 1,
    intraIns: 1,
    interIns: 3,
    intraSub: 1,
    intraTrn: 1,
    intraDel: 1,
    // matches lowercase letters, whitespace or apostrophe
    intraChars: "[a-z\\s\\d']",
    interLft: 2,
    interRgt: 0,
  });

  const nameIndexes = fuzzySearch.filter(
    locationNamesAndAlternateNames,
    searchQuery,
  );
  if (!nameIndexes) {
    return [];
  }

  const info = fuzzySearch.info(
    nameIndexes,
    locationNamesAndAlternateNames,
    searchQuery,
  );
  const order = fuzzySearch.sort(
    info,
    locationNamesAndAlternateNames,
    searchQuery,
  );
  // Since `order` contains a ranked array of indexes to the indexes of the most
  // closely matching names, we can retrieve the associated location data from
  // the `nameIndexToLocationMap` in that order to get `resultsOrdered` :
  const resultsOrdered = order.map(
    (orderIndex: number) =>
      nameIndexToLocationMap.get(nameIndexes[orderIndex])!,
  );
  // Since some locations might have matched multiple times (via their alternate
  // names), we only keep the first instance of every location:
  const uniqueResultsOrdered = resultsOrdered.filter(
    (resultIndex, matchIndex) =>
      resultsOrdered.indexOf(resultIndex) === matchIndex,
  );
  // Split and only sort the first x percentage of results by population.
  // The motivation behind this is to improve the score for matches but not all
  // of them in order to not move up weak ones.
  const maxSortByPopulationThreshold = 0.75;
  const locationSplitIndex = Math.ceil(
    uniqueResultsOrdered.length * maxSortByPopulationThreshold,
  );
  const resultsSortedByPopulation = [
    ...uniqueResultsOrdered
      .slice(0, locationSplitIndex)
      .sort(
        (a: RelevantLocation, b: RelevantLocation) =>
          Number(b.p ?? 0) - Number(a.p ?? 0),
      ),
    ...uniqueResultsOrdered.slice(locationSplitIndex),
  ];

  return resultsSortedByPopulation;
}