- Add VideoProcessor service to PostHandler for frame-based video moderation - Implement multi-frame extraction and Azure OpenAI Vision analysis for video content - Enhance VideoStitchingService with filters, speed control, and text overlays - Add image upload dialogs for group avatar and banner in GroupCreationModal - Implement navigation placeholders for mentions, hashtags, and URLs in sojornRichText
818 lines
25 KiB
Markdown
818 lines
25 KiB
Markdown
# 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<EnhancedBeacon> 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<String> actionItems;
|
|
final String? neighborhood;
|
|
final double? radiusMeters;
|
|
}
|
|
```
|
|
|
|
### Beacon Cluster Model
|
|
```dart
|
|
class BeaconCluster {
|
|
final List<EnhancedBeacon> 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 = <BeaconCategory, int>{};
|
|
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<BeaconCategory> categories;
|
|
final Set<BeaconStatus> 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.**
|