diff --git a/frontend/src/pages/OutlinerAdminReviewerStats.tsx b/frontend/src/pages/OutlinerAdminReviewerStats.tsx index a126ff8..c719689 100644 --- a/frontend/src/pages/OutlinerAdminReviewerStats.tsx +++ b/frontend/src/pages/OutlinerAdminReviewerStats.tsx @@ -14,10 +14,20 @@ type SortField = 'reviewer' | 'approvals' | 'rejections' | 'rejection_rate'; type ReviewVerifierSortField = 'reviewer' | 'approvals' | 'rejections' | 'total_segments'; type SortDir = 'asc' | 'desc'; -// Rejection % = rejections / approvals * 100 (0 when no approvals) -const rejectionRate = (row: { approvals: number; rejections: number }) => - row.approvals > 0 ? (row.rejections / row.approvals) * 100 : 0; -const REJECTION_RATE_FORMULA = 'Rejection % = (No. of Rejections / No. of Approvals) × 100'; +// Rejection % = rejections / (approvals + rejections) * 100 (0 when no decisions) +const rejectionRate = (row: { approvals: number; rejections: number }) => { + const denom = row.approvals + row.rejections; + return denom > 0 ? (row.rejections / denom) * 100 : 0; +}; +const REJECTION_RATE_FORMULA = + 'Rejection % = No. of Rejections / (No. of Approvals + No. of Rejections) × 100'; + +const shareRate = (part: number, total: number) => + total > 0 ? (part / total) * 100 : 0; +const VF_APPROVALS_RATE_FORMULA = 'Approvals % = No. of Approvals / Total Segments × 100'; +const VF_REJECTIONS_RATE_FORMULA = 'Rejections % = No. of Rejections / Total Segments × 100'; +const RV_APPROVALS_RATE_FORMULA = + 'Approvals % = No. of Approvals / (No. of Approvals + No. of Rejections) × 100'; const cardPanel = 'rounded-2xl border border-border/70 bg-card/95 p-6 shadow-elegant backdrop-blur-[2px]'; @@ -212,10 +222,16 @@ function OutlinerAdminReviewerStats() { {row.total_segments.toLocaleString()} - {row.approvals.toLocaleString()} + {row.approvals.toLocaleString()}{' '} + + ({shareRate(row.approvals, row.total_segments).toFixed(1)}%) + - {row.rejections.toLocaleString()} + {row.rejections.toLocaleString()}{' '} + + ({shareRate(row.rejections, row.total_segments).toFixed(1)}%) + ))} @@ -265,23 +281,13 @@ function OutlinerAdminReviewerStats() { - - - @@ -297,16 +303,16 @@ function OutlinerAdminReviewerStats() { {row.reviewer} - {row.approvals.toLocaleString()} + {row.approvals.toLocaleString()}{' '} + + ({shareRate(row.approvals, row.approvals + row.rejections).toFixed(1)}%) + - {row.rejections.toLocaleString()} - - - {rejectionRate(row).toFixed(1)}% + {row.rejections.toLocaleString()}{' '} + + ({rejectionRate(row).toFixed(1)}%) + ))} diff --git a/frontend/src/pages/OutlinerAdminStatistics.tsx b/frontend/src/pages/OutlinerAdminStatistics.tsx index 9787405..b1fba9f 100644 --- a/frontend/src/pages/OutlinerAdminStatistics.tsx +++ b/frontend/src/pages/OutlinerAdminStatistics.tsx @@ -26,6 +26,19 @@ const annotatorRejectionRate = (approved: number, rejectedSegments: number) => { const ANNOTATOR_REJECTION_RATE_FORMULA = 'Rejection % = rejected_segments / (segments_approved + rejected_segments) × 100'; +const editedRate = (edited: number, denom: number) => + denom > 0 ? (edited / denom) * 100 : 0; +const annotatorApprovalRate = (approved: number, rejectedSegments: number) => { + const denom = approved + rejectedSegments; + return denom > 0 ? (approved / denom) * 100 : 0; +}; +const ANNOTATOR_APPROVAL_RATE_FORMULA = + 'Approved % = segments_approved / (segments_approved + rejected_segments) × 100'; +const ANNOTATOR_EDITED_RATE_FORMULA = + 'Edited % = edited_segments / segments_approved × 100'; +const REVIEWER_EDITED_RATE_FORMULA = + 'Edited % = edited_segments / segments_reviewed × 100'; + const cardPanel = 'rounded-2xl border border-border/70 bg-card/95 p-6 shadow-elegant backdrop-blur-[2px]'; @@ -228,10 +241,7 @@ function OutlinerAdminStatistics() { /> - + - Rejected toggleAnnotatorSort('rejection_rate')} > - Rejection % + Rejected field="rejection_rate" active={annotatorSortField} @@ -275,24 +284,42 @@ function OutlinerAdminStatistics() { {row.name} - - {row.segments_approved.toLocaleString()} + + {row.segments_approved.toLocaleString()}{' '} + + ( + {annotatorApprovalRate( + row.segments_approved, + row.rejected_segments, + ).toFixed(1)} + %) + - {row.edited_segments.toLocaleString()} - - - {row.rejection_count.toLocaleString()} + {row.edited_segments.toLocaleString()}{' '} + + ({editedRate(row.edited_segments, row.segments_approved).toFixed(1)}%) + - {annotatorRejectionRate( - row.segments_approved, - row.rejected_segments, - ).toFixed(1)} - % + {row.rejection_count.toLocaleString()}{' '} + + ( + {annotatorRejectionRate( + row.segments_approved, + row.rejected_segments, + ).toFixed(1)} + %) + ))} @@ -300,24 +327,42 @@ function OutlinerAdminStatistics() { Total - - {annotatorTotal.toLocaleString()} + + {annotatorTotal.toLocaleString()}{' '} + + ( + {annotatorApprovalRate( + annotatorTotal, + annotatorRejectedSegmentsTotal, + ).toFixed(1)} + %) + - {annotatorEditedTotal.toLocaleString()} - - - {annotatorRejectedTotal.toLocaleString()} + {annotatorEditedTotal.toLocaleString()}{' '} + + ({editedRate(annotatorEditedTotal, annotatorTotal).toFixed(1)}%) + - {annotatorRejectionRate( - annotatorTotal, - annotatorRejectedSegmentsTotal, - ).toFixed(1)} - % + {annotatorRejectedTotal.toLocaleString()}{' '} + + ( + {annotatorRejectionRate( + annotatorTotal, + annotatorRejectedSegmentsTotal, + ).toFixed(1)} + %) + @@ -375,10 +420,7 @@ function OutlinerAdminStatistics() { /> - + - Rejected