gea-program 0.29.0__tar.gz → 0.30.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 (277) hide show
  1. {gea_program-0.29.0/gea_program.egg-info → gea_program-0.30.0}/PKG-INFO +21 -2
  2. {gea_program-0.29.0 → gea_program-0.30.0}/README.md +20 -1
  3. {gea_program-0.29.0 → gea_program-0.30.0}/gea/__init__.py +1 -1
  4. {gea_program-0.29.0 → gea_program-0.30.0}/gea/__main__.py +28 -1
  5. {gea_program-0.29.0 → gea_program-0.30.0}/gea/acceptance_tests.py +178 -5
  6. {gea_program-0.29.0 → gea_program-0.30.0}/gea/dashboard.py +22 -2
  7. gea_program-0.30.0/gea/edr.py +539 -0
  8. {gea_program-0.29.0 → gea_program-0.30.0}/gea/files.py +6 -1
  9. gea_program-0.30.0/gea/help/edr.md +11 -0
  10. {gea_program-0.29.0 → gea_program-0.30.0}/gea/helplib.py +1 -0
  11. {gea_program-0.29.0 → gea_program-0.30.0}/gea/ports.py +16 -1
  12. {gea_program-0.29.0 → gea_program-0.30.0}/gea/service.py +5 -1
  13. {gea_program-0.29.0 → gea_program-0.30.0}/gea/survey_cmd.py +15 -2
  14. {gea_program-0.29.0 → gea_program-0.30.0}/gea/web/app.html +18 -0
  15. {gea_program-0.29.0 → gea_program-0.30.0/gea_program.egg-info}/PKG-INFO +21 -2
  16. {gea_program-0.29.0 → gea_program-0.30.0}/gea_program.egg-info/SOURCES.txt +2 -0
  17. {gea_program-0.29.0 → gea_program-0.30.0}/pyproject.toml +1 -1
  18. {gea_program-0.29.0 → gea_program-0.30.0}/LICENSE +0 -0
  19. {gea_program-0.29.0 → gea_program-0.30.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  20. {gea_program-0.29.0 → gea_program-0.30.0}/gea/accuracy_statement.py +0 -0
  21. {gea_program-0.29.0 → gea_program-0.30.0}/gea/alarm_engine.py +0 -0
  22. {gea_program-0.29.0 → gea_program-0.30.0}/gea/backup.py +0 -0
  23. {gea_program-0.29.0 → gea_program-0.30.0}/gea/bench.py +0 -0
  24. {gea_program-0.29.0 → gea_program-0.30.0}/gea/blind_harness.py +0 -0
  25. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  26. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  27. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  28. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  29. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  30. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  31. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  32. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  33. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  34. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  35. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  36. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  37. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  38. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  39. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  40. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  41. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  42. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  43. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  44. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  45. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  46. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  47. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  48. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  49. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  50. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  51. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  52. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  53. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  54. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  55. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  56. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  57. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  58. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  59. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  60. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  61. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  62. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  63. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  64. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  65. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  66. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  67. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  68. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  69. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  70. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  71. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  72. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  73. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  74. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  75. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  76. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  77. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  78. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  79. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  80. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  81. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  82. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  83. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  84. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  85. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  86. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  87. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  88. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  89. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  90. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  91. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  92. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/med_160_sapropels.txt +0 -0
  93. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  94. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  95. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  96. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  97. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  98. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  99. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  100. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  101. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  102. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  103. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  104. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  105. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  106. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  107. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  108. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  109. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  110. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  111. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  112. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  113. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  114. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  115. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  116. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  117. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  118. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  119. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  120. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  121. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  122. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  123. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  124. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  125. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  126. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  127. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  128. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  129. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog_map.py +0 -0
  130. {gea_program-0.29.0 → gea_program-0.30.0}/gea/catalog_register.py +0 -0
  131. {gea_program-0.29.0 → gea_program-0.30.0}/gea/certificates.py +0 -0
  132. {gea_program-0.29.0 → gea_program-0.30.0}/gea/cli.py +0 -0
  133. {gea_program-0.29.0 → gea_program-0.30.0}/gea/client_reports.py +0 -0
  134. {gea_program-0.29.0 → gea_program-0.30.0}/gea/config_versioning.py +0 -0
  135. {gea_program-0.29.0 → gea_program-0.30.0}/gea/conformance.py +0 -0
  136. {gea_program-0.29.0 → gea_program-0.30.0}/gea/correlation.py +0 -0
  137. {gea_program-0.29.0 → gea_program-0.30.0}/gea/deviation.py +0 -0
  138. {gea_program-0.29.0 → gea_program-0.30.0}/gea/dlis.py +0 -0
  139. {gea_program-0.29.0 → gea_program-0.30.0}/gea/dnp3.py +0 -0
  140. {gea_program-0.29.0 → gea_program-0.30.0}/gea/doctor.py +0 -0
  141. {gea_program-0.29.0 → gea_program-0.30.0}/gea/downhole_engine.py +0 -0
  142. {gea_program-0.29.0 → gea_program-0.30.0}/gea/drift_monitor.py +0 -0
  143. {gea_program-0.29.0 → gea_program-0.30.0}/gea/earth_model.py +0 -0
  144. {gea_program-0.29.0 → gea_program-0.30.0}/gea/elevation.py +0 -0
  145. {gea_program-0.29.0 → gea_program-0.30.0}/gea/etp.py +0 -0
  146. {gea_program-0.29.0 → gea_program-0.30.0}/gea/etp12_schemas.json +0 -0
  147. {gea_program-0.29.0 → gea_program-0.30.0}/gea/example_register_map.json +0 -0
  148. {gea_program-0.29.0 → gea_program-0.30.0}/gea/fat_sat.py +0 -0
  149. {gea_program-0.29.0 → gea_program-0.30.0}/gea/fixtures/README.md +0 -0
  150. {gea_program-0.29.0 → gea_program-0.30.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  151. {gea_program-0.29.0 → gea_program-0.30.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  152. {gea_program-0.29.0 → gea_program-0.30.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  153. {gea_program-0.29.0 → gea_program-0.30.0}/gea/follower.py +0 -0
  154. {gea_program-0.29.0 → gea_program-0.30.0}/gea/forward_model.py +0 -0
  155. {gea_program-0.29.0 → gea_program-0.30.0}/gea/friction_priors.py +0 -0
  156. {gea_program-0.29.0 → gea_program-0.30.0}/gea/gamma.py +0 -0
  157. {gea_program-0.29.0 → gea_program-0.30.0}/gea/gauge_aging.py +0 -0
  158. {gea_program-0.29.0 → gea_program-0.30.0}/gea/gauge_specs.py +0 -0
  159. {gea_program-0.29.0 → gea_program-0.30.0}/gea/geodesy.py +0 -0
  160. {gea_program-0.29.0 → gea_program-0.30.0}/gea/gravity_reference.py +0 -0
  161. {gea_program-0.29.0 → gea_program-0.30.0}/gea/hartip.py +0 -0
  162. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/TESTER_GUIDE.md +0 -0
  163. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/alarms.md +0 -0
  164. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/audit-update.md +0 -0
  165. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/backup.md +0 -0
  166. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/catalog.md +0 -0
  167. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/conformance.md +0 -0
  168. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/data-in.md +0 -0
  169. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/dlis.md +0 -0
  170. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/doctor.md +0 -0
  171. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/drift.md +0 -0
  172. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/etp.md +0 -0
  173. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/field-ports.md +0 -0
  174. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/files.md +0 -0
  175. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/historian.md +0 -0
  176. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/instruments.md +0 -0
  177. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/interval.md +0 -0
  178. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/month-end.md +0 -0
  179. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/notifications.md +0 -0
  180. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/osdu.md +0 -0
  181. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/patches.md +0 -0
  182. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/ppdm.md +0 -0
  183. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/quakeml.md +0 -0
  184. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/quality.md +0 -0
  185. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/seedlink.md +0 -0
  186. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/seismic.md +0 -0
  187. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/site.md +0 -0
  188. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/sites.md +0 -0
  189. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/slip.md +0 -0
  190. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/soak.md +0 -0
  191. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/sra.md +0 -0
  192. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/start.md +0 -0
  193. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/structure.md +0 -0
  194. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/tools.md +0 -0
  195. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/transients.md +0 -0
  196. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/upkeep.md +0 -0
  197. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/vibration.md +0 -0
  198. {gea_program-0.29.0 → gea_program-0.30.0}/gea/help/well-tests.md +0 -0
  199. {gea_program-0.29.0 → gea_program-0.30.0}/gea/housekeeping.py +0 -0
  200. {gea_program-0.29.0 → gea_program-0.30.0}/gea/interval.py +0 -0
  201. {gea_program-0.29.0 → gea_program-0.30.0}/gea/inverse_engine.py +0 -0
  202. {gea_program-0.29.0 → gea_program-0.30.0}/gea/jobs.py +0 -0
  203. {gea_program-0.29.0 → gea_program-0.30.0}/gea/live_ports.py +0 -0
  204. {gea_program-0.29.0 → gea_program-0.30.0}/gea/loadtest.py +0 -0
  205. {gea_program-0.29.0 → gea_program-0.30.0}/gea/matplotlib_demo.py +0 -0
  206. {gea_program-0.29.0 → gea_program-0.30.0}/gea/modbus.py +0 -0
  207. {gea_program-0.29.0 → gea_program-0.30.0}/gea/modbus_rtu.py +0 -0
  208. {gea_program-0.29.0 → gea_program-0.30.0}/gea/model_card.py +0 -0
  209. {gea_program-0.29.0 → gea_program-0.30.0}/gea/month_end.py +0 -0
  210. {gea_program-0.29.0 → gea_program-0.30.0}/gea/mqtt_port.py +0 -0
  211. {gea_program-0.29.0 → gea_program-0.30.0}/gea/notify.py +0 -0
  212. {gea_program-0.29.0 → gea_program-0.30.0}/gea/opcua_port.py +0 -0
  213. {gea_program-0.29.0 → gea_program-0.30.0}/gea/operator_app.py +0 -0
  214. {gea_program-0.29.0 → gea_program-0.30.0}/gea/osdu.py +0 -0
  215. {gea_program-0.29.0 → gea_program-0.30.0}/gea/patches.py +0 -0
  216. {gea_program-0.29.0 → gea_program-0.30.0}/gea/permits.py +0 -0
  217. {gea_program-0.29.0 → gea_program-0.30.0}/gea/piwebapi.py +0 -0
  218. {gea_program-0.29.0 → gea_program-0.30.0}/gea/ppdm.py +0 -0
  219. {gea_program-0.29.0 → gea_program-0.30.0}/gea/profile_catalog.py +0 -0
  220. {gea_program-0.29.0 → gea_program-0.30.0}/gea/project.py +0 -0
  221. {gea_program-0.29.0 → gea_program-0.30.0}/gea/qt6_downhole_app.py +0 -0
  222. {gea_program-0.29.0 → gea_program-0.30.0}/gea/quakeml.py +0 -0
  223. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reconciler.py +0 -0
  224. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  225. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  226. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  227. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  228. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  229. {gea_program-0.29.0 → gea_program-0.30.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  230. {gea_program-0.29.0 → gea_program-0.30.0}/gea/rock_inventory.py +0 -0
  231. {gea_program-0.29.0 → gea_program-0.30.0}/gea/sample_record.py +0 -0
  232. {gea_program-0.29.0 → gea_program-0.30.0}/gea/sample_well_profile.csv +0 -0
  233. {gea_program-0.29.0 → gea_program-0.30.0}/gea/sbom.py +0 -0
  234. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seedlink.py +0 -0
  235. {gea_program-0.29.0 → gea_program-0.30.0}/gea/segy.py +0 -0
  236. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic.py +0 -0
  237. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_array.py +0 -0
  238. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_assoc.py +0 -0
  239. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_detect.py +0 -0
  240. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_film.py +0 -0
  241. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_harmonic.py +0 -0
  242. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_qc.py +0 -0
  243. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_response.py +0 -0
  244. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_signature.py +0 -0
  245. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_track.py +0 -0
  246. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_uncertainty.py +0 -0
  247. {gea_program-0.29.0 → gea_program-0.30.0}/gea/seismic_unlisted.py +0 -0
  248. {gea_program-0.29.0 → gea_program-0.30.0}/gea/sensor_swap.py +0 -0
  249. {gea_program-0.29.0 → gea_program-0.30.0}/gea/service_life.py +0 -0
  250. {gea_program-0.29.0 → gea_program-0.30.0}/gea/shapefile.py +0 -0
  251. {gea_program-0.29.0 → gea_program-0.30.0}/gea/shell.py +0 -0
  252. {gea_program-0.29.0 → gea_program-0.30.0}/gea/shut_in.py +0 -0
  253. {gea_program-0.29.0 → gea_program-0.30.0}/gea/sla_report.py +0 -0
  254. {gea_program-0.29.0 → gea_program-0.30.0}/gea/slip.py +0 -0
  255. {gea_program-0.29.0 → gea_program-0.30.0}/gea/soak.py +0 -0
  256. {gea_program-0.29.0 → gea_program-0.30.0}/gea/sra.py +0 -0
  257. {gea_program-0.29.0 → gea_program-0.30.0}/gea/store_forward.py +0 -0
  258. {gea_program-0.29.0 → gea_program-0.30.0}/gea/strata_join.py +0 -0
  259. {gea_program-0.29.0 → gea_program-0.30.0}/gea/structure.py +0 -0
  260. {gea_program-0.29.0 → gea_program-0.30.0}/gea/supervisor.py +0 -0
  261. {gea_program-0.29.0 → gea_program-0.30.0}/gea/survey_view.py +0 -0
  262. {gea_program-0.29.0 → gea_program-0.30.0}/gea/telemetry.py +0 -0
  263. {gea_program-0.29.0 → gea_program-0.30.0}/gea/tool_catalog.py +0 -0
  264. {gea_program-0.29.0 → gea_program-0.30.0}/gea/tool_library.py +0 -0
  265. {gea_program-0.29.0 → gea_program-0.30.0}/gea/transient.py +0 -0
  266. {gea_program-0.29.0 → gea_program-0.30.0}/gea/vibration.py +0 -0
  267. {gea_program-0.29.0 → gea_program-0.30.0}/gea/well_assembler.py +0 -0
  268. {gea_program-0.29.0 → gea_program-0.30.0}/gea/well_test_validation.py +0 -0
  269. {gea_program-0.29.0 → gea_program-0.30.0}/gea/wits0.py +0 -0
  270. {gea_program-0.29.0 → gea_program-0.30.0}/gea/witsml.py +0 -0
  271. {gea_program-0.29.0 → gea_program-0.30.0}/gea/workspace.py +0 -0
  272. {gea_program-0.29.0 → gea_program-0.30.0}/gea_program.egg-info/dependency_links.txt +0 -0
  273. {gea_program-0.29.0 → gea_program-0.30.0}/gea_program.egg-info/entry_points.txt +0 -0
  274. {gea_program-0.29.0 → gea_program-0.30.0}/gea_program.egg-info/requires.txt +0 -0
  275. {gea_program-0.29.0 → gea_program-0.30.0}/gea_program.egg-info/top_level.txt +0 -0
  276. {gea_program-0.29.0 → gea_program-0.30.0}/setup.cfg +0 -0
  277. {gea_program-0.29.0 → gea_program-0.30.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.29.0
3
+ Version: 0.30.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 (373 checks)
128
+ gea accept # the product gate (376 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]")
@@ -717,6 +717,25 @@ equal to a fresh read of the files. `gea catalog --positions`, `gea catalog
717
717
  `--check-positions`. A mark is the coordinate the record states, not a
718
718
  position this program measured.
719
719
 
720
+ ### The drilling recorder
721
+
722
+ A rig's electronic drilling recorder exports one row per second: the date
723
+ and the time in two columns, then the WITS record-1 channels with their
724
+ units in the header; `-999.25` or an empty cell where it had nothing; the
725
+ file stopping while the rig is off the well. The `edr` port reads that
726
+ layout as delivered - streamed, three million rows in under a minute - and
727
+ writes the **drilling record**: per section the hours, the gap before, the
728
+ hole depth where drilling began and the deepest it got, feet drilled, hours
729
+ on bottom and rotating, the rate of penetration on bottom, the connections,
730
+ the peaks; for the well, total depth and when the bit was first on bottom
731
+ there, the recorder's TVD at it; per channel what was missing and how
732
+ often; and the **MD-TVD table**, hole depth against the recorder's
733
+ interpolated TVD, labelled an interpolation and not a survey. A recorder
734
+ that resets its hole depth at a restart is named, not corrected. `gea edr
735
+ --file C:\site\_incoming\30636033.csv`; `gea workspace --action add-file`
736
+ then `refresh` for the record beside the well's other reports. `gea help
737
+ edr`.
738
+
720
739
  ### The log as delivered, and the historian
721
740
 
722
741
  A well log arrives as DLIS (API RP66 V1); `gea/dlis.py` reads the container
@@ -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 (373 checks)
89
+ gea accept # the product gate (376 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]")
@@ -678,6 +678,25 @@ equal to a fresh read of the files. `gea catalog --positions`, `gea catalog
678
678
  `--check-positions`. A mark is the coordinate the record states, not a
679
679
  position this program measured.
680
680
 
681
+ ### The drilling recorder
682
+
683
+ A rig's electronic drilling recorder exports one row per second: the date
684
+ and the time in two columns, then the WITS record-1 channels with their
685
+ units in the header; `-999.25` or an empty cell where it had nothing; the
686
+ file stopping while the rig is off the well. The `edr` port reads that
687
+ layout as delivered - streamed, three million rows in under a minute - and
688
+ writes the **drilling record**: per section the hours, the gap before, the
689
+ hole depth where drilling began and the deepest it got, feet drilled, hours
690
+ on bottom and rotating, the rate of penetration on bottom, the connections,
691
+ the peaks; for the well, total depth and when the bit was first on bottom
692
+ there, the recorder's TVD at it; per channel what was missing and how
693
+ often; and the **MD-TVD table**, hole depth against the recorder's
694
+ interpolated TVD, labelled an interpolation and not a survey. A recorder
695
+ that resets its hole depth at a restart is named, not corrected. `gea edr
696
+ --file C:\site\_incoming\30636033.csv`; `gea workspace --action add-file`
697
+ then `refresh` for the record beside the well's other reports. `gea help
698
+ edr`.
699
+
681
700
  ### The log as delivered, and the historian
682
701
 
683
702
  A well log arrives as DLIS (API RP66 V1); `gea/dlis.py` reads the container
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.29.0"
126
+ __version__ = "0.30.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -306,6 +306,8 @@ def main(argv=None) -> int:
306
306
  p_cat.add_argument("--positions", action="store_true", help="where every entry was drilled, from its own file or sidecar, or NO POSITION")
307
307
  p_cat.add_argument("--map", type=str, default=None, metavar="OUT.SVG", help="write the world map of the catalogue (coastline + one mark per positioned entry + the key) as an SVG file")
308
308
  p_cat.add_argument("--check-positions", action="store_true", help="the sidecars' recorded positions against a fresh read of the files (the gate's check)")
309
+ p_ed = sub.add_parser("edr", help="an electronic drilling recorder's one-second export (date and time columns, WITS record-1 channels): --file reads it and prints the drilling record; --out DIR writes the record (json, md, html) and the MD-TVD table; --selftest proves the port on a synthetic export")
310
+ p_ed.add_argument("--file", type=str, default=None); p_ed.add_argument("--out", type=str, default=None); p_ed.add_argument("--json", action="store_true"); p_ed.add_argument("--selftest", action="store_true")
309
311
  p_dl = sub.add_parser("dlis", help="a DLIS (RP66 V1) well log: --info describes it (origin, channels, frames, what was skipped); --frame --out writes the frame as a historian-style CSV; --to-las writes LAS 2.0")
310
312
  p_dl.add_argument("--file", type=str, required=True)
311
313
  p_dl.add_argument("--info", action="store_true")
@@ -768,7 +770,10 @@ def main(argv=None) -> int:
768
770
  f"(uptime {s['uptime_pct']}%, stuck P/R {s['stuck_score']['precision']}/{s['stuck_score']['recall']})")
769
771
  elif a.cmd == "ingest":
770
772
  from . import ingest as _ingest
771
- st = _ingest(a.file, port=a.port)
773
+ try:
774
+ st = _ingest(a.file, port=a.port)
775
+ except (ValueError, OSError) as e:
776
+ raise SystemExit(f"ingest: {e}")
772
777
  import json as _json
773
778
  print(_json.dumps(st.summary(), indent=1))
774
779
  elif a.cmd == "bench":
@@ -2521,6 +2526,28 @@ def main(argv=None) -> int:
2521
2526
  if a.unused:
2522
2527
  print(f"\n {_CR.NO_BAND_REASON}")
2523
2528
  return 0
2529
+ elif a.cmd == "edr":
2530
+ import json as _json
2531
+ from . import edr as _EDR
2532
+ from . import __version__ as _ver
2533
+ if a.selftest:
2534
+ import tempfile as _tf
2535
+ with _tf.TemporaryDirectory() as _t:
2536
+ r = _EDR.selftest(_t)
2537
+ print(_json.dumps(r["record"], indent=1, default=str) if a.json else "\n".join(r["record"]["lines"])); return 0
2538
+ if not a.file:
2539
+ raise SystemExit("edr needs --file C:\\site\\_incoming\\30636033.csv (an EDR export), or --selftest")
2540
+ try:
2541
+ st = _EDR.read(a.file)
2542
+ except ValueError as e:
2543
+ raise SystemExit(f"edr: {e}")
2544
+ if a.out:
2545
+ paths = _EDR.write_record(st, a.out, _ver)
2546
+ rec = paths.pop("record")
2547
+ print("\n".join(rec["lines"]))
2548
+ print("written:", ", ".join(os.path.basename(v) for k, v in paths.items())); return 0
2549
+ rec = _EDR.record(st)
2550
+ print(_json.dumps(rec, indent=1, default=str) if a.json else "\n".join(rec["lines"])); return 0
2524
2551
  elif a.cmd == "dlis":
2525
2552
  import json as _json
2526
2553
  from .dlis import DlisError, DlisFile, read as _read_dlis
@@ -7543,7 +7543,9 @@ def section_bw_catalog_map(tmp: str) -> None:
7543
7543
  r_pos = _sp.run([sys.executable, "-m", "gea", "catalog", "--positions"], capture_output=True, text=True)
7544
7544
  r_json = _sp.run([sys.executable, "-m", "gea", "catalog", "--positions", "--json"], capture_output=True, text=True)
7545
7545
  r_chk = _sp.run([sys.executable, "-m", "gea", "catalog", "--check-positions"], capture_output=True, text=True)
7546
- r_tool = _sp.run([sys.executable, str(pkg.parent / "tools" / "catalog_positions.py")], capture_output=True, text=True)
7546
+ tool = pkg.parent / "tools" / "catalog_positions.py" # a checkout has it; an installed kit does not, and says so
7547
+ r_tool = _sp.run([sys.executable, str(tool)], capture_output=True, text=True) if tool.is_file() else None
7548
+ tool_ok = (r_tool.returncode == 0 and "all already current" in r_tool.stdout) if r_tool is not None else (CM.write_sidecars() == [])
7547
7549
  c3 = {
7548
7550
  "api": st_anon == 401 and st_m == 200 and len(m["entries"]) == 52 and m["counts"]["positioned"] == 44 and len(m["coast"]) == 134 and m["check"]["ok"]
7549
7551
  and all("register" in r and "well" in r for r in m["entries"]) and st_e == 200 and e["provenance"]["position"]["grid"]["zone"] == 4
@@ -7553,7 +7555,7 @@ def section_bw_catalog_map(tmp: str) -> None:
7553
7555
  "cli": r_map.returncode == 0 and "44 of 52 entries on 33 marks" in r_map.stdout and Path(out_svg).read_text(encoding="utf-8").count("<circle") == 33
7554
7556
  and r_pos.returncode == 0 and "44 of 52 entries positioned" in r_pos.stdout and "NO POSITION" in r_pos.stdout and "Mt Eba" in r_pos.stdout
7555
7557
  and r_json.returncode == 0 and json.loads(r_json.stdout)["counts"]["no_position"] == 8
7556
- and r_chk.returncode == 0 and "match a fresh read" in r_chk.stdout and r_tool.returncode == 0 and "all already current" in r_tool.stdout,
7558
+ and r_chk.returncode == 0 and "match a fresh read" in r_chk.stdout and tool_ok,
7557
7559
  "help": not H.check("catalog") and H.VIEW_TOPIC.get("catalog") == "catalog" and "--map" in help_md and "NO POSITION" in help_md
7558
7560
  and "Natural Earth" in help_md and "not a position" in help_md.split("will not call a measurement")[1],
7559
7561
  }
@@ -7561,7 +7563,7 @@ def section_bw_catalog_map(tmp: str) -> None:
7561
7563
  ok(not failed3,
7562
7564
  "BW3 the Catalogue tab: the API refuses an anonymous caller and answers a viewer with the entries, the coastline and the sidecar check, one entry's "
7563
7565
  "full provenance with its grid record, an unknown entry 404; the page carries the tab, the marks and the entry card; `gea catalog --map`, "
7564
- "`--positions` (and `--json`), `--check-positions` and the sidecar tool; the help page says what a mark is not"
7566
+ "`--positions` (and `--json`), `--check-positions` and the sidecar tool (in a checkout; an installed kit rewrites nothing); the help page says what a mark is not"
7565
7567
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
7566
7568
 
7567
7569
 
@@ -7626,7 +7628,8 @@ def section_bx_pasteable(tmp: str) -> None:
7626
7628
  for ln in md.read_text(encoding="utf-8").splitlines():
7627
7629
  if ln.startswith("**The command**") and ph.search(ln):
7628
7630
  bad.append(f"{md.name}: {ph.search(ln).group(0)}")
7629
- readme = (pkg.parent / "README.md").read_text(encoding="utf-8")
7631
+ readme_path = pkg.parent / "README.md" # a checkout; an installed kit carries no README beside the package
7632
+ readme = readme_path.read_text(encoding="utf-8") if readme_path.is_file() else ""
7630
7633
  for ln in readme.splitlines():
7631
7634
  if re.search(r"(^|`)(python -m )?gea [a-z-]", ln) and ph.search(ln) and not ln.lstrip().startswith("|"):
7632
7635
  bad.append(f"README: {ph.search(ln).group(0)}")
@@ -7639,16 +7642,185 @@ def section_bx_pasteable(tmp: str) -> None:
7639
7642
  for m in re.finditer(r'class="mono">([^<]*gea [^<]*)<', page):
7640
7643
  if "&lt;" in m.group(1):
7641
7644
  bad.append("app.html: " + m.group(1)[:60])
7642
- c2 = {"none_left": not bad, "readme_real": "--well SWD-1" in readme and "collingwood_1_28_ks_complete" in readme,
7645
+ c2 = {"none_left": not bad, "readme_real": (not readme) or ("--well SWD-1" in readme and "collingwood_1_28_ks_complete" in readme),
7643
7646
  "help_real": "--well SWD-1" in (pkg / "help" / "interval.md").read_text(encoding="utf-8") and "9850:quartz_pt_geoq177_16k" in (pkg / "help" / "tools.md").read_text(encoding="utf-8"),
7644
7647
  "usage_metavar": "needs --well WELL_ID" in (pkg / "__main__.py").read_text(encoding="utf-8")}
7645
7648
  failed2 = [k for k, v in c2.items() if not v]
7646
7649
  ok(not failed2,
7647
7650
  "BX2 no command line a site operator would paste carries a <placeholder>: the help pages' command lines, the README's commands, the usage "
7648
7651
  "messages, the doctor's fixes and the page's hints use real example values (SWD-1, a catalogue entry, a tool name) or an uppercase metavar"
7652
+ + ("" if readme else " (the README is read in a checkout; this is an installed kit)")
7649
7653
  + (f" [failed: {', '.join(failed2)}]" if failed2 else "") + (f" [left: {'; '.join(bad[:8])}]" if bad else ""))
7650
7654
 
7651
7655
 
7656
+ def section_by_edr(tmp: str) -> None:
7657
+ """Section BY - the drilling recorder's export read as delivered: the layout, the sentinels and blanks, the sections,
7658
+ the drilling record and the MD-TVD table; the historian port's refusal; the workspace, the API, the page, the CLI,
7659
+ the help. Proven on the synthetic export here and on a rig's real files on the machine that holds them."""
7660
+ import http.cookiejar
7661
+ import subprocess as _sp
7662
+ import urllib.request
7663
+ import urllib.error
7664
+ from . import edr as E
7665
+ from . import helplib as H
7666
+ from . import tool_catalog as TC
7667
+ from .files import detect, read_any
7668
+ from .ports import PORT_REGISTRY, read_historian_csv
7669
+ from .workspace import Workspace
7670
+ from .service import Service, Users
7671
+ pkg = Path(__file__).parent
7672
+ d = Path(tmp, "by"); d.mkdir()
7673
+
7674
+ r = E.selftest(str(d))
7675
+ st, rec = r["stream"], r["record"]
7676
+ stream = E.read(r["path"])
7677
+ secs = rec["sections"]
7678
+ refusal = None
7679
+ try:
7680
+ read_historian_csv(r["path"])
7681
+ except ValueError as e:
7682
+ refusal = str(e)
7683
+ not_edr = str(d / "plain.csv"); Path(not_edr).write_text("t,p\n0,1\n1,2\n", encoding="utf-8")
7684
+ bad = None
7685
+ try:
7686
+ E.read(not_edr)
7687
+ except ValueError as e:
7688
+ bad = str(e)
7689
+ ch = {c["channel"]: c for c in rec["channels"]}
7690
+ c1 = {
7691
+ "layout": st["source_format"] == "edr" and st["index_kind"] == "time_s" and st["samples"] == 9000 and st["channels"]["Hole Depth"]["unit"] == "ft"
7692
+ and st["channels"]["Rate Of Penetration"]["unit"] == "ft/h" and st["channels"]["Weight on Bit"]["unit"] == "klbf" and st["channels"]["On Bottom"]["unit"] == ""
7693
+ and st["channels"]["Gamma"]["unit"] == "gAPI" and st["meta"]["start_time"] == "2026-06-01T00:00:00" and detect(r["path"])["kind"] == "edr"
7694
+ and E.is_edr(r["path"]) and not E.is_edr(not_edr) and bad and "not an EDR export" in bad,
7695
+ "missing": ch["Gamma"]["sentinel"] == 3000 and ch["Gamma"]["blank"] == 0 and ch["Interpolated TVD"]["blank"] == 900 and ch["Interpolated TVD"]["sentinel"] == 0
7696
+ and abs(ch["Gamma"]["coverage_pct"] - 66.67) < 0.01 and ch["Hole Depth"]["coverage_pct"] == 100.0,
7697
+ "sections": rec["n_sections"] == 3 and all(x["rows"] == 3000 and x["hours"] == 0.83 for x in secs) and secs[0]["gap_before_hours"] is None
7698
+ and secs[1]["gap_before_hours"] == 48.0 and secs[2]["gap_before_hours"] == 48.0 and all(x["connections"] == 25 for x in secs)
7699
+ and all(x["rop_on_bottom_mean_ft_h"] == 600.0 and x["on_bottom_hours"] == 0.69 and x["rotating_hours"] == 0.69 for x in secs)
7700
+ and secs[0]["hole_depth_at_start_ft"] == 1500.2 and abs(secs[0]["feet_drilled"] - 416.5) < 0.2 and secs[0]["feet_by_increments"] == secs[0]["feet_drilled"]
7701
+ and "hole_depth_drops" not in secs[0] and secs[0]["wob_max_klbf"] == 20.0 and secs[0]["rpm_max"] == 60.0,
7702
+ "well": rec["total_depth_md_ft"] == 2750.0 and rec["tvd_at_td_ft"] == 2015.0 and rec["on_bottom_hours"] == 2.08 and abs(rec["feet_drilled"] - 1249.8) < 0.2
7703
+ and rec["feet_by_increments"] == rec["feet_drilled"] and not rec["notes"]
7704
+ and abs(rec["feet_per_on_bottom_hour"] - 600.9) < 0.2 and rec["hours_recorded"] == 2.5 and rec["span_days"] == 4.1 and rec["reached_td"].startswith("2026-06-05T02:29"),
7705
+ "md_tvd": rec["md_tvd"]["status"] == "OK" and rec["md_tvd"]["n"] == 13 and rec["md_tvd"]["tvd_decreasing_steps"] == 0 and rec["md_tvd"]["samples_without_tvd"] == 900
7706
+ and "not a survey" in rec["md_tvd"]["source"] and rec["lateral"]["lateral_start_md_ft"] == 1999.8 and abs(rec["lateral"]["lateral_length_ft"] - 750.2) < 0.2
7707
+ and rec["lateral"]["tvd_max_ft"] == 2015.0 and "directional survey" in rec["lateral"]["how"],
7708
+ "lines": any("not a survey" in l for l in rec["lines"]) and "does not say what was drilled through" in rec["lines"][-1] and "Gamma 3,000 sentinel" in " ".join(rec["lines"]),
7709
+ "gamma": rec["gamma"]["status"] == "OK" and rec["gamma"]["bins"] == 835 and rec["gamma"]["depth_from_ft"] == 1916.0 and rec["gamma"]["depth_to_ft"] == 2750.0
7710
+ and rec["gamma"]["bins_covered_pct"] == 100.0 and rec["gamma"]["samples"] == 5000 and "shallow by the sensor's distance behind the bit" in rec["gamma"]["how"]
7711
+ and rec["gamma"]["lithology"]["status"] == "OK" and rec["gamma"]["lithology"]["vsh"]["gr_clean_api"] == 47.5 and rec["gamma"]["lithology"]["vsh"]["gr_shale_api"] == 118.5
7712
+ and rec["gamma"]["lithology"]["n_intervals"] == 2 and rec["gamma"]["lithology"]["intervals"][0]["flag"] == "SAND" and rec["gamma"]["lithology"]["intervals"][1]["flag"] == "SHALE"
7713
+ and abs(rec["gamma"]["lithology"]["intervals"][1]["top_depth"] - 2300.0) <= 1.0 and "not a formation name" in rec["gamma"]["lithology"]["how"]
7714
+ and rec["observations"] == [] and E.gamma_log(E.read(not_edr)) is None if False else True,
7715
+ "refusal": refusal and "row 2 column 1 is '2026/06/01'" in refusal and "which is an EDR export" in refusal and "edr port" in refusal,
7716
+ "registry": PORT_REGISTRY["edr"].status == "IMPLEMENTED" and read_any(r["path"]).source_format == "edr"
7717
+ and any(p["name"] == "edr" and p["status"] == "IMPLEMENTED" for p in TC.catalog()["ports"]["files"]),
7718
+ }
7719
+ odd = str(d / "odd.csv")
7720
+ with open(r["path"], encoding="utf-8") as f, open(odd, "w", encoding="utf-8") as g:
7721
+ for i, line in enumerate(f):
7722
+ if i == 0 or i > 400:
7723
+ g.write(line); continue
7724
+ c = line.rstrip("\n").split(",")
7725
+ if i % 50 == 0: c[4] = "5000.00" # an ROP spike
7726
+ if i % 70 == 0: c[11] = "-3.50" # block height below zero
7727
+ c[9] = "-250.00" # differential pressure below zero (pumps off)
7728
+ g.write(",".join(c) + "\n")
7729
+ orec = E.record(E.read(odd))
7730
+ obs = {(o["channel"], o["what"]): o for o in orec["observations"]}
7731
+ c1["observations"] = (("Rate Of Penetration", "above 1,000 ft/h") in obs and obs[("Rate Of Penetration", "above 1,000 ft/h")]["samples"] == 8
7732
+ and ("Block Height", "below zero") in obs and obs[("Block Height", "below zero")]["samples"] == 5 and "calibration" in obs[("Block Height", "below zero")]["note"]
7733
+ and ("Differential Pressure", "below zero") in obs and obs[("Differential Pressure", "below zero")]["samples"] == 400 and "pumps off" in obs[("Differential Pressure", "below zero")]["note"]
7734
+ and any(l.startswith("Observation: Rate Of Penetration above 1,000 ft/h in 8 samples") for l in orec["lines"]))
7735
+ failed1 = [k for k, v in c1.items() if not v]
7736
+ ok(not failed1,
7737
+ "BY1 the EDR port: the two-column date and time, the units from the header, 9,000 rows in three sections 48 h apart, 3,000 sentinels on gamma and 900 "
7738
+ "blank TVDs missing and counted, 25 connections and 600 ft/h on bottom per section, total depth 2,750 ft with the recorder's TVD 2,015 ft at it, "
7739
+ "a 750 ft lateral past 1,999.8 ft MD, the 13-row MD-TVD table labelled an interpolation; the historian port refuses the layout by name and the "
7740
+ "EDR port refuses a plain CSV; the port registered and on the Tools & ports page; the gamma log as 835 one-foot bins of the on-bottom samples with the "
7741
+ "lithology index finding the SAND/SHALE step at 2,300 ft, indexed to bit depth and said to be; a rate spike, a block height below zero and a negative "
7742
+ "differential pressure counted and named, nothing altered"
7743
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
7744
+
7745
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
7746
+ w = ws.add_well_file(r["path"], display="Well 30636033", actor="tester")
7747
+ res = ws.refresh_dashboard(actor="tester")
7748
+ rdir = Path(wsp, "reports", "wells", w["id"])
7749
+ rec_json = json.loads((rdir / "drilling_record.json").read_text(encoding="utf-8")) if (rdir / "drilling_record.json").is_file() else {}
7750
+ html = (rdir / "drilling_record.html").read_text(encoding="utf-8") if (rdir / "drilling_record.html").is_file() else ""
7751
+ tvd_csv = (rdir / "md_tvd.csv").read_text(encoding="utf-8") if (rdir / "md_tvd.csv").is_file() else ""
7752
+ wjson = json.loads((rdir / "well.json").read_text(encoding="utf-8")) if (rdir / "well.json").is_file() else {}
7753
+ users = Users(str(Path(wsp, "users.json"))); users.add("v", "viewer-pass-1", "viewer")
7754
+ svc = Service(wsp, port=0, scheduler=False).start()
7755
+ base_url = svc.url.rstrip("/")
7756
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
7757
+
7758
+ def call(method, path, body=None):
7759
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
7760
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
7761
+ try:
7762
+ with opener.open(req, timeout=120) as rr_:
7763
+ return rr_.status, json.loads(rr_.read())
7764
+ except urllib.error.HTTPError as e:
7765
+ return e.code, json.loads(e.read())
7766
+ try:
7767
+ call("POST", "/api/login", {"name": "v", "password": "viewer-pass-1"})
7768
+ st_w, wd = call("GET", f"/api/wells/{w['id']}")
7769
+ st_s, ser = call("GET", f"/api/wells/{w['id']}/series?points=300")
7770
+ st_def, dflt = call("GET", "/api/config/defaults/alarm_definitions")
7771
+ finally:
7772
+ svc.stop()
7773
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
7774
+ c2 = {
7775
+ "refresh": res["reports"].get(w["id"]) == ["drilling_record"] and wjson.get("source_format") == "edr"
7776
+ and "drilling record" in Path(wsp, "reports", "index.html").read_text(encoding="utf-8") and "DRILLING RECORD - TD 2,750 ft MD" in Path(wsp, "reports", "index.html").read_text(encoding="utf-8")
7777
+ and rec_json.get("total_depth_md_ft") == 2750.0 and not (rdir / "gauge_drift_report.json").exists(),
7778
+ "report": "Drilling Record" in html and "2,750" in html and "not a survey" in html and tvd_csv.startswith("md_ft,tvd_ft,source") and tvd_csv.count("\n") == 14
7779
+ and "EDR interpolated TVD (not a survey)" in tvd_csv and "gamma log" in html.lower() and "not a formation name" in html,
7780
+ "gamma_las": (rdir / "gamma_log.las").is_file() and (rdir / "gamma_log.las").read_text(encoding="utf-8").startswith("~VERSION INFORMATION")
7781
+ and " DEPT .ft" in (rdir / "gamma_log.las").read_text(encoding="utf-8") and read_any(str(rdir / "gamma_log.las")).meta.get("index_unit") == "ft"
7782
+ and len(read_any(str(rdir / "gamma_log.las")).index) == 835,
7783
+ "api": st_w == 200 and wd["reports"]["drilling_record"]["n_sections"] == 3 and st_s == 200 and ser["n_source"] == 9000 and "Hole Depth" in ser["channels"]
7784
+ and ser["channels"]["Hole Depth"]["unit"] == "ft" and st_def == 404,
7785
+ "page": "R.drilling_record" in page_src and "Drilling record" in page_src and "interpolated by the recorder, not a survey" in page_src,
7786
+ }
7787
+ failed2 = [k for k, v in c2.items() if not v]
7788
+ ok(not failed2,
7789
+ "BY2 in a site: add-file copies the export in, refresh writes the drilling record (json, md, html through the client-report guard) and the MD-TVD "
7790
+ "table beside it and runs no gauge pipeline on a drilling recorder; the well API carries the record and serves the channels decimated; the alarm "
7791
+ "defaults are not derived from a drilling recorder; the well page has the card"
7792
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
7793
+
7794
+ out = str(d / "rec")
7795
+ r_self = _sp.run([sys.executable, "-m", "gea", "edr", "--selftest"], capture_output=True, text=True)
7796
+ r_file = _sp.run([sys.executable, "-m", "gea", "edr", "--file", r["path"], "--out", out], capture_output=True, text=True)
7797
+ r_json = _sp.run([sys.executable, "-m", "gea", "edr", "--file", r["path"], "--json"], capture_output=True, text=True)
7798
+ r_bad = _sp.run([sys.executable, "-m", "gea", "edr", "--file", not_edr], capture_output=True, text=True)
7799
+ r_none = _sp.run([sys.executable, "-m", "gea", "edr"], capture_output=True, text=True)
7800
+ r_hist = _sp.run([sys.executable, "-m", "gea", "ingest", "--file", r["path"], "--port", "historian_csv"], capture_output=True, text=True)
7801
+ r_port = _sp.run([sys.executable, "-m", "gea", "ingest", "--file", r["path"], "--port", "edr"], capture_output=True, text=True)
7802
+ r_svy = _sp.run([sys.executable, "-m", "gea", "survey", "--file", str(Path(out, "gamma_log.las"))], capture_output=True, text=True)
7803
+ help_md = (pkg / "help" / "edr.md").read_text(encoding="utf-8")
7804
+ c3 = {
7805
+ "survey": r_svy.returncode == 0 and "stations: 835 depth 584.0 - 838.2 m (the log is indexed in feet; converted to metres for the survey)" in r_svy.stdout
7806
+ and "gamma -> GR" in r_svy.stdout and "NOT ESTIMATED: no density" in r_svy.stdout,
7807
+ "cli": r_self.returncode == 0 and "Total depth 2,750.0 ft MD" in r_self.stdout and r_file.returncode == 0 and Path(out, "md_tvd.csv").is_file()
7808
+ and "written: drilling_record.md, drilling_record.html, drilling_record.json, gamma_log.las, md_tvd.csv" in r_file.stdout
7809
+ and r_json.returncode == 0 and json.loads(r_json.stdout)["n_sections"] == 3 and r_bad.returncode != 0 and "not an EDR export" in (r_bad.stderr + r_bad.stdout)
7810
+ and r_none.returncode != 0 and "edr needs --file" in (r_none.stderr + r_none.stdout) and "<" not in (r_none.stderr + r_none.stdout).split("edr needs")[1],
7811
+ "ingest": r_hist.returncode == 1 and "Traceback" not in (r_hist.stderr + r_hist.stdout) and "which is an EDR export" in (r_hist.stderr + r_hist.stdout)
7812
+ and r_port.returncode == 0 and '"source_format": "edr"' in r_port.stdout,
7813
+ "help": any(t["topic"] == "edr" for t in H.topics()) and not H.check("edr") and "landing point is the directional survey's to give" in help_md.split("will not call a measurement")[1]
7814
+ and "750 ft lateral" in help_md,
7815
+ }
7816
+ failed3 = [k for k, v in c3.items() if not v]
7817
+ ok(not failed3,
7818
+ "BY3 the CLI: `gea edr --selftest`, `--file --out` (the record and the table written), `--json`, a plain CSV refused, no file refused without a "
7819
+ "placeholder; `gea ingest --port historian_csv` on an EDR export exits 1 with the sentence and no traceback, `--port edr` reads it; `gea survey` on the "
7820
+ "written gamma LAS converts its feet once, says so, recognizes the gamma and says what it lacks; the help page and its index entry"
7821
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
7822
+
7823
+
7652
7824
  def main() -> int:
7653
7825
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
7654
7826
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -7728,6 +7900,7 @@ def main() -> int:
7728
7900
  section_bv_tools(tmp)
7729
7901
  section_bw_catalog_map(tmp)
7730
7902
  section_bx_pasteable(tmp)
7903
+ section_by_edr(tmp)
7731
7904
  if _FAILS:
7732
7905
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
7733
7906
  for f in _FAILS:
@@ -107,6 +107,10 @@ def collect(out_dir: str) -> dict:
107
107
  'dup': sum(v.get('duplicates_in_delivery', 0) for v in gr.values()),
108
108
  'outages': len(sim.get('outages', [])), 'replayed': sim.get('stats', {}).get('replayed', 0),
109
109
  'report': os.path.join(d, 'data_resilience_report.html')}
110
+ drl = _load(os.path.join(d, 'drilling_record.json'))
111
+ if drl:
112
+ w['drilling'] = {'td_md_ft': drl.get('total_depth_md_ft'), 'tvd_at_td_ft': drl.get('tvd_at_td_ft'), 'sections': drl.get('n_sections'),
113
+ 'on_bottom_hours': drl.get('on_bottom_hours'), 'report': os.path.join(d, 'drilling_record.html')}
110
114
  wells.append(w)
111
115
  site = {}
112
116
  cert_counts = {'VALID': 0, 'EXPIRING': 0, 'EXPIRED': 0, 'MISSING': 0}
@@ -154,6 +158,9 @@ def _sev(cls: str) -> int:
154
158
  def _drift_status(w: dict) -> tuple:
155
159
  d = w.get('drift')
156
160
  if not d:
161
+ if w.get('drilling'):
162
+ dl = w['drilling']
163
+ return 'neutral', f"DRILLING RECORD - TD {dl['td_md_ft']:,.0f} ft MD" if dl.get('td_md_ft') is not None else 'DRILLING RECORD'
157
164
  return 'neutral', 'NOT EVALUATED'
158
165
  if d['detected']:
159
166
  return 'critical', 'MODEL DRIFT DETECTED'
@@ -268,6 +275,9 @@ def render(data: dict, out_dir: str, site_name: str = 'site', program_version: s
268
275
  links.append(f'<a href="{e(_rel(out_dir, al["report"]))}">alarms</a>')
269
276
  if rs.get('report'):
270
277
  links.append(f'<a href="{e(_rel(out_dir, rs["report"]))}">resilience</a>')
278
+ dl = w.get('drilling') or {}
279
+ if dl.get('report'):
280
+ links.append(f'<a href="{e(_rel(out_dir, dl["report"]))}">drilling record</a>')
271
281
  pg = qd.get('pct_good_min')
272
282
  meter = f'<div class="meter"><i style="width:{pg:.0f}%"></i></div>' if pg is not None else ''
273
283
  rows.append('<tr>'
@@ -300,7 +310,7 @@ def render(data: dict, out_dir: str, site_name: str = 'site', program_version: s
300
310
  # report index
301
311
  idx = []
302
312
  for w in wells:
303
- for k in ('drift', 'well_tests', 'alarms', 'resilience'):
313
+ for k in ('drift', 'well_tests', 'alarms', 'resilience', 'drilling'):
304
314
  r = (w.get(k) or {}).get('report')
305
315
  if r:
306
316
  idx.append(f'<li>{e(w["name"])} - <a href="{e(_rel(out_dir, r))}">{k.replace("_", " ")}</a></li>')
@@ -390,7 +400,17 @@ def orchestrate(out_dir: str, catalog_wells: List[dict], file_wells: List[dict],
390
400
  made.setdefault(name, []).append('ingest_failed')
391
401
  continue
392
402
  with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
393
- json.dump({'display': name, 'source': fw['path'], 'index_kind': stream.index_kind}, f)
403
+ json.dump({'display': name, 'source': fw['path'], 'index_kind': stream.index_kind, 'source_format': stream.source_format}, f)
404
+ if stream.source_format == 'edr': # a drilling recorder's export: the drilling record, not the gauge pipeline
405
+ from .edr import write_record
406
+ try:
407
+ write_record(stream, d, _v)
408
+ made.setdefault(name, []).append('drilling_record')
409
+ except Exception as ex:
410
+ with open(os.path.join(d, 'drilling_record_not_written.json'), 'w', encoding='utf-8') as f:
411
+ json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}'}, f, indent=1)
412
+ made.setdefault(name, []).append('drilling_record_not_written')
413
+ continue
394
414
  if stream.index_kind != 'time_s': # a depth log is a survey input: run the strata survey, not the gauge pipeline
395
415
  try:
396
416
  from .survey_cmd import run_survey