259 lines
7.8 KiB
JavaScript

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`);
});