gea-program 0.34.0__tar.gz → 0.35.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 (289) hide show
  1. {gea_program-0.34.0/gea_program.egg-info → gea_program-0.35.0}/PKG-INFO +18 -1
  2. {gea_program-0.34.0 → gea_program-0.35.0}/README.md +17 -0
  3. {gea_program-0.34.0 → gea_program-0.35.0}/gea/__init__.py +1 -1
  4. {gea_program-0.34.0 → gea_program-0.35.0}/gea/__main__.py +60 -0
  5. {gea_program-0.34.0 → gea_program-0.35.0}/gea/acceptance_tests.py +166 -0
  6. gea_program-0.35.0/gea/control_room.py +509 -0
  7. gea_program-0.35.0/gea/help/control-room.md +11 -0
  8. {gea_program-0.34.0 → gea_program-0.35.0}/gea/helplib.py +2 -1
  9. {gea_program-0.34.0 → gea_program-0.35.0}/gea/service.py +97 -4
  10. {gea_program-0.34.0 → gea_program-0.35.0}/gea/web/app.html +181 -5
  11. {gea_program-0.34.0 → gea_program-0.35.0/gea_program.egg-info}/PKG-INFO +18 -1
  12. {gea_program-0.34.0 → gea_program-0.35.0}/gea_program.egg-info/SOURCES.txt +2 -0
  13. {gea_program-0.34.0 → gea_program-0.35.0}/pyproject.toml +1 -1
  14. {gea_program-0.34.0 → gea_program-0.35.0}/LICENSE +0 -0
  15. {gea_program-0.34.0 → gea_program-0.35.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  16. {gea_program-0.34.0 → gea_program-0.35.0}/gea/accuracy_statement.py +0 -0
  17. {gea_program-0.34.0 → gea_program-0.35.0}/gea/alarm_engine.py +0 -0
  18. {gea_program-0.34.0 → gea_program-0.35.0}/gea/backup.py +0 -0
  19. {gea_program-0.34.0 → gea_program-0.35.0}/gea/bench.py +0 -0
  20. {gea_program-0.34.0 → gea_program-0.35.0}/gea/blind_harness.py +0 -0
  21. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  22. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  23. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  24. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  25. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  26. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  27. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  28. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  29. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  30. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  31. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  32. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  33. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  34. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  35. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  36. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  37. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  38. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  39. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  40. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  41. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  42. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  43. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  44. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  45. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  46. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  47. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  48. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  49. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  50. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  51. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  52. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  53. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  54. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  55. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  56. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  57. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  58. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  59. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  60. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  61. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  62. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  63. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  64. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  65. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  66. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  67. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  68. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  69. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  70. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  71. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  72. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  73. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  74. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  75. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  76. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  77. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  78. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  79. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  80. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  81. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  82. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  83. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  84. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  85. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  86. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  87. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  88. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/med_160_sapropels.txt +0 -0
  89. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  90. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  91. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  92. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  93. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  94. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  95. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  96. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  97. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  98. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  99. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  100. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  101. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  102. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  103. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  104. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  105. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  106. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  107. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  108. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  109. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  110. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  111. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  112. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  113. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  114. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  115. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  116. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  117. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  118. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  119. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  120. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  121. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  122. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  123. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  124. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  125. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog_map.py +0 -0
  126. {gea_program-0.34.0 → gea_program-0.35.0}/gea/catalog_register.py +0 -0
  127. {gea_program-0.34.0 → gea_program-0.35.0}/gea/certificates.py +0 -0
  128. {gea_program-0.34.0 → gea_program-0.35.0}/gea/cli.py +0 -0
  129. {gea_program-0.34.0 → gea_program-0.35.0}/gea/client_reports.py +0 -0
  130. {gea_program-0.34.0 → gea_program-0.35.0}/gea/config_versioning.py +0 -0
  131. {gea_program-0.34.0 → gea_program-0.35.0}/gea/conformance.py +0 -0
  132. {gea_program-0.34.0 → gea_program-0.35.0}/gea/correlation.py +0 -0
  133. {gea_program-0.34.0 → gea_program-0.35.0}/gea/dashboard.py +0 -0
  134. {gea_program-0.34.0 → gea_program-0.35.0}/gea/deviation.py +0 -0
  135. {gea_program-0.34.0 → gea_program-0.35.0}/gea/dlis.py +0 -0
  136. {gea_program-0.34.0 → gea_program-0.35.0}/gea/dnp3.py +0 -0
  137. {gea_program-0.34.0 → gea_program-0.35.0}/gea/doctor.py +0 -0
  138. {gea_program-0.34.0 → gea_program-0.35.0}/gea/downhole_engine.py +0 -0
  139. {gea_program-0.34.0 → gea_program-0.35.0}/gea/drift_monitor.py +0 -0
  140. {gea_program-0.34.0 → gea_program-0.35.0}/gea/earth_model.py +0 -0
  141. {gea_program-0.34.0 → gea_program-0.35.0}/gea/edr.py +0 -0
  142. {gea_program-0.34.0 → gea_program-0.35.0}/gea/elevation.py +0 -0
  143. {gea_program-0.34.0 → gea_program-0.35.0}/gea/etp.py +0 -0
  144. {gea_program-0.34.0 → gea_program-0.35.0}/gea/etp12_schemas.json +0 -0
  145. {gea_program-0.34.0 → gea_program-0.35.0}/gea/example_register_map.json +0 -0
  146. {gea_program-0.34.0 → gea_program-0.35.0}/gea/fat_sat.py +0 -0
  147. {gea_program-0.34.0 → gea_program-0.35.0}/gea/files.py +0 -0
  148. {gea_program-0.34.0 → gea_program-0.35.0}/gea/fixtures/README.md +0 -0
  149. {gea_program-0.34.0 → gea_program-0.35.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  150. {gea_program-0.34.0 → gea_program-0.35.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  151. {gea_program-0.34.0 → gea_program-0.35.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  152. {gea_program-0.34.0 → gea_program-0.35.0}/gea/follower.py +0 -0
  153. {gea_program-0.34.0 → gea_program-0.35.0}/gea/forward_model.py +0 -0
  154. {gea_program-0.34.0 → gea_program-0.35.0}/gea/friction_priors.py +0 -0
  155. {gea_program-0.34.0 → gea_program-0.35.0}/gea/gamma.py +0 -0
  156. {gea_program-0.34.0 → gea_program-0.35.0}/gea/gauge_aging.py +0 -0
  157. {gea_program-0.34.0 → gea_program-0.35.0}/gea/gauge_specs.py +0 -0
  158. {gea_program-0.34.0 → gea_program-0.35.0}/gea/geodesy.py +0 -0
  159. {gea_program-0.34.0 → gea_program-0.35.0}/gea/gravity_reference.py +0 -0
  160. {gea_program-0.34.0 → gea_program-0.35.0}/gea/hartip.py +0 -0
  161. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/TESTER_GUIDE.md +0 -0
  162. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/alarms.md +0 -0
  163. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/audit-update.md +0 -0
  164. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/backup.md +0 -0
  165. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/catalog.md +0 -0
  166. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/charts.md +0 -0
  167. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/conformance.md +0 -0
  168. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/data-in.md +0 -0
  169. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/dlis.md +0 -0
  170. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/doctor.md +0 -0
  171. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/drift.md +0 -0
  172. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/edr.md +0 -0
  173. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/etp.md +0 -0
  174. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/field-ports.md +0 -0
  175. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/files.md +0 -0
  176. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/historian.md +0 -0
  177. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/instruments.md +0 -0
  178. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/interval.md +0 -0
  179. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/map.md +0 -0
  180. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/month-end.md +0 -0
  181. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/notifications.md +0 -0
  182. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/osdu.md +0 -0
  183. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/patches.md +0 -0
  184. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/ppdm.md +0 -0
  185. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/quakeml.md +0 -0
  186. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/quality.md +0 -0
  187. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/seedlink.md +0 -0
  188. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/seismic.md +0 -0
  189. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/setup.md +0 -0
  190. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/site.md +0 -0
  191. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/sites.md +0 -0
  192. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/slip.md +0 -0
  193. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/soak.md +0 -0
  194. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/sra.md +0 -0
  195. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/start.md +0 -0
  196. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/structure.md +0 -0
  197. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/tools.md +0 -0
  198. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/transients.md +0 -0
  199. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/upkeep.md +0 -0
  200. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/vibration.md +0 -0
  201. {gea_program-0.34.0 → gea_program-0.35.0}/gea/help/well-tests.md +0 -0
  202. {gea_program-0.34.0 → gea_program-0.35.0}/gea/housekeeping.py +0 -0
  203. {gea_program-0.34.0 → gea_program-0.35.0}/gea/interval.py +0 -0
  204. {gea_program-0.34.0 → gea_program-0.35.0}/gea/inverse_engine.py +0 -0
  205. {gea_program-0.34.0 → gea_program-0.35.0}/gea/jobs.py +0 -0
  206. {gea_program-0.34.0 → gea_program-0.35.0}/gea/live_ports.py +0 -0
  207. {gea_program-0.34.0 → gea_program-0.35.0}/gea/loadtest.py +0 -0
  208. {gea_program-0.34.0 → gea_program-0.35.0}/gea/matplotlib_demo.py +0 -0
  209. {gea_program-0.34.0 → gea_program-0.35.0}/gea/modbus.py +0 -0
  210. {gea_program-0.34.0 → gea_program-0.35.0}/gea/modbus_rtu.py +0 -0
  211. {gea_program-0.34.0 → gea_program-0.35.0}/gea/model_card.py +0 -0
  212. {gea_program-0.34.0 → gea_program-0.35.0}/gea/month_end.py +0 -0
  213. {gea_program-0.34.0 → gea_program-0.35.0}/gea/mqtt_port.py +0 -0
  214. {gea_program-0.34.0 → gea_program-0.35.0}/gea/notify.py +0 -0
  215. {gea_program-0.34.0 → gea_program-0.35.0}/gea/opcua_port.py +0 -0
  216. {gea_program-0.34.0 → gea_program-0.35.0}/gea/operator_app.py +0 -0
  217. {gea_program-0.34.0 → gea_program-0.35.0}/gea/osdu.py +0 -0
  218. {gea_program-0.34.0 → gea_program-0.35.0}/gea/patches.py +0 -0
  219. {gea_program-0.34.0 → gea_program-0.35.0}/gea/permits.py +0 -0
  220. {gea_program-0.34.0 → gea_program-0.35.0}/gea/piwebapi.py +0 -0
  221. {gea_program-0.34.0 → gea_program-0.35.0}/gea/ports.py +0 -0
  222. {gea_program-0.34.0 → gea_program-0.35.0}/gea/ppdm.py +0 -0
  223. {gea_program-0.34.0 → gea_program-0.35.0}/gea/profile_catalog.py +0 -0
  224. {gea_program-0.34.0 → gea_program-0.35.0}/gea/project.py +0 -0
  225. {gea_program-0.34.0 → gea_program-0.35.0}/gea/qt6_downhole_app.py +0 -0
  226. {gea_program-0.34.0 → gea_program-0.35.0}/gea/quakeml.py +0 -0
  227. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reconciler.py +0 -0
  228. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  229. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  230. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  231. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  232. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  233. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  234. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/tool_sources.json +0 -0
  235. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/us_outline.json +0 -0
  236. {gea_program-0.34.0 → gea_program-0.35.0}/gea/reference/us_outline.provenance.json +0 -0
  237. {gea_program-0.34.0 → gea_program-0.35.0}/gea/rock_inventory.py +0 -0
  238. {gea_program-0.34.0 → gea_program-0.35.0}/gea/sample_record.py +0 -0
  239. {gea_program-0.34.0 → gea_program-0.35.0}/gea/sample_well_profile.csv +0 -0
  240. {gea_program-0.34.0 → gea_program-0.35.0}/gea/sbom.py +0 -0
  241. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seedlink.py +0 -0
  242. {gea_program-0.34.0 → gea_program-0.35.0}/gea/segy.py +0 -0
  243. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic.py +0 -0
  244. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_array.py +0 -0
  245. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_assoc.py +0 -0
  246. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_detect.py +0 -0
  247. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_film.py +0 -0
  248. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_harmonic.py +0 -0
  249. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_qc.py +0 -0
  250. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_response.py +0 -0
  251. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_signature.py +0 -0
  252. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_track.py +0 -0
  253. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_uncertainty.py +0 -0
  254. {gea_program-0.34.0 → gea_program-0.35.0}/gea/seismic_unlisted.py +0 -0
  255. {gea_program-0.34.0 → gea_program-0.35.0}/gea/sensor_swap.py +0 -0
  256. {gea_program-0.34.0 → gea_program-0.35.0}/gea/series_cache.py +0 -0
  257. {gea_program-0.34.0 → gea_program-0.35.0}/gea/service_life.py +0 -0
  258. {gea_program-0.34.0 → gea_program-0.35.0}/gea/setup_checklist.py +0 -0
  259. {gea_program-0.34.0 → gea_program-0.35.0}/gea/shapefile.py +0 -0
  260. {gea_program-0.34.0 → gea_program-0.35.0}/gea/shell.py +0 -0
  261. {gea_program-0.34.0 → gea_program-0.35.0}/gea/shut_in.py +0 -0
  262. {gea_program-0.34.0 → gea_program-0.35.0}/gea/site_map.py +0 -0
  263. {gea_program-0.34.0 → gea_program-0.35.0}/gea/sla_report.py +0 -0
  264. {gea_program-0.34.0 → gea_program-0.35.0}/gea/slip.py +0 -0
  265. {gea_program-0.34.0 → gea_program-0.35.0}/gea/soak.py +0 -0
  266. {gea_program-0.34.0 → gea_program-0.35.0}/gea/sra.py +0 -0
  267. {gea_program-0.34.0 → gea_program-0.35.0}/gea/store_forward.py +0 -0
  268. {gea_program-0.34.0 → gea_program-0.35.0}/gea/strata_join.py +0 -0
  269. {gea_program-0.34.0 → gea_program-0.35.0}/gea/structure.py +0 -0
  270. {gea_program-0.34.0 → gea_program-0.35.0}/gea/supervisor.py +0 -0
  271. {gea_program-0.34.0 → gea_program-0.35.0}/gea/survey_cmd.py +0 -0
  272. {gea_program-0.34.0 → gea_program-0.35.0}/gea/survey_view.py +0 -0
  273. {gea_program-0.34.0 → gea_program-0.35.0}/gea/telemetry.py +0 -0
  274. {gea_program-0.34.0 → gea_program-0.35.0}/gea/tool_catalog.py +0 -0
  275. {gea_program-0.34.0 → gea_program-0.35.0}/gea/tool_library.py +0 -0
  276. {gea_program-0.34.0 → gea_program-0.35.0}/gea/tool_taxonomy.py +0 -0
  277. {gea_program-0.34.0 → gea_program-0.35.0}/gea/transient.py +0 -0
  278. {gea_program-0.34.0 → gea_program-0.35.0}/gea/vibration.py +0 -0
  279. {gea_program-0.34.0 → gea_program-0.35.0}/gea/well_assembler.py +0 -0
  280. {gea_program-0.34.0 → gea_program-0.35.0}/gea/well_test_validation.py +0 -0
  281. {gea_program-0.34.0 → gea_program-0.35.0}/gea/wits0.py +0 -0
  282. {gea_program-0.34.0 → gea_program-0.35.0}/gea/witsml.py +0 -0
  283. {gea_program-0.34.0 → gea_program-0.35.0}/gea/workspace.py +0 -0
  284. {gea_program-0.34.0 → gea_program-0.35.0}/gea_program.egg-info/dependency_links.txt +0 -0
  285. {gea_program-0.34.0 → gea_program-0.35.0}/gea_program.egg-info/entry_points.txt +0 -0
  286. {gea_program-0.34.0 → gea_program-0.35.0}/gea_program.egg-info/requires.txt +0 -0
  287. {gea_program-0.34.0 → gea_program-0.35.0}/gea_program.egg-info/top_level.txt +0 -0
  288. {gea_program-0.34.0 → gea_program-0.35.0}/setup.cfg +0 -0
  289. {gea_program-0.34.0 → gea_program-0.35.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.34.0
3
+ Version: 0.35.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
@@ -648,6 +648,23 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
648
648
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
649
649
  `rtserve.iris.washington.edu:18000`, network `TX`.
650
650
 
651
+ ### The control room
652
+
653
+ The Control room tab opens the wall: a screen read from across the room, one
654
+ tile per well, worst first, each with its state in a shape and a word -
655
+ Serious, Watch, Good, No data - the reasons in plain words, the alarms, the
656
+ gauges, and for a drilling recorder the bottom, the bit, the rate of
657
+ penetration and the hours on bottom at the end of the record. It refreshes
658
+ itself every 30 seconds, says how old its last answer is, and puts a notice
659
+ across the screen when the service stops answering; a recorded well is
660
+ described at the end of its record and never called stale. F is full screen;
661
+ the screen is kept awake where the browser allows it; when the wells do not
662
+ fit, the pages turn, the worst first. The outgoing operator writes the shift
663
+ handover - what is open, what is being watched, what was done - and it stays
664
+ on the wall until the incoming operator acknowledges it; both go in the audit
665
+ log. A wall pass lets a screen show the wall and nothing else with no one's
666
+ account signed in. `gea wall`, `gea handover`, `gea help control-room`.
667
+
651
668
  ### The site map
652
669
 
653
670
  The Map tab puts everything in the workspace that has a position in one
@@ -609,6 +609,23 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
609
609
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
610
610
  `rtserve.iris.washington.edu:18000`, network `TX`.
611
611
 
612
+ ### The control room
613
+
614
+ The Control room tab opens the wall: a screen read from across the room, one
615
+ tile per well, worst first, each with its state in a shape and a word -
616
+ Serious, Watch, Good, No data - the reasons in plain words, the alarms, the
617
+ gauges, and for a drilling recorder the bottom, the bit, the rate of
618
+ penetration and the hours on bottom at the end of the record. It refreshes
619
+ itself every 30 seconds, says how old its last answer is, and puts a notice
620
+ across the screen when the service stops answering; a recorded well is
621
+ described at the end of its record and never called stale. F is full screen;
622
+ the screen is kept awake where the browser allows it; when the wells do not
623
+ fit, the pages turn, the worst first. The outgoing operator writes the shift
624
+ handover - what is open, what is being watched, what was done - and it stays
625
+ on the wall until the incoming operator acknowledges it; both go in the audit
626
+ log. A wall pass lets a screen show the wall and nothing else with no one's
627
+ account signed in. `gea wall`, `gea handover`, `gea help control-room`.
628
+
612
629
  ### The site map
613
630
 
614
631
  The Map tab puts everything in the workspace that has a position in one
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.34.0"
126
+ __version__ = "0.35.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -475,6 +475,21 @@ def main(argv=None) -> int:
475
475
  p_ck.add_argument("--note", type=str, default="", help="--confirm: what it was checked against (required)")
476
476
  p_ck.add_argument("--actor", type=str, default="cli")
477
477
 
478
+ p_wl = sub.add_parser("wall", help="the control-room board as text: a line per well, worst first, its state and why (the same board the wall screen shows)")
479
+ p_wl.add_argument("--workspace", type=str, default=None, help="the workspace folder, e.g. C:\\site")
480
+ p_wl.add_argument("--json", action="store_true")
481
+
482
+ p_ho = sub.add_parser("handover", help="the shift handover: list the notes, sign one (--write) or acknowledge one (--ack)")
483
+ p_ho.add_argument("--workspace", type=str, default=None, help="the workspace folder, e.g. C:\\site")
484
+ p_ho.add_argument("--write", action="store_true", help="sign a note: give at least one of --open, --watching, --done")
485
+ p_ho.add_argument("--open", type=str, default="", help="--write: what the next shift must pick up")
486
+ p_ho.add_argument("--watching", type=str, default="", help="--write: what is being watched, and why")
487
+ p_ho.add_argument("--done", type=str, default="", help="--write: what this shift did")
488
+ p_ho.add_argument("--ack", type=str, default=None, help="acknowledge the note with this id (not your own)")
489
+ p_ho.add_argument("--comment", type=str, default="", help="--ack: an optional comment")
490
+ p_ho.add_argument("--actor", type=str, default=None, help="who signs or acknowledges (required with --write and --ack)")
491
+ p_ho.add_argument("--json", action="store_true")
492
+
478
493
  p_sv = sub.add_parser("serve", help="serve the dashboard over a workspace: the page, the reports and the JSON API (standard library HTTP)")
479
494
  p_sv.add_argument("--workspace", type=str, required=True, help="the workspace folder (gea workspace --action init)")
480
495
  p_sv.add_argument("--host", type=str, default="127.0.0.1", help="bind address; 0.0.0.0 exposes it on the site network (put TLS in front)")
@@ -1922,6 +1937,51 @@ def main(argv=None) -> int:
1922
1937
  else:
1923
1938
  print(_SC.text(r))
1924
1939
  return 0 if r["ready"] else 2
1940
+ elif a.cmd in ("wall", "handover"):
1941
+ import json as _json
1942
+ from . import control_room as _CR
1943
+ from .cli import _safe_stdout
1944
+ from .workspace import Workspace, WorkspaceError
1945
+ _safe_stdout()
1946
+ if not a.workspace:
1947
+ raise SystemExit(f"{a.cmd} needs --workspace, e.g. --workspace C:\\site")
1948
+ try:
1949
+ ws = Workspace(a.workspace)
1950
+ except WorkspaceError as e:
1951
+ raise SystemExit(f"{a.cmd}: {e}")
1952
+ if a.cmd == "wall":
1953
+ b = _CR.board(ws)
1954
+ print(_json.dumps(b, indent=1) if a.json else _CR.text(b))
1955
+ return 0
1956
+ try:
1957
+ if a.write:
1958
+ if not a.actor:
1959
+ raise SystemExit("handover --write needs --actor, the name the note is signed with")
1960
+ b = _CR.board(ws)
1961
+ counts = {**b["counts"], "alarms_unacked": sum(((t.get("alarms") or {}).get("unacked") or 0) for t in b["tiles"])}
1962
+ n = _CR.write_note(ws, a.actor, a.open, a.watching, a.done, counts)
1963
+ print(f"handover {n['id']} signed by {n['by']} at {n['utc']}")
1964
+ return 0
1965
+ if a.ack:
1966
+ if not a.actor:
1967
+ raise SystemExit("handover --ack needs --actor, the incoming operator's name")
1968
+ r = _CR.ack_note(ws, a.ack, a.actor, a.comment)
1969
+ print(f"handover {a.ack} acknowledged by {r['by']} at {r['utc']}")
1970
+ return 0
1971
+ except (ValueError, KeyError, PermissionError) as e:
1972
+ raise SystemExit(f"handover: {e.args[0] if e.args else e}")
1973
+ ns = _CR.notes(ws, 0)
1974
+ if a.json:
1975
+ print(_json.dumps(ns, indent=1))
1976
+ elif not ns:
1977
+ print("no handover notes yet")
1978
+ else:
1979
+ for n in ns:
1980
+ print(f"{n['id']} {n['utc']} by {n['by']} " + (f"acknowledged by {n['ack']['by']} at {n['ack']['utc']}" if n.get('ack') else "NOT YET ACKNOWLEDGED"))
1981
+ for k in _CR.NOTE_FIELDS:
1982
+ if n.get(k):
1983
+ print(f" {k}: {n[k]}")
1984
+ return 0
1925
1985
  elif a.cmd == "serve":
1926
1986
  from .service import Service
1927
1987
  from .workspace import WorkspaceError
@@ -8448,6 +8448,171 @@ def section_cc_site_map(tmp: str) -> None:
8448
8448
  "bar, the not-placed wells with the way to place them, what is at a click; the help page"
8449
8449
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
8450
8450
 
8451
+
8452
+ def section_cd_control_room(tmp: str) -> None:
8453
+ """Section CD - the control room: the wall board judged by its stated rule (a shape and a word for each state, worst
8454
+ first, a recorded well never called stale, a live feed's age measured), the shift handover signed by one operator and
8455
+ acknowledged by another with both in the audit log, wall passes that show the wall and nothing else, the page."""
8456
+ import http.cookiejar
8457
+ import urllib.request
8458
+ import urllib.error
8459
+ from datetime import datetime as _dt, timedelta as _td, timezone as _tz
8460
+ from . import control_room as CR
8461
+ from . import edr as E
8462
+ from . import helplib as H
8463
+ from .workspace import Workspace
8464
+ from .service import Service, Users
8465
+ pkg = Path(__file__).parent
8466
+ d = Path(tmp, "cd"); d.mkdir()
8467
+
8468
+ ev = E.selftest_events(str(d))
8469
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
8470
+ wd = ws.add_well_file(ev["path"], display="Drilling well", actor="tester")
8471
+ wa = ws.add_well_file(ev["path"], display="Alarm well", actor="tester")
8472
+ wc = ws.add_well_catalog("volve_f12_f14_production_excerpt", "15/9-F-12", 10000.0, actor="tester")
8473
+ ws.refresh_dashboard(actor="tester")
8474
+ wn = ws.add_well_file(ev["path"], display="New well", actor="tester") # added after the refresh: no reports yet
8475
+ ra = Path(ws.reports_dir, "wells", wa["id"])
8476
+ (ra / "alarm_event_report.json").write_text(json.dumps({"active": [
8477
+ {"alarm_id": "SPP.HH_RANGE", "state": "ACTIVE_UNACKED", "priority": "P2", "tag_id": "SPP"},
8478
+ {"alarm_id": "HKLD.QUALITY", "state": "ACTIVE_ACKED", "priority": "P4", "tag_id": "HKLD"}], "shelved": []}), encoding="utf-8")
8479
+ now = _dt(2026, 10, 15, 12, 0, 0, tzinfo=_tz.utc)
8480
+ iso = lambda s: (now - _td(seconds=s)).strftime("%Y-%m-%dT%H:%M:%SZ")
8481
+ feed = lambda age, status="CONNECTED", wid=wn["id"]: [{"name": "p1", "well_id": wid, "status": status, "last_sample_utc": (iso(age) if age is not None else None), "patch": {"stale_after_s": 120.0}}]
8482
+ b0 = CR.board(ws, [], now=now)
8483
+ t0 = {t["well_id"]: t for t in b0["tiles"]}
8484
+ dr = t0[wd["id"]]["drilling"]
8485
+ live = {k: {t["well_id"]: t for t in CR.board(ws, feed(*a), now=now)["tiles"]}[wn["id"]] for k, a in
8486
+ {"fresh": (30,), "late": (400,), "silent": (1000,), "down": (60, "DOWN"), "none": (None,)}.items()}
8487
+ c1 = {
8488
+ "states": t0[wa["id"]]["state"] == "SERIOUS" and t0[wa["id"]]["reasons"][:2] == ["1 unacknowledged P2 alarm(s)", "1 other active alarm(s)"]
8489
+ and t0[wc["id"]]["state"] == "SERIOUS" and "no explanation" in t0[wc["id"]]["reasons"][0]
8490
+ and t0[wd["id"]]["state"] == "GOOD" and t0[wd["id"]]["reasons"] == ["nothing needs attention"]
8491
+ and t0[wn["id"]]["state"] == "NO_DATA",
8492
+ "shape_word": all(t["shape"] == CR.SHAPE[t["state"]] and t["word"] == CR.WORD[t["state"]] for t in b0["tiles"])
8493
+ and len(set(CR.SHAPE.values())) == 4 and len(set(CR.WORD.values())) == 4,
8494
+ "order": [t["state"] for t in b0["tiles"]] == sorted((t["state"] for t in b0["tiles"]), key=CR.STATES.index)
8495
+ and b0["counts"] == {"SERIOUS": 2, "WATCH": 0, "GOOD": 1, "NO_DATA": 1},
8496
+ "recorded": t0[wd["id"]]["live"] is None and t0[wd["id"]]["recorded"]["record_end_utc"] == dr["record_end_utc"]
8497
+ and "never called stale" in b0["rule"],
8498
+ "read_only": not Path(wsp, "handover").exists() and not Path(wsp, "wall").exists(),
8499
+ "drilling": dr["cache"] and dr["td_md_ft"] == 5750.0 and dr["bottom_ft"] == 5750.0 and dr["on_bottom_hours"] is not None
8500
+ and (dr["rop_ft_h"] is not None) == bool(dr["drilling_now"]),
8501
+ "live": live["fresh"]["state"] == "GOOD" and live["late"]["state"] == "WATCH" and "past the 2 min stale limit" in live["late"]["reasons"][0]
8502
+ and live["silent"]["state"] == "SERIOUS" and live["silent"]["reasons"][0].startswith("no live sample for 16 min")
8503
+ and live["down"]["state"] == "SERIOUS" and live["none"]["state"] == "WATCH" and abs(live["fresh"]["live"]["age_s"] - 30) < 1e-6,
8504
+ }
8505
+ failed1 = [k for k, v in c1.items() if not v]
8506
+ ok(not failed1,
8507
+ "CD1 the wall board by its stated rule, read without writing anything: an unacknowledged P2 alarm and a gauge off with no explanation SERIOUS, a well with reports and "
8508
+ "nothing else GOOD, a well with neither reports nor a live sample NO DATA, each state its own shape and word, worst first; a recorded well "
8509
+ "described at the end of its record and never called stale (bottom 5,750 ft, the ROP only while drilling at the end); a live feed's "
8510
+ "measured age against its stale limit - 30 s good, 400 s watch, 1,000 s serious, a patch down serious"
8511
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
8512
+
8513
+ users = Users(str(Path(wsp, "users.json")))
8514
+ for n_, r_ in (("adm", "admin"), ("op1", "operator"), ("op2", "operator"), ("v", "viewer")):
8515
+ users.add(n_, f"{n_}-pass-123", r_)
8516
+ svc = Service(wsp, port=0, scheduler=False).start()
8517
+ base_url = svc.url.rstrip("/")
8518
+
8519
+ def opener():
8520
+ return urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
8521
+
8522
+ def call(op, method, path, body=None):
8523
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
8524
+ if method == "POST":
8525
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
8526
+ try:
8527
+ with op.open(req, timeout=120) as rr_:
8528
+ return rr_.status, json.loads(rr_.read())
8529
+ except urllib.error.HTTPError as e:
8530
+ return e.code, json.loads(e.read())
8531
+
8532
+ def signin(name):
8533
+ op = opener(); call(op, "POST", "/api/login", {"name": name, "password": f"{name}-pass-123"}); return op
8534
+ try:
8535
+ anon = opener()
8536
+ st_anon, _ = call(anon, "GET", "/api/wall")
8537
+ V, O1, O2, A = signin("v"), signin("op1"), signin("op2"), signin("adm")
8538
+ st_v, bv = call(V, "GET", "/api/wall")
8539
+ st_vw, _ = call(V, "POST", "/api/handover", {"open": "x"})
8540
+ st_empty, _ = call(O1, "POST", "/api/handover", {"open": " ", "watching": "", "done": ""})
8541
+ st_long, _ = call(O1, "POST", "/api/handover", {"open": "x" * 2001})
8542
+ st_w, note = call(O1, "POST", "/api/handover", {"open": "Pump 2 liner swap not finished.", "watching": "SPP on the alarm well.", "done": "Reamed to bottom."})
8543
+ _, bdg1 = call(V, "GET", "/api/badges")
8544
+ _, wall1 = call(V, "GET", "/api/wall")
8545
+ st_self, self_ = call(O1, "POST", "/api/handover/ack", {"id": note.get("id")})
8546
+ st_ack, ack = call(O2, "POST", "/api/handover/ack", {"id": note.get("id"), "comment": "read at 06:05"})
8547
+ st_ack2, _ = call(O2, "POST", "/api/handover/ack", {"id": note.get("id")})
8548
+ st_nf, _ = call(O2, "POST", "/api/handover/ack", {"id": "H-none"})
8549
+ _, bdg2 = call(V, "GET", "/api/badges")
8550
+ _, hv = call(V, "GET", "/api/handover")
8551
+ # wall passes
8552
+ st_pv, _ = call(V, "POST", "/api/wall/pass", {"label": "doghouse"})
8553
+ st_pnl, _ = call(A, "POST", "/api/wall/pass", {"label": " "})
8554
+ st_pc, wp = call(A, "POST", "/api/wall/pass", {"label": "doghouse wall", "days": 30})
8555
+ _, plist = call(A, "GET", "/api/wall/passes")
8556
+ W = opener()
8557
+ st_bad, _ = call(W, "POST", "/api/wall/enter", {"code": wp["id"] + ".wrong"})
8558
+ st_in, _ = call(W, "POST", "/api/wall/enter", {"code": wp["code"]})
8559
+ st_ww, wwall = call(W, "GET", "/api/wall")
8560
+ _, wsess = call(W, "GET", "/api/session")
8561
+ st_wo, _ = call(W, "GET", "/api/overview")
8562
+ st_wh, _ = call(W, "GET", "/api/handover")
8563
+ st_wpost, _ = call(W, "POST", "/api/handover", {"open": "x"})
8564
+ st_rv, _ = call(A, "POST", "/api/wall/pass/revoke", {"id": wp["id"]})
8565
+ st_after, _ = call(W, "GET", "/api/wall")
8566
+ _, plist2 = call(A, "GET", "/api/wall/passes")
8567
+ L = opener()
8568
+ lock = [call(L, "POST", "/api/wall/enter", {"code": "W000000.nope"})[0] for _ in range(6)]
8569
+ finally:
8570
+ svc.stop()
8571
+ audit = [e["action"] for e in ws.audit_log(5000)]
8572
+ raw = (Path(wsp, "wall", "passes.json")).read_text(encoding="utf-8")
8573
+ lines = Path(wsp, "handover", "notes.jsonl").read_text(encoding="utf-8").splitlines()
8574
+ c2 = {
8575
+ "roles": st_anon == 401 and st_v == 200 and len(bv["tiles"]) == 4 and st_vw == 403,
8576
+ "note": st_empty == 400 and st_long == 400 and st_w == 200 and note["by"] == "op1" and note["ack"] is None
8577
+ and note["board"]["SERIOUS"] == 2 and note["board"]["alarms_unacked"] == 1
8578
+ and wall1["handover"]["id"] == note["id"] and wall1["handover"]["ack"] is None and bdg1["handover_unacked"] == 1,
8579
+ "ack": st_self == 403 and "author cannot acknowledge" in self_["error"] and st_ack == 200 and ack["by"] == "op2"
8580
+ and st_ack2 == 400 and st_nf == 404 and bdg2["handover_unacked"] == 0
8581
+ and hv["notes"][0]["ack"]["by"] == "op2" and hv["notes"][0]["ack"]["comment"] == "read at 06:05",
8582
+ "append_only": len(lines) == 2 and json.loads(lines[0])["kind"] == "note" and json.loads(lines[1])["kind"] == "ack",
8583
+ "audit": all(a_ in audit for a_ in ("handover.write", "handover.ack", "wall.pass.create", "wall.pass.failed", "wall.pass.enter", "wall.pass.revoke")),
8584
+ }
8585
+ failed2 = [k for k, v in c2.items() if not v]
8586
+ ok(not failed2,
8587
+ "CD2 the shift handover: a viewer reads the wall and cannot write; an empty or over-long note refused; a signed note keeps its author, its "
8588
+ "time and the board's counts (2 serious, 1 unacknowledged alarm), shows on the wall and badges the tab until acknowledged; the author "
8589
+ "cannot acknowledge it, the next operator can (once, with a comment); notes and acknowledgements appended, never rewritten; every step in "
8590
+ "the audit log"
8591
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
8592
+
8593
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
8594
+ help_md = (pkg / "help" / "control-room.md").read_text(encoding="utf-8")
8595
+ c3 = {
8596
+ "pass": st_pv == 403 and st_pnl == 400 and st_pc == 200 and wp["code"].startswith(wp["id"] + ".") and wp["code"].split(".", 1)[1] not in raw
8597
+ and "hash" not in json.dumps(plist) and plist["passes"][0]["status"] == "ACTIVE",
8598
+ "wall_only": st_bad == 401 and st_in == 200 and st_ww == 200 and len(wwall["tiles"]) == 4 and wsess["user"] is None
8599
+ and wsess["wall_pass"]["label"] == "doghouse wall" and st_wo == 401 and st_wh == 401 and st_wpost == 401,
8600
+ "revoke": st_rv == 200 and st_after == 401 and plist2["passes"][0]["status"] == "REVOKED" and lock[-1] == 429,
8601
+ "page": all(x in page_src for x in ("VIEWS.wall", "VIEWS.control", "['#/control', 'Control room']", "/api/wall", "/api/handover/ack",
8602
+ "NOT UPDATING", "wakeLock", "requestFullscreen", "SIGNED OUT", "This is a wall screen",
8603
+ "crShape", "data-age0", "turns pages", "/api/wall/pass/revoke", "handover_unacked")),
8604
+ "help": any(t["topic"] == "control-room" for t in H.topics()) and not H.check("control-room") and H.VIEW_TOPIC.get("control") == "control-room"
8605
+ and "never called stale" in help_md.split("will not call a measurement")[1],
8606
+ }
8607
+ failed3 = [k for k, v in c3.items() if not v]
8608
+ ok(not failed3,
8609
+ "CD3 wall passes: issued by an administrator only and named for the screen, the code shown once and only its hash kept; a wrong pass "
8610
+ "refused and locked out after repeats; a pass shows the wall and nothing else - no other page, no handover list, no writing - until it is "
8611
+ "revoked; the page: the wall full screen with its shapes and words, the age of its last answer, NOT UPDATING across the screen when the "
8612
+ "service stops, the screen kept awake, pages turned when the wells do not fit, the handover written and acknowledged; the help page"
8613
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
8614
+
8615
+
8451
8616
  def main() -> int:
8452
8617
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
8453
8618
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -8532,6 +8697,7 @@ def main() -> int:
8532
8697
  section_ca_setup_checklist(tmp)
8533
8698
  section_cb_charts(tmp)
8534
8699
  section_cc_site_map(tmp)
8700
+ section_cd_control_room(tmp)
8535
8701
  if _FAILS:
8536
8702
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
8537
8703
  for f in _FAILS: