declinecurve / app.py
Rian Rachmanto
changing from google to duckdb
6afbd8c
import streamlit as st
import pandas as pd
import numpy as np
import seaborn as sns
from google.cloud import bigquery
from google.oauth2 import service_account
import os
import json
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
json_key_path = "/Users/rianrachmanto/pypro/bigquery/intricate-idiom-379506-1454314d9d25.json"
with open(json_key_path) as f:
credentials_info = json.load(f)
credentials = service_account.Credentials.from_service_account_info(credentials_info)
client = bigquery.Client(credentials=credentials)
QUERY = """
SELECT
DATEPRD,
NPD_WELL_BORE_NAME,
BORE_OIL_VOL,
BORE_GAS_VOL,
BORE_WAT_VOL
FROM `intricate-idiom-379506.volveprod.volveprod`
WHERE
BORE_OIL_VOL IS NOT NULL
AND BORE_GAS_VOL IS NOT NULL
AND BORE_WAT_VOL IS NOT NULL
ORDER BY
NPD_WELL_BORE_NAME ASC, DATEPRD DESC;
"""
# Run the query using the client
query_job = client.query(QUERY)
st.set_option('deprecation.showPyplotGlobalUse', False)
st.title("DECLINE CURVE ANALYSIS (DCA)")
# Create data handler function
def data_handler(query_job):
results = query_job.result()
df = results.to_dataframe()
st.write(df.head())
# Ensure df_fil is a copy to avoid SettingWithCopyWarning
df_fil = df[(df['BORE_OIL_VOL'] > 0) & (df['BORE_GAS_VOL'] > 0) & (df['BORE_WAT_VOL'] > 0)].copy()
df_fil.loc[:, 'DATEPRD'] = pd.to_datetime(df_fil['DATEPRD'])
sns.set_theme(style="darkgrid")
st.write(sns.relplot(
data=df_fil,
x="DATEPRD", y="BORE_OIL_VOL", col="NPD_WELL_BORE_NAME", hue="NPD_WELL_BORE_NAME",
kind="line", palette="crest", linewidth=4, zorder=5,
col_wrap=2, height=3, aspect=1.5, legend=False
).fig)
# Create a dataframe for monthly average
df_monthly = df_fil.groupby(['NPD_WELL_BORE_NAME', pd.Grouper(key='DATEPRD', freq='M')]).mean()
df_monthly = df_monthly.reset_index()
df_monthly_24 = df_monthly[df_monthly['DATEPRD'] >= '2015-01-01']
st.title("Monthly Average")
sns.set_theme(style="darkgrid")
st.write(sns.relplot(
data=df_monthly_24,
x="DATEPRD", y="BORE_OIL_VOL", col="NPD_WELL_BORE_NAME", hue="NPD_WELL_BORE_NAME",
kind="line", palette="crest", linewidth=4, zorder=5,
col_wrap=2, height=3, aspect=1.5, legend=False
).fig)
return df_monthly_24
df_monthly_24 = data_handler(query_job)
# Add a "Forecast" button
if st.button("Forecast"):
# Create an empty dictionary to store dataframes
well_dataframes = {}
# Iterate through unique well names and filter the data
for well_name in df_monthly_24['NPD_WELL_BORE_NAME'].unique():
well_df = df_monthly_24[df_monthly_24['NPD_WELL_BORE_NAME'] == well_name]
well_dataframes[well_name] = well_df
# Initialize forecast variables
t_forecast_dict = {}
q_forecast_dict = {}
Qp_forecast_dict = {}
# Iterate through unique well names and perform forecasting for each well
for well_name, well_df in well_dataframes.items():
st.write(f"Forecasting for Well: {well_name}")
# Create a 't' array where t is DATEPRD
t = well_df['DATEPRD'].values
# Create a 'q' array where q is BORE_OIL_VOL
q = well_df['BORE_OIL_VOL'].values
# Subtract one datetime from another for 't'
timedelta_t = [j - i for i, j in zip(t[:-1], t[1:])]
timedelta_t = np.array(timedelta_t)
timedelta_t = timedelta_t / np.timedelta64(1, 'D') # Convert timedelta to days
# Take cumulative sum over timedeltas for 't'
t = np.cumsum(timedelta_t)
t = np.append(0, t)
t = t.astype(float)
# Normalize 't' and 'q' data
t_normalized = t / max(t)
q_normalized = q / max(q)
# Function for exponential decline
def exponential(t, qi, di):
return qi * np.exp(-di * t)
# Fit the exponential decline model to the normalized data
popt, pcov = curve_fit(exponential, t_normalized, q_normalized)
qi, di = popt
# Check if di is <= 0.0, if so, skip this well
if di <= 0.0:
print(f'Skipping well {well_name} due to di <= 0.0')
continue
# De-normalize qi and di
qi = qi * max(q)
di = di / max(t)
# Initialize forecast variables
t_forecast = []
q_forecast = []
Qp_forecast = []
# Initial values
t_current = 0
q_current = exponential(t_current, qi, di)
# Function to calculate cumulative production
def cumpro(q_forecast, qi, di):
return (qi - q_forecast) / di
Qp_current = cumpro(q_current, qi, di)
# Start forecasting until q_forecast reaches 25
while q_current >= 25:
t_forecast.append(t_current)
q_forecast.append(q_current)
Qp_forecast.append(Qp_current)
# Increment time step
t_current += 1
q_current = exponential(t_current, qi, di)
Qp_current = cumpro(q_current, qi, di)
# Convert lists to numpy arrays for convenience
t_forecast = np.array(t_forecast)
q_forecast = np.array(q_forecast)
Qp_forecast = np.array(Qp_forecast)
# Display results in Streamlit
st.write('Final Rate:', np.round(q_forecast[-1], 3), 'BOPD')
st.write('Final Cumulative Production:', np.round(Qp_forecast[-1], 2), 'BBL OIL')
# Plot the results using Matplotlib and display them in Streamlit
plt.figure(figsize=(15, 5))
plt.subplot(1, 2, 1)
plt.plot(t, q, '.', color='red', label='Production Data')
plt.plot(t_forecast, q_forecast, label='Forecast')
plt.title('Oil Production Rate Result of DCA', size=13, pad=15)
plt.xlabel('Days')
plt.ylabel('Rate (BBL OIL/d)')
plt.xlim(left=0)
plt.ylim(bottom=0)
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(t_forecast, Qp_forecast)
plt.title('OIL Cumulative Production Result of DCA', size=13, pad=15)
plt.xlabel('Days')
plt.ylabel('Production (BBL OIL)')
plt.xlim(left=0)
plt.ylim(bottom=0)
# Display the Matplotlib figure in Streamlit
st.pyplot()