gea-program 0.26.0__tar.gz → 0.28.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 (273) hide show
  1. {gea_program-0.26.0/gea_program.egg-info → gea_program-0.28.0}/PKG-INFO +26 -2
  2. {gea_program-0.26.0 → gea_program-0.28.0}/README.md +25 -1
  3. {gea_program-0.26.0 → gea_program-0.28.0}/gea/__init__.py +1 -1
  4. {gea_program-0.26.0 → gea_program-0.28.0}/gea/__main__.py +72 -0
  5. {gea_program-0.26.0 → gea_program-0.28.0}/gea/acceptance_tests.py +157 -0
  6. gea_program-0.28.0/gea/fixtures/README.md +10 -0
  7. gea_program-0.28.0/gea/fixtures/comcat_delaware_basin_2026-09.txt +29 -0
  8. gea_program-0.28.0/gea/fixtures/comcat_youngstown_2011-2012.txt +10 -0
  9. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/sra.md +1 -1
  10. gea_program-0.28.0/gea/help/tools.md +11 -0
  11. {gea_program-0.26.0 → gea_program-0.28.0}/gea/helplib.py +2 -1
  12. {gea_program-0.26.0 → gea_program-0.28.0}/gea/service.py +19 -0
  13. {gea_program-0.26.0 → gea_program-0.28.0}/gea/sra.py +43 -0
  14. gea_program-0.28.0/gea/tool_catalog.py +190 -0
  15. {gea_program-0.26.0 → gea_program-0.28.0}/gea/web/app.html +56 -1
  16. {gea_program-0.26.0 → gea_program-0.28.0/gea_program.egg-info}/PKG-INFO +26 -2
  17. {gea_program-0.26.0 → gea_program-0.28.0}/gea_program.egg-info/SOURCES.txt +5 -0
  18. {gea_program-0.26.0 → gea_program-0.28.0}/pyproject.toml +2 -2
  19. {gea_program-0.26.0 → gea_program-0.28.0}/LICENSE +0 -0
  20. {gea_program-0.26.0 → gea_program-0.28.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  21. {gea_program-0.26.0 → gea_program-0.28.0}/gea/accuracy_statement.py +0 -0
  22. {gea_program-0.26.0 → gea_program-0.28.0}/gea/alarm_engine.py +0 -0
  23. {gea_program-0.26.0 → gea_program-0.28.0}/gea/backup.py +0 -0
  24. {gea_program-0.26.0 → gea_program-0.28.0}/gea/bench.py +0 -0
  25. {gea_program-0.26.0 → gea_program-0.28.0}/gea/blind_harness.py +0 -0
  26. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  27. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  28. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  29. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  30. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  31. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  32. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  33. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  34. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  35. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  36. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  37. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  38. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  39. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  40. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  41. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  42. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  43. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  44. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  45. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  46. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  47. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  48. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  49. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  50. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  51. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  52. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  53. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  54. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  55. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  56. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  57. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  58. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  59. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  60. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  61. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  62. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  63. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  64. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  65. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  66. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  67. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  68. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  69. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  70. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  71. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  72. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  73. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  74. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  75. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  76. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  77. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  78. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  79. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  80. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  81. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  82. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  83. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  84. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  85. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  86. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  87. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  88. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  89. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  90. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  91. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  92. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  93. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/med_160_sapropels.txt +0 -0
  94. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  95. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  96. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  97. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  98. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  99. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  100. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  101. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  102. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  103. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  104. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  105. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  106. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  107. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  108. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  109. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  110. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  111. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  112. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  113. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  114. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  115. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  116. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  117. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  118. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  119. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  120. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  121. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  122. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  123. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  124. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  125. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  126. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  127. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  128. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  129. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  130. {gea_program-0.26.0 → gea_program-0.28.0}/gea/catalog_register.py +0 -0
  131. {gea_program-0.26.0 → gea_program-0.28.0}/gea/certificates.py +0 -0
  132. {gea_program-0.26.0 → gea_program-0.28.0}/gea/cli.py +0 -0
  133. {gea_program-0.26.0 → gea_program-0.28.0}/gea/client_reports.py +0 -0
  134. {gea_program-0.26.0 → gea_program-0.28.0}/gea/config_versioning.py +0 -0
  135. {gea_program-0.26.0 → gea_program-0.28.0}/gea/conformance.py +0 -0
  136. {gea_program-0.26.0 → gea_program-0.28.0}/gea/correlation.py +0 -0
  137. {gea_program-0.26.0 → gea_program-0.28.0}/gea/dashboard.py +0 -0
  138. {gea_program-0.26.0 → gea_program-0.28.0}/gea/deviation.py +0 -0
  139. {gea_program-0.26.0 → gea_program-0.28.0}/gea/dlis.py +0 -0
  140. {gea_program-0.26.0 → gea_program-0.28.0}/gea/dnp3.py +0 -0
  141. {gea_program-0.26.0 → gea_program-0.28.0}/gea/doctor.py +0 -0
  142. {gea_program-0.26.0 → gea_program-0.28.0}/gea/downhole_engine.py +0 -0
  143. {gea_program-0.26.0 → gea_program-0.28.0}/gea/drift_monitor.py +0 -0
  144. {gea_program-0.26.0 → gea_program-0.28.0}/gea/earth_model.py +0 -0
  145. {gea_program-0.26.0 → gea_program-0.28.0}/gea/elevation.py +0 -0
  146. {gea_program-0.26.0 → gea_program-0.28.0}/gea/etp.py +0 -0
  147. {gea_program-0.26.0 → gea_program-0.28.0}/gea/etp12_schemas.json +0 -0
  148. {gea_program-0.26.0 → gea_program-0.28.0}/gea/example_register_map.json +0 -0
  149. {gea_program-0.26.0 → gea_program-0.28.0}/gea/fat_sat.py +0 -0
  150. {gea_program-0.26.0 → gea_program-0.28.0}/gea/files.py +0 -0
  151. {gea_program-0.26.0 → gea_program-0.28.0}/gea/follower.py +0 -0
  152. {gea_program-0.26.0 → gea_program-0.28.0}/gea/forward_model.py +0 -0
  153. {gea_program-0.26.0 → gea_program-0.28.0}/gea/friction_priors.py +0 -0
  154. {gea_program-0.26.0 → gea_program-0.28.0}/gea/gamma.py +0 -0
  155. {gea_program-0.26.0 → gea_program-0.28.0}/gea/gauge_aging.py +0 -0
  156. {gea_program-0.26.0 → gea_program-0.28.0}/gea/gauge_specs.py +0 -0
  157. {gea_program-0.26.0 → gea_program-0.28.0}/gea/geodesy.py +0 -0
  158. {gea_program-0.26.0 → gea_program-0.28.0}/gea/gravity_reference.py +0 -0
  159. {gea_program-0.26.0 → gea_program-0.28.0}/gea/hartip.py +0 -0
  160. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/TESTER_GUIDE.md +0 -0
  161. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/alarms.md +0 -0
  162. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/audit-update.md +0 -0
  163. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/backup.md +0 -0
  164. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/catalog.md +0 -0
  165. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/conformance.md +0 -0
  166. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/data-in.md +0 -0
  167. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/dlis.md +0 -0
  168. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/doctor.md +0 -0
  169. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/drift.md +0 -0
  170. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/etp.md +0 -0
  171. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/field-ports.md +0 -0
  172. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/files.md +0 -0
  173. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/historian.md +0 -0
  174. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/instruments.md +0 -0
  175. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/interval.md +0 -0
  176. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/month-end.md +0 -0
  177. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/notifications.md +0 -0
  178. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/osdu.md +0 -0
  179. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/patches.md +0 -0
  180. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/ppdm.md +0 -0
  181. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/quakeml.md +0 -0
  182. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/quality.md +0 -0
  183. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/seedlink.md +0 -0
  184. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/seismic.md +0 -0
  185. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/site.md +0 -0
  186. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/sites.md +0 -0
  187. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/slip.md +0 -0
  188. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/soak.md +0 -0
  189. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/start.md +0 -0
  190. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/structure.md +0 -0
  191. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/transients.md +0 -0
  192. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/upkeep.md +0 -0
  193. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/vibration.md +0 -0
  194. {gea_program-0.26.0 → gea_program-0.28.0}/gea/help/well-tests.md +0 -0
  195. {gea_program-0.26.0 → gea_program-0.28.0}/gea/housekeeping.py +0 -0
  196. {gea_program-0.26.0 → gea_program-0.28.0}/gea/interval.py +0 -0
  197. {gea_program-0.26.0 → gea_program-0.28.0}/gea/inverse_engine.py +0 -0
  198. {gea_program-0.26.0 → gea_program-0.28.0}/gea/jobs.py +0 -0
  199. {gea_program-0.26.0 → gea_program-0.28.0}/gea/live_ports.py +0 -0
  200. {gea_program-0.26.0 → gea_program-0.28.0}/gea/loadtest.py +0 -0
  201. {gea_program-0.26.0 → gea_program-0.28.0}/gea/matplotlib_demo.py +0 -0
  202. {gea_program-0.26.0 → gea_program-0.28.0}/gea/modbus.py +0 -0
  203. {gea_program-0.26.0 → gea_program-0.28.0}/gea/modbus_rtu.py +0 -0
  204. {gea_program-0.26.0 → gea_program-0.28.0}/gea/model_card.py +0 -0
  205. {gea_program-0.26.0 → gea_program-0.28.0}/gea/month_end.py +0 -0
  206. {gea_program-0.26.0 → gea_program-0.28.0}/gea/mqtt_port.py +0 -0
  207. {gea_program-0.26.0 → gea_program-0.28.0}/gea/notify.py +0 -0
  208. {gea_program-0.26.0 → gea_program-0.28.0}/gea/opcua_port.py +0 -0
  209. {gea_program-0.26.0 → gea_program-0.28.0}/gea/operator_app.py +0 -0
  210. {gea_program-0.26.0 → gea_program-0.28.0}/gea/osdu.py +0 -0
  211. {gea_program-0.26.0 → gea_program-0.28.0}/gea/patches.py +0 -0
  212. {gea_program-0.26.0 → gea_program-0.28.0}/gea/permits.py +0 -0
  213. {gea_program-0.26.0 → gea_program-0.28.0}/gea/piwebapi.py +0 -0
  214. {gea_program-0.26.0 → gea_program-0.28.0}/gea/ports.py +0 -0
  215. {gea_program-0.26.0 → gea_program-0.28.0}/gea/ppdm.py +0 -0
  216. {gea_program-0.26.0 → gea_program-0.28.0}/gea/profile_catalog.py +0 -0
  217. {gea_program-0.26.0 → gea_program-0.28.0}/gea/project.py +0 -0
  218. {gea_program-0.26.0 → gea_program-0.28.0}/gea/qt6_downhole_app.py +0 -0
  219. {gea_program-0.26.0 → gea_program-0.28.0}/gea/quakeml.py +0 -0
  220. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reconciler.py +0 -0
  221. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  222. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  223. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  224. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  225. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  226. {gea_program-0.26.0 → gea_program-0.28.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  227. {gea_program-0.26.0 → gea_program-0.28.0}/gea/rock_inventory.py +0 -0
  228. {gea_program-0.26.0 → gea_program-0.28.0}/gea/sample_record.py +0 -0
  229. {gea_program-0.26.0 → gea_program-0.28.0}/gea/sample_well_profile.csv +0 -0
  230. {gea_program-0.26.0 → gea_program-0.28.0}/gea/sbom.py +0 -0
  231. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seedlink.py +0 -0
  232. {gea_program-0.26.0 → gea_program-0.28.0}/gea/segy.py +0 -0
  233. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic.py +0 -0
  234. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_array.py +0 -0
  235. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_assoc.py +0 -0
  236. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_detect.py +0 -0
  237. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_film.py +0 -0
  238. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_harmonic.py +0 -0
  239. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_qc.py +0 -0
  240. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_response.py +0 -0
  241. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_signature.py +0 -0
  242. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_track.py +0 -0
  243. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_uncertainty.py +0 -0
  244. {gea_program-0.26.0 → gea_program-0.28.0}/gea/seismic_unlisted.py +0 -0
  245. {gea_program-0.26.0 → gea_program-0.28.0}/gea/sensor_swap.py +0 -0
  246. {gea_program-0.26.0 → gea_program-0.28.0}/gea/service_life.py +0 -0
  247. {gea_program-0.26.0 → gea_program-0.28.0}/gea/shapefile.py +0 -0
  248. {gea_program-0.26.0 → gea_program-0.28.0}/gea/shell.py +0 -0
  249. {gea_program-0.26.0 → gea_program-0.28.0}/gea/shut_in.py +0 -0
  250. {gea_program-0.26.0 → gea_program-0.28.0}/gea/sla_report.py +0 -0
  251. {gea_program-0.26.0 → gea_program-0.28.0}/gea/slip.py +0 -0
  252. {gea_program-0.26.0 → gea_program-0.28.0}/gea/soak.py +0 -0
  253. {gea_program-0.26.0 → gea_program-0.28.0}/gea/store_forward.py +0 -0
  254. {gea_program-0.26.0 → gea_program-0.28.0}/gea/strata_join.py +0 -0
  255. {gea_program-0.26.0 → gea_program-0.28.0}/gea/structure.py +0 -0
  256. {gea_program-0.26.0 → gea_program-0.28.0}/gea/supervisor.py +0 -0
  257. {gea_program-0.26.0 → gea_program-0.28.0}/gea/survey_cmd.py +0 -0
  258. {gea_program-0.26.0 → gea_program-0.28.0}/gea/survey_view.py +0 -0
  259. {gea_program-0.26.0 → gea_program-0.28.0}/gea/telemetry.py +0 -0
  260. {gea_program-0.26.0 → gea_program-0.28.0}/gea/tool_library.py +0 -0
  261. {gea_program-0.26.0 → gea_program-0.28.0}/gea/transient.py +0 -0
  262. {gea_program-0.26.0 → gea_program-0.28.0}/gea/vibration.py +0 -0
  263. {gea_program-0.26.0 → gea_program-0.28.0}/gea/well_assembler.py +0 -0
  264. {gea_program-0.26.0 → gea_program-0.28.0}/gea/well_test_validation.py +0 -0
  265. {gea_program-0.26.0 → gea_program-0.28.0}/gea/wits0.py +0 -0
  266. {gea_program-0.26.0 → gea_program-0.28.0}/gea/witsml.py +0 -0
  267. {gea_program-0.26.0 → gea_program-0.28.0}/gea/workspace.py +0 -0
  268. {gea_program-0.26.0 → gea_program-0.28.0}/gea_program.egg-info/dependency_links.txt +0 -0
  269. {gea_program-0.26.0 → gea_program-0.28.0}/gea_program.egg-info/entry_points.txt +0 -0
  270. {gea_program-0.26.0 → gea_program-0.28.0}/gea_program.egg-info/requires.txt +0 -0
  271. {gea_program-0.26.0 → gea_program-0.28.0}/gea_program.egg-info/top_level.txt +0 -0
  272. {gea_program-0.26.0 → gea_program-0.28.0}/setup.cfg +0 -0
  273. {gea_program-0.26.0 → gea_program-0.28.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.26.0
3
+ Version: 0.28.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 (365 checks)
128
+ gea accept # the product gate (368 checks)
129
129
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
130
130
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
131
131
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -648,6 +648,30 @@ 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
+ ### Tools and ports
652
+
653
+ The Tools & ports tab (`#/tools`, `gea tools`) is the program's two
654
+ registers in one place: the tool library - every downhole and surface tool
655
+ with its source citation, ratings, what it measures, the telemetry interface
656
+ it declares and its datasheet aging rate where the vendor published one -
657
+ and the ports, the file formats read and the eleven live protocols spoken,
658
+ each with what it speaks to, what the site must supply and what to rehearse
659
+ against. Tools whose declared interface no port in this build speaks are
660
+ listed by name. The rating check hangs a tool string in a well and reports
661
+ each station's tool against the conditions there, with the basis stated:
662
+ `gea tools --rating --station 16000:quartz_pt_geoq177_16k` exits 1 when a
663
+ station is over a rating. `gea help tools`.
664
+
665
+ ### ComCat
666
+
667
+ The seismicity response plan reads a TexNet export; it reads the FDSN text
668
+ export of USGS ComCat - or any FDSN event service - the same way, with the
669
+ contributor and magnitude type on every event. `gea comcat --lat --lon
670
+ --radius-km --start --end` prints the query to open; `gea comcat --file`
671
+ reads the answer; `sra-report --catalog` takes it. Proven on two verbatim
672
+ public-domain extracts (the Delaware Basin, September 2026; the 2011
673
+ Youngstown sequence with its M 4.0), kept as fixtures with their provenance.
674
+
651
675
  ### Slip tendency on the mapped faults
652
676
 
653
677
  The stress state the site declares - the three gradients and the ambient
@@ -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 (365 checks)
89
+ gea accept # the product gate (368 checks)
90
90
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
91
91
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
92
92
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -609,6 +609,30 @@ 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
+ ### Tools and ports
613
+
614
+ The Tools & ports tab (`#/tools`, `gea tools`) is the program's two
615
+ registers in one place: the tool library - every downhole and surface tool
616
+ with its source citation, ratings, what it measures, the telemetry interface
617
+ it declares and its datasheet aging rate where the vendor published one -
618
+ and the ports, the file formats read and the eleven live protocols spoken,
619
+ each with what it speaks to, what the site must supply and what to rehearse
620
+ against. Tools whose declared interface no port in this build speaks are
621
+ listed by name. The rating check hangs a tool string in a well and reports
622
+ each station's tool against the conditions there, with the basis stated:
623
+ `gea tools --rating --station 16000:quartz_pt_geoq177_16k` exits 1 when a
624
+ station is over a rating. `gea help tools`.
625
+
626
+ ### ComCat
627
+
628
+ The seismicity response plan reads a TexNet export; it reads the FDSN text
629
+ export of USGS ComCat - or any FDSN event service - the same way, with the
630
+ contributor and magnitude type on every event. `gea comcat --lat --lon
631
+ --radius-km --start --end` prints the query to open; `gea comcat --file`
632
+ reads the answer; `sra-report --catalog` takes it. Proven on two verbatim
633
+ public-domain extracts (the Delaware Basin, September 2026; the 2011
634
+ Youngstown sequence with its M 4.0), kept as fixtures with their provenance.
635
+
612
636
  ### Slip tendency on the mapped faults
613
637
 
614
638
  The stress state the site declares - the three gradients and the ambient
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.26.0"
126
+ __version__ = "0.28.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -278,6 +278,18 @@ def main(argv=None) -> int:
278
278
  p_lp.add_argument("--stream-csv", type=str, default=None, help="also write a historian-style CSV the other commands ingest")
279
279
  p_lp.add_argument("--selftest", action="store_true", help="etp: the codec round-trip and a session against the simulated store on the loopback")
280
280
 
281
+ p_tl = sub.add_parser("tools", help="the tool library and the port registry: every tool with its citation, ratings and the interface it declares; every file format and live protocol; --rating checks a tool string")
282
+ p_tl.add_argument("--json", action="store_true")
283
+ p_tl.add_argument("--list", action="store_true", help="just the tool names")
284
+ p_tl.add_argument("--rating", action="store_true", help="check a tool string: --station <md_ft>:<tool> (repeatable)")
285
+ p_tl.add_argument("--station", type=str, action="append", default=None, help="a station as <measured depth in feet>:<tool name>")
286
+ p_tl.add_argument("--surface-temp-f", type=float, default=75.0); p_tl.add_argument("--temp-gradient-f-ft", type=float, default=0.018)
287
+ p_tl.add_argument("--surface-pressure-psi", type=float, default=14.7); p_tl.add_argument("--pressure-gradient-psi-ft", type=float, default=0.465)
288
+ p_cc = sub.add_parser("comcat", help="USGS ComCat / any FDSN event service: --lat --lon --radius-km --start --end prints the text-format query to open; --file reads the export it returns")
289
+ p_cc.add_argument("--lat", type=float, default=None); p_cc.add_argument("--lon", type=float, default=None); p_cc.add_argument("--radius-km", type=float, default=25.0)
290
+ p_cc.add_argument("--start", type=str, default=None); p_cc.add_argument("--end", type=str, default=None); p_cc.add_argument("--min-mag", type=float, default=1.0)
291
+ p_cc.add_argument("--file", type=str, default=None, help="an FDSN text export to read and summarise")
292
+ p_cc.add_argument("--json", action="store_true")
281
293
  p_sl = sub.add_parser("slip", help="slip tendency on the mapped faults of a layer at a position, from declared gradients: --layer --lat --lon --depth-ft --sv --shmin --shmax (--shmax-azimuth or --wsm)")
282
294
  p_sl.add_argument("--layer", type=str, required=True); p_sl.add_argument("--member", type=str, default=None); p_sl.add_argument("--feature-kind", type=str, choices=["fault", "fold"], default=None)
283
295
  p_sl.add_argument("--lat", type=float, required=True); p_sl.add_argument("--lon", type=float, required=True); p_sl.add_argument("--radius-km", type=float, default=25.0)
@@ -2351,6 +2363,66 @@ def main(argv=None) -> int:
2351
2363
  stop.set()
2352
2364
  print("etp-sim: stopped")
2353
2365
  return 0
2366
+ elif a.cmd == "tools":
2367
+ import json as _json
2368
+ from . import tool_catalog as _TC
2369
+ if a.rating or a.station:
2370
+ stations = []
2371
+ for spec in (a.station or []):
2372
+ if ":" not in spec:
2373
+ raise SystemExit(f"--station takes <measured depth in feet>:<tool name>, got {spec!r}")
2374
+ md, _, nm = spec.partition(":")
2375
+ try:
2376
+ stations.append((float(md), nm.strip()))
2377
+ except ValueError:
2378
+ raise SystemExit(f"--station depth is not a number: {md!r}")
2379
+ try:
2380
+ r = _TC.check_string(stations, surface_temp_F=a.surface_temp_f, temp_gradient_F_per_ft=a.temp_gradient_f_ft,
2381
+ surface_pressure_psi=a.surface_pressure_psi, pressure_gradient_psi_per_ft=a.pressure_gradient_psi_ft)
2382
+ except ValueError as e:
2383
+ raise SystemExit(f"tools: {e}")
2384
+ if a.json:
2385
+ print(_json.dumps(r, indent=1)); return 0 if not r["n_over_rating"] else 1
2386
+ for line in r["lines"]:
2387
+ print(line)
2388
+ return 0 if not r["n_over_rating"] else 1
2389
+ if a.list:
2390
+ for t in _TC.tools():
2391
+ print(t["name"])
2392
+ return 0
2393
+ if a.json:
2394
+ print(_json.dumps(_TC.catalog(), indent=1)); return 0
2395
+ print(_TC.text())
2396
+ return 0
2397
+ elif a.cmd == "comcat":
2398
+ import json as _json
2399
+ from . import sra as _SR
2400
+ from . import geodesy as _GD
2401
+ if a.file:
2402
+ c = _SR.read_catalog(a.file)
2403
+ if c.get("format") != "fdsn-text":
2404
+ print(f"comcat: {a.file} is not an FDSN text export (it read as a {c.get('basis', '')[:60]}...)"); return 1
2405
+ ev = c["events"]
2406
+ if a.lat is not None and a.lon is not None:
2407
+ for e in ev:
2408
+ dm = _GD.geodesic_m(a.lat, a.lon, e["lat"], e["lon"])["distance_m"]
2409
+ e["distance_km"] = round(dm / 1000.0, 2) if dm is not None else None
2410
+ if a.json:
2411
+ print(_json.dumps(c, indent=1, default=str)); return 0
2412
+ big = max(ev, key=lambda e: e["magnitude"]) if ev else None
2413
+ ts = sorted(e["time"] for e in ev)
2414
+ print(f"comcat: {c['n_events']} event(s) ({c['n_skipped']} skipped) from {ts[0][:10] if ts else '-'} to {ts[-1][:10] if ts else '-'}; "
2415
+ f"contributors {', '.join(c['contributors'])}; magnitude types {', '.join(c['magnitude_types'])}")
2416
+ if big:
2417
+ print(f" largest: M {big['magnitude']:.1f} {big['mag_type']} {big['time']} at {big['lat']:.4f}, {big['lon']:.4f}, {big['depth_km']} km - {big['place']} ({big['event_id']})")
2418
+ for e in sorted(ev, key=lambda x: x["time"])[:40]:
2419
+ print(f" {e['time'][:19]} M {e['magnitude']:>4.1f} {e['mag_type']:<6s} {e['lat']:9.4f} {e['lon']:10.4f} {str(e['depth_km']):>7s} km " + (f"{e['distance_km']:7.2f} km " if e.get("distance_km") is not None else "") + f"{e['place']}")
2420
+ return 0
2421
+ if a.lat is None or a.lon is None or not a.start or not a.end:
2422
+ raise SystemExit("comcat needs --lat --lon --start --end (--radius-km, --min-mag), or --file <export>")
2423
+ print(_SR.comcat_url(a.lat, a.lon, a.radius_km, a.start, a.end, a.min_mag))
2424
+ print(" open it in a browser (this machine may not reach the service) and save the text it returns; `gea comcat --file <saved>` reads it, and sra-report --catalog takes it.")
2425
+ return 0
2354
2426
  elif a.cmd == "slip":
2355
2427
  import json as _json
2356
2428
  from . import shapefile as _SF, slip as _SL, friction_priors as _FP
@@ -7274,6 +7274,161 @@ def section_bt_slip(tmp: str) -> None:
7274
7274
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
7275
7275
 
7276
7276
 
7277
+ def section_bu_comcat(tmp: str) -> None:
7278
+ """Section BU - the USGS ComCat / FDSN text export read as a catalogue, proven on two verbatim public-domain
7279
+ extracts kept as fixtures (the Delaware Basin, September 2026; the Youngstown 2011 sequence); the SRA seismicity
7280
+ on the real Youngstown sequence; the query builder; the CLI."""
7281
+ import subprocess as _sp
7282
+ from . import sra as SR
7283
+ from . import geodesy as GD
7284
+ pkg = Path(__file__).parent
7285
+ d = Path(tmp, "bu"); d.mkdir()
7286
+ fx = pkg / "fixtures"
7287
+ de = SR.read_catalog(str(fx / "comcat_delaware_basin_2026-09.txt"))
7288
+ yo = SR.read_catalog(str(fx / "comcat_youngstown_2011-2012.txt"))
7289
+ big = max(yo["events"], key=lambda e: e["magnitude"])
7290
+ url = SR.comcat_url(41.1, -80.68, 25.0, "2011-03-01", "2012-03-01", 1.5)
7291
+ # the Youngstown sequence against an SRA-shaped area around the Northstar 1 site: 10 km, M 2.0 threshold, plan date 2011-12-01
7292
+ sra = SR.define("Youngstown", 41.12, -80.69, 10.0, "2011-12-01", threshold_m=2.0, formation_boundary="base of the Knox")
7293
+ from datetime import datetime as _dt, timezone as _tz
7294
+ se = SR.seismicity(sra, yo, as_of=_dt(2012, 2, 1, tzinfo=_tz.utc))
7295
+ far = next(e for e in yo["events"] if e["event_id"] == "ld60004308")
7296
+ dist_far = GD.geodesic_m(41.12, -80.69, far["lat"], far["lon"])["distance_m"] / 1000.0
7297
+ r_file = _sp.run([sys.executable, "-m", "gea", "comcat", "--file", str(fx / "comcat_youngstown_2011-2012.txt"), "--lat", "41.1", "--lon", "-80.68"], capture_output=True, text=True)
7298
+ r_url = _sp.run([sys.executable, "-m", "gea", "comcat", "--lat", "31.42", "--lon", "-103.5", "--radius-km", "30", "--start", "2026-09-01", "--end", "2026-10-01", "--min-mag", "2.5"], capture_output=True, text=True)
7299
+ r_bad = _sp.run([sys.executable, "-m", "gea", "comcat", "--file", str(pkg / "catalog" / "collingwood_1_28_ks_complete.las")], capture_output=True, text=True)
7300
+ help_md = (pkg / "help" / "sra.md").read_text(encoding="utf-8")
7301
+ c1 = {
7302
+ "delaware": de["format"] == "fdsn-text" and de["n_events"] == 28 and de["n_skipped"] == 0 and de["contributors"] == ["tx", "us"] and de["magnitude_types"] == ["mb_lg", "ml"]
7303
+ and max(e["magnitude"] for e in de["events"]) == 3.4 and de["events"][0]["event_id"] == "tx2026tgiogl" and de["events"][0]["time"] == "2026-09-30T13:35:58Z"
7304
+ and de["events"][0]["depth_km"] == 6.2024 and "mixes scales" in de["basis"] and sum(1 for e in de["events"] if e["contributor"] == "us") == 4,
7305
+ "youngstown": yo["n_events"] == 9 and big["event_id"] == "ld60029101" and big["magnitude"] == 4.0 and big["time"] == "2011-12-31T20:05:01Z" and big["lat"] == 41.1215 and big["lon"] == -80.6843333
7306
+ and big["place"] == "3 km NW of Youngstown, Ohio" and yo["contributors"] == ["ld"],
7307
+ "seismicity": se["n_inside"] == 9 and se["largest"]["event_id"] == "ld60029101" and se["largest"]["magnitude"] == 4.0 and se["n_at_or_above"] == 6 and dist_far < 10.0
7308
+ and se["trigger"]["status"] == "TRIGGERED" and se["trigger"]["respond_by"] == "2012-01-02T20:05:01Z" and se["goal"]["clock_started"] == "2011-12-31",
7309
+ "url": url == "https://earthquake.usgs.gov/fdsnws/event/1/query?format=text&starttime=2011-03-01&endtime=2012-03-01&latitude=41.1000&longitude=-80.6800&maxradiuskm=25&minmagnitude=1.5&orderby=time-asc",
7310
+ "cli": r_file.returncode == 0 and "largest: M 4.0 ml 2011-12-31T20:05:01Z at 41.1215, -80.6843" in r_file.stdout and "1.68 km" in r_file.stdout
7311
+ and r_url.returncode == 0 and "maxradiuskm=30&minmagnitude=2.5" in r_url.stdout and r_bad.returncode == 1 and "not an FDSN text export" in r_bad.stdout,
7312
+ "help_and_fixtures": "gea comcat" in help_md and (fx / "README.md").read_text(encoding="utf-8").count("earthquake.usgs.gov/fdsnws/event/1/query") == 2,
7313
+ }
7314
+ failed1 = [k for k, v in c1.items() if not v]
7315
+ ok(not failed1,
7316
+ "BU1 the FDSN text export read as a catalogue with contributors and magnitude types: the Delaware Basin window of September 2026 (28 events, TexNet "
7317
+ "and NEIC, ML and mb_Lg, largest M 3.4) and the Youngstown 2011 sequence (9 events, the M 4.0 of 2011-12-31 3 km NW of Youngstown); the SRA seismicity "
7318
+ "on the real sequence inside a 10 km area (six at or above M 2.0, the M 4.0 TRIGGERED, the goal clock from 2011-12-31); the query builder; `gea comcat --file` with distances and `--lat --lon --start --end` printing the query; "
7319
+ "a LAS refused; the help and the fixtures' provenance"
7320
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
7321
+
7322
+
7323
+ def section_bv_tools(tmp: str) -> None:
7324
+ """Section BV - the tool library and the port registry as one register: every tool with its citation, ratings,
7325
+ declared interface and published aging rate (or the reason there is none); every file format and live protocol
7326
+ with what the site must supply; the tools no port speaks named; the rating check; the API, the page, the CLI, the help."""
7327
+ import http.cookiejar
7328
+ import subprocess as _sp
7329
+ import urllib.request
7330
+ import urllib.error
7331
+ from . import tool_catalog as TC
7332
+ from . import helplib as H
7333
+ from .ports import PORT_REGISTRY
7334
+ from .tool_library import PARAMETERS_USER_SUPPLIED, TOOL_LIBRARY
7335
+ from .telemetry import TelemetryRecorder, TelemetryConfig
7336
+ from .downhole_engine import DownholeEngine, SimulatorConfig
7337
+ from .workspace import Workspace
7338
+ from .service import Service, Users
7339
+ pkg = Path(__file__).parent
7340
+ d = Path(tmp, "bv"); d.mkdir()
7341
+
7342
+ c = TC.catalog()
7343
+ by = {t["name"]: t for t in c["tools"]}
7344
+ q30, piezo, g6 = by["quartz_pt_geoq177_30k"], by["piezoresistive_pt_class"], by["surface_interface_g6"]
7345
+ rate = TC.check_string([(500.0, "quartz_pt_geoq177_30k"), (16000.0, "quartz_pt_geoq177_16k")])
7346
+ deep = rate["stations"][1]
7347
+ ok_all = TC.check_string([(500.0, "quartz_pt_geoq177_30k")])
7348
+ bad_tool = empty = None
7349
+ try:
7350
+ TC.check_string([(100.0, "no_such_tool")])
7351
+ except ValueError as e:
7352
+ bad_tool = str(e)
7353
+ try:
7354
+ TC.check_string([])
7355
+ except ValueError as e:
7356
+ empty = str(e)
7357
+ c1 = {
7358
+ "tools": c["counts"]["tools"] == len(TOOL_LIBRARY) and c["counts"]["fully_specified"] + c["counts"]["user_supplied"] == len(TOOL_LIBRARY)
7359
+ and all(t["source"] for t in c["tools"]) and q30["aging_rate_pct_fs_yr"] is not None and q30["aging_note"].startswith("the datasheet")
7360
+ and q30["temp_rating_C"] == 177.0 and q30["pressure_rating_psi"] == 30000.0 and q30["gauge_spec"]["full_scale_psi"] == 30000.0,
7361
+ "user_supplied_named": piezo["spec_status"] == PARAMETERS_USER_SUPPLIED and piezo["aging_rate_pct_fs_yr"] is None
7362
+ and "does not invent vendor numbers" in piezo["aging_note"] and g6["measures"] == [],
7363
+ "ports": c["counts"]["file_ports"] == len(PORT_REGISTRY) and {p["name"] for p in c["ports"]["live"]} >= {"wits0", "witsml", "etp", "opcua", "mqtt", "modbus_g6", "modbus_rtu", "dnp3", "hartip", "piwebapi", "seedlink"}
7364
+ and all(p["needs"] and p["speaks"] and p["rehearse"] for p in c["ports"]["live"]) and any(p["status"] == "IMPLEMENTED" for p in c["ports"]["files"])
7365
+ and any(p["name"] == "dlis" for p in c["ports"]["files"]),
7366
+ "interfaces": "modbus_rtu" in q30["ports"] and not piezo["ports"] and {u["tool"] for u in c["unmatched"]} == {t["name"] for t in c["tools"] if not t["ports"]}
7367
+ and "piezoresistive_pt_class" in {u["tool"] for u in c["unmatched"]},
7368
+ "rating": rate["n_over_rating"] == 1 and deep["over_temp_rating"] and not deep["over_pressure_rating"] and abs(deep["station_temp_C"] - 183.9) < 0.2
7369
+ and "OVER its 177 C rating" in rate["lines"][2] and "declared gradients" in rate["basis"] and "no survey given" in rate["deviation"]
7370
+ and ok_all["n_over_rating"] == 0 and bad_tool and "not in the tool library" in bad_tool and empty and "at least one station" in empty,
7371
+ }
7372
+ failed1 = [k for k, v in c1.items() if not v]
7373
+ ok(not failed1,
7374
+ f"BV1 the register: all {len(TOOL_LIBRARY)} tools with their citations, the quartz gauge's datasheet aging rate and ratings, the piezoresistive "
7375
+ "class carrying no rate and saying why, the interface card measuring nothing; every file port of the registry and all eleven live protocols with "
7376
+ "what each needs from the site and what to rehearse against; the tools no port in this build speaks named; the rating check flagging a 177 C gauge "
7377
+ "at 16,000 ft and clearing a shallow one, an unknown tool and an empty string refused"
7378
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
7379
+
7380
+ hist = str(d / "hist.csv")
7381
+ TelemetryRecorder(engine=DownholeEngine(SimulatorConfig()), config=TelemetryConfig(duration_hours=0.5, seed=3)).run().export_csv(hist)
7382
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
7383
+ ws.add_well_file(hist, display="SWD 1", actor="tester")
7384
+ users = Users(str(Path(wsp, "users.json"))); users.add("v", "viewer-pass-1", "viewer")
7385
+ svc = Service(wsp, port=0, scheduler=False).start()
7386
+ base_url = svc.url.rstrip("/")
7387
+ opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
7388
+
7389
+ def call(method, path, body=None):
7390
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
7391
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
7392
+ try:
7393
+ with opener.open(req, timeout=120) as rr_:
7394
+ return rr_.status, json.loads(rr_.read())
7395
+ except urllib.error.HTTPError as e:
7396
+ return e.code, json.loads(e.read())
7397
+ try:
7398
+ st_anon = call("GET", "/api/tools")[0]
7399
+ call("POST", "/api/login", {"name": "v", "password": "viewer-pass-1"})
7400
+ st_g, got = call("GET", "/api/tools")
7401
+ st_r, rr = call("POST", "/api/tools/rating", {"stations": [{"md_ft": 500, "tool": "quartz_pt_geoq177_30k"}, {"md_ft": 16000, "tool": "quartz_pt_geoq177_16k"}]})
7402
+ st_bad = call("POST", "/api/tools/rating", {"stations": [{"md_ft": 100, "tool": "nope"}]})[0]
7403
+ st_bad2 = call("POST", "/api/tools/rating", {"stations": [{"tool": "quartz_pt_geoq177_30k"}]})[0]
7404
+ finally:
7405
+ svc.stop()
7406
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
7407
+ help_md = (pkg / "help" / "tools.md").read_text(encoding="utf-8")
7408
+ r_txt = _sp.run([sys.executable, "-m", "gea", "tools"], capture_output=True, text=True)
7409
+ r_list = _sp.run([sys.executable, "-m", "gea", "tools", "--list"], capture_output=True, text=True)
7410
+ r_json = _sp.run([sys.executable, "-m", "gea", "tools", "--json"], capture_output=True, text=True)
7411
+ r_rate = _sp.run([sys.executable, "-m", "gea", "tools", "--rating", "--station", "500:quartz_pt_geoq177_30k", "--station", "16000:quartz_pt_geoq177_16k"], capture_output=True, text=True)
7412
+ r_badst = _sp.run([sys.executable, "-m", "gea", "tools", "--rating", "--station", "500"], capture_output=True, text=True)
7413
+ c2 = {
7414
+ "api": st_anon == 401 and st_g == 200 and got["counts"]["tools"] == len(TOOL_LIBRARY) and st_r == 200 and rr["n_over_rating"] == 1
7415
+ and rr["stations"][1]["over_temp_rating"] and st_bad == 400 and st_bad2 == 400,
7416
+ "page": "VIEWS.tools" in page_src and "'#/tools', 'Tools & ports'" in page_src and "/api/tools/rating" in page_src
7417
+ and "Tools no live port in this build speaks" in page_src and "Rating check" in page_src,
7418
+ "cli": r_txt.returncode == 0 and "file ports:" in r_txt.stdout and "live ports:" in r_txt.stdout and "does not invent vendor numbers" in r_txt.stdout
7419
+ and r_list.returncode == 0 and r_list.stdout.split() == sorted(TOOL_LIBRARY) and r_json.returncode == 0 and json.loads(r_json.stdout)["counts"]["live_ports"] == 11
7420
+ and r_rate.returncode == 1 and "OVER its 177 C rating" in r_rate.stdout and r_badst.returncode != 0 and "<measured depth in feet>:<tool name>" in (r_badst.stderr + r_badst.stdout),
7421
+ "help": any(t["topic"] == "tools" for t in H.topics()) and not H.check("tools") and H.VIEW_TOPIC.get("tools") == "tools"
7422
+ and "not a promise the tool survives" in help_md.split("will not call a measurement")[1],
7423
+ }
7424
+ failed2 = [k for k, v in c2.items() if not v]
7425
+ ok(not failed2,
7426
+ "BV2 the Tools & ports tab: the API refuses an anonymous caller and answers a viewer, runs the rating check and refuses an unknown tool and a "
7427
+ "station with no depth; the page carries the tab, the unmatched-tools table and the rating-check form; `gea tools`, `--list`, `--json` and "
7428
+ "`--rating --station` (exit 1 when a station is over a rating, and a malformed station named); the help page and its view mapping"
7429
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
7430
+
7431
+
7277
7432
  def main() -> int:
7278
7433
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
7279
7434
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -7349,6 +7504,8 @@ def main() -> int:
7349
7504
  section_br_logs_historian(tmp)
7350
7505
  section_bs_catalog(tmp)
7351
7506
  section_bt_slip(tmp)
7507
+ section_bu_comcat(tmp)
7508
+ section_bv_tools(tmp)
7352
7509
  if _FAILS:
7353
7510
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
7354
7511
  for f in _FAILS:
@@ -0,0 +1,10 @@
1
+ # fixtures
2
+
3
+ Small, verbatim, public-domain extracts kept so the gate can prove a reader on real records without a network.
4
+
5
+ - `comcat_delaware_basin_2026-09.txt` - USGS ComCat, FDSN event web service text output, fetched 2026-10-12:
6
+ `https://earthquake.usgs.gov/fdsnws/event/1/query?format=text&starttime=2026-09-01&endtime=2026-10-01&minlatitude=30.5&maxlatitude=32.5&minlongitude=-104.5&maxlongitude=-102.0&minmagnitude=2.5&orderby=time`
7
+ (28 events M >= 2.5, Delaware and Midland basins; contributors tx = TexNet, us = NEIC). US Government work, public domain.
8
+ - `comcat_youngstown_2011-2012.txt` - the same service, fetched 2026-10-12:
9
+ `https://earthquake.usgs.gov/fdsnws/event/1/query?format=text&starttime=2011-03-01&endtime=2012-03-01&latitude=41.1&longitude=-80.68&maxradiuskm=25&minmagnitude=1.5&orderby=time-asc`
10
+ (9 events within 25 km of Youngstown, Ohio: the 2011 sequence that ended with the M 4.0 of 2011-12-31; contributor ld = Lamont-Doherty). Public domain.
@@ -0,0 +1,29 @@
1
+ #EventID|Time|Latitude|Longitude|Depth/km|Author|Catalog|Contributor|ContributorID|MagType|Magnitude|MagAuthor|EventLocationName
2
+ tx2026tgiogl|2026-09-30T13:35:58.370|31.618|-104.363|6.2024|tx|tx|tx|tx2026tgiogl|ml|2.9|tx|61 km S of Whites City, New Mexico
3
+ tx2026tfzcgy|2026-09-30T08:50:13.448|31.624|-104.364|6.9714|tx|tx|tx|tx2026tfzcgy|ml|3.3|tx|61 km S of Whites City, New Mexico
4
+ tx2026tbppja|2026-09-27T23:45:52.397|31.617|-104.312|8.4583|tx|tx|tx|tx2026tbppja|ml|2.5|tx|59 km NW of Toyah, Texas
5
+ tx2026sxmsve|2026-09-25T18:01:21.042|31.379|-103.705|6.228|tx|tx|tx|tx2026sxmsve|ml|2.6|tx|11 km NE of Toyah, Texas
6
+ tx2026svkhwu|2026-09-24T14:39:26.649|30.831|-103.394|3.3569|tx|tx|tx|tx2026svkhwu|ml|2.9|tx|37 km ESE of Balmorhea, Texas
7
+ tx2026ssvrgl|2026-09-23T05:08:42.441|32.401|-102.046|7.1533|tx|tx|tx|tx2026ssvrgl|ml|3|tx|34 km WSW of Ackerly, Texas
8
+ tx2026sspzfy|2026-09-23T02:16:59.235|32.436|-102.098|6.958|tx|tx|tx|tx2026sspzfy|ml|2.8|tx|35 km SSW of Los Ybanez, Texas
9
+ tx2026srxfgq|2026-09-22T16:50:40.191|31.668|-104.402|6.0999|tx|tx|tx|tx2026srxfgq|ml|2.6|tx|56 km S of Whites City, New Mexico
10
+ tx2026srvtwi|2026-09-22T16:07:21.955|31.605|-103.933|7.7533|tx|tx|tx|tx2026srvtwi|ml|2.5|tx|33 km WSW of Mentone, Texas
11
+ tx2026srfrih|2026-09-22T08:04:38.331|31.666|-104.25|6.0999|tx|tx|tx|tx2026srfrih|ml|3.1|tx|57 km SSE of Whites City, New Mexico
12
+ us7000tisq|2026-09-20T19:23:14.330|32.2823|-103.7703|7.6|us|us|us|us7000tisq|mb_lg|2.9|us|29 km ENE of Malaga, New Mexico
13
+ tx2026sngzur|2026-09-20T04:23:40.180|31.668|-104.362|7.1765|tx|tx|tx|tx2026sngzur|ml|3.2|tx|56 km S of Whites City, New Mexico
14
+ tx2026sipbbo|2026-09-17T15:04:16.188|32.074|-103.679|7.7533|tx|tx|tx|tx2026sipbbo|ml|2.7|tx|40 km ESE of Malaga, New Mexico
15
+ tx2026siects|2026-09-17T09:34:33.358|31.665|-104.453|6.0999|tx|tx|tx|tx2026siects|ml|2.5|tx|57 km S of Whites City, New Mexico
16
+ tx2026sfaiyw|2026-09-15T16:27:44.534|31.615|-104.113|6.0999|tx|tx|tx|tx2026sfaiyw|ml|2.6|tx|45 km NW of Toyah, Texas
17
+ tx2026selaoc|2026-09-15T08:48:29.775|31.657|-104.135|6.1127|tx|tx|tx|tx2026selaoc|ml|3.3|tx|49 km NW of Toyah, Texas
18
+ tx2026sawssd|2026-09-13T10:20:00.004|31.661|-104.432|6.0999|tx|tx|tx|tx2026sawssd|ml|3.4|tx|57 km S of Whites City, New Mexico
19
+ tx2026sapxnl|2026-09-13T06:54:23.103|31.661|-104.432|6.0999|tx|tx|tx|tx2026sapxnl|ml|2.9|tx|57 km S of Whites City, New Mexico
20
+ tx2026saectr|2026-09-13T00:58:24.306|31.751|-104.336|7.074|tx|tx|tx|tx2026saectr|ml|2.9|tx|47 km S of Whites City, New Mexico
21
+ us7000tgvj|2026-09-12T00:22:12.507|32.2629|-103.2847|7.91|us|us|us|us7000tgvj|mb_lg|2.6|us|18 km NNW of Jal, New Mexico
22
+ tx2026rupmzs|2026-09-10T00:15:33.655|31.64|-104.408|6.3306|tx|tx|tx|tx2026rupmzs|ml|3|tx|59 km S of Whites City, New Mexico
23
+ us7000tg2k|2026-09-09T16:00:21.180|32.3229|-104.4961|3.897|us|us|us|us7000tg2k|ml|2.5|us|19 km NW of Whites City, New Mexico
24
+ tx2026rqcbab|2026-09-07T13:11:15.952|32.005|-103.24|6.0999|tx|tx|tx|tx2026rqcbab|ml|2.7|tx|12 km SSW of Jal, New Mexico
25
+ tx2026roctny|2026-09-06T11:24:04.550|31.542|-104.128|8.8428|tx|tx|tx|tx2026roctny|ml|2.5|tx|40 km NW of Toyah, Texas
26
+ us7000tegw|2026-09-05T03:41:56.500|32.3519|-104.4998|5|us|us|us|us7000tegw|ml|2.7|us|22 km NNW of Whites City, New Mexico
27
+ tx2026rliwah|2026-09-04T23:14:17.831|31.667|-104.37|6.9202|tx|tx|tx|tx2026rliwah|ml|2.5|tx|56 km S of Whites City, New Mexico
28
+ tx2026rjxsxm|2026-09-04T04:35:25.328|31.542|-104.129|8.5095|tx|tx|tx|tx2026rjxsxm|ml|3.1|tx|40 km NW of Toyah, Texas
29
+ tx2026rjmsif|2026-09-03T23:02:00.297|31.656|-104.387|6.0999|tx|tx|tx|tx2026rjmsif|ml|2.5|tx|57 km S of Whites City, New Mexico
@@ -0,0 +1,10 @@
1
+ #EventID|Time|Latitude|Longitude|Depth/km|Author|Catalog|Contributor|ContributorID|MagType|Magnitude|MagAuthor|EventLocationName
2
+ ld60004308|2011-03-17T10:53:11.670|41.126|-80.6056667|3.54|ld|ld|ld|ld60004308|ml|2.1|ld|2 km WSW of Maplewood Park, Ohio
3
+ ld60007303|2011-08-22T08:00:32.640|41.1303333|-80.7081667|5|ld|ld|ld|ld60007303|ml|1.87|ld|2 km SSW of Girard, Ohio
4
+ ld60009603|2011-09-26T01:06:10.840|41.115|-80.6823333|5|ld|ld|ld|ld60009603|ml|2.12|ld|3 km WNW of Youngstown, Ohio
5
+ ld60009803|2011-09-30T00:52:38.710|41.1346667|-80.6845|5|ld|ld|ld|ld60009803|ml|2.44|ld|2 km SSE of Girard, Ohio
6
+ ld60010908|2011-10-20T22:41:10.820|41.1253333|-80.704|6.39|ld|ld|ld|ld60010908|ml|2.04|ld|3 km S of Girard, Ohio
7
+ ld60012603|2011-11-25T06:47:28.250|41.124|-80.6771667|5|ld|ld|ld|ld60012603|ml|1.79|ld|3 km NW of Youngstown, Ohio
8
+ ld60013703|2011-12-24T06:24:58.810|41.1225|-80.7046667|5|ld|ld|ld|ld60013703|ml|2.37|ld|3 km S of Girard, Ohio
9
+ ld60029101|2011-12-31T20:05:01.060|41.1215|-80.6843333|5|ld|ld|ld|ld60029101|ml|4|ld|3 km NW of Youngstown, Ohio
10
+ ld60014118|2012-01-13T22:29:35.120|41.1245|-80.6923333|5.79|ld|ld|ld|ld60014118|ml|1.74|ld|3 km SSE of Girard, Ohio
@@ -8,7 +8,7 @@
8
8
 
9
9
  **The number to check**: `Response:` in section "Seismicity against the plan" - TRIGGERED names the event, its magnitude and the deadline for the response group's meeting; NOT TRIGGERED names why. `Goal:` gives the days without a threshold event and the date the plan's goal falls on. The packet's front says COMPLETE or INCOMPLETE with the count of gaps.
10
10
 
11
- **What this page will not call a measurement**: which earthquakes are aftershocks - the operator declares them with `--aftershocks` and they are shown as exempt, never decided here. A volume read off a pressure gauge - a well with no rate channel has no volume and no maximum rate, and the packet says NOT RECORDED. A depth tier from depth alone - the tier is stratigraphic, above or below the named formation base, and is declared. The TexNet reporting tool's own upload template - the export carries the Notice's parameter names and the mapping is the operator's step. A catalogue nobody handed in - the catalogue is a file the operator exports from TexNet, and its export date is on the packet.
11
+ **What this page will not call a measurement**: which earthquakes are aftershocks - the operator declares them with `--aftershocks` and they are shown as exempt, never decided here. A volume read off a pressure gauge - a well with no rate channel has no volume and no maximum rate, and the packet says NOT RECORDED. A depth tier from depth alone - the tier is stratigraphic, above or below the named formation base, and is declared. The TexNet reporting tool's own upload template - the export carries the Notice's parameter names and the mapping is the operator's step. A catalogue nobody handed in - the catalogue is a file the operator exports from TexNet, or the FDSN text export of USGS ComCat (`gea comcat --lat --lon --radius-km --start --end` prints the query; the file it returns reads directly, with its contributors and magnitude types on the packet), and its export date is on the packet.
12
12
 
13
13
  The shape is the Commission's: the operator-led response plans on file (Gardendale, Northern Culberson-Reeves, Stanton) - a circle of about 9 km, a plan written against M 3.5, an 18-month goal, a response group meeting within 48 hours of a threshold event, quarterly checkpoints with Commission staff, and disposal wells in two tiers by the base of the Wolfcamp - and the December 2023 Notice to Operators on disposal-well monitoring and reporting in the Permian Basin, which names the four daily parameters and the three bottomhole-pressure methods. A downhole gauge this program monitors is the Notice's permanent probe.
14
14
 
@@ -0,0 +1,11 @@
1
+ # Tools and ports
2
+
3
+ > What this program knows how to measure with, and what it knows how to speak. The tool library is the downhole and surface tools - each with the citation its numbers came from, the conditions it is rated for, and the telemetry interface it declares; the port registry is the file formats and live protocols that bring a tool's readings in. The page draws the line between them: a tool declares an interface, a port speaks one, and a tool whose interface no port in this build speaks is named rather than hidden.
4
+
5
+ **The command**: the page is Tools & ports on the dashboard; `gea tools` prints the same register at the command line (`--json` for the machine record, `--rating --station <ft>:<tool> ...` for the rating check). A tool whose vendor published no numbers is listed as PARAMETERS_USER_SUPPLIED: `gea config` takes your own datasheet (`load_gauge_spec_json`) and the tool is then simulated and aged from it. The live protocols each name what the site must supply before they run and what to rehearse against - `gea wits0-sim`, `gea etp-sim`, `gea seedlink-sim`, or the stand-in slave, outstation, device and historian the field ports ship with.
6
+
7
+ **What it writes**: nothing to the workspace - this page is a register, not a record. It states, per tool: the class, what it measures, the temperature and pressure ratings, the published aging rate in %FS/yr where the datasheet gives one (and the reason there is none where it does not), the interface it declares, the ports that can carry it, and its source. Per port: the transport, the status (IMPLEMENTED, or DECLARED_SITE_DETAILS_REQUIRED with what is missing), and what it speaks to. The rating check returns, per station, the temperature and pressure at that depth, the tool's ratings and whether the station is within them, with the basis of the conditions printed beside the answer.
8
+
9
+ **The number to check**: the aging rate against your own datasheet before you trust a drift report built on it, and the rating check's per-station temperature against the tool's rating - the check that catches a 177 °C gauge hung in a 233 °C zone before the well does.
10
+
11
+ **What this page will not call a measurement**: a vendor number nobody published - a tool with no datasheet rate carries none and raises rather than inventing one, and the page says PARAMETERS_USER_SUPPLIED. A rating as a prediction - a station within its ratings is arithmetic on the vendor's number and the declared conditions, not a promise the tool survives. The conditions themselves - without a well profile they are the gradients declared on the page, and the answer prints which basis it used. A port as a connection - a port listed here speaks a protocol; whether the site's own device answers is what the Patch panel shows.
@@ -42,6 +42,7 @@ INDEX: List[tuple] = [
42
42
  ('catalog', 'The catalogue', 'every shipped public dataset, what in this program reads it or that nothing does; the measured friction envelope; the core check; any log through the survey'),
43
43
  ('dlis', 'The DLIS log', 'a well log as it is delivered (API RP66 V1), read as the standard lays it out: the frames, the channels, what was skipped; into the strata survey'),
44
44
  ('historian', 'The historian pull', 'an operator\'s gauge history out of the PI System through PI Web API, read-only, every value with its archive time and flags'),
45
+ ('tools', 'Tools and ports', 'the downhole and surface tools this program knows, each with its citation and ratings, the formats and protocols that bring their readings in, and the rating check on a tool string'),
45
46
  ('field-ports', 'The field ports', 'Modbus RTU on the serial line, DNP3 from the pad RTU, HART-IP from the transmitter itself - read-only, polled, with the stand-ins to rehearse against'),
46
47
  ('slip', 'Slip tendency on the mapped faults', 'the declared stress state resolved on each mapped fault around a well: the slip tendency, the pressure to slip, the World Stress Map as the SHmax source, the published Hennings values beside'),
47
48
  ('interval', 'The interval against the mapped tops', 'the declared disposal interval, subsea, beside each formation-top bracket; the depth tier against the mapped base; the catalogue events against the mapped faults'),
@@ -66,7 +67,7 @@ REQUIRED_LINES = ('**The command**', '**What it writes**', '**The number to chec
66
67
  VIEW_TOPIC: Dict[str, str] = {
67
68
  'home': 'start', 'wells': 'data-in', 'well': 'drift', 'live': 'patches', 'patch': 'patches', 'files': 'files', 'alarms': 'alarms',
68
69
  'approvals': 'well-tests', 'config': 'site', 'reports': 'drift', 'verify': 'start', 'survey': 'data-in', 'jobs': 'site',
69
- 'admin': 'site', 'prefs': 'site', 'search': 'start', 'welcome': 'start', 'seismic': 'seismic', 'seismic_station': 'seismic', 'track': 'seismic', 'sar': 'seismic', 'sites': 'sites', 'audit': 'audit-update',
70
+ 'admin': 'site', 'prefs': 'site', 'search': 'start', 'welcome': 'start', 'seismic': 'seismic', 'seismic_station': 'seismic', 'track': 'seismic', 'sar': 'seismic', 'sites': 'sites', 'audit': 'audit-update', 'tools': 'tools',
70
71
  }
71
72
 
72
73
 
@@ -1637,6 +1637,9 @@ def make_handler(app: App):
1637
1637
  raise ApiError(404, str(e))
1638
1638
  if path == '/api/stress':
1639
1639
  return self._json(200, {'stress': app.ws.stress(), 'stress_map': app.ws.stress_map()})
1640
+ if path == '/api/tools':
1641
+ from . import tool_catalog as _TC
1642
+ return self._json(200, _TC.catalog())
1640
1643
  if path == '/api/stress/wsm':
1641
1644
  try:
1642
1645
  lat, lon = float(qs['lat'][0]), float(qs['lon'][0]); rad = float(qs.get('radius_km', ['100'])[0])
@@ -1839,6 +1842,22 @@ def make_handler(app: App):
1839
1842
  depth_ft=(float(b['depth_ft']) if b.get('depth_ft') is not None else None), actor=user['name']))
1840
1843
  except WorkspaceError as e:
1841
1844
  raise ApiError(404, str(e))
1845
+ if path == '/api/tools/rating':
1846
+ from . import tool_catalog as _TC
1847
+ b = body or {}
1848
+ try:
1849
+ stations = [(float(x['md_ft']), str(x['tool'])) for x in (b.get('stations') or [])]
1850
+ except (KeyError, TypeError, ValueError):
1851
+ raise ApiError(400, 'each station needs md_ft and tool')
1852
+ try: # the conditions are the declared gradients, and the answer says so
1853
+ return self._json(200, _TC.check_string(stations, profile=None, deviation=None,
1854
+ surface_temp_F=float(b.get('surface_temp_F', 75.0)),
1855
+ temp_gradient_F_per_ft=float(b.get('temp_gradient_F_per_ft', 0.018)),
1856
+ surface_pressure_psi=float(b.get('surface_pressure_psi', 14.7)),
1857
+ pressure_gradient_psi_per_ft=float(b.get('pressure_gradient_psi_per_ft', 0.465)),
1858
+ name=str(b.get('name', 'toolstring'))))
1859
+ except (ValueError, KeyError) as e:
1860
+ raise ApiError(400, str(e))
1842
1861
  if path == '/api/stress':
1843
1862
  App.require(user, 'operator')
1844
1863
  try:
@@ -340,6 +340,10 @@ def read_catalog(path: str, mapping: Optional[Dict[str, str]] = None) -> dict:
340
340
  """A TexNet earthquake-catalogue export (CSV). The columns are found by name; a mapping wins. Every event
341
341
  keeps its own id so a claim in the packet can be traced to the catalogue line it came from."""
342
342
  from .permits import read_table, _float
343
+ with open(path, encoding='utf-8-sig', errors='replace') as f:
344
+ first = f.readline()
345
+ if first.startswith('#EventID|') or first.startswith('EventID|Time|'):
346
+ return read_fdsn_text(path)
343
347
  header, rows = read_table(path)
344
348
  cols = _detect(header, mapping)
345
349
  events, skipped = [], 0
@@ -364,6 +368,45 @@ def read_catalog(path: str, mapping: Optional[Dict[str, str]] = None) -> dict:
364
368
  'datum': 'WGS84', 'basis': 'the TexNet earthquake catalogue as exported by the operator; positions WGS84, magnitudes ML'}
365
369
 
366
370
 
371
+ def read_fdsn_text(path: str) -> dict:
372
+ """The FDSN event web service's text format - USGS ComCat (earthquake.usgs.gov/fdsnws/event/1/query?format=text), and every
373
+ FDSN-compliant catalogue service: `#EventID|Time|Latitude|Longitude|Depth/km|Author|Catalog|Contributor|ContributorID|MagType|
374
+ Magnitude|MagAuthor|EventLocationName`. The contributor (tx = TexNet, us = NEIC, ld = Lamont-Doherty) and the magnitude type ride
375
+ on each event, since a ComCat window mixes networks and magnitude scales."""
376
+ from .permits import _float
377
+ events, skipped, n_rows = [], 0, 0
378
+ with open(path, encoding='utf-8-sig', errors='replace') as f:
379
+ header = [h.strip().lstrip('#') for h in f.readline().rstrip('\n').split('|')]
380
+ idx = {h.lower(): i for i, h in enumerate(header)}
381
+ for line in f:
382
+ if not line.strip() or line.startswith('#'):
383
+ continue
384
+ n_rows += 1
385
+ parts = line.rstrip('\n').split('|')
386
+ def g(name):
387
+ i = idx.get(name)
388
+ return parts[i].strip() if i is not None and i < len(parts) else ''
389
+ t = _parse_dt(g('time').replace('T', ' ').split('.')[0]) if g('time') else None
390
+ m, la, lo = _float(g('magnitude')), _float(g('latitude')), _float(g('longitude'))
391
+ if t is None or m is None or la is None or lo is None:
392
+ skipped += 1; continue
393
+ events.append({'event_id': g('eventid') or f'row{n_rows}', 'time': _utc(t), 'magnitude': m, 'lat': la, 'lon': lo, 'depth_km': _float(g('depth/km')),
394
+ 'mag_type': g('magtype'), 'contributor': g('contributor'), 'catalog': g('catalog'), 'place': g('eventlocationname')})
395
+ contribs = sorted({e['contributor'] for e in events if e['contributor']})
396
+ mtypes = sorted({e['mag_type'] for e in events if e['mag_type']})
397
+ return {'path': os.path.abspath(path), 'export_mtime_utc': _utc(datetime.fromtimestamp(os.path.getmtime(path), timezone.utc)),
398
+ 'columns': {h.lower(): h for h in header}, 'n_rows': n_rows, 'n_events': len(events), 'n_skipped': skipped, 'events': events, 'datum': 'WGS84',
399
+ 'format': 'fdsn-text', 'contributors': contribs, 'magnitude_types': mtypes,
400
+ 'basis': f"an FDSN event-service text export (USGS ComCat or another FDSN catalogue); positions WGS84; contributors {', '.join(contribs) or '?'}; "
401
+ f"magnitude types {', '.join(mtypes) or '?'} - a window across networks mixes scales, and the plan's threshold is read as the catalogue's reported magnitude"}
402
+
403
+
404
+ def comcat_url(lat: float, lon: float, radius_km: float, start: str, end: str, min_magnitude: float = 1.0, fmt: str = 'text') -> str:
405
+ """The ComCat query for a circle and a window - the URL to open in a browser or hand to a machine that can reach the service."""
406
+ return ('https://earthquake.usgs.gov/fdsnws/event/1/query?format=' + fmt + f'&starttime={start}&endtime={end}&latitude={lat:.4f}&longitude={lon:.4f}'
407
+ f'&maxradiuskm={radius_km:g}&minmagnitude={min_magnitude:g}&orderby=time-asc')
408
+
409
+
367
410
  def _detect(header: Sequence[str], mapping: Optional[Dict[str, str]]) -> Dict[str, Optional[str]]:
368
411
  from .permits import _norm
369
412
  norm = {_norm(h): h for h in header}