Skip to content

Global Giving Activity Index is fabricated from static annual-report constants and falsely claims 'live sampling' #5504

Description

@jsawyerdev

Summary

getGivingSummary() performs zero live data fetches (verified: no fetch() call anywhere in the file). Every "platform" figure is a static division of a 2023/2024 published annual total by 365, every sampling/velocity field is hardcoded to 0, and the resulting composite "Activity Index" is stamped with the real request time via lastUpdated/generatedAt: new Date().toISOString() on every call. The panel's own info tooltip states the index "Combines live sampling with published annual reports" — this is false; no sampling of any kind occurs anywhere in this code path.

Evidence

  • server/worldmonitor/giving/v1/get-giving-summary.ts — confirmed via direct grep: zero fetch( calls in the file.
  • Lines 38-47 (getGoFundMeEstimate), 63-72 (getGlobalGivingEstimate), 76-85 (getJustGivingEstimate), 89-99 (getCryptoGivingEstimate), 103-112 (getInstitutionalBaseline) — all return static constants derived from published annual reports (e.g. dailyVolumeUsd: 9_000_000_000 / 365), with activeCampaignsSampled: 0, newCampaigns24h: 0, donationVelocity: 0, transactions24h: 0 hardcoded on every platform.
  • Each platform object sets lastUpdated: new Date().toISOString() (e.g. line 45) and the top-level handler sets generatedAt: new Date().toISOString() (line ~219) — the current real-time timestamp is attached to numbers that never change between requests.
  • computeActivityIndex() (~128-149) derives the headline "Activity Index" from these permanently-zero velocity/new-campaign fields, so the index is mathematically static across all requests (verified: totalVelocity/totalNew are always 0, so their conditional bonuses never fire); computeTrend() (~158-162) consequently always reports 'stable'.
  • The handler unconditionally returns dataAvailable: true (end of getGivingSummary) since the computation has no external dependency that can fail — there is no path by which this endpoint ever reports data as unavailable or stale.
  • src/locales/en.json:1700 — the panel's info tooltip: "Combines live sampling with published annual reports" and "GoFundMe, GlobalGiving, JustGiving campaign sampling" — verified false against the source above; no sampling occurs.
  • src/components/GivingPanel.ts (~36-60) — renders Activity Index, Trend, and Est. Daily Flow as color-coded headline stat boxes; verified via grep: zero calls to Panel.setDataBadge() (the app's standing live/cached/unavailable disclosure convention, src/components/Panel.ts:620) anywhere in the file. The per-platform breakdown further down the panel does show an honest "annual" freshness badge per row — only the headline composite lacks any qualifier.

Why this matters

This is presented as a "real-time global intelligence dashboard" metric with a moving trend indicator and an always-current timestamp, but it is entirely static arithmetic on 1-2 year old published totals, with an explicit, incorrect claim of "live sampling" in the product's own explanatory copy. A user has no way to know — and is actively told the opposite of — the fact that "rising/falling/stable" and the headline dollar figure never reflect anything that happened today.

Attack or failure scenario

Not applicable as a technical exploit — the failure mode is that every viewer of this panel is shown a fabricated "live" signal with a false description of its own methodology, indefinitely, with no code path that would ever surface this as stale, estimated, or non-live to the user.

Root cause

The feature was built entirely from published annual-report baselines (reasonable, given the difficulty of live-sampling GoFundMe/GlobalGiving/JustGiving), but the response schema and UI copy were written as if live sampling were happening, rather than being honestly labeled as a static/annual estimate composite.

Recommended fix

  • Correct the info tooltip (en.json:1700) to state plainly that the index is derived from published annual reports, not live sampling.
  • Remove or repurpose lastUpdated/generatedAt so they reflect the actual age of the underlying published data (e.g. oecdDataYear, report publication dates) instead of the current request time.
  • Either implement genuine live sampling for at least one platform, or relabel trend/activityIndex as a static "estimated composite" rather than an index with directional movement, since it structurally cannot move without live inputs.
  • Apply Panel.setDataBadge() (or equivalent) to the headline stats to disclose "annual estimate" the same way the per-platform table already does.

Acceptance criteria

  • The tooltip and any other UI copy accurately describe the index as derived from static annual-report baselines, with no claim of live sampling that isn't true.
  • Timestamps shown to the user reflect the actual freshness of the underlying published data, not request time.
  • The headline stat boxes carry the same freshness disclosure the per-platform table already has.

Suggested labels

bug, documentation

Priority

P1

Severity

High — a flagship composite metric on a paid intelligence product is entirely fabricated arithmetic on stale published data, actively mislabeled as "live sampling" in the product's own UI copy, with a real-time timestamp attached to numbers that structurally cannot change.

Confidence

Confirmed — verified directly against source: zero fetch calls in the handler, all constants and zeroed sampling fields read directly, and the false tooltip text confirmed verbatim in en.json.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions