first commit

This commit is contained in:
mega 2026-03-19 11:32:17 +00:00
commit 4b98219bf7
144 changed files with 31561 additions and 0 deletions

104
simulators/seed.py Normal file
View file

@ -0,0 +1,104 @@
"""
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())