Skip to content

fix: drop empty APR timeseries buckets#403

Open
matheus1lva wants to merge 1 commit into
mainfrom
codex/fix-yvusd-apr-zero-buckets
Open

fix: drop empty APR timeseries buckets#403
matheus1lva wants to merge 1 commit into
mainfrom
codex/fix-yvusd-apr-zero-buckets

Conversation

@matheus1lva
Copy link
Copy Markdown
Collaborator

@matheus1lva matheus1lva commented May 9, 2026

Summary

Fixes bad zero/empty interior APR datapoints by dropping aggregate buckets that contain no non-zero samples. The existing SQL already treated zero values as missing with NULLIF; this removes the COALESCE fallback that converted fully empty buckets back into chart-visible zeroes.

How to review

  • Review the timeseries aggregation changes in packages/web/app/api/gql/resolvers/timeseries.ts and packages/web/app/api/rest/timeseries/db.ts.
  • Confirm both GraphQL and REST/cache refresh paths use the same rule: average non-zero samples, then omit buckets where the average is null.
  • Confirm this does not add interpolation or smoothing; it only removes buckets made entirely of zero/null samples.
  • Note: the unlocked yvUSD wiggle is not fixed here. This change should make bad zero drops disappear, while any remaining oscillation should be investigated in the upstream yvUSD APR webhook/provider data.

Local/staging setup notes

  • For API-only validation against existing affected rows, set y our POSTGRES_* env vars to point to the read-only replica.
  • have redis running with docker-compose up -d redis
  • set redis into the env vars as well

Test plan

  • Manual: start local web API from repo root.
bun --filter web dev
  • Manual: in a second terminal, set endpoint variables.
export API_ORIGIN="http://localhost:3000"
export GQL_URL="${API_ORIGIN%/}/api/gql"
export LOCKED_YVUSD="0xAaaFEa48472f77563961Cdb53291DEDfB46F9040"
export UNLOCKED_YVUSD="0x696d02Db93291651ED510704c9b286841d506987"
  • Manual: run raw locked yvUSD netAPR GraphQL curl.
curl -sS "$GQL_URL" \
  -H 'content-type: application/json' \
  --data-binary @- <<'JSON'
{
  "query": "query LockedYvusdNetApr { timeseries(chainId: 1, address: \"0xAaaFEa48472f77563961Cdb53291DEDfB46F9040\", label: \"yvusd-estimated-apr\", component: \"netAPR\", period: \"1 day\", limit: 1000) { chainId address label component value period time } }"
}
JSON
  • Manual: run locked yvUSD netAPR validation. Expected result: output shows zeroRows: 0, nonNumericRows: 0, unordered: 0, and rows greater than 0 if DB has the affected series.
curl -sS "$GQL_URL" \
  -H 'content-type: application/json' \
  --data-binary @- <<'JSON' \
| jq '{
  rows: (.data.timeseries // [] | length),
  zeroRows: (.data.timeseries // [] | map(select(.value == 0)) | length),
  nonNumericRows: (.data.timeseries // [] | map(select((.value | type) != "number")) | length),
  unordered: (.data.timeseries // [] as $rows | [range(1; ($rows | length)) as $i | select(($rows[$i - 1].time | tonumber) > ($rows[$i].time | tonumber))] | length)
}'
{
  "query": "query LockedYvusdNetApr { timeseries(chainId: 1, address: \"0xAaaFEa48472f77563961Cdb53291DEDfB46F9040\", label: \"yvusd-estimated-apr\", component: \"netAPR\", period: \"1 day\", limit: 1000) { chainId address label component value period time } }"
}
JSON
  • Manual: run unlocked yvUSD netAPR validation. Expected result: output shows zeroRows: 0 and prints min / max. Any remaining non-zero wiggle is source/provider behavior, not this SQL fallback.
curl -sS "$GQL_URL" \
  -H 'content-type: application/json' \
  --data-binary @- <<'JSON' \
| jq '{
  rows: (.data.timeseries // [] | length),
  zeroRows: (.data.timeseries // [] | map(select(.value == 0)) | length),
  min: (.data.timeseries // [] | if length > 0 then map(.value) | min else null end),
  max: (.data.timeseries // [] | if length > 0 then map(.value) | max else null end)
}'
{
  "query": "query UnlockedYvusdNetApr { timeseries(chainId: 1, address: \"0x696d02Db93291651ED510704c9b286841d506987\", label: \"yvusd-estimated-apr\", component: \"netAPR\", period: \"1 day\", limit: 1000) { chainId address label component value period time } }"
}
JSON
  • Manual: compare against production for locked yvUSD netAPR. Expected result: local branch has no zero rows; production may still show zero rows until this PR deploys and cache refresh runs.
for URL in "https://kong.yearn.fi/api/gql" "$GQL_URL"; do
  echo "Checking $URL"
  curl -sS "$URL" \
    -H 'content-type: application/json' \
    --data-binary @- <<'JSON' \
  | jq '{
  rows: (.data.timeseries // [] | length),
  zeroRows: (.data.timeseries // [] | map(select(.value == 0)) | length)
}'
{
  "query": "query LockedYvusdNetApr { timeseries(chainId: 1, address: \"0xAaaFEa48472f77563961Cdb53291DEDfB46F9040\", label: \"yvusd-estimated-apr\", component: \"netAPR\", period: \"1 day\", limit: 1000) { chainId address label component value period time } }"
}
JSON
done
  • Manual: run REST cache historical refresh in staging/local if validating cached chart payloads. Expected result: command completes and cached data omits all-zero buckets.
bun packages/web/app/api/rest/timeseries/refresh-historical.ts
  • Manual: run REST latest refresh in staging/local if validating latest cache override behavior. Expected result: latest cache does not reintroduce zero rows.
bun packages/web/app/api/rest/timeseries/refresh.ts
  • Manual: open affected chart after refresh and confirm locked yvUSD visible zero drop/spike is gone.
open "$API_ORIGIN"
  • Automated (when applicable): web tests passed locally before PR update.
bun --filter web test
# Expected: 5 pass, 0 fail
  • Automated (when applicable): whitespace check passed locally before PR update.
git diff --check
# Expected: no output, exit code 0

Risk / impact

  • Impact is limited to timeseries aggregation responses: all-zero/all-null buckets are omitted instead of emitted as value 0.
  • This matches the existing intent of NULLIF(value, 0), which already treated zeros as missing samples.
  • Legitimate non-zero samples in a bucket are still averaged and returned unchanged.
    EOF 2>&1

Treat zero APR samples as missing during aggregation and exclude buckets that contain no non-zero values. This prevents indexer downtime gaps from being served as zero-value interior datapoints in yvUSD APR charts.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 9, 2026

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

Project Deployment Actions Updated (UTC)
kong Ready Ready Preview, Comment May 9, 2026 9:50pm

Request Review

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant