maghwa commited on
Commit
f6db4ca
Β·
verified Β·
1 Parent(s): 2d6e3ea

Update annotation_app.py

Browse files
Files changed (1) hide show
  1. annotation_app.py +223 -13
annotation_app.py CHANGED
@@ -286,13 +286,20 @@ def index():
286
 
287
 
288
  @app.route('/annotate/<model>/<month>')
289
- # @login_required
290
  def annotate(model, month):
291
- """Annotation page for specific model and month."""
 
 
 
292
  result = load_result(model, month)
293
 
294
  if not result:
295
- return f"Result not found: {model}/{month}", 404
 
 
 
 
296
 
297
  # Check if already annotated
298
  ann_file = ANNOTATIONS_DIR / f"{model}_{month}_annotations.json"
@@ -305,17 +312,220 @@ def annotate(model, month):
305
  # Extract inferences by category
306
  category_inferences = {}
307
  for category in CATEGORIES:
308
- category_inferences[category] = extract_inferences(result['response'], category)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
- return render_template('annotate.html',
311
- model=model,
312
- month=month,
313
- response=result['response'],
314
- categories=CATEGORIES,
315
- category_inferences=category_inferences,
316
- existing_annotation=existing_annotation,
317
- trajectory_stats=result.get('metadata', {}).get('trajectory_stats', {}))
318
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
  @app.route('/api/save_annotation', methods=['POST'])
321
  #@login_required
 
286
 
287
 
288
  @app.route('/annotate/<model>/<month>')
289
+ #@login_required
290
  def annotate(model, month):
291
+ """Annotation page for specific model and month - Simplified version"""
292
+ print(f"πŸ“ Annotate called: model={model}, month={month}")
293
+
294
+ # Load result
295
  result = load_result(model, month)
296
 
297
  if not result:
298
+ print(f"❌ Result not found for {model}/{month}")
299
+ return f"<h1>Error: Result not found for {model}/{month}</h1>", 404
300
+
301
+ response_text = result.get('response', '')
302
+ print(f"βœ… Result loaded. Response length: {len(response_text)} chars")
303
 
304
  # Check if already annotated
305
  ann_file = ANNOTATIONS_DIR / f"{model}_{month}_annotations.json"
 
312
  # Extract inferences by category
313
  category_inferences = {}
314
  for category in CATEGORIES:
315
+ inferences = extract_inferences(response_text, category)
316
+ category_inferences[category] = inferences
317
+ print(f" {category}: {len(inferences)} inferences")
318
+
319
+ # Count annotations
320
+ annotation_count = 0
321
+ if existing_annotation:
322
+ for cat_anns in existing_annotation.get('annotations', {}).values():
323
+ annotation_count += len(cat_anns)
324
+
325
+ # Build HTML directly (no template needed)
326
+ html = f'''
327
+ <!DOCTYPE html>
328
+ <html>
329
+ <head>
330
+ <title>{model.upper()} - {month} 2017</title>
331
+ <meta charset="utf-8">
332
+ <style>
333
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
334
+ body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; }}
335
+ .header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; }}
336
+ .header h1 {{ font-size: 24px; margin-bottom: 5px; }}
337
+ .header p {{ opacity: 0.9; font-size: 14px; }}
338
+ .back-btn {{ display: inline-block; background: rgba(255,255,255,0.2); color: white; padding: 8px 16px;
339
+ border-radius: 6px; text-decoration: none; margin-top: 10px; }}
340
+ .back-btn:hover {{ background: rgba(255,255,255,0.3); }}
341
+ .container {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; padding: 20px; max-width: 1400px; margin: 0 auto; }}
342
+ .panel {{ background: white; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }}
343
+ .panel h2 {{ color: #333; margin-bottom: 15px; font-size: 18px; display: flex; align-items: center; gap: 8px; }}
344
+ .response-text {{ background: #f8f9fa; padding: 15px; border-radius: 8px; white-space: pre-wrap;
345
+ line-height: 1.6; max-height: 70vh; overflow-y: auto; font-size: 14px; }}
346
+ .category {{ margin-bottom: 25px; }}
347
+ .category-title {{ background: #667eea; color: white; padding: 10px 15px; border-radius: 6px;
348
+ font-weight: 600; margin-bottom: 10px; }}
349
+ .inference-item {{ background: #f8f9fa; padding: 12px; margin-bottom: 8px; border-radius: 6px;
350
+ border-left: 3px solid #ddd; }}
351
+ .inference-text {{ margin-bottom: 10px; color: #333; }}
352
+ .annotation-buttons {{ display: flex; gap: 8px; }}
353
+ .btn {{ padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;
354
+ font-weight: 600; transition: all 0.2s; }}
355
+ .btn-tp {{ background: #10b981; color: white; }}
356
+ .btn-tp:hover {{ background: #059669; }}
357
+ .btn-fp {{ background: #ef4444; color: white; }}
358
+ .btn-fp:hover {{ background: #dc2626; }}
359
+ .btn-pa {{ background: #f59e0b; color: white; }}
360
+ .btn-pa:hover {{ background: #d97706; }}
361
+ .save-btn {{ background: #667eea; color: white; padding: 12px 24px; border: none; border-radius: 8px;
362
+ font-size: 16px; font-weight: 600; cursor: pointer; width: 100%; margin-top: 20px; }}
363
+ .save-btn:hover {{ background: #5568d3; }}
364
+ .stats {{ background: #f0f9ff; padding: 10px 15px; border-radius: 6px; margin-bottom: 15px;
365
+ display: flex; justify-content: space-between; }}
366
+ .stat-item {{ font-size: 13px; color: #0369a1; }}
367
+ </style>
368
+ </head>
369
+ <body>
370
+ <div class="header">
371
+ <h1>πŸ”’ {model.replace('local_', '').upper()} - {month} 2017</h1>
372
+ <p>Annotate privacy inferences</p>
373
+ <a href="/" class="back-btn">← Back to Dashboard</a>
374
+ </div>
375
+
376
+ <div class="container">
377
+ <!-- Left Panel: LLM Response -->
378
+ <div class="panel">
379
+ <h2>πŸ“„ LLM Response</h2>
380
+ <div class="stats">
381
+ <span class="stat-item">Response: {len(response_text)} characters</span>
382
+ </div>
383
+ <div class="response-text">{response_text}</div>
384
+ </div>
385
+
386
+ <!-- Right Panel: Annotations -->
387
+ <div class="panel">
388
+ <h2>✍️ Annotate Inferences</h2>
389
+ <div class="stats">
390
+ <span class="stat-item">{annotation_count} / {sum(len(infs) for infs in category_inferences.values())} annotated</span>
391
+ </div>
392
+
393
+ <form id="annotation-form">
394
+ <input type="hidden" name="model" value="{model}">
395
+ <input type="hidden" name="month" value="{month}">
396
+ '''
397
+
398
+ # Add categories and inferences
399
+ for category in CATEGORIES:
400
+ inferences = category_inferences[category]
401
+ if not inferences:
402
+ continue
403
+
404
+ html += f'''
405
+ <div class="category">
406
+ <div class="category-title">{category.upper()} ({len(inferences)} inferences)</div>
407
+ '''
408
+
409
+ for idx, inference in enumerate(inferences):
410
+ # Check existing annotation
411
+ existing_label = ''
412
+ if existing_annotation:
413
+ for ann in existing_annotation.get('annotations', {}).get(category, []):
414
+ if ann['inference'] == inference:
415
+ existing_label = ann['label']
416
+ break
417
+
418
+ html += f'''
419
+ <div class="inference-item">
420
+ <div class="inference-text">{inference}</div>
421
+ <div class="annotation-buttons">
422
+ <button type="button" class="btn btn-tp" onclick="annotate('{category}', {idx}, 'TP', this)"
423
+ {'style="opacity:1; transform:scale(1.05);"' if existing_label == 'TP' else ''}>
424
+ βœ“ Correct
425
+ </button>
426
+ <button type="button" class="btn btn-fp" onclick="annotate('{category}', {idx}, 'FP', this)"
427
+ {'style="opacity:1; transform:scale(1.05);"' if existing_label == 'FP' else ''}>
428
+ βœ— Faux
429
+ </button>
430
+ <button type="button" class="btn btn-pa" onclick="annotate('{category}', {idx}, 'PA', this)"
431
+ {'style="opacity:1; transform:scale(1.05);"' if existing_label == 'PA' else ''}>
432
+ ~ Partiel
433
+ </button>
434
+ </div>
435
+ <input type="hidden" name="annotation_{category}_{idx}" value="{existing_label}">
436
+ </div>
437
+ '''
438
+
439
+ html += '''
440
+ </div>
441
+ '''
442
 
443
+ html += '''
444
+ <button type="submit" class="save-btn">πŸ’Ύ Save Annotations</button>
445
+ </form>
446
+ </div>
447
+ </div>
448
+
449
+ <script>
450
+ const annotations = {};
451
+
452
+ function annotate(category, idx, label, button) {
453
+ const key = category + '_' + idx;
454
+ annotations[key] = { category, inference: button.parentElement.previousElementSibling.textContent.trim(), label };
455
+
456
+ // Update hidden input
457
+ document.querySelector(`input[name="annotation_${key}"]`).value = label;
458
+
459
+ // Visual feedback
460
+ const buttons = button.parentElement.querySelectorAll('.btn');
461
+ buttons.forEach(b => { b.style.opacity = '0.5'; b.style.transform = 'scale(1)'; });
462
+ button.style.opacity = '1';
463
+ button.style.transform = 'scale(1.05)';
464
+
465
+ updateProgress();
466
+ }
467
+
468
+ function updateProgress() {
469
+ const total = document.querySelectorAll('.inference-item').length;
470
+ const annotated = Object.keys(annotations).length;
471
+ document.querySelector('.stats .stat-item').textContent = annotated + ' / ' + total + ' annotated';
472
+ }
473
+
474
+ document.getElementById('annotation-form').addEventListener('submit', async function(e) {
475
+ e.preventDefault();
476
+
477
+ const formData = new FormData(this);
478
+ const model = formData.get('model');
479
+ const month = formData.get('month');
480
+
481
+ // Build annotations object
482
+ const result = {};
483
+ for (const [key, value] of Object.entries(annotations)) {
484
+ const { category, inference, label } = value;
485
+ if (!result[category]) result[category] = [];
486
+ result[category].push({ inference, label });
487
+ }
488
+
489
+ // Save via API
490
+ const response = await fetch('/api/save_annotation', {
491
+ method: 'POST',
492
+ headers: { 'Content-Type': 'application/json' },
493
+ body: JSON.stringify({ model, month, annotations: result })
494
+ });
495
+
496
+ if (response.ok) {
497
+ alert('βœ… Annotations saved successfully!');
498
+ window.location.reload();
499
+ } else {
500
+ alert('❌ Error saving annotations');
501
+ }
502
+ });
503
+
504
+ // Load existing annotations
505
+ window.addEventListener('load', () => {
506
+ document.querySelectorAll('input[type="hidden"][name^="annotation_"]').forEach(input => {
507
+ if (input.value) {
508
+ const parts = input.name.split('_');
509
+ const category = parts[1];
510
+ const idx = parts[2];
511
+ const label = input.value;
512
+
513
+ const key = category + '_' + idx;
514
+ const button = document.querySelector(`.btn-${label.toLowerCase()}[onclick*="${category}"][onclick*="${idx}"]`);
515
+ if (button) {
516
+ const inference = button.parentElement.previousElementSibling.textContent.trim();
517
+ annotations[key] = { category, inference, label };
518
+ }
519
+ }
520
+ });
521
+ updateProgress();
522
+ });
523
+ </script>
524
+ </body>
525
+ </html>
526
+ '''
527
+
528
+ return html
529
 
530
  @app.route('/api/save_annotation', methods=['POST'])
531
  #@login_required