@@ -29,7 +29,7 @@ import type {
2929import { computeFleetAnalytics , type FleetAnalytics } from "../orb/analytics" ;
3030import { computeAgentHealth , computeCalibration , type AgentHealth , type Calibration } from "../review/ops" ;
3131import { computeGateEval , type GateEvalReport } from "../review/parity" ;
32- import { computeCycleTimeAggregate , type CycleTimeAggregate } from "../review/stats" ;
32+ import { computeCycleTimeAggregate , computeFindingAcceptance , type CycleTimeAggregate } from "../review/stats" ;
3333import { loadUpstreamStatus , type UpstreamStatus } from "../upstream/ruleset" ;
3434import { nowIso } from "../utils/json" ;
3535import { buildRecommendationQualityReport , type RecommendationQualityReport } from "./recommendation-quality-report" ;
@@ -48,6 +48,17 @@ export type OperatorDashboardNoiseMetric = {
4848 spark : number [ ] ;
4949} ;
5050
51+ /** Finding acceptance rate (#1967), reshaped for the dashboard's `AcceptanceRateCard` (see
52+ * apps/gittensory-ui/src/components/site/app-panels/acceptance-rate-card.tsx). The card's field names
53+ * (windowDays/accepted/total/rate) intentionally differ from `FindingAcceptanceAggregate`'s
54+ * (flagged/addressed/unaddressed/acceptanceRate) — this is the UI-facing shape, mapped in buildOperatorDashboardPayload. */
55+ export type OperatorDashboardFindingAcceptance = {
56+ windowDays : number ;
57+ accepted : number ;
58+ total : number ;
59+ rate : number | null ;
60+ } ;
61+
5162export type OperatorDashboardPayload = {
5263 generatedAt : string ;
5364 metrics : OperatorDashboardMetric [ ] ;
@@ -76,6 +87,9 @@ export type OperatorDashboardPayload = {
7687 // Slop-band calibration (#2196): org-wide per-band merge/close rates over resolved PRs carrying a persisted
7788 // slop band — is the deterministic slop score predictive? Bands only, never raw scores. Fails safe to empty.
7889 slopCalibration : SlopOutcomeCalibration ;
90+ // Finding acceptance rate (#1967): share of gate-flagged (hold|close) PRs later merged, reshaped to the
91+ // AcceptanceRateCard's field names. Fails safe to an empty aggregate (rate: null) on any read error.
92+ acceptance : OperatorDashboardFindingAcceptance ;
7993} ;
8094
8195const USAGE_WINDOW_DAYS = 7 ;
@@ -111,6 +125,7 @@ export async function buildOperatorDashboardPayload(
111125 calibration ,
112126 agentHealth ,
113127 slopCalibration ,
128+ findingAcceptance ,
114129 ] = await Promise . all ( [
115130 listRepositories ( env ) ,
116131 listInstallations ( env ) ,
@@ -135,6 +150,9 @@ export async function buildOperatorDashboardPayload(
135150 computeCalibration ( env , operatorAgentConfig ( env ) ) ,
136151 computeAgentHealth ( env , operatorAgentConfig ( env ) ) ,
137152 buildOrgSlopCalibration ( env ) ,
153+ // #1967: reuse the existing finding-acceptance aggregate (no new compute); fails safe to an empty
154+ // aggregate on any read error.
155+ computeFindingAcceptance ( env , { days : GATE_ANALYTICS_WINDOW_DAYS , nowMs : Date . now ( ) } ) ,
138156 ] ) ;
139157 const weeklyValueReport = buildWeeklyValueReport ( {
140158 generatedAt : nowIso ( ) ,
@@ -154,6 +172,13 @@ export async function buildOperatorDashboardPayload(
154172 } ) ;
155173 const installedRepos = repositories . filter ( ( repo : RepositoryRecord ) => repo . isInstalled ) . length ;
156174 const registeredRepos = repositories . filter ( ( repo : RepositoryRecord ) => repo . isRegistered ) . length ;
175+ // #1967: map FindingAcceptanceAggregate's field names onto the AcceptanceRateCard's expected shape.
176+ const acceptance : OperatorDashboardFindingAcceptance = {
177+ windowDays : GATE_ANALYTICS_WINDOW_DAYS ,
178+ accepted : findingAcceptance . addressed ,
179+ total : findingAcceptance . flagged ,
180+ rate : findingAcceptance . acceptanceRate ,
181+ } ;
157182 return {
158183 generatedAt : nowIso ( ) ,
159184 metrics : [
@@ -239,6 +264,7 @@ export async function buildOperatorDashboardPayload(
239264 calibration,
240265 agentHealth,
241266 slopCalibration,
267+ acceptance,
242268 } ;
243269}
244270
0 commit comments