metrics + overview: stop counting utility accounts as members, and surface agent usage

The panel has always excluded operator/utility emails from member counts
(OPERATOR_EMAILS), but the metrics counted every row in the members table, so the
panel said 75 while every metric surface said 76. The one affected row is
info@inference.coop — the reading mailbox, not a person.

- metrics_snapshot.py now applies the same OPERATOR_EMAILS definition (same variable
  name, same default) so the two cannot drift apart again. No backfill: history stays
  as published and the daily job writes the corrected value going forward.
- admin_overview now also returns an `agents` list, built from the LiteLLM teams it
  already fetches (anything not member:*), so bot and sandbox spend is visible rather
  than structurally invisible. Kept out of member counts and member aggregates, like
  the operator category.
This commit is contained in:
inference-bot committed 2026-10-29 20:57:10 +00:00
1 parent b11552fa49
commit 592cf83656
2 files changed
+37 -10

No files matched your search

+16
View File
@@ -3131,9 +3131,25 @@ async def admin_overview(request: Request):
total_spend += spend total_spend += spend
total_all_time += member_all_time total_all_time += member_all_time
# Bot and sandbox teams: real spend, but not members. Surfaced in their own
# category so the co-op's own usage is accountable without inflating member
# counts — the same reasoning as the operator accounts above.
agents = [
{
"alias": alias,
"spend": float(t.get("spend") or 0.0),
"budget": t.get("max_budget"),
"team_members": len(t.get("members_with_roles") or []),
}
for alias, t in teams.items()
if alias and not alias.startswith("member:")
]
agents.sort(key=lambda a: a["spend"], reverse=True)
return { return {
"members": members, "members": members,
"admins": admins, "admins": admins,
"agents": agents,
"totals": { "totals": {
"members": len(members), "members": len(members),
"active": sum(1 for m in members if m["active"]), "active": sum(1 for m in members if m["active"]),
+21 -10
View File
@@ -143,21 +143,32 @@ def listmonk_joins() -> list[str] | None:
return joins or None return joins or None
# Utility/operator accounts are not members. The panel has always excluded them
# (OPERATOR_EMAILS in app/main.py) while the metrics counted every row, so the two
# disagreed. Same variable name, same default, so they cannot drift apart again.
OPERATOR_EMAILS = {
e.strip().lower()
for e in os.environ.get("OPERATOR_EMAILS", "info@inference.coop,bot@inference.coop").split(",")
if e.strip()
}
def from_sqlite() -> dict: def from_sqlite() -> dict:
"""Members, plus the provisioning dates that give the growth curve.""" """Members, plus the provisioning dates that give the growth curve.
Operator/utility accounts are excluded, matching the panel's member count.
"""
conn = connect() conn = connect()
try: try:
active = conn.execute("SELECT COUNT(*) FROM members WHERE active=1").fetchone()[0] rows = conn.execute("SELECT email, active, welcome_sent_at FROM members").fetchall()
total = conn.execute("SELECT COUNT(*) FROM members").fetchone()[0]
joins = [
row[0]
for row in conn.execute(
"SELECT welcome_sent_at FROM members WHERE welcome_sent_at IS NOT NULL AND welcome_sent_at != ''"
)
]
finally: finally:
conn.close() conn.close()
return {"active": active, "total": total, "joins": joins} real = [r for r in rows if (r[0] or "").strip().lower() not in OPERATOR_EMAILS]
return {
"active": sum(1 for r in real if r[1]),
"total": len(real),
"joins": [r[2] for r in real if r[2]],
}
def from_listmonk() -> int | None: def from_listmonk() -> int | None: