const express = require('express'); const cors = require('cors'); const app = express(); const PORT = 8000; // Middleware app.use(cors()); app.use(express.json()); // Swiss cantons for validation const SWISS_CANTONS = { // Map of postal code prefixes to cantons '1': 'VD/GE/VS/FR/NE', // 1000-1999 '2': 'NE/JU/BE', // 2000-2999 '3': 'BE/FR/SO/VS', // 3000-3999 '4': 'BS/BL/AG/SO/JU', // 4000-4999 '5': 'AG/ZH/SO', // 5000-5999 '6': 'LU/ZG/NW/OW/UR/TI', // 6000-6999 '7': 'GR', // 7000-7999 '8': 'ZH/SH/TG/SG/GL', // 8000-8999 '9': 'SG/AR/AI/TG/GL', // 9000-9999 }; // Common street types in Switzerland const STREET_TYPES = [ 'strasse', 'str.', 'str', 'weg', 'gasse', 'platz', 'allee', 'avenue', 'ave', 'boulevard', 'promenade', 'quartier', 'rue', 'route', 'chemin', 'via', 'corso' ]; // Swiss address format validation function validateSwissAddress(address) { // Looking for patterns like street name + number, postal code (4 digits) + city const addressComponents = { street: null, houseNumber: null, postalCode: null, city: null, canton: null, valid: false, formattedAddress: address, confidence: 0 // 0-100 confidence score }; // Clean and normalize the address const cleanAddress = address.replace(/\s+/g, ' ') .replace(/,\s*/g, ', ') .trim(); // First try to match standard Swiss format: Street Number, PostalCode City const fullMatch = cleanAddress.match( /([A-Za-zäöüÄÖÜß\s\.-]+[\s-]+\d+\w*),?\s*(\d{4})\s+([A-Za-zäöüÄÖÜß\s\.-]+)/i ); if (fullMatch) { addressComponents.street = fullMatch[1].trim().replace(/\s+(\d+\w*)$/, ''); addressComponents.houseNumber = fullMatch[1].match(/(\d+\w*)$/)[1]; addressComponents.postalCode = fullMatch[2]; addressComponents.city = fullMatch[3].trim(); addressComponents.confidence = 90; } else { // Try to extract components individually // Extract street with house number - more flexible pattern let streetMatch = null; // First try to find a street with a known suffix for (const type of STREET_TYPES) { const regex = new RegExp(`([A-Za-zäöüÄÖÜß\\s\\.-]+(${type})\\s+)(\\d+\\w*)`, 'i'); const match = cleanAddress.match(regex); if (match) { streetMatch = match; addressComponents.confidence += 20; break; } } // If no match with known street types, try generic pattern if (!streetMatch) { streetMatch = cleanAddress.match(/([A-Za-zäöüÄÖÜß\s\.-]+)(?:\s+)(\d+\w*)/i); if (streetMatch) { addressComponents.confidence += 10; } } if (streetMatch) { addressComponents.street = streetMatch[1].trim(); addressComponents.houseNumber = streetMatch[2]; } // Extract postal code (4 digits in Switzerland) and city const postalCityMatch = cleanAddress.match(/\b(\d{4})\s+([A-Za-zäöüÄÖÜß\s\.-]+)\b/i); if (postalCityMatch) { addressComponents.postalCode = postalCityMatch[1]; addressComponents.city = postalCityMatch[2].trim(); addressComponents.confidence += 40; // Determine canton from postal code const postalPrefix = postalCityMatch[1].charAt(0); addressComponents.canton = SWISS_CANTONS[postalPrefix] || null; if (addressComponents.canton) { addressComponents.confidence += 10; } } } // Check if we have the essential components if (addressComponents.street && addressComponents.postalCode && addressComponents.city) { addressComponents.valid = true; // Format the address in a standardized Swiss way addressComponents.formattedAddress = `${addressComponents.street} ${addressComponents.houseNumber}, ${addressComponents.postalCode} ${addressComponents.city}`; // Add canton if available if (addressComponents.canton) { addressComponents.formattedAddress += ` (${addressComponents.canton})`; } } return addressComponents; } // Endpoint for address validation app.post('/api/validate-address', (req, res) => { const { address } = req.body; if (!address) { return res.status(400).json({ message: 'Address is required', success: false }); } console.log('Validating address:', address); // Validate the address const validationResult = validateSwissAddress(address); if (validationResult.valid) { console.log('Address is valid:', validationResult.formattedAddress); console.log('Confidence score:', validationResult.confidence); return res.json(validationResult.formattedAddress); } else { console.log('Address validation failed, returning original:', address); // Return the original address if validation fails return res.json(address); } }); // Endpoint for detailed address validation app.post('/api/validate-address/detailed', (req, res) => { const { address } = req.body; if (!address) { return res.status(400).json({ success: false, message: 'Address is required' }); } console.log('Performing detailed validation for:', address); // Validate the address const validationResult = validateSwissAddress(address); // Return the full validation result for detailed analysis return res.json({ success: validationResult.valid, original: address, formatted: validationResult.formattedAddress, components: { street: validationResult.street, houseNumber: validationResult.houseNumber, postalCode: validationResult.postalCode, city: validationResult.city, canton: validationResult.canton }, confidence: validationResult.confidence, valid: validationResult.valid }); }); // Mock address lookup endpoint for testing app.get('/api/address-lookup', (req, res) => { const { postalCode, city } = req.query; if (!postalCode && !city) { return res.status(400).json({ success: false, message: 'Postal code or city is required' }); } // Sample street data for common Swiss cities const streetData = { // Bern '3000': [ 'Bundesplatz', 'Bahnhofstrasse', 'Marktgasse', 'Spitalgasse', 'Kramgasse', 'Münstergasse', 'Gerechtigkeitsgasse', 'Postgasse', 'Aarbergergasse' ], // Zürich '8000': [ 'Bahnhofstrasse', 'Rämistrasse', 'Uraniastrasse', 'Limmatquai', 'Talstrasse', 'Seefeldstrasse', 'Bellerivestrasse', 'Mythengasse', 'Europaallee' ], // Basel '4000': [ 'Freiestrasse', 'Steinenvorstadt', 'Marktplatz', 'Gerbergasse', 'Spalenberg', 'Greifengasse', 'Rheingasse', 'Clarastrasse', 'St. Alban-Vorstadt' ], // Lausanne '1000': [ 'Rue du Petit-Chêne', 'Avenue du Léman', 'Rue de Bourg', 'Place de la Palud', 'Avenue de Cour', 'Rue Centrale', 'Route de Berne' ] }; // Default response let streets = []; if (postalCode && streetData[postalCode]) { streets = streetData[postalCode]; } else if (city) { // Map city names to postal codes const cityMap = { 'bern': '3000', 'zürich': '8000', 'zurich': '8000', 'basel': '4000', 'lausanne': '1000' }; const normalizedCity = city.toLowerCase(); if (cityMap[normalizedCity] && streetData[cityMap[normalizedCity]]) { streets = streetData[cityMap[normalizedCity]]; } } return res.json({ success: true, streets: streets.map(street => ({ name: street, postalCode: postalCode || '' })) }); }); // Health check endpoint app.get('/api/health', (req, res) => { res.json({ status: 'up', message: 'Address validation service is running' }); }); // Start the server app.listen(PORT, () => { console.log(`Address validation service running on http://localhost:${PORT}`); console.log(`Test the service at http://localhost:${PORT}/api/health`); });