gea-program 0.79.0__tar.gz → 0.81.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 (428) hide show
  1. {gea_program-0.79.0/gea_program.egg-info → gea_program-0.81.0}/PKG-INFO +15 -5
  2. {gea_program-0.79.0 → gea_program-0.81.0}/README.md +14 -4
  3. {gea_program-0.79.0 → gea_program-0.81.0}/gea/__init__.py +1 -1
  4. {gea_program-0.79.0 → gea_program-0.81.0}/gea/acceptance_tests.py +347 -1
  5. {gea_program-0.79.0 → gea_program-0.81.0}/gea/driller_link.py +28 -8
  6. {gea_program-0.79.0 → gea_program-0.81.0}/gea/edr.py +3 -1
  7. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/IT_SECURITY.md +4 -1
  8. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/driller.md +2 -2
  9. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/install.md +2 -2
  10. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/reader.md +1 -1
  11. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/recorder.md +1 -1
  12. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/01-install.md +2 -2
  13. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/19-dossier-reader.md +1 -1
  14. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/22-updates.md +1 -1
  15. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/25-install-recorder.md +2 -2
  16. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/28-driller-office.md +3 -3
  17. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/29-driller-rig.md +4 -4
  18. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial.md +2 -2
  19. {gea_program-0.79.0 → gea_program-0.81.0}/gea/web/app.html +1 -1
  20. {gea_program-0.79.0 → gea_program-0.81.0/gea_program.egg-info}/PKG-INFO +15 -5
  21. {gea_program-0.79.0 → gea_program-0.81.0}/pyproject.toml +1 -1
  22. {gea_program-0.79.0 → gea_program-0.81.0}/LICENSE +0 -0
  23. {gea_program-0.79.0 → gea_program-0.81.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  24. {gea_program-0.79.0 → gea_program-0.81.0}/gea/__main__.py +0 -0
  25. {gea_program-0.79.0 → gea_program-0.81.0}/gea/accuracy_statement.py +0 -0
  26. {gea_program-0.79.0 → gea_program-0.81.0}/gea/alarm_engine.py +0 -0
  27. {gea_program-0.79.0 → gea_program-0.81.0}/gea/alerts.py +0 -0
  28. {gea_program-0.79.0 → gea_program-0.81.0}/gea/backup.py +0 -0
  29. {gea_program-0.79.0 → gea_program-0.81.0}/gea/bench.py +0 -0
  30. {gea_program-0.79.0 → gea_program-0.81.0}/gea/bit_seismic.py +0 -0
  31. {gea_program-0.79.0 → gea_program-0.81.0}/gea/blind_harness.py +0 -0
  32. {gea_program-0.79.0 → gea_program-0.81.0}/gea/casing_design.py +0 -0
  33. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  34. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  35. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  36. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  37. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  38. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  39. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  40. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  41. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  42. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  43. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  44. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  45. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  46. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  47. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  48. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  49. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  50. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  51. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  52. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  53. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  54. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  55. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  56. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  57. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  58. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  59. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  60. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  61. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  62. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  63. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  64. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  65. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  66. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  67. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  68. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  69. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  70. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  71. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  72. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  73. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  74. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  75. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  76. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  77. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  78. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  79. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  80. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  81. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  82. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  83. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  84. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  85. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  86. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  87. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  88. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  89. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  90. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  91. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  92. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  93. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  94. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  95. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  96. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  97. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  98. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  99. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  100. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/med_160_sapropels.txt +0 -0
  101. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  102. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  103. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  104. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  105. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  106. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  107. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  108. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  109. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  110. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  111. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  112. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  113. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  114. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  115. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  116. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  117. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  118. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  119. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  120. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  121. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  122. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  123. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  124. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  125. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  126. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  127. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  128. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  129. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  130. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  131. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  132. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  133. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  134. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  135. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  136. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  137. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog_map.py +0 -0
  138. {gea_program-0.79.0 → gea_program-0.81.0}/gea/catalog_register.py +0 -0
  139. {gea_program-0.79.0 → gea_program-0.81.0}/gea/certificates.py +0 -0
  140. {gea_program-0.79.0 → gea_program-0.81.0}/gea/cli.py +0 -0
  141. {gea_program-0.79.0 → gea_program-0.81.0}/gea/client_reports.py +0 -0
  142. {gea_program-0.79.0 → gea_program-0.81.0}/gea/collision.py +0 -0
  143. {gea_program-0.79.0 → gea_program-0.81.0}/gea/config_versioning.py +0 -0
  144. {gea_program-0.79.0 → gea_program-0.81.0}/gea/conformance.py +0 -0
  145. {gea_program-0.79.0 → gea_program-0.81.0}/gea/control_room.py +0 -0
  146. {gea_program-0.79.0 → gea_program-0.81.0}/gea/correlation.py +0 -0
  147. {gea_program-0.79.0 → gea_program-0.81.0}/gea/dashboard.py +0 -0
  148. {gea_program-0.79.0 → gea_program-0.81.0}/gea/declarations.py +0 -0
  149. {gea_program-0.79.0 → gea_program-0.81.0}/gea/decline.py +0 -0
  150. {gea_program-0.79.0 → gea_program-0.81.0}/gea/deviation.py +0 -0
  151. {gea_program-0.79.0 → gea_program-0.81.0}/gea/dlis.py +0 -0
  152. {gea_program-0.79.0 → gea_program-0.81.0}/gea/dnp3.py +0 -0
  153. {gea_program-0.79.0 → gea_program-0.81.0}/gea/doctor.py +0 -0
  154. {gea_program-0.79.0 → gea_program-0.81.0}/gea/dossier.py +0 -0
  155. {gea_program-0.79.0 → gea_program-0.81.0}/gea/dossier_verify.py +0 -0
  156. {gea_program-0.79.0 → gea_program-0.81.0}/gea/downhole_engine.py +0 -0
  157. {gea_program-0.79.0 → gea_program-0.81.0}/gea/drift_monitor.py +0 -0
  158. {gea_program-0.79.0 → gea_program-0.81.0}/gea/drill_string.py +0 -0
  159. {gea_program-0.79.0 → gea_program-0.81.0}/gea/earth_model.py +0 -0
  160. {gea_program-0.79.0 → gea_program-0.81.0}/gea/economics.py +0 -0
  161. {gea_program-0.79.0 → gea_program-0.81.0}/gea/ed25519.py +0 -0
  162. {gea_program-0.79.0 → gea_program-0.81.0}/gea/elevation.py +0 -0
  163. {gea_program-0.79.0 → gea_program-0.81.0}/gea/etp.py +0 -0
  164. {gea_program-0.79.0 → gea_program-0.81.0}/gea/etp12_schemas.json +0 -0
  165. {gea_program-0.79.0 → gea_program-0.81.0}/gea/example_register_map.json +0 -0
  166. {gea_program-0.79.0 → gea_program-0.81.0}/gea/fat_sat.py +0 -0
  167. {gea_program-0.79.0 → gea_program-0.81.0}/gea/field_pull.py +0 -0
  168. {gea_program-0.79.0 → gea_program-0.81.0}/gea/files.py +0 -0
  169. {gea_program-0.79.0 → gea_program-0.81.0}/gea/fixtures/README.md +0 -0
  170. {gea_program-0.79.0 → gea_program-0.81.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  171. {gea_program-0.79.0 → gea_program-0.81.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  172. {gea_program-0.79.0 → gea_program-0.81.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  173. {gea_program-0.79.0 → gea_program-0.81.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  174. {gea_program-0.79.0 → gea_program-0.81.0}/gea/follower.py +0 -0
  175. {gea_program-0.79.0 → gea_program-0.81.0}/gea/forward_model.py +0 -0
  176. {gea_program-0.79.0 → gea_program-0.81.0}/gea/frac_hits.py +0 -0
  177. {gea_program-0.79.0 → gea_program-0.81.0}/gea/friction_priors.py +0 -0
  178. {gea_program-0.79.0 → gea_program-0.81.0}/gea/gamma.py +0 -0
  179. {gea_program-0.79.0 → gea_program-0.81.0}/gea/gauge_aging.py +0 -0
  180. {gea_program-0.79.0 → gea_program-0.81.0}/gea/gauge_specs.py +0 -0
  181. {gea_program-0.79.0 → gea_program-0.81.0}/gea/geodesy.py +0 -0
  182. {gea_program-0.79.0 → gea_program-0.81.0}/gea/geomag.py +0 -0
  183. {gea_program-0.79.0 → gea_program-0.81.0}/gea/gravity_reference.py +0 -0
  184. {gea_program-0.79.0 → gea_program-0.81.0}/gea/hartip.py +0 -0
  185. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/TESTER_GUIDE.md +0 -0
  186. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/alarms.md +0 -0
  187. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/alerts.md +0 -0
  188. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/audit-update.md +0 -0
  189. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/backup.md +0 -0
  190. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/bit-image.md +0 -0
  191. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/casing.md +0 -0
  192. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/catalog.md +0 -0
  193. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/charts.md +0 -0
  194. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/collision.md +0 -0
  195. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/company-sign-in.md +0 -0
  196. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/conformance.md +0 -0
  197. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/control-room.md +0 -0
  198. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/data-in.md +0 -0
  199. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/declare.md +0 -0
  200. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/decline.md +0 -0
  201. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/dlis.md +0 -0
  202. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/doctor.md +0 -0
  203. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/dossier.md +0 -0
  204. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/drift.md +0 -0
  205. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/drillstring.md +0 -0
  206. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/economics.md +0 -0
  207. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/edr.md +0 -0
  208. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/etp.md +0 -0
  209. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/field-ports.md +0 -0
  210. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/files.md +0 -0
  211. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/frac-hits.md +0 -0
  212. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/historian.md +0 -0
  213. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/https.md +0 -0
  214. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/hydraulics.md +0 -0
  215. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/instruments.md +0 -0
  216. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/interval.md +0 -0
  217. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/invoice-check.md +0 -0
  218. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/map.md +0 -0
  219. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/month-end.md +0 -0
  220. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/notifications.md +0 -0
  221. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/opseis.md +0 -0
  222. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/osdu.md +0 -0
  223. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/pad-summary.md +0 -0
  224. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/patches.md +0 -0
  225. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/penetrator.md +0 -0
  226. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/plan.md +0 -0
  227. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/pore-pressure.md +0 -0
  228. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/ppdm.md +0 -0
  229. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/quakeml.md +0 -0
  230. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/quality.md +0 -0
  231. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/records.md +0 -0
  232. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/report-history.md +0 -0
  233. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/rig-time.md +0 -0
  234. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/rocks.md +0 -0
  235. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/seedlink.md +0 -0
  236. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/seismic.md +0 -0
  237. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/setup.md +0 -0
  238. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/simulation.md +0 -0
  239. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/site.md +0 -0
  240. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/sites.md +0 -0
  241. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/slip.md +0 -0
  242. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/soak.md +0 -0
  243. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/sra.md +0 -0
  244. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/start.md +0 -0
  245. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/structure.md +0 -0
  246. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tools.md +0 -0
  247. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/torque-drag.md +0 -0
  248. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/transients.md +0 -0
  249. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/02-sign-in.md +0 -0
  250. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/03-site.md +0 -0
  251. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/04-add-well.md +0 -0
  252. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/05-records.md +0 -0
  253. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/06-well-page.md +0 -0
  254. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/07-declare.md +0 -0
  255. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/08-patches.md +0 -0
  256. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/09-alarms.md +0 -0
  257. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/10-control-room.md +0 -0
  258. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/11-summary-month.md +0 -0
  259. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/12-where-the-well-is.md +0 -0
  260. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/13-drilling-pages.md +0 -0
  261. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/14-production-pages.md +0 -0
  262. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/15-seismic.md +0 -0
  263. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/16-seismicity.md +0 -0
  264. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/17-recorder-office.md +0 -0
  265. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/18-reports.md +0 -0
  266. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/20-admin.md +0 -0
  267. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/21-upkeep.md +0 -0
  268. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/23-vpn.md +0 -0
  269. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/24-the-box.md +0 -0
  270. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/26-monitor.md +0 -0
  271. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/tutorial/27-mirror-vpn.md +0 -0
  272. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/upkeep.md +0 -0
  273. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/vibration.md +0 -0
  274. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/vpn.md +0 -0
  275. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/well-report.md +0 -0
  276. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/well-tests.md +0 -0
  277. {gea_program-0.79.0 → gea_program-0.81.0}/gea/help/well3d.md +0 -0
  278. {gea_program-0.79.0 → gea_program-0.81.0}/gea/helplib.py +0 -0
  279. {gea_program-0.79.0 → gea_program-0.81.0}/gea/housekeeping.py +0 -0
  280. {gea_program-0.79.0 → gea_program-0.81.0}/gea/hydraulics.py +0 -0
  281. {gea_program-0.79.0 → gea_program-0.81.0}/gea/icon.py +0 -0
  282. {gea_program-0.79.0 → gea_program-0.81.0}/gea/interval.py +0 -0
  283. {gea_program-0.79.0 → gea_program-0.81.0}/gea/inverse_engine.py +0 -0
  284. {gea_program-0.79.0 → gea_program-0.81.0}/gea/invoice_check.py +0 -0
  285. {gea_program-0.79.0 → gea_program-0.81.0}/gea/jobs.py +0 -0
  286. {gea_program-0.79.0 → gea_program-0.81.0}/gea/launcher.py +0 -0
  287. {gea_program-0.79.0 → gea_program-0.81.0}/gea/live_ports.py +0 -0
  288. {gea_program-0.79.0 → gea_program-0.81.0}/gea/loadtest.py +0 -0
  289. {gea_program-0.79.0 → gea_program-0.81.0}/gea/matplotlib_demo.py +0 -0
  290. {gea_program-0.79.0 → gea_program-0.81.0}/gea/modbus.py +0 -0
  291. {gea_program-0.79.0 → gea_program-0.81.0}/gea/modbus_rtu.py +0 -0
  292. {gea_program-0.79.0 → gea_program-0.81.0}/gea/model_card.py +0 -0
  293. {gea_program-0.79.0 → gea_program-0.81.0}/gea/month_end.py +0 -0
  294. {gea_program-0.79.0 → gea_program-0.81.0}/gea/mqtt_port.py +0 -0
  295. {gea_program-0.79.0 → gea_program-0.81.0}/gea/notify.py +0 -0
  296. {gea_program-0.79.0 → gea_program-0.81.0}/gea/oidc.py +0 -0
  297. {gea_program-0.79.0 → gea_program-0.81.0}/gea/oidc_mock.py +0 -0
  298. {gea_program-0.79.0 → gea_program-0.81.0}/gea/op_seismicity.py +0 -0
  299. {gea_program-0.79.0 → gea_program-0.81.0}/gea/opcua_port.py +0 -0
  300. {gea_program-0.79.0 → gea_program-0.81.0}/gea/operator_app.py +0 -0
  301. {gea_program-0.79.0 → gea_program-0.81.0}/gea/osdu.py +0 -0
  302. {gea_program-0.79.0 → gea_program-0.81.0}/gea/pad_summary.py +0 -0
  303. {gea_program-0.79.0 → gea_program-0.81.0}/gea/patches.py +0 -0
  304. {gea_program-0.79.0 → gea_program-0.81.0}/gea/pdf_report.py +0 -0
  305. {gea_program-0.79.0 → gea_program-0.81.0}/gea/penetrator.py +0 -0
  306. {gea_program-0.79.0 → gea_program-0.81.0}/gea/permits.py +0 -0
  307. {gea_program-0.79.0 → gea_program-0.81.0}/gea/piwebapi.py +0 -0
  308. {gea_program-0.79.0 → gea_program-0.81.0}/gea/pore_pressure.py +0 -0
  309. {gea_program-0.79.0 → gea_program-0.81.0}/gea/ports.py +0 -0
  310. {gea_program-0.79.0 → gea_program-0.81.0}/gea/ppdm.py +0 -0
  311. {gea_program-0.79.0 → gea_program-0.81.0}/gea/profile_catalog.py +0 -0
  312. {gea_program-0.79.0 → gea_program-0.81.0}/gea/project.py +0 -0
  313. {gea_program-0.79.0 → gea_program-0.81.0}/gea/qt6_downhole_app.py +0 -0
  314. {gea_program-0.79.0 → gea_program-0.81.0}/gea/quakeml.py +0 -0
  315. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reader.py +0 -0
  316. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reader_app.py +0 -0
  317. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reconciler.py +0 -0
  318. {gea_program-0.79.0 → gea_program-0.81.0}/gea/recorder_link.py +0 -0
  319. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  320. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  321. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/WMM2025.COF +0 -0
  322. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  323. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/eia_prices.provenance.json +0 -0
  324. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  325. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/iscwsa_rev4_example.json +0 -0
  326. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/iscwsa_rev5_example.json +0 -0
  327. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
  328. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
  329. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  330. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  331. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  332. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  333. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/mahoning_example.json +0 -0
  334. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/mud_motor_specs.json +0 -0
  335. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/owsg_tool_models.json +0 -0
  336. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/penetrator_ratings.json +0 -0
  337. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/seismicity_protocols.json +0 -0
  338. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/tool_sources.json +0 -0
  339. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/us_outline.json +0 -0
  340. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/us_outline.provenance.json +0 -0
  341. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_3d.provenance.json +0 -0
  342. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  343. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  344. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  345. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_surfaces.json.gz +0 -0
  346. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  347. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  348. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  349. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  350. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  351. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  352. {gea_program-0.79.0 → gea_program-0.81.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  353. {gea_program-0.79.0 → gea_program-0.81.0}/gea/report_history.py +0 -0
  354. {gea_program-0.79.0 → gea_program-0.81.0}/gea/rig_state.py +0 -0
  355. {gea_program-0.79.0 → gea_program-0.81.0}/gea/rock_inventory.py +0 -0
  356. {gea_program-0.79.0 → gea_program-0.81.0}/gea/sample_record.py +0 -0
  357. {gea_program-0.79.0 → gea_program-0.81.0}/gea/sample_well_profile.csv +0 -0
  358. {gea_program-0.79.0 → gea_program-0.81.0}/gea/sbom.py +0 -0
  359. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seedlink.py +0 -0
  360. {gea_program-0.79.0 → gea_program-0.81.0}/gea/segy.py +0 -0
  361. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic.py +0 -0
  362. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_array.py +0 -0
  363. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_assoc.py +0 -0
  364. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_detect.py +0 -0
  365. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_film.py +0 -0
  366. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_harmonic.py +0 -0
  367. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_qc.py +0 -0
  368. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_response.py +0 -0
  369. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_signature.py +0 -0
  370. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_track.py +0 -0
  371. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_uncertainty.py +0 -0
  372. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_unlisted.py +0 -0
  373. {gea_program-0.79.0 → gea_program-0.81.0}/gea/seismic_watch.py +0 -0
  374. {gea_program-0.79.0 → gea_program-0.81.0}/gea/sensor_swap.py +0 -0
  375. {gea_program-0.79.0 → gea_program-0.81.0}/gea/series_cache.py +0 -0
  376. {gea_program-0.79.0 → gea_program-0.81.0}/gea/service.py +0 -0
  377. {gea_program-0.79.0 → gea_program-0.81.0}/gea/service_life.py +0 -0
  378. {gea_program-0.79.0 → gea_program-0.81.0}/gea/setup_checklist.py +0 -0
  379. {gea_program-0.79.0 → gea_program-0.81.0}/gea/shapefile.py +0 -0
  380. {gea_program-0.79.0 → gea_program-0.81.0}/gea/shell.py +0 -0
  381. {gea_program-0.79.0 → gea_program-0.81.0}/gea/shut_in.py +0 -0
  382. {gea_program-0.79.0 → gea_program-0.81.0}/gea/simulation.py +0 -0
  383. {gea_program-0.79.0 → gea_program-0.81.0}/gea/site_map.py +0 -0
  384. {gea_program-0.79.0 → gea_program-0.81.0}/gea/sla_report.py +0 -0
  385. {gea_program-0.79.0 → gea_program-0.81.0}/gea/slip.py +0 -0
  386. {gea_program-0.79.0 → gea_program-0.81.0}/gea/soak.py +0 -0
  387. {gea_program-0.79.0 → gea_program-0.81.0}/gea/sra.py +0 -0
  388. {gea_program-0.79.0 → gea_program-0.81.0}/gea/store_forward.py +0 -0
  389. {gea_program-0.79.0 → gea_program-0.81.0}/gea/strata_join.py +0 -0
  390. {gea_program-0.79.0 → gea_program-0.81.0}/gea/structure.py +0 -0
  391. {gea_program-0.79.0 → gea_program-0.81.0}/gea/supervisor.py +0 -0
  392. {gea_program-0.79.0 → gea_program-0.81.0}/gea/survey_cmd.py +0 -0
  393. {gea_program-0.79.0 → gea_program-0.81.0}/gea/survey_view.py +0 -0
  394. {gea_program-0.79.0 → gea_program-0.81.0}/gea/telemetry.py +0 -0
  395. {gea_program-0.79.0 → gea_program-0.81.0}/gea/timestamp.py +0 -0
  396. {gea_program-0.79.0 → gea_program-0.81.0}/gea/tls.py +0 -0
  397. {gea_program-0.79.0 → gea_program-0.81.0}/gea/tool_catalog.py +0 -0
  398. {gea_program-0.79.0 → gea_program-0.81.0}/gea/tool_library.py +0 -0
  399. {gea_program-0.79.0 → gea_program-0.81.0}/gea/tool_taxonomy.py +0 -0
  400. {gea_program-0.79.0 → gea_program-0.81.0}/gea/torque_drag.py +0 -0
  401. {gea_program-0.79.0 → gea_program-0.81.0}/gea/transient.py +0 -0
  402. {gea_program-0.79.0 → gea_program-0.81.0}/gea/tray.py +0 -0
  403. {gea_program-0.79.0 → gea_program-0.81.0}/gea/tutorial.py +0 -0
  404. {gea_program-0.79.0 → gea_program-0.81.0}/gea/updates.py +0 -0
  405. {gea_program-0.79.0 → gea_program-0.81.0}/gea/vibration.py +0 -0
  406. {gea_program-0.79.0 → gea_program-0.81.0}/gea/vpn.py +0 -0
  407. {gea_program-0.79.0 → gea_program-0.81.0}/gea/web/reader.html +0 -0
  408. {gea_program-0.79.0 → gea_program-0.81.0}/gea/well3d.py +0 -0
  409. {gea_program-0.79.0 → gea_program-0.81.0}/gea/well_assembler.py +0 -0
  410. {gea_program-0.79.0 → gea_program-0.81.0}/gea/well_records.py +0 -0
  411. {gea_program-0.79.0 → gea_program-0.81.0}/gea/well_report.py +0 -0
  412. {gea_program-0.79.0 → gea_program-0.81.0}/gea/well_test_validation.py +0 -0
  413. {gea_program-0.79.0 → gea_program-0.81.0}/gea/wellplan.py +0 -0
  414. {gea_program-0.79.0 → gea_program-0.81.0}/gea/wellpos.py +0 -0
  415. {gea_program-0.79.0 → gea_program-0.81.0}/gea/winident.py +0 -0
  416. {gea_program-0.79.0 → gea_program-0.81.0}/gea/wits0.py +0 -0
  417. {gea_program-0.79.0 → gea_program-0.81.0}/gea/witsml.py +0 -0
  418. {gea_program-0.79.0 → gea_program-0.81.0}/gea/workspace.py +0 -0
  419. {gea_program-0.79.0 → gea_program-0.81.0}/gea/workspaces.py +0 -0
  420. {gea_program-0.79.0 → gea_program-0.81.0}/gea/x25519.py +0 -0
  421. {gea_program-0.79.0 → gea_program-0.81.0}/gea/xlsx_read.py +0 -0
  422. {gea_program-0.79.0 → gea_program-0.81.0}/gea_program.egg-info/SOURCES.txt +0 -0
  423. {gea_program-0.79.0 → gea_program-0.81.0}/gea_program.egg-info/dependency_links.txt +0 -0
  424. {gea_program-0.79.0 → gea_program-0.81.0}/gea_program.egg-info/entry_points.txt +0 -0
  425. {gea_program-0.79.0 → gea_program-0.81.0}/gea_program.egg-info/requires.txt +0 -0
  426. {gea_program-0.79.0 → gea_program-0.81.0}/gea_program.egg-info/top_level.txt +0 -0
  427. {gea_program-0.79.0 → gea_program-0.81.0}/setup.cfg +0 -0
  428. {gea_program-0.79.0 → gea_program-0.81.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.79.0
3
+ Version: 0.81.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
@@ -476,7 +476,17 @@ reasons and the confidence of each choice. A position filter (since
476
476
  0.78.0) places the bit in the layer the way a geosteerer does, from the
477
477
  gamma and the deep-resistivity distance against the model, and the screen
478
478
  draws the band it puts the layer in. The driller accepts, rejects or
479
- records what was done instead, by name; every recommendation, answer and
479
+ records what was done instead, by name. The third dimension (since
480
+ 0.80.0): the lateral has an azimuth, the agent's sixth choice is where to
481
+ aim it and the recommendation reads as one toolface; the layer is a surface with
482
+ a dip across the plan line as well as along it, the hole is placed by
483
+ minimum curvature through the surveys' azimuths, the corridor the lease
484
+ and the offset wells leave the hole is in the twin and on the screen's
485
+ plan view, and the config's model carries the plan's azimuth, the cross
486
+ dip and the corridor. A vibration index read off the surface torque,
487
+ weight and hook load (since 0.81.0) stands in for the downhole tool a
488
+ string does not carry, and the record says which the agent read. Every
489
+ recommendation, answer and
480
490
  command is on record with its hash, and GEA pulls the records into the
481
491
  well from the **Drill string** tab's card (`gea workspace --path C:\site
482
492
  --action driller-pull --box http://192.168.1.60:8790 --well SWD-1`). The
@@ -830,7 +840,7 @@ unsigned, the key hand-overs - and shows the reports as they were signed. A
830
840
  reader made from the workspace knows the site's key fingerprints, so it
831
841
  says whether a dossier is the site's; it allows no network request, and it
832
842
  leaves the trusted time-stamp to `verify_dossier.py`. For a client who wants
833
- it installed, every release also carries `GEA-Reader-Setup-0.79.0.exe`: GEA
843
+ it installed, every release also carries `GEA-Reader-Setup-0.81.0.exe`: GEA
834
844
  Reader as a Windows program of its own - its own icon and taskbar button,
835
845
  Check with GEA Reader on a dossier's right-click menu, fingerprints the client
836
846
  saves kept on that computer, nothing of GEA-Program. `gea help reader`.
@@ -1595,7 +1605,7 @@ attaches the zips to the GitHub release of that tag.
1595
1605
 
1596
1606
  ### The Windows installer
1597
1607
 
1598
- Every release also carries `GEA-Program-Setup-0.79.0.exe` (for 0.79.0): the
1608
+ Every release also carries `GEA-Program-Setup-0.81.0.exe` (for 0.81.0): the
1599
1609
  same Windows kit installed like any Windows program, for the signed-in user
1600
1610
  alone and with no administrator rights (`%LOCALAPPDATA%\Programs\GEA-Program`).
1601
1611
  It asks for the site workspace (an existing one such as `C:\site` is used as
@@ -1614,7 +1624,7 @@ it, removes the program and leaves the workspace. Silent, for a site's
1614
1624
  software deployment:
1615
1625
 
1616
1626
  ```
1617
- GEA-Program-Setup-0.79.0.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /WORKSPACE="C:\site" /SITENAME="Pad 3" /TASKS="desktopicon"
1627
+ GEA-Program-Setup-0.81.0.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /WORKSPACE="C:\site" /SITENAME="Pad 3" /TASKS="desktopicon"
1618
1628
  ```
1619
1629
 
1620
1630
  The setup is built by Inno Setup 6 from `tools/installer.iss` in the same
@@ -437,7 +437,17 @@ reasons and the confidence of each choice. A position filter (since
437
437
  0.78.0) places the bit in the layer the way a geosteerer does, from the
438
438
  gamma and the deep-resistivity distance against the model, and the screen
439
439
  draws the band it puts the layer in. The driller accepts, rejects or
440
- records what was done instead, by name; every recommendation, answer and
440
+ records what was done instead, by name. The third dimension (since
441
+ 0.80.0): the lateral has an azimuth, the agent's sixth choice is where to
442
+ aim it and the recommendation reads as one toolface; the layer is a surface with
443
+ a dip across the plan line as well as along it, the hole is placed by
444
+ minimum curvature through the surveys' azimuths, the corridor the lease
445
+ and the offset wells leave the hole is in the twin and on the screen's
446
+ plan view, and the config's model carries the plan's azimuth, the cross
447
+ dip and the corridor. A vibration index read off the surface torque,
448
+ weight and hook load (since 0.81.0) stands in for the downhole tool a
449
+ string does not carry, and the record says which the agent read. Every
450
+ recommendation, answer and
441
451
  command is on record with its hash, and GEA pulls the records into the
442
452
  well from the **Drill string** tab's card (`gea workspace --path C:\site
443
453
  --action driller-pull --box http://192.168.1.60:8790 --well SWD-1`). The
@@ -791,7 +801,7 @@ unsigned, the key hand-overs - and shows the reports as they were signed. A
791
801
  reader made from the workspace knows the site's key fingerprints, so it
792
802
  says whether a dossier is the site's; it allows no network request, and it
793
803
  leaves the trusted time-stamp to `verify_dossier.py`. For a client who wants
794
- it installed, every release also carries `GEA-Reader-Setup-0.79.0.exe`: GEA
804
+ it installed, every release also carries `GEA-Reader-Setup-0.81.0.exe`: GEA
795
805
  Reader as a Windows program of its own - its own icon and taskbar button,
796
806
  Check with GEA Reader on a dossier's right-click menu, fingerprints the client
797
807
  saves kept on that computer, nothing of GEA-Program. `gea help reader`.
@@ -1556,7 +1566,7 @@ attaches the zips to the GitHub release of that tag.
1556
1566
 
1557
1567
  ### The Windows installer
1558
1568
 
1559
- Every release also carries `GEA-Program-Setup-0.79.0.exe` (for 0.79.0): the
1569
+ Every release also carries `GEA-Program-Setup-0.81.0.exe` (for 0.81.0): the
1560
1570
  same Windows kit installed like any Windows program, for the signed-in user
1561
1571
  alone and with no administrator rights (`%LOCALAPPDATA%\Programs\GEA-Program`).
1562
1572
  It asks for the site workspace (an existing one such as `C:\site` is used as
@@ -1575,7 +1585,7 @@ it, removes the program and leaves the workspace. Silent, for a site's
1575
1585
  software deployment:
1576
1586
 
1577
1587
  ```
1578
- GEA-Program-Setup-0.79.0.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /WORKSPACE="C:\site" /SITENAME="Pad 3" /TASKS="desktopicon"
1588
+ GEA-Program-Setup-0.81.0.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /WORKSPACE="C:\site" /SITENAME="Pad 3" /TASKS="desktopicon"
1579
1589
  ```
1580
1590
 
1581
1591
  The setup is built by Inno Setup 6 from `tools/installer.iss` in the same
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.79.0"
126
+ __version__ = "0.81.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -16566,6 +16566,7 @@ def section_dv_driller(tmp: str) -> None:
16566
16566
  time.sleep(0.2)
16567
16567
  s_ov, _ = call("/api/answer", {"what": "override", "by": "tester", "note": "held", "override": {"build_deg_100ft": 0.0, "wob_klb": 25}})
16568
16568
  md = drl.feed.snapshot()["state"]["hole_depth"]
16569
+ drl.cfg["limits"]["vibration_index"] = 5.0 # the section's end alone ends this arming: the vibration index the feed now derives (0.81.0) may cross 1.2 in the shale
16569
16570
  s_long, _ = call("/api/arm", {"by": "tester", "to_md": md + 5000})
16570
16571
  s_arm, armed = call("/api/arm", {"by": "tester", "to_md": md + 150})
16571
16572
  t_end = time.monotonic() + 30
@@ -16698,7 +16699,7 @@ def section_dv_driller(tmp: str) -> None:
16698
16699
  e_ = inf["estimate"]; truth = inf["lithology"]
16699
16700
  guess = max(("above", e_["p_above"]), ("in", e_["p_in"]), ("below", e_["p_below"]), key=lambda x: x[1])[0]
16700
16701
  conf[truth][guess] += 1; n_steps += 1
16701
- errs.append(abs(e_["d_top"] - (lt.tvd - sc.layer_top(lt.vs))))
16702
+ errs.append(abs(e_["d_top"] - (lt.tvd - sc.layer_top(lt.vs, getattr(lt, "y", 0.0)))))
16702
16703
  right = sum(conf[k][k] for k in conf); total = sum(sum(v.values()) for v in conf.values())
16703
16704
  # a filter told nothing but the gamma of a flat layer sits where the model says; told a boundary, it moves
16704
16705
  f1 = PF.PositionFilter(9000.0, 0.0, 40.0, 60.0, 130.0, 115.0, seed=3)
@@ -16841,6 +16842,349 @@ def section_dw_shadow(tmp: str) -> None:
16841
16842
  "refuses the wrong well; the card, the workspace command and the help page carry it" + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
16842
16843
 
16843
16844
 
16845
+ def section_dx_third_dimension(tmp: str) -> None:
16846
+ """Section DX - the third dimension of GEA Driller: the directional arithmetic (minimum curvature, the dogleg of a
16847
+ build and a turn, the toolface), the twin in the plan's frame (the turn, the layer as a surface, the corridor,
16848
+ an older five-choice action), the filter's cross dip; the rig side (the bit placed from surveys with azimuths,
16849
+ the twin as the rig publishing the azimuth, a decision with the turn and the toolface, an override with a turn,
16850
+ the limit on both together, the config's refusals, the screen, the help); the policy with six heads holding the
16851
+ corridor on the held-out scenes, and shadow mode judging the turn with the record's azimuth and not without.
16852
+ The sixth choice is where to aim the azimuth (left of the plan, along it, right of it); the tool turns toward it
16853
+ at its own rate, as a directional driller holds the azimuth he is given."""
16854
+ import importlib
16855
+ import math as _m
16856
+ from . import driller_link as DL
16857
+ from . import helplib as H
16858
+ pkg = Path(__file__).parent
16859
+ d = Path(tmp, "dx"); d.mkdir()
16860
+ prog = DL.program()
16861
+ if prog is None:
16862
+ ok(True, "DX the driller's program (gea-driller) is not installed beside GEA in this install; nothing to check here")
16863
+ return
16864
+ if prog["env"] and prog["env"] not in sys.path:
16865
+ sys.path.insert(0, prog["env"])
16866
+ T = importlib.import_module("gea_driller.twin"); G = importlib.import_module("gea_driller.geometry"); PF = importlib.import_module("gea_driller.position")
16867
+ P = importlib.import_module("gea_driller.policy"); A = importlib.import_module("gea_driller.advisor"); C = importlib.import_module("gea_driller.config")
16868
+ S = importlib.import_module("gea_driller.service"); R = importlib.import_module("gea_driller.rig"); SH = importlib.import_module("gea_driller.shadow")
16869
+ np_ = importlib.import_module("numpy")
16870
+ # -- DX1 the arithmetic, the twin, the filter -----------------------------------------------------------------------------
16871
+ dn, de, dv, dl = G.min_curvature(90.0, 30.0, 90.0, 30.0, 1000.0)
16872
+ _, _, dv_b, dl_b = G.min_curvature(90.0, 0.0, 91.5, 0.0, 100.0)
16873
+ _, _, _, dl_t = G.min_curvature(90.0, 0.0, 90.0, 2.0, 100.0)
16874
+ _, _, _, dl_t60 = G.min_curvature(60.0, 0.0, 60.0, 2.0, 100.0)
16875
+ lh = T.Lateral(T.Scene(3)); lh.reset(); lt = T.Lateral(T.Scene(3)); lt.reset(); l5 = T.Lateral(T.Scene(3)); l5.reset()
16876
+ for _ in range(8):
16877
+ lh.step(T.HOLD_ACTION); lt.step((2, 1, 1, 1, 0, 2)); l5.step((2, 1, 1, 1, 0))
16878
+ sc = T.Scene(3)
16879
+ cross = (sc.layer_top(500.0, 100.0) - sc.layer_top(500.0, 0.0)) - 100.0 * _m.tan(_m.radians(sc.cross_dip_deg))
16880
+ # a hole aimed right of the plan and never brought back leaves the corridor and is lost
16881
+ lw = T.Lateral(T.Scene(9)); lw.scene.corridor_ft = 50.0; lw.reset()
16882
+ while not lw.done and lw.steps < 400:
16883
+ lw.step((2, 1, 1, 1, 0, 2))
16884
+ try:
16885
+ T.full_action((1, 2, 3)); short_refused = False
16886
+ except ValueError:
16887
+ short_refused = True
16888
+ # the filter: with a cross dip in the model, the top moves with the offset; the cross dip is read only across the plan line
16889
+ f_along = PF.PositionFilter(9000.0, 0.0, 40.0, 60.0, 130.0, 115.0, seed=3, model_cross_dip_deg=2.0)
16890
+ f_across = PF.PositionFilter(9000.0, 0.0, 40.0, 60.0, 130.0, 115.0, seed=3, model_cross_dip_deg=2.0)
16891
+ for k in range(40):
16892
+ f_along.predict(30.0, 0.0); f_along.update(9020.0, 60.0) # along the plan: the gamma in the layer, nothing learnt of the cross dip
16893
+ f_across.predict(0.0, 10.0); f_across.update(9020.0 + 10.0 * (k + 1) * _m.tan(_m.radians(1.0)), 62.0, 4.0) # across: the top tracks a real cross dip of 1 degree, the tool says so
16894
+ e_along = f_along.estimate(9020.0); e_across = f_across.estimate(9020.0 + 400.0 * _m.tan(_m.radians(1.0)))
16895
+ top_shift = float(f_along.model_top(0.0, 100.0) - f_along.model_top(0.0, 0.0))
16896
+ c1 = {
16897
+ "straight": abs(dn - 1000.0 * _m.cos(_m.radians(30.0))) < 1e-6 and abs(de - 500.0) < 1e-6 and abs(dv) < 1e-6 and abs(dl) < 1e-9,
16898
+ "dogleg": abs(dl_b - 1.5) < 1e-9 and abs(dl_t - 2.0) < 1e-6 and abs(dl_t60 - 2.0 * _m.sin(_m.radians(60.0))) < 0.02 and abs(dv_b + 100.0 * _m.sin(_m.radians(0.75))) < 0.05,
16899
+ "toolface": abs(G.toolface_of(1.5, 0.0, 90.0)) < 1e-9 and abs(G.toolface_of(0.0, 2.0, 90.0) - 90.0) < 1e-9 and abs(G.toolface_of(-1.5, 0.0, 90.0) - 180.0) < 1e-9
16900
+ and abs(G.toolface_of(0.0, -2.0, 90.0) - 270.0) < 1e-9 and abs(G.dls_of(3.0, 4.0, 90.0) - 5.0) < 1e-9,
16901
+ "turns": lt.az > lh.az + 1.5 and lt.y > lh.y + 5.0 and 2.5 < lt.az - lh.az < 6.0 and abs(T.turn_toward(4.0, 0.0) - 2.0) < 1e-9 and abs(T.turn_toward(0.0, 0.5) + 0.75) < 1e-9,
16902
+ "surface": abs(cross) < 1.0 + sc.und_amp and T.Scene(1).cross_dip_deg != T.Scene(2).cross_dip_deg and "cross_dip_deg" in sc.describe() and "corridor_ft" in sc.describe(),
16903
+ "lost": lw.done and lw.ended == "outside the corridor" and lw.summary()["lost"] and lw.max_offset > lw.scene.corridor_ft + T.CORRIDOR_END_FT and any("outside the corridor" in e for e in lw.events),
16904
+ "five_choices": abs(l5.az - lh.az) < 1e-9 and abs(l5.y - lh.y) < 1e-9 and short_refused and len(T.ACTION_SIZES) == 6 and T.ACTION_NAMES[-1] == "aim",
16905
+ "obs": len(T.OBS_NAMES) == T.OBS_SIZE == 34 == lt.observe().shape[0] and all(np_.isfinite(lt.observe())) and "offset_right_of_plan" in T.OBS_NAMES,
16906
+ "filter": abs(top_shift - 100.0 * _m.tan(_m.radians(2.0))) < 1e-9 and abs(e_along["cross_dip_deg"] - 2.0) < 0.3 and e_along["cross_dip_sigma"] > 0.3
16907
+ and abs(e_across["cross_dip_deg"] - 1.0) < 0.6 and e_across["cross_dip_sigma"] < e_along["cross_dip_sigma"],
16908
+ }
16909
+ failed1 = [k for k, v in c1.items() if not v]
16910
+ ok(not failed1,
16911
+ "DX1 minimum curvature lands a straight hole 30° off the plan where the sine says, a build's dogleg is the build and a turn's counts by the inclination's sine, "
16912
+ "the toolface of a build is up and of a turn is right; in the twin an aim right of the plan turns the hole toward it at the tool's rate and swings it to the "
16913
+ "right, the layer's top moves across the plan line by the cross dip, a hole aimed out of the corridor is lost and says so, a five-choice action is taken aimed "
16914
+ "along the plan and a shorter one refused, "
16915
+ "the observation is 34 named and finite; the filter moves the model's top by its cross dip and reads the cross dip only across the plan line"
16916
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
16917
+ # -- DX2 the rig side --------------------------------------------------------------------------------------------------------
16918
+ shipped = P.Policy.load(S.SHIPPED_POLICY)
16919
+ cfg = json.loads(json.dumps(C.EXAMPLE)); cfg["output"]["dir"] = str(d / "rig"); cfg["status"]["port"] = 0; cfg["status"]["bind"] = "127.0.0.1"
16920
+ cfg["rig"]["transport"] = "sim"; cfg["model"]["plan_azimuth_deg"] = 40.0; cfg["model"]["corridor_ft"] = 120.0; cfg["model"]["cross_dip_deg"] = 1.0
16921
+ cfg = C.check(cfg)
16922
+ heel_tvd0 = float(cfg["model"]["heel_tvd_ft"]) # the twin as the rig rewrites the model block in place below
16923
+ try:
16924
+ C.check(json.loads(json.dumps(cfg)) | {"model": {**cfg["model"], "cross_dip_deg": 20.0}}); cross_refused = False
16925
+ except C.ConfigError:
16926
+ cross_refused = True
16927
+ try:
16928
+ C.check(json.loads(json.dumps(cfg)) | {"model": {**cfg["model"], "corridor_ft": 2.0}}); corridor_refused = False
16929
+ except C.ConfigError:
16930
+ corridor_refused = True
16931
+ # the bit placed from surveys: a feed that holds 90 degrees at the plan's azimuth plus 30 for 600 ft
16932
+ feed = R.Feed(cfg); feed.state.update({"hole_depth": 10800.0, "gamma": 60.0, "continuous_inc": 90.0, "continuous_azi": 70.0}); feed.last_utc = time.time(); feed.connected = True
16933
+ for md_ in (10200.0, 10290.0, 10380.0, 10470.0, 10560.0, 10650.0):
16934
+ feed.add_survey(md_, 90.0, 70.0, "test")
16935
+ adv = A.Advisor(cfg, shipped, feed, A.Records(cfg))
16936
+ obs_, ctx = adv.observation(feed.snapshot())
16937
+ rec = adv.decide(feed.snapshot())
16938
+ words_turn = adv._words(1.5, 25.0, 120.0, 500.0, "drill", {"inc": 90.0, "bed": 0.1, "balling": 0.0, "plan_az": 40.0}, {}, 2.0)
16939
+ words_aim = adv._words(1.5, 25.0, 120.0, 500.0, "drill", {"inc": 90.0, "bed": 0.1, "balling": 0.0, "plan_az": 40.0}, {}, 2.0, 4.0)
16940
+ words_hold = adv._words(0.0, 25.0, 120.0, 500.0, "drill", {"inc": 90.0, "bed": 0.1, "balling": 0.0, "plan_az": 40.0}, {}, 0.0, 0.0)
16941
+ ov = adv._action_of({"turn_deg_100ft": -2.0, "build_deg_100ft": 1.5}, [2, 1, 1, 1, 0])
16942
+ # the limit holds the build and the turn together: a 4 deg/100 ft limit on a 3 build with a 2 turn clips both
16943
+ cfg_l = json.loads(json.dumps(cfg)); cfg_l["limits"]["max_dls_deg_100ft"] = 3.0
16944
+ adv_l = A.Advisor(C.check(cfg_l), shipped, feed, A.Records(cfg_l))
16945
+ rl = adv_l.decide(feed.snapshot())
16946
+ # the twin as the rig publishes the azimuth and the plan view has what it needs
16947
+ drl = S.Driller(cfg, policy=shipped, twin_seed=11, twin_pace_s=0.5)
16948
+ drl.start()
16949
+ try:
16950
+ t_end = time.monotonic() + 20
16951
+ while time.monotonic() < t_end and len(drl.advisor.history) < 2:
16952
+ time.sleep(0.2)
16953
+ live = drl.live(); snap = drl.feed.snapshot()
16954
+ import urllib.request
16955
+ with urllib.request.urlopen(drl.url + "/live", timeout=5) as r_:
16956
+ screen = r_.read().decode("utf-8")
16957
+ cur = dict(drl.advisor.history[-1]) if drl.advisor.history else None
16958
+ finally:
16959
+ drl.stop()
16960
+ sv_text = Path(cfg["output"]["dir"], "surveys.csv").read_text(encoding="utf-8")
16961
+ c2 = {
16962
+ "placed": abs(ctx["vs"] - 600.0 * _m.cos(_m.radians(30.0))) < 0.5 and abs(ctx["y"] - 600.0 * _m.sin(_m.radians(30.0))) < 0.5 and abs(ctx["az"] - 30.0) < 1e-9
16963
+ and abs(ctx["tvd"] - heel_tvd0) < 0.5 and ctx["corridor"] == 120.0 and obs_.shape[0] == T.OBS_SIZE and abs(obs_[29] * 50.0 - ctx["y"]) < 1e-6,
16964
+ "decision": rec is not None and rec["recommend"].get("aim_deg") in T.AIM_CHOICES and abs(rec["recommend"]["turn_deg_100ft"]) <= T.TURN_RATE + 1e-9
16965
+ and abs(rec["recommend"]["turn_deg_100ft"] - T.turn_toward(rec["recommend"]["aim_deg"], 30.0)) < 1e-6 and rec["recommend"]["steer_az"] in ("hold", "turn right", "turn left")
16966
+ and abs(rec["recommend"]["aim_azimuth_deg"] - (40.0 + rec["recommend"]["aim_deg"])) < 1e-9
16967
+ and 0.0 <= rec["recommend"]["toolface_deg"] < 360.0 and any("plan line" in r_ for r_ in rec["reasons"]) and "aim" in " ".join(rec["reasons"]) and len(rec["action"]) == 6,
16968
+ "words": "turn right 2.0°/100 ft" in words_turn and "toolface 53°" in words_turn and "Build +1.5" in words_turn and words_hold.startswith("Hold angle and azimuth")
16969
+ and "aim 4° right of the plan (azimuth 44.0°), turning right 2.0°/100 ft" in words_aim,
16970
+ "override": ov == (3, 1, 1, 1, 0, 0),
16971
+ "limit": rl is not None and G.dls_of(rl["recommend"]["build_deg_100ft"], rl["recommend"]["turn_deg_100ft"], 90.0) <= 3.0 + 1e-9,
16972
+ "refusals": cross_refused and corridor_refused,
16973
+ "twin_rig": snap["state"].get("continuous_azi") is not None and live["plan_azimuth_deg"] is not None and live["model"]["corridor_ft"] > 0 and live["path"] and "y" in live["path"][-1]
16974
+ and live["advisor"]["position"] is not None and "true_y" in live["twin"] and cur is not None and "turn_deg_100ft" in cur["recommend"]
16975
+ and all(s_.get("azi") is not None for s_ in snap["surveys"]),
16976
+ "screen": "The plan view" in screen and "drawPlan" in screen and "turn_deg_100ft" in screen and "off the plan line" in screen and 'placeholder="aim' in screen,
16977
+ "surveys_file": "azimuth_deg" in sv_text.splitlines()[0] and len(sv_text.splitlines()) >= 2 and sv_text.splitlines()[1].split(",")[2] not in ("", "0.00"),
16978
+ "help": not H.check("driller") and "The third dimension" in (pkg / "help" / "driller.md").read_text(encoding="utf-8") and "plan view" in (pkg / "help" / "tutorial" / "28-driller-office.md").read_text(encoding="utf-8"),
16979
+ }
16980
+ failed2 = [k for k, v in c2.items() if not v]
16981
+ ok(not failed2,
16982
+ "DX2 the advisor places the bit from surveys 30° off the plan where the arithmetic says (along, right, depth), a decision carries the aim, the turn it asks "
16983
+ "from where the hole points and the toolface, with the azimuth and the corridor among its reasons, the words name a build and an aim as one toolface, an "
16984
+ "override takes the aim or the turn made, the DLS limit "
16985
+ "holds the build and the turn together, a cross dip and a corridor out of range are refused, the twin as the rig publishes the azimuth and surveys with it, "
16986
+ "the screen carries the plan view, and the help and the tutorial say so" + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
16987
+ # -- DX3 the policy and shadow mode -------------------------------------------------------------------------------------------
16988
+ ev = P.evaluate(P.policy_act_fn(shipped), range(20), "agent"); tb = P.evaluate(T.textbook_action, range(20), "textbook")
16989
+ lat_az = str(d / "lat_az.csv"); lat_no = str(d / "lat_no.csv")
16990
+ info = SH.twin_lateral(lat_az, seed=5, act_fn=P.policy_act_fn(shipped)); SH.twin_lateral(lat_no, seed=5, act_fn=P.policy_act_fn(shipped), with_azimuth=False)
16991
+ sc5 = info["scene"]
16992
+ model5 = {"layer_top_tvd_ft": sc5["layer_top_heel_ft"], "thickness_ft": sc5["thickness_ft"], "dip_deg": sc5["dip_deg"], "cross_dip_deg": sc5["cross_dip_deg"],
16993
+ "gamma_in": sc5["gamma_in_above_below"][0], "gamma_above": sc5["gamma_in_above_below"][1], "gamma_below": sc5["gamma_in_above_below"][2],
16994
+ "heel_md_ft": info["heel_md"], "plan_azimuth_deg": sc5["plan_azimuth_deg"], "corridor_ft": sc5["corridor_ft"]}
16995
+ r_az = SH.run(lat_az, shipped, model=model5, out_dir=str(d / "sh_az"), well="Twin 5"); r_no = SH.run(lat_no, shipped, model=model5, well="Twin 5")
16996
+ md_az = Path(r_az["paths"]["md"]).read_text(encoding="utf-8")
16997
+ c3 = {
16998
+ "heads": list(shipped.to_json()["action_sizes"]) == list(T.ACTION_SIZES) and shipped.to_json()["obs_size"] == 34 and int(shipped.meta.get("trained_steps", 0)) >= 1000000,
16999
+ "corridor": ev["corridor_pct"] >= 90.0 and ev["lost"] == 0 and ev["max_offset_ft"] < 200.0,
17000
+ "textbook_corridor": tb["corridor_pct"] >= 95.0 and tb["lost"] == 0,
17001
+ "layer": ev["in_layer_pct"] > tb["in_layer_pct"] and ev["reward"] > tb["reward"],
17002
+ "shadow_turn": r_az["summary"]["azimuth_in_record"] and r_az["summary"]["turn_agree_pct"] is not None and r_az["summary"]["turn_agree_pct"] >= 30.0
17003
+ and (r_az["summary"]["turn_oppose_pct"] or 0.0) <= 30.0 and "The turn (right, hold or left" in md_az,
17004
+ "shadow_without": not r_no["summary"]["azimuth_in_record"] and r_no["summary"]["turn_agree_pct"] is None and all(x["actual"]["turn"] is None for x in r_no["decisions"]),
17005
+ "model_through": r_az["model"]["plan_azimuth_deg"] == sc5["plan_azimuth_deg"] and r_az["model"]["cross_dip_deg"] == sc5["cross_dip_deg"],
17006
+ }
17007
+ failed3 = [k for k, v in c3.items() if not v]
17008
+ ok(not failed3,
17009
+ f"DX3 the shipped policy has six heads over 34 observations; on twenty held-out scenes it keeps {ev['corridor_pct']:.0f}% of the hole in the corridor "
17010
+ f"(none lost, {ev['in_layer_pct']:.0f}% in the layer against the textbook's {tb['in_layer_pct']:.0f}%, SIMULATION) and the textbook holds the corridor; shadow mode "
17011
+ f"judges the turn from a record with the azimuth ({r_az['summary']['turn_agree_pct'] or 0:.0f}% agreeing) and leaves it unjudged without one, and a given model's "
17012
+ "plan azimuth and cross dip go through" + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
17013
+
17014
+
17015
+ def section_dy_vibration(tmp: str) -> None:
17016
+ """Section DY - the vibration index from the feed: the index off the surface channels (a steady feed near zero, a
17017
+ torque swinging as the twin's limit swings it reading the limit, a weight swinging reading lateral, the statistics
17018
+ path equal to the samples path, pooled statistics, a window without torque or weight reading nothing); on the twin
17019
+ the derived index tracking the twin's own; the rig side (the twin as the rig publishing the surface channels and
17020
+ no downhole index, the advisor reading the derived one and saying so, the downhole item winning, a feed with
17021
+ neither reading none, the loop disarming on the derived index, the screen, the help); GEA's side (the lateral
17022
+ file from the self-test export with the statistics, the replay reading the stick-slip stretch over the limit
17023
+ and the advisor easing there, a record without the statistics unjudged, the card)."""
17024
+ import http.cookiejar
17025
+ import importlib
17026
+ import math as _m
17027
+ import urllib.error
17028
+ import urllib.request
17029
+ from . import driller_link as DL
17030
+ from . import edr as E
17031
+ from . import helplib as H
17032
+ from .service import Service, Users
17033
+ from .workspace import Workspace
17034
+ pkg = Path(__file__).parent
17035
+ d = Path(tmp, "dy"); d.mkdir()
17036
+ prog = DL.program()
17037
+ if prog is None:
17038
+ ok(True, "DY the driller's program (gea-driller) is not installed beside GEA in this install; nothing to check here")
17039
+ return
17040
+ if prog["env"] and prog["env"] not in sys.path:
17041
+ sys.path.insert(0, prog["env"])
17042
+ T = importlib.import_module("gea_driller.twin"); V = importlib.import_module("gea_driller.vibration"); P = importlib.import_module("gea_driller.policy")
17043
+ A = importlib.import_module("gea_driller.advisor"); C = importlib.import_module("gea_driller.config"); S = importlib.import_module("gea_driller.service")
17044
+ R = importlib.import_module("gea_driller.rig"); SH = importlib.import_module("gea_driller.shadow")
17045
+ np_ = importlib.import_module("numpy")
17046
+ # -- DY1 the index --------------------------------------------------------------------------------------------------------
17047
+ tt = np_.arange(60)
17048
+ steady = {"torque": 10 + 0.02 * np_.sin(tt), "rpm": 120 + 0 * tt, "wob": 25 + 0.05 * np_.cos(tt), "hkld": 175 - 0.05 * np_.cos(tt), "spp": 2800 + 0 * tt}
17049
+ ss = {"torque": 10 * (1 + 0.48 * np_.sin(2 * _m.pi * tt / 8)), "rpm": 120 + 0 * tt, "wob": 25 + 0 * tt, "hkld": 175 + 0 * tt, "spp": 2800 + 0 * tt}
17050
+ lat = {"torque": 10 + 0 * tt, "rpm": 120 + 0 * tt, "wob": 25 * (1 + 0.24 * np_.sin(2 * _m.pi * tt / 5)), "hkld": 175 - 25 * 0.24 * np_.sin(2 * _m.pi * tt / 5), "spp": 2800 + 0 * tt}
17051
+ v0 = V.index(steady); v1 = V.index(ss); v2 = V.index(lat)
17052
+ v1s = V.index_from_stats(V.stats(ss))
17053
+ pooled = V.index_from_stats(V.combine_stats([V.stats({k: v[:30] for k, v in ss.items()}), V.stats({k: v[30:] for k, v in ss.items()})]))
17054
+ none = V.index({"rpm": 120 + 0 * tt, "spp": 2800 + 0 * tt})
17055
+ short = V.index({k: v[:5] for k, v in ss.items()})
17056
+ # on the twin: the derived index against the twin's own, step by step over twenty scenes
17057
+ tr, de = [], []
17058
+ for sd in range(20):
17059
+ lt = T.Lateral(T.Scene(sd)); o = lt.reset()
17060
+ while not lt.done:
17061
+ o, _, _, inf = lt.step(T.textbook_action(o))
17062
+ if inf.get("conditioning") != "drill":
17063
+ continue
17064
+ smp = [lt.surface_sample() for _ in range(20)]
17065
+ v = V.index({"torque": [x["torque"] for x in smp], "rpm": [x["rpm"] for x in smp], "wob": [x["wob"] for x in smp], "hkld": [x["hookload"] for x in smp], "spp": [x["spp"] for x in smp]})
17066
+ tr.append(lt.vib); de.append(v["index"])
17067
+ tr_, de_ = np_.array(tr), np_.array(de)
17068
+ corr = float(np_.corrcoef(tr_, de_)[0, 1]); agree = float(((tr_ > T.VIB_LIMIT) == (de_ > T.VIB_LIMIT)).mean())
17069
+ c1 = {
17070
+ "steady": v0 is not None and v0["index"] < 0.1,
17071
+ "stick_slip": v1 is not None and abs(v1["index"] - 1.2) < 0.08 and v1["kind"] == "stick-slip" and abs(v1["torque_amplitude"] - 0.48) < 0.03,
17072
+ "lateral": v2 is not None and abs(v2["index"] - 1.2) < 0.1 and v2["kind"] == "lateral" and v2["stick_slip"] < 0.1,
17073
+ "paths": v1s["index"] == v1["index"] and abs(pooled["index"] - v1["index"]) < 0.08,
17074
+ "nothing": none is None and short is None,
17075
+ "twin": len(tr) > 1500 and corr > 0.95 and agree >= 0.95 and float(np_.median(np_.abs(tr_ - de_))) < 0.05,
17076
+ "words": "stick-slip" in V.words(v1, 1.2) and "over the limit" in V.words(v1, 1.0) and "over the limit" not in V.words(v0, 1.2),
17077
+ }
17078
+ failed1 = [k for k, v in c1.items() if not v]
17079
+ ok(not failed1,
17080
+ f"DY1 the vibration index off the surface channels: a steady feed reads {v0['index'] if v0 else 'none'}, a torque swinging ±48% reads {v1['index'] if v1 else 'none'} as stick-slip, "
17081
+ f"a weight swinging ±24% reads {v2['index'] if v2 else 'none'} as lateral, the statistics path equals the samples path and pooled windows agree, a window without torque "
17082
+ f"or weight reads nothing; on twenty twin scenes the derived index tracks the twin's own (correlation {corr:.3f}, the limit's crossings agreeing {100 * agree:.0f}%, "
17083
+ "SIMULATION - the twin's calibration, not a string's)" + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
17084
+ # -- DY2 the rig side ---------------------------------------------------------------------------------------------------------
17085
+ shipped = P.Policy.load(S.SHIPPED_POLICY)
17086
+ cfg = json.loads(json.dumps(C.EXAMPLE)); cfg["output"]["dir"] = str(d / "rig"); cfg["status"]["port"] = 0; cfg["status"]["bind"] = "127.0.0.1"
17087
+ cfg["rig"]["transport"] = "sim"; cfg["closed_loop"]["allowed"] = True; cfg["closed_loop"]["stale_s"] = 30
17088
+ cfg = C.check(cfg)
17089
+ try:
17090
+ C.check(json.loads(json.dumps(cfg)) | {"rig": {**cfg["rig"], "vibration_window_s": 2}}); win_refused = False
17091
+ except C.ConfigError:
17092
+ win_refused = True
17093
+ drl = S.Driller(cfg, policy=shipped, twin_seed=11, twin_pace_s=0.6)
17094
+ drl.start()
17095
+ try:
17096
+ t_end = time.monotonic() + 25
17097
+ while time.monotonic() < t_end and len(drl.advisor.history) < 3:
17098
+ time.sleep(0.2)
17099
+ snap = drl.feed.snapshot(); live = drl.live()
17100
+ cur = dict(drl.advisor.history[-1]) if drl.advisor.history else None
17101
+ md = snap["state"].get("hole_depth")
17102
+ rq = urllib.request.Request(drl.url + "/api/arm", data=json.dumps({"by": "tester", "to_md": md + 400}).encode(), headers={"Content-Type": "application/json"}, method="POST")
17103
+ with urllib.request.urlopen(rq, timeout=5) as r_:
17104
+ armed = json.load(r_)
17105
+ drl.cfg["limits"]["vibration_index"] = 0.01 # the limit lowered under the loop: the derived index crosses it at the next look
17106
+ t_end = time.monotonic() + 8
17107
+ while time.monotonic() < t_end and drl.advisor.armed is not None:
17108
+ time.sleep(0.2)
17109
+ drl.cfg["limits"]["vibration_index"] = 1.2
17110
+ last = drl.advisor.arm_log[-1]
17111
+ with urllib.request.urlopen(drl.url + "/live", timeout=5) as r_:
17112
+ screen = r_.read().decode("utf-8")
17113
+ finally:
17114
+ drl.stop()
17115
+ # the downhole item wins; a feed with neither reads none
17116
+ feed = R.Feed(cfg); feed.state.update({"hole_depth": 10500.0, "gamma": 60.0, "continuous_inc": 90.0, "vibration": 1.7}); feed.last_utc = time.time(); feed.connected = True
17117
+ for k in range(30):
17118
+ feed.window.append(float(k), torque=10 * (1 + 0.1 * _m.sin(k)), rpm=120.0, wob=25.0, hkld=175.0, spp=2800.0)
17119
+ v_tool = A.Advisor.vibration_of(feed.snapshot())
17120
+ feed.state["vibration"] = None
17121
+ v_der = A.Advisor.vibration_of(feed.snapshot())
17122
+ feed2 = R.Feed(cfg); feed2.state.update({"hole_depth": 10500.0, "gamma": 60.0}); feed2.last_utc = time.time(); feed2.connected = True
17123
+ v_none = A.Advisor.vibration_of(feed2.snapshot())
17124
+ adv2 = A.Advisor(cfg, shipped, feed2, A.Records(json.loads(json.dumps(cfg)) | {"output": {"dir": str(d / "rig2")}}))
17125
+ rec2 = adv2.decide(feed2.snapshot())
17126
+ decs = [json.loads(l) for l in open(d / "rig" / "decisions.jsonl", encoding="utf-8")]
17127
+ c2 = {
17128
+ "twin_rig": snap["state"].get("torque") is not None and snap["state"].get("hookload") is not None and snap["state"].get("vibration") is None and snap.get("vibration") is not None
17129
+ and snap["vibration"]["index"] > 0 and snap.get("window_n", 0) >= 10 and abs(snap["vibration"]["index"] - drl.feed.lat.vib) < 0.25,
17130
+ "advisor": cur is not None and cur["context"]["vibration_source"] == V.SOURCE and cur["context"]["vibration"] > 0 and any("derived from the surface data" in r_ for r_ in cur["reasons"])
17131
+ and live["vibration"]["source"] == V.SOURCE and live["vibration"]["parts"]["kind"] in ("stick-slip", "lateral"),
17132
+ "wins": v_tool[0] == 1.7 and v_tool[1] == "the downhole tool" and v_der[1] == V.SOURCE and 0 < v_der[0] < 1.0,
17133
+ "none": v_none[0] == 0.0 and v_none[1].startswith("none") and rec2 is not None and any("vibration unread" in r_ for r_ in rec2["reasons"]),
17134
+ "disarms": armed.get("ok") and last["event"] == "disarmed" and "vibration" in last["reason"] and "surface data" in last["reason"],
17135
+ "records": any((x.get("context") or {}).get("vibration_source") == V.SOURCE for x in decs),
17136
+ "screen": "from surface data" in screen and "downhole tool" in screen,
17137
+ "refusal": win_refused,
17138
+ "help": not H.check("driller") and "The vibration index from the feed" in (pkg / "help" / "driller.md").read_text(encoding="utf-8")
17139
+ and "surface" in (pkg / "help" / "tutorial" / "29-driller-rig.md").read_text(encoding="utf-8"),
17140
+ }
17141
+ failed2 = [k for k, v in c2.items() if not v]
17142
+ ok(not failed2,
17143
+ "DY2 the twin as the rig publishes the surface torque, weight and hook load and no downhole index; the feed's window gives the derived index close to the "
17144
+ "twin's own, the advisor reads it and says so in its reasons, the records and the screen; the downhole tool's item wins when it comes, a feed with neither "
17145
+ "reads none and says so; the closed loop disarms on the derived index crossing the limit; a window too short is refused; the help and the tutorial say so"
17146
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
17147
+ # -- DY3 GEA's side: the lateral file with the statistics, the replay over the limit, the card ---------------------------------------
17148
+ ws = Workspace.create(str(d / "ws"), "Pad 3 vibration", actor="tester")
17149
+ exp = E.selftest(str(d), 3000)
17150
+ w = ws.add_well_file(exp["path"], display="Well EDR", actor="tester")
17151
+ lat_csv = str(d / "lat.csv")
17152
+ info = DL.lateral_from_edr(ws, w["id"], lat_csv)
17153
+ head = Path(lat_csv).read_text(encoding="utf-8").splitlines()[0].split(",")
17154
+ rec = DL.shadow(ws, w["id"], actor="tester")
17155
+ sm = rec["summary"]
17156
+ # the twin's lateral with and without the statistics
17157
+ tw_csv = str(d / "tw.csv"); tw_no = str(d / "tw_no.csv")
17158
+ tinfo = SH.twin_lateral(tw_csv, seed=5, act_fn=P.policy_act_fn(shipped)); SH.twin_lateral(tw_no, seed=5, act_fn=P.policy_act_fn(shipped), with_stats=False)
17159
+ r_tw = SH.run(tw_csv, shipped, well="Twin 5"); r_no = SH.run(tw_no, shipped, well="Twin 5")
17160
+ tv = dict(tinfo["true_vibration"]); mds = np_.array(sorted(tv))
17161
+ tvv = np_.array([tv[m_] for m_ in mds])
17162
+ def true_over(md_): # the twin's own index averaged over the 30 ft the replay's window pooled
17163
+ j0, j1 = int(np_.searchsorted(mds, md_ - 30.0)), int(np_.searchsorted(mds, md_, side="right"))
17164
+ return float(tvv[j0:j1].mean()) if j1 > j0 else float("nan")
17165
+ pairs = np_.array([(x["vibration"], true_over(x["md"])) for x in r_tw["decisions"] if x["vibration"] is not None])
17166
+ pairs = pairs[np_.isfinite(pairs[:, 1])]
17167
+ corr_tw = float(np_.corrcoef(pairs[:, 0], pairs[:, 1])[0, 1]) if len(pairs) > 10 else 0.0
17168
+ md_text = Path(ws.reports_dir, rec["report"]["md"]).read_text(encoding="utf-8")
17169
+ app_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
17170
+ c3 = {
17171
+ "lateral_file": all(c in head for c in DL.LATERAL_STATS) and info["vibration_rows"] >= 1000,
17172
+ "over_limit": sm["vibration_in_record"] and sm["vibration_over_limit_pct"] is not None and 5.0 <= sm["vibration_over_limit_pct"] <= 40.0 and sm["vibration_mean"] > 0.3
17173
+ and sm["eased_when_over_pct"] is not None and sm["eased_when_over_pct"] >= 50.0,
17174
+ "report": "stood over the limit" in md_text and "| Vib |" in md_text,
17175
+ "twin_lateral": r_tw["summary"]["vibration_in_record"] and len(pairs) > 50 and corr_tw > 0.7 and float(np_.median(np_.abs(pairs[:, 0] - pairs[:, 1]))) < 0.12,
17176
+ "without": not r_no["summary"]["vibration_in_record"] and r_no["summary"]["vibration_over_limit_pct"] is None and all(x["vibration"] is None for x in r_no["decisions"]),
17177
+ "selftest_export": any(abs(float(r_.split(",")[12]) - 10.0) > 4.0 for r_ in Path(exp["path"]).read_text(encoding="utf-8").splitlines()[1:] if r_.split(",")[12] not in ("",)),
17178
+ "card": "Vibration over limit" in app_src and "eased" in app_src,
17179
+ }
17180
+ failed3 = [k for k, v in c3.items() if not v]
17181
+ ok(not failed3,
17182
+ f"DY3 GEA's lateral file carries the surface channels' statistics per foot ({info['vibration_rows']} feet of them from the self-test export), the replay reads the "
17183
+ f"stick-slip stretch over the limit at {sm['vibration_over_limit_pct'] or 0:.0f}% of the decisions and the advisor eases there {sm['eased_when_over_pct'] or 0:.0f}% of the "
17184
+ f"time, the report says so; on the twin's lateral the replayed index tracks the twin's own (correlation {corr_tw:.2f}); a record without the statistics is unjudged; "
17185
+ "the card carries the column" + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
17186
+
17187
+
16844
17188
  def main() -> int:
16845
17189
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
16846
17190
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -16971,6 +17315,8 @@ def main() -> int:
16971
17315
  section_du_tutorial(tmp)
16972
17316
  section_dv_driller(tmp)
16973
17317
  section_dw_shadow(tmp)
17318
+ section_dx_third_dimension(tmp)
17319
+ section_dy_vibration(tmp)
16974
17320
  if _FAILS:
16975
17321
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
16976
17322
  for f in _FAILS:
@@ -281,6 +281,11 @@ def wells_with_records(ws) -> List[dict]:
281
281
  # -- shadow mode: a drilled well of this workspace replayed through the advisor ---------------------------------------------
282
282
 
283
283
  LATERAL_COLUMNS = ('md_ft', 'tvd_ft', 'gamma_api', 'wob_klb', 'rpm', 'flow_gpm', 'rop_ft_hr', 'spp_psi', 'hours')
284
+ # per foot, the surface channels' mean and RMS about it over the seconds since the foot before (since 0.81.0): what the
285
+ # driller's vibration index is read off when the string had no downhole tool - the same names the driller's program reads
286
+ LATERAL_STATS = ('torque_mean', 'torque_rms', 'rpm_mean', 'rpm_rms', 'wob_mean', 'wob_rms', 'hkld_mean', 'hkld_rms', 'spp_mean', 'spp_rms')
287
+ _STAT_CH = {'torque': 'torque', 'rpm': 'rpm', 'wob': 'wob', 'hkld': 'hkld', 'spp': 'spp'}
288
+ _STAT_MIN = 5 # seconds of a foot at least; the index pools the feet of a decision
284
289
 
285
290
 
286
291
  def lateral_from_edr(ws, well_id: str, out_csv: str) -> dict:
@@ -296,7 +301,7 @@ def lateral_from_edr(ws, well_id: str, out_csv: str) -> dict:
296
301
  if not src or not E.is_edr(src):
297
302
  raise ValueError(f"{well_id} has no drilling-recorder export (an EDR CSV) under its sources")
298
303
  st = E.read(src)
299
- ch = {k: E._ch(st, k) for k in ('hole', 'tvd', 'gamma', 'wob', 'rpm', 'flow', 'rop', 'spp', 'onb')}
304
+ ch = {k: E._ch(st, k) for k in ('hole', 'tvd', 'gamma', 'wob', 'rpm', 'flow', 'rop', 'spp', 'onb', 'torque', 'hkld')}
300
305
  hole = ch['hole']
301
306
  if hole is None or ch['tvd'] is None:
302
307
  raise ValueError('the export carries no hole depth or no interpolated TVD; the lateral cannot be placed')
@@ -308,32 +313,47 @@ def lateral_from_edr(ws, well_id: str, out_csv: str) -> dict:
308
313
  n = len(hole)
309
314
  rows = []
310
315
  seen = set(); g_acc = []
316
+ s_acc = {k: [] for k in _STAT_CH}
311
317
  for i in range(n):
312
318
  h = hole[i]
313
319
  if h is None or (isinstance(h, float) and (h != h)) or not keep_h[i]:
314
320
  continue
315
321
  if ch['gamma'] is not None and not (ch['gamma'][i] != ch['gamma'][i]):
316
322
  g_acc.append(float(ch['gamma'][i]))
323
+ for k, c in _STAT_CH.items():
324
+ v = _v(ch[c], i)
325
+ if v == v: # not NaN
326
+ s_acc[k].append(v)
317
327
  foot = int(h)
318
328
  if foot in seen:
319
329
  continue
320
330
  seen.add(foot)
321
331
  tvd = ch['tvd'][i]
322
- rows.append({'md_ft': float(h), 'tvd_ft': float(tvd) if not (tvd != tvd) else float('nan'),
323
- 'gamma_api': float(sorted(g_acc)[len(g_acc) // 2]) if g_acc else float('nan'),
324
- 'wob_klb': _v(ch['wob'], i), 'rpm': _v(ch['rpm'], i), 'flow_gpm': _v(ch['flow'], i), 'rop_ft_hr': _v(ch['rop'], i), 'spp_psi': _v(ch['spp'], i),
325
- 'hours': float(secs[i]) / 3600.0})
326
- g_acc = []
332
+ row = {'md_ft': float(h), 'tvd_ft': float(tvd) if not (tvd != tvd) else float('nan'),
333
+ 'gamma_api': float(sorted(g_acc)[len(g_acc) // 2]) if g_acc else float('nan'),
334
+ 'wob_klb': _v(ch['wob'], i), 'rpm': _v(ch['rpm'], i), 'flow_gpm': _v(ch['flow'], i), 'rop_ft_hr': _v(ch['rop'], i), 'spp_psi': _v(ch['spp'], i),
335
+ 'hours': float(secs[i]) / 3600.0}
336
+ for k in _STAT_CH:
337
+ a = s_acc[k]
338
+ if len(a) >= _STAT_MIN:
339
+ m = sum(a) / len(a)
340
+ row[f'{k}_mean'] = m; row[f'{k}_rms'] = (sum((x - m) ** 2 for x in a) / len(a)) ** 0.5
341
+ else:
342
+ row[f'{k}_mean'] = float('nan'); row[f'{k}_rms'] = float('nan')
343
+ rows.append(row)
344
+ g_acc = []; s_acc = {k: [] for k in _STAT_CH}
327
345
  if len(rows) < 100:
328
346
  raise ValueError(f'the export yields only {len(rows)} feet of new hole; a lateral is thousands')
329
347
  rows.sort(key=lambda r: r['md_ft'])
330
348
  os.makedirs(os.path.dirname(out_csv), exist_ok=True)
349
+ cols = list(LATERAL_COLUMNS) + list(LATERAL_STATS)
331
350
  with open(out_csv, 'w', encoding='utf-8', newline='') as f:
332
- w = _csv.DictWriter(f, fieldnames=list(LATERAL_COLUMNS)); w.writeheader()
351
+ w = _csv.DictWriter(f, fieldnames=cols); w.writeheader()
333
352
  for r in rows:
334
- w.writerow({c: ('' if (isinstance(r[c], float) and r[c] != r[c]) or r[c] is None else f'{r[c]:.3f}') for c in LATERAL_COLUMNS})
353
+ w.writerow({c: ('' if (isinstance(r[c], float) and r[c] != r[c]) or r[c] is None else f'{r[c]:.3f}') for c in cols})
335
354
  rows.sort(key=lambda r: r['md_ft'])
336
355
  return {'rows': len(rows), 'source': src, 'md_from': rows[0]['md_ft'], 'md_to': rows[-1]['md_ft'], 'gamma_rows': sum(1 for r in rows if r['gamma_api'] == r['gamma_api']),
356
+ 'vibration_rows': sum(1 for r in rows if r['torque_mean'] == r['torque_mean'] or r['wob_mean'] == r['wob_mean']),
337
357
  'seconds': int(n), 'excluded_rows': int((~keep_h).sum()), 'depth_events': {k: len(v) for k, v in de.items() if isinstance(v, list)}}
338
358
 
339
359
 
@@ -748,8 +748,10 @@ def selftest(tmp: str, rows_per_section: int = 3000) -> dict:
748
748
  if on:
749
749
  depth += 600.0 / 3600.0 # 600 ft/h while on bottom (a self-test cadence, not a rig's)
750
750
  tvd = depth if depth < 2000.0 else 2000.0 + (depth - 2000.0) * 0.02 # a lateral past 2,000 ft
751
+ # a stretch of stick-slip in the lateral: the surface torque swinging ±60% with an eight-second period (since 0.81.0)
752
+ tq = 10.0 * (1.0 + 0.6 * math.sin(2 * math.pi * i / 8.0)) if (s == 2 and 1000 <= i < 2000 and on) else 10.0
751
753
  row = [t.strftime('%Y/%m/%d'), t.strftime('%H:%M:%S'), f'{depth:.1f}', f'{depth - (0 if on else 30):.1f}', '600.00' if on else '0.00',
752
- '20.0' if on else '0.0', '60.00' if on else '0.00', '2500.00', '150.0', '400.00', '500.00', '50.00', '10.000', str(on),
754
+ '20.0' if on else '0.0', '60.00' if on else '0.00', '2500.00', '150.0', '400.00', '500.00', '50.00', f'{tq:.3f}', str(on),
753
755
  '-999.25' if s == 0 else f'{(45.0 if depth < 2300.0 else 115.0) + (i % 7):.2f}', '100', '0.00' if i % 500 == 0 else '45.00', '' if (i % 10 == 0) else f'{tvd:.2f}']
754
756
  w.writerow(row); t += timedelta(seconds=1)
755
757
  st = read(p)
@@ -180,7 +180,10 @@ declared extras.
180
180
  feed, a crossed limit or the section's end; this version's only downlink
181
181
  keeps every command on record and sends none. The records carry no
182
182
  secret; GEA pulls them checked against their hashes. The twin as the rig
183
- on the office PC binds to 127.0.0.1 only.
183
+ on the office PC binds to 127.0.0.1 only. Since 0.80.0 the records carry
184
+ the hole's azimuth and its offset from the plan line, as the surveys
185
+ give them - a position, not a coordinate: the plan's azimuth and the
186
+ heel are in the rig PC's config, no surface location is.
184
187
 
185
188
  ## What it records
186
189