export interface ParsedAddress { street: string; house: string; apartment: string; entrance: string; floor: string; remaining: string; // unrecognized parts } // Common Russian/Kazakh address patterns const STREET_PREFIXES = ['ул\\.', 'ул', 'пр\\.', 'пр', 'пр-т', 'бульвар', 'пер\\.', 'пер', 'ш\\.', 'шоссе', 'тракт']; const HOUSE_PATTERNS = ['д\\.', 'дом', 'д(?=\\s*\\d)', 'строение', 'стр\\.']; const APARTMENT_PATTERNS = ['кв\\.', 'квартира', 'кв(?=\\s*\\d)', 'офис', 'оф\\.']; const ENTRANCE_PATTERNS = ['подъезд', 'под\\.', 'под(?=\\s*\\d)', 'п(?=\\s*\\d)']; const FLOOR_PATTERNS = ['этаж', 'эт\\.', 'эт(?=\\s*\\d)', 'э(?=\\s*\\d)']; function createPattern(prefixes: string[]): RegExp { const prefixPart = prefixes.join('|'); // Match prefix followed by optional spaces/separators and then the value // Use Unicode property \p{L} for letters to support Cyrillic return new RegExp(`(?:${prefixPart})[\\s\\.]*([0-9]+[\\p{L}\\-]*|[\\p{L}][\\p{L}\\d\\-]*)`, 'iu'); } function extractValue(text: string, patterns: string[]): { value: string; remaining: string } { const regex = createPattern(patterns); const match = text.match(regex); if (match) { // Remove the matched part from text const remaining = text.replace(match[0], '').trim().replace(/^[,.\s]+/, ''); return { value: match[1].trim(), remaining }; } return { value: '', remaining: text }; } export function parseAddress(address: string): ParsedAddress { let remaining = address.trim(); // Extract components in order const streetResult = extractValue(remaining, STREET_PREFIXES); const street = streetResult.value; remaining = streetResult.remaining; // Try to extract house: first standalone number at the start, then with prefix let house = ''; let houseResult; // Try standalone number first (e.g., "ул. Абая 5" - house is "5" without "д." prefix) const standaloneHouseMatch = remaining.match(/^\s*(\d+[\p{L}]?)(?:\s*[,;]|\s+(?=кв|под|э|п\s|э\s|д\.|д\s|дом))/iu); if (standaloneHouseMatch) { house = standaloneHouseMatch[1]; remaining = remaining.slice(standaloneHouseMatch[0].length).trim().replace(/^[,;\s]+/, ''); } else { // Fallback: try with prefix patterns houseResult = extractValue(remaining, HOUSE_PATTERNS); house = houseResult.value; remaining = houseResult.remaining; } const apartmentResult = extractValue(remaining, APARTMENT_PATTERNS); const apartment = apartmentResult.value; remaining = apartmentResult.remaining; const entranceResult = extractValue(remaining, ENTRANCE_PATTERNS); const entrance = entranceResult.value; remaining = entranceResult.remaining; const floorResult = extractValue(remaining, FLOOR_PATTERNS); const floor = floorResult.value; remaining = floorResult.remaining; // Clean up remaining - remove common separators remaining = remaining .replace(/^[,.\s]+/, '') .replace(/[,.\s]+$/, '') .trim(); return { street, house, apartment, entrance, floor, remaining }; } // Format address for display export function formatAddressShort(addr: ParsedAddress): string { const parts: string[] = []; if (addr.street) parts.push(addr.street); if (addr.house) parts.push(`д. ${addr.house}`); if (addr.apartment) parts.push(`кв. ${addr.apartment}`); return parts.join(', ') || addr.remaining; } export function formatAddressDetails(addr: ParsedAddress): string { const parts: string[] = []; if (addr.entrance) parts.push(`Подъезд ${addr.entrance}`); if (addr.floor) parts.push(`этаж ${addr.floor}`); return parts.join(', '); } // Build full address from components export function buildFullAddress( street: string, house: string, apartment?: string, entrance?: string, floor?: string ): string { const parts: string[] = []; if (street) parts.push(street); if (house) parts.push(`д. ${house}`); if (apartment) parts.push(`кв. ${apartment}`); if (entrance || floor) { const details: string[] = []; if (entrance) details.push(`подъезд ${entrance}`); if (floor) details.push(`этаж ${floor}`); parts.push(details.join(', ')); } return parts.join(', '); }