You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#1967 ("Maintainer analytics dashboard (gate precision, calibration, acceptance rate)") and #2197 ("feat(ui): finding acceptance-rate card") are both closed, and both pieces exist in the codebase today — but they were never connected to each other, so the shipped AcceptanceRateCard UI has been showing its "not yet available" empty state in production since #2197 landed.
Backend (src/review/stats.ts:198-218,326,361): computeFindingAcceptance(env, { days, nowMs }) → FindingAcceptanceAggregate ({ flagged, addressed, unaddressed, acceptanceRate }) is fully implemented, tested, and already wired into computeStats()'s StatsPayload.findingAcceptance (line 442) — but computeStats/StatsPayload only powers GET /stats/data (handleStats), which is gated by GITTENSORY_REVIEW_STATS_TOKEN. I could not find handleStats mounted as a live route in src/index.ts/src/server.ts at all — it may be effectively dead code today, a separate pre-existing condition worth double-checking while in this area.
Frontend (apps/gittensory-ui/src/routes/app.analytics.tsx:129,236 + apps/gittensory-ui/src/components/site/app-panels/acceptance-rate-card.tsx): AcceptanceRateCard already exists, already renders correctly, and already expects data.acceptance?: FindingAcceptance where FindingAcceptance = { windowDays, accepted, total, rate }. Its own header comment says explicitly: "the backend acceptance computation is tracked separately in Maintainer analytics dashboard (gate precision, calibration, acceptance rate) #1967 ... degrades to a 'not yet available' empty state until it lands."
The actual dashboard this card lives on is OperatorDashboardPayload (src/services/operator-dashboard.ts:51, built by buildOperatorDashboardPayload), which currently imports and calls only computeCycleTimeAggregate from stats.ts (line 32, 127) — never computeFindingAcceptance. That's the actual gap: Maintainer analytics dashboard (gate precision, calibration, acceptance rate) #1967 built the computation, but wired it to a route (handleStats) that isn't the one the UI card reads from.
Requirements
In src/services/operator-dashboard.ts, add computeFindingAcceptance(env, { days: 90, nowMs: Date.now() }) to the existing Promise.all([...]) batch (around line 115-131), matching the exact pattern and "fails safe to an empty aggregate" comment style already used for computeCycleTimeAggregate (line 126-127) and computeGateEval (line 124-125).
Map the shape, don't pass the aggregate through directly — FindingAcceptanceAggregate ({flagged, addressed, unaddressed, acceptanceRate}) and the UI's FindingAcceptance ({windowDays, accepted, total, rate}) use different field names for the same concepts:
windowDays ← the days value passed into the call (90)
accepted ← addressed
total ← flagged
rate ← acceptanceRate
Add the mapped result as acceptance on the object buildOperatorDashboardPayload returns, matching OperatorDashboardPayload's expected shape (currently defined without an acceptance field at all — add it there too, per apps/gittensory-ui/src/routes/app.analytics.tsx:129's acceptance?: FindingAcceptance).
Regenerate apps/gittensory-ui/public/openapi.json (npm run ui:openapi) since this adds a field to a response schema.
Test to the usual 99%-patch-coverage bar: both branches of the flagged→acceptanceRate null case (0 flagged), and a real mapped-value case. AcceptanceRateCard's own existing tests (acceptance-rate-card.test.tsx) already cover the UI side and should need no changes — this is a pure backend-wiring + type change.
Separately worth a quick look (not necessarily in this same PR, flagging for awareness): confirm whether handleStats/GET /stats/data is actually mounted anywhere at all. If it's genuinely dead/unreachable, that's a separate small cleanup opportunity.
Deliverables
computeFindingAcceptance called from buildOperatorDashboardPayload, mapped to the UI's FindingAcceptance shape, and included in OperatorDashboardPayload.
Updated/added unit test(s) in test/unit/operator-dashboard.test.ts covering both the has-data and zero-flagged cases.
AcceptanceRateCard on the operator dashboard (/app/analytics or wherever it's mounted) shows real numbers — "X% of flagged findings were acted on, Y of Z" — instead of its empty state, the moment this ships. No feature flag or config needed; this is pure wiring of two already-complete, already-tested pieces that were built against different consumers.
Context
#1967("Maintainer analytics dashboard (gate precision, calibration, acceptance rate)") and#2197("feat(ui): finding acceptance-rate card") are both closed, and both pieces exist in the codebase today — but they were never connected to each other, so the shippedAcceptanceRateCardUI has been showing its "not yet available" empty state in production since #2197 landed.src/review/stats.ts:198-218,326,361):computeFindingAcceptance(env, { days, nowMs })→FindingAcceptanceAggregate({ flagged, addressed, unaddressed, acceptanceRate }) is fully implemented, tested, and already wired intocomputeStats()'sStatsPayload.findingAcceptance(line 442) — butcomputeStats/StatsPayloadonly powersGET /stats/data(handleStats), which is gated byGITTENSORY_REVIEW_STATS_TOKEN. I could not findhandleStatsmounted as a live route insrc/index.ts/src/server.tsat all — it may be effectively dead code today, a separate pre-existing condition worth double-checking while in this area.apps/gittensory-ui/src/routes/app.analytics.tsx:129,236+apps/gittensory-ui/src/components/site/app-panels/acceptance-rate-card.tsx):AcceptanceRateCardalready exists, already renders correctly, and already expectsdata.acceptance?: FindingAcceptancewhereFindingAcceptance = { windowDays, accepted, total, rate }. Its own header comment says explicitly: "the backend acceptance computation is tracked separately in Maintainer analytics dashboard (gate precision, calibration, acceptance rate) #1967 ... degrades to a 'not yet available' empty state until it lands."OperatorDashboardPayload(src/services/operator-dashboard.ts:51, built bybuildOperatorDashboardPayload), which currently imports and calls onlycomputeCycleTimeAggregatefromstats.ts(line 32, 127) — nevercomputeFindingAcceptance. That's the actual gap: Maintainer analytics dashboard (gate precision, calibration, acceptance rate) #1967 built the computation, but wired it to a route (handleStats) that isn't the one the UI card reads from.Requirements
src/services/operator-dashboard.ts, addcomputeFindingAcceptance(env, { days: 90, nowMs: Date.now() })to the existingPromise.all([...])batch (around line 115-131), matching the exact pattern and "fails safe to an empty aggregate" comment style already used forcomputeCycleTimeAggregate(line 126-127) andcomputeGateEval(line 124-125).FindingAcceptanceAggregate({flagged, addressed, unaddressed, acceptanceRate}) and the UI'sFindingAcceptance({windowDays, accepted, total, rate}) use different field names for the same concepts:windowDays← thedaysvalue passed into the call (90)accepted←addressedtotal←flaggedrate←acceptanceRateacceptanceon the objectbuildOperatorDashboardPayloadreturns, matchingOperatorDashboardPayload's expected shape (currently defined without anacceptancefield at all — add it there too, perapps/gittensory-ui/src/routes/app.analytics.tsx:129'sacceptance?: FindingAcceptance).apps/gittensory-ui/public/openapi.json(npm run ui:openapi) since this adds a field to a response schema.AcceptanceRateCard's own existing tests (acceptance-rate-card.test.tsx) already cover the UI side and should need no changes — this is a pure backend-wiring + type change.handleStats/GET /stats/datais actually mounted anywhere at all. If it's genuinely dead/unreachable, that's a separate small cleanup opportunity.Deliverables
computeFindingAcceptancecalled frombuildOperatorDashboardPayload, mapped to the UI'sFindingAcceptanceshape, and included inOperatorDashboardPayload.test/unit/operator-dashboard.test.tscovering both the has-data and zero-flagged cases.apps/gittensory-ui/public/openapi.json.Expected outcome
AcceptanceRateCardon the operator dashboard (/app/analyticsor wherever it's mounted) shows real numbers — "X% of flagged findings were acted on, Y of Z" — instead of its empty state, the moment this ships. No feature flag or config needed; this is pure wiring of two already-complete, already-tested pieces that were built against different consumers.