Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Well Production Dashboard</title> | |
| <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> | |
| </head> | |
| <body> | |
| <custom-navbar></custom-navbar> | |
| <div class="header-container"> | |
| <h1>FluidMagic SPD Welltest Converter</h1> | |
| <div class="intro-section"> | |
| <img src="{{ url_for('static', filename='spd_image.png') }}" alt="Intro Image" class="intro-image"> | |
| <p class="intro-text"> | |
| This application allows for reading of welltest data from IMS database with correct SPD tags to get measured actual production rates. | |
| The welltest data is converted to consistent well stream compositions and corresponding PVT properties using the FluidMagic library. | |
| Select a facility and well to view test results and production charts. | |
| by curtesy of the Equinor SPD and FluidMagic PVT groups. | |
| </p> | |
| </div> | |
| <div class="select-row"> | |
| <div class="select-group"> | |
| <label for="gov_fcty_name">Facility</label> | |
| <select id="gov_fcty_name" class="form-select"> | |
| <option value="">Select Facility</option> | |
| {% for f in fac_codes %} | |
| <option value="{{ f }}">{{ f }}</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <div class="select-group"> | |
| <label for="well_select">Well</label> | |
| <select id="well_select" class="form-select" disabled> | |
| <option value="">Select a facility first</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="content-row"> | |
| <div class="table-container" id="test_results"> | |
| <div class="results-placeholder"> | |
| <div class="placeholder-text"> | |
| <i class="fas fa-table"></i> Select a well to view test results | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chart-container" id="plot"> | |
| <div class="chart-placeholder"> | |
| <div class="placeholder-text"> | |
| <i class="fas fa-chart-line"></i> Select a well to view production chart | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="components/navbar.js"></script> | |
| <script> | |
| const facilitySelect = document.getElementById('gov_fcty_name'); | |
| const wellSelect = document.getElementById('well_select'); | |
| const resultsDiv = document.getElementById('test_results'); | |
| const plotDiv = document.getElementById('plot'); | |
| // Format numbers to 1 decimal place | |
| const formatNumber = (num) => { | |
| if (num === null || num === undefined) return ''; | |
| return Number(num).toFixed(1); | |
| }; | |
| // When facility changes → fetch wells | |
| facilitySelect.addEventListener('change', function() { | |
| const selectedFacility = this.value; | |
| if (!selectedFacility) { | |
| wellSelect.innerHTML = '<option value="">-- Select a facility first --</option>'; | |
| wellSelect.disabled = true; | |
| resultsDiv.innerHTML = ''; | |
| plotDiv.innerHTML = ''; | |
| return; | |
| } | |
| fetch(`/api/get_wellnames?facility=${encodeURIComponent(selectedFacility)}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| wellSelect.innerHTML = ''; | |
| if (data.length === 0) { | |
| wellSelect.innerHTML = '<option value="">No wells found</option>'; | |
| wellSelect.disabled = true; | |
| } else { | |
| wellSelect.innerHTML = '<option value="">-- Select Well --</option>'; | |
| data.forEach(f => { | |
| const option = document.createElement('option'); | |
| option.value = f; | |
| option.textContent = f; | |
| wellSelect.appendChild(option); | |
| }); | |
| wellSelect.disabled = false; | |
| } | |
| }) | |
| .catch(err => { | |
| console.error('Error fetching wells:', err); | |
| wellSelect.innerHTML = '<option value="">Error loading wells</option>'; | |
| wellSelect.disabled = true; | |
| }); | |
| }); | |
| // When well changes → fetch test results & update table + plot | |
| wellSelect.addEventListener('change', function() { | |
| const facility = facilitySelect.value; | |
| const well = this.value; | |
| if (!well) { | |
| resultsDiv.innerHTML = ''; | |
| plotDiv.innerHTML = ''; | |
| return; | |
| } | |
| resultsDiv.innerHTML = '<p>Loading test results...</p>'; | |
| plotDiv.innerHTML = ''; | |
| fetch(`/api/get_test_results?facility=${encodeURIComponent(facility)}&well=${encodeURIComponent(well)}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (!data || data.length === 0) { | |
| resultsDiv.innerHTML = '<p>No test results found.</p>'; | |
| plotDiv.innerHTML = ''; | |
| return; | |
| } | |
| // Build table | |
| let html = ` | |
| <h2>Test Results for Well: ${well}</h2> | |
| <table> | |
| <tr> | |
| <th>TEST_START_DATE</th> | |
| <th>TEST_END_DATE</th> | |
| <th>OIL_RATE</th> | |
| <th>GAS_RATE</th> | |
| <th>WATER_RATE</th> | |
| <th>WHP</th> | |
| <th>WHT</th> | |
| <th>AVG_DOWNHOLE_PRESS</th> | |
| <th>AVG_DOWNHOLE_TEMP</th> | |
| <th>Separator Avg Oil Rate [Sm3/d]</th> | |
| <th>Separator Avg Oil Rate Actual [m3/d]</th> | |
| </tr> | |
| `; | |
| const dates = []; | |
| const oilRates = []; | |
| const oilSepRates = []; | |
| data.forEach(row => { | |
| html += ` | |
| <tr> | |
| <td>${row.TEST_START_DATE || ''}</td> | |
| <td>${row.TEST_END_DATE || ''}</td> | |
| <td>${formatNumber(row.OIL_RATE)}</td> | |
| <td>${formatNumber(row.GAS_RATE)}</td> | |
| <td>${formatNumber(row.WATER_RATE)}</td> | |
| <td>${formatNumber(row.WHP)}</td> | |
| <td>${formatNumber(row.WHT)}</td> | |
| <td>${formatNumber(row.AVG_DOWNHOLE_PRESS)}</td> | |
| <td>${formatNumber(row.AVG_DOWNHOLE_TEMP)}</td> | |
| <td>${formatNumber(row['Separator Avg Oil Rate'])}</td> | |
| <td>${formatNumber(row['Separator Avg Oil Rate Actual'])}</td> | |
| </tr> | |
| `; | |
| const date = row.TEST_START_DATE || row.TEST_END_DATE; | |
| if (date) { | |
| dates.push(date); | |
| oilRates.push(row.OIL_RATE || 0); | |
| oilSepRates.push(row['Separator Avg Oil Rate Actual'] || 0); | |
| } | |
| }); | |
| html += '</table>'; | |
| resultsDiv.innerHTML = html; | |
| // Plotly chart | |
| const traces = [ | |
| { | |
| x: dates, | |
| y: oilRates, | |
| mode: 'lines+markers', | |
| name: 'Oil Rate', | |
| line: { shape: 'spline' } | |
| }, | |
| { | |
| x: dates, | |
| y: oilSepRates, | |
| mode: 'lines+markers', | |
| name: 'Oil Separator Rate', | |
| line: { shape: 'spline' } | |
| } | |
| ]; | |
| const layout = { | |
| title: `Production Rates for ${well}`, | |
| xaxis: { title: 'Test Date' }, | |
| yaxis: { title: 'Rate' }, | |
| legend: { orientation: 'h', y: -0.2 } | |
| }; | |
| Plotly.newPlot('plot', traces, layout, { responsive: true }); | |
| }) | |
| .catch(err => { | |
| console.error('Error fetching test results:', err); | |
| resultsDiv.innerHTML = '<p>Error loading test results</p>'; | |
| plotDiv.innerHTML = ''; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Well Production Dashboard</title> | |
| <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> | |
| </head> | |
| <body> | |
| <custom-navbar></custom-navbar> | |
| <div class="header-container"> | |
| <h1>FluidMagic SPD Welltest Converter</h1> | |
| <div class="intro-section"> | |
| <img src="{{ url_for('static', filename='spd_image.png') }}" alt="Intro Image" class="intro-image"> | |
| <p class="intro-text"> | |
| This application allows for reading of welltest data from IMS database with correct SPD tags to get measured actual production rates. | |
| The welltest data is converted to consistent well stream compositions and corresponding PVT properties using the FluidMagic library. | |
| Select a facility and well to view test results and production charts. | |
| by curtesy of the Equinor SPD and FluidMagic PVT groups. | |
| </p> | |
| </div> | |
| <div class="select-row"> | |
| <div class="select-group"> | |
| <label for="gov_fcty_name">Facility</label> | |
| <select id="gov_fcty_name" class="form-select"> | |
| <option value="">Select Facility</option> | |
| {% for f in fac_codes %} | |
| <option value="{{ f }}">{{ f }}</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <div class="select-group"> | |
| <label for="well_select">Well</label> | |
| <select id="well_select" class="form-select" disabled> | |
| <option value="">Select a facility first</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="content-row"> | |
| <div class="table-container" id="test_results"> | |
| <div class="results-placeholder"> | |
| <div class="placeholder-text"> | |
| <i class="fas fa-table"></i> Select a well to view test results | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chart-container" id="plot"> | |
| <div class="chart-placeholder"> | |
| <div class="placeholder-text"> | |
| <i class="fas fa-chart-line"></i> Select a well to view production chart | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="components/navbar.js"></script> | |
| <script> | |
| const facilitySelect = document.getElementById('gov_fcty_name'); | |
| const wellSelect = document.getElementById('well_select'); | |
| const resultsDiv = document.getElementById('test_results'); | |
| const plotDiv = document.getElementById('plot'); | |
| // Format numbers to 1 decimal place | |
| const formatNumber = (num) => { | |
| if (num === null || num === undefined) return ''; | |
| return Number(num).toFixed(1); | |
| }; | |
| // When facility changes → fetch wells | |
| facilitySelect.addEventListener('change', function() { | |
| const selectedFacility = this.value; | |
| if (!selectedFacility) { | |
| wellSelect.innerHTML = '<option value="">-- Select a facility first --</option>'; | |
| wellSelect.disabled = true; | |
| resultsDiv.innerHTML = ''; | |
| plotDiv.innerHTML = ''; | |
| return; | |
| } | |
| fetch(`/api/get_wellnames?facility=${encodeURIComponent(selectedFacility)}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| wellSelect.innerHTML = ''; | |
| if (data.length === 0) { | |
| wellSelect.innerHTML = '<option value="">No wells found</option>'; | |
| wellSelect.disabled = true; | |
| } else { | |
| wellSelect.innerHTML = '<option value="">-- Select Well --</option>'; | |
| data.forEach(f => { | |
| const option = document.createElement('option'); | |
| option.value = f; | |
| option.textContent = f; | |
| wellSelect.appendChild(option); | |
| }); | |
| wellSelect.disabled = false; | |
| } | |
| }) | |
| .catch(err => { | |
| console.error('Error fetching wells:', err); | |
| wellSelect.innerHTML = '<option value="">Error loading wells</option>'; | |
| wellSelect.disabled = true; | |
| }); | |
| }); | |
| // When well changes → fetch test results & update table + plot | |
| wellSelect.addEventListener('change', function() { | |
| const facility = facilitySelect.value; | |
| const well = this.value; | |
| if (!well) { | |
| resultsDiv.innerHTML = ''; | |
| plotDiv.innerHTML = ''; | |
| return; | |
| } | |
| resultsDiv.innerHTML = '<p>Loading test results...</p>'; | |
| plotDiv.innerHTML = ''; | |
| fetch(`/api/get_test_results?facility=${encodeURIComponent(facility)}&well=${encodeURIComponent(well)}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (!data || data.length === 0) { | |
| resultsDiv.innerHTML = '<p>No test results found.</p>'; | |
| plotDiv.innerHTML = ''; | |
| return; | |
| } | |
| // Build table | |
| let html = ` | |
| <h2>Test Results for Well: ${well}</h2> | |
| <table> | |
| <tr> | |
| <th>TEST_START_DATE</th> | |
| <th>TEST_END_DATE</th> | |
| <th>OIL_RATE</th> | |
| <th>GAS_RATE</th> | |
| <th>WATER_RATE</th> | |
| <th>WHP</th> | |
| <th>WHT</th> | |
| <th>AVG_DOWNHOLE_PRESS</th> | |
| <th>AVG_DOWNHOLE_TEMP</th> | |
| <th>Separator Avg Oil Rate [Sm3/d]</th> | |
| <th>Separator Avg Oil Rate Actual [m3/d]</th> | |
| </tr> | |
| `; | |
| const dates = []; | |
| const oilRates = []; | |
| const oilSepRates = []; | |
| data.forEach(row => { | |
| html += ` | |
| <tr> | |
| <td>${row.TEST_START_DATE || ''}</td> | |
| <td>${row.TEST_END_DATE || ''}</td> | |
| <td>${formatNumber(row.OIL_RATE)}</td> | |
| <td>${formatNumber(row.GAS_RATE)}</td> | |
| <td>${formatNumber(row.WATER_RATE)}</td> | |
| <td>${formatNumber(row.WHP)}</td> | |
| <td>${formatNumber(row.WHT)}</td> | |
| <td>${formatNumber(row.AVG_DOWNHOLE_PRESS)}</td> | |
| <td>${formatNumber(row.AVG_DOWNHOLE_TEMP)}</td> | |
| <td>${formatNumber(row['Separator Avg Oil Rate'])}</td> | |
| <td>${formatNumber(row['Separator Avg Oil Rate Actual'])}</td> | |
| </tr> | |
| `; | |
| const date = row.TEST_START_DATE || row.TEST_END_DATE; | |
| if (date) { | |
| dates.push(date); | |
| oilRates.push(row.OIL_RATE || 0); | |
| oilSepRates.push(row['Separator Avg Oil Rate Actual'] || 0); | |
| } | |
| }); | |
| html += '</table>'; | |
| resultsDiv.innerHTML = html; | |
| // Plotly chart | |
| const traces = [ | |
| { | |
| x: dates, | |
| y: oilRates, | |
| mode: 'lines+markers', | |
| name: 'Oil Rate', | |
| line: { shape: 'spline' } | |
| }, | |
| { | |
| x: dates, | |
| y: oilSepRates, | |
| mode: 'lines+markers', | |
| name: 'Oil Separator Rate', | |
| line: { shape: 'spline' } | |
| } | |
| ]; | |
| const layout = { | |
| title: `Production Rates for ${well}`, | |
| xaxis: { title: 'Test Date' }, | |
| yaxis: { title: 'Rate' }, | |
| legend: { orientation: 'h', y: -0.2 } | |
| }; | |
| Plotly.newPlot('plot', traces, layout, { responsive: true }); | |
| }) | |
| .catch(err => { | |
| console.error('Error fetching test results:', err); | |
| resultsDiv.innerHTML = '<p>Error loading test results</p>'; | |
| plotDiv.innerHTML = ''; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |