src/models/ (see Database Schema Overview), the Isaac Sim EC2-instance accounting objects — 4 tables, 3 views, 2 PL/pgSQL functions and 3 triggers — exist only as hand-written SQL inside a single Alembic migration and are read/written with a raw psycopg2 cursor, never through the ORM.
This page documents those objects, why they diverge from the ORM, the (currently dormant) code that uses them, and the exact set of database objects a DB-layer refactor must recreate.
These tables track NVIDIA Isaac Sim runs on a cloud EC2 GPU instance (
g6e family) used to simulate drones. Nothing here runs on an aircraft.Everything lives in one migration
All Isaac Sim database objects are created by exactly one migration and are never altered by a later one:db.Model for any of these objects:
db.create_all()will never create them. The dev-mode shortcut (APP_ENVIRONMENT=dev+python main.py) only reflects ORM models, so a dev database bootstrapped that way is missing the entire Isaac Sim schema — tables, views, functions and triggers. See Migrations, DB Connection & Dev Mode. Onlyflask db upgradeprovisions them.- Alembic autogenerate cannot see them.
flask db migratebuilds diffs fromdb.metadata, which knows nothing about these tables. Any future change to the Isaac Sim schema must be hand-authored raw SQL (op.execute(...)/op.create_table(...)), exactly likee85b13d42b08— you cannot autogenerate it. - Views, functions and triggers have no ORM equivalent at all. SQLAlchemy models cannot express
CREATE VIEW,CREATE FUNCTIONorCREATE TRIGGER, so even if models were added for the tables, the view/function/trigger layer would still be raw SQL.
src/models/__init__.py is empty; ORM models register on db.metadata only as a side effect of a service importing them. The Isaac Sim tables never had a model to import, so they were provisioned as raw DDL from day one rather than being an accidental omission.Entity model
The 4 tables
isaac_sim_instances
One row per launched Isaac Sim EC2 instance. user_id FK is ON DELETE CASCADE; drone_id FK is ON DELETE SET NULL.
Also indexed
created_at DESC (idx_isaac_instances_created). updated_at is bumped by a trigger (below).
isaac_sim_usage_tracking
Immutable session-history log; one row appended per completed session. Both FKs are ON DELETE CASCADE.
isaac_sim_budget
One row per calendar month (month = YYYY-MM, UNIQUE). Rolls up spend across all users for cost-cap enforcement.
isaac_sim_config
Key/value settings, seeded with 11 rows on migration via INSERT … ON CONFLICT (config_key) DO NOTHING.
The 3 views
Read them exactly like tables, e.g.:
The 2 functions and 3 triggers
Two PL/pgSQL functions, wired to threeBEFORE UPDATE triggers:
The only consumer: InstanceSchedulerService
Every read and write of these tables goes throughsrc/service/instance_scheduler_service.py, which holds a raw psycopg2 connection (self.db) and drives it directly — self.db.cursor(), cursor.execute(sql, params), self.db.commit(), self.db.rollback(). It never touches the SQLAlchemy db from src/connector/db_connection.py.
Its intended behaviour is an async loop that runs every 60 s:
The service also exposes extend_instance_runtime(instance_id, hours) (auto_shutdown_time += INTERVAL) and get_scheduler_stats() (state counts + upcoming-shutdown count). Cost is estimated in code at ~$0.90/hour plus $0.10 storage (instance_scheduler_service.py:206, :271).
Timezone mismatch (latent)
The table columns are naiveDateTime() (no timezone=True), so psycopg2 returns naive datetime objects, but the scheduler compares them against datetime.now(timezone.utc) (aware) at instance_scheduler_service.py:136 and subtracts them at :270. Mixing aware and naive datetimes raises TypeError in Python. This is masked today only because the service is not running; anyone wiring it in must normalize timezones (or make the columns TIMESTAMPTZ) first.
What a refactor must preserve
If you migrate the data layer (new ORM, schema rebuild, or “clean up dead code”), treat the Isaac Sim objects as a unit. Recreating only the tables silently drops the views and the budget math.1
All 4 tables with their exact FK actions
isaac_sim_instances (user_id CASCADE, drone_id SET NULL), isaac_sim_usage_tracking (both CASCADE), isaac_sim_budget, isaac_sim_config — plus every index (idx_isaac_instances_*, idx_isaac_usage_*, idx_isaac_budget_month).2
The 3 views verbatim
active_isaac_instances, isaac_monthly_usage_summary, isaac_budget_status. They quote "user" (reserved word) — keep the quoting.3
Both functions and all 3 triggers
update_isaac_budget_percentage() + update_updated_at_column(), and the three BEFORE UPDATE triggers. Without the budget trigger, budget_percentage/budget_exceeded are never populated by anything.4
The 11 seed rows in isaac_sim_config
Re-seed with
ON CONFLICT (config_key) DO NOTHING so a re-run is idempotent.5
Raw-SQL migration authoring
Any change here is invisible to
flask db migrate; write DDL by hand with op.execute()/op.create_table(), and provide a matching downgrade() that drops in reverse dependency order (views → triggers → functions → tables), mirroring e85b13d42b08.The migration’s own
downgrade() is the canonical teardown order: DROP VIEW (3) → DROP TRIGGER (3) → DROP FUNCTION (2) → DROP TABLE (config, budget, usage_tracking, instances). Follow it if you ever hand-write a replacement.Related pages
Database Schema Overview
The ORM-backed tables these Isaac Sim objects deliberately bypass.
Migrations, DB Connection & Dev Mode
The Alembic chain, DB URI/pool config, and why db.create_all() misses these objects.
Platform Services (incl. Isaac Sim)
IsaacSimService, the boto3 single-instance manager behind the live routes.
Isaac Sim API
The /api/v1/isaac-sim/* HTTP endpoints (which do not use these tables).

