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 |
|