Add POI feature and imporve the overall backend implementation

This commit is contained in:
Urtzi Alfaro
2025-11-12 15:34:10 +01:00
parent e8096cd979
commit 5783c7ed05
173 changed files with 16862 additions and 9078 deletions

247
frontend/src/types/poi.ts Normal file
View File

@@ -0,0 +1,247 @@
/**
* POI (Point of Interest) Type Definitions
*
* Types for POI detection, context, and visualization
*/
export interface POILocation {
latitude: number;
longitude: number;
}
export interface POI {
osm_id: string;
type: 'node' | 'way';
lat: number;
lon: number;
tags: Record<string, string>;
name: string;
distance_m?: number;
zone?: string;
}
export interface POIFeatures {
proximity_score: number;
weighted_proximity_score: number;
count_0_100m: number;
count_100_300m: number;
count_300_500m: number;
count_500_1000m: number;
total_count: number;
distance_to_nearest_m: number;
has_within_100m: boolean;
has_within_300m: boolean;
has_within_500m: boolean;
}
export interface POICategoryData {
pois: POI[];
features: POIFeatures;
count: number;
error?: string;
}
export interface POICategories {
schools: POICategoryData;
offices: POICategoryData;
gyms_sports: POICategoryData;
residential: POICategoryData;
tourism: POICategoryData;
competitors: POICategoryData;
transport_hubs: POICategoryData;
coworking: POICategoryData;
retail: POICategoryData;
}
export interface POISummary {
total_pois_detected: number;
categories_with_pois: string[];
high_impact_categories: string[];
categories_count: number;
}
export interface CompetitorAnalysis {
competitive_pressure_score: number;
direct_competitors_count: number;
nearby_competitors_count: number;
market_competitors_count: number;
total_competitors_count: number;
competitive_zone: 'low_competition' | 'moderate_competition' | 'high_competition';
market_type: 'underserved' | 'normal_market' | 'competitive_market' | 'bakery_district';
competitive_advantage: 'first_mover' | 'local_leader' | 'quality_focused' | 'differentiation_required';
competitor_details: POI[];
nearest_competitor: POI | null;
}
export interface POIContext {
id: string;
tenant_id: string;
location: POILocation;
poi_detection_results: POICategories;
ml_features: Record<string, number>;
total_pois_detected: number;
high_impact_categories: string[];
relevant_categories: string[];
detection_timestamp: string;
detection_source: string;
detection_status: 'completed' | 'partial' | 'failed';
detection_error?: string;
next_refresh_date?: string;
last_refreshed_at?: string;
created_at: string;
updated_at: string;
}
export interface RelevanceReportItem {
category: string;
relevant: boolean;
reason: string;
proximity_score: number;
count: number;
distance_to_nearest_m: number;
}
export interface POIDetectionResponse {
status: 'success' | 'error';
source: 'detection' | 'cache';
poi_context: POIContext;
feature_selection?: {
features: Record<string, number>;
relevant_categories: string[];
relevance_report: RelevanceReportItem[];
total_features: number;
total_relevant_categories: number;
};
competitor_analysis?: CompetitorAnalysis;
competitive_insights?: string[];
}
export interface POIContextResponse {
poi_context: POIContext;
is_stale: boolean;
needs_refresh: boolean;
}
export interface FeatureImportanceItem {
category: string;
is_relevant: boolean;
proximity_score: number;
weighted_score: number;
total_count: number;
distance_to_nearest_m: number;
has_within_100m: boolean;
rejection_reason?: string;
}
export interface FeatureImportanceResponse {
tenant_id: string;
feature_importance: FeatureImportanceItem[];
total_categories: number;
relevant_categories: number;
}
export interface POICacheStats {
total_cached_locations: number;
cache_ttl_days: number;
coordinate_precision: number;
}
// Category metadata for UI display
export interface POICategoryMetadata {
name: string;
displayName: string;
icon: string;
color: string;
description: string;
}
export const POI_CATEGORY_METADATA: Record<string, POICategoryMetadata> = {
schools: {
name: 'schools',
displayName: 'Schools',
icon: '🏫',
color: '#22c55e',
description: 'Educational institutions causing morning/afternoon rush patterns'
},
offices: {
name: 'offices',
displayName: 'Offices',
icon: '🏢',
color: '#3b82f6',
description: 'Office buildings and business centers'
},
gyms_sports: {
name: 'gyms_sports',
displayName: 'Gyms & Sports',
icon: '🏋️',
color: '#8b5cf6',
description: 'Fitness centers and sports facilities'
},
residential: {
name: 'residential',
displayName: 'Residential',
icon: '🏘️',
color: '#64748b',
description: 'Residential buildings and housing'
},
tourism: {
name: 'tourism',
displayName: 'Tourism',
icon: '🗼',
color: '#f59e0b',
description: 'Tourist attractions, hotels, and points of interest'
},
competitors: {
name: 'competitors',
displayName: 'Competitors',
icon: '🥖',
color: '#ef4444',
description: 'Competing bakeries and pastry shops'
},
transport_hubs: {
name: 'transport_hubs',
displayName: 'Transport Hubs',
icon: '🚇',
color: '#06b6d4',
description: 'Public transport stations and hubs'
},
coworking: {
name: 'coworking',
displayName: 'Coworking',
icon: '💼',
color: '#84cc16',
description: 'Coworking spaces and shared offices'
},
retail: {
name: 'retail',
displayName: 'Retail',
icon: '🛍️',
color: '#ec4899',
description: 'Retail shops and commercial areas'
}
};
export const IMPACT_LEVELS = {
HIGH: { label: 'High Impact', color: '#22c55e', threshold: 2.0 },
MODERATE: { label: 'Moderate Impact', color: '#f59e0b', threshold: 1.0 },
LOW: { label: 'Low Impact', color: '#64748b', threshold: 0 }
} as const;
export type ImpactLevel = keyof typeof IMPACT_LEVELS;
export function getImpactLevel(proximityScore: number): ImpactLevel {
if (proximityScore >= IMPACT_LEVELS.HIGH.threshold) return 'HIGH';
if (proximityScore >= IMPACT_LEVELS.MODERATE.threshold) return 'MODERATE';
return 'LOW';
}
export function formatDistance(meters: number): string {
if (meters < 1000) {
return `${Math.round(meters)}m`;
}
return `${(meters / 1000).toFixed(1)}km`;
}
export function formatCategoryName(category: string): string {
return POI_CATEGORY_METADATA[category]?.displayName || category.replace(/_/g, ' ');
}