feat: reorganize supabase config - flat db init structure, add edge functions, mcp, kong api config

This commit is contained in:
2026-02-22 00:31:50 +00:00
parent 95af17c02d
commit 31ecf136f7
54 changed files with 4383 additions and 1106 deletions
@@ -0,0 +1,317 @@
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
interface BatchGeocodingRequest {
limit?: number
force_refresh?: boolean
institute_ids?: string[]
}
interface GeocodingResult {
institute_id: string
success: boolean
message: string
coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
geojson?: any
osm?: any
}
error?: string
}
serve(async (req: Request) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// Get environment variables
const supabaseUrl = Deno.env.get('SUPABASE_URL')
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_RATE_KEY')
const searxngUrl = Deno.env.get('SEARXNG_URL') || 'https://search.kevlarai.com'
if (!supabaseUrl || !supabaseServiceKey) {
throw new Error('Missing required environment variables')
}
// Create Supabase client
const supabase = createClient(supabaseUrl, supabaseServiceKey)
// Parse request body
const body: BatchGeocodingRequest = await req.json()
const limit = body.limit || 10
const forceRefresh = body.force_refresh || false
// Get institutes that need geocoding
let query = supabase
.from('institutes')
.select('id, name, address, geo_coordinates')
.not('import_id', 'is', null)
if (!forceRefresh) {
// Only get institutes without coordinates or with empty coordinates
query = query.or('geo_coordinates.is.null,geo_coordinates.eq.{}')
}
if (body.institute_ids && body.institute_ids.length > 0) {
query = query.in('id', body.institute_ids)
}
const { data: institutes, error: fetchError } = await query.limit(limit)
if (fetchError) {
throw new Error(`Failed to fetch institutes: ${fetchError.message}`)
}
if (!institutes || institutes.length === 0) {
return new Response(
JSON.stringify({
success: true,
message: 'No institutes found that need geocoding',
processed: 0
}),
{
status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
console.log(`Processing ${institutes.length} institutes for geocoding`)
const results: GeocodingResult[] = []
let successCount = 0
let errorCount = 0
// Process institutes sequentially to avoid overwhelming the SearXNG service
for (const institute of institutes) {
try {
const address = institute.address as any
if (!address) {
results.push({
institute_id: institute.id,
success: false,
message: 'No address information available',
error: 'Missing address data'
})
errorCount++
continue
}
// Build search query from address components
const addressParts = [
address.street,
address.town,
address.county,
address.postcode,
address.country
].filter(Boolean)
if (addressParts.length === 0) {
results.push({
institute_id: institute.id,
success: false,
message: 'No valid address components found',
error: 'Empty address parts'
})
errorCount++
continue
}
const searchQuery = addressParts.join(', ')
console.log(`Geocoding institute ${institute.id}: ${searchQuery}`)
// Query SearXNG for geocoding
const geocodingResult = await geocodeAddress(searchQuery, searxngUrl)
if (geocodingResult.success && geocodingResult.coordinates) {
// Update institute with geospatial coordinates
const { error: updateError } = await supabase
.from('institutes')
.update({
geo_coordinates: {
latitude: geocodingResult.coordinates.latitude,
longitude: geocodingResult.coordinates.longitude,
boundingbox: geocodingResult.coordinates.boundingbox,
geojson: geocodingResult.coordinates.geojson,
osm: geocodingResult.coordinates.osm,
search_query: searchQuery,
geocoded_at: new Date().toISOString()
}
})
.eq('id', institute.id)
if (updateError) {
throw new Error(`Failed to update institute: ${updateError.message}`)
}
results.push({
institute_id: institute.id,
success: true,
message: 'Successfully geocoded',
coordinates: geocodingResult.coordinates
})
successCount++
// Log the successful geocoding
await supabase
.from('function_logs')
.insert({
file_id: null,
step: 'batch_geocoding',
message: 'Successfully geocoded institute address in batch',
data: {
institute_id: institute.id,
search_query: searchQuery,
coordinates: geocodingResult.coordinates
}
})
} else {
results.push({
institute_id: institute.id,
success: false,
message: 'Geocoding failed',
error: geocodingResult.error || 'Unknown error'
})
errorCount++
}
// Add a small delay between requests to be respectful to the SearXNG service
await new Promise(resolve => setTimeout(resolve, 100))
} catch (error) {
console.error(`Error processing institute ${institute.id}:`, error)
results.push({
institute_id: institute.id,
success: false,
message: 'Processing error',
error: error.message
})
errorCount++
}
}
// Log the batch operation
await supabase
.from('function_logs')
.insert({
file_id: null,
step: 'batch_geocoding_complete',
message: 'Batch geocoding operation completed',
data: {
total_processed: institutes.length,
successful: successCount,
failed: errorCount,
results: results
}
})
return new Response(
JSON.stringify({
success: true,
message: 'Batch geocoding completed',
summary: {
total_processed: institutes.length,
successful: successCount,
failed: errorCount
},
results: results
}),
{
status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
} catch (error) {
console.error('Error in batch institute geocoder:', error)
return new Response(
JSON.stringify({
error: 'Internal server error',
details: error.message
}),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
})
async function geocodeAddress(searchQuery: string, searxngUrl: string): Promise<{
success: boolean
coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
geojson?: any
osm?: any
}
error?: string
}> {
try {
// Format search query for OSM
const osmQuery = `!osm ${searchQuery}`
const searchUrl = `${searxngUrl}/search?q=${encodeURIComponent(osmQuery)}&format=json`
const response = await fetch(searchUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'User-Agent': 'ClassroomCopilot-BatchGeocoder/1.0'
}
})
if (!response.ok) {
throw new Error(`SearXNG request failed: ${response.status} ${response.statusText}`)
}
const data = await response.json()
// Check if we have results - the number_of_results field might be unreliable
// so we check the results array directly
if (!data.results || data.results.length === 0) {
return {
success: false,
error: 'No results returned from SearXNG'
}
}
const result = data.results[0]
if (!result.latitude || !result.longitude) {
return {
success: false,
error: 'Missing latitude or longitude in SearXNG response'
}
}
return {
success: true,
coordinates: {
latitude: parseFloat(result.latitude),
longitude: parseFloat(result.longitude),
boundingbox: result.boundingbox || [],
geojson: result.geojson,
osm: result.osm
}
}
} catch (error) {
console.error('Geocoding error:', error)
return {
success: false,
error: error.message
}
}
}
@@ -0,0 +1,315 @@
// Example usage of Institute Geocoder functions
// This file demonstrates how to integrate the geocoding functions in your frontend
import { createClient } from '@supabase/supabase-js'
// Initialize Supabase client
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
const supabase = createClient(supabaseUrl, supabaseAnonKey)
// Types for institute data
interface Institute {
id: string
name: string
address: {
street?: string
town?: string
county?: string
postcode?: string
country?: string
}
geo_coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
search_query: string
geocoded_at: string
}
}
interface GeocodingResult {
success: boolean
message: string
coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
}
error?: string
}
// 1. Geocode a single institute
export async function geocodeInstitute(instituteId: string): Promise<GeocodingResult> {
try {
const { data, error } = await supabase.functions.invoke('institute-geocoder', {
body: { institute_id: instituteId }
})
if (error) {
throw new Error(error.message)
}
return data
} catch (error) {
console.error('Geocoding failed:', error)
return {
success: false,
message: 'Geocoding failed',
error: error instanceof Error ? error.message : 'Unknown error'
}
}
}
// 2. Batch geocode multiple institutes
export async function batchGeocodeInstitutes(
limit: number = 10,
forceRefresh: boolean = false
): Promise<any> {
try {
const { data, error } = await supabase.functions.invoke('institute-geocoder/batch', {
body: {
limit,
force_refresh: forceRefresh
}
})
if (error) {
throw new Error(error.message)
}
return data
} catch (error) {
console.error('Batch geocoding failed:', error)
throw error
}
}
// 3. Get institutes that need geocoding
export async function getInstitutesNeedingGeocoding(): Promise<Institute[]> {
try {
const { data, error } = await supabase
.from('institutes')
.select('id, name, address, geo_coordinates')
.or('geo_coordinates.is.null,geo_coordinates.eq.{}')
.not('import_id', 'is', null)
if (error) {
throw new Error(error.message)
}
return data || []
} catch (error) {
console.error('Failed to fetch institutes:', error)
return []
}
}
// 4. Display institute on a map (example with Leaflet)
export function displayInstituteOnMap(
institute: Institute,
mapElement: HTMLElement
): void {
if (!institute.geo_coordinates) {
console.warn('Institute has no coordinates:', institute.name)
return
}
// This is a placeholder - you'd need to implement actual map rendering
// For example, using Leaflet, Mapbox, or Google Maps
const { latitude, longitude } = institute.geo_coordinates
console.log(`Displaying ${institute.name} at ${latitude}, ${longitude}`)
// Example map implementation:
// const map = L.map(mapElement).setView([latitude, longitude], 13)
// L.marker([latitude, longitude]).addTo(map).bindPopup(institute.name)
}
// 5. React component example
export function InstituteGeocoder() {
const [institutes, setInstitutes] = useState<Institute[]>([])
const [loading, setLoading] = useState(false)
const [geocodingProgress, setGeocodingProgress] = useState(0)
// Load institutes that need geocoding
useEffect(() => {
loadInstitutes()
}, [])
async function loadInstitutes() {
const data = await getInstitutesNeedingGeocoding()
setInstitutes(data)
}
// Geocode all institutes
async function geocodeAllInstitutes() {
setLoading(true)
setGeocodingProgress(0)
try {
const result = await batchGeocodeInstitutes(institutes.length, false)
if (result.success) {
setGeocodingProgress(100)
// Reload institutes to show updated coordinates
await loadInstitutes()
}
} catch (error) {
console.error('Batch geocoding failed:', error)
} finally {
setLoading(false)
}
}
// Geocode single institute
async function geocodeSingleInstitute(instituteId: string) {
try {
const result = await geocodeInstitute(instituteId)
if (result.success) {
// Reload institutes to show updated coordinates
await loadInstitutes()
}
} catch (error) {
console.error('Single geocoding failed:', error)
}
}
return (
<div className="institute-geocoder">
<h2>Institute Geocoding</h2>
<div className="controls">
<button
onClick={geocodeAllInstitutes}
disabled={loading || institutes.length === 0}
>
{loading ? 'Geocoding...' : `Geocode All (${institutes.length})`}
</button>
{loading && (
<div className="progress">
<div
className="progress-bar"
style={{ width: `${geocodingProgress}%` }}
/>
</div>
)}
</div>
<div className="institutes-list">
{institutes.map(institute => (
<div key={institute.id} className="institute-item">
<h3>{institute.name}</h3>
<p>
{institute.address.street && `${institute.address.street}, `}
{institute.address.town && `${institute.address.town}, `}
{institute.address.county && `${institute.address.county}, `}
{institute.address.postcode}
</p>
{institute.geo_coordinates ? (
<div className="coordinates">
<span>📍 {institute.geo_coordinates.latitude}, {institute.geo_coordinates.longitude}</span>
<span>Geocoded: {new Date(institute.geo_coordinates.geocoded_at).toLocaleDateString()}</span>
</div>
) : (
<button
onClick={() => geocodeSingleInstitute(institute.id)}
disabled={loading}
>
Geocode
</button>
)}
</div>
))}
</div>
</div>
)
}
// 6. Utility functions for working with coordinates
export class CoordinateUtils {
// Calculate distance between two points (Haversine formula)
static calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const R = 6371 // Earth's radius in kilometers
const dLat = this.toRadians(lat2 - lat1)
const dLon = this.toRadians(lon2 - lon1)
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
// Convert degrees to radians
private static toRadians(degrees: number): number {
return degrees * (Math.PI / 180)
}
// Check if coordinates are within a bounding box
static isWithinBounds(
lat: number,
lon: number,
bounds: [number, number, number, number] // [minLat, maxLat, minLon, maxLon]
): boolean {
return lat >= bounds[0] && lat <= bounds[1] &&
lon >= bounds[2] && lon <= bounds[3]
}
// Format coordinates for display
static formatCoordinates(lat: number, lon: number): string {
const latDir = lat >= 0 ? 'N' : 'S'
const lonDir = lon >= 0 ? 'E' : 'W'
return `${Math.abs(lat).toFixed(6)}°${latDir}, ${Math.abs(lon).toFixed(6)}°${lonDir}`
}
}
// 7. Example of using coordinates in Neo4j queries
export const neo4jQueries = {
// Create institute node with location
createInstituteWithLocation: `
CREATE (i:Institute {
id: $institute_id,
name: $name,
location: point({latitude: $latitude, longitude: $longitude})
})
RETURN i
`,
// Find institutes within radius
findInstitutesWithinRadius: `
MATCH (i:Institute)
WHERE distance(i.location, point({latitude: $centerLat, longitude: $centerLon})) < $radiusMeters
RETURN i, distance(i.location, point({latitude: $centerLat, longitude: $centerLon})) as distance
ORDER BY distance
`,
// Find institutes in bounding box
findInstitutesInBounds: `
MATCH (i:Institute)
WHERE i.location.latitude >= $minLat
AND i.location.latitude <= $maxLat
AND i.location.longitude >= $minLon
AND i.location.longitude <= $maxLon
RETURN i
`
}
export default {
geocodeInstitute,
batchGeocodeInstitutes,
getInstitutesNeedingGeocoding,
displayInstituteOnMap,
InstituteGeocoder,
CoordinateUtils,
neo4jQueries
}
@@ -0,0 +1,325 @@
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
interface GeocodingRequest {
institute_id: string
address?: string
street?: string
town?: string
county?: string
postcode?: string
country?: string
}
interface SearXNGResponse {
query: string
number_of_results: number
results: Array<{
title: string
longitude: string
latitude: string
boundingbox: string[]
geojson?: any
osm?: any
}>
}
interface GeocodingResult {
success: boolean
message: string
coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
geojson?: any
osm?: any
}
error?: string
}
serve(async (req: Request) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
// Get environment variables
const supabaseUrl = Deno.env.get('SUPABASE_URL')
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
const searxngUrl = Deno.env.get('SEARXNG_URL') || 'https://search.kevlarai.com'
if (!supabaseUrl || !supabaseServiceKey) {
throw new Error('Missing required environment variables')
}
// Create Supabase client
const supabase = createClient(supabaseUrl, supabaseServiceKey)
// Parse request body
const body: GeocodingRequest = await req.json()
if (!body.institute_id) {
return new Response(
JSON.stringify({ error: 'institute_id is required' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Get institute data from database
const { data: institute, error: fetchError } = await supabase
.from('institutes')
.select('*')
.eq('id', body.institute_id)
.single()
if (fetchError || !institute) {
return new Response(
JSON.stringify({ error: 'Institute not found' }),
{
status: 404,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Build search query from address components
let searchQuery = ''
if (body.address) {
searchQuery = body.address
} else {
const addressParts = [
body.street,
body.town,
body.county,
body.postcode,
body.country
].filter(Boolean)
searchQuery = addressParts.join(', ')
}
// If no search query provided, try to build from institute data
if (!searchQuery && institute.address) {
const address = institute.address as any
const addressParts = [
address.street,
address.town,
address.county,
address.postcode,
address.country
].filter(Boolean)
searchQuery = addressParts.join(', ')
}
if (!searchQuery) {
return new Response(
JSON.stringify({ error: 'No address information available for geocoding' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Query SearXNG for geocoding
const geocodingResult = await geocodeAddressWithFallback(institute.address, searxngUrl)
if (!geocodingResult.success) {
return new Response(
JSON.stringify({
error: 'Geocoding failed',
details: geocodingResult.error
}),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Update institute with geospatial coordinates
const { error: updateError } = await supabase
.from('institutes')
.update({
geo_coordinates: {
latitude: geocodingResult.coordinates!.latitude,
longitude: geocodingResult.coordinates!.longitude,
boundingbox: geocodingResult.coordinates!.boundingbox,
geojson: geocodingResult.coordinates!.geojson,
osm: geocodingResult.coordinates!.osm,
search_query: searchQuery,
geocoded_at: new Date().toISOString()
}
})
.eq('id', body.institute_id)
if (updateError) {
throw new Error(`Failed to update institute: ${updateError.message}`)
}
// Log the geocoding operation
await supabase
.from('function_logs')
.insert({
file_id: null,
step: 'geocoding',
message: 'Successfully geocoded institute address',
data: {
institute_id: body.institute_id,
search_query: searchQuery,
coordinates: geocodingResult.coordinates
}
})
return new Response(
JSON.stringify({
success: true,
message: 'Institute geocoded successfully',
institute_id: body.institute_id,
coordinates: geocodingResult.coordinates
}),
{
status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
} catch (error) {
console.error('Error in institute geocoder:', error)
return new Response(
JSON.stringify({
error: 'Internal server error',
details: error.message
}),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
})
async function geocodeAddress(searchQuery: string, searxngUrl: string): Promise<GeocodingResult> {
try {
console.log(`Geocoding address: ${searchQuery}`)
// Build the SearXNG query
const query = `!osm ${searchQuery}`
const url = `${searxngUrl}/search?q=${encodeURIComponent(query)}&format=json`
console.log(`SearXNG URL: ${url}`)
const response = await fetch(url)
if (!response.ok) {
throw new Error(`SearXNG request failed: ${response.status} ${response.statusText}`)
}
const data: SearXNGResponse = await response.json()
console.log(`SearXNG response: ${JSON.stringify(data, null, 2)}`)
// Check if we have results
if (!data.results || data.results.length === 0) {
return {
success: false,
message: 'No results returned from SearXNG',
error: 'No results returned from SearXNG'
}
}
// Get the best result (first one)
const bestResult = data.results[0]
if (!bestResult.latitude || !bestResult.longitude) {
return {
success: false,
message: 'Result missing coordinates',
error: 'Result missing coordinates'
}
}
return {
success: true,
message: 'Geocoding successful',
coordinates: {
latitude: parseFloat(bestResult.latitude),
longitude: parseFloat(bestResult.longitude),
boundingbox: bestResult.boundingbox || [],
geojson: bestResult.geojson || null,
osm: bestResult.osm || null
}
}
} catch (error) {
console.error('Error in geocodeAddress:', error)
return {
success: false,
message: 'Geocoding failed',
error: error.message
}
}
}
async function geocodeAddressWithFallback(address: any, searxngUrl: string): Promise<GeocodingResult> {
// Strategy 1: Try full address (street + town + county + postcode)
if (address.street && address.town && address.county && address.postcode) {
const fullQuery = `${address.street}, ${address.town}, ${address.county}, ${address.postcode}`
console.log(`Trying full address: ${fullQuery}`)
const result = await geocodeAddress(fullQuery, searxngUrl)
if (result.success) {
console.log('Full address geocoding successful')
return result
}
}
// Strategy 2: Try town + county + postcode
if (address.town && address.county && address.postcode) {
const mediumQuery = `${address.town}, ${address.county}, ${address.postcode}`
console.log(`Trying medium address: ${mediumQuery}`)
const result = await geocodeAddress(mediumQuery, searxngUrl)
if (result.success) {
console.log('Medium address geocoding successful')
return result
}
}
// Strategy 3: Try just postcode
if (address.postcode) {
console.log(`Trying postcode only: ${address.postcode}`)
const result = await geocodeAddress(address.postcode, searxngUrl)
if (result.success) {
console.log('Postcode geocoding successful')
return result
}
}
// Strategy 4: Try town + postcode
if (address.town && address.postcode) {
const simpleQuery = `${address.town}, ${address.postcode}`
console.log(`Trying simple address: ${simpleQuery}`)
const result = await geocodeAddress(simpleQuery, searxngUrl)
if (result.success) {
console.log('Simple address geocoding successful')
return result
}
}
// All strategies failed
return {
success: false,
message: 'All geocoding strategies failed',
error: 'No coordinates found with any address combination'
}
}
@@ -0,0 +1,142 @@
// Test script for institute geocoder functions
// This can be run in the browser console or as a standalone test
interface TestCase {
name: string
address: string
expected_coords?: {
latitude: number
longitude: number
}
}
const testCases: TestCase[] = [
{
name: "10 Downing Street, London",
address: "10 Downing Street, London",
expected_coords: {
latitude: 51.5034878,
longitude: -0.1276965
}
},
{
name: "Buckingham Palace, London",
address: "Buckingham Palace, London",
expected_coords: {
latitude: 51.501364,
longitude: -0.124432
}
},
{
name: "Big Ben, London",
address: "Big Ben, London",
expected_coords: {
latitude: 51.499479,
longitude: -0.124809
}
}
]
async function testGeocoding() {
console.log("🧪 Starting Institute Geocoder Tests...")
for (const testCase of testCases) {
console.log(`\n📍 Testing: ${testCase.name}`)
try {
// Test the SearXNG service directly
const searchQuery = `!osm ${testCase.address}`
const searchUrl = `https://search.kevlarai.com/search?q=${encodeURIComponent(searchQuery)}&format=json`
console.log(`🔍 Searching: ${searchUrl}`)
const response = await fetch(searchUrl)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const data = await response.json()
console.log(`📊 Results: ${data.number_of_results} found`)
if (data.results && data.results.length > 0) {
const result = data.results[0]
const coords = {
latitude: parseFloat(result.latitude),
longitude: parseFloat(result.longitude)
}
console.log(`✅ Coordinates: ${coords.latitude}, ${coords.longitude}`)
if (testCase.expected_coords) {
const latDiff = Math.abs(coords.latitude - testCase.expected_coords.latitude)
const lonDiff = Math.abs(coords.longitude - testCase.expected_coords.longitude)
if (latDiff < 0.01 && lonDiff < 0.01) {
console.log(`🎯 Accuracy: High (within 0.01 degrees)`)
} else if (latDiff < 0.1 && lonDiff < 0.1) {
console.log(`🎯 Accuracy: Medium (within 0.1 degrees)`)
} else {
console.log(`⚠️ Accuracy: Low (difference > 0.1 degrees)`)
}
}
if (result.boundingbox) {
console.log(`🗺️ Bounding Box: ${result.boundingbox.join(', ')}`)
}
if (result.geojson) {
console.log(`🗺️ GeoJSON: ${result.geojson.type} with ${result.geojson.coordinates?.[0]?.length || 0} points`)
}
} else {
console.log(`❌ No results found`)
}
} catch (error) {
console.error(`❌ Test failed: ${error.message}`)
}
}
console.log("\n🏁 Testing completed!")
}
// Test address parsing function
function testAddressParsing() {
console.log("\n🔧 Testing Address Parsing...")
const testAddresses = [
{
street: "10 Downing Street",
town: "London",
county: "Greater London",
postcode: "SW1A 2AA",
country: "United Kingdom"
},
{
street: "Buckingham Palace",
town: "London",
county: "Greater London",
postcode: "SW1A 1AA",
country: "United Kingdom"
}
]
for (const addr of testAddresses) {
const parts = [addr.street, addr.town, addr.county, addr.postcode, addr.country].filter(Boolean)
const searchQuery = parts.join(', ')
console.log(`📍 Address: ${searchQuery}`)
}
}
// Run tests if this script is executed directly
if (typeof window !== 'undefined') {
// Browser environment
window.testGeocoding = testGeocoding
window.testAddressParsing = testAddressParsing
console.log("🧪 Institute Geocoder tests loaded. Run testGeocoding() or testAddressParsing() to test.")
} else {
// Node.js environment
console.log("🧪 Institute Geocoder tests loaded.")
}
export { testGeocoding, testAddressParsing }