gea-program 0.38.0__tar.gz → 0.40.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.38.0/gea_program.egg-info → gea_program-0.40.0}/PKG-INFO +25 -1
- {gea_program-0.38.0 → gea_program-0.40.0}/README.md +24 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/__init__.py +1 -1
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/__main__.py +70 -2
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/acceptance_tests.py +312 -0
- gea_program-0.40.0/gea/drill_string.py +631 -0
- gea_program-0.40.0/gea/help/drillstring.md +11 -0
- gea_program-0.40.0/gea/help/simulation.md +11 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/helplib.py +3 -1
- gea_program-0.40.0/gea/reference/mud_motor_specs.json +1000 -0
- gea_program-0.40.0/gea/reference/volve_f4_interpretation.las.gz +0 -0
- gea_program-0.40.0/gea/reference/volve_f4_logs.provenance.json +46 -0
- gea_program-0.40.0/gea/reference/volve_f4_lwd.las.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/service.py +31 -0
- gea_program-0.40.0/gea/simulation.py +424 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/tool_catalog.py +6 -1
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/web/app.html +348 -12
- {gea_program-0.38.0 → gea_program-0.40.0/gea_program.egg-info}/PKG-INFO +25 -1
- {gea_program-0.38.0 → gea_program-0.40.0}/gea_program.egg-info/SOURCES.txt +8 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/pyproject.toml +1 -1
- {gea_program-0.38.0 → gea_program-0.40.0}/LICENSE +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/backup.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/bench.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/blind_harness.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog_map.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/catalog_register.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/certificates.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/cli.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/client_reports.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/config_versioning.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/conformance.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/control_room.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/correlation.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/dashboard.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/deviation.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/dlis.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/dnp3.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/doctor.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/earth_model.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/economics.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/edr.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/elevation.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/etp.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/example_register_map.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/fat_sat.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/files.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/follower.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/forward_model.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/friction_priors.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/gamma.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/geodesy.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/hartip.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/alarms.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/backup.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/catalog.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/charts.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/conformance.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/control-room.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/data-in.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/dlis.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/doctor.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/drift.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/economics.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/edr.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/etp.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/files.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/historian.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/instruments.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/interval.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/map.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/month-end.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/notifications.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/osdu.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/patches.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/quality.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/seismic.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/setup.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/site.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/sites.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/slip.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/soak.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/sra.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/start.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/structure.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/tools.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/transients.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/vibration.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/well-report.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/help/well3d.md +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/housekeeping.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/interval.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/jobs.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/live_ports.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/loadtest.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/modbus.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/model_card.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/month_end.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/notify.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/opcua_port.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/operator_app.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/osdu.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/patches.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/permits.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/piwebapi.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/ports.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/ppdm.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/project.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/quakeml.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reconciler.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/eia_prices.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_3d.provenance.json +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_surfaces.json.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/sample_record.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/sbom.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seedlink.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/segy.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_array.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_film.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_response.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_track.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/series_cache.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/service_life.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/shapefile.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/shell.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/shut_in.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/site_map.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/sla_report.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/slip.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/soak.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/sra.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/store_forward.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/strata_join.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/structure.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/supervisor.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/survey_view.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/telemetry.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/tool_library.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/transient.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/vibration.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/well3d.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/well_assembler.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/well_report.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/wits0.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/witsml.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea/workspace.py +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.38.0 → gea_program-0.40.0}/setup.cfg +0 -0
- {gea_program-0.38.0 → gea_program-0.40.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.40.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
|
|
@@ -648,6 +648,30 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
|
|
|
648
648
|
itself; `gea help seedlink`. A TexNet station is served by EarthScope at
|
|
649
649
|
`rtserve.iris.washington.edu:18000`, network `TX`.
|
|
650
650
|
|
|
651
|
+
### Drill string
|
|
652
|
+
|
|
653
|
+
The Drill String tab holds each run's string from the bit up - drawn as a
|
|
654
|
+
build diagram - and follows every component's hours, footage and, for a
|
|
655
|
+
motor, revolutions from the drilling record across runs and wells against
|
|
656
|
+
its declared service interval. The downhole motor tracker charts the
|
|
657
|
+
differential against the maker's limits, the stalls, the motor's and the
|
|
658
|
+
bit's speed and a torque estimate, and judges the motor's stability from
|
|
659
|
+
the slow oscillations of the torque and the differential, block by block.
|
|
660
|
+
The tool catalogue carries 41 downhole motors with the maker's figures.
|
|
661
|
+
`gea drillstring`, `gea tools --action motors`, `gea help drillstring`.
|
|
662
|
+
|
|
663
|
+
### Simulation
|
|
664
|
+
|
|
665
|
+
The Simulation tab drills a well again on the screen: the bit runs down its
|
|
666
|
+
path in 3D among the mapped formations, on the clock it was drilled by,
|
|
667
|
+
while a mud-log panel beside it grows with the hole - the formations, the
|
|
668
|
+
rock and fluid volumes, the logs and the drilling parameters - and a
|
|
669
|
+
readout says what the record carries at the bit. Volve 15/9-F-4 ships with
|
|
670
|
+
its logging-while-drilling record and Schlumberger's interpretation of it
|
|
671
|
+
(mineral volumes, porosity, water saturation, hydrocarbon volume); every
|
|
672
|
+
well with a drilling record plays on its recorder's own clock.
|
|
673
|
+
`gea simulation`, `gea help simulation`.
|
|
674
|
+
|
|
651
675
|
### The client well report
|
|
652
676
|
|
|
653
677
|
`gea well-report` writes one printable document per well and one for the
|
|
@@ -609,6 +609,30 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
|
|
|
609
609
|
itself; `gea help seedlink`. A TexNet station is served by EarthScope at
|
|
610
610
|
`rtserve.iris.washington.edu:18000`, network `TX`.
|
|
611
611
|
|
|
612
|
+
### Drill string
|
|
613
|
+
|
|
614
|
+
The Drill String tab holds each run's string from the bit up - drawn as a
|
|
615
|
+
build diagram - and follows every component's hours, footage and, for a
|
|
616
|
+
motor, revolutions from the drilling record across runs and wells against
|
|
617
|
+
its declared service interval. The downhole motor tracker charts the
|
|
618
|
+
differential against the maker's limits, the stalls, the motor's and the
|
|
619
|
+
bit's speed and a torque estimate, and judges the motor's stability from
|
|
620
|
+
the slow oscillations of the torque and the differential, block by block.
|
|
621
|
+
The tool catalogue carries 41 downhole motors with the maker's figures.
|
|
622
|
+
`gea drillstring`, `gea tools --action motors`, `gea help drillstring`.
|
|
623
|
+
|
|
624
|
+
### Simulation
|
|
625
|
+
|
|
626
|
+
The Simulation tab drills a well again on the screen: the bit runs down its
|
|
627
|
+
path in 3D among the mapped formations, on the clock it was drilled by,
|
|
628
|
+
while a mud-log panel beside it grows with the hole - the formations, the
|
|
629
|
+
rock and fluid volumes, the logs and the drilling parameters - and a
|
|
630
|
+
readout says what the record carries at the bit. Volve 15/9-F-4 ships with
|
|
631
|
+
its logging-while-drilling record and Schlumberger's interpretation of it
|
|
632
|
+
(mineral volumes, porosity, water saturation, hydrocarbon volume); every
|
|
633
|
+
well with a drilling record plays on its recorder's own clock.
|
|
634
|
+
`gea simulation`, `gea help simulation`.
|
|
635
|
+
|
|
612
636
|
### The client well report
|
|
613
637
|
|
|
614
638
|
`gea well-report` writes one printable document per well and one for the
|
|
@@ -285,8 +285,8 @@ def main(argv=None) -> int:
|
|
|
285
285
|
p_tl.add_argument("--station", type=str, action="append", default=None, help="a station as DEPTH_FT:TOOL, e.g. 9850:quartz_pt_geoq177_16k")
|
|
286
286
|
p_tl.add_argument("--surface-temp-f", type=float, default=75.0); p_tl.add_argument("--temp-gradient-f-ft", type=float, default=0.018)
|
|
287
287
|
p_tl.add_argument("--surface-pressure-psi", type=float, default=14.7); p_tl.add_argument("--pressure-gradient-psi-ft", type=float, default=0.465)
|
|
288
|
-
p_tl.add_argument("--action", choices=["classes", "tutorial", "fleet", "fleet-add", "fleet-remove"], default=None,
|
|
289
|
-
help="the downhole tool classes in use across the industry: classes (--category, --find), tutorial --class ID,
|
|
288
|
+
p_tl.add_argument("--action", choices=["classes", "tutorial", "fleet", "fleet-add", "fleet-remove", "motors"], default=None,
|
|
289
|
+
help="the downhole tool classes in use across the industry: classes (--category, --find), tutorial --class ID, the workspace's own fleet (fleet, fleet-add, fleet-remove), and the downhole motor specifications (motors)")
|
|
290
290
|
p_tl.add_argument("--category", type=str, default=None, help="classes: one category (bits, drive, measure, bha, casing, completion, logging, fishing, testing)")
|
|
291
291
|
p_tl.add_argument("--find", type=str, default="", help="classes: words to look for in the name and description")
|
|
292
292
|
p_tl.add_argument("--class", dest="class_id", type=str, default=None, help="tutorial / fleet-add: the tool class id, e.g. pdm_motor")
|
|
@@ -516,6 +516,20 @@ def main(argv=None) -> int:
|
|
|
516
516
|
p_3d.add_argument("--workspace", type=str, default=None)
|
|
517
517
|
p_3d.add_argument("--json", action="store_true")
|
|
518
518
|
|
|
519
|
+
p_ds = sub.add_parser("drillstring", help="the drill string: each declared run's components from the bit up, their usage from the drilling record and their service life; the downhole motor's statistics and the stability of its oscillations")
|
|
520
|
+
p_ds.add_argument("--workspace", type=str, required=True)
|
|
521
|
+
p_ds.add_argument("--well", type=str, default=None)
|
|
522
|
+
p_ds.add_argument("--declare", type=str, default=None, help="a JSON file declaring a run: {well_id, name, start, end, components:[{class_id, maker, model, serial, od_in, length_ft, motor_id | motor{...}, service_limit, service_basis, service_source}]}")
|
|
523
|
+
p_ds.add_argument("--remove", type=str, default=None, help="a run id to remove")
|
|
524
|
+
p_ds.add_argument("--actor", type=str, default="cli")
|
|
525
|
+
p_ds.add_argument("--json", action="store_true")
|
|
526
|
+
|
|
527
|
+
p_sm = sub.add_parser("simulation", help="the simulation in words: the scenes (Volve 15/9-F-4 with its logs and interpretation, Volve F-5 and F-10 on the survey clock, every drilling-recorder well), and what the record says at a depth (the Simulation tab replays it)")
|
|
528
|
+
p_sm.add_argument("--workspace", type=str, default=None)
|
|
529
|
+
p_sm.add_argument("--scene", type=str, default=None, help="e.g. volve-f4, or well:Well-30636033")
|
|
530
|
+
p_sm.add_argument("--at", type=float, default=None, help="a measured depth, in the scene's unit (m for Volve, ft for a workspace well)")
|
|
531
|
+
p_sm.add_argument("--json", action="store_true")
|
|
532
|
+
|
|
519
533
|
p_wl = sub.add_parser("wall", help="the control-room board as text: a line per well, worst first, its state and why (the same board the wall screen shows)")
|
|
520
534
|
p_wl.add_argument("--workspace", type=str, default=None, help="the workspace folder, e.g. C:\\site")
|
|
521
535
|
p_wl.add_argument("--json", action="store_true")
|
|
@@ -2035,6 +2049,56 @@ def main(argv=None) -> int:
|
|
|
2035
2049
|
r = _WR.write_pad(ws, __version__, actor=a.actor)
|
|
2036
2050
|
print(f"pad: {r['paths']['html']}")
|
|
2037
2051
|
return 0
|
|
2052
|
+
elif a.cmd == "drillstring":
|
|
2053
|
+
import json as _json
|
|
2054
|
+
from . import drill_string as _DS
|
|
2055
|
+
from .cli import _safe_stdout
|
|
2056
|
+
from .workspace import Workspace, WorkspaceError
|
|
2057
|
+
_safe_stdout()
|
|
2058
|
+
try:
|
|
2059
|
+
ws = Workspace(a.workspace)
|
|
2060
|
+
except WorkspaceError as e:
|
|
2061
|
+
raise SystemExit(f"drillstring: {e}")
|
|
2062
|
+
try:
|
|
2063
|
+
if a.declare:
|
|
2064
|
+
with open(a.declare, encoding="utf-8") as f:
|
|
2065
|
+
d = _json.load(f)
|
|
2066
|
+
r = _DS.save_run(ws, a.actor, d.get("well_id", ""), d.get("name", ""), d.get("components") or [], d.get("start"), d.get("end"),
|
|
2067
|
+
d.get("depth_in_ft"), d.get("depth_out_ft"), d.get("note", ""), d.get("run_id"))
|
|
2068
|
+
print(f"declared {r['id']}: {r['name']} on {r['well_id']}, {len(r['components'])} component(s)")
|
|
2069
|
+
return 0
|
|
2070
|
+
if a.remove:
|
|
2071
|
+
_DS.remove_run(ws, a.actor, a.remove); print(f"removed {a.remove}"); return 0
|
|
2072
|
+
p = _DS.page(ws, a.well)
|
|
2073
|
+
except (ValueError, KeyError, OSError, WorkspaceError) as e:
|
|
2074
|
+
raise SystemExit(f"drillstring: {e.args[0] if e.args else e}")
|
|
2075
|
+
print(_json.dumps(p, default=str) if a.json else _DS.text(p))
|
|
2076
|
+
return 0
|
|
2077
|
+
elif a.cmd == "simulation":
|
|
2078
|
+
import json as _json
|
|
2079
|
+
from . import simulation as _SIM
|
|
2080
|
+
from .cli import _safe_stdout
|
|
2081
|
+
from .workspace import Workspace, WorkspaceError
|
|
2082
|
+
_safe_stdout()
|
|
2083
|
+
ws = None
|
|
2084
|
+
if a.workspace:
|
|
2085
|
+
try:
|
|
2086
|
+
ws = Workspace(a.workspace)
|
|
2087
|
+
except WorkspaceError as e:
|
|
2088
|
+
raise SystemExit(f"simulation: {e}")
|
|
2089
|
+
if not a.scene:
|
|
2090
|
+
for s_ in _SIM.scenes(ws):
|
|
2091
|
+
print(f"{s_['id']:<28} {s_['title']} ({s_['note']})")
|
|
2092
|
+
return 0
|
|
2093
|
+
try:
|
|
2094
|
+
sim = _SIM.scene(ws, a.scene)
|
|
2095
|
+
except KeyError as e:
|
|
2096
|
+
raise SystemExit(f"simulation: {e.args[0] if e.args else e}")
|
|
2097
|
+
if a.json:
|
|
2098
|
+
print(_json.dumps(sim if a.at is None else _SIM.at(sim, a.at * (0.3048 if sim.get('depth_unit') == 'ft' else 1.0)), default=str))
|
|
2099
|
+
else:
|
|
2100
|
+
print(_SIM.text(sim, a.at))
|
|
2101
|
+
return 0
|
|
2038
2102
|
elif a.cmd in ("trajectory", "well3d"):
|
|
2039
2103
|
import json as _json
|
|
2040
2104
|
from . import well3d as _W3
|
|
@@ -2610,6 +2674,10 @@ def main(argv=None) -> int:
|
|
|
2610
2674
|
from . import tool_taxonomy as _TX
|
|
2611
2675
|
from .cli import _safe_stdout
|
|
2612
2676
|
_safe_stdout() # the cited figures carry fractions and degree signs
|
|
2677
|
+
if a.action == "motors":
|
|
2678
|
+
from . import drill_string as _DS
|
|
2679
|
+
print(_json.dumps(_DS.catalogue(), indent=1) if a.json else _DS.motor_text())
|
|
2680
|
+
return 0
|
|
2613
2681
|
if a.action == "classes":
|
|
2614
2682
|
cs = _TX.classes(a.category, a.find)
|
|
2615
2683
|
if a.category and a.category not in _TX.CATEGORY_NAMES:
|
|
@@ -9137,6 +9137,316 @@ def section_cg_well_report(tmp: str) -> None:
|
|
|
9137
9137
|
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
9138
9138
|
|
|
9139
9139
|
|
|
9140
|
+
def section_ch_simulation(tmp: str) -> None:
|
|
9141
|
+
"""Section CH - the simulation: Volve 15/9-F-4's logs and their interpretation shipped verbatim and checked against
|
|
9142
|
+
themselves, read back at a depth to the value in the file; the bit on the clock each record carries (stepped along
|
|
9143
|
+
measured depth where none was recorded, on the survey stations' times, on a drilling recorder's own clock) with the
|
|
9144
|
+
hole never behind the bit; a workspace well's path from its survey or from the recorder's MD-TVD pairs; the API, the
|
|
9145
|
+
CLI, the tab and its help page."""
|
|
9146
|
+
import gzip as _gz
|
|
9147
|
+
import hashlib as _hl
|
|
9148
|
+
import http.cookiejar
|
|
9149
|
+
import subprocess as _sp
|
|
9150
|
+
import tempfile as _tf
|
|
9151
|
+
import urllib.request
|
|
9152
|
+
import urllib.error
|
|
9153
|
+
from . import edr as E
|
|
9154
|
+
from . import helplib as H
|
|
9155
|
+
from . import ports as PT
|
|
9156
|
+
from . import simulation as SIM
|
|
9157
|
+
from . import well3d as W
|
|
9158
|
+
from .workspace import Workspace
|
|
9159
|
+
from .service import Service, Users
|
|
9160
|
+
pkg = Path(__file__).parent
|
|
9161
|
+
d = Path(tmp, "ch"); d.mkdir()
|
|
9162
|
+
prov = json.loads((pkg / "reference" / "volve_f4_logs.provenance.json").read_text(encoding="utf-8"))
|
|
9163
|
+
hashes = all(_hl.sha256((pkg / "reference" / f["file"]).read_bytes()).hexdigest() == f["sha256"]
|
|
9164
|
+
and _hl.sha256(_gz.decompress((pkg / "reference" / f["file"]).read_bytes())).hexdigest() == f["sha256_uncompressed"]
|
|
9165
|
+
and len(f["source"]["commit"]) == 40 for f in prov["files"].values())
|
|
9166
|
+
files = {}
|
|
9167
|
+
for k, f in prov["files"].items():
|
|
9168
|
+
p = d / (k + ".las"); p.write_bytes(_gz.decompress((pkg / "reference" / f["file"]).read_bytes())); files[k] = PT.read_las(str(p))
|
|
9169
|
+
itp, lwd = files["interpretation"], files["lwd"]
|
|
9170
|
+
|
|
9171
|
+
def ch(st, n):
|
|
9172
|
+
v = np.asarray(st.channels[n].values, dtype=float).copy(); v[v <= -999] = np.nan; return v
|
|
9173
|
+
mins = sum(np.nan_to_num(ch(itp, n)) for n in ("VQFM", "VCLA", "VCLC", "VDOL", "VPYR", "VSID", "VANH", "VEVA", "VCOA"))
|
|
9174
|
+
phit, swt, vuhy = ch(itp, "PHIT"), ch(itp, "SWT"), ch(itp, "VUHY")
|
|
9175
|
+
sim = SIM.scene(None, "volve-f4")
|
|
9176
|
+
col = sim["columns"][0]
|
|
9177
|
+
sums = [sum(x or 0 for x in r) for r in col["f"]]
|
|
9178
|
+
a = SIM.at(sim, 3300.0)
|
|
9179
|
+
md_l, gr = np.asarray(lwd.index, dtype=float), ch(lwd, "GRMA")
|
|
9180
|
+
gr_hand = float(np.interp(3300.0, md_l, gr))
|
|
9181
|
+
md_i = np.asarray(itp.index, dtype=float)
|
|
9182
|
+
rp = sim["scene"]["wells"][0]["replay"]
|
|
9183
|
+
c1 = {
|
|
9184
|
+
"shipped": hashes and "may not be sold" in prov["license"] and "Equinor" in prov["attribution"] and len(lwd.index) == 3636 and len(itp.index) == 3531
|
|
9185
|
+
and abs(float(lwd.index[0]) - 2955.95) < 1e-6,
|
|
9186
|
+
"self_consistent": float(np.nanmax(np.abs(mins + phit - 1))) < 2e-3 and float(np.nanmax(np.abs(vuhy - phit * (1 - swt)))) < 1e-3,
|
|
9187
|
+
"volumes": col["parts"][:2] == ["Water (in the pores)", "Hydrocarbon (in the pores)"] and min(sums) > 0.995 and max(sums) < 1.005
|
|
9188
|
+
and abs(a["columns"]["Rock and fluid volumes"]["Hydrocarbon (in the pores)"] - float(vuhy[int(np.argmin(np.abs(md_i - 3300.0)))])) < 2e-3,
|
|
9189
|
+
"read_back": abs(a["tracks"]["Gamma ray"]["value"] - gr_hand) < 0.01 * max(1.0, abs(gr_hand)) and a["formation"] == "Hugin Fm. VOLVE"
|
|
9190
|
+
and {"gr", "rhob", "nphi", "rt", "phit", "sw", "k"} <= {t["id"] for t in sim["tracks"]},
|
|
9191
|
+
"depth_clock": sim["clock"]["kind"] == "measured depth" and len(rp["md"]) == SIM.DEPTH_STEPS and rp["md"][0] == 0.0 and abs(rp["md"][-1] - 3510.0) < 0.01
|
|
9192
|
+
and all(t is None for t in rp["t"]) and "no drilling times" in sim["clock"]["note"],
|
|
9193
|
+
}
|
|
9194
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
9195
|
+
ok(not failed1,
|
|
9196
|
+
"CH1 Volve 15/9-F-4's logging-while-drilling record and Schlumberger's interpretation of it shipped verbatim with their hashes, the "
|
|
9197
|
+
"licence and the attribution; the interpretation's mineral volumes and total porosity summing to one and its hydrocarbon volume "
|
|
9198
|
+
"equal to the porosity times one less the water saturation; the volume column summing to one; the gamma ray read back at 3,300 m as "
|
|
9199
|
+
"the file has it, in the Hugin formation; the bit stepped along measured depth because the survey carries no drilling times"
|
|
9200
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
9201
|
+
|
|
9202
|
+
f5 = SIM.scene(None, "volve-f5"); f5w = next(w for w in W.volve_scene()["wells"] if w["id"] == "F-5")
|
|
9203
|
+
r5 = f5["scene"]["wells"][0]["replay"]
|
|
9204
|
+
ev = E.selftest_events(str(d))
|
|
9205
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
9206
|
+
wb = ws.add_well_file(ev["path"], display="Well B", actor="tester"); wc = ws.add_well_file(ev["path"], display="Well C", actor="tester")
|
|
9207
|
+
ws.set_well(wb["id"], surface={"lat": 31.42, "lon": -104.10, "datum": "NAD83", "elevation": {"ft": 3000.0, "datum": "KB", "source": "test plat"}}, actor="tester")
|
|
9208
|
+
csv_ok = "MD (ft),Inc (deg),Azi (deg)\n0,0,0\n1000,0,0\n2000,10,90\n3000,30,90\n4000,45,95\n5750,60,100\n"
|
|
9209
|
+
W.attach_trajectory(ws, wb["id"], "s.csv", csv_ok.encode(), "tester", "true", "ft", None, None, None, "test survey")
|
|
9210
|
+
ws.refresh_dashboard(actor="tester")
|
|
9211
|
+
rec = json.loads(Path(ws.reports_dir, "wells", wc["id"], "drilling_record.json").read_text(encoding="utf-8"))
|
|
9212
|
+
not_built = None
|
|
9213
|
+
import shutil as _sh
|
|
9214
|
+
_sh.rmtree(Path(wsp, "cache", "series", wc["id"]), ignore_errors=True) # no chart cache yet: the read-only path says so
|
|
9215
|
+
try:
|
|
9216
|
+
SIM.scene(ws, "well:" + wc["id"], build_cache=False)
|
|
9217
|
+
except KeyError as e:
|
|
9218
|
+
not_built = str(e)
|
|
9219
|
+
sb, sc_ = SIM.scene(ws, "well:" + wb["id"]), SIM.scene(ws, "well:" + wc["id"])
|
|
9220
|
+
rc = sc_["scene"]["wells"][0]["replay"]
|
|
9221
|
+
holes = [h for h in rc["hole"] if h is not None]
|
|
9222
|
+
bad = None
|
|
9223
|
+
try:
|
|
9224
|
+
SIM.scene(ws, "nope")
|
|
9225
|
+
except KeyError as e:
|
|
9226
|
+
bad = str(e)
|
|
9227
|
+
pc = sc_["scene"]["wells"][0]["path"]
|
|
9228
|
+
mt = rec["md_tvd"]
|
|
9229
|
+
c2 = {
|
|
9230
|
+
"survey_clock": f5["clock"]["kind"] == "recorded" and r5["t"] == f5w["replay"]["t"] and r5["md"] == f5w["replay"]["md"]
|
|
9231
|
+
and all(b >= a_ for a_, b in zip(r5["hole"], r5["hole"][1:])) and all(h >= m - 1e-6 for h, m in zip(r5["hole"], r5["md"])),
|
|
9232
|
+
"recorder_clock": sc_["clock"]["kind"] == "recorded" and rc["t"][0].startswith("2026-07-01") and not rc["t"][0].endswith("Z")
|
|
9233
|
+
and abs(max(holes) - rec["total_depth_md_ft"] * 0.3048) < 0.5 and all(b >= a_ - 1e-6 for a_, b in zip(holes, holes[1:]))
|
|
9234
|
+
and not_built is not None and "chart cache" in not_built,
|
|
9235
|
+
"tracks": {t["name"] for t in sc_["tracks"]} >= {"Gamma", "Rate of penetration"} and sc_["depth_unit"] == "ft"
|
|
9236
|
+
and any("density" in n for n in sc_["not_measured"]),
|
|
9237
|
+
"paths": sb["path_how"].startswith("the directional survey") and sb["scene"]["z_ref"] == "sea level"
|
|
9238
|
+
and sc_["path_how"].startswith("the drilling recorder's own MD-TVD pairs") and sc_["scene"]["z_ref"].startswith("the rig floor")
|
|
9239
|
+
and (mt.get("status") != "OK" or abs(pc["z"][pc["md"].index(next(m for m in pc["md"] if abs(m - mt["rows"][-1]["md_ft"] * 0.3048) < 0.01))] - mt["rows"][-1]["tvd_ft"] * 0.3048) < 0.01)
|
|
9240
|
+
and all(y == 0.0 for y in pc["y"]) and sc_["scene"]["no_north"] is True and sb["scene"]["no_north"] is False,
|
|
9241
|
+
"unknown": bad is not None and "nope" in bad,
|
|
9242
|
+
}
|
|
9243
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
9244
|
+
ok(not failed2,
|
|
9245
|
+
"CH2 the clock: 15/9-F-5's bit on its survey stations' own times as the 3D tab replays them; a drilling-recorder well's bit and "
|
|
9246
|
+
"bottom of the hole on the recorder's own clock (not called UTC), the hole never behind the bit and reaching the record's total "
|
|
9247
|
+
"depth; its channels by bit depth, and density, porosity and fluids named as not measured; its path from the attached survey, or "
|
|
9248
|
+
"from the recorder's MD-TVD pairs in one vertical plane below the rig floor when nothing places it; an unknown scene named"
|
|
9249
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
9250
|
+
|
|
9251
|
+
Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer")
|
|
9252
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
9253
|
+
base_url = svc.url.rstrip("/")
|
|
9254
|
+
o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
9255
|
+
rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": "v", "password": "viewer-pass-1"}).encode())
|
|
9256
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
|
|
9257
|
+
|
|
9258
|
+
def getj(path):
|
|
9259
|
+
try:
|
|
9260
|
+
with o.open(base_url + path, timeout=120) as r_:
|
|
9261
|
+
return r_.status, json.loads(r_.read())
|
|
9262
|
+
except urllib.error.HTTPError as e:
|
|
9263
|
+
return e.code, json.loads(e.read())
|
|
9264
|
+
try:
|
|
9265
|
+
s_l, li = getj("/api/sim/scenes")
|
|
9266
|
+
s_f, jf = getj("/api/sim/scene/volve-f4")
|
|
9267
|
+
s_w, jw = getj("/api/sim/scene/well%3A" + wb["id"])
|
|
9268
|
+
s_n, _ = getj("/api/sim/scene/nope")
|
|
9269
|
+
finally:
|
|
9270
|
+
svc.stop()
|
|
9271
|
+
cli = _sp.run([sys.executable, "-m", "gea", "simulation"], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
9272
|
+
cli_at = _sp.run([sys.executable, "-m", "gea", "simulation", "--scene", "volve-f4", "--at", "3300"], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
9273
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
9274
|
+
c3 = {
|
|
9275
|
+
"api": s_l == 200 and [x["id"] for x in li["scenes"]][:3] == ["volve-f4", "volve-f5", "volve-f10"] and "well:" + wb["id"] in [x["id"] for x in li["scenes"]]
|
|
9276
|
+
and s_f == 200 and jf["columns"][0]["name"] == "Rock and fluid volumes" and s_w == 200 and jw["well_id"] == wb["id"] and s_n == 404,
|
|
9277
|
+
"cli": cli.returncode == 0 and "volve-f4" in cli.stdout and cli_at.returncode == 0 and "in Hugin Fm. VOLVE" in cli_at.stdout and "Hydrocarbon (in the pores)" in cli_at.stdout,
|
|
9278
|
+
"page": all(x in page_src for x in ("VIEWS.simulation", "['#/simulation', 'Simulation']", "simPanel", "simSeek", "show the log below the bit", "uCut",
|
|
9279
|
+
"/api/sim/scene/", "a gap in the record", "W3.simBit")),
|
|
9280
|
+
"help": any(t[0] == "simulation" for t in H.INDEX) and not H.check("simulation") and H.VIEW_TOPIC.get("simulation") == "simulation",
|
|
9281
|
+
}
|
|
9282
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
9283
|
+
ok(not failed3,
|
|
9284
|
+
"CH3 the API (the scenes, a Volve well and a workspace well by name, an unknown one 404), the CLI (the list, and what the record says "
|
|
9285
|
+
"at a depth), the Simulation tab - the 3D view's own renderer with the hole drilled so far and the string inside it, the mud-log panel "
|
|
9286
|
+
"growing with the hole, the readout, a click taking the bit to the depth - and its help page"
|
|
9287
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
9288
|
+
|
|
9289
|
+
|
|
9290
|
+
def section_ci_drill_string(tmp: str) -> None:
|
|
9291
|
+
"""Section CI - the drill string: the downhole motor catalogue as the maker's sheets give it; a run declared from the
|
|
9292
|
+
bit up and refused when a part of it is unsourced or impossible; its usage, the motor's revolutions, speed, stalls and
|
|
9293
|
+
torque estimate equal to the hand sums over a labelled synthetic run; components followed across runs to their
|
|
9294
|
+
service interval; the stability rule finding a growing oscillation and a moved frequency and not calling red noise a
|
|
9295
|
+
peak; the API, the CLI, the tab and the help page."""
|
|
9296
|
+
import http.cookiejar
|
|
9297
|
+
import subprocess as _sp
|
|
9298
|
+
import urllib.request
|
|
9299
|
+
import urllib.error
|
|
9300
|
+
from datetime import datetime, timedelta
|
|
9301
|
+
from . import drill_string as DS
|
|
9302
|
+
from . import helplib as H
|
|
9303
|
+
from . import tool_catalog as TC
|
|
9304
|
+
from .workspace import Workspace
|
|
9305
|
+
from .service import Service, Users
|
|
9306
|
+
pkg = Path(__file__).parent
|
|
9307
|
+
d = Path(tmp, "ci"); d.mkdir()
|
|
9308
|
+
cat = DS.catalogue()
|
|
9309
|
+
ms = cat["motors"]
|
|
9310
|
+
m64, m89 = DS.motor_spec("msd-6-3-4-78-6.4"), DS.motor_spec("msd-8-3-8-89-4.0")
|
|
9311
|
+
c1 = {
|
|
9312
|
+
"catalogue": len(ms) == 41 and len({m["id"] for m in ms}) == 41 and cat["publisher"].startswith("MS Directional") and cat["url"].startswith("https://")
|
|
9313
|
+
and cat["fetched"] == "2026-10-17" and TC.catalog()["counts"]["motors"] == 41,
|
|
9314
|
+
"sane": all(m["flow_gpm"][0] < m["flow_gpm"][1] and 0 < m["rev_per_gal"] < 2 and m["max_operating_diff_psi"] <= m["max_diff_psi"]
|
|
9315
|
+
and m["stator_lobes"] == m["rotor_lobes"] + 1 and m["max_torque_ft_lb"] > 0 and m["od_in"] > 4 for m in ms),
|
|
9316
|
+
"as_printed": (m64["rev_per_gal"], m64["flow_gpm"], m64["max_operating_diff_psi"], m64["max_diff_psi"], m64["max_torque_ft_lb"]) == (0.28, [300, 600], 1000, 1820, 14360)
|
|
9317
|
+
and (m89["rev_per_gal"], m89["max_diff_psi"], m89["max_torque_ft_lb"], m89["max_operating_wob_lb"]) == (0.09, 1158, 32935, 70000)
|
|
9318
|
+
and "Motor Handbook" in m64["source"],
|
|
9319
|
+
}
|
|
9320
|
+
try:
|
|
9321
|
+
DS.motor_spec("nope"); c1["unknown"] = False
|
|
9322
|
+
except KeyError:
|
|
9323
|
+
c1["unknown"] = True
|
|
9324
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
9325
|
+
ok(not failed1,
|
|
9326
|
+
"CI1 the downhole motor catalogue: 41 power sections with the maker's flow range, revolutions per gallon, maximum operating and "
|
|
9327
|
+
"maximum differential, maximum torque and weight on bit as its spec sheets print them, the handbook and the date it was read; "
|
|
9328
|
+
"every lobe count one more on the stator than the rotor; counted in the tool catalogue; an unknown motor named"
|
|
9329
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
9330
|
+
|
|
9331
|
+
syn = DS.synthetic_motor_record(str(d))
|
|
9332
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
9333
|
+
w = ws.add_well_file(syn["path"], display="SYNTHETIC motor well", actor="tester")
|
|
9334
|
+
ws.refresh_dashboard(actor="tester")
|
|
9335
|
+
comps = [{"class_id": "pdc_bit", "maker": "Acme", "model": "X616", "serial": "B1", "od_in": 8.75, "length_ft": 1.2},
|
|
9336
|
+
{"class_id": "pdm_motor", "motor_id": "msd-6-3-4-78-6.4", "serial": "M100", "length_ft": 30, "service_limit": 20, "service_basis": "circulating_hours", "service_source": "rental contract"},
|
|
9337
|
+
{"class_id": "mwd_mud_pulse", "maker": "Acme", "model": "MWD", "serial": "W7", "od_in": 6.75, "length_ft": 32},
|
|
9338
|
+
{"class_id": "drilling_jar", "maker": "Acme", "model": "Jar", "serial": "J2", "od_in": 6.5, "length_ft": 31, "service_limit": 300, "service_basis": "rotating_hours", "service_source": "shop rule"}]
|
|
9339
|
+
refused = 0
|
|
9340
|
+
for bad in ([{"class_id": "packer"}], [{"class_id": "pdc_bit", "service_limit": 10}], [{"class_id": "drilling_jar", "service_limit": 10, "service_basis": "revolutions", "service_source": "s"}],
|
|
9341
|
+
[{"class_id": "pdm_motor", "motor": {"rev_per_gal": 0.3}}], [{"class_id": "pdm_motor", "motor": {"rev_per_gal": 0.3, "lobes": "seven", "source": "s"}}], []):
|
|
9342
|
+
try:
|
|
9343
|
+
DS.save_run(ws, "tester", w["id"], "bad", bad)
|
|
9344
|
+
except ValueError:
|
|
9345
|
+
refused += 1
|
|
9346
|
+
for args in (("nope", "r", comps), (w["id"], "r", comps, "2026-08-01T10:00", "2026-08-01T09:00")):
|
|
9347
|
+
try:
|
|
9348
|
+
DS.save_run(ws, "tester", *args)
|
|
9349
|
+
except (ValueError, KeyError):
|
|
9350
|
+
refused += 1
|
|
9351
|
+
r1 = DS.save_run(ws, "tester", w["id"], "Run 1", comps)
|
|
9352
|
+
half = (datetime.fromisoformat(syn["start"]) + timedelta(hours=8)).isoformat()
|
|
9353
|
+
r2 = DS.save_run(ws, "tester", w["id"], "Run 2 (second half)", [comps[1]], start=half)
|
|
9354
|
+
tr = DS.run_view(ws, r1)["track"]; u = tr["usage"]; st = tr["motor_stats"]; h = st["hourly"]
|
|
9355
|
+
gal = syn["gallons"]
|
|
9356
|
+
trk = {e["serial"]: e for e in DS.tracking(ws)}
|
|
9357
|
+
c2 = {
|
|
9358
|
+
"refused": refused == 8,
|
|
9359
|
+
"usage": u["circulating_hours"] == 16.0 and u["drilling_hours"] == 16.0 and abs(u["gallons"] - gal) < 1 and abs(u["footage_ft"] - 960.0) < 1.0
|
|
9360
|
+
and abs(u["revolutions"] - round(0.28 * gal)) < 2,
|
|
9361
|
+
"motor": h["motor_rpm"][0] == 140.0 and h["bit_rpm"][0] == 190.0 and abs(h["whirl_hz"][0] - round(140 / 60 * 7, 3)) < 1e-6
|
|
9362
|
+
and abs(st["torque_slope_ft_lb_per_psi"] - round(14360 / 1820, 3)) < 1e-9 and 4600 < h["torque_est_ft_lb"][0] < 4900,
|
|
9363
|
+
"stall": len(st["stalls"]) == 1 and st["stalls"][0]["at"] == syn["stall_at"] and st["stalls"][0]["seconds"] == 20 and st["stalls"][0]["peak_psi"] == 1900
|
|
9364
|
+
and 0.0 < st["hours_above_max_operating"] <= 0.01 and st["diff_on_bottom_psi"]["p50"] == 600.0,
|
|
9365
|
+
"tracking": trk["M100"]["totals"]["circulating_hours"] == 24.0 and len(trk["M100"]["runs"]) == 2 and trk["M100"]["life"]["status"] == "DUE"
|
|
9366
|
+
and trk["J2"]["life"]["status"] == "OK" and trk["J2"]["totals"]["revolutions"] == 0.0 and "life" not in trk["B1"],
|
|
9367
|
+
"audit": [a["action"] for a in ws.audit_log(200)].count("drillstring.run.declare") == 2,
|
|
9368
|
+
}
|
|
9369
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
9370
|
+
ok(not failed2,
|
|
9371
|
+
"CI2 a run from the bit up: an unknown class, a service interval with no source, revolutions for a jar, a motor's own figures with "
|
|
9372
|
+
"no spec sheet or a malformed lobe count, an empty string, an unknown well and a run that ends before it starts refused; over a "
|
|
9373
|
+
"labelled synthetic run the circulating hours, the gallons, the footage and the motor's revolutions equal the hand sums, its no-load "
|
|
9374
|
+
"speed is revolutions per gallon times the flow and the bit's the rotary's plus that, the whirl seven times the rotor's turning, the "
|
|
9375
|
+
"torque estimated from the maker's slope, the one stall found at its second; a motor followed across two runs to its interval"
|
|
9376
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
9377
|
+
|
|
9378
|
+
stab = tr["stability"]
|
|
9379
|
+
tq_rows = [b["torque"] for b in stab["blocks"]]
|
|
9380
|
+
n = 8 * 7200
|
|
9381
|
+
tt = np.arange(n, dtype=float)
|
|
9382
|
+
rng = np.random.default_rng(3)
|
|
9383
|
+
moved = DS._stability(tt, np.ones(n, bool), 9.0 + 0.6 * np.where(tt < n / 2, np.sin(2 * np.pi * 0.10 * tt), np.sin(2 * np.pi * 0.20 * tt)) + rng.normal(0, 0.05, n),
|
|
9384
|
+
None, datetime(2026, 1, 1))
|
|
9385
|
+
walk = DS._stability(tt, np.ones(n, bool), 9.0 + np.cumsum(rng.normal(0, 0.02, n)), None, datetime(2026, 1, 1))
|
|
9386
|
+
short = DS._stability(tt[:3 * 3600], np.ones(3 * 3600, bool), 9.0 + rng.normal(0, 0.05, 3 * 3600), None, datetime(2026, 1, 1))
|
|
9387
|
+
c3 = {
|
|
9388
|
+
"grows": stab["verdict"]["status"] == "CHANGED" and stab["verdict"]["changed"] == ["surface torque oscillation"] and len(stab["blocks"]) == 8
|
|
9389
|
+
and all(abs(r["f_hz"] - 0.1016) < 0.002 for r in tq_rows) and 3.5 < tq_rows[-1]["index"] / tq_rows[0]["index"] < 4.5
|
|
9390
|
+
and abs(tq_rows[0]["index"] - 0.25 / np.sqrt(2) / 9.0) < 0.003,
|
|
9391
|
+
"moves": moved["verdict"]["status"] == "CHANGED" and "surface torque frequency" in moved["verdict"]["changed"]
|
|
9392
|
+
and abs(moved["blocks"][0]["torque"]["f_hz"] - 0.1016) < 0.002 and abs(moved["blocks"][-1]["torque"]["f_hz"] - 0.20) < 0.004,
|
|
9393
|
+
"red_noise": sum(1 for b in walk["blocks"] if b["torque"]["f_hz"]) <= 1,
|
|
9394
|
+
"short": short["verdict"]["status"] == "TOO FEW BLOCKS",
|
|
9395
|
+
}
|
|
9396
|
+
Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
|
|
9397
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
9398
|
+
base_url = svc.url.rstrip("/")
|
|
9399
|
+
|
|
9400
|
+
def opener(name, pw):
|
|
9401
|
+
o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
9402
|
+
rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw}).encode())
|
|
9403
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
|
|
9404
|
+
return o
|
|
9405
|
+
|
|
9406
|
+
def call(o, method, path, body=None):
|
|
9407
|
+
rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
9408
|
+
if method == "POST":
|
|
9409
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
|
|
9410
|
+
try:
|
|
9411
|
+
with o.open(rq, timeout=120) as rr_:
|
|
9412
|
+
return rr_.status, json.loads(rr_.read())
|
|
9413
|
+
except urllib.error.HTTPError as e:
|
|
9414
|
+
return e.code, json.loads(e.read())
|
|
9415
|
+
try:
|
|
9416
|
+
V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
|
|
9417
|
+
s_g, pg = call(V, "GET", "/api/drillstring?well=" + w["id"])
|
|
9418
|
+
s_m, mm = call(V, "GET", "/api/tools/motors")
|
|
9419
|
+
s_vp, _ = call(V, "POST", "/api/drillstring/run", {"well_id": w["id"], "name": "x", "components": comps})
|
|
9420
|
+
s_op, rr = call(O, "POST", "/api/drillstring/run", {"well_id": w["id"], "name": "Run 3", "components": comps[:1]})
|
|
9421
|
+
s_bad, _ = call(O, "POST", "/api/drillstring/run", {"well_id": w["id"], "name": "x", "components": [{"class_id": "packer"}]})
|
|
9422
|
+
s_nw, _ = call(O, "POST", "/api/drillstring/run", {"well_id": "nope", "name": "x", "components": comps})
|
|
9423
|
+
s_rm, _ = call(O, "POST", "/api/drillstring/remove", {"run_id": rr.get("id", "")})
|
|
9424
|
+
s_rm2, _ = call(O, "POST", "/api/drillstring/remove", {"run_id": rr.get("id", "")})
|
|
9425
|
+
s_uw, _ = call(V, "GET", "/api/drillstring?well=nope")
|
|
9426
|
+
s_ac, _ = call(O, "POST", "/api/drillstring/nope", {})
|
|
9427
|
+
finally:
|
|
9428
|
+
svc.stop()
|
|
9429
|
+
cli = _sp.run([sys.executable, "-m", "gea", "drillstring", "--workspace", wsp], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
9430
|
+
cli_m = _sp.run([sys.executable, "-m", "gea", "tools", "--action", "motors"], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
9431
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
9432
|
+
c3.update({
|
|
9433
|
+
"api": s_g == 200 and len(pg["runs"]) == 2 and pg["runs"][0]["track"]["usage"]["circulating_hours"] == 16.0 and s_m == 200 and len(mm["motors"]) == 41
|
|
9434
|
+
and s_vp == 403 and s_op == 200 and s_bad == 400 and s_nw == 404 and s_rm == 200 and s_rm2 == 404 and s_uw == 404 and s_ac == 404,
|
|
9435
|
+
"cli": cli.returncode == 0 and "stability: CHANGED" in cli.stdout and "DUE: Positive displacement mud motor" in cli.stdout
|
|
9436
|
+
and cli_m.returncode == 0 and "msd-6-3-4-78-6.4" in cli_m.stdout,
|
|
9437
|
+
"page": all(x in page_src for x in ("VIEWS.drillstring", "['#/drillstring', 'Drill string']", "dsDiagram", "Declare a run", "Oscillation index, block by block",
|
|
9438
|
+
"Distinct oscillation frequency", "id=\"mtCard\"", "/api/tools/motors", "Every component, across every run")),
|
|
9439
|
+
"help": any(t[0] == "drillstring" for t in H.INDEX) and not H.check("drillstring") and H.VIEW_TOPIC.get("drillstring") == "drillstring",
|
|
9440
|
+
})
|
|
9441
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
9442
|
+
ok(not failed3,
|
|
9443
|
+
"CI3 the stability: a torque oscillation at 0.10 Hz growing fourfold found as a change at its frequency and amplitude, one that moves "
|
|
9444
|
+
"from 0.10 to 0.20 Hz found as moved, red noise rising to the band's edge not called a peak, a short record too few blocks to "
|
|
9445
|
+
"compare; the API (a viewer reads, only an operator declares; a bad class, an unknown well, a second removal and an unknown action "
|
|
9446
|
+
"named), the CLI, the Drill String tab with its build diagram, the motor table on the Tools tab, and the help page"
|
|
9447
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
9448
|
+
|
|
9449
|
+
|
|
9140
9450
|
def main() -> int:
|
|
9141
9451
|
print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
|
|
9142
9452
|
# a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
|
|
@@ -9225,6 +9535,8 @@ def main() -> int:
|
|
|
9225
9535
|
section_ce_well3d(tmp)
|
|
9226
9536
|
section_cf_economics(tmp)
|
|
9227
9537
|
section_cg_well_report(tmp)
|
|
9538
|
+
section_ch_simulation(tmp)
|
|
9539
|
+
section_ci_drill_string(tmp)
|
|
9228
9540
|
if _FAILS:
|
|
9229
9541
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
9230
9542
|
for f in _FAILS:
|