gea-program 0.8.0__tar.gz → 0.9.0__tar.gz
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_program-0.8.0 → gea_program-0.9.0}/PKG-INFO +25 -2
- {gea_program-0.8.0 → gea_program-0.9.0}/README.md +24 -1
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/__init__.py +1 -1
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/__main__.py +68 -1
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/acceptance_tests.py +176 -21
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/client_reports.py +78 -0
- gea_program-0.9.0/gea/help/sra.md +15 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/helplib.py +1 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/jobs.py +15 -6
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/service.py +13 -1
- gea_program-0.9.0/gea/sra.py +613 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/web/app.html +3 -3
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/workspace.py +115 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea_program.egg-info/PKG-INFO +25 -2
- {gea_program-0.8.0 → gea_program-0.9.0}/gea_program.egg-info/SOURCES.txt +2 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/pyproject.toml +1 -1
- {gea_program-0.8.0 → gea_program-0.9.0}/LICENSE +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/bench.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/blind_harness.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/certificates.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/cli.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/config_versioning.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/correlation.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/dashboard.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/deviation.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/doctor.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/earth_model.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/example_register_map.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/fat_sat.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/files.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/follower.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/forward_model.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/gamma.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/geodesy.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/alarms.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/data-in.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/doctor.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/drift.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/files.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/instruments.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/notifications.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/patches.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/quality.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/seismic.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/site.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/sites.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/start.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/transients.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/housekeeping.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/live_ports.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/loadtest.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/modbus.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/model_card.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/notify.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/opcua_port.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/operator_app.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/patches.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/permits.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/ports.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/project.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reconciler.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/sample_record.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/sbom.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/segy.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_array.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_film.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_response.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_track.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/service_life.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/shell.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/shut_in.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/sla_report.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/store_forward.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/strata_join.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/supervisor.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/survey_view.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/telemetry.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/tool_library.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/transient.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/well_assembler.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/wits0.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea/witsml.py +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/setup.cfg +0 -0
- {gea_program-0.8.0 → gea_program-0.9.0}/tests/test_acceptance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gea-program
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
|
|
5
5
|
Author: Daniel T. Murphy
|
|
6
6
|
License: MPL-2.0
|
|
@@ -125,7 +125,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
|
|
|
125
125
|
gea client-report --report accuracy --out client_report
|
|
126
126
|
gea model-cards --out model_cards
|
|
127
127
|
gea sbom --out sbom
|
|
128
|
-
gea accept # the product gate (
|
|
128
|
+
gea accept # the product gate (308 checks)
|
|
129
129
|
gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
|
|
130
130
|
gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
|
|
131
131
|
gea gui # the desktop window (pip install "gea-program[desktop]")
|
|
@@ -528,6 +528,29 @@ so one geophone can still say when each rig was working - and the Seismic
|
|
|
528
528
|
Station Report and the station page carry the section, with the tracks drawn
|
|
529
529
|
against time.
|
|
530
530
|
|
|
531
|
+
### The Seismicity Response Area packet
|
|
532
|
+
|
|
533
|
+
The Railroad Commission of Texas declares a Seismicity Response Area around a
|
|
534
|
+
cluster of felt earthquakes and expects the operators inside it to run a
|
|
535
|
+
response plan. The plans on file have one shape - a circle of about 9 km, a
|
|
536
|
+
plan written against M 3.5, an 18-month goal, a response group meeting
|
|
537
|
+
within 48 hours of a threshold event, quarterly checkpoints with Commission
|
|
538
|
+
staff, disposal wells in two tiers by the base of the Wolfcamp - and the
|
|
539
|
+
Commission's December 2023 Notice to Operators names the data: four daily
|
|
540
|
+
parameters (maximum and average surface injection pressure, injection
|
|
541
|
+
volume, maximum injection rate) and three bottomhole-pressure methods, one
|
|
542
|
+
of which is a permanent downhole probe. `gea/sra.py` builds the packet to
|
|
543
|
+
that shape from what the site holds. The operator declares what the program
|
|
544
|
+
cannot measure (`set-well`: surface position and datum, API and UIC, depth
|
|
545
|
+
tier, which channels are the pressure, the rate and the downhole gauge, the
|
|
546
|
+
BHP method); `sra-define` puts the area on the site; `sra-report --catalog
|
|
547
|
+
texnet.csv` writes the packet - membership by geodesic distance, the daily
|
|
548
|
+
record per well rolled up by month, the catalogue's events against the plan
|
|
549
|
+
with the trigger and the goal clock, the schedule, and every gap by name -
|
|
550
|
+
and a daily export under the Notice's own parameter names. It will not
|
|
551
|
+
invent a volume for a well with no rate channel, a tier from depth alone, or
|
|
552
|
+
an aftershock: those are declarations, and the packet says so.
|
|
553
|
+
|
|
531
554
|
### Audit / Update
|
|
532
555
|
|
|
533
556
|
The Audit / Update tab holds three things. The whole audit log
|
|
@@ -86,7 +86,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
|
|
|
86
86
|
gea client-report --report accuracy --out client_report
|
|
87
87
|
gea model-cards --out model_cards
|
|
88
88
|
gea sbom --out sbom
|
|
89
|
-
gea accept # the product gate (
|
|
89
|
+
gea accept # the product gate (308 checks)
|
|
90
90
|
gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
|
|
91
91
|
gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
|
|
92
92
|
gea gui # the desktop window (pip install "gea-program[desktop]")
|
|
@@ -489,6 +489,29 @@ so one geophone can still say when each rig was working - and the Seismic
|
|
|
489
489
|
Station Report and the station page carry the section, with the tracks drawn
|
|
490
490
|
against time.
|
|
491
491
|
|
|
492
|
+
### The Seismicity Response Area packet
|
|
493
|
+
|
|
494
|
+
The Railroad Commission of Texas declares a Seismicity Response Area around a
|
|
495
|
+
cluster of felt earthquakes and expects the operators inside it to run a
|
|
496
|
+
response plan. The plans on file have one shape - a circle of about 9 km, a
|
|
497
|
+
plan written against M 3.5, an 18-month goal, a response group meeting
|
|
498
|
+
within 48 hours of a threshold event, quarterly checkpoints with Commission
|
|
499
|
+
staff, disposal wells in two tiers by the base of the Wolfcamp - and the
|
|
500
|
+
Commission's December 2023 Notice to Operators names the data: four daily
|
|
501
|
+
parameters (maximum and average surface injection pressure, injection
|
|
502
|
+
volume, maximum injection rate) and three bottomhole-pressure methods, one
|
|
503
|
+
of which is a permanent downhole probe. `gea/sra.py` builds the packet to
|
|
504
|
+
that shape from what the site holds. The operator declares what the program
|
|
505
|
+
cannot measure (`set-well`: surface position and datum, API and UIC, depth
|
|
506
|
+
tier, which channels are the pressure, the rate and the downhole gauge, the
|
|
507
|
+
BHP method); `sra-define` puts the area on the site; `sra-report --catalog
|
|
508
|
+
texnet.csv` writes the packet - membership by geodesic distance, the daily
|
|
509
|
+
record per well rolled up by month, the catalogue's events against the plan
|
|
510
|
+
with the trigger and the goal clock, the schedule, and every gap by name -
|
|
511
|
+
and a daily export under the Notice's own parameter names. It will not
|
|
512
|
+
invent a volume for a well with no rate channel, a tier from depth alone, or
|
|
513
|
+
an aftershock: those are declarations, and the packet says so.
|
|
514
|
+
|
|
492
515
|
### Audit / Update
|
|
493
516
|
|
|
494
517
|
The Audit / Update tab holds three things. The whole audit log
|
|
@@ -281,9 +281,33 @@ def main(argv=None) -> int:
|
|
|
281
281
|
p_ws.add_argument("--action", type=str, default="list",
|
|
282
282
|
choices=["init", "add-file", "add-catalog", "add-live", "remove", "list", "migrate", "refresh", "audit",
|
|
283
283
|
"add-seismic", "refresh-seismic", "remove-seismic", "sar-film", "refresh-all", "add-track", "refresh-track", "remove-track",
|
|
284
|
-
"seismic-dataset", "seismic-field", "add-site", "sites", "site-report", "remove-site", "rename"
|
|
284
|
+
"seismic-dataset", "seismic-field", "add-site", "sites", "site-report", "remove-site", "rename",
|
|
285
|
+
"set-well", "sra-define", "sra-report"])
|
|
285
286
|
p_ws.add_argument("--stations", type=str, nargs="+", default=None, help="add-track: two or more array station ids of this workspace")
|
|
286
287
|
p_ws.add_argument("--site", type=str, default=None, help="site-report/remove-site: the site id")
|
|
288
|
+
p_ws.add_argument("--api", type=str, default=None, help="set-well: the API number")
|
|
289
|
+
p_ws.add_argument("--uic", type=str, default=None, help="set-well: the UIC permit number")
|
|
290
|
+
p_ws.add_argument("--depth-tier", type=str, choices=["shallow", "deep"], default=None, help="set-well: above or below the named formation base")
|
|
291
|
+
p_ws.add_argument("--formation", type=str, default=None, help="set-well: the basis of the tier, e.g. 'completed below the base of the Wolfcamp'")
|
|
292
|
+
p_ws.add_argument("--channel-pressure", type=str, default=None, help="set-well: the channel that is the surface injection pressure (psi)")
|
|
293
|
+
p_ws.add_argument("--channel-rate", type=str, default=None, help="set-well: the channel that is the injection rate")
|
|
294
|
+
p_ws.add_argument("--rate-unit", type=str, default=None, help="set-well: the rate channel's unit: bbl/min or bbl/d")
|
|
295
|
+
p_ws.add_argument("--bhp-method", type=str, choices=["calculated", "dip_in", "probe"], default=None, help="set-well: how bottomhole pressure is known")
|
|
296
|
+
p_ws.add_argument("--channel-bhp", type=str, default=None, help="set-well: the downhole gauge channel (probe method)")
|
|
297
|
+
p_ws.add_argument("--top-interval-ft", type=float, default=None, help="set-well: top of the disposal interval, ft (calculated method)")
|
|
298
|
+
p_ws.add_argument("--gradient-psi-ft", type=float, default=None, help="set-well: fluid gradient, psi/ft (calculated method)")
|
|
299
|
+
p_ws.add_argument("--radius-km", type=float, default=None, help="sra-define: the area's radius in km (Gardendale: 9.08)")
|
|
300
|
+
p_ws.add_argument("--plan-date", type=str, default=None, help="sra-define: the plan date, YYYY-MM-DD")
|
|
301
|
+
p_ws.add_argument("--threshold-m", type=float, default=3.5, help="sra-define: the magnitude the plan is written against (3.5)")
|
|
302
|
+
p_ws.add_argument("--goal-months", type=int, default=18, help="sra-define: months without a threshold event that meet the goal (18)")
|
|
303
|
+
p_ws.add_argument("--response-hours", type=int, default=48, help="sra-define: hours within which the response group meets (48)")
|
|
304
|
+
p_ws.add_argument("--checkpoint-months", type=int, default=3, help="sra-define: months between checkpoints with Commission staff (3)")
|
|
305
|
+
p_ws.add_argument("--formation-boundary", type=str, default="base of the Wolfcamp", help="sra-define: the stratigraphic boundary between the two tiers")
|
|
306
|
+
p_ws.add_argument("--triggering-event", type=str, default="", help="sra-define: the catalogue id of the event the plan answers")
|
|
307
|
+
p_ws.add_argument("--catalog", type=str, default=None, help="sra-report: a TexNet earthquake-catalogue export (CSV)")
|
|
308
|
+
p_ws.add_argument("--aftershocks", type=str, nargs="*", default=None, help="sra-report: catalogue ids the operator declares as aftershocks (exempt)")
|
|
309
|
+
p_ws.add_argument("--start", type=str, default=None, help="sra-report: first day of the record, YYYY-MM-DD")
|
|
310
|
+
p_ws.add_argument("--end", type=str, default=None, help="sra-report: last day of the record, YYYY-MM-DD")
|
|
287
311
|
p_ws.add_argument("--wells", type=str, nargs="+", default=None, help="add-site: well ids of this workspace")
|
|
288
312
|
p_ws.add_argument("--seismic", type=str, nargs="+", default=None, help="add-site: seismic station ids of this workspace")
|
|
289
313
|
p_ws.add_argument("--tracks", type=str, nargs="+", default=None, help="add-site: track ids of this workspace")
|
|
@@ -362,6 +386,9 @@ def main(argv=None) -> int:
|
|
|
362
386
|
p_w0.add_argument("--seed", type=int, default=1)
|
|
363
387
|
p_w0.add_argument("--connect", type=str, default=None, help="host:port - instead of listening, connect to a listening tap and push frames")
|
|
364
388
|
|
|
389
|
+
p_sra = sub.add_parser("sra", help="the Seismicity Response Area packet: the self-test on a labelled scene")
|
|
390
|
+
p_sra.add_argument("--action", choices=["selftest"], default="selftest")
|
|
391
|
+
p_sra.add_argument("--json", action="store_true")
|
|
365
392
|
p_dr = sub.add_parser("doctor", help="which code is running and can it serve: Python, the package, duplicates, the page, PyPI; with --workspace also the site folder and the port")
|
|
366
393
|
p_dr.add_argument("--workspace", type=str, default=None)
|
|
367
394
|
p_dr.add_argument("--host", type=str, default="127.0.0.1")
|
|
@@ -1443,6 +1470,18 @@ def main(argv=None) -> int:
|
|
|
1443
1470
|
return 0
|
|
1444
1471
|
print(f"notifications: {'configured' if n.cfg else 'not configured'}" + (f" - {len(n.cfg['channels'])} channel(s), {len(n.cfg['rules'])} rule(s), quiet {n.cfg['quiet_s']} s" if n.cfg else " (gea notify --example)"))
|
|
1445
1472
|
return 0
|
|
1473
|
+
elif a.cmd == "sra":
|
|
1474
|
+
from . import sra as _SRA
|
|
1475
|
+
r = _SRA.selftest()
|
|
1476
|
+
if a.json:
|
|
1477
|
+
print(json.dumps({k: v for k, v in r.items() if k != "packet"}, indent=1))
|
|
1478
|
+
else:
|
|
1479
|
+
print(f"sra selftest [{r['label']}]: {r['status']}")
|
|
1480
|
+
for k, v in r["checks"].items():
|
|
1481
|
+
print(f" {'ok ' if v else 'BAD'} {k}")
|
|
1482
|
+
print(f" deep well volume {r['deep_mean_volume_bbl_day']} bbl/day; probe offset {r['probe_offset_psi']} psi")
|
|
1483
|
+
print(_SRA.report_text(r["packet"]))
|
|
1484
|
+
return 0 if r["status"] == "OK" else 1
|
|
1446
1485
|
elif a.cmd == "doctor":
|
|
1447
1486
|
from .doctor import run as _doctor
|
|
1448
1487
|
return _doctor(a.workspace, a.host, a.port, a.json)
|
|
@@ -1630,6 +1669,34 @@ def main(argv=None) -> int:
|
|
|
1630
1669
|
if not a.site:
|
|
1631
1670
|
raise SystemExit("remove-site needs --site <id>")
|
|
1632
1671
|
ws.remove_site(a.site, actor=a.actor); print("removed (folder kept):", a.site); return 0
|
|
1672
|
+
elif a.action == "set-well":
|
|
1673
|
+
if not a.well:
|
|
1674
|
+
raise SystemExit("set-well needs --well <id>")
|
|
1675
|
+
surface = {"lat": a.lat, "lon": a.lon, "datum": a.datum} if (a.lat is not None or a.lon is not None) else None
|
|
1676
|
+
disposal = {k: v for k, v in {"api_number": a.api, "uic_number": a.uic, "depth_tier": a.depth_tier, "formation_basis": a.formation,
|
|
1677
|
+
"channel_pressure": a.channel_pressure, "channel_rate": a.channel_rate, "rate_unit": a.rate_unit,
|
|
1678
|
+
"bhp_method": a.bhp_method, "channel_bhp": a.channel_bhp, "top_interval_ft": a.top_interval_ft,
|
|
1679
|
+
"gradient_psi_ft": a.gradient_psi_ft}.items() if v is not None}
|
|
1680
|
+
if not surface and not disposal:
|
|
1681
|
+
raise SystemExit("set-well: nothing to declare (give --lat/--lon, or any of --api --uic --depth-tier --channel-pressure ...)")
|
|
1682
|
+
w = ws.set_well(a.well, surface=surface, disposal=disposal or None, actor=a.actor)
|
|
1683
|
+
print(f"well {a.well}: surface {w.get('surface')}; disposal {w.get('disposal')}"); return 0
|
|
1684
|
+
elif a.action == "sra-define":
|
|
1685
|
+
if not a.site or not a.name or a.lat is None or a.lon is None or a.radius_km is None or not a.plan_date:
|
|
1686
|
+
raise SystemExit("sra-define needs --site, --name, --lat, --lon, --radius-km and --plan-date")
|
|
1687
|
+
sra = ws.sra_define(a.site, actor=a.actor, name=a.name, lat=a.lat, lon=a.lon, radius_km=a.radius_km, plan_date=a.plan_date,
|
|
1688
|
+
datum=a.datum or "WGS84", threshold_m=a.threshold_m, goal_months=a.goal_months, response_hours=a.response_hours,
|
|
1689
|
+
checkpoint_months=a.checkpoint_months, formation_boundary=a.formation_boundary,
|
|
1690
|
+
triggering_event=a.triggering_event, note=a.note or "")
|
|
1691
|
+
print(f"sra: {sra['name']} on site {a.site} - {sra['radius_km']} km around {sra['centre']['lat']}, {sra['centre']['lon']} "
|
|
1692
|
+
f"({sra['centre']['datum']}); plan {sra['plan_date']}; M {sra['threshold_m']}; goal {sra['goal_months']} months"); return 0
|
|
1693
|
+
elif a.action == "sra-report":
|
|
1694
|
+
from .sra import report_text as _sra_text
|
|
1695
|
+
if not a.site:
|
|
1696
|
+
raise SystemExit("sra-report needs --site <id>")
|
|
1697
|
+
r = ws.write_sra_packet(a.site, catalog_csv=a.catalog, aftershocks=a.aftershocks, start=a.start, end=a.end, actor=a.actor)
|
|
1698
|
+
print(_sra_text(r["packet"]))
|
|
1699
|
+
print(" report:", r["paths"]["html"]); print(" daily export:", r["paths"]["daily_csv"], f"({r['export_rows']} rows)"); return 0
|
|
1633
1700
|
elif a.action == "seismic-dataset":
|
|
1634
1701
|
r = ws.write_seismic_dataset_report(actor=a.actor)
|
|
1635
1702
|
inv = r["inventory"]
|
|
@@ -2914,7 +2914,8 @@ def section_am_report_samples(tmp: str) -> None:
|
|
|
2914
2914
|
"model_card_well_baseline.html", "model_card_well_test_detector.html", "model_card_rock_density_inventory.html",
|
|
2915
2915
|
"model_card_strata_property_estimator.html", "gauge_drift_report_monitored_SYNTHETIC.html", "alarm_event_report_SYNTHETIC.html",
|
|
2916
2916
|
"data_resilience_report_SYNTHETIC.html", "sla_report_SYNTHETIC.html", "sat_protocol.html", "seismic_station_report_SYNTHETIC.html", "seismic_track_report_SYNTHETIC.html",
|
|
2917
|
-
"seismic_dataset_report_SYNTHETIC.html", "seismic_field_report_SYNTHETIC.html", "site_report_SYNTHETIC.html"
|
|
2917
|
+
"seismic_dataset_report_SYNTHETIC.html", "seismic_field_report_SYNTHETIC.html", "site_report_SYNTHETIC.html",
|
|
2918
|
+
"sra_packet_SYNTHETIC.html"}
|
|
2918
2919
|
have = {p.name for p in d.glob("*.html")}
|
|
2919
2920
|
md = (d / "SAMPLES.md").read_text(encoding="utf-8") if (d / "SAMPLES.md").exists() else ""
|
|
2920
2921
|
texts = {n: (d / n).read_text(encoding="utf-8", errors="replace") for n in sorted(have & want)}
|
|
@@ -4430,27 +4431,51 @@ def section_bb_supervisor(tmp: str) -> None:
|
|
|
4430
4431
|
"happens to share a recycled process id is still reported, and a process id that is not the last start's excludes nothing")
|
|
4431
4432
|
|
|
4432
4433
|
# -- the exit code reaches the launcher, and the launcher acts on it -------------------
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4434
|
+
# In a checkout the launchers are the templates in tools/build_installer.py. In an installed kit there
|
|
4435
|
+
# is no tools/ - the launcher under test is the one the kit installed beside its python/, found the way
|
|
4436
|
+
# doctor finds it. (The v0.8.0 kit gate failed on GitHub reading tools/ from site-packages.)
|
|
4437
|
+
from .doctor import check_launcher, check_workspace, kit_dir
|
|
4438
|
+
cmd = sh = None
|
|
4439
|
+
tools_py = pkg.parent / "tools" / "build_installer.py"
|
|
4440
|
+
if (pkg.parent / "pyproject.toml").exists() and tools_py.exists():
|
|
4441
|
+
spec = importlib.util.spec_from_file_location("bb_installer", str(tools_py))
|
|
4442
|
+
bi = importlib.util.module_from_spec(spec); spec.loader.exec_module(bi)
|
|
4443
|
+
cmd = bi.WIN_SCRIPTS["start-dashboard.cmd"]; sh = bi.LINUX_SCRIPTS["start-dashboard.sh"]; where = "the checkout's templates"
|
|
4444
|
+
else:
|
|
4445
|
+
kd = kit_dir(); where = f"the installed kit at {kd}"
|
|
4446
|
+
if kd and os.path.isfile(os.path.join(kd, "start-dashboard.cmd")):
|
|
4447
|
+
cmd = open(os.path.join(kd, "start-dashboard.cmd"), encoding="utf-8", errors="replace").read()
|
|
4448
|
+
if kd and os.path.isfile(os.path.join(kd, "start-dashboard.sh")):
|
|
4449
|
+
sh = open(os.path.join(kd, "start-dashboard.sh"), encoding="utf-8", errors="replace").read()
|
|
4450
|
+
cmd_ok = sh_ok = True
|
|
4451
|
+
if cmd is not None:
|
|
4452
|
+
body = [ln for ln in cmd.strip().splitlines() if ln.strip() and not ln.strip().lower().startswith("rem ")]
|
|
4453
|
+
labels = set(_re.findall(r"^:(\w+)", cmd, _re.M)); gotos = set(_re.findall(r"goto\s+(\w+)", cmd))
|
|
4454
|
+
cmd_ok = ("title GEA - Operations Control Panel" in cmd
|
|
4455
|
+
and '"%GEA_RC%"=="86"' in cmd and "goto gea_run" in cmd and not gotos - labels
|
|
4456
|
+
and body[-1].lower().startswith("powershell") and "-NoExit" in body[-1] and "start-dashboard.cmd" in body[-1]
|
|
4457
|
+
and "python" not in body[-1].lower().split("write-host")[0])
|
|
4458
|
+
if sh is not None:
|
|
4459
|
+
sh_ok = 'rc" = "86"' in sh and "continue" in sh and sh.count("exit \"$rc\"") == 1
|
|
4460
|
+
ok((cmd is not None or sh is not None) and cmd_ok and sh_ok,
|
|
4461
|
+
f"BB3 the launcher ({where}): the Windows one names the window, runs the panel again when it exits with 86, resolves every label it jumps "
|
|
4462
|
+
"to, and ends by handing the window to a PowerShell prompt that says how to start the panel again - it never falls back to the shell "
|
|
4463
|
+
"underneath, which is how an operator was left at a Python prompt; the shell one does the same with a loop")
|
|
4464
|
+
cmd_ok = sh_ok = True
|
|
4465
|
+
if cmd is not None:
|
|
4466
|
+
lines = cmd.splitlines()
|
|
4467
|
+
cmd_ok = ("auto_restart.flag" in cmd and "GEA_FAILS" in cmd and "GTR 5" in cmd and ":gea_read_flag" in lines
|
|
4468
|
+
and lines[lines.index(":gea_read_flag") + 1].strip().startswith("set /p GEA_AUTO=<") and cmd.count("set /p GEA_AUTO=<") == 1)
|
|
4469
|
+
if sh is not None:
|
|
4470
|
+
sh_ok = "auto_restart.flag" in sh and "fails" in sh and "-le 5" in sh
|
|
4471
|
+
ok(SV.AUTO_MAX == 5 and cmd_ok and sh_ok,
|
|
4451
4472
|
"BB4 a code that is neither 0 nor 86 is a death, not a decision: the launcher reads the operator's answer from records/auto_restart.flag, "
|
|
4452
4473
|
"tries at most five times in a row so a panel that crashes while starting hands over the error instead of flickering all night, and reads "
|
|
4453
4474
|
"that file outside an if-block because a redirect inside parentheses is parsed before the block runs")
|
|
4475
|
+
if cmd is None: # a Linux kit: BB6 needs a Windows launcher text to build its good and bad kits from
|
|
4476
|
+
cmd = ("@echo off\ntitle GEA - Operations Control Panel\n:gea_run\npython\\python.exe -m gea serve\nset GEA_RC=%ERRORLEVEL%\n"
|
|
4477
|
+
"if \"%GEA_RC%\"==\"86\" goto gea_run\n:gea_read_flag\nset /p GEA_AUTO=<\"%GEA_WORKSPACE%\\records\\auto_restart.flag\"\n"
|
|
4478
|
+
"powershell.exe -NoLogo -NoExit -Command \"Write-Host 'start-dashboard.cmd'\"\n")
|
|
4454
4479
|
|
|
4455
4480
|
# -- the flag file: what the launcher will read, not what the manifest says ------------
|
|
4456
4481
|
ok(SV.auto_restart(wsp) is False and SV.set_auto_restart(wsp, True) is True and SV.auto_restart(wsp) is True
|
|
@@ -4461,7 +4486,6 @@ def section_bb_supervisor(tmp: str) -> None:
|
|
|
4461
4486
|
"unreadable flag reads as off, since a program does not start itself again on the strength of a file nobody can be sure about")
|
|
4462
4487
|
|
|
4463
4488
|
# -- doctor reads the installed launcher ----------------------------------------------
|
|
4464
|
-
from .doctor import check_launcher, check_workspace, kit_dir
|
|
4465
4489
|
good = d / "kit_good"; (good / "python").mkdir(parents=True)
|
|
4466
4490
|
(good / "start-dashboard.cmd").write_text(cmd, encoding="utf-8")
|
|
4467
4491
|
bad = d / "kit_bad"; (bad / "python").mkdir(parents=True)
|
|
@@ -4570,9 +4594,139 @@ def section_bb_supervisor(tmp: str) -> None:
|
|
|
4570
4594
|
"panel's own page carries the Stop and Restart controls, the authorisation step behind a popup, and the auto-restart switch")
|
|
4571
4595
|
|
|
4572
4596
|
|
|
4597
|
+
def section_bc_sra(tmp: str) -> None:
|
|
4598
|
+
"""Section BC - the Seismicity Response Area packet. The Railroad Commission's operator-led response plans
|
|
4599
|
+
and its Notice to Operators on disposal-well monitoring name a shape; the packet is built to it from what
|
|
4600
|
+
the site holds, and names what it is missing rather than leaving a column blank."""
|
|
4601
|
+
import csv as _csv
|
|
4602
|
+
import subprocess as _sp
|
|
4603
|
+
from datetime import datetime as _dt, timedelta as _td, timezone as _tz
|
|
4604
|
+
from . import sra as SR
|
|
4605
|
+
from .client_reports import forbidden_terms, sra_packet_report, write
|
|
4606
|
+
from .workspace import Workspace, WorkspaceError
|
|
4607
|
+
pkg = Path(__file__).parent
|
|
4608
|
+
d = Path(tmp, "bc"); d.mkdir()
|
|
4609
|
+
|
|
4610
|
+
# -- the self-test: the labelled scene ----------------------------------------------
|
|
4611
|
+
r = SR.selftest()
|
|
4612
|
+
ok(r["status"] == "OK" and all(r["checks"].values()) and r["label"] == "SIMULATION_SELF_TEST"
|
|
4613
|
+
and abs(r["deep_mean_volume_bbl_day"] - 8640.0) / 8640.0 < 0.03 and abs(r["probe_offset_psi"] - 0.465 * 9800) < 15,
|
|
4614
|
+
"BC1 the self-test on a labelled scene: membership by geodesic distance with a NAD27 well's shift named, the four daily parameters from "
|
|
4615
|
+
"the well's own channels with the volume integrated from the rate to within 3 %, two shut-in days at zero, the probe's offset from "
|
|
4616
|
+
"surface pressure recovered, the shallow well refusing a volume it has no rate channel for, the catalogue's trigger and exempt "
|
|
4617
|
+
"aftershock and goal clock, the gaps named, the checkpoints from the plan date")
|
|
4618
|
+
|
|
4619
|
+
# -- the area: declarations, refused when not an area --------------------------------
|
|
4620
|
+
sra = SR.define("Test SRA", 31.95, -102.25, 9.08, "2026-02-25", "NAD27", triggering_event="x")
|
|
4621
|
+
bad = 0
|
|
4622
|
+
for kw in ({"datum": "UNKNOWN"}, {"radius_km": 0}, {"plan_date": "not a date"}, {"name": " "}):
|
|
4623
|
+
try:
|
|
4624
|
+
SR.define(**{**{"name": "T", "lat": 31.95, "lon": -102.25, "radius_km": 9.08, "plan_date": "2026-02-25", "datum": "WGS84"}, **kw});
|
|
4625
|
+
except ValueError:
|
|
4626
|
+
bad += 1
|
|
4627
|
+
cps = SR.checkpoints(sra)
|
|
4628
|
+
ok(sra["centre"]["datum"] == "NAD27" and sra["centre_wgs84"]["shift_m"] > 10 and sra["area_km2"] == 259.0 and sra["threshold_m"] == 3.5
|
|
4629
|
+
and sra["goal_months"] == 18 and sra["response_hours"] == 48 and sra["checkpoint_months"] == 3 and bad == 4
|
|
4630
|
+
and [c["date"] for c in cps][:2] == ["2026-05-25", "2026-08-25"] and cps[-1]["n"] == "goal" and cps[-1]["date"] == "2027-08-25"
|
|
4631
|
+
and "not a measurement" in " ".join(sra["not_a_measurement"]).lower().replace("never derived here", "not a measurement"),
|
|
4632
|
+
"BC2 an area is a centre on a stated datum (carried to WGS84 with its shift named), a radius, a plan date and the plan's numbers - "
|
|
4633
|
+
"M 3.5, 18 months, 48 hours, quarterly - recorded as declarations; an unknown datum, a zero radius, a non-date or a blank name is refused; "
|
|
4634
|
+
"the checkpoints fall every three months from the plan date and the goal date closes the list")
|
|
4635
|
+
|
|
4636
|
+
# -- the catalogue reader: a TexNet-shaped export ------------------------------------
|
|
4637
|
+
cat = d / "texnet.csv"
|
|
4638
|
+
with open(cat, "w", newline="") as f:
|
|
4639
|
+
w = _csv.writer(f); w.writerow(["EventID", "Origin Date", "Origin Time", "Local Magnitude", "Latitude", "Longitude", "Depth of Hypocenter (Km)"])
|
|
4640
|
+
w.writerow(["texnet2026aaaa", "2026-02-20", "04:15:10", "4.4", "31.950", "-102.250", "7.2"]) # before the plan date
|
|
4641
|
+
w.writerow(["texnet2026aaab", "2026-03-05", "11:02:33", "3.7", "31.960", "-102.240", "6.8"]) # counts
|
|
4642
|
+
w.writerow(["texnet2026aaac", "2026-03-06", "01:00:00", "3.6", "31.955", "-102.245", "6.5"]) # declared aftershock
|
|
4643
|
+
w.writerow(["texnet2026aaad", "2026-03-09", "21:40:00", "2.0", "31.940", "-102.260", "6.1"])
|
|
4644
|
+
w.writerow(["texnet2026aaae", "2026-03-12", "03:00:00", "3.9", "32.500", "-102.700", "8.0"]) # outside
|
|
4645
|
+
w.writerow(["", "", "", "", "", "", ""]) # a blank line is skipped, not an event
|
|
4646
|
+
c = SR.read_catalog(str(cat))
|
|
4647
|
+
se = SR.seismicity(sra, c, as_of=_dt(2026, 4, 1, tzinfo=_tz.utc), aftershocks=["texnet2026aaac"])
|
|
4648
|
+
ok(c["n_events"] == 5 and c["n_skipped"] == 1 and c["columns"]["origin_date"] == "Origin Date" and c["columns"]["origin_time"] == "Origin Time"
|
|
4649
|
+
and c["events"][0]["time"] == "2026-02-20T04:15:10Z" and c["events"][0]["depth_km"] == 7.2
|
|
4650
|
+
and se["n_inside"] == 4 and se["n_at_or_above"] == 3 and se["n_counted"] == 1 and se["n_exempt"] == 1
|
|
4651
|
+
and se["largest"]["event_id"] == "texnet2026aaaa" and se["trigger"]["status"] == "TRIGGERED" and se["trigger"]["event_id"] == "texnet2026aaab"
|
|
4652
|
+
and se["trigger"]["respond_by"] == "2026-03-07T11:02:33Z" and se["goal"]["clock_started"] == "2026-03-05" and se["goal"]["status"] == "RUNNING"
|
|
4653
|
+
and se["goal"]["goal_date"] == "2027-09-05",
|
|
4654
|
+
"BC3 a TexNet-shaped export is read by its column names (date and time in separate columns, the depth under its long name), a blank line "
|
|
4655
|
+
"is skipped and counted; against the plan: the M 4.4 before the plan date is the largest but does not count, the declared aftershock is "
|
|
4656
|
+
"exempt, the M 3.9 outside the circle is outside, the M 3.7 triggers the 48-hour response with its deadline, and the 18-month clock "
|
|
4657
|
+
"starts from it")
|
|
4658
|
+
|
|
4659
|
+
# -- the daily record from a stream: refused without a calendar --------------------------------
|
|
4660
|
+
from .ports import LiveStream, StreamChannel
|
|
4661
|
+
import numpy as _np
|
|
4662
|
+
st = LiveStream(name="x", source_format="synthetic", index_kind="time_s", index=_np.arange(100) * 600.0,
|
|
4663
|
+
channels={"P": StreamChannel("P", "psi", _np.full(100, 1500.0))}, meta={})
|
|
4664
|
+
rr = SR.daily_records(st, {"channel_pressure": "P"})
|
|
4665
|
+
sc = SR.synthetic_scene(days=3)
|
|
4666
|
+
dr = SR.daily_records(sc["streams"]["deep-1"], {**sc["wells"][0]["disposal"], "rate_unit": "furlongs"})
|
|
4667
|
+
ok(rr["status"] == "REFUSED" and "calendar" in rr["detail"]
|
|
4668
|
+
and dr["parameters"]["injection_volume_bbl"]["status"] == "NOT RECORDED" and "furlongs" in dr["parameters"]["injection_volume_bbl"]["why"]
|
|
4669
|
+
and dr["parameters"]["max_surface_injection_pressure_psi"]["status"] == "RECORDED",
|
|
4670
|
+
"BC4 a daily record needs a calendar: a stream with no start time is refused, not dated from zero; a rate in a unit the program does not "
|
|
4671
|
+
"convert leaves the volume NOT RECORDED with the unit named, while the pressures are still recorded")
|
|
4672
|
+
|
|
4673
|
+
# -- through the workspace and the command line ----------------------------------------
|
|
4674
|
+
t0 = _dt(2026, 3, 1, tzinfo=_tz.utc); n = 6 * 144
|
|
4675
|
+
hist = d / "swd.csv"
|
|
4676
|
+
with open(hist, "w", newline="") as f:
|
|
4677
|
+
w = _csv.writer(f); w.writerow(["timestamp", "P_surf_psi", "Q_bpm", "P_bh_psi"])
|
|
4678
|
+
for i in range(n):
|
|
4679
|
+
w.writerow([(t0 + _td(seconds=600 * i)).strftime("%Y-%m-%dT%H:%M:%SZ"), "1500.0", "5.0", f"{1500.0 + 0.465 * 9000:.1f}"])
|
|
4680
|
+
ws = Workspace.create(str(d / "ws"), "Pad 3", actor="tester")
|
|
4681
|
+
w1 = ws.add_well_file(str(hist), display="SWD 1", actor="tester")
|
|
4682
|
+
try:
|
|
4683
|
+
ws.set_well(w1["id"], disposal={"depth_tier": "middle"}, actor="tester"); tier_bad = False
|
|
4684
|
+
except WorkspaceError:
|
|
4685
|
+
tier_bad = True
|
|
4686
|
+
run = lambda *a: _sp.run([sys.executable, "-m", "gea", "workspace", "--path", ws.path, "--actor", "tester", *a], capture_output=True, text=True)
|
|
4687
|
+
r1 = run("--action", "set-well", "--well", w1["id"], "--lat", "31.96", "--lon", "-102.24", "--datum", "NAD27", "--api", "42-329-12345", "--uic", "123456",
|
|
4688
|
+
"--depth-tier", "deep", "--formation", "completed below the base of the Wolfcamp", "--channel-pressure", "P_surf_psi", "--channel-rate", "Q_bpm",
|
|
4689
|
+
"--rate-unit", "bbl/min", "--bhp-method", "probe", "--channel-bhp", "P_bh_psi")
|
|
4690
|
+
r2 = run("--action", "add-site", "--name", "SRA block", "--wells", w1["id"], "--client", "Acme Operating")
|
|
4691
|
+
r3 = run("--action", "sra-report", "--site", "SRA-block") # no area yet: refused
|
|
4692
|
+
r4 = run("--action", "sra-define", "--site", "SRA-block", "--name", "Test SRA", "--lat", "31.95", "--lon", "-102.25", "--datum", "WGS84",
|
|
4693
|
+
"--radius-km", "9.08", "--plan-date", "2026-02-25")
|
|
4694
|
+
r5 = run("--action", "sra-report", "--site", "SRA-block", "--catalog", str(cat), "--aftershocks", "texnet2026aaac")
|
|
4695
|
+
ws2 = Workspace(ws.path)
|
|
4696
|
+
out = Path(ws.path, "reports", "sites", "SRA-block")
|
|
4697
|
+
rows = list(_csv.DictReader(open(out / "sra_daily_export.csv", encoding="utf-8")))
|
|
4698
|
+
md = (out / "sra_packet.md").read_text(encoding="utf-8")
|
|
4699
|
+
audit = Path(ws.path, "records", "audit.jsonl").read_text(encoding="utf-8")
|
|
4700
|
+
pkj = json.loads((out / "sra_packet.json").read_text(encoding="utf-8"))["packet"]
|
|
4701
|
+
ok(tier_bad and r1.returncode == 0 and r2.returncode == 0 and r3.returncode != 0 and "sra-define" in (r3.stderr + r3.stdout)
|
|
4702
|
+
and r4.returncode == 0 and r5.returncode == 0 and "COMPLETE" in r5.stdout and "TRIGGERED" in r5.stdout
|
|
4703
|
+
and ws2.site("SRA-block")["sra"]["name"] == "Test SRA" and pkj["status"] == "COMPLETE" and pkj["wells"][0]["inside"] and pkj["wells"][0]["depth_tier"] == "deep"
|
|
4704
|
+
and len(rows) == 6 and rows[0]["API number"] == "42-329-12345" and rows[0]["UIC permit number"] == "123456"
|
|
4705
|
+
and abs(float(rows[1]["Injection volume (barrels per day)"]) - 7200.0) < 10 and rows[1]["Maximum surface injection pressure (pounds per square inch)"] == "1500.0"
|
|
4706
|
+
and abs(float(rows[1]["Bottomhole pressure (pounds per square inch)"]) - (1500 + 0.465 * 9000)) < 1 and rows[1]["BHP method"] == "probe"
|
|
4707
|
+
and "Maximum surface injection pressure (pounds per square inch)" in rows[0]
|
|
4708
|
+
and not forbidden_terms(md) and "well.declare" in audit and "site.sra_define" in audit and "site.sra_packet" in audit,
|
|
4709
|
+
"BC5 `set-well` records what the operator declares - surface position and datum, API and UIC, depth tier by the named formation, which "
|
|
4710
|
+
"channels are the surface pressure, the rate and the downhole gauge, the BHP method - and refuses a tier that is not shallow or deep; "
|
|
4711
|
+
"`sra-define` puts the area on the site; `sra-report` is refused until it does; then the packet is COMPLETE for a well with everything "
|
|
4712
|
+
"declared, the daily export carries the Notice's parameter names with the API and UIC on every line, the volume integrates to 7,200 bbl "
|
|
4713
|
+
"a day from 5 bbl/min, the probe is the bottomhole pressure, the report passes the vocabulary gate, and every step is audited")
|
|
4714
|
+
|
|
4715
|
+
# -- the service offers it and the page carries it ------------------------------------------
|
|
4716
|
+
svc_src = (pkg / "service.py").read_text(encoding="utf-8"); page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
4717
|
+
r6 = _sp.run([sys.executable, "-m", "gea", "sra"], capture_output=True, text=True)
|
|
4718
|
+
ok("'sra_report'" in svc_src and "sra_packet.html" in svc_src and "Seismicity Response Area</th>" in page_src and "sra-define" in page_src
|
|
4719
|
+
and r6.returncode == 0 and "SIMULATION_SELF_TEST" in r6.stdout and "OK" in r6.stdout
|
|
4720
|
+
and (pkg / "help" / "sra.md").is_file(),
|
|
4721
|
+
"BC6 the Sites page carries the area and the packet's status beside each site; `gea sra` runs the self-test from the command line and "
|
|
4722
|
+
"labels it SIMULATION_SELF_TEST; the help page exists")
|
|
4723
|
+
|
|
4724
|
+
|
|
4573
4725
|
def main() -> int:
|
|
4574
4726
|
print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
|
|
4575
|
-
|
|
4727
|
+
# a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
|
|
4728
|
+
# the checks have already passed or failed on their own, and the folder is the temp folder
|
|
4729
|
+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
|
|
4576
4730
|
section_a_cli(tmp)
|
|
4577
4731
|
section_b_las(tmp)
|
|
4578
4732
|
section_c_reconciler(tmp)
|
|
@@ -4625,6 +4779,7 @@ def main() -> int:
|
|
|
4625
4779
|
section_az_uncertainty(tmp)
|
|
4626
4780
|
section_ba_sites(tmp)
|
|
4627
4781
|
section_bb_supervisor(tmp)
|
|
4782
|
+
section_bc_sra(tmp)
|
|
4628
4783
|
if _FAILS:
|
|
4629
4784
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
4630
4785
|
for f in _FAILS:
|
|
@@ -1584,6 +1584,84 @@ def seismic_track_report(track: dict, histories: List[dict], positions: dict, ve
|
|
|
1584
1584
|
'evaluated_at_utc': now.strftime('%Y-%m-%dT%H:%M:%SZ'), '_records': [], '_catalogue_obj': None})
|
|
1585
1585
|
|
|
1586
1586
|
|
|
1587
|
+
def sra_packet_report(pk: dict, evaluated_at: Optional[datetime] = None, program_version: str = '') -> Document:
|
|
1588
|
+
"""The Seismicity Response Area packet: what an operator inside an SRA puts in front of the Railroad
|
|
1589
|
+
Commission, built from what the site holds. The area and the plan as declared; which wells and stations
|
|
1590
|
+
are inside it and each well's depth tier; the four daily parameters the Commission's Notice names, per
|
|
1591
|
+
well, rolled up by month, with the bottomhole-pressure method; the catalogue's events against the plan -
|
|
1592
|
+
the response trigger and the goal clock; the checkpoint schedule; and every gap by name."""
|
|
1593
|
+
now = evaluated_at or _utc_now()
|
|
1594
|
+
s = pk['sra']; site = pk.get('site') or {}; m = pk['membership']; se = pk.get('seismicity')
|
|
1595
|
+
report_id = f"SRA-{str(site.get('id') or s['name']).replace('/', '-')[:24]}-{now.strftime('%Y%m%dT%H%M%SZ')}"
|
|
1596
|
+
secs = []
|
|
1597
|
+
secs.append(Section('1', 'The area and the plan', [
|
|
1598
|
+
f"{s['name']}: a circle of {s['radius_km']:g} km ({s['area_km2']:,} km\u00b2) around {s['centre']['lat']:.5f}, {s['centre']['lon']:.5f} "
|
|
1599
|
+
f"on {s['centre']['datum']}. Plan date {s['plan_date']}. The plan is written against earthquakes of magnitude {s['threshold_m']:.1f} and "
|
|
1600
|
+
f"above inside the area: the goal is {s['goal_months']} months without one; the response to one is a meeting of the operator-led "
|
|
1601
|
+
f"response group within {s['response_hours']} hours; the checkpoints with Commission staff fall every {s['checkpoint_months']} months. "
|
|
1602
|
+
f"Disposal wells are placed in two tiers by the {s['formation_boundary']}."
|
|
1603
|
+
+ (f" Triggering event: {s['triggering_event']}." if s.get('triggering_event') else '')
|
|
1604
|
+
+ (f" {s['note']}" if s.get('note') else ''),
|
|
1605
|
+
'Every number in this paragraph is a declaration copied from the plan, not a measurement.']))
|
|
1606
|
+
wrows = [[w['id'], w['display'] or '-', w['api_number'] or '-', w['uic_number'] or '-',
|
|
1607
|
+
('inside' if w['inside'] else ('outside' if w['inside'] is False else 'undecided')),
|
|
1608
|
+
_fmt(w['distance_km']), w['depth_tier'], (w['bhp_method'] or '-'), w.get('daily_status') or '-'] for w in pk['wells']]
|
|
1609
|
+
srows = [[x['id'], x.get('display') or '-', x.get('kind') or '-', ('inside' if x['inside'] else ('outside' if x['inside'] is False else 'undecided')),
|
|
1610
|
+
_fmt(x.get('distance_km'))] for x in pk['stations']]
|
|
1611
|
+
secs.append(Section('2', 'What is inside the area', [
|
|
1612
|
+
f"{m['n_wells_inside']} well(s) and {m['n_stations_inside']} seismic station(s) of the site fall inside the area by geodesic distance "
|
|
1613
|
+
f"from the centre, with every position carried to WGS84 first; {m['n_undecided']} could not be placed."],
|
|
1614
|
+
[Table(['Well', 'Name', 'API', 'UIC', 'Membership', 'Distance (km)', 'Depth tier', 'BHP method', 'Daily record'], wrows, 'Wells'),
|
|
1615
|
+
Table(['Station', 'Name', 'Kind', 'Membership', 'Distance (km)'], srows, 'Seismic stations')]))
|
|
1616
|
+
n = 3
|
|
1617
|
+
inside_wells = [w for w in pk['wells'] if w['inside'] and w.get('months')]
|
|
1618
|
+
for w in inside_wells:
|
|
1619
|
+
pr = w.get('parameters', {}); bh = w.get('bhp', {})
|
|
1620
|
+
paras = [f"{w['display'] or w['id']} ({w['depth_tier']}): {w.get('daily_detail') or ''}",
|
|
1621
|
+
' '.join(f"{v['label']}: {v['status']}" + (f" from channel {v['channel']}" if v.get('channel') else '') + '.' for v in pr.values()),
|
|
1622
|
+
f"Bottomhole pressure: {bh.get('label', '-')}, reported {bh.get('reported', '-')}" + (f"; {bh['how']}" if bh.get('how') else '') + '.']
|
|
1623
|
+
secs.append(Section(str(n), f"Daily record, rolled up by month: {w['display'] or w['id']}", paras,
|
|
1624
|
+
[Table(['Month', 'Days', 'Recorded', 'Missing', 'Max surface inj. pressure (psi)', 'Avg surface inj. pressure (psi)',
|
|
1625
|
+
'Injection volume (bbl)', 'Max injection rate (bbl/min)', 'BHP (psi)'],
|
|
1626
|
+
[[r['month'], r['days'], r['recorded'], r['missing'], _fmt(r['max_surface_injection_pressure_psi']),
|
|
1627
|
+
_fmt(r['avg_surface_injection_pressure_psi']), _fmt(r['injection_volume_bbl']),
|
|
1628
|
+
_fmt(r['max_injection_rate_bbl_min']), _fmt(r['bhp_psi'])] for r in w['months']])]))
|
|
1629
|
+
n += 1
|
|
1630
|
+
if se:
|
|
1631
|
+
lg = se.get('largest')
|
|
1632
|
+
paras = [f"Catalogue export of {se['catalog'].get('export_mtime_utc', '-')[:10]}: {se['n_inside']} event(s) inside the area, "
|
|
1633
|
+
f"{se['n_at_or_above']} at or above M {s['threshold_m']:.1f}, {se['n_exempt']} of those declared aftershocks and exempt."
|
|
1634
|
+
+ (f" Largest inside the area: M {lg['magnitude']:.1f}, {lg['time'][:10]}, {lg['distance_km']:g} km from the centre ({lg['event_id']})." if lg else ''),
|
|
1635
|
+
f"Response: {se['trigger']['status']}. {se['trigger']['detail']}.",
|
|
1636
|
+
f"Goal: {se['goal']['status']}. {se['goal']['detail']}."]
|
|
1637
|
+
ev = [[e['event_id'], e['time'][:16].replace('T', ' '), f"{e['magnitude']:.1f}", _fmt(e['distance_km']), _fmt(e.get('depth_km')),
|
|
1638
|
+
('yes' if e['after_plan'] else 'no'), ('exempt' if e['exempt'] else ('counts' if (e['at_or_above'] and e['after_plan']) else '-'))]
|
|
1639
|
+
for e in se['events'] if e['at_or_above'] or e is se['events'][-1]]
|
|
1640
|
+
secs.append(Section(str(n), 'Seismicity against the plan', paras,
|
|
1641
|
+
[Table(['Event', 'Origin (UTC)', 'M', 'Distance (km)', 'Depth (km)', 'After plan date', 'Standing'], ev,
|
|
1642
|
+
'Events at or above the threshold inside the area (and the latest event)')]))
|
|
1643
|
+
n += 1
|
|
1644
|
+
else:
|
|
1645
|
+
secs.append(Section(str(n), 'Seismicity against the plan', ['No catalogue export was handed in. This section is empty and the goal clock is not stated.']))
|
|
1646
|
+
n += 1
|
|
1647
|
+
secs.append(Section(str(n), 'The schedule', ['Checkpoints with Commission staff from the plan date, and the goal date.'],
|
|
1648
|
+
[Table(['#', 'Date', 'What'], [[c['n'], c['date'], c['what']] for c in pk['checkpoints']])]))
|
|
1649
|
+
n += 1
|
|
1650
|
+
secs.append(Section(str(n), 'What this packet is missing', pk['gaps'] or ['Nothing: every declaration is present and every parameter is recorded.']))
|
|
1651
|
+
secs.append(Section(str(n + 1), 'What this packet does not call a measurement',
|
|
1652
|
+
[x.rstrip('.') + '.' for x in dict.fromkeys(pk.get('not_a_measurement') or [])]))
|
|
1653
|
+
front = [['Report ID', report_id], ['Area', s['name']], ['Site', site.get('display') or '-'], ['Client', site.get('client') or '-'],
|
|
1654
|
+
['Generated (UTC)', now.strftime('%Y-%m-%dT%H:%M:%SZ')],
|
|
1655
|
+
['Program', f'{PROGRAM_NAME}' + (f' build {program_version}' if program_version else '')],
|
|
1656
|
+
['Result', f"{pk['status']}: {m['n_wells_inside']} WELL(S) INSIDE, " + (f"RESPONSE {se['trigger']['status']}, GOAL {se['goal']['status']}" if se else 'NO CATALOGUE')
|
|
1657
|
+
+ (f", {len(pk['gaps'])} GAP(S)" if pk['gaps'] else '')]]
|
|
1658
|
+
return Document(title=f'{PROGRAM_NAME} - Seismicity Response Area Packet', report_id=report_id, front=front, sections=secs,
|
|
1659
|
+
footer=('Built to the operator-led response plans the Railroad Commission of Texas has on file and its December 2023 Notice to '
|
|
1660
|
+
'Operators on disposal-well monitoring in the Permian Basin. The daily export carries the Notice\'s parameter names; '
|
|
1661
|
+
'the mapping onto the TexNet reporting tool\'s template is the operator\'s step.'),
|
|
1662
|
+
data={'packet': pk, 'report_id': report_id, 'evaluated_at_utc': now.strftime('%Y-%m-%dT%H:%M:%SZ'), '_records': [], '_catalogue_obj': None})
|
|
1663
|
+
|
|
1664
|
+
|
|
1587
1665
|
def forbidden_terms(text: str) -> List[str]:
|
|
1588
1666
|
low = text.lower()
|
|
1589
1667
|
return [t for t in FORBIDDEN_TERMS if t in low]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# The Seismicity Response Area
|
|
2
|
+
|
|
3
|
+
> What an operator inside a Railroad Commission Seismicity Response Area puts in front of the Commission, built from what the site holds.
|
|
4
|
+
|
|
5
|
+
**The command**: declare each disposal well once - `gea workspace --path C:\site --action set-well --well <id> --lat LAT --lon LON --datum NAD27 --api 42-329-12345 --uic 123456 --depth-tier deep --formation "completed below the base of the Wolfcamp" --channel-pressure P_surf_psi --channel-rate Q_bpm --rate-unit bbl/min --bhp-method probe --channel-bhp P_bh_psi`; define the area on the site once - `--action sra-define --site <id> --name "Gardendale SRA" --lat 31.95 --lon -102.25 --datum WGS84 --radius-km 9.08 --plan-date 2022-12-16 --triggering-event texnet2022yxab`; then every month - `--action sra-report --site <id> --catalog texnet_export.csv [--aftershocks id1 id2] [--start 2026-03-01 --end 2026-03-31]`. `gea sra` runs the self-test on a labelled scene.
|
|
6
|
+
|
|
7
|
+
**What it writes**: `reports/sites/<site>/sra_packet.html` (and `.md`, `.json`) - the area and the plan as declared, what is inside it with each well's depth tier, the daily record per well rolled up by month, the seismicity against the plan, the checkpoint schedule, and every gap by name; and `sra_daily_export.csv` - one line per well per day under the Notice's own parameter names (maximum surface injection pressure, average surface injection pressure, injection volume, maximum injection rate), the bottomhole pressure and its method, and the well's API and UIC numbers. Every declaration and every packet is in `records/audit.jsonl`.
|
|
8
|
+
|
|
9
|
+
**The number to check**: `Response:` in section "Seismicity against the plan" - TRIGGERED names the event, its magnitude and the deadline for the response group's meeting; NOT TRIGGERED names why. `Goal:` gives the days without a threshold event and the date the plan's goal falls on. The packet's front says COMPLETE or INCOMPLETE with the count of gaps.
|
|
10
|
+
|
|
11
|
+
**What this page will not call a measurement**: which earthquakes are aftershocks - the operator declares them with `--aftershocks` and they are shown as exempt, never decided here. A volume read off a pressure gauge - a well with no rate channel has no volume and no maximum rate, and the packet says NOT RECORDED. A depth tier from depth alone - the tier is stratigraphic, above or below the named formation base, and is declared. The TexNet reporting tool's own upload template - the export carries the Notice's parameter names and the mapping is the operator's step. A catalogue nobody handed in - the catalogue is a file the operator exports from TexNet, and its export date is on the packet.
|
|
12
|
+
|
|
13
|
+
The shape is the Commission's: the operator-led response plans on file (Gardendale, Northern Culberson-Reeves, Stanton) - a circle of about 9 km, a plan written against M 3.5, an 18-month goal, a response group meeting within 48 hours of a threshold event, quarterly checkpoints with Commission staff, and disposal wells in two tiers by the base of the Wolfcamp - and the December 2023 Notice to Operators on disposal-well monitoring and reporting in the Permian Basin, which names the four daily parameters and the three bottomhole-pressure methods. A downhole gauge this program monitors is the Notice's permanent probe.
|
|
14
|
+
|
|
15
|
+
Related: `gea help sites`, `gea help seismic`, `gea help data-in`.
|
|
@@ -38,6 +38,7 @@ INDEX: List[tuple] = [
|
|
|
38
38
|
('patches', 'Patches', 'supervised live connections, their states and records'),
|
|
39
39
|
('files', 'Files', 'import and export roots, detection by content, the evidence pack'),
|
|
40
40
|
('notifications', 'Notifications', 'rules, channels, the delivery log'),
|
|
41
|
+
('sra', 'The Seismicity Response Area', 'the packet an operator inside an SRA puts in front of the Commission'),
|
|
41
42
|
('doctor', 'Which code is running', 'gea doctor and the serve start-up gate'),
|
|
42
43
|
('audit-update', 'Audit / Update', 'the whole audit log with filters, the program against PyPI, every report against its source'),
|
|
43
44
|
('upkeep', 'Upkeep', 'housekeeping, sessions, the TLS proxy, the load test'),
|
|
@@ -62,17 +62,24 @@ class JobRunner:
|
|
|
62
62
|
def _dir(self, job_id: str) -> str:
|
|
63
63
|
return os.path.join(self.ws.jobs_dir, job_id)
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _read_json(p: str, attempts: int = 40) -> dict:
|
|
67
|
+
"""Read a job file that a writer may be mid-replace on. The file is never half-written (see _write), but
|
|
68
|
+
on Windows a read during os.replace is refused with PermissionError, and the v0.7.0 kit run #8 and the
|
|
69
|
+
v0.9.0 ship both fell over on exactly that - the first in _read, the second in list(), which had its own
|
|
70
|
+
open() and none of this. Every read of a job file goes through here now."""
|
|
71
|
+
for attempt in range(attempts):
|
|
68
72
|
try:
|
|
69
73
|
with open(p, encoding='utf-8') as f:
|
|
70
74
|
return json.load(f)
|
|
71
75
|
except (json.JSONDecodeError, FileNotFoundError, PermissionError):
|
|
72
|
-
time.sleep(0.05)
|
|
76
|
+
time.sleep(0.05)
|
|
73
77
|
with open(p, encoding='utf-8') as f:
|
|
74
78
|
return json.load(f)
|
|
75
79
|
|
|
80
|
+
def _read(self, job_id: str) -> dict:
|
|
81
|
+
return self._read_json(os.path.join(self._dir(job_id), 'job.json'))
|
|
82
|
+
|
|
76
83
|
def _write(self, job: dict) -> None:
|
|
77
84
|
p = os.path.join(self._dir(job['id']), 'job.json')
|
|
78
85
|
tmp = p + '.tmp'
|
|
@@ -119,8 +126,10 @@ class JobRunner:
|
|
|
119
126
|
for name in sorted(os.listdir(self.ws.jobs_dir), reverse=True):
|
|
120
127
|
p = os.path.join(self.ws.jobs_dir, name, 'job.json')
|
|
121
128
|
if os.path.isfile(p):
|
|
122
|
-
|
|
123
|
-
j =
|
|
129
|
+
try:
|
|
130
|
+
j = self._read_json(p)
|
|
131
|
+
except (OSError, json.JSONDecodeError):
|
|
132
|
+
continue # a job whose file cannot be read right now is left out of this listing, not a crash
|
|
124
133
|
if status is None or j['status'] == status:
|
|
125
134
|
out.append(j)
|
|
126
135
|
if len(out) >= limit:
|