| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Well Injection Pair Analysis Simulator - Duri Field</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> |
| <style> |
| #map { height: 500px; } |
| .chart-container { |
| width: 100%; |
| height: 400px; |
| position: relative; |
| } |
| .well-icon { |
| background-size: contain; |
| background-repeat: no-repeat; |
| border-radius: 50%; |
| width: 24px; |
| height: 24px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| font-weight: bold; |
| font-size: 10px; |
| } |
| .production-well { |
| background-color: #3b82f6; |
| } |
| .injection-well { |
| background-color: #ef4444; |
| } |
| .connection-line { |
| stroke-dasharray: 5, 5; |
| } |
| .tooltip { |
| position: absolute; |
| padding: 8px; |
| background: rgba(0, 0, 0, 0.8); |
| color: white; |
| border-radius: 4px; |
| pointer-events: none; |
| font-size: 12px; |
| z-index: 1000; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="mb-8"> |
| <h1 class="text-3xl font-bold text-blue-800">Well Injection Pair Analysis Simulator</h1> |
| <h2 class="text-xl text-gray-600">Duri Field, Riau - Sumatra</h2> |
| <p class="mt-2 text-gray-700">Simulate injection well impacts on production wells in the same zone</p> |
| </header> |
|
|
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| |
| <div class="bg-white p-6 rounded-lg shadow-md col-span-1"> |
| <h3 class="text-xl font-semibold mb-4 text-blue-700">Simulation Parameters</h3> |
| |
| <div class="mb-4"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Number of Production Wells</label> |
| <input type="number" id="prodWellCount" min="1" max="20" value="5" |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <div class="mb-4"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Number of Injection Wells</label> |
| <input type="number" id="injWellCount" min="1" max="10" value="3" |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <div class="mb-4"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Maximum Distance (meters)</label> |
| <input type="number" id="maxDistance" min="100" max="5000" value="2000" step="100" |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <div class="mb-4"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Injection Rate (bbl/day)</label> |
| <input type="number" id="injectionRate" min="100" max="10000" value="2000" step="100" |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <div class="mb-6"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Simulation Duration (days)</label> |
| <input type="number" id="simDuration" min="30" max="365" value="90" step="10" |
| class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <button id="runSimulation" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"> |
| Run Simulation |
| </button> |
| |
| <button id="resetSimulation" class="w-full mt-2 bg-gray-500 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"> |
| Reset |
| </button> |
| </div> |
| |
| |
| <div class="bg-white p-6 rounded-lg shadow-md col-span-2"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-semibold text-blue-700">Field Map & Simulation Results</h3> |
| <div class="flex space-x-2"> |
| <button id="showConnections" class="bg-green-500 hover:bg-green-600 text-white text-sm py-1 px-2 rounded"> |
| Show Connections |
| </button> |
| <button id="exportData" class="bg-purple-500 hover:bg-purple-600 text-white text-sm py-1 px-2 rounded"> |
| Export Data |
| </button> |
| </div> |
| </div> |
| |
| <div id="map" class="mb-6 rounded-lg overflow-hidden"></div> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div class="bg-gray-50 p-4 rounded-lg"> |
| <h4 class="font-medium text-gray-800 mb-2">Production Well Impact Summary</h4> |
| <div id="prodWellSummary" class="text-sm"> |
| <p class="text-gray-600">Run simulation to see results</p> |
| </div> |
| </div> |
| <div class="bg-gray-50 p-4 rounded-lg"> |
| <h4 class="font-medium text-gray-800 mb-2">Injection Well Contribution</h4> |
| <div id="injWellSummary" class="text-sm"> |
| <p class="text-gray-600">Run simulation to see results</p> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mt-4"> |
| <h4 class="font-medium text-gray-800 mb-2">Pressure & Production Impact Chart</h4> |
| <div id="chartContainer" class="chart-container"> |
| <canvas id="impactChart"></canvas> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mt-8 bg-white p-6 rounded-lg shadow-md"> |
| <h3 class="text-xl font-semibold mb-4 text-blue-700">Pair Analysis Details</h3> |
| <div class="overflow-x-auto"> |
| <table class="min-w-full divide-y divide-gray-200"> |
| <thead class="bg-gray-50"> |
| <tr> |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Production Well</th> |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Injection Well</th> |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distance (m)</th> |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pressure Impact (psi)</th> |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Prod. Rate Change (%)</th> |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Water Cut Change (%)</th> |
| </tr> |
| </thead> |
| <tbody id="pairAnalysisTable" class="bg-white divide-y divide-gray-200"> |
| <tr> |
| <td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">No simulation data available</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
| |
| <div class="mt-8 bg-white p-6 rounded-lg shadow-md"> |
| <h3 class="text-xl font-semibold mb-4 text-blue-700">Mitigation Recommendations</h3> |
| <div id="mitigationRecommendations" class="space-y-3 text-gray-700"> |
| <p>Based on the simulation results, recommendations will appear here for optimizing injection patterns and mitigating negative impacts on production wells.</p> |
| </div> |
| </div> |
| </div> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <script> |
| |
| const map = L.map('map').setView([1.421, 101.252], 12); |
| |
| |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
| }).addTo(map); |
| |
| |
| const satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { |
| attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community' |
| }); |
| |
| |
| const baseLayers = { |
| "Street Map": L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'), |
| "Satellite": satelliteLayer |
| }; |
| |
| L.control.layers(baseLayers, null, {position: 'topright'}).addTo(map); |
| |
| |
| let productionWells = []; |
| let injectionWells = []; |
| let connections = []; |
| let impactChart = null; |
| let tooltip = null; |
| |
| |
| const runSimulationBtn = document.getElementById('runSimulation'); |
| const resetSimulationBtn = document.getElementById('resetSimulation'); |
| const showConnectionsBtn = document.getElementById('showConnections'); |
| const exportDataBtn = document.getElementById('exportData'); |
| |
| |
| runSimulationBtn.addEventListener('click', runSimulation); |
| resetSimulationBtn.addEventListener('click', resetSimulation); |
| showConnectionsBtn.addEventListener('click', toggleConnections); |
| exportDataBtn.addEventListener('click', exportData); |
| |
| |
| tooltip = document.createElement('div'); |
| tooltip.className = 'tooltip hidden'; |
| document.body.appendChild(tooltip); |
| |
| |
| createInitialWells(); |
| |
| |
| function createInitialWells() { |
| |
| productionWells = []; |
| injectionWells = []; |
| |
| |
| const prodCount = parseInt(document.getElementById('prodWellCount').value); |
| const injCount = parseInt(document.getElementById('injWellCount').value); |
| |
| |
| for (let i = 0; i < prodCount; i++) { |
| const lat = 1.421 + (Math.random() * 0.04 - 0.02); |
| const lng = 101.252 + (Math.random() * 0.04 - 0.02); |
| |
| const well = { |
| id: `P-${i+1}`, |
| name: `PROD-${i+1}`, |
| type: 'production', |
| lat: lat, |
| lng: lng, |
| initialRate: 500 + Math.random() * 1500, |
| waterCut: 10 + Math.random() * 40, |
| pressure: 800 + Math.random() * 400 |
| }; |
| |
| productionWells.push(well); |
| |
| |
| const marker = L.marker([lat, lng], { |
| icon: L.divIcon({ |
| className: 'well-icon production-well', |
| html: well.id |
| }) |
| }).addTo(map); |
| |
| marker.bindPopup(`<b>${well.name}</b><br>Type: Production<br>Initial Rate: ${well.initialRate.toFixed(0)} bbl/day<br>Water Cut: ${well.waterCut.toFixed(1)}%<br>Pressure: ${well.pressure.toFixed(0)} psi`); |
| |
| marker.on('mouseover', function(e) { |
| tooltip.className = 'tooltip'; |
| tooltip.innerHTML = `<b>${well.name}</b><br>Production Well`; |
| tooltip.style.left = e.containerPoint.x + 'px'; |
| tooltip.style.top = (e.containerPoint.y - 30) + 'px'; |
| }); |
| |
| marker.on('mouseout', function() { |
| tooltip.className = 'tooltip hidden'; |
| }); |
| } |
| |
| |
| for (let i = 0; i < injCount; i++) { |
| const lat = 1.421 + (Math.random() * 0.04 - 0.02); |
| const lng = 101.252 + (Math.random() * 0.04 - 0.02); |
| |
| const well = { |
| id: `I-${i+1}`, |
| name: `INJ-${i+1}`, |
| type: 'injection', |
| lat: lat, |
| lng: lng, |
| rate: parseInt(document.getElementById('injectionRate').value), |
| fluid: 'Water' |
| }; |
| |
| injectionWells.push(well); |
| |
| |
| const marker = L.marker([lat, lng], { |
| icon: L.divIcon({ |
| className: 'well-icon injection-well', |
| html: well.id |
| }) |
| }).addTo(map); |
| |
| marker.bindPopup(`<b>${well.name}</b><br>Type: Injection<br>Rate: ${well.rate} bbl/day<br>Fluid: ${well.fluid}`); |
| |
| marker.on('mouseover', function(e) { |
| tooltip.className = 'tooltip'; |
| tooltip.innerHTML = `<b>${well.name}</b><br>Injection Well`; |
| tooltip.style.left = e.containerPoint.x + 'px'; |
| tooltip.style.top = (e.containerPoint.y - 30) + 'px'; |
| }); |
| |
| marker.on('mouseout', function() { |
| tooltip.className = 'tooltip hidden'; |
| }); |
| } |
| } |
| |
| function runSimulation() { |
| |
| connections.forEach(conn => { |
| if (conn.line) map.removeLayer(conn.line); |
| }); |
| connections = []; |
| |
| |
| const maxDistance = parseInt(document.getElementById('maxDistance').value); |
| const injectionRate = parseInt(document.getElementById('injectionRate').value); |
| const duration = parseInt(document.getElementById('simDuration').value); |
| |
| |
| injectionWells.forEach(well => { |
| well.rate = injectionRate; |
| }); |
| |
| |
| const pairAnalysis = []; |
| |
| productionWells.forEach(prodWell => { |
| const impacts = []; |
| |
| injectionWells.forEach(injWell => { |
| |
| const distance = calculateDistance(prodWell, injWell); |
| |
| if (distance <= maxDistance) { |
| |
| const impactFactor = 1 - (distance / maxDistance); |
| const pressureImpact = (injectionRate * impactFactor * 0.05) * (duration / 30); |
| const prodRateChange = (injectionRate * impactFactor * 0.02) * (duration / 30); |
| const waterCutChange = (injectionRate * impactFactor * 0.03) * (duration / 30); |
| |
| impacts.push({ |
| injWell: injWell, |
| distance: distance, |
| pressureImpact: pressureImpact, |
| prodRateChange: prodRateChange, |
| waterCutChange: waterCutChange |
| }); |
| |
| |
| pairAnalysis.push({ |
| prodWell: prodWell, |
| injWell: injWell, |
| distance: distance, |
| pressureImpact: pressureImpact, |
| prodRateChange: prodRateChange, |
| waterCutChange: waterCutChange |
| }); |
| |
| |
| const line = L.polyline( |
| [[prodWell.lat, prodWell.lng], [injWell.lat, injWell.lng]], |
| { |
| color: '#6b7280', |
| weight: 2, |
| dashArray: '5, 5', |
| className: 'connection-line' |
| } |
| ).addTo(map); |
| |
| connections.push({ |
| prodWell: prodWell, |
| injWell: injWell, |
| line: line |
| }); |
| } |
| }); |
| |
| |
| if (impacts.length > 0) { |
| prodWell.totalPressureImpact = impacts.reduce((sum, impact) => sum + impact.pressureImpact, 0); |
| prodWell.totalProdRateChange = impacts.reduce((sum, impact) => sum + impact.prodRateChange, 0); |
| prodWell.totalWaterCutChange = impacts.reduce((sum, impact) => sum + impact.waterCutChange, 0); |
| |
| |
| prodWell.finalPressure = prodWell.pressure + prodWell.totalPressureImpact; |
| prodWell.finalRate = prodWell.initialRate + prodWell.totalProdRateChange; |
| prodWell.finalWaterCut = Math.min(95, prodWell.waterCut + prodWell.totalWaterCutChange); |
| } else { |
| prodWell.totalPressureImpact = 0; |
| prodWell.totalProdRateChange = 0; |
| prodWell.totalWaterCutChange = 0; |
| prodWell.finalPressure = prodWell.pressure; |
| prodWell.finalRate = prodWell.initialRate; |
| prodWell.finalWaterCut = prodWell.waterCut; |
| } |
| }); |
| |
| |
| updateResultsUI(pairAnalysis); |
| |
| |
| generateImpactChart(); |
| |
| |
| generateMitigationRecommendations(pairAnalysis); |
| } |
| |
| function calculateDistance(well1, well2) { |
| |
| const latDiff = well1.lat - well2.lat; |
| const lngDiff = well1.lng - well2.lng; |
| return Math.sqrt(latDiff * latDiff + lngDiff * lngDiff) * 111320; |
| } |
| |
| function updateResultsUI(pairAnalysis) { |
| |
| let prodSummaryHTML = ''; |
| productionWells.forEach(well => { |
| prodSummaryHTML += ` |
| <div class="mb-2 pb-2 border-b border-gray-200"> |
| <div class="flex justify-between"> |
| <span class="font-medium">${well.name}</span> |
| <span class="text-blue-600">${well.id}</span> |
| </div> |
| <div class="grid grid-cols-2 gap-1 text-xs mt-1"> |
| <div>Initial Rate: <span class="font-medium">${well.initialRate.toFixed(0)} bbl/day</span></div> |
| <div>Final Rate: <span class="font-medium">${well.finalRate.toFixed(0)} bbl/day</span></div> |
| <div>Pressure: <span class="font-medium">${well.pressure.toFixed(0)} → ${well.finalPressure.toFixed(0)} psi</span></div> |
| <div>Water Cut: <span class="font-medium">${well.waterCut.toFixed(1)}% → ${well.finalWaterCut.toFixed(1)}%</span></div> |
| </div> |
| </div> |
| `; |
| }); |
| document.getElementById('prodWellSummary').innerHTML = prodSummaryHTML; |
| |
| |
| let injSummaryHTML = ''; |
| injectionWells.forEach(well => { |
| |
| const affectedWells = pairAnalysis.filter(pair => pair.injWell.id === well.id).length; |
| |
| injSummaryHTML += ` |
| <div class="mb-2 pb-2 border-b border-gray-200"> |
| <div class="flex justify-between"> |
| <span class="font-medium">${well.name}</span> |
| <span class="text-red-600">${well.id}</span> |
| </div> |
| <div class="grid grid-cols-2 gap-1 text-xs mt-1"> |
| <div>Rate: <span class="font-medium">${well.rate} bbl/day</span></div> |
| <div>Affects: <span class="font-medium">${affectedWells} prod. wells</span></div> |
| </div> |
| </div> |
| `; |
| }); |
| document.getElementById('injWellSummary').innerHTML = injSummaryHTML; |
| |
| |
| let tableHTML = ''; |
| pairAnalysis.forEach(pair => { |
| tableHTML += ` |
| <tr> |
| <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">${pair.prodWell.name}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-red-600">${pair.injWell.name}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${pair.distance.toFixed(0)}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${pair.pressureImpact.toFixed(1)}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${pair.prodRateChange.toFixed(1)}</td> |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${pair.waterCutChange.toFixed(1)}</td> |
| </tr> |
| `; |
| }); |
| |
| if (tableHTML === '') { |
| tableHTML = ` |
| <tr> |
| <td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">No connections within specified distance</td> |
| </tr> |
| `; |
| } |
| |
| document.getElementById('pairAnalysisTable').innerHTML = tableHTML; |
| } |
| |
| function generateImpactChart() { |
| const ctx = document.getElementById('impactChart').getContext('2d'); |
| |
| |
| if (impactChart) { |
| impactChart.destroy(); |
| } |
| |
| |
| const labels = productionWells.map(well => well.name); |
| const initialRates = productionWells.map(well => well.initialRate); |
| const finalRates = productionWells.map(well => well.finalRate); |
| const initialWaterCuts = productionWells.map(well => well.waterCut); |
| const finalWaterCuts = productionWells.map(well => well.finalWaterCut); |
| |
| impactChart = new Chart(ctx, { |
| type: 'bar', |
| data: { |
| labels: labels, |
| datasets: [ |
| { |
| label: 'Initial Production Rate (bbl/day)', |
| data: initialRates, |
| backgroundColor: 'rgba(59, 130, 246, 0.5)', |
| borderColor: 'rgba(59, 130, 246, 1)', |
| borderWidth: 1 |
| }, |
| { |
| label: 'Final Production Rate (bbl/day)', |
| data: finalRates, |
| backgroundColor: 'rgba(16, 185, 129, 0.5)', |
| borderColor: 'rgba(16, 185, 129, 1)', |
| borderWidth: 1 |
| }, |
| { |
| label: 'Initial Water Cut (%)', |
| data: initialWaterCuts, |
| backgroundColor: 'rgba(249, 168, 37, 0.5)', |
| borderColor: 'rgba(249, 168, 37, 1)', |
| borderWidth: 1, |
| type: 'line', |
| yAxisID: 'y1' |
| }, |
| { |
| label: 'Final Water Cut (%)', |
| data: finalWaterCuts, |
| backgroundColor: 'rgba(220, 38, 38, 0.5)', |
| borderColor: 'rgba(220, 38, 38, 1)', |
| borderWidth: 1, |
| type: 'line', |
| yAxisID: 'y1' |
| } |
| ] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| scales: { |
| y: { |
| beginAtZero: true, |
| title: { |
| display: true, |
| text: 'Production Rate (bbl/day)' |
| } |
| }, |
| y1: { |
| position: 'right', |
| beginAtZero: true, |
| max: 100, |
| title: { |
| display: true, |
| text: 'Water Cut (%)' |
| }, |
| grid: { |
| drawOnChartArea: false |
| } |
| } |
| }, |
| plugins: { |
| title: { |
| display: true, |
| text: 'Production Well Performance Before and After Injection' |
| }, |
| tooltip: { |
| callbacks: { |
| label: function(context) { |
| let label = context.dataset.label || ''; |
| if (label) { |
| label += ': '; |
| } |
| label += context.parsed.y.toFixed(1); |
| return label; |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| function generateMitigationRecommendations(pairAnalysis) { |
| let recommendationsHTML = ''; |
| |
| if (pairAnalysis.length === 0) { |
| recommendationsHTML = ` |
| <div class="p-4 bg-green-50 rounded-lg"> |
| <h4 class="font-medium text-green-800">No Significant Impacts Detected</h4> |
| <p class="text-sm text-green-700 mt-1">Based on current injection patterns and distances, no production wells are significantly affected.</p> |
| </div> |
| `; |
| } else { |
| |
| const highWaterCutWells = [...productionWells] |
| .sort((a, b) => (b.finalWaterCut - b.waterCut) - (a.finalWaterCut - a.waterCut)) |
| .slice(0, 3); |
| |
| if (highWaterCutWells.length > 0 && highWaterCutWells[0].finalWaterCut - highWaterCutWells[0].waterCut > 10) { |
| recommendationsHTML += ` |
| <div class="p-4 bg-yellow-50 rounded-lg mb-3"> |
| <h4 class="font-medium text-yellow-800">Water Breakthrough Risk</h4> |
| <p class="text-sm text-yellow-700 mt-1">The following production wells show significant water cut increase (>10%):</p> |
| <ul class="list-disc list-inside text-sm text-yellow-700 mt-2 ml-2"> |
| ${highWaterCutWells.map(well => |
| `<li>${well.name}: Water cut increased from ${well.waterCut.toFixed(1)}% to ${well.finalWaterCut.toFixed(1)}%</li>` |
| ).join('')} |
| </ul> |
| <p class="text-sm text-yellow-700 mt-2">Consider adjusting injection rates or patterns for injectors affecting these wells.</p> |
| </div> |
| `; |
| } |
| |
| |
| const injectorImpactCount = {}; |
| pairAnalysis.forEach(pair => { |
| if (!injectorImpactCount[pair.injWell.id]) { |
| injectorImpactCount[pair.injWell.id] = 0; |
| } |
| injectorImpactCount[pair.injWell.id]++; |
| }); |
| |
| const mostImpactfulInjectors = Object.entries(injectorImpactCount) |
| .sort((a, b) => b[1] - a[1]) |
| .slice(0, 2); |
| |
| if (mostImpactfulInjectors.length > 0 && mostImpactfulInjectors[0][1] > 3) { |
| recommendationsHTML += ` |
| <div class="p-4 bg-blue-50 rounded-lg mb-3"> |
| <h4 class="font-medium text-blue-800">High-Impact Injectors</h4> |
| <p class="text-sm text-blue-700 mt-1">The following injectors affect multiple production wells:</p> |
| <ul class="list-disc list-inside text-sm text-blue-700 mt-2 ml-2"> |
| ${mostImpactfulInjectors.map(([id, count]) => |
| `<li>${id}: Affects ${count} production wells</li>` |
| ).join('')} |
| </ul> |
| <p class="text-sm text-blue-700 mt-2">Consider monitoring these injectors closely as they have broad impact across the field.</p> |
| </div> |
| `; |
| } |
| |
| |
| const affectedProdWellIds = new Set(pairAnalysis.map(pair => pair.prodWell.id)); |
| const isolatedProdWells = productionWells.filter(well => !affectedProdWellIds.has(well.id)); |
| |
| if (isolatedProdWells.length > 0) { |
| recommendationsHTML += ` |
| <div class="p-4 bg-purple-50 rounded-lg"> |
| <h4 class="font-medium text-purple-800">Isolated Production Wells</h4> |
| <p class="text-sm text-purple-700 mt-1">The following production wells are not receiving pressure support from any injector:</p> |
| <ul class="list-disc list-inside text-sm text-purple-700 mt-2 ml-2"> |
| ${isolatedProdWells.map(well => |
| `<li>${well.name}</li>` |
| ).join('')} |
| </ul> |
| <p class="text-sm text-purple-700 mt-2">Consider adding new injectors or adjusting existing ones to provide pressure support to these wells.</p> |
| </div> |
| `; |
| } |
| } |
| |
| document.getElementById('mitigationRecommendations').innerHTML = recommendationsHTML || ` |
| <p class="text-gray-700">No specific mitigation recommendations based on current simulation parameters.</p> |
| `; |
| } |
| |
| function toggleConnections() { |
| const isVisible = connections.length > 0 && map.hasLayer(connections[0].line); |
| |
| if (isVisible) { |
| connections.forEach(conn => { |
| map.removeLayer(conn.line); |
| }); |
| showConnectionsBtn.textContent = 'Show Connections'; |
| showConnectionsBtn.className = 'bg-green-500 hover:bg-green-600 text-white text-sm py-1 px-2 rounded'; |
| } else { |
| connections.forEach(conn => { |
| conn.line.addTo(map); |
| }); |
| showConnectionsBtn.textContent = 'Hide Connections'; |
| showConnectionsBtn.className = 'bg-gray-500 hover:bg-gray-600 text-white text-sm py-1 px-2 rounded'; |
| } |
| } |
| |
| function exportData() { |
| |
| const data = { |
| simulationParameters: { |
| productionWellCount: parseInt(document.getElementById('prodWellCount').value), |
| injectionWellCount: parseInt(document.getElementById('injWellCount').value), |
| maxDistance: parseInt(document.getElementById('maxDistance').value), |
| injectionRate: parseInt(document.getElementById('injectionRate').value), |
| duration: parseInt(document.getElementById('simDuration').value) |
| }, |
| productionWells: productionWells, |
| injectionWells: injectionWells, |
| pairAnalysis: connections.map(conn => { |
| const pair = productionWells.find(p => p.id === conn.prodWell.id); |
| const inj = injectionWells.find(i => i.id === conn.injWell.id); |
| const distance = calculateDistance(pair, inj); |
| |
| return { |
| productionWell: pair.name, |
| injectionWell: inj.name, |
| distance: distance, |
| pressureImpact: pair.totalPressureImpact, |
| productionRateChange: pair.totalProdRateChange, |
| waterCutChange: pair.totalWaterCutChange |
| }; |
| }) |
| }; |
| |
| |
| const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2)); |
| const downloadAnchorNode = document.createElement('a'); |
| downloadAnchorNode.setAttribute("href", dataStr); |
| downloadAnchorNode.setAttribute("download", "well_injection_simulation.json"); |
| document.body.appendChild(downloadAnchorNode); |
| downloadAnchorNode.click(); |
| downloadAnchorNode.remove(); |
| |
| |
| alert('Simulation data exported successfully!'); |
| } |
| |
| function resetSimulation() { |
| |
| connections.forEach(conn => { |
| if (conn.line) map.removeLayer(conn.line); |
| }); |
| connections = []; |
| |
| |
| document.getElementById('prodWellSummary').innerHTML = '<p class="text-gray-600">Run simulation to see results</p>'; |
| document.getElementById('injWellSummary').innerHTML = '<p class="text-gray-600">Run simulation to see results</p>'; |
| document.getElementById('pairAnalysisTable').innerHTML = '<tr><td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">No simulation data available</td></tr>'; |
| document.getElementById('mitigationRecommendations').innerHTML = '<p class="text-gray-700">Based on the simulation results, recommendations will appear here for optimizing injection patterns and mitigating negative impacts on production wells.</p>'; |
| |
| |
| if (impactChart) { |
| impactChart.destroy(); |
| impactChart = null; |
| } |
| |
| |
| createInitialWells(); |
| |
| |
| showConnectionsBtn.textContent = 'Show Connections'; |
| showConnectionsBtn.className = 'bg-green-500 hover:bg-green-600 text-white text-sm py-1 px-2 rounded'; |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=alterzick/simulation-pair-analysis" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |