Skip to content

fix(web): compute stats and reputation score from database aggregates (#185) - #377

Merged
blockchain-maxis merged 3 commits into
blockchain-maxis:mainfrom
ntttttbl123-blip:fix/profile-stats-aggregates-185
Sep 3, 2026
Merged

fix(web): compute stats and reputation score from database aggregates (#185)#377
blockchain-maxis merged 3 commits into
blockchain-maxis:mainfrom
ntttttbl123-blip:fix/profile-stats-aggregates-185

Conversation

@ntttttbl123-blip

Copy link
Copy Markdown
Contributor

Closes #185

Summary of Changes

Computes profile statistics (successful invocations count, distinct functions exercised, and reputation score) using database aggregate queries across the full operation history rather than deriving stats from the paginated first 100 rows.

Key Changes

  • Database Aggregates (�pps/web/lib/profiles.ts): Added \safeDbProfileStats\ and \getProfileStats\ to run database aggregations (\count\ of successful invocations and distinct function queries) across all operations belonging to a profile's wallets without truncation.
  • Profile Page & tRPC Router (�pps/web/app/p/[handle]/page.tsx, �pps/web/lib/server/trpc.ts): Updated callers to fetch full dataset stats via \getProfileStats(handle, operations).
  • Unit Tests (�pps/web/lib/profiles.test.ts): Added unit tests verifying \computeStats\ scoring formula and \getProfileStats\ execution.

Verification Status

  • \pnpm --filter @signet/web test: All 63 tests passing.
  • \pnpm --filter @signet/web typecheck: Passed with zero TypeScript errors.

  • ode scripts/check-docs.mjs: Passed cleanly.

@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@ntttttbl123-blip Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@netlify

netlify Bot commented Aug 31, 2026

Copy link
Copy Markdown

Deploy Preview for stellar-signet ready!

Name Link
🔨 Latest commit 87a568f
🔍 Latest deploy log https://app.netlify.com/projects/stellar-signet/deploys/6a9962382153130007405169
😎 Deploy Preview https://deploy-preview-377--stellar-signet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

@ntttttbl123-blip is attempting to deploy a commit to the blockchainmaxis-8449's projects Team on Vercel.

A member of the Team first needs to authorize it.

@blockchain-maxis blockchain-maxis left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Computing the stats from a database aggregate instead of the paginated 100-row slice is the right fix for #185, and the fallback shape — getProfileStats tries the DB, falls back to computeStats over whatever operations the caller already fetched — means a deployment with no DATABASE_URL behaves exactly as it does today. Threading it through both app/p/[handle]/page.tsx and the profile tRPC procedure covers both readers.

The scoring formula is now written twice, and that's the thing I'd most like changed. safeDbProfileStats has:

const reputation = Math.min(100, Math.min(60, invocations * 6) + Math.min(40, uniqueFunctions * 10));

which is character-for-character what computeStats already does at apps/web/lib/profiles.ts:214-217. Two copies of a scoring rule drift — someone tunes the caps in one and the same profile scores differently depending on whether the database happened to be reachable, which is a nastier version of the bug this PR is fixing. Please extract it, e.g. function scoreReputation(invocations: number, uniqueFunctions: number): number, and call it from both. Your new computeStats test asserting 2 * 6 + 2 * 10 would then be pinning the one implementation rather than one of two.

Smaller note: distinct: ['function', 'decodedFunction'] gives distinct pairs, so the new Set(...) after it is what actually produces the unique-function count — the two aren't redundant, but it's worth a comment saying the distinct is a pre-filter and the Set is the real dedupe on the coalesced name, otherwise the next reader will assume one of them is dead.

The other blocker is CI. e2e smoke tests fails on this branch:

> 16 |   await expect(page.getByPlaceholder('your-handle')).toBeVisible();
     at openClaimForm (apps/web/e2e/claim.spec.ts:16:54)

Being straight with you about what I know: main is green, and this reproduced across all three Playwright retries and again on a full re-run of the job, so it isn't the flake it first looks like. But I could not tie it to your diff — nothing here touches the marketing page or the claim form, and there's no server error in the run, the form just never opens. The one variable I can point at is that this branch is based on 8216232 and the claim flow has moved since (#347 reworked connect-wallet.tsx and claim.spec.ts).

So: rebase onto main and push. If the e2e failure goes with the rebase, it was the stale base and we're done. If it survives, tell me and I'll dig into it myself rather than leave you chasing something that may not be yours.

Resolves the conflict with main's truncation-disclosure work additively:
the operations window keeps its `truncated`/`cap` metadata, and the stats
are now sourced from `getProfileStats`, which reports whether they are exact
database aggregates or derived from that capped window.

@blockchain-maxis blockchain-maxis left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this is the right fix for #185: the count and the distinct-function query now run in the database over the whole history, so the score stops being a property of the page fetch.

Merged main into the branch as maintainer and resolved the conflict with the truncation-disclosure work that landed in the meantime. Three adjustments on top of your commit:

  • getProfileStats now returns an exact flag. The profile page kept main's truncated/cap metadata for the operations list, but the stats are no longer qualified as lower bounds when they came from a database aggregate — otherwise an exact total rendered as "412+".
  • safeDbProfileStats returns null instead of a zeroed result when the profile has no indexed operations. A profile row can exist with the operations still served by Horizon or the curated demo JSON, and a confident 0 next to a non-empty list is worse than falling back.
  • The reputation formula is extracted into one scoreOf helper shared by the in-memory and database paths, and the OG image and the dashboard were moved onto getProfileStats too, so all three surfaces agree on the same number.

@blockchain-maxis
blockchain-maxis merged commit 1126426 into blockchain-maxis:main Sep 3, 2026
10 of 11 checks passed
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.

Stats and reputation score are computed over a truncated operation set

2 participants