gea-program 0.15.0__tar.gz → 0.16.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 (241) hide show
  1. {gea_program-0.15.0 → gea_program-0.16.0}/PKG-INFO +31 -4
  2. {gea_program-0.15.0 → gea_program-0.16.0}/README.md +30 -3
  3. {gea_program-0.15.0 → gea_program-0.16.0}/gea/__init__.py +2 -1
  4. {gea_program-0.15.0 → gea_program-0.16.0}/gea/__main__.py +42 -4
  5. {gea_program-0.15.0 → gea_program-0.16.0}/gea/acceptance_tests.py +129 -0
  6. {gea_program-0.15.0 → gea_program-0.16.0}/gea/doctor.py +25 -1
  7. gea_program-0.16.0/gea/etp.py +1027 -0
  8. gea_program-0.16.0/gea/etp12_schemas.json +3702 -0
  9. gea_program-0.16.0/gea/help/etp.md +11 -0
  10. {gea_program-0.15.0 → gea_program-0.16.0}/gea/helplib.py +1 -0
  11. {gea_program-0.15.0 → gea_program-0.16.0}/gea/patches.py +5 -2
  12. {gea_program-0.15.0 → gea_program-0.16.0}/gea/service.py +10 -1
  13. {gea_program-0.15.0 → gea_program-0.16.0}/gea/web/app.html +1 -1
  14. {gea_program-0.15.0 → gea_program-0.16.0}/gea/workspace.py +1 -1
  15. {gea_program-0.15.0 → gea_program-0.16.0}/gea_program.egg-info/PKG-INFO +31 -4
  16. {gea_program-0.15.0 → gea_program-0.16.0}/gea_program.egg-info/SOURCES.txt +3 -0
  17. {gea_program-0.15.0 → gea_program-0.16.0}/pyproject.toml +2 -2
  18. {gea_program-0.15.0 → gea_program-0.16.0}/LICENSE +0 -0
  19. {gea_program-0.15.0 → gea_program-0.16.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  20. {gea_program-0.15.0 → gea_program-0.16.0}/gea/accuracy_statement.py +0 -0
  21. {gea_program-0.15.0 → gea_program-0.16.0}/gea/alarm_engine.py +0 -0
  22. {gea_program-0.15.0 → gea_program-0.16.0}/gea/bench.py +0 -0
  23. {gea_program-0.15.0 → gea_program-0.16.0}/gea/blind_harness.py +0 -0
  24. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  25. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  26. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  27. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  28. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  29. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  30. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  31. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  32. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  33. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  34. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  35. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  36. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  37. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  38. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  39. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  40. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  41. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  42. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  43. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  44. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  45. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  46. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  47. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  48. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  49. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  50. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  51. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  52. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  53. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  54. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  55. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  56. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  57. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  58. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  59. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  60. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  61. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  62. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  63. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  64. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  65. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  66. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  67. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  68. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  69. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  70. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  71. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  72. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  73. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  74. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  75. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  76. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  77. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  78. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  79. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  80. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  81. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  82. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  83. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  84. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  85. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  86. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  87. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  88. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  89. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  90. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  91. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/med_160_sapropels.txt +0 -0
  92. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  93. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  94. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  95. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  96. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  97. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  98. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  99. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  100. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  101. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  102. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  103. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  104. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  105. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  106. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  107. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  108. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  109. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  110. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  111. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  112. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  113. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  114. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  115. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  116. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  117. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  118. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  119. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  120. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  121. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  122. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  123. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  124. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  125. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  126. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  127. {gea_program-0.15.0 → gea_program-0.16.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  128. {gea_program-0.15.0 → gea_program-0.16.0}/gea/certificates.py +0 -0
  129. {gea_program-0.15.0 → gea_program-0.16.0}/gea/cli.py +0 -0
  130. {gea_program-0.15.0 → gea_program-0.16.0}/gea/client_reports.py +0 -0
  131. {gea_program-0.15.0 → gea_program-0.16.0}/gea/config_versioning.py +0 -0
  132. {gea_program-0.15.0 → gea_program-0.16.0}/gea/conformance.py +0 -0
  133. {gea_program-0.15.0 → gea_program-0.16.0}/gea/correlation.py +0 -0
  134. {gea_program-0.15.0 → gea_program-0.16.0}/gea/dashboard.py +0 -0
  135. {gea_program-0.15.0 → gea_program-0.16.0}/gea/deviation.py +0 -0
  136. {gea_program-0.15.0 → gea_program-0.16.0}/gea/downhole_engine.py +0 -0
  137. {gea_program-0.15.0 → gea_program-0.16.0}/gea/drift_monitor.py +0 -0
  138. {gea_program-0.15.0 → gea_program-0.16.0}/gea/earth_model.py +0 -0
  139. {gea_program-0.15.0 → gea_program-0.16.0}/gea/example_register_map.json +0 -0
  140. {gea_program-0.15.0 → gea_program-0.16.0}/gea/fat_sat.py +0 -0
  141. {gea_program-0.15.0 → gea_program-0.16.0}/gea/files.py +0 -0
  142. {gea_program-0.15.0 → gea_program-0.16.0}/gea/follower.py +0 -0
  143. {gea_program-0.15.0 → gea_program-0.16.0}/gea/forward_model.py +0 -0
  144. {gea_program-0.15.0 → gea_program-0.16.0}/gea/gamma.py +0 -0
  145. {gea_program-0.15.0 → gea_program-0.16.0}/gea/gauge_aging.py +0 -0
  146. {gea_program-0.15.0 → gea_program-0.16.0}/gea/gauge_specs.py +0 -0
  147. {gea_program-0.15.0 → gea_program-0.16.0}/gea/geodesy.py +0 -0
  148. {gea_program-0.15.0 → gea_program-0.16.0}/gea/gravity_reference.py +0 -0
  149. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/TESTER_GUIDE.md +0 -0
  150. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/alarms.md +0 -0
  151. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/audit-update.md +0 -0
  152. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/conformance.md +0 -0
  153. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/data-in.md +0 -0
  154. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/doctor.md +0 -0
  155. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/drift.md +0 -0
  156. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/files.md +0 -0
  157. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/instruments.md +0 -0
  158. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/notifications.md +0 -0
  159. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/osdu.md +0 -0
  160. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/patches.md +0 -0
  161. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/ppdm.md +0 -0
  162. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/quakeml.md +0 -0
  163. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/quality.md +0 -0
  164. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/seismic.md +0 -0
  165. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/site.md +0 -0
  166. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/sites.md +0 -0
  167. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/sra.md +0 -0
  168. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/start.md +0 -0
  169. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/transients.md +0 -0
  170. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/upkeep.md +0 -0
  171. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/vibration.md +0 -0
  172. {gea_program-0.15.0 → gea_program-0.16.0}/gea/help/well-tests.md +0 -0
  173. {gea_program-0.15.0 → gea_program-0.16.0}/gea/housekeeping.py +0 -0
  174. {gea_program-0.15.0 → gea_program-0.16.0}/gea/inverse_engine.py +0 -0
  175. {gea_program-0.15.0 → gea_program-0.16.0}/gea/jobs.py +0 -0
  176. {gea_program-0.15.0 → gea_program-0.16.0}/gea/live_ports.py +0 -0
  177. {gea_program-0.15.0 → gea_program-0.16.0}/gea/loadtest.py +0 -0
  178. {gea_program-0.15.0 → gea_program-0.16.0}/gea/matplotlib_demo.py +0 -0
  179. {gea_program-0.15.0 → gea_program-0.16.0}/gea/modbus.py +0 -0
  180. {gea_program-0.15.0 → gea_program-0.16.0}/gea/model_card.py +0 -0
  181. {gea_program-0.15.0 → gea_program-0.16.0}/gea/mqtt_port.py +0 -0
  182. {gea_program-0.15.0 → gea_program-0.16.0}/gea/notify.py +0 -0
  183. {gea_program-0.15.0 → gea_program-0.16.0}/gea/opcua_port.py +0 -0
  184. {gea_program-0.15.0 → gea_program-0.16.0}/gea/operator_app.py +0 -0
  185. {gea_program-0.15.0 → gea_program-0.16.0}/gea/osdu.py +0 -0
  186. {gea_program-0.15.0 → gea_program-0.16.0}/gea/permits.py +0 -0
  187. {gea_program-0.15.0 → gea_program-0.16.0}/gea/ports.py +0 -0
  188. {gea_program-0.15.0 → gea_program-0.16.0}/gea/ppdm.py +0 -0
  189. {gea_program-0.15.0 → gea_program-0.16.0}/gea/profile_catalog.py +0 -0
  190. {gea_program-0.15.0 → gea_program-0.16.0}/gea/project.py +0 -0
  191. {gea_program-0.15.0 → gea_program-0.16.0}/gea/qt6_downhole_app.py +0 -0
  192. {gea_program-0.15.0 → gea_program-0.16.0}/gea/quakeml.py +0 -0
  193. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reconciler.py +0 -0
  194. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  195. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  196. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  197. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  198. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  199. {gea_program-0.15.0 → gea_program-0.16.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  200. {gea_program-0.15.0 → gea_program-0.16.0}/gea/rock_inventory.py +0 -0
  201. {gea_program-0.15.0 → gea_program-0.16.0}/gea/sample_record.py +0 -0
  202. {gea_program-0.15.0 → gea_program-0.16.0}/gea/sample_well_profile.csv +0 -0
  203. {gea_program-0.15.0 → gea_program-0.16.0}/gea/sbom.py +0 -0
  204. {gea_program-0.15.0 → gea_program-0.16.0}/gea/segy.py +0 -0
  205. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic.py +0 -0
  206. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_array.py +0 -0
  207. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_assoc.py +0 -0
  208. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_detect.py +0 -0
  209. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_film.py +0 -0
  210. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_harmonic.py +0 -0
  211. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_qc.py +0 -0
  212. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_response.py +0 -0
  213. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_signature.py +0 -0
  214. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_track.py +0 -0
  215. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_uncertainty.py +0 -0
  216. {gea_program-0.15.0 → gea_program-0.16.0}/gea/seismic_unlisted.py +0 -0
  217. {gea_program-0.15.0 → gea_program-0.16.0}/gea/sensor_swap.py +0 -0
  218. {gea_program-0.15.0 → gea_program-0.16.0}/gea/service_life.py +0 -0
  219. {gea_program-0.15.0 → gea_program-0.16.0}/gea/shell.py +0 -0
  220. {gea_program-0.15.0 → gea_program-0.16.0}/gea/shut_in.py +0 -0
  221. {gea_program-0.15.0 → gea_program-0.16.0}/gea/sla_report.py +0 -0
  222. {gea_program-0.15.0 → gea_program-0.16.0}/gea/sra.py +0 -0
  223. {gea_program-0.15.0 → gea_program-0.16.0}/gea/store_forward.py +0 -0
  224. {gea_program-0.15.0 → gea_program-0.16.0}/gea/strata_join.py +0 -0
  225. {gea_program-0.15.0 → gea_program-0.16.0}/gea/supervisor.py +0 -0
  226. {gea_program-0.15.0 → gea_program-0.16.0}/gea/survey_cmd.py +0 -0
  227. {gea_program-0.15.0 → gea_program-0.16.0}/gea/survey_view.py +0 -0
  228. {gea_program-0.15.0 → gea_program-0.16.0}/gea/telemetry.py +0 -0
  229. {gea_program-0.15.0 → gea_program-0.16.0}/gea/tool_library.py +0 -0
  230. {gea_program-0.15.0 → gea_program-0.16.0}/gea/transient.py +0 -0
  231. {gea_program-0.15.0 → gea_program-0.16.0}/gea/vibration.py +0 -0
  232. {gea_program-0.15.0 → gea_program-0.16.0}/gea/well_assembler.py +0 -0
  233. {gea_program-0.15.0 → gea_program-0.16.0}/gea/well_test_validation.py +0 -0
  234. {gea_program-0.15.0 → gea_program-0.16.0}/gea/wits0.py +0 -0
  235. {gea_program-0.15.0 → gea_program-0.16.0}/gea/witsml.py +0 -0
  236. {gea_program-0.15.0 → gea_program-0.16.0}/gea_program.egg-info/dependency_links.txt +0 -0
  237. {gea_program-0.15.0 → gea_program-0.16.0}/gea_program.egg-info/entry_points.txt +0 -0
  238. {gea_program-0.15.0 → gea_program-0.16.0}/gea_program.egg-info/requires.txt +0 -0
  239. {gea_program-0.15.0 → gea_program-0.16.0}/gea_program.egg-info/top_level.txt +0 -0
  240. {gea_program-0.15.0 → gea_program-0.16.0}/setup.cfg +0 -0
  241. {gea_program-0.15.0 → gea_program-0.16.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.15.0
3
+ Version: 0.16.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
@@ -72,7 +72,7 @@ reaches a client document. Nothing unmeasured is ever reported as met.
72
72
  rate-controlled replay, duplicate suppression, per-record latency.
73
73
  - **Configuration versioning, SBOM, monthly SLA, FAT/SAT**
74
74
  (`config_versioning.py`, `sbom.py`, `sla_report.py`, `fat_sat.py`).
75
- - **Live protocol ports** (`live_ports.py`, `wits0.py`, `witsml.py`, `opcua_port.py`,
75
+ - **Live protocol ports** (`live_ports.py`, `wits0.py`, `witsml.py`, `etp.py`, `opcua_port.py`,
76
76
  `mqtt_port.py`, `modbus.py`): WITS Level 0 from the drill floor (TCP connect,
77
77
  TCP listen, serial), WITSML 1.4.1 stores (read-only, polled), OPC UA (read and
78
78
  subscribe), MQTT (number, JSON, Sparkplug B), Modbus TCP; every message becomes
@@ -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 (333 checks)
128
+ gea accept # the product gate (337 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]")
@@ -143,7 +143,7 @@ gea swaps / gea certificates / gea notify / gea housekeeping / gea loadtest #
143
143
 
144
144
  ## Live data
145
145
 
146
- The engine reads live data through five protocol ports. All are **read-only**,
146
+ The engine reads live data through six protocol ports. All are **read-only**,
147
147
  all map only what the site declares (a tag map the client owns; an empty map
148
148
  is declined), and all write every received message to a JSON-lines recording
149
149
  that `--replay` turns back into records without a connection - which is how a
@@ -156,6 +156,9 @@ gea wits0-sim --port 5001 # a WITS0 sender to rehear
156
156
  gea wits0 --config wits0.json --seconds 60 --record floor/session.jsonl --out floor --stream-csv floor/stream.csv # in the first window, while the sender runs
157
157
  gea witsml --write-example-config witsml.json # a WITSML 1.4.1 store: url, uids, mnemonics -> tags; credentials by env name
158
158
  gea witsml --config witsml.json --seconds 60 --out store # needs a real store: the example url is a placeholder until you edit it
159
+ gea etp --write-example-config etp.json # a WITSML 2.x store over ETP v1.2: ws:// or wss:// url, bearer token by env name, channel URIs -> tags, units
160
+ gea etp-sim --port 9800 # an ETP store with two channels to rehearse with - run it in a SECOND window and leave it running
161
+ gea etp --config etp.json --seconds 60 --record rig/session.jsonl --out rig # subscribes; the store pushes every value as it is written
159
162
  gea opcua --write-example-config opcua.json # edit: endpoint, security, credential env names, nodes
160
163
  gea opcua --config opcua.json --seconds 60 --record opc/session.jsonl --out opc --stream-csv opc/stream.csv # needs a real OPC UA server, likewise
161
164
  gea mqtt --write-example-config mqtt.json # edit: broker, TLS, credential env names, topics
@@ -167,6 +170,7 @@ gea ingest --file floor/stream.csv # a stream CSV feeds the h
167
170
  |---|---|---|---|
168
171
  | `wits0` | WITS Level 0 frames (`&&` ... `!!`, four-digit item codes) over TCP connect, TCP listen or serial; record-01 dictionary built in | numeric GOOD; sentinels (-9999, -999.25) and non-numeric -> GAP with the reason | items 0105/0106 (date, time) when present, else arrival; ingest at arrival |
169
172
  | `witsml` | WITSML 1.4.1 SOAP store: GetVersion, GetCap, GetFromStore on one log object, rows newer than the last seen at each poll | numeric GOOD; the store's nullValue and empty fields -> GAP | the index curve of a time log; arrival for a depth log |
173
+ | `etp` | Energistics ETP v1.2 over WebSocket (`etp12.energistics.org`, Avro binary to the published schemas): RequestSession/OpenSession, GetChannelMetadata for the declared URIs, SubscribeChannels, ChannelData pushed by the store (Protocol 21) | numeric GOOD; a non-numeric or null value -> BAD with the reason; a channel the map does not name is counted unmapped | the store's own index: an ETP DateTime index is microseconds since the epoch; an ElapsedTime index is seconds from its declared start; a depth index leaves the arrival time standing, named |
170
174
  | `opcua` | opc.tcp, read + subscribe; Basic256Sha256 SignAndEncrypt when a certificate and key are given | StatusCode severity bits: Good -> GOOD, Uncertain -> STALE, Bad -> GAP (value withheld) | source timestamp, else server timestamp, else arrival |
171
175
  | `mqtt` | MQTT v5 / v3.1.1, TLS optional, QoS per topic; `+`/`#` wildcards | number: GOOD or GAP; JSON: value/time/quality paths; Sparkplug B: `is_null` -> GAP, `Quality` != 192 -> STALE | JSON time path, Sparkplug metric timestamp, else arrival |
172
176
  | `modbus_g6` | Modbus TCP, user-supplied register map with a citation | read failures -> GAP | arrival |
@@ -619,6 +623,29 @@ promoted; the group and the support are declarations; a matched frequency
619
623
  is not a fault size. CSV or miniSEED/SAC; `gea vibration` runs the labelled
620
624
  pump; `gea help vibration`.
621
625
 
626
+ ### WITSML 2.x over ETP
627
+
628
+ The newer rigs and stores do not poll: Energistics ETP v1.2 is a WebSocket
629
+ session, Avro-encoded to the published schemas, on which the customer names
630
+ the channels it wants and the store pushes every value as it is written.
631
+ `gea/etp.py` is that customer: the handshake with the subprotocol, the
632
+ binary encoding and the payload limits; RequestSession for Discovery, Store
633
+ and ChannelSubscribe and what the store answered; the channel metadata for
634
+ the URIs the mapping declares (a URI the store does not have is named as
635
+ missing); the subscription; and each ChannelData DataItem as a record under
636
+ the mapping's tag and unit, with the store's own index as the source time -
637
+ microseconds since the epoch for a DateTime index, seconds from the declared
638
+ start for ElapsedTime, the arrival time standing (and named) for a depth
639
+ index - and the arrival as the ingest time, so the latency is per record.
640
+ Client message ids even, the store's odd; a ProtocolException carried with
641
+ its code; a client or a store without the subprotocol refused at the
642
+ handshake. The patch supervisor runs it like every other port; the page
643
+ offers it; `gea etp-sim` is a store with two channels of a synthetic
644
+ disposal well; `gea etp --selftest` runs the codec and a loopback session;
645
+ `gea help etp`. The doctor now also names a panel started through the
646
+ `gea.exe` launcher, which pip cannot replace while it runs - stop, update,
647
+ start with `python -m gea serve`.
648
+
622
649
  ### Conformity, in the standards' words
623
650
 
624
651
  The drift report's "bias inside the instrument's class" is a statement of
@@ -33,7 +33,7 @@ reaches a client document. Nothing unmeasured is ever reported as met.
33
33
  rate-controlled replay, duplicate suppression, per-record latency.
34
34
  - **Configuration versioning, SBOM, monthly SLA, FAT/SAT**
35
35
  (`config_versioning.py`, `sbom.py`, `sla_report.py`, `fat_sat.py`).
36
- - **Live protocol ports** (`live_ports.py`, `wits0.py`, `witsml.py`, `opcua_port.py`,
36
+ - **Live protocol ports** (`live_ports.py`, `wits0.py`, `witsml.py`, `etp.py`, `opcua_port.py`,
37
37
  `mqtt_port.py`, `modbus.py`): WITS Level 0 from the drill floor (TCP connect,
38
38
  TCP listen, serial), WITSML 1.4.1 stores (read-only, polled), OPC UA (read and
39
39
  subscribe), MQTT (number, JSON, Sparkplug B), Modbus TCP; every message becomes
@@ -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 (333 checks)
89
+ gea accept # the product gate (337 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]")
@@ -104,7 +104,7 @@ gea swaps / gea certificates / gea notify / gea housekeeping / gea loadtest #
104
104
 
105
105
  ## Live data
106
106
 
107
- The engine reads live data through five protocol ports. All are **read-only**,
107
+ The engine reads live data through six protocol ports. All are **read-only**,
108
108
  all map only what the site declares (a tag map the client owns; an empty map
109
109
  is declined), and all write every received message to a JSON-lines recording
110
110
  that `--replay` turns back into records without a connection - which is how a
@@ -117,6 +117,9 @@ gea wits0-sim --port 5001 # a WITS0 sender to rehear
117
117
  gea wits0 --config wits0.json --seconds 60 --record floor/session.jsonl --out floor --stream-csv floor/stream.csv # in the first window, while the sender runs
118
118
  gea witsml --write-example-config witsml.json # a WITSML 1.4.1 store: url, uids, mnemonics -> tags; credentials by env name
119
119
  gea witsml --config witsml.json --seconds 60 --out store # needs a real store: the example url is a placeholder until you edit it
120
+ gea etp --write-example-config etp.json # a WITSML 2.x store over ETP v1.2: ws:// or wss:// url, bearer token by env name, channel URIs -> tags, units
121
+ gea etp-sim --port 9800 # an ETP store with two channels to rehearse with - run it in a SECOND window and leave it running
122
+ gea etp --config etp.json --seconds 60 --record rig/session.jsonl --out rig # subscribes; the store pushes every value as it is written
120
123
  gea opcua --write-example-config opcua.json # edit: endpoint, security, credential env names, nodes
121
124
  gea opcua --config opcua.json --seconds 60 --record opc/session.jsonl --out opc --stream-csv opc/stream.csv # needs a real OPC UA server, likewise
122
125
  gea mqtt --write-example-config mqtt.json # edit: broker, TLS, credential env names, topics
@@ -128,6 +131,7 @@ gea ingest --file floor/stream.csv # a stream CSV feeds the h
128
131
  |---|---|---|---|
129
132
  | `wits0` | WITS Level 0 frames (`&&` ... `!!`, four-digit item codes) over TCP connect, TCP listen or serial; record-01 dictionary built in | numeric GOOD; sentinels (-9999, -999.25) and non-numeric -> GAP with the reason | items 0105/0106 (date, time) when present, else arrival; ingest at arrival |
130
133
  | `witsml` | WITSML 1.4.1 SOAP store: GetVersion, GetCap, GetFromStore on one log object, rows newer than the last seen at each poll | numeric GOOD; the store's nullValue and empty fields -> GAP | the index curve of a time log; arrival for a depth log |
134
+ | `etp` | Energistics ETP v1.2 over WebSocket (`etp12.energistics.org`, Avro binary to the published schemas): RequestSession/OpenSession, GetChannelMetadata for the declared URIs, SubscribeChannels, ChannelData pushed by the store (Protocol 21) | numeric GOOD; a non-numeric or null value -> BAD with the reason; a channel the map does not name is counted unmapped | the store's own index: an ETP DateTime index is microseconds since the epoch; an ElapsedTime index is seconds from its declared start; a depth index leaves the arrival time standing, named |
131
135
  | `opcua` | opc.tcp, read + subscribe; Basic256Sha256 SignAndEncrypt when a certificate and key are given | StatusCode severity bits: Good -> GOOD, Uncertain -> STALE, Bad -> GAP (value withheld) | source timestamp, else server timestamp, else arrival |
132
136
  | `mqtt` | MQTT v5 / v3.1.1, TLS optional, QoS per topic; `+`/`#` wildcards | number: GOOD or GAP; JSON: value/time/quality paths; Sparkplug B: `is_null` -> GAP, `Quality` != 192 -> STALE | JSON time path, Sparkplug metric timestamp, else arrival |
133
137
  | `modbus_g6` | Modbus TCP, user-supplied register map with a citation | read failures -> GAP | arrival |
@@ -580,6 +584,29 @@ promoted; the group and the support are declarations; a matched frequency
580
584
  is not a fault size. CSV or miniSEED/SAC; `gea vibration` runs the labelled
581
585
  pump; `gea help vibration`.
582
586
 
587
+ ### WITSML 2.x over ETP
588
+
589
+ The newer rigs and stores do not poll: Energistics ETP v1.2 is a WebSocket
590
+ session, Avro-encoded to the published schemas, on which the customer names
591
+ the channels it wants and the store pushes every value as it is written.
592
+ `gea/etp.py` is that customer: the handshake with the subprotocol, the
593
+ binary encoding and the payload limits; RequestSession for Discovery, Store
594
+ and ChannelSubscribe and what the store answered; the channel metadata for
595
+ the URIs the mapping declares (a URI the store does not have is named as
596
+ missing); the subscription; and each ChannelData DataItem as a record under
597
+ the mapping's tag and unit, with the store's own index as the source time -
598
+ microseconds since the epoch for a DateTime index, seconds from the declared
599
+ start for ElapsedTime, the arrival time standing (and named) for a depth
600
+ index - and the arrival as the ingest time, so the latency is per record.
601
+ Client message ids even, the store's odd; a ProtocolException carried with
602
+ its code; a client or a store without the subprotocol refused at the
603
+ handshake. The patch supervisor runs it like every other port; the page
604
+ offers it; `gea etp-sim` is a store with two channels of a synthetic
605
+ disposal well; `gea etp --selftest` runs the codec and a loopback session;
606
+ `gea help etp`. The doctor now also names a panel started through the
607
+ `gea.exe` launcher, which pip cannot replace while it runs - stop, update,
608
+ start with `python -m gea serve`.
609
+
583
610
  ### Conformity, in the standards' words
584
611
 
585
612
  The drift report's "bias inside the instrument's class" is a statement of
@@ -87,6 +87,7 @@ from .opcua_port import ASYNCUA_AVAILABLE, OpcUaTap # registers the 'op
87
87
  from .mqtt_port import PAHO_AVAILABLE, MqttTap # registers the 'mqtt' port at import
88
88
  from .wits0 import Wits0Tap, SERIAL_AVAILABLE # registers the 'wits0' port at import
89
89
  from .witsml import WitsmlTap # registers the 'witsml' port at import
90
+ from .etp import EtpTap # registers the 'etp' port at import (WITSML 2.x over ETP v1.2)
90
91
  from .well_assembler import (
91
92
  WellAssembly, WellComponent, assemble, assemble_ktb_hb, assemble_odp_504b,
92
93
  assemble_site_1027, assemble_u1324, BUILTIN_ASSEMBLIES,
@@ -122,7 +123,7 @@ from .profile_catalog import (
122
123
  read_pangaea_txt,
123
124
  )
124
125
 
125
- __version__ = "0.15.0"
126
+ __version__ = "0.16.0"
126
127
  __all__ = [
127
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
128
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -265,7 +265,8 @@ def main(argv=None) -> int:
265
265
  for name, helptxt in (("opcua", "OPC UA client port: read once or subscribe; record; replay a recording offline"),
266
266
  ("mqtt", "MQTT subscriber port (3.1.1/5.0; number | json | Sparkplug B): run; record; replay offline"),
267
267
  ("wits0", "WITS Level 0 port (drill floor: mud logger / EDR) over TCP connect, TCP listen or serial: run; record; replay offline"),
268
- ("witsml", "WITSML 1.4.1 read-only client: poll a log object from the store; record; replay offline")):
268
+ ("witsml", "WITSML 1.4.1 read-only client: poll a log object from the store; record; replay offline"),
269
+ ("etp", "WITSML 2.x over Energistics ETP v1.2 (WebSocket, Avro): subscribe to channels and the store pushes; record; replay the session's shape")):
269
270
  p_lp = sub.add_parser(name, help=helptxt)
270
271
  p_lp.add_argument("--config", type=str, default=None, help="site map JSON (client-owned)")
271
272
  p_lp.add_argument("--write-example-config", type=str, default=None, help="write an example map here and exit")
@@ -275,6 +276,7 @@ def main(argv=None) -> int:
275
276
  p_lp.add_argument("--read-once", action="store_true", help="opcua: one Read of all nodes instead of a subscription")
276
277
  p_lp.add_argument("--out", type=str, default=None, help="write the records CSV here")
277
278
  p_lp.add_argument("--stream-csv", type=str, default=None, help="also write a historian-style CSV the other commands ingest")
279
+ p_lp.add_argument("--selftest", action="store_true", help="etp: the codec round-trip and a session against the simulated store on the loopback")
278
280
 
279
281
  p_ws = sub.add_parser("workspace", help="the client site folder: init, add wells (file | catalogue | live), list, migrate a --out folder, refresh the dashboard, audit log")
280
282
  p_ws.add_argument("--path", type=str, required=True, help="the workspace folder")
@@ -400,6 +402,11 @@ def main(argv=None) -> int:
400
402
  p_us.add_argument("--password-env", type=str, default="GEA_PASSWORD", help="environment variable holding the password (never a flag, never on the command line)")
401
403
  p_us.add_argument("--actor", type=str, default="cli")
402
404
 
405
+ p_es = sub.add_parser("etp-sim", help="an ETP v1.2 store for testing a patch without a rig: two channels of a synthetic disposal well, pushed as a customer subscribes")
406
+ p_es.add_argument("--port", type=int, default=9800)
407
+ p_es.add_argument("--frames", type=int, default=600)
408
+ p_es.add_argument("--interval", type=float, default=1.0, help="seconds between samples")
409
+ p_es.add_argument("--seed", type=int, default=1)
403
410
  p_w0 = sub.add_parser("wits0-sim", help="a WITS0 sender for testing a patch without a rig: listens once, then streams a deterministic drilling sequence")
404
411
  p_w0.add_argument("--port", type=int, default=5001)
405
412
  p_w0.add_argument("--frames", type=int, default=600)
@@ -1966,10 +1973,30 @@ def main(argv=None) -> int:
1966
1973
  stop.set()
1967
1974
  print("wits0-sim: stopped")
1968
1975
  return 0
1969
- elif a.cmd in ("opcua", "mqtt", "wits0", "witsml"):
1976
+ elif a.cmd == "etp-sim":
1977
+ from . import etp as _E
1978
+ th, port, stop = _E.simulate_store(port=a.port, frames=a.frames, interval_s=a.interval, seed=a.seed, verbose=True)
1979
+ print(f"etp-sim: an ETP v1.2 store listening on ws://127.0.0.1:{port}/ - this window is the STORE and stays here; nothing happens until a customer connects.\n"
1980
+ f" Open a SECOND window and run: gea etp --config etp.json --seconds 60 --out floor (with etp.json: url ws://127.0.0.1:{port}/ and the two example channel URIs)\n"
1981
+ f" or on the dashboard: Patch panel -> Add a patch -> etp, url ws://127.0.0.1:{port}/. Then it pushes {a.frames} samples per channel {a.interval:g} s apart. Ctrl+C stops it.", flush=True)
1982
+ try:
1983
+ while th.is_alive():
1984
+ th.join(0.5)
1985
+ except KeyboardInterrupt:
1986
+ stop.set()
1987
+ print("etp-sim: stopped")
1988
+ return 0
1989
+ elif a.cmd in ("opcua", "mqtt", "wits0", "witsml", "etp"):
1970
1990
  import json as _json
1971
1991
  from .live_ports import records_to_stream, summarize, write_records_csv
1972
- mod = __import__({"opcua": "gea.opcua_port", "mqtt": "gea.mqtt_port", "wits0": "gea.wits0", "witsml": "gea.witsml"}[a.cmd], fromlist=["x"])
1992
+ mod = __import__({"opcua": "gea.opcua_port", "mqtt": "gea.mqtt_port", "wits0": "gea.wits0", "witsml": "gea.witsml", "etp": "gea.etp"}[a.cmd], fromlist=["x"])
1993
+ if a.cmd == "etp" and getattr(a, "selftest", False):
1994
+ r = mod.selftest()
1995
+ print(f"etp selftest [{r['label']}]: {r['status']}")
1996
+ for k, v in r["checks"].items():
1997
+ print(f" {'ok ' if v else 'BAD'} {k}")
1998
+ print(f" {r['n_records']} record(s) on {r['tags']}; store {r['store'].get('application')} protocols {r['store'].get('protocols')}; counts {r['counts']}")
1999
+ return 0 if r["status"] == "OK" else 1
1973
2000
  if a.write_example_config:
1974
2001
  print("config:", mod.write_example_config(a.write_example_config))
1975
2002
  return 0
@@ -1994,6 +2021,11 @@ def main(argv=None) -> int:
1994
2021
  tap = mod.Wits0Tap(a.config, recording_path=a.record)
1995
2022
  recs = tap.run(a.seconds)
1996
2023
  src = f"{tap.cfg['transport']} {tap.cfg.get('host') or tap.cfg.get('device') or ('listen:' + str(tap.cfg.get('listen_port')))}"
2024
+ elif a.cmd == "etp":
2025
+ tap = mod.EtpTap(a.config, recording_path=a.record)
2026
+ recs = tap.run(a.seconds)
2027
+ src = tap.cfg["url"] + (f" (store: {tap.state.get('store', {}).get('application')}, {tap.state.get('channels')} channel(s) subscribed"
2028
+ + (f", missing {tap.state.get('missing_uris')}" if tap.state.get('missing_uris') else '') + ')')
1997
2029
  else:
1998
2030
  tap = mod.WitsmlTap(a.config, recording_path=a.record)
1999
2031
  recs = tap.run(a.seconds)
@@ -2001,11 +2033,17 @@ def main(argv=None) -> int:
2001
2033
  except (NotImplementedError, ConnectionError, ValueError) as e:
2002
2034
  hint = {"wits0": "nothing is sending on that host:port - start `gea wits0-sim --port 5001` in a SECOND window first (it must be running while this reads), or point the config at the rig's real WITS0 feed",
2003
2035
  "witsml": "no WITSML store answers at that url - the example config points at 127.0.0.1:8000, a placeholder; put the real store's url, uids and credential env names in it",
2036
+ "etp": "no ETP store answers at that url - start `gea etp-sim --port 9800` in a SECOND window first, or point the config at the rig's ETP endpoint (wss://...) with the channel URIs it publishes",
2004
2037
  "opcua": "no OPC UA server answers at that endpoint - the example config is a placeholder; put the real server's endpoint, security and node ids in it",
2005
2038
  "mqtt": "no MQTT broker answers at that host:port - the example config is a placeholder; put the real broker in it"}.get(a.cmd, "")
2006
2039
  raise SystemExit(f"{a.cmd}: {e}" + (f"\n {hint}" if hint else "")) # the reason and what to do, without a traceback
2007
2040
  print(f"{a.cmd}: {src}")
2008
- print(_json.dumps(summarize(recs), indent=1))
2041
+ if a.cmd == "etp" and a.replay:
2042
+ shape = getattr(mod.replay, "summary", {})
2043
+ print(_json.dumps({"session_shape": shape, "records": 0,
2044
+ "note": "an ETP recording holds the messages as received with ChannelData as counts (the samples went to --out); the replay reproduces the session's shape, not the samples"}, indent=1))
2045
+ else:
2046
+ print(_json.dumps(summarize(recs), indent=1))
2009
2047
  if a.out:
2010
2048
  print(" records:", write_records_csv(recs, a.out))
2011
2049
  if a.stream_csv and recs:
@@ -5453,6 +5453,134 @@ def section_bi_conformance(tmp: str) -> None:
5453
5453
  finally:
5454
5454
  svc.stop()
5455
5455
 
5456
+
5457
+ def section_bj_etp(tmp: str) -> None:
5458
+ """Section BJ - the live-port leg on ETP v1.2: the Avro codec to the specification's schemas, the WebSocket
5459
+ with the ETP handshake, the session, the ChannelSubscribe stream into records, the patch supervisor
5460
+ running it, the API and the page knowing the protocol, and a store that is not ETP refused at the door."""
5461
+ import http.cookiejar
5462
+ import socket as _socket
5463
+ import subprocess as _sp
5464
+ import urllib.request
5465
+ import urllib.error
5466
+ from . import etp as E
5467
+ from . import helplib as H
5468
+ from .patches import PatchSupervisor, PROTOCOLS
5469
+ from .ports import PORT_REGISTRY
5470
+ from .telemetry import TelemetryRecorder, TelemetryConfig
5471
+ from .downhole_engine import DownholeEngine, SimulatorConfig
5472
+ from .workspace import Workspace, PORTS
5473
+ from .service import Service, Users
5474
+ pkg = Path(__file__).parent
5475
+ d = Path(tmp, "bj"); d.mkdir()
5476
+
5477
+ # -- the codec, to the schemas as published ------------------------------------------------------------
5478
+ a = E.avro()
5479
+ buf = E.encode_message(0, 1, {'applicationName': 'g', 'applicationVersion': '1', 'clientInstanceId': b'\x07' * 16, 'requestedProtocols': [], 'supportedDataObjects': [],
5480
+ 'currentDateTime': 5, 'earliestRetainedChangeTime': 0}, message_id=2)
5481
+ hdr, body, name = E.decode_message(buf)
5482
+ zz = [E.Avro._zigzag(x) for x in (0, -1, 1, -2, 2, 63, 64, -64, 2 ** 40)]
5483
+ back = [E.Avro._unzigzag(b, 0)[0] for b in zz]
5484
+ bad_mt = E.decode_message(a.encode(E.HEADER, {'protocol': 99, 'messageType': 99, 'correlationId': 0, 'messageId': 1, 'messageFlags': 0}) + b'\x00')
5485
+ ok(len(a.messages) == 20 and (21, 4) in a.messages and (0, 1000) in a.messages and E.SCHEMA_FILE.endswith('etp12_schemas.json') and os.path.isfile(E.SCHEMA_FILE)
5486
+ and name == 'RequestSession' and body['applicationName'] == 'g' and body['supportedFormats'] == ['xml'] and body['serverAuthorizationRequired'] is False
5487
+ and hdr == {'protocol': 0, 'messageType': 1, 'correlationId': 0, 'messageId': 2, 'messageFlags': 0}
5488
+ and zz[0] == b'\x00' and zz[1] == b'\x01' and zz[2] == b'\x02' and zz[5] == b'\x7e' and zz[6] == b'\x80\x01' and back == [0, -1, 1, -2, 2, 63, 64, -64, 2 ** 40]
5489
+ and bad_mt[1] is None and 'no schema' in bad_mt[2]
5490
+ and E.FLAG_MULTIPART == 0x01 and E.FLAG_FINAL == 0x02 and E.FLAG_ACK == 0x10 and E.FLAG_EXTENSION == 0x20 and E.ETP_SUBPROTOCOL == 'etp12.energistics.org',
5491
+ "BJ1 the Avro binary codec is driven by the ETP v1.2 message schemas as published (twenty messages, the header and the error record): zig-zag "
5492
+ "varints at their boundaries, defaults filled for fields not given, a record decoded back to what was encoded, a message of a kind this port "
5493
+ "does not know left undecoded and named; the message flags and the subprotocol are the specification's")
5494
+
5495
+ # -- the session on the loopback: the self-test, and what the records carry ----------------------------------
5496
+ r = E.selftest(frames=4, interval_s=0.15)
5497
+ ok(r["status"] == "OK" and all(r["checks"].values()) and r["label"] == "SIMULATION_SELF_TEST" and r["n_records"] == 8
5498
+ and r["store_messages"][:3] == ['RequestSession', 'GetChannelMetadata', 'SubscribeChannels'] and r["counts"]["exceptions"] == 0,
5499
+ "BJ2 a customer session against the simulated store over a real WebSocket on the loopback: the ETP handshake (subprotocol, binary encoding, "
5500
+ "payload limits), RequestSession answered by OpenSession, the channel metadata fetched for the declared URIs, the subscription, ChannelData "
5501
+ "pushed at the store's own index (microseconds since the epoch) and carried as each record's source time with the latency measured, the "
5502
+ "client's ids even and the store's odd, a URI the store does not have named as missing, a client without the subprotocol refused at the door")
5503
+
5504
+ # -- the patch supervisor, the API, the page ---------------------------------------------------------------------
5505
+ hist = str(d / "hist.csv")
5506
+ TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=0.5, seed=3)).run().export_csv(hist)
5507
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
5508
+ w = ws.add_well_file(hist, display="SWD 1", actor="tester")
5509
+ th, port, stop = E.simulate_store(frames=400, interval_s=0.1, seed=5)
5510
+ cfg = json.loads(json.dumps(E.EXAMPLE_CONFIG)); cfg["url"] = f"ws://127.0.0.1:{port}/"
5511
+ sup = PatchSupervisor(ws)
5512
+ sup.store.add("rig", "etp", w["id"], cfg, "tester", priority=2, stale_after_s=5)
5513
+ sup.start("rig", "tester")
5514
+ time.sleep(3.0)
5515
+ s1 = {x["name"]: x for x in sup.states()}["rig"]
5516
+ sup.stop("rig", "tester")
5517
+ s2 = {x["name"]: x for x in sup.states()}["rig"]
5518
+ stop.set(); th.join(timeout=3)
5519
+ users = Users(str(Path(wsp, "users.json"))); users.add("op", "operator-pass-1", "operator")
5520
+ svc = Service(wsp, port=0, scheduler=False).start()
5521
+ base = svc.url.rstrip("/")
5522
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
5523
+
5524
+ def call(method, path, body=None):
5525
+ req = urllib.request.Request(base + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
5526
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
5527
+ try:
5528
+ with opener.open(req, timeout=120) as rr:
5529
+ return rr.status, json.loads(rr.read())
5530
+ except urllib.error.HTTPError as e:
5531
+ return e.code, json.loads(e.read())
5532
+ try:
5533
+ call("POST", "/api/login", {"name": "op", "password": "operator-pass-1"})
5534
+ st_def, dflt = call("GET", "/api/config/defaults/etp")
5535
+ st_pl, pl = call("GET", "/api/patches")
5536
+ st_bad = call("POST", "/api/patches/add", {"name": "bad", "protocol": "etp", "well_id": w["id"], "config": {"url": "http://x/"}})[0]
5537
+ st_add = call("POST", "/api/patches/add", {"name": "rig2", "protocol": "etp", "well_id": w["id"], "config": cfg, "priority": 1, "stale_after_s": 5})[0]
5538
+ finally:
5539
+ svc.stop()
5540
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
5541
+ r1 = _sp.run([sys.executable, "-m", "gea", "etp", "--write-example-config", str(d / "etp.json")], capture_output=True, text=True)
5542
+ r2 = _sp.run([sys.executable, "-m", "gea", "etp", "--config", str(d / "etp.json"), "--seconds", "1"], capture_output=True, text=True)
5543
+ th5, port5, stop5 = E.simulate_store(frames=8, interval_s=0.1)
5544
+ cfg5 = json.loads(json.dumps(E.EXAMPLE_CONFIG)); cfg5["url"] = f"ws://127.0.0.1:{port5}/"
5545
+ (d / "etp5.json").write_text(json.dumps(cfg5), encoding="utf-8")
5546
+ r3 = _sp.run([sys.executable, "-m", "gea", "etp", "--config", str(d / "etp5.json"), "--seconds", "3", "--out", str(d / "recs.csv"), "--record", str(d / "rec.jsonl")], capture_output=True, text=True)
5547
+ stop5.set(); th5.join(timeout=3)
5548
+ r4 = _sp.run([sys.executable, "-m", "gea", "etp", "--config", str(d / "etp5.json"), "--replay", str(d / "rec.jsonl")], capture_output=True, text=True)
5549
+ help_md = (pkg / "help" / "etp.md").read_text(encoding="utf-8")
5550
+ ok(s1["status"] == "CONNECTED" and s1["samples_total"] >= 20 and set(s1["tags"]) == {"P_surf_psi", "Q_bpm"} and s1["tags"]["P_surf_psi"]["unit"] == "psi"
5551
+ and 1400 < s1["tags"]["P_surf_psi"]["value"] < 1600 and s1["latency_p95_s"] is not None and s1["latency_p95_s"] < 3.0 and s2["status"] == "STOPPED"
5552
+ and any(f.startswith("patch_rig_") and f.endswith(".records.csv") for f in os.listdir(Path(wsp, "wells", w["id"], "records", "live")))
5553
+ and "etp" in PROTOCOLS and "etp" in PORTS and PORT_REGISTRY["etp"].status == "IMPLEMENTED_REQUIRES_SITE_CONFIG"
5554
+ and st_def == 200 and dflt["content"]["url"].startswith("ws://") and "etp" in pl["protocols"] and st_bad == 400 and st_add == 200
5555
+ and '<option value="etp">' in page_src
5556
+ and r1.returncode == 0 and r2.returncode != 0 and "etp-sim" in (r2.stderr + r2.stdout)
5557
+ and r3.returncode == 0 and (d / "recs.csv").is_file() and (d / "rec.jsonl").is_file() and '"records": 16' in r3.stdout and '"GOOD": 16' in r3.stdout
5558
+ and r4.returncode == 0 and '"channel_data": 8' in r4.stdout and '"exceptions": 0' in r4.stdout and "session's shape" in r4.stdout and any(t["topic"] == "etp" for t in H.topics()) and not H.check("etp")
5559
+ and "microseconds" in help_md and "arrival time" in help_md.split("will not call a measurement")[1],
5560
+ "BJ3 the patch supervisor runs an ETP patch like any other port - CONNECTED with the two channels under their declared tags and units, "
5561
+ "latency measured, records on disk, STOPPED on request; the service knows the protocol (its example config, the protocol list, a bad "
5562
+ "config refused, a good one added); the page offers it; `gea etp` writes the example, names the missing store with the simulator to "
5563
+ "start, records a session and replays its shape; the help page names the depth-indexed sample it will not give a time to")
5564
+
5565
+ # -- the doctor names a panel that cannot be updated in place ---------------------------------------------------
5566
+ from . import doctor as D
5567
+ argv0 = sys.argv[0]
5568
+ try:
5569
+ sys.argv[0] = r"C:\Users\op\AppData\Roaming\Python\Python312\Scripts\gea.exe"
5570
+ env_l = D.check_environment()
5571
+ sys.argv[0] = os.path.join(os.path.dirname(argv0) or ".", "__main__.py")
5572
+ env_m = D.check_environment()
5573
+ finally:
5574
+ sys.argv[0] = argv0
5575
+ fl = [x for x in env_l if "console launcher" in x["what"]]
5576
+ ok(D.started_through_launcher(r"C:\py\Scripts\gea.exe").endswith("gea.exe") and D.started_through_launcher("/home/op/.local/bin/gea") == "/home/op/.local/bin/gea"
5577
+ and D.started_through_launcher("/x/gea/__main__.py") is None and D.started_through_launcher("-m") is None
5578
+ and len(fl) == 1 and fl[0]["level"] == "warn" and "WinError 32" in fl[0]["what"] and "python -m gea serve" in fl[0]["fix"]
5579
+ and not any("console launcher" in x["what"] for x in env_m),
5580
+ "BJ4 the doctor (and so `gea serve` at start) names a panel started through the gea / gea.exe console launcher: pip cannot replace that "
5581
+ "launcher while it runs, so the program cannot be updated in place - stop, update, start with `python -m gea serve`; a panel started "
5582
+ "with `python -m gea` draws no such finding, and `gea update` from a launcher-started panel on Windows refuses to run pip and says why")
5583
+
5456
5584
  def main() -> int:
5457
5585
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
5458
5586
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -5517,6 +5645,7 @@ def main() -> int:
5517
5645
  section_bg_ppdm(tmp)
5518
5646
  section_bh_vibration(tmp)
5519
5647
  section_bi_conformance(tmp)
5648
+ section_bj_etp(tmp)
5520
5649
  if _FAILS:
5521
5650
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
5522
5651
  for f in _FAILS:
@@ -22,6 +22,7 @@ from __future__ import annotations
22
22
  import importlib.util
23
23
  import json
24
24
  import os
25
+ import re
25
26
  import socket
26
27
  import sys
27
28
  import sysconfig
@@ -116,6 +117,11 @@ def check_environment() -> List[dict]:
116
117
  f.append(_finding('info', f'no `gea` launcher in {" or ".join(candidates)}; use `python -m gea ...`'))
117
118
  elif found and not any(c in on_path for c in found):
118
119
  f.append(_finding('warn', f'the `gea` launcher is in {found[0]}, which is not on PATH', 'use `python -m gea ...`, or add that folder to PATH'))
120
+ launcher = started_through_launcher()
121
+ if launcher:
122
+ f.append(_finding('warn', f'this process was started through the console launcher {launcher}: while it runs, pip cannot replace '
123
+ f'that launcher on Windows (WinError 32), so the program cannot be updated in place',
124
+ 'stop the panel, then update, then start it with `python -m gea serve` (the kit launcher and start-gea.cmd do)'))
119
125
  newest = pypi_newest()
120
126
 
121
127
  def _vt(v):
@@ -131,6 +137,18 @@ def check_environment() -> List[dict]:
131
137
  return f
132
138
 
133
139
 
140
+ def started_through_launcher(argv0: Optional[str] = None) -> Optional[str]:
141
+ """The path of the `gea` / `gea.exe` console launcher this process was started through, or None when it was
142
+ started as `python -m gea` (or from a checkout). On Windows the launcher .exe is held open by the process it
143
+ started, so pip cannot replace it while the panel runs: `gea update` from such a panel, and `pip install
144
+ --upgrade` beside it, fail with WinError 32. The kit launchers and start-gea.cmd use `python -m gea` for this."""
145
+ a0 = sys.argv[0] if argv0 is None else argv0
146
+ base = re.split(r'[\\/]', a0 or '')[-1].lower() # either separator: the launcher path may be Windows-shaped
147
+ if base in ('gea', 'gea.exe', 'gea-script.py', 'gea-script.pyw'):
148
+ return a0
149
+ return None
150
+
151
+
134
152
  def pypi_newest(timeout_s: float = 3.0) -> Optional[str]:
135
153
  try:
136
154
  import urllib.request
@@ -150,9 +168,15 @@ def update(check_only: bool = False, extras: str = 'live,plotting,xls,desktop',
150
168
  vt = lambda v: tuple(int(x) if x.isdigit() else 0 for x in str(v).split('.'))
151
169
  res = {'running': __version__, 'newest_pypi': newest, 'python': sys.executable, 'extras': extras,
152
170
  'state': 'unknown' if newest is None else ('current' if vt(newest) <= vt(__version__) else 'behind'), 'ran_pip': False, 'returncode': None}
171
+ launcher = started_through_launcher()
172
+ res['started_through_launcher'] = launcher
153
173
  if newest is None:
154
174
  res['note'] = 'PyPI is not reachable from here; an offline installation is updated by installing the newer kit from the release page'
155
- if not check_only and newest is not None and res['state'] == 'behind':
175
+ if not check_only and newest is not None and res['state'] == 'behind' and launcher and os.name == 'nt':
176
+ res['note'] = (f'not run: this program was started through {launcher}, which pip cannot replace while it runs (WinError 32); '
177
+ 'stop the panel, update from a prompt (`python -m pip install --upgrade "gea-program[live]"`), then start it with `python -m gea serve`')
178
+ res['returncode'] = 2
179
+ elif not check_only and newest is not None and res['state'] == 'behind':
156
180
  spec = f'gea-program[{extras}]=={newest}' if extras else f'gea-program=={newest}'
157
181
  r = subprocess.run([sys.executable, '-m', 'pip', 'install', '--upgrade', spec], capture_output=True, text=True)
158
182
  res.update({'ran_pip': True, 'returncode': r.returncode, 'pip_tail': (r.stdout + r.stderr)[-1500:]})