Skip to content

Commit b4829d7

Browse files
committed
API Latency Template
1 parent 99b1e28 commit b4829d7

File tree

2 files changed

+72
-80
lines changed

2 files changed

+72
-80
lines changed
Lines changed: 71 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,100 @@
11
from preswald import (
2-
Workflow, connect, get_df, text, table, plotly, selectbox,
3-
slider, checkbox, separator
2+
Workflow,
3+
connect,
4+
get_df,
5+
table,
6+
text,
7+
plotly,
8+
selectbox,
9+
slider,
10+
separator,
411
)
5-
import pandas as pd
612
import plotly.express as px
713

814
workflow = Workflow()
915

16+
# --- 1. Load data and fix types ---
1017
@workflow.atom()
11-
def init():
12-
text("# 🧪 A/B Testing Dashboard")
13-
text("Analyze A/B test results, compare conversion rates, evaluate statistical significance, and discover top-performing variants.")
14-
connect()
15-
16-
@workflow.atom(dependencies=["init"])
1718
def load_data():
18-
df = get_df("sample_csv").copy()
19-
df["Conversion Rate"] = df["Conversion Rate"].str.replace('%', '', regex=False).astype(float)
20-
df["Significance"] = df["Significance"].astype(str)
21-
table(df, title="📋 Raw Data", limit=10)
22-
return df
23-
24-
@workflow.atom(dependencies=["load_data"])
25-
def filter_significant(load_data):
26-
show_only = checkbox("Show only significant results?", default=False)
27-
return load_data[load_data["Significance"].str.lower() == "yes"] if show_only else load_data
19+
connect()
20+
df = get_df("sample_csv")
2821

29-
@workflow.atom(dependencies=["filter_significant"])
30-
def variant_selector(filter_significant):
31-
variants = filter_significant["Variant"].unique().tolist()
32-
return selectbox("🎛️ Choose a Variant", options=variants, default=variants[0])
22+
# Coerce latency columns to float
23+
df["P50"] = df["P50"].astype("float64")
24+
df["P95"] = df["P95"].astype("float64")
25+
df["P99"] = df["P99"].astype("float64")
3326

34-
@workflow.atom(dependencies=["filter_significant", "variant_selector"])
35-
def show_variant_table(filter_significant, variant_selector):
36-
df = filter_significant[filter_significant["Variant"] == variant_selector].reset_index(drop=True).copy()
37-
df["Test Number"] = df.index + 1
38-
table(df, title=f"🔍 Details for {variant_selector}")
3927
return df
4028

41-
@workflow.atom(dependencies=["show_variant_table"])
42-
def plot_variant_trend(show_variant_table):
43-
df = show_variant_table
44-
text(f"## 📈 Conversion Trend: {df['Variant'].iloc[0]}")
45-
fig = px.line(
46-
df, x="Test Number", y="Conversion Rate", markers=True,
47-
title="Conversion Rate Over Time", labels={"Conversion Rate": "Conversion Rate (%)"}
48-
)
49-
plotly(fig)
29+
# --- 2. Intro text ---
30+
@workflow.atom()
31+
def intro():
32+
text("# 🚦 API Latency Explorer")
33+
text("Explore P50, P95, and P99 latency across endpoints. Filter, chart, and inspect details.")
5034

51-
@workflow.atom(dependencies=["filter_significant"])
52-
def conversion_lift_analysis(filter_significant):
53-
text("## 🚀 Conversion Lift Compared to Control")
35+
# --- 3. Display table ---
36+
@workflow.atom(dependencies=["load_data"])
37+
def show_table(load_data):
38+
text("## 📋 Full Dataset")
39+
table(load_data)
5440

55-
# Compute average CR per variant
56-
summary = filter_significant.groupby("Variant").agg(
57-
AvgCR=("Conversion Rate", "mean")
58-
).reset_index()
41+
# --- 4. P99 threshold filter ---
42+
@workflow.atom(dependencies=["load_data"])
43+
def p99_filter(load_data):
44+
text("## 🔍 Filter by P99 Latency")
45+
threshold = slider("Maximum P99 (ms)", min_val=100, max_val=300, step=10, default=200)
46+
filtered = load_data[load_data["P99"] <= threshold]
5947

60-
# Get control rate
61-
control_rate = summary[summary["Variant"] == "Control"]["AvgCR"].values[0]
48+
if filtered.shape[0] > 0:
49+
table(filtered, title=f"Endpoints with P99 ≤ {threshold} ms")
50+
else:
51+
text("⚠️ No endpoints under selected threshold.")
6252

63-
# Calculate lift
64-
summary["Lift vs Control (%)"] = summary["AvgCR"] - control_rate
53+
# --- 5. Plot percentiles ---
54+
@workflow.atom(dependencies=["load_data"])
55+
def plot_percentiles(load_data):
56+
text("## 📊 Latency Breakdown (Grouped Bar Chart)")
57+
58+
melted = load_data.melt(
59+
id_vars="Endpoint",
60+
value_vars=["P50", "P95", "P99"],
61+
var_name="Percentile",
62+
value_name="Latency"
63+
)
6564

6665
fig = px.bar(
67-
summary, x="Variant", y="Lift vs Control (%)",
68-
color="Lift vs Control (%)", color_continuous_scale="Viridis",
69-
text="Lift vs Control (%)", title="Conversion Rate Lift (vs. Control)"
66+
melted,
67+
x="Endpoint",
68+
y="Latency",
69+
color="Percentile",
70+
barmode="group",
71+
title="Latency by Endpoint",
72+
labels={"Latency": "ms"}
7073
)
71-
fig.update_traces(texttemplate='%{text:.2f}%', textposition='outside')
74+
fig.update_layout(xaxis_tickangle=-45)
7275
plotly(fig)
7376

74-
@workflow.atom(dependencies=["filter_significant"])
75-
def funnel_view(filter_significant):
76-
text("## 🔄 Mini Funnel: Visitors → Conversions")
77-
summary = filter_significant.groupby("Variant").agg({
78-
"Visitors": "sum",
79-
"Conversions": "sum"
80-
}).reset_index()
77+
# --- 6. Per-endpoint breakdown ---
78+
@workflow.atom(dependencies=["load_data"])
79+
def endpoint_detail(load_data):
80+
text("## 🧪 Inspect Specific Endpoint")
81+
options = load_data["Endpoint"].tolist()
82+
selected = selectbox("Choose Endpoint", options)
8183

84+
row = load_data[load_data["Endpoint"] == selected].iloc[0]
8285
fig = px.bar(
83-
summary.melt(id_vars="Variant", value_vars=["Visitors", "Conversions"]),
84-
x="Variant", y="value", color="variable", barmode="group",
85-
title="Visitor vs Conversion Totals", text="value",
86-
labels={"value": "Count", "variable": "Stage"}
86+
x=["P50", "P95", "P99"],
87+
y=[row["P50"], row["P95"], row["P99"]],
88+
labels={"x": "Percentile", "y": "Latency (ms)"},
89+
title=f"Latency Profile: {selected}"
8790
)
8891
plotly(fig)
8992

90-
@workflow.atom(dependencies=["filter_significant"])
91-
def callout_best_variant(filter_significant):
92-
summary = (
93-
filter_significant.groupby("Variant")["Conversion Rate"]
94-
.mean()
95-
.reset_index()
96-
.sort_values(by="Conversion Rate", ascending=False)
97-
)
98-
best = summary.iloc[0]
99-
text(f"## 🏆 Best Variant: **{best['Variant']}**")
100-
text(f"Achieved an average **{best['Conversion Rate']:.2f}%** conversion rate.")
101-
93+
# --- 7. Footer ---
10294
@workflow.atom()
103-
def wrap_up():
95+
def end_note():
10496
separator()
105-
text("_This interactive dashboard is built with [Preswald](https://preswald.com), empowering anyone to build production dashboards in Python._")
97+
text("✅ Dashboard complete. Use this to spot latency bottlenecks in your backend!")
10698

107-
workflow.execute()
99+
# --- Run ---
100+
workflow.execute()

preswald/templates/api-latency/sample.csv.template

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,4 @@ Endpoint,P50,P95,P99,Last Updated
2727
/payments/verify,110,160,220,2022-09-15 14:45:00
2828
/reviews/delete,75,120,170,2022-09-15 15:00:00
2929
/cart/checkout,95,140,190,2022-09-15 15:15:00
30-
/shipping/estimate,105,160,220,2022-09-15 15:30:00
31-
...
30+
/shipping/estimate,105,160,220,2022-09-15 15:30:00

0 commit comments

Comments
 (0)