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:
Davide Scaini
2026-05-18 21:31:05 +02:00
parent cd80b8e32e
commit 90283c45f4
+12 -12
View File
@@ -174,9 +174,7 @@ def _style_ax(ax: plt.Axes) -> None:
def make_figure(df: pd.DataFrame, output: Path) -> None:
# ── daily logins ──────────────────────────────────────────────────────────
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")
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()
weekly_logins = df[login_mask].set_index("ts").resample("W-MON").size()
# ── feature usage (weekly) ────────────────────────────────────────────────
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):
_style_ax(ax)
# Panel 1 — daily logins + rolling mean
ax_log.bar(daily_logins.index, daily_logins.values,
color=BLUE, alpha=0.30, width=pd.Timedelta(hours=20))
ax_log.plot(daily_logins.index, rolling7.values,
color=BLUE, linewidth=2, label="7-day avg")
ax_log.set_title("Daily logins", color=FG, fontsize=11, pad=8)
# Panel 1 — weekly logins
n_w = len(weekly_logins)
week_x = np.arange(n_w)
ax_log.bar(week_x, weekly_logins.values, color=BLUE, alpha=0.70, width=0.6)
ax_log.set_xticks(week_x)
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.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)
# 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)",
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
fig.suptitle(
f"bincio — {total_logins} logins over {span_days} days "