|
|
<!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> |