Skip to content

fix(dashboard): count rank summary per keyword, not per device - #141

Open
shuvamk wants to merge 1 commit into
every-app:mainfrom
shuvamk:fix/dashboard-rank-summary-per-keyword
Open

fix(dashboard): count rank summary per keyword, not per device#141
shuvamk wants to merge 1 commit into
every-app:mainfrom
shuvamk:fix/dashboard-rank-summary-per-keyword

Conversation

@shuvamk

@shuvamk shuvamk commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Problem

getRankSummary builds the dashboard rank scorecard. It counts the denominator per keyword but the numerators per device:

summary.trackedKeywords += result.rows.length;        // one row = one keyword
for (const row of result.rows) {
  for (const device of ["desktop", "mobile"] as const) {  // both devices
    const { position, previousPosition } = row[device];
    if (position !== null && position <= 10) summary.top10 += 1;
    ...
  }
}

Each row carries both a desktop and a mobile result for the same keyword, so a keyword ranking in the top 10 on both devices adds 2 to top10 but 1 to trackedKeywords. The summary can therefore report more top-10 keywords than tracked keywords (e.g. "2 in top 10 out of 1 tracked"). improved/declined have the same cross-basis inflation.

The tested sibling computeScorecards computes these per keyword for a single device (it takes a device param), confirming the intended unit is per-keyword — not both devices summed into a per-keyword denominator.

Fix

Extract a pure summarizeRankRows helper that classifies each keyword once, using its best (lowest) position across devices, and use it in getRankSummary. All four numbers now share the per-keyword basis, so top10/improved/declined can never exceed trackedKeywords.

Semantic note for reviewers: I collapsed the two devices to the keyword's best position — the natural reading for an overview scorecard ("how many of my keywords rank in the top 10"). The existing null-handling is preserved (a keyword only counts toward improved/declined when it has both a current and a previous position). If you'd prefer a different aggregation (e.g. a fixed primary device, or counting "new"/"lost" like computeScorecards does), the helper is the single place to change it.

Tests

New rankSummary.test.ts: a both-device top-10 keyword counted once; top10 never exceeding trackedKeywords; best-device position used for the top-10 check; a null-current keyword ignored; empty input zeroed.

Verification

  • pnpm test — 715 passed (88 files)
  • pnpm ci:check — prettier, knip, tsc --noEmit (×2), and oxlint all green

🤖 Generated with Claude Code

getRankSummary incremented trackedKeywords once per keyword row but incremented
top10/improved/declined once per device (desktop and mobile) for that same row.
The counters and their denominator were on different bases, so a keyword ranking
in the top 10 on both devices added 2 to top10 while adding 1 to trackedKeywords
— the summary could report more top-10 keywords than tracked keywords.

Extract a pure summarizeRankRows helper that classifies each keyword once by its
best (lowest) position across devices, and use it in getRankSummary. The counts
now share the per-keyword basis, so top10/improved/declined never exceed
trackedKeywords.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@sorcerai sorcerai 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.

Reviewed exact current diff and tests. Summary counts each keyword once using its best device position, restoring top10 <= trackedKeywords.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants