Consensus

Head slot tracking, justification, finalization, and fork choice analysis for PQ Devnet clients.

This notebook examines:

  • Head slot vs current slot (how far behind each client is)
  • Justified and finalized slot progression
  • Head-to-justified, justified-to-finalized, and head-to-finalized distances
  • Fork choice reorgs
Show code
import json
from pathlib import Path

import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from IPython.display import display

# Set default renderer for static HTML output
import plotly.io as pio
pio.renderers.default = "notebook"
Show code
# Resolve devnet_id
DATA_DIR = Path("../data")

if devnet_id is None:
    devnets_path = DATA_DIR / "devnets.json"
    if devnets_path.exists():
        with open(devnets_path) as f:
            devnets = json.load(f).get("devnets", [])
        if devnets:
            devnet_id = devnets[-1]["id"]
            print(f"Using latest devnet: {devnet_id}")
    else:
        raise ValueError("No devnets.json found. Run 'just detect-devnets' first.")

DEVNET_DIR = DATA_DIR / devnet_id
print(f"Loading data from: {DEVNET_DIR}")
Loading data from: ../data/pqdevnet-20260629T1826Z
Show code
# Load devnet metadata
with open(DATA_DIR / "devnets.json") as f:
    devnets_data = json.load(f)
    devnet_info = next((d for d in devnets_data["devnets"] if d["id"] == devnet_id), None)

if devnet_info:
    print(f"Devnet: {devnet_info['id']}")
    print(f"Duration: {devnet_info['duration_hours']:.1f} hours")
    print(f"Time: {devnet_info['start_time']} to {devnet_info['end_time']}")
    print(f"Slots: {devnet_info['start_slot']} \u2192 {devnet_info['end_slot']}")
    print(f"Clients: {', '.join(devnet_info['clients'])}")
Devnet: pqdevnet-20260629T1826Z
Duration: 0.6 hours
Time: 2026-06-29T18:26:42+00:00 to 2026-06-29T19:00:57+00:00
Slots: 441 β†’ 1212
Clients: buildx_buildkit_multiarch0, zeam_0, zeam_1, zeam_10, zeam_11, zeam_12, zeam_13, zeam_14, zeam_15, zeam_16, zeam_17, zeam_18, zeam_19, zeam_2, zeam_20, zeam_21, zeam_22, zeam_23, zeam_24, zeam_25, zeam_26, zeam_27, zeam_28, zeam_29, zeam_3, zeam_30, zeam_31, zeam_4, zeam_5, zeam_6, zeam_7, zeam_8, zeam_9

Load DataΒΆ

Show code
# Unified client list from devnet metadata (includes all containers via cAdvisor)
all_clients = sorted(devnet_info["clients"])
n_cols = min(len(all_clients), 2)
n_rows = -(-len(all_clients) // n_cols)

# Load head slot data
head_df = pd.read_parquet(DEVNET_DIR / "head_slot.parquet")
head_df = head_df.groupby(["client", "metric", "timestamp"], as_index=False)["value"].max()
print(f"Head slot: {len(head_df)} records, clients: {sorted(head_df['client'].unique())}")

# Load finality data
finality_df = pd.read_parquet(DEVNET_DIR / "finality_metrics.parquet")
finality_df = finality_df.groupby(["client", "metric", "timestamp"], as_index=False)["value"].max()
print(f"Finality: {len(finality_df)} records, clients: {sorted(finality_df['client'].unique())}")
print(f"Finality metrics: {sorted(finality_df['metric'].unique())}")

# Load fork choice reorgs
reorgs_df = pd.read_parquet(DEVNET_DIR / "fork_choice_reorgs.parquet")
reorgs_df = reorgs_df.groupby(["client", "timestamp"], as_index=False)["value"].max()
print(f"Reorgs: {len(reorgs_df)} records, clients: {sorted(reorgs_df['client'].unique())}")

print(f"\nAll clients ({len(all_clients)}): {all_clients}")
Head slot: 2240 records, clients: ['zeam_0', 'zeam_1', 'zeam_10', 'zeam_11', 'zeam_12', 'zeam_13', 'zeam_14', 'zeam_15', 'zeam_16', 'zeam_17', 'zeam_18', 'zeam_19', 'zeam_2', 'zeam_20', 'zeam_21', 'zeam_22', 'zeam_23', 'zeam_24', 'zeam_25', 'zeam_26', 'zeam_27', 'zeam_28', 'zeam_29', 'zeam_3', 'zeam_30', 'zeam_31', 'zeam_4', 'zeam_5', 'zeam_6', 'zeam_7', 'zeam_8', 'zeam_9']
Finality: 2240 records, clients: ['zeam_0', 'zeam_1', 'zeam_10', 'zeam_11', 'zeam_12', 'zeam_13', 'zeam_14', 'zeam_15', 'zeam_16', 'zeam_17', 'zeam_18', 'zeam_19', 'zeam_2', 'zeam_20', 'zeam_21', 'zeam_22', 'zeam_23', 'zeam_24', 'zeam_25', 'zeam_26', 'zeam_27', 'zeam_28', 'zeam_29', 'zeam_3', 'zeam_30', 'zeam_31', 'zeam_4', 'zeam_5', 'zeam_6', 'zeam_7', 'zeam_8', 'zeam_9']
Finality metrics: ['lean_latest_finalized_slot', 'lean_latest_justified_slot']
Reorgs: 1120 records, clients: ['zeam_0', 'zeam_1', 'zeam_10', 'zeam_11', 'zeam_12', 'zeam_13', 'zeam_14', 'zeam_15', 'zeam_16', 'zeam_17', 'zeam_18', 'zeam_19', 'zeam_2', 'zeam_20', 'zeam_21', 'zeam_22', 'zeam_23', 'zeam_24', 'zeam_25', 'zeam_26', 'zeam_27', 'zeam_28', 'zeam_29', 'zeam_3', 'zeam_30', 'zeam_31', 'zeam_4', 'zeam_5', 'zeam_6', 'zeam_7', 'zeam_8', 'zeam_9']

All clients (33): ['buildx_buildkit_multiarch0', 'zeam_0', 'zeam_1', 'zeam_10', 'zeam_11', 'zeam_12', 'zeam_13', 'zeam_14', 'zeam_15', 'zeam_16', 'zeam_17', 'zeam_18', 'zeam_19', 'zeam_2', 'zeam_20', 'zeam_21', 'zeam_22', 'zeam_23', 'zeam_24', 'zeam_25', 'zeam_26', 'zeam_27', 'zeam_28', 'zeam_29', 'zeam_3', 'zeam_30', 'zeam_31', 'zeam_4', 'zeam_5', 'zeam_6', 'zeam_7', 'zeam_8', 'zeam_9']

Head Slot vs Current SlotΒΆ

Comparing each client's head slot (lean_head_slot) against the expected current slot (lean_current_slot). A gap indicates the client is falling behind.

Show code
fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

colors = {"lean_head_slot": "#636EFA", "lean_current_slot": "#EF553B"}
labels = {"lean_head_slot": "head_slot", "lean_current_slot": "current_slot"}
legend_added = set()

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = head_df[head_df["client"] == client]
    has_data = False
    for metric in ["lean_current_slot", "lean_head_slot"]:
        mdf = cdf[cdf["metric"] == metric].sort_values("timestamp")
        if mdf.empty:
            continue
        has_data = True
        label = labels[metric]
        show_legend = metric not in legend_added
        legend_added.add(metric)
        fig.add_trace(
            go.Scatter(
                x=mdf["timestamp"], y=mdf["value"],
                name=label, legendgroup=metric,
                showlegend=show_legend,
                line=dict(color=colors[metric]),
            ),
            row=row, col=col,
        )
    if not has_data:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Slot", row=row, col=col)

fig.update_layout(
    title="Head Slot vs Current Slot by Client",
    height=270 * n_rows,
)
fig.show()

Head, Justified & Finalized SlotsΒΆ

Progression of justified and finalized slots over time. With 3SF, both should track closely behind the head slot.

Show code
jf_metrics = ["lean_latest_justified_slot", "lean_latest_finalized_slot"]
jf_df = finality_df[finality_df["metric"].isin(jf_metrics)].copy()

head_only_all = head_df[head_df["metric"] == "lean_head_slot"].copy()
head_only_all["metric"] = "lean_head_slot"

combined = pd.concat([jf_df, head_only_all], ignore_index=True)

fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

colors = {
    "lean_head_slot": "#636EFA",
    "lean_latest_justified_slot": "#00CC96",
    "lean_latest_finalized_slot": "#EF553B",
}
labels = {
    "lean_head_slot": "head",
    "lean_latest_justified_slot": "justified",
    "lean_latest_finalized_slot": "finalized",
}
legend_added = set()

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = combined[combined["client"] == client]
    has_data = False
    for metric in ["lean_head_slot", "lean_latest_justified_slot", "lean_latest_finalized_slot"]:
        mdf = cdf[cdf["metric"] == metric].sort_values("timestamp")
        if mdf.empty:
            continue
        has_data = True
        show_legend = metric not in legend_added
        legend_added.add(metric)
        fig.add_trace(
            go.Scatter(
                x=mdf["timestamp"], y=mdf["value"],
                name=labels[metric], legendgroup=metric,
                showlegend=show_legend,
                line=dict(color=colors[metric]),
            ),
            row=row, col=col,
        )
    if not has_data:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Slot", row=row, col=col)

fig.update_layout(
    title="Head, Justified & Finalized Slot by Client",
    height=270 * n_rows,
)
fig.show()

Head-to-Finalized DistanceΒΆ

Total distance between head slot and finalized slot. This is the combined gap across justification and finalization, showing the overall finality lag.

finalized
justified
head
current
Show code
head_ts_hf = head_df[head_df["metric"] == "lean_head_slot"][["client", "timestamp", "value"]].rename(columns={"value": "head_slot"})
fin_ts_hf = finality_df[finality_df["metric"] == "lean_latest_finalized_slot"][["client", "timestamp", "value"]].rename(columns={"value": "finalized_slot"})
head_fin_lag = head_ts_hf.merge(fin_ts_hf, on=["client", "timestamp"], how="inner")
head_fin_lag["lag"] = head_fin_lag["head_slot"] - head_fin_lag["finalized_slot"]

fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = head_fin_lag[head_fin_lag["client"] == client].sort_values("timestamp")
    if not cdf.empty:
        fig.add_trace(
            go.Scatter(
                x=cdf["timestamp"], y=cdf["lag"],
                name=client, showlegend=False,
                line=dict(color="#636EFA"),
            ),
            row=row, col=col,
        )
    else:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Slots", row=row, col=col)

fig.update_layout(
    title="Head-to-Finalized Distance (head_slot - finalized_slot)",
    height=270 * n_rows,
)
fig.show()

Current-to-Head DistanceΒΆ

Difference between current slot and head slot. A value of 0 means the client is fully synced; higher values indicate falling behind.

finalized
justified
head
current
Show code
current_df = head_df[head_df["metric"] == "lean_current_slot"][["client", "timestamp", "value"]].rename(columns={"value": "current_slot"})
head_only = head_df[head_df["metric"] == "lean_head_slot"][["client", "timestamp", "value"]].rename(columns={"value": "head_slot"})
lag_df = current_df.merge(head_only, on=["client", "timestamp"], how="inner")
lag_df["lag"] = lag_df["current_slot"] - lag_df["head_slot"]

fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = lag_df[lag_df["client"] == client].sort_values("timestamp")
    if not cdf.empty:
        fig.add_trace(
            go.Scatter(
                x=cdf["timestamp"], y=cdf["lag"],
                name=client, showlegend=False,
                line=dict(color="#636EFA"),
            ),
            row=row, col=col,
        )
    else:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Slots behind", row=row, col=col)

fig.update_layout(
    title="Current-to-Head Distance (current_slot - head_slot)",
    height=270 * n_rows,
)
fig.show()

Head-to-Justified DistanceΒΆ

Gap between head slot and justified slot. A growing gap means the client's head is advancing but justification is not keeping up.

finalized
justified
head
current
Show code
head_ts = head_df[head_df["metric"] == "lean_head_slot"][["client", "timestamp", "value"]].rename(columns={"value": "head_slot"})
just_ts = finality_df[finality_df["metric"] == "lean_latest_justified_slot"][["client", "timestamp", "value"]].rename(columns={"value": "justified_slot"})
justification_lag = head_ts.merge(just_ts, on=["client", "timestamp"], how="inner")
justification_lag["lag"] = justification_lag["head_slot"] - justification_lag["justified_slot"]

fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = justification_lag[justification_lag["client"] == client].sort_values("timestamp")
    if not cdf.empty:
        fig.add_trace(
            go.Scatter(
                x=cdf["timestamp"], y=cdf["lag"],
                name=client, showlegend=False,
                line=dict(color="#636EFA"),
            ),
            row=row, col=col,
        )
    else:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Slots", row=row, col=col)

fig.update_layout(
    title="Head-to-Justified Distance (head_slot - justified_slot)",
    height=270 * n_rows,
)
fig.show()

Justified-to-Finalized DistanceΒΆ

Gap between justified slot and finalized slot. A growing gap means justification is advancing but finalization is stalling.

finalized
justified
head
current
Show code
fin_ts = finality_df[finality_df["metric"] == "lean_latest_finalized_slot"][["client", "timestamp", "value"]].rename(columns={"value": "finalized_slot"})
finality_lag = just_ts.merge(fin_ts, on=["client", "timestamp"], how="inner")
finality_lag["lag"] = finality_lag["justified_slot"] - finality_lag["finalized_slot"]

fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = finality_lag[finality_lag["client"] == client].sort_values("timestamp")
    if not cdf.empty:
        fig.add_trace(
            go.Scatter(
                x=cdf["timestamp"], y=cdf["lag"],
                name=client, showlegend=False,
                line=dict(color="#636EFA"),
            ),
            row=row, col=col,
        )
    else:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Slots", row=row, col=col)

fig.update_layout(
    title="Justified-to-Finalized Distance (justified_slot - finalized_slot)",
    height=270 * n_rows,
)
fig.show()

Fork Choice ReorgsΒΆ

Cumulative chain reorgs per client. Reorgs occur when the fork choice rule switches to a different chain head, often caused by late-arriving blocks or attestations.

Show code
fig = make_subplots(
    rows=n_rows, cols=n_cols,
    subplot_titles=all_clients,
    vertical_spacing=0.12 / max(n_rows - 1, 1) * 2,
    horizontal_spacing=0.08,
)

for i, client in enumerate(all_clients):
    row = i // n_cols + 1
    col = i % n_cols + 1
    cdf = reorgs_df[reorgs_df["client"] == client].sort_values("timestamp")
    if not cdf.empty:
        fig.add_trace(
            go.Scatter(
                x=cdf["timestamp"], y=cdf["value"],
                name=client, showlegend=False,
                line=dict(color="#636EFA"),
            ),
            row=row, col=col,
        )
    else:
        fig.add_trace(
            go.Scatter(x=[None], y=[None], showlegend=False, hoverinfo='skip'),
            row=row, col=col,
        )
        _n = (row - 1) * n_cols + col
        _s = "" if _n == 1 else str(_n)
        fig.add_annotation(
            text="No data available",
            xref=f"x{_s} domain", yref=f"y{_s} domain",
            x=0.5, y=0.5,
            showarrow=False,
            font=dict(size=12, color="#999"),
        )
    fig.update_yaxes(title_text="Cumulative reorgs", row=row, col=col)

fig.update_layout(
    title="Fork Choice Reorgs by Client",
    height=270 * n_rows,
)
fig.show()

SummaryΒΆ

Show code
summary_rows = []

for client in all_clients:
    row = {"Client": client}

    # Current-to-head distance
    client_lag = lag_df[lag_df["client"] == client]["lag"]
    if not client_lag.empty:
        row["Avg C-H Dist."] = f"{client_lag.mean():.1f}"
        row["Max C-H Dist."] = f"{client_lag.max():.0f}"

    # Head-to-justified distance
    client_just = justification_lag[justification_lag["client"] == client]["lag"]
    if not client_just.empty:
        row["Avg H-J Dist."] = f"{client_just.mean():.1f}"
        row["Max H-J Dist."] = f"{client_just.max():.0f}"

    # Justified-to-finalized distance
    client_fin = finality_lag[finality_lag["client"] == client]["lag"]
    if not client_fin.empty:
        row["Avg J-F Dist."] = f"{client_fin.mean():.1f}"
        row["Max J-F Dist."] = f"{client_fin.max():.0f}"

    # Head-to-finalized distance
    client_hf = head_fin_lag[head_fin_lag["client"] == client]["lag"]
    if not client_hf.empty:
        row["Avg H-F Dist."] = f"{client_hf.mean():.1f}"
        row["Max H-F Dist."] = f"{client_hf.max():.0f}"

    # Reorgs
    client_reorgs = reorgs_df[reorgs_df["client"] == client]["value"]
    if not client_reorgs.empty:
        row["Reorgs"] = f"{client_reorgs.max():.0f}"

    # Final head slot
    client_head = head_df[(head_df["client"] == client) & (head_df["metric"] == "lean_head_slot")]
    if not client_head.empty:
        row["Final Head Slot"] = f"{client_head['value'].max():.0f}"

    # Final finalized slot
    client_finalized = finality_df[(finality_df["client"] == client) & (finality_df["metric"] == "lean_latest_finalized_slot")]
    if not client_finalized.empty:
        row["Final Finalized Slot"] = f"{client_finalized['value'].max():.0f}"

    summary_rows.append(row)

if summary_rows:
    summary_df = pd.DataFrame(summary_rows).set_index("Client").fillna("-")
    display(summary_df)

print(f"\nDevnet: {devnet_id}")
if devnet_info:
    print(f"Duration: {devnet_info['duration_hours']:.1f} hours")
Avg C-H Dist. Max C-H Dist. Avg H-J Dist. Max H-J Dist. Avg J-F Dist. Max J-F Dist. Avg H-F Dist. Max H-F Dist. Reorgs Final Head Slot Final Finalized Slot
Client
buildx_buildkit_multiarch0 - - - - - - - - - - -
zeam_0 3.3 7 248.7 441 316.0 380 564.7 821 79 1235 414
zeam_1 3.0 8 250.0 441 316.0 380 566.0 821 112 1235 414
zeam_10 10.7 31 244.3 432 316.0 380 560.3 812 79 1226 414
zeam_11 25.1 438 241.1 443 303.5 380 544.6 823 54 1237 414
zeam_12 15.6 31 497.4 758 56.0 56 553.4 814 26 1228 414
zeam_13 6.9 30 246.1 435 316.0 380 562.1 815 52 1229 414
zeam_14 2.9 7 252.1 443 316.0 380 568.1 823 106 1237 414
zeam_15 3.1 7 248.9 441 316.0 380 564.9 821 56 1235 414
zeam_16 10.8 73 242.2 438 316.0 380 558.2 818 74 1232 414
zeam_17 3.0 8 250.0 441 316.0 380 566.0 821 115 1235 414
zeam_18 13.7 63 239.3 440 316.0 380 555.3 820 52 1234 414
zeam_19 49.0 529 240.6 441 278.8 380 519.4 821 44 1235 414
zeam_2 29.5 126 222.5 424 316.0 380 538.5 804 56 1218 414
zeam_20 41.3 618 248.4 543 278.3 380 526.7 822 81 1236 414
zeam_21 82.3 588 310.1 546 177.5 380 487.6 823 44 1237 414
zeam_22 3.5 8 249.5 444 316.0 380 565.5 824 59 1238 414
zeam_23 3.2 9 250.8 445 316.0 380 566.8 825 97 1239 414
zeam_24 3.1 8 249.9 441 316.0 380 565.9 821 98 1235 414
zeam_25 2.6 8 250.4 441 316.0 380 566.4 821 69 1235 414
zeam_26 3.2 8 249.8 441 316.0 380 565.8 821 92 1235 414
zeam_27 3.2 7 251.8 443 316.0 380 567.8 823 65 1237 414
zeam_28 3.3 7 248.7 441 316.0 380 564.7 821 77 1235 414
zeam_29 5.9 30 247.1 441 316.0 380 563.1 821 105 1235 414
zeam_3 3.9 14 248.1 437 316.0 380 564.1 817 78 1231 414
zeam_30 5.3 38 246.7 441 316.0 380 562.7 821 94 1235 414
zeam_31 21.5 222 244.2 441 303.2 380 547.4 821 36 1235 414
zeam_4 3.0 6 248.0 437 316.0 380 564.0 817 84 1231 414
zeam_5 92.1 515 238.1 539 240.5 380 478.7 823 66 1237 414
zeam_6 3.1 7 248.9 441 316.0 380 564.9 821 95 1235 414
zeam_7 15.2 31 454.8 709 100.0 100 554.8 809 32 1223 414
zeam_8 18.8 65 236.2 430 316.0 380 552.2 810 89 1224 414
zeam_9 25.4 783 237.1 425 305.5 380 542.6 805 72 1219 414
Devnet: pqdevnet-20260629T1826Z
Duration: 0.6 hours