gea-program 0.12.0__tar.gz → 0.14.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 (236) hide show
  1. {gea_program-0.12.0/gea_program.egg-info → gea_program-0.14.0}/PKG-INFO +37 -2
  2. {gea_program-0.12.0 → gea_program-0.14.0}/README.md +36 -1
  3. {gea_program-0.12.0 → gea_program-0.14.0}/gea/__init__.py +1 -1
  4. {gea_program-0.12.0 → gea_program-0.14.0}/gea/__main__.py +98 -1
  5. {gea_program-0.12.0 → gea_program-0.14.0}/gea/acceptance_tests.py +217 -0
  6. {gea_program-0.12.0 → gea_program-0.14.0}/gea/client_reports.py +75 -0
  7. gea_program-0.14.0/gea/help/ppdm.md +11 -0
  8. gea_program-0.14.0/gea/help/vibration.md +11 -0
  9. {gea_program-0.12.0 → gea_program-0.14.0}/gea/helplib.py +2 -0
  10. {gea_program-0.12.0 → gea_program-0.14.0}/gea/osdu.py +18 -3
  11. gea_program-0.14.0/gea/ppdm.py +291 -0
  12. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic.py +16 -4
  13. {gea_program-0.12.0 → gea_program-0.14.0}/gea/sra.py +19 -2
  14. gea_program-0.14.0/gea/vibration.py +463 -0
  15. {gea_program-0.12.0 → gea_program-0.14.0}/gea/workspace.py +45 -1
  16. {gea_program-0.12.0 → gea_program-0.14.0/gea_program.egg-info}/PKG-INFO +37 -2
  17. {gea_program-0.12.0 → gea_program-0.14.0}/gea_program.egg-info/SOURCES.txt +4 -0
  18. {gea_program-0.12.0 → gea_program-0.14.0}/pyproject.toml +1 -1
  19. {gea_program-0.12.0 → gea_program-0.14.0}/LICENSE +0 -0
  20. {gea_program-0.12.0 → gea_program-0.14.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  21. {gea_program-0.12.0 → gea_program-0.14.0}/gea/accuracy_statement.py +0 -0
  22. {gea_program-0.12.0 → gea_program-0.14.0}/gea/alarm_engine.py +0 -0
  23. {gea_program-0.12.0 → gea_program-0.14.0}/gea/bench.py +0 -0
  24. {gea_program-0.12.0 → gea_program-0.14.0}/gea/blind_harness.py +0 -0
  25. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  26. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  27. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  28. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  29. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  30. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  31. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  32. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  33. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  34. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  35. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  36. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  37. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  38. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  39. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  40. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  41. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  42. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  43. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  44. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  45. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  46. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  47. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  48. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  49. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  50. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  51. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  52. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  53. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  54. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  55. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  56. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  57. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  58. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  59. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  60. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  61. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  62. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  63. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  64. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  65. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  66. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  67. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  68. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  69. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  70. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  71. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  72. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  73. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  74. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  75. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  76. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  77. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  78. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  79. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  80. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  81. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  82. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  83. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  84. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  85. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  86. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  87. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  88. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  89. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  90. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  91. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  92. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/med_160_sapropels.txt +0 -0
  93. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  94. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  95. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  96. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  97. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  98. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  99. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  100. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  101. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  102. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  103. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  104. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  105. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  106. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  107. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  108. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  109. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  110. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  111. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  112. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  113. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  114. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  115. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  116. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  117. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  118. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  119. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  120. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  121. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  122. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  123. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  124. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  125. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  126. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  127. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  128. {gea_program-0.12.0 → gea_program-0.14.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  129. {gea_program-0.12.0 → gea_program-0.14.0}/gea/certificates.py +0 -0
  130. {gea_program-0.12.0 → gea_program-0.14.0}/gea/cli.py +0 -0
  131. {gea_program-0.12.0 → gea_program-0.14.0}/gea/config_versioning.py +0 -0
  132. {gea_program-0.12.0 → gea_program-0.14.0}/gea/correlation.py +0 -0
  133. {gea_program-0.12.0 → gea_program-0.14.0}/gea/dashboard.py +0 -0
  134. {gea_program-0.12.0 → gea_program-0.14.0}/gea/deviation.py +0 -0
  135. {gea_program-0.12.0 → gea_program-0.14.0}/gea/doctor.py +0 -0
  136. {gea_program-0.12.0 → gea_program-0.14.0}/gea/downhole_engine.py +0 -0
  137. {gea_program-0.12.0 → gea_program-0.14.0}/gea/drift_monitor.py +0 -0
  138. {gea_program-0.12.0 → gea_program-0.14.0}/gea/earth_model.py +0 -0
  139. {gea_program-0.12.0 → gea_program-0.14.0}/gea/example_register_map.json +0 -0
  140. {gea_program-0.12.0 → gea_program-0.14.0}/gea/fat_sat.py +0 -0
  141. {gea_program-0.12.0 → gea_program-0.14.0}/gea/files.py +0 -0
  142. {gea_program-0.12.0 → gea_program-0.14.0}/gea/follower.py +0 -0
  143. {gea_program-0.12.0 → gea_program-0.14.0}/gea/forward_model.py +0 -0
  144. {gea_program-0.12.0 → gea_program-0.14.0}/gea/gamma.py +0 -0
  145. {gea_program-0.12.0 → gea_program-0.14.0}/gea/gauge_aging.py +0 -0
  146. {gea_program-0.12.0 → gea_program-0.14.0}/gea/gauge_specs.py +0 -0
  147. {gea_program-0.12.0 → gea_program-0.14.0}/gea/geodesy.py +0 -0
  148. {gea_program-0.12.0 → gea_program-0.14.0}/gea/gravity_reference.py +0 -0
  149. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/TESTER_GUIDE.md +0 -0
  150. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/alarms.md +0 -0
  151. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/audit-update.md +0 -0
  152. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/data-in.md +0 -0
  153. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/doctor.md +0 -0
  154. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/drift.md +0 -0
  155. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/files.md +0 -0
  156. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/instruments.md +0 -0
  157. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/notifications.md +0 -0
  158. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/osdu.md +0 -0
  159. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/patches.md +0 -0
  160. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/quakeml.md +0 -0
  161. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/quality.md +0 -0
  162. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/seismic.md +0 -0
  163. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/site.md +0 -0
  164. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/sites.md +0 -0
  165. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/sra.md +0 -0
  166. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/start.md +0 -0
  167. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/transients.md +0 -0
  168. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/upkeep.md +0 -0
  169. {gea_program-0.12.0 → gea_program-0.14.0}/gea/help/well-tests.md +0 -0
  170. {gea_program-0.12.0 → gea_program-0.14.0}/gea/housekeeping.py +0 -0
  171. {gea_program-0.12.0 → gea_program-0.14.0}/gea/inverse_engine.py +0 -0
  172. {gea_program-0.12.0 → gea_program-0.14.0}/gea/jobs.py +0 -0
  173. {gea_program-0.12.0 → gea_program-0.14.0}/gea/live_ports.py +0 -0
  174. {gea_program-0.12.0 → gea_program-0.14.0}/gea/loadtest.py +0 -0
  175. {gea_program-0.12.0 → gea_program-0.14.0}/gea/matplotlib_demo.py +0 -0
  176. {gea_program-0.12.0 → gea_program-0.14.0}/gea/modbus.py +0 -0
  177. {gea_program-0.12.0 → gea_program-0.14.0}/gea/model_card.py +0 -0
  178. {gea_program-0.12.0 → gea_program-0.14.0}/gea/mqtt_port.py +0 -0
  179. {gea_program-0.12.0 → gea_program-0.14.0}/gea/notify.py +0 -0
  180. {gea_program-0.12.0 → gea_program-0.14.0}/gea/opcua_port.py +0 -0
  181. {gea_program-0.12.0 → gea_program-0.14.0}/gea/operator_app.py +0 -0
  182. {gea_program-0.12.0 → gea_program-0.14.0}/gea/patches.py +0 -0
  183. {gea_program-0.12.0 → gea_program-0.14.0}/gea/permits.py +0 -0
  184. {gea_program-0.12.0 → gea_program-0.14.0}/gea/ports.py +0 -0
  185. {gea_program-0.12.0 → gea_program-0.14.0}/gea/profile_catalog.py +0 -0
  186. {gea_program-0.12.0 → gea_program-0.14.0}/gea/project.py +0 -0
  187. {gea_program-0.12.0 → gea_program-0.14.0}/gea/qt6_downhole_app.py +0 -0
  188. {gea_program-0.12.0 → gea_program-0.14.0}/gea/quakeml.py +0 -0
  189. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reconciler.py +0 -0
  190. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  191. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  192. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  193. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  194. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  195. {gea_program-0.12.0 → gea_program-0.14.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  196. {gea_program-0.12.0 → gea_program-0.14.0}/gea/rock_inventory.py +0 -0
  197. {gea_program-0.12.0 → gea_program-0.14.0}/gea/sample_record.py +0 -0
  198. {gea_program-0.12.0 → gea_program-0.14.0}/gea/sample_well_profile.csv +0 -0
  199. {gea_program-0.12.0 → gea_program-0.14.0}/gea/sbom.py +0 -0
  200. {gea_program-0.12.0 → gea_program-0.14.0}/gea/segy.py +0 -0
  201. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_array.py +0 -0
  202. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_assoc.py +0 -0
  203. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_detect.py +0 -0
  204. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_film.py +0 -0
  205. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_harmonic.py +0 -0
  206. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_qc.py +0 -0
  207. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_response.py +0 -0
  208. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_signature.py +0 -0
  209. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_track.py +0 -0
  210. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_uncertainty.py +0 -0
  211. {gea_program-0.12.0 → gea_program-0.14.0}/gea/seismic_unlisted.py +0 -0
  212. {gea_program-0.12.0 → gea_program-0.14.0}/gea/sensor_swap.py +0 -0
  213. {gea_program-0.12.0 → gea_program-0.14.0}/gea/service.py +0 -0
  214. {gea_program-0.12.0 → gea_program-0.14.0}/gea/service_life.py +0 -0
  215. {gea_program-0.12.0 → gea_program-0.14.0}/gea/shell.py +0 -0
  216. {gea_program-0.12.0 → gea_program-0.14.0}/gea/shut_in.py +0 -0
  217. {gea_program-0.12.0 → gea_program-0.14.0}/gea/sla_report.py +0 -0
  218. {gea_program-0.12.0 → gea_program-0.14.0}/gea/store_forward.py +0 -0
  219. {gea_program-0.12.0 → gea_program-0.14.0}/gea/strata_join.py +0 -0
  220. {gea_program-0.12.0 → gea_program-0.14.0}/gea/supervisor.py +0 -0
  221. {gea_program-0.12.0 → gea_program-0.14.0}/gea/survey_cmd.py +0 -0
  222. {gea_program-0.12.0 → gea_program-0.14.0}/gea/survey_view.py +0 -0
  223. {gea_program-0.12.0 → gea_program-0.14.0}/gea/telemetry.py +0 -0
  224. {gea_program-0.12.0 → gea_program-0.14.0}/gea/tool_library.py +0 -0
  225. {gea_program-0.12.0 → gea_program-0.14.0}/gea/transient.py +0 -0
  226. {gea_program-0.12.0 → gea_program-0.14.0}/gea/web/app.html +0 -0
  227. {gea_program-0.12.0 → gea_program-0.14.0}/gea/well_assembler.py +0 -0
  228. {gea_program-0.12.0 → gea_program-0.14.0}/gea/well_test_validation.py +0 -0
  229. {gea_program-0.12.0 → gea_program-0.14.0}/gea/wits0.py +0 -0
  230. {gea_program-0.12.0 → gea_program-0.14.0}/gea/witsml.py +0 -0
  231. {gea_program-0.12.0 → gea_program-0.14.0}/gea_program.egg-info/dependency_links.txt +0 -0
  232. {gea_program-0.12.0 → gea_program-0.14.0}/gea_program.egg-info/entry_points.txt +0 -0
  233. {gea_program-0.12.0 → gea_program-0.14.0}/gea_program.egg-info/requires.txt +0 -0
  234. {gea_program-0.12.0 → gea_program-0.14.0}/gea_program.egg-info/top_level.txt +0 -0
  235. {gea_program-0.12.0 → gea_program-0.14.0}/setup.cfg +0 -0
  236. {gea_program-0.12.0 → gea_program-0.14.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.12.0
3
+ Version: 0.14.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 (324 checks)
128
+ gea accept # the product gate (330 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]")
@@ -584,6 +584,41 @@ it first - identifiers, allowed children, required children, enumerations,
584
584
  references - and the file validates against the published XSD. `gea help
585
585
  quakeml`; `gea quakeml` runs the labelled scene.
586
586
 
587
+ ### The well as master data names it
588
+
589
+ `gea workspace --action well-identity --well <id>` names a well the way the
590
+ operator's master data and a regulator's well file name it: the US Well
591
+ Number (the API number, whose standard PPDM has held since 2010) taken apart
592
+ - state and offshore code, county code, unique well and its range, sidetrack
593
+ and event when given, and which component the number identifies (ten digits
594
+ the Well Origin, twelve a Wellbore, fourteen an event) - and the components
595
+ of PPDM's "What is a Well" the site can name: the Well and its one Origin at
596
+ the declared position on its datum, the Wellbore only when the sidetrack
597
+ code was given, the gauge station as a measured depth along the wellbore,
598
+ the Wellhead Stream as the channels recorded at the wellhead, the Well Set
599
+ as the site, every alias typed. The identity rides on every well row of the
600
+ SRA packet (section 2a) and in the OSDU export's Well and Wellbore records.
601
+ It will not assume a sidetrack, invent an injection interval or a
602
+ completion, or name a county. `gea ppdm --action parse --number ...` takes a
603
+ number apart from the command line; `gea help ppdm`.
604
+
605
+ ### Machine vibration
606
+
607
+ The pump's record read the way an operations engineer expects. `gea
608
+ workspace --action vibration-report --well <id> --record pump.csv --vib-unit
609
+ g --rpm 1780 --group 2 --support rigid --bearing 9,7.94,39.04` writes the
610
+ broadband r.m.s. vibration velocity over 10-1000 Hz (2-1000 Hz below 600
611
+ r/min), the quantity ISO 20816-1 defines, and the ISO 20816-3 zone for the
612
+ declared machine group and support class with the boundaries printed; then
613
+ the envelope spectrum of the most impulsive band (chosen by kurtosis, or
614
+ declared) against the bearing's defect frequencies - BPFO, BPFI, BSF, FTF
615
+ from its geometry and the shaft speed - each MATCHED at its fundamental or
616
+ NOT MATCHED, the inner race with its sidebands. A record in counts needs a
617
+ sensitivity; a rate that cannot carry 1000 Hz is PARTIAL BAND, never
618
+ promoted; the group and the support are declarations; a matched frequency
619
+ is not a fault size. CSV or miniSEED/SAC; `gea vibration` runs the labelled
620
+ pump; `gea help vibration`.
621
+
587
622
  ### Audit / Update
588
623
 
589
624
  The Audit / Update tab holds three things. The whole audit log
@@ -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 (324 checks)
89
+ gea accept # the product gate (330 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]")
@@ -545,6 +545,41 @@ it first - identifiers, allowed children, required children, enumerations,
545
545
  references - and the file validates against the published XSD. `gea help
546
546
  quakeml`; `gea quakeml` runs the labelled scene.
547
547
 
548
+ ### The well as master data names it
549
+
550
+ `gea workspace --action well-identity --well <id>` names a well the way the
551
+ operator's master data and a regulator's well file name it: the US Well
552
+ Number (the API number, whose standard PPDM has held since 2010) taken apart
553
+ - state and offshore code, county code, unique well and its range, sidetrack
554
+ and event when given, and which component the number identifies (ten digits
555
+ the Well Origin, twelve a Wellbore, fourteen an event) - and the components
556
+ of PPDM's "What is a Well" the site can name: the Well and its one Origin at
557
+ the declared position on its datum, the Wellbore only when the sidetrack
558
+ code was given, the gauge station as a measured depth along the wellbore,
559
+ the Wellhead Stream as the channels recorded at the wellhead, the Well Set
560
+ as the site, every alias typed. The identity rides on every well row of the
561
+ SRA packet (section 2a) and in the OSDU export's Well and Wellbore records.
562
+ It will not assume a sidetrack, invent an injection interval or a
563
+ completion, or name a county. `gea ppdm --action parse --number ...` takes a
564
+ number apart from the command line; `gea help ppdm`.
565
+
566
+ ### Machine vibration
567
+
568
+ The pump's record read the way an operations engineer expects. `gea
569
+ workspace --action vibration-report --well <id> --record pump.csv --vib-unit
570
+ g --rpm 1780 --group 2 --support rigid --bearing 9,7.94,39.04` writes the
571
+ broadband r.m.s. vibration velocity over 10-1000 Hz (2-1000 Hz below 600
572
+ r/min), the quantity ISO 20816-1 defines, and the ISO 20816-3 zone for the
573
+ declared machine group and support class with the boundaries printed; then
574
+ the envelope spectrum of the most impulsive band (chosen by kurtosis, or
575
+ declared) against the bearing's defect frequencies - BPFO, BPFI, BSF, FTF
576
+ from its geometry and the shaft speed - each MATCHED at its fundamental or
577
+ NOT MATCHED, the inner race with its sidebands. A record in counts needs a
578
+ sensitivity; a rate that cannot carry 1000 Hz is PARTIAL BAND, never
579
+ promoted; the group and the support are declarations; a matched frequency
580
+ is not a fault size. CSV or miniSEED/SAC; `gea vibration` runs the labelled
581
+ pump; `gea help vibration`.
582
+
548
583
  ### Audit / Update
549
584
 
550
585
  The Audit / Update tab holds three things. The whole audit log
@@ -122,7 +122,7 @@ from .profile_catalog import (
122
122
  read_pangaea_txt,
123
123
  )
124
124
 
125
- __version__ = "0.12.0"
125
+ __version__ = "0.14.0"
126
126
  __all__ = [
127
127
  "aging_rate", "rate_psi_yr", "accuracy_psi",
128
128
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -282,7 +282,7 @@ def main(argv=None) -> int:
282
282
  choices=["init", "add-file", "add-catalog", "add-live", "remove", "list", "migrate", "refresh", "audit",
283
283
  "add-seismic", "refresh-seismic", "remove-seismic", "sar-film", "refresh-all", "add-track", "refresh-track", "remove-track",
284
284
  "seismic-dataset", "seismic-field", "add-site", "sites", "site-report", "remove-site", "rename",
285
- "set-well", "sra-define", "sra-report", "associate", "osdu-export", "quakeml-export"])
285
+ "set-well", "sra-define", "sra-report", "associate", "osdu-export", "quakeml-export", "well-identity", "vibration-report"])
286
286
  p_ws.add_argument("--stations", type=str, nargs="+", default=None, help="add-track: two or more array station ids of this workspace")
287
287
  p_ws.add_argument("--site", type=str, default=None, help="site-report/remove-site: the site id")
288
288
  p_ws.add_argument("--api", type=str, default=None, help="set-well: the API number")
@@ -317,6 +317,15 @@ def main(argv=None) -> int:
317
317
  p_ws.add_argument("--operator-org", type=str, default="", help="osdu-export: the operator's Organisation record id on the platform")
318
318
  p_ws.add_argument("--out", type=str, default=None, help="osdu-export: the folder to write the manifest and files into; quakeml-export: the file to write")
319
319
  p_ws.add_argument("--agency", type=str, default="", help="quakeml-export: the agencyID written in creationInfo (default: the site's name)")
320
+ p_ws.add_argument("--record", type=str, default=None, help="vibration-report: the machine record (CSV with a timestamp or elapsed-seconds column, or miniSEED/SAC)")
321
+ p_ws.add_argument("--channel", type=str, default=None, help="vibration-report: the channel to assess (default: the first)")
322
+ p_ws.add_argument("--vib-unit", type=str, default="g", help="vibration-report: the channel's unit - g, m/s2, mm/s, in/s, or counts with --sensitivity")
323
+ p_ws.add_argument("--sensitivity", type=float, default=None, help="vibration-report: units per count, when the record is in counts")
324
+ p_ws.add_argument("--rpm", type=float, default=None, help="vibration-report: the shaft speed, r/min (declared)")
325
+ p_ws.add_argument("--group", type=int, choices=[1, 2], default=None, help="vibration-report: ISO 20816-3 machine group - 1 above 300 kW, 2 from 15 to 300 kW (declared)")
326
+ p_ws.add_argument("--support", type=str, choices=["rigid", "flexible"], default=None, help="vibration-report: the support class (declared)")
327
+ p_ws.add_argument("--bearing", type=str, default=None, help="vibration-report: elements,ball_mm,pitch_mm[,contact_deg] - the bearing geometry (declared)")
328
+ p_ws.add_argument("--demod-band", type=float, nargs=2, default=None, help="vibration-report: the demodulation band in Hz (default: chosen by kurtosis)")
320
329
  p_ws.add_argument("--depth-km", type=float, default=6.0, help="associate: the hypocentral depth the location is made under, km (declared)")
321
330
  p_ws.add_argument("--rms-tol", type=float, default=0.15, help="associate: the RMS residual an event must fit within, s")
322
331
  p_ws.add_argument("--wells", type=str, nargs="+", default=None, help="add-site: well ids of this workspace")
@@ -400,6 +409,21 @@ def main(argv=None) -> int:
400
409
 
401
410
  p_osdu = sub.add_parser("osdu", help="the OSDU-shaped export: the self-test on a labelled site")
402
411
  p_osdu.add_argument("--action", choices=["selftest"], default="selftest")
412
+ p_vib = sub.add_parser("vibration", help="machine vibration: ISO 20816-3 zone and bearing envelope analysis on a record, or the self-test")
413
+ p_vib.add_argument("--action", choices=["selftest", "assess"], default="selftest")
414
+ p_vib.add_argument("--record", type=str, default=None, help="assess: the record (CSV with a time column, or miniSEED/SAC)")
415
+ p_vib.add_argument("--channel", type=str, default=None)
416
+ p_vib.add_argument("--unit", type=str, default="g", help="g, m/s2, mm/s, in/s, or counts with --sensitivity")
417
+ p_vib.add_argument("--sensitivity", type=float, default=None)
418
+ p_vib.add_argument("--rpm", type=float, default=None)
419
+ p_vib.add_argument("--group", type=int, choices=[1, 2], default=None)
420
+ p_vib.add_argument("--support", type=str, choices=["rigid", "flexible"], default=None)
421
+ p_vib.add_argument("--bearing", type=str, default=None, help="elements,ball_mm,pitch_mm[,contact_deg]")
422
+ p_vib.add_argument("--demod-band", type=float, nargs=2, default=None)
423
+ p_vib.add_argument("--json", action="store_true")
424
+ p_ppdm = sub.add_parser("ppdm", help="the well named as the US Well Number and PPDM name it: take a number apart, or run the self-test")
425
+ p_ppdm.add_argument("--action", choices=["selftest", "parse"], default="selftest")
426
+ p_ppdm.add_argument("--number", type=str, default=None, help="parse: a US Well Number (10, 12 or 14 digits, dashes optional)")
403
427
  p_qml = sub.add_parser("quakeml", help="the QuakeML 1.2 catalogue export: the self-test on the association leg's labelled scene")
404
428
  p_qml.add_argument("--action", choices=["selftest"], default="selftest")
405
429
  p_qml.add_argument("--out", type=str, default=None, help="where to leave the self-test's file (default: a temporary folder)")
@@ -1500,6 +1524,57 @@ def main(argv=None) -> int:
1500
1524
  return 0
1501
1525
  print(f"notifications: {'configured' if n.cfg else 'not configured'}" + (f" - {len(n.cfg['channels'])} channel(s), {len(n.cfg['rules'])} rule(s), quiet {n.cfg['quiet_s']} s" if n.cfg else " (gea notify --example)"))
1502
1526
  return 0
1527
+ elif a.cmd == "vibration":
1528
+ from . import vibration as _V
1529
+ if a.action == "assess":
1530
+ if not a.record:
1531
+ raise SystemExit("vibration --action assess needs --record")
1532
+ brg = None
1533
+ if a.bearing:
1534
+ parts = [float(x) for x in a.bearing.split(",")]
1535
+ if len(parts) not in (3, 4):
1536
+ raise SystemExit("--bearing is elements,ball_mm,pitch_mm[,contact_deg]")
1537
+ brg = {"n_elements": int(parts[0]), "d_mm": parts[1], "D_mm": parts[2], "contact_deg": parts[3] if len(parts) == 4 else 0.0}
1538
+ try:
1539
+ rec = _V.read_record(a.record, a.channel)
1540
+ except ValueError as e:
1541
+ raise SystemExit(f"vibration: {e}")
1542
+ r = _V.assess(rec["x"], rec["fs"], a.unit, a.rpm, group=a.group, support=a.support, bearing=brg,
1543
+ band_hz=tuple(a.demod_band) if a.demod_band else None, label=f"{rec['name']}:{rec['channel']}", sensitivity=a.sensitivity)
1544
+ r["gaps"] = list(r["gaps"]) + list(rec["notes"])
1545
+ if a.json:
1546
+ print(json.dumps({k: v for k, v in r.items()}, indent=1, default=str))
1547
+ else:
1548
+ print(_V.report_text(r))
1549
+ return 0 if r["status"] == "ASSESSED" else 1
1550
+ r = _V.selftest()
1551
+ print(f"vibration selftest [{r['label']}]: {r['status']}")
1552
+ for k, v in r["checks"].items():
1553
+ print(f" {'ok ' if v else 'BAD'} {k}")
1554
+ print(_V.report_text(r["assessment"]))
1555
+ return 0 if r["status"] == "OK" else 1
1556
+ elif a.cmd == "ppdm":
1557
+ from . import ppdm as _P
1558
+ if a.action == "parse":
1559
+ if not a.number:
1560
+ raise SystemExit("ppdm --action parse needs --number")
1561
+ r = _P.parse_well_number(a.number)
1562
+ print(f"{r['input']}: {r['status']}" + (f" - {r['form']}; identifies the {r['identifies'].replace('_', ' ')}" if r.get('form') and r['status'] == 'VALID' else ''))
1563
+ if r.get('state'):
1564
+ print(f" state {r['state']['code']} ({r['state']['name'] or 'not a state code'}), county {r['county']['code']}, unique well {r['unique_well']['code']}"
1565
+ f" ({r['unique_well']['range'] or '-'})" + (f"; sidetrack {r['sidetrack']['code']}: {r['sidetrack']['meaning']}" if r.get('sidetrack') else '')
1566
+ + (f"; event {r['event']['code']}: {r['event']['meaning']}" if r.get('event') else ''))
1567
+ for x in r["problems"]:
1568
+ print(f" problem: {x}")
1569
+ for x in r.get("notes") or []:
1570
+ print(f" note: {x}")
1571
+ return 0 if r["status"] == "VALID" else 1
1572
+ r = _P.selftest()
1573
+ print(f"ppdm selftest [{r['label']}]: {r['status']}")
1574
+ for k, v in r["checks"].items():
1575
+ print(f" {'ok ' if v else 'BAD'} {k}")
1576
+ print(_P.report_text(r["identity"]))
1577
+ return 0 if r["status"] == "OK" else 1
1503
1578
  elif a.cmd == "quakeml":
1504
1579
  from . import quakeml as _Q
1505
1580
  r = _Q.selftest(a.out)
@@ -1765,6 +1840,28 @@ def main(argv=None) -> int:
1765
1840
  countries=a.country, operator_org_id=a.operator_org, out_dir=a.out, actor=a.actor)
1766
1841
  print(_osdu_text(r)); v = r["validate"]
1767
1842
  print(f" structure: {'ok' if v['ok'] else 'PROBLEMS'} ({v['records']} record(s))" + ('' if v['ok'] else '; ' + '; '.join(v['problems'][:5]))); return 0
1843
+ elif a.action == "vibration-report":
1844
+ from .vibration import report_text as _vib_text
1845
+ if not a.well or not a.record:
1846
+ raise SystemExit("vibration-report needs --well <id> and --record <file>")
1847
+ brg = None
1848
+ if a.bearing:
1849
+ parts = [float(x) for x in a.bearing.split(",")]
1850
+ if len(parts) not in (3, 4):
1851
+ raise SystemExit("--bearing is elements,ball_mm,pitch_mm[,contact_deg]")
1852
+ brg = {"n_elements": int(parts[0]), "d_mm": parts[1], "D_mm": parts[2], "contact_deg": parts[3] if len(parts) == 4 else 0.0}
1853
+ try:
1854
+ r = ws.vibration_report(a.well, a.record, channel=a.channel, unit=a.vib_unit, rpm=a.rpm, group=a.group, support=a.support, bearing=brg,
1855
+ band_hz=a.demod_band, sensitivity=a.sensitivity, actor=a.actor)
1856
+ except ValueError as e:
1857
+ raise SystemExit(f"vibration-report: {e}")
1858
+ print(_vib_text(r)); print(" written:", r["paths"].get("md") or r["paths"]); return 0
1859
+ elif a.action == "well-identity":
1860
+ from .ppdm import report_text as _ppdm_text
1861
+ if not a.well:
1862
+ raise SystemExit("well-identity needs --well <id>")
1863
+ r = ws.well_identity(a.well)
1864
+ print(_ppdm_text(r)); return 0
1768
1865
  elif a.action == "quakeml-export":
1769
1866
  from .quakeml import report_text as _qml_text
1770
1867
  if not a.site:
@@ -5104,6 +5104,221 @@ def section_bf_quakeml(tmp: str) -> None:
5104
5104
  "a station with no position gets a pick but an arrival without azimuth or distance and is named as a gap; neither is invented; the misfit "
5105
5105
  "region of 1 km by 2 km becomes a 1000 m horizontal uncertainty and a latitude uncertainty of one kilometre in degrees")
5106
5106
 
5107
+
5108
+ def section_bg_ppdm(tmp: str) -> None:
5109
+ """Section BG - the well named the way the operator's master data names it: the US Well Number taken
5110
+ apart and checked, the PPDM "What is a Well" components the site can name, and the identity carried into
5111
+ the SRA packet and the OSDU export without a sidetrack, an interval or a county name being invented."""
5112
+ import csv as _csv
5113
+ import subprocess as _sp
5114
+ from datetime import datetime as _dt, timedelta as _td, timezone as _tz
5115
+ from . import ppdm as P
5116
+ from . import helplib as H
5117
+ from .client_reports import forbidden_terms
5118
+ from .workspace import Workspace
5119
+ pkg = Path(__file__).parent
5120
+ d = Path(tmp, "bg"); d.mkdir()
5121
+
5122
+ # -- the number ----------------------------------------------------------------------------------
5123
+ r = P.selftest()
5124
+ a = P.parse_well_number("42 329 39123 01 00")
5125
+ b = P.parse_well_number("4232939123")
5126
+ ok(r["status"] == "OK" and all(r["checks"].values())
5127
+ and a["status"] == "VALID" and a["api14"] == "42-329-39123-01-00" and a["identifies"] == "event" and a["event"]["meaning"].startswith("the original")
5128
+ and b["status"] == "VALID" and b["api10"] == "42-329-39123" and b["api12"] is None and b["identifies"] == "well_origin"
5129
+ and len(P.STATE_CODES) == 55 and P.STATE_CODES["42"] == "Texas" and P.STATE_CODES["60"] == "Northern Gulf of Mexico" and "52" not in P.STATE_CODES
5130
+ and P.parse_well_number("42-329-61000")["unique_well"]["range"] == "reserved" and P.parse_well_number("42-329-00500")["unique_well"]["range"] == "historical"
5131
+ and P.parse_well_number("42-329-96000")["unique_well"]["range"] == "exempt"
5132
+ and "booklet" in P.parse_well_number("42-329-39123")["county"]["basis"],
5133
+ "BG1 a US Well Number in any written form is taken apart to the standard - state and offshore codes named (51 states and territories, four "
5134
+ "offshore areas, 52-54 reserved), the county code kept as a code because the county booklet is not held, the unique well placed in the "
5135
+ "standard's historical, current, reserved or exempt range - and ten, twelve and fourteen digits name the Well Origin, a Wellbore and an "
5136
+ "event; a reserved or unknown state, a wrong length, a stray letter and a zero unique well are each named as a problem, never repaired")
5137
+
5138
+ # -- the identity, and what it will not name ---------------------------------------------------
5139
+ ident = r["identity"]; c = ident["components"]
5140
+ ok(c["well"]["identifier"] == "42-329-39123" and c["wellbore"]["identifier"] is None and "sidetrack code" in c["wellbore"]["detail"]
5141
+ and c["well_origin"]["position"]["datum"] == "NAD27" and c["wellhead_stream"]["direction"] == "into the ground"
5142
+ and c["wellbore_contact_interval"]["identifier"] is None and c["wellbore_completion"]["identifier"] is None
5143
+ and all(k in P.COMPONENTS for k in ("well", "well_origin", "wellbore", "wellbore_segment", "wellbore_contact_interval", "wellbore_completion", "wellhead_stream", "well_set"))
5144
+ and "one and only one" in P.COMPONENTS["wellbore_segment"] and "not an activity" in P.COMPONENTS["wellbore_completion"]
5145
+ and sum("not given" in g for g in ident["gaps"]) == 1 and sum("not held here" in g for g in ident["gaps"]) == 1,
5146
+ "BG2 the identity names the Well and its Origin at the declared position on its datum, the Wellhead Stream with its direction, the Well Set "
5147
+ "as the site and every alias typed, and names as not named - with the reason - the Wellbore when the sidetrack code was not given, and the "
5148
+ "Contact Interval and the Completion that are the permit's and the completion report's facts; the component definitions are PPDM's")
5149
+
5150
+ # -- through the workspace: the packet and the OSDU export carry it ----------------------------
5151
+ t0 = _dt(2026, 3, 1, tzinfo=_tz.utc)
5152
+ hist = d / "swd.csv"
5153
+ with open(hist, "w", newline="") as f:
5154
+ w = _csv.writer(f); w.writerow(["timestamp", "P_surf_psi", "Q_bpm"])
5155
+ for i in range(2 * 144):
5156
+ w.writerow([(t0 + _td(seconds=600 * i)).strftime("%Y-%m-%dT%H:%M:%SZ"), "1500.0", "5.0"])
5157
+ ws = Workspace.create(str(d / "ws"), "Pad 3", actor="tester")
5158
+ w1 = ws.add_well_file(str(hist), display="SWD 1", actor="tester", station_md_ft=9800.0)
5159
+ run = lambda *a: _sp.run([sys.executable, "-m", "gea", "workspace", "--path", ws.path, "--actor", "tester", *a], capture_output=True, text=True)
5160
+ r0 = run("--action", "well-identity", "--well", w1["id"]) # nothing declared yet
5161
+ r1 = run("--action", "set-well", "--well", w1["id"], "--lat", "31.96", "--lon", "-102.24", "--datum", "NAD27", "--api", "42-329-39123", "--uic", "123456",
5162
+ "--depth-tier", "deep", "--formation", "completed below the base of the Wolfcamp", "--channel-pressure", "P_surf_psi", "--channel-rate", "Q_bpm",
5163
+ "--rate-unit", "bbl/min", "--bhp-method", "calculated")
5164
+ r2 = run("--action", "add-site", "--name", "PPDM block", "--wells", w1["id"], "--client", "Acme Operating")
5165
+ r3 = run("--action", "well-identity", "--well", w1["id"])
5166
+ r4 = run("--action", "sra-define", "--site", "PPDM-block", "--name", "Test SRA", "--lat", "31.95", "--lon", "-102.25", "--datum", "WGS84",
5167
+ "--radius-km", "9.08", "--plan-date", "2026-02-25")
5168
+ r5 = run("--action", "sra-report", "--site", "PPDM-block")
5169
+ out = Path(ws.path, "reports", "sites", "PPDM-block")
5170
+ md = (out / "sra_packet.md").read_text(encoding="utf-8"); html = (out / "sra_packet.html").read_text(encoding="utf-8")
5171
+ pkj = json.loads((out / "sra_packet.json").read_text(encoding="utf-8"))["packet"]
5172
+ ws2 = Workspace(ws.path)
5173
+ o1 = ws2.osdu_export("PPDM-block", partition="opendes", acl_owners=["o"], acl_viewers=["v"], legal_tags=["t"], actor="tester")
5174
+ man1 = json.loads(Path(o1["manifest"]).read_text(encoding="utf-8"))
5175
+ wells1 = [x for x in man1["MasterData"] if x["kind"].endswith(":master-data--Well:1.0.0")]
5176
+ bores1 = [x for x in man1["MasterData"] if x["kind"].endswith(":master-data--Wellbore:1.0.0")]
5177
+ ws2.set_well(w1["id"], disposal={"api_number": "42-329-39123-01"}, actor="tester")
5178
+ o2 = ws2.osdu_export("PPDM-block", partition="opendes", acl_owners=["o"], acl_viewers=["v"], legal_tags=["t"], actor="tester")
5179
+ man2 = json.loads(Path(o2["manifest"]).read_text(encoding="utf-8"))
5180
+ bores2 = [x for x in man2["MasterData"] if x["kind"].endswith(":master-data--Wellbore:1.0.0")]
5181
+ r6 = _sp.run([sys.executable, "-m", "gea", "ppdm", "--action", "parse", "--number", "42-329-39123-01"], capture_output=True, text=True)
5182
+ r7 = _sp.run([sys.executable, "-m", "gea", "ppdm", "--action", "parse", "--number", "53-329-39123"], capture_output=True, text=True)
5183
+ r8 = _sp.run([sys.executable, "-m", "gea", "ppdm"], capture_output=True, text=True)
5184
+ ident_w = pkj["wells"][0]["identity"]
5185
+ ok(r0.returncode == 0 and "no US Well Number declared" in r0.stdout and r1.returncode == 0 and r2.returncode == 0
5186
+ and r3.returncode == 0 and "US Well Number 42-329-39123" in r3.stdout and "state 42 (Texas)" in r3.stdout and "Wellbore: not identified" in r3.stdout
5187
+ and "Well Set: PPDM block" in r3.stdout and "Wellhead Stream: 2 channel(s)" in r3.stdout and "into the ground" in r3.stdout
5188
+ and "9800 ft MD" in r3.stdout and "123456 [UIC permit number]" in r3.stdout
5189
+ and r4.returncode == 0 and r5.returncode == 0 and ident_w["us_well_number"] == "42-329-39123" and ident_w["state"] == "Texas"
5190
+ and ident_w["wellbore"] is None and ident_w["origin_datum"] == "NAD27" and not any("sidetrack" in g or "Completion" in g for g in pkj["gaps"])
5191
+ and "Well identity" in md and "42-329-39123" in md and "not identified" in md and "US Well Number and PPDM" in html
5192
+ and not forbidden_terms(md) and not forbidden_terms(html)
5193
+ and len(wells1) == 1 and wells1[0]["data"]["FacilityID"] == "42-329-39123"
5194
+ and any(al["AliasName"] == "42-329-39123" and al["AliasNameTypeID"].endswith("AliasNameType:USWellNumber:") for al in wells1[0]["data"]["NameAliases"])
5195
+ and wells1[0]["data"]["ExtensionProperties"]["gea"]["us_well_number"]["state"]["name"] == "Texas"
5196
+ and len(bores1) == 1 and bores1[0]["data"]["FacilityID"] == w1["id"] and "not identified" in bores1[0]["data"]["ExtensionProperties"]["gea"]["wellbore_identifier"]
5197
+ and bores2[0]["data"]["FacilityID"] == "42-329-39123-01" and bores2[0]["data"]["ExtensionProperties"]["gea"]["wellbore_identifier"] == "42-329-39123-01"
5198
+ and o2["validate"]["ok"]
5199
+ and r6.returncode == 0 and "identifies the wellbore" in r6.stdout and r7.returncode == 1 and "reserved" in r7.stdout
5200
+ and r8.returncode == 0 and "ppdm selftest" in r8.stdout
5201
+ and any(t["topic"] == "ppdm" for t in H.topics()) and not H.check("ppdm")
5202
+ and "sidetrack code" in (pkg / "help" / "ppdm.md").read_text(encoding="utf-8").split("will not call a measurement")[1],
5203
+ "BG3 `--action well-identity` names a bare well as unidentified and a declared one by its US Well Number, state, origin datum, wellbore "
5204
+ "standing, gauge depth, wellhead stream, well set and typed aliases; the SRA packet carries the identity on every well row and in its own "
5205
+ "table (section 2a) without counting its gaps against the packet, and the packet's prose passes the vocabulary gate; the OSDU export's Well "
5206
+ "record carries the ten-digit number as FacilityID and a typed alias with the state in the extension, and its Wellbore record keeps the "
5207
+ "program's id and says the sidetrack code was not given until a twelve-digit number is declared, when it carries that; `gea ppdm` parses "
5208
+ "a number from the command line with the exit code saying whether it is valid, and runs the self-test; the help page names the sidetrack "
5209
+ "it will not assume")
5210
+
5211
+
5212
+ def section_bh_vibration(tmp: str) -> None:
5213
+ """Section BH - machine vibration: the ISO 20816-3 zone of the declared machine from the broadband r.m.s.
5214
+ velocity, and the bearing's defect frequencies found or not found in the envelope spectrum of the most
5215
+ impulsive band; the declarations named as declarations, the band's limits named, no fault size claimed."""
5216
+ import csv as _csv
5217
+ import subprocess as _sp
5218
+ import numpy as _np
5219
+ from . import vibration as V
5220
+ from . import seismic as S
5221
+ from . import helplib as H
5222
+ from .client_reports import forbidden_terms
5223
+ from .workspace import Workspace
5224
+ pkg = Path(__file__).parent
5225
+ d = Path(tmp, "bh"); d.mkdir()
5226
+
5227
+ # -- the self-test on the labelled pump ----------------------------------------------------------
5228
+ r = V.selftest()
5229
+ a = r["assessment"]
5230
+ ok(r["status"] == "OK" and all(r["checks"].values()) and r["label"] == "SIMULATION_SELF_TEST"
5231
+ and a["zone"]["zone"] == "C" and abs(a["broadband"]["rms_mm_s"] - r["truth"]["unbalance_rms_mm_s"] * (1 + 0.09) ** 0.5) < 0.3
5232
+ and a["envelope"]["match"]["matched"] == ["BPFO"] and len(a["envelope"]["match"]["rows"]["BPFO"]["harmonics_found"]) == 3
5233
+ and r["inner"]["envelope"]["match"]["matched"] == ["BPFI"] and len(r["inner"]["envelope"]["match"]["rows"]["BPFI"]["sidebands"]["found"]) == 2
5234
+ and V.ZONE_BOUNDARIES == {(1, "rigid"): (2.3, 4.5, 7.1), (1, "flexible"): (3.5, 7.1, 11.0), (2, "rigid"): (1.4, 2.8, 4.5), (2, "flexible"): (2.3, 4.5, 7.1)}
5235
+ and V.BAND_HZ == (10.0, 1000.0) and V.BAND_LOW_SPEED_HZ == (2.0, 1000.0) and V.LOW_SPEED_RPM == 600.0
5236
+ and abs(V.bearing_frequencies(1780, 9, 7.94, 39.04)["BPFI"] - 0.5 * 9 * (1780 / 60) * (1 + 7.94 / 39.04)) < 1e-3
5237
+ and abs(V.bearing_frequencies(1780, 9, 7.94, 39.04)["BSF"] - (39.04 / (2 * 7.94)) * (1780 / 60) * (1 - (7.94 / 39.04) ** 2)) < 1e-3
5238
+ and abs(V.bearing_frequencies(1780, 9, 7.94, 39.04)["FTF"] - 0.5 * (1780 / 60) * (1 - 7.94 / 39.04)) < 1e-3
5239
+ and abs(V.bearing_frequencies(1780, 9, 7.94, 39.04, 30.0)["BPFO"] - 0.5 * 9 * (1780 / 60) * (1 - 7.94 / 39.04 * _np.cos(_np.radians(30)))) < 1e-3,
5240
+ "BH1 the self-test on a labelled pump at 1780 r/min: the broadband r.m.s. velocity recovers the unbalance line put into the record to within "
5241
+ "8% and places a Group 2 rigid machine in zone C as the truth says; the most impulsive band is chosen and the outer-race fault is matched at "
5242
+ "its fundamental and two harmonics with the inner race, the ball and the cage not matched; an inner-race scene is matched at BPFI with both "
5243
+ "sidebands at the shaft speed and the outer race not matched; the zone boundaries are ISO 20816-3's for both groups and both supports, the "
5244
+ "band is ISO 20816-1's, and the four defect frequencies are the kinematic formulas including the contact angle")
5245
+
5246
+ # -- what it will not call a measurement ----------------------------------------------------------
5247
+ sc = V.synthetic_scene(seconds=2.0)
5248
+ slow = V.assess(sc["acc_m_s2"][::10], sc["fs"] / 10.0, "m/s2", sc["rpm"], group=2, support="rigid", bearing=sc["bearing"])
5249
+ short = V.assess(sc["acc_m_s2"][:int(0.5 * sc["fs"])], sc["fs"], "m/s2", sc["rpm"], group=2, support="rigid", bearing=sc["bearing"])
5250
+ nod = V.assess(sc["acc_m_s2"], sc["fs"], "m/s2", None)
5251
+ cnt = V.assess(_np.round(sc["acc_m_s2"] / 9.80665 / 1e-4), sc["fs"], "counts", sc["rpm"], group=2, support="rigid", bearing=sc["bearing"])
5252
+ sens = V.assess(_np.round(sc["acc_m_s2"] / 9.80665 / 1e-4), sc["fs"], "g", sc["rpm"], group=2, support="rigid", bearing=sc["bearing"], sensitivity=1e-4)
5253
+ low = V.assess(sc["acc_m_s2"], sc["fs"], "m/s2", 450.0, group=1, support="flexible")
5254
+ declared_band = V.assess(sc["acc_m_s2"], sc["fs"], "m/s2", sc["rpm"], group=2, support="rigid", bearing=sc["bearing"], band_hz=(2000.0, 4000.0))
5255
+ ok(slow["broadband"]["band_partial"] and slow["broadband"]["band_hz"][1] < 1000 and slow["zone"]["zone_qualified"] == "PARTIAL BAND"
5256
+ and any("PARTIAL BAND" in g for g in slow["gaps"])
5257
+ and any("shorter than one second" in g for g in short["gaps"])
5258
+ and nod["zone"] is None and nod["status"] == "PARTIAL" and any("no shaft speed" in g for g in nod["gaps"]) and any("group" in g for g in nod["gaps"])
5259
+ and any("bearing" in g for g in nod["gaps"]) and nod["envelope"]["match"] is None and len(nod["envelope"]["peaks"]) > 0
5260
+ and cnt["status"] == "NOT ASSESSED" and cnt["broadband"]["status"] == "NOT EVALUATED" and cnt["envelope"]["match"]["matched"] == ["BPFO"]
5261
+ and sens["status"] == "ASSESSED" and abs(sens["broadband"]["rms_mm_s"] - a["broadband"]["rms_mm_s"]) < 0.3 and sens["declared"]["sensitivity"] == 1e-4
5262
+ and low["broadband"]["band_hz"][0] == 2.0 and low["zone"]["boundaries_mm_s"] == {"A/B": 3.5, "B/C": 7.1, "C/D": 11.0}
5263
+ and declared_band["envelope"]["band"]["status"] == "DECLARED" and declared_band["envelope"]["band"]["band_hz"] == [2000.0, 4000.0]
5264
+ and declared_band["envelope"]["match"]["matched"] == ["BPFO"]
5265
+ and "fault size" in " ".join(a["not_a_measurement"]) and "declared" in " ".join(a["not_a_measurement"]),
5266
+ "BH2 a record whose rate cannot carry 1000 Hz is evaluated to its own Nyquist frequency and marked PARTIAL BAND, never promoted; a record "
5267
+ "under a second is named; without a shaft speed, a group or a bearing the broadband value is stated, the zone is not, the envelope peaks "
5268
+ "are listed and nothing is matched; a record in counts gets no mm/s and no zone but its envelope is still read, and with a declared "
5269
+ "sensitivity it agrees with the same record in g; below 600 r/min the band starts at 2 Hz and Group 1 flexible has its own boundaries; a "
5270
+ "declared demodulation band is used as declared; the assessment says it is not a fault size and that the machine is a declaration")
5271
+
5272
+ # -- through the workspace and the command line, from a CSV and from a miniSEED record ------------
5273
+ csvp = d / "pump.csv"
5274
+ with open(csvp, "w", newline="") as f:
5275
+ w = _csv.writer(f); w.writerow(["elapsed_s", "acc_g", "temp_C"])
5276
+ for i, x in enumerate(sc["acc_m_s2"]):
5277
+ w.writerow([f"{i / sc['fs']:.6f}", f"{x / 9.80665:.6f}", "41.0"])
5278
+ counts = _np.round(sc["acc_m_s2"] / 9.80665 / 1e-4).astype(_np.int32)
5279
+ S.write_mseed([S.Trace("XX", "PUMP", "", "ACC", 1700000000.1234567, sc["fs"], counts, "synthetic", "INT32")], str(d / "pump.mseed"))
5280
+ back = S.read_any(str(d / "pump.mseed"))
5281
+ hist = d / "swd.csv"
5282
+ with open(hist, "w", newline="") as f:
5283
+ w = _csv.writer(f); w.writerow(["timestamp", "P_surf_psi"])
5284
+ for h in range(24):
5285
+ w.writerow([f"2026-03-01T{h:02d}:00:00Z", "1500"])
5286
+ ws = Workspace.create(str(d / "ws"), "Pad 3", actor="tester")
5287
+ w1 = ws.add_well_file(str(hist), display="SWD 1", actor="tester", station_md_ft=9800.0)
5288
+ run = lambda *a: _sp.run([sys.executable, "-m", "gea", "workspace", "--path", ws.path, "--actor", "tester", *a], capture_output=True, text=True)
5289
+ r1 = run("--action", "vibration-report", "--well", w1["id"], "--record", str(csvp), "--channel", "acc_g", "--vib-unit", "g", "--rpm", "1780",
5290
+ "--group", "2", "--support", "rigid", "--bearing", "9,7.94,39.04")
5291
+ r0 = run("--action", "vibration-report", "--well", w1["id"], "--record", str(csvp), "--channel", "nope")
5292
+ out = Path(ws.path, "reports", "wells", w1["id"])
5293
+ md = (out / "vibration_report.md").read_text(encoding="utf-8"); html = (out / "vibration_report.html").read_text(encoding="utf-8")
5294
+ js = json.loads((out / "vibration_report.json").read_text(encoding="utf-8"))
5295
+ audit = Path(ws.path, "records", "audit.jsonl").read_text(encoding="utf-8")
5296
+ r2 = _sp.run([sys.executable, "-m", "gea", "vibration", "--action", "assess", "--record", str(d / "pump.mseed"), "--unit", "g", "--sensitivity", "1e-4",
5297
+ "--rpm", "1780", "--group", "2", "--support", "rigid", "--bearing", "9,7.94,39.04"], capture_output=True, text=True)
5298
+ r3 = _sp.run([sys.executable, "-m", "gea", "vibration", "--action", "assess", "--record", str(d / "pump.mseed"), "--unit", "counts", "--rpm", "1780",
5299
+ "--group", "2", "--support", "rigid"], capture_output=True, text=True)
5300
+ r4 = _sp.run([sys.executable, "-m", "gea", "vibration"], capture_output=True, text=True)
5301
+ help_md = (pkg / "help" / "vibration.md").read_text(encoding="utf-8")
5302
+ ok(len(back) == 1 and back[0].npts == counts.size and not back[0].gaps and abs(back[0].starttime - 1700000000.1234567) < 2e-6
5303
+ and r1.returncode == 0 and "ISO 20816-3 zone C" in r1.stdout and "BPFO 106.3486 Hz: MATCHED" in r1.stdout and "written:" in r1.stdout
5304
+ and r0.returncode != 0 and "no channel" in (r0.stderr + r0.stdout)
5305
+ and Path(ws.path, "wells", w1["id"], "machine", "pump.csv").is_file()
5306
+ and "| **Result** | ZONE C |" in md and "Bearing defect frequencies" in md and "Group 2 (15 kW to 300 kW)" in md and "fault size" in md
5307
+ and not forbidden_terms(md) and not forbidden_terms(html)
5308
+ and js["assessment"]["record"]["sha256"] and js["assessment"]["zone"]["zone"] == "C" and js["assessment"]["declared"]["bearing"]["n_elements"] == 9
5309
+ and "well.vibration" in audit and '"zone": "C"' in audit
5310
+ and r2.returncode == 0 and "from acceleration (g), 2.0 s at 20000 Hz" in r2.stdout and "zone C" in r2.stdout and "MATCHED" in r2.stdout
5311
+ and r3.returncode == 1 and "NOT ASSESSED" in r3.stdout and "without a sensitivity" in r3.stdout
5312
+ and r4.returncode == 0 and "vibration selftest [SIMULATION_SELF_TEST]: OK" in r4.stdout
5313
+ and any(t["topic"] == "vibration" for t in H.topics()) and not H.check("vibration")
5314
+ and "fault size" in help_md.split("will not call a measurement")[1] and "2.3 / 4.5 / 7.1" in help_md,
5315
+ "BH3 a 20 kHz record round-trips through this program's own miniSEED writer as one trace with no gap (the header's 0.1 ms time field "
5316
+ "is coarser than a sample above 5 kHz and every record read back as a gap until blockette 1001 carried the microseconds); "
5317
+ "`--action vibration-report` copies the record beside the well, writes the report in three forms with the zone as its result, the "
5318
+ "declarations in their own table and the defect frequencies each matched or not, audits it with the zone and the declarations, and names "
5319
+ "a channel that is not there; `gea vibration --action assess` reads a miniSEED record in counts with a declared sensitivity and says "
5320
+ "NOT ASSESSED without one; the self-test runs from the command line; the help page carries the boundaries and names the fault size it will not claim")
5321
+
5107
5322
  def main() -> int:
5108
5323
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
5109
5324
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -5165,6 +5380,8 @@ def main() -> int:
5165
5380
  section_bd_association(tmp)
5166
5381
  section_be_osdu(tmp)
5167
5382
  section_bf_quakeml(tmp)
5383
+ section_bg_ppdm(tmp)
5384
+ section_bh_vibration(tmp)
5168
5385
  if _FAILS:
5169
5386
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
5170
5387
  for f in _FAILS:
@@ -1613,6 +1613,23 @@ def sra_packet_report(pk: dict, evaluated_at: Optional[datetime] = None, program
1613
1613
  f"from the centre, with every position carried to WGS84 first; {m['n_undecided']} could not be placed."],
1614
1614
  [Table(['Well', 'Name', 'API', 'UIC', 'Membership', 'Distance (km)', 'Depth tier', 'BHP method', 'Daily record'], wrows, 'Wells'),
1615
1615
  Table(['Station', 'Name', 'Kind', 'Membership', 'Distance (km)'], srows, 'Seismic stations')]))
1616
+ # the wells named the way the operator's master data names them (v0.13.0)
1617
+ irows = []
1618
+ for w in pk['wells']:
1619
+ i = w.get('identity')
1620
+ if not i:
1621
+ continue
1622
+ irows.append([w['id'], i.get('us_well_number') or '-', (i.get('identifies') or '-').replace('_', ' '), i.get('state') or '-',
1623
+ i.get('wellbore') or 'not identified', i.get('origin_datum') or '-', '; '.join(i.get('aliases') or []),
1624
+ '; '.join(g.split(': ', 1)[1] if ': ' in g else g for g in (i.get('gaps') or [])) or '-'])
1625
+ if irows:
1626
+ secs.append(Section('2a', 'The wells as the US Well Number and PPDM name them', [
1627
+ 'Each well by its US Well Number taken apart - the state, the county, the unique well - and by the components of PPDM\'s "What is a '
1628
+ 'Well" the site can name: the Well and its one Origin at the declared surface position, the Wellbore when the sidetrack code was '
1629
+ 'given, the Well Set as the site, and every alias typed. Ten digits name the Well Origin and nothing below it; a Wellbore is named '
1630
+ 'only when the regulator\'s sidetrack code was declared. The injection interval and the completion are the permit\'s and the '
1631
+ 'completion report\'s facts and are named here as not held.'],
1632
+ [Table(['Well', 'US Well Number', 'Identifies', 'State', 'Wellbore', 'Origin datum', 'Aliases', 'Not named'], irows, 'Well identity')]))
1616
1633
  n = 3
1617
1634
  inside_wells = [w for w in pk['wells'] if w['inside'] and w.get('months')]
1618
1635
  for w in inside_wells:
@@ -1686,6 +1703,64 @@ def sra_packet_report(pk: dict, evaluated_at: Optional[datetime] = None, program
1686
1703
  data={'packet': pk, 'report_id': report_id, 'evaluated_at_utc': now.strftime('%Y-%m-%dT%H:%M:%SZ'), '_records': [], '_catalogue_obj': None})
1687
1704
 
1688
1705
 
1706
+ def vibration_report(r: dict, evaluated_at: Optional[datetime] = None, program_version: str = '') -> Document:
1707
+ """One machine record through ISO 20816-3 and the envelope: the broadband value and its zone for the
1708
+ declared machine, the demodulation band, the bearing's defect frequencies each matched or not, and every
1709
+ declaration and gap by name. Built from `vibration.assess`; nothing is recomputed here."""
1710
+ now = evaluated_at or _utc_now()
1711
+ label = str(r.get('label') or r.get('record', {}).get('name') or 'machine')
1712
+ report_id = f"VIB-{label.replace('/', '-').replace(' ', '-')[:24]}-{now.strftime('%Y%m%dT%H%M%SZ')}"
1713
+ bb = r.get('broadband') or {}; z = r.get('zone'); env = r.get('envelope') or {}; d = r.get('declared') or {}
1714
+ rec = r.get('record') or {}
1715
+ result = (f"ZONE {z['zone']}" + (f" ({z['zone_qualified']})" if z.get('zone_qualified') else '')) if z else r.get('status', '-')
1716
+ paras = []
1717
+ if bb.get('status') == 'EVALUATED':
1718
+ paras.append(f"The broadband r.m.s. vibration velocity is {bb['rms_mm_s']} mm/s over {bb['band_hz'][0]:g} Hz to {bb['band_hz'][1]:g} Hz"
1719
+ + (' - a partial band: the record cannot carry the standard\'s 1000 Hz' if bb.get('band_partial') else '')
1720
+ + f", from {'an' if bb['from'] == 'acceleration' else 'a'} {bb['from']} channel in {bb['unit_in']}, {bb['seconds']} s at {bb['fs_hz']:g} Hz.")
1721
+ else:
1722
+ paras.append(f"The broadband value was not evaluated: {bb.get('detail') or 'see the gaps'}.")
1723
+ if z:
1724
+ paras.append(f"For a {z['group_basis'].split(':')[0]} machine on a {z['support']} support that is ISO 20816-3 zone {z['zone']}: {z['meaning']}. "
1725
+ f"The boundaries for this group and support are A/B {z['boundaries_mm_s']['A/B']}, B/C {z['boundaries_mm_s']['B/C']} and C/D "
1726
+ f"{z['boundaries_mm_s']['C/D']} mm/s" + (f"; {z['margin_to_next_mm_s']} mm/s remain to the next boundary." if z['margin_to_next_mm_s'] is not None else '.'))
1727
+ secs = [Section('1', 'Summary', paras)]
1728
+ secs.append(Section('2', 'What was declared', ['The group, the support class, the shaft speed and the bearing are the operator\'s declarations; the record carries none of them.'],
1729
+ [Table(['Declaration', 'Value'],
1730
+ [['Machine group', (GROUPS_TEXT.get(d.get('group')) if d.get('group') else 'not declared')],
1731
+ ['Support class', d.get('support') or 'not declared'], ['Shaft speed (r/min)', d.get('rpm') if d.get('rpm') is not None else 'not declared'],
1732
+ ['Bearing', (f"{d['bearing']['n_elements']} elements, d {d['bearing']['d_mm']} mm, D {d['bearing']['D_mm']} mm, contact {d['bearing'].get('contact_deg', 0)}°" if d.get('bearing') else 'not declared')],
1733
+ ['Demodulation band', (f"{d['band_hz'][0]:g}-{d['band_hz'][1]:g} Hz, declared" if d.get('band_hz') else 'chosen by kurtosis')],
1734
+ ['Channel unit', d.get('unit') or '-']])]))
1735
+ n = 3
1736
+ if env.get('band') and env['band'].get('band_hz'):
1737
+ b = env['band']; m = env.get('match')
1738
+ ep = [f"The envelope was taken in {b['band_hz'][0]:g}-{b['band_hz'][1]:g} Hz ({b['status'].lower()}" + (f", kurtosis {b['kurtosis']}" if b.get('kurtosis') is not None else '')
1739
+ + f"); the envelope spectrum's resolution is {env.get('resolution_hz')} Hz."]
1740
+ tables = []
1741
+ if m:
1742
+ bf = env['bearing']
1743
+ ep.append(f"Shaft speed {bf['fr']} Hz. {m['status']}. Basis: {m['basis']}.")
1744
+ tables.append(Table(['Defect frequency', 'Hz', 'Standing', 'Found at (Hz)', 'Sidebands at fr'],
1745
+ [[k, v['frequency_hz'], v['standing'], ', '.join(f"{h['found_hz']} ({h['harmonic']}x)" for h in v['harmonics_found']) or '-',
1746
+ (', '.join(str(x) for x in v['sidebands']['found']) or 'none') if v.get('sidebands') else '-'] for k, v in m['rows'].items()], 'Bearing defect frequencies'))
1747
+ elif env.get('peaks'):
1748
+ ep.append('No bearing geometry was declared: the envelope spectrum\'s largest peaks are listed and nothing is matched.')
1749
+ tables.append(Table(['Peak (Hz)', 'Amplitude', 'Over median'], [[p['f_hz'], _fmt(p['amplitude']), p.get('over_median')] for p in env['peaks'][:10]], 'Envelope spectrum peaks'))
1750
+ secs.append(Section(str(n), 'The envelope spectrum', ep, tables)); n += 1
1751
+ secs.append(Section(str(n), 'What needs attention', list(r.get('gaps') or []) or ['Nothing: every declaration was made and the record carries the band.'])); n += 1
1752
+ secs.append(Section(str(n), 'What this report does not call a measurement', [x.rstrip('.') + '.' for x in (r.get('not_a_measurement') or [])]))
1753
+ front = [['Report ID', report_id], ['Record', rec.get('name') or label], ['Channel', rec.get('channel') or '-'],
1754
+ ['Generated (UTC)', now.strftime('%Y-%m-%dT%H:%M:%SZ')],
1755
+ ['Program', f'{PROGRAM_NAME}' + (f' build {program_version}' if program_version else '')], ['Result', result]]
1756
+ return Document(title=f'{PROGRAM_NAME} - Machine Vibration Report', report_id=report_id, front=front, sections=secs,
1757
+ footer='ISO 20816-1 quantity, ISO 20816-3 zones for the declared machine; the envelope against the declared bearing. Nothing here is a fault size.',
1758
+ data={'assessment': r, 'report_id': report_id, 'evaluated_at_utc': now.strftime('%Y-%m-%dT%H:%M:%SZ'), '_records': [], '_catalogue_obj': None})
1759
+
1760
+
1761
+ GROUPS_TEXT = {1: 'Group 1 (above 300 kW)', 2: 'Group 2 (15 kW to 300 kW)'}
1762
+
1763
+
1689
1764
  def forbidden_terms(text: str) -> List[str]:
1690
1765
  low = text.lower()
1691
1766
  return [t for t in FORBIDDEN_TERMS if t in low]
@@ -0,0 +1,11 @@
1
+ # The well as master data names it
2
+
3
+ > The US Well Number taken apart - state, county, unique well, sidetrack, event - and the components of PPDM's "What is a Well" the site can name for each well: the Well and its one Origin, the Wellbore, the Wellhead Stream, the Well Set, and every alias typed.
4
+
5
+ **The command**: `gea workspace --path C:\site --action well-identity --well <id>` prints a well's identity from its declarations (`set-well --api --uic --lat --lon --datum`) and the site it belongs to. `gea ppdm --action parse --number 42-329-39123-01` takes a number apart and names every way it is not valid. `gea ppdm` runs the self-test. The identity also rides on every well row of the SRA packet (section 2a) and in the OSDU export's Well record (the ten-digit number as a typed alias and `FacilityID`, the state and the parts in the extension; the Wellbore record's `FacilityID` is the twelve-digit number when it was given).
6
+
7
+ **What it writes**: the US Well Number in the standard's form `SS-CCC-UUUUU-SS-EE` (the standard the API transferred to the PPDM Association in 2010; PPDM's 2013 US Well Number is its successor): the state or offshore code and its name, the county code, the unique well number and which of the standard's ranges it falls in (historical, current, reserved, exempt), the sidetrack code and the event sequence when given, and which component the number identifies - ten digits the Well Origin, twelve a Wellbore, fourteen an event. Then the components: the Well; the Well Origin at the declared surface position on its datum; the Wellbore, identified only when the sidetrack code was given; the gauge station as a measured depth along the wellbore; the Wellhead Stream as the channels recorded at the wellhead with the declared pressure, rate and bottomhole channels named and the direction (into the ground for a disposal well); the Well Set as the site; the aliases - the operator's name, the program's id, the US Well Number, the UIC permit - each typed.
8
+
9
+ **The number to check**: `IDENTIFIED` or `UNIDENTIFIED` on the identity, `VALID` or `INVALID` on the number, and the `gap:` lines - every component the site cannot name is named as not named, with the reason.
10
+
11
+ **What this page will not call a measurement**: a sidetrack code that was not given - ten digits name the Well Origin and nothing below it, and "00" is the original hole only when the regulator assigned it, so a Wellbore is not identified from a ten-digit number. A Wellbore Contact Interval (the injection interval) or a Wellbore Completion - those are the permit's and the completion report's facts, and the identity names them as not held. A county name - the standard's state and county booklet carries it, this program does not, and a county code is checked for its shape only. A state from a reserved code (52-54) or a code outside the standard.