fix(metrics): bucket closed PRs by close time, not their last-updated time - #9774
Conversation
… time `buildPublicQualityTrend` bucketed a closed PR's outcome by `updatedAt`, which GitHub bumps on every later comment, label change, or edit. A PR closed in an early week that received a comment weeks later was counted in the later week, deflating the early week's merge ratio toward 100% and inflating the later week's toward 0% — on a publicly-exposed payload. Reuse `closedAtMs` (now exported from review-recap.ts, the in-repo definition of "when did this PR close": `closedAt` when the payload carried one, else `updatedAt`) for the closed arm, wrapped in a small `finiteOrNull` to preserve the existing null-means-skip contract. Drop the dead `?? pr.createdAt` fallback: `createdAt` was never a close time and `updatedAt` is always written on the row. Merged-PR bucketing (mergedAt) is unchanged. Closes JSONbored#9700
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-29 08:10:36 UTC
Review summary Nits — 4 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Held for manual review: the gate and required CI are green, but GitHub reports this pull request's mergeable state as |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9774 +/- ##
==========================================
+ Coverage 76.58% 76.64% +0.05%
==========================================
Files 282 284 +2
Lines 59464 59610 +146
Branches 6555 6597 +42
==========================================
+ Hits 45543 45689 +146
Misses 13639 13639
Partials 282 282
Flags with carried forward coverage won't be shown. Click here to find out more.
|
What & why
buildPublicQualityTrendbuckets each PR's terminal outcome into a weekly bucket (src/services/public-quality-metrics.ts). The merged arm keyed on the true terminal timestamp (mergedAt), but the closed arm usedupdatedAt— which GitHub bumps on every later comment, label change, or edit. So a PR closed in week 1 that receives a comment in week 8 was counted in week 8'soutcomesClosed, deflating week 1'smergeRatioPcttoward 100% and inflating week 8's toward 0%. This trend is exposed publicly (opt-inpublicQualityMetrics).The in-repo sibling already does this correctly:
src/services/review-recap.ts'sclosedAtMsis documented as "the best-available terminal timestamp for a closed-unmerged PR:closedAtwhen GitHub's payload carried one, elseupdatedAt". The public-metrics version also carried a dead?? pr.createdAtthird fallback —updatedAtis always written on the row, andcreatedAtwas never a close time.The fix
closedAtMsfromreview-recap.ts(behaviour and doc comment unchanged), so there is one definition of "when did this PR close", and import it intopublic-quality-metrics.ts.finiteOrNull(closedAtMs(pr))— a tiny local adapter that mapsclosedAtMs'NaN-means-skip contract onto thenull-means-skip shapeparseStampalready uses at the call site, so a PR whoseclosedAtandupdatedAtare both unusable is skipped exactly as before.?? pr.createdAtfallback is removed.closedAtMsalready carries theupdatedAtfallback for rows written beforeclosed_atwas persisted, so no PR that used to count viaupdatedAtstops counting.terminalOutcome, the gate-outcome bucketing (outcome.blockedAt ?? outcome.updatedAt, a different record type),MIN_GATE_TREND_SAMPLE, ormergeRatioPct. No new column or migration.grepconfirmspr.updatedAt ?? pr.createdAtno longer appears inpublic-quality-metrics.ts.Tests (
test/unit/public-quality-metrics.test.ts)New
#9700suite:closedAtin an early week andupdatedAtbumped into a later week is counted in the close week, not the update week (named regression; fails on currentmain).closedAt: nulland a validupdatedAtis still counted, inupdatedAt's week (fallback arm).closedAtandupdatedAtmissing/unparseable is skipped and appears in no bucket (fails on currentmain, which fell back tocreatedAt).closedAt/updatedAtpoint at other weeks.The existing "skips trend rows with unparseable timestamps" case, which relied on the removed
createdAtfallback, is updated to assert the created-only PR is now skipped.Validation
npm run typecheckgreen; both suites green.git diff --checkclean; no route/schema/wrangler/migration change, nothing to regenerate.Closes #9700