The co-op had no longitudinal data: every number was an ad-hoc query, so
nothing compounded and no single source owned "how many members do we have,
and when did that change". That cost us a wrong figure in the newsletter ("six
new members" — actually 21; the six were forum joins).
New: metrics_snapshot.py writes one row per day into the portal's SQLite store
(the same members.db), and GET /admin/metrics/series returns them oldest-first
for the panel and dashboard to chart later.
- Cumulative columns (members_active, members_total, members_new,
newsletter_subs, matrix_space_members) are counts AS OF that day;
spend_usd/requests are THAT day's traffic.
- FAIL CLOSED PER METRIC: an unreadable source yields NULL, never a zero — a
zero reads as "nobody" and would bake a lie into a chart. Failures print to
stdout, and a scheduler task's stdout lands in the app log.
- Listmonk's cached subscriber_count is NOT trusted (it reads 142 for a list
holding 71): the count comes from paging the subscribers endpoint.
- Member growth comes from welcome_sent_at, the provisioning timestamp —
the closest thing the portal stores to a join date. Manual/founder members
have no welcome email, so backfilled days are a documented LOWER BOUND;
today's row carries the exact count.
- Runs as an in-app scheduler task (addons.scheduler, 03:15 UTC) rather than
an agent cron: member-facing plumbing should not depend on the agent VM.
- Backfill is idempotent; member growth is reconstructible back to
September and spend/requests back to the gateway's first request.
Charts land later, on the surfaces, once the shape of the series is reviewed.
36 lines
1.0 KiB
JSON
36 lines
1.0 KiB
JSON
{
|
|
"id": "coop.inference.member-portal",
|
|
"title": "Member Portal",
|
|
"author": "Inference Cooperative",
|
|
"description": "Membership middleware: syncs Open Collective members with Cloudron users and LiteLLM keys, and injects per-member keys into inference requests.",
|
|
"version": "0.1.0",
|
|
"healthCheckPath": "/health",
|
|
"httpPort": 8000,
|
|
"addons": {
|
|
"localstorage": {},
|
|
"oidc": {
|
|
"loginRedirectUri": "/",
|
|
"logoutRedirectUri": "/"
|
|
},
|
|
"sendmail": {},
|
|
"scheduler": {
|
|
"membership_sweep": {
|
|
"schedule": "0 3 * * *",
|
|
"command": "/app/code/sweep.sh"
|
|
},
|
|
"rag_refresh": {
|
|
"schedule": "30 3 * * *",
|
|
"command": "/app/code/rag_refresh.py"
|
|
},
|
|
"metrics_snapshot": {
|
|
"schedule": "15 3 * * *",
|
|
"command": "/app/code/metrics_snapshot.py"
|
|
}
|
|
}
|
|
},
|
|
"manifestVersion": 2,
|
|
"website": "https://inference.coop",
|
|
"contactEmail": "info@inference.coop",
|
|
"dockerImage": "registry.inference.coop/co-op/member-portal:20261027-452a27a"
|
|
}
|