gea-program 0.19.0__tar.gz → 0.20.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 (249) hide show
  1. {gea_program-0.19.0 → gea_program-0.20.0}/PKG-INFO +10 -2
  2. {gea_program-0.19.0 → gea_program-0.20.0}/README.md +9 -1
  3. {gea_program-0.19.0 → gea_program-0.20.0}/gea/__init__.py +1 -1
  4. {gea_program-0.19.0 → gea_program-0.20.0}/gea/__main__.py +22 -0
  5. {gea_program-0.19.0 → gea_program-0.20.0}/gea/acceptance_tests.py +111 -10
  6. {gea_program-0.19.0 → gea_program-0.20.0}/gea/blind_harness.py +1 -1
  7. {gea_program-0.19.0 → gea_program-0.20.0}/gea/client_reports.py +9 -0
  8. {gea_program-0.19.0 → gea_program-0.20.0}/gea/correlation.py +1 -1
  9. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/TESTER_GUIDE.md +2 -2
  10. gea_program-0.20.0/gea/help/soak.md +11 -0
  11. {gea_program-0.19.0 → gea_program-0.20.0}/gea/helplib.py +1 -0
  12. {gea_program-0.19.0 → gea_program-0.20.0}/gea/service.py +5 -1
  13. gea_program-0.20.0/gea/soak.py +403 -0
  14. {gea_program-0.19.0 → gea_program-0.20.0}/gea/survey_cmd.py +12 -10
  15. {gea_program-0.19.0 → gea_program-0.20.0}/gea/web/app.html +2 -2
  16. {gea_program-0.19.0 → gea_program-0.20.0}/gea/wits0.py +45 -27
  17. {gea_program-0.19.0 → gea_program-0.20.0}/gea_program.egg-info/PKG-INFO +10 -2
  18. {gea_program-0.19.0 → gea_program-0.20.0}/gea_program.egg-info/SOURCES.txt +2 -0
  19. {gea_program-0.19.0 → gea_program-0.20.0}/pyproject.toml +1 -1
  20. {gea_program-0.19.0 → gea_program-0.20.0}/LICENSE +0 -0
  21. {gea_program-0.19.0 → gea_program-0.20.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  22. {gea_program-0.19.0 → gea_program-0.20.0}/gea/accuracy_statement.py +0 -0
  23. {gea_program-0.19.0 → gea_program-0.20.0}/gea/alarm_engine.py +0 -0
  24. {gea_program-0.19.0 → gea_program-0.20.0}/gea/backup.py +0 -0
  25. {gea_program-0.19.0 → gea_program-0.20.0}/gea/bench.py +0 -0
  26. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  27. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  28. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  29. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  30. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  31. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  32. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  33. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  34. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  35. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  36. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  37. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  38. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  39. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  40. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  41. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  42. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  43. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  44. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  45. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  46. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  47. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  48. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  49. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  50. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  51. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  52. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  53. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  54. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  55. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  56. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  57. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  58. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  59. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  60. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  61. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  62. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  63. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  64. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  65. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  66. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  67. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  68. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  69. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  70. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  71. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  72. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  73. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  74. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  75. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  76. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  77. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  78. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  79. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  80. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  81. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  82. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  83. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  84. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  85. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  86. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  87. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  88. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  89. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  90. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  91. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  92. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  93. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/med_160_sapropels.txt +0 -0
  94. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  95. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  96. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  97. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  98. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  99. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  100. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  101. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  102. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  103. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  104. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  105. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  106. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  107. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  108. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  109. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  110. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  111. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  112. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  113. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  114. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  115. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  116. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  117. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  118. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  119. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  120. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  121. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  122. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  123. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  124. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  125. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  126. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  127. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  128. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  129. {gea_program-0.19.0 → gea_program-0.20.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  130. {gea_program-0.19.0 → gea_program-0.20.0}/gea/certificates.py +0 -0
  131. {gea_program-0.19.0 → gea_program-0.20.0}/gea/cli.py +0 -0
  132. {gea_program-0.19.0 → gea_program-0.20.0}/gea/config_versioning.py +0 -0
  133. {gea_program-0.19.0 → gea_program-0.20.0}/gea/conformance.py +0 -0
  134. {gea_program-0.19.0 → gea_program-0.20.0}/gea/dashboard.py +0 -0
  135. {gea_program-0.19.0 → gea_program-0.20.0}/gea/deviation.py +0 -0
  136. {gea_program-0.19.0 → gea_program-0.20.0}/gea/doctor.py +0 -0
  137. {gea_program-0.19.0 → gea_program-0.20.0}/gea/downhole_engine.py +0 -0
  138. {gea_program-0.19.0 → gea_program-0.20.0}/gea/drift_monitor.py +0 -0
  139. {gea_program-0.19.0 → gea_program-0.20.0}/gea/earth_model.py +0 -0
  140. {gea_program-0.19.0 → gea_program-0.20.0}/gea/etp.py +0 -0
  141. {gea_program-0.19.0 → gea_program-0.20.0}/gea/etp12_schemas.json +0 -0
  142. {gea_program-0.19.0 → gea_program-0.20.0}/gea/example_register_map.json +0 -0
  143. {gea_program-0.19.0 → gea_program-0.20.0}/gea/fat_sat.py +0 -0
  144. {gea_program-0.19.0 → gea_program-0.20.0}/gea/files.py +0 -0
  145. {gea_program-0.19.0 → gea_program-0.20.0}/gea/follower.py +0 -0
  146. {gea_program-0.19.0 → gea_program-0.20.0}/gea/forward_model.py +0 -0
  147. {gea_program-0.19.0 → gea_program-0.20.0}/gea/gamma.py +0 -0
  148. {gea_program-0.19.0 → gea_program-0.20.0}/gea/gauge_aging.py +0 -0
  149. {gea_program-0.19.0 → gea_program-0.20.0}/gea/gauge_specs.py +0 -0
  150. {gea_program-0.19.0 → gea_program-0.20.0}/gea/geodesy.py +0 -0
  151. {gea_program-0.19.0 → gea_program-0.20.0}/gea/gravity_reference.py +0 -0
  152. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/alarms.md +0 -0
  153. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/audit-update.md +0 -0
  154. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/backup.md +0 -0
  155. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/conformance.md +0 -0
  156. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/data-in.md +0 -0
  157. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/doctor.md +0 -0
  158. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/drift.md +0 -0
  159. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/etp.md +0 -0
  160. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/files.md +0 -0
  161. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/instruments.md +0 -0
  162. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/month-end.md +0 -0
  163. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/notifications.md +0 -0
  164. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/osdu.md +0 -0
  165. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/patches.md +0 -0
  166. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/ppdm.md +0 -0
  167. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/quakeml.md +0 -0
  168. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/quality.md +0 -0
  169. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/seedlink.md +0 -0
  170. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/seismic.md +0 -0
  171. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/site.md +0 -0
  172. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/sites.md +0 -0
  173. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/sra.md +0 -0
  174. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/start.md +0 -0
  175. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/transients.md +0 -0
  176. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/upkeep.md +0 -0
  177. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/vibration.md +0 -0
  178. {gea_program-0.19.0 → gea_program-0.20.0}/gea/help/well-tests.md +0 -0
  179. {gea_program-0.19.0 → gea_program-0.20.0}/gea/housekeeping.py +0 -0
  180. {gea_program-0.19.0 → gea_program-0.20.0}/gea/inverse_engine.py +0 -0
  181. {gea_program-0.19.0 → gea_program-0.20.0}/gea/jobs.py +0 -0
  182. {gea_program-0.19.0 → gea_program-0.20.0}/gea/live_ports.py +0 -0
  183. {gea_program-0.19.0 → gea_program-0.20.0}/gea/loadtest.py +0 -0
  184. {gea_program-0.19.0 → gea_program-0.20.0}/gea/matplotlib_demo.py +0 -0
  185. {gea_program-0.19.0 → gea_program-0.20.0}/gea/modbus.py +0 -0
  186. {gea_program-0.19.0 → gea_program-0.20.0}/gea/model_card.py +0 -0
  187. {gea_program-0.19.0 → gea_program-0.20.0}/gea/month_end.py +0 -0
  188. {gea_program-0.19.0 → gea_program-0.20.0}/gea/mqtt_port.py +0 -0
  189. {gea_program-0.19.0 → gea_program-0.20.0}/gea/notify.py +0 -0
  190. {gea_program-0.19.0 → gea_program-0.20.0}/gea/opcua_port.py +0 -0
  191. {gea_program-0.19.0 → gea_program-0.20.0}/gea/operator_app.py +0 -0
  192. {gea_program-0.19.0 → gea_program-0.20.0}/gea/osdu.py +0 -0
  193. {gea_program-0.19.0 → gea_program-0.20.0}/gea/patches.py +0 -0
  194. {gea_program-0.19.0 → gea_program-0.20.0}/gea/permits.py +0 -0
  195. {gea_program-0.19.0 → gea_program-0.20.0}/gea/ports.py +0 -0
  196. {gea_program-0.19.0 → gea_program-0.20.0}/gea/ppdm.py +0 -0
  197. {gea_program-0.19.0 → gea_program-0.20.0}/gea/profile_catalog.py +0 -0
  198. {gea_program-0.19.0 → gea_program-0.20.0}/gea/project.py +0 -0
  199. {gea_program-0.19.0 → gea_program-0.20.0}/gea/qt6_downhole_app.py +0 -0
  200. {gea_program-0.19.0 → gea_program-0.20.0}/gea/quakeml.py +0 -0
  201. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reconciler.py +0 -0
  202. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  203. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  204. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  205. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  206. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  207. {gea_program-0.19.0 → gea_program-0.20.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  208. {gea_program-0.19.0 → gea_program-0.20.0}/gea/rock_inventory.py +0 -0
  209. {gea_program-0.19.0 → gea_program-0.20.0}/gea/sample_record.py +0 -0
  210. {gea_program-0.19.0 → gea_program-0.20.0}/gea/sample_well_profile.csv +0 -0
  211. {gea_program-0.19.0 → gea_program-0.20.0}/gea/sbom.py +0 -0
  212. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seedlink.py +0 -0
  213. {gea_program-0.19.0 → gea_program-0.20.0}/gea/segy.py +0 -0
  214. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic.py +0 -0
  215. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_array.py +0 -0
  216. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_assoc.py +0 -0
  217. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_detect.py +0 -0
  218. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_film.py +0 -0
  219. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_harmonic.py +0 -0
  220. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_qc.py +0 -0
  221. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_response.py +0 -0
  222. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_signature.py +0 -0
  223. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_track.py +0 -0
  224. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_uncertainty.py +0 -0
  225. {gea_program-0.19.0 → gea_program-0.20.0}/gea/seismic_unlisted.py +0 -0
  226. {gea_program-0.19.0 → gea_program-0.20.0}/gea/sensor_swap.py +0 -0
  227. {gea_program-0.19.0 → gea_program-0.20.0}/gea/service_life.py +0 -0
  228. {gea_program-0.19.0 → gea_program-0.20.0}/gea/shell.py +0 -0
  229. {gea_program-0.19.0 → gea_program-0.20.0}/gea/shut_in.py +0 -0
  230. {gea_program-0.19.0 → gea_program-0.20.0}/gea/sla_report.py +0 -0
  231. {gea_program-0.19.0 → gea_program-0.20.0}/gea/sra.py +0 -0
  232. {gea_program-0.19.0 → gea_program-0.20.0}/gea/store_forward.py +0 -0
  233. {gea_program-0.19.0 → gea_program-0.20.0}/gea/strata_join.py +0 -0
  234. {gea_program-0.19.0 → gea_program-0.20.0}/gea/supervisor.py +0 -0
  235. {gea_program-0.19.0 → gea_program-0.20.0}/gea/survey_view.py +0 -0
  236. {gea_program-0.19.0 → gea_program-0.20.0}/gea/telemetry.py +0 -0
  237. {gea_program-0.19.0 → gea_program-0.20.0}/gea/tool_library.py +0 -0
  238. {gea_program-0.19.0 → gea_program-0.20.0}/gea/transient.py +0 -0
  239. {gea_program-0.19.0 → gea_program-0.20.0}/gea/vibration.py +0 -0
  240. {gea_program-0.19.0 → gea_program-0.20.0}/gea/well_assembler.py +0 -0
  241. {gea_program-0.19.0 → gea_program-0.20.0}/gea/well_test_validation.py +0 -0
  242. {gea_program-0.19.0 → gea_program-0.20.0}/gea/witsml.py +0 -0
  243. {gea_program-0.19.0 → gea_program-0.20.0}/gea/workspace.py +0 -0
  244. {gea_program-0.19.0 → gea_program-0.20.0}/gea_program.egg-info/dependency_links.txt +0 -0
  245. {gea_program-0.19.0 → gea_program-0.20.0}/gea_program.egg-info/entry_points.txt +0 -0
  246. {gea_program-0.19.0 → gea_program-0.20.0}/gea_program.egg-info/requires.txt +0 -0
  247. {gea_program-0.19.0 → gea_program-0.20.0}/gea_program.egg-info/top_level.txt +0 -0
  248. {gea_program-0.19.0 → gea_program-0.20.0}/setup.cfg +0 -0
  249. {gea_program-0.19.0 → gea_program-0.20.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.19.0
3
+ Version: 0.20.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 (344 checks)
128
+ gea accept # the product gate (347 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]")
@@ -740,6 +740,14 @@ headers on every response, and `gea housekeeping --workspace C:\site --apply`
740
740
  than truncating them and prunes finished job folders and old recordings. `gea
741
741
  loadtest --patches 16 --seconds 60 --with-service` tells you whether the site
742
742
  machine carries the patches it will be given, with the page still answering.
743
+ `gea soak --hours 72 --patches 4 --with-service --out C:\site\reports\soak`
744
+ runs the site's patch count, an ETP patch and a SeedLink station through
745
+ link relays that are cut on a schedule, and writes the Soak Report from the
746
+ supervisor's own numbers: seconds to reconnect per source after every
747
+ outage, what each protocol lost while the link was down (WITS0 has no
748
+ replay; SeedLink resumes by sequence number, so its gap count must be
749
+ zero), and the page's answer time through it; Verification runs a 15-minute
750
+ one from the page. `gea help soak`.
743
751
  `gea backup --workspace C:\site --out D:\gea-backups --keep 14` copies the
744
752
  whole site into one dated archive with a manifest of every file's hash, onto
745
753
  another disk or a share; schedule it daily from Audit / Update, verify an
@@ -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 (344 checks)
89
+ gea accept # the product gate (347 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]")
@@ -701,6 +701,14 @@ headers on every response, and `gea housekeeping --workspace C:\site --apply`
701
701
  than truncating them and prunes finished job folders and old recordings. `gea
702
702
  loadtest --patches 16 --seconds 60 --with-service` tells you whether the site
703
703
  machine carries the patches it will be given, with the page still answering.
704
+ `gea soak --hours 72 --patches 4 --with-service --out C:\site\reports\soak`
705
+ runs the site's patch count, an ETP patch and a SeedLink station through
706
+ link relays that are cut on a schedule, and writes the Soak Report from the
707
+ supervisor's own numbers: seconds to reconnect per source after every
708
+ outage, what each protocol lost while the link was down (WITS0 has no
709
+ replay; SeedLink resumes by sequence number, so its gap count must be
710
+ zero), and the page's answer time through it; Verification runs a 15-minute
711
+ one from the page. `gea help soak`.
704
712
  `gea backup --workspace C:\site --out D:\gea-backups --keep 14` copies the
705
713
  whole site into one dated archive with a manifest of every file's hash, onto
706
714
  another disk or a share; schedule it daily from Audit / Update, verify an
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.19.0"
126
+ __version__ = "0.20.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -392,6 +392,19 @@ def main(argv=None) -> int:
392
392
  p_hk.add_argument("--actor", type=str, default="housekeeping")
393
393
  p_hk.add_argument("--json", action="store_true")
394
394
 
395
+ p_sk = sub.add_parser("soak", help="the patches through link outages for hours, measured: N WITS0 patches, an ETP patch and a SeedLink station through relays that are cut on a schedule; the Soak Report")
396
+ p_sk.add_argument("--hours", type=float, default=None, help="how long (72 for the go-live soak); or --seconds")
397
+ p_sk.add_argument("--seconds", type=float, default=None)
398
+ p_sk.add_argument("--patches", type=int, default=2, help="WITS0 patches (the site's own count)")
399
+ p_sk.add_argument("--no-etp", action="store_true"); p_sk.add_argument("--no-seedlink", action="store_true")
400
+ p_sk.add_argument("--outage-every", type=float, default=600.0, help="seconds between link outages")
401
+ p_sk.add_argument("--outage-len", type=float, default=60.0, help="seconds each outage lasts")
402
+ p_sk.add_argument("--interval", type=float, default=1.0, help="simulator frame interval (s)")
403
+ p_sk.add_argument("--reconnect-budget", type=float, default=90.0, help="seconds a source may take to be back and receiving after the link returns")
404
+ p_sk.add_argument("--with-service", action="store_true", help="also serve the page and time its answers through the outages")
405
+ p_sk.add_argument("--out", type=str, default=None, help="where soak.json and the Soak Report go (default: the throw-away workspace's reports/soak)")
406
+ p_sk.add_argument("--keep", action="store_true", help="keep the throw-away workspace")
407
+ p_sk.add_argument("--json", action="store_true")
395
408
  p_lt = sub.add_parser("loadtest", help="how many supervised patches this machine carries: N WITS0 simulators and patches for a while, with the service answering")
396
409
  p_lt.add_argument("--patches", type=int, default=8)
397
410
  p_lt.add_argument("--seconds", type=float, default=30.0)
@@ -915,6 +928,15 @@ def main(argv=None) -> int:
915
928
  for x in r['records_removed'][:20]:
916
929
  print(f" record {x['well_id']}/{x['file']} {x['age_days']} d")
917
930
  return 0
931
+ elif a.cmd == "soak":
932
+ import json as _json
933
+ from .soak import run as _soak
934
+ secs = a.seconds if a.seconds is not None else (a.hours * 3600.0 if a.hours is not None else 600.0)
935
+ r = _soak(seconds=secs, patches=a.patches, etp=not a.no_etp, seedlink=not a.no_seedlink, outage_every_s=a.outage_every, outage_s=a.outage_len,
936
+ interval_s=a.interval, with_service=a.with_service, keep=a.keep, out_dir=a.out, reconnect_budget_s=a.reconnect_budget, verbose=not a.json)
937
+ if a.json:
938
+ print(_json.dumps({k: v for k, v in r.items() if k != "samples_timeline"}, indent=1, default=str))
939
+ return 0 if r["ok"] else 1
918
940
  elif a.cmd == "loadtest":
919
941
  import json as _json
920
942
  from .loadtest import run as _lt
@@ -646,7 +646,7 @@ def section_s_correlation() -> None:
646
646
  and all("continuity" in r["continuity_claim"].lower()
647
647
  or r["continuity_claim"] == "TWIN_HOLE_ELIGIBLE"
648
648
  for r in df["refused"] + df["ok"])
649
- and "honest census" in df["finding"],
649
+ and "census as it stands" in df["finding"],
650
650
  "S2 correlation: every cross-site pair carries a continuity claim "
651
651
  "bounded by distance, and thin pairs refuse with counts")
652
652
 
@@ -767,26 +767,27 @@ def section_y_survey() -> None:
767
767
  """Section Y - the one-command user path: gea survey."""
768
768
  from .survey_cmd import run_survey
769
769
  txt, d = run_survey(demo=True)
770
- ok('one honest answer' in txt and d['n_stations'] == 65
771
- and d['exclusions']['washout_or_null_stations'] == 46,
770
+ from .client_reports import forbidden_terms
771
+ ok('one answer with its provenance' in txt and d['n_stations'] == 65
772
+ and d['exclusions']['washout_or_null_stations'] == 46 and not forbidden_terms(txt),
772
773
  "Y1 survey demo: end-to-end on the bundled KTB excerpt - 65 "
773
- "stations, 46 exclusions DISCLOSED, one readable report")
774
+ "stations, 46 exclusions DISCLOSED, one readable report that passes the same internal-register guard every client report passes")
774
775
  ok('vp_m_s' in d and abs(d.get('vp_cross_check_pct', 99)) < 5.0,
775
776
  "Y2 survey self-grading: the file carries its own sonic and the "
776
777
  "estimate lands within 5 pct of measured (demo: ~+0.7 pct) - the "
777
778
  "tool grades itself when the data allows")
778
- ok('ASSUMPTION' in txt and 'refused to guess' in txt,
779
- "Y3 survey honesty: the prior-family assumption is printed where "
780
- "it acts and the refusals section is always present")
779
+ ok('ASSUMPTION' in txt and 'does not call a measurement' in txt,
780
+ "Y3 survey: the prior-family assumption is printed where "
781
+ "it acts and the what-this-tool-does-not-call-a-measurement section is always present")
781
782
  import tempfile, os as _os
782
783
  with tempfile.TemporaryDirectory() as td:
783
784
  f = _os.path.join(td, 'empty.las')
784
785
  open(f, 'w').write('~Version\n VERS. 2.0:\n~Well\n~Curve\n'
785
786
  'DEPT.M : depth\n~ASCII\n1.0\n2.0\n')
786
787
  txt2, d2 = run_survey(path=f)
787
- ok(d2['refusals'] and 'REFUSED' in txt2,
788
- "Y4 survey refusal: a LAS with no density and no gravity gets "
789
- "an honest refusal naming the unlocking channel, not an "
788
+ ok(d2['refusals'] and 'NOT ESTIMATED' in txt2 and not forbidden_terms(txt2),
789
+ "Y4 survey: a LAS with no density and no gravity gets "
790
+ "NOT ESTIMATED naming the unlocking channel, not an "
790
791
  "invented answer")
791
792
 
792
793
 
@@ -5996,6 +5997,105 @@ def section_bm_month_end(tmp: str) -> None:
5996
5997
  "says the index collects and does not recompute"
5997
5998
  + (f" [failed: {', '.join(failed2)}; cli={r_cli.stdout[-200:]!r} {r_cli.stderr[-200:]!r}]" if failed2 else ""))
5998
5999
 
6000
+
6001
+ def section_bn_soak(tmp: str) -> None:
6002
+ """Section BN - the soak: the link relay cuts and restores a connection; the patches, the ETP store and the
6003
+ SeedLink station through relays under scheduled outages, every one back and receiving, SeedLink without a gap,
6004
+ the page answering; the Soak Report; the CLI, the page row, the guard on plain-text deliverables."""
6005
+ import socket as _socket
6006
+ import subprocess as _sp
6007
+ from . import soak as SK
6008
+ from . import helplib as H
6009
+ from .client_reports import forbidden_terms, guard_text
6010
+ pkg = Path(__file__).parent
6011
+ d = Path(tmp, "bn"); d.mkdir()
6012
+
6013
+ # -- the relay alone: bytes through, a cut severs, a restore accepts again on the same port -------------------------
6014
+ echo = _socket.socket(); echo.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1); echo.bind(("127.0.0.1", 0)); echo.listen(4); echo.settimeout(5)
6015
+ eport = echo.getsockname()[1]
6016
+
6017
+ def echo_once():
6018
+ try:
6019
+ c, _ = echo.accept(); c.settimeout(5)
6020
+ while True:
6021
+ b = c.recv(4096)
6022
+ if not b:
6023
+ break
6024
+ c.sendall(b.upper())
6025
+ except OSError:
6026
+ pass
6027
+ threading.Thread(target=echo_once, daemon=True).start()
6028
+ rl = SK.LinkRelay(("127.0.0.1", eport)); rl.start()
6029
+ cl = _socket.create_connection(("127.0.0.1", rl.port), timeout=5); cl.sendall(b"ping"); back = cl.recv(16)
6030
+ rl.cut()
6031
+ severed = False
6032
+ try:
6033
+ cl.settimeout(2); cl.sendall(b"x"); r_ = cl.recv(16); severed = r_ == b""
6034
+ except OSError:
6035
+ severed = True
6036
+ refused = False
6037
+ try:
6038
+ _socket.create_connection(("127.0.0.1", rl.port), timeout=1).close()
6039
+ except OSError:
6040
+ refused = True
6041
+ threading.Thread(target=echo_once, daemon=True).start()
6042
+ rl.restore(); time.sleep(0.3)
6043
+ cl2 = _socket.create_connection(("127.0.0.1", rl.port), timeout=5); cl2.sendall(b"again"); back2 = cl2.recv(16); cl2.close()
6044
+ rl.stop(); echo.close()
6045
+ ok(back == b"PING" and severed and refused and back2 == b"AGAIN" and rl.stats["cuts"] >= 1 and rl.stats["connections"] == 2 and rl.stats["bytes_to_target"] >= 9,
6046
+ "BN1 the link relay: bytes flow both ways to the target; a cut closes the listener and severs every carried connection (a client's next "
6047
+ "read sees the link gone and a new connection is refused); a restore listens again on the same port and carries a new connection")
6048
+
6049
+ # -- the soak itself, short: two WITS0 patches, the ETP store, the SeedLink station, two outages, the page ----------
6050
+ res = SK.run(seconds=75, patches=2, etp=True, seedlink=True, outage_every_s=22, outage_s=5, interval_s=0.5, with_service=True, out_dir=str(d / "out"),
6051
+ reconnect_budget_s=30, verbose=False)
6052
+ per = {p["protocol"]: p for p in res["per_source"]}
6053
+ rep_md = (d / "out" / "soak_report.md").read_text(encoding="utf-8") if (d / "out" / "soak_report.md").exists() else ""
6054
+ c = {
6055
+ "ran_two_outages": len(res["outages"]) >= 2 and all(o["connections_cut"] >= 4 for o in res["outages"]),
6056
+ "every_source_back": all(o["all_back"] for o in res["outages"]) and res["checks"]["every_outage_recovered"],
6057
+ "down_during": all(set(o["down_during"]) >= {"station"} or len(o["down_during"]) >= 1 for o in res["outages"]),
6058
+ "wits0_loss_counted": per["wits0"]["lost"] is not None and per["wits0"]["lost"] >= 1 and ("no replay" in per["wits0"]["resume"] or "gone" in per["wits0"]["resume"]),
6059
+ "etp_no_replay_named": per["etp"]["lost"] is None and "store" in per["etp"]["resume"] and per["etp"]["received"] > 0,
6060
+ "seedlink_resumed": per["seedlink"]["gaps"] == 0 and per["seedlink"]["lost"] == 0 and per["seedlink"]["reconnects"] >= 1 and "sequence" in per["seedlink"]["resume"],
6061
+ "all_connected_at_end": res["checks"]["all_connected_at_end"] and all(p["status"] == "CONNECTED" for p in res["per_source"]),
6062
+ "page_answered": res["service"]["probes_during_outage"] >= 2 and res["checks"]["page_answered_through_outages"],
6063
+ "verdict_ok": res["ok"] and "OK" in res["verdict"] and "no gap" in res["verdict"],
6064
+ "report_written": res.get("report") and os.path.isfile(res["report"]) and "Outages and recovery" in rep_md and "does not call a measurement" in rep_md
6065
+ and not forbidden_terms(rep_md) and os.path.isfile(d / "out" / "soak.json"),
6066
+ }
6067
+ failed = [k for k, v in c.items() if not v]
6068
+ ok(not failed,
6069
+ "BN2 the soak: two WITS0 patches, the ETP store and the SeedLink station through relays, the link cut twice on schedule - every source "
6070
+ "down during the cut and back, CONNECTED and receiving, within the budget after it; WITS0's loss counted as the protocol's own (no replay), "
6071
+ "the ETP store's new session named, SeedLink resumed by sequence number with no gap; the page answered through the outages; all connected "
6072
+ "at the end; the Soak Report written with the outage table and what it does not call a measurement, clean of internal-register terms"
6073
+ + (f" [failed: {', '.join(failed)}; verdict={res['verdict']!r}; outages={json.dumps(res['outages'], default=str)[:600]}]" if failed else ""))
6074
+
6075
+ # -- the CLI, the page, the help, the guard on plain text -----------------------------------------------------------
6076
+ r_cli = _sp.run([sys.executable, "-m", "gea", "soak", "--seconds", "30", "--patches", "1", "--no-etp", "--no-seedlink", "--outage-every", "10", "--outage-len", "3", "--json"],
6077
+ capture_output=True, text=True)
6078
+ try:
6079
+ cli = json.loads(r_cli.stdout)
6080
+ except ValueError:
6081
+ cli = {}
6082
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
6083
+ help_md = (pkg / "help" / "soak.md").read_text(encoding="utf-8")
6084
+ guard_err = None
6085
+ try:
6086
+ guard_text("a line that calls itself honest", "a test text")
6087
+ except ValueError as e:
6088
+ guard_err = str(e)
6089
+ from .survey_cmd import run_survey
6090
+ txt, _ = run_survey(demo=True)
6091
+ ok(r_cli.returncode == 0 and cli.get("ok") is True and len(cli.get("outages", [])) >= 1 and cli["config"]["patches_wits0"] == 1 and cli["config"]["seedlink"] is False
6092
+ and "data-v=\"soak\"" in page_src and "Soak" in page_src and "172 checks" not in page_src
6093
+ and any(t["topic"] == "soak" for t in H.topics()) and not H.check("soak") and "declared, not observed" in help_md.split("will not call a measurement")[1]
6094
+ and guard_err and "internal-register" in guard_err and not forbidden_terms(txt) and "does not call a measurement" in txt,
6095
+ "BN3 `gea soak --json` runs the schedule and exits 0 on OK; the Verification page carries the soak row and button and no longer names a "
6096
+ "gate count typed by hand; `gea help soak`; `guard_text` is the client-report guard for plain text, and the strata survey's report passes it "
6097
+ "(its closing line and its NOT ESTIMATED section reworded)")
6098
+
5999
6099
  def main() -> int:
6000
6100
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
6001
6101
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -6064,6 +6164,7 @@ def main() -> int:
6064
6164
  section_bk_seedlink(tmp)
6065
6165
  section_bl_backup(tmp)
6066
6166
  section_bm_month_end(tmp)
6167
+ section_bn_soak(tmp)
6067
6168
  if _FAILS:
6068
6169
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
6069
6170
  for f in _FAILS:
@@ -103,6 +103,6 @@ def accuracy_report() -> Dict:
103
103
  'worst_mae_pct': (ok[-1]['mae_pct'] if ok else None),
104
104
  'median_coverage': (statistics.median(cov) if cov else None),
105
105
  'method': ('leave-one-out over the same machinery clients get; '
106
- 'coverage targets ~0.68 (honest 1-sigma); refusals '
106
+ 'coverage targets ~0.68 (a true 1-sigma); refusals '
107
107
  'listed, never hidden; regenerated live so the '
108
108
  'accuracy table can never be a stale snapshot')}
@@ -1784,6 +1784,15 @@ def forbidden_terms(text: str) -> List[str]:
1784
1784
  return [t for t in FORBIDDEN_TERMS if t in low]
1785
1785
 
1786
1786
 
1787
+ def guard_text(text: str, what: str = 'a client deliverable') -> str:
1788
+ """The guard `write` applies to rendered reports, for any other text the program hands a client - a plain-text
1789
+ report, a printed summary: raise if an internal-register term is in it. Returns the text unchanged."""
1790
+ bad = forbidden_terms(text)
1791
+ if bad:
1792
+ raise ValueError(f'{what} contains internal-register terms: {sorted(set(bad))}')
1793
+ return text
1794
+
1795
+
1787
1796
  def write(doc: Document, out_dir: str, basename: str = 'gauge_drift_report') -> dict:
1788
1797
  """Write markdown, HTML, the machine JSON, the records CSV and the tag
1789
1798
  catalogue CSV. Returns the paths. Raises if the rendered report contains
@@ -151,5 +151,5 @@ def depth_frame_pairs(em: EarthModel = None, target_bins: int = TARGET_BINS,
151
151
  'n_refused': len(refused),
152
152
  'finding': ('the library currently supports %d cross-site '
153
153
  'depth-frame correlations; %d candidate pairs '
154
- 'refused thin - the honest census, not a failure'
154
+ 'refused thin - the census as it stands, not a failure'
155
155
  % (len(ok), len(refused)))}
@@ -57,7 +57,7 @@ BROWSER" below. `verify.cmd` runs the full test from the kit.
57
57
 
58
58
  16. In a few seconds a report appears that starts with:
59
59
 
60
- GEA SURVEY - one well, one honest answer
60
+ GEA SURVEY - one well, one answer with its provenance
61
61
 
62
62
  ## PART 5 — YOU'RE DONE. CHECK THESE THREE THINGS:
63
63
 
@@ -65,7 +65,7 @@ BROWSER" below. `verify.cmd` runs the full test from the kit.
65
65
  scientific well, public archive).
66
66
  18. Find the line starting with **CROSS-CHECK**. The program checks its own
67
67
  answer against the well's real measurement — it should say about **+0.7%**.
68
- 19. Find the section **"what this tool refused to guess."** That is on
68
+ 19. Find the section **"what this tool does not call a measurement."** That is on
69
69
  purpose. This program tells you when it doesn't know something instead
70
70
  of making it up.
71
71
 
@@ -0,0 +1,11 @@
1
+ # The soak
2
+
3
+ > The load test says whether the machine carries the site's patches with the page still answering. The soak says what happens to those patches when the link goes down and comes back, again and again, over hours - how long each protocol takes to reconnect, what it loses while the link is down, whether the resume leaves a gap, whether the page keeps answering - with the supervisor's own numbers, not a model's.
4
+
5
+ **The command**: `gea soak --hours 72 --patches 4 --outage-every 600 --outage-len 60 --with-service --out C:\site\reports\soak` runs N WITS0 patches, an ETP patch and a SeedLink live station through link relays on this machine, cuts every relay for the declared seconds on the declared interval, and writes `soak.json` and the Soak Report into `--out`; `--patches` is the site's own patch count. On the dashboard, Verification -> Soak (15 min) runs a short one as a job at the site's patch count with outages of 20 s every 2 min; the 72-hour run is a command-line job for the machine that will serve the site. `gea help soak`.
6
+
7
+ **What it writes**: per outage - when the link was cut, for how long, how many connections it carried, which sources were down during it, and how many seconds after the link came back each source was CONNECTED and receiving again (or NOT BACK within the budget); per source - received, produced, lost (WITS0's frames sent while the link was down, which the protocol does not replay), gaps (SeedLink's, which must be zero: the resume by sequence number is the proof), reconnection attempts, latency p95, and how the protocol resumes; the page's answer time outside and during the outages when `--with-service`; the checks - all connected at the start and at the end, every outage recovered within the budget, no SeedLink gap, the page answering - and the verdict.
8
+
9
+ **The number to check**: `every source back within N s`, with N under the budget; `SeedLink resumed with no gap`; the page p95 during outages under a second; `OK`.
10
+
11
+ **What this page will not call a measurement**: the field link - these are relays on the loopback and the outage lengths were declared, not observed. A protocol's loss recovered - WITS0 and ETP frames sent while the link was down are gone by the protocol's nature; they are counted, never recovered, and the store-and-forward buffer the Data Resilience report models is the answer to that, not this soak. A soak shorter than asked - the summary says how long it ran. A site's real patch count - run it at the number the site will carry.
@@ -50,6 +50,7 @@ INDEX: List[tuple] = [
50
50
  ('backup', 'The backup', 'the site copied off the machine, verified, and restored once before it is trusted'),
51
51
  ('doctor', 'Which code is running', 'gea doctor and the serve start-up gate'),
52
52
  ('audit-update', 'Audit / Update', 'the whole audit log with filters, the program against PyPI, every report against its source'),
53
+ ('soak', 'The soak', 'the patches through link outages for hours: reconnection, resume, loss by protocol, the page answering - measured, not modelled'),
53
54
  ('upkeep', 'Upkeep', 'housekeeping, sessions, the TLS proxy, the load test'),
54
55
  ]
55
56
  # the four lines every page must carry, in this order
@@ -75,7 +75,7 @@ PBKDF2_ROUNDS = 200_000
75
75
  # Commands the page may run as jobs. Anything else is refused (never `serve`, never a shell).
76
76
  RUNNABLE = ('accept', 'fat-sat', 'sbom', 'permits', 'sla-report', 'model-cards', 'client-report', 'dashboard', 'workspace', 'drift-monitor',
77
77
  'well-test', 'alarms', 'notify', 'swaps', 'certificates', 'transient', 'housekeeping', 'store-forward', 'config', 'reconcile', 'ingest', 'opcua', 'mqtt', 'report', 'gamma', 'bench',
78
- 'service-life', 'telemetry', 'run', 'wells', 'survey', 'wits0', 'witsml', 'wits0-sim', 'etp', 'etp-sim', 'files', 'doctor', 'update', 'backup')
78
+ 'service-life', 'telemetry', 'run', 'wells', 'survey', 'wits0', 'witsml', 'wits0-sim', 'etp', 'etp-sim', 'files', 'doctor', 'update', 'backup', 'soak')
79
79
  WEB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web')
80
80
 
81
81
 
@@ -1375,6 +1375,10 @@ class App:
1375
1375
  return self._job(['fat-sat', '--kind', 'SAT', '--out', os.path.join(self.ws.reports_dir)], user, 'SAT protocol')
1376
1376
  if what == 'sbom':
1377
1377
  return self._job(['sbom', '--out', self.ws.reports_dir], user, 'SBOM')
1378
+ if what == 'soak':
1379
+ n = max(1, len(self.patches.store.list())) # the site's own patch count, never fewer than one
1380
+ return self._job(['soak', '--patches', str(n), '--seconds', '900', '--outage-every', '120', '--outage-len', '20', '--with-service',
1381
+ '--out', os.path.join(self.ws.reports_dir, 'soak')], user, f'soak: {n} patch(es) through link outages for 15 min')
1378
1382
  if what == 'standalone':
1379
1383
  tool = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'tools', 'standalone_check.py')
1380
1384
  if not os.path.isfile(tool):