04bd80b25b
Major milestone: GPS coordinates now auto-extract from filenames and uploads appear on map with full end-to-end workflow functional! ✨ GPS Auto-Extraction Features: - JavaScript GPS extractor class matching Python patterns - Supports 3 filename formats: * N/S/E/W: 34.0478N_118.2349W_filename.sub * lat/lon prefix: lat34.0478lon-118.2348_filename.sub * Signed decimal: -34.0478_118.2348_filename.sub - Auto-populates latitude/longitude form fields on file drop - Green notification toast shows detected coordinates - File list shows GPS badge for files with coordinates 🗺️ Web Interface Improvements: - Upload endpoint now stores captures in-memory - Query endpoint returns uploaded captures for map display - Stats endpoint shows real-time upload counts - Map displays uploaded captures as markers - Color-coded by frequency band 📁 Updated Files: - static/js/upload.js: GPS extraction + auto-population - src/api/main_simple.py: In-memory storage + endpoints - src/parser/gps_extractor.py: Backend GPS extraction (Python) - scripts/test_gps_extraction.py: Python test suite - test_gps_extraction.html: Browser test suite 📊 T-Embed Files Updated: - 34.0478N_118.2348W_1637_raw_8.sub: 315 MHz Princeton - 34.0478N_118.2349W_1351_raw_10.sub: 433.92 MHz Princeton - 34.0478N_118.2349W_1650_test_raw.sub: 433.92 MHz RAW - All now have proper Flipper SubGhz headers ✅ Tested Features: - GPS extraction from filename: 34.0478N_118.2349W → 34.0478, -118.2349 - Auto-population of GPS fields in upload form - File upload with GPS validation - Capture appears on map after upload - Statistics update in real-time - Frequency distribution calculated correctly 🎯 End-to-End Flow Working: 1. User drops .sub file with GPS in filename 2. GPS auto-detected and form fields populate 3. User clicks Upload 4. Server parses RF data + GPS coordinates 5. Capture stored in memory 6. Map refreshes and displays new marker 7. Stats update with new counts 🚀 Demo: http://localhost:8000 Upload 34.0478N_118.2349W_1351_raw_10.sub and watch it appear on map! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
737 lines
20 KiB
Markdown
737 lines
20 KiB
Markdown
# GigLez Implementation Plan - Post Wigle Analysis
|
|
|
|
**Date**: 2026-01-12
|
|
**Status**: Ready to Implement
|
|
**Based on**: Wigle.net 15+ years of proven wardriving infrastructure
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
We've completed a comprehensive analysis of Wigle.net's infrastructure, including:
|
|
- Technology stack (MySQL/MariaDB, ElasticSearch, custom mapping)
|
|
- Android client source code analysis (database schema, upload mechanisms, GPS handling)
|
|
- API documentation (authentication, CSV format, rate limiting)
|
|
- Architectural patterns (deduplication, performance optimizations, session management)
|
|
|
|
**Key Finding**: Wigle has successfully scaled to **349M WiFi networks** and **billions of observations** using proven patterns we can adapt for GigLez's IoT RF device mapping.
|
|
|
|
---
|
|
|
|
## Current State Assessment
|
|
|
|
### ✅ Completed
|
|
- Project architecture and documentation (CLAUDE.md)
|
|
- Database schema design (docs/database_schema.md)
|
|
- .sub file parser implementation (src/parser/sub_parser.py)
|
|
- Signature matching engine foundation (src/matcher/engine.py)
|
|
- Wigle infrastructure analysis (docs/wigle_analysis.md)
|
|
- Architecture decisions (docs/architecture_decisions.md)
|
|
|
|
### 🚧 In Progress
|
|
- None (ready to start implementation)
|
|
|
|
### ❌ Not Started
|
|
- PostgreSQL database setup
|
|
- SQLAlchemy ORM models
|
|
- FastAPI endpoints
|
|
- Web interface
|
|
- Signature database imports
|
|
|
|
---
|
|
|
|
## Wigle Analysis Key Takeaways
|
|
|
|
### 1. Database Architecture
|
|
|
|
**Wigle's Approach** (SQLite 3-table design):
|
|
```
|
|
network (BSSID primary key) → Core entity storage
|
|
location (observations) → Many-to-one with network
|
|
route (GPS tracks) → Independent session tracking
|
|
```
|
|
|
|
**GigLez Adaptation** (PostgreSQL + PostGIS):
|
|
```
|
|
captures (file_hash primary key) → .sub file + GPS
|
|
capture_matches (many-to-many) → Device identification results
|
|
devices (reference data) → Known device signatures
|
|
sessions (run_id equivalent) → Wardriving sessions
|
|
```
|
|
|
|
**Why Different**:
|
|
- Wigle: BSSID is a natural unique identifier (MAC address)
|
|
- GigLez: SHA256 file hash ensures deduplication of .sub files
|
|
- Wigle: Observation-level GPS (one per scan)
|
|
- GigLez: File-level GPS (one per .sub file)
|
|
|
|
### 2. Deduplication Strategy
|
|
|
|
**Wigle's Multi-Level Approach**:
|
|
1. **Primary key**: BSSID prevents duplicate networks
|
|
2. **Upload markers**: Track last uploaded ID per user
|
|
3. **Spatial/temporal**: Filter observations within 100m and 24h
|
|
4. **Network-level**: Update existing records instead of duplicating
|
|
|
|
**GigLez Implementation**:
|
|
```python
|
|
# Primary deduplication (database level)
|
|
file_hash = sha256(file_contents) # Primary key
|
|
|
|
# Spatial/temporal deduplication (optional)
|
|
existing = query_captures_within(
|
|
latitude=lat,
|
|
longitude=lon,
|
|
radius_meters=50,
|
|
time_window_hours=1
|
|
)
|
|
|
|
# Upload markers (incremental sync)
|
|
last_uploaded_id = get_user_marker(user_id, session_id)
|
|
new_captures = filter_captures_after(last_uploaded_id)
|
|
```
|
|
|
|
### 3. GPS Handling
|
|
|
|
**Wigle's Quality Checks**:
|
|
- Minimum accuracy threshold: 32 meters
|
|
- Null Island detection: (0.0, 0.0) rejected
|
|
- Multi-provider fallback: GPS → Network → Last known
|
|
- Location interpolation: Fill gaps between observations
|
|
- Kalman filtering: Smooth noisy GPS tracks
|
|
|
|
**GigLez GPS Validation** (from wigle_analysis.md):
|
|
```python
|
|
def validate_gps(lat: float, lon: float, accuracy: float) -> bool:
|
|
# Bounds check
|
|
if not (-90 <= lat <= 90 and -180 <= lon <= 180):
|
|
return False
|
|
|
|
# Null Island check
|
|
if abs(lat) < 0.001 and abs(lon) < 0.001:
|
|
return False
|
|
|
|
# Accuracy threshold
|
|
if accuracy > 50: # meters
|
|
return False
|
|
|
|
return True
|
|
```
|
|
|
|
### 4. Upload System
|
|
|
|
**Wigle CSV Format**:
|
|
```csv
|
|
WigleWifi-1.6,appRelease=2.70,model=Pixel,release=13,device=blueline,...
|
|
MAC,SSID,AuthMode,FirstSeen,Channel,Frequency,RSSI,CurrentLatitude,CurrentLongitude,...
|
|
00:11:22:33:44:55,MyNetwork,WPA2,2026-01-12 10:00:00,6,2437,-65,40.7128,-74.0060,...
|
|
```
|
|
|
|
**GigLez JSON + Binary Format**:
|
|
```json
|
|
{
|
|
"version": "GigLez-1.0",
|
|
"session_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"device_info": {
|
|
"model": "LilyGo T-Embed",
|
|
"firmware": "Bruce-2.1",
|
|
"app_version": "1.0.0"
|
|
},
|
|
"captures": [
|
|
{
|
|
"filename": "capture_001.sub",
|
|
"sha256": "abc123...",
|
|
"latitude": 40.7128,
|
|
"longitude": -74.0060,
|
|
"accuracy": 5.0,
|
|
"altitude": 10.5,
|
|
"timestamp": "2026-01-12T10:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 5. Performance Optimizations
|
|
|
|
**Wigle's Proven Patterns**:
|
|
- Transaction batching (512 operations per commit)
|
|
- Prepared statements (avoid SQL parsing overhead)
|
|
- LRU caching (256 entries for device lookups)
|
|
- Background threading (offload DB writes)
|
|
- Pragma optimizations (temp_store=MEMORY, journal_mode=PERSIST)
|
|
|
|
**GigLez Adaptations**:
|
|
```python
|
|
# Transaction batching (PostgreSQL)
|
|
with db.begin():
|
|
for i in range(0, len(captures), 512):
|
|
batch = captures[i:i+512]
|
|
db.bulk_insert_mappings(Capture, batch)
|
|
|
|
# LRU caching (device signatures)
|
|
from functools import lru_cache
|
|
|
|
@lru_cache(maxsize=256)
|
|
def get_device_signature(device_id: int):
|
|
return db.query(Device).filter_by(id=device_id).first()
|
|
|
|
# Async background tasks (FastAPI)
|
|
from fastapi import BackgroundTasks
|
|
|
|
@app.post("/api/captures")
|
|
async def upload_captures(files: List[UploadFile], background: BackgroundTasks):
|
|
background.add_task(process_captures, files)
|
|
return {"status": "processing"}
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Roadmap
|
|
|
|
### Phase 1: Foundation (Weeks 1-2)
|
|
|
|
#### 1.1 Database Setup
|
|
**Priority**: CRITICAL
|
|
**Effort**: 2 days
|
|
|
|
```bash
|
|
# Install PostgreSQL with PostGIS
|
|
sudo apt install postgresql postgresql-contrib postgis
|
|
|
|
# Create database and user
|
|
sudo -u postgres createdb giglez
|
|
sudo -u postgres createuser giglez_user -P
|
|
|
|
# Enable PostGIS extension
|
|
psql -d giglez -c "CREATE EXTENSION postgis;"
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Install PostgreSQL 14+ and PostGIS 3.x
|
|
- [ ] Create database and user
|
|
- [ ] Convert docs/database_schema.md to SQL script
|
|
- [ ] Add PostGIS geometry columns (ST_SetSRID)
|
|
- [ ] Create spatial indexes (GIST)
|
|
- [ ] Test basic geospatial queries
|
|
|
|
**Deliverable**: `scripts/create_schema.sql`
|
|
|
|
#### 1.2 SQLAlchemy ORM Models
|
|
**Priority**: CRITICAL
|
|
**Effort**: 3 days
|
|
|
|
```python
|
|
# src/database/models.py
|
|
from sqlalchemy import Column, String, Integer, Float, DateTime, Text
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from geoalchemy2 import Geometry
|
|
|
|
Base = declarative_base()
|
|
|
|
class Capture(Base):
|
|
__tablename__ = 'captures'
|
|
|
|
file_hash = Column(String(64), primary_key=True)
|
|
latitude = Column(Float, nullable=False)
|
|
longitude = Column(Float, nullable=False)
|
|
geom = Column(Geometry('POINT', srid=4326))
|
|
timestamp = Column(DateTime, nullable=False)
|
|
frequency = Column(Integer, nullable=False)
|
|
protocol = Column(String(100))
|
|
session_id = Column(String(64))
|
|
file_path = Column(String(500))
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Create ORM models for all tables
|
|
- [ ] Add relationship definitions
|
|
- [ ] Implement geom column auto-population (before_insert)
|
|
- [ ] Add validation constraints
|
|
- [ ] Create database migration with Alembic
|
|
- [ ] Write unit tests for model operations
|
|
|
|
**Deliverable**: `src/database/models.py`, `alembic/versions/001_initial.py`
|
|
|
|
#### 1.3 GPS Validation Module
|
|
**Priority**: HIGH
|
|
**Effort**: 1 day
|
|
|
|
Copy from `docs/wigle_analysis.md` Section 10 (GPS validator example).
|
|
|
|
**Tasks**:
|
|
- [ ] Create `src/gps/validator.py`
|
|
- [ ] Implement bounds checking
|
|
- [ ] Implement Null Island detection
|
|
- [ ] Implement accuracy filtering
|
|
- [ ] Add unit tests (valid/invalid cases)
|
|
|
|
**Deliverable**: `src/gps/validator.py`
|
|
|
|
---
|
|
|
|
### Phase 2: Upload System (Weeks 3-4)
|
|
|
|
#### 2.1 FastAPI Application Setup
|
|
**Priority**: CRITICAL
|
|
**Effort**: 2 days
|
|
|
|
```python
|
|
# src/api/main.py
|
|
from fastapi import FastAPI, UploadFile, File, Form
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
app = FastAPI(title="GigLez API", version="1.0.0")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_methods=["*"],
|
|
allow_headers=["*"]
|
|
)
|
|
|
|
@app.post("/api/captures/upload")
|
|
async def upload_captures(
|
|
manifest: str = Form(...),
|
|
files: List[UploadFile] = File(...)
|
|
):
|
|
# Parse JSON manifest
|
|
data = json.loads(manifest)
|
|
|
|
# Process files
|
|
results = await process_uploads(data, files)
|
|
|
|
return {"uploaded": len(results), "captures": results}
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Create FastAPI application structure
|
|
- [ ] Add CORS middleware
|
|
- [ ] Implement authentication (JWT + API keys)
|
|
- [ ] Add rate limiting (slowapi)
|
|
- [ ] Configure logging (loguru)
|
|
- [ ] Generate OpenAPI docs
|
|
|
|
**Deliverable**: `src/api/main.py`, `src/api/auth.py`
|
|
|
|
#### 2.2 File Upload Endpoint
|
|
**Priority**: CRITICAL
|
|
**Effort**: 3 days
|
|
|
|
**Tasks**:
|
|
- [ ] Accept multipart/form-data (JSON manifest + .sub files)
|
|
- [ ] Validate manifest schema (JSON schema)
|
|
- [ ] Compute SHA256 hash per file
|
|
- [ ] Check for duplicates before processing
|
|
- [ ] Parse .sub files with existing parser
|
|
- [ ] Validate GPS coordinates
|
|
- [ ] Store files in organized directory structure
|
|
- [ ] Insert captures into database (with transaction batching)
|
|
- [ ] Return upload summary
|
|
|
|
**Deliverable**: `src/api/routes/captures.py`
|
|
|
|
#### 2.3 Upload Marker System
|
|
**Priority**: MEDIUM
|
|
**Effort**: 2 days
|
|
|
|
From `docs/wigle_analysis.md` Section 10:
|
|
|
|
```python
|
|
# Track last uploaded capture ID per user/session
|
|
class UploadMarker(Base):
|
|
__tablename__ = 'upload_markers'
|
|
|
|
user_id = Column(Integer, primary_key=True)
|
|
session_id = Column(String(64), primary_key=True)
|
|
last_capture_id = Column(String(64), nullable=False)
|
|
last_upload_time = Column(DateTime, nullable=False)
|
|
|
|
# Query for incremental sync
|
|
def get_unuploaded_captures(user_id, session_id):
|
|
marker = get_marker(user_id, session_id)
|
|
return query_captures_after(marker.last_capture_id)
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Create `upload_markers` table
|
|
- [ ] Implement marker update on successful upload
|
|
- [ ] Add incremental sync endpoint
|
|
- [ ] Handle resume after failed upload
|
|
|
|
**Deliverable**: `src/database/markers.py`
|
|
|
|
---
|
|
|
|
### Phase 3: Device Matching (Weeks 5-6)
|
|
|
|
#### 3.1 Signature Database Import
|
|
**Priority**: HIGH
|
|
**Effort**: 4 days
|
|
|
|
**Tasks**:
|
|
- [ ] Clone Flipper Zero firmware repository
|
|
- [ ] Extract .sub files from assets
|
|
- [ ] Parse Flipper signatures (protocol, frequency, bit patterns)
|
|
- [ ] Import into `devices` and `signatures` tables
|
|
- [ ] Clone RTL_433 repository
|
|
- [ ] Parse protocol definitions (JSON)
|
|
- [ ] Map RTL_433 fields to database schema
|
|
- [ ] Import test data samples
|
|
- [ ] Create import scripts (idempotent)
|
|
|
|
**Deliverable**: `scripts/import_flipper.py`, `scripts/import_rtl433.py`
|
|
|
|
#### 3.2 Matching Engine Implementation
|
|
**Priority**: HIGH
|
|
**Effort**: 5 days
|
|
|
|
From `src/matcher/engine.py` (already scaffolded), implement strategies:
|
|
|
|
```python
|
|
# src/matcher/strategies.py
|
|
class ExactMatchStrategy(MatchStrategy):
|
|
"""Exact match: protocol + frequency + bit_length"""
|
|
def match(self, metadata, db):
|
|
results = db.query(
|
|
protocol=metadata.protocol,
|
|
frequency=metadata.frequency,
|
|
bit_length=metadata.bit_length
|
|
)
|
|
return [MatchResult(..., confidence=1.0) for r in results]
|
|
|
|
class PartialMatchStrategy(MatchStrategy):
|
|
"""Partial match: protocol + frequency"""
|
|
def match(self, metadata, db):
|
|
results = db.query(
|
|
protocol=metadata.protocol,
|
|
frequency=metadata.frequency
|
|
)
|
|
return [MatchResult(..., confidence=0.8) for r in results]
|
|
|
|
class BitPatternStrategy(MatchStrategy):
|
|
"""Bit pattern matching with masks"""
|
|
def match(self, metadata, db):
|
|
# Compare key_data against signature bit_patterns
|
|
# Use bit_mask to ignore variable bits
|
|
pass
|
|
|
|
class TimingStrategy(MatchStrategy):
|
|
"""RAW timing pattern matching"""
|
|
def match(self, metadata, db):
|
|
# Extract timing patterns
|
|
# Compare against known timing signatures
|
|
pass
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Implement ExactMatchStrategy
|
|
- [ ] Implement PartialMatchStrategy
|
|
- [ ] Implement BitPatternStrategy
|
|
- [ ] Implement TimingStrategy
|
|
- [ ] Add confidence scoring algorithm
|
|
- [ ] Store match results in `capture_matches` table
|
|
- [ ] Add unit tests for each strategy
|
|
|
|
**Deliverable**: `src/matcher/strategies.py` (complete)
|
|
|
|
---
|
|
|
|
### Phase 4: Web Interface (Weeks 7-8)
|
|
|
|
#### 4.1 Map Visualization
|
|
**Priority**: MEDIUM
|
|
**Effort**: 5 days
|
|
|
|
```html
|
|
<!-- src/web/templates/map.html -->
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
</head>
|
|
<body>
|
|
<div id="map" style="height: 600px;"></div>
|
|
<script>
|
|
const map = L.map('map').setView([40.7128, -74.0060], 13);
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
|
|
|
// Fetch captures and add markers
|
|
fetch('/api/captures?bbox=' + map.getBounds().toBBoxString())
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
data.captures.forEach(capture => {
|
|
L.marker([capture.latitude, capture.longitude])
|
|
.bindPopup(`
|
|
<b>${capture.protocol || 'Unknown'}</b><br>
|
|
Frequency: ${capture.frequency} Hz<br>
|
|
Device: ${capture.device_name || 'Unidentified'}
|
|
`)
|
|
.addTo(map);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Set up FastAPI static file serving
|
|
- [ ] Create Leaflet.js map interface
|
|
- [ ] Implement marker clustering (Leaflet.markercluster)
|
|
- [ ] Add heatmap layer (Leaflet.heat)
|
|
- [ ] Create device detail popup
|
|
- [ ] Add search/filter UI
|
|
- [ ] Implement bounding box query optimization
|
|
|
|
**Deliverable**: `src/web/static/`, `src/web/templates/`
|
|
|
|
#### 4.2 API Query Endpoints
|
|
**Priority**: HIGH
|
|
**Effort**: 3 days
|
|
|
|
```python
|
|
@app.get("/api/captures")
|
|
async def query_captures(
|
|
lat: float = None,
|
|
lon: float = None,
|
|
radius_km: float = 1.0,
|
|
bbox: str = None, # "min_lon,min_lat,max_lon,max_lat"
|
|
protocol: str = None,
|
|
device_id: int = None,
|
|
start_time: datetime = None,
|
|
end_time: datetime = None,
|
|
limit: int = 100
|
|
):
|
|
# Build geospatial query
|
|
query = db.query(Capture)
|
|
|
|
if lat and lon:
|
|
# Radius query
|
|
query = query.filter(
|
|
func.ST_DWithin(
|
|
Capture.geom,
|
|
func.ST_SetSRID(func.ST_MakePoint(lon, lat), 4326),
|
|
radius_km * 1000
|
|
)
|
|
)
|
|
elif bbox:
|
|
# Bounding box query
|
|
min_lon, min_lat, max_lon, max_lat = map(float, bbox.split(','))
|
|
query = query.filter(
|
|
Capture.geom.ST_Within(
|
|
func.ST_MakeEnvelope(min_lon, min_lat, max_lon, max_lat, 4326)
|
|
)
|
|
)
|
|
|
|
# Apply filters
|
|
if protocol:
|
|
query = query.filter(Capture.protocol == protocol)
|
|
|
|
results = query.limit(limit).all()
|
|
return {"captures": [c.to_dict() for c in results]}
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Implement geospatial queries (radius, bounding box)
|
|
- [ ] Add filtering (protocol, frequency, device, time range)
|
|
- [ ] Implement pagination
|
|
- [ ] Add statistics endpoint (`/api/stats`)
|
|
- [ ] Add heatmap data endpoint (`/api/heatmap`)
|
|
- [ ] Add device catalog endpoint (`/api/devices`)
|
|
- [ ] Optimize queries with indexes
|
|
|
|
**Deliverable**: `src/api/routes/query.py`
|
|
|
|
---
|
|
|
|
### Phase 5: Performance & Optimization (Weeks 9-10)
|
|
|
|
#### 5.1 Transaction Batching
|
|
**Priority**: HIGH
|
|
**Effort**: 2 days
|
|
|
|
From `docs/wigle_analysis.md` Section 6:
|
|
|
|
```python
|
|
# Batch insert 512 captures per transaction
|
|
BATCH_SIZE = 512
|
|
|
|
def bulk_insert_captures(captures: List[dict]):
|
|
with db.begin():
|
|
for i in range(0, len(captures), BATCH_SIZE):
|
|
batch = captures[i:i+BATCH_SIZE]
|
|
db.bulk_insert_mappings(Capture, batch)
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Implement batch insert for captures
|
|
- [ ] Add batch update for existing records
|
|
- [ ] Configure SQLAlchemy connection pooling
|
|
- [ ] Add transaction retry logic
|
|
- [ ] Benchmark performance (before/after)
|
|
|
|
**Deliverable**: `src/database/batch.py`
|
|
|
|
#### 5.2 Caching Layer
|
|
**Priority**: MEDIUM
|
|
**Effort**: 3 days
|
|
|
|
```python
|
|
from functools import lru_cache
|
|
import redis
|
|
|
|
# In-memory LRU cache for device lookups
|
|
@lru_cache(maxsize=256)
|
|
def get_device(device_id: int):
|
|
return db.query(Device).get(device_id)
|
|
|
|
# Redis cache for API responses
|
|
redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
|
|
|
@app.get("/api/devices/{device_id}")
|
|
async def get_device_api(device_id: int):
|
|
cache_key = f"device:{device_id}"
|
|
cached = redis_client.get(cache_key)
|
|
|
|
if cached:
|
|
return json.loads(cached)
|
|
|
|
device = db.query(Device).get(device_id)
|
|
redis_client.setex(cache_key, 3600, json.dumps(device.to_dict()))
|
|
|
|
return device.to_dict()
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Add LRU caching for device/signature lookups
|
|
- [ ] Install Redis for API response caching
|
|
- [ ] Implement cache invalidation strategy
|
|
- [ ] Add cache warming on startup
|
|
- [ ] Monitor cache hit rates
|
|
|
|
**Deliverable**: `src/cache/`, Redis configuration
|
|
|
|
#### 5.3 Materialized Views
|
|
**Priority**: LOW
|
|
**Effort**: 2 days
|
|
|
|
From `docs/database_schema.md` (lines 362-401):
|
|
|
|
```sql
|
|
-- Pre-compute device statistics
|
|
CREATE MATERIALIZED VIEW device_statistics AS
|
|
SELECT
|
|
d.id,
|
|
COUNT(c.id) as total_captures,
|
|
MIN(c.timestamp) as first_seen,
|
|
MAX(c.timestamp) as last_seen
|
|
FROM devices d
|
|
LEFT JOIN captures c ON c.device_id = d.id
|
|
GROUP BY d.id;
|
|
|
|
-- Refresh strategy (cron job)
|
|
REFRESH MATERIALIZED VIEW CONCURRENTLY device_statistics;
|
|
```
|
|
|
|
**Tasks**:
|
|
- [ ] Create materialized views (device_statistics, geographic_heatmap)
|
|
- [ ] Set up refresh schedule (cron or pg_cron)
|
|
- [ ] Add concurrent refresh to avoid locking
|
|
- [ ] Update API to query views instead of raw tables
|
|
|
|
**Deliverable**: `scripts/refresh_views.sh`, cron configuration
|
|
|
|
---
|
|
|
|
## Immediate Next Steps (This Week)
|
|
|
|
### Monday-Tuesday: Database Setup
|
|
1. Install PostgreSQL and PostGIS
|
|
2. Create `scripts/create_schema.sql` from `docs/database_schema.md`
|
|
3. Add PostGIS geometry columns
|
|
4. Create spatial indexes
|
|
5. Test basic geospatial queries
|
|
|
|
### Wednesday-Thursday: SQLAlchemy Models
|
|
1. Create `src/database/models.py`
|
|
2. Implement all table models
|
|
3. Add geom auto-population
|
|
4. Set up Alembic migrations
|
|
5. Write unit tests
|
|
|
|
### Friday: GPS Validation
|
|
1. Create `src/gps/validator.py`
|
|
2. Implement validation functions from `docs/wigle_analysis.md`
|
|
3. Add unit tests
|
|
4. Integrate with upload pipeline
|
|
|
|
---
|
|
|
|
## Success Metrics
|
|
|
|
### Phase 1 Complete
|
|
- ✅ PostgreSQL database created with PostGIS
|
|
- ✅ All tables created from schema
|
|
- ✅ SQLAlchemy models working
|
|
- ✅ GPS validation module tested
|
|
- ✅ Can insert/query captures programmatically
|
|
|
|
### MVP Complete (Phase 3)
|
|
- ✅ 1000+ signatures imported (Flipper + RTL_433)
|
|
- ✅ Automatic device matching works
|
|
- ✅ API accepts .sub file uploads
|
|
- ✅ Web interface shows captures on map
|
|
- ✅ End-to-end workflow: upload → parse → match → visualize
|
|
|
|
### Production Ready (Phase 5)
|
|
- ✅ Transaction batching implemented (512 ops/commit)
|
|
- ✅ LRU caching for device lookups
|
|
- ✅ Redis caching for API responses
|
|
- ✅ Query performance < 100ms (p95)
|
|
- ✅ Can handle 10,000+ captures/day
|
|
|
|
---
|
|
|
|
## Resources & References
|
|
|
|
### Documentation
|
|
- `docs/wigle_analysis.md` - Complete Wigle client analysis
|
|
- `docs/architecture_decisions.md` - 10 key architectural decisions
|
|
- `docs/database_schema.md` - PostgreSQL schema with PostGIS
|
|
- `CLAUDE.md` - Project directives and requirements
|
|
- `PROJECT_STATUS.md` - Current status and roadmap
|
|
|
|
### Code Examples
|
|
- GPS validator: `docs/wigle_analysis.md` Section 10
|
|
- Upload markers: `docs/wigle_analysis.md` Section 4
|
|
- Batch processing: `docs/wigle_analysis.md` Section 6
|
|
- LRU caching: `docs/architecture_decisions.md` Decision 9
|
|
|
|
### External Resources
|
|
- Wigle Android client: https://github.com/wiglenet/wigle-wifi-wardriving
|
|
- Wigle API docs: https://api.wigle.net/
|
|
- PostGIS documentation: https://postgis.net/docs/
|
|
- FastAPI documentation: https://fastapi.tiangolo.com/
|
|
- Leaflet.js documentation: https://leafletjs.com/
|
|
|
|
---
|
|
|
|
## Risk Mitigation
|
|
|
|
### Technical Risks
|
|
1. **Database size growth**: Implement archival strategy, compress old captures
|
|
2. **Query performance**: Add indexes, use materialized views, implement caching
|
|
3. **File storage limits**: Use object storage (S3/MinIO) for .sub files
|
|
4. **GPS accuracy**: Follow Wigle's validation patterns (32m threshold)
|
|
|
|
### Implementation Risks
|
|
1. **Scope creep**: Focus on MVP first (upload → parse → match → visualize)
|
|
2. **Over-engineering**: Start simple, optimize when needed (measure first)
|
|
3. **Signature database maintenance**: Automate imports with scripts
|
|
|
|
---
|
|
|
|
**Next Action**: Start Phase 1 database setup (PostgreSQL + PostGIS installation)
|