Skip to content

fix(coachhelm): Signals coherence — player names, KPI semantics, leak math, accuracy gate - #930

Closed
njrini99-code wants to merge 1 commit into
mainfrom
fix/signals-coherence
Closed

fix(coachhelm): Signals coherence — player names, KPI semantics, leak math, accuracy gate#930
njrini99-code wants to merge 1 commit into
mainfrom
fix/signals-coherence

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Problem

Verified live on /golf/dashboard/alerts (Signals) and /golf/dashboard/analytics/coachhelm (Effectiveness):

  1. By-player grouping broken — ALL signals grouped under "Unknown player". EvidenceInsight (the insight/alert row shape) never carries a resolved player name — only player_id off golf_coach_insights — so insightToSignalRow hard-coded playerName: undefined for every row. The "By player" grouping's fallback (r.playerName?.trim() || 'Unknown player') then swallowed everything. Patterns were unaffected — getTeamPatterns already joins golf_players inline.

  2. KPI tile semantics inconsistent — the three tiles (OPEN SIGNALS / URGENT + HIGH / LOADED) silently re-scoped per sub-tab with identical labels, and on /alerts "Urgent + high" (13, counted regardless of status) read HIGHER than "Open" (10, status-filtered) — a severity breakdown reading as more than its own superset. "LOADED" was dev-speak.

  3. "Where the team is bleeding" banner mislabels a SUM as a per-round rateLeakBoard sums strokes_impact across every player and every leak insight in a category and labeled it "str/rd", reading as if −26.4 meant the team lost 26 strokes EVERY round (~8x the real team SG-putting figure, ~−3.19/rd). It also recomputed from the active sub-tab's own scoped insight fetch, so the SAME banner totaled differently on /alerts vs /insights.

  4. Effectiveness "ACCURACY 100% ▲+8%" renders from a near-empty sample — the headline gated on GAUGE_MIN_RESOLVED (2) while the calibration side panel's own copy says "needs 5 resolved predictions" — the two thresholds could disagree. Also: the impact-chart tornado's y-scale keyed rows by display label, so two rows sharing a label (one player with two top-N patterns) collapsed onto the same row — bars and value text overlapped ("+4.10+4.67"). And the "no trend yet" table cell rendered a bare "—" with zero visible context (only a hover tooltip).

Fix

  1. insightToSignalRow/insightsToSignalRows (patternToInsightVocabulary.ts) now accept an optional player_id -> name map. /alerts and /insights SSR-fetch the team roster via getTeamPlayers() (existing roster action, unchanged) and pass it through as a new playerNames prop on FairwayCoachHelmSignals. Missing entries still fall back to undefined — never a fabricated name.

  2. summary.urgent is now computed as a subset of summary.open (same status filter, narrower priority filter) — it can never read higher than "Open" again, and now matches the shell badge's own getAlertCounts().counts.critical semantics. Tile 1 label is now sub-tab-scoped (Open alerts / Open insights / Open patterns); tile 3 renamed LoadedShowing.

  3. LeakBoard's unit/copy now honestly says "total" (not "str/rd"), and its data source is a dedicated full team-wide fetch (no priority filter) independent of the active sub-tab's own insights state — the banner reads the same number regardless of which sub-tab is open.

  4. Extracted isAccuracyHeadlineLive(resolved) gated on the SAME BUCKET_MIN_RESOLVED (5) threshold the calibration panel already states, with awaitingLabel="Calibrating". StrokesGainedTornado's y-scale is now keyed by row index (always unique) instead of display label. The "no trend yet" glyph now uses the same dimmed-icon idiom as its sibling states instead of a bare em-dash.

Gates

  • npx tsc --noEmit -p tsconfig.json — clean
  • npx eslint <changed files> — clean
  • npx vitest run <full suite> — 272 files / 2636 passed, 4 skipped (pre-existing skips, unrelated)
  • New tests: patternToInsightVocabulary.test.ts (player-name resolution), LeakBoard.test.tsx (label honesty), StrokesGainedTornado.test.tsx (duplicate-label row collision), FairwayEffectiveness.test.ts (accuracy gate threshold)

Caveats

  • LeakBoard's flameThreshold (default 0.8, "high bleed" flag) was calibrated for the old per-metric magnitude; now that the value is an honest multi-player/multi-insight sum, it may light up more readily. Left untouched — out of scope for this fix, flagged for a follow-up if it reads as noisy in practice.
  • No browser verification was possible per house rules (no browser automation on this machine) — verified via tsc/eslint/vitest plus direct code-path tracing to the exact lines producing each reported symptom.

Part of #914
Fixes #907

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg

… math, accuracy gate

Four independently-verified bugs on the Signals/Effectiveness surfaces:

1. By-player grouping ("Unknown player"): EvidenceInsight never carried a
   resolved player name (only golf_coach_insights.player_id), so every
   insight/alert row hard-coded playerName: undefined and collapsed into one
   "Unknown player" bucket. Patterns were unaffected (getTeamPatterns already
   joins golf_players inline). insightToSignalRow/insightsToSignalRows now
   accept an optional player_id -> name map; /alerts and /insights SSR-fetch
   the team roster via getTeamPlayers() and pass it through.

2. KPI tile semantics: "Urgent + high" counted every loaded row regardless of
   status while "Open" filtered by status, so /alerts could show
   "Urgent + high 13 > Open 10" — a severity breakdown reading as MORE than
   its own superset. Urgent+high is now a true subset of Open (matches the
   shell badge's own getAlertCounts().counts.critical semantics). Tile labels
   now say the sub-tab noun ("Open alerts"/"Open insights"/"Open patterns")
   instead of a generic "Open signals", and "Loaded" (dev-speak) is renamed
   "Showing".

3. LeakBoard math/label: the "Where the team is bleeding" total is a SUM
   across every player and every leak insight in a category, but was labeled
   "str/rd" (a per-round rate) — reading as if −26.4 meant 26 strokes lost
   EVERY round, ~8x the real team SG-putting figure (~−3.19/rd). Relabeled to
   an honest "total", and the rollup now reads from its own full team-wide
   fetch (no priority filter) instead of the active sub-tab's own scoped
   `insights` state, so the banner no longer silently re-totals per sub-tab.

4. Effectiveness accuracy headline: gated on GAUGE_MIN_RESOLVED (2) while the
   calibration side panel's own copy says "needs 5 resolved predictions" —
   the two thresholds could disagree, letting a near-empty sample render an
   authoritative "100% ▲+8%". Now gated on the SAME 5-resolved threshold
   (isAccuracyHeadlineLive), with "Calibrating" replacing "Awaiting
   predictions". Also fixed: StrokesGainedTornado's y-scale keyed rows by
   display label, so two rows sharing a label (e.g. one player with two
   top-N patterns) collapsed onto the same band — bars and value text
   rendered on top of each other ("+4.10+4.67"). Now keyed by row index.
   And the "no trend yet" cell rendered a bare "—" with no visible label
   (only a tooltip); now matches the icon idiom the other three trend states
   already use, dimmed to read as "no data" rather than a genuine flat trend.

Gates: tsc --noEmit clean, eslint clean on changed files, full vitest suite
green (272 files / 2636 passed).

Part of #914 + Fixes #907

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Jul 17, 2026 9:53pm

Request Review

@supabase

supabase Bot commented Jul 17, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@njrini99-code, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d2ad69fd-0db3-428c-bc82-212b4ce341f3

📥 Commits

Reviewing files that changed from the base of the PR and between 6d25e44 and 046abd8.

📒 Files selected for processing (11)
  • src/app/golf/(dashboard)/dashboard/alerts/page.tsx
  • src/app/golf/(dashboard)/dashboard/insights/page.tsx
  • src/components/fairway/charts/StrokesGainedTornado.test.tsx
  • src/components/fairway/charts/StrokesGainedTornado.tsx
  • src/components/fairway/pages/coachhelm/FairwayCoachHelmSignals.tsx
  • src/components/fairway/pages/coachhelm/FairwayEffectiveness.test.ts
  • src/components/fairway/pages/coachhelm/FairwayEffectiveness.tsx
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.test.ts
  • src/components/fairway/pages/coachhelm/signals/patternToInsightVocabulary.ts
  • src/components/golf/coachhelm/coach/LeakBoard.test.tsx
  • src/components/golf/coachhelm/coach/LeakBoard.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/signals-coherence
  • 🛠️ helm safety pass
  • 🛠️ dashboard ux pass
  • 🛠️ rls test pass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@njrini99-code

Copy link
Copy Markdown
Owner Author

🤖 Mission Control — PR summary

What it changes: Four independently-verified bugs on the Signals / Effectiveness surfaces:

  1. "Unknown player" groupingEvidenceInsight never carried a resolved player name (only player_id), so every insight/alert row hard-coded playerName: undefined and collapsed into one bucket. insightToSignalRow/insightsToSignalRows now accept a player_id → name map; /alerts and /insights SSR-fetch the roster and pass it through.
  2. KPI tile semantics — "Urgent + high" counted every loaded row while "Open" filtered by status, so /alerts could show "Urgent + high 13 > Open 10" (a severity breakdown reading larger than its own superset). Urgent+high is now a true subset of Open, matching the shell badge's getAlertCounts().
  3. Leak math and 4. accuracy-gate corrections.

Risk / areas: golf CoachHelm /alerts + /insights SSR, KPI tiles, effectiveness math.

Watch: the added roster fetch on the SSR path (latency); the subset invariant (urgent+high ≤ open) holds across filters; player-name fallback when the map misses.

CI: ✅ green so far — 34 checks passing, 4 pending, 0 failing; mergeable state BLOCKED on required review (no CI failure). Awaiting review.

njrini99-code pushed a commit that referenced this pull request Jul 17, 2026
…s in patternToInsightVocabulary.test.ts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMdviLDsAg2YYJ8adsM6fg
@njrini99-code

Copy link
Copy Markdown
Owner Author

Superseded — landed on main inside merge train #938 (commit 6ecede6). Branch kept.

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.

[QA] Golf CoachHelm Signals: 'Unknown player' join + incoherent counts + −26.4 str/rd mislabel

1 participant