Skip to content

Prevent silent undercounting in collection analytics #890

Description

@kylymo

Plan 002: Make collection analytics event completeness explicit

Executor: follow every step and verification gate. Modify only listed files. Stop and report if a STOP condition occurs.

Status

  • Priority: P1
  • Effort: M
  • Risk: MED
  • Depends on: none
  • Category: correctness
  • Planned at: commit c16bc4f, 2026-07-16

Why this matters

The analytics query requests at most MAX_MARKET_EVENTS rows and only supplies an upper timestamp bound. For busy collections, bounded ranges can exceed the cap, yet the result is normalized and rendered without a general completeness signal. This can make charts and totals appear complete when they are truncated.

Current state

  • app/composables/useCollectionAnalytics.ts:71-77 defines a 5,000-row cap and a result flag.
  • :310-331 calls collectionAnalyticsMarketEvents with limit and timestampLte, then marks allRangeCapped only when range === 'all' and the returned row count reaches the cap.
  • The TODO at :311-312 explicitly identifies missing timestampGte and limit+1 truncation detection.

Commands and expected results

Purpose Command Expected result
Tests pnpm run test -- app/composables/useCollectionAnalytics.spec.ts Boundary and truncation tests pass
Typecheck pnpm run typecheck exit 0
Lint pnpm run lint -- app/composables/useCollectionAnalytics.ts exit 0

Scope

In scope:

  • app/composables/useCollectionAnalytics.ts
  • Related analytics query/types/UI test files needed to expose completeness accurately

Out of scope:

  • Redesigning all analytics charts
  • Changing historical event semantics outside the selected range

Steps

Step 1: Bound the query to the selected range

Compute and send both lower and upper timestamps for every bounded range. Preserve the existing all-time behavior intentionally and document it in the query result contract.

Verify: Unit-test each supported range's lower/upper timestamp variables.

Step 2: Detect truncation without false negatives

Request one extra row (MAX_MARKET_EVENTS + 1) or use the API's supported page metadata, then trim to the display cap and set an explicit completeness flag whenever more rows exist.

Verify: Test zero, exactly-cap, and cap-plus-one rows.

Step 3: Expose the limitation to consumers

Use the existing allRangeCapped result or rename it to a range-agnostic field, then add a clear UI indication wherever analytics present capped results. Do not silently alter numeric totals.

Verify: Component test or existing composable consumer test asserts the warning state.

Test plan

  • 1-hour, 1-day, 7-day, 30-day, and 1-year lower bounds are correct.
  • Exactly-cap rows are complete; cap-plus-one is truncated.
  • All-time results preserve intentional cap signaling.
  • Empty responses remain valid.

Done criteria

  • Bounded queries cannot include events outside the selected range
  • Truncation is detected for every range
  • Users can distinguish incomplete analytics
  • Tests, typecheck, and lint pass

STOP conditions

  • GraphQL schema does not support the proposed lower-bound field; inspect schema/query types and choose the supported equivalent.
  • Existing consumers rely on the old flag name in ways requiring broader redesign.

Maintenance notes

If pagination is feasible in the backend, prefer paginated retrieval for exports and retain the capped path only for interactive charts.

Issue body

Bug Description

Collection analytics caps market events at 5,000 but only signals capping for the all range. Bounded ranges can silently undercount busy collections.

Evidence

  • app/composables/useCollectionAnalytics.ts:310-331
  • Existing TODO explicitly calls for timestampGte and limit+1 truncation detection.

Expected Behavior

Each selected range is bounded by both timestamps, and any cap is surfaced to the user or handled through pagination.

Acceptance Criteria

  • Add lower-bound variables for bounded ranges.
  • Detect cap-plus-one for all ranges.
  • Add regression tests for boundary and truncation cases.
  • Preserve explicit user-facing semantics for incomplete results.
  • Verification commands pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions