import numpy as np, pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from ms_charts import COLORS, PLOTLY_CONFIG
import plotly.graph_objects as go
from plotly.subplots import make_subplots
rng = np.random.default_rng(42)
H = 24 * 7 # one week of hourly readings
hours, dow = np.arange(H) % 24, (np.arange(H) // 24) % 7
work = ((hours >= 8) & (hours < 18) & (dow < 5)).astype(float) # weekday office hours
# hidden behaviour types (the model never sees these)
spec = { # count, cpu base, office-hours lift, cpu noise, memory base, memory noise
"idle": (150, 3, 0, 1.5, 14, 3),
"office": (220, 10, 45, 8.0, 30, 5),
"batch": (140, 66, 0, 6.0, 40, 5),
"memory": (130, 15, 6, 4.0, 84, 3),
"steady": (260, 30, 2, 4.0, 46, 4),
}
series, rows, kinds = [], [], []
for kind, (n, cb, lift, cn, mb, mn) in spec.items():
for _ in range(n):
c0, l, m0 = cb * rng.uniform(.8, 1.2), lift * rng.uniform(.7, 1.3), mb * rng.uniform(.88, 1.1)
spikes = (rng.random(H) < .03) * rng.uniform(10, 40, H) * (kind != "idle")
cpu = np.clip(c0 + l * work + rng.normal(0, cn, H) + spikes, 0, 100)
mem = np.clip(m0 + (6 * work if kind == "office" else 0) + rng.normal(0, mn, H), 0, 100)
series.append(cpu); kinds.append(kind)
day, night = cpu[work == 1].mean(), cpu[work == 0].mean()
rows.append(dict(avg_cpu=cpu.mean(), p95_cpu=np.percentile(cpu, 95), cpu_std=cpu.std(),
avg_mem=mem.mean(), p95_mem=np.percentile(mem, 95),
day_night_ratio=min((day + 1) / (night + 1), 8), idle_share=(cpu < 5).mean()))
features = pd.DataFrame(rows)
series, kinds = np.array(series), np.array(kinds)
order = rng.permutation(len(features)) # shuffle so nothing is in order
features, series, kinds = features.iloc[order].reset_index(drop=True), series[order], kinds[order]