usage_stats: fix reindex alignment bug, switch logins panel to weekly
resample("D") produces midnight-aligned bins; reindexing against a
range built from a raw timestamp (not midnight) caused all values to
be 0. Also switched the logins panel from daily to weekly to match the
feature usage panel — less noisy for a small app.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+12
-12
@@ -174,9 +174,7 @@ def _style_ax(ax: plt.Axes) -> None:
|
|||||||
def make_figure(df: pd.DataFrame, output: Path) -> None:
|
def make_figure(df: pd.DataFrame, output: Path) -> None:
|
||||||
# ── daily logins ──────────────────────────────────────────────────────────
|
# ── daily logins ──────────────────────────────────────────────────────────
|
||||||
login_mask = (df["method"] == "POST") & (df["path"] == "/api/auth/login") & (df["status"] == 200)
|
login_mask = (df["method"] == "POST") & (df["path"] == "/api/auth/login") & (df["status"] == 200)
|
||||||
full_range = pd.date_range(df["ts"].min(), df["ts"].max(), freq="D", tz="UTC")
|
weekly_logins = df[login_mask].set_index("ts").resample("W-MON").size()
|
||||||
daily_logins = df[login_mask].set_index("ts").resample("D").size().reindex(full_range, fill_value=0)
|
|
||||||
rolling7 = daily_logins.rolling(7, center=True, min_periods=1).mean()
|
|
||||||
|
|
||||||
# ── feature usage (weekly) ────────────────────────────────────────────────
|
# ── feature usage (weekly) ────────────────────────────────────────────────
|
||||||
feat_df = df[df["feature"].notna()].copy()
|
feat_df = df[df["feature"].notna()].copy()
|
||||||
@@ -212,16 +210,18 @@ def make_figure(df: pd.DataFrame, output: Path) -> None:
|
|||||||
for ax in (ax_log, ax_heat, ax_feat):
|
for ax in (ax_log, ax_heat, ax_feat):
|
||||||
_style_ax(ax)
|
_style_ax(ax)
|
||||||
|
|
||||||
# Panel 1 — daily logins + rolling mean
|
# Panel 1 — weekly logins
|
||||||
ax_log.bar(daily_logins.index, daily_logins.values,
|
n_w = len(weekly_logins)
|
||||||
color=BLUE, alpha=0.30, width=pd.Timedelta(hours=20))
|
week_x = np.arange(n_w)
|
||||||
ax_log.plot(daily_logins.index, rolling7.values,
|
ax_log.bar(week_x, weekly_logins.values, color=BLUE, alpha=0.70, width=0.6)
|
||||||
color=BLUE, linewidth=2, label="7-day avg")
|
ax_log.set_xticks(week_x)
|
||||||
ax_log.set_title("Daily logins", color=FG, fontsize=11, pad=8)
|
ax_log.set_xticklabels(
|
||||||
|
[str(w.date()) for w in weekly_logins.index],
|
||||||
|
rotation=30, ha="right", fontsize=8, color=FG,
|
||||||
|
)
|
||||||
|
ax_log.set_title("Weekly logins", color=FG, fontsize=11, pad=8)
|
||||||
ax_log.set_ylabel("count", color=FG, fontsize=9)
|
ax_log.set_ylabel("count", color=FG, fontsize=9)
|
||||||
ax_log.yaxis.set_major_locator(ticker.MaxNLocator(integer=True, nbins=5))
|
ax_log.yaxis.set_major_locator(ticker.MaxNLocator(integer=True, nbins=5))
|
||||||
ax_log.tick_params(axis="x", rotation=25)
|
|
||||||
ax_log.legend(fontsize=8, framealpha=0.15, facecolor=BG, edgecolor=GRID)
|
|
||||||
ax_log.grid(axis="y", color=GRID, linewidth=0.5)
|
ax_log.grid(axis="y", color=GRID, linewidth=0.5)
|
||||||
|
|
||||||
# Panel 2 — heatmap
|
# Panel 2 — heatmap
|
||||||
@@ -262,7 +262,7 @@ def make_figure(df: pd.DataFrame, output: Path) -> None:
|
|||||||
ax_feat.text(0.5, 0.5, "No feature data (no Referer headers yet)",
|
ax_feat.text(0.5, 0.5, "No feature data (no Referer headers yet)",
|
||||||
ha="center", va="center", color=FG, transform=ax_feat.transAxes)
|
ha="center", va="center", color=FG, transform=ax_feat.transAxes)
|
||||||
|
|
||||||
total_logins = int(login_mask.sum())
|
total_logins = int(weekly_logins.sum())
|
||||||
span_days = (df["ts"].max() - df["ts"].min()).days + 1
|
span_days = (df["ts"].max() - df["ts"].min()).days + 1
|
||||||
fig.suptitle(
|
fig.suptitle(
|
||||||
f"bincio — {total_logins} logins over {span_days} days "
|
f"bincio — {total_logins} logins over {span_days} days "
|
||||||
|
|||||||
Reference in New Issue
Block a user