Download index.html from alterzick/simulation-pair-analysis: direct link, hf CLI and curl.
- Browser
- Download file 38.8 kB
-
https://huggingface.co/spaces/alterzick/simulation-pair-analysis/resolve/main/index.html
- Command line
-
hf download hf://spaces/alterzick/simulation-pair-analysis/index.html
-
curl -L -o index.html https://huggingface.co/spaces/alterzick/simulation-pair-analysis/resolve/main/index.html
38.8 kB
| <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"> | |
| <!-- Control Panel --> | |
| <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> | |
| <!-- Map and Results --> | |
| <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> | |
| // Initialize map centered on Duri, Riau | |
| const map = L.map('map').setView([1.421, 101.252], 12); | |
| // Add base map layer | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | |
| }).addTo(map); | |
| // Add satellite imagery option | |
| 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' | |
| }); | |
| // Add base map layer control | |
| 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); | |
| // Initialize variables | |
| let productionWells = []; | |
| let injectionWells = []; | |
| let connections = []; | |
| let impactChart = null; | |
| let tooltip = null; | |
| // DOM elements | |
| const runSimulationBtn = document.getElementById('runSimulation'); | |
| const resetSimulationBtn = document.getElementById('resetSimulation'); | |
| const showConnectionsBtn = document.getElementById('showConnections'); | |
| const exportDataBtn = document.getElementById('exportData'); | |
| // Event listeners | |
| runSimulationBtn.addEventListener('click', runSimulation); | |
| resetSimulationBtn.addEventListener('click', resetSimulation); | |
| showConnectionsBtn.addEventListener('click', toggleConnections); | |
| exportDataBtn.addEventListener('click', exportData); | |
| // Initialize tooltip | |
| tooltip = document.createElement('div'); | |
| tooltip.className = 'tooltip hidden'; | |
| document.body.appendChild(tooltip); | |
| // Create initial wells (for demo purposes) | |
| createInitialWells(); | |
| // Functions | |
| function createInitialWells() { | |
| // Clear existing wells | |
| productionWells = []; | |
| injectionWells = []; | |
| // Get counts from inputs | |
| const prodCount = parseInt(document.getElementById('prodWellCount').value); | |
| const injCount = parseInt(document.getElementById('injWellCount').value); | |
| // Create production wells | |
| 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); | |
| // Add to map | |
| 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'; | |
| }); | |
| } | |
| // Create injection wells | |
| 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); | |
| // Add to map | |
| 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() { | |
| // Clear previous connections | |
| connections.forEach(conn => { | |
| if (conn.line) map.removeLayer(conn.line); | |
| }); | |
| connections = []; | |
| // Get simulation parameters | |
| const maxDistance = parseInt(document.getElementById('maxDistance').value); | |
| const injectionRate = parseInt(document.getElementById('injectionRate').value); | |
| const duration = parseInt(document.getElementById('simDuration').value); | |
| // Update injection rates | |
| injectionWells.forEach(well => { | |
| well.rate = injectionRate; | |
| }); | |
| // Calculate distances and impacts | |
| const pairAnalysis = []; | |
| productionWells.forEach(prodWell => { | |
| const impacts = []; | |
| injectionWells.forEach(injWell => { | |
| // Calculate distance between wells | |
| const distance = calculateDistance(prodWell, injWell); | |
| if (distance <= maxDistance) { | |
| // Calculate impact based on distance and injection rate | |
| 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 | |
| }); | |
| // Add to pair analysis table | |
| pairAnalysis.push({ | |
| prodWell: prodWell, | |
| injWell: injWell, | |
| distance: distance, | |
| pressureImpact: pressureImpact, | |
| prodRateChange: prodRateChange, | |
| waterCutChange: waterCutChange | |
| }); | |
| // Create connection line | |
| 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 | |
| }); | |
| } | |
| }); | |
| // Calculate total impacts on production well | |
| 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); | |
| // Update production well properties | |
| 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; | |
| } | |
| }); | |
| // Update UI with results | |
| updateResultsUI(pairAnalysis); | |
| // Generate chart | |
| generateImpactChart(); | |
| // Generate mitigation recommendations | |
| generateMitigationRecommendations(pairAnalysis); | |
| } | |
| function calculateDistance(well1, well2) { | |
| // Simple distance calculation (for demo purposes) | |
| const latDiff = well1.lat - well2.lat; | |
| const lngDiff = well1.lng - well2.lng; | |
| return Math.sqrt(latDiff * latDiff + lngDiff * lngDiff) * 111320; // Convert to meters | |
| } | |
| function updateResultsUI(pairAnalysis) { | |
| // Update production well summary | |
| 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; | |
| // Update injection well summary | |
| let injSummaryHTML = ''; | |
| injectionWells.forEach(well => { | |
| // Calculate how many production wells this injector affects | |
| 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; | |
| // Update pair analysis table | |
| 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'); | |
| // Destroy previous chart if exists | |
| if (impactChart) { | |
| impactChart.destroy(); | |
| } | |
| // Prepare data | |
| 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 { | |
| // Find wells with highest water cut increase | |
| 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> | |
| `; | |
| } | |
| // Find injectors affecting most production wells | |
| 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> | |
| `; | |
| } | |
| // Find isolated production wells (not affected by any injector) | |
| 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() { | |
| // Prepare data for export | |
| 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 | |
| }; | |
| }) | |
| }; | |
| // Create download link | |
| 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(); | |
| // Show notification | |
| alert('Simulation data exported successfully!'); | |
| } | |
| function resetSimulation() { | |
| // Clear connections | |
| connections.forEach(conn => { | |
| if (conn.line) map.removeLayer(conn.line); | |
| }); | |
| connections = []; | |
| // Reset UI | |
| 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>'; | |
| // Destroy chart if exists | |
| if (impactChart) { | |
| impactChart.destroy(); | |
| impactChart = null; | |
| } | |
| // Recreate wells with current parameters | |
| createInitialWells(); | |
| // Reset connection button | |
| 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> |