alterzick commited on
Commit
1f9af53
·
verified ·
1 Parent(s): 1c96387

Add 2 files

Browse files
Files changed (2) hide show
  1. index.html +285 -36
  2. prompts.txt +2 -1
index.html CHANGED
@@ -62,6 +62,23 @@
62
  transform: translateY(-1px);
63
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
64
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  </style>
66
  </head>
67
  <body class="bg-gradient-to-br from-slate-50 to-slate-100 min-h-screen">
@@ -70,7 +87,7 @@
70
  <div class="text-center mb-10">
71
  <h1 class="text-4xl font-bold text-gray-800 mb-3">Decline Curve Analysis</h1>
72
  <p class="text-lg text-gray-600 max-w-3xl mx-auto">
73
- Enter current well information and upload your production data in Excel format to analyze production decline and forecast future performance.
74
  Supports exponential, hyperbolic, and harmonic decline models.
75
  </p>
76
  </div>
@@ -135,13 +152,57 @@
135
  </div>
136
  </div>
137
 
138
- <!-- Upload Section -->
139
  <div class="bg-white rounded-2xl shadow-xl p-8 mb-8 max-w-4xl mx-auto transform hover:shadow-2xl transition-shadow duration-300">
140
  <div class="flex items-center mb-6 text-blue-700">
141
  <i class="fas fa-chart-line text-2xl mr-3"></i>
142
- <h2 class="text-2xl font-semibold">Upload Production Data</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  </div>
144
 
 
145
  <div id="dropZone" class="upload-area mb-6">
146
  <i class="fas fa-cloud-upload-alt text-5xl text-gray-400 mb-4"></i>
147
  <h3 class="text-xl font-medium text-gray-700 mb-2">Drag & Drop Excel File</h3>
@@ -159,9 +220,12 @@
159
 
160
  <!-- Results Section -->
161
  <div id="resultsSection" class="hidden bg-white rounded-2xl shadow-xl p-8 max-w-6xl mx-auto">
162
- <div class="flex items-center mb-6 text-green-700">
163
- <i class="fas fa-check-circle text-2xl mr-3"></i>
164
- <h2 class="text-2xl font-semibold">Analysis Results</h2>
 
 
 
165
  </div>
166
 
167
  <!-- Key Metrics Row -->
@@ -213,12 +277,8 @@
213
  <div class="mb-6">
214
  <div class="flex justify-between items-center mb-4">
215
  <h3 class="text-xl font-semibold text-gray-800">Production Rate vs Time</h3>
216
- <div class="flex space-x-2">
217
- <select id="declineModel" class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
218
- <option value="exponential">Exponential</option>
219
- <option value="hyperbolic" selected>Hyperbolic</option>
220
- <option value="harmonic">Harmonic</option>
221
- </select>
222
  <input type="number" id="forecastMonths" class="w-20 px-3 py-2 border border-gray-300 rounded-lg text-sm text-center" value="24" min="1" max="120">
223
  <span class="self-center text-sm text-gray-600">months</span>
224
  </div>
@@ -257,6 +317,7 @@
257
  <script>
258
  // Global variables
259
  let productionData = [];
 
260
  let productionChart = null;
261
  let currentAnalysis = null;
262
 
@@ -265,9 +326,14 @@
265
  const fileInput = document.getElementById('fileInput');
266
  const uploadBtn = document.getElementById('uploadBtn');
267
  const resultsSection = document.getElementById('resultsSection');
268
- const declineModel = document.getElementById('declineModel');
269
  const forecastMonths = document.getElementById('forecastMonths');
270
  const exportBtn = document.getElementById('exportBtn');
 
 
 
 
 
271
 
272
  // Current well information input elements
273
  const currentMonthInput = document.getElementById('currentMonthInput');
@@ -279,6 +345,13 @@
279
  const cumulativeProductionEl = document.getElementById('cumulativeProduction');
280
  const productionLifeEl = document.getElementById('productionLife');
281
 
 
 
 
 
 
 
 
282
  // Event Listeners
283
  dropZone.addEventListener('click', () => fileInput.click());
284
  dropZone.addEventListener('dragover', handleDragOver);
@@ -286,10 +359,29 @@
286
  dropZone.addEventListener('drop', handleDrop);
287
  fileInput.addEventListener('change', handleFileSelect);
288
  uploadBtn.addEventListener('click', processFile);
289
- declineModel.addEventListener('change', updateChart);
290
  forecastMonths.addEventListener('change', updateChart);
291
  exportBtn.addEventListener('click', exportResults);
292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  // Update the current performance display when manual inputs change
294
  currentMonthInput.addEventListener('change', updateManualCurrentPerformance);
295
  currentProductionInput.addEventListener('input', updateManualCurrentPerformance);
@@ -299,6 +391,18 @@
299
  const now = new Date();
300
  const dateString = now.toLocaleDateString('default', { month: 'short', year: 'numeric' });
301
  currentMonthEl.textContent = dateString;
 
 
 
 
 
 
 
 
 
 
 
 
302
  });
303
 
304
  // Update current performance display with manual inputs
@@ -383,6 +487,12 @@
383
  // Sort by date
384
  productionData.sort((a, b) => a.timeMonths - b.timeMonths);
385
 
 
 
 
 
 
 
386
  // Make sure current performance info is updated
387
  if (currentMonthInput.value || currentProductionInput.value) {
388
  updateManualCurrentPerformance();
@@ -391,8 +501,8 @@
391
  // Display results
392
  displayResults();
393
 
394
- // Analyze decline curve
395
- currentAnalysis = performDeclineAnalysis(productionData);
396
 
397
  // Update analysis info
398
  updateAnalysisInfo(currentAnalysis);
@@ -401,11 +511,14 @@
401
  updateCurrentPerformance();
402
 
403
  // Create chart
404
- createChart(productionData, currentAnalysis);
405
 
406
  // Show results section
407
  resultsSection.classList.remove('hidden');
408
 
 
 
 
409
  // Scroll to results
410
  resultsSection.scrollIntoView({ behavior: 'smooth' });
411
 
@@ -418,6 +531,107 @@
418
  reader.readAsArrayBuffer(file);
419
  }
420
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  function parseProductionData(jsonData) {
422
  const parsedData = [];
423
  let startDate = null;
@@ -469,15 +683,34 @@
469
  return parsedData;
470
  }
471
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  function performDeclineAnalysis(data) {
473
  // Simple decline curve analysis using hyperbolic decline
474
  // q(t) = qi / (1 + b * Di * t)^(1/b)
475
 
476
- const qi = data[0].rate; // Initial rate
477
- const productionMonths = data[data.length - 1].timeMonths - data[0].timeMonths;
 
 
478
 
479
  // Simple estimation of decline parameters
480
- // This is a basic implementation - in reality, you'd use non-linear regression
481
  let Di = 0; // Nominal decline rate
482
  let b = 0.5; // Decline exponent (b=0 exponential, b=1 harmonic, 0<b<1 hyperbolic)
483
  let rSquared = 0;
@@ -487,14 +720,14 @@
487
  const qf = data[data.length - 1].rate;
488
 
489
  // For hyperbolic decline, we'll make a simple estimation
490
- const avgDecline = (qi - qf) / qi / productionMonths;
491
- Di = avgDecline * 12; // Annual decline rate
492
 
493
  // Adjust b factor based on the shape of the decline
494
- // This is very simplified - real analysis would use proper regression
495
- if (qf / qi > 0.5) {
496
  b = 0.3; // Steeper decline
497
- } else if (qf / qi > 0.3) {
498
  b = 0.5;
499
  } else {
500
  b = 0.8; // Gentle decline
@@ -504,6 +737,16 @@
504
  b = 0.5;
505
  }
506
 
 
 
 
 
 
 
 
 
 
 
507
  // Calculate R-squared (very simplified)
508
  let ssRes = 0;
509
  let ssTot = 0;
@@ -515,7 +758,7 @@
515
  ssTot += Math.pow(point.rate - yMean, 2);
516
  });
517
 
518
- rSquared = 1 - (ssRes / Math.max(ssTot, Number.EPSILON));
519
 
520
  return { qi, Di, b, rSquared };
521
  }
@@ -582,7 +825,7 @@
582
  const tableBody = document.getElementById('tableBody');
583
  tableBody.innerHTML = '';
584
 
585
- productionData.forEach(point => {
586
  const row = document.createElement('tr');
587
  row.innerHTML = `
588
  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700">${point.date.toLocaleDateString()}</td>
@@ -595,6 +838,8 @@
595
  }
596
 
597
  function updateAnalysisInfo(analysis) {
 
 
598
  document.getElementById('qiValue').textContent = `${analysis.qi.toFixed(2)} BOPD`;
599
  document.getElementById('diValue').textContent = `${(analysis.Di * 100).toFixed(1)}%/yr`;
600
  document.getElementById('bValue').textContent = analysis.b.toFixed(2);
@@ -602,6 +847,8 @@
602
  }
603
 
604
  function createChart(data, analysis) {
 
 
605
  const ctx = document.getElementById('productionChart').getContext('2d');
606
 
607
  // Destroy existing chart if it exists
@@ -609,18 +856,16 @@
609
  productionChart.destroy();
610
  }
611
 
612
- const actualDates = data.map(d => d.timeMonths);
613
  const actualRates = data.map(d => d.rate);
614
 
615
  // Generate forecast data
616
- const maxTime = Math.max(...data.map(d => d.timeMonths));
617
  const forecastTime = [];
618
  const forecastRates = [];
619
 
620
  const forecastPeriod = parseInt(forecastMonths.value);
621
- const model = declineModel.value;
622
-
623
- const b = model === 'exponential' ? 0 : (model === 'harmonic' ? 1 : analysis.b);
624
 
625
  // Create data points for the chart
626
  for (let t = 0; t <= maxTime + forecastPeriod; t += 0.5) {
@@ -737,8 +982,11 @@
737
  }
738
 
739
  function updateChart() {
740
- if (productionData.length > 0 && currentAnalysis) {
741
- createChart(productionData, currentAnalysis);
 
 
 
742
  }
743
  }
744
 
@@ -751,7 +999,7 @@
751
  // Create a worksheet
752
  const ws_data = [
753
  ['Date', 'Rate (BOPD)', 'Cumulative (STB)', 'Time (months)'],
754
- ...productionData.map(d => [d.date.toLocaleDateString(), d.rate, d.cumulative, d.timeMonths])
755
  ];
756
 
757
  const ws = XLSX.utils.aoa_to_sheet(ws_data);
@@ -768,7 +1016,8 @@
768
  ['Current Month', currentMonthEl.textContent],
769
  ['Current Production', currentProductionEl.textContent],
770
  ['Cumulative Production', cumulativeProductionEl.textContent],
771
- ['Model Used', declineModel.options[declineModel.selectedIndex].text],
 
772
  ['Forecast Period', `${forecastMonths.value} months`]
773
  ], { origin: -1 });
774
 
 
62
  transform: translateY(-1px);
63
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
64
  }
65
+
66
+ .filter-badge {
67
+ background: linear-gradient(90deg, #3b82f6, #60a5fa);
68
+ padding: 0.25rem 0.75rem;
69
+ border-radius: 9999px;
70
+ color: white;
71
+ font-size: 0.75rem;
72
+ font-weight: 600;
73
+ margin: 0.25rem;
74
+ display: inline-flex;
75
+ align-items: center;
76
+ }
77
+
78
+ .filter-badge i {
79
+ margin-right: 0.25rem;
80
+ font-size: 0.625rem;
81
+ }
82
  </style>
83
  </head>
84
  <body class="bg-gradient-to-br from-slate-50 to-slate-100 min-h-screen">
 
87
  <div class="text-center mb-10">
88
  <h1 class="text-4xl font-bold text-gray-800 mb-3">Decline Curve Analysis</h1>
89
  <p class="text-lg text-gray-600 max-w-3xl mx-auto">
90
+ Enter current well information, select the analysis model, specify date range for calculations, and upload your production data in Excel format to analyze production decline and forecast future performance.
91
  Supports exponential, hyperbolic, and harmonic decline models.
92
  </p>
93
  </div>
 
152
  </div>
153
  </div>
154
 
155
+ <!-- Upload Section with Advanced Controls -->
156
  <div class="bg-white rounded-2xl shadow-xl p-8 mb-8 max-w-4xl mx-auto transform hover:shadow-2xl transition-shadow duration-300">
157
  <div class="flex items-center mb-6 text-blue-700">
158
  <i class="fas fa-chart-line text-2xl mr-3"></i>
159
+ <h2 class="text-2xl font-semibold">Upload & Configure Analysis</h2>
160
+ </div>
161
+
162
+ <!-- Analysis Configuration Section -->
163
+ <div class="bg-indigo-50 p-6 rounded-xl border border-indigo-200 mb-8">
164
+ <h3 class="text-lg font-semibold text-indigo-800 mb-4 flex items-center">
165
+ <i class="fas fa-cogs mr-2"></i> Analysis Configuration
166
+ </h3>
167
+
168
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
169
+ <div>
170
+ <label for="declineModelSelect" class="block text-sm font-medium text-gray-700 mb-2">Decline Curve Model</label>
171
+ <div class="relative">
172
+ <select id="declineModelSelect" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 appearance-none bg-white">
173
+ <option value="hyperbolic" selected>Hyperbolic Decline</option>
174
+ <option value="exponential">Exponential Decline</option>
175
+ <option value="harmonic">Harmonic Decline</option>
176
+ </select>
177
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-gray-700">
178
+ <i class="fas fa-chevron-down"></i>
179
+ </div>
180
+ </div>
181
+ <p id="modelDescription" class="mt-2 text-sm text-gray-600">
182
+ Hyperbolic decline provides the most flexible modeling of production decline patterns.
183
+ </p>
184
+ </div>
185
+
186
+ <div>
187
+ <label class="block text-sm font-medium text-gray-700 mb-2">Date Range for Analysis</label>
188
+ <div class="grid grid-cols-2 gap-2">
189
+ <div>
190
+ <label for="startDateFilter" class="block text-xs text-gray-500 mb-1">From</label>
191
+ <input type="date" id="startDateFilter" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
192
+ </div>
193
+ <div>
194
+ <label for="endDateFilter" class="block text-xs text-gray-500 mb-1">To</label>
195
+ <input type="date" id="endDateFilter" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500">
196
+ </div>
197
+ </div>
198
+ <div id="dateFilterStatus" class="mt-2 text-sm text-gray-500">
199
+ Use date filters to analyze specific production periods.
200
+ </div>
201
+ </div>
202
+ </div>
203
  </div>
204
 
205
+ <!-- Upload Section -->
206
  <div id="dropZone" class="upload-area mb-6">
207
  <i class="fas fa-cloud-upload-alt text-5xl text-gray-400 mb-4"></i>
208
  <h3 class="text-xl font-medium text-gray-700 mb-2">Drag & Drop Excel File</h3>
 
220
 
221
  <!-- Results Section -->
222
  <div id="resultsSection" class="hidden bg-white rounded-2xl shadow-xl p-8 max-w-6xl mx-auto">
223
+ <div class="flex items-center justify-between mb-6">
224
+ <div class="flex items-center text-green-700">
225
+ <i class="fas fa-check-circle text-2xl mr-3"></i>
226
+ <h2 class="text-2xl font-semibold">Analysis Results</h2>
227
+ </div>
228
+ <div id="activeFilters" class="flex flex-wrap"></div>
229
  </div>
230
 
231
  <!-- Key Metrics Row -->
 
277
  <div class="mb-6">
278
  <div class="flex justify-between items-center mb-4">
279
  <h3 class="text-xl font-semibold text-gray-800">Production Rate vs Time</h3>
280
+ <div class="flex items-center space-x-2">
281
+ <span class="text-sm text-gray-600">Forecast:</span>
 
 
 
 
282
  <input type="number" id="forecastMonths" class="w-20 px-3 py-2 border border-gray-300 rounded-lg text-sm text-center" value="24" min="1" max="120">
283
  <span class="self-center text-sm text-gray-600">months</span>
284
  </div>
 
317
  <script>
318
  // Global variables
319
  let productionData = [];
320
+ let filteredData = [];
321
  let productionChart = null;
322
  let currentAnalysis = null;
323
 
 
326
  const fileInput = document.getElementById('fileInput');
327
  const uploadBtn = document.getElementById('uploadBtn');
328
  const resultsSection = document.getElementById('resultsSection');
329
+ const declineModelSelect = document.getElementById('declineModelSelect');
330
  const forecastMonths = document.getElementById('forecastMonths');
331
  const exportBtn = document.getElementById('exportBtn');
332
+ const modelDescription = document.getElementById('modelDescription');
333
+ const startDateFilter = document.getElementById('startDateFilter');
334
+ const endDateFilter = document.getElementById('endDateFilter');
335
+ const dateFilterStatus = document.getElementById('dateFilterStatus');
336
+ const activeFilters = document.getElementById('activeFilters');
337
 
338
  // Current well information input elements
339
  const currentMonthInput = document.getElementById('currentMonthInput');
 
345
  const cumulativeProductionEl = document.getElementById('cumulativeProduction');
346
  const productionLifeEl = document.getElementById('productionLife');
347
 
348
+ // Model descriptions
349
+ const modelDescriptions = {
350
+ 'exponential': 'Exponential decline assumes a constant percentage decline rate over time. Suitable for pressure-dominated reservoirs.',
351
+ 'hyperbolic': 'Hyperbolic decline provides the most flexibility, combining elements of both exponential and harmonic decline. Often the best fit for actual production data.',
352
+ 'harmonic': 'Harmonic decline assumes the decline rate decreases proportionally with production rate. Suitable for boundary-dominated flow.'
353
+ };
354
+
355
  // Event Listeners
356
  dropZone.addEventListener('click', () => fileInput.click());
357
  dropZone.addEventListener('dragover', handleDragOver);
 
359
  dropZone.addEventListener('drop', handleDrop);
360
  fileInput.addEventListener('change', handleFileSelect);
361
  uploadBtn.addEventListener('click', processFile);
 
362
  forecastMonths.addEventListener('change', updateChart);
363
  exportBtn.addEventListener('click', exportResults);
364
 
365
+ // Model selection
366
+ declineModelSelect.addEventListener('change', function() {
367
+ modelDescription.textContent = modelDescriptions[this.value];
368
+ if (currentAnalysis) {
369
+ updateAnalysisParameters();
370
+ updateChart();
371
+ }
372
+ });
373
+
374
+ // Date filters
375
+ startDateFilter.addEventListener('change', function() {
376
+ applyDateFilter();
377
+ updateDateFilterStatus();
378
+ });
379
+
380
+ endDateFilter.addEventListener('change', function() {
381
+ applyDateFilter();
382
+ updateDateFilterStatus();
383
+ });
384
+
385
  // Update the current performance display when manual inputs change
386
  currentMonthInput.addEventListener('change', updateManualCurrentPerformance);
387
  currentProductionInput.addEventListener('input', updateManualCurrentPerformance);
 
391
  const now = new Date();
392
  const dateString = now.toLocaleDateString('default', { month: 'short', year: 'numeric' });
393
  currentMonthEl.textContent = dateString;
394
+
395
+ // Set default model description
396
+ modelDescription.textContent = modelDescriptions[declineModelSelect.value];
397
+
398
+ // Set date filter defaults to maximum range
399
+ const minDate = new Date();
400
+ minDate.setFullYear(minDate.getFullYear() - 10);
401
+ startDateFilter.min = minDate.toISOString().split('T')[0];
402
+
403
+ const maxDate = new Date();
404
+ maxDate.setFullYear(maxDate.getFullYear() + 1);
405
+ endDateFilter.max = maxDate.toISOString().split('T')[0];
406
  });
407
 
408
  // Update current performance display with manual inputs
 
487
  // Sort by date
488
  productionData.sort((a, b) => a.timeMonths - b.timeMonths);
489
 
490
+ // Set date filter min/max values based on data
491
+ setFilterDateBounds();
492
+
493
+ // Apply any existing filters
494
+ applyDateFilter();
495
+
496
  // Make sure current performance info is updated
497
  if (currentMonthInput.value || currentProductionInput.value) {
498
  updateManualCurrentPerformance();
 
501
  // Display results
502
  displayResults();
503
 
504
+ // Analyze decline curve with selected model
505
+ currentAnalysis = performDeclineAnalysis(filteredData);
506
 
507
  // Update analysis info
508
  updateAnalysisInfo(currentAnalysis);
 
511
  updateCurrentPerformance();
512
 
513
  // Create chart
514
+ createChart(filteredData, currentAnalysis);
515
 
516
  // Show results section
517
  resultsSection.classList.remove('hidden');
518
 
519
+ // Show active filters if any
520
+ updateActiveFiltersDisplay();
521
+
522
  // Scroll to results
523
  resultsSection.scrollIntoView({ behavior: 'smooth' });
524
 
 
531
  reader.readAsArrayBuffer(file);
532
  }
533
 
534
+ function setFilterDateBounds() {
535
+ if (productionData.length === 0) return;
536
+
537
+ // Set min/max for date filters based on data
538
+ const minDate = new Date(productionData[0].date);
539
+ const maxDate = new Date(productionData[productionData.length - 1].date);
540
+
541
+ startDateFilter.min = minDate.toISOString().split('T')[0];
542
+ startDateFilter.max = maxDate.toISOString().split('T')[0];
543
+ startDateFilter.value = minDate.toISOString().split('T')[0];
544
+
545
+ endDateFilter.min = minDate.toISOString().split('T')[0];
546
+ endDateFilter.max = maxDate.toISOString().split('T')[0];
547
+ endDateFilter.value = maxDate.toISOString().split('T')[0];
548
+
549
+ updateDateFilterStatus();
550
+ }
551
+
552
+ function applyDateFilter() {
553
+ if (productionData.length === 0) return;
554
+
555
+ const start = startDateFilter.value ? new Date(startDateFilter.value) : null;
556
+ const end = endDateFilter.value ? new Date(endDateFilter.value) : null;
557
+
558
+ // Apply filters
559
+ filteredData = productionData.filter(point => {
560
+ const pointDate = new Date(point.date);
561
+ const afterStart = !start || pointDate >= start;
562
+ const beforeEnd = !end || pointDate <= end;
563
+ return afterStart && beforeEnd;
564
+ });
565
+
566
+ // Reset time months based on filtered data
567
+ if (filteredData.length > 0) {
568
+ const firstDate = new Date(filteredData[0].date);
569
+ filteredData.forEach(point => {
570
+ point.timeMonths = (new Date(point.date) - firstDate) / (1000 * 60 * 60 * 24 * 30.44);
571
+ point.timeMonths = parseFloat(point.timeMonths.toFixed(2));
572
+ });
573
+ }
574
+
575
+ // Update analysis if we have new filtered data
576
+ if (currentAnalysis && filteredData.length > 0) {
577
+ currentAnalysis = performDeclineAnalysis(filteredData);
578
+ updateAnalysisInfo(currentAnalysis);
579
+ createChart(filteredData, currentAnalysis);
580
+ }
581
+
582
+ // Update display results
583
+ displayResults();
584
+ updateActiveFiltersDisplay();
585
+ }
586
+
587
+ function updateDateFilterStatus() {
588
+ const start = startDateFilter.value;
589
+ const end = endDateFilter.value;
590
+
591
+ if (!start && !end) {
592
+ dateFilterStatus.textContent = "No date filters applied. Analyzing all available data.";
593
+ dateFilterStatus.className = "mt-2 text-sm text-gray-500";
594
+ } else if (start && end) {
595
+ dateFilterStatus.textContent = `Analyzing data from ${start} to ${end}.`;
596
+ dateFilterStatus.className = "mt-2 text-sm text-green-600 font-medium";
597
+ } else if (start) {
598
+ dateFilterStatus.textContent = `Analyzing data from ${start} onwards.`;
599
+ dateFilterStatus.className = "mt-2 text-sm text-blue-600 font-medium";
600
+ } else if (end) {
601
+ dateFilterStatus.textContent = `Analyzing data up to ${end}.`;
602
+ dateFilterStatus.className = "mt-2 text-sm text-blue-600 font-medium";
603
+ }
604
+ }
605
+
606
+ function updateActiveFiltersDisplay() {
607
+ activeFilters.innerHTML = '';
608
+
609
+ const start = startDateFilter.value;
610
+ const end = endDateFilter.value;
611
+ const model = declineModelSelect.options[declineModelSelect.selectedIndex].text;
612
+
613
+ // Add model filter badge
614
+ const modelBadge = document.createElement('div');
615
+ modelBadge.className = 'filter-badge';
616
+ modelBadge.innerHTML = `<i class="fas fa-cogs"></i> ${model}`;
617
+ activeFilters.appendChild(modelBadge);
618
+
619
+ // Add date range badges if filters are active
620
+ if (start) {
621
+ const startBadge = document.createElement('div');
622
+ startBadge.className = 'filter-badge';
623
+ startBadge.innerHTML = `<i class="fas fa-calendar"></i> From: ${new Date(start).toLocaleDateString()}`;
624
+ activeFilters.appendChild(startBadge);
625
+ }
626
+
627
+ if (end) {
628
+ const endBadge = document.createElement('div');
629
+ endBadge.className = 'filter-badge';
630
+ endBadge.innerHTML = `<i class="fas fa-calendar"></i> To: ${new Date(end).toLocaleDateString()}`;
631
+ activeFilters.appendChild(endBadge);
632
+ }
633
+ }
634
+
635
  function parseProductionData(jsonData) {
636
  const parsedData = [];
637
  let startDate = null;
 
683
  return parsedData;
684
  }
685
 
686
+ function updateAnalysisParameters() {
687
+ // Update b factor based on selected model
688
+ switch(declineModelSelect.value) {
689
+ case 'exponential':
690
+ currentAnalysis.b = 0;
691
+ break;
692
+ case 'harmonic':
693
+ currentAnalysis.b = 1;
694
+ break;
695
+ case 'hyperbolic':
696
+ // Keep calculated value or default to 0.5
697
+ if (currentAnalysis.b === 0 || currentAnalysis.b === 1) {
698
+ currentAnalysis.b = 0.5;
699
+ }
700
+ break;
701
+ }
702
+ }
703
+
704
  function performDeclineAnalysis(data) {
705
  // Simple decline curve analysis using hyperbolic decline
706
  // q(t) = qi / (1 + b * Di * t)^(1/b)
707
 
708
+ if (data.length === 0) return null;
709
+
710
+ const qi = data[0].rate; // Initial rate from first point in filtered data
711
+ const productionMonths = data.length > 1 ? (data[data.length - 1].timeMonths - data[0].timeMonths) : 0;
712
 
713
  // Simple estimation of decline parameters
 
714
  let Di = 0; // Nominal decline rate
715
  let b = 0.5; // Decline exponent (b=0 exponential, b=1 harmonic, 0<b<1 hyperbolic)
716
  let rSquared = 0;
 
720
  const qf = data[data.length - 1].rate;
721
 
722
  // For hyperbolic decline, we'll make a simple estimation
723
+ const avgDecline = (qi - qf) / qi / (productionMonths/12); // Annual decline rate
724
+ Di = Math.abs(avgDecline);
725
 
726
  // Adjust b factor based on the shape of the decline
727
+ const declineRatio = qf / qi;
728
+ if (declineRatio > 0.5) {
729
  b = 0.3; // Steeper decline
730
+ } else if (declineRatio > 0.3) {
731
  b = 0.5;
732
  } else {
733
  b = 0.8; // Gentle decline
 
737
  b = 0.5;
738
  }
739
 
740
+ // Apply model-specific constraints
741
+ switch(declineModelSelect.value) {
742
+ case 'exponential':
743
+ b = 0;
744
+ break;
745
+ case 'harmonic':
746
+ b = 1;
747
+ break;
748
+ }
749
+
750
  // Calculate R-squared (very simplified)
751
  let ssRes = 0;
752
  let ssTot = 0;
 
758
  ssTot += Math.pow(point.rate - yMean, 2);
759
  });
760
 
761
+ rSquared = ssTot > 0 ? 1 - (ssRes / ssTot) : 0;
762
 
763
  return { qi, Di, b, rSquared };
764
  }
 
825
  const tableBody = document.getElementById('tableBody');
826
  tableBody.innerHTML = '';
827
 
828
+ (filteredData.length > 0 ? filteredData : productionData).forEach(point => {
829
  const row = document.createElement('tr');
830
  row.innerHTML = `
831
  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700">${point.date.toLocaleDateString()}</td>
 
838
  }
839
 
840
  function updateAnalysisInfo(analysis) {
841
+ if (!analysis) return;
842
+
843
  document.getElementById('qiValue').textContent = `${analysis.qi.toFixed(2)} BOPD`;
844
  document.getElementById('diValue').textContent = `${(analysis.Di * 100).toFixed(1)}%/yr`;
845
  document.getElementById('bValue').textContent = analysis.b.toFixed(2);
 
847
  }
848
 
849
  function createChart(data, analysis) {
850
+ if (!analysis || data.length === 0) return;
851
+
852
  const ctx = document.getElementById('productionChart').getContext('2d');
853
 
854
  // Destroy existing chart if it exists
 
856
  productionChart.destroy();
857
  }
858
 
859
+ const actualTime = data.map(d => d.timeMonths);
860
  const actualRates = data.map(d => d.rate);
861
 
862
  // Generate forecast data
863
+ const maxTime = Math.max(...actualTime);
864
  const forecastTime = [];
865
  const forecastRates = [];
866
 
867
  const forecastPeriod = parseInt(forecastMonths.value);
868
+ const b = analysis.b; // Use the b value from analysis which is set by the model selection
 
 
869
 
870
  // Create data points for the chart
871
  for (let t = 0; t <= maxTime + forecastPeriod; t += 0.5) {
 
982
  }
983
 
984
  function updateChart() {
985
+ if ((filteredData.length > 0 ? filteredData : productionData).length > 0 && currentAnalysis) {
986
+ updateAnalysisParameters();
987
+ currentAnalysis = performDeclineAnalysis(filteredData.length > 0 ? filteredData : productionData);
988
+ updateAnalysisInfo(currentAnalysis);
989
+ createChart(filteredData.length > 0 ? filteredData : productionData, currentAnalysis);
990
  }
991
  }
992
 
 
999
  // Create a worksheet
1000
  const ws_data = [
1001
  ['Date', 'Rate (BOPD)', 'Cumulative (STB)', 'Time (months)'],
1002
+ ...(filteredData.length > 0 ? filteredData : productionData).map(d => [d.date.toLocaleDateString(), d.rate, d.cumulative, d.timeMonths])
1003
  ];
1004
 
1005
  const ws = XLSX.utils.aoa_to_sheet(ws_data);
 
1016
  ['Current Month', currentMonthEl.textContent],
1017
  ['Current Production', currentProductionEl.textContent],
1018
  ['Cumulative Production', cumulativeProductionEl.textContent],
1019
+ ['Model Used', declineModelSelect.options[declineModelSelect.selectedIndex].text],
1020
+ ['Date Range', startDateFilter.value ? (endDateFilter.value ? `${startDateFilter.value} to ${endDateFilter.value}` : `From ${startDateFilter.value}`) : (endDateFilter.value ? `To ${endDateFilter.value}` : 'All data')],
1021
  ['Forecast Period', `${forecastMonths.value} months`]
1022
  ], { origin: -1 });
1023
 
prompts.txt CHANGED
@@ -1,3 +1,4 @@
1
  tolong buatkan decline curve analisis untuk produksi sumur dengan melakukan upload excel dan di tampilkan dalam grafik
2
  tolong tambahkan informasi untuk bulan dan produksi saat ini
3
- tolong tambahkan input manual untuk bulan dan produksi saat ini sebagai informasi tambahan sebelum upload excel
 
 
1
  tolong buatkan decline curve analisis untuk produksi sumur dengan melakukan upload excel dan di tampilkan dalam grafik
2
  tolong tambahkan informasi untuk bulan dan produksi saat ini
3
+ tolong tambahkan input manual untuk bulan dan produksi saat ini sebagai informasi tambahan sebelum upload excel
4
+ tambahkan picklist untuk pemilihan model dca , dan tambahkan filter jika di lakukan perhitungan dari tanggal berapa ke tanggal berapa