/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * Decompose a French street name into Stage 3 components. French convention puts the street type as * a leading word: "Rue de Rivoli", "Avenue des Champs-Élysées", "Bd Voltaire". * * The street type becomes street_prefix in our schema. The remaining tokens form the street name. * * Examples: "Rue de Rivoli" → { prefix: "Rue", street: "de Rivoli" } "Avenue des Champs-Élysées" → * { prefix: "Avenue", street: "des Champs-Élysées" } "Boulevard Voltaire" → { prefix: * "Boulevard", street: "Voltaire" } * * Sources street types from `core/data/libpostal/dictionaries/fr/street_types.txt`. */ import { loadLibpostalDictionary } from "#adapters/utils" /** * Tokens a BAN street needs before a type/article/name decomposition is attempted. */ const MIN_TOKENS_FOR_DECOMPOSE = 3 const STREET_TYPES_FR = await loadLibpostalDictionary("fr", "street_types.txt") export interface DecomposedFrStreet { prefix: string | null street: string } /** * Decompose a French street name into prefix (leading type word) and street name. * * If the first 1-2 tokens match a known street type (allowing for multi-word like "ancien chemin"), they become the * prefix. Returns `{ prefix: null, street: original }` if no match. */ export function decomposeFrStreet(fullname: string): DecomposedFrStreet { const trimmed = fullname.trim() if (!trimmed) return { prefix: null, street: "" } const tokens = trimmed.split(/\s+/) if (tokens.length < 2) return { prefix: null, street: trimmed } const norm = (s: string) => s.toLowerCase().replace(/[.,;]$/, "") // Try 2-word prefix first (e.g. "ancien chemin") if (tokens.length >= MIN_TOKENS_FOR_DECOMPOSE) { const twoWord = norm(tokens[0]!) + " " + norm(tokens[1]!) if (STREET_TYPES_FR.has(twoWord)) { return { prefix: tokens.slice(0, 2).join(" "), street: tokens.slice(2).join(" ") } } } // Then try 1-word prefix const first = norm(tokens[0]!) if (STREET_TYPES_FR.has(first)) { return { prefix: tokens[0]!, street: tokens.slice(1).join(" ") } } return { prefix: null, street: trimmed } }