gea-program 0.59.0__tar.gz → 0.60.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 (370) hide show
  1. {gea_program-0.59.0 → gea_program-0.60.0}/PKG-INFO +11 -1
  2. {gea_program-0.59.0 → gea_program-0.60.0}/README.md +10 -0
  3. {gea_program-0.59.0 → gea_program-0.60.0}/gea/__init__.py +1 -1
  4. {gea_program-0.59.0 → gea_program-0.60.0}/gea/__main__.py +10 -1
  5. {gea_program-0.59.0 → gea_program-0.60.0}/gea/acceptance_tests.py +175 -2
  6. {gea_program-0.59.0 → gea_program-0.60.0}/gea/dashboard.py +149 -128
  7. {gea_program-0.59.0 → gea_program-0.60.0}/gea/files.py +2 -0
  8. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/catalog.md +1 -1
  9. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/month-end.md +1 -1
  10. {gea_program-0.59.0 → gea_program-0.60.0}/gea/invoice_check.py +3 -1
  11. {gea_program-0.59.0 → gea_program-0.60.0}/gea/month_end.py +20 -2
  12. {gea_program-0.59.0 → gea_program-0.60.0}/gea/rig_state.py +4 -2
  13. {gea_program-0.59.0 → gea_program-0.60.0}/gea/service.py +32 -9
  14. {gea_program-0.59.0 → gea_program-0.60.0}/gea/tls.py +3 -0
  15. {gea_program-0.59.0 → gea_program-0.60.0}/gea/web/app.html +16 -10
  16. {gea_program-0.59.0 → gea_program-0.60.0}/gea/workspace.py +38 -0
  17. {gea_program-0.59.0 → gea_program-0.60.0}/gea_program.egg-info/PKG-INFO +11 -1
  18. {gea_program-0.59.0 → gea_program-0.60.0}/pyproject.toml +1 -1
  19. {gea_program-0.59.0 → gea_program-0.60.0}/LICENSE +0 -0
  20. {gea_program-0.59.0 → gea_program-0.60.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  21. {gea_program-0.59.0 → gea_program-0.60.0}/gea/accuracy_statement.py +0 -0
  22. {gea_program-0.59.0 → gea_program-0.60.0}/gea/alarm_engine.py +0 -0
  23. {gea_program-0.59.0 → gea_program-0.60.0}/gea/alerts.py +0 -0
  24. {gea_program-0.59.0 → gea_program-0.60.0}/gea/backup.py +0 -0
  25. {gea_program-0.59.0 → gea_program-0.60.0}/gea/bench.py +0 -0
  26. {gea_program-0.59.0 → gea_program-0.60.0}/gea/blind_harness.py +0 -0
  27. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  28. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  29. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  30. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  31. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  32. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  33. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  34. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  35. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  36. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  37. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  38. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  39. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  40. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  41. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  42. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  43. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  44. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  45. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  46. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  47. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  48. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  49. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  50. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  51. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  52. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  53. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  54. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  55. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  56. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  57. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  58. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  59. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  60. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  61. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  62. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  63. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  64. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  65. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  66. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  67. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  68. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  69. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  70. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  71. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  72. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  73. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  74. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  75. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  76. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  77. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  78. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  79. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  80. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  81. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  82. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  83. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  84. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  85. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  86. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  87. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  88. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  89. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  90. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  91. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  92. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  93. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  94. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/med_160_sapropels.txt +0 -0
  95. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  96. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  97. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  98. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  99. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  100. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  101. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  102. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  103. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  104. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  105. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  106. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  107. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  108. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  109. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  110. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  111. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  112. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  113. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  114. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  115. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  116. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  117. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  118. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  119. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  120. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  121. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  122. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  123. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  124. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  125. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  126. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  127. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  128. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  129. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  130. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  131. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog_map.py +0 -0
  132. {gea_program-0.59.0 → gea_program-0.60.0}/gea/catalog_register.py +0 -0
  133. {gea_program-0.59.0 → gea_program-0.60.0}/gea/certificates.py +0 -0
  134. {gea_program-0.59.0 → gea_program-0.60.0}/gea/cli.py +0 -0
  135. {gea_program-0.59.0 → gea_program-0.60.0}/gea/client_reports.py +0 -0
  136. {gea_program-0.59.0 → gea_program-0.60.0}/gea/collision.py +0 -0
  137. {gea_program-0.59.0 → gea_program-0.60.0}/gea/config_versioning.py +0 -0
  138. {gea_program-0.59.0 → gea_program-0.60.0}/gea/conformance.py +0 -0
  139. {gea_program-0.59.0 → gea_program-0.60.0}/gea/control_room.py +0 -0
  140. {gea_program-0.59.0 → gea_program-0.60.0}/gea/correlation.py +0 -0
  141. {gea_program-0.59.0 → gea_program-0.60.0}/gea/declarations.py +0 -0
  142. {gea_program-0.59.0 → gea_program-0.60.0}/gea/decline.py +0 -0
  143. {gea_program-0.59.0 → gea_program-0.60.0}/gea/deviation.py +0 -0
  144. {gea_program-0.59.0 → gea_program-0.60.0}/gea/dlis.py +0 -0
  145. {gea_program-0.59.0 → gea_program-0.60.0}/gea/dnp3.py +0 -0
  146. {gea_program-0.59.0 → gea_program-0.60.0}/gea/doctor.py +0 -0
  147. {gea_program-0.59.0 → gea_program-0.60.0}/gea/dossier.py +0 -0
  148. {gea_program-0.59.0 → gea_program-0.60.0}/gea/dossier_verify.py +0 -0
  149. {gea_program-0.59.0 → gea_program-0.60.0}/gea/downhole_engine.py +0 -0
  150. {gea_program-0.59.0 → gea_program-0.60.0}/gea/drift_monitor.py +0 -0
  151. {gea_program-0.59.0 → gea_program-0.60.0}/gea/drill_string.py +0 -0
  152. {gea_program-0.59.0 → gea_program-0.60.0}/gea/earth_model.py +0 -0
  153. {gea_program-0.59.0 → gea_program-0.60.0}/gea/economics.py +0 -0
  154. {gea_program-0.59.0 → gea_program-0.60.0}/gea/ed25519.py +0 -0
  155. {gea_program-0.59.0 → gea_program-0.60.0}/gea/edr.py +0 -0
  156. {gea_program-0.59.0 → gea_program-0.60.0}/gea/elevation.py +0 -0
  157. {gea_program-0.59.0 → gea_program-0.60.0}/gea/etp.py +0 -0
  158. {gea_program-0.59.0 → gea_program-0.60.0}/gea/etp12_schemas.json +0 -0
  159. {gea_program-0.59.0 → gea_program-0.60.0}/gea/example_register_map.json +0 -0
  160. {gea_program-0.59.0 → gea_program-0.60.0}/gea/fat_sat.py +0 -0
  161. {gea_program-0.59.0 → gea_program-0.60.0}/gea/fixtures/README.md +0 -0
  162. {gea_program-0.59.0 → gea_program-0.60.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  163. {gea_program-0.59.0 → gea_program-0.60.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  164. {gea_program-0.59.0 → gea_program-0.60.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  165. {gea_program-0.59.0 → gea_program-0.60.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  166. {gea_program-0.59.0 → gea_program-0.60.0}/gea/follower.py +0 -0
  167. {gea_program-0.59.0 → gea_program-0.60.0}/gea/forward_model.py +0 -0
  168. {gea_program-0.59.0 → gea_program-0.60.0}/gea/frac_hits.py +0 -0
  169. {gea_program-0.59.0 → gea_program-0.60.0}/gea/friction_priors.py +0 -0
  170. {gea_program-0.59.0 → gea_program-0.60.0}/gea/gamma.py +0 -0
  171. {gea_program-0.59.0 → gea_program-0.60.0}/gea/gauge_aging.py +0 -0
  172. {gea_program-0.59.0 → gea_program-0.60.0}/gea/gauge_specs.py +0 -0
  173. {gea_program-0.59.0 → gea_program-0.60.0}/gea/geodesy.py +0 -0
  174. {gea_program-0.59.0 → gea_program-0.60.0}/gea/geomag.py +0 -0
  175. {gea_program-0.59.0 → gea_program-0.60.0}/gea/gravity_reference.py +0 -0
  176. {gea_program-0.59.0 → gea_program-0.60.0}/gea/hartip.py +0 -0
  177. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/IT_SECURITY.md +0 -0
  178. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/TESTER_GUIDE.md +0 -0
  179. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/alarms.md +0 -0
  180. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/alerts.md +0 -0
  181. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/audit-update.md +0 -0
  182. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/backup.md +0 -0
  183. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/charts.md +0 -0
  184. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/collision.md +0 -0
  185. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/company-sign-in.md +0 -0
  186. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/conformance.md +0 -0
  187. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/control-room.md +0 -0
  188. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/data-in.md +0 -0
  189. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/declare.md +0 -0
  190. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/decline.md +0 -0
  191. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/dlis.md +0 -0
  192. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/doctor.md +0 -0
  193. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/dossier.md +0 -0
  194. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/drift.md +0 -0
  195. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/drillstring.md +0 -0
  196. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/economics.md +0 -0
  197. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/edr.md +0 -0
  198. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/etp.md +0 -0
  199. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/field-ports.md +0 -0
  200. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/files.md +0 -0
  201. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/frac-hits.md +0 -0
  202. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/historian.md +0 -0
  203. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/https.md +0 -0
  204. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/hydraulics.md +0 -0
  205. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/instruments.md +0 -0
  206. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/interval.md +0 -0
  207. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/invoice-check.md +0 -0
  208. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/map.md +0 -0
  209. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/notifications.md +0 -0
  210. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/opseis.md +0 -0
  211. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/osdu.md +0 -0
  212. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/pad-summary.md +0 -0
  213. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/patches.md +0 -0
  214. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/penetrator.md +0 -0
  215. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/plan.md +0 -0
  216. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/pore-pressure.md +0 -0
  217. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/ppdm.md +0 -0
  218. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/quakeml.md +0 -0
  219. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/quality.md +0 -0
  220. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/report-history.md +0 -0
  221. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/rig-time.md +0 -0
  222. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/seedlink.md +0 -0
  223. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/seismic.md +0 -0
  224. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/setup.md +0 -0
  225. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/simulation.md +0 -0
  226. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/site.md +0 -0
  227. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/sites.md +0 -0
  228. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/slip.md +0 -0
  229. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/soak.md +0 -0
  230. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/sra.md +0 -0
  231. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/start.md +0 -0
  232. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/structure.md +0 -0
  233. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/tools.md +0 -0
  234. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/torque-drag.md +0 -0
  235. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/transients.md +0 -0
  236. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/upkeep.md +0 -0
  237. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/vibration.md +0 -0
  238. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/well-report.md +0 -0
  239. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/well-tests.md +0 -0
  240. {gea_program-0.59.0 → gea_program-0.60.0}/gea/help/well3d.md +0 -0
  241. {gea_program-0.59.0 → gea_program-0.60.0}/gea/helplib.py +0 -0
  242. {gea_program-0.59.0 → gea_program-0.60.0}/gea/housekeeping.py +0 -0
  243. {gea_program-0.59.0 → gea_program-0.60.0}/gea/hydraulics.py +0 -0
  244. {gea_program-0.59.0 → gea_program-0.60.0}/gea/interval.py +0 -0
  245. {gea_program-0.59.0 → gea_program-0.60.0}/gea/inverse_engine.py +0 -0
  246. {gea_program-0.59.0 → gea_program-0.60.0}/gea/jobs.py +0 -0
  247. {gea_program-0.59.0 → gea_program-0.60.0}/gea/live_ports.py +0 -0
  248. {gea_program-0.59.0 → gea_program-0.60.0}/gea/loadtest.py +0 -0
  249. {gea_program-0.59.0 → gea_program-0.60.0}/gea/matplotlib_demo.py +0 -0
  250. {gea_program-0.59.0 → gea_program-0.60.0}/gea/modbus.py +0 -0
  251. {gea_program-0.59.0 → gea_program-0.60.0}/gea/modbus_rtu.py +0 -0
  252. {gea_program-0.59.0 → gea_program-0.60.0}/gea/model_card.py +0 -0
  253. {gea_program-0.59.0 → gea_program-0.60.0}/gea/mqtt_port.py +0 -0
  254. {gea_program-0.59.0 → gea_program-0.60.0}/gea/notify.py +0 -0
  255. {gea_program-0.59.0 → gea_program-0.60.0}/gea/oidc.py +0 -0
  256. {gea_program-0.59.0 → gea_program-0.60.0}/gea/oidc_mock.py +0 -0
  257. {gea_program-0.59.0 → gea_program-0.60.0}/gea/op_seismicity.py +0 -0
  258. {gea_program-0.59.0 → gea_program-0.60.0}/gea/opcua_port.py +0 -0
  259. {gea_program-0.59.0 → gea_program-0.60.0}/gea/operator_app.py +0 -0
  260. {gea_program-0.59.0 → gea_program-0.60.0}/gea/osdu.py +0 -0
  261. {gea_program-0.59.0 → gea_program-0.60.0}/gea/pad_summary.py +0 -0
  262. {gea_program-0.59.0 → gea_program-0.60.0}/gea/patches.py +0 -0
  263. {gea_program-0.59.0 → gea_program-0.60.0}/gea/pdf_report.py +0 -0
  264. {gea_program-0.59.0 → gea_program-0.60.0}/gea/penetrator.py +0 -0
  265. {gea_program-0.59.0 → gea_program-0.60.0}/gea/permits.py +0 -0
  266. {gea_program-0.59.0 → gea_program-0.60.0}/gea/piwebapi.py +0 -0
  267. {gea_program-0.59.0 → gea_program-0.60.0}/gea/pore_pressure.py +0 -0
  268. {gea_program-0.59.0 → gea_program-0.60.0}/gea/ports.py +0 -0
  269. {gea_program-0.59.0 → gea_program-0.60.0}/gea/ppdm.py +0 -0
  270. {gea_program-0.59.0 → gea_program-0.60.0}/gea/profile_catalog.py +0 -0
  271. {gea_program-0.59.0 → gea_program-0.60.0}/gea/project.py +0 -0
  272. {gea_program-0.59.0 → gea_program-0.60.0}/gea/qt6_downhole_app.py +0 -0
  273. {gea_program-0.59.0 → gea_program-0.60.0}/gea/quakeml.py +0 -0
  274. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reconciler.py +0 -0
  275. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  276. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  277. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/WMM2025.COF +0 -0
  278. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  279. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/eia_prices.provenance.json +0 -0
  280. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  281. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/iscwsa_rev4_example.json +0 -0
  282. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/iscwsa_rev5_example.json +0 -0
  283. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
  284. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
  285. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  286. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  287. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  288. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  289. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/mahoning_example.json +0 -0
  290. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/mud_motor_specs.json +0 -0
  291. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/owsg_tool_models.json +0 -0
  292. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/penetrator_ratings.json +0 -0
  293. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/seismicity_protocols.json +0 -0
  294. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/tool_sources.json +0 -0
  295. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/us_outline.json +0 -0
  296. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/us_outline.provenance.json +0 -0
  297. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_3d.provenance.json +0 -0
  298. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  299. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  300. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  301. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_surfaces.json.gz +0 -0
  302. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  303. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  304. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  305. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  306. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  307. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  308. {gea_program-0.59.0 → gea_program-0.60.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  309. {gea_program-0.59.0 → gea_program-0.60.0}/gea/report_history.py +0 -0
  310. {gea_program-0.59.0 → gea_program-0.60.0}/gea/rock_inventory.py +0 -0
  311. {gea_program-0.59.0 → gea_program-0.60.0}/gea/sample_record.py +0 -0
  312. {gea_program-0.59.0 → gea_program-0.60.0}/gea/sample_well_profile.csv +0 -0
  313. {gea_program-0.59.0 → gea_program-0.60.0}/gea/sbom.py +0 -0
  314. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seedlink.py +0 -0
  315. {gea_program-0.59.0 → gea_program-0.60.0}/gea/segy.py +0 -0
  316. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic.py +0 -0
  317. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_array.py +0 -0
  318. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_assoc.py +0 -0
  319. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_detect.py +0 -0
  320. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_film.py +0 -0
  321. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_harmonic.py +0 -0
  322. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_qc.py +0 -0
  323. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_response.py +0 -0
  324. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_signature.py +0 -0
  325. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_track.py +0 -0
  326. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_uncertainty.py +0 -0
  327. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_unlisted.py +0 -0
  328. {gea_program-0.59.0 → gea_program-0.60.0}/gea/seismic_watch.py +0 -0
  329. {gea_program-0.59.0 → gea_program-0.60.0}/gea/sensor_swap.py +0 -0
  330. {gea_program-0.59.0 → gea_program-0.60.0}/gea/series_cache.py +0 -0
  331. {gea_program-0.59.0 → gea_program-0.60.0}/gea/service_life.py +0 -0
  332. {gea_program-0.59.0 → gea_program-0.60.0}/gea/setup_checklist.py +0 -0
  333. {gea_program-0.59.0 → gea_program-0.60.0}/gea/shapefile.py +0 -0
  334. {gea_program-0.59.0 → gea_program-0.60.0}/gea/shell.py +0 -0
  335. {gea_program-0.59.0 → gea_program-0.60.0}/gea/shut_in.py +0 -0
  336. {gea_program-0.59.0 → gea_program-0.60.0}/gea/simulation.py +0 -0
  337. {gea_program-0.59.0 → gea_program-0.60.0}/gea/site_map.py +0 -0
  338. {gea_program-0.59.0 → gea_program-0.60.0}/gea/sla_report.py +0 -0
  339. {gea_program-0.59.0 → gea_program-0.60.0}/gea/slip.py +0 -0
  340. {gea_program-0.59.0 → gea_program-0.60.0}/gea/soak.py +0 -0
  341. {gea_program-0.59.0 → gea_program-0.60.0}/gea/sra.py +0 -0
  342. {gea_program-0.59.0 → gea_program-0.60.0}/gea/store_forward.py +0 -0
  343. {gea_program-0.59.0 → gea_program-0.60.0}/gea/strata_join.py +0 -0
  344. {gea_program-0.59.0 → gea_program-0.60.0}/gea/structure.py +0 -0
  345. {gea_program-0.59.0 → gea_program-0.60.0}/gea/supervisor.py +0 -0
  346. {gea_program-0.59.0 → gea_program-0.60.0}/gea/survey_cmd.py +0 -0
  347. {gea_program-0.59.0 → gea_program-0.60.0}/gea/survey_view.py +0 -0
  348. {gea_program-0.59.0 → gea_program-0.60.0}/gea/telemetry.py +0 -0
  349. {gea_program-0.59.0 → gea_program-0.60.0}/gea/tool_catalog.py +0 -0
  350. {gea_program-0.59.0 → gea_program-0.60.0}/gea/tool_library.py +0 -0
  351. {gea_program-0.59.0 → gea_program-0.60.0}/gea/tool_taxonomy.py +0 -0
  352. {gea_program-0.59.0 → gea_program-0.60.0}/gea/torque_drag.py +0 -0
  353. {gea_program-0.59.0 → gea_program-0.60.0}/gea/transient.py +0 -0
  354. {gea_program-0.59.0 → gea_program-0.60.0}/gea/vibration.py +0 -0
  355. {gea_program-0.59.0 → gea_program-0.60.0}/gea/well3d.py +0 -0
  356. {gea_program-0.59.0 → gea_program-0.60.0}/gea/well_assembler.py +0 -0
  357. {gea_program-0.59.0 → gea_program-0.60.0}/gea/well_report.py +0 -0
  358. {gea_program-0.59.0 → gea_program-0.60.0}/gea/well_test_validation.py +0 -0
  359. {gea_program-0.59.0 → gea_program-0.60.0}/gea/wellplan.py +0 -0
  360. {gea_program-0.59.0 → gea_program-0.60.0}/gea/wellpos.py +0 -0
  361. {gea_program-0.59.0 → gea_program-0.60.0}/gea/wits0.py +0 -0
  362. {gea_program-0.59.0 → gea_program-0.60.0}/gea/witsml.py +0 -0
  363. {gea_program-0.59.0 → gea_program-0.60.0}/gea/xlsx_read.py +0 -0
  364. {gea_program-0.59.0 → gea_program-0.60.0}/gea_program.egg-info/SOURCES.txt +0 -0
  365. {gea_program-0.59.0 → gea_program-0.60.0}/gea_program.egg-info/dependency_links.txt +0 -0
  366. {gea_program-0.59.0 → gea_program-0.60.0}/gea_program.egg-info/entry_points.txt +0 -0
  367. {gea_program-0.59.0 → gea_program-0.60.0}/gea_program.egg-info/requires.txt +0 -0
  368. {gea_program-0.59.0 → gea_program-0.60.0}/gea_program.egg-info/top_level.txt +0 -0
  369. {gea_program-0.59.0 → gea_program-0.60.0}/setup.cfg +0 -0
  370. {gea_program-0.59.0 → gea_program-0.60.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.59.0
3
+ Version: 0.60.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
@@ -673,6 +673,16 @@ and the decline terms. A well with no directional survey is read on the
673
673
  drilling recorder's own Interpolated TVD where one is needed, labelled as
674
674
  the recorder's interpolation. `gea help declare`.
675
675
 
676
+ ### When one well cannot be reported
677
+
678
+ Refresh every report goes on past a well it cannot read: that well is
679
+ written down with why (`reports\wells\<well>\refresh_failed.json`), shown
680
+ NOT REPORTED on the overview and the printed dashboard, and named by the
681
+ refresh job and the month-end packet; every other well is reported. A
682
+ catalogue well is added only from an entry that carries a downhole gauge
683
+ series, with a well inside it and its gauge depth; a live seismic station
684
+ whose feed has written nothing yet is listed as having no recording yet.
685
+
676
686
  ### Hydraulics and ECD
677
687
 
678
688
  The pressures the mud makes while it circulates, from the six-speed
@@ -634,6 +634,16 @@ and the decline terms. A well with no directional survey is read on the
634
634
  drilling recorder's own Interpolated TVD where one is needed, labelled as
635
635
  the recorder's interpolation. `gea help declare`.
636
636
 
637
+ ### When one well cannot be reported
638
+
639
+ Refresh every report goes on past a well it cannot read: that well is
640
+ written down with why (`reports\wells\<well>\refresh_failed.json`), shown
641
+ NOT REPORTED on the overview and the printed dashboard, and named by the
642
+ refresh job and the month-end packet; every other well is reported. A
643
+ catalogue well is added only from an entry that carries a downhole gauge
644
+ series, with a well inside it and its gauge depth; a live seismic station
645
+ whose feed has written nothing yet is listed as having no recording yet.
646
+
637
647
  ### Hydraulics and ECD
638
648
 
639
649
  The pressures the mud makes while it circulates, from the six-speed
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.59.0"
126
+ __version__ = "0.60.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -3226,7 +3226,16 @@ def main(argv=None) -> int:
3226
3226
  print(_json.dumps(ws.migrate_out_dir(a.from_dir, actor=a.actor), indent=1)); return 0
3227
3227
  elif a.action == "refresh":
3228
3228
  r = ws.refresh_dashboard(actor=a.actor, outages=a.outage, month=a.month, run_sat=a.sat)
3229
- print("dashboard:", r.get("index") if isinstance(r, dict) else r); return 0
3229
+ print("dashboard:", r.get("index") if isinstance(r, dict) else r)
3230
+ for wname, kinds in sorted(((r.get("reports") or {}) if isinstance(r, dict) else {}).items()):
3231
+ if "refresh_failed" in kinds:
3232
+ try:
3233
+ with open(os.path.join(ws.reports_dir, "wells", wname, "refresh_failed.json"), encoding="utf-8") as fh:
3234
+ why = _json.load(fh).get("reason", "")
3235
+ except (OSError, ValueError):
3236
+ why = ""
3237
+ print(f"well {wname}: its reports could not be written - {why} (the other wells were reported)")
3238
+ return 0
3230
3239
  elif a.action == "audit":
3231
3240
  for row in ws.audit_log(limit=50):
3232
3241
  print(row["utc"], row["actor"], row["action"], _json.dumps(row["detail"]))
@@ -10472,8 +10472,9 @@ def section_cn_four(tmp: str) -> None:
10472
10472
  t0 = now.replace(hour=10, minute=59, second=0) if now.hour < 23 else now.replace(hour=10, minute=59, second=0)
10473
10473
  due_before = PS.due_now(ws, t0)
10474
10474
  s1 = PS.write(ws, __version__, "tester", nt, now + timedelta(minutes=2))
10475
- due_after = PS.due_now(ws, (now + timedelta(minutes=2)).replace(hour=23, minute=0))
10476
- nxt = (now + timedelta(days=1)).replace(hour=11, minute=1, second=0)
10475
+ w1 = now + timedelta(minutes=2) # the summary's own day: two minutes on may already be tomorrow (UTC)
10476
+ due_after = PS.due_now(ws, w1.replace(hour=23, minute=0))
10477
+ nxt = (w1 + timedelta(days=1)).replace(hour=11, minute=1, second=0)
10477
10478
  due_next = PS.due_now(ws, nxt)
10478
10479
  ws.audit("tester", "test.after", {})
10479
10480
  s2 = PS.write(ws, __version__, "tester", nt, now + timedelta(minutes=5), send=False)
@@ -13138,6 +13139,177 @@ def section_db_declarations_engineering(tmp: str) -> None:
13138
13139
  + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
13139
13140
 
13140
13141
 
13142
+ def section_dc_every_tab(tmp: str) -> None:
13143
+ """Section DC - every tab and button answers: the Verification tab reads the site reports itself; the Rig time
13144
+ tab and the charts with a live well whose stream has not arrived; a catalogue well only from a gauge series, its
13145
+ well and its gauge depth; one well that cannot be read never stops the refresh or the month-end, and is shown
13146
+ with why; a live seismic station with no recording yet is a state, not a failure; a field left empty is answered
13147
+ in the form's words."""
13148
+ import http.cookiejar
13149
+ import urllib.error
13150
+ import urllib.request
13151
+ from . import dashboard as D
13152
+ from . import hydraulics as HY
13153
+ from . import month_end as ME
13154
+ from .service import Service, Users, friendly_error
13155
+ from .workspace import Workspace, WorkspaceError, catalog_gauge_wells, gauge_entries
13156
+ pkg = Path(__file__).parent
13157
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
13158
+ d = Path(tmp, "every_tab"); d.mkdir()
13159
+ hy = HY.selftest_files(str(d / "hy"))
13160
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
13161
+ w_edr = ws.add_well_file(hy["record"], display="HY-1", actor="tester")["id"]
13162
+ w_cat = ws.add_well_catalog("volve_f12_f14_production_excerpt", "15/9-F-12", 10000.0, actor="tester")["id"]
13163
+
13164
+ # -- DC1 a catalogue well: a gauge series, a well inside it, a gauge depth -------------------------------------------
13165
+ def answer(*a):
13166
+ try:
13167
+ ws.add_well_catalog(*a, actor="tester")
13168
+ return ""
13169
+ except WorkspaceError as e:
13170
+ return str(e)
13171
+ other = next(k for k in __import__("gea").CATALOG if k not in gauge_entries())
13172
+ m_series, m_empty = answer(other, "", 10000.0), answer("volve_f12_f14_production_excerpt", "", 10000.0)
13173
+ m_wrong, m_md0 = answer("volve_f12_f14_production_excerpt", "15/9-F-99", 10000.0), answer("volve_f12_f14_production_excerpt", "15/9-F-14", 0)
13174
+ m_none = answer("volve_f12_f14_production_excerpt", "15/9-F-14", None)
13175
+ n_wells = len(ws.wells())
13176
+ c1 = {
13177
+ "gauges": gauge_entries() == ["volve_f12_f14_production_excerpt"] and catalog_gauge_wells("volve_f12_f14_production_excerpt") == ["15/9-F-12", "15/9-F-14"]
13178
+ and catalog_gauge_wells(other) == [],
13179
+ "answered": "is not a downhole gauge series" in m_series and "volve_f12_f14_production_excerpt" in m_series
13180
+ and "choose the well inside" in m_empty and "15/9-F-14" in m_empty and "has no well '15/9-F-99'" in m_wrong
13181
+ and "greater than zero" in m_md0 and "the station MD is needed" in m_none,
13182
+ "nothing_added": n_wells == 2,
13183
+ "form": "entries.filter(e => (e.gauge_wells || []).length)" in page_src and '<select id="cWell">' in page_src
13184
+ and "Enter the station MD in feet" in page_src,
13185
+ }
13186
+ failed1 = [k for k, v in c1.items() if not v]
13187
+ ok(not failed1,
13188
+ "DC1 a catalogue well is added only from an entry that carries a downhole gauge series, naming a well inside it and a gauge depth above zero: "
13189
+ "an entry that is not a series, a well left empty or not in the entry, and a depth of zero or none are each answered with what to choose, and "
13190
+ "nothing is added; the form lists only the series and their wells" + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
13191
+
13192
+ # -- DC2 one well that cannot be read: the refresh and the month-end go on, and say which and why ----------------------
13193
+ bad = ws._register({"id": "old_entry__", "display": "old entry", "kind": "catalog", "files": [],
13194
+ "source": {"entry": other, "well": ""}, "station_md_ft": 10000.0}, "tester")["id"] # as an older version could add it
13195
+ res = ws.refresh_dashboard(actor="tester")
13196
+ rep = res.get("reports") or {}
13197
+ data = D.collect(ws.reports_dir)
13198
+ failed_row = next((w for w in data["wells"] if w["slug"].startswith(other)), {})
13199
+ me = ME.assemble(ws, "2026-09", actor="tester")
13200
+ me_err = " | ".join(me.get("errors") or [])
13201
+ cat_name = f"volve_f12_f14_production_excerpt__15/9-F-12".replace("/", "-")
13202
+ c2 = {
13203
+ "others_reported": "drift" in rep.get(cat_name, []) and "drilling_record" in rep.get(w_edr, []),
13204
+ "failed_named": any("refresh_failed" in v for k, v in rep.items() if k.startswith(other))
13205
+ and os.path.isfile(os.path.join(ws.reports_dir, "wells", f"{other}__", "refresh_failed.json")),
13206
+ "shown": bool(failed_row.get("failed")) and D._drift_status(failed_row)[1].startswith("NOT REPORTED"),
13207
+ "month_end": me.get("included", 0) > 0 and f"well {other}__" in me_err and "dashboard refresh" not in me_err,
13208
+ "page": "w.failed ? statusPill('serious', 'not reported')" in page_src,
13209
+ }
13210
+ ws.remove_well(bad, actor="tester")
13211
+ failed2 = [k for k, v in c2.items() if not v]
13212
+ ok(not failed2,
13213
+ "DC2 a well that cannot be read (a catalogue entry an older version accepted) never stops the refresh: every other well is reported, the "
13214
+ "failed one is written down with why and shown NOT REPORTED on the overview; the month-end packet is assembled and names that one well"
13215
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
13216
+
13217
+ # -- DC3 a live well with no stream yet, a live station with no recording yet ------------------------------------------
13218
+ from .wits0 import EXAMPLE_CONFIG as _WITS
13219
+ wits_cfg = d / "wits0.json"
13220
+ wits_cfg.write_text(json.dumps(_WITS), encoding="utf-8")
13221
+ w_live = ws.add_well_live("Rig feed", "wits0", str(wits_cfg), actor="tester")["id"]
13222
+ st = ws.add_live_station("Pad 3 seismometer", {"host": "127.0.0.1", "port": 18000, "streams": [{"network": "XX", "station": "PAD3"}]}, actor="tester")
13223
+ try:
13224
+ ws.refresh_seismic(st["id"], actor="tester")
13225
+ m_seis = ""
13226
+ except WorkspaceError as e:
13227
+ m_seis = str(e)
13228
+ me2 = ME.assemble(ws, "2026-09", actor="tester", refresh=True)
13229
+ with open(os.path.splitext(me2["index"])[0] + ".json", encoding="utf-8") as fh_:
13230
+ me2_items = json.load(fh_).get("items") or []
13231
+ seis_item = next((i for i in me2_items if i["kind"] == "seismic" and i["id"] == st["id"]), {})
13232
+
13233
+ Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
13234
+ svc = Service(wsp, port=0, scheduler=False).start()
13235
+ base_url = svc.url.rstrip("/")
13236
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
13237
+
13238
+ def call(method, path, body=None):
13239
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
13240
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
13241
+ try:
13242
+ with o.open(rq, timeout=120) as rr_:
13243
+ return rr_.status, json.loads(rr_.read())
13244
+ except urllib.error.HTTPError as e:
13245
+ return e.code, json.loads(e.read())
13246
+ try:
13247
+ call("POST", "/api/login", {"name": "op", "password": "op-pass-123"})
13248
+ st_rig, rig = call("GET", "/api/rig-time")
13249
+ st_cm, cm = call("GET", f"/api/wells/{w_live}/chart/meta")
13250
+ st_cx, _ = call("GET", "/api/wells/no-such-well/chart/meta")
13251
+ st_cat, cat = call("GET", "/api/catalog")
13252
+ st_add, add = call("POST", "/api/wells/catalog", {"entry": other, "well": "", "station_md": 10000})
13253
+ st_pp, pp = call("POST", "/api/pore-pressure/measure", {"well_id": w_edr, "depth_m": "", "value": "", "unit": "psi", "kind": "pore", "source": "x"})
13254
+ st_cf, cf = call("GET", "/api/config/")
13255
+ st_cd, cd = call("GET", "/api/config/defaults/")
13256
+ st_fw, fw = call("POST", "/api/files/watch", {"root": ""})
13257
+ st_sl, sl = call("GET", "/api/seismic")
13258
+ st_tp, tp = call("POST", f"/api/wells/{w_edr}/transient-params", {"params": {"mu_cp": 0.8}})
13259
+ st_tp0, tp0 = call("POST", f"/api/wells/{w_edr}/transient-params", {"params": {}})
13260
+ finally:
13261
+ svc.stop()
13262
+ gauges_api = {e["entry"]: e["gauge_wells"] for e in cat.get("entries", []) if e.get("gauge_wells")}
13263
+ c3 = {
13264
+ "seismic": "has no recording yet" in m_seis and seis_item.get("status") == "NOT AVAILABLE" and "no recording yet" in seis_item.get("reason", "")
13265
+ and not any(st["id"] in e for e in me2.get("errors") or []),
13266
+ "rig_time": st_rig == 200 and isinstance(rig, dict),
13267
+ "chart": st_cm == 200 and cm.get("available") is False and bool(cm.get("reason")) and st_cx == 404
13268
+ and "meta.available === false" in page_src,
13269
+ "catalog_api": st_cat == 200 and gauges_api == {"volve_f12_f14_production_excerpt": ["15/9-F-12", "15/9-F-14"]}
13270
+ and st_add == 400 and "is not a downhole gauge series" in add.get("error", ""),
13271
+ "site_reports": st_sl == 200 and "site_reports" in sl,
13272
+ }
13273
+ failed3 = [k for k, v in c3.items() if not v]
13274
+ ok(not failed3,
13275
+ "DC3 a live seismic station whose feed has written nothing is answered as having no recording yet and is NOT AVAILABLE in the month-end, not "
13276
+ "a failure; the Rig time tab answers with a live well whose stream has not arrived; that well's chart says there is nothing to chart yet "
13277
+ "(an unknown well is still not found); the catalogue API names each series' wells and answers a well that is not one with what to choose"
13278
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
13279
+
13280
+ # -- DC4 the form's words, and the Verification tab's own data -------------------------------------------------------------
13281
+ def _tls_msg():
13282
+ from . import tls as TLS
13283
+ try:
13284
+ TLS.use_site_certificate(ws, "tester", "", "")
13285
+ return ""
13286
+ except ValueError as e:
13287
+ return str(e)
13288
+ verify = page_src[page_src.index("VIEWS.verify = async"):]
13289
+ verify = verify[:verify.index("\nVIEWS.", 10)] if "\nVIEWS." in verify[10:] else verify
13290
+ c4 = {
13291
+ "words": friendly_error("could not convert string to float: ''") == "a number is needed: a field was left empty"
13292
+ and friendly_error("could not convert string to float: 'abc'") == "'abc' is not a number"
13293
+ and friendly_error("no such well") == "no such well",
13294
+ "pore": st_pp == 400 and pp.get("error") == "a number is needed: a field was left empty",
13295
+ "config": st_cf == 404 and "choose a configuration by name first" in cf.get("error", "")
13296
+ and st_cd == 404 and "choose a configuration by name first" in cd.get("error", "")
13297
+ and page_src.count("if (!$('#cfgName').value.trim()) return toast('Choose a configuration by name first', 'err');") == 3,
13298
+ "watch": st_fw == 400 and "add an import root under Administration first" in fw.get("error", "")
13299
+ and "w.title = 'Add an import root under Administration first'" in page_src,
13300
+ "params": st_tp == 200 and st_tp0 == 200 and "unknown parameter" not in json.dumps(tp0),
13301
+ "tls_path": _tls_msg().startswith("give the certificate file's full path"),
13302
+ "verify": "let siteReports = {}; try { siteReports = (await get('/api/seismic')).site_reports || {}; }" in verify
13303
+ and verify.index("let siteReports") < verify.index("siteReports.seismic_dataset_report"),
13304
+ }
13305
+ failed4 = [k for k, v in c4.items() if not v]
13306
+ ok(not failed4,
13307
+ "DC4 a field left empty, or text where a number goes, is answered in the form's words rather than the program's; a configuration button with "
13308
+ "no name chosen, a folder watch with no import root and the site's certificate with no path given say what to do first; a well's transient "
13309
+ "parameters saved empty are cleared; the Verification tab reads the site reports itself"
13310
+ + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
13311
+
13312
+
13141
13313
  def PR_read_ok(path) -> bool:
13142
13314
  from . import pdf_report as PR
13143
13315
  try:
@@ -13255,6 +13427,7 @@ def main() -> int:
13255
13427
  section_cz_it_review(tmp)
13256
13428
  section_da_hydraulics(tmp)
13257
13429
  section_db_declarations_engineering(tmp)
13430
+ section_dc_every_tab(tmp)
13258
13431
  if _FAILS:
13259
13432
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
13260
13433
  for f in _FAILS:
@@ -63,6 +63,9 @@ def collect(out_dir: str) -> dict:
63
63
  continue
64
64
  meta = _load(os.path.join(d, 'well.json')) or {}
65
65
  w = {'name': meta.get('display', name), 'slug': name, 'dir': d, 'source': meta.get('source', '')}
66
+ rf = _load(os.path.join(d, 'refresh_failed.json'))
67
+ if rf or meta.get('ingest_failed'): # a well whose reports could not be written is shown, with why
68
+ w['failed'] = (rf or {}).get('reason') or meta.get('ingest_failed')
66
69
  gd = _load(os.path.join(d, 'gauge_drift_report.json'))
67
70
  if gd:
68
71
  ev = gd.get('evaluation', {})
@@ -157,6 +160,8 @@ def _sev(cls: str) -> int:
157
160
 
158
161
  def _drift_status(w: dict) -> tuple:
159
162
  d = w.get('drift')
163
+ if not d and w.get('failed'):
164
+ return 'critical', 'NOT REPORTED - ' + str(w['failed'])[:160]
160
165
  if not d:
161
166
  if w.get('drilling'):
162
167
  dl = w['drilling']
@@ -369,142 +374,158 @@ def orchestrate(out_dir: str, catalog_wells: List[dict], file_wells: List[dict],
369
374
  name = f"{cw['entry']}__{cw['well']}".replace('/', '-')
370
375
  d = os.path.join(out_dir, 'wells', name)
371
376
  os.makedirs(d, exist_ok=True)
372
- stream, smap = production_live_stream(cw['entry'], cw['well'], cw['md_ft'])
373
- with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
374
- json.dump({'display': cw['well'], 'source': f"{cw['entry']} (station MD {cw['md_ft']:g} ft, caller-supplied)"}, f)
375
- ms = None
376
- if monitor_root:
377
- mon = DriftMonitor(cfg, os.path.join(monitor_root, name), well_name=cw['well'])
378
- r = mon.run_scheduled(stream, station_map=smap, force=True)
379
- ev = r['evaluation']
380
- ms = mon.status(); ms['history'] = mon.history(); ms['change_log'] = mon.change_log()
381
- else:
382
- ev = Reconciler(cfg).reconcile(stream, station_map=smap)
383
- write(gauge_drift_report(ev, stream, well_name=cw['well'], program_version=_v, gauge_spec=gauge_spec, monitor_status=ms), d)
384
- made.setdefault(name, []).append('drift')
385
- crit = load_criteria(criteria_path)
386
- src = CATALOG[cw['entry']].stream()
387
- det = WellTestValidator(crit, volve_channel_map(cw['well'])).detect(src)
388
- trail = ApprovalTrail(os.path.join(d, 'well_test_records'), levels=crit.get('approval_levels'))
389
- write(well_test_report(det, approvals=trail, well_name=cw['well'], program_version=_v), d, basename='well_test_validation')
390
- made[name].append('well_tests')
377
+ if os.path.exists(os.path.join(d, 'refresh_failed.json')):
378
+ os.remove(os.path.join(d, 'refresh_failed.json'))
379
+ try:
380
+ stream, smap = production_live_stream(cw['entry'], cw['well'], cw['md_ft'])
381
+ with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
382
+ json.dump({'display': cw['well'], 'source': f"{cw['entry']} (station MD {cw['md_ft']:g} ft, caller-supplied)"}, f)
383
+ ms = None
384
+ if monitor_root:
385
+ mon = DriftMonitor(cfg, os.path.join(monitor_root, name), well_name=cw['well'])
386
+ r = mon.run_scheduled(stream, station_map=smap, force=True)
387
+ ev = r['evaluation']
388
+ ms = mon.status(); ms['history'] = mon.history(); ms['change_log'] = mon.change_log()
389
+ else:
390
+ ev = Reconciler(cfg).reconcile(stream, station_map=smap)
391
+ write(gauge_drift_report(ev, stream, well_name=cw['well'], program_version=_v, gauge_spec=gauge_spec, monitor_status=ms), d)
392
+ made.setdefault(name, []).append('drift')
393
+ crit = load_criteria(criteria_path)
394
+ src = CATALOG[cw['entry']].stream()
395
+ det = WellTestValidator(crit, volve_channel_map(cw['well'])).detect(src)
396
+ trail = ApprovalTrail(os.path.join(d, 'well_test_records'), levels=crit.get('approval_levels'))
397
+ write(well_test_report(det, approvals=trail, well_name=cw['well'], program_version=_v), d, basename='well_test_validation')
398
+ made[name].append('well_tests')
399
+ except Exception as ex: # one well that cannot be read never blocks the other wells' reports
400
+ with open(os.path.join(d, 'refresh_failed.json'), 'w', encoding='utf-8') as f:
401
+ json.dump({'well': name, 'entry': cw.get('entry'), 'reason': f'{type(ex).__name__}: {ex}',
402
+ 'evaluated_at_utc': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}, f, indent=1)
403
+ made.setdefault(name, []).append('refresh_failed')
391
404
  for fw in file_wells:
392
405
  name = fw.get('name') or os.path.splitext(os.path.basename(fw['path']))[0]
393
406
  d = os.path.join(out_dir, 'wells', name)
394
407
  os.makedirs(d, exist_ok=True)
395
- from .files import read_any
408
+ if os.path.exists(os.path.join(d, 'refresh_failed.json')):
409
+ os.remove(os.path.join(d, 'refresh_failed.json'))
396
410
  try:
397
- stream = read_any(fw['path'])
398
- except Exception as ex: # one unreadable file never blocks the other wells' reports
399
- with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
400
- json.dump({'display': name, 'source': fw['path'], 'ingest_failed': f'{type(ex).__name__}: {ex}'}, f)
401
- made.setdefault(name, []).append('ingest_failed')
402
- continue
403
- with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
404
- json.dump({'display': name, 'source': fw['path'], 'index_kind': stream.index_kind, 'source_format': stream.source_format}, f)
405
- cache_dir = os.path.join(series_cache_root, name) if series_cache_root else None
406
- if stream.source_format == 'edr': # a drilling recorder's export: the drilling record, not the gauge pipeline
407
- from .edr import write_record
408
- try:
409
- write_record(stream, d, _v, cache_dir=cache_dir)
410
- made.setdefault(name, []).append('drilling_record')
411
- except Exception as ex:
412
- with open(os.path.join(d, 'drilling_record_not_written.json'), 'w', encoding='utf-8') as f:
413
- json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}'}, f, indent=1)
414
- made.setdefault(name, []).append('drilling_record_not_written')
415
- continue
416
- if cache_dir:
411
+ from .files import read_any
417
412
  try:
418
- from . import series_cache as _SCH
419
- _SCH.build(stream, cache_dir)
420
- except Exception:
421
- pass # the cache is a convenience; the page builds it on first view
422
- if stream.index_kind != 'time_s': # a depth log is a survey input: run the strata survey, not the gauge pipeline
413
+ stream = read_any(fw['path'])
414
+ except Exception as ex: # one unreadable file never blocks the other wells' reports
415
+ with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
416
+ json.dump({'display': name, 'source': fw['path'], 'ingest_failed': f'{type(ex).__name__}: {ex}'}, f)
417
+ made.setdefault(name, []).append('ingest_failed')
418
+ continue
419
+ with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
420
+ json.dump({'display': name, 'source': fw['path'], 'index_kind': stream.index_kind, 'source_format': stream.source_format}, f)
421
+ cache_dir = os.path.join(series_cache_root, name) if series_cache_root else None
422
+ if stream.source_format == 'edr': # a drilling recorder's export: the drilling record, not the gauge pipeline
423
+ from .edr import write_record
424
+ try:
425
+ write_record(stream, d, _v, cache_dir=cache_dir)
426
+ made.setdefault(name, []).append('drilling_record')
427
+ except Exception as ex:
428
+ with open(os.path.join(d, 'drilling_record_not_written.json'), 'w', encoding='utf-8') as f:
429
+ json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}'}, f, indent=1)
430
+ made.setdefault(name, []).append('drilling_record_not_written')
431
+ continue
432
+ if cache_dir:
433
+ try:
434
+ from . import series_cache as _SCH
435
+ _SCH.build(stream, cache_dir)
436
+ except Exception:
437
+ pass # the cache is a convenience; the page builds it on first view
438
+ if stream.index_kind != 'time_s': # a depth log is a survey input: run the strata survey, not the gauge pipeline
439
+ try:
440
+ from .survey_cmd import run_survey
441
+ txt, machine = run_survey(path=fw['path'])
442
+ with open(os.path.join(d, 'survey_report.txt'), 'w', encoding='utf-8') as f:
443
+ f.write(txt)
444
+ with open(os.path.join(d, 'survey.json'), 'w', encoding='utf-8') as f:
445
+ json.dump(machine, f, indent=1, default=str)
446
+ made.setdefault(name, []).append('survey')
447
+ except Exception as ex:
448
+ with open(os.path.join(d, 'survey_not_run.json'), 'w', encoding='utf-8') as f:
449
+ json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}', 'index_kind': stream.index_kind}, f, indent=1)
450
+ made.setdefault(name, []).append('survey_not_run')
451
+ continue
452
+ cat = TagCatalogue.from_stream(stream, gauge_spec=gauge_spec)
453
+ # instruments: swaps segment the fit; certificates print beside the bias (Band 2)
454
+ inst = None
455
+ rdir = fw.get('records_dir')
456
+ fit_stream = stream
457
+ if rdir and os.path.isdir(rdir):
458
+ from .sensor_swap import SwapRegister, segment_mask, detect_swaps
459
+ from .certificates import CertificateRegister
460
+ reg = SwapRegister(os.path.join(rdir, 'sensor_swaps.jsonl'))
461
+ fit_stream, notes = segment_mask(stream, reg)
462
+ latest = reg.latest_by_tag()
463
+ certs = CertificateRegister(os.path.join(rdir, 'certificates.jsonl'))
464
+ ptags = [c for c in stream.channels if c.lower().startswith('p_')]
465
+ cert_rows = certs.status(ptags, serial_by_tag={k: v['new_serial'] for k, v in latest.items()}) if certs.list() else []
466
+ try:
467
+ def _unrecorded(c):
468
+ for tg in [c['tag_id']] + [x.strip() for x in (c.get('ambiguous_with') or '').split(',') if x.strip()]:
469
+ e = latest.get(tg)
470
+ if e and abs(_epoch_s(e['swap_utc']) - _epoch_s(c.get('swap_utc'))) <= 2 * 3600 * 24:
471
+ return False # this step is explained by a recorded swap (on it or on its ambiguous peer)
472
+ return True
473
+ cands = [c for c in detect_swaps(stream) if _unrecorded(c)]
474
+ except Exception:
475
+ cands = []
476
+ inst = {'swaps': reg.list(), 'swap_notes': notes, 'certificates': cert_rows, 'candidates': cands[:10],
477
+ 'certificate_summary': CertificateRegister.summary(cert_rows) if cert_rows else None}
478
+ with open(os.path.join(d, 'instruments.json'), 'w', encoding='utf-8') as f:
479
+ json.dump(inst, f, indent=1, default=str)
423
480
  try:
424
- from .survey_cmd import run_survey
425
- txt, machine = run_survey(path=fw['path'])
426
- with open(os.path.join(d, 'survey_report.txt'), 'w', encoding='utf-8') as f:
427
- f.write(txt)
428
- with open(os.path.join(d, 'survey.json'), 'w', encoding='utf-8') as f:
429
- json.dump(machine, f, indent=1, default=str)
430
- made.setdefault(name, []).append('survey')
431
- except Exception as ex:
432
- with open(os.path.join(d, 'survey_not_run.json'), 'w', encoding='utf-8') as f:
433
- json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}', 'index_kind': stream.index_kind}, f, indent=1)
434
- made.setdefault(name, []).append('survey_not_run')
435
- continue
436
- cat = TagCatalogue.from_stream(stream, gauge_spec=gauge_spec)
437
- # instruments: swaps segment the fit; certificates print beside the bias (Band 2)
438
- inst = None
439
- rdir = fw.get('records_dir')
440
- fit_stream = stream
441
- if rdir and os.path.isdir(rdir):
442
- from .sensor_swap import SwapRegister, segment_mask, detect_swaps
443
- from .certificates import CertificateRegister
444
- reg = SwapRegister(os.path.join(rdir, 'sensor_swaps.jsonl'))
445
- fit_stream, notes = segment_mask(stream, reg)
446
- latest = reg.latest_by_tag()
447
- certs = CertificateRegister(os.path.join(rdir, 'certificates.jsonl'))
448
- ptags = [c for c in stream.channels if c.lower().startswith('p_')]
449
- cert_rows = certs.status(ptags, serial_by_tag={k: v['new_serial'] for k, v in latest.items()}) if certs.list() else []
481
+ ev = Reconciler(cfg).reconcile(fit_stream)
482
+ write(gauge_drift_report(ev, stream, catalogue=cat, well_name=name, program_version=_v, gauge_spec=gauge_spec, instruments=inst), d)
483
+ made.setdefault(name, []).append('drift')
484
+ except ValueError as e: # a stream with no downhole gauge stations (a drill-floor feed): drift does not apply
485
+ with open(os.path.join(d, 'drift_not_applicable.json'), 'w', encoding='utf-8') as f:
486
+ json.dump({'well': name, 'reason': str(e), 'channels': list(stream.channels), 'evaluated_at_utc': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}, f, indent=1)
487
+ made.setdefault(name, []).append('drift_not_applicable')
488
+ # shut-ins and the build-up analyses (Band 2): criteria and parameters from the records folder when present
450
489
  try:
451
- def _unrecorded(c):
452
- for tg in [c['tag_id']] + [x.strip() for x in (c.get('ambiguous_with') or '').split(',') if x.strip()]:
453
- e = latest.get(tg)
454
- if e and abs(_epoch_s(e['swap_utc']) - _epoch_s(c.get('swap_utc'))) <= 2 * 3600 * 24:
455
- return False # this step is explained by a recorded swap (on it or on its ambiguous peer)
456
- return True
457
- cands = [c for c in detect_swaps(stream) if _unrecorded(c)]
458
- except Exception:
459
- cands = []
460
- inst = {'swaps': reg.list(), 'swap_notes': notes, 'certificates': cert_rows, 'candidates': cands[:10],
461
- 'certificate_summary': CertificateRegister.summary(cert_rows) if cert_rows else None}
462
- with open(os.path.join(d, 'instruments.json'), 'w', encoding='utf-8') as f:
463
- json.dump(inst, f, indent=1, default=str)
464
- try:
465
- ev = Reconciler(cfg).reconcile(fit_stream)
466
- write(gauge_drift_report(ev, stream, catalogue=cat, well_name=name, program_version=_v, gauge_spec=gauge_spec, instruments=inst), d)
467
- made.setdefault(name, []).append('drift')
468
- except ValueError as e: # a stream with no downhole gauge stations (a drill-floor feed): drift does not apply
469
- with open(os.path.join(d, 'drift_not_applicable.json'), 'w', encoding='utf-8') as f:
470
- json.dump({'well': name, 'reason': str(e), 'channels': list(stream.channels), 'evaluated_at_utc': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}, f, indent=1)
471
- made.setdefault(name, []).append('drift_not_applicable')
472
- # shut-ins and the build-up analyses (Band 2): criteria and parameters from the records folder when present
473
- try:
474
- from .shut_in import detect_shut_ins, extract_buildup, load_criteria as _si_crit
475
- from .transient import analyze_buildup
476
- from .client_reports import transient_report
477
- si_c = _si_crit(os.path.join(rdir, 'shut_in.json') if rdir else None)
478
- det = detect_shut_ins(stream, criteria=si_c)
479
- prm = None
480
- if rdir and os.path.isfile(os.path.join(rdir, 'transient_params.json')):
481
- with open(os.path.join(rdir, 'transient_params.json'), encoding='utf-8') as f:
482
- prm = json.load(f)
483
- analyses = []
484
- for si in [x for x in det['shut_ins'] if x['qualified']][:6]:
485
- b = extract_buildup(stream, si)
486
- a = analyze_buildup(b['dt_h'], b['p_ws'], b['tp_h'], b['p_wf'], prm)
487
- a['shut_in_id'] = si['shut_in_id']
488
- analyses.append(a)
489
- if det['shut_ins']:
490
- write(transient_report(det, analyses, well_name=name, program_version=_v), d, basename='pressure_transient_report')
491
- made.setdefault(name, []).append('transient')
492
- else:
493
- with open(os.path.join(d, 'shut_ins.json'), 'w', encoding='utf-8') as f:
494
- json.dump(det, f, indent=1, default=str)
495
- except Exception as ex: # the transient look is additional; it never blocks the alarms
496
- with open(os.path.join(d, 'transient_not_run.json'), 'w', encoding='utf-8') as f:
497
- json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}'}, f, indent=1)
498
- recs = records_from_stream(stream, cat)
499
- eng = AlarmEngine(defaults_from_catalogue(cat), event_log_path=os.path.join(d, 'alarm_events.jsonl'))
500
- eng.process(recs)
501
- write(alarm_event_report(eng, well_name=name, program_version=_v), d, basename='alarm_event_report')
502
- made[name].append('alarms')
503
- if outages:
504
- cad = next((t.cadence_s for t in cat.tags.values() if t.cadence_s), 60.0)
505
- sim = simulate([r for r in recs if r.tag_id.startswith('P_raw')], parse_outages(outages), BufferConfig(cadence_s=cad))
506
- write(data_resilience_report(sim, site_name=name, program_version=_v), d, basename='data_resilience_report')
507
- made[name].append('resilience')
490
+ from .shut_in import detect_shut_ins, extract_buildup, load_criteria as _si_crit
491
+ from .transient import analyze_buildup
492
+ from .client_reports import transient_report
493
+ si_c = _si_crit(os.path.join(rdir, 'shut_in.json') if rdir else None)
494
+ det = detect_shut_ins(stream, criteria=si_c)
495
+ prm = None
496
+ if rdir and os.path.isfile(os.path.join(rdir, 'transient_params.json')):
497
+ with open(os.path.join(rdir, 'transient_params.json'), encoding='utf-8') as f:
498
+ prm = json.load(f)
499
+ analyses = []
500
+ for si in [x for x in det['shut_ins'] if x['qualified']][:6]:
501
+ b = extract_buildup(stream, si)
502
+ a = analyze_buildup(b['dt_h'], b['p_ws'], b['tp_h'], b['p_wf'], prm)
503
+ a['shut_in_id'] = si['shut_in_id']
504
+ analyses.append(a)
505
+ if det['shut_ins']:
506
+ write(transient_report(det, analyses, well_name=name, program_version=_v), d, basename='pressure_transient_report')
507
+ made.setdefault(name, []).append('transient')
508
+ else:
509
+ with open(os.path.join(d, 'shut_ins.json'), 'w', encoding='utf-8') as f:
510
+ json.dump(det, f, indent=1, default=str)
511
+ except Exception as ex: # the transient look is additional; it never blocks the alarms
512
+ with open(os.path.join(d, 'transient_not_run.json'), 'w', encoding='utf-8') as f:
513
+ json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}'}, f, indent=1)
514
+ recs = records_from_stream(stream, cat)
515
+ eng = AlarmEngine(defaults_from_catalogue(cat), event_log_path=os.path.join(d, 'alarm_events.jsonl'))
516
+ eng.process(recs)
517
+ write(alarm_event_report(eng, well_name=name, program_version=_v), d, basename='alarm_event_report')
518
+ made[name].append('alarms')
519
+ if outages:
520
+ cad = next((t.cadence_s for t in cat.tags.values() if t.cadence_s), 60.0)
521
+ sim = simulate([r for r in recs if r.tag_id.startswith('P_raw')], parse_outages(outages), BufferConfig(cadence_s=cad))
522
+ write(data_resilience_report(sim, site_name=name, program_version=_v), d, basename='data_resilience_report')
523
+ made[name].append('resilience')
524
+ except Exception as ex: # one well that cannot be reported never blocks the other wells' reports
525
+ with open(os.path.join(d, 'refresh_failed.json'), 'w', encoding='utf-8') as f:
526
+ json.dump({'well': name, 'source': fw.get('path'), 'reason': f'{type(ex).__name__}: {ex}',
527
+ 'evaluated_at_utc': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}, f, indent=1)
528
+ made.setdefault(name, []).append('refresh_failed')
508
529
  write(accuracy_statement_report(library_backtest(), program_version=_v), out_dir, basename='accuracy_statement')
509
530
  cards = build_cards()
510
531
  for c in cards:
@@ -232,6 +232,8 @@ class Roots:
232
232
  """An absolute path inside the named root, or WorkspaceError when it would leave it."""
233
233
  root = next((r for r in self.list(which) if r['name'] == name), None)
234
234
  if root is None:
235
+ if not name:
236
+ raise WorkspaceError(f'no {which} root chosen' + ('' if self.list(which) else f': add an {which} root under Administration first'))
235
237
  raise WorkspaceError(f'no {which} root named {name}')
236
238
  base = os.path.realpath(root['path'])
237
239
  full = os.path.realpath(os.path.join(base, rel or ''))
@@ -2,7 +2,7 @@
2
2
 
3
3
  > The program ships 52 public datasets, each with a provenance sidecar saying where it came from, under what licence, and what it covers. A sidecar does not say what in this program reads the entry; the register does. `gea catalog --list` prints every entry with the bands, commands and reports that take it, or the plain statement that nothing does yet - CARRIED, NO BAND - and the gate holds that register to the code. The Catalogue tab on the dashboard is the world map of them: where each entry was drilled, taken from the entry's own file (a PANGAEA event line, a LAS header, a KTB Gauss-Krueger grid reference unprojected and shifted to WGS84) or from the sidecar's source record, or NO POSITION - nothing is looked up or guessed. 44 of the 52 are positioned, on 33 marks; the 8 that are not are listed with what their files do say. The entries with a use: every LAS log runs through the strata survey; seven measured-friction and strength datasets become the envelope a slip statement discloses its coefficient against; the Volve core checks the published sandstone range.
4
4
 
5
- **The command**: `gea catalog --list` (the register), `--unused` (the carried entries, with the reason), `--check` (every entry read through its own reader, the shape or the error), `--entry volve_f12_f14_production_excerpt --info` (the provenance in full and how it reads), `--entry volve_f12_f14_production_excerpt --stream-csv out.csv` (the entry as a stream file any other command ingests), `--friction` (the measured friction and strength, each with its source, and the envelope against Byerlee's 0.6), `--core` (the Volve 15/9-19 A core plugs against the published sandstone range). `--positions` (where every entry was drilled, how that is known, or NO POSITION), `--map catalogue.svg` (the world map as a file: the Natural Earth 1:110m coastline, one numbered mark per position, the key, the NO POSITION list), `--check-positions` (the sidecars' recorded positions against a fresh read of the files - the gate's check; `tools/catalog_positions.py` rewrites them after a catalogue change). `gea survey --catalog collingwood_1_28_ks_complete` runs the strata survey on any LAS entry - Scorpio E1 (South Australia), University 6-17 (Texas), Kennetcook #2 (Nova Scotia), Collingwood 1-28 (Kansas), L07-01 (Dutch North Sea), 15/9-19 SR (Volve) - and says what it found or why it did not estimate.
5
+ **The command**: `gea catalog --list` (the register), `--unused` (the carried entries, with the reason), `--check` (every entry read through its own reader, the shape or the error), `--entry volve_f12_f14_production_excerpt --info` (the provenance in full and how it reads), `--entry volve_f12_f14_production_excerpt --stream-csv out.csv` (the entry as a stream file any other command ingests), `--friction` (the measured friction and strength, each with its source, and the envelope against Byerlee's 0.6), `--core` (the Volve 15/9-19 A core plugs against the published sandstone range). `--positions` (where every entry was drilled, how that is known, or NO POSITION), `--map catalogue.svg` (the world map as a file: the Natural Earth 1:110m coastline, one numbered mark per position, the key, the NO POSITION list), `--check-positions` (the sidecars' recorded positions against a fresh read of the files - the gate's check; `tools/catalog_positions.py` rewrites them after a catalogue change). `gea survey --catalog collingwood_1_28_ks_complete` runs the strata survey on any LAS entry - Scorpio E1 (South Australia), University 6-17 (Texas), Kennetcook #2 (Nova Scotia), Collingwood 1-28 (Kansas), L07-01 (Dutch North Sea), 15/9-19 SR (Volve) - and says what it found or why it did not estimate. A catalogue well (Wells -> Add a catalogue well, or `gea workspace --path C:\site --action add-catalog --entry volve_f12_f14_production_excerpt --well 15/9-F-12 --station-md 10000`) is added only from an entry that carries a downhole gauge series, naming a well inside it and the gauge depth from the completion records; the form lists only those entries.
6
6
 
7
7
  **What it writes**: each sidecar's `position` block - status, latitude, longitude, datum, the source line it was read from, and for a Gauss-Krueger reference the grid values, the DHDN position and the shift applied; the map SVG; the register - entry, kind, tier, file size, USED or CARRIED, and each use in the user's terms; the check - 52 of 52 entries read; the friction envelope - friction coefficients 0.21 to 0.90 on 70 measurements from three datasets (ODP Costa Rica, IODP Hikurangi, IODP Nankai), cohesion 188-990 kPa (Sumatra), vane shear 19.5-290 kPa (Barbados), fault-zone peak shear strength 1.7-1.9 MPa at slow slip (JFAST), uniaxial compressive strength 3-265 MPa on crystalline cores (KTB pilot hole) - none from the Permian, and the page says so; the core check - bulk density per plug as grain density x (1 - porosity) + brine x porosity, the brine stated, 65 of 70 Volve plugs inside the published 2.05-2.55 g/cc sandstone range, written into the rock inventory model card's evaluation.
8
8