feat: score trend samples by percentile and sample recent uploads - #71
Merged
devhims merged 3 commits intoSep 21, 2026
Merged
Conversation
…ploads the report scored every video with fitted curves against the sample median, so a score moved when the divisor moved and nobody could say why 22 or why a 21 day decay. each signal is now a percentile rank inside the sample: velocity, freshness, channel performance, engagement and acceleration. the weights are unchanged, so 50 still means the middle of the sample, but the number now reads as a position and stays comparable when the sample's absolute numbers move. methodologyVersion goes to 3.0. a lifetime average could also win the ranking. an estimated video carries 0.55 weight on views divided by age, so an old hit outranked a genuinely accelerating new video, and it could be banded Breakout on that basis. Breakout now requires measured growth between snapshots and at least five enriched videos, since the sample is its own reference frame. relevance search returns a topic's greatest hits, so recent uploads were being crowded out: the real Claude Code sample held one video from the last seven days out of eight. the candidate pool now reads deeper and up to half the enrichment budget is reserved for videos published inside a 14 day window, with the per-channel cap only relaxed to avoid returning a short sample. the report says how many recent candidates it saw and how recent uploads compare with the established ones, and length advice comes from recent uploads when the window holds enough of them. searchRank now carries the video's position in the relevance list rather than its position in the enrichment set, and is absent for a video only the recency pass found. the dashboard was showing lifetime averages even where measured growth existed. charts, the leaderboard and the scatter now show the velocity the score actually used and label it measured or estimated. the cumulative-views tile is replaced by recent versus established, which is the actionable one. not included: date-filtered search. SearchFilters declares dateFrom, dateTo and sort 'date' but youtube-client implements none of them, so an exact window needs a library release. the recency window is derived from each candidate's published age text instead, which cannot see recent uploads that rank too poorly to appear at all. the report states that limitation in its confidence reasons.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #67. Base retargets to
mainonce that merges.Why
The Trend Lab's numbers were internally consistent but answered the wrong question. Three specific failures, all visible in a real
Claude Codescan:50 + 22 * log2(v / median),exp(-ageHours / 504),engagementRate * 12). The median for that query moved from 276 to 112 views/hour between two scans hours apart, so a score of 58 meant different things each time, and nobody can say why 22.What changed
Percentile scoring. Velocity, freshness, channel performance, engagement and acceleration are now percentile ranks inside the sample. Weights are unchanged, so 50 is still the middle, but the number reads as a position and survives the divisor moving. A missing signal scores 50 so it neither helps nor hurts.
methodologyVersiongoes to3.0, and each video exposespercentiles,velocityRankandeffectiveViewsPerHour.Breakout requires measurement. It now needs
signalSource: observedand at least 5 enriched videos.confidence.reasonssays how many videos were withheld and why.Recency reservation. The candidate pool reads deeper (4 pages or 4x the limit) and up to half the enrichment budget goes to videos published inside a 14 day window. The per-channel cap of 3 is only relaxed to avoid returning a short sample. New
windowblock andsummary.medianRecentViewsPerHour/summary.recentVelocityLift; length advice comes from recent uploads when the window holds at least two.searchRankis now the relevance position rather than the enrichment position, and is absent for a video only the recency pass found.Dashboard. Charts, leaderboard and scatter were showing lifetime averages even where measured growth existed. They now show the velocity the score used, labelled measured or estimated. The cumulative-views tile is replaced by recent versus established.
Effect on the real sample
Same eight videos, scored both ways:
The new top row is the one a creator can act on. Breakout count is 0, honestly, because nothing leads on every axis.
Not included
Date-filtered search.
SearchFiltersdeclaresdateFrom,dateToandsort: 'date', butpackages/all-things-youtube/src/youtube-client.tsimplements none of them: onlytype,channelId,minViews,duration,live,captionsOnlyandsort: 'views'do anything. An exact window needs a library release plus a processor lockfile bump, so the window is derived from each candidate's published-age text instead. That cannot see recent uploads ranking too poorly to appear at all, and the report says so inconfidence.reasons.Two follow-ups this exposes, both out of scope here: the agent's
search_youtubetool advertises those same unimplemented filters to the model, and a cold scan still sheds videos (This YouTube processor is at capacity.) because enrichment fans out 6 videos, which is 12 concurrent container calls.Also deferred from the design discussion: topic-level rollups in D1 so scans produce week-over-week deltas (needs a migration), and subscriber tiers for a "can a small channel win here" signal (needs one
getChannelper unique channel).Testing
platform: 669 tests pass, 10 intrends.test.tscovering percentile midpoints, the Breakout gate on both estimated momentum and thin samples, the recency reservation against the channel cap, and neutral ranks for missing signals.web: 54 tests pass.tsc --noEmitclean in both packages;npm run docs:checkclean after regeneratingdocs/api-reference/openapi.json.buildTrendReportin a throwaway test, since the scoring change cannot be verified against production until this deploys.