gea-program 0.36.0__tar.gz → 0.38.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.36.0/gea_program.egg-info → gea_program-0.38.0}/PKG-INFO +23 -1
- {gea_program-0.36.0 → gea_program-0.38.0}/README.md +22 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/__init__.py +1 -1
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/__main__.py +81 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/acceptance_tests.py +340 -1
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/client_reports.py +27 -1
- gea_program-0.38.0/gea/economics.py +454 -0
- gea_program-0.38.0/gea/help/economics.md +11 -0
- gea_program-0.38.0/gea/help/well-report.md +11 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/helplib.py +3 -1
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/month_end.py +14 -0
- gea_program-0.38.0/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
- gea_program-0.38.0/gea/reference/eia_prices.provenance.json +51 -0
- gea_program-0.38.0/gea/reference/eia_wti_daily.csv.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/service.py +41 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/web/app.html +78 -1
- gea_program-0.38.0/gea/well_report.py +469 -0
- {gea_program-0.36.0 → gea_program-0.38.0/gea_program.egg-info}/PKG-INFO +23 -1
- {gea_program-0.36.0 → gea_program-0.38.0}/gea_program.egg-info/SOURCES.txt +7 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/pyproject.toml +1 -1
- {gea_program-0.36.0 → gea_program-0.38.0}/LICENSE +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/accuracy_statement.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/alarm_engine.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/backup.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/bench.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/blind_harness.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/med_160_sapropels.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog_map.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/catalog_register.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/certificates.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/cli.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/config_versioning.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/conformance.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/control_room.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/correlation.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/dashboard.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/deviation.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/dlis.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/dnp3.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/doctor.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/downhole_engine.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/drift_monitor.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/earth_model.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/edr.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/elevation.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/etp.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/etp12_schemas.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/example_register_map.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/fat_sat.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/files.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/fixtures/README.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/fixtures/ne_110m_coastline.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/follower.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/forward_model.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/friction_priors.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/gamma.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/gauge_aging.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/gauge_specs.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/geodesy.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/gravity_reference.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/hartip.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/TESTER_GUIDE.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/alarms.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/audit-update.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/backup.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/catalog.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/charts.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/conformance.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/control-room.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/data-in.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/dlis.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/doctor.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/drift.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/edr.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/etp.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/field-ports.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/files.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/historian.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/instruments.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/interval.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/map.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/month-end.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/notifications.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/osdu.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/patches.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/ppdm.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/quakeml.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/quality.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/seedlink.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/seismic.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/setup.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/site.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/sites.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/slip.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/soak.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/sra.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/start.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/structure.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/tools.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/transients.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/upkeep.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/vibration.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/well-tests.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/help/well3d.md +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/housekeeping.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/interval.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/inverse_engine.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/jobs.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/live_ports.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/loadtest.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/matplotlib_demo.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/modbus.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/modbus_rtu.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/model_card.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/mqtt_port.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/notify.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/opcua_port.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/operator_app.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/osdu.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/patches.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/permits.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/piwebapi.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/ports.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/ppdm.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/profile_catalog.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/project.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/qt6_downhole_app.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/quakeml.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reconciler.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/libmseed_steim.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/tool_sources.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/us_outline.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/us_outline.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_3d.provenance.json +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_surfaces.json.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/reference/volve_well_picks.csv.gz +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/rock_inventory.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/sample_record.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/sample_well_profile.csv +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/sbom.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seedlink.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/segy.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_array.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_assoc.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_detect.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_film.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_harmonic.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_qc.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_response.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_signature.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_track.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_uncertainty.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/seismic_unlisted.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/sensor_swap.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/series_cache.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/service_life.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/setup_checklist.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/shapefile.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/shell.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/shut_in.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/site_map.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/sla_report.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/slip.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/soak.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/sra.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/store_forward.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/strata_join.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/structure.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/supervisor.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/survey_cmd.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/survey_view.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/telemetry.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/tool_catalog.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/tool_library.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/tool_taxonomy.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/transient.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/vibration.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/well3d.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/well_assembler.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/well_test_validation.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/wits0.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/witsml.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea/workspace.py +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea_program.egg-info/dependency_links.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea_program.egg-info/entry_points.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea_program.egg-info/requires.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/gea_program.egg-info/top_level.txt +0 -0
- {gea_program-0.36.0 → gea_program-0.38.0}/setup.cfg +0 -0
- {gea_program-0.36.0 → gea_program-0.38.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.38.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,28 @@ 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 client well report
|
|
652
|
+
|
|
653
|
+
`gea well-report` writes one printable document per well and one for the
|
|
654
|
+
pad: the drilling record and its depth events with who checked them, a chart
|
|
655
|
+
of depth against days, the declared location and what the mapped structure
|
|
656
|
+
says there, the well path from its survey in plan and section, and the
|
|
657
|
+
declared cost against the market while it was drilled - each number with
|
|
658
|
+
where it came from, and what the report cannot say yet with what would
|
|
659
|
+
supply it. Buttons on the well page and the Reports page; included in the
|
|
660
|
+
month-end packet. `gea help well-report`.
|
|
661
|
+
|
|
662
|
+
### Economics
|
|
663
|
+
|
|
664
|
+
The Economics tab puts each well's days on its drilling record, feet
|
|
665
|
+
drilled and feet per day beside what it cost by the operator's own declared
|
|
666
|
+
day rates, items and AFE - cost per foot, per day and by section, never
|
|
667
|
+
divided while the rates leave time uncovered - and sets each well against
|
|
668
|
+
the U.S. Energy Information Administration's daily WTI and Henry Hub spot
|
|
669
|
+
prices while it was drilled. The prices ship with the program and refresh
|
|
670
|
+
on request; any other series (a rig count) is imported with its source.
|
|
671
|
+
`gea economics`, `gea help economics`.
|
|
672
|
+
|
|
651
673
|
### The well in 3D
|
|
652
674
|
|
|
653
675
|
The 3D tab draws each well's path from its directional survey by the
|
|
@@ -609,6 +609,28 @@ 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 client well report
|
|
613
|
+
|
|
614
|
+
`gea well-report` writes one printable document per well and one for the
|
|
615
|
+
pad: the drilling record and its depth events with who checked them, a chart
|
|
616
|
+
of depth against days, the declared location and what the mapped structure
|
|
617
|
+
says there, the well path from its survey in plan and section, and the
|
|
618
|
+
declared cost against the market while it was drilled - each number with
|
|
619
|
+
where it came from, and what the report cannot say yet with what would
|
|
620
|
+
supply it. Buttons on the well page and the Reports page; included in the
|
|
621
|
+
month-end packet. `gea help well-report`.
|
|
622
|
+
|
|
623
|
+
### Economics
|
|
624
|
+
|
|
625
|
+
The Economics tab puts each well's days on its drilling record, feet
|
|
626
|
+
drilled and feet per day beside what it cost by the operator's own declared
|
|
627
|
+
day rates, items and AFE - cost per foot, per day and by section, never
|
|
628
|
+
divided while the rates leave time uncovered - and sets each well against
|
|
629
|
+
the U.S. Energy Information Administration's daily WTI and Henry Hub spot
|
|
630
|
+
prices while it was drilled. The prices ship with the program and refresh
|
|
631
|
+
on request; any other series (a rig count) is imported with its source.
|
|
632
|
+
`gea economics`, `gea help economics`.
|
|
633
|
+
|
|
612
634
|
### The well in 3D
|
|
613
635
|
|
|
614
636
|
The 3D tab draws each well's path from its directional survey by the
|
|
@@ -475,6 +475,30 @@ 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_wr = sub.add_parser("well-report", help="the client well report: one printable document per well (drilling record, depth events, progress chart, location and structure, well path, cost and the market) and one for the pad")
|
|
479
|
+
p_wr.add_argument("--workspace", type=str, required=True)
|
|
480
|
+
p_wr.add_argument("--well", type=str, default=None, help="one well's report (the default is every well and the pad)")
|
|
481
|
+
p_wr.add_argument("--pad", action="store_true", help="the pad report only")
|
|
482
|
+
p_wr.add_argument("--actor", type=str, default="cli")
|
|
483
|
+
|
|
484
|
+
p_ec = sub.add_parser("economics", help="each well's time, footage and declared cost against the market while it was drilled; declare rates, items and an AFE; refresh or import price series")
|
|
485
|
+
p_ec.add_argument("--workspace", type=str, required=True)
|
|
486
|
+
p_ec.add_argument("--json", action="store_true")
|
|
487
|
+
p_ec.add_argument("--well", type=str, default=None, help="the well a rate, item or AFE is for")
|
|
488
|
+
p_ec.add_argument("--rate", type=float, default=None, help="a day rate, USD/day, over --from/--to (open when left out)")
|
|
489
|
+
p_ec.add_argument("--item", type=float, default=None, help="a one-off cost, USD (a bit, a cement job), optionally --section N")
|
|
490
|
+
p_ec.add_argument("--afe", type=float, default=None, help="the well's AFE, USD")
|
|
491
|
+
p_ec.add_argument("--what", type=str, default="", help="what the rate or item pays for")
|
|
492
|
+
p_ec.add_argument("--section", type=int, default=None)
|
|
493
|
+
p_ec.add_argument("--from", dest="date_from", type=str, default=None, help="rate start, ISO date or date-time on the recorder's clock")
|
|
494
|
+
p_ec.add_argument("--to", dest="date_to", type=str, default=None, help="rate end")
|
|
495
|
+
p_ec.add_argument("--source", type=str, default="", help="where the number came from (contract, invoice, AFE number)")
|
|
496
|
+
p_ec.add_argument("--refresh-prices", action="store_true", help="fetch the latest EIA spot-price copies into the workspace")
|
|
497
|
+
p_ec.add_argument("--import-series", type=str, default=None, help="a CSV of dates and values to keep beside the prices (a rig count)")
|
|
498
|
+
p_ec.add_argument("--name", type=str, default="")
|
|
499
|
+
p_ec.add_argument("--unit", type=str, default="")
|
|
500
|
+
p_ec.add_argument("--actor", type=str, default="cli")
|
|
501
|
+
|
|
478
502
|
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
503
|
p_tj.add_argument("--workspace", type=str, required=True)
|
|
480
504
|
p_tj.add_argument("--well", type=str, required=True)
|
|
@@ -1954,6 +1978,63 @@ def main(argv=None) -> int:
|
|
|
1954
1978
|
else:
|
|
1955
1979
|
print(_SC.text(r))
|
|
1956
1980
|
return 0 if r["ready"] else 2
|
|
1981
|
+
elif a.cmd == "economics":
|
|
1982
|
+
import json as _json
|
|
1983
|
+
from . import economics as _EC
|
|
1984
|
+
from .cli import _safe_stdout
|
|
1985
|
+
from .workspace import Workspace, WorkspaceError
|
|
1986
|
+
_safe_stdout()
|
|
1987
|
+
try:
|
|
1988
|
+
ws = Workspace(a.workspace)
|
|
1989
|
+
except WorkspaceError as e:
|
|
1990
|
+
raise SystemExit(f"economics: {e}")
|
|
1991
|
+
try:
|
|
1992
|
+
if a.rate is not None:
|
|
1993
|
+
r = _EC.set_rate(ws, a.well or "", a.actor, a.rate, a.what, a.source, a.date_from, a.date_to)
|
|
1994
|
+
print(f"{a.well}: {r['usd_per_day']:,.2f} USD/day {r['what']} from {r['from'] or 'open'} to {r['to'] or 'open'}")
|
|
1995
|
+
return 0
|
|
1996
|
+
if a.item is not None:
|
|
1997
|
+
r = _EC.add_item(ws, a.well or "", a.actor, a.item, a.what, a.source, a.section)
|
|
1998
|
+
print(f"{a.well}: {r['usd']:,.2f} USD {r['what']}")
|
|
1999
|
+
return 0
|
|
2000
|
+
if a.afe is not None:
|
|
2001
|
+
r = _EC.set_afe(ws, a.well or "", a.actor, a.afe, a.source)
|
|
2002
|
+
print(f"{a.well}: AFE {r['usd']:,.2f} USD ({r['source']})")
|
|
2003
|
+
return 0
|
|
2004
|
+
if a.refresh_prices:
|
|
2005
|
+
for r in _EC.refresh_prices(ws, a.actor):
|
|
2006
|
+
print(f"{r['id']}: {r['status']}" + (f" to {r['as_of']} ({r['rows']} days)" if r.get('as_of') else f" - {r.get('why', '')}"))
|
|
2007
|
+
return 0
|
|
2008
|
+
if a.import_series:
|
|
2009
|
+
with open(a.import_series, "rb") as f:
|
|
2010
|
+
r = _EC.import_series(ws, a.actor, a.name, a.unit, a.source, f.read())
|
|
2011
|
+
print(f"{r['name']}: {r['rows']} values, {r['first']} to {r['as_of']} ({r['unit']})")
|
|
2012
|
+
return 0
|
|
2013
|
+
except (ValueError, KeyError, OSError, WorkspaceError) as e:
|
|
2014
|
+
raise SystemExit(f"economics: {e.args[0] if e.args else e}")
|
|
2015
|
+
e = _EC.economics(ws)
|
|
2016
|
+
print(_json.dumps(e, indent=1) if a.json else _EC.text(e))
|
|
2017
|
+
return 0
|
|
2018
|
+
elif a.cmd == "well-report":
|
|
2019
|
+
from . import well_report as _WR
|
|
2020
|
+
from .cli import _safe_stdout
|
|
2021
|
+
from .workspace import Workspace, WorkspaceError
|
|
2022
|
+
_safe_stdout()
|
|
2023
|
+
try:
|
|
2024
|
+
ws = Workspace(a.workspace)
|
|
2025
|
+
except WorkspaceError as e:
|
|
2026
|
+
raise SystemExit(f"well-report: {e}")
|
|
2027
|
+
ids = [w["id"] for w in ws.wells()]
|
|
2028
|
+
if a.well and a.well not in ids:
|
|
2029
|
+
raise SystemExit(f"well-report: no well {a.well} (the wells are: {', '.join(ids) or 'none'})")
|
|
2030
|
+
todo = [] if a.pad else ([a.well] if a.well else ids)
|
|
2031
|
+
for wid in todo:
|
|
2032
|
+
r = _WR.write_well(ws, wid, __version__, actor=a.actor)
|
|
2033
|
+
print(f"{wid}: {r['paths']['html']}" + (f"\n not in it yet: {'; '.join(r['missing'])}" if r["missing"] else ""))
|
|
2034
|
+
if a.pad or not a.well:
|
|
2035
|
+
r = _WR.write_pad(ws, __version__, actor=a.actor)
|
|
2036
|
+
print(f"pad: {r['paths']['html']}")
|
|
2037
|
+
return 0
|
|
1957
2038
|
elif a.cmd in ("trajectory", "well3d"):
|
|
1958
2039
|
import json as _json
|
|
1959
2040
|
from . import well3d as _W3
|
|
@@ -2917,7 +2917,7 @@ def section_am_report_samples(tmp: str) -> None:
|
|
|
2917
2917
|
"model_card_strata_property_estimator.html", "gauge_drift_report_monitored_SYNTHETIC.html", "alarm_event_report_SYNTHETIC.html",
|
|
2918
2918
|
"data_resilience_report_SYNTHETIC.html", "sla_report_SYNTHETIC.html", "sat_protocol.html", "seismic_station_report_SYNTHETIC.html", "seismic_track_report_SYNTHETIC.html",
|
|
2919
2919
|
"seismic_dataset_report_SYNTHETIC.html", "seismic_field_report_SYNTHETIC.html", "site_report_SYNTHETIC.html",
|
|
2920
|
-
"sra_packet_SYNTHETIC.html"}
|
|
2920
|
+
"sra_packet_SYNTHETIC.html", "well_report_SYNTHETIC.html", "pad_report_SYNTHETIC.html"}
|
|
2921
2921
|
have = {p.name for p in d.glob("*.html")}
|
|
2922
2922
|
md = (d / "SAMPLES.md").read_text(encoding="utf-8") if (d / "SAMPLES.md").exists() else ""
|
|
2923
2923
|
texts = {n: (d / n).read_text(encoding="utf-8", errors="replace") for n in sorted(have & want)}
|
|
@@ -8800,6 +8800,343 @@ def section_ce_well3d(tmp: str) -> None:
|
|
|
8800
8800
|
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
8801
8801
|
|
|
8802
8802
|
|
|
8803
|
+
|
|
8804
|
+
def section_cf_economics(tmp: str) -> None:
|
|
8805
|
+
"""Section CF - economics: the shipped EIA spot prices checked against their provenance and against two days EIA
|
|
8806
|
+
published, series refreshed and imported only when they read, a well's cost from declared rates done by hand and
|
|
8807
|
+
left undivided when the rates leave time uncovered, the market while the well was drilled, the API, the CLI, the tab."""
|
|
8808
|
+
import base64 as _b64
|
|
8809
|
+
import gzip as _gz
|
|
8810
|
+
import hashlib as _hl
|
|
8811
|
+
import http.cookiejar
|
|
8812
|
+
import subprocess as _sp
|
|
8813
|
+
import urllib.request
|
|
8814
|
+
import urllib.error
|
|
8815
|
+
from datetime import datetime as _dt
|
|
8816
|
+
from . import economics as EC
|
|
8817
|
+
from . import edr as E
|
|
8818
|
+
from . import helplib as H
|
|
8819
|
+
from .workspace import Workspace
|
|
8820
|
+
from .service import Service, Users
|
|
8821
|
+
pkg = Path(__file__).parent
|
|
8822
|
+
d = Path(tmp, "cf"); d.mkdir()
|
|
8823
|
+
|
|
8824
|
+
prov = json.loads((pkg / "reference" / "eia_prices.provenance.json").read_text(encoding="utf-8"))
|
|
8825
|
+
hashes = all(_hl.sha256((pkg / "reference" / s["file"]).read_bytes()).hexdigest() == s["sha256"]
|
|
8826
|
+
and _hl.sha256(_gz.decompress((pkg / "reference" / s["file"]).read_bytes())).hexdigest() == s["sha256_uncompressed"]
|
|
8827
|
+
and len(s["source"]["commit"]) == 40 for s in prov["series"].values())
|
|
8828
|
+
wd, wv, _ = EC.read_series(None, "wti")
|
|
8829
|
+
hd, hv, _ = EC.read_series(None, "henry_hub")
|
|
8830
|
+
refused = 0
|
|
8831
|
+
for bad in ("Date,Price\n2020-01-01,1\n2020-01-01,2\n", "Date,Price\n2020-01-01,1\n2020-01-02,x\n", "Date,Price\n2020-13-01,1\n2020-01-02,2\n", "Date,Price\n"):
|
|
8832
|
+
try:
|
|
8833
|
+
EC._parse_series(bad)
|
|
8834
|
+
except ValueError:
|
|
8835
|
+
refused += 1
|
|
8836
|
+
bd, bv, _ = EC._parse_series("Date,Price\n2020-01-03,3\n2020-01-01,1\n2020-01-02,\n")
|
|
8837
|
+
ev = E.selftest_events(str(d))
|
|
8838
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
8839
|
+
wb = ws.add_well_file(ev["path"], display="Well B", actor="tester"); wc = ws.add_well_file(ev["path"], display="Well C", actor="tester")
|
|
8840
|
+
later = ("Date,Price\n" + "".join(f"{x},{y}\n" for x, y in zip(wd, wv)) + "2099-01-01,50.0\n").encode()
|
|
8841
|
+
hh_short = "Date,Price\n2001-01-02,9.0\n2001-01-03,9.5\n".encode()
|
|
8842
|
+
calls = {}
|
|
8843
|
+
|
|
8844
|
+
def fake(u):
|
|
8845
|
+
calls[u] = True
|
|
8846
|
+
if "oil-prices" in u:
|
|
8847
|
+
return later
|
|
8848
|
+
if "natural-gas" in u:
|
|
8849
|
+
return hh_short
|
|
8850
|
+
raise OSError("no network")
|
|
8851
|
+
ref = EC.refresh_prices(ws, "tester", fetch=fake)
|
|
8852
|
+
ref_status = {r["id"]: r["status"] for r in ref}
|
|
8853
|
+
imp_refused = 0
|
|
8854
|
+
for kw in ({"name": "", "unit": "rigs", "source": "s"}, {"name": "Rigs", "unit": "", "source": "s"}, {"name": "Rigs", "unit": "rigs", "source": ""}, {"name": "wti", "unit": "x", "source": "s"}):
|
|
8855
|
+
try:
|
|
8856
|
+
EC.import_series(ws, "tester", kw["name"], kw["unit"], kw["source"], b"Date,Count\n2026-06-26,300\n2026-07-03,301\n")
|
|
8857
|
+
except ValueError:
|
|
8858
|
+
imp_refused += 1
|
|
8859
|
+
imp = EC.import_series(ws, "tester", "Permian rig count", "rigs", "test", b"Week,Rigs\n2026-06-26,300\n2026-07-03,301\n2026-07-10,299\n")
|
|
8860
|
+
c1 = {
|
|
8861
|
+
"shipped": hashes and prov["series"]["wti"]["eia_series"] == "RWTC" and prov["series"]["henry_hub"]["eia_series"] == "RNGWHHD"
|
|
8862
|
+
and "public domain" in prov["license"].lower() and wv[wd.index("2020-04-20")] == -36.98 and hv[hd.index("2021-02-17")] == 23.86
|
|
8863
|
+
and len(wd) == prov["series"]["wti"]["rows"] and wd == sorted(wd),
|
|
8864
|
+
"parse": refused == 4 and bd == ["2020-01-01", "2020-01-03"] and bv == [1.0, 3.0],
|
|
8865
|
+
"refresh": ref_status == {"wti": "REFRESHED", "henry_hub": "NOT REFRESHED"} and EC.read_series(ws, "wti")[0][-1] == "2099-01-01"
|
|
8866
|
+
and EC.read_series(ws, "henry_hub")[2]["kind"] == "shipped" and len(calls) == 2,
|
|
8867
|
+
"import": imp_refused == 4 and imp["id"] == "permian_rig_count" and imp["rows"] == 3 and imp["date_column"] == "Week" and imp["value_column"] == "Rigs"
|
|
8868
|
+
and Path(wsp, "economics", "series", "permian_rig_count.csv").read_bytes().startswith(b"Week,Rigs"),
|
|
8869
|
+
}
|
|
8870
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
8871
|
+
ok(not failed1,
|
|
8872
|
+
"CF1 the price series: EIA's daily WTI (RWTC) and Henry Hub (RNGWHHD) spot prices shipped with their provenance and hashes, carrying the "
|
|
8873
|
+
"-36.98 of 20 April 2020 and the 23.86 of 17 February 2021 as EIA published them; a series with a repeated or bad date or a bad number "
|
|
8874
|
+
"refused, a blank day left out; a refreshed copy kept only when it reads and runs later than the one held; an imported series kept byte "
|
|
8875
|
+
"for byte with its name, unit and source"
|
|
8876
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
8877
|
+
|
|
8878
|
+
ws.refresh_dashboard(actor="tester")
|
|
8879
|
+
rec = json.loads(Path(ws.reports_dir, "wells", wb["id"], "drilling_record.json").read_text(encoding="utf-8"))
|
|
8880
|
+
t0, t1 = _dt.fromisoformat(rec["start"]), _dt.fromisoformat(rec["end"])
|
|
8881
|
+
days = (t1 - t0).total_seconds() / 86400.0
|
|
8882
|
+
mid = "2026-07-02T12:00:00"
|
|
8883
|
+
EC.set_rate(ws, wb["id"], "tester", 30000, "rig spread", "contract", rec["start"], mid)
|
|
8884
|
+
part = EC.economics(ws)
|
|
8885
|
+
pb = next(w for w in part["wells"] if w["well_id"] == wb["id"])
|
|
8886
|
+
bad = 0
|
|
8887
|
+
for args in ((30000, "rig", "c", "2026-07-02", "2026-07-05"), (30000, "", "c", None, None), (30000, "rig", "", None, None), (-5, "rig", "c", "2027-01-01", None),
|
|
8888
|
+
(30000, "rig", "c", "2027-01-02", "2027-01-01")):
|
|
8889
|
+
try:
|
|
8890
|
+
EC.set_rate(ws, wb["id"], "tester", *args)
|
|
8891
|
+
except ValueError:
|
|
8892
|
+
bad += 1
|
|
8893
|
+
EC.set_rate(ws, wb["id"], "tester", 32000, "rig spread", "contract", mid, None)
|
|
8894
|
+
EC.add_item(ws, wb["id"], "tester", 18000, "bit", "invoice", 1)
|
|
8895
|
+
EC.set_afe(ws, wb["id"], "tester", 150000, "AFE 1")
|
|
8896
|
+
full = EC.economics(ws)
|
|
8897
|
+
fb = next(w for w in full["wells"] if w["well_id"] == wb["id"])
|
|
8898
|
+
first = (_dt.fromisoformat(mid) - t0).total_seconds() / 86400.0
|
|
8899
|
+
by_hand = round(30000 * first + 32000 * (days - first) + 18000, 2)
|
|
8900
|
+
win = [v for x, v in zip(wd, wv) if rec["start"][:10] <= x <= rec["end"][:10]]
|
|
8901
|
+
c2 = {
|
|
8902
|
+
"partial": pb["cost"]["complete"] is False and pb["cost"]["per_ft"] is None and "cover" in pb["cost"]["why_incomplete"]
|
|
8903
|
+
and abs(pb["cost"]["usd"] - round(30000 * first, 2)) < 0.01,
|
|
8904
|
+
"refused": bad == 5,
|
|
8905
|
+
"by_hand": fb["cost"]["complete"] and abs(fb["cost"]["usd"] - by_hand) < 0.01 and abs(fb["cost"]["per_ft"] - round(by_hand / rec["feet_drilled"], 2)) < 0.01
|
|
8906
|
+
and abs(fb["cost"]["vs_afe_pct"] - round(100 * (by_hand - 150000) / 150000, 1)) < 0.05 and fb["days"] == round(days, 2),
|
|
8907
|
+
"sections": all(s["complete"] for s in fb["sections"]) and fb["sections"][0]["cost_usd"] > 18000,
|
|
8908
|
+
"market": fb["market"]["wti"]["n"] == len(win) and abs(fb["market"]["wti"]["mean"] - round(sum(win) / len(win), 3)) < 1e-9
|
|
8909
|
+
and full["chart"]["wells"][0]["from"] == rec["start"][:10] and "permian_rig_count" in full["chart"]["series"],
|
|
8910
|
+
"correlation": "spearman_cost_per_ft_vs_wti" not in full["correlation"] and full["correlation"]["n_wells"] == 1
|
|
8911
|
+
and abs(EC._spearman([1, 2, 3, 4], [10, 20, 30, 40]) - 1) < 1e-12 and abs(EC._spearman([1, 2, 3, 4], [4, 3, 2, 1]) + 1) < 1e-12,
|
|
8912
|
+
"audit": all(a in [x["action"] for x in ws.audit_log(500)] for a in ("economics.rate", "economics.item", "economics.afe", "economics.prices.refresh", "economics.series.import")),
|
|
8913
|
+
}
|
|
8914
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
8915
|
+
ok(not failed2,
|
|
8916
|
+
"CF2 a well's cost: two day rates and a bit over the drilling record's own time come to the dollar the hand sum gives, with cost per foot "
|
|
8917
|
+
"and the difference from the AFE; while the rates leave time uncovered the cost is shown, marked incomplete and not divided by the footage; "
|
|
8918
|
+
"overlapping periods, a rate with no source or no purpose, a negative rate and a period that ends before it starts refused; the WTI mean "
|
|
8919
|
+
"while drilling equal to the days EIA published in that window; no correlation across fewer than ten wells; every declaration audited"
|
|
8920
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
8921
|
+
|
|
8922
|
+
Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
|
|
8923
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
8924
|
+
base_url = svc.url.rstrip("/")
|
|
8925
|
+
|
|
8926
|
+
def opener(name, pw):
|
|
8927
|
+
o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
8928
|
+
rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw}).encode())
|
|
8929
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
|
|
8930
|
+
return o
|
|
8931
|
+
|
|
8932
|
+
def call(o, method, path, body=None):
|
|
8933
|
+
rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
8934
|
+
if method == "POST":
|
|
8935
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
|
|
8936
|
+
try:
|
|
8937
|
+
with o.open(rq, timeout=120) as rr_:
|
|
8938
|
+
return rr_.status, json.loads(rr_.read())
|
|
8939
|
+
except urllib.error.HTTPError as e:
|
|
8940
|
+
return e.code, json.loads(e.read())
|
|
8941
|
+
try:
|
|
8942
|
+
V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
|
|
8943
|
+
st_g, eg = call(V, "GET", "/api/economics")
|
|
8944
|
+
st_vr, _ = call(V, "POST", "/api/economics/rate", {"well_id": wc["id"], "usd_per_day": 1000, "what": "rig", "source": "s"})
|
|
8945
|
+
st_or, _ = call(O, "POST", "/api/economics/rate", {"well_id": wc["id"], "usd_per_day": 25000, "what": "rig spread", "source": "contract"})
|
|
8946
|
+
st_bad, _ = call(O, "POST", "/api/economics/rate", {"well_id": wc["id"], "usd_per_day": "x", "what": "rig", "source": "s"})
|
|
8947
|
+
st_imp, imp2 = call(O, "POST", "/api/economics/import", {"name": "Midland WTI", "unit": "USD/bbl", "source": "test", "content_b64": _b64.b64encode(b"Date,Price\n2026-07-01,70\n2026-07-02,71\n").decode()})
|
|
8948
|
+
st_rm, _ = call(O, "POST", "/api/economics/remove", {"well_id": wc["id"], "kind": "rate", "index": 0})
|
|
8949
|
+
st_rm2, _ = call(O, "POST", "/api/economics/remove", {"well_id": wc["id"], "kind": "rate", "index": 0})
|
|
8950
|
+
st_ac, _ = call(O, "POST", "/api/economics/nope", {})
|
|
8951
|
+
finally:
|
|
8952
|
+
svc.stop()
|
|
8953
|
+
cli = _sp.run([sys.executable, "-m", "gea", "economics", "--workspace", wsp], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
8954
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
8955
|
+
help_md = (pkg / "help" / "economics.md").read_text(encoding="utf-8")
|
|
8956
|
+
c3 = {
|
|
8957
|
+
"api": st_g == 200 and len(eg["wells"]) == 2 and st_vr == 403 and st_or == 200 and st_bad == 400 and st_imp == 200 and imp2["id"] == "midland_wti"
|
|
8958
|
+
and st_rm == 200 and st_rm2 == 404 and st_ac == 404,
|
|
8959
|
+
"cli": cli.returncode == 0 and "USD/ft" in cli.stdout and "incomplete" not in cli.stdout.split("Well B")[1].split("Well C")[0] and "U.S. Energy Information Administration" in cli.stdout,
|
|
8960
|
+
"page": all(x in page_src for x in ("VIEWS.economics", "['#/economics', 'Economics']", "ecChart", "ec-hit", "ArrowLeft", "/api/economics/rate",
|
|
8961
|
+
"/api/economics/import", "/api/economics/refresh", "incomplete", "a gap of more than six days is not bridged")),
|
|
8962
|
+
"help": any(t["topic"] == "economics" for t in H.topics()) and not H.check("economics") and H.VIEW_TOPIC.get("economics") == "economics"
|
|
8963
|
+
and "fewer than ten wells" in help_md.split("will not call a measurement")[1],
|
|
8964
|
+
}
|
|
8965
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
8966
|
+
ok(not failed3,
|
|
8967
|
+
"CF3 the API (a viewer reads, only an operator declares; a bad number, an unknown action and a second removal of the same rate named), "
|
|
8968
|
+
"the CLI and the Economics tab: one chart per series on its own axis with the wells' drilling windows shaded and a crosshair by pointer or "
|
|
8969
|
+
"arrows, the wells' cost table, the cost-basis forms, refresh and import; the help page"
|
|
8970
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
8971
|
+
|
|
8972
|
+
|
|
8973
|
+
def section_cg_well_report(tmp: str) -> None:
|
|
8974
|
+
"""Section CG - the client well report: one document per well and one for the pad, assembled from the drilling record,
|
|
8975
|
+
the depth events and their check, the declared location and the mapped structure there, the survey's path, and the
|
|
8976
|
+
declared cost against the market; drawings made from the report's own numbers; each missing part named with what
|
|
8977
|
+
would supply it, never drawn as zero; the vocabulary guard; the month-end packet, the API, the CLI, the pages."""
|
|
8978
|
+
import http.cookiejar
|
|
8979
|
+
import math as _m
|
|
8980
|
+
import subprocess as _sp
|
|
8981
|
+
import urllib.request
|
|
8982
|
+
import urllib.error
|
|
8983
|
+
import xml.etree.ElementTree as _ET
|
|
8984
|
+
from . import client_reports as CR
|
|
8985
|
+
from . import economics as EC
|
|
8986
|
+
from . import edr as E
|
|
8987
|
+
from . import helplib as H
|
|
8988
|
+
from . import month_end as ME
|
|
8989
|
+
from . import setup_checklist as SC
|
|
8990
|
+
from . import shapefile as SF
|
|
8991
|
+
from . import well3d as W
|
|
8992
|
+
from . import well_report as WR
|
|
8993
|
+
from .workspace import Workspace
|
|
8994
|
+
from .service import Service, Users
|
|
8995
|
+
pkg = Path(__file__).parent
|
|
8996
|
+
d = Path(tmp, "cg"); d.mkdir()
|
|
8997
|
+
ev = E.selftest_events(str(d))
|
|
8998
|
+
wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
|
|
8999
|
+
wb = ws.add_well_file(ev["path"], display="Well B", actor="tester"); wc = ws.add_well_file(ev["path"], display="Well C", actor="tester")
|
|
9000
|
+
ws.set_well(wb["id"], surface={"lat": 31.42, "lon": -104.10, "datum": "NAD83", "elevation": {"ft": 3000.0, "datum": "KB", "source": "test plat"}},
|
|
9001
|
+
identity={"api_number": "42-389-12345"}, actor="tester")
|
|
9002
|
+
lat1, lon1 = 31.42, -104.10
|
|
9003
|
+
kmlon = 1.0 / (111.195 * _m.cos(_m.radians(lat1)))
|
|
9004
|
+
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)],
|
|
9005
|
+
[("ElevTop_ft", "F", 10, 1)], SF.NAD83_GEOGCS)
|
|
9006
|
+
ws.add_layer(str(d / "Top_A.shp"), "Top A", actor="tester", formation="A", role="top", citation="test")
|
|
9007
|
+
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"
|
|
9008
|
+
W.attach_trajectory(ws, wb["id"], "s.csv", csv_ok.encode(), "tester", "true", "ft", None, None, None, "test survey")
|
|
9009
|
+
ws.add_site("Pad 3", wells=[wb["id"], wc["id"]], client="Test Client", actor="tester")
|
|
9010
|
+
ws.refresh_dashboard(actor="tester")
|
|
9011
|
+
rec = json.loads(Path(ws.reports_dir, "wells", wb["id"], "drilling_record.json").read_text(encoding="utf-8"))
|
|
9012
|
+
EC.set_rate(ws, wb["id"], "tester", 30000, "rig spread", "contract")
|
|
9013
|
+
EC.add_item(ws, wb["id"], "tester", 18000, "bit", "invoice", 1)
|
|
9014
|
+
EC.set_afe(ws, wb["id"], "tester", 150000, "AFE 1")
|
|
9015
|
+
SC.confirm(ws, f"events:{wb['id']}", "tester", "daily drilling reports 1-5")
|
|
9016
|
+
r = WR.write_well(ws, wb["id"], "0.0-test", actor="tester")
|
|
9017
|
+
wdir = Path(ws.reports_dir, "wells", wb["id"])
|
|
9018
|
+
md = (wdir / "well_report.md").read_text(encoding="utf-8"); ht = (wdir / "well_report.html").read_text(encoding="utf-8")
|
|
9019
|
+
js = json.loads((wdir / "well_report.json").read_text(encoding="utf-8"))
|
|
9020
|
+
figs = sorted(Path(p).name for p in r["paths"].get("figures", []))
|
|
9021
|
+
parsed = 0
|
|
9022
|
+
for f in figs:
|
|
9023
|
+
try:
|
|
9024
|
+
_ET.fromstring((wdir / f).read_text(encoding="utf-8")); parsed += 1
|
|
9025
|
+
except _ET.ParseError:
|
|
9026
|
+
pass
|
|
9027
|
+
st = W.trajectory_of(ws, wb["id"])["stations"]
|
|
9028
|
+
p = W.Path([x["md"] for x in st], [x["incl"] for x in st], [x["azi"] for x in st], 0.0, 0.0, 0.0, 0.0)
|
|
9029
|
+
tvd_ft = float(p.tvd[-1]) / 0.3048
|
|
9030
|
+
fb = next(w for w in EC.economics(ws)["wells"] if w["well_id"] == wb["id"])
|
|
9031
|
+
c1 = {
|
|
9032
|
+
"complete": r["missing"] == [] and js["missing"] == [] and js["report_id"] == r["report_id"] and "42-389-12345" in md and "Test Client" in md
|
|
9033
|
+
and "KB 3,000.0 ft (test plat)" in md,
|
|
9034
|
+
"record": f"{rec['total_depth_md_ft']:,.1f} ft" in md and f"{rec['feet_drilled']:,.1f} ft" in md and rec["source"] in md
|
|
9035
|
+
and md.count("\n| STALE OPENING |") + md.count("\n| RE-REFERENCE |") + md.count("\n| SIDETRACK |") + md.count("\n| CORRECTION |") == len(rec["depth_events"])
|
|
9036
|
+
and "Checked by tester" in md and "daily drilling reports 1-5" in md,
|
|
9037
|
+
"figures": figs == sorted(["well_report_progress.svg", "well_report_plan.svg", "well_report_section.svg", "well_report_wti.svg", "well_report_henry_hub.svg"])
|
|
9038
|
+
and parsed == 5 and ht.count("<figure") == 5 and ht.count("<figcaption") == 5 and md.count("![") == 5 and "@media print" in ht and "break-inside:avoid" in ht,
|
|
9039
|
+
"path": f"{tvd_ft:,.1f}" in md and f"reaches {tvd_ft:,.0f} ft true vertical depth" in md and "6 stations, azimuths true" in md,
|
|
9040
|
+
"tops": "| A | 4,500 to 4,600 | -1,600 and -1,500 | Top A |" in md and "Between the -1,600 ft and -1,500 ft contours" in md,
|
|
9041
|
+
"cost": f"${fb['cost']['usd']:,.0f}" in md and f"${fb['cost']['per_ft']:,.2f}" in md and "over the whole record" in md and "$18,000.00 bit (section 1)" in md
|
|
9042
|
+
and f"{fb['market']['wti']['mean']:,.2f}" in md,
|
|
9043
|
+
"guarded": not CR.forbidden_terms(md) and not CR.forbidden_terms(ht),
|
|
9044
|
+
}
|
|
9045
|
+
failed1 = [k for k, v in c1.items() if not v]
|
|
9046
|
+
ok(not failed1,
|
|
9047
|
+
"CG1 a well's report: the identity, client and declared reference with their sources; the drilling record's own total depth, footage "
|
|
9048
|
+
"and every depth event with the person's check; depth against days, the plan and the vertical section from the survey (TVD to the "
|
|
9049
|
+
"minimum-curvature value), the mapped top at the location as a depth band from the two contours it lies between below the declared "
|
|
9050
|
+
"reference, the declared cost to the dollar of the Economics tab and the market while drilling - five drawings, each valid SVG with a "
|
|
9051
|
+
"caption and words for a reader who cannot see it, none split across a printed page"
|
|
9052
|
+
+ (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
|
|
9053
|
+
|
|
9054
|
+
rc = WR.write_well(ws, wc["id"], "0.0-test", actor="tester")
|
|
9055
|
+
cdir = Path(ws.reports_dir, "wells", wc["id"])
|
|
9056
|
+
mdc = (cdir / "well_report.md").read_text(encoding="utf-8")
|
|
9057
|
+
EC.add_item(ws, wb["id"], "tester", 1, "lattice rental", "test") # a declared word the client register does not carry
|
|
9058
|
+
guarded = False
|
|
9059
|
+
try:
|
|
9060
|
+
WR.write_well(ws, wb["id"], "0.0-test", actor="tester")
|
|
9061
|
+
except ValueError as e:
|
|
9062
|
+
guarded = "lattice" in str(e)
|
|
9063
|
+
EC.remove(ws, wb["id"], "tester", "item", 1)
|
|
9064
|
+
rp = WR.write_pad(ws, "0.0-test", actor="tester")
|
|
9065
|
+
pmd = Path(ws.reports_dir, "pad_report.md").read_text(encoding="utf-8")
|
|
9066
|
+
psvg = Path(ws.reports_dir, "pad_report_progress.svg").read_text(encoding="utf-8")
|
|
9067
|
+
pk = ME.assemble(ws, "2026-09", actor="tester", refresh=False)
|
|
9068
|
+
idx = json.loads((Path(pk["folder"]) / "INDEX.json").read_text(encoding="utf-8"))
|
|
9069
|
+
by_name = {i["name"]: i for i in idx["items"]}
|
|
9070
|
+
c2 = {
|
|
9071
|
+
"missing_named": rc["missing"] == ["a person's check of the depth events", "the surface location", "the directional survey", "the cost basis (day rates)"]
|
|
9072
|
+
and "not declared" in mdc and "No directional survey is attached" in mdc and "the well is not placed" in mdc
|
|
9073
|
+
and "Cost (declared)" not in mdc and "Not yet checked by a person" in mdc and "what would supply it" in mdc
|
|
9074
|
+
and sorted(Path(x).name for x in rc["paths"]["figures"]) == ["well_report_henry_hub.svg", "well_report_progress.svg", "well_report_wti.svg"],
|
|
9075
|
+
"guard": guarded and not Path(ws.reports_dir, "wells", wb["id"], "well_report.html").read_text(encoding="utf-8").lower().count("lattice"),
|
|
9076
|
+
"pad": "| Well B | 2026-07-01 |" in pmd and "| Well C | 2026-07-01 |" in pmd and psvg.count("<path ") == 2 and "| Well B | nothing |" in pmd
|
|
9077
|
+
and "| Well C | a person's check of the depth events; the surface location; the directional survey; the cost basis (day rates) |" in pmd,
|
|
9078
|
+
"month_end": by_name[f"Well B: client well report"]["status"] == "INCLUDED" and "well_report_section.svg" in [f["file"] for f in by_name["Well B: client well report"]["files"]]
|
|
9079
|
+
and by_name["Pad report"]["status"] == "INCLUDED" and pk["errors"] == [],
|
|
9080
|
+
"audit": [a["action"] for a in ws.audit_log(500)].count("report.well") >= 3 and "report.pad" in [a["action"] for a in ws.audit_log(500)],
|
|
9081
|
+
}
|
|
9082
|
+
failed2 = [k for k, v in c2.items() if not v]
|
|
9083
|
+
ok(not failed2,
|
|
9084
|
+
"CG2 what a report cannot say: a well with no check of its depth events, no surface location, no survey and no cost basis gets its "
|
|
9085
|
+
"record, its progress and the market, and each missing part named with what would supply it - no path, no cost, nothing drawn as "
|
|
9086
|
+
"zero; a declared word the client register does not carry stops the report before it is written; the pad report puts every well side "
|
|
9087
|
+
"by side on one chart with what each still lacks; the month-end packet writes and includes them with their drawings; every run audited"
|
|
9088
|
+
+ (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
|
|
9089
|
+
|
|
9090
|
+
Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
|
|
9091
|
+
svc = Service(wsp, port=0, scheduler=False).start()
|
|
9092
|
+
base_url = svc.url.rstrip("/")
|
|
9093
|
+
|
|
9094
|
+
def opener(name, pw):
|
|
9095
|
+
o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
|
9096
|
+
rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw}).encode())
|
|
9097
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
|
|
9098
|
+
return o
|
|
9099
|
+
|
|
9100
|
+
def call(o, method, path, body=None):
|
|
9101
|
+
rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
|
|
9102
|
+
if method == "POST":
|
|
9103
|
+
rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
|
|
9104
|
+
try:
|
|
9105
|
+
with o.open(rq, timeout=120) as rr_:
|
|
9106
|
+
ct = rr_.headers.get("Content-Type", "")
|
|
9107
|
+
raw = rr_.read()
|
|
9108
|
+
return rr_.status, (json.loads(raw) if "json" in ct else raw.decode("utf-8", "replace"))
|
|
9109
|
+
except urllib.error.HTTPError as e:
|
|
9110
|
+
return e.code, json.loads(e.read())
|
|
9111
|
+
try:
|
|
9112
|
+
V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
|
|
9113
|
+
st_v, _ = call(V, "POST", "/api/reports/well-report", {"well_id": wb["id"]})
|
|
9114
|
+
st_o, jo = call(O, "POST", "/api/reports/well-report", {"well_id": wb["id"]})
|
|
9115
|
+
st_p, jp = call(O, "POST", "/api/reports/well-report", {"pad": True})
|
|
9116
|
+
st_n, _ = call(O, "POST", "/api/reports/well-report", {"well_id": "nope"})
|
|
9117
|
+
st_h, page = call(V, "GET", jo.get("url", "/x") if isinstance(jo, dict) else "/x")
|
|
9118
|
+
st_r, ri = call(V, "GET", "/api/reports")
|
|
9119
|
+
finally:
|
|
9120
|
+
svc.stop()
|
|
9121
|
+
cli = _sp.run([sys.executable, "-m", "gea", "well-report", "--workspace", wsp], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
9122
|
+
cli_bad = _sp.run([sys.executable, "-m", "gea", "well-report", "--workspace", wsp, "--well", "nope"], capture_output=True, text=True, cwd=str(pkg.parent))
|
|
9123
|
+
page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
|
|
9124
|
+
c3 = {
|
|
9125
|
+
"api": st_v == 403 and st_o == 200 and jo["missing"] == [] and jo["url"] == f"/reports/wells/{wb['id']}/well_report.html" and st_p == 200
|
|
9126
|
+
and jp["url"] == "/reports/pad_report.html" and st_n == 404 and st_h == 200 and "<figure" in page and st_r == 200 and "pad_report.html" in ri["site"]
|
|
9127
|
+
and "well_report_plan.svg" in ri["wells"][wb["id"]],
|
|
9128
|
+
"cli": cli.returncode == 0 and f"{wb['id']}: " in cli.stdout and "pad: " in cli.stdout and "not in it yet" in cli.stdout.split(f"{wc['id']}: ")[1]
|
|
9129
|
+
and cli_bad.returncode != 0 and "no well nope" in cli_bad.stderr,
|
|
9130
|
+
"page": all(x in page_src for x in ("/api/reports/well-report", "wrRun", "data-wr", "Client well report", "Write the pad report", "Not in it yet")),
|
|
9131
|
+
"help": any(t[0] == "well-report" for t in H.INDEX) and not H.check("well-report"),
|
|
9132
|
+
}
|
|
9133
|
+
failed3 = [k for k, v in c3.items() if not v]
|
|
9134
|
+
ok(not failed3,
|
|
9135
|
+
"CG3 the API (a viewer reads the report, only an operator writes it; an unknown well named), the CLI (every well and the pad, or one, "
|
|
9136
|
+
"or the pad alone), the buttons on the well page and the Reports page, and the help page"
|
|
9137
|
+
+ (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
|
|
9138
|
+
|
|
9139
|
+
|
|
8803
9140
|
def main() -> int:
|
|
8804
9141
|
print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
|
|
8805
9142
|
# a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
|
|
@@ -8886,6 +9223,8 @@ def main() -> int:
|
|
|
8886
9223
|
section_cc_site_map(tmp)
|
|
8887
9224
|
section_cd_control_room(tmp)
|
|
8888
9225
|
section_ce_well3d(tmp)
|
|
9226
|
+
section_cf_economics(tmp)
|
|
9227
|
+
section_cg_well_report(tmp)
|
|
8889
9228
|
if _FAILS:
|
|
8890
9229
|
print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
|
|
8891
9230
|
for f in _FAILS:
|
|
@@ -90,12 +90,23 @@ class Table:
|
|
|
90
90
|
caption: str = ''
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
@dataclass
|
|
94
|
+
class Figure:
|
|
95
|
+
"""A drawing the report carries: SVG made by the program from the numbers in the report's own tables, with a caption
|
|
96
|
+
and the words a reader who cannot see it gets instead."""
|
|
97
|
+
name: str # the file stem it is written under, beside the report
|
|
98
|
+
svg: str
|
|
99
|
+
caption: str
|
|
100
|
+
alt: str
|
|
101
|
+
|
|
102
|
+
|
|
93
103
|
@dataclass
|
|
94
104
|
class Section:
|
|
95
105
|
number: str
|
|
96
106
|
title: str
|
|
97
107
|
paragraphs: List[str] = field(default_factory=list)
|
|
98
108
|
tables: List[Table] = field(default_factory=list)
|
|
109
|
+
figures: List[Figure] = field(default_factory=list)
|
|
99
110
|
|
|
100
111
|
|
|
101
112
|
@dataclass
|
|
@@ -910,6 +921,11 @@ def render_markdown(doc: Document) -> str:
|
|
|
910
921
|
for p in s.paragraphs:
|
|
911
922
|
L.append(p)
|
|
912
923
|
L.append('')
|
|
924
|
+
for fg in s.figures:
|
|
925
|
+
L.append(f'')
|
|
926
|
+
L.append('')
|
|
927
|
+
L.append(f'*{fg.caption}*')
|
|
928
|
+
L.append('')
|
|
913
929
|
for t in s.tables:
|
|
914
930
|
if t.caption:
|
|
915
931
|
L.append(f'*{t.caption}*')
|
|
@@ -932,7 +948,9 @@ _CSS = ('body{font-family:Segoe UI,Arial,sans-serif;margin:2rem auto;max-width:1
|
|
|
932
948
|
'font-size:.85rem;width:100%}th,td{border:1px solid #d0d0d0;padding:.3rem .5rem;text-align:left;'
|
|
933
949
|
'vertical-align:top}th{background:#f2f2f2}caption{text-align:left;font-style:italic;padding:.2rem 0}'
|
|
934
950
|
'.front td:first-child{font-weight:600;width:14rem}.result-bad{color:#a40000;font-weight:700}'
|
|
935
|
-
'.result-ok{color:#0a6b1f;font-weight:700}footer{margin-top:2rem;font-size:.8rem;color:#555}'
|
|
951
|
+
'.result-ok{color:#0a6b1f;font-weight:700}footer{margin-top:2rem;font-size:.8rem;color:#555}'
|
|
952
|
+
'figure{margin:1rem 0}figure svg{max-width:100%;height:auto;display:block}figcaption{font-size:.8rem;font-style:italic;color:#444}'
|
|
953
|
+
'@media print{body{margin:0;max-width:none}h2{break-after:avoid}table,figure{break-inside:avoid}}')
|
|
936
954
|
|
|
937
955
|
|
|
938
956
|
def render_html(doc: Document) -> str:
|
|
@@ -950,6 +968,8 @@ def render_html(doc: Document) -> str:
|
|
|
950
968
|
H.append(f'<h2>{e(s.number)}. {e(s.title)}</h2>')
|
|
951
969
|
for p in s.paragraphs:
|
|
952
970
|
H.append(f'<p>{e(p)}</p>')
|
|
971
|
+
for fg in s.figures:
|
|
972
|
+
H.append(f'<figure role="img" aria-label="{e(fg.alt)}">{fg.svg}<figcaption>{e(fg.caption)}</figcaption></figure>')
|
|
953
973
|
for t in s.tables:
|
|
954
974
|
H.append('<table>')
|
|
955
975
|
if t.caption:
|
|
@@ -1888,6 +1908,12 @@ def write(doc: Document, out_dir: str, basename: str = 'gauge_drift_report') ->
|
|
|
1888
1908
|
f.write(md)
|
|
1889
1909
|
with open(paths['html'], 'w', encoding='utf-8', newline='') as f:
|
|
1890
1910
|
f.write(ht)
|
|
1911
|
+
for s in doc.sections:
|
|
1912
|
+
for fg in s.figures:
|
|
1913
|
+
fp = os.path.join(out_dir, fg.name + '.svg')
|
|
1914
|
+
with open(fp, 'w', encoding='utf-8', newline='') as f:
|
|
1915
|
+
f.write(fg.svg)
|
|
1916
|
+
paths.setdefault('figures', []).append(fp)
|
|
1891
1917
|
machine = {k: v for k, v in doc.data.items() if not k.startswith('_')}
|
|
1892
1918
|
with open(paths['json'], 'w', encoding='utf-8') as f:
|
|
1893
1919
|
json.dump(machine, f, indent=1, default=str)
|