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:
1 parent
b11552fa49
commit
592cf83656
2 files changed
+37
-10
No files matched your search
+16
@@ -3131,9 +3131,25 @@ async def admin_overview(request: Request):
|
||||
total_spend += spend
|
||||
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 {
|
||||
"members": members,
|
||||
"admins": admins,
|
||||
"agents": agents,
|
||||
"totals": {
|
||||
"members": len(members),
|
||||
"active": sum(1 for m in members if m["active"]),
|
||||
|
||||
+21
-10
@@ -143,21 +143,32 @@ def listmonk_joins() -> list[str] | 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:
|
||||
"""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()
|
||||
try:
|
||||
active = conn.execute("SELECT COUNT(*) FROM members WHERE active=1").fetchone()[0]
|
||||
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 != ''"
|
||||
)
|
||||
]
|
||||
rows = conn.execute("SELECT email, active, welcome_sent_at FROM members").fetchall()
|
||||
finally:
|
||||
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:
|
||||
|
||||
Reference in new issue
Block a user