gea-program 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gea/BENCH_TEST_PROTOCOL.md +97 -0
- gea/IMPORT_RECORD.md +61 -0
- gea/__init__.py +160 -0
- gea/__main__.py +661 -0
- gea/acceptance_tests.py +1315 -0
- gea/accuracy_statement.py +181 -0
- gea/alarm_engine.py +307 -0
- gea/bench.py +144 -0
- gea/blind_harness.py +108 -0
- gea/case_study.py +229 -0
- gea/catalog/acex_lomonosov_age_depth_model.provenance.json +23 -0
- gea/catalog/acex_lomonosov_age_depth_model.txt +28 -0
- gea/catalog/agassiz77_canada_temperature.csv +68 -0
- gea/catalog/agassiz77_canada_temperature.provenance.json +17 -0
- gea/catalog/barbados_110_consolidation.provenance.json +24 -0
- gea/catalog/barbados_110_consolidation.txt +91 -0
- gea/catalog/bengal_u1452_grain_size.provenance.json +20 -0
- gea/catalog/bengal_u1452_grain_size.txt +252 -0
- gea/catalog/blake_164_methane_isotopes.provenance.json +22 -0
- gea/catalog/blake_164_methane_isotopes.txt +68 -0
- gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +23 -0
- gea/catalog/chicxulub_m0077a_pwave_velocity.txt +735 -0
- gea/catalog/collingwood_1_28_ks_complete.las +128 -0
- gea/catalog/collingwood_1_28_ks_complete.provenance.json +17 -0
- gea/catalog/costa_rica_odp_friction_envelope.provenance.json +24 -0
- gea/catalog/costa_rica_odp_friction_envelope.txt +57 -0
- gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +21 -0
- gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +94 -0
- gea/catalog/dsdp_504b_physical_properties.provenance.json +24 -0
- gea/catalog/dsdp_504b_physical_properties.txt +82 -0
- gea/catalog/dsdp_504b_sound_velocity.provenance.json +21 -0
- gea/catalog/dsdp_504b_sound_velocity.txt +81 -0
- gea/catalog/elgygytgyn_5011_turbidites.provenance.json +20 -0
- gea/catalog/elgygytgyn_5011_turbidites.txt +193 -0
- gea/catalog/epica_domec_co2_800kyr.provenance.json +21 -0
- gea/catalog/epica_domec_co2_800kyr.txt +265 -0
- gea/catalog/fram_909_organic_petrography.provenance.json +22 -0
- gea/catalog/fram_909_organic_petrography.txt +40 -0
- gea/catalog/gbr_325_coral_uth_ages.provenance.json +23 -0
- gea/catalog/gbr_325_coral_uth_ages.txt +71 -0
- gea/catalog/gisp2_greenland_temperature.csv +599 -0
- gea/catalog/gisp2_greenland_temperature.provenance.json +17 -0
- gea/catalog/gom_308_t2p_insitu.provenance.json +21 -0
- gea/catalog/gom_308_t2p_insitu.txt +40 -0
- gea/catalog/guaymas_385_dom_d13c.provenance.json +21 -0
- gea/catalog/guaymas_385_dom_d13c.txt +103 -0
- gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +26 -0
- gea/catalog/hikurangi_u1520_friction_insitu.txt +74 -0
- gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +22 -0
- gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +60 -0
- gea/catalog/iodp_u1324_pore_pressure.provenance.json +22 -0
- gea/catalog/iodp_u1324_pore_pressure.txt +42 -0
- gea/catalog/jfast_c0019_slow_slip_events.provenance.json +28 -0
- gea/catalog/jfast_c0019_slow_slip_events.txt +35 -0
- gea/catalog/kennetcook_2_p129_excerpt.las +139 -0
- gea/catalog/kennetcook_2_p129_excerpt.provenance.json +17 -0
- gea/catalog/ktb_hb_bhgm_density.dat +227 -0
- gea/catalog/ktb_hb_bhgm_density.provenance.json +22 -0
- gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +20 -0
- gea/catalog/ktb_hb_complog_6020_excerpt.txt +72 -0
- gea/catalog/ktb_hb_hlog246_temperature.dat +1636 -0
- gea/catalog/ktb_hb_hlog246_temperature.provenance.json +22 -0
- gea/catalog/ktb_hb_rockmech_compress.dat +33 -0
- gea/catalog/ktb_hb_rockmech_compress.provenance.json +22 -0
- gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +2817 -0
- gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +20 -0
- gea/catalog/ktb_vb_rockmech_compress.dat +125 -0
- gea/catalog/ktb_vb_rockmech_compress.provenance.json +22 -0
- gea/catalog/ktb_vb_vlog251_temperature.dat +1127 -0
- gea/catalog/ktb_vb_vlog251_temperature.provenance.json +24 -0
- gea/catalog/l06_06_nl_survey.csv +201 -0
- gea/catalog/l06_06_nl_survey.provenance.json +17 -0
- gea/catalog/l07_01_nl_excerpt.las +90 -0
- gea/catalog/l07_01_nl_excerpt.provenance.json +17 -0
- gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +21 -0
- gea/catalog/mariana_1200_serpentinite_geochem.txt +63 -0
- gea/catalog/med_160_sapropels.provenance.json +22 -0
- gea/catalog/med_160_sapropels.txt +43 -0
- gea/catalog/nankai_megasplay_shear_strength.provenance.json +26 -0
- gea/catalog/nankai_megasplay_shear_strength.txt +42 -0
- gea/catalog/odp_1027b_thermal_conductivity.provenance.json +21 -0
- gea/catalog/odp_1027b_thermal_conductivity.txt +52 -0
- gea/catalog/odp_1027c_cork_temperature.provenance.json +20 -0
- gea/catalog/odp_1027c_cork_temperature.txt +26 -0
- gea/catalog/odp_1165b_thermal_conductivity.provenance.json +22 -0
- gea/catalog/odp_1165b_thermal_conductivity.txt +102 -0
- gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +27 -0
- gea/catalog/odp_1274a_mantle_peridotite_mad.txt +44 -0
- gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +27 -0
- gea/catalog/odp_504b_dike_elastic_moduli.txt +85 -0
- gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +19 -0
- gea/catalog/odp_504b_leg137_borehole_fluids.txt +68 -0
- gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +28 -0
- gea/catalog/odp_735b_gabbro_elastic_moduli.txt +127 -0
- gea/catalog/peru_201_sulfate_reduction.provenance.json +22 -0
- gea/catalog/peru_201_sulfate_reduction.txt +322 -0
- gea/catalog/scorpio_e1_sa_excerpt.las +113 -0
- gea/catalog/scorpio_e1_sa_excerpt.provenance.json +17 -0
- gea/catalog/sumatra_362_cohesion.provenance.json +21 -0
- gea/catalog/sumatra_362_cohesion.txt +38 -0
- gea/catalog/university_6_17_no1_tx_excerpt.las +119 -0
- gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +17 -0
- gea/catalog/ursa_308_xrd_mineralogy.provenance.json +21 -0
- gea/catalog/ursa_308_xrd_mineralogy.txt +46 -0
- gea/catalog/volve_15_9_19_sr_excerpt.las +183 -0
- gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +16 -0
- gea/catalog/volve_15_9_19a_core_excerpt.csv +88 -0
- gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +18 -0
- gea/catalog/volve_f12_f14_production_excerpt.csv +167 -0
- gea/catalog/volve_f12_f14_production_excerpt.provenance.json +21 -0
- gea/catalog/walvis_208_petm_carbonate.provenance.json +21 -0
- gea/catalog/walvis_208_petm_carbonate.txt +268 -0
- gea/catalog/woodlark_1109_rock_eval.provenance.json +23 -0
- gea/catalog/woodlark_1109_rock_eval.txt +30 -0
- gea/cli.py +125 -0
- gea/client_reports.py +943 -0
- gea/config_versioning.py +133 -0
- gea/correlation.py +155 -0
- gea/dashboard.py +390 -0
- gea/deviation.py +70 -0
- gea/downhole_engine.py +395 -0
- gea/drift_monitor.py +310 -0
- gea/earth_model.py +230 -0
- gea/example_register_map.json +14 -0
- gea/fat_sat.py +68 -0
- gea/follower.py +98 -0
- gea/forward_model.py +139 -0
- gea/gamma.py +176 -0
- gea/gauge_specs.py +112 -0
- gea/gravity_reference.py +116 -0
- gea/inverse_engine.py +215 -0
- gea/matplotlib_demo.py +85 -0
- gea/modbus.py +229 -0
- gea/model_card.py +248 -0
- gea/operator_app.py +442 -0
- gea/ports.py +340 -0
- gea/profile_catalog.py +773 -0
- gea/project.py +213 -0
- gea/qt6_downhole_app.py +144 -0
- gea/quartz_hpht_extension.py +152 -0
- gea/reconciler.py +206 -0
- gea/rock_inventory.py +404 -0
- gea/sample_record.py +430 -0
- gea/sample_well_profile.csv +15 -0
- gea/sbom.py +116 -0
- gea/segy.py +181 -0
- gea/service_life.py +173 -0
- gea/shell.py +107 -0
- gea/sla_report.py +199 -0
- gea/store_forward.py +234 -0
- gea/strata_join.py +186 -0
- gea/survey_cmd.py +264 -0
- gea/survey_view.py +138 -0
- gea/telemetry.py +306 -0
- gea/tool_library.py +260 -0
- gea/well_assembler.py +457 -0
- gea/well_test_validation.py +369 -0
- gea_program-0.1.0.dist-info/METADATA +138 -0
- gea_program-0.1.0.dist-info/RECORD +163 -0
- gea_program-0.1.0.dist-info/WHEEL +5 -0
- gea_program-0.1.0.dist-info/entry_points.txt +2 -0
- gea_program-0.1.0.dist-info/licenses/LICENSE +373 -0
- gea_program-0.1.0.dist-info/top_level.txt +1 -0
gea/model_card.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
"""model_card — one card per model, built from the live objects.
|
|
5
|
+
|
|
6
|
+
A model card (after Mitchell et al., "Model Cards for Model Reporting",
|
|
7
|
+
FAT* 2019) states what a model is for, what it takes in and where that came
|
|
8
|
+
from, how it is configured, how it was evaluated, what it must not be used
|
|
9
|
+
for, and what has changed since. The inspection clause of a production
|
|
10
|
+
scope of work asks for exactly that: source, training or calibration data,
|
|
11
|
+
feature definitions, settings, validation and back-test results, drift and
|
|
12
|
+
re-fit records. This module generates the cards from the program's own
|
|
13
|
+
objects at call time, so a card can never describe a model other than the
|
|
14
|
+
one that ships.
|
|
15
|
+
|
|
16
|
+
Cards produced:
|
|
17
|
+
|
|
18
|
+
well_baseline expected P/T at a station from the well model
|
|
19
|
+
gauge_aging_envelope expected instrument drift band at station P/T
|
|
20
|
+
strata_property_estimator k-nearest conditional over the co-located library
|
|
21
|
+
rock_density_inventory the cited density anchors used for classification
|
|
22
|
+
quality_rules the record-layer sample rules
|
|
23
|
+
well_test_detector the stable-period criteria
|
|
24
|
+
|
|
25
|
+
Every input carries its citation; every calibration dataset carries the
|
|
26
|
+
provenance the catalogue keeps for it (database, URL, licence, fetch date);
|
|
27
|
+
every component carries the sha256 of the source file that implements it
|
|
28
|
+
(named by role, with the file hash, so an inspector can match it to the
|
|
29
|
+
delivered source). Evaluation sections are recomputed, not quoted.
|
|
30
|
+
|
|
31
|
+
Headless-safe: numpy only.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import hashlib
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from datetime import datetime, timezone
|
|
41
|
+
from typing import Dict, List, Optional
|
|
42
|
+
|
|
43
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _sha(name: str) -> str:
|
|
47
|
+
p = os.path.join(_HERE, name)
|
|
48
|
+
if not os.path.exists(p):
|
|
49
|
+
return 'absent'
|
|
50
|
+
with open(p, 'rb') as f:
|
|
51
|
+
return hashlib.sha256(f.read()).hexdigest()[:16]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ModelCard:
|
|
56
|
+
model_id: str
|
|
57
|
+
title: str
|
|
58
|
+
version: str
|
|
59
|
+
intended_use: str
|
|
60
|
+
out_of_scope: str
|
|
61
|
+
inputs: List[dict] # {name, unit, source}
|
|
62
|
+
settings: List[dict] # {setting, value, basis}
|
|
63
|
+
calibration_data: List[dict] # {dataset, records, database, url, licence, fetch_date}
|
|
64
|
+
evaluation: List[dict] # {metric, value, method}
|
|
65
|
+
limitations: List[str]
|
|
66
|
+
refit_history: List[dict] = field(default_factory=list)
|
|
67
|
+
components: List[dict] = field(default_factory=list) # {role, sha256}
|
|
68
|
+
owner: str = 'assigned by the client at deployment'
|
|
69
|
+
generated_utc: str = ''
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict:
|
|
72
|
+
return {k: getattr(self, k) for k in self.__dataclass_fields__}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _prov(entry: str) -> dict:
|
|
76
|
+
from . import CATALOG
|
|
77
|
+
try:
|
|
78
|
+
p = CATALOG[entry].provenance
|
|
79
|
+
st = CATALOG[entry].stream()
|
|
80
|
+
n = int(len(st.index))
|
|
81
|
+
except Exception:
|
|
82
|
+
return {'dataset': entry, 'records': '-', 'database': 'catalogue entry not on this machine', 'url': '', 'licence': '', 'fetch_date': ''}
|
|
83
|
+
return {'dataset': entry, 'records': n, 'database': str(p.get('source_database', ''))[:160],
|
|
84
|
+
'url': str(p.get('source_url', '')), 'licence': str(p.get('license', ''))[:120],
|
|
85
|
+
'fetch_date': str(p.get('fetch_date', ''))}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _refits(monitor_log_dir: Optional[str], station_prefix: str = '') -> List[dict]:
|
|
89
|
+
if not monitor_log_dir:
|
|
90
|
+
return []
|
|
91
|
+
p = os.path.join(monitor_log_dir, 'change_log.jsonl')
|
|
92
|
+
if not os.path.exists(p):
|
|
93
|
+
return []
|
|
94
|
+
out = []
|
|
95
|
+
with open(p, encoding='utf-8') as f:
|
|
96
|
+
for line in f:
|
|
97
|
+
if not line.strip():
|
|
98
|
+
continue
|
|
99
|
+
e = json.loads(line)
|
|
100
|
+
if e.get('type') == 'REFIT_OFFSET' and e.get('status') in ('APPLIED', 'BLOCKED_ANNUAL_LIMIT', 'REJECTED'):
|
|
101
|
+
out.append({'timestamp_utc': e.get('decided_utc') or e.get('timestamp_utc'), 'station': e.get('station'),
|
|
102
|
+
'coefficient': e.get('coefficient'), 'before': e.get('before'), 'after': e.get('after'),
|
|
103
|
+
'status': e.get('status'), 'approver': e.get('approver', '')})
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_cards(monitor_log_dir: Optional[str] = None, program_version: str = '') -> List[ModelCard]:
|
|
108
|
+
from . import __version__ as _v, GAUGE_SPECS, SimulatorConfig
|
|
109
|
+
from .downhole_engine import SimulatorConfig as _SC
|
|
110
|
+
from .accuracy_statement import library_backtest, MIN_TRIALS, N_BOOT, SEED
|
|
111
|
+
from .sample_record import DEFAULT_TAG_CLASSES
|
|
112
|
+
from .well_test_validation import DEFAULT_CRITERIA
|
|
113
|
+
from .rock_inventory import rock_inventory
|
|
114
|
+
from . import strata_join as SJ
|
|
115
|
+
from .reconciler import ReconcilerConfig
|
|
116
|
+
ver = program_version or _v
|
|
117
|
+
now = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
118
|
+
cards: List[ModelCard] = []
|
|
119
|
+
|
|
120
|
+
# 1. well baseline --------------------------------------------------------------
|
|
121
|
+
sc = _SC()
|
|
122
|
+
rc = ReconcilerConfig()
|
|
123
|
+
cards.append(ModelCard(
|
|
124
|
+
model_id='well_baseline', title='Well pressure and temperature baseline', version=ver,
|
|
125
|
+
intended_use='Expected pressure and temperature at a gauge station (by measured depth) for drift evaluation: '
|
|
126
|
+
'residual = measured - baseline. With a measured profile the baseline interpolates the archived log; '
|
|
127
|
+
'without one it uses linear gradients from surface conditions.',
|
|
128
|
+
out_of_scope='Not a reservoir model; not a flow model; not for allocation; not a prediction of production behaviour. '
|
|
129
|
+
'A linear-gradient baseline is a template and must be replaced by the well\'s measured profile before '
|
|
130
|
+
'any classification is acted on.',
|
|
131
|
+
inputs=[{'name': 'station measured depth', 'unit': 'ft', 'source': 'completion records (caller-supplied; flagged when not in the archive)'},
|
|
132
|
+
{'name': 'deviation survey', 'unit': 'deg / ft', 'source': 'survey file when supplied; vertical otherwise'},
|
|
133
|
+
{'name': 'well P/T profile', 'unit': 'psi, degF vs TVD', 'source': 'measured catalogue assembly when supplied; else linear gradients'}],
|
|
134
|
+
settings=[{'setting': 'surface pressure (template)', 'value': f'{sc.surface_pressure_psi:g} psi', 'basis': 'template default; replaced by the profile'},
|
|
135
|
+
{'setting': 'pressure gradient (template)', 'value': f'{sc.pressure_gradient_psi_per_ft:g} psi/ft', 'basis': 'template default; replaced by the profile'},
|
|
136
|
+
{'setting': 'surface temperature (template)', 'value': f'{sc.surface_temp_F:g} degF', 'basis': 'template default; replaced by the profile'},
|
|
137
|
+
{'setting': 'temperature gradient (template)', 'value': f'{sc.temp_gradient_F_per_ft:g} degF/ft', 'basis': 'template default; replaced by the profile'},
|
|
138
|
+
{'setting': 'bias significance', 'value': f'{rc.bias_n_sigma:g} x noise/sqrt(n) + 1 psi', 'basis': 'engineering setting, printed on every drift report'},
|
|
139
|
+
{'setting': 'bias too large for calibration', 'value': f'{rc.model_mismatch_psi:g} psi', 'basis': 'engineering setting'},
|
|
140
|
+
{'setting': 'minimum window for a trend', 'value': f'{rc.min_trend_span_years*365.25:.0f} days', 'basis': 'engineering setting'}],
|
|
141
|
+
calibration_data=[{'dataset': 'measured well assemblies in the catalogue (when used)', 'records': '-',
|
|
142
|
+
'database': 'each assembly carries its own provenance file', 'url': '', 'licence': '', 'fetch_date': ''}],
|
|
143
|
+
evaluation=[{'metric': 'Volve 15/9-F-12 daily downhole pressure vs linear-gradient baseline', 'value': 'UNEXPLAINED_TREND (drawdown, -1,096 psi/yr); template baseline cannot follow production',
|
|
144
|
+
'method': 'drift report on the catalogue excerpt; demonstrates the out-of-scope note above'}],
|
|
145
|
+
limitations=['Linear gradients are a template, not a measurement.', 'Deviation is applied only when a survey is supplied.',
|
|
146
|
+
'The baseline has no production dynamics; drawdown reads as an unexplained trend by design and is routed to investigation.'],
|
|
147
|
+
refit_history=_refits(monitor_log_dir),
|
|
148
|
+
components=[{'role': 'baseline and drift classification', 'sha256': _sha('reconciler.py')},
|
|
149
|
+
{'role': 'engine and well configuration', 'sha256': _sha('downhole_engine.py')},
|
|
150
|
+
{'role': 'drift monitor (log, staleness, re-fit)', 'sha256': _sha('drift_monitor.py')}],
|
|
151
|
+
generated_utc=now))
|
|
152
|
+
|
|
153
|
+
# 2. gauge aging envelope -------------------------------------------------------
|
|
154
|
+
specs = list(GAUGE_SPECS.values())
|
|
155
|
+
cards.append(ModelCard(
|
|
156
|
+
model_id='gauge_aging_envelope', title='Gauge aging envelope at station conditions', version=ver,
|
|
157
|
+
intended_use='The expected instrument drift band (psi/yr) at a station\'s pressure and temperature, used to judge whether a '
|
|
158
|
+
'measured residual trend is instrument aging (inside the band) or something else (outside it).',
|
|
159
|
+
out_of_scope='Not a measurement of any specific gauge\'s drift; not a warranty figure; not a substitute for recalibration records.',
|
|
160
|
+
inputs=[{'name': 'gauge datasheet', 'unit': 'full scale psi; drift %FS/yr; rating C; accuracy %FS', 'source': s.source[:140]} for s in specs] +
|
|
161
|
+
[{'name': 'station pressure and temperature', 'unit': 'psi, degC', 'source': 'well baseline model'}],
|
|
162
|
+
settings=[{'setting': 'thermal knee', 'value': f'{specs[0].thermal_knee_C:g} C', 'basis': 'engineering fit (template); vendor performance page confirms drift focus >= 150 C'},
|
|
163
|
+
{'setting': 'pressure knee', 'value': f'{specs[0].pressure_knee_psi:g} psi', 'basis': 'engineering fit (template)'},
|
|
164
|
+
{'setting': 'thermal / pressure exponents', 'value': f'{specs[0].thermal_exponent:g} / {specs[0].pressure_exponent:g}', 'basis': 'engineering fit (template)'},
|
|
165
|
+
{'setting': 'lower bound of the band', 'value': 'program aging model, ratio 0.969 of the conventional model', 'basis': 'engineering model without field validation; the band, not the bound, is used'},
|
|
166
|
+
{'setting': 'upper bound of the band', 'value': 'conventional aging model at the datasheet baseline', 'basis': 'datasheet baseline drift scaled by the temperature and pressure factors'}],
|
|
167
|
+
calibration_data=[{'dataset': s.name, 'records': '-', 'database': 'public datasheet', 'url': '', 'licence': 'vendor-published specification', 'fetch_date': (s.source.split('fetched ')[1][:10] if 'fetched ' in s.source else '')} for s in specs],
|
|
168
|
+
evaluation=[{'metric': 'field validation of the band', 'value': 'NONE ON RECORD', 'method': 'no gauge-drift field dataset has been evaluated against the band; it is a model band and is labelled so on every report'}],
|
|
169
|
+
limitations=['The band is a model, not a measurement.', 'Datasheet drift bounds are reference-condition limits; stressed-service drift can exceed them.',
|
|
170
|
+
'The lower bound comes from an engineering model with no field validation; classification uses the whole band with a 2x margin, never the lower bound alone.'],
|
|
171
|
+
components=[{'role': 'aging models', 'sha256': _sha('quartz_hpht_extension.py')}, {'role': 'gauge datasheets', 'sha256': _sha('gauge_specs.py')}],
|
|
172
|
+
generated_utc=now))
|
|
173
|
+
|
|
174
|
+
# 3. strata property estimator ---------------------------------------------------
|
|
175
|
+
bt = library_backtest()
|
|
176
|
+
entries = sorted({e for _, (_, props) in SJ.WELL_GROUPS.items() for e, _c in props.values()} | {'ktb_hb_complog_6020_excerpt'})
|
|
177
|
+
cards.append(ModelCard(
|
|
178
|
+
model_id='strata_property_estimator', title='Strata property estimator (k-nearest empirical conditional)', version=ver,
|
|
179
|
+
intended_use='Estimate one rock property from another measured at the same depth interval, with a stated spread, from the '
|
|
180
|
+
'library of co-located public measurements; used for strata interpretation and blind-scored on every run.',
|
|
181
|
+
out_of_scope='Not for wells outside the library\'s geological families without a site prior; not a substitute for a measured log; '
|
|
182
|
+
'quantities in the NOT ACCEPTABLE band of the accuracy statement are excluded from any claim.',
|
|
183
|
+
inputs=[{'name': 'given property (per pair)', 'unit': 'library units', 'source': 'co-located measurement from the catalogue entry named in calibration data'}],
|
|
184
|
+
settings=[{'setting': 'neighbours k', 'value': '7', 'basis': 'engineering setting'},
|
|
185
|
+
{'setting': 'minimum trials to score', 'value': str(MIN_TRIALS), 'basis': 'accuracy statement rule'},
|
|
186
|
+
{'setting': 'bootstrap resamples / seed', 'value': f'{N_BOOT} / {SEED}', 'basis': 'reproducible CI'},
|
|
187
|
+
{'setting': 'depth bins per well (m)', 'value': ', '.join(f'{w}: {b:g}' for w, (b, _) in SJ.WELL_GROUPS.items()), 'basis': 'co-location tolerance per well'}],
|
|
188
|
+
calibration_data=[_prov(e) for e in entries],
|
|
189
|
+
evaluation=[{'metric': f"{r['well']} {r['given']} -> {r['target']}", 'value': f"MAPE {r['mape_pct']:.2f} % (90 % CI {r['mape_ci_lo_pct']:.2f}-{r['mape_ci_hi_pct']:.2f}); coverage {r['coverage_at_ci']}; {r['band']}",
|
|
190
|
+
'method': f"leave-one-out, n={r['n']}"} for r in bt['statements']] +
|
|
191
|
+
[{'metric': f"{r['well']} {r['given']} -> {r['target']}", 'value': f"PENDING (n={r['n']} < {MIN_TRIALS})", 'method': 'leave-one-out'} for r in bt['pending']],
|
|
192
|
+
limitations=['Estimates are empirical conditionals over a small public library; spreads are somewhat too narrow (median coverage below the CI level).',
|
|
193
|
+
'Five of fourteen quantities are NOT ACCEPTABLE at the 95 % target and are so labelled.',
|
|
194
|
+
'Extrapolation outside the given property\'s support is flagged and returns no estimate.'],
|
|
195
|
+
components=[{'role': 'estimator and priors', 'sha256': _sha('inverse_engine.py')}, {'role': 'co-located library', 'sha256': _sha('strata_join.py')},
|
|
196
|
+
{'role': 'accuracy statement', 'sha256': _sha('accuracy_statement.py')}],
|
|
197
|
+
generated_utc=now))
|
|
198
|
+
|
|
199
|
+
# 4. rock density inventory --------------------------------------------------------
|
|
200
|
+
inv = rock_inventory()
|
|
201
|
+
cards.append(ModelCard(
|
|
202
|
+
model_id='rock_density_inventory', title='Rock density inventory (cited anchors)', version=ver,
|
|
203
|
+
intended_use='Classify a measured bulk density into ranked candidate rock types by overlap with published density ranges.',
|
|
204
|
+
out_of_scope='Not a lithology log; overlapping ranges return several candidates by design; densities outside the inventory return no candidate.',
|
|
205
|
+
inputs=[{'name': f"{k} ({v['tier']})", 'unit': f"g/cc, anchor {v['anchor']}, range {v['lo']}-{v['hi']}", 'source': str(v['citation']).split('; primitive')[0][:120]} for k, v in inv.items()],
|
|
206
|
+
settings=[{'setting': 'anchors', 'value': str(len(inv)), 'basis': 'standard geophysics density tables (Telford et al. 1990; Schoen 2015)'}],
|
|
207
|
+
calibration_data=[{'dataset': 'published density tables', 'records': len(inv), 'database': 'Telford, Geldart & Sheriff (1990); Schoen (2015)', 'url': '', 'licence': 'published reference values', 'fetch_date': ''}],
|
|
208
|
+
evaluation=[{'metric': 'anchor reproduction', 'value': f"worst anchor residual {max(abs(float(v['residual_pct'])) for v in inv.values()):.3f} %", 'method': 'each anchor re-derived at gate time'}],
|
|
209
|
+
limitations=['Ranges overlap; the classifier returns ranked candidates, never one confident name.'],
|
|
210
|
+
components=[{'role': 'inventory and classifier', 'sha256': _sha('rock_inventory.py')}],
|
|
211
|
+
generated_utc=now))
|
|
212
|
+
|
|
213
|
+
# 5. quality rules --------------------------------------------------------------------
|
|
214
|
+
cards.append(ModelCard(
|
|
215
|
+
model_id='quality_rules', title='Sample quality rules (record layer)', version=ver,
|
|
216
|
+
intended_use='Flag every sample GOOD / RANGE / ROC / FLATLINE / SPIKE / STALE / GAP with the rule and limit that fired, per tag, before any evaluation.',
|
|
217
|
+
out_of_scope='Not an alarm; not a correction - flagged samples are excluded from evaluations and counted, never repaired.',
|
|
218
|
+
inputs=[{'name': 'tag definition', 'unit': 'engineering range, cadence, limits', 'source': 'tag catalogue; ranges from the gauge datasheet when supplied'}],
|
|
219
|
+
settings=[{'setting': f'{cls}: range', 'value': f"{d['eng_range'][0]} to {d['eng_range'][1]} {d['unit']}", 'basis': 'class default; datasheet overrides'} for cls, d in DEFAULT_TAG_CLASSES.items() if cls != 'generic'] +
|
|
220
|
+
[{'setting': 'flatline run', 'value': '3 samples', 'basis': 'operations setting'}, {'setting': 'staleness', 'value': '3 x cadence', 'basis': 'operations setting'},
|
|
221
|
+
{'setting': 'spike', 'value': '6 x MAD of rolling median, window 21', 'basis': 'operations setting'}, {'setting': 'rate of change', 'value': 'off unless set per tag', 'basis': 'operations setting; not a datasheet quantity'}],
|
|
222
|
+
calibration_data=[_prov('volve_f12_f14_production_excerpt')],
|
|
223
|
+
evaluation=[{'metric': 'Volve 15/9-F-12 downhole pressure, 157 daily samples', 'value': '9 FLATLINE (the archive\'s documented stuck-sensor run), 5 SPIKE, 0 RANGE', 'method': 'rules applied to the catalogue excerpt'}],
|
|
224
|
+
limitations=['Rules are per-sample; a slow drift is not a quality flag (it is the drift evaluation\'s job).', 'The spike rule at daily cadence can flag real operational excursions; each flag prints its deviation so an engineer can judge.'],
|
|
225
|
+
components=[{'role': 'record and rules', 'sha256': _sha('sample_record.py')}],
|
|
226
|
+
generated_utc=now))
|
|
227
|
+
|
|
228
|
+
# 6. well-test detector -----------------------------------------------------------------
|
|
229
|
+
c = DEFAULT_CRITERIA
|
|
230
|
+
cards.append(ModelCard(
|
|
231
|
+
model_id='well_test_detector', title='Well-test stable-period detector', version=ver,
|
|
232
|
+
intended_use='Detect periods where the well is on stream at a fixed operating point with stable rates and pressures; accept them as well tests with virtual rates, reject the rest with reason codes.',
|
|
233
|
+
out_of_scope='Not an allocation; accepted tests are released only after the approval trail is complete.',
|
|
234
|
+
inputs=[{'name': 'on-stream hours, rates, pressures, operating point', 'unit': 'as tagged', 'source': 'production historian channels mapped by role'}],
|
|
235
|
+
settings=[{'setting': k, 'value': str(v), 'basis': 'criteria file (client-agreed logic), hashed on every report'} for k, v in c.items() if k not in ('name', 'basis', 'approval_levels')],
|
|
236
|
+
calibration_data=[_prov('volve_f12_f14_production_excerpt')],
|
|
237
|
+
evaluation=[{'metric': 'Volve 15/9-F-12, 2008-02-12 to 2008-07-21', 'value': '7 tests accepted, 32 candidates rejected with reason codes, 131/158 eligible samples', 'method': 'default criteria file'}],
|
|
238
|
+
limitations=['Criteria defaults are for daily-cadence data; a client at minute cadence sets its own file.', 'Negligible streams (below the fraction of the largest rate) are not stability criteria and are disclosed as such.'],
|
|
239
|
+
components=[{'role': 'detector and approval trail', 'sha256': _sha('well_test_validation.py')}],
|
|
240
|
+
generated_utc=now))
|
|
241
|
+
return cards
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def card_index(cards: List[ModelCard]) -> dict:
|
|
245
|
+
return {'generated_utc': cards[0].generated_utc if cards else '', 'cards': [
|
|
246
|
+
{'model_id': c.model_id, 'title': c.title, 'version': c.version, 'n_inputs': len(c.inputs),
|
|
247
|
+
'n_calibration_datasets': len(c.calibration_data), 'n_evaluations': len(c.evaluation), 'n_refits': len(c.refit_history)}
|
|
248
|
+
for c in cards]}
|