Files
qwen3-6-lora/data/raw/sanitized/plans/puedes-conectarte-a-jira-delightful-bonbon.md

7.4 KiB
Raw Permalink Blame History

GFIBER-707 — Fix "Cumulative Results" percentages + add "Pending" card

Context

Jira GFIBER-707 (Bug, High priority): the "Cumulative results" dashboard panel shows incorrect percentages for the Customer-Initiated / Agent-Initiated cards. Acceptance criteria:

  • A third blue card "Pending" is added to the right of the existing two.
  • The 3 cards, added together, sum 100%.

Root cause found in code (utils/dashboard/metrics.ts:157-160): customerInitiatedRate and agentInitiatedRate are computed as count / team.offers * 100, where team.offers = accepted + rejected. But the numerators (customerInitiated, agentInitiated, unverifiedAccepted) are counted only over accepted roster tickets and, by construction, exactly partition accepted (every accepted ticket is customer-initiated, agent-initiated, or unclassified/null). Since offers >= accepted whenever there's at least one rejected ticket, the two existing rates under-report and can never sum to 100% — confirmed by the unit test at __tests__/utils/dashboard/metrics.test.ts:170-183 (offers = 3 accepted + 1 declined = 4, so a fully-classified accepted ticket only registers as 25%, not 100/N%).

The fix: switch the denominator for all three initiation rates to metrics.accepted (which is already what the tile's caption text claims — "{count} initiated by {initiator} / {total} total accepted offers" already uses total=metrics.accepted). This makes customerInitiatedRate + agentInitiatedRate + unverifiedAcceptedRate === 100 exactly whenever there's at least one accepted ticket, satisfying the AC. The two existing percentages will change (increase) as a side effect — this is the actual bug fix, not a regression.

Decisions confirmed with the user:

  • Denominator for all 3 rates → metrics.accepted (not offers).
  • Third card label: "Pending Validation Rate", caption: "{count} pending validation / {total} total accepted offer(s)".

Changes

1. utils/dashboard/metrics.ts

  • In buildDashboardMetrics, change:
    const customerInitiatedRate = team.offers > 0 ? (customerInitiated / team.offers) * 100 : 0;
    const agentInitiatedRate = team.offers > 0 ? (agentInitiated / team.offers) * 100 : 0;
    
    to use team.accepted as the denominator, and add:
    const unverifiedAcceptedRate = team.accepted > 0 ? (unverifiedAccepted / team.accepted) * 100 : 0;
    
  • Add unverifiedAcceptedRate: number to the DashboardMetrics type (next to agentInitiatedRate), with a doc comment matching the existing style (/** (unverifiedAccepted / accepted) × 100 — percentage of accepted offers not yet classified. */).
  • Update the doc comments on customerInitiatedRate/agentInitiatedRate to say "percentage of accepted offers" (not "total offers"), and update the return statement to include unverifiedAcceptedRate.

2. packages/design-system/src/components/InitiationRateTile/index.tsx

  • Widen initiator: "customer" | "agent" to initiator: "customer" | "agent" | "pending".
  • Adjust the caption line: when initiator === "pending", render "{count} pending validation / {total} total accepted offer(s)" instead of "{count} initiated by {initiator} / ...". Simplest approach: branch on initiator === "pending" for the middle phrase ("pending validation" vs initiated by {initiator}), keeping the rest of the sentence identical.
  • No style changes needed — style.module.scss already renders every tile with the same blue tint (--fiber-blue-light / --fiber-text-blue), matching the "blue card" requirement in the AC.
  • Update the doc comment on the initiator prop to mention the "pending" case.

3. packages/design-system/src/components/InitiationRateTile/InitiationRateTile.stories.tsx

  • Add a PendingValidation story (label: "Pending Validation Rate", initiator: "pending", plausible rate/count/total), and widen the story's grid render from gridTemplateColumns: "1fr 1fr" to "1fr 1fr 1fr" (or leave 2-col if stories render independently — check current behavior first).

4. app/(protected)/dashboard/page.tsx (CumulativeResults, ~lines 439-463)

  • Change the grid wrapper from grid grid-cols-2 gap-3 sm:grid-cols-2 to 3 columns (grid grid-cols-1 gap-3 sm:grid-cols-3, matching the responsive pattern used elsewhere in the file).
  • Add a third <InitiationRateTile>:
    <InitiationRateTile
      label="Pending Validation Rate"
      rate={metrics.unverifiedAcceptedRate}
      count={metrics.unverifiedAccepted}
      total={metrics.accepted}
      initiator="pending"
    />
    
  • Leave the helper-text condition (customerInitiatedRate === 0 && agentInitiatedRate === 0) as-is — it still correctly means "nothing classified yet" (in which case pending = 100%).

5. app/(protected)/dashboard/api/export/route.ts (Sheet 3 — Performance summary, ~lines 346-352)

  • After the existing agentRateRow, add:
    const pendingRateRow = sheet3.addRow(["Pending Validation Rate", metrics.unverifiedAcceptedRate / 100]);
    pendingRateRow.getCell(2).numFmt = "0.0%";
    
    This is a new row inserted into Block A, which shifts every subsequent row (blank separator, leaderboard header, leaderboard data rows) down by 1 in the worksheet.

Tests to update

  • __tests__/utils/dashboard/metrics.test.ts: update existing assertions that hardcode the old offers-based math (e.g. "only counts initiation types from roster users", "counts customer_initiated and agent_initiated correctly" — the (1/4)*100 expectations become (1/accepted)*100), and add:
    • New assertions for unverifiedAcceptedRate.
    • A dedicated test asserting customerInitiatedRate + agentInitiatedRate + unverifiedAcceptedRate === 100 (within floating-point tolerance) for a realistic mixed fixture — this directly encodes the GFIBER-707 acceptance criterion.
  • packages/design-system/.../InitiationRateTile.test.tsx and .test.cy.tsx: add a "pending" case asserting the "N pending validation / M total accepted offer(s)" caption text.
  • __tests__/dashboard-export-route.test.ts: this file has hardcoded row indices that must shift by +1 because of the new export row:
    • "Block A includes the new initiation type rows after Pitch Rate" (line ~436-450): loop range 2..142..15; add expect(metricLabels).toContain("Pending Validation Rate").
    • "Block B leaderboard has one row per agent user" (line ~452-464): header now at row 17 (was 16), data rows 18/19 (were 17/18) — update the comment and indices.
    • "includes zero-sales agents in the leaderboard" (line ~466-476): loop range 17..1818..19.

Verification

  1. Run the affected unit test suites:
    npx vitest run __tests__/utils/dashboard/metrics.test.ts __tests__/dashboard-export-route.test.ts
    
  2. Run the design-system component tests for the tile:
    npx vitest run packages/design-system/src/components/InitiationRateTile
    
  3. Full test suite + typecheck to catch any other consumer of DashboardMetrics/InitiationRateTile's initiator prop:
    npm run build   # or the repo's typecheck script — confirms the widened `initiator` union doesn't break other call sites
    npm test
    
  4. Manually load /dashboard (any team view) in a browser and confirm: 3 blue cards render side by side, and the 3 rate percentages sum to 100.0% for a mix of accepted tickets with customer_initiated, agent_initiated, and null initiation types.