Skip to content

Commit 64c9eea

Browse files
exclude operational dips/ramp-ups acceptably well
1 parent fe90ac4 commit 64c9eea

1 file changed

Lines changed: 43 additions & 19 deletions

File tree

src/geophires_docs/generate_fervo_project_red_2026_docs.py

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
_MODEL_ROLLING_WINDOW = 15
2828

2929
_CONTINUOUS_OPERATION_MIN_TEMP_C = 175.0
30-
_MAX_TRANSIENT_TEMP_CHANGE_C = 10.0
30+
_STATISTICAL_BUFFER_SIZE = 5
31+
_STATISTICAL_MIN_BUFFER = 3
32+
_STATISTICAL_Z_SCORE = 2.0
33+
_STATISTICAL_MIN_STD = 1.5
3134

3235

3336
def extract_plot_data(
@@ -64,17 +67,12 @@ def pixel_to_data(px: float, py: float) -> tuple[float, float]:
6467
def _calculate_variance_analysis(
6568
df_actual: pd.DataFrame, df_model: pd.DataFrame, steady_state_start_years: float = _STEADY_STATE_START_YEARS
6669
) -> pd.DataFrame:
67-
df_steady_state = df_actual[df_actual['Time_Years'] > steady_state_start_years].copy()
70+
post_ramp_mask = df_actual['Time_Years'] > steady_state_start_years
71+
n_before = len(df_actual[post_ramp_mask])
6872

69-
# TODO: Replace hardcoded minimum temperature threshold with a robust statistical baseline envelope
70-
n_before = len(df_steady_state)
73+
is_steady = _get_steady_state_mask(df_actual, steady_state_start_years)
74+
df_steady_state = df_actual[is_steady].copy()
7175

72-
temp_diffs = df_steady_state['Temperature_C'].diff().abs().fillna(0.0)
73-
is_steady = (df_steady_state['Temperature_C'] >= _CONTINUOUS_OPERATION_MIN_TEMP_C) & (
74-
temp_diffs <= _MAX_TRANSIENT_TEMP_CHANGE_C
75-
)
76-
77-
df_steady_state = df_steady_state[is_steady].copy()
7876
_log.info(f'Continuous operation filter: dropped {n_before - len(df_steady_state)} transient/dip points.')
7977

8078
model_interpolator = interp1d(
@@ -195,6 +193,40 @@ def _dedupe_centers(centers_px: list[tuple[int, int]], min_dist_px: float) -> li
195193
return accepted
196194

197195

196+
def _get_steady_state_mask(df_prod: pd.DataFrame, steady_state_start_years: float) -> pd.Series:
197+
"""
198+
Identifies steady-state production points iteratively. Maintains a rolling
199+
buffer of verified plateau points to mathematically isolate and reject
200+
the steep walls and floors of transient shut-ins.
201+
"""
202+
is_steady = pd.Series(False, index=df_prod.index)
203+
post_ramp_idx = df_prod.index[df_prod['Time_Years'] > steady_state_start_years]
204+
205+
valid_buffer: list[float] = []
206+
207+
for idx in post_ramp_idx:
208+
temp = df_prod.at[idx, 'Temperature_C']
209+
210+
if temp < _CONTINUOUS_OPERATION_MIN_TEMP_C:
211+
continue
212+
213+
if len(valid_buffer) >= _STATISTICAL_MIN_BUFFER:
214+
history = valid_buffer[-_STATISTICAL_BUFFER_SIZE:]
215+
mean_temp = float(np.mean(history))
216+
std_temp = float(np.std(history, ddof=1))
217+
218+
# Prevent excessively tight windows during flat extractions
219+
std_temp = max(std_temp, _STATISTICAL_MIN_STD)
220+
221+
if abs(temp - mean_temp) > _STATISTICAL_Z_SCORE * std_temp:
222+
continue
223+
224+
is_steady.at[idx] = True
225+
valid_buffer.append(temp)
226+
227+
return is_steady
228+
229+
198230
def _regenerate_graph_from_csv(
199231
production_csv_path: Path,
200232
model_csv_path: Path,
@@ -205,16 +237,8 @@ def _regenerate_graph_from_csv(
205237
df_prod = pd.read_csv(production_csv_path)
206238
df_model = pd.read_csv(model_csv_path)
207239

208-
# Apply the exclusion logic directly via boolean mask to avoid float-merge bugs across CSVs
209240
is_ramp_up = df_prod['Time_Years'] <= steady_state_start_years
210-
211-
post_ramp_up_mask = ~is_ramp_up
212-
temp_diffs = df_prod.loc[post_ramp_up_mask, 'Temperature_C'].diff().abs().fillna(0.0)
213-
214-
is_steady = pd.Series(False, index=df_prod.index)
215-
is_steady.loc[post_ramp_up_mask] = (
216-
df_prod.loc[post_ramp_up_mask, 'Temperature_C'] >= _CONTINUOUS_OPERATION_MIN_TEMP_C
217-
) & (temp_diffs <= _MAX_TRANSIENT_TEMP_CHANGE_C)
241+
is_steady = _get_steady_state_mask(df_prod, steady_state_start_years)
218242

219243
df_included = df_prod[is_ramp_up | is_steady]
220244
df_excluded = df_prod[~(is_ramp_up | is_steady)]

0 commit comments

Comments
 (0)