rafmacalaba commited on
Commit
03cc8ff
Β·
1 Parent(s): d3a8b2a

feat: add progress tracking with docs/pages/mentions verification stats

Browse files

- /api/progress endpoint computes per-doc and overall stats
- ProgressBar component: collapsible bar showing verification %
- Expanded view: colored progress bars for docs, pages, mentions
- Shows count of human annotations

app/api/progress/route.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { HF_DATASET_BASE_URL, MAX_DOCS_TO_SCAN } from '../../../utils/config.js';
2
+
3
+ /**
4
+ * GET /api/progress
5
+ * Returns progress stats: total docs, pages, mentions, and how many are verified.
6
+ */
7
+ export async function GET() {
8
+ try {
9
+ const linksUrl = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/wbg_data/wbg_pdf_links.json`;
10
+ const linksRes = await fetch(linksUrl, {
11
+ headers: { 'Authorization': `Bearer ${process.env.HF_TOKEN}` },
12
+ next: { revalidate: 300 } // cache 5 min
13
+ });
14
+
15
+ if (!linksRes.ok) {
16
+ return new Response(JSON.stringify({ error: 'Failed to fetch links' }), { status: 500 });
17
+ }
18
+
19
+ const links = await linksRes.json();
20
+ const activeLinks = links
21
+ .filter(l => l.status === 'success' && l.has_revalidation === true)
22
+ .slice(0, MAX_DOCS_TO_SCAN);
23
+
24
+ // Fetch all docs in parallel
25
+ const results = await Promise.allSettled(
26
+ activeLinks.map(async (link) => {
27
+ const docUrl = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/wbg_extractions/doc_${link.index}/raw/doc_${link.index}_direct_judged.jsonl`;
28
+ const docRes = await fetch(docUrl, {
29
+ headers: { 'Authorization': `Bearer ${process.env.HF_TOKEN}` }
30
+ });
31
+ if (!docRes.ok) return null;
32
+
33
+ const pagesData = await docRes.json();
34
+
35
+ let totalMentions = 0;
36
+ let verifiedMentions = 0;
37
+ let totalPages = 0;
38
+ let completedPages = 0;
39
+ let humanAnnotations = 0;
40
+
41
+ for (const page of pagesData) {
42
+ const datasets = (page.datasets || []).filter(ds => {
43
+ // Exclude consensus non-datasets
44
+ if (ds.dataset_tag === 'non-dataset' && ds.dataset_name?.judge_agrees === true) {
45
+ return false;
46
+ }
47
+ return true;
48
+ });
49
+
50
+ if (datasets.length === 0) continue;
51
+
52
+ totalPages++;
53
+ totalMentions += datasets.length;
54
+
55
+ let pageVerified = 0;
56
+ for (const ds of datasets) {
57
+ if (ds.human_validated === true) {
58
+ verifiedMentions++;
59
+ pageVerified++;
60
+ }
61
+ if (ds.source === 'human') {
62
+ humanAnnotations++;
63
+ }
64
+ }
65
+
66
+ // A page is "completed" if all its mentions are verified
67
+ if (pageVerified === datasets.length) {
68
+ completedPages++;
69
+ }
70
+ }
71
+
72
+ const docComplete = totalPages > 0 && completedPages === totalPages;
73
+
74
+ return {
75
+ index: link.index,
76
+ totalPages,
77
+ completedPages,
78
+ totalMentions,
79
+ verifiedMentions,
80
+ humanAnnotations,
81
+ complete: docComplete,
82
+ };
83
+ })
84
+ );
85
+
86
+ const docs = results
87
+ .filter(r => r.status === 'fulfilled' && r.value !== null)
88
+ .map(r => r.value);
89
+
90
+ const summary = {
91
+ totalDocs: docs.length,
92
+ completedDocs: docs.filter(d => d.complete).length,
93
+ totalPages: docs.reduce((s, d) => s + d.totalPages, 0),
94
+ completedPages: docs.reduce((s, d) => s + d.completedPages, 0),
95
+ totalMentions: docs.reduce((s, d) => s + d.totalMentions, 0),
96
+ verifiedMentions: docs.reduce((s, d) => s + d.verifiedMentions, 0),
97
+ humanAnnotations: docs.reduce((s, d) => s + d.humanAnnotations, 0),
98
+ docs,
99
+ };
100
+
101
+ return new Response(JSON.stringify(summary), {
102
+ status: 200,
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ 'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=59'
106
+ }
107
+ });
108
+ } catch (error) {
109
+ console.error('Progress API error:', error);
110
+ return new Response(
111
+ JSON.stringify({ error: 'Failed to compute progress' }),
112
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
113
+ );
114
+ }
115
+ }
app/components/ProgressBar.js ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useEffect } from 'react';
4
+
5
+ export default function ProgressBar() {
6
+ const [progress, setProgress] = useState(null);
7
+ const [expanded, setExpanded] = useState(false);
8
+ const [loading, setLoading] = useState(true);
9
+
10
+ useEffect(() => {
11
+ fetch('/api/progress')
12
+ .then(res => res.json())
13
+ .then(data => {
14
+ setProgress(data);
15
+ setLoading(false);
16
+ })
17
+ .catch(() => setLoading(false));
18
+ }, []);
19
+
20
+ if (loading || !progress) {
21
+ return null; // Don't show anything while loading
22
+ }
23
+
24
+ const docPct = progress.totalDocs > 0
25
+ ? Math.round((progress.completedDocs / progress.totalDocs) * 100) : 0;
26
+ const pagePct = progress.totalPages > 0
27
+ ? Math.round((progress.completedPages / progress.totalPages) * 100) : 0;
28
+ const mentionPct = progress.totalMentions > 0
29
+ ? Math.round((progress.verifiedMentions / progress.totalMentions) * 100) : 0;
30
+
31
+ return (
32
+ <div className="progress-container">
33
+ <button
34
+ className="progress-toggle"
35
+ onClick={() => setExpanded(!expanded)}
36
+ >
37
+ <span className="progress-summary">
38
+ πŸ“Š Progress: {progress.verifiedMentions}/{progress.totalMentions} mentions verified ({mentionPct}%)
39
+ </span>
40
+ <span className="progress-chevron">{expanded ? 'β–²' : 'β–Ό'}</span>
41
+ </button>
42
+
43
+ {expanded && (
44
+ <div className="progress-details">
45
+ <div className="progress-row">
46
+ <span className="progress-label">πŸ“„ Documents</span>
47
+ <div className="progress-bar-track">
48
+ <div
49
+ className="progress-bar-fill docs-fill"
50
+ style={{ width: `${docPct}%` }}
51
+ />
52
+ </div>
53
+ <span className="progress-stat">{progress.completedDocs}/{progress.totalDocs} ({docPct}%)</span>
54
+ </div>
55
+ <div className="progress-row">
56
+ <span className="progress-label">πŸ“‘ Pages</span>
57
+ <div className="progress-bar-track">
58
+ <div
59
+ className="progress-bar-fill pages-fill"
60
+ style={{ width: `${pagePct}%` }}
61
+ />
62
+ </div>
63
+ <span className="progress-stat">{progress.completedPages}/{progress.totalPages} ({pagePct}%)</span>
64
+ </div>
65
+ <div className="progress-row">
66
+ <span className="progress-label">🏷️ Mentions</span>
67
+ <div className="progress-bar-track">
68
+ <div
69
+ className="progress-bar-fill mentions-fill"
70
+ style={{ width: `${mentionPct}%` }}
71
+ />
72
+ </div>
73
+ <span className="progress-stat">{progress.verifiedMentions}/{progress.totalMentions} ({mentionPct}%)</span>
74
+ </div>
75
+ <div className="progress-row">
76
+ <span className="progress-label">✍️ Human annotations</span>
77
+ <span className="progress-stat highlight">{progress.humanAnnotations}</span>
78
+ </div>
79
+ </div>
80
+ )}
81
+ </div>
82
+ );
83
+ }
app/globals.css CHANGED
@@ -158,6 +158,100 @@ h4 {
158
  overflow: hidden;
159
  }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  .pane {
162
  flex: 1;
163
  padding: 20px 24px;
 
158
  overflow: hidden;
159
  }
160
 
161
+ /* ── Progress Bar ────────────────────────────── */
162
+
163
+ .progress-container {
164
+ border-bottom: 1px solid var(--border-color);
165
+ background: var(--pane-bg);
166
+ flex-shrink: 0;
167
+ }
168
+
169
+ .progress-toggle {
170
+ display: flex;
171
+ align-items: center;
172
+ justify-content: space-between;
173
+ width: 100%;
174
+ padding: 5px 20px;
175
+ background: none;
176
+ border: none;
177
+ color: var(--text-color);
178
+ cursor: pointer;
179
+ font-size: 0.78rem;
180
+ }
181
+
182
+ .progress-toggle:hover {
183
+ background: rgba(255, 255, 255, 0.03);
184
+ }
185
+
186
+ .progress-summary {
187
+ font-weight: 600;
188
+ }
189
+
190
+ .progress-chevron {
191
+ font-size: 0.6rem;
192
+ color: #64748b;
193
+ }
194
+
195
+ .progress-details {
196
+ padding: 8px 20px 10px;
197
+ display: flex;
198
+ flex-direction: column;
199
+ gap: 6px;
200
+ border-top: 1px solid var(--border-color);
201
+ }
202
+
203
+ .progress-row {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 10px;
207
+ font-size: 0.75rem;
208
+ }
209
+
210
+ .progress-label {
211
+ width: 130px;
212
+ flex-shrink: 0;
213
+ color: #94a3b8;
214
+ }
215
+
216
+ .progress-bar-track {
217
+ flex: 1;
218
+ height: 6px;
219
+ background: var(--surface);
220
+ border-radius: 3px;
221
+ overflow: hidden;
222
+ }
223
+
224
+ .progress-bar-fill {
225
+ height: 100%;
226
+ border-radius: 3px;
227
+ transition: width 0.5s ease;
228
+ }
229
+
230
+ .docs-fill {
231
+ background: var(--success);
232
+ }
233
+
234
+ .pages-fill {
235
+ background: var(--accent);
236
+ }
237
+
238
+ .mentions-fill {
239
+ background: #f59e0b;
240
+ }
241
+
242
+ .progress-stat {
243
+ font-size: 0.72rem;
244
+ color: #94a3b8;
245
+ white-space: nowrap;
246
+ min-width: 80px;
247
+ text-align: right;
248
+ }
249
+
250
+ .progress-stat.highlight {
251
+ color: var(--success);
252
+ font-weight: 600;
253
+ }
254
+
255
  .pane {
256
  flex: 1;
257
  padding: 20px 24px;
app/page.js CHANGED
@@ -7,6 +7,7 @@ import MarkdownAnnotator from './components/MarkdownAnnotator';
7
  import AnnotationPanel from './components/AnnotationPanel';
8
  import AnnotationModal from './components/AnnotationModal';
9
  import PageNavigator from './components/PageNavigator';
 
10
 
11
  export default function Home() {
12
  const [documents, setDocuments] = useState([]);
@@ -430,6 +431,7 @@ export default function Home() {
430
  )}
431
  </div>
432
  </div>
 
433
  <div className="container">
434
  <div className="pane left-pane">
435
  <div className="pane-header">
 
7
  import AnnotationPanel from './components/AnnotationPanel';
8
  import AnnotationModal from './components/AnnotationModal';
9
  import PageNavigator from './components/PageNavigator';
10
+ import ProgressBar from './components/ProgressBar';
11
 
12
  export default function Home() {
13
  const [documents, setDocuments] = useState([]);
 
431
  )}
432
  </div>
433
  </div>
434
+ <ProgressBar />
435
  <div className="container">
436
  <div className="pane left-pane">
437
  <div className="pane-header">