gea-program 0.18.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.18.0 → gea_program-0.20.0}/PKG-INFO +28 -2
  2. {gea_program-0.18.0 → gea_program-0.20.0}/README.md +27 -1
  3. {gea_program-0.18.0 → gea_program-0.20.0}/gea/__init__.py +1 -1
  4. {gea_program-0.18.0 → gea_program-0.20.0}/gea/__main__.py +30 -2
  5. {gea_program-0.18.0 → gea_program-0.20.0}/gea/acceptance_tests.py +236 -10
  6. {gea_program-0.18.0 → gea_program-0.20.0}/gea/blind_harness.py +1 -1
  7. {gea_program-0.18.0 → gea_program-0.20.0}/gea/client_reports.py +9 -0
  8. {gea_program-0.18.0 → gea_program-0.20.0}/gea/correlation.py +1 -1
  9. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/TESTER_GUIDE.md +2 -2
  10. gea_program-0.20.0/gea/help/month-end.md +11 -0
  11. gea_program-0.20.0/gea/help/soak.md +11 -0
  12. {gea_program-0.18.0 → gea_program-0.20.0}/gea/helplib.py +2 -0
  13. gea_program-0.20.0/gea/month_end.py +290 -0
  14. {gea_program-0.18.0 → gea_program-0.20.0}/gea/service.py +32 -1
  15. gea_program-0.20.0/gea/soak.py +403 -0
  16. {gea_program-0.18.0 → gea_program-0.20.0}/gea/survey_cmd.py +12 -10
  17. {gea_program-0.18.0 → gea_program-0.20.0}/gea/web/app.html +8 -2
  18. {gea_program-0.18.0 → gea_program-0.20.0}/gea/wits0.py +45 -27
  19. {gea_program-0.18.0 → gea_program-0.20.0}/gea/workspace.py +8 -0
  20. {gea_program-0.18.0 → gea_program-0.20.0}/gea_program.egg-info/PKG-INFO +28 -2
  21. {gea_program-0.18.0 → gea_program-0.20.0}/gea_program.egg-info/SOURCES.txt +4 -0
  22. {gea_program-0.18.0 → gea_program-0.20.0}/pyproject.toml +1 -1
  23. {gea_program-0.18.0 → gea_program-0.20.0}/LICENSE +0 -0
  24. {gea_program-0.18.0 → gea_program-0.20.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  25. {gea_program-0.18.0 → gea_program-0.20.0}/gea/accuracy_statement.py +0 -0
  26. {gea_program-0.18.0 → gea_program-0.20.0}/gea/alarm_engine.py +0 -0
  27. {gea_program-0.18.0 → gea_program-0.20.0}/gea/backup.py +0 -0
  28. {gea_program-0.18.0 → gea_program-0.20.0}/gea/bench.py +0 -0
  29. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  30. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  31. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  32. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  33. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  34. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  35. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  36. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  37. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  38. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  39. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  40. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  41. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  42. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  43. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  44. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  45. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  46. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  47. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  48. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  49. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  50. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  51. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  52. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  53. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  54. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  55. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  56. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  57. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  58. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  59. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  60. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  61. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  62. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  63. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  64. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  65. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  66. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  67. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  68. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  69. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  70. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  71. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  72. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  73. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  74. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  75. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  76. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  77. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  78. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  79. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  80. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  81. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  82. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  83. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  84. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  85. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  86. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  87. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  88. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  89. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  90. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  91. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  92. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  93. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  94. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  95. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  96. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/med_160_sapropels.txt +0 -0
  97. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  98. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  99. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  100. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  101. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  102. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  103. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  104. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  105. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  106. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  107. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  108. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  109. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  110. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  111. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  112. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  113. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  114. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  115. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  116. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  117. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  118. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  119. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  120. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  121. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  122. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  123. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  124. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  125. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  126. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  127. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  128. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  129. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  130. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  131. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  132. {gea_program-0.18.0 → gea_program-0.20.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  133. {gea_program-0.18.0 → gea_program-0.20.0}/gea/certificates.py +0 -0
  134. {gea_program-0.18.0 → gea_program-0.20.0}/gea/cli.py +0 -0
  135. {gea_program-0.18.0 → gea_program-0.20.0}/gea/config_versioning.py +0 -0
  136. {gea_program-0.18.0 → gea_program-0.20.0}/gea/conformance.py +0 -0
  137. {gea_program-0.18.0 → gea_program-0.20.0}/gea/dashboard.py +0 -0
  138. {gea_program-0.18.0 → gea_program-0.20.0}/gea/deviation.py +0 -0
  139. {gea_program-0.18.0 → gea_program-0.20.0}/gea/doctor.py +0 -0
  140. {gea_program-0.18.0 → gea_program-0.20.0}/gea/downhole_engine.py +0 -0
  141. {gea_program-0.18.0 → gea_program-0.20.0}/gea/drift_monitor.py +0 -0
  142. {gea_program-0.18.0 → gea_program-0.20.0}/gea/earth_model.py +0 -0
  143. {gea_program-0.18.0 → gea_program-0.20.0}/gea/etp.py +0 -0
  144. {gea_program-0.18.0 → gea_program-0.20.0}/gea/etp12_schemas.json +0 -0
  145. {gea_program-0.18.0 → gea_program-0.20.0}/gea/example_register_map.json +0 -0
  146. {gea_program-0.18.0 → gea_program-0.20.0}/gea/fat_sat.py +0 -0
  147. {gea_program-0.18.0 → gea_program-0.20.0}/gea/files.py +0 -0
  148. {gea_program-0.18.0 → gea_program-0.20.0}/gea/follower.py +0 -0
  149. {gea_program-0.18.0 → gea_program-0.20.0}/gea/forward_model.py +0 -0
  150. {gea_program-0.18.0 → gea_program-0.20.0}/gea/gamma.py +0 -0
  151. {gea_program-0.18.0 → gea_program-0.20.0}/gea/gauge_aging.py +0 -0
  152. {gea_program-0.18.0 → gea_program-0.20.0}/gea/gauge_specs.py +0 -0
  153. {gea_program-0.18.0 → gea_program-0.20.0}/gea/geodesy.py +0 -0
  154. {gea_program-0.18.0 → gea_program-0.20.0}/gea/gravity_reference.py +0 -0
  155. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/alarms.md +0 -0
  156. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/audit-update.md +0 -0
  157. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/backup.md +0 -0
  158. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/conformance.md +0 -0
  159. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/data-in.md +0 -0
  160. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/doctor.md +0 -0
  161. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/drift.md +0 -0
  162. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/etp.md +0 -0
  163. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/files.md +0 -0
  164. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/instruments.md +0 -0
  165. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/notifications.md +0 -0
  166. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/osdu.md +0 -0
  167. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/patches.md +0 -0
  168. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/ppdm.md +0 -0
  169. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/quakeml.md +0 -0
  170. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/quality.md +0 -0
  171. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/seedlink.md +0 -0
  172. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/seismic.md +0 -0
  173. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/site.md +0 -0
  174. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/sites.md +0 -0
  175. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/sra.md +0 -0
  176. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/start.md +0 -0
  177. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/transients.md +0 -0
  178. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/upkeep.md +0 -0
  179. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/vibration.md +0 -0
  180. {gea_program-0.18.0 → gea_program-0.20.0}/gea/help/well-tests.md +0 -0
  181. {gea_program-0.18.0 → gea_program-0.20.0}/gea/housekeeping.py +0 -0
  182. {gea_program-0.18.0 → gea_program-0.20.0}/gea/inverse_engine.py +0 -0
  183. {gea_program-0.18.0 → gea_program-0.20.0}/gea/jobs.py +0 -0
  184. {gea_program-0.18.0 → gea_program-0.20.0}/gea/live_ports.py +0 -0
  185. {gea_program-0.18.0 → gea_program-0.20.0}/gea/loadtest.py +0 -0
  186. {gea_program-0.18.0 → gea_program-0.20.0}/gea/matplotlib_demo.py +0 -0
  187. {gea_program-0.18.0 → gea_program-0.20.0}/gea/modbus.py +0 -0
  188. {gea_program-0.18.0 → gea_program-0.20.0}/gea/model_card.py +0 -0
  189. {gea_program-0.18.0 → gea_program-0.20.0}/gea/mqtt_port.py +0 -0
  190. {gea_program-0.18.0 → gea_program-0.20.0}/gea/notify.py +0 -0
  191. {gea_program-0.18.0 → gea_program-0.20.0}/gea/opcua_port.py +0 -0
  192. {gea_program-0.18.0 → gea_program-0.20.0}/gea/operator_app.py +0 -0
  193. {gea_program-0.18.0 → gea_program-0.20.0}/gea/osdu.py +0 -0
  194. {gea_program-0.18.0 → gea_program-0.20.0}/gea/patches.py +0 -0
  195. {gea_program-0.18.0 → gea_program-0.20.0}/gea/permits.py +0 -0
  196. {gea_program-0.18.0 → gea_program-0.20.0}/gea/ports.py +0 -0
  197. {gea_program-0.18.0 → gea_program-0.20.0}/gea/ppdm.py +0 -0
  198. {gea_program-0.18.0 → gea_program-0.20.0}/gea/profile_catalog.py +0 -0
  199. {gea_program-0.18.0 → gea_program-0.20.0}/gea/project.py +0 -0
  200. {gea_program-0.18.0 → gea_program-0.20.0}/gea/qt6_downhole_app.py +0 -0
  201. {gea_program-0.18.0 → gea_program-0.20.0}/gea/quakeml.py +0 -0
  202. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reconciler.py +0 -0
  203. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  204. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  205. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  206. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  207. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  208. {gea_program-0.18.0 → gea_program-0.20.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  209. {gea_program-0.18.0 → gea_program-0.20.0}/gea/rock_inventory.py +0 -0
  210. {gea_program-0.18.0 → gea_program-0.20.0}/gea/sample_record.py +0 -0
  211. {gea_program-0.18.0 → gea_program-0.20.0}/gea/sample_well_profile.csv +0 -0
  212. {gea_program-0.18.0 → gea_program-0.20.0}/gea/sbom.py +0 -0
  213. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seedlink.py +0 -0
  214. {gea_program-0.18.0 → gea_program-0.20.0}/gea/segy.py +0 -0
  215. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic.py +0 -0
  216. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_array.py +0 -0
  217. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_assoc.py +0 -0
  218. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_detect.py +0 -0
  219. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_film.py +0 -0
  220. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_harmonic.py +0 -0
  221. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_qc.py +0 -0
  222. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_response.py +0 -0
  223. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_signature.py +0 -0
  224. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_track.py +0 -0
  225. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_uncertainty.py +0 -0
  226. {gea_program-0.18.0 → gea_program-0.20.0}/gea/seismic_unlisted.py +0 -0
  227. {gea_program-0.18.0 → gea_program-0.20.0}/gea/sensor_swap.py +0 -0
  228. {gea_program-0.18.0 → gea_program-0.20.0}/gea/service_life.py +0 -0
  229. {gea_program-0.18.0 → gea_program-0.20.0}/gea/shell.py +0 -0
  230. {gea_program-0.18.0 → gea_program-0.20.0}/gea/shut_in.py +0 -0
  231. {gea_program-0.18.0 → gea_program-0.20.0}/gea/sla_report.py +0 -0
  232. {gea_program-0.18.0 → gea_program-0.20.0}/gea/sra.py +0 -0
  233. {gea_program-0.18.0 → gea_program-0.20.0}/gea/store_forward.py +0 -0
  234. {gea_program-0.18.0 → gea_program-0.20.0}/gea/strata_join.py +0 -0
  235. {gea_program-0.18.0 → gea_program-0.20.0}/gea/supervisor.py +0 -0
  236. {gea_program-0.18.0 → gea_program-0.20.0}/gea/survey_view.py +0 -0
  237. {gea_program-0.18.0 → gea_program-0.20.0}/gea/telemetry.py +0 -0
  238. {gea_program-0.18.0 → gea_program-0.20.0}/gea/tool_library.py +0 -0
  239. {gea_program-0.18.0 → gea_program-0.20.0}/gea/transient.py +0 -0
  240. {gea_program-0.18.0 → gea_program-0.20.0}/gea/vibration.py +0 -0
  241. {gea_program-0.18.0 → gea_program-0.20.0}/gea/well_assembler.py +0 -0
  242. {gea_program-0.18.0 → gea_program-0.20.0}/gea/well_test_validation.py +0 -0
  243. {gea_program-0.18.0 → gea_program-0.20.0}/gea/witsml.py +0 -0
  244. {gea_program-0.18.0 → gea_program-0.20.0}/gea_program.egg-info/dependency_links.txt +0 -0
  245. {gea_program-0.18.0 → gea_program-0.20.0}/gea_program.egg-info/entry_points.txt +0 -0
  246. {gea_program-0.18.0 → gea_program-0.20.0}/gea_program.egg-info/requires.txt +0 -0
  247. {gea_program-0.18.0 → gea_program-0.20.0}/gea_program.egg-info/top_level.txt +0 -0
  248. {gea_program-0.18.0 → gea_program-0.20.0}/setup.cfg +0 -0
  249. {gea_program-0.18.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.18.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 (342 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]")
@@ -710,6 +710,24 @@ after the report was written - and "Update every report from its source"
710
710
  dashboard for every well, then every seismic station, errors collected and
711
711
  listed, never hidden.
712
712
 
713
+ ### The month-end packet
714
+
715
+ A client's month was five or six commands and a walk through the reports
716
+ folder. `gea workspace --path C:\site --action month-end --period 2026-09`
717
+ (or `--period previous`) runs the refresh for the period and collects every
718
+ report the program writes - each well's drift report, alarm month, well-test
719
+ validation, shut-in report, data resilience, machine vibration and the
720
+ certificate register's ISO/IEC 17025 7.8 conformity; each site's SRA packet
721
+ for the period and site report; every seismic station and track; the
722
+ accuracy statement, the SLA month, the FAT/SAT protocol, the model cards and
723
+ the SBOM - into `reports/month_end/<period>/` with `INDEX` (html, md, json:
724
+ every item INCLUDED with its files or NOT AVAILABLE with the reason that
725
+ names what the site lacks; every failed step; OPEN for a month that has not
726
+ ended) and `MANIFEST.json` (every file's SHA-256), zipped beside it. Nothing
727
+ is recomputed in the index. On the Reports page: the packets on record,
728
+ Assemble as a job, and Schedule monthly (day and hour, the previous month).
729
+ `gea help month-end`.
730
+
713
731
  ### Before a site goes live
714
732
 
715
733
  `deploy/README.md` is the checklist: keep the service on the loopback
@@ -722,6 +740,14 @@ headers on every response, and `gea housekeeping --workspace C:\site --apply`
722
740
  than truncating them and prunes finished job folders and old recordings. `gea
723
741
  loadtest --patches 16 --seconds 60 --with-service` tells you whether the site
724
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`.
725
751
  `gea backup --workspace C:\site --out D:\gea-backups --keep 14` copies the
726
752
  whole site into one dated archive with a manifest of every file's hash, onto
727
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 (342 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]")
@@ -671,6 +671,24 @@ after the report was written - and "Update every report from its source"
671
671
  dashboard for every well, then every seismic station, errors collected and
672
672
  listed, never hidden.
673
673
 
674
+ ### The month-end packet
675
+
676
+ A client's month was five or six commands and a walk through the reports
677
+ folder. `gea workspace --path C:\site --action month-end --period 2026-09`
678
+ (or `--period previous`) runs the refresh for the period and collects every
679
+ report the program writes - each well's drift report, alarm month, well-test
680
+ validation, shut-in report, data resilience, machine vibration and the
681
+ certificate register's ISO/IEC 17025 7.8 conformity; each site's SRA packet
682
+ for the period and site report; every seismic station and track; the
683
+ accuracy statement, the SLA month, the FAT/SAT protocol, the model cards and
684
+ the SBOM - into `reports/month_end/<period>/` with `INDEX` (html, md, json:
685
+ every item INCLUDED with its files or NOT AVAILABLE with the reason that
686
+ names what the site lacks; every failed step; OPEN for a month that has not
687
+ ended) and `MANIFEST.json` (every file's SHA-256), zipped beside it. Nothing
688
+ is recomputed in the index. On the Reports page: the packets on record,
689
+ Assemble as a job, and Schedule monthly (day and hour, the previous month).
690
+ `gea help month-end`.
691
+
674
692
  ### Before a site goes live
675
693
 
676
694
  `deploy/README.md` is the checklist: keep the service on the loopback
@@ -683,6 +701,14 @@ headers on every response, and `gea housekeeping --workspace C:\site --apply`
683
701
  than truncating them and prunes finished job folders and old recordings. `gea
684
702
  loadtest --patches 16 --seconds 60 --with-service` tells you whether the site
685
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`.
686
712
  `gea backup --workspace C:\site --out D:\gea-backups --keep 14` copies the
687
713
  whole site into one dated archive with a manifest of every file's hash, onto
688
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.18.0"
126
+ __version__ = "0.20.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -285,7 +285,7 @@ def main(argv=None) -> int:
285
285
  "add-seismic", "refresh-seismic", "remove-seismic", "sar-film", "refresh-all", "add-track", "refresh-track", "remove-track",
286
286
  "seismic-dataset", "seismic-field", "add-site", "sites", "site-report", "remove-site", "rename",
287
287
  "set-well", "sra-define", "sra-report", "associate", "osdu-export", "quakeml-export", "well-identity", "vibration-report",
288
- "add-live-station", "live-fold", "live-run"])
288
+ "add-live-station", "live-fold", "live-run", "month-end"])
289
289
  p_ws.add_argument("--stations", type=str, nargs="+", default=None, help="add-track: two or more array station ids of this workspace")
290
290
  p_ws.add_argument("--site", type=str, default=None, help="site-report/remove-site: the site id")
291
291
  p_ws.add_argument("--api", type=str, default=None, help="set-well: the API number")
@@ -307,7 +307,7 @@ def main(argv=None) -> int:
307
307
  p_ws.add_argument("--checkpoint-months", type=int, default=3, help="sra-define: months between checkpoints with Commission staff (3)")
308
308
  p_ws.add_argument("--formation-boundary", type=str, default="base of the Wolfcamp", help="sra-define: the stratigraphic boundary between the two tiers")
309
309
  p_ws.add_argument("--triggering-event", type=str, default="", help="sra-define: the catalogue id of the event the plan answers")
310
- p_ws.add_argument("--catalog", type=str, default=None, help="sra-report: a TexNet earthquake-catalogue export (CSV)")
310
+ p_ws.add_argument("--catalog", type=str, default=None, help="sra-report / month-end: a TexNet earthquake-catalogue export (CSV)")
311
311
  p_ws.add_argument("--aftershocks", type=str, nargs="*", default=None, help="sra-report: catalogue ids the operator declares as aftershocks (exempt)")
312
312
  p_ws.add_argument("--start", type=str, default=None, help="sra-report: first day of the record, YYYY-MM-DD")
313
313
  p_ws.add_argument("--end", type=str, default=None, help="sra-report: last day of the record, YYYY-MM-DD")
@@ -359,6 +359,8 @@ def main(argv=None) -> int:
359
359
  p_ws.add_argument("--seedlink-config", type=str, default=None, help="add-live-station: the SeedLink config JSON (gea seedlink --write-example-config)")
360
360
  p_ws.add_argument("--include-open", action="store_true", help="live-fold: fold the current day's files too (they are still being written)")
361
361
  p_ws.add_argument("--seconds", type=float, default=None, help="live-run: how long to run (default: until Ctrl+C)")
362
+ p_ws.add_argument("--period", type=str, default="previous", help="month-end: the month, YYYY-MM, or 'previous' (the last closed month)")
363
+ p_ws.add_argument("--no-refresh", action="store_true", help="month-end: collect the reports as they stand without refreshing first")
362
364
  p_ws.add_argument("--name", type=str, default=None, help="init: site name; add-*: display name")
363
365
  p_ws.add_argument("--file", type=str, default=None, help="add-file: the client's data file; add-live: the port configuration JSON")
364
366
  p_ws.add_argument("--entry", type=str, default=None, help="add-catalog: catalogue entry")
@@ -390,6 +392,19 @@ def main(argv=None) -> int:
390
392
  p_hk.add_argument("--actor", type=str, default="housekeeping")
391
393
  p_hk.add_argument("--json", action="store_true")
392
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")
393
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")
394
409
  p_lt.add_argument("--patches", type=int, default=8)
395
410
  p_lt.add_argument("--seconds", type=float, default=30.0)
@@ -913,6 +928,15 @@ def main(argv=None) -> int:
913
928
  for x in r['records_removed'][:20]:
914
929
  print(f" record {x['well_id']}/{x['file']} {x['age_days']} d")
915
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
916
940
  elif a.cmd == "loadtest":
917
941
  import json as _json
918
942
  from .loadtest import run as _lt
@@ -1944,6 +1968,10 @@ def main(argv=None) -> int:
1944
1968
  raise SystemExit("well-identity needs --well <id>")
1945
1969
  r = ws.well_identity(a.well)
1946
1970
  print(_ppdm_text(r)); return 0
1971
+ elif a.action == "month-end":
1972
+ from .month_end import report_text as _me_text
1973
+ r = ws.month_end(a.period, actor=a.actor, refresh=not a.no_refresh, catalog_csv=a.catalog)
1974
+ print(_me_text(r)); return 0 if not r["errors"] else 1
1947
1975
  elif a.action == "add-live-station":
1948
1976
  if not a.seedlink_config or not a.name:
1949
1977
  raise SystemExit("add-live-station needs --name <display> and --seedlink-config <json>")
@@ -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
 
@@ -5872,6 +5873,229 @@ def section_bl_backup(tmp: str) -> None:
5872
5873
  "`--verify` says OK or names the tampered file, `--restore` brings the accounts back; the help page says a backup never restored is a hope"
5873
5874
  + (f" [failed: {', '.join(failed2)}; r_st0={r_st0.stdout[-120:]!r} {r_st0.stderr[-120:]!r}]" if failed2 else ""))
5874
5875
 
5876
+
5877
+ def section_bm_month_end(tmp: str) -> None:
5878
+ """Section BM - the month-end packet: the refresh for the period, every report collected with INCLUDED or NOT
5879
+ AVAILABLE and the reason, the index and the manifest, the archive, the records; the page, the job and the
5880
+ monthly schedule."""
5881
+ import http.cookiejar
5882
+ import subprocess as _sp
5883
+ import urllib.request
5884
+ import urllib.error
5885
+ import zipfile
5886
+ from . import month_end as ME
5887
+ from . import helplib as H
5888
+ from .telemetry import TelemetryRecorder, TelemetryConfig
5889
+ from .downhole_engine import DownholeEngine, SimulatorConfig
5890
+ from .workspace import Workspace
5891
+ from .service import Service, Users
5892
+ pkg = Path(__file__).parent
5893
+ d = Path(tmp, "bm"); d.mkdir()
5894
+ hist = str(d / "hist.csv")
5895
+ TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=2.0, seed=5)).run().export_csv(hist)
5896
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
5897
+ w = ws.add_well_file(hist, display="SWD 1", actor="tester")
5898
+ site = ws.add_site("Pad 3 site", wells=[w["id"]], actor="tester")
5899
+ bad_period = None
5900
+ try:
5901
+ ME.period_bounds("2026-9")
5902
+ except ValueError as e:
5903
+ bad_period = str(e)
5904
+ pb = ME.period_bounds("2026-02")
5905
+ rec = ME.assemble(ws, "2026-09", actor="tester")
5906
+ idx = json.loads((Path(rec["folder"]) / "INDEX.json").read_text(encoding="utf-8"))
5907
+ man = json.loads((Path(rec["folder"]) / "MANIFEST.json").read_text(encoding="utf-8"))
5908
+ by_name = {i["name"]: i for i in idx["items"]}
5909
+ v_folder = ME.verify(rec["folder"]); v_zip = ME.verify(rec["archive"])
5910
+ with zipfile.ZipFile(rec["archive"]) as z:
5911
+ znames = set(z.namelist())
5912
+ # a second assembly of the same period rebuilds whole; a future month is OPEN
5913
+ rec2 = ME.assemble(ws, "2026-09", actor="tester", refresh=False)
5914
+ from datetime import datetime as _dt, timezone as _tz
5915
+ now = _dt.now(_tz.utc)
5916
+ cur = f"{now.year:04d}-{now.month:02d}"
5917
+ rec3 = ME.assemble(ws, cur, actor="tester", refresh=False)
5918
+ pk = ME.packets(ws)
5919
+ audit = [a["action"] for a in ws.audit_log()]
5920
+ c1 = {
5921
+ "period_checked": bool(bad_period) and "YYYY-MM" in bad_period and pb["days"] == 28 and pb["start"] == "2026-02-01T00:00:00Z" and pb["end"] == "2026-02-28T23:59:59Z" and pb["closed"] is True,
5922
+ "included": by_name["SWD 1: gauge drift report"]["status"] == "INCLUDED" and by_name["SWD 1: alarm event report"]["status"] == "INCLUDED"
5923
+ and by_name["SLA month 2026-09"]["status"] == "INCLUDED" and by_name["Accuracy statement"]["status"] == "INCLUDED"
5924
+ and by_name["Pad 3 site: site report"]["status"] == "INCLUDED" and by_name["Model cards and SBOM"]["status"] == "INCLUDED",
5925
+ "not_available_with_reason": by_name["SWD 1: machine vibration"]["status"] == "NOT AVAILABLE" and "no machine record" in by_name["SWD 1: machine vibration"]["reason"]
5926
+ and by_name["Pad 3 site: SRA packet for 2026-09"]["status"] == "NOT AVAILABLE" and "no Seismicity Response Area" in by_name["Pad 3 site: SRA packet for 2026-09"]["reason"]
5927
+ and "no certificate register" in by_name["SWD 1: certificate conformity (ISO/IEC 17025 7.8)"]["reason"],
5928
+ "files_hashed": all(f["sha256"] and f["bytes"] > 0 for i in idx["items"] for f in i["files"]) and man["n_files"] == len(man["files"]) and man["n_files"] >= 20
5929
+ and "wells/SWD-1/gauge_drift_report.html" in man["files"] and "site/sla_report_2026-09.html" in man["files"] and "INDEX.html" in man["files"],
5930
+ "record": rec["included"] == len([i for i in idx["items"] if i["status"] == "INCLUDED"]) and rec["closed"] is True and rec["errors"] == [] and os.path.isfile(rec["archive"])
5931
+ and os.path.isfile(rec["archive"] + "") and rec["archive"].endswith("month_end_Pad-3_2026-09.zip"),
5932
+ "verify": v_folder["status"] == "OK" and v_zip["status"] == "OK" and v_zip["n_files"] == man["n_files"] and "MANIFEST.json" in znames and "INDEX.html" in znames,
5933
+ "rebuilt_whole_and_open": rec2["generated_utc"] >= rec["generated_utc"] and rec2["refresh"] is False and rec3["closed"] is False and "OPEN" in (Path(rec3["folder"]) / "INDEX.md").read_text(encoding="utf-8")
5934
+ and len(pk) == 3 and all(p["present"] for p in pk) and pk[-1]["period"] == cur,
5935
+ "audited": audit.count("month_end.assemble") == 3 and "dashboard.refresh" in audit and "site.report" in audit,
5936
+ "not_a_measurement": "does not call a measurement" in (Path(rec["folder"]) / "INDEX.md").read_text(encoding="utf-8"),
5937
+ }
5938
+ failed1 = [k for k, v in c1.items() if not v]
5939
+ ok(not failed1,
5940
+ "BM1 the month-end packet: the period checked (YYYY-MM; February's 28 days; a past month closed), the refresh run for it, every report "
5941
+ "collected - the well's drift and alarm reports, the SLA month, the accuracy statement, the site report, the model cards - INCLUDED with "
5942
+ "hashed files, and the vibration report, the SRA packet and the certificate conformity NOT AVAILABLE with the reason that names what the "
5943
+ "site lacks; INDEX and MANIFEST written, the archive verifies against the manifest, a second assembly rebuilds the period whole, the "
5944
+ "current month is marked OPEN, every packet on record, every assembly in the audit"
5945
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
5946
+
5947
+ # -- the page, the job, the schedule, the CLI, the help --------------------------------------------------------
5948
+ users = Users(str(Path(wsp, "users.json"))); users.add("op", "operator-pass-1", "operator"); users.add("adm", "admin-pass-1", "admin")
5949
+ svc = Service(wsp, port=0, scheduler=False).start()
5950
+ base = svc.url.rstrip("/")
5951
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
5952
+
5953
+ def call(method, path, body=None):
5954
+ req = urllib.request.Request(base + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
5955
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
5956
+ try:
5957
+ with opener.open(req, timeout=300) as rr_:
5958
+ return rr_.status, json.loads(rr_.read())
5959
+ except urllib.error.HTTPError as e:
5960
+ return e.code, json.loads(e.read())
5961
+ try:
5962
+ call("POST", "/api/login", {"name": "op", "password": "operator-pass-1"})
5963
+ st_l, lst = call("GET", "/api/month-end")
5964
+ st_bad = call("POST", "/api/month-end/run", {"period": "Sept 2026"})[0]
5965
+ st_sched_op = call("POST", "/api/month-end/schedule", {"day": 1, "at": "03:00"})[0]
5966
+ st_run, job = call("POST", "/api/month-end/run", {"period": "2026-08", "no_refresh": True})
5967
+ done = svc.app.runner.wait(job["id"], 300)
5968
+ st_l2, lst2 = call("GET", "/api/month-end")
5969
+ req = urllib.request.Request(base + "/reports/month_end/2026-08/INDEX.html"); req.add_header("X-GEA-Action", "1")
5970
+ with opener.open(req, timeout=60) as rr_:
5971
+ html_ok = rr_.status == 200 and b"Month-End Packet" in rr_.read()
5972
+ call("POST", "/api/login", {"name": "adm", "password": "admin-pass-1"})
5973
+ st_sched_bad = call("POST", "/api/month-end/schedule", {"day": 31, "at": "03:00"})[0]
5974
+ st_sched, sch = call("POST", "/api/month-end/schedule", {"day": 2, "at": "03:15"})
5975
+ st_l3, lst3 = call("GET", "/api/month-end")
5976
+ finally:
5977
+ svc.stop()
5978
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
5979
+ help_md = (pkg / "help" / "month-end.md").read_text(encoding="utf-8")
5980
+ r_cli = _sp.run([sys.executable, "-m", "gea", "workspace", "--path", wsp, "--action", "month-end", "--period", "2026-07", "--no-refresh"], capture_output=True, text=True)
5981
+ r_prev = _sp.run([sys.executable, "-m", "gea", "workspace", "--path", wsp, "--action", "month-end", "--no-refresh"], capture_output=True, text=True)
5982
+ c2 = {
5983
+ "list": st_l == 200 and len(lst["packets"]) == 3 and lst["previous"] == ME.previous_period() and lst["schedule"] is None,
5984
+ "refusals": st_bad == 400 and st_sched_op == 403 and st_sched_bad == 400,
5985
+ "job": st_run == 200 and done["status"] == "DONE" and st_l2 == 200 and any(p["period"] == "2026-08" and p["present"] for p in lst2["packets"]) and html_ok,
5986
+ "schedule": st_sched == 200 and sch["name"] == "month-end-monthly" and sch["monthly_day"] == 2 and sch["daily_at"] == "03:15" and "previous" in sch["args"]
5987
+ and st_l3 == 200 and lst3["schedule"]["name"] == "month-end-monthly",
5988
+ "page": "Month-end packet" in page_src and "/api/month-end/run" in page_src and "/api/month-end/schedule" in page_src,
5989
+ "cli": r_cli.returncode == 0 and "month-end 2026-07 (closed)" in r_cli.stdout and r_prev.returncode == 0 and f"month-end {ME.previous_period()} (closed)" in r_prev.stdout,
5990
+ "help": any(t["topic"] == "month-end" for t in H.topics()) and not H.check("month-end") and "does not recompute" in help_md.split("will not call a measurement")[1],
5991
+ }
5992
+ failed2 = [k for k, v in c2.items() if not v]
5993
+ ok(not failed2,
5994
+ "BM2 the Reports page carries the packet card: the packets on record with their index and archive, the period defaulting to the last closed "
5995
+ "month, Assemble as a job (a malformed period refused) whose INDEX the page then serves, Schedule monthly for an administrator only (day 1..28) "
5996
+ "as the `month-end-monthly` entry for the previous month; `gea workspace --action month-end` with a period or with 'previous'; the help page "
5997
+ "says the index collects and does not recompute"
5998
+ + (f" [failed: {', '.join(failed2)}; cli={r_cli.stdout[-200:]!r} {r_cli.stderr[-200:]!r}]" if failed2 else ""))
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
+
5875
6099
  def main() -> int:
5876
6100
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
5877
6101
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -5939,6 +6163,8 @@ def main() -> int:
5939
6163
  section_bj_etp(tmp)
5940
6164
  section_bk_seedlink(tmp)
5941
6165
  section_bl_backup(tmp)
6166
+ section_bm_month_end(tmp)
6167
+ section_bn_soak(tmp)
5942
6168
  if _FAILS:
5943
6169
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
5944
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 month-end packet
2
+
3
+ > A client's month was five or six commands and a walk through the reports folder. The packet is that month assembled once: the refresh for the period, then every report the program writes for every well, site, station and track, collected into one dated folder and one archive, with an index that says item by item what is there and what is not, and a manifest of every file's hash.
4
+
5
+ **The command**: `gea workspace --path C:\site --action month-end --period 2026-09` (or `--period previous`, the last closed month; `--no-refresh` collects the reports as they stand; `--catalog` hands the regulator's catalogue CSV to the SRA packet). On the dashboard: Reports -> Month-end packet -> the period and Assemble (a job), and for an administrator Schedule monthly (a schedule entry, `month-end-monthly`, on the day and hour given, for the previous month). The result is `reports/month_end/<period>/` with `INDEX.html` / `.md` / `.json` and `MANIFEST.json`, and `reports/month_end/month_end_<site>_<period>.zip`; each packet is logged in `records/month_end.jsonl` and in the audit (`month_end.assemble`).
6
+
7
+ **What it writes**: for the site - the dashboard index, the accuracy statement, the SLA month for that period, the FAT/SAT protocol, the model cards and the SBOM; for every well - the gauge drift report, the alarm event report, the well-test validation, the shut-in / build-up report, the data resilience report, the machine vibration report, and the certificate register's conformity against ISO/IEC 17025 7.8 as it stands; for every site - the SRA packet for the period (when a Seismicity Response Area is defined) and the site report; for every seismic station and track - their reports. Each item is INCLUDED with its files or NOT AVAILABLE with the reason (no machine record, no SRA defined, no certificate register, no well test detected, never refreshed); every step that failed is listed; a month that has not ended is marked OPEN.
8
+
9
+ **The number to check**: `N INCLUDED, M NOT AVAILABLE, 0 FAILED` on the index's front page, and `closed: yes` for a month that has ended.
10
+
11
+ **What this page will not call a measurement**: an item that is NOT AVAILABLE - the reason names what the site lacks, and the packet never stands in for it. A number in any of the reports - each carries its own method and declarations; the index collects and does not recompute. A month that has not ended - the packet says OPEN and the one assembled after the month closes supersedes it. A packet whose files no longer match its manifest - `MANIFEST.json` is the check, and a packet is rebuilt whole, never merged.
@@ -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.
@@ -46,9 +46,11 @@ INDEX: List[tuple] = [
46
46
  ('conformance', 'Conformity, in the standards\' words', 'certificates against ISO/IEC 17025 7.8 and the bias against its class under a named ILAC-G8 decision rule'),
47
47
  ('etp', 'WITSML 2.x over ETP', 'the live port on Energistics ETP v1.2: subscribe to the store\'s channels and it pushes'),
48
48
  ('seedlink', 'Live stations (SeedLink)', 'the seismic leg\'s live port: a station\'s records as it writes them, resumed by sequence number, folded into its record list'),
49
+ ('month-end', 'The month-end packet', 'the month\'s deliverable assembled once: every report collected, INCLUDED or NOT AVAILABLE with the reason, hashed'),
49
50
  ('backup', 'The backup', 'the site copied off the machine, verified, and restored once before it is trusted'),
50
51
  ('doctor', 'Which code is running', 'gea doctor and the serve start-up gate'),
51
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'),
52
54
  ('upkeep', 'Upkeep', 'housekeeping, sessions, the TLS proxy, the load test'),
53
55
  ]
54
56
  # the four lines every page must carry, in this order