diff --git a/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotRecord.java b/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotRecord.java index 65e44d4a..5a91a33e 100644 --- a/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotRecord.java +++ b/src/main/java/com/clearfolio/viewer/analytics/KpiSnapshotRecord.java @@ -14,7 +14,7 @@ * @param succeededJobs succeeded jobs in the snapshot * @param failedJobs failed jobs in the snapshot * @param deadLetteredJobs dead-lettered jobs in the snapshot - * @param conversionSuccessRate succeeded jobs divided by total jobs + * @param conversionSuccessRate succeeded jobs divided by terminal succeeded/failed jobs * @param p95TimeToPreviewMs p95 time to preview, when available */ public record KpiSnapshotRecord( diff --git a/src/main/java/com/clearfolio/viewer/api/KpiSnapshotExportResponse.java b/src/main/java/com/clearfolio/viewer/api/KpiSnapshotExportResponse.java index 82babf93..b5c40a29 100644 --- a/src/main/java/com/clearfolio/viewer/api/KpiSnapshotExportResponse.java +++ b/src/main/java/com/clearfolio/viewer/api/KpiSnapshotExportResponse.java @@ -15,7 +15,7 @@ * @param succeededJobs succeeded jobs in the exported snapshot * @param failedJobs failed jobs in the exported snapshot * @param deadLetteredJobs dead-lettered jobs in the exported snapshot - * @param conversionSuccessRate succeeded jobs divided by total jobs + * @param conversionSuccessRate succeeded jobs divided by terminal succeeded/failed jobs * @param p95TimeToPreviewMs p95 time to preview, when available */ public record KpiSnapshotExportResponse( diff --git a/src/main/java/com/clearfolio/viewer/api/KpiSnapshotResponse.java b/src/main/java/com/clearfolio/viewer/api/KpiSnapshotResponse.java index a9a29b76..fe0e4f5e 100644 --- a/src/main/java/com/clearfolio/viewer/api/KpiSnapshotResponse.java +++ b/src/main/java/com/clearfolio/viewer/api/KpiSnapshotResponse.java @@ -17,7 +17,7 @@ * @param succeededJobs jobs with a ready preview artifact * @param failedJobs jobs in failed state * @param deadLetteredJobs failed jobs that exhausted retry handling - * @param conversionSuccessRate succeeded jobs divided by total jobs + * @param conversionSuccessRate succeeded jobs divided by terminal succeeded/failed jobs * @param p95TimeToPreviewMs p95 processing time for succeeded jobs with timestamps */ public record KpiSnapshotResponse( @@ -64,7 +64,8 @@ public static KpiSnapshotResponse from(List jobs) { } int total = jobs.size(); - double successRate = total == 0 ? 0.0 : (double) succeeded / total; + int terminal = succeeded + failed; + double successRate = terminal == 0 ? 0.0 : (double) succeeded / terminal; return new KpiSnapshotResponse( total, submitted, diff --git a/src/test/java/com/clearfolio/viewer/api/KpiSnapshotResponseTest.java b/src/test/java/com/clearfolio/viewer/api/KpiSnapshotResponseTest.java index 1e2dd385..eba5afc2 100644 --- a/src/test/java/com/clearfolio/viewer/api/KpiSnapshotResponseTest.java +++ b/src/test/java/com/clearfolio/viewer/api/KpiSnapshotResponseTest.java @@ -29,11 +29,44 @@ void fromSkipsPreviewTimingWhenSucceededJobsDoNotHaveCompleteTimestamps() { assertNull(response.p95TimeToPreviewMs()); } + @Test + void conversionSuccessRateUsesOnlyTerminalOutcomes() { + KpiSnapshotResponse response = KpiSnapshotResponse.from(List.of( + job(ConversionJobStatus.SUCCEEDED), + job(ConversionJobStatus.SUCCEEDED), + job(ConversionJobStatus.FAILED), + job(ConversionJobStatus.SUBMITTED), + job(ConversionJobStatus.PROCESSING) + )); + + assertEquals(5, response.totalJobs()); + assertEquals(1, response.submittedJobs()); + assertEquals(1, response.processingJobs()); + assertEquals(2, response.succeededJobs()); + assertEquals(1, response.failedJobs()); + assertEquals(2.0 / 3.0, response.conversionSuccessRate(), 1.0e-12); + } + + @Test + void conversionSuccessRateIsZeroUntilOneJobReachesATerminalOutcome() { + KpiSnapshotResponse response = KpiSnapshotResponse.from(List.of( + job(ConversionJobStatus.SUBMITTED), + job(ConversionJobStatus.PROCESSING) + )); + + assertEquals(0.0, response.conversionSuccessRate()); + } + private ConversionJob succeededJob(Instant startedAt, Instant completedAt) { - ConversionJob job = mock(ConversionJob.class); - when(job.getStatus()).thenReturn(ConversionJobStatus.SUCCEEDED); + ConversionJob job = job(ConversionJobStatus.SUCCEEDED); when(job.getStartedAt()).thenReturn(startedAt); when(job.getCompletedAt()).thenReturn(completedAt); + return job; + } + + private ConversionJob job(ConversionJobStatus status) { + ConversionJob job = mock(ConversionJob.class); + when(job.getStatus()).thenReturn(status); when(job.isDeadLettered()).thenReturn(false); return job; } diff --git a/src/test/java/com/clearfolio/viewer/controller/AnalyticsControllerTest.java b/src/test/java/com/clearfolio/viewer/controller/AnalyticsControllerTest.java index 7752589e..da895f91 100644 --- a/src/test/java/com/clearfolio/viewer/controller/AnalyticsControllerTest.java +++ b/src/test/java/com/clearfolio/viewer/controller/AnalyticsControllerTest.java @@ -84,7 +84,7 @@ void kpiSnapshotSummarizesCurrentJobStates() { .jsonPath("$.succeededJobs").isEqualTo(1) .jsonPath("$.failedJobs").isEqualTo(1) .jsonPath("$.deadLetteredJobs").isEqualTo(1) - .jsonPath("$.conversionSuccessRate").value(value -> assertEquals(0.25, (Double) value)) + .jsonPath("$.conversionSuccessRate").value(value -> assertEquals(0.5, (Double) value)) .jsonPath("$.p95TimeToPreviewMs").value(value -> { assertNotNull(value); assertTrue(((Number) value).longValue() >= 0L);