gea-program 0.34.0__tar.gz → 0.36.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.34.0/gea_program.egg-info → gea_program-0.36.0}/PKG-INFO +35 -1
- {gea_program-0.34.0 → gea_program-0.36.0}/README.md +34 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/__init__.py +1 -1
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/__main__.py +105 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/acceptance_tests.py +354 -0
- gea_program-0.36.0/gea/control_room.py +509 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/geodesy.py +1 -0
- gea_program-0.36.0/gea/help/control-room.md +11 -0
- gea_program-0.36.0/gea/help/well3d.md +11 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/helplib.py +3 -1
- gea_program-0.36.0/gea/reference/volve_3d.provenance.json +198 -0
- gea_program-0.36.0/gea/reference/volve_surfaces.json.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_traj_F-10.xml.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_traj_F-4.xml.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- gea_program-0.36.0/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/service.py +122 -4
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/web/app.html +379 -6
- gea_program-0.36.0/gea/well3d.py +688 -0
- {gea_program-0.34.0 → gea_program-0.36.0/gea_program.egg-info}/PKG-INFO +35 -1
- {gea_program-0.34.0 → gea_program-0.36.0}/gea_program.egg-info/SOURCES.txt +13 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/pyproject.toml +1 -1
- {gea_program-0.34.0 → gea_program-0.36.0}/LICENSE +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/backup.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/bench.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/blind_harness.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog_map.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/catalog_register.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/certificates.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/cli.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/client_reports.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/config_versioning.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/conformance.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/correlation.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/dashboard.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/deviation.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/dlis.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/dnp3.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/doctor.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/earth_model.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/edr.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/elevation.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/etp.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/example_register_map.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/fat_sat.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/files.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/follower.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/forward_model.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/friction_priors.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/gamma.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/hartip.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/alarms.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/backup.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/catalog.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/charts.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/conformance.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/data-in.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/dlis.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/doctor.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/drift.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/edr.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/etp.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/files.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/historian.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/instruments.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/interval.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/map.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/month-end.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/notifications.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/osdu.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/patches.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/quality.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/seismic.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/setup.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/site.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/sites.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/slip.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/soak.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/sra.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/start.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/structure.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/tools.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/transients.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/vibration.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/housekeeping.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/interval.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/jobs.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/live_ports.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/loadtest.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/modbus.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/model_card.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/month_end.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/notify.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/opcua_port.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/operator_app.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/osdu.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/patches.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/permits.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/piwebapi.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/ports.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/ppdm.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/project.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/quakeml.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reconciler.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/sample_record.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/sbom.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seedlink.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/segy.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_array.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_film.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_response.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_track.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/series_cache.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/service_life.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/shapefile.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/shell.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/shut_in.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/site_map.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/sla_report.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/slip.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/soak.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/sra.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/store_forward.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/strata_join.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/structure.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/supervisor.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/survey_view.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/telemetry.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/tool_catalog.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/tool_library.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/transient.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/vibration.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/well_assembler.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/wits0.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/witsml.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea/workspace.py +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.34.0 → gea_program-0.36.0}/setup.cfg +0 -0
- {gea_program-0.34.0 → gea_program-0.36.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.36.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,40 @@ 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
|
+
### The well in 3D
|
|
652
|
+
|
|
653
|
+
The 3D tab draws each well's path from its directional survey by the
|
|
654
|
+
minimum-curvature method, below sea level from its declared rig floor, among
|
|
655
|
+
the mapped formations - interpreted horizon surfaces, or a formation-top
|
|
656
|
+
map's contours at their elevations - with its picks on it. Colour the wells
|
|
657
|
+
by formation, inclination, dogleg severity, or a drilling recorder's gamma,
|
|
658
|
+
rate of penetration or weight on bit along the bit depth; replay the bit
|
|
659
|
+
going down the hole from the recorder's bit depth or the survey stations'
|
|
660
|
+
own times. Turn, pan and zoom by pointer, touch or keys. The Volve field
|
|
661
|
+
(Equinor's open data: three original surveys, the field's formation picks,
|
|
662
|
+
five mapped horizons) ships as the worked example, checked against itself.
|
|
663
|
+
Until then the workspace's formation-top contours are drawn on their own.
|
|
664
|
+
A workspace well is drawn once it has a position, a reference elevation and
|
|
665
|
+
a survey: `gea trajectory` attaches one, verbatim, with its azimuth
|
|
666
|
+
reference stated. `gea well3d`, `gea help well3d`.
|
|
667
|
+
|
|
668
|
+
### The control room
|
|
669
|
+
|
|
670
|
+
The Control room tab opens the wall: a screen read from across the room, one
|
|
671
|
+
tile per well, worst first, each with its state in a shape and a word -
|
|
672
|
+
Serious, Watch, Good, No data - the reasons in plain words, the alarms, the
|
|
673
|
+
gauges, and for a drilling recorder the bottom, the bit, the rate of
|
|
674
|
+
penetration and the hours on bottom at the end of the record. It refreshes
|
|
675
|
+
itself every 30 seconds, says how old its last answer is, and puts a notice
|
|
676
|
+
across the screen when the service stops answering; a recorded well is
|
|
677
|
+
described at the end of its record and never called stale. F is full screen;
|
|
678
|
+
the screen is kept awake where the browser allows it; when the wells do not
|
|
679
|
+
fit, the pages turn, the worst first. The outgoing operator writes the shift
|
|
680
|
+
handover - what is open, what is being watched, what was done - and it stays
|
|
681
|
+
on the wall until the incoming operator acknowledges it; both go in the audit
|
|
682
|
+
log. A wall pass lets a screen show the wall and nothing else with no one's
|
|
683
|
+
account signed in. `gea wall`, `gea handover`, `gea help control-room`.
|
|
684
|
+
|
|
651
685
|
### The site map
|
|
652
686
|
|
|
653
687
|
The Map tab puts everything in the workspace that has a position in one
|
|
@@ -609,6 +609,40 @@ 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
|
+
### The well in 3D
|
|
613
|
+
|
|
614
|
+
The 3D tab draws each well's path from its directional survey by the
|
|
615
|
+
minimum-curvature method, below sea level from its declared rig floor, among
|
|
616
|
+
the mapped formations - interpreted horizon surfaces, or a formation-top
|
|
617
|
+
map's contours at their elevations - with its picks on it. Colour the wells
|
|
618
|
+
by formation, inclination, dogleg severity, or a drilling recorder's gamma,
|
|
619
|
+
rate of penetration or weight on bit along the bit depth; replay the bit
|
|
620
|
+
going down the hole from the recorder's bit depth or the survey stations'
|
|
621
|
+
own times. Turn, pan and zoom by pointer, touch or keys. The Volve field
|
|
622
|
+
(Equinor's open data: three original surveys, the field's formation picks,
|
|
623
|
+
five mapped horizons) ships as the worked example, checked against itself.
|
|
624
|
+
Until then the workspace's formation-top contours are drawn on their own.
|
|
625
|
+
A workspace well is drawn once it has a position, a reference elevation and
|
|
626
|
+
a survey: `gea trajectory` attaches one, verbatim, with its azimuth
|
|
627
|
+
reference stated. `gea well3d`, `gea help well3d`.
|
|
628
|
+
|
|
629
|
+
### The control room
|
|
630
|
+
|
|
631
|
+
The Control room tab opens the wall: a screen read from across the room, one
|
|
632
|
+
tile per well, worst first, each with its state in a shape and a word -
|
|
633
|
+
Serious, Watch, Good, No data - the reasons in plain words, the alarms, the
|
|
634
|
+
gauges, and for a drilling recorder the bottom, the bit, the rate of
|
|
635
|
+
penetration and the hours on bottom at the end of the record. It refreshes
|
|
636
|
+
itself every 30 seconds, says how old its last answer is, and puts a notice
|
|
637
|
+
across the screen when the service stops answering; a recorded well is
|
|
638
|
+
described at the end of its record and never called stale. F is full screen;
|
|
639
|
+
the screen is kept awake where the browser allows it; when the wells do not
|
|
640
|
+
fit, the pages turn, the worst first. The outgoing operator writes the shift
|
|
641
|
+
handover - what is open, what is being watched, what was done - and it stays
|
|
642
|
+
on the wall until the incoming operator acknowledges it; both go in the audit
|
|
643
|
+
log. A wall pass lets a screen show the wall and nothing else with no one's
|
|
644
|
+
account signed in. `gea wall`, `gea handover`, `gea help control-room`.
|
|
645
|
+
|
|
612
646
|
### The site map
|
|
613
647
|
|
|
614
648
|
The Map tab puts everything in the workspace that has a position in one
|
|
@@ -475,6 +475,38 @@ def main(argv=None) -> int:
|
|
|
475
475
|
p_ck.add_argument("--note", type=str, default="", help="--confirm: what it was checked against (required)")
|
|
476
476
|
p_ck.add_argument("--actor", type=str, default="cli")
|
|
477
477
|
|
|
478
|
+
p_tj = sub.add_parser("trajectory", help="attach a well's directional survey (WITSML trajectory or a table of MD, Inc, Azi), kept verbatim beside the well, so the 3D tab can draw its path")
|
|
479
|
+
p_tj.add_argument("--workspace", type=str, required=True)
|
|
480
|
+
p_tj.add_argument("--well", type=str, required=True)
|
|
481
|
+
p_tj.add_argument("--file", type=str, required=True)
|
|
482
|
+
p_tj.add_argument("--azimuth-reference", type=str, required=True, choices=("grid", "true", "magnetic"))
|
|
483
|
+
p_tj.add_argument("--grid", type=str, default=None, help="grid azimuths: the grid, e.g. TX_CENTRAL or UTM14N")
|
|
484
|
+
p_tj.add_argument("--declination", type=float, default=None, help="magnetic azimuths: the declination used, degrees east positive")
|
|
485
|
+
p_tj.add_argument("--md-unit", type=str, default="ft", choices=("ft", "m"))
|
|
486
|
+
p_tj.add_argument("--depth-reference", type=str, default=None, help="KB, DF or GL: must match the well's declared reference elevation")
|
|
487
|
+
p_tj.add_argument("--source", type=str, required=True, help='where the survey came from, e.g. "RRC imaged records, directional survey"')
|
|
488
|
+
p_tj.add_argument("--actor", type=str, default="cli")
|
|
489
|
+
|
|
490
|
+
p_3d = sub.add_parser("well3d", help="the 3D scene in words: each well's path, how it was checked, the surfaces and the replay (the 3D tab draws it)")
|
|
491
|
+
p_3d.add_argument("--scene", type=str, default="volve", choices=("volve", "workspace"))
|
|
492
|
+
p_3d.add_argument("--workspace", type=str, default=None)
|
|
493
|
+
p_3d.add_argument("--json", action="store_true")
|
|
494
|
+
|
|
495
|
+
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)")
|
|
496
|
+
p_wl.add_argument("--workspace", type=str, default=None, help="the workspace folder, e.g. C:\\site")
|
|
497
|
+
p_wl.add_argument("--json", action="store_true")
|
|
498
|
+
|
|
499
|
+
p_ho = sub.add_parser("handover", help="the shift handover: list the notes, sign one (--write) or acknowledge one (--ack)")
|
|
500
|
+
p_ho.add_argument("--workspace", type=str, default=None, help="the workspace folder, e.g. C:\\site")
|
|
501
|
+
p_ho.add_argument("--write", action="store_true", help="sign a note: give at least one of --open, --watching, --done")
|
|
502
|
+
p_ho.add_argument("--open", type=str, default="", help="--write: what the next shift must pick up")
|
|
503
|
+
p_ho.add_argument("--watching", type=str, default="", help="--write: what is being watched, and why")
|
|
504
|
+
p_ho.add_argument("--done", type=str, default="", help="--write: what this shift did")
|
|
505
|
+
p_ho.add_argument("--ack", type=str, default=None, help="acknowledge the note with this id (not your own)")
|
|
506
|
+
p_ho.add_argument("--comment", type=str, default="", help="--ack: an optional comment")
|
|
507
|
+
p_ho.add_argument("--actor", type=str, default=None, help="who signs or acknowledges (required with --write and --ack)")
|
|
508
|
+
p_ho.add_argument("--json", action="store_true")
|
|
509
|
+
|
|
478
510
|
p_sv = sub.add_parser("serve", help="serve the dashboard over a workspace: the page, the reports and the JSON API (standard library HTTP)")
|
|
479
511
|
p_sv.add_argument("--workspace", type=str, required=True, help="the workspace folder (gea workspace --action init)")
|
|
480
512
|
p_sv.add_argument("--host", type=str, default="127.0.0.1", help="bind address; 0.0.0.0 exposes it on the site network (put TLS in front)")
|
|
@@ -1922,6 +1954,79 @@ def main(argv=None) -> int:
|
|
|
1922
1954
|
else:
|
|
1923
1955
|
print(_SC.text(r))
|
|
1924
1956
|
return 0 if r["ready"] else 2
|
|
1957
|
+
elif a.cmd in ("trajectory", "well3d"):
|
|
1958
|
+
import json as _json
|
|
1959
|
+
from . import well3d as _W3
|
|
1960
|
+
from .cli import _safe_stdout
|
|
1961
|
+
from .workspace import Workspace, WorkspaceError
|
|
1962
|
+
_safe_stdout()
|
|
1963
|
+
ws = None
|
|
1964
|
+
if a.cmd == "trajectory" or a.scene == "workspace":
|
|
1965
|
+
if not a.workspace:
|
|
1966
|
+
raise SystemExit(f"{a.cmd} needs --workspace, e.g. --workspace C:\\site")
|
|
1967
|
+
try:
|
|
1968
|
+
ws = Workspace(a.workspace)
|
|
1969
|
+
except WorkspaceError as e:
|
|
1970
|
+
raise SystemExit(f"{a.cmd}: {e}")
|
|
1971
|
+
if a.cmd == "trajectory":
|
|
1972
|
+
try:
|
|
1973
|
+
with open(a.file, "rb") as f:
|
|
1974
|
+
content = f.read()
|
|
1975
|
+
r = _W3.attach_trajectory(ws, a.well, a.file, content, a.actor, a.azimuth_reference, a.md_unit, a.depth_reference, a.grid,
|
|
1976
|
+
a.declination, a.source)
|
|
1977
|
+
except (ValueError, OSError, WorkspaceError) as e:
|
|
1978
|
+
raise SystemExit(f"trajectory: {e}")
|
|
1979
|
+
print(f"{a.well}: {r['stations']} stations, {r['md_range_m'][0]:,.1f} to {r['md_range_m'][1]:,.1f} m MD, azimuths {r['azimuth_reference']}"
|
|
1980
|
+
+ (f" ({r['grid']})" if r.get('grid') else "") + f"; kept as wells/{a.well}/trajectory/{r['file']}")
|
|
1981
|
+
return 0
|
|
1982
|
+
sc = _W3.scene(ws, a.scene)
|
|
1983
|
+
print(_json.dumps(sc, indent=1) if a.json else _W3.text(sc))
|
|
1984
|
+
return 0
|
|
1985
|
+
elif a.cmd in ("wall", "handover"):
|
|
1986
|
+
import json as _json
|
|
1987
|
+
from . import control_room as _CR
|
|
1988
|
+
from .cli import _safe_stdout
|
|
1989
|
+
from .workspace import Workspace, WorkspaceError
|
|
1990
|
+
_safe_stdout()
|
|
1991
|
+
if not a.workspace:
|
|
1992
|
+
raise SystemExit(f"{a.cmd} needs --workspace, e.g. --workspace C:\\site")
|
|
1993
|
+
try:
|
|
1994
|
+
ws = Workspace(a.workspace)
|
|
1995
|
+
except WorkspaceError as e:
|
|
1996
|
+
raise SystemExit(f"{a.cmd}: {e}")
|
|
1997
|
+
if a.cmd == "wall":
|
|
1998
|
+
b = _CR.board(ws)
|
|
1999
|
+
print(_json.dumps(b, indent=1) if a.json else _CR.text(b))
|
|
2000
|
+
return 0
|
|
2001
|
+
try:
|
|
2002
|
+
if a.write:
|
|
2003
|
+
if not a.actor:
|
|
2004
|
+
raise SystemExit("handover --write needs --actor, the name the note is signed with")
|
|
2005
|
+
b = _CR.board(ws)
|
|
2006
|
+
counts = {**b["counts"], "alarms_unacked": sum(((t.get("alarms") or {}).get("unacked") or 0) for t in b["tiles"])}
|
|
2007
|
+
n = _CR.write_note(ws, a.actor, a.open, a.watching, a.done, counts)
|
|
2008
|
+
print(f"handover {n['id']} signed by {n['by']} at {n['utc']}")
|
|
2009
|
+
return 0
|
|
2010
|
+
if a.ack:
|
|
2011
|
+
if not a.actor:
|
|
2012
|
+
raise SystemExit("handover --ack needs --actor, the incoming operator's name")
|
|
2013
|
+
r = _CR.ack_note(ws, a.ack, a.actor, a.comment)
|
|
2014
|
+
print(f"handover {a.ack} acknowledged by {r['by']} at {r['utc']}")
|
|
2015
|
+
return 0
|
|
2016
|
+
except (ValueError, KeyError, PermissionError) as e:
|
|
2017
|
+
raise SystemExit(f"handover: {e.args[0] if e.args else e}")
|
|
2018
|
+
ns = _CR.notes(ws, 0)
|
|
2019
|
+
if a.json:
|
|
2020
|
+
print(_json.dumps(ns, indent=1))
|
|
2021
|
+
elif not ns:
|
|
2022
|
+
print("no handover notes yet")
|
|
2023
|
+
else:
|
|
2024
|
+
for n in ns:
|
|
2025
|
+
print(f"{n['id']} {n['utc']} by {n['by']} " + (f"acknowledged by {n['ack']['by']} at {n['ack']['utc']}" if n.get('ack') else "NOT YET ACKNOWLEDGED"))
|
|
2026
|
+
for k in _CR.NOTE_FIELDS:
|
|
2027
|
+
if n.get(k):
|
|
2028
|
+
print(f" {k}: {n[k]}")
|
|
2029
|
+
return 0
|
|
1925
2030
|
elif a.cmd == "serve":
|
|
1926
2031
|
from .service import Service
|
|
1927
2032
|
from .workspace import WorkspaceError
|
|
@@ -8448,6 +8448,358 @@ def section_cc_site_map(tmp: str) -> None:
|
|
|
8448
8448
|
"bar, the not-placed wells with the way to place them, what is at a click; the help page"
|
|
8449
8449
|
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
8450
8450
|
|
|
8451
|
+
|
|
8452
|
+
def section_cd_control_room(tmp: str) -> None:
|
|
8453
|
+
"""Section CD - the control room: the wall board judged by its stated rule (a shape and a word for each state, worst
|
|
8454
|
+
first, a recorded well never called stale, a live feed's age measured), the shift handover signed by one operator and
|
|
8455
|
+
acknowledged by another with both in the audit log, wall passes that show the wall and nothing else, the page."""
|
|
8456
|
+
import http.cookiejar
|
|
8457
|
+
import urllib.request
|
|
8458
|
+
import urllib.error
|
|
8459
|
+
from datetime import datetime as _dt, timedelta as _td, timezone as _tz
|
|
8460
|
+
from . import control_room as CR
|
|
8461
|
+
from . import edr as E
|
|
8462
|
+
from . import helplib as H
|
|
8463
|
+
from .workspace import Workspace
|
|
8464
|
+
from .service import Service, Users
|
|
8465
|
+
pkg = Path(__file__).parent
|
|
8466
|
+
d = Path(tmp, "cd"); d.mkdir()
|
|
8467
|
+
|
|
8468
|
+
ev = E.selftest_events(str(d))
|
|
8469
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
8470
|
+
wd = ws.add_well_file(ev["path"], display="Drilling well", actor="tester")
|
|
8471
|
+
wa = ws.add_well_file(ev["path"], display="Alarm well", actor="tester")
|
|
8472
|
+
wc = ws.add_well_catalog("volve_f12_f14_production_excerpt", "15/9-F-12", 10000.0, actor="tester")
|
|
8473
|
+
ws.refresh_dashboard(actor="tester")
|
|
8474
|
+
wn = ws.add_well_file(ev["path"], display="New well", actor="tester") # added after the refresh: no reports yet
|
|
8475
|
+
ra = Path(ws.reports_dir, "wells", wa["id"])
|
|
8476
|
+
(ra / "alarm_event_report.json").write_text(json.dumps({"active": [
|
|
8477
|
+
{"alarm_id": "SPP.HH_RANGE", "state": "ACTIVE_UNACKED", "priority": "P2", "tag_id": "SPP"},
|
|
8478
|
+
{"alarm_id": "HKLD.QUALITY", "state": "ACTIVE_ACKED", "priority": "P4", "tag_id": "HKLD"}], "shelved": []}), encoding="utf-8")
|
|
8479
|
+
now = _dt(2026, 10, 15, 12, 0, 0, tzinfo=_tz.utc)
|
|
8480
|
+
iso = lambda s: (now - _td(seconds=s)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
8481
|
+
feed = lambda age, status="CONNECTED", wid=wn["id"]: [{"name": "p1", "well_id": wid, "status": status, "last_sample_utc": (iso(age) if age is not None else None), "patch": {"stale_after_s": 120.0}}]
|
|
8482
|
+
b0 = CR.board(ws, [], now=now)
|
|
8483
|
+
t0 = {t["well_id"]: t for t in b0["tiles"]}
|
|
8484
|
+
dr = t0[wd["id"]]["drilling"]
|
|
8485
|
+
live = {k: {t["well_id"]: t for t in CR.board(ws, feed(*a), now=now)["tiles"]}[wn["id"]] for k, a in
|
|
8486
|
+
{"fresh": (30,), "late": (400,), "silent": (1000,), "down": (60, "DOWN"), "none": (None,)}.items()}
|
|
8487
|
+
c1 = {
|
|
8488
|
+
"states": t0[wa["id"]]["state"] == "SERIOUS" and t0[wa["id"]]["reasons"][:2] == ["1 unacknowledged P2 alarm(s)", "1 other active alarm(s)"]
|
|
8489
|
+
and t0[wc["id"]]["state"] == "SERIOUS" and "no explanation" in t0[wc["id"]]["reasons"][0]
|
|
8490
|
+
and t0[wd["id"]]["state"] == "GOOD" and t0[wd["id"]]["reasons"] == ["nothing needs attention"]
|
|
8491
|
+
and t0[wn["id"]]["state"] == "NO_DATA",
|
|
8492
|
+
"shape_word": all(t["shape"] == CR.SHAPE[t["state"]] and t["word"] == CR.WORD[t["state"]] for t in b0["tiles"])
|
|
8493
|
+
and len(set(CR.SHAPE.values())) == 4 and len(set(CR.WORD.values())) == 4,
|
|
8494
|
+
"order": [t["state"] for t in b0["tiles"]] == sorted((t["state"] for t in b0["tiles"]), key=CR.STATES.index)
|
|
8495
|
+
and b0["counts"] == {"SERIOUS": 2, "WATCH": 0, "GOOD": 1, "NO_DATA": 1},
|
|
8496
|
+
"recorded": t0[wd["id"]]["live"] is None and t0[wd["id"]]["recorded"]["record_end_utc"] == dr["record_end_utc"]
|
|
8497
|
+
and "never called stale" in b0["rule"],
|
|
8498
|
+
"read_only": not Path(wsp, "handover").exists() and not Path(wsp, "wall").exists(),
|
|
8499
|
+
"drilling": dr["cache"] and dr["td_md_ft"] == 5750.0 and dr["bottom_ft"] == 5750.0 and dr["on_bottom_hours"] is not None
|
|
8500
|
+
and (dr["rop_ft_h"] is not None) == bool(dr["drilling_now"]),
|
|
8501
|
+
"live": live["fresh"]["state"] == "GOOD" and live["late"]["state"] == "WATCH" and "past the 2 min stale limit" in live["late"]["reasons"][0]
|
|
8502
|
+
and live["silent"]["state"] == "SERIOUS" and live["silent"]["reasons"][0].startswith("no live sample for 16 min")
|
|
8503
|
+
and live["down"]["state"] == "SERIOUS" and live["none"]["state"] == "WATCH" and abs(live["fresh"]["live"]["age_s"] - 30) < 1e-6,
|
|
8504
|
+
}
|
|
8505
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
8506
|
+
ok(not failed1,
|
|
8507
|
+
"CD1 the wall board by its stated rule, read without writing anything: an unacknowledged P2 alarm and a gauge off with no explanation SERIOUS, a well with reports and "
|
|
8508
|
+
"nothing else GOOD, a well with neither reports nor a live sample NO DATA, each state its own shape and word, worst first; a recorded well "
|
|
8509
|
+
"described at the end of its record and never called stale (bottom 5,750 ft, the ROP only while drilling at the end); a live feed's "
|
|
8510
|
+
"measured age against its stale limit - 30 s good, 400 s watch, 1,000 s serious, a patch down serious"
|
|
8511
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
8512
|
+
|
|
8513
|
+
users = Users(str(Path(wsp, "users.json")))
|
|
8514
|
+
for n_, r_ in (("adm", "admin"), ("op1", "operator"), ("op2", "operator"), ("v", "viewer")):
|
|
8515
|
+
users.add(n_, f"{n_}-pass-123", r_)
|
|
8516
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
8517
|
+
base_url = svc.url.rstrip("/")
|
|
8518
|
+
|
|
8519
|
+
def opener():
|
|
8520
|
+
return urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
8521
|
+
|
|
8522
|
+
def call(op, method, path, body=None):
|
|
8523
|
+
req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
8524
|
+
if method == "POST":
|
|
8525
|
+
req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
|
|
8526
|
+
try:
|
|
8527
|
+
with op.open(req, timeout=120) as rr_:
|
|
8528
|
+
return rr_.status, json.loads(rr_.read())
|
|
8529
|
+
except urllib.error.HTTPError as e:
|
|
8530
|
+
return e.code, json.loads(e.read())
|
|
8531
|
+
|
|
8532
|
+
def signin(name):
|
|
8533
|
+
op = opener(); call(op, "POST", "/api/login", {"name": name, "password": f"{name}-pass-123"}); return op
|
|
8534
|
+
try:
|
|
8535
|
+
anon = opener()
|
|
8536
|
+
st_anon, _ = call(anon, "GET", "/api/wall")
|
|
8537
|
+
V, O1, O2, A = signin("v"), signin("op1"), signin("op2"), signin("adm")
|
|
8538
|
+
st_v, bv = call(V, "GET", "/api/wall")
|
|
8539
|
+
st_vw, _ = call(V, "POST", "/api/handover", {"open": "x"})
|
|
8540
|
+
st_empty, _ = call(O1, "POST", "/api/handover", {"open": " ", "watching": "", "done": ""})
|
|
8541
|
+
st_long, _ = call(O1, "POST", "/api/handover", {"open": "x" * 2001})
|
|
8542
|
+
st_w, note = call(O1, "POST", "/api/handover", {"open": "Pump 2 liner swap not finished.", "watching": "SPP on the alarm well.", "done": "Reamed to bottom."})
|
|
8543
|
+
_, bdg1 = call(V, "GET", "/api/badges")
|
|
8544
|
+
_, wall1 = call(V, "GET", "/api/wall")
|
|
8545
|
+
st_self, self_ = call(O1, "POST", "/api/handover/ack", {"id": note.get("id")})
|
|
8546
|
+
st_ack, ack = call(O2, "POST", "/api/handover/ack", {"id": note.get("id"), "comment": "read at 06:05"})
|
|
8547
|
+
st_ack2, _ = call(O2, "POST", "/api/handover/ack", {"id": note.get("id")})
|
|
8548
|
+
st_nf, _ = call(O2, "POST", "/api/handover/ack", {"id": "H-none"})
|
|
8549
|
+
_, bdg2 = call(V, "GET", "/api/badges")
|
|
8550
|
+
_, hv = call(V, "GET", "/api/handover")
|
|
8551
|
+
# wall passes
|
|
8552
|
+
st_pv, _ = call(V, "POST", "/api/wall/pass", {"label": "doghouse"})
|
|
8553
|
+
st_pnl, _ = call(A, "POST", "/api/wall/pass", {"label": " "})
|
|
8554
|
+
st_pc, wp = call(A, "POST", "/api/wall/pass", {"label": "doghouse wall", "days": 30})
|
|
8555
|
+
_, plist = call(A, "GET", "/api/wall/passes")
|
|
8556
|
+
W = opener()
|
|
8557
|
+
st_bad, _ = call(W, "POST", "/api/wall/enter", {"code": wp["id"] + ".wrong"})
|
|
8558
|
+
st_in, _ = call(W, "POST", "/api/wall/enter", {"code": wp["code"]})
|
|
8559
|
+
st_ww, wwall = call(W, "GET", "/api/wall")
|
|
8560
|
+
_, wsess = call(W, "GET", "/api/session")
|
|
8561
|
+
st_wo, _ = call(W, "GET", "/api/overview")
|
|
8562
|
+
st_wh, _ = call(W, "GET", "/api/handover")
|
|
8563
|
+
st_wpost, _ = call(W, "POST", "/api/handover", {"open": "x"})
|
|
8564
|
+
st_rv, _ = call(A, "POST", "/api/wall/pass/revoke", {"id": wp["id"]})
|
|
8565
|
+
st_after, _ = call(W, "GET", "/api/wall")
|
|
8566
|
+
_, plist2 = call(A, "GET", "/api/wall/passes")
|
|
8567
|
+
L = opener()
|
|
8568
|
+
lock = [call(L, "POST", "/api/wall/enter", {"code": "W000000.nope"})[0] for _ in range(6)]
|
|
8569
|
+
finally:
|
|
8570
|
+
svc.stop()
|
|
8571
|
+
audit = [e["action"] for e in ws.audit_log(5000)]
|
|
8572
|
+
raw = (Path(wsp, "wall", "passes.json")).read_text(encoding="utf-8")
|
|
8573
|
+
lines = Path(wsp, "handover", "notes.jsonl").read_text(encoding="utf-8").splitlines()
|
|
8574
|
+
c2 = {
|
|
8575
|
+
"roles": st_anon == 401 and st_v == 200 and len(bv["tiles"]) == 4 and st_vw == 403,
|
|
8576
|
+
"note": st_empty == 400 and st_long == 400 and st_w == 200 and note["by"] == "op1" and note["ack"] is None
|
|
8577
|
+
and note["board"]["SERIOUS"] == 2 and note["board"]["alarms_unacked"] == 1
|
|
8578
|
+
and wall1["handover"]["id"] == note["id"] and wall1["handover"]["ack"] is None and bdg1["handover_unacked"] == 1,
|
|
8579
|
+
"ack": st_self == 403 and "author cannot acknowledge" in self_["error"] and st_ack == 200 and ack["by"] == "op2"
|
|
8580
|
+
and st_ack2 == 400 and st_nf == 404 and bdg2["handover_unacked"] == 0
|
|
8581
|
+
and hv["notes"][0]["ack"]["by"] == "op2" and hv["notes"][0]["ack"]["comment"] == "read at 06:05",
|
|
8582
|
+
"append_only": len(lines) == 2 and json.loads(lines[0])["kind"] == "note" and json.loads(lines[1])["kind"] == "ack",
|
|
8583
|
+
"audit": all(a_ in audit for a_ in ("handover.write", "handover.ack", "wall.pass.create", "wall.pass.failed", "wall.pass.enter", "wall.pass.revoke")),
|
|
8584
|
+
}
|
|
8585
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
8586
|
+
ok(not failed2,
|
|
8587
|
+
"CD2 the shift handover: a viewer reads the wall and cannot write; an empty or over-long note refused; a signed note keeps its author, its "
|
|
8588
|
+
"time and the board's counts (2 serious, 1 unacknowledged alarm), shows on the wall and badges the tab until acknowledged; the author "
|
|
8589
|
+
"cannot acknowledge it, the next operator can (once, with a comment); notes and acknowledgements appended, never rewritten; every step in "
|
|
8590
|
+
"the audit log"
|
|
8591
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
8592
|
+
|
|
8593
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
8594
|
+
help_md = (pkg / "help" / "control-room.md").read_text(encoding="utf-8")
|
|
8595
|
+
c3 = {
|
|
8596
|
+
"pass": st_pv == 403 and st_pnl == 400 and st_pc == 200 and wp["code"].startswith(wp["id"] + ".") and wp["code"].split(".", 1)[1] not in raw
|
|
8597
|
+
and "hash" not in json.dumps(plist) and plist["passes"][0]["status"] == "ACTIVE",
|
|
8598
|
+
"wall_only": st_bad == 401 and st_in == 200 and st_ww == 200 and len(wwall["tiles"]) == 4 and wsess["user"] is None
|
|
8599
|
+
and wsess["wall_pass"]["label"] == "doghouse wall" and st_wo == 401 and st_wh == 401 and st_wpost == 401,
|
|
8600
|
+
"revoke": st_rv == 200 and st_after == 401 and plist2["passes"][0]["status"] == "REVOKED" and lock[-1] == 429,
|
|
8601
|
+
"page": all(x in page_src for x in ("VIEWS.wall", "VIEWS.control", "['#/control', 'Control room']", "/api/wall", "/api/handover/ack",
|
|
8602
|
+
"NOT UPDATING", "wakeLock", "requestFullscreen", "SIGNED OUT", "This is a wall screen",
|
|
8603
|
+
"crShape", "data-age0", "turns pages", "/api/wall/pass/revoke", "handover_unacked")),
|
|
8604
|
+
"help": any(t["topic"] == "control-room" for t in H.topics()) and not H.check("control-room") and H.VIEW_TOPIC.get("control") == "control-room"
|
|
8605
|
+
and "never called stale" in help_md.split("will not call a measurement")[1],
|
|
8606
|
+
}
|
|
8607
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
8608
|
+
ok(not failed3,
|
|
8609
|
+
"CD3 wall passes: issued by an administrator only and named for the screen, the code shown once and only its hash kept; a wrong pass "
|
|
8610
|
+
"refused and locked out after repeats; a pass shows the wall and nothing else - no other page, no handover list, no writing - until it is "
|
|
8611
|
+
"revoked; the page: the wall full screen with its shapes and words, the age of its last answer, NOT UPDATING across the screen when the "
|
|
8612
|
+
"service stops, the screen kept awake, pages turned when the wells do not fit, the handover written and acknowledged; the help page"
|
|
8613
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
8614
|
+
|
|
8615
|
+
|
|
8616
|
+
|
|
8617
|
+
def section_ce_well3d(tmp: str) -> None:
|
|
8618
|
+
"""Section CE - the well in 3D: the minimum-curvature method against a known arc and against the survey companies'
|
|
8619
|
+
own positions, the shipped Volve excerpt checked against itself (frame, picks, surfaces, provenance), a workspace
|
|
8620
|
+
well drawn from an attached survey among its formation-top contours with its recorder replay, the API and the tab."""
|
|
8621
|
+
import base64 as _b64
|
|
8622
|
+
import gzip as _gz
|
|
8623
|
+
import hashlib as _hl
|
|
8624
|
+
import http.cookiejar
|
|
8625
|
+
import math as _m
|
|
8626
|
+
import subprocess as _sp
|
|
8627
|
+
import urllib.request
|
|
8628
|
+
import urllib.error
|
|
8629
|
+
from . import edr as E
|
|
8630
|
+
from . import geodesy as G
|
|
8631
|
+
from . import helplib as H
|
|
8632
|
+
from . import shapefile as SF
|
|
8633
|
+
from . import well3d as W
|
|
8634
|
+
from .workspace import Workspace
|
|
8635
|
+
from .service import Service, Users
|
|
8636
|
+
pkg = Path(__file__).parent
|
|
8637
|
+
d = Path(tmp, "ce"); d.mkdir()
|
|
8638
|
+
|
|
8639
|
+
R = 1000.0
|
|
8640
|
+
q = W.min_curvature([0.0, _m.pi * R / 2], [0.0, 90.0], [0.0, 0.0])
|
|
8641
|
+
p_arc = W.Path([0.0, _m.pi * R / 2], [0.0, 90.0], [0.0, 0.0], 0.0, 0.0, 0.0)
|
|
8642
|
+
mid = p_arc.at(_m.pi * R / 4)
|
|
8643
|
+
tr4 = W.read_witsml_trajectory(_gz.decompress((pkg / "reference" / "volve_traj_F-4.xml.gz").read_bytes()).decode())[0]
|
|
8644
|
+
tr10 = W.read_witsml_trajectory(_gz.decompress((pkg / "reference" / "volve_traj_F-10.xml.gz").read_bytes()).decode())[0]
|
|
8645
|
+
f5 = [t for n in ("36in", "17.5in", "12.25in", "8.5in") for t in W.read_witsml_trajectory(_gz.decompress((pkg / "reference" / f"volve_traj_F-5_{n}.xml.gz").read_bytes()).decode())]
|
|
8646
|
+
rep5, _ = W.replay_from_stations(f5)
|
|
8647
|
+
rep4, note4 = W.replay_from_stations([tr4])
|
|
8648
|
+
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"
|
|
8649
|
+
refused = []
|
|
8650
|
+
for bad in ("Depth,Angle\n0,0\n", "MD,Inc,Azi\n0,0,0\n0,1,2\n", "MD,Inc,Azi\n0,0,0\n100,x,2\n", "MD,Inc,Azi\n0,0,0\n100,200,2\n"):
|
|
8651
|
+
try:
|
|
8652
|
+
W.read_survey_csv(bad)
|
|
8653
|
+
except ValueError:
|
|
8654
|
+
refused.append(True)
|
|
8655
|
+
st_csv = W.read_survey_csv(csv_ok, "ft")
|
|
8656
|
+
c1 = {
|
|
8657
|
+
"arc": abs(q["n"][1] - R) < 1e-9 and abs(q["v"][1] - R) < 1e-9 and abs(mid[1] - R * (1 - _m.cos(_m.pi / 4))) < 1e-6
|
|
8658
|
+
and abs(mid[2] - R * _m.sin(_m.pi / 4)) < 1e-6 and abs(q["dls"][1] - 90.0 / (_m.pi * R / 2) * 30.0) < 1e-9,
|
|
8659
|
+
"units": tr4["azi_ref"] == "true north" and abs(tr4["stations"][40]["incl"] - _m.degrees(0.446106147512686)) < 1e-9 and tr10["grid_cor_deg"] == -0.95
|
|
8660
|
+
and len(tr4["stations"]) == 87 and abs(st_csv[-1]["md"] - 5750 * 0.3048) < 1e-9,
|
|
8661
|
+
"company": all(x is not None and x < 0.01 for x in (W.file_position_miss(tr4["stations"]), W.file_position_miss(tr10["stations"]),
|
|
8662
|
+
W.file_position_miss(f5[-1]["stations"]))),
|
|
8663
|
+
"csv": len(refused) == 4,
|
|
8664
|
+
"replay": rep5 is not None and len(rep5["t"]) == 100 and rep5["t"][0].startswith("2007-11-16") and rep5["t"][-1].startswith("2008-07-29")
|
|
8665
|
+
and rep4 is None and "1 distinct time" in note4 and all(m0 <= m1 or t0 < t1 for (t0, m0), (t1, m1) in zip(zip(rep5["t"], rep5["md"]), zip(rep5["t"][1:], rep5["md"][1:]))),
|
|
8666
|
+
}
|
|
8667
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
8668
|
+
ok(not failed1,
|
|
8669
|
+
"CE1 the minimum-curvature method: a quarter circle of 1,000 m radius ends 1,000 m out and 1,000 m down, and its middle lies on the arc "
|
|
8670
|
+
"(292.9 m out, 707.1 m down), not on a chord; the WITSML reader takes radians and degrees alike; the paths computed from the Volve files "
|
|
8671
|
+
"reproduce the TVD and displacements the survey companies wrote in them to under a centimetre; a survey table without its columns, with a "
|
|
8672
|
+
"repeated depth or a bad row refused; the replay only from stations measured while drilling (F-5: 100 steps, Nov 2007 to Jul 2008; F-4's "
|
|
8673
|
+
"one-time resurvey not replayed)"
|
|
8674
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
8675
|
+
|
|
8676
|
+
sc = W.volve_scene()
|
|
8677
|
+
wl = {w["id"]: w for w in sc["wells"]}
|
|
8678
|
+
picks = W.volve_picks()
|
|
8679
|
+
dms = lambda dd, mm, ss: dd + mm / 60 + ss / 3600
|
|
8680
|
+
frame = []
|
|
8681
|
+
for wn, la, lo in (("NO 15/9-F-1 C", dms(58, 26, 29.907), dms(1, 53, 14.708)), ("NO 15/9-F-11 B", dms(58, 26, 29.960), dms(1, 53, 14.870))):
|
|
8682
|
+
sb = next(p for p in picks if p["WELL"] == wn and p["PICKS"] == "Seabed")
|
|
8683
|
+
a = G.unproject(float(sb["EASTING"]), float(sb["NORTHING"]), G.utm(31, True, "INTL1924"))
|
|
8684
|
+
b = G.unproject(float(sb["EASTING"]), float(sb["NORTHING"]), G.utm(31, True, "WGS84"))
|
|
8685
|
+
frame.append((G.geodesic_m(a[0], a[1], la, lo)["distance_m"], G.geodesic_m(b[0], b[1], la, lo)["distance_m"]))
|
|
8686
|
+
prov = json.loads((pkg / "reference" / "volve_3d.provenance.json").read_text(encoding="utf-8"))
|
|
8687
|
+
hashes_ok = all(_hl.sha256((pkg / "reference" / k).read_bytes()).hexdigest() == v["sha256"] for k, v in prov["outputs"].items())
|
|
8688
|
+
pts = prov["outputs"]["volve_surfaces.json.gz"]["points"]
|
|
8689
|
+
# the repository stores every text file with LF line ends (.gitattributes: text=auto eol=lf), so a package file holding
|
|
8690
|
+
# CRLF would reach a checkout - and the published wheel - as different bytes from the ones hashed here
|
|
8691
|
+
crlf = []
|
|
8692
|
+
for dp, dn, fn in os.walk(pkg):
|
|
8693
|
+
dn[:] = [x for x in dn if x not in ("__pycache__", "catalog_operator")]
|
|
8694
|
+
for f_ in fn:
|
|
8695
|
+
b_ = Path(dp, f_).read_bytes()
|
|
8696
|
+
if b"\0" not in b_[:8000] and b"\r\n" in b_:
|
|
8697
|
+
crlf.append(os.path.relpath(os.path.join(dp, f_), pkg))
|
|
8698
|
+
c2 = {
|
|
8699
|
+
"frame": all(a < 0.1 and 140 < b < 160 for a, b in frame) and "ED50" in sc["frame"]["crs"],
|
|
8700
|
+
"picks": all(wl[k]["pick_miss_max_m"] < 0.3 and abs(wl[k]["ref_elev_m"] - 54.9) < 1e-6 for k in ("F-4", "F-5", "F-10"))
|
|
8701
|
+
and abs(wl["F-10"]["td_md"] - 5300.07) < 0.01 and len(sc["picks_only"]) == 31,
|
|
8702
|
+
"azimuth": wl["F-4"]["azimuth"]["applied"] == "as grid" and "14 m" in wl["F-4"]["azimuth"]["note"] and wl["F-5"]["azimuth"]["file_reference"] == "grid north",
|
|
8703
|
+
"surfaces": len(sc["surfaces"]) == 5 and all(s["affine_miss_m"] < 0.01 and s["tie_median_m"] < 7.0 and len(s["ties"]) >= 23 for s in sc["surfaces"])
|
|
8704
|
+
and all(pts[s["id"]] == [s["n_source_points"], s["n_points"]] for s in sc["surfaces"])
|
|
8705
|
+
and [s["id"] for s in sc["surfaces"]] == ["Ty", "SHETLAND", "BCU", "Hugin_Fm_Top", "Hugin_Base"],
|
|
8706
|
+
"line_ends": not crlf and all(k.endswith(".gz") or b"\r\n" not in (pkg / "reference" / k).read_bytes() for k in prov["outputs"]),
|
|
8707
|
+
"provenance": hashes_ok and "commercial purposes" in prov["license"] and "may not be sold" in prov["license"] and len(prov["sources"]) == 12
|
|
8708
|
+
and all(len(v["commit"]) == 40 for v in prov["sources"].values()) and "Equinor ASA" in sc["attribution"] and "not sold" in sc["attribution"],
|
|
8709
|
+
}
|
|
8710
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
8711
|
+
ok(not failed2,
|
|
8712
|
+
"CE2 the Volve excerpt checked against itself: its eastings and northings are ED50 zone 31N (the slots unproject within 0.1 m of the "
|
|
8713
|
+
"operator's own log headers, about 150 m off if read as WGS84); the three paths land on Equinor's picks to under 0.3 m from a 54.9 m rig "
|
|
8714
|
+
"floor; 15/9-F-4's azimuths read as grid, with the 14 m it would miss by otherwise stated; five surfaces drawn as delivered, every fourth "
|
|
8715
|
+
"line, each within a few metres of its picks at the median; every shipped file hashed in its provenance with the pinned public copy it "
|
|
8716
|
+
"came from (and no package file with CRLF line ends, which a checkout would change from the bytes hashed), the licence (commercial use, no sale) and the attribution on the page"
|
|
8717
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
8718
|
+
|
|
8719
|
+
ev = E.selftest_events(str(d))
|
|
8720
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
8721
|
+
wb = ws.add_well_file(ev["path"], display="Well B", actor="tester"); wc = ws.add_well_file(ev["path"], display="Well C", actor="tester")
|
|
8722
|
+
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")
|
|
8723
|
+
lat1, lon1 = 31.42, -104.10
|
|
8724
|
+
kmlon = 1.0 / (111.195 * _m.cos(_m.radians(lat1)))
|
|
8725
|
+
SF.write(str(d / "Top_A"), "PolyLine", [([[(lon1 + (i - 1.5) * kmlon + 0.0005 * k, lat1 - 0.05 + 0.001 * k) for k in range(100)]], {"ElevTop_ft": -1500.0 - 100.0 * i}) for i in range(4)],
|
|
8726
|
+
[("ElevTop_ft", "F", 10, 1)], SF.NAD83_GEOGCS)
|
|
8727
|
+
SF.write(str(d / "Faults"), "PolyLine", [([[(lon1 - 0.02 + 0.001 * j, lat1) for j in range(40)]], {"TYPE": "normal fault"})], [("TYPE", "C", 20, 0)], SF.NAD83_GEOGCS)
|
|
8728
|
+
ws.add_layer(str(d / "Top_A.shp"), "Top A", actor="tester", formation="A", role="top", citation="test")
|
|
8729
|
+
ws.add_layer(str(d / "Faults.shp"), "Faults", actor="tester", citation="test")
|
|
8730
|
+
sc0 = W.workspace_scene(ws) # no survey yet: the formation tops alone
|
|
8731
|
+
att_refused = 0
|
|
8732
|
+
for kw in ({"azimuth_reference": ""}, {"azimuth_reference": "grid"}, {"azimuth_reference": "magnetic"}, {"azimuth_reference": "true", "source": " "}):
|
|
8733
|
+
k = {"azimuth_reference": "true", "source": "s", **kw}
|
|
8734
|
+
try:
|
|
8735
|
+
W.attach_trajectory(ws, wb["id"], "s.csv", csv_ok.encode(), "tester", k["azimuth_reference"], "ft", None, None, None, k["source"])
|
|
8736
|
+
except ValueError:
|
|
8737
|
+
att_refused += 1
|
|
8738
|
+
ws.refresh_dashboard(actor="tester")
|
|
8739
|
+
Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
|
|
8740
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
8741
|
+
base_url = svc.url.rstrip("/")
|
|
8742
|
+
|
|
8743
|
+
def opener(name, pw):
|
|
8744
|
+
o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
8745
|
+
rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw}).encode())
|
|
8746
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
|
|
8747
|
+
return o
|
|
8748
|
+
|
|
8749
|
+
def call(o, method, path, body=None):
|
|
8750
|
+
rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
8751
|
+
if method == "POST":
|
|
8752
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
|
|
8753
|
+
try:
|
|
8754
|
+
with o.open(rq, timeout=120) as rr_:
|
|
8755
|
+
return rr_.status, json.loads(rr_.read())
|
|
8756
|
+
except urllib.error.HTTPError as e:
|
|
8757
|
+
return e.code, json.loads(e.read())
|
|
8758
|
+
try:
|
|
8759
|
+
V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
|
|
8760
|
+
st_l, ls = call(V, "GET", "/api/3d/scenes")
|
|
8761
|
+
st_v, vs = call(V, "GET", "/api/3d/scene/volve")
|
|
8762
|
+
st_n, _ = call(V, "GET", "/api/3d/scene/nope")
|
|
8763
|
+
body = {"filename": "survey.csv", "content_b64": _b64.b64encode(csv_ok.encode()).decode(), "azimuth_reference": "grid", "grid": "TX_CENTRAL",
|
|
8764
|
+
"md_unit": "ft", "source": "test survey"}
|
|
8765
|
+
st_pv, _ = call(V, "POST", f"/api/wells/{wb['id']}/trajectory", body)
|
|
8766
|
+
st_b64, _ = call(O, "POST", f"/api/wells/{wb['id']}/trajectory", {**body, "content_b64": "%%%"})
|
|
8767
|
+
st_at, at = call(O, "POST", f"/api/wells/{wb['id']}/trajectory", body)
|
|
8768
|
+
st_w, wsc = call(V, "GET", "/api/3d/scene/workspace")
|
|
8769
|
+
finally:
|
|
8770
|
+
svc.stop()
|
|
8771
|
+
kept = (Path(wsp, "wells", wb["id"], "trajectory", "survey.csv").read_bytes() == csv_ok.encode())
|
|
8772
|
+
audit = [e["action"] for e in ws.audit_log(500)]
|
|
8773
|
+
cli = _sp.run([sys.executable, "-m", "gea", "well3d", "--scene", "workspace", "--workspace", wsp], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
8774
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
8775
|
+
help_md = (pkg / "help" / "well3d.md").read_text(encoding="utf-8")
|
|
8776
|
+
w0 = (wsc.get("wells") or [{}])[0]
|
|
8777
|
+
c3 = {
|
|
8778
|
+
"attach": att_refused == 4 and st_pv == 403 and st_b64 == 400 and st_at == 200 and at["stations"] == 6 and at["grid"] == "TX_CENTRAL" and kept
|
|
8779
|
+
and at["sha256"] == _hl.sha256(csv_ok.encode()).hexdigest() and "well.trajectory" in audit,
|
|
8780
|
+
"scene": st_l == 200 and [s["id"] for s in ls["scenes"]] == ["workspace", "volve"] and st_v == 200 and len(vs["wells"]) == 3 and st_n == 404
|
|
8781
|
+
and st_w == 200 and len(wsc["wells"]) == 1 and abs(w0["td_md"] - 1752.6) < 0.01 and "TX_CENTRAL grid north" in w0["azimuth"]["applied"]
|
|
8782
|
+
and w0["replay"]["kind"] == "drilling recorder" and "Gamma" in w0["channels"] and not w0["replay"]["t"][0].endswith("Z")
|
|
8783
|
+
and [n["id"] for n in wsc["not_placed"]] == [wc["id"]] and wsc["layers"][0]["lines"][0]["z"] == round(1500 * 0.3048, 1)
|
|
8784
|
+
and any(n["display"] == "Faults" and "no depth" in n["why"] for n in wsc["layers_not_drawn"]),
|
|
8785
|
+
"tops_alone": sc0["wells"] == [] and [l["name"] for l in sc0["layers"]] == ["Top A"] and sc0["frame"]["area"] == "the placed wells"
|
|
8786
|
+
and {n["id"]: n["why"] for n in sc0["not_placed"]}[wb["id"]] == "no directional survey attached",
|
|
8787
|
+
"cli": cli.returncode == 0 and "Well B: 6 stations" in cli.stdout and "contour lines at their elevations" in cli.stdout,
|
|
8788
|
+
"page": all(x in page_src for x in ("VIEWS['3d']", "['#/3d', '3D']", "getContext('webgl'", "OES_element_index_uint", "w3Tube", "w3SurfMesh",
|
|
8789
|
+
"/api/3d/scene/", "w3Play", "the recorder's clock", "ArrowLeft", "see-through", "/trajectory`", "w3Hit")),
|
|
8790
|
+
"help": any(t["topic"] == "well3d" for t in H.topics()) and not H.check("well3d") and H.VIEW_TOPIC.get("3d") == "well3d"
|
|
8791
|
+
and "a well with no survey" in help_md.split("will not call a measurement")[1],
|
|
8792
|
+
}
|
|
8793
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
8794
|
+
ok(not failed3,
|
|
8795
|
+
"CE3 a workspace well in 3D: a survey attached only with its azimuth reference stated (a grid named, a declination given) and its source, "
|
|
8796
|
+
"kept byte for byte and audited; the path turned from TX_CENTRAL grid north by the convergence there, set below sea level from the "
|
|
8797
|
+
"declared rig floor among the formation-top contours at their elevations (a fault trace listed as having no depth; the tops drawn alone "
|
|
8798
|
+
"before any survey), the recorder's bit depth replayed on its own clock and its gamma along the path; a well without a position listed, not drawn; the API, the CLI and the 3D "
|
|
8799
|
+
"tab (WebGL by hand, the replay, turn and zoom by pointer and keys); the help page"
|
|
8800
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
8801
|
+
|
|
8802
|
+
|
|
8451
8803
|
def main() -> int:
|
|
8452
8804
|
print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
|
|
8453
8805
|
# a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
|
|
@@ -8532,6 +8884,8 @@ def main() -> int:
|
|
|
8532
8884
|
section_ca_setup_checklist(tmp)
|
|
8533
8885
|
section_cb_charts(tmp)
|
|
8534
8886
|
section_cc_site_map(tmp)
|
|
8887
|
+
section_cd_control_room(tmp)
|
|
8888
|
+
section_ce_well3d(tmp)
|
|
8535
8889
|
if _FAILS:
|
|
8536
8890
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
8537
8891
|
for f in _FAILS:
|