Database Schema
This document provides a comprehensive overview of the database schema used in the Tracker GraphQL application, including tables, relationships, and indexes.
Overview
The Tracker GraphQL application uses PostgreSQL with TimescaleDB and PostGIS extensions to store and manage data. The database schema is designed to efficiently handle:
- Tracker devices and their location history
- Client and brand information
- Production runs
- Storage and delivery locations
- User management and authentication
- Geospatial data and geocoding
- Time-series data for location tracking
Entity-Relationship Diagram
The following diagram illustrates the relationships between the main tables in the database:
Tables
Base
The Base class is the foundation for all models in the application, using SQLAlchemy's declarative base.
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
The application automatically creates the TimescaleDB extension when the database is initialized:
create_timescaledb_extension = DDL("CREATE EXTENSION IF NOT EXISTS timescaledb;")
event.listen(
Base.metadata,
"after_create",
create_timescaledb_extension.execute_if(dialect="postgresql"),
)
Client
Table Name: clients
Description: Represents organizations that use the tracking system.
Columns:
id(Integer, Primary Key): Unique identifiername(String, Not Null): Client namelogo(String): URL to client logo image
Relationships:
- One-to-many with
Brand: A client can have multiple brands
Brand
Table Name: brands
Description: Represents brands owned by clients.
Columns:
id(Integer, Primary Key): Unique identifiername(String, Not Null): Brand namelogo(String): URL to brand logo imageclient_id(Integer, Foreign Key): Reference to the client that owns this brand
Relationships:
- Many-to-one with
Client: A brand belongs to one client - One-to-many with
ProductionRun: A brand can have multiple production runs
Indexes:
idx_brands_client_id: Improves query performance when filtering brands by client
ProductionRun
Table Name: production_runs
Description: Represents a production batch that is tracked through the system.
Columns:
id(Integer, Primary Key): Unique identifierstart_date(DateTime, Not Null): When the production run startedend_date(DateTime): When the production run ended (can be null if ongoing)brand_id(Integer, Foreign Key): Reference to the brand associated with this production rundescription(String): Optional description of the production runimage_url(String): Optional URL to an image representing the production run
Relationships:
- Many-to-one with
Brand: A production run belongs to one brand - One-to-many with
Tracker: A production run can have multiple trackers - Many-to-many with
DeliveryLocation: Through junction tableproduction_run_delivery_locations - Many-to-many with
StorageLocation: Through junction tableproduction_run_storage_locations - One-to-many with
DeliveryLocation(direct): A production run can have multiple delivery locations - One-to-many with
StorageLocation(direct): A production run can have multiple storage locations
Indexes:
idx_production_runs_brand_id: Improves query performance when filtering production runs by brandidx_production_runs_start_date: Optimizes queries that filter or sort by start dateidx_production_runs_end_date: Optimizes queries that filter or sort by end date
Tracker
Table Name: trackers
Description: Represents physical tracker devices that report location data.
Columns:
id(Integer, Primary Key): Unique identifiername(String, Not Null): Tracker name/identifieradvertisement_key(String, Not Null): Public key used for advertisingprivate_key(String, Not Null): Private key for secure communicationhashed_advertisement_key(String, Not Null): Hashed version of the advertisement keylast_report_requested(DateTime): When a location report was last requestedlast_report_received(DateTime): When a location report was last receivedproduction_run_id(Integer, Foreign Key): Reference to the production run this tracker is part ofcurrent_status(Enum): Current status of the tracker (CREATED, IN_TRANSIT, IN_STORAGE, DELIVERED)current_state_start(DateTime): When the current status begantotal_transit_time(Interval): Accumulated time spent in transittotal_storage_time(Interval): Accumulated time spent in storage
Relationships:
- Many-to-one with
ProductionRun: A tracker belongs to one production run - One-to-many with
LocationHistory: A tracker has multiple location history records - One-to-many with
StatusHistory: A tracker has multiple status history records - One-to-many with
LocationProcessingStatus: A tracker has multiple location processing status records - Many-to-many with
User: ThroughUserTrackerNotificationfor subscriptions - One-to-many with
Notification: A tracker can generate multiple notifications
Indexes:
idx_trackers_production_run_current_status: Composite index for filtering trackers by production run and statusidx_trackers_last_report_received: Optimizes queries that filter or sort by last report timeidx_trackers_id: Index on the primary key for faster joins- Index on
name: Improves performance when searching by tracker name
LocationHistory
Table Name: location_history
Description: Stores the location history of trackers. This is implemented as a materialized view in TimescaleDB.
Columns:
id(Integer, Primary Key): Unique identifiertimestamp(DateTime, Not Null): When the location was recordedlocation(Geometry('POINT', 4326), Not Null): Geographic coordinates stored as PostGIS pointnearest_city(String): Name of the nearest city (populated by geocoding)tracker_id(Integer, Foreign Key, Not Null): Reference to the trackerconfidence(Integer): Confidence level of the location data
Relationships:
- Many-to-one with
Tracker: A location history record belongs to one tracker
Indexes:
idx_location_history_tracker_timestamp: Composite index for efficiently querying location history by tracker and timeidx_location_history_nearest_city: Improves performance when filtering by nearest city
Methods:
get_coordinates(session): Extracts latitude and longitude from the PostGIS geometry
LocationReport
Table Name: location_reports
Description: Raw location data collected from trackers before processing.
Columns:
id(Integer, Primary Key): Unique identifierhashed_adv_key(String, Not Null): Hashed advertisement key of the trackertimestamp(DateTime, Not Null): When the location was reportedlocation(Geometry('POINT', 4326)): Geographic coordinates stored as PostGIS pointconfidence(Integer): Confidence level of the location datanearest_city(String): Name of the nearest city (populated by geocoding)
Indexes:
idx_location_report_location: Spatial index for geographic queriesidx_location_report_timestamp: Improves performance when filtering by timeidx_location_report_hashed_adv_key: Optimizes lookups by hashed advertisement keyuq_hashed_adv_key_timestamp: Unique constraint to prevent duplicate reports
Methods:
get_coordinates(session): Extracts latitude and longitude from the PostGIS geometry
LocationProcessingStatus
Table Name: location_processing_status
Description: Tracks which location history rows have been processed by the geofence processing system.
Columns:
id(Integer, Primary Key): Unique identifiertracker_id(Integer, Foreign Key, Not Null): Reference to the trackertime_bucket(DateTime, Not Null): Time bucket for processingprocessed(Boolean): Whether this time bucket has been processedprocessed_at(DateTime): When the processing occurred
Relationships:
- Many-to-one with
Tracker: A processing status record belongs to one tracker
Indexes:
idx_location_processing_processed: Improves performance when filtering by processed statusidx_location_processing_tracker_id: Optimizes lookups by trackeruq_tracker_time_bucket: Unique constraint on tracker_id and time_bucket
StorageLocation
Table Name: storage_locations
Description: Represents locations where trackers can be stored.
Columns:
id(Integer, Primary Key): Unique identifiername(String, Not Null): Location namelocation(Geometry('POINT'), Not Null): Geographic coordinates stored as PostGIS pointproduction_run_id(Integer, Foreign Key): Reference to the production run this storage location is associated with
Relationships:
- One-to-many with
StatusHistory: A storage location can be referenced in multiple status history records - Many-to-one with
ProductionRun: A storage location can be associated with a production run
Indexes:
idx_storage_locations_geom: Spatial index for geographic queries
Methods:
get_coordinates(session): Extracts latitude and longitude from the PostGIS geometry
DeliveryLocation
Table Name: delivery_locations
Description: Represents locations where trackers can be delivered.
Columns:
id(Integer, Primary Key): Unique identifiername(String, Not Null): Location namelocation(Geometry('POINT'), Not Null): Geographic coordinates stored as PostGIS pointproduction_run_id(Integer, Foreign Key): Reference to the production run this delivery location is associated with
Relationships:
- One-to-many with
StatusHistory: A delivery location can be referenced in multiple status history records - Many-to-one with
ProductionRun: A delivery location can be associated with a production run
Indexes:
idx_delivery_locations_geom: Spatial index for geographic queries
Methods:
get_coordinates(session): Extracts latitude and longitude from the PostGIS geometry
StatusHistory
Table Name: status_history
Description: Tracks changes in tracker status over time.
Columns:
id(Integer, Primary Key): Unique identifiertimestamp(DateTime, Not Null): When the status change occurredstatus(Enum, Not Null): Status value (CREATED, IN_TRANSIT, IN_STORAGE, DELIVERED)tracker_id(Integer, Foreign Key): Reference to the trackerstorage_location_id(Integer, Foreign Key): Reference to a storage location (if status is IN_STORAGE)delivery_location_id(Integer, Foreign Key): Reference to a delivery location (if status is DELIVERED)
Relationships:
- Many-to-one with
Tracker: A status history record belongs to one tracker - Many-to-one with
StorageLocation: A status history record can reference a storage location - Many-to-one with
DeliveryLocation: A status history record can reference a delivery location
Indexes:
idx_status_history_tracker_timestamp: Composite index for efficiently querying status history by tracker and timeidx_status_history_status: Improves performance when filtering by status
User
Table Name: users
Description: Represents users of the system.
Columns:
id(Integer, Primary Key): Unique identifiername(String, Not Null): User's nameemail(String, Not Null, Unique): User's email addresspassword_hash(String, Not Null): Hashed passwordclient_list(Array of Integer): List of client IDs the user has access tonotification_preferences(JSONB): User's notification preferences
Indexes:
idx_users_email: Improves performance when looking up users by email
Methods:
set_password(password): Sets the password hashcheck_password(password): Verifies a password against the stored hash
UserTrackerNotification
Table Name: user_tracker_notifications
Description: Represents subscriptions of users to tracker notifications.
Columns:
id(Integer, Primary Key): Unique identifieruser_id(Integer, Foreign Key, Not Null): Reference to the usertracker_id(Integer, Foreign Key, Not Null): Reference to the trackercreated_at(DateTime): When the subscription was created
Relationships:
- Many-to-one with
User: A subscription belongs to one user - Many-to-one with
Tracker: A subscription is for one tracker
Indexes:
idx_user_tracker_notifications_user_id: Improves performance when looking up subscriptions by useridx_user_tracker_notifications_tracker_id: Improves performance when looking up subscriptions by tracker
Notification
Table Name: notifications
Description: Stores notifications sent to users.
Columns:
id(Integer, Primary Key): Unique identifieruser_id(Integer, Foreign Key, Not Null): Reference to the usertracker_id(Integer, Foreign Key, Not Null): Reference to the trackermessage(Text, Not Null): Notification messagestatus(String, Not Null): Status of the notificationread(Boolean): Whether the notification has been readcreated_at(DateTime): When the notification was created
Relationships:
- Many-to-one with
User: A notification belongs to one user - Many-to-one with
Tracker: A notification is about one tracker
Indexes:
idx_notifications_user_id: Improves performance when looking up notifications by useridx_notifications_user_read: Composite index for efficiently querying unread notifications for a user
Association Tables
production_run_delivery_locations
Description: Junction table for the many-to-many relationship between production runs and delivery locations.
Columns:
production_run_id(Integer, Foreign Key): Reference to a production rundelivery_location_id(Integer, Foreign Key): Reference to a delivery location
production_run_storage_locations
Description: Junction table for the many-to-many relationship between production runs and storage locations.
Columns:
production_run_id(Integer, Foreign Key): Reference to a production runstorage_location_id(Integer, Foreign Key): Reference to a storage location
TimescaleDB Integration
The application leverages TimescaleDB for efficient time-series data management:
- The
location_historytable is implemented as a materialized view that combines data from continuous aggregates - Raw location data is stored in the
location_reportstable - Continuous aggregates provide efficient access to historical data at different time resolutions
- Automatic data retention policies ensure efficient storage usage
For more details on the TimescaleDB integration, see the TimescaleDB Integration document.
Spatial Data with PostGIS
The application uses PostGIS for spatial data management:
- Location data is stored as PostGIS
POINTgeometries - Spatial indexes (GiST) are used for efficient geographic queries
- PostGIS functions are used for spatial operations like distance calculations
- The
get_coordinatesmethods extract latitude and longitude from PostGIS geometries
Indexes and Performance Optimization
The database schema includes various indexes to optimize query performance:
- Primary Key Indexes: Automatically created for all primary key columns
- Foreign Key Indexes: Created for foreign key columns to improve join performance
- Composite Indexes: Used for frequently combined filter conditions (e.g., tracker_id + timestamp)
- Spatial Indexes: GiST indexes for PostGIS geometry columns
- Unique Constraints: Enforce data integrity and improve lookup performance
Geofence Processing
The application includes a geofence processing system that:
- Monitors location history data
- Detects when trackers enter or exit defined geographic areas (storage and delivery locations)
- Updates tracker status based on geofence events
- Generates notifications for status changes
The location_processing_status table tracks which location data has been processed to avoid duplicate processing.