What goes in a community health dashboard?
A community health dashboard asks whether the community would still work if the busiest three people went on vacation. It combines the answering rate, response speed, contributor concentration, and how many new members ever get a first reply — the leading indicators that decay long before headline member counts do.
Which cards belong on this dashboard?
- Share of questions answered within 24 and 72 hours
- Median time to first response, trended by month
- Contributor concentration: share of replies from the top 5 members
- Bus factor: number of members producing half of all replies
- New-member first-post rate within 30 days of joining
- Share of first posts that received a reply
- Moderation and spam load: flagged, removed, and pending items
- Month-over-month active members and the direction of travel
What data does this dashboard need?
- Posts and replies with author, topic, parent, and timestamps
- A question flag and an accepted-answer flag where the platform has one
- Moderation events: flags raised, actions taken, and their timestamps
- Member join dates, for the new-member first-post view
- A staff flag, so 'community answers' can be separated from staff answers
How do you build it?
- Land forum, community-platform, and event data in a database and keep member IDs, post timestamps, event IDs, and RSVP status.
- Build models at the grain this dashboard needs, with the active-member window and the check-in rule as explicit columns.
- Create one saved question per card and certify the shared models and definitions.
- Add dashboard filters for Date range, category or space, staff vs. community, member tenure, moderation status.
- Show data freshness and the definitions in use — community exports run on a schedule, and every viewer should know what "active" means here.
Example card SQL
WITH replies AS (
SELECT
member_id,
COUNT(*) AS reply_count
FROM community_posts
WHERE parent_post_id IS NOT NULL
AND created_at >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY member_id
), ranked AS (
SELECT
member_id,
reply_count,
ROW_NUMBER() OVER (ORDER BY reply_count DESC) AS rank,
SUM(reply_count) OVER (ORDER BY reply_count DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_replies,
SUM(reply_count) OVER () AS total_replies
FROM replies
)
SELECT
ROUND(
100.0 * MAX(running_replies) FILTER (WHERE rank <= 5)
/ NULLIF(MAX(total_replies), 0), 1
) AS top5_share_pct,
MIN(rank) FILTER (WHERE running_replies >= 0.5 * total_replies)
AS bus_factor,
MAX(total_replies) AS total_replies,
COUNT(*) AS contributors
FROM ranked;