All files / src/utils eventLocationBuildingMatcher.ts

100% Statements 41/41
100% Branches 12/12
100% Functions 12/12
100% Lines 36/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71    5x 434x         5x 513x   5x 513x 513x     5x 80x 80x       80x             80x   290x       5x     29x 29x   27x 27x 27x 15x 100x   15x     14x 181x 181x 14x   167x   14x   9x 9x 144x 513x 9x   9x   5x 5x    
import { Building, BUILDINGS } from "../data/buildings";
 
const normalizeLocationText = (value: string): string =>
  value
    .toLowerCase()
    .replaceAll(/[^a-z0-9]+/g, " ")
    .trim();
 
const escapeRegExp = (value: string): string =>
  value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
 
const containsAlias = (normalizedLocation: string, alias: string): boolean => {
  const aliasPattern = new RegExp(String.raw`\b${escapeRegExp(alias)}\b`);
  return aliasPattern.test(normalizedLocation);
};
 
const BUILDING_ALIAS_INDEX = BUILDINGS.map((building) => {
  const normalizedName = normalizeLocationText(building.name);
  const simplifiedName = normalizedName
    .replaceAll(/\b(building|complex|annex|library|wing|center|centre)\b/g, " ")
    .replaceAll(/\s+/g, " ")
    .trim();
  const aliases = new Set<string>([
    normalizeLocationText(building.id),
    normalizedName,
    simplifiedName,
    ...building.aliases.map(normalizeLocationText),
  ]);
 
  return {
    building,
    aliases: [...aliases].filter((alias) => alias.length >= 2),
  };
});
 
export const findBuildingFromLocationText = (
  locationText: string,
): Building | null => {
  const trimmed = locationText.trim();
  if (!trimmed) return null;
 
  const upper = trimmed.toUpperCase();
  const roomCodeMatch = /\b([A-Z]{1,3})[-\s]?[A-Z]?\d{2,4}[A-Z]?\b/.exec(upper);
  if (roomCodeMatch?.[1]) {
    const byRoomCode = BUILDINGS.find(
      (building) => building.id.toUpperCase() === roomCodeMatch[1],
    );
    if (byRoomCode) return byRoomCode;
  }
 
  const byBuildingId = BUILDINGS.find((building) => {
    const id = building.id.toUpperCase();
    if (id.length === 1) {
      return new RegExp(String.raw`\b${id}[-\s]?\d{2,4}[A-Z]?\b`).test(upper);
    }
    return new RegExp(String.raw`\b${id}(?=\b|[-\s])`).test(upper);
  });
  if (byBuildingId) return byBuildingId;
 
  const normalizedLocation = normalizeLocationText(trimmed);
  const aliasMatches = BUILDING_ALIAS_INDEX.flatMap(({ building, aliases }) =>
    aliases
      .filter((alias) => containsAlias(normalizedLocation, alias))
      .map((alias) => ({ building, score: alias.length })),
  );
  if (aliasMatches.length === 0) return null;
 
  aliasMatches.sort((a, b) => b.score - a.score);
  return aliasMatches[0].building;
};