gea-program 0.47.0__tar.gz → 0.48.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.47.0 → gea_program-0.48.0}/PKG-INFO +14 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/README.md +13 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/__init__.py +1 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/__main__.py +13 -3
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/acceptance_tests.py +201 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/alerts.py +20 -2
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/declarations.py +136 -2
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/dossier.py +36 -2
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/alerts.md +1 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/declare.md +2 -2
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/dossier.md +2 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/invoice-check.md +1 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/pad-summary.md +1 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/rig-time.md +3 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/invoice_check.py +25 -7
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/notify.py +3 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/pad_summary.py +41 -4
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/rig_state.py +144 -8
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/service.py +17 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/web/app.html +15 -7
- gea_program-0.48.0/gea/xlsx_read.py +205 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea_program.egg-info/PKG-INFO +14 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/gea_program.egg-info/SOURCES.txt +1 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/pyproject.toml +1 -1
- {gea_program-0.47.0 → gea_program-0.48.0}/LICENSE +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/backup.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/bench.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/blind_harness.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog_map.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/catalog_register.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/certificates.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/cli.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/client_reports.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/config_versioning.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/conformance.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/control_room.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/correlation.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/dashboard.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/deviation.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/dlis.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/dnp3.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/doctor.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/dossier_verify.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/drill_string.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/earth_model.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/economics.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/ed25519.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/edr.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/elevation.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/etp.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/example_register_map.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/fat_sat.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/files.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/follower.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/forward_model.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/friction_priors.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/gamma.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/geodesy.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/hartip.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/alarms.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/backup.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/catalog.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/charts.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/conformance.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/control-room.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/data-in.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/dlis.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/doctor.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/drift.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/drillstring.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/economics.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/edr.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/etp.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/files.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/historian.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/instruments.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/interval.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/map.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/month-end.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/notifications.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/opseis.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/osdu.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/patches.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/penetrator.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/quality.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/report-history.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/seismic.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/setup.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/simulation.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/site.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/sites.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/slip.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/soak.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/sra.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/start.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/structure.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/tools.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/transients.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/vibration.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/well-report.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/help/well3d.md +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/helplib.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/housekeeping.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/interval.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/jobs.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/live_ports.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/loadtest.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/modbus.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/model_card.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/month_end.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/op_seismicity.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/opcua_port.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/operator_app.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/osdu.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/patches.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/pdf_report.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/penetrator.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/permits.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/piwebapi.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/ports.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/ppdm.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/project.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/quakeml.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reconciler.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/eia_prices.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/mahoning_example.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/mud_motor_specs.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/penetrator_ratings.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/seismicity_protocols.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_3d.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_surfaces.json.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/report_history.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/sample_record.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/sbom.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seedlink.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/segy.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_array.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_film.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_response.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_track.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/seismic_watch.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/series_cache.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/service_life.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/shapefile.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/shell.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/shut_in.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/simulation.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/site_map.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/sla_report.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/slip.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/soak.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/sra.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/store_forward.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/strata_join.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/structure.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/supervisor.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/survey_view.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/telemetry.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/tool_catalog.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/tool_library.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/transient.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/vibration.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/well3d.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/well_assembler.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/well_report.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/wits0.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/witsml.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea/workspace.py +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.47.0 → gea_program-0.48.0}/setup.cfg +0 -0
- {gea_program-0.47.0 → gea_program-0.48.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.48.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,19 @@ 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
|
+
### One record, tied together
|
|
652
|
+
|
|
653
|
+
The invoice check, rig time, the daily summary, the alerts, the signed
|
|
654
|
+
dossier and the declarations sheet now read each other: an invoice that
|
|
655
|
+
differs is an alert; the morning summary carries each well's invoice check
|
|
656
|
+
and yesterday's rig time; a signed dossier carries the contract, the
|
|
657
|
+
invoices and daily reports as attached, the rig-time targets and casing,
|
|
658
|
+
and both checks as they read at signing. The declarations sheet declares
|
|
659
|
+
the contract, the rig-time targets, block weights, casing shoes and the
|
|
660
|
+
watch and summary settings, and every file the program reads as a table -
|
|
661
|
+
the sheet, an invoice, a daily report - may be an Excel workbook. Casing
|
|
662
|
+
shoes split rig time by hole section.
|
|
663
|
+
|
|
651
664
|
### Rig time
|
|
652
665
|
|
|
653
666
|
What the rig did each second of the drilling record, read from the hook
|
|
@@ -609,6 +609,19 @@ 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
|
+
### One record, tied together
|
|
613
|
+
|
|
614
|
+
The invoice check, rig time, the daily summary, the alerts, the signed
|
|
615
|
+
dossier and the declarations sheet now read each other: an invoice that
|
|
616
|
+
differs is an alert; the morning summary carries each well's invoice check
|
|
617
|
+
and yesterday's rig time; a signed dossier carries the contract, the
|
|
618
|
+
invoices and daily reports as attached, the rig-time targets and casing,
|
|
619
|
+
and both checks as they read at signing. The declarations sheet declares
|
|
620
|
+
the contract, the rig-time targets, block weights, casing shoes and the
|
|
621
|
+
watch and summary settings, and every file the program reads as a table -
|
|
622
|
+
the sheet, an invoice, a daily report - may be an Excel workbook. Casing
|
|
623
|
+
shoes split rig time by hole section.
|
|
624
|
+
|
|
612
625
|
### Rig time
|
|
613
626
|
|
|
614
627
|
What the rig did each second of the drilling record, read from the hook
|
|
@@ -599,6 +599,9 @@ def main(argv=None) -> int:
|
|
|
599
599
|
p_rt.add_argument("--trip-out", type=str, default=None, help="declare the tripping-out target, ft/h")
|
|
600
600
|
p_rt.add_argument("--source", type=str, default="", help="where the targets came from")
|
|
601
601
|
p_rt.add_argument("--block-weight", type=str, default=None, help="with --well: the block weight in klbf where the estimate is wrong ('' removes it)")
|
|
602
|
+
p_rt.add_argument("--casing", type=str, default=None, help="with --well, --shoe-md and --source: declare a casing shoe, its size in inches as written (13-3/8)")
|
|
603
|
+
p_rt.add_argument("--shoe-md", type=str, default=None, help="the shoe's measured depth, ft")
|
|
604
|
+
p_rt.add_argument("--set-time", type=str, default=None, help="when the casing was set (the recorder's clock); otherwise when the hole is 50 ft past the shoe")
|
|
602
605
|
p_rt.add_argument("--actor", type=str, default="cli")
|
|
603
606
|
p_rt.add_argument("--json", action="store_true")
|
|
604
607
|
|
|
@@ -2205,9 +2208,10 @@ def main(argv=None) -> int:
|
|
|
2205
2208
|
if not a.file:
|
|
2206
2209
|
raise SystemExit("declare: give --file <sheet.csv> (or --template <out.csv> to start one)")
|
|
2207
2210
|
try:
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
+
from . import xlsx_read as _XR
|
|
2212
|
+
with open(a.file, "rb") as f:
|
|
2213
|
+
text_ = _XR.text_of(f.read())
|
|
2214
|
+
except (OSError, ValueError, KeyError) as e:
|
|
2211
2215
|
raise SystemExit(f"declare: {e}")
|
|
2212
2216
|
p = _DC.apply(ws, text_, a.actor, os.path.dirname(os.path.abspath(a.file)), write=a.apply)
|
|
2213
2217
|
print(_json.dumps(p, default=str) if a.json else _DC.text(p))
|
|
@@ -2376,6 +2380,12 @@ def main(argv=None) -> int:
|
|
|
2376
2380
|
_safe_stdout()
|
|
2377
2381
|
try:
|
|
2378
2382
|
ws = Workspace(a.workspace)
|
|
2383
|
+
if a.casing:
|
|
2384
|
+
if not a.well:
|
|
2385
|
+
raise ValueError("--casing needs --well")
|
|
2386
|
+
c_ = _RS.set_casing(ws, a.actor, a.well, a.casing, a.shoe_md, a.source, a.set_time)
|
|
2387
|
+
print(f"casing {c_['size']} in, shoe at {c_['shoe_md_ft']:,.0f} ft MD ({c_['source']})")
|
|
2388
|
+
return 0
|
|
2379
2389
|
if a.w2w_min is not None or a.trip_in is not None or a.trip_out is not None or a.block_weight is not None:
|
|
2380
2390
|
if a.block_weight is not None and not a.well:
|
|
2381
2391
|
raise ValueError("--block-weight needs --well")
|
|
@@ -10913,6 +10913,206 @@ def section_cp_rig_time(tmp: str) -> None:
|
|
|
10913
10913
|
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
10914
10914
|
|
|
10915
10915
|
|
|
10916
|
+
def section_cq_tied_together(tmp: str) -> None:
|
|
10917
|
+
"""Section CQ - the recent parts tied together: an Excel workbook read without Excel (shared and inline strings,
|
|
10918
|
+
numbers, booleans, dates and times from their cell formats, the 1904 epoch, a named sheet) and taken wherever a
|
|
10919
|
+
CSV is - the invoice check's files and the declarations sheet; the declarations sheet's new tables (the contract,
|
|
10920
|
+
the rig-time targets, block weights, casing, the watch and summary settings) planned, written, round-tripped and
|
|
10921
|
+
refused when wrong; rig time by hole section from declared casing; the invoice differing raised as an alert and
|
|
10922
|
+
cleared; the daily summary carrying each invoice check and yesterday's rig time; a signed dossier carrying the
|
|
10923
|
+
contract, the files, the targets, the casing and both checks as they read at signing; the API, the CLI, the tabs
|
|
10924
|
+
and the help pages."""
|
|
10925
|
+
import http.cookiejar
|
|
10926
|
+
import subprocess as _sp
|
|
10927
|
+
import urllib.request
|
|
10928
|
+
import urllib.error
|
|
10929
|
+
import base64 as _b64
|
|
10930
|
+
import zipfile as _zf
|
|
10931
|
+
from datetime import datetime as _dtm, timedelta as _td, timezone as _tz
|
|
10932
|
+
from . import __version__
|
|
10933
|
+
from . import alerts as AL
|
|
10934
|
+
from . import declarations as DC
|
|
10935
|
+
from . import dossier as DZ
|
|
10936
|
+
from . import helplib as H
|
|
10937
|
+
from . import invoice_check as IC
|
|
10938
|
+
from . import pad_summary as PS
|
|
10939
|
+
from . import rig_state as RS
|
|
10940
|
+
from . import seismic_watch as SW
|
|
10941
|
+
from . import xlsx_read as XR
|
|
10942
|
+
from .workspace import Workspace
|
|
10943
|
+
from .service import Service, Users
|
|
10944
|
+
pkg = Path(__file__).parent
|
|
10945
|
+
d = Path(tmp, "cq"); d.mkdir()
|
|
10946
|
+
|
|
10947
|
+
# -- CQ1 the workbook -------------------------------------------------------------------------------------------------
|
|
10948
|
+
XR.write_simple(str(d / "a.xlsx"), [["Date", "Rate Type", "Quantity", "Note"], [_dtm(2026, 7, 1), "Operating day rate", 1, 'with, a "comma"'],
|
|
10949
|
+
[_dtm(2026, 7, 2, 6, 30), "Standby", 12.5, None]])
|
|
10950
|
+
a = XR.rows((d / "a.xlsx").read_bytes())
|
|
10951
|
+
ns = 'xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'
|
|
10952
|
+
with _zf.ZipFile(d / "b.xlsx", "w") as z: # a hand-made workbook: two sheets, inline strings, booleans, the 1904 epoch, a custom date format
|
|
10953
|
+
z.writestr("xl/workbook.xml", f'<workbook {ns}><workbookPr date1904="1"/><sheets><sheet name="Notes" sheetId="1" r:id="rId1"/><sheet name="Invoice" sheetId="2" r:id="rId2"/></sheets></workbook>')
|
|
10954
|
+
z.writestr("xl/_rels/workbook.xml.rels", '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="worksheets/sheet1.xml" Type="x"/><Relationship Id="rId2" Target="/xl/worksheets/sheet2.xml" Type="x"/></Relationships>')
|
|
10955
|
+
z.writestr("xl/styles.xml", f'<styleSheet {ns}><numFmts count="1"><numFmt numFmtId="164" formatCode="dd/mm/yyyy\\ hh:mm"/><numFmt numFmtId="165" formatCode="#,##0.00"/></numFmts>'
|
|
10956
|
+
'<cellXfs count="3"><xf numFmtId="0"/><xf numFmtId="164"/><xf numFmtId="165"/></cellXfs></styleSheet>')
|
|
10957
|
+
z.writestr("xl/worksheets/sheet1.xml", f'<worksheet {ns}><sheetData><row r="1"><c r="A1" t="inlineStr"><is><t>notes</t></is></c></row></sheetData></worksheet>')
|
|
10958
|
+
z.writestr("xl/worksheets/sheet2.xml", f'<worksheet {ns}><sheetData><row r="1"><c r="A1" t="inlineStr"><is><t>When</t></is></c><c r="C1" t="inlineStr"><is><t>Paid</t></is></c></row>'
|
|
10959
|
+
'<row r="3"><c r="A3" s="1"><v>44743.25</v></c><c r="B3" s="2"><v>1234.5</v></c><c r="C3" t="b"><v>1</v></c></row></sheetData></worksheet>')
|
|
10960
|
+
bx = (d / "b.xlsx").read_bytes()
|
|
10961
|
+
b2 = XR.rows(bx, "Invoice")
|
|
10962
|
+
c1 = {
|
|
10963
|
+
"simple": a == [["Date", "Rate Type", "Quantity", "Note"], ["2026-07-01", "Operating day rate", "1", 'with, a "comma"'], ["2026-07-02T06:30", "Standby", "12.5"]],
|
|
10964
|
+
"csv": XR.to_csv((d / "a.xlsx").read_bytes()).splitlines()[1] == '2026-07-01,Operating day rate,1,"with, a ""comma"""',
|
|
10965
|
+
"sheets": XR.sheets(bx) == ["Notes", "Invoice"] and XR.rows(bx) == [["notes"]],
|
|
10966
|
+
"hand_made": b2 == [["When", "", "Paid"], [], ["2026-07-02T06:00", "1234.5", "TRUE"]],
|
|
10967
|
+
"text_of": XR.text_of(b"a,b\n1,2\n") == "a,b\n1,2\n" and not XR.is_xlsx(b"PK not a zip"),
|
|
10968
|
+
}
|
|
10969
|
+
try:
|
|
10970
|
+
XR.rows(bx, "Nope"); c1["named"] = False
|
|
10971
|
+
except ValueError as e:
|
|
10972
|
+
c1["named"] = "Notes, Invoice" in str(e)
|
|
10973
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
10974
|
+
ok(not failed1,
|
|
10975
|
+
"CQ1 an Excel workbook read with the standard library: shared and inline strings, a comma and quotes kept, whole numbers without "
|
|
10976
|
+
"'.0', dates and date-times from built-in and custom formats, the 1904 epoch, booleans, a gap row kept as a gap, the first or a "
|
|
10977
|
+
"named sheet; CSV text passed through" + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
10978
|
+
|
|
10979
|
+
# -- CQ2 the declarations sheet, casing, and the invoice as a workbook --------------------------------------------------
|
|
10980
|
+
F = RS.selftest_edr(str(d))
|
|
10981
|
+
G = IC.selftest_files(str(d))
|
|
10982
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad CQ", actor="tester")
|
|
10983
|
+
w1 = ws.add_well_file(F["path"], display="RIG-1", actor="tester"); w2 = ws.add_well_file(F["path"], display="RIG-2", actor="tester")
|
|
10984
|
+
wi = ws.add_well_file(G["edr"], display="INV-1", actor="tester")
|
|
10985
|
+
sheet = ("[contract],day_start,report_date,operating,standby,repair,repair_hours_per_month,source\n,06:00,start,30000,25000,0,2,daywork contract A\n"
|
|
10986
|
+
"[rig_time],w2w_min,trip_in_ft_h,source\n,,2800,program 2026\n[block_weights],well,block_weight_klbf\n,RIG-2,42\n"
|
|
10987
|
+
"[casing],well,size_in,shoe_md_ft,set,source\n,RIG-1,9-5/8,10150,,tally\n,RIG-2,9-5/8,10150,,tally\n,RIG-1,13-3/8,2000,,tally\n"
|
|
10988
|
+
"[settings],setting,value\n,watch_every_min,10\n,summary_at,10:45\n,summary_send,no\n")
|
|
10989
|
+
rows_ = [ln.split(",") for ln in sheet.strip().splitlines()]
|
|
10990
|
+
XR.write_simple(str(d / "decl.xlsx"), rows_)
|
|
10991
|
+
p_x = DC.plan(ws, XR.text_of((d / "decl.xlsx").read_bytes()))
|
|
10992
|
+
p_w = DC.apply(ws, sheet, "tester", write=True)
|
|
10993
|
+
p_rt = DC.plan(ws, DC.template(ws))
|
|
10994
|
+
p_bad = DC.plan(ws, "[contract],operating,source\n,30000,\n[settings],setting,value\n,watch_mode,sometimes\n,colour,blue\n[casing],well,size_in,shoe_md_ft,source\n,RIG-1,9 5/8,100,x\n,NOPE,7,100,x\n")
|
|
10995
|
+
pad = RS.pad(ws)
|
|
10996
|
+
W1 = next(w for w in pad["wells"] if w["display"] == "RIG-1")
|
|
10997
|
+
secs = [c["section"] for c in W1["connection_list"]]
|
|
10998
|
+
XR.write_simple(str(d / "inv.xlsx"), [ln.split(",") if i == 0 else [(_dtm.fromisoformat(x) if j == 0 else (float(x) if re.fullmatch(r"[\d.]+", x) else x)) for j, x in enumerate(ln.split(","))]
|
|
10999
|
+
for i, ln in enumerate(G["invoice"].strip().splitlines())])
|
|
11000
|
+
IC.attach(ws, "tester", wi["id"], "ddr", "ddr.csv", G["ddr"].encode(), "reports")
|
|
11001
|
+
IC.attach(ws, "tester", wi["id"], "invoice", "inv.xlsx", (d / "inv.xlsx").read_bytes(), "invoice as a workbook")
|
|
11002
|
+
ic = IC.check(ws, wi["id"])
|
|
11003
|
+
c2 = {
|
|
11004
|
+
"xlsx_sheet": p_x["counts"]["NEW"] == 6 and p_x["counts"]["CHANGE"] == 3 and not p_x["errors"],
|
|
11005
|
+
"written": p_w["applied"] and IC.terms(ws)["rates_usd_per_day"] == {"operating": 30000.0, "standby": 25000.0, "repair": 0.0}
|
|
11006
|
+
and RS.settings(ws)["targets"] == {"trip_in_ft_h": 2800.0} and RS.settings(ws)["block_weight_klbf"] == {w2["id"]: 42.0}
|
|
11007
|
+
and [x["size"] for x in RS.casing(ws)[w1["id"]]] == ["13-3/8", "9-5/8"] and SW.settings(ws)["every_min"] == 10
|
|
11008
|
+
and PS.settings(ws)["at"] == "10:45" and PS.settings(ws)["send"] is False,
|
|
11009
|
+
"round_trip": p_rt["counts"]["NEW"] == 0 and p_rt["counts"]["CHANGE"] == 0 and not p_rt["errors"],
|
|
11010
|
+
"refused": len(p_bad["errors"]) == 5 and any("watch_mode" in e for e in p_bad["errors"]) and any("no setting 'colour'" in e for e in p_bad["errors"])
|
|
11011
|
+
and any("13-3/8, 9-5/8 or 7" in e for e in p_bad["errors"]) and any("came from" in e for e in p_bad["errors"]),
|
|
11012
|
+
"sections": secs == ["below the 13-3/8 in shoe", "below the 9-5/8 in shoe", "below the 9-5/8 in shoe"]
|
|
11013
|
+
and all(x["hole"] == "CASED" for x in W1["trips"]) and set(pad["targets"]["by_section"]) == {"below the 9-5/8 in shoe"}
|
|
11014
|
+
and next(w for w in pad["wells"] if w["display"] == "RIG-2")["block_weight_klbf"] == 42.0,
|
|
11015
|
+
"invoice_xlsx": ic["status"] == "DIFFERS" and ic["totals"]["usd_in_question"] == 37666.67,
|
|
11016
|
+
}
|
|
11017
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
11018
|
+
ok(not failed2,
|
|
11019
|
+
"CQ2 the declarations sheet's new tables - the contract, the rig-time targets, a block weight, casing shoes, the watch and summary "
|
|
11020
|
+
"settings - read from a workbook, written, round-tripped to nothing to do, and refused when wrong (no source, an unknown setting, a "
|
|
11021
|
+
"size not in inches as written, a well not there); connections by hole section and trips inside casing; an invoice attached as a "
|
|
11022
|
+
"workbook checked to the same 37,666.67" + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
11023
|
+
|
|
11024
|
+
# -- CQ3 the alert, the summary, the dossier ----------------------------------------------------------------------
|
|
11025
|
+
now = _dtm(2026, 7, 2, 12, 0, tzinfo=_tz.utc)
|
|
11026
|
+
k1 = AL.check_conditions(ws, None, now, force=True)
|
|
11027
|
+
st1 = dict(AL.state(ws)["conditions"])
|
|
11028
|
+
g = PS.gather(ws, _dtm(2026, 8, 2, 7, 0, tzinfo=_tz.utc), _dtm(2026, 8, 1, 0, 0, tzinfo=_tz.utc))
|
|
11029
|
+
RS.write_report(ws, __version__, "tester")
|
|
11030
|
+
e1 = DZ.build(ws, "tester", __version__, None)
|
|
11031
|
+
with _zf.ZipFile(os.path.join(ws.reports_dir, e1["path"])) as z:
|
|
11032
|
+
names = set(z.namelist())
|
|
11033
|
+
rt = json.loads(z.read("results/rig_time.json"))
|
|
11034
|
+
icj = json.loads(z.read(f"results/invoice_check/{wi['id']}.json"))
|
|
11035
|
+
v1 = DZ.verify(os.path.join(ws.reports_dir, e1["path"]), ws)
|
|
11036
|
+
for f_ in IC.files(ws, wi["id"]): # the corrected report and invoice replace them
|
|
11037
|
+
IC.remove(ws, "tester", f_["file"])
|
|
11038
|
+
ddr_ok = G["ddr"].replace("2026-07-02,14:00,16:00,2,6,Trip out", "2026-07-02,14:00,16:00,2,7,Service rig").replace("6200,7600", "6200,7400")
|
|
11039
|
+
inv_ok = ("Date,Rate Type,Quantity,Unit,Unit Price,Amount\n2026-07-01,Operating day rate,1,day,30000,30000\n"
|
|
11040
|
+
"2026-07-02,Operating day rate,22,hour,1250,27500\n2026-07-02,Repair rate,2,hour,0,0\n"
|
|
11041
|
+
"2026-07-03,Standby rate,20,hour,1041.6667,20833.33\n2026-07-03,Operating day rate,4,hour,1250,5000\n2026-07-04,Operating day rate,1,day,30000,30000\n")
|
|
11042
|
+
IC.attach(ws, "tester", wi["id"], "ddr", "ddr_ok.csv", ddr_ok.encode(), "corrected reports")
|
|
11043
|
+
IC.attach(ws, "tester", wi["id"], "invoice", "inv_ok.csv", inv_ok.encode(), "corrected invoice")
|
|
11044
|
+
k2 = AL.check_conditions(ws, None, now + _td(minutes=1))
|
|
11045
|
+
c3 = {
|
|
11046
|
+
"alert": [a_["event"] for a_ in k1 if a_["event"] == "invoice.differs"] == ["invoice.differs"] and f"invoice.differs:{wi['id']}" in st1
|
|
11047
|
+
and "37,666.67" in next(a_ for a_ in k1 if a_["event"] == "invoice.differs")["detail"],
|
|
11048
|
+
"cleared": [a_["event"] for a_ in k2] == ["condition.cleared"] and k2[0]["cleared"] == "invoice.differs",
|
|
11049
|
+
"summary": any(x["item"] == "the invoice check of INV-1" and x["status"] == "DIFFERS" for x in g["due"])
|
|
11050
|
+
and [x["well"] for x in g["rig_time"]] == ["RIG-1", "RIG-2"] and all(x["connections"] == 3 and x["date"] == "2026-08-01" for x in g["rig_time"])
|
|
11051
|
+
and all(x["lost_usd"] is not None for x in g["rig_time"]),
|
|
11052
|
+
"dossier": v1["status"] == "VALID" and {"declarations/economics/invoices/terms.json", "declarations/economics/invoices/files.json",
|
|
11053
|
+
"declarations/drillstring/rig_time.json", "declarations/drillstring/casing.json"} <= names
|
|
11054
|
+
and any(n.startswith(f"declarations/economics/invoices/{wi['id']}/") and n.endswith("_inv.xlsx") for n in names)
|
|
11055
|
+
and icj["status"] == "DIFFERS" and len(rt["wells"]) == 3 and rt["targets"]["trip_in_ft_h"]["value"] == 2800,
|
|
11056
|
+
}
|
|
11057
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
11058
|
+
ok(not failed3,
|
|
11059
|
+
"CQ3 an invoice that differs raised as an alert with its dollars, and cleared when a corrected invoice replaces it; the daily summary "
|
|
11060
|
+
"carrying the invoice check and yesterday's rig time well by well with its cost at the contract's rate; a signed pad dossier carrying "
|
|
11061
|
+
"the contract, the invoice files as attached, the targets and the casing, and both checks as they read at signing - VALID"
|
|
11062
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
11063
|
+
|
|
11064
|
+
# -- CQ4 the API, the CLI, the tabs, the help ------------------------------------------------------------------------
|
|
11065
|
+
Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
|
|
11066
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
11067
|
+
base_url = svc.url.rstrip("/")
|
|
11068
|
+
|
|
11069
|
+
def opener(name, pw_):
|
|
11070
|
+
o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
11071
|
+
rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
|
|
11072
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
|
|
11073
|
+
return o
|
|
11074
|
+
|
|
11075
|
+
def call(o, method, path, body=None):
|
|
11076
|
+
rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
11077
|
+
if method == "POST":
|
|
11078
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
|
|
11079
|
+
try:
|
|
11080
|
+
with o.open(rq, timeout=120) as rr_:
|
|
11081
|
+
return rr_.status, json.loads(rr_.read())
|
|
11082
|
+
except urllib.error.HTTPError as e:
|
|
11083
|
+
return e.code, json.loads(e.read())
|
|
11084
|
+
try:
|
|
11085
|
+
V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
|
|
11086
|
+
s_vc, _ = call(V, "POST", "/api/rig-time/casing", {"well_id": w2["id"], "size": "7", "shoe_md_ft": 9000, "source": "x"})
|
|
11087
|
+
s_oc, ocj = call(O, "POST", "/api/rig-time/casing", {"well_id": w2["id"], "size": "7", "shoe_md_ft": 9000, "source": "tally"})
|
|
11088
|
+
s_ob, _ = call(O, "POST", "/api/rig-time/casing", {"well_id": w2["id"], "size": "seven", "shoe_md_ft": 9000, "source": "tally"})
|
|
11089
|
+
s_or, _ = call(O, "POST", "/api/rig-time/casing/remove", {"well_id": w2["id"], "size": "7"})
|
|
11090
|
+
s_dx, dxj = call(O, "POST", "/api/declare", {"content_b64": _b64.b64encode((d / "decl.xlsx").read_bytes()).decode()})
|
|
11091
|
+
s_ia, iaj = call(O, "POST", "/api/invoice-check/attach", {"well_id": wi["id"], "kind": "invoice", "filename": "inv2.xlsx", "source": "workbook",
|
|
11092
|
+
"content_b64": _b64.b64encode((d / "inv.xlsx").read_bytes()).decode()})
|
|
11093
|
+
s_al, alj = call(V, "GET", "/api/alerts")
|
|
11094
|
+
finally:
|
|
11095
|
+
svc.stop()
|
|
11096
|
+
cli_c = _sp.run([sys.executable, "-m", "gea", "rig-time", "--workspace", wsp, "--well", w2["id"], "--casing", "13-3/8", "--shoe-md", "2000", "--source", "tally"],
|
|
11097
|
+
capture_output=True, text=True, cwd=str(pkg.parent))
|
|
11098
|
+
cli_d = _sp.run([sys.executable, "-m", "gea", "declare", "--workspace", wsp, "--file", str(d / "decl.xlsx")], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
11099
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
11100
|
+
c4 = {
|
|
11101
|
+
"api": s_vc == 403 and s_oc == 200 and ocj["shoe_md_ft"] == 9000 and s_ob == 400 and s_or == 200 and s_dx == 200 and dxj["counts"]["SAME"] == 9
|
|
11102
|
+
and s_ia == 200 and iaj["lines"] == 7 and s_al == 200 and "invoice.differs" in alj["events"],
|
|
11103
|
+
"cli": cli_c.returncode == 0 and "13-3/8 in, shoe at 2,000 ft" in cli_c.stdout and cli_d.returncode == 0 and "already declared" in cli_d.stdout,
|
|
11104
|
+
"page": all(x in page_src for x in ("rtC", "/api/rig-time/casing", ".xlsx", "content_b64: DC.b64", "'invoice.differs': 'Invoice differs'")),
|
|
11105
|
+
"help": all(not H.check(t_) for t_ in ("declare", "rig-time", "invoice-check", "dossier", "alerts", "pad-summary"))
|
|
11106
|
+
and "[casing]" in (pkg / "help" / "declare.md").read_text(encoding="utf-8") and "Hole sections" in (pkg / "help" / "rig-time.md").read_text(encoding="utf-8")
|
|
11107
|
+
and "invoice.differs" in (pkg / "help" / "alerts.md").read_text(encoding="utf-8") and "results/" in (pkg / "help" / "dossier.md").read_text(encoding="utf-8"),
|
|
11108
|
+
}
|
|
11109
|
+
failed4 = [k for k, v in c4.items() if not v]
|
|
11110
|
+
ok(not failed4,
|
|
11111
|
+
"CQ4 on the API (only an operator declares or removes a casing shoe, a size that is not one refused; a declarations workbook and an "
|
|
11112
|
+
"invoice workbook sent as they are; the new alert listed), the CLI (a casing shoe, a declarations workbook), the tabs and the help pages"
|
|
11113
|
+
+ (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
|
|
11114
|
+
|
|
11115
|
+
|
|
10916
11116
|
def main() -> int:
|
|
10917
11117
|
print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
|
|
10918
11118
|
# a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
|
|
@@ -11010,6 +11210,7 @@ def main() -> int:
|
|
|
11010
11210
|
section_cn_four(tmp)
|
|
11011
11211
|
section_co_invoice_check(tmp)
|
|
11012
11212
|
section_cp_rig_time(tmp)
|
|
11213
|
+
section_cq_tied_together(tmp)
|
|
11013
11214
|
if _FAILS:
|
|
11014
11215
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
11015
11216
|
for f in _FAILS:
|
|
@@ -13,7 +13,9 @@ worth an event, for the conditions the operator does not watch by hand:
|
|
|
13
13
|
- `penetrator.outside` - a line of an ESP well's penetrator check is OUTSIDE its rating (the Completion tab);
|
|
14
14
|
- `dossier.changed` - the newest signed dossier of a well or the pad no longer matches the workspace: a source
|
|
15
15
|
record or an earlier audit line has changed since it was signed (checked once a day);
|
|
16
|
-
- `
|
|
16
|
+
- `invoice.differs` - a well's invoice cross-check finds a day that DIFFERS (checked once a day, and when a file is
|
|
17
|
+
attached);
|
|
18
|
+
- `condition.cleared` - one of the last four is no longer so.
|
|
17
19
|
|
|
18
20
|
Every alert is written to `records/alerts.jsonl` whether or not any notification channel is set up - the Alarms
|
|
19
21
|
tab lists them - and is handed to the notification rules, which send it where the site said (a rule per event
|
|
@@ -28,7 +30,7 @@ from datetime import datetime, timezone
|
|
|
28
30
|
from typing import Dict, List, Optional
|
|
29
31
|
|
|
30
32
|
EVENTS = ('seismic.protocol', 'seismic.event', 'seismic.watch_down', 'seismic.watch_up', 'motor.due', 'penetrator.outside',
|
|
31
|
-
'dossier.changed', 'condition.cleared')
|
|
33
|
+
'dossier.changed', 'invoice.differs', 'condition.cleared')
|
|
32
34
|
CONDITIONS_EVERY_S = 900 # motors and penetrators: every 15 minutes
|
|
33
35
|
DOSSIERS_EVERY_S = 86400 # dossiers: once a day
|
|
34
36
|
_LOCK = threading.Lock()
|
|
@@ -173,5 +175,21 @@ def check_conditions(ws, notifier, now: Optional[datetime] = None, force: bool =
|
|
|
173
175
|
'dossier_id': e['dossier_id'], 'detail': '; '.join(f"{c['check']}: {c['detail']}" for c in bad)}, now, raised)
|
|
174
176
|
except Exception as ex:
|
|
175
177
|
last['dossiers_error'] = f'{type(ex).__name__}: {ex}'
|
|
178
|
+
if due('invoices', DOSSIERS_EVERY_S):
|
|
179
|
+
last['invoices'] = _utc(now)
|
|
180
|
+
try:
|
|
181
|
+
from . import invoice_check as IC
|
|
182
|
+
if IC.terms(ws):
|
|
183
|
+
for wid in sorted({f['well_id'] for f in IC.files(ws)}):
|
|
184
|
+
if wid not in wells:
|
|
185
|
+
continue
|
|
186
|
+
r = IC.check(ws, wid)
|
|
187
|
+
T = r.get('totals') or {}
|
|
188
|
+
_transition(ws, notifier, st, f'invoice.differs:{wid}', r.get('status') == 'DIFFERS',
|
|
189
|
+
{'event': 'invoice.differs', 'well_id': wid, 'well': wells[wid], 'what': f'the invoice cross-check of {wells[wid]}',
|
|
190
|
+
'detail': f"{T.get('differs', 0)} day(s) differ, {T.get('usd_in_question', 0):,.2f} USD in question of {T.get('billed_checked_usd', 0):,.2f} billed at day rates",
|
|
191
|
+
'usd_in_question': T.get('usd_in_question')}, now, raised)
|
|
192
|
+
except Exception as ex:
|
|
193
|
+
last['invoices_error'] = f'{type(ex).__name__}: {ex}'
|
|
176
194
|
save_state(ws, st)
|
|
177
195
|
return raised
|
|
@@ -67,9 +67,24 @@ TABLES: Dict[str, dict] = {
|
|
|
67
67
|
'gas_group', 'temp_class', 'ambient_min_f', 'ambient_max_f', 'source'),
|
|
68
68
|
'need': ('well', 'source'),
|
|
69
69
|
'about': "the ESP drive's output limits and the motor nameplate, the wellhead's hazardous-area classification (e.g. Class I, Division 1), gas group, temperature class and the site's ambient range"},
|
|
70
|
+
'contract': {'cols': ('day_start', 'report_date', 'operating', 'standby', 'repair', 'moving', 'force_majeure', 'repair_hours_per_job',
|
|
71
|
+
'repair_hours_per_month', 'source'),
|
|
72
|
+
'need': ('operating', 'source'),
|
|
73
|
+
'about': "the drilling contract's terms for the invoice check: the hour the report day starts on the rig's clock (06:00), whether a report is dated by the day it starts or ends (start/end), the day rates in USD (operating, and standby, repair, moving and force majeure where the contract has them), the repair allowance in hours a job and a month, and the contract it came from"},
|
|
74
|
+
'rig_time': {'cols': ('w2w_min', 'trip_in_ft_h', 'trip_out_ft_h', 'source'), 'need': ('source',),
|
|
75
|
+
'about': "the rig-time targets from the drilling program: weight-to-weight connection minutes and tripping speeds in and out (ft/h); blank keeps the pad's own best quarter"},
|
|
76
|
+
'block_weights': {'cols': ('well', 'block_weight_klbf'), 'need': ('well', 'block_weight_klbf'),
|
|
77
|
+
'about': "a well's travelling block weight in klbf, where the rig-time estimate from the hook load is wrong"},
|
|
78
|
+
'casing': {'cols': ('well', 'size_in', 'shoe_md_ft', 'set', 'source'), 'need': ('well', 'size_in', 'shoe_md_ft', 'source'),
|
|
79
|
+
'about': "casing shoes: the size in inches as written (13-3/8, 9-5/8, 7), the shoe's measured depth in ft, when it was set (the recorder's clock; blank: when the hole is 50 ft past it) and the tally or report it came from"},
|
|
80
|
+
'settings': {'cols': ('setting', 'value'), 'need': ('setting', 'value'),
|
|
81
|
+
'about': "the program's settings: watch_enabled (yes/no), watch_every_min, watch_mode (operations/always), summary_enabled (yes/no), summary_at (HH:MM UTC), summary_send (yes/no)"},
|
|
70
82
|
}
|
|
71
|
-
ORDER = ('wells', 'fleet', 'rates', 'items', 'afe', 'surveys', 'runs', 'components', 'penetrators', 'esp_drives', 'operations', 'catalogs'
|
|
72
|
-
|
|
83
|
+
ORDER = ('wells', 'fleet', 'rates', 'items', 'afe', 'surveys', 'runs', 'components', 'penetrators', 'esp_drives', 'operations', 'catalogs',
|
|
84
|
+
'contract', 'rig_time', 'block_weights', 'casing', 'settings')
|
|
85
|
+
APPLY_ORDER = ('wells', 'fleet', 'rates', 'items', 'afe', 'surveys', 'runs', 'penetrators', 'esp_drives', 'operations', 'catalogs',
|
|
86
|
+
'contract', 'rig_time', 'block_weights', 'casing', 'settings')
|
|
87
|
+
SETTINGS = ('watch_enabled', 'watch_every_min', 'watch_mode', 'summary_enabled', 'summary_at', 'summary_send')
|
|
73
88
|
|
|
74
89
|
|
|
75
90
|
class SheetError(ValueError):
|
|
@@ -525,6 +540,98 @@ def plan(ws, text: str, base_dir: str = '') -> dict:
|
|
|
525
540
|
rec['lat'], rec['lon'], rec['radius_km'], rec['source']))
|
|
526
541
|
guard('catalogs', rec, b)
|
|
527
542
|
|
|
543
|
+
# the contract, rig time, casing and settings
|
|
544
|
+
from . import invoice_check as IC
|
|
545
|
+
from . import rig_state as RS
|
|
546
|
+
from . import seismic_watch as SW
|
|
547
|
+
from . import pad_summary as PS
|
|
548
|
+
for rec in tables.get('contract', [])[:1]:
|
|
549
|
+
def b(rec=rec):
|
|
550
|
+
rates = {k: rec.get(k) for k in IC.KINDS if (rec.get(k) or '').strip()}
|
|
551
|
+
new = IC.terms_record(rec.get('source', ''), (rec.get('day_start') or '06:00').strip(), (rec.get('report_date') or 'start').strip(), rates,
|
|
552
|
+
rec.get('repair_hours_per_job'), rec.get('repair_hours_per_month'))
|
|
553
|
+
old = IC.terms(ws)
|
|
554
|
+
keys = ('source', 'day_start', 'report_date', 'rates_usd_per_day', 'repair_hours_per_job', 'repair_hours_per_month')
|
|
555
|
+
if old and all(old.get(k) == new[k] for k in keys):
|
|
556
|
+
add('contract', rec['_row'], None, 'SAME', 'the contract terms already declared')
|
|
557
|
+
return
|
|
558
|
+
add('contract', rec['_row'], None, 'CHANGE' if old else 'NEW',
|
|
559
|
+
f"operating {new['rates_usd_per_day']['operating']:,.2f} USD/day, report day from {new['day_start']} ({new['source']})",
|
|
560
|
+
lambda actor, rec=rec, rates=rates: IC.set_terms(ws, actor, rec.get('source', ''), (rec.get('day_start') or '06:00').strip(),
|
|
561
|
+
(rec.get('report_date') or 'start').strip(), rates, rec.get('repair_hours_per_job'),
|
|
562
|
+
rec.get('repair_hours_per_month')))
|
|
563
|
+
guard('contract', rec, b)
|
|
564
|
+
if len(tables.get('contract', [])) > 1:
|
|
565
|
+
errs.append(f"row {tables['contract'][1]['_row']}: [contract] the contract is one row")
|
|
566
|
+
for rec in tables.get('rig_time', [])[:1]:
|
|
567
|
+
def b(rec=rec):
|
|
568
|
+
vals = {k: _f(rec.get(k), k) for k in ('w2w_min', 'trip_in_ft_h', 'trip_out_ft_h')}
|
|
569
|
+
if not any(v is not None for v in vals.values()):
|
|
570
|
+
raise SheetError('give at least one target')
|
|
571
|
+
cur = RS.settings(ws).get('targets', {})
|
|
572
|
+
want = {k: v for k, v in vals.items() if v is not None}
|
|
573
|
+
if all(cur.get(k) == v for k, v in want.items()) and RS.settings(ws).get('targets_source') == rec['source'].strip():
|
|
574
|
+
add('rig_time', rec['_row'], None, 'SAME', 'the targets already declared')
|
|
575
|
+
return
|
|
576
|
+
add('rig_time', rec['_row'], None, 'CHANGE' if cur else 'NEW', ', '.join(f"{k.replace('_', ' ')} {v:g}" for k, v in want.items()) + f" ({rec['source'].strip()})",
|
|
577
|
+
lambda actor, vals=vals, rec=rec: RS.set_settings(ws, actor, rec['source'], vals['w2w_min'], vals['trip_in_ft_h'], vals['trip_out_ft_h']))
|
|
578
|
+
guard('rig_time', rec, b)
|
|
579
|
+
bw_have = RS.settings(ws).get('block_weight_klbf', {})
|
|
580
|
+
for rec in tables.get('block_weights', []):
|
|
581
|
+
def b(rec=rec):
|
|
582
|
+
wid = _well_id(ws, rec['well'], wells)
|
|
583
|
+
v = _f(rec.get('block_weight_klbf'), 'the block weight')
|
|
584
|
+
if bw_have.get(wid) == v:
|
|
585
|
+
add('block_weights', rec['_row'], wid, 'SAME', f'{v:g} klbf already declared')
|
|
586
|
+
return
|
|
587
|
+
add('block_weights', rec['_row'], wid, 'CHANGE' if wid in bw_have else 'NEW', f'block weight {v:g} klbf',
|
|
588
|
+
lambda actor, wid=wid, v=v: RS.set_settings(ws, actor, '', None, None, None, (wid, v)))
|
|
589
|
+
guard('block_weights', rec, b)
|
|
590
|
+
cs_have = RS.casing(ws)
|
|
591
|
+
for rec in tables.get('casing', []):
|
|
592
|
+
def b(rec=rec):
|
|
593
|
+
wid = _well_id(ws, rec['well'], wells)
|
|
594
|
+
new = RS.casing_record(ws, wid, rec.get('size_in', ''), rec.get('shoe_md_ft'), rec.get('source', ''), rec.get('set') or None)
|
|
595
|
+
old = next((x for x in cs_have.get(wid, []) if x['size'] == new['size']), None)
|
|
596
|
+
if old == new:
|
|
597
|
+
add('casing', rec['_row'], wid, 'SAME', f"{new['size']} in shoe already declared")
|
|
598
|
+
return
|
|
599
|
+
add('casing', rec['_row'], wid, 'CHANGE' if old else 'NEW', f"{new['size']} in shoe at {new['shoe_md_ft']:,.0f} ft ({new['source']})",
|
|
600
|
+
lambda actor, wid=wid, rec=rec: RS.set_casing(ws, actor, wid, rec.get('size_in', ''), rec.get('shoe_md_ft'), rec.get('source', ''), rec.get('set') or None))
|
|
601
|
+
guard('casing', rec, b)
|
|
602
|
+
sw_now, ps_now = SW.settings(ws), PS.settings(ws)
|
|
603
|
+
yes = lambda v: str(v).strip().lower() in ('yes', 'y', 'true', '1', 'on')
|
|
604
|
+
for rec in tables.get('settings', []):
|
|
605
|
+
def b(rec=rec):
|
|
606
|
+
k, v = rec['setting'].strip(), rec['value'].strip()
|
|
607
|
+
if k not in SETTINGS:
|
|
608
|
+
raise SheetError(f"no setting {k!r}: one of {', '.join(SETTINGS)}")
|
|
609
|
+
if k.endswith(('_enabled', '_send')) and v.lower() not in ('yes', 'no', 'y', 'n', 'true', 'false', '1', '0', 'on', 'off'):
|
|
610
|
+
raise SheetError(f'{k} is yes or no, not {v!r}')
|
|
611
|
+
if k == 'watch_every_min':
|
|
612
|
+
x = _f(v, k)
|
|
613
|
+
cur, want, fn = sw_now['every_min'], x, (lambda actor, x=x: SW.set_settings(ws, actor, every_min=x))
|
|
614
|
+
elif k == 'watch_mode':
|
|
615
|
+
if v not in ('operations', 'always'):
|
|
616
|
+
raise SheetError("watch_mode is 'operations' or 'always'")
|
|
617
|
+
cur, want, fn = sw_now['mode'], v, (lambda actor, v=v: SW.set_settings(ws, actor, mode=v))
|
|
618
|
+
elif k == 'watch_enabled':
|
|
619
|
+
cur, want, fn = sw_now['enabled'], yes(v), (lambda actor, v=v: SW.set_settings(ws, actor, enabled=yes(v)))
|
|
620
|
+
elif k == 'summary_enabled':
|
|
621
|
+
cur, want, fn = ps_now['enabled'], yes(v), (lambda actor, v=v: PS.set_settings(ws, actor, enabled=yes(v)))
|
|
622
|
+
elif k == 'summary_send':
|
|
623
|
+
cur, want, fn = ps_now['send'], yes(v), (lambda actor, v=v: PS.set_settings(ws, actor, send=yes(v)))
|
|
624
|
+
else:
|
|
625
|
+
if not re.fullmatch(r'([01]?\d|2[0-3]):[0-5]\d', v):
|
|
626
|
+
raise SheetError('summary_at is HH:MM, 24-hour, UTC')
|
|
627
|
+
want = f"{int(v.split(':')[0]):02d}:{v.split(':')[1]}"
|
|
628
|
+
cur, fn = ps_now['at'], (lambda actor, w_=want: PS.set_settings(ws, actor, at=w_))
|
|
629
|
+
if cur == want:
|
|
630
|
+
add('settings', rec['_row'], None, 'SAME', f'{k} already {_g(want)}')
|
|
631
|
+
return
|
|
632
|
+
add('settings', rec['_row'], None, 'CHANGE', f'{k} {_g(cur)} -> {_g(want)}', fn)
|
|
633
|
+
guard('settings', rec, b)
|
|
634
|
+
|
|
528
635
|
steps.sort(key=lambda s: (APPLY_ORDER.index(s['table']), s['row']))
|
|
529
636
|
counts = {k: sum(1 for s in steps if s['action'] == k) for k in ('NEW', 'CHANGE', 'SAME')}
|
|
530
637
|
return {'steps': steps, 'errors': sorted(errs, key=lambda e: int(re.match(r'row (\d+)', e).group(1)) if re.match(r'row (\d+)', e) else 0),
|
|
@@ -669,4 +776,31 @@ def template(ws) -> str:
|
|
|
669
776
|
head('catalogs')
|
|
670
777
|
for c in OS.catalogs(ws):
|
|
671
778
|
w.writerow(['# ' + c['file'] + ' is attached (' + str(c['n_events']) + ' events); list it here only to replace it'])
|
|
779
|
+
from . import invoice_check as IC
|
|
780
|
+
from . import rig_state as RS
|
|
781
|
+
from . import seismic_watch as SW
|
|
782
|
+
from . import pad_summary as PS
|
|
783
|
+
head('contract')
|
|
784
|
+
t = IC.terms(ws)
|
|
785
|
+
if t:
|
|
786
|
+
r = t['rates_usd_per_day']
|
|
787
|
+
w.writerow(['', t['day_start'], t.get('report_date', 'start')] + [_g(r.get(k)) for k in IC.KINDS]
|
|
788
|
+
+ [_g(t.get('repair_hours_per_job')), _g(t.get('repair_hours_per_month')), t['source']])
|
|
789
|
+
head('rig_time')
|
|
790
|
+
rs_ = RS.settings(ws)
|
|
791
|
+
if rs_.get('targets'):
|
|
792
|
+
tg = rs_['targets']
|
|
793
|
+
w.writerow(['', _g(tg.get('w2w_min')), _g(tg.get('trip_in_ft_h')), _g(tg.get('trip_out_ft_h')), rs_.get('targets_source', '')])
|
|
794
|
+
head('block_weights')
|
|
795
|
+
for wid, v in rs_.get('block_weight_klbf', {}).items():
|
|
796
|
+
w.writerow(['', wid, _g(v)])
|
|
797
|
+
head('casing')
|
|
798
|
+
for wid, lst in RS.casing(ws).items():
|
|
799
|
+
for c in lst:
|
|
800
|
+
w.writerow(['', wid, c['size'], _g(c['shoe_md_ft']), c.get('set') or '', c['source']])
|
|
801
|
+
head('settings')
|
|
802
|
+
sw, ps = SW.settings(ws), PS.settings(ws)
|
|
803
|
+
for k, v in (('watch_enabled', _g(bool(sw['enabled']))), ('watch_every_min', _g(float(sw['every_min']))), ('watch_mode', sw['mode']),
|
|
804
|
+
('summary_enabled', _g(bool(ps['enabled']))), ('summary_at', ps['at']), ('summary_send', _g(bool(ps['send'])))):
|
|
805
|
+
w.writerow(['', k, v])
|
|
672
806
|
return buf.getvalue()
|
|
@@ -5,7 +5,8 @@ A dossier is a zip:
|
|
|
5
5
|
- `manifest.json` - the well or pad, the time, the program and its version, and every file the dossier carries
|
|
6
6
|
with its size and SHA-256: the reports as they stand (with the history version each one is), the declarations
|
|
7
7
|
behind them (the well's identity and position, its survey, rates and AFE, runs, penetrator, operations,
|
|
8
|
-
catalogues, the fleet
|
|
8
|
+
catalogues, the fleet, the contract's terms with the invoices and daily reports attached, the rig-time targets
|
|
9
|
+
and casing), the invoice cross-check and the rig time as they read at signing (`results/`), the audit log, and - by hash, or embedded when asked - the source records the reports
|
|
9
10
|
were made from; the audit log's hash chain (every line chained to the one before, across its rotated segments)
|
|
10
11
|
and its length; the previous dossier's id and manifest hash, so the dossiers of a workspace form one chain;
|
|
11
12
|
- `signature.json` - an Ed25519 signature (RFC 8032) over the manifest's exact bytes, with the public key;
|
|
@@ -272,11 +273,15 @@ def _gather(ws, well_id: Optional[str]) -> Dict[str, List[tuple]]:
|
|
|
272
273
|
out_list.append((os.path.join(wd, 'well.json'), f"declarations/wells/{w['id']}/well.json"))
|
|
273
274
|
files_in(os.path.join(wd, 'trajectory'), f"declarations/wells/{w['id']}/trajectory")
|
|
274
275
|
for rel in (('economics', 'costs.json'), ('drillstring', 'runs.json'), ('completion', 'esp.json'), ('seismicity', 'operations.json'),
|
|
275
|
-
('seismicity', 'catalogs.json'), ('seismicity', 'settings.json'), ('config', 'fleet.json'), ('workspace.json',)
|
|
276
|
+
('seismicity', 'catalogs.json'), ('seismicity', 'settings.json'), ('config', 'fleet.json'), ('workspace.json',),
|
|
277
|
+
('economics', 'invoices', 'terms.json'), ('economics', 'invoices', 'files.json'), ('drillstring', 'rig_time.json'),
|
|
278
|
+
('drillstring', 'casing.json')):
|
|
276
279
|
p = os.path.join(ws.path, *rel)
|
|
277
280
|
if os.path.isfile(p):
|
|
278
281
|
out_list.append((p, 'declarations/' + '/'.join(rel)))
|
|
279
282
|
files_in(os.path.join(ws.path, 'seismicity', 'catalogs'), 'declarations/seismicity/catalogs')
|
|
283
|
+
for w in wells: # the contractor's invoices and daily reports, as attached
|
|
284
|
+
files_in(os.path.join(ws.path, 'economics', 'invoices', w['id']), f"declarations/economics/invoices/{w['id']}")
|
|
280
285
|
for p in audit_segments(ws):
|
|
281
286
|
out['audit'].append((p, 'audit/' + os.path.basename(p)))
|
|
282
287
|
for w in wells:
|
|
@@ -289,6 +294,32 @@ def _gather(ws, well_id: Optional[str]) -> Dict[str, List[tuple]]:
|
|
|
289
294
|
return out
|
|
290
295
|
|
|
291
296
|
|
|
297
|
+
def _results(ws, well_id: Optional[str]) -> List[tuple]:
|
|
298
|
+
"""The invoice cross-check of each well with invoices or reports attached, and the rig time of each well with a
|
|
299
|
+
drilling record, as JSON computed when the dossier is signed (a check that cannot run is left out, and says why)."""
|
|
300
|
+
out = []
|
|
301
|
+
wells = [well_id] if well_id else [w['id'] for w in ws.wells()]
|
|
302
|
+
try:
|
|
303
|
+
from . import invoice_check as IC
|
|
304
|
+
if IC.terms(ws):
|
|
305
|
+
for wid in wells:
|
|
306
|
+
if IC.files(ws, wid):
|
|
307
|
+
r = IC.check(ws, wid)
|
|
308
|
+
out.append((f'results/invoice_check/{wid}.json', json.dumps(r, indent=1, sort_keys=True, default=str).encode('utf-8')))
|
|
309
|
+
except Exception as e:
|
|
310
|
+
out.append(('results/invoice_check/not_run.txt', f'the invoice check did not run: {type(e).__name__}: {e}\n'.encode('utf-8')))
|
|
311
|
+
try:
|
|
312
|
+
from . import rig_state as RS
|
|
313
|
+
p = RS.pad(ws)
|
|
314
|
+
ws_ = [w for w in p['wells'] if w['well_id'] in wells]
|
|
315
|
+
if ws_:
|
|
316
|
+
p = {**p, 'wells': ws_}
|
|
317
|
+
out.append(('results/rig_time.json', json.dumps(p, indent=1, sort_keys=True, default=str).encode('utf-8')))
|
|
318
|
+
except Exception as e:
|
|
319
|
+
out.append(('results/rig_time_not_run.txt', f'rig time did not run: {type(e).__name__}: {e}\n'.encode('utf-8')))
|
|
320
|
+
return out
|
|
321
|
+
|
|
322
|
+
|
|
292
323
|
def _history_versions(ws) -> Dict[str, dict]:
|
|
293
324
|
"""The latest history entry of each report file, by its path under reports/, with that version's folder."""
|
|
294
325
|
from . import report_history as RH
|
|
@@ -354,6 +385,9 @@ def build(ws, actor: str, version: str, well_id: Optional[str] = None, include_s
|
|
|
354
385
|
if hv and hv['sha256'] == row['sha256']:
|
|
355
386
|
row['history'] = {'dir': hv['dir'], 'written_utc': hv['utc'], 'report_id': hv['report_id']}
|
|
356
387
|
files.append(row); payload.append((arc, b))
|
|
388
|
+
for arc, b in _results(ws, well_id): # the checks as they read at signing
|
|
389
|
+
files.append({'path': arc, 'role': 'result', 'bytes': len(b), 'sha256': hashlib.sha256(b).hexdigest(), 'workspace_path': None})
|
|
390
|
+
payload.append((arc, b))
|
|
357
391
|
kh = key_history(ws)
|
|
358
392
|
if kh['rotations']: # the key's lineage, so a client who knows an earlier fingerprint can follow it
|
|
359
393
|
b = json.dumps({'schema': 1, 'rotations': kh['rotations'],
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**How the watch reads the catalogue**: it runs in the service's background poller, so only while `gea serve` (the control panel) is running. An operation is under way when a declared operation (Operations & Seismicity, or the declarations sheet's `[operations]`) has started and has not ended, at a well with a surface position. A poll reads the USGS real-time summary feed of the past day, all magnitudes, updated every minute - the feed USGS asks automated programs to use - and keeps the earthquakes inside the circle around the operating wells (out to the screening distance, 20 km by default, beyond the farthest). On the first poll, or after a gap of a day or more, it asks the FDSN event service for the whole gap instead (from the operation's start, at most 30 days back). USGS revises magnitudes and locations; the newest version wins, and a revision that reaches a higher protocol level is a new alert.
|
|
8
8
|
|
|
9
|
-
**The alerts**: `seismic.protocol` (a protocol level reached, once per level), `seismic.event` (any earthquake within the association radius during or after an operation, once), `seismic.watch_down` (no answer from the catalogue for three intervals while an operation runs) and `seismic.watch_up`; `motor.due` (checked every 15 minutes), `penetrator.outside` (every 15 minutes), `dossier.changed` (the newest dossier of each well and of the pad, re-checked daily), and `condition.cleared` when one of the last
|
|
9
|
+
**The alerts**: `seismic.protocol` (a protocol level reached, once per level), `seismic.event` (any earthquake within the association radius during or after an operation, once), `seismic.watch_down` (no answer from the catalogue for three intervals while an operation runs) and `seismic.watch_up`; `motor.due` (checked every 15 minutes), `penetrator.outside` (every 15 minutes), `dossier.changed` (the newest dossier of each well and of the pad, re-checked daily), `invoice.differs` (a well's invoice cross-check finds a day that differs; checked daily and when a file is attached), and `condition.cleared` when one of the last four ends.
|
|
10
10
|
|
|
11
11
|
**Getting them to a person**: every alert is written to `records/alerts.jsonl` and shown on the Alarms tab. To have it sent, set up notifications (Administration, Notifications: load the example, edit, validate and commit) with an email (SMTP) or webhook channel and a rule naming the alert, for example `{"event": "seismic.protocol", "channels": ["mail"]}`; the password stays in an environment variable named by `password_env`. A text message: most carriers deliver email sent to the phone's email-to-text address, or a webhook to a messaging service. The `quiet_s` window keeps one alert from repeating.
|
|
12
12
|
|