"""Populate an empty database with representative data. Runs on startup when CX_SEED_DEMO is on, and only when the relevant table is empty, so it never overwrites real work. """ from __future__ import annotations import datetime as dt import json import os import random from pathlib import Path from sqlalchemy import select from sqlalchemy.orm import Session from ..models import (AppSetting, Handover, HandoverItem, HandoverStatus, ItemState, RunpodColour, RunpodEventType, RunpodHost, ShiftName) from ..runpod.service import record_event from .data import HANDOVER, RUNPOD_BOARD, RUNPOD_COLOURS # Optional: the monitoring script's own exports, if they have been mounted in. RUNPOD_EXPORT_DIR = os.environ.get("CX_RUNPOD_EXPORT_DIR", "/seed/runpod") def seed_handover(db: Session) -> str: if db.scalar(select(Handover).limit(1)): return "handover: already present, left alone" row = Handover( shift_date=dt.date.fromisoformat(HANDOVER["shift_date"]), shift=ShiftName(HANDOVER["shift"]), handing_to=HANDOVER["handing_to"], team_members=HANDOVER["team_members"], significant_issues_checked=HANDOVER["significant_issues_checked"], hubspot_checked=HANDOVER["hubspot_checked"], total_open_tickets=HANDOVER["total_open_tickets"], member_checks=HANDOVER["member_checks"], other_comments=HANDOVER["other_comments"], status=HandoverStatus.DRAFT, ) for position, item in enumerate(HANDOVER["items"]): row.items.append(HandoverItem( position=position, title=item["title"], zendesk_tickets=item.get("zendesk_tickets", ""), jira_key=item.get("jira_key", ""), jira_status=item.get("jira_status", ""), body=item.get("body", ""), links=item.get("links", []), state=ItemState(item.get("state", "in_progress")), remove_at_end_of_shift=item.get("remove_at_end_of_shift", False), first_raised_on=row.shift_date, )) db.add(row) db.commit() return f"handover: seeded {row.shift.value} {row.shift_date} with {len(row.items)} items" def _load_export(name: str): path = Path(RUNPOD_EXPORT_DIR) / name if not path.is_file(): return None try: return json.loads(path.read_text()) except (OSError, json.JSONDecodeError): return None def seed_runpod(db: Session) -> str: if db.scalar(select(RunpodHost).limit(1)): return "runpod: already present, left alone" now = dt.datetime.now(dt.timezone.utc) rng = random.Random(20260806) # deterministic, so the demo looks the same each time hosts: dict[str, RunpodHost] = {} # 1. The board rows from the handover, with their CX state. for entry in RUNPOD_BOARD: host = RunpodHost( machine_id=entry["machine_id"], name=entry["name"], listed=False, gpu_reserved=0, gpu_total=8, gpu_type="RTX 5090" if "5090" in entry["name"] else "RTX 4090", data_center="NO1" if entry["name"].startswith("no1") else "CA1", colour=RunpodColour(entry.get("colour", "white")), zendesk_ticket=entry.get("zendesk_ticket", ""), jira_key=entry.get("jira_key", ""), jira_status=entry.get("jira_status", ""), last_error=entry.get("last_error", ""), next_steps=entry.get("next_steps", ""), unlisted_at=now - dt.timedelta(hours=rng.randint(6, 96)), ) db.add(host) db.flush() hosts[host.machine_id] = host # 2. Anything else the monitoring script had recorded. hosts_db = _load_export("hosts_db.json") or {} jira_db = _load_export("jira_db.json") or {} for machine_id, record in list(hosts_db.items())[:200]: if machine_id in hosts: continue jira = jira_db.get(machine_id, {}) host = RunpodHost( machine_id=machine_id, name=str(record.get("name") or ""), listed=bool(record.get("listed", True)), gpu_reserved=int(record.get("gpuReserved") or 0), gpu_total=8, historic_count=int(jira.get("historic_count") or 0), jira_key=jira.get("jira_issue_key") or "", jira_status=jira.get("jira_status") or "", ) if not host.listed: host.unlisted_at = now - dt.timedelta(hours=rng.randint(2, 240)) db.add(host) db.flush() hosts[machine_id] = host # 3. A plausible history, so the timeline and ranking have something to show. for host in hosts.values(): rounds = max(host.historic_count, 1 if not host.listed else 0) cursor = now - dt.timedelta(days=min(45, 3 + rounds * 4)) for _ in range(min(rounds, 12)): cursor += dt.timedelta(hours=rng.randint(8, 72)) if cursor >= now: break record_event(db, host, RunpodEventType.UNLISTED, actor="runpod", detail="Unlisted automatically after a critical error", error_hint=host.last_error or "gpu health check failed", gpu_reserved=rng.choice([0, 1, 2, 8])) host.events[-1].occurred_at = cursor host.unlist_count += 1 cursor += dt.timedelta(hours=rng.randint(4, 48)) if cursor >= now or not host.listed: continue operator = rng.choice(["parham.monfared@nexgencloud.com", "luis.sarabando@nexgencloud.com", "kheano.martinez@nexgencloud.com"]) record_event(db, host, RunpodEventType.LISTED, actor=operator, detail="Relisted after burn-in passed", zendesk_ticket=host.zendesk_ticket) host.events[-1].occurred_at = cursor if not host.listed and host.last_error: record_event(db, host, RunpodEventType.UNLISTED, actor="runpod", detail="Current unlisting", error_hint=host.last_error, zendesk_ticket=host.zendesk_ticket, jira_key=host.jira_key) host.events[-1].occurred_at = host.unlisted_at or now host.unlist_count += 1 db.merge(AppSetting(key="runpod_colours", value=RUNPOD_COLOURS)) db.commit() unlisted = sum(1 for h in hosts.values() if not h.listed) return f"runpod: seeded {len(hosts)} machines ({unlisted} unlisted) with history" def run(db: Session) -> list[str]: return [seed_handover(db), seed_runpod(db)]