gea-program 0.50.0__tar.gz → 0.52.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.
Files changed (358) hide show
  1. {gea_program-0.50.0 → gea_program-0.52.0}/PKG-INFO +30 -1
  2. {gea_program-0.50.0 → gea_program-0.52.0}/README.md +29 -0
  3. {gea_program-0.50.0 → gea_program-0.52.0}/gea/__init__.py +1 -1
  4. {gea_program-0.50.0 → gea_program-0.52.0}/gea/__main__.py +106 -0
  5. {gea_program-0.50.0 → gea_program-0.52.0}/gea/acceptance_tests.py +370 -0
  6. {gea_program-0.50.0 → gea_program-0.52.0}/gea/alerts.py +20 -2
  7. {gea_program-0.50.0 → gea_program-0.52.0}/gea/collision.py +1 -1
  8. {gea_program-0.50.0 → gea_program-0.52.0}/gea/dossier.py +28 -4
  9. gea_program-0.52.0/gea/frac_hits.py +518 -0
  10. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/alerts.md +1 -1
  11. gea_program-0.52.0/gea/help/frac-hits.md +17 -0
  12. gea_program-0.52.0/gea/help/pore-pressure.md +17 -0
  13. {gea_program-0.50.0 → gea_program-0.52.0}/gea/helplib.py +3 -1
  14. {gea_program-0.50.0 → gea_program-0.52.0}/gea/notify.py +3 -1
  15. gea_program-0.52.0/gea/pore_pressure.py +682 -0
  16. gea_program-0.52.0/gea/reference/l0701_sonic_gr.las.gz +0 -0
  17. gea_program-0.52.0/gea/reference/l0701_sonic_gr.provenance.json +14 -0
  18. {gea_program-0.50.0 → gea_program-0.52.0}/gea/service.py +41 -0
  19. {gea_program-0.50.0 → gea_program-0.52.0}/gea/web/app.html +130 -2
  20. {gea_program-0.50.0 → gea_program-0.52.0}/gea_program.egg-info/PKG-INFO +30 -1
  21. {gea_program-0.50.0 → gea_program-0.52.0}/gea_program.egg-info/SOURCES.txt +6 -0
  22. {gea_program-0.50.0 → gea_program-0.52.0}/pyproject.toml +1 -1
  23. {gea_program-0.50.0 → gea_program-0.52.0}/LICENSE +0 -0
  24. {gea_program-0.50.0 → gea_program-0.52.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  25. {gea_program-0.50.0 → gea_program-0.52.0}/gea/accuracy_statement.py +0 -0
  26. {gea_program-0.50.0 → gea_program-0.52.0}/gea/alarm_engine.py +0 -0
  27. {gea_program-0.50.0 → gea_program-0.52.0}/gea/backup.py +0 -0
  28. {gea_program-0.50.0 → gea_program-0.52.0}/gea/bench.py +0 -0
  29. {gea_program-0.50.0 → gea_program-0.52.0}/gea/blind_harness.py +0 -0
  30. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  31. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  32. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  33. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  34. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  35. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  36. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  37. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  38. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  39. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  40. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  41. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  42. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  43. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  44. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  45. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  46. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  47. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  48. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  49. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  50. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  51. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  52. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  53. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  54. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  55. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  56. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  57. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  58. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  59. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  60. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  61. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  62. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  63. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  64. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  65. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  66. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  67. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  68. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  69. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  70. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  71. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  72. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  73. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  74. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  75. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  76. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  77. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  78. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  79. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  80. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  81. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  82. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  83. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  84. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  85. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  86. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  87. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  88. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  89. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  90. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  91. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  92. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  93. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  94. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  95. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  96. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  97. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/med_160_sapropels.txt +0 -0
  98. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  99. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  100. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  101. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  102. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  103. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  104. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  105. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  106. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  107. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  108. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  109. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  110. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  111. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  112. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  113. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  114. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  115. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  116. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  117. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  118. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  119. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  120. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  121. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  122. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  123. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  124. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  125. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  126. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  127. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  128. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  129. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  130. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  131. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  132. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  133. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  134. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog_map.py +0 -0
  135. {gea_program-0.50.0 → gea_program-0.52.0}/gea/catalog_register.py +0 -0
  136. {gea_program-0.50.0 → gea_program-0.52.0}/gea/certificates.py +0 -0
  137. {gea_program-0.50.0 → gea_program-0.52.0}/gea/cli.py +0 -0
  138. {gea_program-0.50.0 → gea_program-0.52.0}/gea/client_reports.py +0 -0
  139. {gea_program-0.50.0 → gea_program-0.52.0}/gea/config_versioning.py +0 -0
  140. {gea_program-0.50.0 → gea_program-0.52.0}/gea/conformance.py +0 -0
  141. {gea_program-0.50.0 → gea_program-0.52.0}/gea/control_room.py +0 -0
  142. {gea_program-0.50.0 → gea_program-0.52.0}/gea/correlation.py +0 -0
  143. {gea_program-0.50.0 → gea_program-0.52.0}/gea/dashboard.py +0 -0
  144. {gea_program-0.50.0 → gea_program-0.52.0}/gea/declarations.py +0 -0
  145. {gea_program-0.50.0 → gea_program-0.52.0}/gea/deviation.py +0 -0
  146. {gea_program-0.50.0 → gea_program-0.52.0}/gea/dlis.py +0 -0
  147. {gea_program-0.50.0 → gea_program-0.52.0}/gea/dnp3.py +0 -0
  148. {gea_program-0.50.0 → gea_program-0.52.0}/gea/doctor.py +0 -0
  149. {gea_program-0.50.0 → gea_program-0.52.0}/gea/dossier_verify.py +0 -0
  150. {gea_program-0.50.0 → gea_program-0.52.0}/gea/downhole_engine.py +0 -0
  151. {gea_program-0.50.0 → gea_program-0.52.0}/gea/drift_monitor.py +0 -0
  152. {gea_program-0.50.0 → gea_program-0.52.0}/gea/drill_string.py +0 -0
  153. {gea_program-0.50.0 → gea_program-0.52.0}/gea/earth_model.py +0 -0
  154. {gea_program-0.50.0 → gea_program-0.52.0}/gea/economics.py +0 -0
  155. {gea_program-0.50.0 → gea_program-0.52.0}/gea/ed25519.py +0 -0
  156. {gea_program-0.50.0 → gea_program-0.52.0}/gea/edr.py +0 -0
  157. {gea_program-0.50.0 → gea_program-0.52.0}/gea/elevation.py +0 -0
  158. {gea_program-0.50.0 → gea_program-0.52.0}/gea/etp.py +0 -0
  159. {gea_program-0.50.0 → gea_program-0.52.0}/gea/etp12_schemas.json +0 -0
  160. {gea_program-0.50.0 → gea_program-0.52.0}/gea/example_register_map.json +0 -0
  161. {gea_program-0.50.0 → gea_program-0.52.0}/gea/fat_sat.py +0 -0
  162. {gea_program-0.50.0 → gea_program-0.52.0}/gea/files.py +0 -0
  163. {gea_program-0.50.0 → gea_program-0.52.0}/gea/fixtures/README.md +0 -0
  164. {gea_program-0.50.0 → gea_program-0.52.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  165. {gea_program-0.50.0 → gea_program-0.52.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  166. {gea_program-0.50.0 → gea_program-0.52.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  167. {gea_program-0.50.0 → gea_program-0.52.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  168. {gea_program-0.50.0 → gea_program-0.52.0}/gea/follower.py +0 -0
  169. {gea_program-0.50.0 → gea_program-0.52.0}/gea/forward_model.py +0 -0
  170. {gea_program-0.50.0 → gea_program-0.52.0}/gea/friction_priors.py +0 -0
  171. {gea_program-0.50.0 → gea_program-0.52.0}/gea/gamma.py +0 -0
  172. {gea_program-0.50.0 → gea_program-0.52.0}/gea/gauge_aging.py +0 -0
  173. {gea_program-0.50.0 → gea_program-0.52.0}/gea/gauge_specs.py +0 -0
  174. {gea_program-0.50.0 → gea_program-0.52.0}/gea/geodesy.py +0 -0
  175. {gea_program-0.50.0 → gea_program-0.52.0}/gea/geomag.py +0 -0
  176. {gea_program-0.50.0 → gea_program-0.52.0}/gea/gravity_reference.py +0 -0
  177. {gea_program-0.50.0 → gea_program-0.52.0}/gea/hartip.py +0 -0
  178. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/TESTER_GUIDE.md +0 -0
  179. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/alarms.md +0 -0
  180. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/audit-update.md +0 -0
  181. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/backup.md +0 -0
  182. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/catalog.md +0 -0
  183. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/charts.md +0 -0
  184. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/collision.md +0 -0
  185. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/conformance.md +0 -0
  186. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/control-room.md +0 -0
  187. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/data-in.md +0 -0
  188. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/declare.md +0 -0
  189. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/dlis.md +0 -0
  190. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/doctor.md +0 -0
  191. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/dossier.md +0 -0
  192. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/drift.md +0 -0
  193. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/drillstring.md +0 -0
  194. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/economics.md +0 -0
  195. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/edr.md +0 -0
  196. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/etp.md +0 -0
  197. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/field-ports.md +0 -0
  198. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/files.md +0 -0
  199. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/historian.md +0 -0
  200. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/instruments.md +0 -0
  201. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/interval.md +0 -0
  202. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/invoice-check.md +0 -0
  203. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/map.md +0 -0
  204. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/month-end.md +0 -0
  205. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/notifications.md +0 -0
  206. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/opseis.md +0 -0
  207. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/osdu.md +0 -0
  208. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/pad-summary.md +0 -0
  209. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/patches.md +0 -0
  210. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/penetrator.md +0 -0
  211. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/plan.md +0 -0
  212. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/ppdm.md +0 -0
  213. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/quakeml.md +0 -0
  214. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/quality.md +0 -0
  215. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/report-history.md +0 -0
  216. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/rig-time.md +0 -0
  217. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/seedlink.md +0 -0
  218. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/seismic.md +0 -0
  219. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/setup.md +0 -0
  220. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/simulation.md +0 -0
  221. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/site.md +0 -0
  222. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/sites.md +0 -0
  223. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/slip.md +0 -0
  224. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/soak.md +0 -0
  225. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/sra.md +0 -0
  226. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/start.md +0 -0
  227. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/structure.md +0 -0
  228. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/tools.md +0 -0
  229. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/transients.md +0 -0
  230. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/upkeep.md +0 -0
  231. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/vibration.md +0 -0
  232. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/well-report.md +0 -0
  233. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/well-tests.md +0 -0
  234. {gea_program-0.50.0 → gea_program-0.52.0}/gea/help/well3d.md +0 -0
  235. {gea_program-0.50.0 → gea_program-0.52.0}/gea/housekeeping.py +0 -0
  236. {gea_program-0.50.0 → gea_program-0.52.0}/gea/interval.py +0 -0
  237. {gea_program-0.50.0 → gea_program-0.52.0}/gea/inverse_engine.py +0 -0
  238. {gea_program-0.50.0 → gea_program-0.52.0}/gea/invoice_check.py +0 -0
  239. {gea_program-0.50.0 → gea_program-0.52.0}/gea/jobs.py +0 -0
  240. {gea_program-0.50.0 → gea_program-0.52.0}/gea/live_ports.py +0 -0
  241. {gea_program-0.50.0 → gea_program-0.52.0}/gea/loadtest.py +0 -0
  242. {gea_program-0.50.0 → gea_program-0.52.0}/gea/matplotlib_demo.py +0 -0
  243. {gea_program-0.50.0 → gea_program-0.52.0}/gea/modbus.py +0 -0
  244. {gea_program-0.50.0 → gea_program-0.52.0}/gea/modbus_rtu.py +0 -0
  245. {gea_program-0.50.0 → gea_program-0.52.0}/gea/model_card.py +0 -0
  246. {gea_program-0.50.0 → gea_program-0.52.0}/gea/month_end.py +0 -0
  247. {gea_program-0.50.0 → gea_program-0.52.0}/gea/mqtt_port.py +0 -0
  248. {gea_program-0.50.0 → gea_program-0.52.0}/gea/op_seismicity.py +0 -0
  249. {gea_program-0.50.0 → gea_program-0.52.0}/gea/opcua_port.py +0 -0
  250. {gea_program-0.50.0 → gea_program-0.52.0}/gea/operator_app.py +0 -0
  251. {gea_program-0.50.0 → gea_program-0.52.0}/gea/osdu.py +0 -0
  252. {gea_program-0.50.0 → gea_program-0.52.0}/gea/pad_summary.py +0 -0
  253. {gea_program-0.50.0 → gea_program-0.52.0}/gea/patches.py +0 -0
  254. {gea_program-0.50.0 → gea_program-0.52.0}/gea/pdf_report.py +0 -0
  255. {gea_program-0.50.0 → gea_program-0.52.0}/gea/penetrator.py +0 -0
  256. {gea_program-0.50.0 → gea_program-0.52.0}/gea/permits.py +0 -0
  257. {gea_program-0.50.0 → gea_program-0.52.0}/gea/piwebapi.py +0 -0
  258. {gea_program-0.50.0 → gea_program-0.52.0}/gea/ports.py +0 -0
  259. {gea_program-0.50.0 → gea_program-0.52.0}/gea/ppdm.py +0 -0
  260. {gea_program-0.50.0 → gea_program-0.52.0}/gea/profile_catalog.py +0 -0
  261. {gea_program-0.50.0 → gea_program-0.52.0}/gea/project.py +0 -0
  262. {gea_program-0.50.0 → gea_program-0.52.0}/gea/qt6_downhole_app.py +0 -0
  263. {gea_program-0.50.0 → gea_program-0.52.0}/gea/quakeml.py +0 -0
  264. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reconciler.py +0 -0
  265. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  266. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  267. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/WMM2025.COF +0 -0
  268. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  269. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/eia_prices.provenance.json +0 -0
  270. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  271. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/iscwsa_rev4_example.json +0 -0
  272. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/iscwsa_rev5_example.json +0 -0
  273. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  274. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  275. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  276. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  277. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/mahoning_example.json +0 -0
  278. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/mud_motor_specs.json +0 -0
  279. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/owsg_tool_models.json +0 -0
  280. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/penetrator_ratings.json +0 -0
  281. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/seismicity_protocols.json +0 -0
  282. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/tool_sources.json +0 -0
  283. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/us_outline.json +0 -0
  284. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/us_outline.provenance.json +0 -0
  285. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_3d.provenance.json +0 -0
  286. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  287. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  288. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  289. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_surfaces.json.gz +0 -0
  290. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  291. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  292. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  293. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  294. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  295. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  296. {gea_program-0.50.0 → gea_program-0.52.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  297. {gea_program-0.50.0 → gea_program-0.52.0}/gea/report_history.py +0 -0
  298. {gea_program-0.50.0 → gea_program-0.52.0}/gea/rig_state.py +0 -0
  299. {gea_program-0.50.0 → gea_program-0.52.0}/gea/rock_inventory.py +0 -0
  300. {gea_program-0.50.0 → gea_program-0.52.0}/gea/sample_record.py +0 -0
  301. {gea_program-0.50.0 → gea_program-0.52.0}/gea/sample_well_profile.csv +0 -0
  302. {gea_program-0.50.0 → gea_program-0.52.0}/gea/sbom.py +0 -0
  303. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seedlink.py +0 -0
  304. {gea_program-0.50.0 → gea_program-0.52.0}/gea/segy.py +0 -0
  305. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic.py +0 -0
  306. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_array.py +0 -0
  307. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_assoc.py +0 -0
  308. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_detect.py +0 -0
  309. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_film.py +0 -0
  310. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_harmonic.py +0 -0
  311. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_qc.py +0 -0
  312. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_response.py +0 -0
  313. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_signature.py +0 -0
  314. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_track.py +0 -0
  315. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_uncertainty.py +0 -0
  316. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_unlisted.py +0 -0
  317. {gea_program-0.50.0 → gea_program-0.52.0}/gea/seismic_watch.py +0 -0
  318. {gea_program-0.50.0 → gea_program-0.52.0}/gea/sensor_swap.py +0 -0
  319. {gea_program-0.50.0 → gea_program-0.52.0}/gea/series_cache.py +0 -0
  320. {gea_program-0.50.0 → gea_program-0.52.0}/gea/service_life.py +0 -0
  321. {gea_program-0.50.0 → gea_program-0.52.0}/gea/setup_checklist.py +0 -0
  322. {gea_program-0.50.0 → gea_program-0.52.0}/gea/shapefile.py +0 -0
  323. {gea_program-0.50.0 → gea_program-0.52.0}/gea/shell.py +0 -0
  324. {gea_program-0.50.0 → gea_program-0.52.0}/gea/shut_in.py +0 -0
  325. {gea_program-0.50.0 → gea_program-0.52.0}/gea/simulation.py +0 -0
  326. {gea_program-0.50.0 → gea_program-0.52.0}/gea/site_map.py +0 -0
  327. {gea_program-0.50.0 → gea_program-0.52.0}/gea/sla_report.py +0 -0
  328. {gea_program-0.50.0 → gea_program-0.52.0}/gea/slip.py +0 -0
  329. {gea_program-0.50.0 → gea_program-0.52.0}/gea/soak.py +0 -0
  330. {gea_program-0.50.0 → gea_program-0.52.0}/gea/sra.py +0 -0
  331. {gea_program-0.50.0 → gea_program-0.52.0}/gea/store_forward.py +0 -0
  332. {gea_program-0.50.0 → gea_program-0.52.0}/gea/strata_join.py +0 -0
  333. {gea_program-0.50.0 → gea_program-0.52.0}/gea/structure.py +0 -0
  334. {gea_program-0.50.0 → gea_program-0.52.0}/gea/supervisor.py +0 -0
  335. {gea_program-0.50.0 → gea_program-0.52.0}/gea/survey_cmd.py +0 -0
  336. {gea_program-0.50.0 → gea_program-0.52.0}/gea/survey_view.py +0 -0
  337. {gea_program-0.50.0 → gea_program-0.52.0}/gea/telemetry.py +0 -0
  338. {gea_program-0.50.0 → gea_program-0.52.0}/gea/tool_catalog.py +0 -0
  339. {gea_program-0.50.0 → gea_program-0.52.0}/gea/tool_library.py +0 -0
  340. {gea_program-0.50.0 → gea_program-0.52.0}/gea/tool_taxonomy.py +0 -0
  341. {gea_program-0.50.0 → gea_program-0.52.0}/gea/transient.py +0 -0
  342. {gea_program-0.50.0 → gea_program-0.52.0}/gea/vibration.py +0 -0
  343. {gea_program-0.50.0 → gea_program-0.52.0}/gea/well3d.py +0 -0
  344. {gea_program-0.50.0 → gea_program-0.52.0}/gea/well_assembler.py +0 -0
  345. {gea_program-0.50.0 → gea_program-0.52.0}/gea/well_report.py +0 -0
  346. {gea_program-0.50.0 → gea_program-0.52.0}/gea/well_test_validation.py +0 -0
  347. {gea_program-0.50.0 → gea_program-0.52.0}/gea/wellplan.py +0 -0
  348. {gea_program-0.50.0 → gea_program-0.52.0}/gea/wellpos.py +0 -0
  349. {gea_program-0.50.0 → gea_program-0.52.0}/gea/wits0.py +0 -0
  350. {gea_program-0.50.0 → gea_program-0.52.0}/gea/witsml.py +0 -0
  351. {gea_program-0.50.0 → gea_program-0.52.0}/gea/workspace.py +0 -0
  352. {gea_program-0.50.0 → gea_program-0.52.0}/gea/xlsx_read.py +0 -0
  353. {gea_program-0.50.0 → gea_program-0.52.0}/gea_program.egg-info/dependency_links.txt +0 -0
  354. {gea_program-0.50.0 → gea_program-0.52.0}/gea_program.egg-info/entry_points.txt +0 -0
  355. {gea_program-0.50.0 → gea_program-0.52.0}/gea_program.egg-info/requires.txt +0 -0
  356. {gea_program-0.50.0 → gea_program-0.52.0}/gea_program.egg-info/top_level.txt +0 -0
  357. {gea_program-0.50.0 → gea_program-0.52.0}/setup.cfg +0 -0
  358. {gea_program-0.50.0 → gea_program-0.52.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.50.0
3
+ Version: 0.52.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,35 @@ 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
+ ### Pore pressure
652
+
653
+ The mud-weight window from a well's logs or its drilling record. The
654
+ overburden from the density log (or Gardner's density from the sonic); the
655
+ shale's normal compaction trend over an interval the operator declares
656
+ normally pressured; the pore pressure by Eaton's method from the sonic, the
657
+ deep resistivity or the corrected d-exponent computed from the drilling
658
+ record; the fracture gradient by Eaton's relation and the Hubbert-Willis
659
+ minimum - all as equivalent mud weights against true vertical depth, with
660
+ formation tests, kicks and leak-off tests beside them, the Eaton exponent
661
+ that fits the tests, and the mud weight used flagged where it falls outside
662
+ the window. The Dutch offshore well L07-01 (NLOG open data) is the worked
663
+ example. The **Pore pressure** tab, `gea pore-pressure`,
664
+ `gea help pore-pressure`.
665
+
666
+ ### Frac hits
667
+
668
+ What each fracturing stage did to the pressure in the wells around it. A
669
+ treatment well's stage table (from the frac company's post-job report, CSV
670
+ or Excel) is read against every well with a pressure gauge in its record:
671
+ the pressure's departure from its own trend while the stage pumps and after
672
+ it stops, the first response and the fluid pumped by then, and the type -
673
+ no response, poroelastic (the rock squeezed, falling at the stop), mixed
674
+ (a hydraulic connection, holding or rising after it) or a frac hit (the
675
+ stage's fluid reaching the well). With surveys, the distance from each
676
+ stage to the monitored well; with an earthquake catalogue, the earthquakes
677
+ during each stage. A frac hit is an alert. The **Frac hits** tab,
678
+ `gea frac-hits`, `gea help frac-hits`.
679
+
651
680
  ### Plan vs actual and the survey tools
652
681
 
653
682
  Each well against its directional plan: at every survey station, how far
@@ -609,6 +609,35 @@ 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
+ ### Pore pressure
613
+
614
+ The mud-weight window from a well's logs or its drilling record. The
615
+ overburden from the density log (or Gardner's density from the sonic); the
616
+ shale's normal compaction trend over an interval the operator declares
617
+ normally pressured; the pore pressure by Eaton's method from the sonic, the
618
+ deep resistivity or the corrected d-exponent computed from the drilling
619
+ record; the fracture gradient by Eaton's relation and the Hubbert-Willis
620
+ minimum - all as equivalent mud weights against true vertical depth, with
621
+ formation tests, kicks and leak-off tests beside them, the Eaton exponent
622
+ that fits the tests, and the mud weight used flagged where it falls outside
623
+ the window. The Dutch offshore well L07-01 (NLOG open data) is the worked
624
+ example. The **Pore pressure** tab, `gea pore-pressure`,
625
+ `gea help pore-pressure`.
626
+
627
+ ### Frac hits
628
+
629
+ What each fracturing stage did to the pressure in the wells around it. A
630
+ treatment well's stage table (from the frac company's post-job report, CSV
631
+ or Excel) is read against every well with a pressure gauge in its record:
632
+ the pressure's departure from its own trend while the stage pumps and after
633
+ it stops, the first response and the fluid pumped by then, and the type -
634
+ no response, poroelastic (the rock squeezed, falling at the stop), mixed
635
+ (a hydraulic connection, holding or rising after it) or a frac hit (the
636
+ stage's fluid reaching the well). With surveys, the distance from each
637
+ stage to the monitored well; with an earthquake catalogue, the earthquakes
638
+ during each stage. A frac hit is an alert. The **Frac hits** tab,
639
+ `gea frac-hits`, `gea help frac-hits`.
640
+
612
641
  ### Plan vs actual and the survey tools
613
642
 
614
643
  Each well against its directional plan: at every survey station, how far
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.50.0"
126
+ __version__ = "0.52.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -622,6 +622,38 @@ def main(argv=None) -> int:
622
622
  p_pl.add_argument("--actor", type=str, default="cli")
623
623
  p_pl.add_argument("--json", action="store_true")
624
624
 
625
+ p_pp = sub.add_parser("pore-pressure", help="the mud-weight window: overburden, pore pressure by Eaton from the sonic, the resistivity or the d-exponent, and the fracture gradient")
626
+ p_pp.add_argument("--workspace", type=str, default=None)
627
+ p_pp.add_argument("--well", type=str, default=None)
628
+ p_pp.add_argument("--example", action="store_true", help="the worked example (L07-01, public data)")
629
+ p_pp.add_argument("--indicator", type=str, default=None, help="auto, sonic, resistivity or dexponent")
630
+ for k_, h_ in (("exponent", "Eaton's exponent"), ("nct-top", "top of the normally pressured interval, m TVD"), ("nct-bottom", "its bottom, m TVD"),
631
+ ("gr-cutoff", "the shale cut-off, gAPI"), ("normal-gradient", "the normal pore gradient, psi/ft"), ("poisson", "Poisson's ratio"),
632
+ ("density-above", "the mean density above the logs, g/cm3"), ("water-depth", "the water depth, m"), ("air-gap", "the air gap, m"),
633
+ ("bit", "the bit size for the d-exponent, in")):
634
+ p_pp.add_argument("--" + k_, type=str, default=None, help=h_)
635
+ p_pp.add_argument("--measure", type=str, default=None, help="add a measurement DEPTH_M,VALUE[,psi|ppg[,pore|kick|lot|fit[,tvd|md]]]")
636
+ p_pp.add_argument("--source", type=str, default="", help="where the values came from")
637
+ p_pp.add_argument("--write", action="store_true", help="write the report")
638
+ p_pp.add_argument("--actor", type=str, default="cli")
639
+ p_pp.add_argument("--json", action="store_true")
640
+
641
+ p_fh = sub.add_parser("frac-hits", help="what each fracturing stage did to the pressure in the wells around it: none, poroelastic, mixed or a frac hit, the first response and the volume pumped by then")
642
+ p_fh.add_argument("--workspace", type=str, required=True)
643
+ p_fh.add_argument("--well", type=str, default=None, help="the treatment well (with --stages or --remove) or the monitored well (with --channel)")
644
+ p_fh.add_argument("--stages", type=str, default=None, help="attach FILE as the well's stage table (CSV or Excel)")
645
+ p_fh.add_argument("--depth-unit", type=str, default="ft")
646
+ p_fh.add_argument("--utc-offset", type=str, default=None, help="the stage clock's hours from UTC (for the earthquake count)")
647
+ p_fh.add_argument("--remove", action="store_true", help="take the well's stage table off it")
648
+ p_fh.add_argument("--channel", type=str, default=None, help="with --well: the monitored pressure channel ('' for the first in psi)")
649
+ p_fh.add_argument("--hit-psi", type=str, default=None, help="a frac hit from this many psi")
650
+ p_fh.add_argument("--min-psi", type=str, default=None, help="the smallest response, psi")
651
+ p_fh.add_argument("--k-sigma", type=str, default=None, help="the response threshold in multiples of the trend's noise")
652
+ p_fh.add_argument("--source", type=str, default="", help="where the stage table or the rule came from")
653
+ p_fh.add_argument("--write", action="store_true", help="write the report")
654
+ p_fh.add_argument("--actor", type=str, default="cli")
655
+ p_fh.add_argument("--json", action="store_true")
656
+
625
657
  p_rt = sub.add_parser("rig-time", help="what the rig did each second of the drilling record - drilling, connections, trips, the slips - and the time lost against the pad's own best")
626
658
  p_rt.add_argument("--workspace", type=str, required=True)
627
659
  p_rt.add_argument("--well", type=str, default=None, help="with --write: one well's report (without it, the pad's)")
@@ -2440,6 +2472,80 @@ def main(argv=None) -> int:
2440
2472
  raise SystemExit(f"collision: {e.args[0] if e.args else e}")
2441
2473
  print(_json.dumps({k: v for k, v in r.items()}, default=str) if a.json else _CO.text(r))
2442
2474
  return 0
2475
+ elif a.cmd == "pore-pressure":
2476
+ import json as _json
2477
+ from . import pore_pressure as _PPF
2478
+ from .cli import _safe_stdout
2479
+ from .workspace import Workspace, WorkspaceError
2480
+ _safe_stdout()
2481
+ try:
2482
+ ws = Workspace(a.workspace) if a.workspace else None
2483
+ scene = "example" if (a.example or ws is None) else "workspace"
2484
+ keys = {"exponent": a.exponent, "nct_top_m": a.nct_top, "nct_bottom_m": a.nct_bottom, "gr_cutoff": a.gr_cutoff,
2485
+ "normal_gradient_psi_ft": a.normal_gradient, "poisson": a.poisson, "density_above_gcc": a.density_above,
2486
+ "water_depth_m": a.water_depth, "air_gap_m": a.air_gap, "bit_in": a.bit, "indicator": a.indicator}
2487
+ if ws is not None and (any(v is not None for v in keys.values()) or a.measure):
2488
+ if not a.well:
2489
+ raise ValueError("--well is needed")
2490
+ if a.measure:
2491
+ p_ = [x.strip() for x in a.measure.split(",")]
2492
+ r_ = _PPF.add_measurement(ws, a.actor, a.well, p_[0], p_[1], p_[2] if len(p_) > 2 else "psi", p_[3] if len(p_) > 3 else "pore",
2493
+ p_[4] if len(p_) > 4 else "tvd", a.source)
2494
+ print(f"measurement kept for {a.well}: {r_['kind']} {r_['value']:g} {r_['unit']} at {r_['depth_m']:g} m {r_['depth_kind'].upper()}")
2495
+ return 0
2496
+ c_ = _PPF.set_well(ws, a.actor, a.well, a.source, **{k: v for k, v in keys.items() if v is not None})
2497
+ print(f"{a.well}: {c_['indicator']}, exponent {c_['exponent'] or 'Eaton'}, trend {c_['nct_top_m']} to {c_['nct_bottom_m']} m, normal {c_['normal_gradient_psi_ft']:g} psi/ft, Poisson {c_['poisson']:g}")
2498
+ return 0
2499
+ if a.write:
2500
+ if ws is None:
2501
+ raise ValueError("--write needs --workspace")
2502
+ r_ = _PPF.write_report(ws, __version__, a.well, a.actor, scene)
2503
+ print(f"wrote {r_['report_id']}: {r_['paths']['html']}")
2504
+ if r_['paths'].get('pdf'):
2505
+ print(f" printable: {r_['paths']['pdf']}")
2506
+ return 0
2507
+ r = _PPF.page(ws, scene, a.well)
2508
+ except (KeyError, ValueError, OSError, WorkspaceError) as e:
2509
+ raise SystemExit(f"pore-pressure: {e.args[0] if e.args else e}")
2510
+ print(_json.dumps(r, default=str) if a.json else _PPF.text(r))
2511
+ return 0
2512
+ elif a.cmd == "frac-hits":
2513
+ import json as _json
2514
+ from . import frac_hits as _FH
2515
+ from .cli import _safe_stdout
2516
+ from .workspace import Workspace, WorkspaceError
2517
+ _safe_stdout()
2518
+ try:
2519
+ ws = Workspace(a.workspace)
2520
+ if a.stages or a.remove or a.channel is not None:
2521
+ if not a.well:
2522
+ raise ValueError("--well is needed")
2523
+ if a.stages:
2524
+ with open(a.stages, "rb") as f_:
2525
+ r_ = _FH.attach_stages(ws, a.well, os.path.basename(a.stages), f_.read(), a.actor, a.source, a.depth_unit, a.utc_offset)
2526
+ print(f"stage table attached to {a.well}: {r_['stages']} stages, {r_['first'][:16]} to {r_['last'][:16]}")
2527
+ return 0
2528
+ if a.remove:
2529
+ _FH.remove_stages(ws, a.well, a.actor)
2530
+ print(f"stage table removed from {a.well}")
2531
+ return 0
2532
+ if a.channel is not None or a.hit_psi is not None or a.min_psi is not None or a.k_sigma is not None:
2533
+ s_ = _FH.set_settings(ws, a.actor, a.source, (a.well, a.channel) if a.channel is not None else None,
2534
+ hit_psi=a.hit_psi, min_psi=a.min_psi, k_sigma=a.k_sigma)
2535
+ print(f"rule: frac hit from {s_['hit_psi']:g} psi, threshold {s_['k_sigma']:g} x noise and at least {s_['min_psi']:g} psi; monitors: "
2536
+ + (", ".join(f"{k} {v['channel']}" for k, v in s_['monitors'].items()) or "the first pressure channel of each well"))
2537
+ return 0
2538
+ if a.write:
2539
+ r_ = _FH.write_report(ws, __version__, a.actor)
2540
+ print(f"wrote {r_['report_id']}: {r_['paths']['html']}")
2541
+ if r_['paths'].get('pdf'):
2542
+ print(f" printable: {r_['paths']['pdf']}")
2543
+ return 0
2544
+ r = _FH.page(ws)
2545
+ except (KeyError, ValueError, OSError, WorkspaceError) as e:
2546
+ raise SystemExit(f"frac-hits: {e.args[0] if e.args else e}")
2547
+ print(_json.dumps(r, default=str) if a.json else _FH.text(r))
2548
+ return 0
2443
2549
  elif a.cmd == "plan":
2444
2550
  import json as _json
2445
2551
  from . import wellplan as _PL
@@ -11537,6 +11537,374 @@ def section_cs_plan_and_tools(tmp: str) -> None:
11537
11537
  + (f" [failed: {', '.join(failed5)}]" if failed5 else ""))
11538
11538
 
11539
11539
 
11540
+ def section_ct_frac_hits(tmp: str) -> None:
11541
+ """Section CT - frac hits: each fracturing stage read on the pressure of the wells around it - none, poroelastic,
11542
+ mixed or a frac hit - with the first response and the fluid pumped by then, checked on a labelled record built to
11543
+ answer each way; the stage table read from CSV or Excel by its column names and refused when wrong; the distance
11544
+ from each stage to the monitored well and the earthquakes during it; the alert, raised once; the API, the CLI, the
11545
+ report, the dossier, the tab and the help."""
11546
+ import base64 as _b64
11547
+ import http.cookiejar
11548
+ import subprocess as _sp
11549
+ import urllib.request
11550
+ import urllib.error
11551
+ import zipfile as _zf
11552
+ from datetime import datetime, timedelta
11553
+ from . import __version__ as _ver
11554
+ from . import alerts as AL
11555
+ from . import dossier as DZ
11556
+ from . import frac_hits as FH
11557
+ from . import helplib as H
11558
+ from . import op_seismicity as OS
11559
+ from . import well3d as W
11560
+ from . import xlsx_read as XR
11561
+ from .workspace import Workspace
11562
+ from .service import Service, Users
11563
+ pkg = Path(__file__).parent
11564
+ d = Path(tmp, "frac"); d.mkdir()
11565
+
11566
+ # -- CT1 the labelled record ---------------------------------------------------------------------------------------------
11567
+ f = FH.selftest_files(str(d))
11568
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
11569
+ tw = ws.add_well_file(f["gauge"], display="Treat 1H", actor="tester")["id"]
11570
+ mw = ws.add_well_file(f["gauge"], display="Monitor 2H", actor="tester")["id"]
11571
+ FH.attach_stages(ws, tw, "frac_stages.csv", Path(f["stages"]).read_bytes(), "tester", "post-job report (test)", "ft", -5)
11572
+ r = FH.analyse(ws)
11573
+ got = {s_["stage"]: s_["responses"][0] for s_ in r["stages"] if s_["responses"]}
11574
+ hit = got.get("4", {})
11575
+ t_, p_, t0_ = FH._series(ws, mw, "P_gauge_psi")
11576
+ st4 = next(s_ for s_ in r["stages"] if s_["stage"] == "4")
11577
+ a4 = (datetime.fromisoformat(st4["start"]) - t0_).total_seconds(); b4 = a4 + 7200.0
11578
+ strict = FH.respond(t_, p_, a4, b4, {**FH.DEFAULTS, "hit_psi": 1000.0})
11579
+ c1 = {
11580
+ "types": {k: v.get("type") for k, v in got.items()} == f["expected"] and r["count"] == {"NONE": 2, "POROELASTIC": 2, "MIXED": 1, "FRAC HIT": 1},
11581
+ "first": abs(hit.get("first_response_s", 0) - 1200.0) <= 30.0 and abs(hit.get("vfr_bbl", 0) - 9400.0 * hit.get("first_response_s", 0) / 7200.0) < 0.1
11582
+ and abs(got["2"]["first_response_s"] - 2640.0) <= 60.0,
11583
+ "after": got["2"]["after_stop"] == "fell" and got["3"]["after_stop"] == "rose" and hit.get("after_stop") == "held" and abs(hit.get("peak_psi", 0) - 900.0) < 5.0,
11584
+ "noise": all(0.4 < v["noise_psi"] < 0.7 and v["threshold_psi"] == 5.0 for v in got.values()) and all(v["first_response_s"] is None for k, v in got.items() if k in ("1", "6")),
11585
+ "rule": strict["type"] == "MIXED" and abs(got["5"]["peak_psi"] - 8.0) < 1.5,
11586
+ "own_well": all(x["monitor_id"] != tw for s_ in r["stages"] for x in s_["responses"]),
11587
+ }
11588
+ failed1 = [k for k, v in c1.items() if not v]
11589
+ ok(not failed1,
11590
+ "CT1 a labelled gauge record built so its six stages answer each way: nothing, poroelastic (25 psi, falling at the stop), "
11591
+ "mixed (60 psi, rising after it), a frac hit (900 psi, holding), poroelastic (8 psi), nothing - each read as built, with the "
11592
+ "first response twenty minutes into the hit and the fluid pumped by then in proportion; the trend's noise taken as the "
11593
+ "half-psi it is; a declared 1,000 psi rule reading the same stage as mixed; a well never its own monitor"
11594
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
11595
+
11596
+ # -- CT2 the stage table --------------------------------------------------------------------------------------------------
11597
+ alt = "Stage No,Pump Start,Pump End,Top MD (m),Bottom MD (m),Slurry Volume (gal),Total Proppant\n1,08/03/2026 07:00,08/03/2026 09:00,4000,4060,420000,400000\n"
11598
+ a_ = FH.read_stages(alt.encode())
11599
+ XR.write_simple(str(d / "st.xlsx"), [["Stage", "Start", "End"], [1, datetime(2026, 8, 3, 7), datetime(2026, 8, 3, 9)], [2, datetime(2026, 8, 3, 12), datetime(2026, 8, 3, 14)]])
11600
+ x_ = FH.read_stages((d / "st.xlsx").read_bytes())
11601
+ refused = 0
11602
+ for bad in ("Stage,Start\n1,2026-08-03 07:00\n", "Stage,Start,End\n1,2026-08-03 09:00,2026-08-03 07:00\n",
11603
+ "Stage,Start,End\n1,2026-08-03 07:00,2026-08-03 09:00\n1,2026-08-03 10:00,2026-08-03 11:00\n", "Stage,Start,End\n1,yesterday,today\n"):
11604
+ try:
11605
+ FH.read_stages(bad.encode())
11606
+ except ValueError:
11607
+ refused += 1
11608
+ try:
11609
+ FH.attach_stages(ws, tw, "s.csv", alt.encode(), "tester", " ")
11610
+ no_src = False
11611
+ except ValueError:
11612
+ no_src = True
11613
+ c2 = {
11614
+ "synonyms": a_[0]["top_md_m"] == 4000.0 and a_[0]["bottom_md_m"] == 4060.0 and abs(a_[0]["volume_bbl"] - 10000.0) < 1e-6 and a_[0]["proppant_lb"] == 400000.0
11615
+ and a_[0]["start"] == "2026-08-03T07:00:00",
11616
+ "excel": [s_["stage"] for s_ in x_] == ["1", "2"] and x_[1]["end"] == "2026-08-03T14:00:00",
11617
+ "feet": abs(st4["top_md_m"] - 14000 * 0.3048) < 1e-6,
11618
+ "refused": refused == 4 and no_src,
11619
+ "channel": FH.pressure_channel(ws, mw) == ("P_gauge_psi", "psi"),
11620
+ }
11621
+ failed2 = [k for k, v in c2.items() if not v]
11622
+ ok(not failed2,
11623
+ "CT2 the stage table read by its column names in either wording, with its units (metres, gallons), from CSV or Excel; a table "
11624
+ "without an end column, a stage ending before it starts, a stage listed twice or a time that is not one refused, and a table "
11625
+ "without its source; the monitored channel found by its unit"
11626
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
11627
+
11628
+ # -- CT3 the distance, the earthquakes and the alert ---------------------------------------------------------------------
11629
+ for wid, dlat in ((tw, 0.0), (mw, 0.003)):
11630
+ ws.set_well(wid, surface={"lat": 31.42 + dlat, "lon": -104.10, "datum": "NAD83", "elevation": {"ft": 3000.0, "datum": "KB", "source": "test plat"}}, actor="tester")
11631
+ sv = "MD (ft),Inc (deg),Azi (deg)\n" + "\n".join(f"{m_},{0.0 if m_ <= 9000 else min(90.0, (m_ - 9000) * 0.09)},90" for m_ in range(0, 16001, 100)) + "\n"
11632
+ W.attach_trajectory(ws, wid, "s.csv", sv.encode(), "tester", "true", "ft", None, None, None, "MWD report (test)")
11633
+ qtime = datetime.fromisoformat(st4["start"]) + timedelta(hours=5, minutes=30) # 30 min into stage 4, in UTC (the stage clock is UTC-5)
11634
+ cat = ("#EventID|Time|Latitude|Longitude|Depth/km|Author|Catalog|Contributor|ContributorID|MagType|Magnitude|MagAuthor|EventLocationName\n"
11635
+ f"tx2026a|{qtime.isoformat()}|31.43|-104.05|5.0|TexNet|TX|TexNet|a|ML|2.1|TexNet|test\n"
11636
+ f"tx2026b|{(qtime + timedelta(days=3)).isoformat()}|31.43|-104.05|5.0|TexNet|TX|TexNet|b|ML|1.8|TexNet|test\n")
11637
+ OS.attach_catalog(ws, "tester", "cat.txt", cat.encode(), "2026-08-01", "2026-08-10", 1.0, 31.42, -104.10, 20.0, "TexNet (test)")
11638
+ r3 = FH.analyse(ws)
11639
+ q = {s_["stage"]: s_["earthquakes"] for s_ in r3["stages"]}
11640
+ dist = [s_["responses"][0].get("distance_m") for s_ in r3["stages"] if s_["responses"]]
11641
+ raised1 = AL.check_conditions(ws, None, force=True)
11642
+ raised2 = AL.check_conditions(ws, None, force=True)
11643
+ fh1 = [e for e in raised1 if e["event"] == "frac.hit"]
11644
+ c3 = {
11645
+ "distance": all(x is not None and 300.0 < x < 360.0 for x in dist),
11646
+ "quakes": [len(q[k]) for k in sorted(q)] == [0, 0, 0, 1, 0, 0] and q["4"][0]["event_id"] == "tx2026a",
11647
+ "alert": len(fh1) == 1 and fh1[0]["stage"] == "4" and fh1[0]["monitor"] == "Monitor 2H" and not [e for e in raised2 if e["event"] == "frac.hit"]
11648
+ and "frac.hit" in AL.EVENTS,
11649
+ }
11650
+ failed3 = [k for k, v in c3.items() if not v]
11651
+ ok(not failed3,
11652
+ "CT3 with both wells surveyed, each stage's mid-point a third of a kilometre from the monitored lateral as built; with a "
11653
+ "catalogue and the stage clock five hours behind UTC, the one earthquake inside stage 4's window counted beside it and the "
11654
+ "one three days later not; the frac hit raised as an alert once, not again"
11655
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
11656
+
11657
+ # -- CT4 the API, the CLI, the report, the dossier, the tab and the help -------------------------------------------------
11658
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
11659
+ svc = Service(wsp, port=0, scheduler=False).start()
11660
+ base_url = svc.url.rstrip("/")
11661
+
11662
+ def opener(name, pw_):
11663
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
11664
+ rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
11665
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
11666
+ return o
11667
+
11668
+ def call(o, method, path, body=None):
11669
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
11670
+ if method == "POST":
11671
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
11672
+ try:
11673
+ with o.open(rq, timeout=120) as rr_:
11674
+ return rr_.status, json.loads(rr_.read())
11675
+ except urllib.error.HTTPError as e:
11676
+ return e.code, json.loads(e.read())
11677
+ xb = _b64.b64encode((d / "st.xlsx").read_bytes()).decode()
11678
+ try:
11679
+ V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
11680
+ s_vg, vg = call(V, "GET", "/api/frac-hits")
11681
+ s_va, _ = call(V, "POST", "/api/frac-hits/stages", {"well_id": mw, "filename": "st.xlsx", "content_b64": xb, "source": "x"})
11682
+ s_oa, oa = call(O, "POST", "/api/frac-hits/stages", {"well_id": mw, "filename": "st.xlsx", "content_b64": xb, "source": "plan (test)"})
11683
+ s_bo, _ = call(O, "POST", "/api/frac-hits/stages", {"well_id": mw, "filename": "b.csv", "content": "Stage,Start\n1,2026-08-03\n", "source": "x"})
11684
+ s_ns, _ = call(O, "POST", "/api/frac-hits/settings", {"hit_psi": 500})
11685
+ s_rs, rs = call(O, "POST", "/api/frac-hits/settings", {"hit_psi": 500, "source": "frac plan (test)"})
11686
+ s_bc, _ = call(O, "POST", "/api/frac-hits/settings", {"monitor_id": "nowhere", "channel": "P"})
11687
+ s_rm, _ = call(O, "POST", "/api/frac-hits/remove", {"well_id": mw})
11688
+ s_r2, _ = call(O, "POST", "/api/frac-hits/remove", {"well_id": mw})
11689
+ s_vw, _ = call(V, "POST", "/api/frac-hits/write", {})
11690
+ s_ow, ow = call(O, "POST", "/api/frac-hits/write", {})
11691
+ s_al, alj = call(V, "GET", "/api/alerts")
11692
+ finally:
11693
+ svc.stop()
11694
+ cli_t = _sp.run([sys.executable, "-m", "gea", "frac-hits", "--workspace", wsp], capture_output=True, text=True, cwd=str(pkg.parent))
11695
+ cli_r = _sp.run([sys.executable, "-m", "gea", "frac-hits", "--workspace", wsp, "--hit-psi", "250", "--source", "URTeC 2020-3129 (test)"], capture_output=True, text=True, cwd=str(pkg.parent))
11696
+ cli_w = _sp.run([sys.executable, "-m", "gea", "frac-hits", "--workspace", wsp, "--write"], capture_output=True, text=True, cwd=str(pkg.parent))
11697
+ dz = DZ.build(ws, "tester", _ver, None)
11698
+ names = _zf.ZipFile(Path(ws.reports_dir, dz["path"])).namelist()
11699
+ audit = Path(ws.audit_path).read_text(encoding="utf-8")
11700
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
11701
+ c4 = {
11702
+ "api": s_vg == 200 and vg["count"]["FRAC HIT"] == 1 and len(vg["stages"]) == 6 and s_va == 403 and s_oa == 200 and oa["stages"] == 2 and s_bo == 400
11703
+ and s_ns == 400 and s_rs == 200 and rs["hit_psi"] == 500.0 and s_bc == 404 and s_rm == 200 and s_r2 == 404 and s_vw == 403 and s_ow == 200
11704
+ and ow["report_id"].startswith("FDI-") and s_al == 200 and "frac.hit" in alj["events"],
11705
+ "cli": cli_t.returncode == 0 and "FRAC HIT 1" in cli_t.stdout and "stage 4" in cli_t.stdout and cli_r.returncode == 0 and "frac hit from 250 psi" in cli_r.stdout
11706
+ and cli_w.returncode == 0 and "frac_hits" in cli_w.stdout,
11707
+ "report": PR_read_ok(Path(ws.reports_dir, "frac_hits.pdf")) and all(x in audit for x in ("frac.stages", "frac.stages_removed", "frac.settings", "report.frac_hits")),
11708
+ "dossier": f"declarations/completion/frac/{tw}/frac_stages.csv" in names and "declarations/completion/frac/stages.json" in names and "results/frac_hits.json" in names,
11709
+ "page": all(x in page_src for x in ("VIEWS.frac", "['#/frac', 'Frac hits']", "/api/frac-hits/stages", "fhTrace", "'frac.hit': 'Frac hit'")),
11710
+ "help": not H.check("frac-hits") and H.VIEW_TOPIC.get("frac") == "frac-hits" and "frac.hit" in (pkg / "help" / "alerts.md").read_text(encoding="utf-8"),
11711
+ }
11712
+ failed4 = [k for k, v in c4.items() if not v]
11713
+ ok(not failed4,
11714
+ "CT4 on the API (anyone reads the stages and their responses; only an operator attaches a stage table - an Excel one - or a "
11715
+ "rule with its source, names a monitored channel, takes a table off or writes the report; the new alert listed), the CLI, "
11716
+ "the report as PDF, a signed dossier carrying the stage tables and the check, the tab and the help"
11717
+ + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
11718
+
11719
+
11720
+ def section_cu_pore_pressure(tmp: str) -> None:
11721
+ """Section CU - pore pressure and fracture gradient: the arithmetic (overburden, Gardner, Eaton, the d-exponent and
11722
+ its correction, Eaton's and Hubbert-Willis's fracture gradients) by hand; a depth log and a drilling record built so
11723
+ Eaton's method must return a known overpressure, read back by the sonic, the resistivity and the corrected
11724
+ d-exponent; measurements and the fitted exponent; the worked example; the API, the CLI, the report, the dossier,
11725
+ the tab and the help."""
11726
+ import gzip
11727
+ import hashlib as _hl
11728
+ import http.cookiejar
11729
+ import math as _m
11730
+ import subprocess as _sp
11731
+ import urllib.request
11732
+ import urllib.error
11733
+ import zipfile as _zf
11734
+ import numpy as _np
11735
+ from . import __version__ as _ver
11736
+ from . import dossier as DZ
11737
+ from . import helplib as H
11738
+ from . import pore_pressure as PPF
11739
+ from .workspace import Workspace
11740
+ from .service import Service, Users
11741
+ pkg = Path(__file__).parent
11742
+ d = Path(tmp, "porepressure"); d.mkdir()
11743
+
11744
+ # -- CU1 the arithmetic by hand ------------------------------------------------------------------------------------------
11745
+ dd = float(PPF.dexponent([30.0], [100.0], [20000.0], 8.5)[0])
11746
+ d_hand = _m.log10(30.0 / 6000.0) / _m.log10(12.0 * 20000.0 / 8.5e6)
11747
+ dc_ = float(PPF.dc([d_hand], 8.95, [10.0])[0])
11748
+ ob = PPF.overburden_psi(_np.array([1000.0, 2000.0]), _np.array([2.3, 2.3]), 0.0, 0.0, 1.03, 2.3)
11749
+ ob_sea = PPF.overburden_psi(_np.array([137.0, 1137.0]), _np.array([2.0, 2.0]), 100.0, 37.0, 1.03, 2.0)
11750
+ psi_hand = lambda rho, h: rho * 1000.0 * 9.80665 * h / 6894.757293168
11751
+ S_, Ph_ = 10000.0, 4650.0
11752
+ pp_s = float(PPF.eaton(S_, Ph_, 120.0, 100.0, 3.0, -1)) # sonic slower than its trend: overpressure
11753
+ pp_r = float(PPF.eaton(S_, Ph_, 0.8, 1.0, 1.2, 1)) # resistivity below its trend: overpressure
11754
+ c1 = {
11755
+ "dexp": abs(dd - d_hand) < 1e-12 and abs(d_hand - 1.48529) < 1e-4 and abs(dc_ - d_hand * 0.895) < 1e-12 and _np.isnan(PPF.dexponent([0.0], [100.0], [20000.0], 8.5)[0]),
11756
+ "overburden": abs(ob[0] - psi_hand(2.3, 1000.0)) < 1e-6 and abs(ob[1] - psi_hand(2.3, 2000.0)) < 1e-6
11757
+ and abs(ob_sea[0] - psi_hand(1.03, 100.0)) < 1e-6 and abs(ob_sea[1] - psi_hand(1.03, 100.0) - psi_hand(2.0, 1000.0)) < 1e-6,
11758
+ "gardner": abs(float(PPF.gardner(_np.array([100.0]))[0]) - 0.23 * 10000.0 ** 0.25) < 1e-12,
11759
+ "eaton": abs(pp_s - (S_ - (S_ - Ph_) * (100.0 / 120.0) ** 3)) < 1e-9 and pp_s > Ph_ and abs(pp_r - (S_ - (S_ - Ph_) * 0.8 ** 1.2)) < 1e-9
11760
+ and abs(float(PPF.eaton(S_, Ph_, 100.0, 100.0, 3.0, -1)) - Ph_) < 1e-9,
11761
+ "fracture": abs(float(PPF.eaton_fg(S_, 6000.0, 0.25)) - float(PPF.hubbert_willis(S_, 6000.0))) < 1e-9
11762
+ and abs(float(PPF.eaton_fg(S_, 6000.0, 0.4)) - (0.4 / 0.6 * 4000.0 + 6000.0)) < 1e-9,
11763
+ "ppg": abs(float(PPF.ppg(5200.0, 10000.0 * 0.3048)) - 10.0) < 1e-9,
11764
+ }
11765
+ failed1 = [k for k, v in c1.items() if not v]
11766
+ ok(not failed1,
11767
+ "CU1 the arithmetic by hand: Jorden and Shirley's d-exponent (1.4853 for 30 ft/h, 100 rpm, 20 klbf on an 8-1/2 in bit) and "
11768
+ "Rehm and McClendon's correction; the overburden of rock alone and of sea then rock below an air gap; Gardner's density; "
11769
+ "Eaton's ratio for the sonic and the resistivity, returning hydrostatic on the trend; Eaton's fracture gradient equal to "
11770
+ "Hubbert-Willis's at a Poisson's ratio of 0.25; an equivalent mud weight"
11771
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
11772
+
11773
+ # -- CU2 the labelled log and drilling record --------------------------------------------------------------------------
11774
+ f = PPF.selftest_files(str(d))
11775
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
11776
+ wl = ws.add_well_file(f["las"], display="LOG-1", actor="tester")["id"]
11777
+ we = ws.add_well_file(f["edr"], display="EDR-1", actor="tester")["id"]
11778
+
11779
+ def err_of(wid):
11780
+ x = PPF.page(ws, "workspace", wid)["result"]
11781
+ c = x["curves"]
11782
+ z = _np.array(c["tvd_m"], float)
11783
+ pp = _np.array([_np.nan if v is None else v for v in c["pp_psi"]], float)
11784
+ Pt = PPF.truth(z)[2]
11785
+ away = (_np.abs(z - 2200.0) > 10.0) & (_np.abs(z - 2600.0) > 10.0) & _np.isfinite(pp)
11786
+ return x, float(_np.max(_np.abs(pp[away] - Pt[away]) / Pt[away])), int(away.sum())
11787
+ PPF.set_well(ws, "tester", wl, "labelled log (test)", indicator="sonic", nct_top_m=600, nct_bottom_m=2100, smooth_m=5, density_above_gcc=2.3)
11788
+ xs, es, ns = err_of(wl)
11789
+ PPF.set_well(ws, "tester", wl, "labelled log (test)", indicator="resistivity")
11790
+ xr, er, nr = err_of(wl)
11791
+ no_bit = PPF.page(ws, "workspace", we)["detail"]
11792
+ PPF.set_well(ws, "tester", we, "labelled record (test)", bit_in=8.5, nct_top_m=1050, nct_bottom_m=2100, smooth_m=6, density_above_gcc=2.3)
11793
+ xd, ed, nd = err_of(we)
11794
+ c2 = {
11795
+ "sonic": es < 0.002 and ns > 300 and abs(xs["trend"]["a"] - 2.25) < 1e-4 and abs(xs["trend"]["b_per_m"] + 1.2e-4) < 1e-8
11796
+ and xs["overpressure_from_tvd_m"] is not None and abs(xs["overpressure_from_tvd_m"] - 2200.0) <= 5.0 and abs(xs["overburden_td_psi"] - float(PPF.truth(_np.array([3500.0]))[0][0])) < 1.0,
11797
+ "resistivity": er < 0.002 and nr > 300 and xr["indicator"] == "resistivity" and abs(xr["trend"]["b_per_m"] - 1.5e-4) < 1e-8,
11798
+ "dexponent": "bit size" in no_bit and ed < 0.002 and nd > 300 and xd["indicator"] == "dexponent" and "Mud Weight In" in xd["how"] and abs(xd["trend"]["b_per_m"] - 1.0e-4) < 2e-6,
11799
+ "mud_flag": any(fl["what"] == "the mud weight below the pore pressure" and abs(fl["from_tvd_m"] - 2200.0) < 6 and abs(fl["to_tvd_m"] - 2600.0) < 6 for fl in xd["flags"]),
11800
+ }
11801
+ failed2 = [k for k, v in c2.items() if not v]
11802
+ ok(not failed2,
11803
+ "CU2 a depth log and a drilling record built so that Eaton's method must find a pore pressure 40 % of the way from "
11804
+ "hydrostatic to the overburden between 2,200 and 2,600 m: read back by the sonic, the resistivity and the corrected "
11805
+ "d-exponent to within 0.2 % away from the zone's edges, the trends recovered as built, the overburden to the psi; the "
11806
+ "d-exponent refused without a bit size; the 9.5 ppg mud flagged below the pore pressure across the zone"
11807
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
11808
+
11809
+ # -- CU3 measurements, the fitted exponent, refusals and the worked example ----------------------------------------------
11810
+ PPF.set_well(ws, "tester", wl, "labelled log (test)", indicator="sonic")
11811
+ pt = float(PPF.truth(_np.array([2400.0]))[2][0])
11812
+ PPF.add_measurement(ws, "tester", wl, 2400.0, round(pt, 1), "psi", "pore", "tvd", "MDT (test)")
11813
+ PPF.add_measurement(ws, "tester", wl, 1500.0, 16.2, "ppg", "lot", "tvd", "LOT (test)")
11814
+ xm = PPF.page(ws, "workspace", wl)["result"]
11815
+ refused = 0
11816
+ for kw in ({"poisson": 0.6}, {"normal_gradient_psi_ft": 0.3}, {"nct_top_m": 2000, "nct_bottom_m": 1000}, {"indicator": "magic"}, {"exponent": 2.0}):
11817
+ try:
11818
+ PPF.set_well(ws, "tester", wl, "" if "exponent" in kw else "x", **kw)
11819
+ except ValueError:
11820
+ refused += 1
11821
+ for args in ((wl, -5, 100), (wl, 1000, 100, "bar"), (wl, 1000, 100, "psi", "dst"), (wl, 1000, 100, "psi", "pore", "tvd", "")):
11822
+ try:
11823
+ PPF.add_measurement(ws, "tester", args[0], args[1], args[2], *(args[3:5] or ()), **({"depth_kind": args[5], "source": args[6]} if len(args) > 6 else {"source": "s"}))
11824
+ except ValueError:
11825
+ refused += 1
11826
+ ex = PPF.example()
11827
+ raw = gzip.open(pkg / "reference" / "l0701_sonic_gr.las.gz").read()
11828
+ prov = json.loads((pkg / "reference" / "l0701_sonic_gr.provenance.json").read_text(encoding="utf-8"))
11829
+ c3 = {
11830
+ "measured": len(xm["measured"]) == 2 and abs(xm["measured"][0]["computed_psi"] - pt) < 0.005 * pt and xm["fit_exponent"] is not None and abs(xm["fit_exponent"] - 3.0) < 0.06
11831
+ and abs(xm["measured"][1]["psi"] - 16.2 * 0.052 * 1500.0 / 0.3048) < 0.1 and xm["measured"][1]["kind"] == "lot",
11832
+ "refused": refused == 9,
11833
+ "example": ex["indicator"] == "sonic" and ex["channel"] == "DT" and ex["trend"]["points"] > 5000 and 18.0 < ex["overburden_td_ppg"] < 21.0
11834
+ and "Gardner from the sonic" in ex["density_from"] and "density log" in ex["density_from"] and ex["max_pp_ppg"] is not None and "NLOG" in ex["attribution"],
11835
+ "provenance": _hl.sha256(raw).hexdigest() == prov["sha256_uncompressed"] and _hl.sha256((pkg / "reference" / "l0701_sonic_gr.las.gz").read_bytes()).hexdigest() == prov["sha256"],
11836
+ }
11837
+ failed3 = [k for k, v in c3.items() if not v]
11838
+ ok(not failed3,
11839
+ "CU3 a formation test drawn beside the curve it confirms (to half a percent) and the Eaton exponent fitted to it (3); a "
11840
+ "leak-off test in ppg turned to psi at its depth; a Poisson's ratio, a normal gradient, a trend interval or an indicator "
11841
+ "out of range, a value without its source, and a measurement without a depth, a unit, a kind or a source refused; the "
11842
+ "worked example L07-01 read from its sonic with the overburden from its density log and Gardner's, the file as hashed"
11843
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
11844
+
11845
+ # -- CU4 the API, the CLI, the report, the dossier, the tab and the help -------------------------------------------------
11846
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
11847
+ svc = Service(wsp, port=0, scheduler=False).start()
11848
+ base_url = svc.url.rstrip("/")
11849
+
11850
+ def opener(name, pw_):
11851
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
11852
+ rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
11853
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
11854
+ return o
11855
+
11856
+ def call(o, method, path, body=None):
11857
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
11858
+ if method == "POST":
11859
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
11860
+ try:
11861
+ with o.open(rq, timeout=120) as rr_:
11862
+ return rr_.status, json.loads(rr_.read())
11863
+ except urllib.error.HTTPError as e:
11864
+ return e.code, json.loads(e.read())
11865
+ try:
11866
+ V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
11867
+ s_vg, vg = call(V, "GET", "/api/pore-pressure?well=" + wl)
11868
+ s_ve, ve = call(V, "GET", "/api/pore-pressure?scene=example")
11869
+ s_vs, _ = call(V, "POST", "/api/pore-pressure/settings", {"well_id": wl, "poisson": 0.3, "source": "x"})
11870
+ s_os, os_ = call(O, "POST", "/api/pore-pressure/settings", {"well_id": wl, "poisson": "0.3", "source": "frac gradient study (test)"})
11871
+ s_ob, _ = call(O, "POST", "/api/pore-pressure/settings", {"well_id": wl, "poisson": 0.7, "source": "x"})
11872
+ s_om, _ = call(O, "POST", "/api/pore-pressure/measure", {"well_id": wl, "depth_m": 3000, "value": 9.0, "unit": "ppg", "kind": "kick", "source": "kick (test)"})
11873
+ s_bm, _ = call(O, "POST", "/api/pore-pressure/measure", {"well_id": wl, "depth_m": 3000, "value": 9.0, "unit": "ppg", "kind": "kick", "source": ""})
11874
+ s_ur, _ = call(O, "POST", "/api/pore-pressure/unmeasure", {"well_id": wl, "index": 2})
11875
+ s_u2, _ = call(O, "POST", "/api/pore-pressure/unmeasure", {"well_id": wl, "index": 9})
11876
+ s_vw, _ = call(V, "POST", "/api/pore-pressure/write", {"well_id": wl})
11877
+ s_ow, ow = call(O, "POST", "/api/pore-pressure/write", {"well_id": wl})
11878
+ finally:
11879
+ svc.stop()
11880
+ cli_x = _sp.run([sys.executable, "-m", "gea", "pore-pressure", "--example"], capture_output=True, text=True, cwd=str(pkg.parent))
11881
+ cli_s = _sp.run([sys.executable, "-m", "gea", "pore-pressure", "--workspace", wsp, "--well", wl, "--gr-cutoff", "90", "--source", "shale cut-off (test)"], capture_output=True, text=True, cwd=str(pkg.parent))
11882
+ cli_m = _sp.run([sys.executable, "-m", "gea", "pore-pressure", "--workspace", wsp, "--well", wl, "--measure", "2500,13.0,ppg,pore,tvd", "--source", "RFT (test)"], capture_output=True, text=True, cwd=str(pkg.parent))
11883
+ cli_t = _sp.run([sys.executable, "-m", "gea", "pore-pressure", "--workspace", wsp, "--well", wl], capture_output=True, text=True, cwd=str(pkg.parent))
11884
+ dz = DZ.build(ws, "tester", _ver, None)
11885
+ names = _zf.ZipFile(Path(ws.reports_dir, dz["path"])).namelist()
11886
+ audit = Path(ws.audit_path).read_text(encoding="utf-8")
11887
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
11888
+ c4 = {
11889
+ "api": s_vg == 200 and vg["result"]["indicator"] == "sonic" and s_ve == 200 and ve["result"]["well"] == "L07-01" and s_vs == 403 and s_os == 200 and os_["poisson"] == 0.3
11890
+ and s_ob == 400 and s_om == 200 and s_bm == 400 and s_ur == 200 and s_u2 == 404 and s_vw == 403 and s_ow == 200 and ow["report_id"].startswith("PPF-"),
11891
+ "cli": cli_x.returncode == 0 and "L07-01" in cli_x.stdout and cli_s.returncode == 0 and cli_m.returncode == 0 and "pore 13 ppg at 2500 m TVD" in cli_m.stdout
11892
+ and cli_t.returncode == 0 and "gamma ray >= 90" in cli_t.stdout and "the exponent that fits" in cli_t.stdout,
11893
+ "report": PR_read_ok(Path(ws.reports_dir, "wells", wl, "pore_pressure.pdf"))
11894
+ and all(x in audit for x in ("pore_pressure.settings", "pore_pressure.measurement", "pore_pressure.measurement_removed", "report.pore_pressure")),
11895
+ "dossier": "declarations/geomech/pore_pressure.json" in names and f"results/pore_pressure/{wl}.json" in names and f"results/pore_pressure/{we}.json" in names,
11896
+ "page": all(x in page_src for x in ("VIEWS.porepressure", "['#/porepressure', 'Pore pressure']", "/api/pore-pressure/settings", "ppDepthPlot", "ppTrendPlot")),
11897
+ "help": not H.check("pore-pressure") and H.VIEW_TOPIC.get("porepressure") == "pore-pressure",
11898
+ }
11899
+ failed4 = [k for k, v in c4.items() if not v]
11900
+ ok(not failed4,
11901
+ "CU4 on the API (anyone reads a well's window and the worked example; only an operator declares how a well is read - a "
11902
+ "Poisson's ratio of 0.7 refused - adds or removes a measurement - one without its source refused - or writes the report), "
11903
+ "the CLI (the example, a cut-off, a measurement, the well), the report as PDF, a signed dossier carrying the declarations "
11904
+ "and each declared well's window, the tab and the help"
11905
+ + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
11906
+
11907
+
11540
11908
  def PR_read_ok(path) -> bool:
11541
11909
  from . import pdf_report as PR
11542
11910
  try:
@@ -11645,6 +12013,8 @@ def main() -> int:
11645
12013
  section_cq_tied_together(tmp)
11646
12014
  section_cr_collision(tmp)
11647
12015
  section_cs_plan_and_tools(tmp)
12016
+ section_ct_frac_hits(tmp)
12017
+ section_cu_pore_pressure(tmp)
11648
12018
  if _FAILS:
11649
12019
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
11650
12020
  for f in _FAILS: