gea-program 0.23.0__tar.gz → 0.24.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 (263) hide show
  1. {gea_program-0.23.0 → gea_program-0.24.0}/PKG-INFO +20 -2
  2. {gea_program-0.23.0 → gea_program-0.24.0}/README.md +19 -1
  3. {gea_program-0.23.0 → gea_program-0.24.0}/gea/__init__.py +1 -1
  4. {gea_program-0.23.0 → gea_program-0.24.0}/gea/__main__.py +125 -4
  5. {gea_program-0.23.0 → gea_program-0.24.0}/gea/acceptance_tests.py +193 -0
  6. gea_program-0.24.0/gea/dlis.py +624 -0
  7. {gea_program-0.23.0 → gea_program-0.24.0}/gea/files.py +18 -2
  8. gea_program-0.24.0/gea/help/dlis.md +11 -0
  9. gea_program-0.24.0/gea/help/historian.md +11 -0
  10. {gea_program-0.23.0 → gea_program-0.24.0}/gea/helplib.py +2 -0
  11. {gea_program-0.23.0 → gea_program-0.24.0}/gea/patches.py +35 -5
  12. gea_program-0.24.0/gea/piwebapi.py +366 -0
  13. {gea_program-0.23.0 → gea_program-0.24.0}/gea/ports.py +4 -0
  14. {gea_program-0.23.0 → gea_program-0.24.0}/gea/service.py +3 -3
  15. {gea_program-0.23.0 → gea_program-0.24.0}/gea/survey_cmd.py +13 -3
  16. {gea_program-0.23.0 → gea_program-0.24.0}/gea/web/app.html +1 -1
  17. {gea_program-0.23.0 → gea_program-0.24.0}/gea/workspace.py +1 -1
  18. {gea_program-0.23.0 → gea_program-0.24.0}/gea_program.egg-info/PKG-INFO +20 -2
  19. {gea_program-0.23.0 → gea_program-0.24.0}/gea_program.egg-info/SOURCES.txt +4 -0
  20. {gea_program-0.23.0 → gea_program-0.24.0}/pyproject.toml +1 -1
  21. {gea_program-0.23.0 → gea_program-0.24.0}/LICENSE +0 -0
  22. {gea_program-0.23.0 → gea_program-0.24.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  23. {gea_program-0.23.0 → gea_program-0.24.0}/gea/accuracy_statement.py +0 -0
  24. {gea_program-0.23.0 → gea_program-0.24.0}/gea/alarm_engine.py +0 -0
  25. {gea_program-0.23.0 → gea_program-0.24.0}/gea/backup.py +0 -0
  26. {gea_program-0.23.0 → gea_program-0.24.0}/gea/bench.py +0 -0
  27. {gea_program-0.23.0 → gea_program-0.24.0}/gea/blind_harness.py +0 -0
  28. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  29. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  30. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  31. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  32. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  33. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  34. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  35. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  36. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  37. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  38. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  39. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  40. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  41. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  42. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  43. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  44. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  45. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  46. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  47. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  48. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  49. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  50. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  51. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  52. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  53. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  54. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  55. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  56. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  57. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  58. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  59. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  60. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  61. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  62. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  63. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  64. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  65. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  66. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  67. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  68. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  69. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  70. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  71. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  72. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  73. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  74. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  75. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  76. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  77. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  78. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  79. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  80. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  81. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  82. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  83. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  84. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  85. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  86. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  87. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  88. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  89. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  90. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  91. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  92. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  93. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  94. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  95. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/med_160_sapropels.txt +0 -0
  96. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  97. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  98. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  99. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  100. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  101. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  102. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  103. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  104. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  105. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  106. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  107. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  108. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  109. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  110. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  111. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  112. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  113. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  114. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  115. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  116. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  117. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  118. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  119. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  120. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  121. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  122. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  123. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  124. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  125. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  126. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  127. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  128. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  129. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  130. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  131. {gea_program-0.23.0 → gea_program-0.24.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  132. {gea_program-0.23.0 → gea_program-0.24.0}/gea/certificates.py +0 -0
  133. {gea_program-0.23.0 → gea_program-0.24.0}/gea/cli.py +0 -0
  134. {gea_program-0.23.0 → gea_program-0.24.0}/gea/client_reports.py +0 -0
  135. {gea_program-0.23.0 → gea_program-0.24.0}/gea/config_versioning.py +0 -0
  136. {gea_program-0.23.0 → gea_program-0.24.0}/gea/conformance.py +0 -0
  137. {gea_program-0.23.0 → gea_program-0.24.0}/gea/correlation.py +0 -0
  138. {gea_program-0.23.0 → gea_program-0.24.0}/gea/dashboard.py +0 -0
  139. {gea_program-0.23.0 → gea_program-0.24.0}/gea/deviation.py +0 -0
  140. {gea_program-0.23.0 → gea_program-0.24.0}/gea/dnp3.py +0 -0
  141. {gea_program-0.23.0 → gea_program-0.24.0}/gea/doctor.py +0 -0
  142. {gea_program-0.23.0 → gea_program-0.24.0}/gea/downhole_engine.py +0 -0
  143. {gea_program-0.23.0 → gea_program-0.24.0}/gea/drift_monitor.py +0 -0
  144. {gea_program-0.23.0 → gea_program-0.24.0}/gea/earth_model.py +0 -0
  145. {gea_program-0.23.0 → gea_program-0.24.0}/gea/elevation.py +0 -0
  146. {gea_program-0.23.0 → gea_program-0.24.0}/gea/etp.py +0 -0
  147. {gea_program-0.23.0 → gea_program-0.24.0}/gea/etp12_schemas.json +0 -0
  148. {gea_program-0.23.0 → gea_program-0.24.0}/gea/example_register_map.json +0 -0
  149. {gea_program-0.23.0 → gea_program-0.24.0}/gea/fat_sat.py +0 -0
  150. {gea_program-0.23.0 → gea_program-0.24.0}/gea/follower.py +0 -0
  151. {gea_program-0.23.0 → gea_program-0.24.0}/gea/forward_model.py +0 -0
  152. {gea_program-0.23.0 → gea_program-0.24.0}/gea/gamma.py +0 -0
  153. {gea_program-0.23.0 → gea_program-0.24.0}/gea/gauge_aging.py +0 -0
  154. {gea_program-0.23.0 → gea_program-0.24.0}/gea/gauge_specs.py +0 -0
  155. {gea_program-0.23.0 → gea_program-0.24.0}/gea/geodesy.py +0 -0
  156. {gea_program-0.23.0 → gea_program-0.24.0}/gea/gravity_reference.py +0 -0
  157. {gea_program-0.23.0 → gea_program-0.24.0}/gea/hartip.py +0 -0
  158. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/TESTER_GUIDE.md +0 -0
  159. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/alarms.md +0 -0
  160. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/audit-update.md +0 -0
  161. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/backup.md +0 -0
  162. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/conformance.md +0 -0
  163. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/data-in.md +0 -0
  164. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/doctor.md +0 -0
  165. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/drift.md +0 -0
  166. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/etp.md +0 -0
  167. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/field-ports.md +0 -0
  168. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/files.md +0 -0
  169. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/instruments.md +0 -0
  170. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/interval.md +0 -0
  171. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/month-end.md +0 -0
  172. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/notifications.md +0 -0
  173. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/osdu.md +0 -0
  174. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/patches.md +0 -0
  175. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/ppdm.md +0 -0
  176. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/quakeml.md +0 -0
  177. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/quality.md +0 -0
  178. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/seedlink.md +0 -0
  179. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/seismic.md +0 -0
  180. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/site.md +0 -0
  181. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/sites.md +0 -0
  182. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/soak.md +0 -0
  183. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/sra.md +0 -0
  184. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/start.md +0 -0
  185. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/structure.md +0 -0
  186. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/transients.md +0 -0
  187. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/upkeep.md +0 -0
  188. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/vibration.md +0 -0
  189. {gea_program-0.23.0 → gea_program-0.24.0}/gea/help/well-tests.md +0 -0
  190. {gea_program-0.23.0 → gea_program-0.24.0}/gea/housekeeping.py +0 -0
  191. {gea_program-0.23.0 → gea_program-0.24.0}/gea/interval.py +0 -0
  192. {gea_program-0.23.0 → gea_program-0.24.0}/gea/inverse_engine.py +0 -0
  193. {gea_program-0.23.0 → gea_program-0.24.0}/gea/jobs.py +0 -0
  194. {gea_program-0.23.0 → gea_program-0.24.0}/gea/live_ports.py +0 -0
  195. {gea_program-0.23.0 → gea_program-0.24.0}/gea/loadtest.py +0 -0
  196. {gea_program-0.23.0 → gea_program-0.24.0}/gea/matplotlib_demo.py +0 -0
  197. {gea_program-0.23.0 → gea_program-0.24.0}/gea/modbus.py +0 -0
  198. {gea_program-0.23.0 → gea_program-0.24.0}/gea/modbus_rtu.py +0 -0
  199. {gea_program-0.23.0 → gea_program-0.24.0}/gea/model_card.py +0 -0
  200. {gea_program-0.23.0 → gea_program-0.24.0}/gea/month_end.py +0 -0
  201. {gea_program-0.23.0 → gea_program-0.24.0}/gea/mqtt_port.py +0 -0
  202. {gea_program-0.23.0 → gea_program-0.24.0}/gea/notify.py +0 -0
  203. {gea_program-0.23.0 → gea_program-0.24.0}/gea/opcua_port.py +0 -0
  204. {gea_program-0.23.0 → gea_program-0.24.0}/gea/operator_app.py +0 -0
  205. {gea_program-0.23.0 → gea_program-0.24.0}/gea/osdu.py +0 -0
  206. {gea_program-0.23.0 → gea_program-0.24.0}/gea/permits.py +0 -0
  207. {gea_program-0.23.0 → gea_program-0.24.0}/gea/ppdm.py +0 -0
  208. {gea_program-0.23.0 → gea_program-0.24.0}/gea/profile_catalog.py +0 -0
  209. {gea_program-0.23.0 → gea_program-0.24.0}/gea/project.py +0 -0
  210. {gea_program-0.23.0 → gea_program-0.24.0}/gea/qt6_downhole_app.py +0 -0
  211. {gea_program-0.23.0 → gea_program-0.24.0}/gea/quakeml.py +0 -0
  212. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reconciler.py +0 -0
  213. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  214. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  215. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  216. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  217. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  218. {gea_program-0.23.0 → gea_program-0.24.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  219. {gea_program-0.23.0 → gea_program-0.24.0}/gea/rock_inventory.py +0 -0
  220. {gea_program-0.23.0 → gea_program-0.24.0}/gea/sample_record.py +0 -0
  221. {gea_program-0.23.0 → gea_program-0.24.0}/gea/sample_well_profile.csv +0 -0
  222. {gea_program-0.23.0 → gea_program-0.24.0}/gea/sbom.py +0 -0
  223. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seedlink.py +0 -0
  224. {gea_program-0.23.0 → gea_program-0.24.0}/gea/segy.py +0 -0
  225. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic.py +0 -0
  226. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_array.py +0 -0
  227. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_assoc.py +0 -0
  228. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_detect.py +0 -0
  229. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_film.py +0 -0
  230. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_harmonic.py +0 -0
  231. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_qc.py +0 -0
  232. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_response.py +0 -0
  233. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_signature.py +0 -0
  234. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_track.py +0 -0
  235. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_uncertainty.py +0 -0
  236. {gea_program-0.23.0 → gea_program-0.24.0}/gea/seismic_unlisted.py +0 -0
  237. {gea_program-0.23.0 → gea_program-0.24.0}/gea/sensor_swap.py +0 -0
  238. {gea_program-0.23.0 → gea_program-0.24.0}/gea/service_life.py +0 -0
  239. {gea_program-0.23.0 → gea_program-0.24.0}/gea/shapefile.py +0 -0
  240. {gea_program-0.23.0 → gea_program-0.24.0}/gea/shell.py +0 -0
  241. {gea_program-0.23.0 → gea_program-0.24.0}/gea/shut_in.py +0 -0
  242. {gea_program-0.23.0 → gea_program-0.24.0}/gea/sla_report.py +0 -0
  243. {gea_program-0.23.0 → gea_program-0.24.0}/gea/soak.py +0 -0
  244. {gea_program-0.23.0 → gea_program-0.24.0}/gea/sra.py +0 -0
  245. {gea_program-0.23.0 → gea_program-0.24.0}/gea/store_forward.py +0 -0
  246. {gea_program-0.23.0 → gea_program-0.24.0}/gea/strata_join.py +0 -0
  247. {gea_program-0.23.0 → gea_program-0.24.0}/gea/structure.py +0 -0
  248. {gea_program-0.23.0 → gea_program-0.24.0}/gea/supervisor.py +0 -0
  249. {gea_program-0.23.0 → gea_program-0.24.0}/gea/survey_view.py +0 -0
  250. {gea_program-0.23.0 → gea_program-0.24.0}/gea/telemetry.py +0 -0
  251. {gea_program-0.23.0 → gea_program-0.24.0}/gea/tool_library.py +0 -0
  252. {gea_program-0.23.0 → gea_program-0.24.0}/gea/transient.py +0 -0
  253. {gea_program-0.23.0 → gea_program-0.24.0}/gea/vibration.py +0 -0
  254. {gea_program-0.23.0 → gea_program-0.24.0}/gea/well_assembler.py +0 -0
  255. {gea_program-0.23.0 → gea_program-0.24.0}/gea/well_test_validation.py +0 -0
  256. {gea_program-0.23.0 → gea_program-0.24.0}/gea/wits0.py +0 -0
  257. {gea_program-0.23.0 → gea_program-0.24.0}/gea/witsml.py +0 -0
  258. {gea_program-0.23.0 → gea_program-0.24.0}/gea_program.egg-info/dependency_links.txt +0 -0
  259. {gea_program-0.23.0 → gea_program-0.24.0}/gea_program.egg-info/entry_points.txt +0 -0
  260. {gea_program-0.23.0 → gea_program-0.24.0}/gea_program.egg-info/requires.txt +0 -0
  261. {gea_program-0.23.0 → gea_program-0.24.0}/gea_program.egg-info/top_level.txt +0 -0
  262. {gea_program-0.23.0 → gea_program-0.24.0}/setup.cfg +0 -0
  263. {gea_program-0.23.0 → gea_program-0.24.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.23.0
3
+ Version: 0.24.0
4
4
  Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
5
5
  Author: Daniel T. Murphy
6
6
  License: MPL-2.0
@@ -125,7 +125,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
125
125
  gea client-report --report accuracy --out client_report
126
126
  gea model-cards --out model_cards
127
127
  gea sbom --out sbom
128
- gea accept # the product gate (356 checks)
128
+ gea accept # the product gate (359 checks)
129
129
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
130
130
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
131
131
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -648,6 +648,24 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
648
648
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
649
649
  `rtserve.iris.washington.edu:18000`, network `TX`.
650
650
 
651
+ ### The log as delivered, and the historian
652
+
653
+ A well log arrives as DLIS (API RP66 V1); `gea/dlis.py` reads the container
654
+ as the standard lays it out, with no dependency - the storage unit label,
655
+ visible records, logical record segments and trailers, the sets of objects
656
+ with every representation code, the frames and channels, the frame data -
657
+ and hands the strata survey the same stream the LAS port does: `gea dlis
658
+ --file log.dlis --info` describes it, `--to-las` exports a frame, `gea survey
659
+ --file log.dlis` reads it directly, and the Files page detects it by its
660
+ label. Encrypted records are skipped and named, never decrypted or guessed.
661
+ Proven on a real Schlumberger station log (104 channels, two frames, 3,252
662
+ records in a tenth of a second). The operator's gauge history in a PI System
663
+ comes out through PI Web API (`gea piwebapi`, protocol `piwebapi` on the
664
+ Patch panel): GET only, the archive between two times with its paging
665
+ followed, every value with the archive's timestamp and flags, credentials
666
+ from environment variables the map names. `gea help dlis`, `gea help
667
+ historian`.
668
+
651
669
  ### The field ports
652
670
 
653
671
  The three links a gauge panel on a pad actually offers, each read-only in
@@ -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 (356 checks)
89
+ gea accept # the product gate (359 checks)
90
90
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
91
91
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
92
92
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -609,6 +609,24 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
609
609
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
610
610
  `rtserve.iris.washington.edu:18000`, network `TX`.
611
611
 
612
+ ### The log as delivered, and the historian
613
+
614
+ A well log arrives as DLIS (API RP66 V1); `gea/dlis.py` reads the container
615
+ as the standard lays it out, with no dependency - the storage unit label,
616
+ visible records, logical record segments and trailers, the sets of objects
617
+ with every representation code, the frames and channels, the frame data -
618
+ and hands the strata survey the same stream the LAS port does: `gea dlis
619
+ --file log.dlis --info` describes it, `--to-las` exports a frame, `gea survey
620
+ --file log.dlis` reads it directly, and the Files page detects it by its
621
+ label. Encrypted records are skipped and named, never decrypted or guessed.
622
+ Proven on a real Schlumberger station log (104 channels, two frames, 3,252
623
+ records in a tenth of a second). The operator's gauge history in a PI System
624
+ comes out through PI Web API (`gea piwebapi`, protocol `piwebapi` on the
625
+ Patch panel): GET only, the archive between two times with its paging
626
+ followed, every value with the archive's timestamp and flags, credentials
627
+ from environment variables the map names. `gea help dlis`, `gea help
628
+ historian`.
629
+
612
630
  ### The field ports
613
631
 
614
632
  The three links a gauge panel on a pad actually offers, each read-only in
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.23.0"
126
+ __version__ = "0.24.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -278,6 +278,21 @@ def main(argv=None) -> int:
278
278
  p_lp.add_argument("--stream-csv", type=str, default=None, help="also write a historian-style CSV the other commands ingest")
279
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")
280
280
 
281
+ p_dl = sub.add_parser("dlis", help="a DLIS (RP66 V1) well log: --info describes it (origin, channels, frames, what was skipped); --frame --out writes the frame as a historian-style CSV; --to-las writes LAS 2.0")
282
+ p_dl.add_argument("--file", type=str, required=True)
283
+ p_dl.add_argument("--info", action="store_true")
284
+ p_dl.add_argument("--frame", type=str, default=None, help="the frame to read when the file holds several")
285
+ p_dl.add_argument("--out", type=str, default=None, help="write the frame's channels as CSV (index first)")
286
+ p_dl.add_argument("--to-las", type=str, default=None, help="write the frame as a LAS 2.0 file")
287
+ p_dl.add_argument("--json", action="store_true")
288
+ p_pi = sub.add_parser("piwebapi", help="the PI Web API historian pull, read-only: --config (base_url, auth by environment variable names, tags with paths); --selftest against the stand-in")
289
+ p_pi.add_argument("--config", type=str, default=None)
290
+ p_pi.add_argument("--write-example-config", type=str, default=None)
291
+ p_pi.add_argument("--out", type=str, default=None, help="write the records CSV here")
292
+ p_pi.add_argument("--stream-csv", type=str, default=None)
293
+ p_pi.add_argument("--seconds", type=float, default=None, help="ignored: a pull is one request per tag (kept so the live tap's call shape is the same)")
294
+ p_pi.add_argument("--record", type=str, default=None, help="write the pull's summary as one JSON line here")
295
+ p_pi.add_argument("--selftest", action="store_true")
281
296
  for name, helptxt in (("modbus-rtu", "Modbus RTU port, read-only: the serial line (pyserial) or RTU over TCP; functions 03/04 only; the stand-in slave with --selftest"),
282
297
  ("dnp3", "DNP3 (IEEE 1815) master, read-only: integrity and event polls over TCP; analog inputs, binaries, counters with flags and event times; the stand-in outstation with --selftest"),
283
298
  ("hartip", "HART-IP client, read-only: session, command 0, 3 and 9; the variables with their unit codes and status; the stand-in device with --selftest")):
@@ -671,8 +686,9 @@ def main(argv=None) -> int:
671
686
  p_fl.add_argument("--station-md", type=float, default=None)
672
687
  p_fl.add_argument("--actor", type=str, default="cli")
673
688
 
674
- p_sy = sub.add_parser("survey", help="a LAS file in, one strata report out (--demo: the bundled public KTB excerpt); --out writes report.txt + survey.json")
675
- p_sy.add_argument("--file", type=str, default=None, help="LAS 2.0 file")
689
+ p_sy = sub.add_parser("survey", help="a LAS or DLIS file in, one strata report out (--demo: the bundled public KTB excerpt); --out writes report.txt + survey.json")
690
+ p_sy.add_argument("--file", type=str, default=None, help="LAS 2.0 or DLIS (RP66 V1) file")
691
+ p_sy.add_argument("--frame", type=str, default=None, help="DLIS: the frame to read when the file holds several (gea dlis --file X --info lists them)")
676
692
  p_sy.add_argument("--demo", action="store_true")
677
693
  p_sy.add_argument("--family", type=str, default="continental_crystalline", help="prior family for the estimator")
678
694
  p_sy.add_argument("--lat", type=float, default=None)
@@ -808,8 +824,8 @@ def main(argv=None) -> int:
808
824
  import json as _json
809
825
  from .survey_cmd import run_survey
810
826
  if not (a.file or a.demo):
811
- raise SystemExit("survey needs --file <LAS> or --demo")
812
- txt, machine = run_survey(path=a.file, demo=a.demo, family=a.family, lat=a.lat, elev=a.elev)
827
+ raise SystemExit("survey needs --file <LAS or DLIS> or --demo")
828
+ txt, machine = run_survey(path=a.file, demo=a.demo, family=a.family, lat=a.lat, elev=a.elev, frame=a.frame)
813
829
  print(txt)
814
830
  if a.out:
815
831
  os.makedirs(a.out, exist_ok=True)
@@ -2282,6 +2298,111 @@ def main(argv=None) -> int:
2282
2298
  stop.set()
2283
2299
  print("etp-sim: stopped")
2284
2300
  return 0
2301
+ elif a.cmd == "dlis":
2302
+ import json as _json
2303
+ from .dlis import DlisError, DlisFile, read as _read_dlis
2304
+ try:
2305
+ if a.info or not (a.out or a.to_las):
2306
+ with open(a.file, "rb") as f:
2307
+ info = DlisFile(f.read(), os.path.basename(a.file)).describe()
2308
+ if a.json:
2309
+ print(_json.dumps(info, indent=1, default=str)); return 0
2310
+ o = info["origin"]
2311
+ print(f"dlis: {a.file}")
2312
+ print(f" storage set: {info['storage_unit']['storage_set_id']}; file {info['file_header'].get('ID', '').strip()} seq {info['file_header'].get('SEQUENCE-NUMBER', '').strip()}")
2313
+ print(f" well: {o.get('WELL-NAME', '').strip() or '?'} field: {o.get('FIELD-NAME', '').strip() or '?'} company: {o.get('COMPANY', '').strip() or '?'} producer: {o.get('PRODUCER-NAME', '').strip() or '?'} created: {o.get('CREATION-TIME', '?')}")
2314
+ print(f" {info['n_records']} logical records; sets: " + ", ".join(f"{t}{'(' + n + ')' if n else ''} x{k}" for t, n, k in info["eflr_sets"]))
2315
+ if info.get("encrypted_records"):
2316
+ print(" encrypted records skipped: " + ", ".join(f"{k} x{n}" for k, n in info["encrypted_records"].items()))
2317
+ for u in info["unparsed"]:
2318
+ print(" not parsed: " + u)
2319
+ print(f" {len(info['channels'])} channels:")
2320
+ for c in info["channels"]:
2321
+ print(f" {c['name']:<12s} {c['units']:<10s} {c['code']:<7s} dim {c['dimension']} {c['long_name']}")
2322
+ for fr in info["frames"]:
2323
+ print(f" frame {fr['name']}: index {fr['index_type']} {fr['direction']}, spacing {fr['spacing']} {fr['spacing_units']}, {fr['index_min']}..{fr['index_max']}; {len(fr['channels'])} channels: {', '.join(fr['channels'][:12])}{' ...' if len(fr['channels']) > 12 else ''}")
2324
+ return 0
2325
+ st = _read_dlis(a.file, frame=a.frame)
2326
+ except (DlisError, OSError) as e:
2327
+ raise SystemExit(f"dlis: {e}")
2328
+ print(f"dlis: frame {st.meta['frame']}: {len(st.index)} rows, index {st.meta['index_channel']} ({st.meta['index_units']}) {float(st.index[0]):g}..{float(st.index[-1]):g}, {len(st.channels)} channels"
2329
+ + (f"; {st.meta['notes']}" if st.meta.get("notes") else ""))
2330
+ if a.out:
2331
+ import csv as _csv
2332
+ with open(a.out, "w", newline="", encoding="utf-8") as f:
2333
+ w = _csv.writer(f)
2334
+ w.writerow([st.meta["index_channel"]] + list(st.channels))
2335
+ w.writerow([st.meta["index_units"]] + [st.channels[c].unit for c in st.channels])
2336
+ for i in range(len(st.index)):
2337
+ w.writerow([float(st.index[i])] + ["" if st.channels[c].values[i] != st.channels[c].values[i] else float(st.channels[c].values[i]) for c in st.channels])
2338
+ print(" csv:", a.out)
2339
+ if a.to_las:
2340
+ with open(a.to_las, "w", encoding="utf-8") as f:
2341
+ f.write("~VERSION INFORMATION\n VERS. 2.0 : CWLS LOG ASCII STANDARD - VERSION 2.0\n WRAP. NO : ONE LINE PER DEPTH STEP\n")
2342
+ f.write("~WELL INFORMATION\n")
2343
+ f.write(f" STRT.{st.meta['index_units']:<6s} {float(st.index[0]):.4f} : START\n STOP.{st.meta['index_units']:<6s} {float(st.index[-1]):.4f} : STOP\n")
2344
+ f.write(f" STEP.{st.meta['index_units']:<6s} {(float(st.index[1]) - float(st.index[0])) if len(st.index) > 1 else 0.0:.4f} : STEP\n NULL. -999.25 : NULL VALUE\n")
2345
+ f.write(f" WELL. {st.meta.get('WELL-NAME', '').strip()} : WELL\n FLD . {st.meta.get('FIELD-NAME', '').strip()} : FIELD\n COMP. {st.meta.get('COMPANY', '').strip()} : COMPANY\n SRVC. {st.meta.get('PRODUCER-NAME', '').strip()} : SERVICE COMPANY\n")
2346
+ f.write(f" SRC . DLIS {os.path.basename(a.file)} frame {st.meta['frame']} : SOURCE (gea dlis --to-las)\n")
2347
+ f.write("~CURVE INFORMATION\n")
2348
+ f.write(f" {st.meta['index_channel']:<8s}.{st.meta['index_units']:<8s} : INDEX\n")
2349
+ for c in st.channels:
2350
+ f.write(f" {c:<8s}.{st.channels[c].unit:<8s} : {c}\n")
2351
+ f.write("~A\n")
2352
+ for i in range(len(st.index)):
2353
+ f.write(f"{float(st.index[i]):.4f}" + "".join(f" {(-999.25 if st.channels[c].values[i] != st.channels[c].values[i] else float(st.channels[c].values[i])):.4f}" for c in st.channels) + "\n")
2354
+ print(" las:", a.to_las)
2355
+ return 0
2356
+ elif a.cmd == "piwebapi":
2357
+ import json as _json
2358
+ from .live_ports import records_to_stream, summarize, write_records_csv
2359
+ from . import piwebapi as _PI
2360
+ if a.selftest:
2361
+ r = _PI.selftest()
2362
+ print(f"piwebapi selftest: {'OK' if r['ok'] else 'FAILED'}")
2363
+ for k, v in r.items():
2364
+ if k != "ok":
2365
+ print(f" {'ok ' if v else 'BAD'} {k}")
2366
+ return 0 if r["ok"] else 1
2367
+ if a.write_example_config:
2368
+ with open(a.write_example_config, "w", encoding="utf-8") as f:
2369
+ _json.dump(_PI.EXAMPLE_CONFIG, f, indent=1)
2370
+ print("config:", a.write_example_config); return 0
2371
+ if not a.config:
2372
+ raise SystemExit("piwebapi needs --config (or --write-example-config to start one)")
2373
+ cfg = _PI.load_config(a.config)
2374
+ sim_h = None
2375
+ try:
2376
+ if cfg.get("base_url") == "sim":
2377
+ sim = _PI.SimServer(); sim_h, port_ = sim.serve(); cfg = {**cfg, "base_url": f"http://127.0.0.1:{port_}/piwebapi", "auth": "none"}
2378
+ try:
2379
+ port = _PI.PiPort(cfg)
2380
+ r = port.pull()
2381
+ except (_PI.PiError, ValueError) as e:
2382
+ raise SystemExit(f"piwebapi: {e}")
2383
+ finally:
2384
+ if sim_h is not None:
2385
+ sim_h.close()
2386
+ recs = port.records
2387
+ print(f"piwebapi: {_json.dumps(port.summary(), default=str)}")
2388
+ print(_json.dumps(summarize(recs), indent=1))
2389
+ if a.record:
2390
+ with open(a.record, "w", encoding="utf-8") as f:
2391
+ f.write(_json.dumps({"pull": r, "summary": port.summary()}, default=str) + "\n")
2392
+ if a.out:
2393
+ print(" records:", write_records_csv(recs, a.out))
2394
+ if a.stream_csv and recs:
2395
+ st = records_to_stream(recs, name="piwebapi")
2396
+ import csv as _csv
2397
+ from datetime import datetime as _dt, timedelta as _td
2398
+ t0 = _dt.fromisoformat(st.meta["start_time"])
2399
+ with open(a.stream_csv, "w", newline="", encoding="utf-8") as f:
2400
+ w = _csv.writer(f)
2401
+ w.writerow(["timestamp"] + list(st.channels))
2402
+ for i, t in enumerate(st.index):
2403
+ w.writerow([(t0 + _td(seconds=float(t))).isoformat()] + ["" if st.channels[c].values[i] != st.channels[c].values[i] else round(float(st.channels[c].values[i]), 4) for c in st.channels])
2404
+ print(" stream:", a.stream_csv)
2405
+ return 0 if not port.faults else 1
2285
2406
  elif a.cmd in ("modbus-rtu", "dnp3", "hartip"):
2286
2407
  import json as _json
2287
2408
  from .live_ports import records_to_stream, summarize, write_records_csv
@@ -6774,6 +6774,198 @@ def section_bq_field_ports(tmp: str) -> None:
6774
6774
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
6775
6775
 
6776
6776
 
6777
+ def section_br_logs_historian(tmp: str) -> None:
6778
+ """Section BR - the DLIS reader proven on files this program wrote (every layer of RP66 V1, the representation
6779
+ codes, segmentation across visible records, encrypted records skipped and named, two frames, the refusals), into
6780
+ the files detector, the survey and the CLI; the PI Web API pull against the stand-in server (paging, flags,
6781
+ digital and system states, units, basic auth, plain HTTP refused), on the Patch panel and the CLI; the help."""
6782
+ import http.cookiejar
6783
+ import struct
6784
+ import subprocess as _sp
6785
+ import urllib.request
6786
+ import urllib.error
6787
+ from . import dlis as DL
6788
+ from . import piwebapi as PI
6789
+ from . import helplib as H
6790
+ from .files import detect, preview, read_any
6791
+ from .patches import PROTOCOLS, PatchSupervisor, validate_port_config
6792
+ from .survey_cmd import run_survey
6793
+ from .telemetry import TelemetryRecorder, TelemetryConfig
6794
+ from .downhole_engine import DownholeEngine, SimulatorConfig
6795
+ from .workspace import Workspace
6796
+ from .service import Service, Users
6797
+ pkg = Path(__file__).parent
6798
+ d = Path(tmp, "br"); d.mkdir()
6799
+
6800
+ # -- BR1: DLIS ------------------------------------------------------------------------------------------------------
6801
+ n = 3000
6802
+ depth = [1000.0 + 0.1524 * i for i in range(n)]
6803
+ rho = [2.3 + 0.3 * np.sin(i / 50.0) for i in range(n)]; rho[5] = -999.25
6804
+ dt = [80.0 + 10.0 * np.cos(i / 70.0) for i in range(n)]
6805
+ gr = [60.0 + 40.0 * np.sin(i / 20.0) for i in range(n)]
6806
+ cnt = [i % 7 for i in range(n)]
6807
+ p_gate = DL.write(str(d / "gate.dlis"), [("DEPT", "m", 7, depth), ("RHOB", "g/cm3", 2, rho), ("DTCO", "us/ft", 2, dt), ("GR", "gAPI", 2, gr), ("CNT", "", 15, cnt)], well="GATE 1", max_record=256)
6808
+ st = DL.read(p_gate)
6809
+ f = DL.DlisFile(Path(p_gate).read_bytes(), "gate"); info = f.describe()
6810
+ # the representation codes, each through a cursor
6811
+ c = DL._Cursor(struct.pack(">f", 1.5) + struct.pack(">d", -2.25) + struct.pack(">h", -300) + struct.pack(">i", -70000) + bytes([200]) + struct.pack(">H", 60000)
6812
+ + struct.pack(">I", 4000000000) + bytes([0x7F]) + struct.pack(">H", 0x8000 | 300) + struct.pack(">I", 0xC0000000 | 70000)
6813
+ + bytes([3]) + b"ABC" + bytes([4]) + b"TEXT" + bytes([126, 0x1A, 20, 22, 48, 50]) + struct.pack(">H", 250) + bytes([1, 0, 3]) + b"OBJ")
6814
+ vals = [c.value(2), c.value(7), c.value(13), c.value(14), c.value(15), c.value(16), c.value(17), c.uvari(), c.uvari(), c.uvari(), c.ident(), c.ascii(), c.value(21), c.obname()]
6815
+ fshort = DL._Cursor(struct.pack(">H", (0x7FF << 4) | 0)).value(1)
6816
+ # a second file: two frames, a time frame and a depth frame, a 2-D channel, an encrypted record planted
6817
+ raw = bytearray(Path(p_gate).read_bytes())
6818
+ # plant: flip the encryption bit of the first IFLR segment found (type 0, not EFLR)
6819
+ p = DL.SUL_LEN; planted = 0
6820
+ while p + 4 <= len(raw) and not planted:
6821
+ vlen = struct.unpack(">H", raw[p:p + 2])[0]; q = p + 4
6822
+ while q + 4 <= p + vlen:
6823
+ slen = struct.unpack(">H", raw[q:q + 2])[0]
6824
+ if not (raw[q + 2] & 0x80) and raw[q + 3] == 0 and not (raw[q + 2] & 0x60):
6825
+ raw[q + 2] |= 0x10; planted = 1; break
6826
+ q += slen
6827
+ p += vlen
6828
+ st_enc = DL.read(bytes(raw))
6829
+ not_dlis = None
6830
+ try:
6831
+ DL.read(b"~VERSION INFORMATION\n VERS. 2.0\n" + bytes(100))
6832
+ except DL.DlisError as e:
6833
+ not_dlis = str(e)
6834
+ two = str(d / "two.dlis")
6835
+ DL.write(two, [("TIME", "s", 7, [float(i) for i in range(50)]), ("P", "psi", 2, [3000.0 + i for i in range(50)])], frame_name="TFRAME", index_type="TIME")
6836
+ two_err = None
6837
+ try:
6838
+ DL.read(two + ".nope")
6839
+ except (DL.DlisError, OSError) as e:
6840
+ two_err = str(e)
6841
+ st_t = DL.read(two)
6842
+ c1 = {
6843
+ "round_trip": st.index_kind == "depth" and len(st.index) == n and list(st.channels) == ["RHOB", "DTCO", "GR", "CNT"] and st.channels["RHOB"].unit == "g/cm3"
6844
+ and np.isnan(st.channels["RHOB"].values[5]) and float(np.nanmax(np.abs(st.channels["RHOB"].values[6:] - np.array(rho[6:])))) < 1e-6
6845
+ and float(np.max(np.abs(st.index - np.array(depth)))) < 1e-9 and st.channels["CNT"].values[13] == 6.0,
6846
+ "layers": info["storage_unit"]["version"] == "V1.00" and info["storage_unit"]["max_record_length"] == 256 and info["origin"]["WELL-NAME"] == "GATE 1"
6847
+ and [t for t, _, _ in info["eflr_sets"]] == ["FILE-HEADER", "ORIGIN", "CHANNEL", "FRAME"] and info["frames"][0]["spacing"] is not None
6848
+ and abs(info["frames"][0]["spacing"] - 0.1524) < 1e-9 and info["n_records"] == 4 + n and st.meta["spacing"].startswith("0.1524 m"),
6849
+ "segmented": len(DL.segments(Path(p_gate).read_bytes())) > info["n_records"], # the CHANNEL and FRAME sets span segments at 256-byte records
6850
+ "codes": (abs(vals[0] - 1.5) < 1e-7 and vals[1] == -2.25 and vals[2] == -300 and vals[3] == -70000 and vals[4] == 200 and vals[5] == 60000 and vals[6] == 4000000000
6851
+ and vals[7:10] == [127, 300, 70000] and vals[10] == "ABC" and vals[11] == "TEXT" and vals[12].startswith("2026-10-20T22:48:50.250") and vals[13] == (1, 0, "OBJ")
6852
+ and abs(fshort - 2047.0) < 1e-9),
6853
+ "encrypted_skipped": planted == 1 and len(st_enc.index) == n - 1 and "encrypted records skipped: IFLR type 0 x1" in st_enc.meta["notes"],
6854
+ "refusals": not_dlis and "not a DLIS V1 storage unit label" in not_dlis and two_err and st_t.index_kind == "time_s" and st_t.meta["frame"] == "TFRAME",
6855
+ }
6856
+ failed1 = [k for k, v in c1.items() if not v]
6857
+ ok(not failed1,
6858
+ "BR1 DLIS read as specified: a file this program wrote round-trips (the index, every channel with its units, the null sentinel, an integer channel); "
6859
+ "the storage unit label, origin, sets and frame spacing described; a 1 KB visible record forces segmentation and the segments reassemble; the "
6860
+ "representation codes (FSINGL, FDOUBL, the integers, UVARI in all three lengths, IDENT, ASCII, DTIME, OBNAME, FSHORT) decode; a planted encrypted "
6861
+ "record is skipped and named; a LAS handed to the reader is refused by its label; a time-indexed frame is read as time"
6862
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
6863
+
6864
+ # -- BR2: PI Web API ------------------------------------------------------------------------------------------------
6865
+ sp = PI.selftest()
6866
+ sim = PI.SimServer(n_values=30, page=7); h, port = sim.serve()
6867
+ try:
6868
+ prt = PI.PiPort({**PI.EXAMPLE_CONFIG, "base_url": f"http://127.0.0.1:{port}/piwebapi", "auth": "none", "mode": "interpolated", "mapping": PI.EXAMPLE_CONFIG["mapping"][:1]})
6869
+ r = prt.pull()
6870
+ prt2 = PI.PiPort({**PI.EXAMPLE_CONFIG, "base_url": f"http://127.0.0.1:{port}/piwebapi", "auth": "none", "mode": "recorded", "mapping": PI.EXAMPLE_CONFIG["mapping"][:1]})
6871
+ r2 = prt2.pull()
6872
+ finally:
6873
+ h.close()
6874
+ bad_mode = None
6875
+ try:
6876
+ PI.PiPort({**PI.EXAMPLE_CONFIG, "mode": "latest"})
6877
+ except ValueError as e:
6878
+ bad_mode = str(e)
6879
+ c2 = {
6880
+ "selftest": sp["ok"],
6881
+ "interpolated": r["tags"]["DHP_psi"]["items"] == 6 and len(prt.records) == 6 and prt.records[1].timestamp_utc.startswith("2026-10-01T00:25:00"),
6882
+ "paged": r2["tags"]["DHP_psi"]["items"] == 30 and sum("startIndex=" in u for u in prt2.client.calls) == 4 and prt2.summary()["calls"] == 6,
6883
+ "bad_mode": bad_mode and "mode must be one of" in bad_mode,
6884
+ }
6885
+ failed2 = [k for k, v in c2.items() if not v]
6886
+ ok(not failed2,
6887
+ "BR2 PI Web API: the self-test (classification of Good, Questionable, Substituted, not Good, digital and system states; recorded values paged "
6888
+ "through Links.Next; a point the server lacks named with its HTTP status; a bar point recorded in psi; basic auth from the environment with a "
6889
+ "wrong password a 401; a credential over plain HTTP refused); an interpolated pull at the interval; a 30-value archive in 7-value pages fetched "
6890
+ "in 4 follow-ups; a mode the port does not offer refused"
6891
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
6892
+
6893
+ # -- BR3: the detector, the survey, the supervisor, the service, the CLI, the help ---------------------------------
6894
+ det = detect(p_gate); pv = preview(p_gate)
6895
+ st_any = read_any(p_gate)
6896
+ txt, machine = run_survey(path=p_gate)
6897
+ time_refused = None
6898
+ try:
6899
+ run_survey(path=two)
6900
+ except SystemExit as e:
6901
+ time_refused = str(e)
6902
+ hist = str(d / "hist.csv")
6903
+ TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=0.5, seed=3)).run().export_csv(hist)
6904
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
6905
+ w = ws.add_well_file(hist, display="Well H", actor="tester")
6906
+ w_dlis = ws.add_well_file(p_gate, display="Gate log", actor="tester")
6907
+ sup = PatchSupervisor(ws)
6908
+ sup.store.add("hist", "piwebapi", w["id"], {**PI.EXAMPLE_CONFIG, "base_url": "sim", "interval_s": 0.5, "mode": "recorded"}, "tester", priority=1, stale_after_s=30)
6909
+ bad_cfg = None
6910
+ try:
6911
+ validate_port_config("piwebapi", {"base_url": "sim", "mapping": [{"address": "\\\\X\\Y", "tag_id": "P"}]})
6912
+ except ValueError as e:
6913
+ bad_cfg = str(e)
6914
+ sup.start("hist", "tester")
6915
+ time.sleep(2.5)
6916
+ s1 = {x["name"]: x for x in sup.states()}["hist"]
6917
+ sup.stop("hist", "tester")
6918
+ users = Users(str(Path(wsp, "users.json"))); users.add("op", "operator-pass-1", "operator")
6919
+ svc = Service(wsp, port=0, scheduler=False).start()
6920
+ base_url = svc.url.rstrip("/")
6921
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
6922
+
6923
+ def call(method, path, body=None):
6924
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
6925
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
6926
+ try:
6927
+ with opener.open(req, timeout=120) as rr_:
6928
+ return rr_.status, json.loads(rr_.read())
6929
+ except urllib.error.HTTPError as e:
6930
+ return e.code, json.loads(e.read())
6931
+ try:
6932
+ call("POST", "/api/login", {"name": "op", "password": "operator-pass-1"})
6933
+ st_p, pl = call("GET", "/api/patches")
6934
+ st_x, ex = call("GET", "/api/config/defaults/piwebapi")
6935
+ finally:
6936
+ svc.stop()
6937
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
6938
+ r_info = _sp.run([sys.executable, "-m", "gea", "dlis", "--file", p_gate, "--info"], capture_output=True, text=True)
6939
+ r_las = _sp.run([sys.executable, "-m", "gea", "dlis", "--file", p_gate, "--to-las", str(d / "gate.las"), "--out", str(d / "gate.csv")], capture_output=True, text=True)
6940
+ r_sv = _sp.run([sys.executable, "-m", "gea", "survey", "--file", str(d / "gate.las")], capture_output=True, text=True)
6941
+ pi_cfg = str(d / "pi.json")
6942
+ with open(pi_cfg, "w", encoding="utf-8") as fcfg:
6943
+ json.dump({**PI.EXAMPLE_CONFIG, "base_url": "sim"}, fcfg)
6944
+ r_pi = _sp.run([sys.executable, "-m", "gea", "piwebapi", "--config", pi_cfg, "--out", str(d / "pi.csv"), "--stream-csv", str(d / "pi_stream.csv")], capture_output=True, text=True)
6945
+ r_pis = _sp.run([sys.executable, "-m", "gea", "piwebapi", "--selftest"], capture_output=True, text=True)
6946
+ c3 = {
6947
+ "detected": det["kind"] == "dlis" and "gea gate file" in det["detail"] and pv["lines"] and "GATE 1 / GATE by gea-program" in pv["lines"][0] and st_any.source_format == "dlis",
6948
+ "survey": "read by the dlis port" in txt and machine["channels_found"].get("density") == "RHOB" and machine["n_stations"] == n
6949
+ and time_refused and "not depth" in time_refused and "TFRAME" not in time_refused,
6950
+ "imported": w_dlis["kind"] == "file" and os.path.isfile(Path(wsp, "wells", w_dlis["id"], "source", "gate.dlis")),
6951
+ "supervised": "piwebapi" in PROTOCOLS and bad_cfg and "source" in bad_cfg and s1["status"] in ("CONNECTED", "STOPPED") and s1["samples_total"] >= 500
6952
+ and abs(s1["tags"]["DHP_psi"]["value"] - (3185.5 + 0.01 * 249)) < 1e-6,
6953
+ "service": st_p == 200 and "piwebapi" in pl["protocols"] and st_x == 200 and ex["content"]["auth"] == "basic" and 'value="piwebapi"' in page_src,
6954
+ "cli": r_info.returncode == 0 and "well: GATE 1" in r_info.stdout and "frame FRAME1: index BOREHOLE-DEPTH INCREASING" in r_info.stdout
6955
+ and r_las.returncode == 0 and os.path.isfile(d / "gate.las") and r_sv.returncode == 0 and "density -> RHOB" in r_sv.stdout
6956
+ and r_pi.returncode == 0 and '"records": 750' in r_pi.stdout and os.path.isfile(d / "pi_stream.csv") and r_pis.returncode == 0,
6957
+ "help": all(any(t["topic"] == x for t in H.topics()) and not H.check(x) for x in ("dlis", "historian"))
6958
+ and "does not decrypt" in (pkg / "help" / "dlis.md").read_text(encoding="utf-8").split("will not call a measurement")[1]
6959
+ and "no code path that writes a value" in (pkg / "help" / "historian.md").read_text(encoding="utf-8").split("will not call a measurement")[1],
6960
+ }
6961
+ failed3 = [k for k, v in c3.items() if not v]
6962
+ ok(not failed3,
6963
+ "BR3 a DLIS detected by its label, previewed and read like a LAS; the strata survey runs on it and refuses a time-indexed frame by name; "
6964
+ "imported as a well; a PI pull supervised on the Patch panel with the window moving (a config without its source refused); the service "
6965
+ "lists the protocol and serves the example; `gea dlis --info`, `--to-las` then `gea survey` on the LAS, `gea piwebapi` against the stand-in; the help pages"
6966
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
6967
+
6968
+
6777
6969
  def main() -> int:
6778
6970
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
6779
6971
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -6846,6 +7038,7 @@ def main() -> int:
6846
7038
  section_bo_structure(tmp)
6847
7039
  section_bp_interval(tmp)
6848
7040
  section_bq_field_ports(tmp)
7041
+ section_br_logs_historian(tmp)
6849
7042
  if _FAILS:
6850
7043
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
6851
7044
  for f in _FAILS: