gea-program 0.27.0__tar.gz → 0.28.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.27.0/gea_program.egg-info → gea_program-0.28.0}/PKG-INFO +16 -2
- {gea_program-0.27.0 → gea_program-0.28.0}/README.md +15 -1
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/__init__.py +1 -1
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/__main__.py +38 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/acceptance_tests.py +110 -0
- gea_program-0.28.0/gea/help/tools.md +11 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/helplib.py +2 -1
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/service.py +19 -0
- gea_program-0.28.0/gea/tool_catalog.py +190 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/web/app.html +56 -1
- {gea_program-0.27.0 → gea_program-0.28.0/gea_program.egg-info}/PKG-INFO +16 -2
- {gea_program-0.27.0 → gea_program-0.28.0}/gea_program.egg-info/SOURCES.txt +2 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/pyproject.toml +1 -1
- {gea_program-0.27.0 → gea_program-0.28.0}/LICENSE +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/backup.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/bench.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/blind_harness.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/catalog_register.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/certificates.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/cli.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/client_reports.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/config_versioning.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/conformance.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/correlation.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/dashboard.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/deviation.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/dlis.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/dnp3.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/doctor.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/earth_model.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/elevation.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/etp.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/example_register_map.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/fat_sat.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/files.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/follower.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/forward_model.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/friction_priors.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/gamma.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/geodesy.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/hartip.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/alarms.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/backup.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/catalog.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/conformance.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/data-in.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/dlis.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/doctor.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/drift.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/etp.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/files.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/historian.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/instruments.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/interval.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/month-end.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/notifications.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/osdu.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/patches.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/quality.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/seismic.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/site.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/sites.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/slip.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/soak.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/sra.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/start.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/structure.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/transients.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/vibration.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/housekeeping.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/interval.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/jobs.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/live_ports.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/loadtest.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/modbus.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/model_card.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/month_end.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/notify.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/opcua_port.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/operator_app.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/osdu.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/patches.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/permits.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/piwebapi.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/ports.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/ppdm.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/project.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/quakeml.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reconciler.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/sample_record.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/sbom.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seedlink.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/segy.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_array.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_film.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_response.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_track.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/service_life.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/shapefile.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/shell.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/shut_in.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/sla_report.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/slip.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/soak.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/sra.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/store_forward.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/strata_join.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/structure.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/supervisor.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/survey_view.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/telemetry.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/tool_library.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/transient.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/vibration.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/well_assembler.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/wits0.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/witsml.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea/workspace.py +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.27.0 → gea_program-0.28.0}/setup.cfg +0 -0
- {gea_program-0.27.0 → gea_program-0.28.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.28.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 (368 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]")
|
|
@@ -648,6 +648,20 @@ 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
|
+
### Tools and ports
|
|
652
|
+
|
|
653
|
+
The Tools & ports tab (`#/tools`, `gea tools`) is the program's two
|
|
654
|
+
registers in one place: the tool library - every downhole and surface tool
|
|
655
|
+
with its source citation, ratings, what it measures, the telemetry interface
|
|
656
|
+
it declares and its datasheet aging rate where the vendor published one -
|
|
657
|
+
and the ports, the file formats read and the eleven live protocols spoken,
|
|
658
|
+
each with what it speaks to, what the site must supply and what to rehearse
|
|
659
|
+
against. Tools whose declared interface no port in this build speaks are
|
|
660
|
+
listed by name. The rating check hangs a tool string in a well and reports
|
|
661
|
+
each station's tool against the conditions there, with the basis stated:
|
|
662
|
+
`gea tools --rating --station 16000:quartz_pt_geoq177_16k` exits 1 when a
|
|
663
|
+
station is over a rating. `gea help tools`.
|
|
664
|
+
|
|
651
665
|
### ComCat
|
|
652
666
|
|
|
653
667
|
The seismicity response plan reads a TexNet export; it reads the FDSN text
|
|
@@ -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 (368 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]")
|
|
@@ -609,6 +609,20 @@ 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
|
+
### Tools and ports
|
|
613
|
+
|
|
614
|
+
The Tools & ports tab (`#/tools`, `gea tools`) is the program's two
|
|
615
|
+
registers in one place: the tool library - every downhole and surface tool
|
|
616
|
+
with its source citation, ratings, what it measures, the telemetry interface
|
|
617
|
+
it declares and its datasheet aging rate where the vendor published one -
|
|
618
|
+
and the ports, the file formats read and the eleven live protocols spoken,
|
|
619
|
+
each with what it speaks to, what the site must supply and what to rehearse
|
|
620
|
+
against. Tools whose declared interface no port in this build speaks are
|
|
621
|
+
listed by name. The rating check hangs a tool string in a well and reports
|
|
622
|
+
each station's tool against the conditions there, with the basis stated:
|
|
623
|
+
`gea tools --rating --station 16000:quartz_pt_geoq177_16k` exits 1 when a
|
|
624
|
+
station is over a rating. `gea help tools`.
|
|
625
|
+
|
|
612
626
|
### ComCat
|
|
613
627
|
|
|
614
628
|
The seismicity response plan reads a TexNet export; it reads the FDSN text
|
|
@@ -278,6 +278,13 @@ def main(argv=None) -> int:
|
|
|
278
278
|
p_lp.add_argument("--stream-csv", type=str, default=None, help="also write a historian-style CSV the other commands ingest")
|
|
279
279
|
p_lp.add_argument("--selftest", action="store_true", help="etp: the codec round-trip and a session against the simulated store on the loopback")
|
|
280
280
|
|
|
281
|
+
p_tl = sub.add_parser("tools", help="the tool library and the port registry: every tool with its citation, ratings and the interface it declares; every file format and live protocol; --rating checks a tool string")
|
|
282
|
+
p_tl.add_argument("--json", action="store_true")
|
|
283
|
+
p_tl.add_argument("--list", action="store_true", help="just the tool names")
|
|
284
|
+
p_tl.add_argument("--rating", action="store_true", help="check a tool string: --station <md_ft>:<tool> (repeatable)")
|
|
285
|
+
p_tl.add_argument("--station", type=str, action="append", default=None, help="a station as <measured depth in feet>:<tool name>")
|
|
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
|
+
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)
|
|
281
288
|
p_cc = sub.add_parser("comcat", help="USGS ComCat / any FDSN event service: --lat --lon --radius-km --start --end prints the text-format query to open; --file reads the export it returns")
|
|
282
289
|
p_cc.add_argument("--lat", type=float, default=None); p_cc.add_argument("--lon", type=float, default=None); p_cc.add_argument("--radius-km", type=float, default=25.0)
|
|
283
290
|
p_cc.add_argument("--start", type=str, default=None); p_cc.add_argument("--end", type=str, default=None); p_cc.add_argument("--min-mag", type=float, default=1.0)
|
|
@@ -2356,6 +2363,37 @@ def main(argv=None) -> int:
|
|
|
2356
2363
|
stop.set()
|
|
2357
2364
|
print("etp-sim: stopped")
|
|
2358
2365
|
return 0
|
|
2366
|
+
elif a.cmd == "tools":
|
|
2367
|
+
import json as _json
|
|
2368
|
+
from . import tool_catalog as _TC
|
|
2369
|
+
if a.rating or a.station:
|
|
2370
|
+
stations = []
|
|
2371
|
+
for spec in (a.station or []):
|
|
2372
|
+
if ":" not in spec:
|
|
2373
|
+
raise SystemExit(f"--station takes <measured depth in feet>:<tool name>, got {spec!r}")
|
|
2374
|
+
md, _, nm = spec.partition(":")
|
|
2375
|
+
try:
|
|
2376
|
+
stations.append((float(md), nm.strip()))
|
|
2377
|
+
except ValueError:
|
|
2378
|
+
raise SystemExit(f"--station depth is not a number: {md!r}")
|
|
2379
|
+
try:
|
|
2380
|
+
r = _TC.check_string(stations, surface_temp_F=a.surface_temp_f, temp_gradient_F_per_ft=a.temp_gradient_f_ft,
|
|
2381
|
+
surface_pressure_psi=a.surface_pressure_psi, pressure_gradient_psi_per_ft=a.pressure_gradient_psi_ft)
|
|
2382
|
+
except ValueError as e:
|
|
2383
|
+
raise SystemExit(f"tools: {e}")
|
|
2384
|
+
if a.json:
|
|
2385
|
+
print(_json.dumps(r, indent=1)); return 0 if not r["n_over_rating"] else 1
|
|
2386
|
+
for line in r["lines"]:
|
|
2387
|
+
print(line)
|
|
2388
|
+
return 0 if not r["n_over_rating"] else 1
|
|
2389
|
+
if a.list:
|
|
2390
|
+
for t in _TC.tools():
|
|
2391
|
+
print(t["name"])
|
|
2392
|
+
return 0
|
|
2393
|
+
if a.json:
|
|
2394
|
+
print(_json.dumps(_TC.catalog(), indent=1)); return 0
|
|
2395
|
+
print(_TC.text())
|
|
2396
|
+
return 0
|
|
2359
2397
|
elif a.cmd == "comcat":
|
|
2360
2398
|
import json as _json
|
|
2361
2399
|
from . import sra as _SR
|
|
@@ -7320,6 +7320,115 @@ def section_bu_comcat(tmp: str) -> None:
|
|
|
7320
7320
|
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
7321
7321
|
|
|
7322
7322
|
|
|
7323
|
+
def section_bv_tools(tmp: str) -> None:
|
|
7324
|
+
"""Section BV - the tool library and the port registry as one register: every tool with its citation, ratings,
|
|
7325
|
+
declared interface and published aging rate (or the reason there is none); every file format and live protocol
|
|
7326
|
+
with what the site must supply; the tools no port speaks named; the rating check; the API, the page, the CLI, the help."""
|
|
7327
|
+
import http.cookiejar
|
|
7328
|
+
import subprocess as _sp
|
|
7329
|
+
import urllib.request
|
|
7330
|
+
import urllib.error
|
|
7331
|
+
from . import tool_catalog as TC
|
|
7332
|
+
from . import helplib as H
|
|
7333
|
+
from .ports import PORT_REGISTRY
|
|
7334
|
+
from .tool_library import PARAMETERS_USER_SUPPLIED, TOOL_LIBRARY
|
|
7335
|
+
from .telemetry import TelemetryRecorder, TelemetryConfig
|
|
7336
|
+
from .downhole_engine import DownholeEngine, SimulatorConfig
|
|
7337
|
+
from .workspace import Workspace
|
|
7338
|
+
from .service import Service, Users
|
|
7339
|
+
pkg = Path(__file__).parent
|
|
7340
|
+
d = Path(tmp, "bv"); d.mkdir()
|
|
7341
|
+
|
|
7342
|
+
c = TC.catalog()
|
|
7343
|
+
by = {t["name"]: t for t in c["tools"]}
|
|
7344
|
+
q30, piezo, g6 = by["quartz_pt_geoq177_30k"], by["piezoresistive_pt_class"], by["surface_interface_g6"]
|
|
7345
|
+
rate = TC.check_string([(500.0, "quartz_pt_geoq177_30k"), (16000.0, "quartz_pt_geoq177_16k")])
|
|
7346
|
+
deep = rate["stations"][1]
|
|
7347
|
+
ok_all = TC.check_string([(500.0, "quartz_pt_geoq177_30k")])
|
|
7348
|
+
bad_tool = empty = None
|
|
7349
|
+
try:
|
|
7350
|
+
TC.check_string([(100.0, "no_such_tool")])
|
|
7351
|
+
except ValueError as e:
|
|
7352
|
+
bad_tool = str(e)
|
|
7353
|
+
try:
|
|
7354
|
+
TC.check_string([])
|
|
7355
|
+
except ValueError as e:
|
|
7356
|
+
empty = str(e)
|
|
7357
|
+
c1 = {
|
|
7358
|
+
"tools": c["counts"]["tools"] == len(TOOL_LIBRARY) and c["counts"]["fully_specified"] + c["counts"]["user_supplied"] == len(TOOL_LIBRARY)
|
|
7359
|
+
and all(t["source"] for t in c["tools"]) and q30["aging_rate_pct_fs_yr"] is not None and q30["aging_note"].startswith("the datasheet")
|
|
7360
|
+
and q30["temp_rating_C"] == 177.0 and q30["pressure_rating_psi"] == 30000.0 and q30["gauge_spec"]["full_scale_psi"] == 30000.0,
|
|
7361
|
+
"user_supplied_named": piezo["spec_status"] == PARAMETERS_USER_SUPPLIED and piezo["aging_rate_pct_fs_yr"] is None
|
|
7362
|
+
and "does not invent vendor numbers" in piezo["aging_note"] and g6["measures"] == [],
|
|
7363
|
+
"ports": c["counts"]["file_ports"] == len(PORT_REGISTRY) and {p["name"] for p in c["ports"]["live"]} >= {"wits0", "witsml", "etp", "opcua", "mqtt", "modbus_g6", "modbus_rtu", "dnp3", "hartip", "piwebapi", "seedlink"}
|
|
7364
|
+
and all(p["needs"] and p["speaks"] and p["rehearse"] for p in c["ports"]["live"]) and any(p["status"] == "IMPLEMENTED" for p in c["ports"]["files"])
|
|
7365
|
+
and any(p["name"] == "dlis" for p in c["ports"]["files"]),
|
|
7366
|
+
"interfaces": "modbus_rtu" in q30["ports"] and not piezo["ports"] and {u["tool"] for u in c["unmatched"]} == {t["name"] for t in c["tools"] if not t["ports"]}
|
|
7367
|
+
and "piezoresistive_pt_class" in {u["tool"] for u in c["unmatched"]},
|
|
7368
|
+
"rating": rate["n_over_rating"] == 1 and deep["over_temp_rating"] and not deep["over_pressure_rating"] and abs(deep["station_temp_C"] - 183.9) < 0.2
|
|
7369
|
+
and "OVER its 177 C rating" in rate["lines"][2] and "declared gradients" in rate["basis"] and "no survey given" in rate["deviation"]
|
|
7370
|
+
and ok_all["n_over_rating"] == 0 and bad_tool and "not in the tool library" in bad_tool and empty and "at least one station" in empty,
|
|
7371
|
+
}
|
|
7372
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
7373
|
+
ok(not failed1,
|
|
7374
|
+
f"BV1 the register: all {len(TOOL_LIBRARY)} tools with their citations, the quartz gauge's datasheet aging rate and ratings, the piezoresistive "
|
|
7375
|
+
"class carrying no rate and saying why, the interface card measuring nothing; every file port of the registry and all eleven live protocols with "
|
|
7376
|
+
"what each needs from the site and what to rehearse against; the tools no port in this build speaks named; the rating check flagging a 177 C gauge "
|
|
7377
|
+
"at 16,000 ft and clearing a shallow one, an unknown tool and an empty string refused"
|
|
7378
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
7379
|
+
|
|
7380
|
+
hist = str(d / "hist.csv")
|
|
7381
|
+
TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=0.5, seed=3)).run().export_csv(hist)
|
|
7382
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
7383
|
+
ws.add_well_file(hist, display="SWD 1", actor="tester")
|
|
7384
|
+
users = Users(str(Path(wsp, "users.json"))); users.add("v", "viewer-pass-1", "viewer")
|
|
7385
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
7386
|
+
base_url = svc.url.rstrip("/")
|
|
7387
|
+
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
7388
|
+
|
|
7389
|
+
def call(method, path, body=None):
|
|
7390
|
+
req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
7391
|
+
req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
|
|
7392
|
+
try:
|
|
7393
|
+
with opener.open(req, timeout=120) as rr_:
|
|
7394
|
+
return rr_.status, json.loads(rr_.read())
|
|
7395
|
+
except urllib.error.HTTPError as e:
|
|
7396
|
+
return e.code, json.loads(e.read())
|
|
7397
|
+
try:
|
|
7398
|
+
st_anon = call("GET", "/api/tools")[0]
|
|
7399
|
+
call("POST", "/api/login", {"name": "v", "password": "viewer-pass-1"})
|
|
7400
|
+
st_g, got = call("GET", "/api/tools")
|
|
7401
|
+
st_r, rr = call("POST", "/api/tools/rating", {"stations": [{"md_ft": 500, "tool": "quartz_pt_geoq177_30k"}, {"md_ft": 16000, "tool": "quartz_pt_geoq177_16k"}]})
|
|
7402
|
+
st_bad = call("POST", "/api/tools/rating", {"stations": [{"md_ft": 100, "tool": "nope"}]})[0]
|
|
7403
|
+
st_bad2 = call("POST", "/api/tools/rating", {"stations": [{"tool": "quartz_pt_geoq177_30k"}]})[0]
|
|
7404
|
+
finally:
|
|
7405
|
+
svc.stop()
|
|
7406
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
7407
|
+
help_md = (pkg / "help" / "tools.md").read_text(encoding="utf-8")
|
|
7408
|
+
r_txt = _sp.run([sys.executable, "-m", "gea", "tools"], capture_output=True, text=True)
|
|
7409
|
+
r_list = _sp.run([sys.executable, "-m", "gea", "tools", "--list"], capture_output=True, text=True)
|
|
7410
|
+
r_json = _sp.run([sys.executable, "-m", "gea", "tools", "--json"], capture_output=True, text=True)
|
|
7411
|
+
r_rate = _sp.run([sys.executable, "-m", "gea", "tools", "--rating", "--station", "500:quartz_pt_geoq177_30k", "--station", "16000:quartz_pt_geoq177_16k"], capture_output=True, text=True)
|
|
7412
|
+
r_badst = _sp.run([sys.executable, "-m", "gea", "tools", "--rating", "--station", "500"], capture_output=True, text=True)
|
|
7413
|
+
c2 = {
|
|
7414
|
+
"api": st_anon == 401 and st_g == 200 and got["counts"]["tools"] == len(TOOL_LIBRARY) and st_r == 200 and rr["n_over_rating"] == 1
|
|
7415
|
+
and rr["stations"][1]["over_temp_rating"] and st_bad == 400 and st_bad2 == 400,
|
|
7416
|
+
"page": "VIEWS.tools" in page_src and "'#/tools', 'Tools & ports'" in page_src and "/api/tools/rating" in page_src
|
|
7417
|
+
and "Tools no live port in this build speaks" in page_src and "Rating check" in page_src,
|
|
7418
|
+
"cli": r_txt.returncode == 0 and "file ports:" in r_txt.stdout and "live ports:" in r_txt.stdout and "does not invent vendor numbers" in r_txt.stdout
|
|
7419
|
+
and r_list.returncode == 0 and r_list.stdout.split() == sorted(TOOL_LIBRARY) and r_json.returncode == 0 and json.loads(r_json.stdout)["counts"]["live_ports"] == 11
|
|
7420
|
+
and r_rate.returncode == 1 and "OVER its 177 C rating" in r_rate.stdout and r_badst.returncode != 0 and "<measured depth in feet>:<tool name>" in (r_badst.stderr + r_badst.stdout),
|
|
7421
|
+
"help": any(t["topic"] == "tools" for t in H.topics()) and not H.check("tools") and H.VIEW_TOPIC.get("tools") == "tools"
|
|
7422
|
+
and "not a promise the tool survives" in help_md.split("will not call a measurement")[1],
|
|
7423
|
+
}
|
|
7424
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
7425
|
+
ok(not failed2,
|
|
7426
|
+
"BV2 the Tools & ports tab: the API refuses an anonymous caller and answers a viewer, runs the rating check and refuses an unknown tool and a "
|
|
7427
|
+
"station with no depth; the page carries the tab, the unmatched-tools table and the rating-check form; `gea tools`, `--list`, `--json` and "
|
|
7428
|
+
"`--rating --station` (exit 1 when a station is over a rating, and a malformed station named); the help page and its view mapping"
|
|
7429
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
7430
|
+
|
|
7431
|
+
|
|
7323
7432
|
def main() -> int:
|
|
7324
7433
|
print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
|
|
7325
7434
|
# a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
|
|
@@ -7396,6 +7505,7 @@ def main() -> int:
|
|
|
7396
7505
|
section_bs_catalog(tmp)
|
|
7397
7506
|
section_bt_slip(tmp)
|
|
7398
7507
|
section_bu_comcat(tmp)
|
|
7508
|
+
section_bv_tools(tmp)
|
|
7399
7509
|
if _FAILS:
|
|
7400
7510
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
7401
7511
|
for f in _FAILS:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Tools and ports
|
|
2
|
+
|
|
3
|
+
> What this program knows how to measure with, and what it knows how to speak. The tool library is the downhole and surface tools - each with the citation its numbers came from, the conditions it is rated for, and the telemetry interface it declares; the port registry is the file formats and live protocols that bring a tool's readings in. The page draws the line between them: a tool declares an interface, a port speaks one, and a tool whose interface no port in this build speaks is named rather than hidden.
|
|
4
|
+
|
|
5
|
+
**The command**: the page is Tools & ports on the dashboard; `gea tools` prints the same register at the command line (`--json` for the machine record, `--rating --station <ft>:<tool> ...` for the rating check). A tool whose vendor published no numbers is listed as PARAMETERS_USER_SUPPLIED: `gea config` takes your own datasheet (`load_gauge_spec_json`) and the tool is then simulated and aged from it. The live protocols each name what the site must supply before they run and what to rehearse against - `gea wits0-sim`, `gea etp-sim`, `gea seedlink-sim`, or the stand-in slave, outstation, device and historian the field ports ship with.
|
|
6
|
+
|
|
7
|
+
**What it writes**: nothing to the workspace - this page is a register, not a record. It states, per tool: the class, what it measures, the temperature and pressure ratings, the published aging rate in %FS/yr where the datasheet gives one (and the reason there is none where it does not), the interface it declares, the ports that can carry it, and its source. Per port: the transport, the status (IMPLEMENTED, or DECLARED_SITE_DETAILS_REQUIRED with what is missing), and what it speaks to. The rating check returns, per station, the temperature and pressure at that depth, the tool's ratings and whether the station is within them, with the basis of the conditions printed beside the answer.
|
|
8
|
+
|
|
9
|
+
**The number to check**: the aging rate against your own datasheet before you trust a drift report built on it, and the rating check's per-station temperature against the tool's rating - the check that catches a 177 °C gauge hung in a 233 °C zone before the well does.
|
|
10
|
+
|
|
11
|
+
**What this page will not call a measurement**: a vendor number nobody published - a tool with no datasheet rate carries none and raises rather than inventing one, and the page says PARAMETERS_USER_SUPPLIED. A rating as a prediction - a station within its ratings is arithmetic on the vendor's number and the declared conditions, not a promise the tool survives. The conditions themselves - without a well profile they are the gradients declared on the page, and the answer prints which basis it used. A port as a connection - a port listed here speaks a protocol; whether the site's own device answers is what the Patch panel shows.
|
|
@@ -42,6 +42,7 @@ INDEX: List[tuple] = [
|
|
|
42
42
|
('catalog', 'The catalogue', 'every shipped public dataset, what in this program reads it or that nothing does; the measured friction envelope; the core check; any log through the survey'),
|
|
43
43
|
('dlis', 'The DLIS log', 'a well log as it is delivered (API RP66 V1), read as the standard lays it out: the frames, the channels, what was skipped; into the strata survey'),
|
|
44
44
|
('historian', 'The historian pull', 'an operator\'s gauge history out of the PI System through PI Web API, read-only, every value with its archive time and flags'),
|
|
45
|
+
('tools', 'Tools and ports', 'the downhole and surface tools this program knows, each with its citation and ratings, the formats and protocols that bring their readings in, and the rating check on a tool string'),
|
|
45
46
|
('field-ports', 'The field ports', 'Modbus RTU on the serial line, DNP3 from the pad RTU, HART-IP from the transmitter itself - read-only, polled, with the stand-ins to rehearse against'),
|
|
46
47
|
('slip', 'Slip tendency on the mapped faults', 'the declared stress state resolved on each mapped fault around a well: the slip tendency, the pressure to slip, the World Stress Map as the SHmax source, the published Hennings values beside'),
|
|
47
48
|
('interval', 'The interval against the mapped tops', 'the declared disposal interval, subsea, beside each formation-top bracket; the depth tier against the mapped base; the catalogue events against the mapped faults'),
|
|
@@ -66,7 +67,7 @@ REQUIRED_LINES = ('**The command**', '**What it writes**', '**The number to chec
|
|
|
66
67
|
VIEW_TOPIC: Dict[str, str] = {
|
|
67
68
|
'home': 'start', 'wells': 'data-in', 'well': 'drift', 'live': 'patches', 'patch': 'patches', 'files': 'files', 'alarms': 'alarms',
|
|
68
69
|
'approvals': 'well-tests', 'config': 'site', 'reports': 'drift', 'verify': 'start', 'survey': 'data-in', 'jobs': 'site',
|
|
69
|
-
'admin': 'site', 'prefs': 'site', 'search': 'start', 'welcome': 'start', 'seismic': 'seismic', 'seismic_station': 'seismic', 'track': 'seismic', 'sar': 'seismic', 'sites': 'sites', 'audit': 'audit-update',
|
|
70
|
+
'admin': 'site', 'prefs': 'site', 'search': 'start', 'welcome': 'start', 'seismic': 'seismic', 'seismic_station': 'seismic', 'track': 'seismic', 'sar': 'seismic', 'sites': 'sites', 'audit': 'audit-update', 'tools': 'tools',
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
|
|
@@ -1637,6 +1637,9 @@ def make_handler(app: App):
|
|
|
1637
1637
|
raise ApiError(404, str(e))
|
|
1638
1638
|
if path == '/api/stress':
|
|
1639
1639
|
return self._json(200, {'stress': app.ws.stress(), 'stress_map': app.ws.stress_map()})
|
|
1640
|
+
if path == '/api/tools':
|
|
1641
|
+
from . import tool_catalog as _TC
|
|
1642
|
+
return self._json(200, _TC.catalog())
|
|
1640
1643
|
if path == '/api/stress/wsm':
|
|
1641
1644
|
try:
|
|
1642
1645
|
lat, lon = float(qs['lat'][0]), float(qs['lon'][0]); rad = float(qs.get('radius_km', ['100'])[0])
|
|
@@ -1839,6 +1842,22 @@ def make_handler(app: App):
|
|
|
1839
1842
|
depth_ft=(float(b['depth_ft']) if b.get('depth_ft') is not None else None), actor=user['name']))
|
|
1840
1843
|
except WorkspaceError as e:
|
|
1841
1844
|
raise ApiError(404, str(e))
|
|
1845
|
+
if path == '/api/tools/rating':
|
|
1846
|
+
from . import tool_catalog as _TC
|
|
1847
|
+
b = body or {}
|
|
1848
|
+
try:
|
|
1849
|
+
stations = [(float(x['md_ft']), str(x['tool'])) for x in (b.get('stations') or [])]
|
|
1850
|
+
except (KeyError, TypeError, ValueError):
|
|
1851
|
+
raise ApiError(400, 'each station needs md_ft and tool')
|
|
1852
|
+
try: # the conditions are the declared gradients, and the answer says so
|
|
1853
|
+
return self._json(200, _TC.check_string(stations, profile=None, deviation=None,
|
|
1854
|
+
surface_temp_F=float(b.get('surface_temp_F', 75.0)),
|
|
1855
|
+
temp_gradient_F_per_ft=float(b.get('temp_gradient_F_per_ft', 0.018)),
|
|
1856
|
+
surface_pressure_psi=float(b.get('surface_pressure_psi', 14.7)),
|
|
1857
|
+
pressure_gradient_psi_per_ft=float(b.get('pressure_gradient_psi_per_ft', 0.465)),
|
|
1858
|
+
name=str(b.get('name', 'toolstring'))))
|
|
1859
|
+
except (ValueError, KeyError) as e:
|
|
1860
|
+
raise ApiError(400, str(e))
|
|
1842
1861
|
if path == '/api/stress':
|
|
1843
1862
|
App.require(user, 'operator')
|
|
1844
1863
|
try:
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
"""tool_catalog - what this program knows how to measure with, and what it knows how to speak.
|
|
5
|
+
|
|
6
|
+
Two registers have lived in the code since the beginning and had no page: the tool library (the downhole and
|
|
7
|
+
surface tools, each with a mandatory source citation, its ratings and the telemetry interface it declares) and
|
|
8
|
+
the port registry (the formats and protocols that bring a tool's readings in). This module puts them side by
|
|
9
|
+
side as one record, and draws the line between them: a tool declares an interface, a port speaks one, and the
|
|
10
|
+
page says which tool each port can carry and which tool has no port in this build.
|
|
11
|
+
|
|
12
|
+
Every entry is a declaration with a citation; nothing here is a measurement. A tool whose vendor published no
|
|
13
|
+
numbers is PARAMETERS_USER_SUPPLIED and says so - its aging rate raises rather than inventing one. A port that
|
|
14
|
+
needs the site's own details (a register map, an endpoint, a point list) is DECLARED_SITE_DETAILS_REQUIRED and
|
|
15
|
+
names what it needs. The rating check is the one piece of arithmetic: a tool string hung in a well, each
|
|
16
|
+
station's tool against the temperature and pressure at that station, from the well's own profile when there is
|
|
17
|
+
one and from declared gradients when there is not.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Dict, List, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
from .ports import PORT_REGISTRY
|
|
24
|
+
from .tool_library import (FULLY_SPECIFIED, PARAMETERS_USER_SUPPLIED, TOOL_LIBRARY, ToolString, drift_model_for,
|
|
25
|
+
rating_check)
|
|
26
|
+
|
|
27
|
+
# the live protocol ports: the module that speaks each, and what the site must supply before it runs
|
|
28
|
+
LIVE_PORTS: Dict[str, dict] = {
|
|
29
|
+
'wits0': {'module': 'gea.wits0', 'transport': 'WITS Level 0 over TCP connect, TCP listen or serial',
|
|
30
|
+
'needs': 'the sender (host and port, or the serial device) and the item map (four-digit code -> tag)',
|
|
31
|
+
'rehearse': 'gea wits0-sim', 'speaks': 'the mud logger or EDR on the drill floor'},
|
|
32
|
+
'witsml': {'module': 'gea.witsml', 'transport': 'WITSML 1.4.1.1 SOAP (read-only STORE interface)',
|
|
33
|
+
'needs': "the store's url, the well/wellbore/log uids and the environment variable names holding the credentials",
|
|
34
|
+
'rehearse': 'the in-package test store', 'speaks': 'a rig or operator WITSML 1.4 store'},
|
|
35
|
+
'etp': {'module': 'gea.etp', 'transport': 'WITSML 2.x over Energistics ETP v1.2 (WebSocket, Avro)',
|
|
36
|
+
'needs': 'the ETP endpoint and the channel URIs the store publishes',
|
|
37
|
+
'rehearse': 'gea etp-sim', 'speaks': 'a newer rig or store that pushes rather than answers polls'},
|
|
38
|
+
'opcua': {'module': 'gea.opcua_port', 'transport': 'OPC UA client (read and subscribe)',
|
|
39
|
+
'needs': "the server endpoint, the security policy and the node ids", 'rehearse': 'a recording, replayed offline',
|
|
40
|
+
'speaks': 'a pad PLC or SCADA server'},
|
|
41
|
+
'mqtt': {'module': 'gea.mqtt_port', 'transport': 'MQTT 3.1.1 / 5.0 subscriber, including Sparkplug B',
|
|
42
|
+
'needs': 'the broker, the topics and the tag map (topic/metric -> tag)', 'rehearse': 'a recording, replayed offline',
|
|
43
|
+
'speaks': 'an edge gateway publishing to a broker'},
|
|
44
|
+
'modbus_g6': {'module': 'gea.modbus', 'transport': 'Modbus TCP (read-only; pymodbus)',
|
|
45
|
+
'needs': "the host and the site's register map with its citation", 'rehearse': 'an in-process loopback server',
|
|
46
|
+
'speaks': 'a G6-class surface interface card on Ethernet'},
|
|
47
|
+
'modbus_rtu': {'module': 'gea.modbus_rtu', 'transport': 'Modbus RTU on RS-485 (pyserial) or RTU over TCP',
|
|
48
|
+
'needs': "the serial device or converter, the unit id and the site's register map with its citation",
|
|
49
|
+
'rehearse': 'the stand-in slave (transport: sim)', 'speaks': 'the RS-485 drop most surface-readout panels are'},
|
|
50
|
+
'dnp3': {'module': 'gea.dnp3', 'transport': 'DNP3 (IEEE 1815) master, read-only, over TCP',
|
|
51
|
+
'needs': "the master and outstation addresses and the RTU's point list with its source",
|
|
52
|
+
'rehearse': 'the stand-in outstation (transport: sim)', 'speaks': 'a pad RTU or pipeline SCADA'},
|
|
53
|
+
'hartip': {'module': 'gea.hartip', 'transport': 'HART-IP (TCP 5094), read-only: commands 0, 3 and 9',
|
|
54
|
+
'needs': "the gateway address and the variable map (pv, sv, tv, qv, loop_mA, dv:<code>) with the instrument's datasheet",
|
|
55
|
+
'rehearse': 'the stand-in device (transport: sim)', 'speaks': 'the transmitter itself through a HART-IP gateway'},
|
|
56
|
+
'piwebapi': {'module': 'gea.piwebapi', 'transport': 'AVEVA (OSIsoft) PI Web API over HTTPS, GET only',
|
|
57
|
+
'needs': "the base url, the tag list with its source and the environment variable names holding the credentials",
|
|
58
|
+
'rehearse': 'the stand-in server (base_url: sim)', 'speaks': "the operator's own PI Data Archive"},
|
|
59
|
+
'seedlink': {'module': 'gea.seedlink', 'transport': 'SeedLink 3 / 4.0 (TCP 18000), read-only',
|
|
60
|
+
'needs': 'the server, the network and station codes and the channel selectors', 'rehearse': 'gea seedlink-sim',
|
|
61
|
+
'speaks': 'a seismic data centre or a digitiser at the pad'},
|
|
62
|
+
}
|
|
63
|
+
# which live port a tool's declared telemetry interface can be carried by, by the words the declaration uses
|
|
64
|
+
_INTERFACE_PORTS: Tuple[Tuple[str, Tuple[str, ...]], ...] = (
|
|
65
|
+
('modbus rs485', ('modbus_rtu', 'modbus_g6')),
|
|
66
|
+
('modbus', ('modbus_g6', 'modbus_rtu')),
|
|
67
|
+
('4-20ma', ()),
|
|
68
|
+
('thermocouple input card', ()),
|
|
69
|
+
('fiber-optic interrogator', ()),
|
|
70
|
+
('vibrating-wire', ()),
|
|
71
|
+
('psk downhole telemetry', ('modbus_rtu', 'modbus_g6')),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def tools() -> List[dict]:
|
|
76
|
+
"""Every tool in the library as the page shows it: what it is, what it measures, what it is rated for, what it
|
|
77
|
+
speaks, whether its numbers are published or the site's, its citation, and its published aging rate if it has one."""
|
|
78
|
+
out = []
|
|
79
|
+
for name, t in sorted(TOOL_LIBRARY.items()):
|
|
80
|
+
row = {'name': name, 'tool_class': t.tool_class, 'measures': list(t.measures), 'telemetry_interface': t.telemetry_interface,
|
|
81
|
+
'spec_status': t.spec_status, 'temp_rating_C': t.temp_rating_C, 'pressure_rating_psi': t.pressure_rating_psi,
|
|
82
|
+
'source': t.source, 'notes': t.notes, 'params': dict(t.params), 'aging_rate_pct_fs_yr': None, 'aging_note': '',
|
|
83
|
+
'ports': ports_for_interface(t.telemetry_interface)}
|
|
84
|
+
if t.gauge_spec is not None:
|
|
85
|
+
row['gauge_spec'] = {'full_scale_psi': t.gauge_spec.full_scale_psi, 'accuracy_pct_fs': t.gauge_spec.accuracy_pct_fs,
|
|
86
|
+
'max_temp_C': t.gauge_spec.max_temp_C, 'source': t.gauge_spec.source}
|
|
87
|
+
try:
|
|
88
|
+
row['aging_rate_pct_fs_yr'] = round(drift_model_for(t)(25.0, 1000.0), 6)
|
|
89
|
+
row['aging_note'] = "the datasheet's published drift rate"
|
|
90
|
+
except ValueError as e:
|
|
91
|
+
row['aging_note'] = str(e)
|
|
92
|
+
out.append(row)
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def ports_for_interface(interface: str) -> List[str]:
|
|
97
|
+
"""The live ports that can carry a declared telemetry interface, by the words the declaration uses."""
|
|
98
|
+
s = (interface or '').lower()
|
|
99
|
+
hits: List[str] = []
|
|
100
|
+
for word, names in _INTERFACE_PORTS:
|
|
101
|
+
if word in s:
|
|
102
|
+
for n in names:
|
|
103
|
+
if n not in hits:
|
|
104
|
+
hits.append(n)
|
|
105
|
+
return hits
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def ports() -> dict:
|
|
109
|
+
"""The two kinds of port: the file formats this program reads, and the live protocols it speaks."""
|
|
110
|
+
files = []
|
|
111
|
+
for name, spec in sorted(PORT_REGISTRY.items()):
|
|
112
|
+
files.append({'name': name, 'transport': spec.transport, 'status': spec.status, 'detail': spec.detail,
|
|
113
|
+
'live': name in LIVE_PORTS})
|
|
114
|
+
live = []
|
|
115
|
+
for name, d in sorted(LIVE_PORTS.items()):
|
|
116
|
+
live.append({'name': name, **d, 'in_registry': name in PORT_REGISTRY,
|
|
117
|
+
'registry_status': (PORT_REGISTRY[name].status if name in PORT_REGISTRY else '')})
|
|
118
|
+
return {'files': files, 'live': live}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def unmatched() -> List[dict]:
|
|
122
|
+
"""The tools whose declared interface no live port in this build speaks - named, not hidden."""
|
|
123
|
+
return [{'tool': t['name'], 'telemetry_interface': t['telemetry_interface']} for t in tools() if not t['ports']]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def catalog() -> dict:
|
|
127
|
+
ts, ps = tools(), ports()
|
|
128
|
+
return {'tools': ts, 'ports': ps, 'unmatched': unmatched(),
|
|
129
|
+
'counts': {'tools': len(ts), 'fully_specified': sum(1 for t in ts if t['spec_status'] == FULLY_SPECIFIED),
|
|
130
|
+
'user_supplied': sum(1 for t in ts if t['spec_status'] == PARAMETERS_USER_SUPPLIED),
|
|
131
|
+
'file_ports': len(ps['files']), 'live_ports': len(ps['live'])},
|
|
132
|
+
'statement': ('Every tool and every port here is a declaration with its source, not a measurement. A tool whose vendor '
|
|
133
|
+
'published no numbers carries none and says so; a port that needs the site\'s own details names them.')}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def check_string(stations: List[Tuple[float, str]], profile=None, deviation=None, surface_temp_F: float = 75.0,
|
|
137
|
+
temp_gradient_F_per_ft: float = 0.018, surface_pressure_psi: float = 14.7,
|
|
138
|
+
pressure_gradient_psi_per_ft: float = 0.465, name: str = 'toolstring') -> dict:
|
|
139
|
+
"""A tool string against a well: each station's tool at that station's conditions. Unknown tool names are
|
|
140
|
+
named, not guessed; the conditions' basis is stated with the answer."""
|
|
141
|
+
bad = [n for _, n in stations if n not in TOOL_LIBRARY]
|
|
142
|
+
if bad:
|
|
143
|
+
raise ValueError(f"not in the tool library: {', '.join(sorted(set(bad)))} (gea tools --list)")
|
|
144
|
+
if not stations:
|
|
145
|
+
raise ValueError('a tool string needs at least one station: (measured depth in feet, tool name)')
|
|
146
|
+
ts = ToolString(stations=[(float(md), n) for md, n in stations], name=name)
|
|
147
|
+
rows = rating_check(ts, profile=profile, deviation=deviation, surface_temp_F=surface_temp_F,
|
|
148
|
+
temp_gradient_F_per_ft=temp_gradient_F_per_ft, surface_pressure_psi=surface_pressure_psi,
|
|
149
|
+
pressure_gradient_psi_per_ft=pressure_gradient_psi_per_ft)
|
|
150
|
+
over = [r for r in rows if not r['ok']]
|
|
151
|
+
basis = ('the well profile' if profile is not None else
|
|
152
|
+
f'declared gradients: {surface_temp_F:g} F + {temp_gradient_F_per_ft:g} F/ft, {surface_pressure_psi:g} psi + {pressure_gradient_psi_per_ft:g} psi/ft')
|
|
153
|
+
return {'name': name, 'stations': rows, 'n_over_rating': len(over), 'basis': basis,
|
|
154
|
+
'deviation': 'measured depth converted to true vertical depth by the survey' if deviation is not None else 'measured depth taken as true vertical depth (no survey given)',
|
|
155
|
+
'lines': [f"{len(rows)} station(s) checked against {basis}; {len(over)} over a rating."] +
|
|
156
|
+
[f"{r['tool']} at {r['md_ft']:,.0f} ft: {r['station_temp_C']:.1f} C, {r['station_pressure_psi']:,.0f} psi"
|
|
157
|
+
+ (f" - OVER its {r['temp_rating_C']:g} C rating" if r['over_temp_rating'] else '')
|
|
158
|
+
+ (f" - OVER its {r['pressure_rating_psi']:,.0f} psi rating" if r['over_pressure_rating'] else '')
|
|
159
|
+
+ ('' if not r['ok'] else ' - within its ratings') for r in rows] +
|
|
160
|
+
['A rating is the vendor\'s, and the conditions are the well\'s or the declaration\'s; this check is arithmetic on both, not a prediction of failure.']}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def text() -> str:
|
|
164
|
+
c = catalog()
|
|
165
|
+
out = [f"tools: {c['counts']['tools']} ({c['counts']['fully_specified']} with published numbers, {c['counts']['user_supplied']} parameters user-supplied)"]
|
|
166
|
+
for t in c['tools']:
|
|
167
|
+
out.append(f" {t['name']:<30s} {t['tool_class']:<24s} {t['spec_status']}")
|
|
168
|
+
out.append(f" measures {', '.join(t['measures']) or '(none - it is an interface)'}; speaks {t['telemetry_interface']}")
|
|
169
|
+
if t['temp_rating_C'] or t['pressure_rating_psi']:
|
|
170
|
+
out.append(f" rated {t['temp_rating_C'] or '?'} C, {t['pressure_rating_psi'] or '?'} psi")
|
|
171
|
+
out.append(f" aging: {('%.4f %%FS/yr - ' % t['aging_rate_pct_fs_yr']) + t['aging_note'] if t['aging_rate_pct_fs_yr'] is not None else t['aging_note']}")
|
|
172
|
+
out.append(f" ports: {', '.join(t['ports']) or 'none in this build'}")
|
|
173
|
+
out.append(f" source: {t['source'][:160]}")
|
|
174
|
+
out.append('')
|
|
175
|
+
out.append(f"file ports: {c['counts']['file_ports']}")
|
|
176
|
+
for p in c['ports']['files']:
|
|
177
|
+
out.append(f" {p['name']:<16s} {p['status']:<32s} {p['transport']}")
|
|
178
|
+
out.append('')
|
|
179
|
+
out.append(f"live ports: {c['counts']['live_ports']}")
|
|
180
|
+
for p in c['ports']['live']:
|
|
181
|
+
out.append(f" {p['name']:<12s} {p['transport']}")
|
|
182
|
+
out.append(f" speaks to {p['speaks']}; needs {p['needs']}; rehearse with {p['rehearse']}")
|
|
183
|
+
if c['unmatched']:
|
|
184
|
+
out.append('')
|
|
185
|
+
out.append('tools whose declared interface no live port in this build speaks:')
|
|
186
|
+
for u in c['unmatched']:
|
|
187
|
+
out.append(f" {u['tool']}: {u['telemetry_interface']}")
|
|
188
|
+
out.append('')
|
|
189
|
+
out.append(c['statement'])
|
|
190
|
+
return '\n'.join(out)
|
|
@@ -264,6 +264,61 @@ VIEWS.well = async (id) => {
|
|
|
264
264
|
catch (e) { $('#chart').innerHTML = `<p class="muted">${esc(e.message)}</p>`; }
|
|
265
265
|
};
|
|
266
266
|
|
|
267
|
+
VIEWS.tools = async () => {
|
|
268
|
+
const c = await get('/api/tools');
|
|
269
|
+
let h = `<h2>Tools and ports</h2>` + help('tools');
|
|
270
|
+
h += `<p class="small muted">${esc(c.statement)}</p>`;
|
|
271
|
+
h += `<div class="tiles">` + tile('Tools in the library', `${c.counts.tools}`, 'neutral', '#/tools', `${c.counts.fully_specified} with published numbers`)
|
|
272
|
+
+ tile('Parameters user-supplied', `${c.counts.user_supplied}`, c.counts.user_supplied ? 'warn' : 'good', '#/tools', 'no vendor numbers on the fetched pages')
|
|
273
|
+
+ tile('File formats read', `${c.counts.file_ports}`, 'neutral', '#/tools')
|
|
274
|
+
+ tile('Live protocols spoken', `${c.counts.live_ports}`, 'neutral', '#/live') + `</div>`;
|
|
275
|
+
|
|
276
|
+
h += `<div class="card"><h3>The tool library <span class="small muted">every entry carries its source; a tool whose vendor published no numbers carries none</span></h3>`;
|
|
277
|
+
h += `<table><thead><tr><th>Tool</th><th>Class</th><th>Measures</th><th>Rated</th><th>Aging (%FS/yr)</th><th>Speaks</th><th>Ports</th><th>Numbers</th></tr></thead><tbody>`;
|
|
278
|
+
for (const t of c.tools) {
|
|
279
|
+
h += `<tr><td><b>${esc(t.name)}</b>${t.notes ? `<div class="small muted">${esc(t.notes)}</div>` : ''}</td><td class="small">${esc(t.tool_class)}</td><td class="small">${esc((t.measures || []).join(', ') || '— (an interface)')}</td>`
|
|
280
|
+
+ `<td class="small">${t.temp_rating_C != null ? fmt(t.temp_rating_C, 0) + ' °C' : '—'}${t.pressure_rating_psi != null ? ' / ' + fmt(t.pressure_rating_psi, 0) + ' psi' : ''}</td>`
|
|
281
|
+
+ `<td>${t.aging_rate_pct_fs_yr != null ? fmt(t.aging_rate_pct_fs_yr, 4) : `<span class="small muted" title="${esc(t.aging_note)}">none on record</span>`}</td>`
|
|
282
|
+
+ `<td class="small">${esc(t.telemetry_interface)}</td><td class="small">${(t.ports || []).map(p => `<a href="#/live">${esc(p)}</a>`).join(', ') || '<span class="muted">none in this build</span>'}</td>`
|
|
283
|
+
+ `<td>${statusPill(t.spec_status === 'FULLY_SPECIFIED' ? 'good' : 'warn', t.spec_status === 'FULLY_SPECIFIED' ? 'published' : 'user-supplied')}</td></tr>`;
|
|
284
|
+
h += `<tr><td colspan="8" class="small muted">source: ${esc(t.source)}</td></tr>`;
|
|
285
|
+
}
|
|
286
|
+
h += `</tbody></table></div>`;
|
|
287
|
+
|
|
288
|
+
if (c.unmatched && c.unmatched.length) {
|
|
289
|
+
h += `<div class="card"><h3>Tools no live port in this build speaks</h3><table><thead><tr><th>Tool</th><th>It declares</th></tr></thead><tbody>${c.unmatched.map(u => `<tr><td>${esc(u.tool)}</td><td class="small">${esc(u.telemetry_interface)}</td></tr>`).join('')}</tbody></table><p class="small muted">Named rather than hidden: a reading from one of these comes in as a file, or through a port the site adds.</p></div>`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
h += `<div class="card"><h3>Live protocols <span class="small muted">what each speaks to, what the site must supply, and what to rehearse against</span></h3>`;
|
|
293
|
+
h += `<table><thead><tr><th>Port</th><th>Transport</th><th>Speaks to</th><th>Needs from the site</th><th>Rehearse with</th></tr></thead><tbody>`;
|
|
294
|
+
for (const p of c.ports.live) h += `<tr><td><a href="#/live">${esc(p.name)}</a></td><td class="small">${esc(p.transport)}</td><td class="small">${esc(p.speaks)}</td><td class="small">${esc(p.needs)}</td><td class="small mono">${esc(p.rehearse)}</td></tr>`;
|
|
295
|
+
h += `</tbody></table></div>`;
|
|
296
|
+
|
|
297
|
+
h += `<div class="card"><h3>File formats <span class="small muted">what a file can be brought in as</span></h3>`;
|
|
298
|
+
h += `<table><thead><tr><th>Port</th><th>Transport</th><th>Status</th><th>Detail</th></tr></thead><tbody>`;
|
|
299
|
+
for (const p of c.ports.files) h += `<tr><td>${esc(p.name)}</td><td class="small">${esc(p.transport)}</td><td>${statusPill(p.status === 'IMPLEMENTED' ? 'good' : 'warn', p.status)}</td><td class="small">${esc(p.detail)}</td></tr>`;
|
|
300
|
+
h += `</tbody></table></div>`;
|
|
301
|
+
|
|
302
|
+
const names = c.tools.filter(t => (t.measures || []).length).map(t => t.name);
|
|
303
|
+
h += `<div class="card"><h3>Rating check <span class="small muted">a tool string hung in a well: each station's tool against the conditions there</span></h3>
|
|
304
|
+
<p class="small muted">The conditions come from the gradients declared here, and the answer says so. A rating is the vendor's; this check is arithmetic on both, not a prediction of failure.</p>
|
|
305
|
+
<div class="row"><label>Surface temperature °F<input id="rcT" type="number" step="0.1" value="75" style="width:7em"></label><label>Gradient °F/ft<input id="rcTG" type="number" step="0.001" value="0.018" style="width:7em"></label><label>Surface pressure psi<input id="rcP" type="number" step="0.1" value="14.7" style="width:7em"></label><label>Gradient psi/ft<input id="rcPG" type="number" step="0.001" value="0.465" style="width:7em"></label></div>
|
|
306
|
+
<div class="row" id="rcRows"></div>
|
|
307
|
+
<div class="row"><button class="secondary" id="rcAdd" type="button">Add a station</button><button id="rcRun" type="button">Check the string</button></div>
|
|
308
|
+
<div id="rcOut"></div></div>`;
|
|
309
|
+
$('#main').innerHTML = h;
|
|
310
|
+
const mk = (md) => { const d = document.createElement('div'); d.className = 'row'; d.style.margin = '0';
|
|
311
|
+
d.innerHTML = `<label>Measured depth ft<input type="number" class="rcMd" value="${md}" style="width:8em"></label><label>Tool<select class="rcTool">${names.map(n => `<option value="${esc(n)}">${esc(n)}</option>`).join('')}</select></label><button class="secondary rcDel" type="button">Remove</button>`;
|
|
312
|
+
d.querySelector('.rcDel').onclick = () => d.remove(); $('#rcRows').appendChild(d); };
|
|
313
|
+
mk(500); mk(9000);
|
|
314
|
+
$('#rcAdd').onclick = () => mk(12000);
|
|
315
|
+
$('#rcRun').onclick = async () => {
|
|
316
|
+
const stations = [...document.querySelectorAll('#rcRows .row')].map(r => ({md_ft: parseFloat(r.querySelector('.rcMd').value), tool: r.querySelector('.rcTool').value})).filter(x => !isNaN(x.md_ft));
|
|
317
|
+
try { const r = await post('/api/tools/rating', {stations, surface_temp_F: parseFloat($('#rcT').value), temp_gradient_F_per_ft: parseFloat($('#rcTG').value), surface_pressure_psi: parseFloat($('#rcP').value), pressure_gradient_psi_per_ft: parseFloat($('#rcPG').value)});
|
|
318
|
+
let o = `<p class="small"><b>${r.n_over_rating ? r.n_over_rating + ' station(s) over a rating' : 'every station within its ratings'}</b> — ${esc(r.basis)}; ${esc(r.deviation)}.</p>`;
|
|
319
|
+
o += `<table><thead><tr><th>Depth (ft)</th><th>Tool</th><th>Temperature (°C)</th><th>Pressure (psi)</th><th>Rated</th><th>Verdict</th></tr></thead><tbody>${r.stations.map(x => `<tr><td>${fmt(x.md_ft, 0)}</td><td>${esc(x.tool)}</td><td>${fmt(x.station_temp_C, 1)}</td><td>${fmt(x.station_pressure_psi, 0)}</td><td class="small">${x.temp_rating_C != null ? fmt(x.temp_rating_C, 0) + ' °C' : '—'}${x.pressure_rating_psi != null ? ' / ' + fmt(x.pressure_rating_psi, 0) + ' psi' : ''}</td><td>${statusPill(x.ok ? 'good' : 'serious', x.ok ? 'within ratings' : (x.over_temp_rating ? 'OVER TEMPERATURE' : 'OVER PRESSURE'))}</td></tr>`).join('')}</tbody></table>`;
|
|
320
|
+
$('#rcOut').innerHTML = o; } catch (e) { toast(e.message, 'err'); } };
|
|
321
|
+
};
|
|
267
322
|
VIEWS.sites = async () => {
|
|
268
323
|
const {sites} = await get('/api/sites');
|
|
269
324
|
let h = `<h2>Sites</h2><p class="muted small">A site is the engagement: the wells, the seismic stations and the tracks that belong to one client's ground, held together so there is one thing to refresh and one thing to hand over. The Site Report pulls every leg of it into one document and names anything that has not been run since the thing it was made from changed.</p>`;
|
|
@@ -1099,7 +1154,7 @@ function authScreen(first) {
|
|
|
1099
1154
|
}
|
|
1100
1155
|
|
|
1101
1156
|
// ---------- routing ---------------------------------------------------------------------
|
|
1102
|
-
const NAV = [['#/', 'Home'], ['#/sites', 'Sites'], ['#/wells', 'Wells'], ['#/seismic', 'Seismic'], ['#/sar', 'SAR panel'], ['#/live', 'Patch panel'], ['#/files', 'Files'], ['#/alarms', 'Alarms'], ['#/approvals', 'Approvals'], ['#/config', 'Configuration'], ['#/reports', 'Reports'], ['#/verify', 'Verification'], ['#/survey', 'Survey'], ['#/jobs', 'Jobs'], ['#/audit', 'Audit / Update'], ['#/admin', 'Administration'], ['#/help', 'Help']];
|
|
1157
|
+
const NAV = [['#/', 'Home'], ['#/sites', 'Sites'], ['#/wells', 'Wells'], ['#/seismic', 'Seismic'], ['#/sar', 'SAR panel'], ['#/live', 'Patch panel'], ['#/files', 'Files'], ['#/alarms', 'Alarms'], ['#/approvals', 'Approvals'], ['#/config', 'Configuration'], ['#/reports', 'Reports'], ['#/verify', 'Verification'], ['#/survey', 'Survey'], ['#/tools', 'Tools & ports'], ['#/jobs', 'Jobs'], ['#/audit', 'Audit / Update'], ['#/admin', 'Administration'], ['#/help', 'Help']];
|
|
1103
1158
|
const BADGE = {'#/alarms': b => [b.alarms_unacked, ''], '#/approvals': b => [b.approvals_pending, 'w'], '#/jobs': b => [b.jobs_failed_24h, ''], '#/live': b => [b.patches_down, '']};
|
|
1104
1159
|
function renderNav() { const cur = (location.hash || '#/').split('?')[0]; $('#nav').innerHTML = NAV.map(([h, l]) => { const f = BADGE[h]; const [n, k] = f ? f(BADGES) : [0, '']; return `<a href="${h}" ${(h === '#/' ? cur === '#/' || cur === '' : cur.startsWith(h)) ? 'aria-current="page"' : ''}>${l}${n ? `<span class="b ${k}" title="${n} needing attention">${n}</span>` : ''}</a>`; }).join(''); }
|
|
1105
1160
|
async function refreshBadges() { if (!me) return; try { BADGES = await get('/api/badges'); renderNav(); const t = BADGES.alarms_unacked ? `(${BADGES.alarms_unacked}) ` : ''; document.title = `${t}GEA-Program · ${workspaceName}`; } catch (e) {} }
|