import os import sqlalchemy from databases import Database from sqlalchemy import MetaData, Table, Column, Integer, String, Float, DateTime, text # Environment: Postgres/TimescaleDB connection URL DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:password@localhost:5432/dereth") # Async database client database = Database(DATABASE_URL) # Metadata for SQLAlchemy Core metadata = MetaData() # Telemetry events hypertable schema telemetry_events = Table( "telemetry_events", metadata, Column("id", Integer, primary_key=True), Column("character_name", String, nullable=False), Column("char_tag", String, nullable=True), Column("session_id", String, nullable=False, index=True), Column("timestamp", DateTime(timezone=True), nullable=False, index=True), Column("ew", Float, nullable=False), Column("ns", Float, nullable=False), Column("z", Float, nullable=False), Column("kills", Integer, nullable=False), Column("kills_per_hour", Float, nullable=True), Column("onlinetime", String, nullable=True), Column("deaths", Integer, nullable=False), Column("rares_found", Integer, nullable=False), Column("prismatic_taper_count", Integer, nullable=False), Column("vt_state", String, nullable=True), # New telemetry metrics Column("mem_mb", Float, nullable=True), Column("cpu_pct", Float, nullable=True), Column("mem_handles", Integer, nullable=True), Column("latency_ms", Float, nullable=True), ) # Persistent kill statistics per character # Persistent kill statistics per character char_stats = Table( "char_stats", metadata, Column("character_name", String, primary_key=True), Column("total_kills", Integer, nullable=False, default=0), ) # Rare event tracking: total and per-session counts rare_stats = Table( "rare_stats", metadata, Column("character_name", String, primary_key=True), Column("total_rares", Integer, nullable=False, default=0), ) rare_stats_sessions = Table( "rare_stats_sessions", metadata, Column("character_name", String, primary_key=True), Column("session_id", String, primary_key=True), Column("session_rares", Integer, nullable=False, default=0), ) # Spawn events: record mob spawns for heatmapping spawn_events = Table( "spawn_events", metadata, Column("id", Integer, primary_key=True), Column("character_name", String, nullable=False), Column("mob", String, nullable=False), Column("timestamp", DateTime(timezone=True), nullable=False, index=True), Column("ew", Float, nullable=False), Column("ns", Float, nullable=False), Column("z", Float, nullable=False), ) async def init_db_async(): """Create tables and enable TimescaleDB hypertable for telemetry_events.""" # Create tables in Postgres engine = sqlalchemy.create_engine(DATABASE_URL) metadata.create_all(engine) # Enable TimescaleDB extension and convert telemetry_events to hypertable # Use a transactional context to ensure DDL statements are committed with engine.begin() as conn: # Enable TimescaleDB extension (may already exist) try: conn.execute(text("CREATE EXTENSION IF NOT EXISTS timescaledb")) except Exception as e: print(f"Warning: failed to create extension timescaledb: {e}") # Create hypertable for telemetry_events, skip default indexes to avoid collisions try: conn.execute(text( "SELECT create_hypertable('telemetry_events', 'timestamp', \ if_not_exists => true, create_default_indexes => false)" )) except Exception as e: print(f"Warning: failed to create hypertable telemetry_events: {e}")