259 lines
7.8 KiB
JavaScript
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`);
|
|
});
|