gea-program 0.21.0__tar.gz → 0.22.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 (255) hide show
  1. {gea_program-0.21.0 → gea_program-0.22.0}/PKG-INFO +24 -2
  2. {gea_program-0.21.0 → gea_program-0.22.0}/README.md +23 -1
  3. {gea_program-0.21.0 → gea_program-0.22.0}/gea/__init__.py +1 -1
  4. {gea_program-0.21.0 → gea_program-0.22.0}/gea/__main__.py +50 -4
  5. {gea_program-0.21.0 → gea_program-0.22.0}/gea/acceptance_tests.py +260 -0
  6. {gea_program-0.21.0 → gea_program-0.22.0}/gea/client_reports.py +33 -0
  7. gea_program-0.22.0/gea/elevation.py +60 -0
  8. gea_program-0.22.0/gea/help/interval.md +11 -0
  9. {gea_program-0.21.0 → gea_program-0.22.0}/gea/helplib.py +1 -0
  10. gea_program-0.22.0/gea/interval.py +250 -0
  11. {gea_program-0.21.0 → gea_program-0.22.0}/gea/service.py +32 -1
  12. {gea_program-0.21.0 → gea_program-0.22.0}/gea/web/app.html +31 -3
  13. {gea_program-0.21.0 → gea_program-0.22.0}/gea/workspace.py +79 -7
  14. {gea_program-0.21.0 → gea_program-0.22.0}/gea_program.egg-info/PKG-INFO +24 -2
  15. {gea_program-0.21.0 → gea_program-0.22.0}/gea_program.egg-info/SOURCES.txt +3 -0
  16. {gea_program-0.21.0 → gea_program-0.22.0}/pyproject.toml +1 -1
  17. {gea_program-0.21.0 → gea_program-0.22.0}/LICENSE +0 -0
  18. {gea_program-0.21.0 → gea_program-0.22.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  19. {gea_program-0.21.0 → gea_program-0.22.0}/gea/accuracy_statement.py +0 -0
  20. {gea_program-0.21.0 → gea_program-0.22.0}/gea/alarm_engine.py +0 -0
  21. {gea_program-0.21.0 → gea_program-0.22.0}/gea/backup.py +0 -0
  22. {gea_program-0.21.0 → gea_program-0.22.0}/gea/bench.py +0 -0
  23. {gea_program-0.21.0 → gea_program-0.22.0}/gea/blind_harness.py +0 -0
  24. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  25. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  26. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  27. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  28. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  29. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  30. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  31. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  32. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  33. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  34. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  35. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  36. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  37. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  38. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  39. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  40. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  41. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  42. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  43. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  44. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  45. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  46. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  47. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  48. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  49. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  50. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  51. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  52. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  53. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  54. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  55. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  56. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  57. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  58. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  59. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  60. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  61. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  62. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  63. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  64. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  65. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  66. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  67. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  68. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  69. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  70. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  71. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  72. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  73. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  74. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  75. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  76. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  77. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  78. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  79. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  80. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  81. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  82. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  83. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  84. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  85. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  86. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  87. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  88. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  89. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  90. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  91. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/med_160_sapropels.txt +0 -0
  92. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  93. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  94. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  95. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  96. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  97. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  98. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  99. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  100. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  101. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  102. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  103. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  104. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  105. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  106. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  107. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  108. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  109. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  110. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  111. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  112. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  113. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  114. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  115. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  116. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  117. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  118. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  119. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  120. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  121. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  122. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  123. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  124. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  125. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  126. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  127. {gea_program-0.21.0 → gea_program-0.22.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  128. {gea_program-0.21.0 → gea_program-0.22.0}/gea/certificates.py +0 -0
  129. {gea_program-0.21.0 → gea_program-0.22.0}/gea/cli.py +0 -0
  130. {gea_program-0.21.0 → gea_program-0.22.0}/gea/config_versioning.py +0 -0
  131. {gea_program-0.21.0 → gea_program-0.22.0}/gea/conformance.py +0 -0
  132. {gea_program-0.21.0 → gea_program-0.22.0}/gea/correlation.py +0 -0
  133. {gea_program-0.21.0 → gea_program-0.22.0}/gea/dashboard.py +0 -0
  134. {gea_program-0.21.0 → gea_program-0.22.0}/gea/deviation.py +0 -0
  135. {gea_program-0.21.0 → gea_program-0.22.0}/gea/doctor.py +0 -0
  136. {gea_program-0.21.0 → gea_program-0.22.0}/gea/downhole_engine.py +0 -0
  137. {gea_program-0.21.0 → gea_program-0.22.0}/gea/drift_monitor.py +0 -0
  138. {gea_program-0.21.0 → gea_program-0.22.0}/gea/earth_model.py +0 -0
  139. {gea_program-0.21.0 → gea_program-0.22.0}/gea/etp.py +0 -0
  140. {gea_program-0.21.0 → gea_program-0.22.0}/gea/etp12_schemas.json +0 -0
  141. {gea_program-0.21.0 → gea_program-0.22.0}/gea/example_register_map.json +0 -0
  142. {gea_program-0.21.0 → gea_program-0.22.0}/gea/fat_sat.py +0 -0
  143. {gea_program-0.21.0 → gea_program-0.22.0}/gea/files.py +0 -0
  144. {gea_program-0.21.0 → gea_program-0.22.0}/gea/follower.py +0 -0
  145. {gea_program-0.21.0 → gea_program-0.22.0}/gea/forward_model.py +0 -0
  146. {gea_program-0.21.0 → gea_program-0.22.0}/gea/gamma.py +0 -0
  147. {gea_program-0.21.0 → gea_program-0.22.0}/gea/gauge_aging.py +0 -0
  148. {gea_program-0.21.0 → gea_program-0.22.0}/gea/gauge_specs.py +0 -0
  149. {gea_program-0.21.0 → gea_program-0.22.0}/gea/geodesy.py +0 -0
  150. {gea_program-0.21.0 → gea_program-0.22.0}/gea/gravity_reference.py +0 -0
  151. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/TESTER_GUIDE.md +0 -0
  152. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/alarms.md +0 -0
  153. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/audit-update.md +0 -0
  154. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/backup.md +0 -0
  155. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/conformance.md +0 -0
  156. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/data-in.md +0 -0
  157. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/doctor.md +0 -0
  158. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/drift.md +0 -0
  159. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/etp.md +0 -0
  160. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/files.md +0 -0
  161. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/instruments.md +0 -0
  162. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/month-end.md +0 -0
  163. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/notifications.md +0 -0
  164. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/osdu.md +0 -0
  165. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/patches.md +0 -0
  166. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/ppdm.md +0 -0
  167. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/quakeml.md +0 -0
  168. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/quality.md +0 -0
  169. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/seedlink.md +0 -0
  170. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/seismic.md +0 -0
  171. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/site.md +0 -0
  172. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/sites.md +0 -0
  173. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/soak.md +0 -0
  174. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/sra.md +0 -0
  175. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/start.md +0 -0
  176. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/structure.md +0 -0
  177. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/transients.md +0 -0
  178. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/upkeep.md +0 -0
  179. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/vibration.md +0 -0
  180. {gea_program-0.21.0 → gea_program-0.22.0}/gea/help/well-tests.md +0 -0
  181. {gea_program-0.21.0 → gea_program-0.22.0}/gea/housekeeping.py +0 -0
  182. {gea_program-0.21.0 → gea_program-0.22.0}/gea/inverse_engine.py +0 -0
  183. {gea_program-0.21.0 → gea_program-0.22.0}/gea/jobs.py +0 -0
  184. {gea_program-0.21.0 → gea_program-0.22.0}/gea/live_ports.py +0 -0
  185. {gea_program-0.21.0 → gea_program-0.22.0}/gea/loadtest.py +0 -0
  186. {gea_program-0.21.0 → gea_program-0.22.0}/gea/matplotlib_demo.py +0 -0
  187. {gea_program-0.21.0 → gea_program-0.22.0}/gea/modbus.py +0 -0
  188. {gea_program-0.21.0 → gea_program-0.22.0}/gea/model_card.py +0 -0
  189. {gea_program-0.21.0 → gea_program-0.22.0}/gea/month_end.py +0 -0
  190. {gea_program-0.21.0 → gea_program-0.22.0}/gea/mqtt_port.py +0 -0
  191. {gea_program-0.21.0 → gea_program-0.22.0}/gea/notify.py +0 -0
  192. {gea_program-0.21.0 → gea_program-0.22.0}/gea/opcua_port.py +0 -0
  193. {gea_program-0.21.0 → gea_program-0.22.0}/gea/operator_app.py +0 -0
  194. {gea_program-0.21.0 → gea_program-0.22.0}/gea/osdu.py +0 -0
  195. {gea_program-0.21.0 → gea_program-0.22.0}/gea/patches.py +0 -0
  196. {gea_program-0.21.0 → gea_program-0.22.0}/gea/permits.py +0 -0
  197. {gea_program-0.21.0 → gea_program-0.22.0}/gea/ports.py +0 -0
  198. {gea_program-0.21.0 → gea_program-0.22.0}/gea/ppdm.py +0 -0
  199. {gea_program-0.21.0 → gea_program-0.22.0}/gea/profile_catalog.py +0 -0
  200. {gea_program-0.21.0 → gea_program-0.22.0}/gea/project.py +0 -0
  201. {gea_program-0.21.0 → gea_program-0.22.0}/gea/qt6_downhole_app.py +0 -0
  202. {gea_program-0.21.0 → gea_program-0.22.0}/gea/quakeml.py +0 -0
  203. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reconciler.py +0 -0
  204. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  205. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  206. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  207. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  208. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  209. {gea_program-0.21.0 → gea_program-0.22.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  210. {gea_program-0.21.0 → gea_program-0.22.0}/gea/rock_inventory.py +0 -0
  211. {gea_program-0.21.0 → gea_program-0.22.0}/gea/sample_record.py +0 -0
  212. {gea_program-0.21.0 → gea_program-0.22.0}/gea/sample_well_profile.csv +0 -0
  213. {gea_program-0.21.0 → gea_program-0.22.0}/gea/sbom.py +0 -0
  214. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seedlink.py +0 -0
  215. {gea_program-0.21.0 → gea_program-0.22.0}/gea/segy.py +0 -0
  216. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic.py +0 -0
  217. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_array.py +0 -0
  218. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_assoc.py +0 -0
  219. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_detect.py +0 -0
  220. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_film.py +0 -0
  221. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_harmonic.py +0 -0
  222. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_qc.py +0 -0
  223. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_response.py +0 -0
  224. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_signature.py +0 -0
  225. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_track.py +0 -0
  226. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_uncertainty.py +0 -0
  227. {gea_program-0.21.0 → gea_program-0.22.0}/gea/seismic_unlisted.py +0 -0
  228. {gea_program-0.21.0 → gea_program-0.22.0}/gea/sensor_swap.py +0 -0
  229. {gea_program-0.21.0 → gea_program-0.22.0}/gea/service_life.py +0 -0
  230. {gea_program-0.21.0 → gea_program-0.22.0}/gea/shapefile.py +0 -0
  231. {gea_program-0.21.0 → gea_program-0.22.0}/gea/shell.py +0 -0
  232. {gea_program-0.21.0 → gea_program-0.22.0}/gea/shut_in.py +0 -0
  233. {gea_program-0.21.0 → gea_program-0.22.0}/gea/sla_report.py +0 -0
  234. {gea_program-0.21.0 → gea_program-0.22.0}/gea/soak.py +0 -0
  235. {gea_program-0.21.0 → gea_program-0.22.0}/gea/sra.py +0 -0
  236. {gea_program-0.21.0 → gea_program-0.22.0}/gea/store_forward.py +0 -0
  237. {gea_program-0.21.0 → gea_program-0.22.0}/gea/strata_join.py +0 -0
  238. {gea_program-0.21.0 → gea_program-0.22.0}/gea/structure.py +0 -0
  239. {gea_program-0.21.0 → gea_program-0.22.0}/gea/supervisor.py +0 -0
  240. {gea_program-0.21.0 → gea_program-0.22.0}/gea/survey_cmd.py +0 -0
  241. {gea_program-0.21.0 → gea_program-0.22.0}/gea/survey_view.py +0 -0
  242. {gea_program-0.21.0 → gea_program-0.22.0}/gea/telemetry.py +0 -0
  243. {gea_program-0.21.0 → gea_program-0.22.0}/gea/tool_library.py +0 -0
  244. {gea_program-0.21.0 → gea_program-0.22.0}/gea/transient.py +0 -0
  245. {gea_program-0.21.0 → gea_program-0.22.0}/gea/vibration.py +0 -0
  246. {gea_program-0.21.0 → gea_program-0.22.0}/gea/well_assembler.py +0 -0
  247. {gea_program-0.21.0 → gea_program-0.22.0}/gea/well_test_validation.py +0 -0
  248. {gea_program-0.21.0 → gea_program-0.22.0}/gea/wits0.py +0 -0
  249. {gea_program-0.21.0 → gea_program-0.22.0}/gea/witsml.py +0 -0
  250. {gea_program-0.21.0 → gea_program-0.22.0}/gea_program.egg-info/dependency_links.txt +0 -0
  251. {gea_program-0.21.0 → gea_program-0.22.0}/gea_program.egg-info/entry_points.txt +0 -0
  252. {gea_program-0.21.0 → gea_program-0.22.0}/gea_program.egg-info/requires.txt +0 -0
  253. {gea_program-0.21.0 → gea_program-0.22.0}/gea_program.egg-info/top_level.txt +0 -0
  254. {gea_program-0.21.0 → gea_program-0.22.0}/setup.cfg +0 -0
  255. {gea_program-0.21.0 → gea_program-0.22.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.21.0
3
+ Version: 0.22.0
4
4
  Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
5
5
  Author: Daniel T. Murphy
6
6
  License: MPL-2.0
@@ -125,7 +125,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
125
125
  gea client-report --report accuracy --out client_report
126
126
  gea model-cards --out model_cards
127
127
  gea sbom --out sbom
128
- gea accept # the product gate (350 checks)
128
+ gea accept # the product gate (353 checks)
129
129
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
130
130
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
131
131
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -648,6 +648,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 interval against the mapped tops
652
+
653
+ Where does the injection sit in the geology? The operator declares the
654
+ disposal interval - top and base, true vertical depth, and the reference
655
+ elevation the depths are measured from with what it is (kelly bushing, ground,
656
+ derrick floor) and where it came from: `gea workspace --action set-well --well
657
+ <id> --top-interval-ft 9850 --base-interval-ft 10400 --depth-ref TVD
658
+ --ref-elev-ft 2962 --ref-elev-datum KB --ref-elev-source "completion report"`
659
+ (`--elev-3dep` fetches the ground elevation from the USGS 3DEP point service
660
+ instead, stored with its raster and named as the ground). A contours layer is
661
+ told what it maps (`add-layer ... --formation-name Wolfcamp --role
662
+ top|isopach|base`). `--action interval --well <id>` then converts the interval
663
+ to subsea with the arithmetic printed and sets it beside each formation-top
664
+ bracket at the well: ABOVE the mapped top, BELOW it, or NOT RESOLVED where
665
+ the bracket overlaps the interval; a formation with a top and an isopach gets
666
+ a base bracket by arithmetic the report shows, and the declared depth tier
667
+ (shallow or deep by the base the SRA names) is CONSISTENT with the map,
668
+ INCONSISTENT, or NOT RESOLVED. The SRA packet's section 2c carries it and
669
+ section 2d sets the catalogue's events against the mapped faults - the
670
+ nearest trace to each epicentre, the counts within 1, 2 and 5 km, proximity
671
+ never called attribution. `gea help interval`.
672
+
651
673
  ### Mapped structure
652
674
 
653
675
  The first geological statement the program makes about a well. A structure
@@ -86,7 +86,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
86
86
  gea client-report --report accuracy --out client_report
87
87
  gea model-cards --out model_cards
88
88
  gea sbom --out sbom
89
- gea accept # the product gate (350 checks)
89
+ gea accept # the product gate (353 checks)
90
90
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
91
91
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
92
92
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -609,6 +609,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 interval against the mapped tops
613
+
614
+ Where does the injection sit in the geology? The operator declares the
615
+ disposal interval - top and base, true vertical depth, and the reference
616
+ elevation the depths are measured from with what it is (kelly bushing, ground,
617
+ derrick floor) and where it came from: `gea workspace --action set-well --well
618
+ <id> --top-interval-ft 9850 --base-interval-ft 10400 --depth-ref TVD
619
+ --ref-elev-ft 2962 --ref-elev-datum KB --ref-elev-source "completion report"`
620
+ (`--elev-3dep` fetches the ground elevation from the USGS 3DEP point service
621
+ instead, stored with its raster and named as the ground). A contours layer is
622
+ told what it maps (`add-layer ... --formation-name Wolfcamp --role
623
+ top|isopach|base`). `--action interval --well <id>` then converts the interval
624
+ to subsea with the arithmetic printed and sets it beside each formation-top
625
+ bracket at the well: ABOVE the mapped top, BELOW it, or NOT RESOLVED where
626
+ the bracket overlaps the interval; a formation with a top and an isopach gets
627
+ a base bracket by arithmetic the report shows, and the declared depth tier
628
+ (shallow or deep by the base the SRA names) is CONSISTENT with the map,
629
+ INCONSISTENT, or NOT RESOLVED. The SRA packet's section 2c carries it and
630
+ section 2d sets the catalogue's events against the mapped faults - the
631
+ nearest trace to each epicentre, the counts within 1, 2 and 5 km, proximity
632
+ never called attribution. `gea help interval`.
633
+
612
634
  ### Mapped structure
613
635
 
614
636
  The first geological statement the program makes about a well. A structure
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.21.0"
126
+ __version__ = "0.22.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -285,7 +285,7 @@ def main(argv=None) -> int:
285
285
  "add-seismic", "refresh-seismic", "remove-seismic", "sar-film", "refresh-all", "add-track", "refresh-track", "remove-track",
286
286
  "seismic-dataset", "seismic-field", "add-site", "sites", "site-report", "remove-site", "rename",
287
287
  "set-well", "sra-define", "sra-report", "associate", "osdu-export", "quakeml-export", "well-identity", "vibration-report",
288
- "add-live-station", "live-fold", "live-run", "month-end", "add-layer", "layers", "remove-layer", "structure"])
288
+ "add-live-station", "live-fold", "live-run", "month-end", "add-layer", "layers", "remove-layer", "structure", "interval"])
289
289
  p_ws.add_argument("--stations", type=str, nargs="+", default=None, help="add-track: two or more array station ids of this workspace")
290
290
  p_ws.add_argument("--site", type=str, default=None, help="site-report/remove-site: the site id")
291
291
  p_ws.add_argument("--api", type=str, default=None, help="set-well: the API number")
@@ -362,6 +362,15 @@ def main(argv=None) -> int:
362
362
  p_ws.add_argument("--layer", type=str, default=None, help="add-layer: the shapefile (zip, folder or .shp); remove-layer: the layer id")
363
363
  p_ws.add_argument("--member", type=str, default=None, help="add-layer: which shapefile inside a zip or folder that holds several (gea structure --layer <zip> --list)")
364
364
  p_ws.add_argument("--citation", type=str, default="", help="add-layer: where the map came from, in the site's words")
365
+ p_ws.add_argument("--formation-name", type=str, default=None, help="add-layer: the formation a contours layer maps (Wolfcamp)")
366
+ p_ws.add_argument("--role", type=str, choices=["top", "isopach", "base"], default=None, help="add-layer: what the contours are of that formation: its top elevation, its thickness, or its base")
367
+ p_ws.add_argument("--base-interval-ft", type=float, default=None, help="set-well: base of the disposal interval, ft")
368
+ p_ws.add_argument("--depth-ref", type=str, choices=["TVD", "MD"], default=None, help="set-well: the interval depths are true vertical (TVD) or measured (MD) depth")
369
+ p_ws.add_argument("--ref-elev-ft", type=float, default=None, help="set-well: the reference elevation the depths are measured from, ft above sea level")
370
+ p_ws.add_argument("--ref-elev-datum", type=str, choices=["KB", "GL", "DF"], default=None, help="set-well: what that elevation is: kelly bushing, ground level, derrick floor")
371
+ p_ws.add_argument("--ref-elev-source", type=str, default=None, help="set-well: where the elevation came from (completion report, survey plat)")
372
+ p_ws.add_argument("--elev-3dep", action="store_true", help="set-well: fetch the GROUND elevation at the surface position from the USGS 3DEP point service and store it as the reference (datum GL, source stated)")
373
+ p_ws.add_argument("--epqs-url", type=str, default=None, help="set-well --elev-3dep / gea elevation: the EPQS endpoint (default the USGS one)")
365
374
  p_ws.add_argument("--period", type=str, default="previous", help="month-end: the month, YYYY-MM, or 'previous' (the last closed month)")
366
375
  p_ws.add_argument("--no-refresh", action="store_true", help="month-end: collect the reports as they stand without refreshing first")
367
376
  p_ws.add_argument("--name", type=str, default=None, help="init: site name; add-*: display name")
@@ -395,6 +404,10 @@ def main(argv=None) -> int:
395
404
  p_hk.add_argument("--actor", type=str, default="housekeeping")
396
405
  p_hk.add_argument("--json", action="store_true")
397
406
 
407
+ p_el = sub.add_parser("elevation", help="the ground elevation of a position from the USGS 3DEP point service: --lat --lon (the ground, NAVD88; not the kelly bushing)")
408
+ p_el.add_argument("--lat", type=float, required=True); p_el.add_argument("--lon", type=float, required=True)
409
+ p_el.add_argument("--url", type=str, default=None, help="the EPQS endpoint (default the USGS one)")
410
+ p_el.add_argument("--json", action="store_true")
398
411
  p_st = sub.add_parser("structure", help="the nearest mapped fault and fold to a position, from a shapefile: --layer <zip|folder|.shp> --lat --lon; --info describes the layer")
399
412
  p_st.add_argument("--layer", type=str, required=True)
400
413
  p_st.add_argument("--lat", type=float, default=None); p_st.add_argument("--lon", type=float, default=None)
@@ -939,6 +952,16 @@ def main(argv=None) -> int:
939
952
  for x in r['records_removed'][:20]:
940
953
  print(f" record {x['well_id']}/{x['file']} {x['age_days']} d")
941
954
  return 0
955
+ elif a.cmd == "elevation":
956
+ from .elevation import EPQS_URL, epqs
957
+ r = epqs(a.lat, a.lon, url=a.url or EPQS_URL)
958
+ if a.json:
959
+ print(json.dumps(r, indent=1)); return 0 if r["status"] == "OK" else 1
960
+ if r["status"] != "OK":
961
+ print(f"elevation: {r['status']} - {r.get('detail', '')}", file=sys.stderr); return 1
962
+ print(f"ground elevation at {a.lat}, {a.lon}: {r['elev_ft']:,.2f} ft ({r['vertical_datum']})")
963
+ print(f" source: {r['source']}")
964
+ return 0
942
965
  elif a.cmd == "structure":
943
966
  import json as _json
944
967
  from . import shapefile as _SF
@@ -1953,13 +1976,28 @@ def main(argv=None) -> int:
1953
1976
  elif a.action == "set-well":
1954
1977
  if not a.well:
1955
1978
  raise SystemExit("set-well needs --well <id>")
1956
- surface = {"lat": a.lat, "lon": a.lon, "datum": a.datum} if (a.lat is not None or a.lon is not None) else None
1979
+ surface = {"lat": a.lat, "lon": a.lon, "datum": a.datum} if (a.lat is not None or a.lon is not None) else {}
1980
+ if a.elev_3dep:
1981
+ from .elevation import EPQS_URL, as_reference, epqs
1982
+ w0 = ws.well(a.well)
1983
+ pos = {"lat": a.lat, "lon": a.lon} if a.lat is not None else (w0.get("surface") or {})
1984
+ if pos.get("lat") is None:
1985
+ raise SystemExit("--elev-3dep needs the surface position (give --lat --lon --datum, or declare it first)")
1986
+ r = epqs(float(pos["lat"]), float(pos["lon"]), url=a.epqs_url or EPQS_URL)
1987
+ if r["status"] != "OK":
1988
+ raise SystemExit(f"3DEP: {r['status']} - {r.get('detail', '')} ({r['url']})")
1989
+ surface["elevation"] = as_reference(r)
1990
+ print(f"3DEP ground elevation {r['elev_ft']:,.2f} ft ({r['source']})")
1991
+ elif a.ref_elev_ft is not None or a.ref_elev_datum or a.ref_elev_source:
1992
+ surface["elevation"] = {"ft": a.ref_elev_ft, "datum": a.ref_elev_datum, "source": a.ref_elev_source}
1957
1993
  disposal = {k: v for k, v in {"api_number": a.api, "uic_number": a.uic, "depth_tier": a.depth_tier, "formation_basis": a.formation,
1958
1994
  "channel_pressure": a.channel_pressure, "channel_rate": a.channel_rate, "rate_unit": a.rate_unit,
1959
1995
  "bhp_method": a.bhp_method, "channel_bhp": a.channel_bhp, "top_interval_ft": a.top_interval_ft,
1996
+ "base_interval_ft": a.base_interval_ft, "depth_reference": a.depth_ref,
1960
1997
  "gradient_psi_ft": a.gradient_psi_ft}.items() if v is not None}
1961
1998
  if not surface and not disposal:
1962
- raise SystemExit("set-well: nothing to declare (give --lat/--lon, or any of --api --uic --depth-tier --channel-pressure ...)")
1999
+ raise SystemExit("set-well: nothing to declare (give --lat/--lon, --ref-elev-ft, or any of --api --uic --depth-tier --top-interval-ft ...)")
2000
+ surface = surface or None
1963
2001
  w = ws.set_well(a.well, surface=surface, disposal=disposal or None, actor=a.actor)
1964
2002
  print(f"well {a.well}: surface {w.get('surface')}; disposal {w.get('disposal')}"); return 0
1965
2003
  elif a.action == "sra-define":
@@ -2021,7 +2059,7 @@ def main(argv=None) -> int:
2021
2059
  elif a.action == "add-layer":
2022
2060
  if not a.layer or not a.name:
2023
2061
  raise SystemExit("add-layer needs --name <display> and --layer <shapefile zip | folder | .shp>")
2024
- L = ws.add_layer(a.layer, a.name, actor=a.actor, citation=a.citation, member=a.member)
2062
+ L = ws.add_layer(a.layer, a.name, actor=a.actor, citation=a.citation, member=a.member, formation=a.formation_name, role=a.role)
2025
2063
  tc = ", ".join(f"{k} {v}" for k, v in list(L["type_counts"].items())[:6])
2026
2064
  print(f"layer {L['id']} ({L['kind']}): {L['n_features']} {L['shape_type']} features, {L['crs']['kind']} {L['crs'].get('datum') or ''}, extent lon {L['bbox'][0]:.3f}..{L['bbox'][2]:.3f} lat {L['bbox'][1]:.3f}..{L['bbox'][3]:.3f}"
2027
2065
  + (f"; contour value {L['value_attr']}" if L.get('value_attr') else ''))
@@ -2050,6 +2088,14 @@ def main(argv=None) -> int:
2050
2088
  for line in L["lines"]:
2051
2089
  print(" " + line)
2052
2090
  return 0 if r["status"] == "OK" else 1
2091
+ elif a.action == "interval":
2092
+ if not a.well:
2093
+ raise SystemExit("interval needs --well <id>")
2094
+ r = ws.well_interval(a.well, a.radius_km if a.radius_km is not None else 50.0, actor=a.actor)
2095
+ print(f"interval {a.well}: {r['status']}")
2096
+ for line in r["lines"]:
2097
+ print(" " + line)
2098
+ return 0 if r["status"] == "OK" else 1
2053
2099
  elif a.action == "month-end":
2054
2100
  from .month_end import report_text as _me_text
2055
2101
  r = ws.month_end(a.period, actor=a.actor, refresh=not a.no_refresh, catalog_csv=a.catalog)
@@ -6336,6 +6336,265 @@ def section_bo_structure(tmp: str) -> None:
6336
6336
  "all four; `gea structure --list` and `--member`"
6337
6337
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
6338
6338
 
6339
+ def section_bp_interval(tmp: str) -> None:
6340
+ """Section BP - the declared interval against the mapped tops: subsea conversion through a stated reference, the
6341
+ interval ABOVE / BELOW / NOT RESOLVED against each formation-top bracket, a base from a top and an isopach by the
6342
+ arithmetic shown, the depth tier against the mapped base; the catalogue events against the mapped faults; the
6343
+ 3DEP ground elevation client against a stand-in service; the workspace, the SRA packet's sections, the API, the
6344
+ page, the CLI, the help."""
6345
+ import http.cookiejar
6346
+ import http.server
6347
+ import math as _math
6348
+ import subprocess as _sp
6349
+ import urllib.request
6350
+ import urllib.error
6351
+ import zipfile
6352
+ from . import shapefile as SF
6353
+ from . import interval as IV
6354
+ from . import elevation as EL
6355
+ from . import helplib as H
6356
+ from .telemetry import TelemetryRecorder, TelemetryConfig
6357
+ from .downhole_engine import DownholeEngine, SimulatorConfig
6358
+ from .workspace import Workspace, WorkspaceError
6359
+ from .service import Service, Users
6360
+ pkg = Path(__file__).parent
6361
+ d = Path(tmp, "bp"); d.mkdir()
6362
+
6363
+ # -- BP1: the module on its own ----------------------------------------------------------------------------------
6364
+ ss = IV.subsea_ft(9850.0, 2962.0)
6365
+ c_above = IV.compare(-2050.0, -2650.0, (-8000.0, -7500.0))
6366
+ c_below = IV.compare(-8100.0, -8700.0, (-8000.0, -7500.0))
6367
+ c_nr = IV.compare(-7400.0, -7900.0, (-8000.0, -7500.0))
6368
+ c_swapped = IV.compare(-2650.0, -2050.0, (-8000.0, -7500.0)) # top and base given the wrong way round
6369
+ bb = IV.base_bracket((-8000.0, -7500.0), (1800.0, 2000.0))
6370
+ t_deep_ok = IV.tier_check("deep", -10050.0, -10650.0, bb)
6371
+ t_deep_bad = IV.tier_check("deep", -2050.0, -2650.0, bb)
6372
+ t_shallow_ok = IV.tier_check("shallow", -2050.0, -2650.0, bb)
6373
+ t_nr = IV.tier_check("shallow", -9500.0, -10100.0, bb)
6374
+ t_none = IV.tier_check(None, -2050.0, -2650.0, bb)
6375
+ br_on = IV.bracket_of({"status": "OK", "on": -7600.0, "between": None})
6376
+ br_none = IV.bracket_of({"status": "OUTSIDE LAYER EXTENT"})
6377
+ named = IV.formation_named("base of the Wolfcamp", ["Delaware", "Wolfcamp", "Bone Spring"])
6378
+ named_none = IV.formation_named("base of the Ellenburger", ["Wolfcamp"])
6379
+ well_md = {"id": "w", "surface": {"lat": 1.0, "lon": 1.0, "elevation": {"ft": 2950.0, "datum": "KB", "source": "report"}},
6380
+ "disposal": {"top_interval_ft": 5000.0, "base_interval_ft": 5600.0, "depth_reference": "MD"}}
6381
+ st_md = IV.statement(well_md, {"status": "OK", "layers": []}, [], None)
6382
+ st_undeclared = IV.statement({"id": "w"}, {"status": "OK", "layers": []}, [], None)
6383
+ well_gl = {"id": "w", "surface": {"lat": 1.0, "lon": 1.0, "elevation": {"ft": 2950.0, "datum": "GL", "source": "USGS 3DEP"}},
6384
+ "disposal": {"top_interval_ft": 5000.0, "base_interval_ft": 5600.0, "depth_reference": "TVD"}}
6385
+ st_gl = IV.statement(well_gl, {"status": "OK", "layers": [{"layer_id": "t", "layer": "t", "layer_kind": "contours", "status": "OK", "between": [-8000.0, -7500.0]}]},
6386
+ [{"id": "t", "kind": "contours", "formation": "Wolfcamp", "role": "top"}], None)
6387
+ c1 = {
6388
+ "subsea": ss == -6888.0,
6389
+ "relations": c_above["relation"] == "ABOVE" and c_below["relation"] == "BELOW" and c_nr["relation"] == "NOT RESOLVED" and c_swapped["relation"] == "ABOVE"
6390
+ and "cannot place the top" in c_nr["detail"],
6391
+ "base_arithmetic": bb == (-10000.0, -9300.0),
6392
+ "tier": t_deep_ok["status"] == "CONSISTENT" and t_deep_bad["status"] == "INCONSISTENT" and t_shallow_ok["status"] == "CONSISTENT"
6393
+ and t_nr["status"] == "NOT RESOLVED" and t_none["status"] == "NOT DECLARED",
6394
+ "brackets_and_names": br_on == (-7600.0, -7600.0) and br_none is None and named == "Wolfcamp" and named_none is None,
6395
+ "md_refused": st_md["status"] == "NOT RESOLVED" and "measured depth" in st_md["lines"][0] and "set-well --depth-ref TVD" in st_md["lines"][0],
6396
+ "undeclared_named": st_undeclared["status"] == "NOT DECLARED" and "--top-interval-ft" in st_undeclared["lines"][0] and "--ref-elev-ft" in st_undeclared["lines"][0]
6397
+ and "--depth-ref" in st_undeclared["lines"][0],
6398
+ "gl_note": st_gl["status"] == "OK" and st_gl["tops"][0]["relation"] == "ABOVE" and any("kelly bushing" in n and "not stated" in n for n in st_gl["notes"]),
6399
+ }
6400
+ failed1 = [k for k, v in c1.items() if not v]
6401
+ ok(not failed1,
6402
+ "BP1 the interval module: subsea = reference - depth; ABOVE, BELOW and NOT RESOLVED against a top bracket (the top and base taken either way round); "
6403
+ "a base from a top bracket and a thickness bracket by the arithmetic shown; the tier CONSISTENT, INCONSISTENT, NOT RESOLVED or NOT DECLARED against "
6404
+ "the base; a measured-depth interval refused naming the remedy; an undeclared interval named by what is missing; a ground-elevation reference "
6405
+ "carries the note that the kelly bushing is not stated"
6406
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
6407
+
6408
+ # -- BP2: the workspace, the SRA packet, the API, the page, the CLI, the help -----------------------------------
6409
+ lat1, lon1 = 31.42, -103.50
6410
+ kmlat = 1.0 / 111.195
6411
+ kmlon = 1.0 / (111.195 * _math.cos(_math.radians(lat1)))
6412
+
6413
+ def ns_contours(name, attr, values, spacing_km=4.0):
6414
+ ctr = [([[(lon1 + (i - (len(values) - 1) / 2) * spacing_km * kmlon, lat1 - 0.3), (lon1 + (i - (len(values) - 1) / 2) * spacing_km * kmlon, lat1 + 0.3)]],
6415
+ {attr: float(v)}) for i, v in enumerate(values)]
6416
+ SF.write(str(d / name), "PolyLine", ctr, [(attr, "F", 10, 1)], SF.NAD83_GEOGCS)
6417
+ with zipfile.ZipFile(str(d / (name + ".zip")), "w") as z:
6418
+ for ext in (".shp", ".shx", ".dbf", ".prj"):
6419
+ z.write(str(d / (name + ext)), name + ext)
6420
+ return str(d / (name + ".zip"))
6421
+ z_top = ns_contours("WC_Top", "ElevTop_ft", [-8500, -8000, -7500, -7000]) # the position between -8,000 and -7,500
6422
+ z_iso = ns_contours("WC_Isopach", "Thick_ft", [1600, 1800, 2000, 2200]) # between 1,800 and 2,000: base between -10,000 and -9,300
6423
+ z_del = ns_contours("DEL_Top", "ElevTop_ft", [-2400, -2000, -1600, -1200]) # between -2,000 and -1,600
6424
+ faults = [([[(lon1 - 0.3, lat1 + 3 * kmlat), (lon1 + 0.3, lat1 + 3 * kmlat)]], {"LONG_DESC": "fault", "NAME": "North Trace"}),
6425
+ ([[(lon1 + 8 * kmlon, lat1 - 0.3), (lon1 + 8 * kmlon, lat1 + 0.3)]], {"LONG_DESC": "dotted fault", "NAME": "East Trace"})]
6426
+ SF.write(str(d / "Faults_Test"), "PolyLine", faults, [("LONG_DESC", "C", 30, 0), ("NAME", "C", 30, 0)], SF.NAD83_GEOGCS)
6427
+ with zipfile.ZipFile(str(d / "Faults_Test.zip"), "w") as z:
6428
+ for ext in (".shp", ".shx", ".dbf", ".prj"):
6429
+ z.write(str(d / ("Faults_Test" + ext)), "Faults_Test" + ext)
6430
+ hist = str(d / "hist.csv")
6431
+ TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=1.0, seed=5)).run().export_csv(hist)
6432
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
6433
+ w = ws.add_well_file(hist, display="SWD 1", actor="tester")
6434
+ lay_t = ws.add_layer(z_top, "WC top", actor="tester", formation="Wolfcamp", role="top", citation="the gate's own layer")
6435
+ lay_i = ws.add_layer(z_iso, "WC isopach", actor="tester", formation="Wolfcamp", role="isopach")
6436
+ ws.add_layer(z_del, "Delaware top", actor="tester", formation="Delaware") # a formation named, the role defaulting to top
6437
+ ws.add_layer(str(d / "Faults_Test.zip"), "Faults", actor="tester")
6438
+ role_err = None
6439
+ try:
6440
+ ws.add_layer(str(d / "Faults_Test.zip"), "Faults as top", actor="tester", formation="X", role="top")
6441
+ except WorkspaceError as e:
6442
+ role_err = str(e)
6443
+ before = ws.well_interval(w["id"], actor="tester")
6444
+ elev_err = None
6445
+ try:
6446
+ ws.set_well(w["id"], surface={"lat": lat1, "lon": lon1, "datum": "NAD83", "elevation": {"ft": 2950.0, "datum": "KB"}}, actor="tester")
6447
+ except WorkspaceError as e:
6448
+ elev_err = str(e)
6449
+ ws.set_well(w["id"], surface={"lat": lat1, "lon": lon1, "datum": "NAD83", "elevation": {"ft": 2950.0, "datum": "KB", "source": "completion report"}}, actor="tester")
6450
+ order_err = None
6451
+ try:
6452
+ ws.set_well(w["id"], disposal={"top_interval_ft": 5600.0, "base_interval_ft": 5000.0}, actor="tester")
6453
+ except WorkspaceError as e:
6454
+ order_err = str(e)
6455
+ ws.set_well(w["id"], disposal={"top_interval_ft": 5000.0, "base_interval_ft": 5600.0, "depth_reference": "TVD", "depth_tier": "shallow",
6456
+ "formation_basis": "completed above the base of the Wolfcamp", "channel_pressure": "P_surf_psi", "channel_rate": "Q_bpm",
6457
+ "rate_unit": "bbl/min", "bhp_method": "probe", "channel_bhp": "P_bh_psi"}, actor="tester")
6458
+ site = ws.add_site("Pad 3 site", wells=[w["id"]], actor="tester")
6459
+ ws.sra_define(site["id"], actor="tester", name="Pad 3 SRA", lat=lat1, lon=lon1, radius_km=9.08, plan_date="2026-01-15")
6460
+ st1 = ws.well_interval(w["id"], actor="tester")
6461
+ rel1 = {t["layer_id"]: t["relation"] for t in st1["tops"]}
6462
+ ws.set_well(w["id"], disposal={"top_interval_ft": 13000.0, "base_interval_ft": 13600.0, "depth_tier": "deep"}, actor="tester")
6463
+ st2 = ws.well_interval(w["id"], actor="tester")
6464
+ ws.set_well(w["id"], disposal={"top_interval_ft": 10300.0, "base_interval_ft": 10900.0, "depth_tier": "deep"}, actor="tester") # -7,350 to -7,950: across the top, above the base
6465
+ st3 = ws.well_interval(w["id"], actor="tester")
6466
+ ws.set_well(w["id"], disposal={"top_interval_ft": 12400.0, "base_interval_ft": 13000.0, "depth_tier": "deep"}, actor="tester") # -9,450 to -10,050: across the base bracket
6467
+ st3b = ws.well_interval(w["id"], actor="tester")
6468
+ ws.set_well(w["id"], disposal={"top_interval_ft": 5000.0, "base_interval_ft": 5600.0, "depth_tier": "shallow"}, actor="tester")
6469
+ # the catalogue: three events, two beside a trace
6470
+ cat = str(d / "cat.csv")
6471
+ with open(cat, "w", encoding="utf-8") as f:
6472
+ f.write("EventID,Origin Date,Origin Time,Local Magnitude,Latitude (WGS84),Longitude (WGS84),Depth of Hypocenter (Km. Rel to MSL)\n")
6473
+ f.write(f"e1,2026-03-01,00:00:00,3.6,{lat1 + 2.5 * kmlat:.6f},{lon1:.6f},6.0\n")
6474
+ f.write(f"e2,2026-03-02,00:00:00,2.0,{lat1:.6f},{lon1 + 7.5 * kmlon:.6f},5.0\n")
6475
+ f.write(f"e3,2026-03-03,00:00:00,2.2,{lat1 - 0.05:.6f},{lon1 - 0.05:.6f},7.0\n")
6476
+ pk = ws.write_sra_packet(site["id"], catalog_csv=cat, actor="tester")
6477
+ sra_md = open(pk["paths"]["markdown"], encoding="utf-8").read()
6478
+ audit = [a["action"] for a in ws.audit_log()]
6479
+ users = Users(str(Path(wsp, "users.json"))); users.add("op", "operator-pass-1", "operator")
6480
+ svc = Service(wsp, port=0, scheduler=False).start()
6481
+ base_url = svc.url.rstrip("/")
6482
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
6483
+
6484
+ def call(method, path, body=None):
6485
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
6486
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
6487
+ try:
6488
+ with opener.open(req, timeout=120) as rr_:
6489
+ return rr_.status, json.loads(rr_.read())
6490
+ except urllib.error.HTTPError as e:
6491
+ return e.code, json.loads(e.read())
6492
+ try:
6493
+ call("POST", "/api/login", {"name": "op", "password": "operator-pass-1"})
6494
+ st_g, got = call("GET", f"/api/wells/{w['id']}/interval")
6495
+ st_d, dec = call("POST", f"/api/wells/{w['id']}/declare", {"disposal": {"top_interval_ft": 13000.0, "base_interval_ft": 13600.0, "depth_tier": "deep"}})
6496
+ st_bad = call("POST", f"/api/wells/{w['id']}/declare", {"disposal": {"depth_reference": "feet"}})[0]
6497
+ st_p, wrote = call("POST", f"/api/wells/{w['id']}/interval", {})
6498
+ st_l, lst = call("GET", "/api/layers")
6499
+ finally:
6500
+ svc.stop()
6501
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
6502
+ help_md = (pkg / "help" / "interval.md").read_text(encoding="utf-8")
6503
+ r_cli = _sp.run([sys.executable, "-m", "gea", "workspace", "--path", wsp, "--action", "interval", "--well", w["id"]], capture_output=True, text=True)
6504
+ r_set = _sp.run([sys.executable, "-m", "gea", "workspace", "--path", wsp, "--action", "set-well", "--well", w["id"], "--top-interval-ft", "5000", "--base-interval-ft", "5600",
6505
+ "--depth-ref", "TVD", "--depth-tier", "shallow", "--ref-elev-ft", "2950", "--ref-elev-datum", "KB", "--ref-elev-source", "completion report"], capture_output=True, text=True)
6506
+ r_add = _sp.run([sys.executable, "-m", "gea", "workspace", "--path", wsp, "--action", "add-layer", "--name", "WC top again", "--layer", z_top, "--formation-name", "Wolfcamp", "--role", "top"],
6507
+ capture_output=True, text=True)
6508
+ sec2c = sra_md.split("The declared interval against the mapped tops")[1].split("## 2d")[0] if "The declared interval against the mapped tops" in sra_md else ""
6509
+ sec2d = sra_md.split("Catalogue events against the mapped faults")[1].split("## 3")[0] if "Catalogue events against the mapped faults" in sra_md else ""
6510
+ c2 = {
6511
+ "layers_declared": lay_t["formation"] == "Wolfcamp" and lay_t["role"] == "top" and lay_i["role"] == "isopach" and role_err and "contours layer" in role_err
6512
+ and next(L_["role"] for L_ in ws.layers() if L_["id"] == "Delaware-top") == "top",
6513
+ "declarations_refused": before["status"] == "NOT DECLARED" and elev_err and "source" in elev_err and order_err and "deeper than its top" in order_err,
6514
+ "shallow_statement": st1["status"] == "OK" and st1["declared"]["top_subsea_ft"] == -2050.0 and st1["declared"]["base_subsea_ft"] == -2650.0
6515
+ and rel1 == {lay_t["id"]: "ABOVE", lay_i["id"]: "THICKNESS", "Delaware-top": "BELOW"}
6516
+ and st1["formations"][0]["base"] == [-10000.0, -9300.0] and st1["tier"]["status"] == "CONSISTENT" and st1["tier"]["formation"] == "Wolfcamp"
6517
+ and os.path.isfile(Path(wsp, "reports", "wells", w["id"], "interval.json")),
6518
+ "deep_and_unresolved": st2["tier"]["status"] == "CONSISTENT" and st2["formations"][0]["interval_vs_base"]["relation"] == "BELOW"
6519
+ and st3["tier"]["status"] == "INCONSISTENT" and {t["layer_id"]: t["relation"] for t in st3["tops"]}[lay_t["id"]] == "NOT RESOLVED"
6520
+ and st3b["tier"]["status"] == "NOT RESOLVED" and st3b["formations"][0]["interval_vs_base"]["relation"] == "NOT RESOLVED",
6521
+ "sra_sections": "## 2c" in sra_md and "| SWD-1 | OK |" in sec2c and "ABOVE WC top (-8,000/-7,500)" in sec2c and "CONSISTENT" in sec2c
6522
+ and "## 2d" in sra_md and "| Faults | e1 |" in sec2d and "North Trace | 0.50 |" in sec2d and "East Trace | 0.50 |" in sec2d
6523
+ and "2 within 1 km, 2 within 2 km, 2 within 5 km" in sec2d and "proximity to a mapped trace is not an attribution" in sec2d,
6524
+ "audited": "well.interval" in audit and audit.count("well.interval") >= 3,
6525
+ "api": st_g == 200 and got["status"] == "OK" and st_d == 200 and dec["disposal"]["depth_tier"] == "deep" and st_bad == 400 and st_p == 200
6526
+ and wrote["tier"]["status"] == "CONSISTENT" and st_l == 200 and any(L_.get("role") == "isopach" for L_ in lst["layers"]),
6527
+ "page": "intervalCardHtml" in page_src and "/api/wells/' + id + '/interval'" in page_src and "/api/elevation" in page_src and "lyRole" in page_src
6528
+ and "kelly bushing is deeper by the KB height" in page_src,
6529
+ "cli": r_cli.returncode == 0 and "BELOW the mapped Wolfcamp top" in r_cli.stdout and "Depth tier against the map (base of the Wolfcamp): CONSISTENT" in r_cli.stdout
6530
+ and r_set.returncode == 0 and "'depth_reference': 'TVD'" in r_set.stdout and r_add.returncode == 0,
6531
+ "help": any(t["topic"] == "interval" for t in H.topics()) and not H.check("interval") and "never a value between them" in help_md.split("will not call a measurement")[1],
6532
+ }
6533
+ failed2 = [k for k, v in c2.items() if not v]
6534
+ ok(not failed2,
6535
+ "BP2 the workspace: a contours layer declared with its formation and role (a role on a faults layer refused); the interval and its reference declared "
6536
+ "(an elevation without its source refused, a base above its top refused), NOT DECLARED before; the shallow interval ABOVE the Wolfcamp top and "
6537
+ "BELOW the Delaware top, the Wolfcamp base from top and isopach, the tier CONSISTENT; a deep interval CONSISTENT; an interval across the top NOT "
6538
+ "RESOLVED against it and INCONSISTENT with a deep declaration; an interval across the base bracket NOT RESOLVED; the SRA packet's 2c and 2d; audited; the API declares, answers and writes; the page card with the 3DEP button; the CLI; the help"
6539
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
6540
+
6541
+ # -- BP3: the 3DEP client against a stand-in service, and set-well --elev-3dep through it ----------------------
6542
+ seen = []
6543
+
6544
+ class _EPQS(http.server.BaseHTTPRequestHandler):
6545
+ def log_message(self, *a): # quiet
6546
+ pass
6547
+
6548
+ def do_GET(self):
6549
+ import urllib.parse as up
6550
+ q = up.parse_qs(up.urlsplit(self.path).query)
6551
+ seen.append(q)
6552
+ y = float(q.get("y", ["0"])[0]); x = float(q.get("x", ["0"])[0])
6553
+ if y > 80:
6554
+ body = json.dumps({"location": {"x": x, "y": y}, "value": "-1000000", "rasterSource": "", "resolution": 0})
6555
+ elif y < -80:
6556
+ body = b"<html>not json</html>"
6557
+ else:
6558
+ body = json.dumps({"location": {"x": x, "y": y, "spatialReference": {"wkid": 4326}}, "locationId": 0, "value": "2951.37",
6559
+ "rasterSource": "https://prd-tnm.s3.amazonaws.com/USGS_13_n32w104.tif", "resolution": 10})
6560
+ data = body.encode() if isinstance(body, str) else body
6561
+ self.send_response(200); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(data))); self.end_headers()
6562
+ self.wfile.write(data)
6563
+ srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), _EPQS)
6564
+ th = threading.Thread(target=srv.serve_forever, daemon=True); th.start()
6565
+ url = f"http://127.0.0.1:{srv.server_address[1]}/v1/json"
6566
+ try:
6567
+ r_ok = EL.epqs(lat1, lon1, url=url)
6568
+ r_nodata = EL.epqs(85.0, lon1, url=url)
6569
+ r_notjson = EL.epqs(-85.0, lon1, url=url)
6570
+ r_unreach = EL.epqs(lat1, lon1, url="http://127.0.0.1:9/v1/json", timeout=3)
6571
+ ref = EL.as_reference(r_ok)
6572
+ r_cli_el = _sp.run([sys.executable, "-m", "gea", "elevation", "--lat", str(lat1), "--lon", str(lon1), "--url", url], capture_output=True, text=True)
6573
+ r_set3 = _sp.run([sys.executable, "-m", "gea", "workspace", "--path", wsp, "--action", "set-well", "--well", w["id"], "--elev-3dep", "--epqs-url", url], capture_output=True, text=True)
6574
+ w3 = ws.well(w["id"])
6575
+ st4 = ws.well_interval(w["id"], actor="tester")
6576
+ finally:
6577
+ srv.shutdown(); srv.server_close()
6578
+ c3 = {
6579
+ "answer": r_ok["status"] == "OK" and r_ok["elev_ft"] == 2951.37 and r_ok["resolution_m"] == 10.0 and "USGS_13_n32w104" in r_ok["raster"]
6580
+ and r_ok["source"].startswith("USGS 3DEP EPQS, ") and "10 m raster" in r_ok["source"] and "NAVD88" in r_ok["vertical_datum"],
6581
+ "query_as_specified": seen and seen[0].get("wkid") == ["4326"] and seen[0].get("units") == ["Feet"] and abs(float(seen[0]["x"][0]) - lon1) < 1e-6 and abs(float(seen[0]["y"][0]) - lat1) < 1e-6,
6582
+ "failures_named": r_nodata["status"] == "NO DATA" and r_notjson["status"] == "NOT JSON" and r_unreach["status"] == "UNREACHABLE" and EL.as_reference(r_nodata) is None,
6583
+ "reference": ref == {"ft": 2951.37, "datum": "GL", "source": r_ok["source"], "vertical_datum": "NAVD88", "raster": r_ok["raster"], "queried_utc": r_ok["queried_utc"]},
6584
+ "cli": r_cli_el.returncode == 0 and "2,951.37 ft" in r_cli_el.stdout and "source: USGS 3DEP EPQS" in r_cli_el.stdout,
6585
+ "set_well": r_set3.returncode == 0 and "3DEP ground elevation 2,951.37 ft" in r_set3.stdout and w3["surface"]["elevation"]["datum"] == "GL"
6586
+ and w3["surface"]["elevation"]["ft"] == 2951.37 and "USGS 3DEP EPQS" in w3["surface"]["elevation"]["source"],
6587
+ "gl_note_in_statement": st4["status"] == "OK" and st4["declared"]["ref_datum"] == "GL" and any("kelly bushing" in n for n in st4["notes"])
6588
+ and st4["declared"]["top_subsea_ft"] == round(2951.37 - 5000.0, 1),
6589
+ }
6590
+ failed3 = [k for k, v in c3.items() if not v]
6591
+ ok(not failed3,
6592
+ "BP3 the 3DEP point service: the query as the service specifies it (x, y, wkid 4326, feet), the answer with its raster, resolution and query "
6593
+ "time as the source, the ground and NAVD88 named; no data, not JSON and unreachable each named; the answer as a reference elevation (datum GL); "
6594
+ "`gea elevation` and `set-well --elev-3dep` through the stand-in service; the statement then carries the kelly-bushing note"
6595
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
6596
+
6597
+
6339
6598
  def main() -> int:
6340
6599
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
6341
6600
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -6406,6 +6665,7 @@ def main() -> int:
6406
6665
  section_bm_month_end(tmp)
6407
6666
  section_bn_soak(tmp)
6408
6667
  section_bo_structure(tmp)
6668
+ section_bp_interval(tmp)
6409
6669
  if _FAILS:
6410
6670
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
6411
6671
  for f in _FAILS:
@@ -1664,6 +1664,39 @@ def sra_packet_report(pk: dict, evaluated_at: Optional[datetime] = None, program
1664
1664
  'outside a layer\'s extent is said to be outside it. A distance to a mapped trace is a fact of the map at that map\'s scale, not a hazard '
1665
1665
  'judgement; the layer\'s own metadata and the site\'s citation say where the map came from.'],
1666
1666
  [Table(['Well', 'Layer', 'Kind', 'Status', 'What the layer says'], srows, 'Mapped structure')]))
1667
+ irows = []
1668
+ for wid, iv in (pk.get('interval') or {}).items():
1669
+ d = iv.get('declared') or {}
1670
+ decl = (f"{d['top_ft']:,.0f}-{d['base_ft']:,.0f} ft {d['depth_reference']} below {d['ref_datum']} {d['ref_elev_ft']:,.0f} ft = "
1671
+ f"{d['top_subsea_ft']:,.0f} to {d['base_subsea_ft']:,.0f} ft subsea") if d else '-'
1672
+ tops = '; '.join(f"{t['relation']} {t['layer']}" + (f" ({t['bracket'][0]:,.0f}/{t['bracket'][1]:,.0f})" if t.get('bracket') else '') for t in iv.get('tops', [])) or '-'
1673
+ tier = iv.get('tier') or {}
1674
+ irows.append([wid, iv.get('status', '-'), decl, tops[:260], (f"{tier.get('status')}: {tier.get('detail', '')}"[:220] if tier else '-')])
1675
+ if irows:
1676
+ secs.append(Section('2c', 'The declared interval against the mapped tops', [
1677
+ 'For each well inside the area, the declared disposal interval - top and base in true vertical depth below the reference elevation the '
1678
+ 'operator stated - converted to subsea elevation and set beside each formation-top bracket the layers give at the well: ABOVE the mapped top, '
1679
+ 'BELOW it, or NOT RESOLVED where the bracket overlaps the interval. Where a formation has a top layer and an isopach layer, its base is the '
1680
+ 'arithmetic shown, and the declared depth tier (shallow or deep by the base the plan names) is CONSISTENT with the map, INCONSISTENT, or '
1681
+ 'NOT RESOLVED. A contour map is read, not interpolated; the completion report is the record of the interval.'],
1682
+ [Table(['Well', 'Status', 'Declared interval', 'Against the mapped tops', 'Depth tier against the map'], irows, 'Interval against the tops')]))
1683
+ ef = pk.get('events_faults') or []
1684
+ if ef:
1685
+ paras = ['The catalogue events inside the area, each against the mapped fault traces of every structure layer: the nearest trace to the epicentre by '
1686
+ 'distance and bearing, and the counts within 1, 2 and 5 km of any trace. An epicentre against a surface trace at the map\'s scale: the '
1687
+ 'hypocentral depth is not used, and proximity to a mapped trace is not an attribution of the event to it.']
1688
+ erows = []
1689
+ for lay in ef:
1690
+ for ln in lay.get('lines', [])[:2]:
1691
+ paras.append(f"{lay.get('layer_display') or lay.get('layer')}: {ln}")
1692
+ for e in lay.get('events', []):
1693
+ f_ = e.get('fault') or {}
1694
+ erows.append([lay.get('layer_display') or lay.get('layer'), e.get('event_id'), (e.get('time') or '')[:16].replace('T', ' '),
1695
+ (f"{e['magnitude']:.1f}" if e.get('magnitude') is not None else '-'), e.get('status', '-'),
1696
+ f_.get('name') or f_.get('type') or '-', (f"{f_['distance_km']:.2f}" if f_.get('distance_km') is not None else '-'),
1697
+ (f"{f_['bearing_deg']:.0f}" if f_.get('bearing_deg') is not None else '-')])
1698
+ secs.append(Section('2d', 'Catalogue events against the mapped faults', paras,
1699
+ [Table(['Layer', 'Event', 'Time (UTC)', 'M', 'Status', 'Nearest mapped fault', 'Distance (km)', 'Bearing'], erows[:200], 'Events against the faults')]))
1667
1700
  n = 3
1668
1701
  inside_wells = [w for w in pk['wells'] if w['inside'] and w.get('months')]
1669
1702
  for w in inside_wells:
@@ -0,0 +1,60 @@
1
+ # This Source Code Form is subject to the terms of the Mozilla Public
2
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ """elevation - the ground elevation of a position from the USGS 3D Elevation Program (3DEP).
5
+
6
+ The Elevation Point Query Service (EPQS) answers one position with the ground elevation from the best 3DEP
7
+ raster it holds, the raster it came from and that raster's resolution: a public, documented, no-key service at
8
+ https://epqs.nationalmap.gov/v1/json?x=<lon>&y=<lat>&wkid=4326&units=Feet . The elevation is orthometric
9
+ (NAVD88 in the conterminous United States) - sea level for the purposes of a subsea depth - and it is the
10
+ GROUND, not the kelly bushing: a depth measured from the KB is deeper by the KB height, which the completion
11
+ report states and this service does not. The program stores what the service answered, with its raster and
12
+ the time of the query, as the reference elevation's source; nothing is fetched unless asked.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import urllib.parse
18
+ import urllib.request
19
+ from datetime import datetime, timezone
20
+ from typing import Optional
21
+
22
+ EPQS_URL = 'https://epqs.nationalmap.gov/v1/json'
23
+ NO_DATA = -1000000.0
24
+
25
+
26
+ def epqs(lat: float, lon: float, url: str = EPQS_URL, timeout: float = 20.0, units: str = 'Feet') -> dict:
27
+ """One query to the EPQS: the ground elevation at (lat, lon), the raster, its resolution, the query time."""
28
+ q = urllib.parse.urlencode({'x': f'{float(lon):.6f}', 'y': f'{float(lat):.6f}', 'wkid': '4326', 'units': units, 'includeDate': 'false'})
29
+ full = url + ('&' if '?' in url else '?') + q
30
+ req = urllib.request.Request(full, headers={'Accept': 'application/json', 'User-Agent': 'gea-program'})
31
+ t0 = datetime.now(timezone.utc)
32
+ try:
33
+ with urllib.request.urlopen(req, timeout=timeout) as r:
34
+ raw = r.read()
35
+ except Exception as e: # noqa: BLE001 - every failure is reported the same way
36
+ return {'status': 'UNREACHABLE', 'detail': f'{type(e).__name__}: {e}', 'url': full, 'queried_utc': t0.isoformat()}
37
+ try:
38
+ d = json.loads(raw.decode('utf-8'))
39
+ except Exception: # noqa: BLE001
40
+ return {'status': 'NOT JSON', 'detail': raw[:120].decode('utf-8', 'replace'), 'url': full, 'queried_utc': t0.isoformat()}
41
+ v = d.get('value')
42
+ try:
43
+ val = float(v)
44
+ except (TypeError, ValueError):
45
+ return {'status': 'NO VALUE', 'detail': f'the service answered without an elevation: {str(d)[:200]}', 'url': full, 'queried_utc': t0.isoformat()}
46
+ if val <= NO_DATA + 1 or val < -1500 or val > 30000:
47
+ return {'status': 'NO DATA', 'detail': f'the service holds no elevation at this position (value {val:g})', 'url': full, 'queried_utc': t0.isoformat()}
48
+ res = d.get('resolution')
49
+ out = {'status': 'OK', 'elev_ft': round(val, 2) if units == 'Feet' else round(val * 3.280839895, 2), 'units_answered': units,
50
+ 'raster': d.get('rasterSource') or d.get('raster') or '', 'resolution_m': (float(res) if isinstance(res, (int, float, str)) and str(res).replace('.', '', 1).isdigit() else None),
51
+ 'vertical_datum': 'NAVD88 (orthometric; the ground, not the kelly bushing)', 'url': full, 'queried_utc': t0.isoformat()}
52
+ out['source'] = f"USGS 3DEP EPQS, {out['queried_utc'][:10]}" + (f", {out['resolution_m']:g} m raster" if out['resolution_m'] else '') + (f" ({out['raster']})" if out['raster'] else '')
53
+ return out
54
+
55
+
56
+ def as_reference(res: dict) -> Optional[dict]:
57
+ """The EPQS answer as a well's reference elevation record (datum GL, the source stated), or None."""
58
+ if res.get('status') != 'OK':
59
+ return None
60
+ return {'ft': res['elev_ft'], 'datum': 'GL', 'source': res['source'], 'vertical_datum': 'NAVD88', 'raster': res.get('raster', ''), 'queried_utc': res['queried_utc']}
@@ -0,0 +1,11 @@
1
+ # The interval against the mapped tops
2
+
3
+ > The second geological statement this program makes about a well, and the first question a reviewer of a disposal well in a seismic response area asks: where does the injection interval sit against the mapped geology? The operator declares the interval and the elevation it is measured from; the formation-top layers the site brought give a bracket at the well; the program puts the two side by side and says ABOVE, BELOW, or NOT RESOLVED - and whether the declared depth tier agrees with the mapped base of the formation the plan names.
4
+
5
+ **The command**: `gea workspace --path C:\site --action set-well --well <id> --top-interval-ft 9850 --base-interval-ft 10400 --depth-ref TVD --ref-elev-ft 2962 --ref-elev-datum KB --ref-elev-source "completion report W-2"` declares the interval in true vertical depth below the kelly bushing at the elevation the completion report states. `--elev-3dep` instead fetches the GROUND elevation at the declared surface position from the USGS 3D Elevation Program point service (`gea elevation --lat --lon` does the same for any position) and stores it as the reference with datum GL and the service's raster as the source. Layers are told what they map: `--action add-layer --name "EIA Wolfcamp top" --layer Wolfcamp_Elevation_Isopachs.zip --member Wolfcamp_Formation_Elevation_201712 --formation-name Wolfcamp --role top`, and the isopach the same way with `--role isopach`. Then `--action interval --well <id>` writes the statement; the well page's "Interval against the mapped tops" card and the SRA packet's section 2c carry it, and section 2d sets the catalogue's events against the mapped faults.
6
+
7
+ **What it writes**: the declared interval converted to subsea elevation - reference elevation minus depth, so 9,850 ft below a KB at 2,962 ft is -6,888 ft subsea - and, for each contours layer with a bracket at the well, whether the interval is ABOVE the mapped top (its base shallower than the shallowest the top can be), BELOW it (its top deeper than the deepest the top can be), or NOT RESOLVED (the bracket overlaps the interval). For a formation with a top layer and an isopach layer, the base bracket by the arithmetic shown - top minus thickness, both as brackets - and the declared depth tier against it: CONSISTENT, INCONSISTENT, or NOT RESOLVED. For the catalogue events inside the SRA, the nearest mapped fault trace to each epicentre with its distance and bearing, and the counts within 1, 2 and 5 km. Written to `reports/wells/<id>/interval.json` and audited as `well.interval`.
8
+
9
+ **The number to check**: the subsea top and base of the interval, and the status beside each top - and the tier line: `CONSISTENT`, `INCONSISTENT`, or `NOT RESOLVED` against the mapped base. `NOT DECLARED` names what is missing.
10
+
11
+ **What this page will not call a measurement**: a depth between two contours - a bracket is two contours, never a value between them, and an interval the bracket overlaps is NOT RESOLVED, not placed. A kelly-bushing depth from a ground elevation - 3DEP answers the ground; the KB is higher by a height only the completion report states, and the page says so whenever the reference is GL. A measured depth - this program converts true vertical depth only; a deviated well's MD needs its survey first. The tier being right - the statement is whether the declaration and the map agree; the completion report is the record. An event's cause - an epicentre near a mapped trace at the map's scale is a distance, not an attribution, and the hypocentral depth is not used.