# Database Schema Design

## Overview

The GigLez database stores RF signal captures with GPS coordinates, device signatures, and community contributions. The schema supports efficient querying by location, frequency, protocol, and device type.

## Core Tables

### 1. captures

Primary table storing raw RF signal captures with GPS attribution.

```sql
CREATE TABLE captures (
    id SERIAL PRIMARY KEY,
    session_id INTEGER REFERENCES sessions(id) ON DELETE CASCADE,

    -- GPS Data
    latitude DECIMAL(10, 8) NOT NULL,
    longitude DECIMAL(11, 8) NOT NULL,
    altitude DECIMAL(8, 2),
    gps_accuracy DECIMAL(6, 2),

    -- Timestamp
    captured_at TIMESTAMP NOT NULL DEFAULT NOW(),

    -- RF Signal Data
    frequency INTEGER NOT NULL,  -- in Hz
    rssi INTEGER,  -- Received Signal Strength Indicator
    modulation VARCHAR(50),  -- OOK, 2FSK, etc.
    preset VARCHAR(100),

    -- Protocol Information (if decoded)
    protocol VARCHAR(100),
    bit_length INTEGER,
    key_data BYTEA,
    timing_element INTEGER,  -- TE value in microseconds

    -- Raw Signal Data
    raw_data TEXT,  -- RAW_Data timings
    raw_format VARCHAR(20),  -- 'RAW', 'BinRAW', 'KEY'

    -- File Storage
    file_path VARCHAR(500),  -- Path to .sub file if stored separately
    file_hash VARCHAR(64),  -- SHA-256 hash for deduplication

    -- Matching
    device_id INTEGER REFERENCES devices(id),
    match_confidence DECIMAL(5, 4),  -- 0.0 to 1.0
    match_method VARCHAR(50),  -- 'auto', 'user', 'community'

    -- Indexes for geospatial queries
    CONSTRAINT valid_latitude CHECK (latitude >= -90 AND latitude <= 90),
    CONSTRAINT valid_longitude CHECK (longitude >= -180 AND longitude <= 180)
);

CREATE INDEX idx_captures_location ON captures USING GIST (ll_to_earth(latitude, longitude));
CREATE INDEX idx_captures_frequency ON captures(frequency);
CREATE INDEX idx_captures_protocol ON captures(protocol);
CREATE INDEX idx_captures_timestamp ON captures(captured_at DESC);
CREATE INDEX idx_captures_session ON captures(session_id);
CREATE INDEX idx_captures_device ON captures(device_id);
CREATE INDEX idx_captures_hash ON captures(file_hash);
```

### 2. sessions

Wardriving/capture sessions to group related captures.

```sql
CREATE TABLE sessions (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),

    -- Session Metadata
    name VARCHAR(200),
    description TEXT,
    started_at TIMESTAMP NOT NULL DEFAULT NOW(),
    ended_at TIMESTAMP,

    -- Privacy Settings
    is_public BOOLEAN DEFAULT true,
    anonymize_gps BOOLEAN DEFAULT false,
    gps_precision_meters INTEGER DEFAULT 10,

    -- Session Statistics (computed)
    total_captures INTEGER DEFAULT 0,
    unique_devices INTEGER DEFAULT 0,
    distance_km DECIMAL(10, 2),

    -- Bounding Box (for quick filtering)
    min_latitude DECIMAL(10, 8),
    max_latitude DECIMAL(10, 8),
    min_longitude DECIMAL(11, 8),
    max_longitude DECIMAL(11, 8)
);

CREATE INDEX idx_sessions_user ON sessions(user_id);
CREATE INDEX idx_sessions_started ON sessions(started_at DESC);
```

### 3. devices

Known IoT device types with signature information.

```sql
CREATE TABLE devices (
    id SERIAL PRIMARY KEY,

    -- Device Identification
    manufacturer VARCHAR(200),
    model VARCHAR(200),
    device_type VARCHAR(100),  -- 'garage_door', 'weather_station', 'key_fob', etc.
    description TEXT,

    -- RF Characteristics
    typical_frequency INTEGER,  -- Most common frequency in Hz
    frequency_range_low INTEGER,
    frequency_range_high INTEGER,
    modulation_types TEXT[],  -- Array: ['OOK', '2FSK']

    -- Protocol Information
    protocol VARCHAR(100),
    bit_length INTEGER,
    encoding VARCHAR(50),  -- 'PWM', 'PPM', 'Manchester', etc.

    -- Metadata
    fcc_id VARCHAR(50),
    manufacturer_code VARCHAR(50),  -- For protocols like KeeLoq

    -- Community Data
    created_at TIMESTAMP DEFAULT NOW(),
    verified BOOLEAN DEFAULT false,
    verification_count INTEGER DEFAULT 0,

    -- Source
    source VARCHAR(50),  -- 'flipper', 'rtl433', 'urh', 'community'
    source_url TEXT
);

CREATE INDEX idx_devices_manufacturer ON devices(manufacturer);
CREATE INDEX idx_devices_type ON devices(device_type);
CREATE INDEX idx_devices_frequency ON devices(typical_frequency);
CREATE INDEX idx_devices_protocol ON devices(protocol);
```

### 4. signatures

Protocol signatures for device matching.

```sql
CREATE TABLE signatures (
    id SERIAL PRIMARY KEY,
    device_id INTEGER REFERENCES devices(id) ON DELETE CASCADE,

    -- Signature Pattern
    protocol VARCHAR(100) NOT NULL,
    frequency INTEGER,
    modulation VARCHAR(50),

    -- Matching Criteria
    bit_pattern BYTEA,
    bit_mask BYTEA,  -- Which bits to match (1=match, 0=ignore)
    timing_min INTEGER,  -- TE range in microseconds
    timing_max INTEGER,

    -- Raw Pattern (for regex-like matching)
    pattern_regex TEXT,

    -- Confidence Weighting
    weight DECIMAL(4, 3) DEFAULT 1.0,  -- Higher = more reliable signature

    -- Metadata
    created_at TIMESTAMP DEFAULT NOW(),
    source VARCHAR(50),
    source_file VARCHAR(500),  -- Original .sub or config file

    UNIQUE(device_id, protocol, bit_pattern)
);

CREATE INDEX idx_signatures_device ON signatures(device_id);
CREATE INDEX idx_signatures_protocol ON signatures(protocol);
CREATE INDEX idx_signatures_frequency ON signatures(frequency);
```

### 5. users

User accounts for community features.

```sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,

    -- Authentication
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,

    -- Profile
    display_name VARCHAR(100),
    avatar_url TEXT,
    bio TEXT,

    -- Statistics
    total_captures INTEGER DEFAULT 0,
    total_identifications INTEGER DEFAULT 0,
    reputation_score INTEGER DEFAULT 0,

    -- Settings
    api_key VARCHAR(64) UNIQUE,
    email_verified BOOLEAN DEFAULT false,

    -- Timestamps
    created_at TIMESTAMP DEFAULT NOW(),
    last_login TIMESTAMP
);

CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_api_key ON users(api_key);
```

### 6. identifications

User-submitted device identifications (with photos).

```sql
CREATE TABLE identifications (
    id SERIAL PRIMARY KEY,
    capture_id INTEGER REFERENCES captures(id) ON DELETE CASCADE,
    device_id INTEGER REFERENCES devices(id),
    user_id INTEGER REFERENCES users(id),

    -- Identification Details
    confidence VARCHAR(20),  -- 'certain', 'likely', 'guess'
    notes TEXT,

    -- Visual Evidence
    photo_urls TEXT[],  -- Array of image URLs

    -- Community Validation
    upvotes INTEGER DEFAULT 0,
    downvotes INTEGER DEFAULT 0,
    verified BOOLEAN DEFAULT false,
    verified_by INTEGER REFERENCES users(id),
    verified_at TIMESTAMP,

    -- Timestamps
    submitted_at TIMESTAMP DEFAULT NOW(),

    UNIQUE(capture_id, user_id, device_id)
);

CREATE INDEX idx_identifications_capture ON identifications(capture_id);
CREATE INDEX idx_identifications_device ON identifications(device_id);
CREATE INDEX idx_identifications_user ON identifications(user_id);
```

### 7. votes

Voting on device identifications.

```sql
CREATE TABLE votes (
    id SERIAL PRIMARY KEY,
    identification_id INTEGER REFERENCES identifications(id) ON DELETE CASCADE,
    user_id INTEGER REFERENCES users(id),

    vote_type INTEGER NOT NULL,  -- 1 = upvote, -1 = downvote
    voted_at TIMESTAMP DEFAULT NOW(),

    UNIQUE(identification_id, user_id)
);

CREATE INDEX idx_votes_identification ON votes(identification_id);
CREATE INDEX idx_votes_user ON votes(user_id);
```

### 8. flipper_signatures

Imported Flipper Zero .sub file signatures.

```sql
CREATE TABLE flipper_signatures (
    id SERIAL PRIMARY KEY,
    signature_id INTEGER REFERENCES signatures(id) ON DELETE CASCADE,

    -- Flipper-Specific Fields
    filetype VARCHAR(50),  -- 'Flipper SubGhz Key File' or 'RAW File'
    version INTEGER,
    preset VARCHAR(100),

    -- Custom Preset Data
    custom_preset_module VARCHAR(50),
    custom_preset_data BYTEA,

    -- Protocol Data
    protocol VARCHAR(100),
    bit INTEGER,
    key BYTEA,
    te INTEGER,  -- Timing element

    -- RAW Data
    raw_data TEXT,
    bin_raw_bit INTEGER,
    bin_raw_te INTEGER,
    bin_raw_data BYTEA,

    -- Source
    source_file VARCHAR(500),
    imported_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_flipper_protocol ON flipper_signatures(protocol);
CREATE INDEX idx_flipper_preset ON flipper_signatures(preset);
```

### 9. rtl433_protocols

Imported RTL_433 protocol definitions.

```sql
CREATE TABLE rtl433_protocols (
    id SERIAL PRIMARY KEY,
    signature_id INTEGER REFERENCES signatures(id) ON DELETE CASCADE,

    -- Protocol Identification
    protocol_number INTEGER,
    protocol_name VARCHAR(200),
    model VARCHAR(200),

    -- RF Characteristics
    frequency INTEGER,
    modulation VARCHAR(50),  -- 'OOK_PWM', 'FSK_PCM', etc.

    -- Timing Information (in microseconds)
    short_width INTEGER,
    long_width INTEGER,
    reset_limit INTEGER,
    gap_limit INTEGER,

    -- Decoding
    decoder_type VARCHAR(50),
    bit_count INTEGER,

    -- JSON Fields Mapping
    json_fields JSONB,  -- Expected output fields

    -- Source
    source_file VARCHAR(500),
    imported_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_rtl433_protocol_num ON rtl433_protocols(protocol_number);
CREATE INDEX idx_rtl433_model ON rtl433_protocols(model);
CREATE INDEX idx_rtl433_modulation ON rtl433_protocols(modulation);
```

## Materialized Views

### device_statistics

Pre-computed statistics for device types.

```sql
CREATE MATERIALIZED VIEW device_statistics AS
SELECT
    d.id as device_id,
    d.manufacturer,
    d.model,
    COUNT(c.id) as total_captures,
    COUNT(DISTINCT c.session_id) as total_sessions,
    MIN(c.captured_at) as first_seen,
    MAX(c.captured_at) as last_seen,
    AVG(c.rssi) as avg_rssi,
    ST_Collect(ST_MakePoint(c.longitude, c.latitude)) as capture_locations
FROM devices d
LEFT JOIN captures c ON c.device_id = d.id
GROUP BY d.id, d.manufacturer, d.model;

CREATE INDEX idx_device_stats_device ON device_statistics(device_id);
```

### geographic_heatmap

Aggregated capture density for mapping.

```sql
CREATE MATERIALIZED VIEW geographic_heatmap AS
SELECT
    ROUND(latitude::numeric, 3) as lat_bucket,
    ROUND(longitude::numeric, 3) as lon_bucket,
    COUNT(*) as capture_count,
    COUNT(DISTINCT device_id) as unique_devices,
    array_agg(DISTINCT protocol) as protocols_seen
FROM captures
WHERE device_id IS NOT NULL
GROUP BY lat_bucket, lon_bucket;

CREATE INDEX idx_heatmap_location ON geographic_heatmap(lat_bucket, lon_bucket);
```

## Functions

### calculate_distance

Calculate distance between two GPS coordinates.

```sql
CREATE OR REPLACE FUNCTION calculate_distance(
    lat1 DECIMAL, lon1 DECIMAL,
    lat2 DECIMAL, lon2 DECIMAL
) RETURNS DECIMAL AS $$
DECLARE
    R DECIMAL := 6371.0;  -- Earth radius in km
    dLat DECIMAL;
    dLon DECIMAL;
    a DECIMAL;
    c DECIMAL;
BEGIN
    dLat := radians(lat2 - lat1);
    dLon := radians(lon2 - lon1);
    a := sin(dLat/2) * sin(dLat/2) +
         cos(radians(lat1)) * cos(radians(lat2)) *
         sin(dLon/2) * sin(dLon/2);
    c := 2 * atan2(sqrt(a), sqrt(1-a));
    RETURN R * c;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
```

### match_signature

Match a capture against all known signatures.

```sql
CREATE OR REPLACE FUNCTION match_signature(
    p_capture_id INTEGER
) RETURNS TABLE(device_id INTEGER, confidence DECIMAL) AS $$
BEGIN
    RETURN QUERY
    SELECT
        s.device_id,
        CASE
            WHEN c.protocol = s.protocol
                AND c.frequency = s.frequency
                AND c.bit_length = s.bit_length THEN 1.0
            WHEN c.protocol = s.protocol
                AND c.frequency = s.frequency THEN 0.8
            WHEN c.protocol = s.protocol THEN 0.5
            ELSE 0.0
        END as confidence
    FROM captures c
    CROSS JOIN signatures s
    WHERE c.id = p_capture_id
        AND c.protocol IS NOT NULL
    ORDER BY confidence DESC
    LIMIT 10;
END;
$$ LANGUAGE plpgsql;
```

## Triggers

### update_session_statistics

Automatically update session statistics when captures are added.

```sql
CREATE OR REPLACE FUNCTION update_session_stats()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE sessions SET
        total_captures = (SELECT COUNT(*) FROM captures WHERE session_id = NEW.session_id),
        unique_devices = (SELECT COUNT(DISTINCT device_id) FROM captures WHERE session_id = NEW.session_id),
        min_latitude = LEAST(min_latitude, NEW.latitude),
        max_latitude = GREATEST(max_latitude, NEW.latitude),
        min_longitude = LEAST(min_longitude, NEW.longitude),
        max_longitude = GREATEST(max_longitude, NEW.longitude)
    WHERE id = NEW.session_id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_update_session_stats
    AFTER INSERT ON captures
    FOR EACH ROW
    EXECUTE FUNCTION update_session_stats();
```

### update_device_verification

Update device verification status based on identification votes.

```sql
CREATE OR REPLACE FUNCTION update_device_verification()
RETURNS TRIGGER AS $$
DECLARE
    net_votes INTEGER;
BEGIN
    SELECT (upvotes - downvotes) INTO net_votes
    FROM identifications
    WHERE id = NEW.identification_id;

    IF net_votes >= 5 THEN
        UPDATE identifications SET verified = true
        WHERE id = NEW.identification_id;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_update_verification
    AFTER INSERT OR UPDATE ON votes
    FOR EACH ROW
    EXECUTE FUNCTION update_device_verification();
```

## Sample Queries

### Find all captures near a location

```sql
SELECT c.*, d.manufacturer, d.model
FROM captures c
LEFT JOIN devices d ON c.device_id = d.id
WHERE calculate_distance(c.latitude, c.longitude, 40.7128, -74.0060) <= 1.0  -- Within 1km
ORDER BY c.captured_at DESC;
```

### Get device density heatmap

```sql
SELECT lat_bucket, lon_bucket, capture_count, unique_devices
FROM geographic_heatmap
WHERE capture_count > 5
ORDER BY capture_count DESC;
```

### Find unidentified captures

```sql
SELECT c.id, c.frequency, c.protocol, c.latitude, c.longitude, c.captured_at
FROM captures c
WHERE c.device_id IS NULL
    AND c.protocol IS NOT NULL
ORDER BY c.captured_at DESC
LIMIT 100;
```

### Top device types by capture count

```sql
SELECT d.manufacturer, d.model, d.device_type, COUNT(c.id) as captures
FROM devices d
JOIN captures c ON c.device_id = d.id
GROUP BY d.id, d.manufacturer, d.model, d.device_type
ORDER BY captures DESC
LIMIT 20;
```
