""" Historical data seeder — runs once at startup. Generates SEED_MINUTES of backdated readings so the dashboard has chart history from the moment the app first loads. """ import asyncio import math import os import random from datetime import datetime, timezone, timedelta import asyncpg DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://dcim:dcim_pass@localhost:5432/dcim") SEED_MINUTES = int(os.getenv("SEED_MINUTES", "30")) SITE_ID = "sg-01" INTERVAL_MINS = 5 # one data point every 5 minutes ROOMS = [ {"room_id": "hall-a", "racks": [f"SG1A01.{i:02d}" for i in range(1, 21)] + [f"SG1A02.{i:02d}" for i in range(1, 21)]}, {"room_id": "hall-b", "racks": [f"SG1B01.{i:02d}" for i in range(1, 21)] + [f"SG1B02.{i:02d}" for i in range(1, 21)]}, ] CRAC_UNITS = [("hall-a", "crac-01"), ("hall-b", "crac-02")] UPS_UNITS = ["ups-01", "ups-02"] LEAK_SENSORS = ["leak-01"] async def seed() -> None: # Strip +asyncpg prefix if present (asyncpg needs plain postgresql://) url = DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://") print(f"Seeder: connecting to database...") conn = await asyncpg.connect(url) try: count = await conn.fetchval( "SELECT COUNT(*) FROM readings WHERE site_id = $1", SITE_ID ) if count > 0: print(f"Seeder: {count} rows already exist — skipping.") return print(f"Seeder: generating {SEED_MINUTES} minutes of history...") rows: list[tuple] = [] now = datetime.now(timezone.utc) for minutes_ago in range(SEED_MINUTES, -1, -INTERVAL_MINS): t = now - timedelta(minutes=minutes_ago) hour = t.hour day_factor = 1.0 + 0.04 * math.sin(math.pi * (hour - 6) / 12) biz_factor = (1.0 + 0.25 * math.sin(math.pi * max(0, hour - 8) / 12) if 8 <= hour <= 20 else 0.85) for room in ROOMS: room_id = room["room_id"] for rack_id in room["racks"]: num = int(rack_id[-2:]) base_temp = 21.5 + num * 0.15 base_load = 2.0 + (num % 5) * 0.8 base_id = f"{SITE_ID}/{room_id}/{rack_id}" temp = base_temp * day_factor + random.gauss(0, 0.2) humidity = 44.0 + random.gauss(0, 1.0) load = base_load * biz_factor + random.gauss(0, 0.1) rows += [ (t, f"{base_id}/temperature", "temperature", SITE_ID, room_id, rack_id, round(temp, 2), "°C"), (t, f"{base_id}/humidity", "humidity", SITE_ID, room_id, rack_id, round(humidity, 1), "%"), (t, f"{base_id}/power_kw", "power_kw", SITE_ID, room_id, rack_id, round(max(0.5, load), 2), "kW"), ] for _, crac_id in CRAC_UNITS: base = f"{SITE_ID}/cooling/{crac_id}" rows += [ (t, f"{base}/supply_temp", "cooling_supply", SITE_ID, None, None, round(17.5 + random.gauss(0, 0.2), 2), "°C"), (t, f"{base}/return_temp", "cooling_return", SITE_ID, None, None, round(28.0 + random.gauss(0, 0.3), 2), "°C"), (t, f"{base}/fan_pct", "cooling_fan", SITE_ID, None, None, round(62.0 + random.gauss(0, 2.0), 1), "%"), ] for ups_id in UPS_UNITS: base = f"{SITE_ID}/power/{ups_id}" rows += [ (t, f"{base}/charge_pct", "ups_charge", SITE_ID, None, None, round(94.0 + random.gauss(0, 0.5), 1), "%"), (t, f"{base}/load_pct", "ups_load", SITE_ID, None, None, round(63.0 + random.gauss(0, 1.0), 1), "%"), (t, f"{base}/runtime_min", "ups_runtime", SITE_ID, None, None, round(57.0 + random.gauss(0, 1.0), 0), "min"), ] for leak_id in LEAK_SENSORS: rows.append((t, f"{SITE_ID}/leak/{leak_id}", "leak", SITE_ID, None, None, 0.0, "")) await conn.executemany(""" INSERT INTO readings (recorded_at, sensor_id, sensor_type, site_id, room_id, rack_id, value, unit) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) """, rows) print(f"Seeder: inserted {len(rows)} readings. Done.") finally: await conn.close() if __name__ == "__main__": asyncio.run(seed())