demo version prepped

This commit is contained in:
2026-04-01 12:40:40 -04:00
parent d44e5f0ad1
commit ed319a6423
62 changed files with 8362 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
import database as db
import sqlite3
from datetime import datetime, timezone
import random
import asyncio
import time
from .sim_config import ALARM_TEXTS, TICKET_TEXTS, ASSIGNEES, SITE_IDS
from logger import get_logger
logger = get_logger(__name__)
next_alarm_cleanup_at: float = 0.0
next_incident_cleanup_at: float = 0.0
# TODO: Simulate weather / natural disasters
async def ticket_simulator(interval=60):
# Generates tickets off of alarming sites.
while True:
conn = db.connect_to_db()
try:
logger.debug("Simulating tickets...")
cur = conn.cursor()
now = int(datetime.now(timezone.utc).timestamp())
# Find sites that have unassigned simulator alarms. Will be used to assigned to bot
rows = cur.execute("""
SELECT DISTINCT site_id FROM alarms
WHERE incident_id IS NULL AND created_by = 'simulator'
""").fetchall()
if not rows:
logger.debug("No unassigned alarms — skipping ticket creation.")
conn.close()
await asyncio.sleep(interval)
continue
site_id = random.choice(rows)[0]
text = random.choice(TICKET_TEXTS)
assigned_to = random.choice(ASSIGNEES)
# NOTE: Get the worse severity alarm that isn't assigned out.
worst = cur.execute("""
SELECT MIN(severity) FROM alarms
WHERE site_id = ? AND incident_id IS NULL
""", (site_id,)).fetchone()[0]
severity = worst or random.randint(1, 5)
cur.execute("""
INSERT INTO incidents (text, severity, site_id, created, updated, assigned_to, status, created_by)
VALUES (?, ?, ?, ?, ?, ?, 'active', 'simulator')
""", (text, severity, site_id, now, now, assigned_to))
incident_id = cur.lastrowid
cur.execute("""
UPDATE alarms SET incident_id = ?, updated = ?
WHERE site_id = ? AND incident_id IS NULL
""", (incident_id, now, site_id))
conn.commit()
# Scan for idle robots for tickets
idle = cur.execute("""
SELECT * FROM robots WHERE current_incident_id IS NULL ORDER BY RANDOM() LIMIT 1
""").fetchone()
# when found, assign them a ticket.
# TODO: Make this smarter, maybe path or proximity. right now they fly everywhere.
if idle:
site = cur.execute("SELECT lat, lon FROM cellsites WHERE id = ?", (site_id,)).fetchone()
cur.execute("""
UPDATE robots SET current_incident_id = ?, target_lat = ?, target_lon = ?, updated = ?
WHERE id = ?
""", (incident_id, site[0], site[1], now, idle[0]))
cur.execute("""
UPDATE incidents SET assigned_to = ? WHERE id = ?
""", (idle[1], incident_id))
conn.commit()
logger.info(f"Robot {idle[1]} dispatched to site {site_id}")
conn.close()
logger.info(f"Ticket created — INC #{incident_id} site={site_id} sev={severity}")
except Exception as e:
logger.error(f"Ticket simulator error: {e}", exc_info=True)
finally:
conn.close()
await asyncio.sleep(interval)
async def alarm_simulator(interval=60):
# Generates random alarms off sites
while True:
conn = db.connect_to_db()
try:
logger.debug("Simulating alarms...")
cur = conn.cursor()
now = int(datetime.now(timezone.utc).timestamp())
site_id = random.choice(SITE_IDS)
severity = random.randint(1, 5)
text = random.choice(ALARM_TEXTS)
status = "active"
created_by = "simulator"
cur.execute("""
INSERT INTO alarms (text, severity, site_id, created, updated, status, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (text, severity, site_id, now, now, status, created_by))
conn.commit()
conn.close()
logger.debug(f"Alarm created — site={site_id} sev={severity}")
except Exception as e:
logger.error(f"Alarm simulator error: {e}", exc_info=True)
finally:
conn.close()
await asyncio.sleep(interval)
async def cleanup_incidents(interval=300, max_age_seconds=3600):
global next_incident_cleanup_at
while True:
next_incident_cleanup_at = time.time() + interval
await asyncio.sleep(interval)
conn = db.connect_to_db()
try:
cur = conn.cursor()
cutoff = int(datetime.now(timezone.utc).timestamp()) - max_age_seconds
cur.execute("DELETE FROM incidents WHERE created_by = 'simulator' AND created < ?", (cutoff,))
# NOTE: robots need to be reset or else will have deleted tickets
cur.execute("""
UPDATE robots SET
current_incident_id = NULL,
current_site_id = NULL,
target_lat = base_lat,
target_lon = base_lon
WHERE current_incident_id IS NOT NULL
AND current_incident_id NOT IN (SELECT id FROM incidents)
""")
affected = cur.rowcount
if affected:
logger.info(f"Reset {affected} robots orphaned by incident cleanup")
conn.commit()
conn.close()
logger.info(f"Incident cleanup done — removed records older than {max_age_seconds}s")
except Exception as e:
logger.error(f"Incident cleanup error: {e}", exc_info=True)
finally:
conn.close()
async def cleanup_alarms(interval=300, max_age_seconds=3600):
global next_alarm_cleanup_at
while True:
next_alarm_cleanup_at = time.time() + interval
await asyncio.sleep(interval)
conn = db.connect_to_db()
try:
cur = conn.cursor()
cutoff = int(datetime.now(timezone.utc).timestamp()) - max_age_seconds
cur.execute("DELETE FROM alarms WHERE created_by = 'simulator' AND created < ?", (cutoff,))
# NOTE: Here we clear any oprhaned incident_ids off the alarm after deleted.
cur.execute("""
UPDATE alarms SET incident_id = NULL
WHERE incident_id IS NOT NULL
AND incident_id NOT IN (SELECT id FROM incidents)
""")
conn.commit()
conn.close()
logger.info(f"Alarm cleanup done — removed records older than {max_age_seconds}s")
except Exception as e:
logger.error(f"Alarm cleanup error: {e}", exc_info=True)
finally:
conn.close()
async def robot_simulator(interval=3):
"""
This is the robot sim that will fly around and "fix" sites after being assigned tickets.
The robot has a fixed time to "work" and then will close the ticket and move on.
"""
while True:
conn = db.connect_to_db()
try:
conn.row_factory = sqlite3.Row
cur = conn.cursor()
now = int(time.time())
in_transit = cur.execute("""
SELECT r.id, r.lat, r.lon, r.target_lat, r.target_lon,
r.current_incident_id, i.site_id as target_site_id
FROM robots r
JOIN incidents i ON r.current_incident_id = i.id
WHERE r.current_incident_id IS NOT NULL
AND r.current_site_id IS NULL
AND r.target_lat IS NOT NULL
""").fetchall()
STEP = 0.2
THRESHOLD = 0.05
for robot in in_transit:
dlat = robot['target_lat'] - robot['lat']
dlon = robot['target_lon'] - robot['lon']
dist = (dlat**2 + dlon**2) ** 0.5
if dist < THRESHOLD:
cur.execute("""
UPDATE robots SET lat = ?, lon = ?, current_site_id = ?, updated = ?
WHERE id = ?
""", (robot['target_lat'], robot['target_lon'], robot['target_site_id'], now, robot['id']))
logger.info(f"Robot {robot['id']} arrived at site {robot['target_site_id']}")
else:
factor = min(STEP / dist, 1.0)
new_lat = robot['lat'] + dlat * factor
new_lon = robot['lon'] + dlon * factor
cur.execute("""
UPDATE robots SET lat = ?, lon = ?, updated = ? WHERE id = ?
""", (new_lat, new_lon, now, robot['id']))
WORK_DURATION = 60 # seconds on site before closing ticket
on_site = cur.execute("""
SELECT id, current_incident_id, current_site_id, base_lat, base_lon, updated
FROM robots
WHERE current_site_id IS NOT NULL
AND current_incident_id IS NOT NULL
""").fetchall()
for robot in on_site:
time_on_site = now - robot['updated']
if time_on_site < WORK_DURATION:
continue
# Close the incident <- not delete
cur.execute("""
UPDATE incidents SET status = 'closed', updated = ? WHERE id = ?
""", (now, robot['current_incident_id']))
# End assignment and return, for more work
cur.execute("""
UPDATE robots SET
current_incident_id = NULL,
current_site_id = NULL,
target_lat = base_lat,
target_lon = base_lon,
updated = ?
WHERE id = ?
""", (now, robot['id']))
logger.info(f"Robot {robot['id']} finished INC #{robot['current_incident_id']} at site {robot['current_site_id']} — returning to base")
conn.commit()
except Exception as e:
logger.error(f"Robot simulator error: {e}", exc_info=True)
finally:
conn.close()
await asyncio.sleep(interval)

View File

@@ -0,0 +1,30 @@
TICKET_TEXTS: list[str] = [
"Investigate signal degradation at site",
"Replace faulty hardware unit",
"Perform antenna realignment",
"Inspect backhaul connection",
"Review capacity thresholds",
"Check power supply unit",
"Diagnose interference source",
"Schedule maintenance window"
]
ASSIGNEES: list[str] = [
"r2d2", "c3po", "hal9000", "glados", "tars",
"case", "wall_e", "data", "bishop", "ash"
]
SITE_IDS: list[int] = [
30859, 31387, 31388, 31389, 31390, 31391, 42041, 42913, 43330, 44117, 44900, 45124, 45356, 46716, 46774, 46780, 46781, 46782, 46827, 47084, 47146, 47157, 47164, 47165, 47240, 48329, 51843, 52032, 53375, 53573, 54492, 56076, 56861, 57316, 57319, 57424, 57564, 57601, 57616, 57805, 57958, 57977, 57978, 58023, 58024, 58033, 58741, 58808, 58874, 58875, 58876, 58877, 58878, 58879, 58880, 58881, 58882, 58883, 58932, 59232, 59258, 59297, 59453, 59566, 59656, 59659, 59865, 59884, 59887, 60402, 60427, 61121, 61122, 61353, 61823, 62029, 62030, 62149, 62210, 62217, 62299, 62463, 62641, 63249, 63295, 64077, 64202, 64361, 64384, 64385, 65709, 65949, 65950, 66608, 67052, 67096, 67097, 68042, 68695, 68696, 68697, 68698, 68699, 68700, 68726, 68779, 69597, 69872, 69873, 70161, 70233, 70234, 70235, 70236, 70237, 70308, 70550, 71094, 71673, 71674, 71675, 71676, 71677, 71678, 71679, 71680, 71969, 72006, 72233, 72316, 72513, 72719, 72761, 72762, 72763, 72764, 73114, 73125, 73141, 73142, 73143, 73213, 73413, 73414, 74068, 74492, 74584, 75083, 75693, 76850, 77873, 78320, 78435, 78644, 79086, 79292, 79305, 79658, 80752, 82021, 82416, 82454, 83444, 83706, 83793, 84308, 84499, 84656, 85437, 85438, 86094, 86424, 86576, 87114, 87115, 87663, 87839, 88598, 88599, 88600, 88782, 88783, 88877, 88878, 89112, 89113, 89266, 89285, 89286, 89287, 89288, 89711, 89712, 90471, 90472, 90473, 90474, 90475, 90476, 90477, 90527, 90528, 90695, 90696, 90697, 90698, 91013, 91159, 91160, 91586, 92163, 93413, 93414, 93415, 93416, 93503, 94041, 94042, 94046, 94047, 94194, 94285, 95112, 95113, 95114, 95115, 95116, 95117, 95118, 95119, 95120, 95121, 95122, 95123, 95124, 95125, 95126, 95127, 95128, 95129, 95130, 95131, 95132, 95133, 95134, 95135, 95136, 95137, 95138, 95139, 96041, 96723, 96808, 97416, 97503, 97505, 97557, 97558, 97594, 97596, 97602, 97853, 97862, 97873, 98150, 98164, 98165, 98208, 98209, 98210, 98211, 98212, 98226, 98227, 98228, 98229, 98602, 98662, 98663, 98664, 98665, 98666, 98667, 98668, 98669, 98670, 98671, 98672, 98673, 98674, 98675, 98676, 98677, 98678, 98679, 98680, 98681, 98760, 98772, 98773, 98774, 98775, 98776, 98901, 98902, 98903, 98904, 98927, 99525, 99528, 99529, 99531, 99758, 99965, 100356, 100568, 100570, 100614, 100615, 101221, 101398, 101469, 101470, 101471, 101602, 101720, 101790, 101814, 102059, 102065, 102108, 102109, 102110, 102163, 102255, 102805, 104288, 104289, 104303, 104307, 104960, 105551, 105552, 105554, 105555, 105620, 105621, 106103, 107003, 107163, 107164, 107165, 107166, 107167, 107168, 109188, 110321, 110322, 110323, 110324, 111233, 111462, 112488, 116358, 116359, 116360, 116361, 116362, 116363, 116364, 116365, 116366, 116367, 116368, 116369, 116370, 116371, 116372, 116373, 116374, 116375, 116376, 116377, 116378, 116379, 116380, 116381, 116382, 116383, 116384, 116385, 116386, 116387, 116388, 116389, 116390, 116391, 116392, 116393, 116394, 116395, 116396, 116397, 116398, 116399, 116400, 116401, 116402, 116403, 116404, 116405, 116406, 116407, 116408, 116409, 116410, 116411, 116412, 116413, 116414, 116415, 116416, 116417, 116418, 116419, 116420, 116421, 116422, 116423, 116424, 116425, 116426, 116427, 116428, 116429, 116430, 116431, 116432, 116433, 116434, 116435, 116436, 116437, 116438, 116439, 116440, 116441, 116442, 116443, 116444, 116445, 116446, 116447, 116448, 116449, 116450, 116451, 116452, 116453, 116454, 116455, 116456, 116457, 116458, 116459, 116460, 116461, 116462, 116463, 116464, 116465, 116466, 116467, 116468, 116469, 116470, 116471, 116472, 116473, 116474, 116475, 116476, 116477, 116478, 116479, 116480, 116481, 116482, 116483, 116484, 116485, 116486, 116487, 116488, 116489, 116490, 116491, 116492, 116493, 116494
]
ALARM_TEXTS: list[str] = [
"Signal degradation detected",
"High interference on frequency",
"Connection timeout",
"Hardware fault reported",
"Capacity threshold exceeded",
"Power supply warning",
"Antenna misalignment detected",
"Backhaul link degraded"
]