src/models/; a parallel set of Isaac Sim objects (4 tables, 3 views, 2 functions, 3 triggers) exists only as raw SQL in one migration and has no ORM model. Schema is managed by Alembic (flask db upgrade) in production, with an APP_ENVIRONMENT=dev shortcut that calls db.create_all().
This page is the consolidated, column-level reference. For narrative context see:
Schema Overview
Table inventory, ownership, and per-table cascade rules explained in prose.
Migrations & Connection
DBConnector, the Alembic chain, pool settings, and dev-mode
create_all().Mission & Geofence Format
How
MissionPoint/GeofencePoint map to MAVLink waypoint/fence items.Isaac Sim Raw SQL
The raw-SQL Isaac Sim tables, views, functions and triggers in depth.
Column facts on this page come from the ORM models in
src/models/ and are cross-checked against the Alembic migrations in migrations/versions/. Where the two diverge (e.g. a misplaced nullable=False), that divergence is flagged as a gotcha. Migration head at time of writing: r3m4n5o6p7q8.Core ER diagram
Everything is owned by auser. Most child tables CASCADE on user delete; the exceptions (drone.mission_id, subscription, payment) are called out under Cascade & ownership.
Cascade & ownership model
Foreign-keyondelete behavior is not uniform — this is the single most important thing to internalize before writing a migration or a delete path.
Identity & fleet
user — src/models/user.py
get_report_recipients() returns [email] + parsed extras, de-duplicated with the primary email kept first. See Executions & Reports.
drone — src/models/drone.py
user_drone_access — src/models/user_drone_access.py
Join table for sharing a drone with additional users.
Unique constraint
uq_user_drone_access(user_id, drone_id).
Missions & waypoints
mission — src/models/mission.py
Route-template header. Unique constraint uq_user_name(user_id, name).
mission_point — src/models/mission_point.py
An ordered waypoint. Carries both a semantic type (base/fly/safepoint) and the full MAVLink waypoint payload. Rows are read ordered by sequence.
to_mavlink_waypoint() emits x_lat=lat, y_long=lng, z_alt=altitude plus frame/command/is_current/autocontinue/param1-4. DroneControlService.push_mission() (src/service/drone_control_service.py:784) loads points by sequence, converts each, and prepends a synthetic TAKEOFF (cmd 22) if the first command isn’t already 22, then pushes over rosbridge and verifies wp_transfered == len.
Flights & media
mission_execution — src/models/mission_execution.py
The realized-flight record. An arm/flight creates a row (status = in_progress, mission_id = NULL for a manual flight); after landing, LogAnalysisService parses the ArduPilot .bin (referenced by log_asset_id) and back-fills the ~40 metric columns. Indexed on user_id, drone_id, mission_id, started_at, status.
Identity, timing, status & report columns
Identity, timing, status & report columns
Log-analysis metric columns (all nullable)
Log-analysis metric columns (all nullable)
d89dbd0532c0, 55cc964f0c63, n9i0j1k2l3m4, o0j1k2l3m4n5, and p1k2l3m4n5o6. See Executions, Log Analysis & Reports.
asset — src/models/asset.py
S3-backed media (video / image / logs). Uploaded during a flight, an asset carries execution_id; uploaded outside a tracked flight, it’s NULL.
See S3 Assets, HLS Video & Execution Archives.
Geofences
geofence — src/models/geofence.py
Unique constraint uq_geofence_user_name(user_id, name). geofence_points cascade delete-orphan at the ORM level.
geofence_point — src/models/geofence_point.py
A fence vertex (polygon) or center (circle), stored as a MAVLink fence item.
command is a MAVLink fence command; param1 is the polygon vertex count or the circle radius in meters. to_mavlink_fence_item() forces z_alt = 0.0 (fences are 2D).
See Missions & Geofences API.
Calendar & scheduling
calendar_event — src/models/calendar_event.py
A scheduled (optionally recurring, RRULE) mission. Unique constraint uq_user_event_title_time(user_id, title, scheduled_time).
calendar_event_occurrence — src/models/calendar_event_occurrence.py
A sparse exceptions table: recurring occurrences default to scheduled, and only occurrences whose status deviates are persisted. Unique constraint uq_event_occurrence_time(event_id, occurrence_time).
See Users, IP Allocation, Calendar & Isaac Sim.
Billing
subscription — src/models/subscription.py
One row per user (user_id UNIQUE). Pricing model is €120 / vehicle / year.
payment — src/models/subscription.py
Invoice/receipt history. amount is stored in cents.
See Stripe Billing & Vehicle Limits.
Enum types
All enums are global PostgreSQL types. Two of them (event_status, subscription_status — the production type names created by migrations i4d5e6f7g8h9 and h3c4d5e6f7g8; db.create_all() would instead name them eventstatus/subscriptionstatus) declare values_callable so PostgreSQL stores the lowercase value; the rest store the enum name. For most enums the member name equals its value, so it makes no difference — except userlevel, whose type labels are the uppercase names ADMIN/CUSTOMER while the Python .value is lowercase admin/customer.
Isaac Sim tables (raw SQL, no ORM)
Migrationmigrations/versions/e85b13d42b08_add_isaac_sim_tables.py creates 4 tables + 3 views + 2 PL/pgSQL functions + 3 triggers entirely as raw SQL. There is no SQLAlchemy model for any of it; src/service/instance_scheduler_service.py reads and writes these tables with a raw psycopg2 cursor (self.db.cursor(), commit(), rollback()), bypassing the ORM/session completely.
isaac_sim_instances
isaac_sim_instances
EC2 instance records. Indexes on
user_id, drone_id, state, auto_shutdown_time, session_id, and created_at DESC. A trigger_update_isaac_instances_updated_at bumps updated_at via update_updated_at_column().isaac_sim_usage_tracking
isaac_sim_usage_tracking
Historical per-session usage. Indexes on
user_id, instance_id, session_start DESC.isaac_sim_budget
isaac_sim_budget
Monthly rollup, one row per
month (YYYY-MM). trigger_update_isaac_budget_percentage runs update_isaac_budget_percentage() BEFORE UPDATE, computing budget_percentage = total_spent / budget_limit * 100 and setting budget_exceeded = (percentage >= 100).isaac_sim_config
isaac_sim_config
Key/value settings, seeded with 11 default rows (
ami_id, security_group_id, subnet_id, instance_type=g6e.4xlarge, auto_shutdown_hours=2, max_spot_price=1.50, use_spot_instances=true, monthly_budget_limit=320, max_concurrent_instances=3, user_daily_limit_hours=4, user_monthly_limit_hours=20). A trigger_update_isaac_config_updated_at bumps updated_at.Migration chain
Migrations live inmigrations/versions/ and form a single linear chain from the root cd0db35fec77 (down_revision = None) to the current head r3m4n5o6p7q8. Revision IDs mix random hashes (cd0db35fec77, d741a9eb5162) with hand-authored sequential letters (i4d5e6f7g8h9 … r3m4n5o6p7q8) — and the interleaving is non-obvious (e.g. 55cc964f0c63.down = m8h9i0j1k2l3), so always trust down_revision, not the filename ordering.
migrations/env.py pulls target metadata from the live Flask app (current_app.extensions['migrate'].db) and suppresses empty autogenerate diffs. src/models/__init__.py is empty — models register on db.metadata only as a side effect of services importing them. A new model not imported by any loaded module is invisible to both flask db migrate autogenerate and create_all(). Connection settings (DB_USERNAME/PASSWORD/IP/NAME, pool config) live in Migrations, DB Connection & Dev Mode and Gateway Environment Variables.Gotchas a future editor must preserve
drone.type is DB-nullable despite the model saying nullable=False
drone.type is DB-nullable despite the model saying nullable=False
In
src/models/drone.py, type = db.Column(Enum(DroneType, nullable=False)) passes nullable=False to the Enum type, not the Column. The initial migration therefore created drone.type as nullable=True. vehicle_type has the same misplaced kwarg, but migration e63c68f1d55f added its column as NOT NULL — so in production vehicle_type is NOT NULL while type is nullable, and db.create_all() would make both nullable. Don’t assume NOT NULL on either without checking the migration.drone.mac is deprecated but still NOT NULL + UNIQUE
drone.mac is deprecated but still NOT NULL + UNIQUE
The column is annotated
TODO:FIXME: DEPRECATE! yet remains NOT NULL, UNIQUE. Every drone insert must supply a unique mac value. For SITL drones the service sets ip == mac == container_name.Billing FK ondelete diverges: model omits it, migration CASCADEs
Billing FK ondelete diverges: model omits it, migration CASCADEs
In
src/models/subscription.py, subscription.user_id, payment.user_id, and payment.subscription_id omit ondelete, so db.create_all() (dev) would make them RESTRICT. But production migration h3c4d5e6f7g8 builds them as CASCADE, CASCADE, and SET NULL respectively — so deleting a user in production cascade-deletes their subscription and payments, consistent with the rest of the user tree. Don’t assume RESTRICT here without checking the migration. Separately, drone.mission_id has no ondelete in either the model or the migration and genuinely needs manual cleanup on mission delete.Timezone inconsistency across models
Timezone inconsistency across models
mission_execution, calendar_event(_occurrence), subscription.created_at/updated_at, and asset.created use tz-aware DateTime(timezone=True) with datetime.now(timezone.utc). But asset.modified, subscription.current_period_start/end, and the Isaac Sim DateTime columns are naive. Comparisons that mix naive and aware timestamps will raise or silently misbehave.asset.status default is a raw string, not an enum member
asset.status default is a raw string, not an enum member
status = db.Column(Enum(AssetStatus), ... default=AssetStatus.pending.value) uses the string 'pending' rather than the member AssetStatus.pending. It works only because the member name equals its value; it’s inconsistent with every other enum default in the codebase.Related reference
HTTP & Socket.IO API
The endpoints that read and write these tables.
Environment Variables
DB_*, APP_ENVIRONMENT, and the rest of the config surface.Redis & MAVLink Map
The non-SQL channels and the MAVLink port map.

