""" Web-based Annotation Interface for Privacy Inferences Modified for Hugging Face Spaces deployment Run: python annotation_app.py Then open: http://localhost:7860 """ from flask import Flask, render_template, request, jsonify, send_file, session, redirect, url_for import json from pathlib import Path from datetime import datetime import os from functools import wraps import zipfile import io from collections import defaultdict app = Flask(__name__) # Security configuration # Configuration Flask # Security configuration - Simplified for HF Spaces app.secret_key = os.environ.get('SECRET_KEY', 'votre-cle-secrete-tres-longue-a-changer-123456789') # Configuration (ligne ~28) RESULTS_DIR = Path("results") ANNOTATIONS_DIR = Path("annotations") ANNOTATIONS_DIR.mkdir(exist_ok=True) MONTHS = [ 'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER' ] CATEGORIES = ['health', 'religion', 'family', 'routines', 'work', 'leisure', 'economics'] # Admin password - DOIT รŠTRE Dร‰FINI AVANT LE DEBUG ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'antoine2025') # Debug: Print config at startup - MAINTENANT C'EST BON print("="*70) print("๐Ÿ” AUTHENTICATION CONFIG") print("="*70) print(f"SECRET_KEY configured: {'Yes' if app.config.get('SECRET_KEY') else 'No'}") print(f"ADMIN_PASSWORD configured: {'Yes' if ADMIN_PASSWORD else 'No'}") print(f"Session cookie secure: {app.config.get('SESSION_COOKIE_SECURE')}") print("="*70) # ============================================================================ # AUTHENTICATION # ============================================================================ def login_required(f): """Decorator to protect routes""" @wraps(f) def decorated_function(*args, **kwargs): # Check session AND cookie if not session.get('logged_in') and request.cookies.get('auth_token') != 'authenticated': return redirect(url_for('login')) # If cookie is valid but session is empty, restore session if not session.get('logged_in') and request.cookies.get('auth_token') == 'authenticated': session['logged_in'] = True return f(*args, **kwargs) return decorated_function @app.route('/login', methods=['GET', 'POST']) def login(): """Login page""" # If already logged in, redirect to index if session.get('logged_in') or request.cookies.get('auth_token') == 'authenticated': return redirect(url_for('index')) if request.method == 'POST': password = request.form.get('password', '') print(f"๐Ÿ” Login attempt") # Debug if password == ADMIN_PASSWORD: session['logged_in'] = True print("โœ… Login successful! Setting cookie...") # Debug # Create response with redirect response = redirect(url_for('index')) # Set a persistent cookie as backup response.set_cookie( 'auth_token', 'authenticated', max_age=3600, # 1 hour httponly=True, samesite='Lax' ) return response else: print("โŒ Login failed - wrong password") # Debug error_message = "Mot de passe incorrect" else: error_message = None # GET request or failed login - show login form return f''' Connexion - Privacy Annotation

๐Ÿ”’ Privacy Annotation

{'
' + error_message + '
' if error_message else ''}

Interface d'annotation pour รฉvaluation des modรจles LLM

''' @app.route('/logout') def logout(): """Logout""" session.clear() response = redirect(url_for('login')) response.set_cookie('auth_token', '', max_age=0) # Delete cookie return response # ============================================================================ # HELPER FUNCTIONS # ============================================================================ def get_available_models(): """Get list of available model directories.""" if not RESULTS_DIR.exists(): return [] return [d.name for d in RESULTS_DIR.iterdir() if d.is_dir() and not d.name.startswith('.')] def load_result(model_name, month): """Load a specific result file.""" filepath = RESULTS_DIR / model_name / "2017" / f"2017_{month}_P1.json" if not filepath.exists(): return None with open(filepath, 'r') as f: return json.load(f) def get_annotation_status(): """Get status of all annotations.""" status = {} models = get_available_models() for model in models: status[model] = {} for month in MONTHS: ann_file = ANNOTATIONS_DIR / f"{model}_{month}_annotations.json" status[model][month] = ann_file.exists() return status def extract_inferences(response, category): """Extract inferences for a specific category.""" inferences = [] lines = response.split('\n') in_category = False for line in lines: # Check if entering this category if category.lower() in line.lower() and (':' in line or category in line): in_category = True continue # Check if entering new category if any(cat.lower() in line.lower() for cat in CATEGORIES if cat != category): in_category = False # Extract inference if in_category and line.strip(): cleaned = line.strip().lstrip('โ€ข-*0123456789.) ') if len(cleaned) > 15: inferences.append(cleaned) return inferences # ============================================================================ # MAIN ROUTES # ============================================================================ @app.route('/') # @login_required # โ† COMMENTร‰ TEMPORAIREMENT POUR DEBUG def index(): """Main page - show annotation dashboard.""" try: models = get_available_models() status = get_annotation_status() print(f"๐Ÿ“Š Models found: {models}") # Debug print(f"๐Ÿ“Š Status: {status}") # Debug return render_template('index.html', models=models, months=MONTHS, status=status) except Exception as e: print(f"โŒ ERROR in index(): {e}") # Debug import traceback traceback.print_exc() return f"

Error loading dashboard

{e}
{traceback.format_exc()}
", 500 @app.route('/annotate//') #@login_required def annotate(model, month): """Annotation page for specific model and month - Simplified version""" print(f"๐Ÿ“ Annotate called: model={model}, month={month}") # Load result result = load_result(model, month) if not result: print(f"โŒ Result not found for {model}/{month}") return f"

Error: Result not found for {model}/{month}

", 404 response_text = result.get('response', '') print(f"โœ… Result loaded. Response length: {len(response_text)} chars") # Check if already annotated ann_file = ANNOTATIONS_DIR / f"{model}_{month}_annotations.json" existing_annotation = None if ann_file.exists(): with open(ann_file, 'r') as f: existing_annotation = json.load(f) # Extract inferences by category category_inferences = {} for category in CATEGORIES: inferences = extract_inferences(response_text, category) category_inferences[category] = inferences print(f" {category}: {len(inferences)} inferences") # Count annotations annotation_count = 0 if existing_annotation: for cat_anns in existing_annotation.get('annotations', {}).values(): annotation_count += len(cat_anns) # Build HTML directly (no template needed) html = f''' {model.upper()} - {month} 2017

๐Ÿ”’ {model.replace('local_', '').upper()} - {month} 2017

Annotate privacy inferences

โ† Back to Dashboard

๐Ÿ“„ LLM Response

Response: {len(response_text)} characters
{response_text}

โœ๏ธ Annotate Inferences

{annotation_count} / {sum(len(infs) for infs in category_inferences.values())} annotated
''' # Add categories and inferences for category in CATEGORIES: inferences = category_inferences[category] if not inferences: continue html += f'''
{category.upper()} ({len(inferences)} inferences)
''' for idx, inference in enumerate(inferences): # Check existing annotation existing_label = '' if existing_annotation: for ann in existing_annotation.get('annotations', {}).get(category, []): if ann['inference'] == inference: existing_label = ann['label'] break html += f'''
{inference}
''' html += '''
''' html += '''
''' return html @app.route('/api/save_annotation', methods=['POST']) #@login_required def save_annotation(): """Save annotation via API.""" data = request.json model = data['model'] month = data['month'] annotations = data['annotations'] # Load original result result = load_result(model, month) if not result: return jsonify({'success': False, 'error': 'Result not found'}), 404 # Create annotation data ann_data = { 'model_name': model, 'month': month, 'year': 2017, 'annotated_at': datetime.now().isoformat(), 'annotation_mode': 'web_interface', 'original_response': result['response'], 'annotations': annotations, 'metadata': { 'trajectory_period': result.get('trajectory_period'), 'prompt_type': result.get('prompt_type'), } } # Save to file ann_file = ANNOTATIONS_DIR / f"{model}_{month}_annotations.json" with open(ann_file, 'w') as f: json.dump(ann_data, f, indent=2) return jsonify({'success': True, 'file': str(ann_file)}) @app.route('/api/metrics/') #@login_required def get_metrics(model): """Calculate metrics for a model.""" pattern = f"{model}_*_annotations.json" ann_files = list(ANNOTATIONS_DIR.glob(pattern)) if not ann_files: return jsonify({'success': False, 'error': 'No annotations found'}) total_tp = 0 total_fp = 0 total_pa = 0 by_category = {} annotated_months = [] for ann_file in ann_files: with open(ann_file, 'r') as f: data = json.load(f) annotated_months.append(data['month']) for category, items in data['annotations'].items(): if category not in by_category: by_category[category] = {'TP': 0, 'FP': 0, 'PA': 0} for item in items: label = item['label'] if label == 'TP': total_tp += 1 by_category[category]['TP'] += 1 elif label == 'FP': total_fp += 1 by_category[category]['FP'] += 1 elif label == 'PA': total_pa += 1 by_category[category]['PA'] += 1 # Calculate precision precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0 # Category metrics category_metrics = {} for category, stats in by_category.items(): tp = stats['TP'] fp = stats['FP'] prec = tp / (tp + fp) if (tp + fp) > 0 else 0 category_metrics[category] = { 'tp': tp, 'fp': fp, 'pa': stats['PA'], 'precision': round(prec, 3) } return jsonify({ 'success': True, 'model': model, 'months_annotated': len(annotated_months), 'annotated_months': annotated_months, 'total_tp': total_tp, 'total_fp': total_fp, 'total_pa': total_pa, 'precision': round(precision, 3), 'by_category': category_metrics }) @app.route('/metrics') #@login_required def metrics_page(): """Metrics dashboard page.""" models = get_available_models() return render_template('metrics.html', models=models) @app.route('/download-annotations') #@login_required def download_annotations(): """Download all annotations as ZIP file""" memory_file = io.BytesIO() # Create ZIP with all annotations with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf: ann_files = list(ANNOTATIONS_DIR.glob('*.json')) if not ann_files: return "Aucune annotation disponible pour le moment.", 404 for file in ann_files: zf.write(file, file.name) memory_file.seek(0) # Filename with date filename = f'annotations_antoine_{datetime.now().strftime("%Y%m%d_%H%M")}.zip' return send_file( memory_file, mimetype='application/zip', as_attachment=True, download_name=filename ) # ============================================================================ # MAIN # ============================================================================ if __name__ == '__main__': # Hugging Face Spaces uses port 7860 by default port = int(os.environ.get('PORT', 7860)) # Create directories if needed templates_dir = Path("templates") templates_dir.mkdir(exist_ok=True) RESULTS_DIR.mkdir(exist_ok=True) ANNOTATIONS_DIR.mkdir(exist_ok=True) print("="*70) print("PRIVACY INFERENCE ANNOTATION WEB INTERFACE") print("="*70) print(f"\nโœ“ Starting server on port {port}...") print(f"โœ“ Results directory: {RESULTS_DIR.absolute()}") print(f"โœ“ Annotations will be saved to: {ANNOTATIONS_DIR.absolute()}") # Check if running on HF if os.environ.get('SPACE_ID'): print(f"โœ“ Running on Hugging Face Spaces") print(f"โœ“ Space ID: {os.environ.get('SPACE_ID')}") else: print(f"\n๐ŸŒ Open your browser to: http://localhost:{port}") print("\nPress Ctrl+C to stop the server") print("="*70 + "\n") # IMPORTANT: host='0.0.0.0' to be accessible from outside # debug=False for production app.run(host='0.0.0.0', port=port, debug=False)