# Beacon System Documentation ## πŸ“ Local Safety & Social Awareness Platform **Version**: 3.0 **Status**: βœ… **COMPLETED** **Last Updated**: February 17, 2026 --- ## 🎯 Overview The Beacon system transforms local safety and community awareness into an engaging, positive platform that connects neighbors and promotes mutual aid rather than fear-mongering. It combines real-time mapping, categorized alerts, and actionable help items to create a safer, more connected community. ## πŸ—οΈ Architecture ### System Components ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Beacon API β”‚ β”‚ Map Service β”‚ β”‚ Notification β”‚ β”‚ │◄──►│ │◄──►│ β”‚ β”‚ β€’ CRUD Operationsβ”‚ β”‚ β€’ Geospatial β”‚ β”‚ β€’ Push Alerts β”‚ β”‚ β€’ Validation β”‚ β”‚ β€’ Clustering β”‚ β”‚ β€’ Email Alerts β”‚ β”‚ β€’ Scoring β”‚ β”‚ β€’ Filtering β”‚ β”‚ β€’ SMS Alerts β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Database β”‚ β”‚ External APIs β”‚ β”‚ User Interface β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β€’ Beacon Data β”‚ β”‚ β€’ Geocoding β”‚ β”‚ β€’ Map View β”‚ β”‚ β€’ Categories β”‚ β”‚ β€’ Reverse Geocodingβ”‚ β”‚ β€’ Beacon Feed β”‚ β”‚ β€’ Relationships β”‚ β”‚ β€’ Weather API β”‚ β”‚ β€’ Detail View β”‚ β”‚ β€’ Analytics β”‚ β”‚ β€’ Address API β”‚ β”‚ β€’ Create/Edit β”‚ └───────────────── └───────────────── β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## 🎨 Core Features ### πŸ“ Map-Based Discovery - **Interactive Map**: Flutter Map with clustered pin visualization - **Real-time Updates**: Live beacon updates without page refresh - **Geospatial Search**: Find beacons by location or radius - **Neighborhood Filtering**: Filter by specific neighborhoods or areas - **Layer Control**: Toggle different beacon categories on map ### 🏷️ Beacon Categories - **Safety Alert**: Emergency situations, public safety concerns - **Community Need**: Requests for help, volunteer opportunities - **Lost & Found**: Missing persons, pets, or items - **Events**: Community events, meetings, gatherings - **Mutual Aid**: Resource sharing, community support initiatives ### βœ… Verification System - **Official Badges**: Verified badges for government and official organizations - **Trust Indicators**: Visual indicators for source reliability - **Confidence Scoring**: Community-driven trust metrics - **Source Validation**: API integration for official verification ### 🀝 Action-Oriented Help - **How to Help**: Specific, actionable help items for each beacon - **Volunteer Opportunities**: Sign-up forms and contact information - **Resource Sharing**: Links to needed resources or donations - **Community Coordination**: Tools for organizing community response ### πŸ“Š Analytics & Insights - **Engagement Metrics**: Track vouch/report ratios and community response - **Resolution Tracking**: Monitor beacon lifecycle from active to resolved - **Impact Assessment**: Measure community impact of beacon activities - **Trend Analysis**: Identify patterns in local safety and community needs --- ## πŸ“± Implementation Details ### Backend Services #### Beacon Handler **File**: `go-backend/internal/handlers/beacon_handler.go` ```go type BeaconHandler struct { db *pgxpool.Pool geoService *GeoService notifier *NotificationService validator *BeaconValidator } // Create new beacon func (h *BeaconHandler) CreateBeacon(c *gin.Context) { var req CreateBeaconRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Validate beacon data if err := h.validator.ValidateBeacon(req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Create beacon with geocoding beacon, err := h.geoService.GeocodeLocation(req.Latitude, req.Longitude) if err != nil { c.JSON(500, gin.H{"error": "Failed to geocode location"}) return } // Save to database id, err := h.db.Exec( `INSERT INTO beacons (title, description, category, latitude, longitude, author_id, is_official, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, req.Title, req.Description, req.Category, beacon.Latitude, beacon.Longitude, req.AuthorID, req.IsOfficial, time.Now(), ) c.JSON(201, gin.H{"id": id, "status": "created"}) } // Get beacons with clustering func (h *BeaconHandler) GetBeacons(c *gin.Context) { var filters BeaconFilters if err := c.ShouldBindQuery(&filters); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Get beacons with clustering beacons, err := h.geoService.GetClusteredBeacons(filters) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, beacons) } ``` #### Geospatial Service **File**: `go-backend/internal/services/geo_service.go` ```go type GeoService struct { db *pgxpool.Pool } // Cluster nearby beacons func (s *GeoService) GetClusteredBeacons(filters BeaconFilters) ([]BeaconCluster, error) { // Get all beacons within radius query := ` SELECT id, title, category, latitude, longitude, author_id, is_official, created_at FROM beacons WHERE ST_DWithin( ST_MakePoint(longitude, latitude, 4326), ST_MakePoint($1, $2, $3), $4 ) ORDER BY created_at DESC ` rows, err := s.db.Query(context.Background(), query, filters.CenterLat, filters.CenterLng, filters.RadiusKm * 1000) if err != nil { return nil, err } defer rows.Close() var beacons []EnhancedBeacon for rows.Next() { var beacon EnhancedBeacon if err := rows.Scan(&beacon.ID, &beacon.Title, &beacon.Category, &beacon.Latitude, &beacon.Longitude, &beacon.AuthorID, &beacon.IsOfficial, &beacon.CreatedAt); err != nil { return nil, err } beacons = append(beacons, beacon) } // Cluster beacons return s.clusterBeacons(beacons), nil } // Geocode address to coordinates func (s *GeoService) GeocodeLocation(lat, lng float64) (*Location, error) { // Use reverse geocoding service // This would integrate with Google Geocoding API or similar return &Location{ Latitude: lat, Longitude: lng, Address: "Reverse geocoded address", City: "City name", Country: "Country name", }, nil } ``` ### Frontend Components #### Enhanced Beacon Map Widget **File**: `sojorn_app/lib/widgets/beacon/enhanced_beacon_map.dart` ```dart class EnhancedBeaconMap extends ConsumerWidget { final List beacons; final Function(EnhancedBeacon)? onBeaconTap; final Function(LatLng)? onMapTap; final BeaconFilter? filter; final bool showUserLocation; final bool enableClustering; @override Widget build(BuildContext context, WidgetRef ref) { return FlutterMap( mapController: _mapController, options: MapOptions( initialCenter: widget.initialCenter ?? _userLocation, initialZoom: _currentZoom, onMapEvent: (event) => _handleMapEvent(event), onTap: (tapPosition, point) => widget.onMapTap?.call(point), ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.example.sojorn', ), MarkerLayer( markers: _buildMapMarkers(), ), if (widget.showUserLocation) MarkerLayer( markers: _buildUserLocationMarker(), ), ], ); } } ``` #### Beacon Detail Screen **File**: `sojorn_app/lib/screens/beacon/enhanced_beacon_detail_screen.dart` ```dart class EnhancedBeaconDetailScreen extends StatelessWidget { final EnhancedBeacon beacon; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.black, title: Text(beacon.title), iconTheme: const IconThemeData(color: Colors.white), ), body: CustomScrollView( slivers: [ // Map view with beacon location SliverAppBar( expandedHeight: 250, pinned: true, flexibleSpace: FlexibleSpaceBar( background: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withOpacity(0.7), Colors.black, ], ), ), ), ), child: FlutterMap( mapController: _mapController, options: MapOptions( initialCenter: LatLng(beacon.lat, beacon.lng), initialZoom: 15.0, ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', ), MarkerLayer( markers: [ Marker( point: LatLng(beacon.lat, beacon.lng), child: _buildBeaconMarker(beacon), ), ], ), ], ), ), // Beacon content SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Title and metadata _buildBeaconHeader(beacon), // Description _buildBeaconDescription(beacon), // Image if available if (beacon.imageUrl != null) _buildBeaconImage(beacon.imageUrl), // Help actions _buildHelpActions(beacon), // Engagement stats _buildEngagementStats(beacon), ], ), ), ), ), ], ), ); } } ``` --- ## πŸ—‚οΈ Data Models ### Enhanced Beacon Model ```dart class EnhancedBeacon { final String id; final String title; final String description; final BeaconCategory category; final BeaconStatus status; final double lat; final double lng; final String authorId; final String authorHandle; final String? authorAvatar; final bool isVerified; final bool isOfficialSource; final String? organizationName; final DateTime createdAt; final DateTime? expiresAt; final int vouchCount; final int reportCount; final double confidenceScore; final String? imageUrl; final List actionItems; final String? neighborhood; final double? radiusMeters; } ``` ### Beacon Cluster Model ```dart class BeaconCluster { final List beacons; final double lat; final double lng; final int count; BeaconCluster({ required this.beacons, required this.lat, required this.lng, }) : count = beacons.length; BeaconCategory get dominantCategory { // Find most common category in cluster final categoryCount = {}; for (final beacon in beacons) { categoryCount[beacon.category] = (categoryCount[beacon.category] ?? 0) + 1; } return categoryCount.entries.reduce((a, b) => a.value > b.value ? a : b ).key; } bool get hasOfficialSource { return beacons.any((b) => b.isOfficialSource); } EnhancedBeacon get priorityBeacon { // Return highest priority beacon final officialBeacons = beacons.where((b) => b.isOfficialSource).toList(); if (officialBeacons.isNotEmpty) { return officialBeacons.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b ); } final highConfidenceBeacons = beacons.where((b) => b.isHighConfidence).toList(); if (highConfidenceBeacons.isNotEmpty) { return highConfidenceBeacons.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b ); } return beacons.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b ); } } ``` ### Beacon Filter Model ```dart class BeaconFilter { final Set categories; final Set statuses; final bool onlyOfficial; final double? radiusKm; final String? neighborhood; bool matches(EnhancedBeacon beacon) { // Category filter if (categories.isNotEmpty && !categories.contains(beacon.category)) { return false; } // Status filter if (statuses.isNotEmpty && !statuses.contains(beacon.status)) { return false; } // Official filter if (onlyOfficial && !beacon.isOfficialSource) { return false; } // Neighborhood filter if (neighborhood != null && beacon.neighborhood != neighborhood) { return false; } return true; } } ``` --- ## πŸ”§ Technical Implementation ### Geospatial Database Schema ```sql -- Beacon table with geospatial capabilities CREATE TABLE IF NOT EXISTS beacons ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(100) NOT NULL, description TEXT, category VARCHAR(50) NOT NULL CHECK (category IN ('safety_alert', 'community_need', 'lost_found', 'event', 'mutual_aid')), status VARCHAR(20) NOT NULL DEFAULT 'active', latitude DECIMAL(10, 8) NOT NULL, longitude DECIMAL(10,8) NOT NULL, author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, is_official BOOLEAN DEFAULT FALSE, is_verified BOOLEAN DEFAULT FALSE, organization_name VARCHAR(100), image_url TEXT, neighborhood VARCHAR(50), radius_meters DECIMAL(8,2), vouch_count INTEGER DEFAULT 0, report_count INTEGER DEFAULT 0, confidence_score DECIMAL(5,2) DEFAULT 0.0, action_items TEXT[], created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), expires_at TIMESTAMP, -- Geospatial index for location queries INDEX idx_beacon_location ON beacons USING GIST ( geography(Point(longitude, latitude), Circle(radius_meters) ); -- Category index for filtering INDEX idx_beacon_category ON beacons(category); -- Status index for filtering INDEX idx_beacon_status ON beacons(status); -- Created at index for sorting INDEX idx_beacon_created_at ON beacons(created_at DESC); -- Author index for user-specific queries INDEX idx_beacon_author_id ON beacons(author_id); ); -- Beacon relationships CREATE TABLE IF NOT EXISTS beacon_relationships ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), beacon_id UUID NOT NULL REFERENCES beacons(id) ON DELETE CASCADE, related_beacon_id UUID NOT NULL REFERENCES beacons(id) ON DELETE CASCADE, relationship_type VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT NOW(), UNIQUE (beacon_id, related_beacon_id, relationship_type) ); ``` ### Clustering Algorithm ```go // Cluster nearby beacons based on distance and zoom level func (s *GeoService) clusterBeacons(beacons []EnhancedBeacon) []BeaconCluster { if (!s.enableClustering || _currentZoom >= 15.0) { // Show individual beacons at high zoom return beacons.map((beacon) => BeaconCluster{ beacons: []EnhancedBeacon{beacon}, lat: beacon.lat, lng: beacon.lng, }); } // Calculate cluster radius based on zoom level clusterRadius := 0.01 * (16.0 - _currentZoom) var clusters []BeaconCluster processedBeacons := make(map[string]bool) for _, beacon := range beacons { if processedBeacons[beacon.ID] { continue } var nearbyBeacons []EnhancedBeacon for _, otherBeacon := range beacons { if processedBeacons[otherBeacon.ID] { continue } distance := calculateDistance(beacon, otherBeacon) if distance <= clusterRadius { nearbyBeacons = append(nearbyBeacons, otherBeacon) processedBeacons[otherBeacon.ID] = true } } if len(nearbyBeacons) > 0 { // Calculate cluster center (average position) avgLat := nearbyBeacons.reduce((sum, beacon) => sum.lat + beacon.lat, 0 ) / float64(len(nearbyBeacons)) avgLng := nearbyBeacons.reduce((sum, beacon) => sum.lng + beacon.lng, 0 ) / float64(nearbyBeacons)) clusters = append(clusters, BeaconCluster{ beacons: nearbyBeacons, lat: avgLat, lng: avgLng, }) // Mark all beacons in this cluster as processed for _, beacon := range nearbyBeacons { processedBeacons[beacon.ID] = true } } } return clusters } func calculateDistance(beacon1, beacon2 EnhancedBeacon) float64 { // Haversine distance calculation lat1 := beacon1.lat * math.Pi / 180 lng1 := beacon1.lng * math.Pi / 180 lat2 := beacon2.lat * math.Pi / 180 lng2 := beacon2.lng * math.Pi / 180 dlat := lat2 - lat1 dlng := lng2 - lng1 a := math.Sin(dlat/2) * math.Sin(dlng/2) c := math.Cos(lat1) * math.Cos(lat2) return 6371 * 2 * math.Asin(math.Sqrt(a*a + c*c*math.Cos(dlng/2)*math.Cos(dlng/2))) } ``` --- ## πŸ“± User Interface ### Map View - **Interactive Controls**: Zoom, pan, and tap interactions - **Cluster Visualization**: Visual clustering of nearby beacons - **Filter Controls**: Category, status, and official source filters - **User Location**: Current location indicator on map - **Legend**: Clear visual indicators for different beacon types ### Beacon Feed - **Card Layout**: Clean, scannable card design - **Category Badges**: Visual category indicators - **Trust Indicators**: Verification and confidence scores - **Action Buttons**: Quick access to help actions - **Preview Images**: Thumbnail previews when available ### Detail View - **Full Context**: Complete beacon information and description - **Map Integration**: Embedded map showing beacon location - **Help Actions**: Detailed help items with contact information - **Engagement**: Vouch, report, and share functionality - **Author Info**: Beacon creator information and verification status ### Creation Flow - **Location Selection**: Map-based location selection or current location - **Category Selection**: Choose appropriate beacon category - **Information Entry**: Title, description, and details - **Image Upload**: Optional image for visual context - **Review & Post**: Preview and publish beacon --- ## πŸ”’ Security & Privacy ### Location Privacy - **Approximate Location**: Beacon locations are approximate to protect exact addresses - **User Control**: Users control location sharing preferences - **Data Minimization**: Only necessary location data is stored - **Retention Policy**: Location data retained according to user preferences ### Content Moderation - **AI Analysis**: AI-powered content analysis for safety - **Community Flagging**: User-driven content reporting system - **Automated Filtering**: Automatic detection of inappropriate content - **Appeal Process**: Fair and transparent appeal system ### User Safety - **Anonymous Reporting**: Option to report anonymously - **Block System**: Block problematic users from seeing beacons - **Reporting History**: Track and manage reporting history - **Emergency Contacts**: Quick access to emergency services --- ## πŸ“Š Analytics & Metrics ### Engagement Metrics - **Vouch/Report Ratio**: Community trust indicators - **Response Time**: Average time to first response - **Resolution Rate**: Percentage of beacons marked as resolved - **Participation Rate**: Community engagement levels ### Geographic Insights - **Hotspot Analysis**: Areas with high beacon activity - **Coverage Maps**: Geographic coverage visualization - **Trend Analysis**: Patterns in local needs and issues - **Resource Distribution**: Analysis of help requests and offers ### Performance Metrics - **Map Performance**: Map rendering and interaction performance - **API Response**: Beacon API response times - **Database Queries**: Database query optimization metrics - **User Experience**: Page load and interaction performance --- ## πŸš€ Deployment ### Environment Configuration ```bash # Geospatial database setup CREATE EXTENSION IF NOT EXISTS postgis; CREATE EXTENSION IF NOT EXISTS postgis_topology; # Beacon service configuration BEACON_SERVICE_URL=http://localhost:8080 BEACON_SERVICE_TIMEOUT=30s BEACON_MAX_RADIUS_KM=50 # Notification settings BEACON_NOTIFICATION_ENABLED=true BEACON_PUSH_NOTIFICATIONS=true BEACON_EMAIL_NOTIFICATIONS=true BEACON_SMS_NOTIFICATIONS=false ``` ### Health Checks ```go // Beacon service health check func (s *BeaconService) HealthCheck() HealthStatus { // Check database connectivity if err := s.db.Ping(context.Background()); err != nil { return HealthStatus{ Status: "unhealthy", Message: "Database connection failed", } } // Check geospatial extensions var result string err := s.db.QueryRow( context.Background(), "SELECT 1 FROM postgis_version", &result, ) if err != nil { return HealthStatus{ Status: "degraded", Message: "PostGIS extensions not available", } } return HealthStatus{ Status: "healthy", Message: "Beacon service ready", } } ``` --- ## πŸ“š Troubleshooting ### Common Issues #### Map Not Loading ```dart // Check map initialization if (_mapController == null) { _initMap(); } // Check network connectivity final connectivity = await Connectivity().checkConnectivity(); if (!connectivity) { _showError('No internet connection'); return; } ``` #### Geocoding Errors ```go // Check PostGIS extensions _, err := db.QueryRow( context.Background(), "SELECT postgis_version", &result, ) if err != nil { log.Error("PostGIS not available: %v", err) return nil, err } ``` #### Clustering Issues ```dart // Check clustering parameters if (_currentZoom < 10.0 && _enableClustering) { // Clustering disabled at low zoom levels return _buildIndividualMarkers(); } // Check cluster radius if (clusterRadius < 0.001) { // Cluster radius too small return _buildIndividualMarkers(); } ``` --- ## πŸ“ Future Enhancements ### Version 3.1 (Planned) - **Real-time Updates**: WebSocket-based live beacon updates - **Advanced Analytics**: More detailed engagement metrics - **Mobile Optimization**: Improved mobile performance - **Offline Support**: Offline map caching and sync ### Version 4.0 (Long-term) - **3D Map View**: 3D visualization of beacon locations - **AR Integration**: Augmented reality beacon discovery - **Voice Commands**: Voice-controlled beacon creation - **Machine Learning**: Predictive beacon suggestions --- ## πŸ“ž Support & Documentation ### User Guides - **Getting Started**: Quick start guide for beacon creation - **Safety Guidelines**: Best practices for beacon usage - **Community Building**: Guide to effective community engagement - **Troubleshooting**: Common issues and solutions ### Developer Resources - **API Documentation**: Complete API reference - **Database Schema**: Database design and relationships - **Integration Guide**: Third-party service integration - **Code Examples**: Sample code and implementations ### Community Support - **Discord**: Beacon development discussion channel - **GitHub**: Issue tracking and feature requests - **Documentation**: Regular updates and improvements - **Training**: Educational resources and tutorials --- **πŸ“ The Beacon system transforms local safety into an engaging, positive community platform that connects neighbors and promotes mutual aid rather than fear-mongering. With intelligent clustering, verified sources, and actionable help items, it creates a safer, more connected community.**