Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Aurora ML Predictions - CAMS Pollution Dashboard</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Arial', sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| color: #333; | |
| } | |
| .container { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 40px; | |
| color: white; | |
| } | |
| .header h1 { | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .header p { | |
| font-size: 1.2em; | |
| opacity: 0.9; | |
| } | |
| .form-container { | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 15px; | |
| padding: 40px; | |
| box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); | |
| backdrop-filter: blur(10px); | |
| } | |
| .form-group { | |
| margin-bottom: 25px; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: bold; | |
| color: #555; | |
| } | |
| .form-group input, .form-group select { | |
| width: 100%; | |
| padding: 15px; | |
| border: 2px solid #e1e1e1; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| transition: border-color 0.3s ease; | |
| } | |
| .form-group input:focus, .form-group select:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| } | |
| .info-box { | |
| background: #f8f9ff; | |
| border: 2px solid #e3e7ff; | |
| border-radius: 10px; | |
| padding: 20px; | |
| margin-bottom: 25px; | |
| } | |
| .info-box h3 { | |
| color: #4c63d2; | |
| margin-bottom: 10px; | |
| } | |
| .info-box ul { | |
| margin-left: 20px; | |
| color: #666; | |
| } | |
| .info-box li { | |
| margin-bottom: 5px; | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 15px 30px; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 18px; | |
| cursor: pointer; | |
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |
| width: 100%; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| .btn:disabled { | |
| background: #bdc3c7; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Loading Animation Styles */ | |
| .loading-overlay { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| z-index: 9999; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| } | |
| .loading-content { | |
| background: white; | |
| padding: 40px; | |
| border-radius: 20px; | |
| text-align: center; | |
| max-width: 500px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | |
| } | |
| .loading-spinner { | |
| width: 80px; | |
| height: 80px; | |
| border: 8px solid #f3f3f3; | |
| border-top: 8px solid #667eea; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 20px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 20px; | |
| background: #f0f0f0; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin: 20px 0; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #667eea, #764ba2); | |
| border-radius: 10px; | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.7; } | |
| } | |
| .loading-steps { | |
| text-align: left; | |
| margin-top: 20px; | |
| } | |
| .loading-step { | |
| padding: 8px 0; | |
| display: flex; | |
| align-items: center; | |
| font-size: 14px; | |
| } | |
| .step-icon { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| margin-right: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 12px; | |
| } | |
| .step-pending { | |
| background: #f0f0f0; | |
| color: #999; | |
| } | |
| .step-active { | |
| background: #667eea; | |
| color: white; | |
| animation: pulse 1s infinite; | |
| } | |
| .step-complete { | |
| background: #28a745; | |
| color: white; | |
| } | |
| .aurora-icon { | |
| font-size: 3em; | |
| margin-bottom: 15px; | |
| animation: float 3s ease-in-out infinite; | |
| } | |
| @keyframes float { | |
| 0%, 100% { transform: translateY(0px); } | |
| 50% { transform: translateY(-10px); } | |
| } | |
| .back-link { | |
| display: inline-block; | |
| margin-bottom: 20px; | |
| color: white; | |
| text-decoration: none; | |
| font-size: 16px; | |
| transition: opacity 0.3s ease; | |
| } | |
| .back-link:hover { | |
| opacity: 0.8; | |
| } | |
| .back-link::before { | |
| content: "β "; | |
| } | |
| .warning-box { | |
| background: #fff8e1; | |
| border: 2px solid #ffcc02; | |
| border-radius: 10px; | |
| padding: 20px; | |
| margin-bottom: 25px; | |
| } | |
| .warning-box h3 { | |
| color: #f57c00; | |
| margin-bottom: 10px; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 10px; | |
| } | |
| .form-container { | |
| padding: 20px; | |
| } | |
| .header h1 { | |
| font-size: 2em; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <a href="{{ url_for('index') }}" class="back-link">Back to Main Dashboard</a> | |
| <div class="header"> | |
| <h1>Aurora ML Predictions</h1> | |
| <p>Generate AI-powered air pollution forecasts using Microsoft's Aurora model</p> | |
| </div> | |
| <div class="form-container"> | |
| <div class="info-box"> | |
| <h3>Enhanced Aurora Features</h3> | |
| <ul> | |
| <li><strong>Dual Time Input:</strong> Uses both T-1 (00:00) and T (12:00) timestamps for better accuracy</li> | |
| <li><strong>Forward Predictions:</strong> Generate 1-4 steps forward, each covering 12 hours</li> | |
| <li><strong>Organized Storage:</strong> Results saved in dated folders for easy management</li> | |
| <li><strong>Multiple Variables:</strong> Predicts PM1, PM2.5, PM10, Oβ, NOβ, CO, SOβ and meteorological variables</li> | |
| <li><strong>Enhanced Visualization:</strong> Step-by-step analysis with time progression</li> | |
| </ul> | |
| </div> | |
| <div class="warning-box"> | |
| <h3>Performance Notes</h3> | |
| <p><strong>CPU Mode:</strong> Aurora will run on CPU for local testing. This is slower but doesn't require GPU.</p> | |
| <p><strong>GPU Mode:</strong> If CUDA GPU is available, Aurora will use it for faster predictions.</p> | |
| <p><strong>Processing Time:</strong> CPU: 5-15 minutes per step | GPU: 1-3 minutes total</p> | |
| <p><strong>Memory:</strong> CPU mode automatically limits to 2 steps to prevent memory issues.</p> | |
| <p><strong>Coverage:</strong> Each step predicts 12 hours forward (max 48 hours with 4 steps).</p> | |
| </div> | |
| <form method="POST"> | |
| <div class="form-group"> | |
| <label for="date">Select Date for Initial Conditions:</label> | |
| <input type="date" | |
| id="date" | |
| name="date" | |
| value="{{ current_date }}" | |
| min="2015-01-01" | |
| max="{{ current_date }}" | |
| required> | |
| <small style="color: #666; font-size: 14px;"> | |
| Aurora will download CAMS data for this date and generate forecasts | |
| </small> | |
| </div> | |
| <div class="form-group"> | |
| <label for="steps">Number of Forward Prediction Steps:</label> | |
| <select id="steps" name="steps" required> | |
| <option value="1">1 step (12 hours forward) - Fastest</option> | |
| <option value="2" selected>2 steps (24 hours forward) - CPU Friendly</option> | |
| <option value="3">3 steps (36 hours forward) - Recommended</option> | |
| <option value="4">4 steps (48 hours forward) - Maximum</option> | |
| </select> | |
| <small style="color: #666; font-size: 14px;"> | |
| Each step represents 12 hours forward from the initial conditions. | |
| Aurora uses T-1 (00:00) and T (12:00) as input, then predicts forward. | |
| </small> | |
| </div> | |
| <button type="submit" class="btn" id="predictBtn"> | |
| Generate Aurora Predictions | |
| </button> | |
| </form> | |
| <!-- Loading Overlay --> | |
| <div class="loading-overlay" id="loadingOverlay"> | |
| <div class="loading-content"> | |
| <div class="aurora-icon"></div> | |
| <h2 style="color: #667eea; margin-bottom: 10px;">Aurora AI Processing</h2> | |
| <p style="color: #666; margin-bottom: 20px;">Generating atmospheric predictions using Microsoft's Aurora model...</p> | |
| <div class="loading-spinner"></div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill"></div> | |
| </div> | |
| <div id="currentStep" style="font-weight: bold; color: #667eea; margin-bottom: 20px;"> | |
| Initializing Aurora pipeline... | |
| </div> | |
| <div class="loading-steps"> | |
| <div class="loading-step"> | |
| <div class="step-icon step-pending" id="step1">1</div> | |
| <span>Downloading CAMS atmospheric data</span> | |
| </div> | |
| <div class="loading-step"> | |
| <div class="step-icon step-pending" id="step2">2</div> | |
| <span>Loading Aurora ML model</span> | |
| </div> | |
| <div class="loading-step"> | |
| <div class="step-icon step-pending" id="step3">3</div> | |
| <span>Processing initial conditions</span> | |
| </div> | |
| <div class="loading-step"> | |
| <div class="step-icon step-pending" id="step4">4</div> | |
| <span>Running AI predictions</span> | |
| </div> | |
| <div class="loading-step"> | |
| <div class="step-icon step-pending" id="step5">5</div> | |
| <span>Saving results and preparing visualization</span> | |
| </div> | |
| </div> | |
| <p style="margin-top: 20px; font-size: 12px; color: #999;"> | |
| <strong>Estimated time:</strong> <span id="estimatedTime">2-5 minutes</span><br> | |
| This may take longer on CPU-only systems. | |
| </p> | |
| </div> | |
| </div> | |
| <div style="margin-top: 30px; padding: 20px; background: #f5f5f5; border-radius: 10px;"> | |
| <h3 style="color: #555; margin-bottom: 15px;">What You'll Get:</h3> | |
| <ul style="color: #666; margin-left: 20px;"> | |
| <li>Interactive visualization of predicted air pollution concentrations</li> | |
| <li>Step-by-step forecast evolution over time</li> | |
| <li>Downloadable NetCDF files with all prediction data</li> | |
| <li>Support for all major pollutants and meteorological variables</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Aurora Prediction Loading Animation | |
| class AuroraLoadingManager { | |
| constructor() { | |
| this.form = document.querySelector('form'); | |
| this.predictBtn = document.getElementById('predictBtn'); | |
| this.overlay = document.getElementById('loadingOverlay'); | |
| this.progressFill = document.getElementById('progressFill'); | |
| this.currentStep = document.getElementById('currentStep'); | |
| this.estimatedTime = document.getElementById('estimatedTime'); | |
| this.steps = [ | |
| { id: 'step1', text: 'Downloading CAMS atmospheric data...', duration: 20000 }, | |
| { id: 'step2', text: 'Loading Aurora ML model into memory...', duration: 30000 }, | |
| { id: 'step3', text: 'Processing initial atmospheric conditions...', duration: 15000 }, | |
| { id: 'step4', text: 'Running AI predictions (this may take a while)...', duration: 60000 }, | |
| { id: 'step5', text: 'Saving results and preparing visualization...', duration: 10000 } | |
| ]; | |
| this.currentStepIndex = 0; | |
| this.startTime = null; | |
| this.init(); | |
| } | |
| init() { | |
| this.form.addEventListener('submit', (e) => { | |
| this.startLoading(); | |
| }); | |
| } | |
| startLoading() { | |
| this.startTime = Date.now(); | |
| this.overlay.style.display = 'flex'; | |
| this.predictBtn.disabled = true; | |
| // Estimate time based on selected steps | |
| const steps = parseInt(document.getElementById('steps').value); | |
| const isCPU = this.detectCPUMode(); | |
| this.updateEstimatedTime(steps, isCPU); | |
| // Start progress simulation | |
| this.simulateProgress(); | |
| } | |
| detectCPUMode() { | |
| // Simple heuristic - if user selected fewer steps, likely CPU mode | |
| const steps = parseInt(document.getElementById('steps').value); | |
| return steps <= 2; | |
| } | |
| updateEstimatedTime(steps, isCPU) { | |
| // Fetch actual system capabilities for better estimates | |
| fetch('/api/aurora_status') | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.available) { | |
| const mode = data.cpu_only ? 'cpu' : 'gpu'; | |
| let estimatedMinutes; | |
| if (mode === 'cpu') { | |
| estimatedMinutes = steps <= 1 ? 5 : 8; | |
| } else { | |
| estimatedMinutes = Math.max(2, steps * 0.5 + 2); | |
| } | |
| this.estimatedTime.textContent = `${estimatedMinutes}-${estimatedMinutes + 2} minutes (${mode.toUpperCase()} mode)`; | |
| } else { | |
| this.estimatedTime.textContent = 'Aurora not available'; | |
| } | |
| }) | |
| .catch(() => { | |
| // Fallback to original logic | |
| let baseTime = steps * (isCPU ? 5 : 1); | |
| baseTime += 2; | |
| this.estimatedTime.textContent = `${baseTime}-${baseTime + 2} minutes`; | |
| }); | |
| } | |
| simulateProgress() { | |
| let totalDuration = this.steps.reduce((sum, step) => sum + step.duration, 0); | |
| let elapsed = 0; | |
| this.progressSteps(0); | |
| } | |
| progressSteps(stepIndex) { | |
| if (stepIndex >= this.steps.length) { | |
| return; // Let the actual response handle completion | |
| } | |
| const step = this.steps[stepIndex]; | |
| const stepElement = document.getElementById(step.id); | |
| // Mark previous steps as complete | |
| for (let i = 0; i < stepIndex; i++) { | |
| const prevStep = document.getElementById(this.steps[i].id); | |
| prevStep.className = 'step-icon step-complete'; | |
| prevStep.innerHTML = 'β'; | |
| } | |
| // Mark current step as active | |
| stepElement.className = 'step-icon step-active'; | |
| this.currentStep.textContent = step.text; | |
| // Update progress bar | |
| const progress = ((stepIndex + 1) / this.steps.length) * 100; | |
| this.progressFill.style.width = `${progress}%`; | |
| // Move to next step after duration | |
| setTimeout(() => { | |
| this.progressSteps(stepIndex + 1); | |
| }, step.duration); | |
| } | |
| // Call this when the actual response is received | |
| completeLoading() { | |
| // Mark all steps as complete | |
| this.steps.forEach((step, index) => { | |
| const stepElement = document.getElementById(step.id); | |
| stepElement.className = 'step-icon step-complete'; | |
| stepElement.innerHTML = 'β'; | |
| }); | |
| this.progressFill.style.width = '100%'; | |
| this.currentStep.textContent = 'Complete! Redirecting to results...'; | |
| // Hide overlay after a short delay | |
| setTimeout(() => { | |
| this.overlay.style.display = 'none'; | |
| this.predictBtn.disabled = false; | |
| }, 2000); | |
| } | |
| } | |
| // Initialize loading manager when page loads | |
| document.addEventListener('DOMContentLoaded', function() { | |
| window.auroraLoader = new AuroraLoadingManager(); | |
| // Handle form validation | |
| const form = document.querySelector('form'); | |
| const dateInput = document.getElementById('date'); | |
| const stepsSelect = document.getElementById('steps'); | |
| form.addEventListener('submit', function(e) { | |
| if (!dateInput.value) { | |
| e.preventDefault(); | |
| alert('Please select a date for the prediction.'); | |
| return; | |
| } | |
| const selectedDate = new Date(dateInput.value); | |
| const today = new Date(); | |
| const minDate = new Date('2015-01-01'); | |
| if (selectedDate > today || selectedDate < minDate) { | |
| e.preventDefault(); | |
| alert('Please select a date between 2015-01-01 and today.'); | |
| return; | |
| } | |
| }); | |
| // Update step recommendations based on selection | |
| stepsSelect.addEventListener('change', function() { | |
| const steps = parseInt(this.value); | |
| const recommendations = { | |
| 1: 'Fastest option - good for testing', | |
| 2: 'CPU-friendly - recommended for local development', | |
| 4: 'Standard forecast - good for GPU systems', | |
| 6: 'Extended forecast - GPU recommended', | |
| 8: 'Long-range forecast - GPU required', | |
| 10: 'Maximum forecast - GPU required' | |
| }; | |
| const small = this.parentNode.querySelector('small'); | |
| small.textContent = `Each step represents 6 hours. ${recommendations[steps] || 'Custom selection'}`; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |