gea-program 0.59.0__tar.gz → 0.61.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 (372) hide show
  1. {gea_program-0.59.0 → gea_program-0.61.0}/PKG-INFO +22 -1
  2. {gea_program-0.59.0 → gea_program-0.61.0}/README.md +21 -0
  3. {gea_program-0.59.0 → gea_program-0.61.0}/gea/__init__.py +1 -1
  4. {gea_program-0.59.0 → gea_program-0.61.0}/gea/__main__.py +60 -1
  5. {gea_program-0.59.0 → gea_program-0.61.0}/gea/acceptance_tests.py +393 -3
  6. gea_program-0.61.0/gea/casing_design.py +631 -0
  7. {gea_program-0.59.0 → gea_program-0.61.0}/gea/dashboard.py +149 -128
  8. {gea_program-0.59.0 → gea_program-0.61.0}/gea/declarations.py +69 -1
  9. {gea_program-0.59.0 → gea_program-0.61.0}/gea/dossier.py +12 -1
  10. {gea_program-0.59.0 → gea_program-0.61.0}/gea/files.py +2 -0
  11. gea_program-0.61.0/gea/help/casing.md +19 -0
  12. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/catalog.md +1 -1
  13. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/declare.md +1 -1
  14. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/interval.md +1 -1
  15. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/month-end.md +1 -1
  16. {gea_program-0.59.0 → gea_program-0.61.0}/gea/helplib.py +2 -1
  17. {gea_program-0.59.0 → gea_program-0.61.0}/gea/hydraulics.py +37 -0
  18. {gea_program-0.59.0 → gea_program-0.61.0}/gea/interval.py +27 -11
  19. {gea_program-0.59.0 → gea_program-0.61.0}/gea/invoice_check.py +3 -1
  20. {gea_program-0.59.0 → gea_program-0.61.0}/gea/month_end.py +20 -2
  21. {gea_program-0.59.0 → gea_program-0.61.0}/gea/rig_state.py +4 -2
  22. {gea_program-0.59.0 → gea_program-0.61.0}/gea/service.py +46 -9
  23. {gea_program-0.59.0 → gea_program-0.61.0}/gea/tls.py +3 -0
  24. {gea_program-0.59.0 → gea_program-0.61.0}/gea/web/app.html +78 -11
  25. {gea_program-0.59.0 → gea_program-0.61.0}/gea/workspace.py +46 -1
  26. {gea_program-0.59.0 → gea_program-0.61.0}/gea_program.egg-info/PKG-INFO +22 -1
  27. {gea_program-0.59.0 → gea_program-0.61.0}/gea_program.egg-info/SOURCES.txt +2 -0
  28. {gea_program-0.59.0 → gea_program-0.61.0}/pyproject.toml +1 -1
  29. {gea_program-0.59.0 → gea_program-0.61.0}/LICENSE +0 -0
  30. {gea_program-0.59.0 → gea_program-0.61.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  31. {gea_program-0.59.0 → gea_program-0.61.0}/gea/accuracy_statement.py +0 -0
  32. {gea_program-0.59.0 → gea_program-0.61.0}/gea/alarm_engine.py +0 -0
  33. {gea_program-0.59.0 → gea_program-0.61.0}/gea/alerts.py +0 -0
  34. {gea_program-0.59.0 → gea_program-0.61.0}/gea/backup.py +0 -0
  35. {gea_program-0.59.0 → gea_program-0.61.0}/gea/bench.py +0 -0
  36. {gea_program-0.59.0 → gea_program-0.61.0}/gea/blind_harness.py +0 -0
  37. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  38. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  39. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  40. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  41. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  42. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  43. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  44. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  45. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  46. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  47. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  48. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  49. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  50. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  51. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  52. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  53. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  54. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  55. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  56. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  57. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  58. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  59. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  60. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  61. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  62. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  63. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  64. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  65. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  66. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  67. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  68. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  69. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  70. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  71. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  72. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  73. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  74. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  75. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  76. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  77. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  78. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  79. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  80. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  81. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  82. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  83. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  84. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  85. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  86. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  87. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  88. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  89. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  90. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  91. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  92. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  93. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  94. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  95. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  96. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  97. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  98. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  99. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  100. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  101. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  102. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  103. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  104. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/med_160_sapropels.txt +0 -0
  105. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  106. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  107. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  108. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  109. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  110. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  111. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  112. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  113. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  114. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  115. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  116. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  117. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  118. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  119. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  120. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  121. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  122. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  123. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  124. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  125. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  126. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  127. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  128. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  129. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  130. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  131. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  132. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  133. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  134. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  135. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  136. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  137. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  138. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  139. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  140. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  141. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog_map.py +0 -0
  142. {gea_program-0.59.0 → gea_program-0.61.0}/gea/catalog_register.py +0 -0
  143. {gea_program-0.59.0 → gea_program-0.61.0}/gea/certificates.py +0 -0
  144. {gea_program-0.59.0 → gea_program-0.61.0}/gea/cli.py +0 -0
  145. {gea_program-0.59.0 → gea_program-0.61.0}/gea/client_reports.py +0 -0
  146. {gea_program-0.59.0 → gea_program-0.61.0}/gea/collision.py +0 -0
  147. {gea_program-0.59.0 → gea_program-0.61.0}/gea/config_versioning.py +0 -0
  148. {gea_program-0.59.0 → gea_program-0.61.0}/gea/conformance.py +0 -0
  149. {gea_program-0.59.0 → gea_program-0.61.0}/gea/control_room.py +0 -0
  150. {gea_program-0.59.0 → gea_program-0.61.0}/gea/correlation.py +0 -0
  151. {gea_program-0.59.0 → gea_program-0.61.0}/gea/decline.py +0 -0
  152. {gea_program-0.59.0 → gea_program-0.61.0}/gea/deviation.py +0 -0
  153. {gea_program-0.59.0 → gea_program-0.61.0}/gea/dlis.py +0 -0
  154. {gea_program-0.59.0 → gea_program-0.61.0}/gea/dnp3.py +0 -0
  155. {gea_program-0.59.0 → gea_program-0.61.0}/gea/doctor.py +0 -0
  156. {gea_program-0.59.0 → gea_program-0.61.0}/gea/dossier_verify.py +0 -0
  157. {gea_program-0.59.0 → gea_program-0.61.0}/gea/downhole_engine.py +0 -0
  158. {gea_program-0.59.0 → gea_program-0.61.0}/gea/drift_monitor.py +0 -0
  159. {gea_program-0.59.0 → gea_program-0.61.0}/gea/drill_string.py +0 -0
  160. {gea_program-0.59.0 → gea_program-0.61.0}/gea/earth_model.py +0 -0
  161. {gea_program-0.59.0 → gea_program-0.61.0}/gea/economics.py +0 -0
  162. {gea_program-0.59.0 → gea_program-0.61.0}/gea/ed25519.py +0 -0
  163. {gea_program-0.59.0 → gea_program-0.61.0}/gea/edr.py +0 -0
  164. {gea_program-0.59.0 → gea_program-0.61.0}/gea/elevation.py +0 -0
  165. {gea_program-0.59.0 → gea_program-0.61.0}/gea/etp.py +0 -0
  166. {gea_program-0.59.0 → gea_program-0.61.0}/gea/etp12_schemas.json +0 -0
  167. {gea_program-0.59.0 → gea_program-0.61.0}/gea/example_register_map.json +0 -0
  168. {gea_program-0.59.0 → gea_program-0.61.0}/gea/fat_sat.py +0 -0
  169. {gea_program-0.59.0 → gea_program-0.61.0}/gea/fixtures/README.md +0 -0
  170. {gea_program-0.59.0 → gea_program-0.61.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  171. {gea_program-0.59.0 → gea_program-0.61.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  172. {gea_program-0.59.0 → gea_program-0.61.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  173. {gea_program-0.59.0 → gea_program-0.61.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  174. {gea_program-0.59.0 → gea_program-0.61.0}/gea/follower.py +0 -0
  175. {gea_program-0.59.0 → gea_program-0.61.0}/gea/forward_model.py +0 -0
  176. {gea_program-0.59.0 → gea_program-0.61.0}/gea/frac_hits.py +0 -0
  177. {gea_program-0.59.0 → gea_program-0.61.0}/gea/friction_priors.py +0 -0
  178. {gea_program-0.59.0 → gea_program-0.61.0}/gea/gamma.py +0 -0
  179. {gea_program-0.59.0 → gea_program-0.61.0}/gea/gauge_aging.py +0 -0
  180. {gea_program-0.59.0 → gea_program-0.61.0}/gea/gauge_specs.py +0 -0
  181. {gea_program-0.59.0 → gea_program-0.61.0}/gea/geodesy.py +0 -0
  182. {gea_program-0.59.0 → gea_program-0.61.0}/gea/geomag.py +0 -0
  183. {gea_program-0.59.0 → gea_program-0.61.0}/gea/gravity_reference.py +0 -0
  184. {gea_program-0.59.0 → gea_program-0.61.0}/gea/hartip.py +0 -0
  185. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/IT_SECURITY.md +0 -0
  186. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/TESTER_GUIDE.md +0 -0
  187. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/alarms.md +0 -0
  188. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/alerts.md +0 -0
  189. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/audit-update.md +0 -0
  190. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/backup.md +0 -0
  191. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/charts.md +0 -0
  192. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/collision.md +0 -0
  193. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/company-sign-in.md +0 -0
  194. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/conformance.md +0 -0
  195. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/control-room.md +0 -0
  196. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/data-in.md +0 -0
  197. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/decline.md +0 -0
  198. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/dlis.md +0 -0
  199. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/doctor.md +0 -0
  200. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/dossier.md +0 -0
  201. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/drift.md +0 -0
  202. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/drillstring.md +0 -0
  203. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/economics.md +0 -0
  204. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/edr.md +0 -0
  205. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/etp.md +0 -0
  206. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/field-ports.md +0 -0
  207. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/files.md +0 -0
  208. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/frac-hits.md +0 -0
  209. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/historian.md +0 -0
  210. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/https.md +0 -0
  211. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/hydraulics.md +0 -0
  212. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/instruments.md +0 -0
  213. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/invoice-check.md +0 -0
  214. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/map.md +0 -0
  215. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/notifications.md +0 -0
  216. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/opseis.md +0 -0
  217. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/osdu.md +0 -0
  218. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/pad-summary.md +0 -0
  219. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/patches.md +0 -0
  220. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/penetrator.md +0 -0
  221. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/plan.md +0 -0
  222. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/pore-pressure.md +0 -0
  223. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/ppdm.md +0 -0
  224. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/quakeml.md +0 -0
  225. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/quality.md +0 -0
  226. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/report-history.md +0 -0
  227. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/rig-time.md +0 -0
  228. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/seedlink.md +0 -0
  229. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/seismic.md +0 -0
  230. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/setup.md +0 -0
  231. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/simulation.md +0 -0
  232. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/site.md +0 -0
  233. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/sites.md +0 -0
  234. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/slip.md +0 -0
  235. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/soak.md +0 -0
  236. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/sra.md +0 -0
  237. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/start.md +0 -0
  238. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/structure.md +0 -0
  239. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/tools.md +0 -0
  240. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/torque-drag.md +0 -0
  241. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/transients.md +0 -0
  242. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/upkeep.md +0 -0
  243. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/vibration.md +0 -0
  244. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/well-report.md +0 -0
  245. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/well-tests.md +0 -0
  246. {gea_program-0.59.0 → gea_program-0.61.0}/gea/help/well3d.md +0 -0
  247. {gea_program-0.59.0 → gea_program-0.61.0}/gea/housekeeping.py +0 -0
  248. {gea_program-0.59.0 → gea_program-0.61.0}/gea/inverse_engine.py +0 -0
  249. {gea_program-0.59.0 → gea_program-0.61.0}/gea/jobs.py +0 -0
  250. {gea_program-0.59.0 → gea_program-0.61.0}/gea/live_ports.py +0 -0
  251. {gea_program-0.59.0 → gea_program-0.61.0}/gea/loadtest.py +0 -0
  252. {gea_program-0.59.0 → gea_program-0.61.0}/gea/matplotlib_demo.py +0 -0
  253. {gea_program-0.59.0 → gea_program-0.61.0}/gea/modbus.py +0 -0
  254. {gea_program-0.59.0 → gea_program-0.61.0}/gea/modbus_rtu.py +0 -0
  255. {gea_program-0.59.0 → gea_program-0.61.0}/gea/model_card.py +0 -0
  256. {gea_program-0.59.0 → gea_program-0.61.0}/gea/mqtt_port.py +0 -0
  257. {gea_program-0.59.0 → gea_program-0.61.0}/gea/notify.py +0 -0
  258. {gea_program-0.59.0 → gea_program-0.61.0}/gea/oidc.py +0 -0
  259. {gea_program-0.59.0 → gea_program-0.61.0}/gea/oidc_mock.py +0 -0
  260. {gea_program-0.59.0 → gea_program-0.61.0}/gea/op_seismicity.py +0 -0
  261. {gea_program-0.59.0 → gea_program-0.61.0}/gea/opcua_port.py +0 -0
  262. {gea_program-0.59.0 → gea_program-0.61.0}/gea/operator_app.py +0 -0
  263. {gea_program-0.59.0 → gea_program-0.61.0}/gea/osdu.py +0 -0
  264. {gea_program-0.59.0 → gea_program-0.61.0}/gea/pad_summary.py +0 -0
  265. {gea_program-0.59.0 → gea_program-0.61.0}/gea/patches.py +0 -0
  266. {gea_program-0.59.0 → gea_program-0.61.0}/gea/pdf_report.py +0 -0
  267. {gea_program-0.59.0 → gea_program-0.61.0}/gea/penetrator.py +0 -0
  268. {gea_program-0.59.0 → gea_program-0.61.0}/gea/permits.py +0 -0
  269. {gea_program-0.59.0 → gea_program-0.61.0}/gea/piwebapi.py +0 -0
  270. {gea_program-0.59.0 → gea_program-0.61.0}/gea/pore_pressure.py +0 -0
  271. {gea_program-0.59.0 → gea_program-0.61.0}/gea/ports.py +0 -0
  272. {gea_program-0.59.0 → gea_program-0.61.0}/gea/ppdm.py +0 -0
  273. {gea_program-0.59.0 → gea_program-0.61.0}/gea/profile_catalog.py +0 -0
  274. {gea_program-0.59.0 → gea_program-0.61.0}/gea/project.py +0 -0
  275. {gea_program-0.59.0 → gea_program-0.61.0}/gea/qt6_downhole_app.py +0 -0
  276. {gea_program-0.59.0 → gea_program-0.61.0}/gea/quakeml.py +0 -0
  277. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reconciler.py +0 -0
  278. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  279. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  280. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/WMM2025.COF +0 -0
  281. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  282. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/eia_prices.provenance.json +0 -0
  283. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  284. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/iscwsa_rev4_example.json +0 -0
  285. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/iscwsa_rev5_example.json +0 -0
  286. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
  287. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
  288. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  289. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  290. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  291. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  292. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/mahoning_example.json +0 -0
  293. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/mud_motor_specs.json +0 -0
  294. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/owsg_tool_models.json +0 -0
  295. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/penetrator_ratings.json +0 -0
  296. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/seismicity_protocols.json +0 -0
  297. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/tool_sources.json +0 -0
  298. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/us_outline.json +0 -0
  299. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/us_outline.provenance.json +0 -0
  300. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_3d.provenance.json +0 -0
  301. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  302. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  303. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  304. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_surfaces.json.gz +0 -0
  305. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  306. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  307. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  308. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  309. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  310. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  311. {gea_program-0.59.0 → gea_program-0.61.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  312. {gea_program-0.59.0 → gea_program-0.61.0}/gea/report_history.py +0 -0
  313. {gea_program-0.59.0 → gea_program-0.61.0}/gea/rock_inventory.py +0 -0
  314. {gea_program-0.59.0 → gea_program-0.61.0}/gea/sample_record.py +0 -0
  315. {gea_program-0.59.0 → gea_program-0.61.0}/gea/sample_well_profile.csv +0 -0
  316. {gea_program-0.59.0 → gea_program-0.61.0}/gea/sbom.py +0 -0
  317. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seedlink.py +0 -0
  318. {gea_program-0.59.0 → gea_program-0.61.0}/gea/segy.py +0 -0
  319. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic.py +0 -0
  320. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_array.py +0 -0
  321. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_assoc.py +0 -0
  322. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_detect.py +0 -0
  323. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_film.py +0 -0
  324. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_harmonic.py +0 -0
  325. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_qc.py +0 -0
  326. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_response.py +0 -0
  327. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_signature.py +0 -0
  328. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_track.py +0 -0
  329. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_uncertainty.py +0 -0
  330. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_unlisted.py +0 -0
  331. {gea_program-0.59.0 → gea_program-0.61.0}/gea/seismic_watch.py +0 -0
  332. {gea_program-0.59.0 → gea_program-0.61.0}/gea/sensor_swap.py +0 -0
  333. {gea_program-0.59.0 → gea_program-0.61.0}/gea/series_cache.py +0 -0
  334. {gea_program-0.59.0 → gea_program-0.61.0}/gea/service_life.py +0 -0
  335. {gea_program-0.59.0 → gea_program-0.61.0}/gea/setup_checklist.py +0 -0
  336. {gea_program-0.59.0 → gea_program-0.61.0}/gea/shapefile.py +0 -0
  337. {gea_program-0.59.0 → gea_program-0.61.0}/gea/shell.py +0 -0
  338. {gea_program-0.59.0 → gea_program-0.61.0}/gea/shut_in.py +0 -0
  339. {gea_program-0.59.0 → gea_program-0.61.0}/gea/simulation.py +0 -0
  340. {gea_program-0.59.0 → gea_program-0.61.0}/gea/site_map.py +0 -0
  341. {gea_program-0.59.0 → gea_program-0.61.0}/gea/sla_report.py +0 -0
  342. {gea_program-0.59.0 → gea_program-0.61.0}/gea/slip.py +0 -0
  343. {gea_program-0.59.0 → gea_program-0.61.0}/gea/soak.py +0 -0
  344. {gea_program-0.59.0 → gea_program-0.61.0}/gea/sra.py +0 -0
  345. {gea_program-0.59.0 → gea_program-0.61.0}/gea/store_forward.py +0 -0
  346. {gea_program-0.59.0 → gea_program-0.61.0}/gea/strata_join.py +0 -0
  347. {gea_program-0.59.0 → gea_program-0.61.0}/gea/structure.py +0 -0
  348. {gea_program-0.59.0 → gea_program-0.61.0}/gea/supervisor.py +0 -0
  349. {gea_program-0.59.0 → gea_program-0.61.0}/gea/survey_cmd.py +0 -0
  350. {gea_program-0.59.0 → gea_program-0.61.0}/gea/survey_view.py +0 -0
  351. {gea_program-0.59.0 → gea_program-0.61.0}/gea/telemetry.py +0 -0
  352. {gea_program-0.59.0 → gea_program-0.61.0}/gea/tool_catalog.py +0 -0
  353. {gea_program-0.59.0 → gea_program-0.61.0}/gea/tool_library.py +0 -0
  354. {gea_program-0.59.0 → gea_program-0.61.0}/gea/tool_taxonomy.py +0 -0
  355. {gea_program-0.59.0 → gea_program-0.61.0}/gea/torque_drag.py +0 -0
  356. {gea_program-0.59.0 → gea_program-0.61.0}/gea/transient.py +0 -0
  357. {gea_program-0.59.0 → gea_program-0.61.0}/gea/vibration.py +0 -0
  358. {gea_program-0.59.0 → gea_program-0.61.0}/gea/well3d.py +0 -0
  359. {gea_program-0.59.0 → gea_program-0.61.0}/gea/well_assembler.py +0 -0
  360. {gea_program-0.59.0 → gea_program-0.61.0}/gea/well_report.py +0 -0
  361. {gea_program-0.59.0 → gea_program-0.61.0}/gea/well_test_validation.py +0 -0
  362. {gea_program-0.59.0 → gea_program-0.61.0}/gea/wellplan.py +0 -0
  363. {gea_program-0.59.0 → gea_program-0.61.0}/gea/wellpos.py +0 -0
  364. {gea_program-0.59.0 → gea_program-0.61.0}/gea/wits0.py +0 -0
  365. {gea_program-0.59.0 → gea_program-0.61.0}/gea/witsml.py +0 -0
  366. {gea_program-0.59.0 → gea_program-0.61.0}/gea/xlsx_read.py +0 -0
  367. {gea_program-0.59.0 → gea_program-0.61.0}/gea_program.egg-info/dependency_links.txt +0 -0
  368. {gea_program-0.59.0 → gea_program-0.61.0}/gea_program.egg-info/entry_points.txt +0 -0
  369. {gea_program-0.59.0 → gea_program-0.61.0}/gea_program.egg-info/requires.txt +0 -0
  370. {gea_program-0.59.0 → gea_program-0.61.0}/gea_program.egg-info/top_level.txt +0 -0
  371. {gea_program-0.59.0 → gea_program-0.61.0}/setup.cfg +0 -0
  372. {gea_program-0.59.0 → gea_program-0.61.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.61.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,27 @@ 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
+
686
+ ### Casing design and kick tolerance
687
+
688
+ Each casing string's burst, collapse and tension ratings by API TR 5C3,
689
+ from its size, weight and grade, against the loads the hole can put on it
690
+ - gas to the surface held by the shoe, an emptied string, the buoyed
691
+ weight with overpull - with the smallest safety factor and where, and the
692
+ gas kick each open hole below a shoe can take and still be shut in. `gea
693
+ casing --example`; declared on the **Casing & kick tolerance** tab, with
694
+ `gea casing --workspace C:\site --well SWD-1 --strings ...`, or in the
695
+ declarations sheet. `gea help casing`.
696
+
676
697
  ### Hydraulics and ECD
677
698
 
678
699
  The pressures the mud makes while it circulates, from the six-speed
@@ -634,6 +634,27 @@ 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
+
647
+ ### Casing design and kick tolerance
648
+
649
+ Each casing string's burst, collapse and tension ratings by API TR 5C3,
650
+ from its size, weight and grade, against the loads the hole can put on it
651
+ - gas to the surface held by the shoe, an emptied string, the buoyed
652
+ weight with overpull - with the smallest safety factor and where, and the
653
+ gas kick each open hole below a shoe can take and still be shut in. `gea
654
+ casing --example`; declared on the **Casing & kick tolerance** tab, with
655
+ `gea casing --workspace C:\site --well SWD-1 --strings ...`, or in the
656
+ declarations sheet. `gea help casing`.
657
+
637
658
  ### Hydraulics and ECD
638
659
 
639
660
  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.61.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -646,6 +646,23 @@ def main(argv=None) -> int:
646
646
  p_dc.add_argument("--actor", type=str, default="cli")
647
647
  p_dc.add_argument("--json", action="store_true")
648
648
 
649
+ p_cs = sub.add_parser("casing", help="casing design and kick tolerance: each string's burst, collapse and tension against the loads the hole can put on it (API TR 5C3 ratings), and the gas kick each open hole can take")
650
+ p_cs.add_argument("--workspace", type=str, default=None)
651
+ p_cs.add_argument("--well", type=str, default=None)
652
+ p_cs.add_argument("--example", action="store_true", help="the worked example (a vertical 10,000 ft well with three strings, declared for the example)")
653
+ p_cs.add_argument("--strings", type=str, default=None, help="shallowest shoe first: 'surface:13-3/8:54.5:J55:0-1500:17.5:9.0; intermediate:9-5/8:47:N80:0-7000:12.25:9.8' (what:od:lb_ft:grade:top-shoe:hole_in:mud_ppg[:wall_in])")
654
+ for k_, h_ in (("td", "the well's TD below the deepest string, ft MD"), ("td-hole", "the hole below the deepest string, in"), ("td-mw", "the mud below the deepest string, ppg"),
655
+ ("pp", "a pore pressure for the well when the Pore pressure tab has none, ppg"), ("fg", "a fracture gradient for the well when the Pore pressure tab has none, ppg"),
656
+ ("kick", "kick intensity above the mud, ppg (0.5)"), ("gas", "gas gradient, psi/ft (0.1)"), ("backup", "burst backup gradient outside, psi/ft (0.465)"),
657
+ ("evacuation", "how far the string is emptied for collapse, 0 to 1 (1: to the shoe)"), ("df-burst", "burst design factor (1.1)"),
658
+ ("df-collapse", "collapse design factor (1.125)"), ("df-tension", "tension design factor (1.6)"), ("overpull", "overpull, klbf (100)"),
659
+ ("bha-od", "BHA outside diameter, in"), ("bha-length", "BHA length, ft"), ("dp-od", "drill pipe outside diameter, in")):
660
+ p_cs.add_argument("--" + k_, type=str, default=None, help=h_)
661
+ p_cs.add_argument("--source", type=str, default="", help="where the casing program came from")
662
+ p_cs.add_argument("--write", action="store_true")
663
+ p_cs.add_argument("--actor", type=str, default="cli")
664
+ p_cs.add_argument("--json", action="store_true")
665
+
649
666
  p_hy = sub.add_parser("hydraulics", help="the pressures the mud makes while it circulates - the standpipe, the bit, the annulus, the ECD against the pore pressure and fracture gradient, surge and swab - and the standpipe pressure the rig measured")
650
667
  p_hy.add_argument("--workspace", type=str, default=None)
651
668
  p_hy.add_argument("--well", type=str, default=None)
@@ -2598,6 +2615,39 @@ def main(argv=None) -> int:
2598
2615
  raise SystemExit(f"decline: {e.args[0] if e.args else e}")
2599
2616
  print(_json.dumps(r, default=str) if a.json else _DCA.text(r))
2600
2617
  return 0
2618
+ elif a.cmd == "casing":
2619
+ import json as _json
2620
+ from . import casing_design as _CSG
2621
+ from .cli import _safe_stdout
2622
+ from .workspace import Workspace, WorkspaceError
2623
+ _safe_stdout()
2624
+ try:
2625
+ ws = Workspace(a.workspace) if a.workspace else None
2626
+ scene = "example" if (a.example or ws is None) else "workspace"
2627
+ keys_ = {"td": "td_md_ft", "td_hole": "td_hole_in", "td_mw": "td_mw_ppg", "pp": "pp_ppg", "fg": "fg_ppg", "kick": "kick_intensity_ppg",
2628
+ "gas": "gas_gradient_psi_ft", "backup": "backup_gradient_psi_ft", "evacuation": "evacuation", "df_burst": "df_burst",
2629
+ "df_collapse": "df_collapse", "df_tension": "df_tension", "overpull": "overpull_klbf", "bha_od": "bha_od_in",
2630
+ "bha_length": "bha_length_ft", "dp_od": "dp_od_in"}
2631
+ vals_ = {v: getattr(a, k) for k, v in keys_.items() if getattr(a, k) is not None}
2632
+ if ws is not None and (vals_ or a.strings is not None):
2633
+ if not a.well:
2634
+ raise ValueError("--well is needed")
2635
+ c_ = _CSG.set_well(ws, a.actor, a.well, a.source, a.strings, **vals_)
2636
+ print(f"{a.well}: {len(c_.get('strings') or [])} string(s): {_CSG.strings_text(c_.get('strings') or []) or 'none declared'}")
2637
+ return 0
2638
+ if a.write:
2639
+ if ws is None:
2640
+ raise ValueError("--write needs --workspace")
2641
+ r_ = _CSG.write_report(ws, __version__, a.well, a.actor, scene)
2642
+ print(f"wrote {r_['report_id']}: {r_['paths']['html']}")
2643
+ if r_['paths'].get('pdf'):
2644
+ print(f" printable: {r_['paths']['pdf']}")
2645
+ return 0
2646
+ r = _CSG.page(ws, scene, a.well)
2647
+ except (KeyError, ValueError, OSError, WorkspaceError) as e:
2648
+ raise SystemExit(f"casing: {e.args[0] if e.args else e}")
2649
+ print(_json.dumps(r, default=str) if a.json else _CSG.text(r))
2650
+ return 0
2601
2651
  elif a.cmd == "hydraulics":
2602
2652
  import json as _json
2603
2653
  from . import hydraulics as _HYD
@@ -3226,7 +3276,16 @@ def main(argv=None) -> int:
3226
3276
  print(_json.dumps(ws.migrate_out_dir(a.from_dir, actor=a.actor), indent=1)); return 0
3227
3277
  elif a.action == "refresh":
3228
3278
  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
3279
+ print("dashboard:", r.get("index") if isinstance(r, dict) else r)
3280
+ for wname, kinds in sorted(((r.get("reports") or {}) if isinstance(r, dict) else {}).items()):
3281
+ if "refresh_failed" in kinds:
3282
+ try:
3283
+ with open(os.path.join(ws.reports_dir, "wells", wname, "refresh_failed.json"), encoding="utf-8") as fh:
3284
+ why = _json.load(fh).get("reason", "")
3285
+ except (OSError, ValueError):
3286
+ why = ""
3287
+ print(f"well {wname}: its reports could not be written - {why} (the other wells were reported)")
3288
+ return 0
3230
3289
  elif a.action == "audit":
3231
3290
  for row in ws.audit_log(limit=50):
3232
3291
  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)
@@ -13057,7 +13058,7 @@ def section_db_declarations_engineering(tmp: str) -> None:
13057
13058
  hcfg, tcfg, pcfg = HY.well_config(ws, w1), TND.well_config(ws, w1), PPF.well_config(ws, w1)
13058
13059
  meas = (PPF._load(ws).get("measured") or {}).get(w1) or []
13059
13060
  c1 = {
13060
- "planned": not p1["errors"] and p1["counts"]["NEW"] == 10 and p1["counts"]["CHANGE"] == 0 and sorted({s["table"] for s in p1["steps"]}) == sorted(DC.ENG_ORDER),
13061
+ "planned": not p1["errors"] and p1["counts"]["NEW"] == 10 and p1["counts"]["CHANGE"] == 0 and sorted({s["table"] for s in p1["steps"]}) == sorted(set(DC.ENG_ORDER) - {"casing_design", "casing_loads"}), # the casing tables: section DD
13061
13062
  "written": a1["applied"] and len(a1["written"]) == 10,
13062
13063
  "landed": WPL.well_settings(ws, w1)["program"] == [{"to_md_m": 1000.0, "model": "MWD+SRGM"}, {"to_md_m": None, "model": "MWD+IFR1"}]
13063
13064
  and bool(ws.well(w1).get("plan")) and pcfg["indicator"] == "dexponent" and pcfg["bit_in"] == 8.75
@@ -13138,6 +13139,393 @@ 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
+
13313
+ def section_dd_casing_design(tmp: str) -> None:
13314
+ """Section DD - casing design and kick tolerance: each string's ratings by API TR 5C3 as API's tables give them, in
13315
+ every collapse regime and reduced under tension; the burst, collapse and tension loads and the smallest safety factor
13316
+ against the design factor, worked by hand on the example; the kick tolerance below each shoe; the program declared
13317
+ from the tab, the command line and the declarations sheet, read on the survey or the recorder's own TVD, written,
13318
+ in the dossier; an interval declared in measured depth converted on the survey or the recorder's TVD."""
13319
+ import http.cookiejar
13320
+ import subprocess as _sp
13321
+ import urllib.error
13322
+ import urllib.request
13323
+ import zipfile as _zf
13324
+ from . import __version__ as _ver
13325
+ from . import casing_design as CSG
13326
+ from . import declarations as DC
13327
+ from . import dossier as DZ
13328
+ from . import edr as E
13329
+ from . import helplib as H
13330
+ from . import hydraulics as HY
13331
+ from . import interval as IV
13332
+ from . import well3d as W
13333
+ from .service import Service, Users
13334
+ from .workspace import Workspace
13335
+ pkg = Path(__file__).parent
13336
+
13337
+ # -- DD1 the ratings -------------------------------------------------------------------------------------------------------
13338
+ def one(od, ppf, grade, **kw):
13339
+ return CSG.check_strings([{"what": "x", "od_in": od, "weight_lbft": ppf, "grade": grade, "top_md_ft": 0, "shoe_md_ft": 1000, "hole_in": 26, "mw_ppg": 9, **kw}])[0]
13340
+ table = [("9-5/8", 47, "N80", 6870, 4750, "plastic", 1086), (7, 29, "P110", 11220, 8530, "plastic", 929), ("13-3/8", 54.5, "J55", 2730, 1130, "transition", 853),
13341
+ (20, 94, "J55", 2110, 520, "elastic", 1481), ("9-5/8", 40, "N80", 5750, 3090, "transition", 916)]
13342
+ rated = [(one(od, w_, g), b, c, reg, by) for od, w_, g, b, c, reg, by in table]
13343
+ thick = CSG.collapse(5.0, 0.5, 110000.0) # D/t 10: the yield regime, 2 Yp (D/t - 1) / (D/t)^2
13344
+ ya0, ya1, ya2 = CSG.reduced_yield(80000.0, 0.0), CSG.reduced_yield(80000.0, 20000.0), CSG.reduced_yield(80000.0, 40000.0)
13345
+
13346
+ def refused(rows):
13347
+ try:
13348
+ CSG.check_strings(rows)
13349
+ return ""
13350
+ except ValueError as e:
13351
+ return str(e)
13352
+ base = {"what": "s", "od_in": "9-5/8", "weight_lbft": 47, "grade": "N80", "top_md_ft": 0, "shoe_md_ft": 5000, "hole_in": 12.25, "mw_ppg": 10}
13353
+ m_grade = refused([{**base, "grade": "X99"}])
13354
+ m_size = refused([{**base, "weight_lbft": 41.3}])
13355
+ m_wall = refused([{**base, "wall_in": 0.3}])
13356
+ m_drift = refused([{**base, "what": "a", "od_in": 7, "weight_lbft": 29, "shoe_md_ft": 3000, "hole_in": 8.5}, {**base, "what": "b"}])
13357
+ try:
13358
+ CSG.parse_strings("surface:13-3/8:54.5:J55")
13359
+ m_parse = ""
13360
+ except ValueError as e:
13361
+ m_parse = str(e)
13362
+ c1 = {
13363
+ "api_tables": all(s["burst_psi"] == b and s["collapse_psi"] == c and s["collapse_regime"] == reg and abs(s["body_yield_klbf"] - by) <= 1.0
13364
+ for s, b, c, reg, by in rated),
13365
+ "yield_regime": thick[1] == "yield" and abs(thick[0] - 2 * 110000.0 * 9.0 / 100.0) < 1e-6,
13366
+ "sizes": CSG.size_in("9-5/8") == 9.625 and CSG.size_in("13 3/8") == 13.375 and CSG.size_in(7) == 7.0 and rated[0][0]["wall_in"] == 0.472
13367
+ and rated[0][0]["id_in"] == 8.681,
13368
+ "tension": ya0 == 80000.0 and ya0 > ya1 > ya2 and CSG.collapse_with_tension(9.625, 0.472, 80000.0, 20000.0) < 4750.0
13369
+ and CSG.reduced_yield(80000.0, -20000.0) == 80000.0,
13370
+ "answered": "the grade is one of" in m_grade and "not in the program's table" in m_size and "weighs about" in m_wall
13371
+ and "does not pass through" in m_drift and "what:od:weight:grade:top-shoe:hole:mw" in m_parse,
13372
+ }
13373
+ failed1 = [k for k, v in c1.items() if not v]
13374
+ ok(not failed1,
13375
+ "DD1 the ratings by API TR 5C3 as API's tables give them - 9-5/8 in 47 lb/ft N80 6,870 psi burst, 4,750 collapse, 1,086 klbf body; 7 in 29 "
13376
+ "P110 11,220 and 8,530; 13-3/8 in 54.5 J55 2,730 and 1,130 (transition); 20 in 94 J55 520 (elastic); 9-5/8 in 40 N80 3,090 (transition); a "
13377
+ "D/t of 10 in the yield regime - the collapse reduced under tension and not raised by compression; a grade, a size, a wall that does not "
13378
+ "match its weight, a string that does not pass through the one above and a short line each answered" + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
13379
+
13380
+ # -- DD2 the loads and the kick tolerance, by hand on the example -------------------------------------------------------------------
13381
+ ex = CSG.example()
13382
+ sv, iv_, pv = ex["strings"]
13383
+ win = CSG.EXAMPLE_WINDOW
13384
+
13385
+ def at(key, z):
13386
+ return float(np.interp(z, win["tvd_ft"], win[key]))
13387
+ # surface string: gas from the pore pressure at the intermediate shoe (7,000 ft), held by the fracture pressure at 1,500 ft
13388
+ p_res, p_frac = 0.052 * at("pp_ppg", 7000.0) * 7000.0, 0.052 * at("fg_ppg", 1500.0) * 1500.0
13389
+ top_in = min(p_res - 0.1 * 7000.0, p_frac - 0.1 * 1500.0)
13390
+ sf_b_top = 2730.0 / top_in
13391
+ sf_c_shoe = 1130.0 / (0.052 * 9.0 * 1500.0)
13392
+ sf_t = 853.2 / ((1 - 9.0 / 65.5) * 54.5 * 1500.0 / 1000.0)
13393
+ k0 = ex["kicks"][0]
13394
+ maasp = 0.052 * (at("fg_ppg", 1500.0) - 9.8) * 1500.0
13395
+ sidpp = 0.052 * 0.5 * 7000.0
13396
+ hgt = (maasp - sidpp) / (0.052 * 9.8 - 0.1)
13397
+ v_bot = hgt * (12.25 ** 2 - 6.5 ** 2) / 1029.4
13398
+ v_shoe = hgt * (12.25 ** 2 - 5.0 ** 2) / 1029.4 * p_frac / (0.052 * 10.3 * 7000.0)
13399
+ weak = {**CSG.DEFAULTS, "strings": CSG.check_strings([{**base, "what": "9-5/8 in J55 36 lb/ft", "weight_lbft": 36, "grade": "J55", "shoe_md_ft": 7000, "mw_ppg": 10}]),
13400
+ "td_md_ft": 10000.0, "td_hole_in": 8.5, "td_mw_ppg": 12.0, "bha_od_in": 6.5, "bha_length_ft": 600.0, "dp_od_in": 5.0}
13401
+ rw = CSG.analyse(weak, lambda md: float(md), win)
13402
+ no_fg = CSG.analyse({**weak, "strings": weak["strings"]}, lambda md: float(md), {"pp_const": 9.0, "fg_const": None})
13403
+ c2 = {
13404
+ "burst": abs(sv["rows"][0]["burst_net_psi"] - top_in) <= 1.0 and sv["rows"][0]["burst_limited_by_shoe"] and abs(sv["rows"][0]["sf_burst"] - sf_b_top) < 0.005,
13405
+ "collapse": abs(sv["rows"][-1]["sf_collapse"] - sf_c_shoe) < 0.005 and sv["rows"][-1]["collapse_rating_psi"] == 1130,
13406
+ "tension": abs(sv["checks"]["tension"]["sf"] - sf_t) < 0.01 and sv["checks"]["tension"]["ok"],
13407
+ "least": sv["checks"]["burst"]["sf"] == min(r_["sf_burst"] for r_ in sv["rows"] if r_["sf_burst"]) and ex["ok"],
13408
+ "kick": abs(k0["maasp_psi"] - maasp) <= 1 and abs(k0["height_ft"] - hgt) < 0.5 and abs(k0["volume_at_bottom_bbl"] - v_bot) < 0.15
13409
+ and abs(k0["volume_shoe_as_bottom_bbl"] - v_shoe) < 0.15 and k0["volume_bbl"] == min(k0["volume_at_bottom_bbl"], k0["volume_shoe_as_bottom_bbl"]),
13410
+ "weak": not rw["ok"] and not rw["strings"][0]["checks"]["burst"]["ok"] and not rw["strings"][0]["checks"]["collapse"]["ok"],
13411
+ "no_fg": no_fg["kicks"][0]["status"] == "NOT KNOWN" and "fracture gradient at the shoe" in no_fg["kicks"][0]["detail"],
13412
+ "production": pv["below"] is None and "at the shoe" in pv["burst_case"],
13413
+ }
13414
+ failed2 = [k for k, v in c2.items() if not v]
13415
+ ok(not failed2,
13416
+ "DD2 the worked example by hand: the surface string's burst load at the top is gas from the pore pressure at the next shoe held to the fracture "
13417
+ "pressure at its own shoe, the collapse at its shoe the 9.0 ppg mud against an empty string, the tension its buoyed weight; the smallest "
13418
+ "safety factor where it falls; the kick tolerance below it MAASP, SIDPP, the bubble's height and its volume at the bottom and at the shoe "
13419
+ "carried back by Boyle's law, the smaller; a 36 lb/ft J55 string at 7,000 ft below its design factors in burst and collapse; no fracture "
13420
+ "gradient, no kick tolerance - said so" + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
13421
+
13422
+ # -- DD3 a well: declared three ways, read on its survey or the recorder's TVD, written, in the dossier ----------------------------------
13423
+ d = Path(tmp, "casing"); d.mkdir()
13424
+ hy = HY.selftest_files(str(d / "hy"))
13425
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
13426
+ w1 = ws.add_well_file(hy["record"], display="HY-1", actor="tester")["id"]
13427
+ w2 = ws.add_well_file(hy["record"], display="HY-2", actor="tester")["id"]
13428
+ W.attach_trajectory(ws, w1, "s.csv", Path(hy["survey"]).read_bytes(), "tester", "true", "ft", None, None, None, "test survey")
13429
+ for wid in (w2,):
13430
+ out = Path(ws.reports_dir, "wells", wid); out.mkdir(parents=True, exist_ok=True)
13431
+ E.write_record(E.read(hy["record"]), str(out), _ver)
13432
+ prog = "surface:13-3/8:54.5:J55:0-1500:17.5:9.0; intermediate:9-5/8:47:N80:0-6000:12.25:10"
13433
+ cli = _sp.run([sys.executable, "-m", "gea", "casing", "--workspace", wsp, "--well", w1, "--strings", prog, "--td", "10000", "--td-hole", "8-1/2", "--td-mw", "11",
13434
+ "--pp", "9.5", "--fg", "14.5", "--bha-od", "6.75", "--bha-length", "400", "--dp-od", "5", "--source", "drilling program (test)"],
13435
+ capture_output=True, text=True, cwd=str(pkg.parent))
13436
+ p1 = CSG.page(ws, "workspace", w1)
13437
+ r1 = p1["result"]
13438
+ sheet = d / "sheet.csv"
13439
+ cols_d, cols_l = DC.TABLES["casing_design"]["cols"], DC.TABLES["casing_loads"]["cols"]
13440
+ rows = [["[casing_design]", *cols_d], ["", "HY-2", "surface", "13-3/8", "54.5", "J55", "0", "1500", "17.5", "9.0", "", "casing tally (test)"],
13441
+ ["", "HY-2", "intermediate", "9.625", "47", "N80", "0", "6000", "12.25", "10", "", "casing tally (test)"],
13442
+ ["[casing_loads]", *cols_l], ["", "HY-2", "10000", "8.5", "11", "9.5", "14.5", "0.5", "", "", "", "", "", "", "", "6.75", "400", "5", "drilling program (test)"]]
13443
+ import csv as _csv
13444
+ import io as _io
13445
+ buf = _io.StringIO(); _csv.writer(buf).writerows(rows); sheet.write_text(buf.getvalue(), encoding="utf-8")
13446
+ pl = DC.apply(ws, sheet.read_text(encoding="utf-8"), "tester", str(d))
13447
+ pw = DC.apply(ws, sheet.read_text(encoding="utf-8"), "tester", str(d), write=True)
13448
+ again = DC.apply(ws, sheet.read_text(encoding="utf-8"), "tester", str(d))
13449
+ tmpl = DC.apply(ws, DC.template(ws), "tester", str(d))
13450
+ bad = sheet.read_text(encoding="utf-8").replace(",N80,", ",X99,")
13451
+ pb = DC.apply(ws, bad, "tester", str(d), write=True)
13452
+ p2 = CSG.page(ws, "workspace", w2)
13453
+ r2 = p2["result"]
13454
+ Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
13455
+ svc = Service(wsp, port=0, scheduler=False).start()
13456
+ base_url = svc.url.rstrip("/")
13457
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
13458
+
13459
+ def call(method, path, body=None):
13460
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
13461
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
13462
+ try:
13463
+ with o.open(rq, timeout=120) as rr_:
13464
+ return rr_.status, json.loads(rr_.read())
13465
+ except urllib.error.HTTPError as e:
13466
+ return e.code, json.loads(e.read())
13467
+ try:
13468
+ call("POST", "/api/login", {"name": "op", "password": "op-pass-123"})
13469
+ st_g, g = call("GET", f"/api/casing-design?well={w1}")
13470
+ st_ex, gx = call("GET", "/api/casing-design?scene=example")
13471
+ st_s, sset = call("POST", "/api/casing-design/settings", {"well_id": w1, "df_burst": "1.2", "source": "drilling program rev 2 (test)"})
13472
+ st_bad, sbad = call("POST", "/api/casing-design/settings", {"well_id": w1, "strings": "surface:13-3/8:54.5:X99:0-1500:17.5:9", "source": "x"})
13473
+ st_w, wr = call("POST", "/api/casing-design/write", {"well_id": w1})
13474
+ finally:
13475
+ svc.stop()
13476
+ cli_ex = _sp.run([sys.executable, "-m", "gea", "casing", "--example"], capture_output=True, text=True, cwd=str(pkg.parent))
13477
+ dz = DZ.build(ws, "tester", _ver, None)
13478
+ with _zf.ZipFile(Path(ws.reports_dir, dz["path"])) as z_:
13479
+ names = set(z_.namelist())
13480
+ rep_json = json.loads(Path(ws.reports_dir, "wells", w1, "casing_design.json").read_text(encoding="utf-8"))
13481
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
13482
+ c3 = {
13483
+ "cli": cli.returncode == 0 and "2 string(s)" in cli.stdout,
13484
+ "survey": r1 and r1["path_from"] == "survey" and r1["strings"][1]["shoe_tvd_ft"] < 6000.0 - 100.0 and len(r1["kicks"]) == 2
13485
+ and r1["kicks"][1]["bottom_md_ft"] == 10000.0 and r1["kicks"][1].get("volume_bbl") is not None,
13486
+ "sheet": not pl["errors"] and [s_["action"] for s_ in pl["steps"] if s_["table"] in ("casing_design", "casing_loads")] == ["NEW", "NEW"]
13487
+ and pw["applied"] and not again["errors"] and again["counts"]["NEW"] == 0 and again["counts"]["CHANGE"] == 0
13488
+ and not tmpl["errors"] and tmpl["counts"]["NEW"] == 0 and tmpl["counts"]["CHANGE"] == 0,
13489
+ "sheet_bad": bool(pb["errors"]) and any("[casing_design]" in e and "the grade is one of" in e for e in pb["errors"]) and not pb["written"],
13490
+ "recorder": r2 and r2["path_from"] == "recorder" and "not a survey" in r2["path_how"] and abs(r2["strings"][1]["shoe_tvd_ft"] - r1["strings"][1]["shoe_tvd_ft"]) < 40.0,
13491
+ "api": st_g == 200 and g["result"]["well"] == w1 and st_ex == 200 and gx["scene"] == "example" and st_s == 200 and sset["df_burst"] == 1.2
13492
+ and st_bad == 400 and "the grade is one of" in sbad.get("error", "") and st_w == 200 and wr["report_id"].startswith("CSG-")
13493
+ and rep_json["config"]["df_burst"] == 1.2,
13494
+ "example_cli": cli_ex.returncode == 0 and "6,870 psi" in cli_ex.stdout and "kick tolerance below" in cli_ex.stdout,
13495
+ "dossier": "declarations/drillstring/casing_design.json" in names and f"results/casing_design/{w1}.json" in names,
13496
+ "page_help": "['#/casing', 'Casing & kick tolerance']" in page_src and "VIEWS.casing = async" in page_src and not H.check("casing")
13497
+ and any(t["topic"] == "casing" for t in H.topics()) and "[casing_design]" in (H.page("declare") or ""),
13498
+ }
13499
+ failed3 = [k for k, v in c3.items() if not v]
13500
+ ok(not failed3,
13501
+ "DD3 a well's casing program declared from the command line, the API and the declarations sheet (planned NEW, written, then SAME, and from "
13502
+ "the template; a bad grade answered with its row, nothing written); read on its survey, or on the drilling recorder's own TVD for a well with "
13503
+ "none, said so; the kick tolerance of each open hole to TD; the report written (CSG-...); the dossier carries the declarations and the "
13504
+ "result; the tab, the help and the sheet's tables" + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
13505
+
13506
+ # -- DD4 an interval declared in measured depth -----------------------------------------------------------------------------------------
13507
+ base_w = {"id": "w", "surface": {"lat": 1.0, "lon": 1.0, "elevation": {"ft": 2950.0, "datum": "KB", "source": "report"}},
13508
+ "disposal": {"top_interval_ft": 7000.0, "base_interval_ft": 7600.0, "depth_reference": "MD"}}
13509
+ path1, path2 = HY.depth_path(ws, w1), HY.depth_path(ws, w2)
13510
+ s1 = IV.statement(base_w, {"status": "OK", "layers": []}, [], None, path1)
13511
+ s2 = IV.statement(base_w, {"status": "OK", "layers": []}, [], None, path2)
13512
+ s3 = IV.statement({**base_w, "disposal": {**base_w["disposal"], "base_interval_ft": 30000.0}}, {"status": "OK", "layers": []}, [], None, path1)
13513
+ tv_top = HY.tvd_at(path1, 7000.0)
13514
+ c4 = {
13515
+ "survey": path1["from"] == "survey" and s1["declared"]["depth_reference"] == "TVD" and abs(s1["declared"]["top_ft"] - tv_top) < 0.2
13516
+ and s1["declared"]["top_ft"] < 7000.0 - 500.0 and s1["declared"]["converted_from_md"]["top_md_ft"] == 7000.0
13517
+ and any("converted to" in n and "survey" in n for n in s1["notes"]) and abs(s1["declared"]["top_subsea_ft"] - (2950.0 - s1["declared"]["top_ft"])) < 0.2,
13518
+ "recorder": path2["from"] == "recorder" and abs(s2["declared"]["top_ft"] - s1["declared"]["top_ft"]) < 40.0 and any("not a survey" in n for n in s2["notes"]),
13519
+ "beyond": s3["status"] == "NOT RESOLVED" and "reaches" in s3["lines"][0],
13520
+ "help": "converted on the well's survey" in (H.page("interval") or ""),
13521
+ }
13522
+ failed4 = [k for k, v in c4.items() if not v]
13523
+ ok(not failed4,
13524
+ "DD4 an interval declared in measured depth is converted to true vertical depth on the well's survey, or the drilling recorder's own TVD for a "
13525
+ "well with none (within 40 ft of the survey's), each said so in the statement; an interval deeper than the depth table reaches stays NOT "
13526
+ "RESOLVED and says how far it reaches" + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
13527
+
13528
+
13141
13529
  def PR_read_ok(path) -> bool:
13142
13530
  from . import pdf_report as PR
13143
13531
  try:
@@ -13255,6 +13643,8 @@ def main() -> int:
13255
13643
  section_cz_it_review(tmp)
13256
13644
  section_da_hydraulics(tmp)
13257
13645
  section_db_declarations_engineering(tmp)
13646
+ section_dc_every_tab(tmp)
13647
+ section_dd_casing_design(tmp)
13258
13648
  if _FAILS:
13259
13649
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
13260
13650
  for f in _FAILS: