profile aggregation

Almost all of this is comments in ProfilePage.tsx. Those comments are the spec.

Split worlds#

GitHub stats.totalSolved is commits. Coding cards drive “problems solved.” Dev cards never enter that sum. UI copy: commits and pull requests are engineering output, never mixed into problems solved.

Ledgers#

  • Problem solving: solved + contests
  • Engineering: commits + PRs (fetchGitHubEngineering, not the card)
  • Consistency: active days summed. Best/current streak is max across platforms, not a merged calendar streak

Difficulty band#

Not three circular gauges on /profile. A segmented band of easy/medium/hard from stats.byDifficulty. Circular meters (DifficultyMeter) are for per-platform pages.

Languages vs topics#

  • GitHub topicAnalysis is language percentages. Averaged across accounts that have data. Private-repo accounts with zeros are dropped so they do not dilute.
  • Coding topicAnalysis is problem counts, merged, top 14. Dev cards excluded on purpose.

Rating lines#

Prefer rating.history. If contest history is longer, use that. One contest is a single point. Filter: contests.rating != null || contests.rank.

Radar#

Needs at least 3 coding platforms. Competitive axes = rating (max if stacked accounts). DSA axes = solved (sum if stacked). Each family normalized to its own max so 2000 rating does not crush 165 solved.

Heatmap merge#

Card heatmaps are fetched as full history (view=all). Source toggle: all | coding | github. Range: last 365 or a calendar year. Merge is sum of counts per date. Hover breakdown keeps {platform, username, count}. Levels are fixed buckets 1 / 3 / 6 / 10, not per-max quartiles.

UniversalHeatmap on deep dives is a different widget: local ymd() to avoid UTC off-by-one, GitHub year-keyed calendars, quartile levels. Do not describe one heatmap as if there were only one.

Chart sizing#

MeasuredChart uses ResizeObserver because Recharts ResponsiveContainer measures 0 under React 19 StrictMode double-mount.

Share#

Long URL = window.location.href. Short URL = upsert primary config, copy /{claimedUsername}. There is no /s/:code anymore. The drop migration removed short_links. The reserved-name widen stayed.