Skip to content

feat(customer-analytics): add account health score prototype#11

Open
lordspline wants to merge 4 commits into
masterfrom
capy/account-health-score-prototype
Open

feat(customer-analytics): add account health score prototype#11
lordspline wants to merge 4 commits into
masterfrom
capy/account-health-score-prototype

Conversation

@lordspline

@lordspline lordspline commented Jun 17, 2026

Copy link
Copy Markdown

Problem

Customer analytics users need an actionable way to assess account health at a glance. Historically, they've asked to prioritize outreach based on engagement patterns—identifying which accounts are thriving, which are at risk, and which need intervention. A clear, transparent scoring mechanism helps users make data-driven decisions without relying on opaque "magic numbers" or LLM-generated summaries that may drift over time.

Changes

This PR introduces a query-time account health score for Customer analytics, computed on demand from existing activity metrics. The score (0–100) and its contributing factors appear in the accounts list and in a new Health tab on account detail rows.

Backend (products/customer_analytics/backend/hogql_queries/):

  • account_health_score.py (new): Core scoring contract and calculator. Determines 30-day activity metrics (current/previous windows, active users, active days, last activity), normalizes against team baselines (p90 activity count, p90 active users), and aggregates five weighted factors into a global score. Status mapping: healthy (≥75), neutral (≥40), at risk (<40), no_data (no activity or missing configuration). No LLMs, no persisted scores.
  • accounts_query_runner.py: Adds health_score as a default column, resolves it post-query, and batches baseline + metrics queries for efficiency. Filters out health-score ordering clauses at the query layer (fallback to default ordering) because scores are computed per-page-row.
  • test/test_accounts_query_runner.py: Adds 12 new test methods covering normalization, no-data conditions, team isolation, unsupported activity sources, batched query execution, and ordering fallback.

Frontend (products/customer_analytics/frontend/components/Accounts/):

  • AccountHealthScore.tsx (new): Types (AccountHealthScore, AccountHealthStatus, AccountHealthFactor), parser (parseAccountHealthScore), badge renderer (AccountHealthScoreBadge with accessible label), and explanation view (AccountHealthScoreExplanation) showing score, status, lookback window, and grid of factor details (value, previous, weight, per-factor description).
  • AccountHealthScore.test.tsx (new): Unit tests for parsing, badge rendering, and explanation display across all four status buckets.
  • AccountsHogQLTable.tsx: Adds health_score column template with badge renderer, pipes health-score cell to AccountNotebooksExpansion on row expand, and marks column as non-sortable.
  • AccountNotebooksExpansion.tsx: Adds Health tab (new, becomes default) containing AccountHealthScoreExplanation. Receives health score as a prop and passes it to the health tab content.
  • accountsExpansionLogic.ts: Expands AccountExpansionTab to include 'health' and sets it as the new default tab.
  • accountsLogic.ts: Adds NON_SORTABLE_COLUMNS set and helper isAccountsColumnSortable() to disable sort toggling on health_score.
  • accountsColumnConfigLogic.ts: Adds 'health_score' to default select, defines an 'account_health' column group for the column picker, and exposes ACCOUNTS_HEALTH_SCORE_COLUMN constant.
  • AccountsMaxTools.tsx: Updates OpenAccountTool default tab from 'usage' to 'health'.

Stories/docs (products/customer_analytics/frontend/components/Accounts/):

  • AccountsTab.stories.tsx: Adds RowExpandedHealthScore story and fixture helpers (healthScore()) demonstrating all four statuses with rich factor data. Updates mock responses to include health-score column.
  • AGENTS.md: Documents expanded row structure (Health tab now first), column contract (synthetic health_score), and adds customer analytics account tab viewed event tracking for 'health'.

Max tools (products/customer_analytics/backend/max_tools/):

  • open_account.py: Updates description and signature to default to 'health' tab.
  • test/test_open_account.py: Updates test assertion for new default tab.

How did you test this code?

  • Backend tests: 68 tests total across test_accounts_query_runner.py (64) and test_open_account.py (4). Covered normalization, baseline/metrics batching, team isolation, no-data fallbacks, unsupported activity sources, and ordering safety.
  • Frontend tests: 46 tests across AccountHealthScore.test.tsx and accountsLogic.test.ts. Tested parsing, badge rendering, factor explanation, non-sortable column behavior, and storybook interactions with MSW mocks.
  • Browser verification: Seeded four representative accounts (healthy, neutral, at_risk, no_data) with controlled $pageview patterns; verified query-time scores appear in list rows and expanded Health tab; captured screenshots at /home/account-health-evidence/*.png for all states.

As an agent, I did not perform manual exploratory testing. All verification came from automated tests and scripted browser evidence.

🤖 Agent context

Autonomy: Fully autonomous

Implemented vertical slice for account health scoring:

  • Inspected existing Customer analytics query paths (AccountsQueryRunner, system.accounts, hogQL-based metrics) and TeamCustomerAnalyticsConfig for activity event/group source configuration.
  • Chose query-time computation to avoid persistence/migration complexity; score lives in AccountsQueryResponse.results only when health_score column is selected.
  • Used team-scoped p90 baselines for normalization to keep scores comparable across accounts.
  • Kept weights fixed and thresholds explicit (≥75 healthy, ≥40 neutral) for auditability; no LLM involvement.
  • Added per-factor breakdowns to the Health tab rather than just a score badge so users can inspect why an account received its status.
  • Disallowed sorting/filtering by score because the score is computed after the query executes on the result page; sorting would be globally inconsistent across pagination.
  • Verified team isolation and tenant boundaries in tests (events from other teams do not leak into metrics).

Open POST-037 POST-037


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@lordspline lordspline added the capy Generated by capy.ai label Jun 17, 2026 — with Capy AI

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 4 comments

Comment thread products/customer_analytics/backend/hogql_queries/account_health_score.py Outdated
Comment thread products/customer_analytics/backend/hogql_queries/accounts_query_runner.py Outdated
Comment thread products/customer_analytics/frontend/components/Accounts/AccountsHogQLTable.tsx Outdated
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 3 comments

Comment thread products/customer_analytics/backend/hogql_queries/account_health_score.py Outdated
Comment thread products/customer_analytics/backend/hogql_queries/account_health_score.py Outdated
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 1 comment

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant