gea-program 0.52.0__tar.gz → 0.54.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 (362) hide show
  1. {gea_program-0.52.0 → gea_program-0.54.0}/PKG-INFO +24 -1
  2. {gea_program-0.52.0 → gea_program-0.54.0}/README.md +23 -0
  3. {gea_program-0.52.0 → gea_program-0.54.0}/gea/__init__.py +1 -1
  4. {gea_program-0.52.0 → gea_program-0.54.0}/gea/__main__.py +94 -0
  5. {gea_program-0.52.0 → gea_program-0.54.0}/gea/acceptance_tests.py +336 -0
  6. gea_program-0.54.0/gea/decline.py +553 -0
  7. {gea_program-0.52.0 → gea_program-0.54.0}/gea/dossier.py +27 -3
  8. gea_program-0.54.0/gea/help/decline.md +17 -0
  9. gea_program-0.54.0/gea/help/torque-drag.md +17 -0
  10. {gea_program-0.52.0 → gea_program-0.54.0}/gea/helplib.py +3 -1
  11. {gea_program-0.52.0 → gea_program-0.54.0}/gea/service.py +37 -0
  12. gea_program-0.54.0/gea/torque_drag.py +509 -0
  13. {gea_program-0.52.0 → gea_program-0.54.0}/gea/web/app.html +118 -1
  14. {gea_program-0.52.0 → gea_program-0.54.0}/gea_program.egg-info/PKG-INFO +24 -1
  15. {gea_program-0.52.0 → gea_program-0.54.0}/gea_program.egg-info/SOURCES.txt +4 -0
  16. {gea_program-0.52.0 → gea_program-0.54.0}/pyproject.toml +1 -1
  17. {gea_program-0.52.0 → gea_program-0.54.0}/LICENSE +0 -0
  18. {gea_program-0.52.0 → gea_program-0.54.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  19. {gea_program-0.52.0 → gea_program-0.54.0}/gea/accuracy_statement.py +0 -0
  20. {gea_program-0.52.0 → gea_program-0.54.0}/gea/alarm_engine.py +0 -0
  21. {gea_program-0.52.0 → gea_program-0.54.0}/gea/alerts.py +0 -0
  22. {gea_program-0.52.0 → gea_program-0.54.0}/gea/backup.py +0 -0
  23. {gea_program-0.52.0 → gea_program-0.54.0}/gea/bench.py +0 -0
  24. {gea_program-0.52.0 → gea_program-0.54.0}/gea/blind_harness.py +0 -0
  25. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  26. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  27. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  28. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  29. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  30. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  31. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  32. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  33. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  34. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  35. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  36. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  37. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  38. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  39. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  40. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  41. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  42. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  43. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  44. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  45. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  46. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  47. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  48. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  49. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  50. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  51. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  52. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  53. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  54. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  55. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  56. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  57. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  58. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  59. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  60. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  61. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  62. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  63. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  64. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  65. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  66. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  67. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  68. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  69. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  70. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  71. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  72. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  73. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  74. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  75. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  76. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  77. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  78. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  79. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  80. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  81. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  82. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  83. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  84. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  85. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  86. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  87. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  88. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  89. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  90. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  91. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  92. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/med_160_sapropels.txt +0 -0
  93. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  94. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  95. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  96. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  97. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  98. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  99. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  100. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  101. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  102. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  103. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  104. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  105. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  106. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  107. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  108. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  109. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  110. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  111. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  112. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  113. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  114. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  115. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  116. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  117. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  118. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  119. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  120. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  121. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  122. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  123. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  124. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  125. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  126. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  127. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  128. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  129. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog_map.py +0 -0
  130. {gea_program-0.52.0 → gea_program-0.54.0}/gea/catalog_register.py +0 -0
  131. {gea_program-0.52.0 → gea_program-0.54.0}/gea/certificates.py +0 -0
  132. {gea_program-0.52.0 → gea_program-0.54.0}/gea/cli.py +0 -0
  133. {gea_program-0.52.0 → gea_program-0.54.0}/gea/client_reports.py +0 -0
  134. {gea_program-0.52.0 → gea_program-0.54.0}/gea/collision.py +0 -0
  135. {gea_program-0.52.0 → gea_program-0.54.0}/gea/config_versioning.py +0 -0
  136. {gea_program-0.52.0 → gea_program-0.54.0}/gea/conformance.py +0 -0
  137. {gea_program-0.52.0 → gea_program-0.54.0}/gea/control_room.py +0 -0
  138. {gea_program-0.52.0 → gea_program-0.54.0}/gea/correlation.py +0 -0
  139. {gea_program-0.52.0 → gea_program-0.54.0}/gea/dashboard.py +0 -0
  140. {gea_program-0.52.0 → gea_program-0.54.0}/gea/declarations.py +0 -0
  141. {gea_program-0.52.0 → gea_program-0.54.0}/gea/deviation.py +0 -0
  142. {gea_program-0.52.0 → gea_program-0.54.0}/gea/dlis.py +0 -0
  143. {gea_program-0.52.0 → gea_program-0.54.0}/gea/dnp3.py +0 -0
  144. {gea_program-0.52.0 → gea_program-0.54.0}/gea/doctor.py +0 -0
  145. {gea_program-0.52.0 → gea_program-0.54.0}/gea/dossier_verify.py +0 -0
  146. {gea_program-0.52.0 → gea_program-0.54.0}/gea/downhole_engine.py +0 -0
  147. {gea_program-0.52.0 → gea_program-0.54.0}/gea/drift_monitor.py +0 -0
  148. {gea_program-0.52.0 → gea_program-0.54.0}/gea/drill_string.py +0 -0
  149. {gea_program-0.52.0 → gea_program-0.54.0}/gea/earth_model.py +0 -0
  150. {gea_program-0.52.0 → gea_program-0.54.0}/gea/economics.py +0 -0
  151. {gea_program-0.52.0 → gea_program-0.54.0}/gea/ed25519.py +0 -0
  152. {gea_program-0.52.0 → gea_program-0.54.0}/gea/edr.py +0 -0
  153. {gea_program-0.52.0 → gea_program-0.54.0}/gea/elevation.py +0 -0
  154. {gea_program-0.52.0 → gea_program-0.54.0}/gea/etp.py +0 -0
  155. {gea_program-0.52.0 → gea_program-0.54.0}/gea/etp12_schemas.json +0 -0
  156. {gea_program-0.52.0 → gea_program-0.54.0}/gea/example_register_map.json +0 -0
  157. {gea_program-0.52.0 → gea_program-0.54.0}/gea/fat_sat.py +0 -0
  158. {gea_program-0.52.0 → gea_program-0.54.0}/gea/files.py +0 -0
  159. {gea_program-0.52.0 → gea_program-0.54.0}/gea/fixtures/README.md +0 -0
  160. {gea_program-0.52.0 → gea_program-0.54.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  161. {gea_program-0.52.0 → gea_program-0.54.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  162. {gea_program-0.52.0 → gea_program-0.54.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  163. {gea_program-0.52.0 → gea_program-0.54.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  164. {gea_program-0.52.0 → gea_program-0.54.0}/gea/follower.py +0 -0
  165. {gea_program-0.52.0 → gea_program-0.54.0}/gea/forward_model.py +0 -0
  166. {gea_program-0.52.0 → gea_program-0.54.0}/gea/frac_hits.py +0 -0
  167. {gea_program-0.52.0 → gea_program-0.54.0}/gea/friction_priors.py +0 -0
  168. {gea_program-0.52.0 → gea_program-0.54.0}/gea/gamma.py +0 -0
  169. {gea_program-0.52.0 → gea_program-0.54.0}/gea/gauge_aging.py +0 -0
  170. {gea_program-0.52.0 → gea_program-0.54.0}/gea/gauge_specs.py +0 -0
  171. {gea_program-0.52.0 → gea_program-0.54.0}/gea/geodesy.py +0 -0
  172. {gea_program-0.52.0 → gea_program-0.54.0}/gea/geomag.py +0 -0
  173. {gea_program-0.52.0 → gea_program-0.54.0}/gea/gravity_reference.py +0 -0
  174. {gea_program-0.52.0 → gea_program-0.54.0}/gea/hartip.py +0 -0
  175. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/TESTER_GUIDE.md +0 -0
  176. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/alarms.md +0 -0
  177. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/alerts.md +0 -0
  178. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/audit-update.md +0 -0
  179. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/backup.md +0 -0
  180. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/catalog.md +0 -0
  181. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/charts.md +0 -0
  182. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/collision.md +0 -0
  183. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/conformance.md +0 -0
  184. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/control-room.md +0 -0
  185. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/data-in.md +0 -0
  186. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/declare.md +0 -0
  187. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/dlis.md +0 -0
  188. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/doctor.md +0 -0
  189. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/dossier.md +0 -0
  190. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/drift.md +0 -0
  191. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/drillstring.md +0 -0
  192. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/economics.md +0 -0
  193. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/edr.md +0 -0
  194. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/etp.md +0 -0
  195. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/field-ports.md +0 -0
  196. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/files.md +0 -0
  197. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/frac-hits.md +0 -0
  198. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/historian.md +0 -0
  199. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/instruments.md +0 -0
  200. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/interval.md +0 -0
  201. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/invoice-check.md +0 -0
  202. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/map.md +0 -0
  203. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/month-end.md +0 -0
  204. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/notifications.md +0 -0
  205. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/opseis.md +0 -0
  206. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/osdu.md +0 -0
  207. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/pad-summary.md +0 -0
  208. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/patches.md +0 -0
  209. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/penetrator.md +0 -0
  210. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/plan.md +0 -0
  211. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/pore-pressure.md +0 -0
  212. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/ppdm.md +0 -0
  213. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/quakeml.md +0 -0
  214. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/quality.md +0 -0
  215. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/report-history.md +0 -0
  216. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/rig-time.md +0 -0
  217. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/seedlink.md +0 -0
  218. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/seismic.md +0 -0
  219. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/setup.md +0 -0
  220. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/simulation.md +0 -0
  221. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/site.md +0 -0
  222. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/sites.md +0 -0
  223. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/slip.md +0 -0
  224. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/soak.md +0 -0
  225. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/sra.md +0 -0
  226. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/start.md +0 -0
  227. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/structure.md +0 -0
  228. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/tools.md +0 -0
  229. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/transients.md +0 -0
  230. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/upkeep.md +0 -0
  231. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/vibration.md +0 -0
  232. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/well-report.md +0 -0
  233. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/well-tests.md +0 -0
  234. {gea_program-0.52.0 → gea_program-0.54.0}/gea/help/well3d.md +0 -0
  235. {gea_program-0.52.0 → gea_program-0.54.0}/gea/housekeeping.py +0 -0
  236. {gea_program-0.52.0 → gea_program-0.54.0}/gea/interval.py +0 -0
  237. {gea_program-0.52.0 → gea_program-0.54.0}/gea/inverse_engine.py +0 -0
  238. {gea_program-0.52.0 → gea_program-0.54.0}/gea/invoice_check.py +0 -0
  239. {gea_program-0.52.0 → gea_program-0.54.0}/gea/jobs.py +0 -0
  240. {gea_program-0.52.0 → gea_program-0.54.0}/gea/live_ports.py +0 -0
  241. {gea_program-0.52.0 → gea_program-0.54.0}/gea/loadtest.py +0 -0
  242. {gea_program-0.52.0 → gea_program-0.54.0}/gea/matplotlib_demo.py +0 -0
  243. {gea_program-0.52.0 → gea_program-0.54.0}/gea/modbus.py +0 -0
  244. {gea_program-0.52.0 → gea_program-0.54.0}/gea/modbus_rtu.py +0 -0
  245. {gea_program-0.52.0 → gea_program-0.54.0}/gea/model_card.py +0 -0
  246. {gea_program-0.52.0 → gea_program-0.54.0}/gea/month_end.py +0 -0
  247. {gea_program-0.52.0 → gea_program-0.54.0}/gea/mqtt_port.py +0 -0
  248. {gea_program-0.52.0 → gea_program-0.54.0}/gea/notify.py +0 -0
  249. {gea_program-0.52.0 → gea_program-0.54.0}/gea/op_seismicity.py +0 -0
  250. {gea_program-0.52.0 → gea_program-0.54.0}/gea/opcua_port.py +0 -0
  251. {gea_program-0.52.0 → gea_program-0.54.0}/gea/operator_app.py +0 -0
  252. {gea_program-0.52.0 → gea_program-0.54.0}/gea/osdu.py +0 -0
  253. {gea_program-0.52.0 → gea_program-0.54.0}/gea/pad_summary.py +0 -0
  254. {gea_program-0.52.0 → gea_program-0.54.0}/gea/patches.py +0 -0
  255. {gea_program-0.52.0 → gea_program-0.54.0}/gea/pdf_report.py +0 -0
  256. {gea_program-0.52.0 → gea_program-0.54.0}/gea/penetrator.py +0 -0
  257. {gea_program-0.52.0 → gea_program-0.54.0}/gea/permits.py +0 -0
  258. {gea_program-0.52.0 → gea_program-0.54.0}/gea/piwebapi.py +0 -0
  259. {gea_program-0.52.0 → gea_program-0.54.0}/gea/pore_pressure.py +0 -0
  260. {gea_program-0.52.0 → gea_program-0.54.0}/gea/ports.py +0 -0
  261. {gea_program-0.52.0 → gea_program-0.54.0}/gea/ppdm.py +0 -0
  262. {gea_program-0.52.0 → gea_program-0.54.0}/gea/profile_catalog.py +0 -0
  263. {gea_program-0.52.0 → gea_program-0.54.0}/gea/project.py +0 -0
  264. {gea_program-0.52.0 → gea_program-0.54.0}/gea/qt6_downhole_app.py +0 -0
  265. {gea_program-0.52.0 → gea_program-0.54.0}/gea/quakeml.py +0 -0
  266. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reconciler.py +0 -0
  267. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  268. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  269. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/WMM2025.COF +0 -0
  270. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  271. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/eia_prices.provenance.json +0 -0
  272. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  273. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/iscwsa_rev4_example.json +0 -0
  274. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/iscwsa_rev5_example.json +0 -0
  275. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/l0701_sonic_gr.las.gz +0 -0
  276. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/l0701_sonic_gr.provenance.json +0 -0
  277. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  278. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  279. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  280. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  281. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/mahoning_example.json +0 -0
  282. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/mud_motor_specs.json +0 -0
  283. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/owsg_tool_models.json +0 -0
  284. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/penetrator_ratings.json +0 -0
  285. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/seismicity_protocols.json +0 -0
  286. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/tool_sources.json +0 -0
  287. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/us_outline.json +0 -0
  288. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/us_outline.provenance.json +0 -0
  289. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_3d.provenance.json +0 -0
  290. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  291. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  292. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  293. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_surfaces.json.gz +0 -0
  294. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  295. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  296. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  297. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  298. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  299. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  300. {gea_program-0.52.0 → gea_program-0.54.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  301. {gea_program-0.52.0 → gea_program-0.54.0}/gea/report_history.py +0 -0
  302. {gea_program-0.52.0 → gea_program-0.54.0}/gea/rig_state.py +0 -0
  303. {gea_program-0.52.0 → gea_program-0.54.0}/gea/rock_inventory.py +0 -0
  304. {gea_program-0.52.0 → gea_program-0.54.0}/gea/sample_record.py +0 -0
  305. {gea_program-0.52.0 → gea_program-0.54.0}/gea/sample_well_profile.csv +0 -0
  306. {gea_program-0.52.0 → gea_program-0.54.0}/gea/sbom.py +0 -0
  307. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seedlink.py +0 -0
  308. {gea_program-0.52.0 → gea_program-0.54.0}/gea/segy.py +0 -0
  309. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic.py +0 -0
  310. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_array.py +0 -0
  311. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_assoc.py +0 -0
  312. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_detect.py +0 -0
  313. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_film.py +0 -0
  314. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_harmonic.py +0 -0
  315. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_qc.py +0 -0
  316. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_response.py +0 -0
  317. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_signature.py +0 -0
  318. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_track.py +0 -0
  319. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_uncertainty.py +0 -0
  320. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_unlisted.py +0 -0
  321. {gea_program-0.52.0 → gea_program-0.54.0}/gea/seismic_watch.py +0 -0
  322. {gea_program-0.52.0 → gea_program-0.54.0}/gea/sensor_swap.py +0 -0
  323. {gea_program-0.52.0 → gea_program-0.54.0}/gea/series_cache.py +0 -0
  324. {gea_program-0.52.0 → gea_program-0.54.0}/gea/service_life.py +0 -0
  325. {gea_program-0.52.0 → gea_program-0.54.0}/gea/setup_checklist.py +0 -0
  326. {gea_program-0.52.0 → gea_program-0.54.0}/gea/shapefile.py +0 -0
  327. {gea_program-0.52.0 → gea_program-0.54.0}/gea/shell.py +0 -0
  328. {gea_program-0.52.0 → gea_program-0.54.0}/gea/shut_in.py +0 -0
  329. {gea_program-0.52.0 → gea_program-0.54.0}/gea/simulation.py +0 -0
  330. {gea_program-0.52.0 → gea_program-0.54.0}/gea/site_map.py +0 -0
  331. {gea_program-0.52.0 → gea_program-0.54.0}/gea/sla_report.py +0 -0
  332. {gea_program-0.52.0 → gea_program-0.54.0}/gea/slip.py +0 -0
  333. {gea_program-0.52.0 → gea_program-0.54.0}/gea/soak.py +0 -0
  334. {gea_program-0.52.0 → gea_program-0.54.0}/gea/sra.py +0 -0
  335. {gea_program-0.52.0 → gea_program-0.54.0}/gea/store_forward.py +0 -0
  336. {gea_program-0.52.0 → gea_program-0.54.0}/gea/strata_join.py +0 -0
  337. {gea_program-0.52.0 → gea_program-0.54.0}/gea/structure.py +0 -0
  338. {gea_program-0.52.0 → gea_program-0.54.0}/gea/supervisor.py +0 -0
  339. {gea_program-0.52.0 → gea_program-0.54.0}/gea/survey_cmd.py +0 -0
  340. {gea_program-0.52.0 → gea_program-0.54.0}/gea/survey_view.py +0 -0
  341. {gea_program-0.52.0 → gea_program-0.54.0}/gea/telemetry.py +0 -0
  342. {gea_program-0.52.0 → gea_program-0.54.0}/gea/tool_catalog.py +0 -0
  343. {gea_program-0.52.0 → gea_program-0.54.0}/gea/tool_library.py +0 -0
  344. {gea_program-0.52.0 → gea_program-0.54.0}/gea/tool_taxonomy.py +0 -0
  345. {gea_program-0.52.0 → gea_program-0.54.0}/gea/transient.py +0 -0
  346. {gea_program-0.52.0 → gea_program-0.54.0}/gea/vibration.py +0 -0
  347. {gea_program-0.52.0 → gea_program-0.54.0}/gea/well3d.py +0 -0
  348. {gea_program-0.52.0 → gea_program-0.54.0}/gea/well_assembler.py +0 -0
  349. {gea_program-0.52.0 → gea_program-0.54.0}/gea/well_report.py +0 -0
  350. {gea_program-0.52.0 → gea_program-0.54.0}/gea/well_test_validation.py +0 -0
  351. {gea_program-0.52.0 → gea_program-0.54.0}/gea/wellplan.py +0 -0
  352. {gea_program-0.52.0 → gea_program-0.54.0}/gea/wellpos.py +0 -0
  353. {gea_program-0.52.0 → gea_program-0.54.0}/gea/wits0.py +0 -0
  354. {gea_program-0.52.0 → gea_program-0.54.0}/gea/witsml.py +0 -0
  355. {gea_program-0.52.0 → gea_program-0.54.0}/gea/workspace.py +0 -0
  356. {gea_program-0.52.0 → gea_program-0.54.0}/gea/xlsx_read.py +0 -0
  357. {gea_program-0.52.0 → gea_program-0.54.0}/gea_program.egg-info/dependency_links.txt +0 -0
  358. {gea_program-0.52.0 → gea_program-0.54.0}/gea_program.egg-info/entry_points.txt +0 -0
  359. {gea_program-0.52.0 → gea_program-0.54.0}/gea_program.egg-info/requires.txt +0 -0
  360. {gea_program-0.52.0 → gea_program-0.54.0}/gea_program.egg-info/top_level.txt +0 -0
  361. {gea_program-0.52.0 → gea_program-0.54.0}/setup.cfg +0 -0
  362. {gea_program-0.52.0 → gea_program-0.54.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.52.0
3
+ Version: 0.54.0
4
4
  Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
5
5
  Author: Daniel T. Murphy
6
6
  License: MPL-2.0
@@ -648,6 +648,29 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
648
648
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
649
649
  `rtserve.iris.washington.edu:18000`, network `TX`.
650
650
 
651
+ ### Decline curves
652
+
653
+ Each well's monthly production fitted by Arps's curves - the modified
654
+ hyperbolic, turned exponential at a terminal decline - forecast month by
655
+ month to its economic limit and valued: the estimated ultimate recovery,
656
+ and the net cash flow discounted at the declared rate, at the SEC 12-month
657
+ average of the first-of-month WTI and Henry Hub prices (shipped from the
658
+ EIA) or a declared price, with the net revenue interest, severance tax and
659
+ operating cost declared. A forecast is not a reserves report, and the page
660
+ says so. The **Decline curves** tab, `gea decline`, `gea help decline`.
661
+
662
+ ### Torque and drag
663
+
664
+ The hook load and torque the drill string should take at each bit depth, by
665
+ the soft-string model of Johancsik et al.: pick-up, slack-off and rotating
666
+ off bottom for the declared friction factors and a fan of others (the
667
+ broomstick), the torque rotating off bottom. Against them, the rig's own
668
+ hook loads and torque from the drilling record, read by the rig states -
669
+ tripping out and in, rotating off bottom - and the friction factors that fit
670
+ them, cased and open hole apart. Volve 15/9-F-10 with an example string is
671
+ the worked example. The **Torque & drag** tab, `gea torque-drag`,
672
+ `gea help torque-drag`.
673
+
651
674
  ### Pore pressure
652
675
 
653
676
  The mud-weight window from a well's logs or its drilling record. The
@@ -609,6 +609,29 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
609
609
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
610
610
  `rtserve.iris.washington.edu:18000`, network `TX`.
611
611
 
612
+ ### Decline curves
613
+
614
+ Each well's monthly production fitted by Arps's curves - the modified
615
+ hyperbolic, turned exponential at a terminal decline - forecast month by
616
+ month to its economic limit and valued: the estimated ultimate recovery,
617
+ and the net cash flow discounted at the declared rate, at the SEC 12-month
618
+ average of the first-of-month WTI and Henry Hub prices (shipped from the
619
+ EIA) or a declared price, with the net revenue interest, severance tax and
620
+ operating cost declared. A forecast is not a reserves report, and the page
621
+ says so. The **Decline curves** tab, `gea decline`, `gea help decline`.
622
+
623
+ ### Torque and drag
624
+
625
+ The hook load and torque the drill string should take at each bit depth, by
626
+ the soft-string model of Johancsik et al.: pick-up, slack-off and rotating
627
+ off bottom for the declared friction factors and a fan of others (the
628
+ broomstick), the torque rotating off bottom. Against them, the rig's own
629
+ hook loads and torque from the drilling record, read by the rig states -
630
+ tripping out and in, rotating off bottom - and the friction factors that fit
631
+ them, cased and open hole apart. Volve 15/9-F-10 with an example string is
632
+ the worked example. The **Torque & drag** tab, `gea torque-drag`,
633
+ `gea help torque-drag`.
634
+
612
635
  ### Pore pressure
613
636
 
614
637
  The mud-weight window from a well's logs or its drilling record. The
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.52.0"
126
+ __version__ = "0.54.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -622,6 +622,35 @@ def main(argv=None) -> int:
622
622
  p_pl.add_argument("--actor", type=str, default="cli")
623
623
  p_pl.add_argument("--json", action="store_true")
624
624
 
625
+ p_dc = sub.add_parser("decline", help="each well's monthly production fitted by Arps's curves, forecast to its economic limit and valued at the SEC 12-month price or a declared one")
626
+ p_dc.add_argument("--workspace", type=str, required=True)
627
+ p_dc.add_argument("--well", type=str, default=None)
628
+ p_dc.add_argument("--attach", type=str, default=None, help="attach FILE as the well's monthly production (CSV or Excel)")
629
+ p_dc.add_argument("--remove", action="store_true")
630
+ for k_, h_ in (("terminal-decline", "the terminal decline, effective annual (0.08 = 8 %%)"), ("b-max", "the largest b fitted"), ("oil-price", "USD/bbl"),
631
+ ("gas-price", "USD/MMBtu"), ("price-basis", "sec or flat"), ("nri", "the net revenue interest"), ("severance", "the severance tax (0.046 = 4.6 %%)"),
632
+ ("opex", "the operating cost, USD a month"), ("discount", "the discount rate (0.10)"), ("start", "fit from this month (YYYY-MM)"),
633
+ ("as-of", "value as of this date (YYYY-MM-DD)")):
634
+ p_dc.add_argument("--" + k_, type=str, default=None, help=h_)
635
+ p_dc.add_argument("--source", type=str, default="", help="where the production or the terms came from")
636
+ p_dc.add_argument("--write", action="store_true")
637
+ p_dc.add_argument("--actor", type=str, default="cli")
638
+ p_dc.add_argument("--json", action="store_true")
639
+
640
+ p_td = sub.add_parser("torque-drag", help="the hook load and torque the string should take at each bit depth (soft-string model), the broomstick, and the friction factors that fit the rig's own trips")
641
+ p_td.add_argument("--workspace", type=str, default=None)
642
+ p_td.add_argument("--well", type=str, default=None)
643
+ p_td.add_argument("--example", action="store_true", help="the worked example (Volve 15/9-F-10 with an example string)")
644
+ p_td.add_argument("--string", type=str, default=None, help="sections from the bit up: 'BHA:300:6.75:95; drill pipe::5:22.6:6.625' (what:length_ft:od_in:lb_ft[:contact_od_in])")
645
+ p_td.add_argument("--mud", type=str, default=None, help="mud density, ppg")
646
+ p_td.add_argument("--mu-cased", type=str, default=None)
647
+ p_td.add_argument("--mu-open", type=str, default=None)
648
+ p_td.add_argument("--block", type=str, default=None, help="the travelling block's weight, klbf (estimated from the record when not declared)")
649
+ p_td.add_argument("--source", type=str, default="", help="where the string and its figures came from")
650
+ p_td.add_argument("--write", action="store_true")
651
+ p_td.add_argument("--actor", type=str, default="cli")
652
+ p_td.add_argument("--json", action="store_true")
653
+
625
654
  p_pp = sub.add_parser("pore-pressure", help="the mud-weight window: overburden, pore pressure by Eaton from the sonic, the resistivity or the d-exponent, and the fracture gradient")
626
655
  p_pp.add_argument("--workspace", type=str, default=None)
627
656
  p_pp.add_argument("--well", type=str, default=None)
@@ -2472,6 +2501,71 @@ def main(argv=None) -> int:
2472
2501
  raise SystemExit(f"collision: {e.args[0] if e.args else e}")
2473
2502
  print(_json.dumps({k: v for k, v in r.items()}, default=str) if a.json else _CO.text(r))
2474
2503
  return 0
2504
+ elif a.cmd == "decline":
2505
+ import json as _json
2506
+ from . import decline as _DCA
2507
+ from .cli import _safe_stdout
2508
+ from .workspace import Workspace, WorkspaceError
2509
+ _safe_stdout()
2510
+ try:
2511
+ ws = Workspace(a.workspace)
2512
+ terms = {"terminal_decline": a.terminal_decline, "b_max": a.b_max, "oil_price": a.oil_price, "gas_price": a.gas_price, "price_basis": a.price_basis,
2513
+ "nri": a.nri, "severance": a.severance, "opex_month": a.opex, "discount": a.discount, "start": a.start}
2514
+ if (a.attach or a.remove or any(v is not None for v in terms.values()) or a.write) and not a.well:
2515
+ raise ValueError("--well is needed")
2516
+ if a.attach:
2517
+ with open(a.attach, "rb") as f_:
2518
+ r_ = _DCA.attach(ws, a.well, os.path.basename(a.attach), f_.read(), a.actor, a.source)
2519
+ print(f"production attached to {a.well}: {r_['months']} months, {r_['first']} to {r_['last']}")
2520
+ return 0
2521
+ if a.remove:
2522
+ _DCA.remove(ws, a.well, a.actor)
2523
+ print(f"production removed from {a.well}")
2524
+ return 0
2525
+ if any(v is not None for v in terms.values()):
2526
+ c_ = _DCA.set_config(ws, a.actor, a.well, a.source, **{k: v for k, v in terms.items() if v is not None})
2527
+ print(f"{a.well}: terminal {c_['terminal_decline']:g}, price {c_['price_basis']}, NRI {c_['nri']:g}, severance {c_['severance']:g}, opex {c_['opex_month']:,.0f}/month, discount {c_['discount']:g}")
2528
+ return 0
2529
+ if a.write:
2530
+ r_ = _DCA.write_report(ws, __version__, a.well, a.actor)
2531
+ print(f"wrote {r_['report_id']}: {r_['paths']['html']}")
2532
+ if r_['paths'].get('pdf'):
2533
+ print(f" printable: {r_['paths']['pdf']}")
2534
+ return 0
2535
+ r = _DCA.page(ws, a.well, a.as_of)
2536
+ except (KeyError, ValueError, OSError, WorkspaceError) as e:
2537
+ raise SystemExit(f"decline: {e.args[0] if e.args else e}")
2538
+ print(_json.dumps(r, default=str) if a.json else _DCA.text(r))
2539
+ return 0
2540
+ elif a.cmd == "torque-drag":
2541
+ import json as _json
2542
+ from . import torque_drag as _TND
2543
+ from .cli import _safe_stdout
2544
+ from .workspace import Workspace, WorkspaceError
2545
+ _safe_stdout()
2546
+ try:
2547
+ ws = Workspace(a.workspace) if a.workspace else None
2548
+ scene = "example" if (a.example or ws is None) else "workspace"
2549
+ if ws is not None and any(v is not None for v in (a.string, a.mud, a.mu_cased, a.mu_open, a.block)):
2550
+ if not a.well:
2551
+ raise ValueError("--well is needed")
2552
+ c_ = _TND.set_well(ws, a.actor, a.well, a.source, _TND.parse_string(a.string) if a.string else None, a.mud, a.mu_cased, a.mu_open, a.block)
2553
+ print(f"{a.well}: " + "; ".join(f"{x['what']} {('to surface' if x['length_ft'] is None else format(x['length_ft'], ',.0f') + ' ft')}" for x in (c_.get('string') or []))
2554
+ + f"; mud {c_['mud_ppg']:g} ppg, mu cased {c_['mu_cased']:g}, open {c_['mu_open']:g}")
2555
+ return 0
2556
+ if a.write:
2557
+ if ws is None:
2558
+ raise ValueError("--write needs --workspace")
2559
+ r_ = _TND.write_report(ws, __version__, a.well, a.actor, scene)
2560
+ print(f"wrote {r_['report_id']}: {r_['paths']['html']}")
2561
+ if r_['paths'].get('pdf'):
2562
+ print(f" printable: {r_['paths']['pdf']}")
2563
+ return 0
2564
+ r = _TND.page(ws, scene, a.well)
2565
+ except (KeyError, ValueError, OSError, WorkspaceError) as e:
2566
+ raise SystemExit(f"torque-drag: {e.args[0] if e.args else e}")
2567
+ print(_json.dumps(r, default=str) if a.json else _TND.text(r))
2568
+ return 0
2475
2569
  elif a.cmd == "pore-pressure":
2476
2570
  import json as _json
2477
2571
  from . import pore_pressure as _PPF
@@ -11905,6 +11905,340 @@ def section_cu_pore_pressure(tmp: str) -> None:
11905
11905
  + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
11906
11906
 
11907
11907
 
11908
+ def section_cv_torque_drag(tmp: str) -> None:
11909
+ """Section CV - torque and drag: the soft-string model against closed forms (a vertical string, a straight inclined
11910
+ one pulled and rotated, the capstan over a bend in the vertical plane and in azimuth); a drilling record made by the
11911
+ model with known friction factors, read back by the rig states and fitted to them; the worked example; the API, the
11912
+ CLI, the report, the dossier, the tab and the help."""
11913
+ import http.cookiejar
11914
+ import math as _m
11915
+ import subprocess as _sp
11916
+ import urllib.request
11917
+ import urllib.error
11918
+ import zipfile as _zf
11919
+ import numpy as _np
11920
+ from . import __version__ as _ver
11921
+ from . import dossier as DZ
11922
+ from . import helplib as H
11923
+ from . import rig_state as RS
11924
+ from . import torque_drag as TND
11925
+ from . import well3d as W
11926
+ from .workspace import Workspace
11927
+ from .service import Service, Users
11928
+ pkg = Path(__file__).parent
11929
+ d = Path(tmp, "torquedrag"); d.mkdir()
11930
+
11931
+ # -- CV1 the model against closed forms ------------------------------------------------------------------------------
11932
+ md = _np.linspace(0.0, 10000.0, 101); n = len(md)
11933
+ bf = TND.buoyancy(10.0)
11934
+ Fv, _ = TND.soft_string(md, _np.zeros(n), _np.zeros(n), _np.full(n - 1, 20.0 * bf), _np.full(n - 1, 3.3125), _np.full(n - 1, 0.3), "out")
11935
+ s60, c60 = _m.sin(_m.radians(60)), _m.cos(_m.radians(60))
11936
+ Fo, _ = TND.soft_string(md, _np.full(n, 60.0), _np.zeros(n), _np.full(n - 1, 20.0), _np.full(n - 1, 3.0), _np.full(n - 1, 0.3), "out")
11937
+ Fi, _ = TND.soft_string(md, _np.full(n, 60.0), _np.zeros(n), _np.full(n - 1, 20.0), _np.full(n - 1, 3.0), _np.full(n - 1, 0.3), "in")
11938
+ Fr, Mr = TND.soft_string(md, _np.full(n, 60.0), _np.zeros(n), _np.full(n - 1, 20.0), _np.full(n - 1, 3.0), _np.full(n - 1, 0.3), "rotate")
11939
+ N_ = 9001
11940
+ mc = _np.linspace(0.0, 1000.0, N_)
11941
+ Fc, _ = TND.soft_string(mc, _np.linspace(0.0, 90.0, N_), _np.zeros(N_), _np.zeros(N_ - 1), _np.full(N_ - 1, 3.0), _np.full(N_ - 1, 0.3), "out", 10000.0)
11942
+ Fa, _ = TND.soft_string(mc, _np.full(N_, 90.0), _np.linspace(0.0, 90.0, N_), _np.zeros(N_ - 1), _np.full(N_ - 1, 3.0), _np.full(N_ - 1, 0.3), "out", 10000.0)
11943
+ cap = 10000.0 * _m.exp(0.3 * _m.pi / 2)
11944
+ refused = 0
11945
+ for bad in ([], [{"what": "x", "length_ft": None, "od_in": 5, "weight_lb_ft": 20}, {"what": "y", "length_ft": None, "od_in": 5, "weight_lb_ft": 20}],
11946
+ [{"what": "x", "length_ft": 100, "od_in": 0.5, "weight_lb_ft": 20}], [{"what": "x", "length_ft": 100, "od_in": 5, "weight_lb_ft": 900}],
11947
+ [{"what": "x", "length_ft": 100, "od_in": 6, "weight_lb_ft": 20, "contact_od_in": 5}]):
11948
+ try:
11949
+ TND.check_string(bad)
11950
+ except ValueError:
11951
+ refused += 1
11952
+ c1 = {
11953
+ "vertical": abs(Fv[0] - 20.0 * bf * 10000.0) < 1e-6 and abs(bf - (1 - 10.0 / 65.5)) < 1e-12,
11954
+ "inclined": abs(Fo[0] - 200000.0 * (c60 + 0.3 * s60)) < 1e-6 and abs(Fi[0] - 200000.0 * (c60 - 0.3 * s60)) < 1e-6 and abs(Fr[0] - 200000.0 * c60) < 1e-6
11955
+ and abs(Mr[0] - 0.3 * 200000.0 * s60 * 3.0 / 12.0) < 1e-6,
11956
+ "capstan": abs(Fc[0] / cap - 1) < 5e-5 and abs(Fa[0] / cap - 1) < 5e-5,
11957
+ "string": refused == 5 and TND.parse_string("BHA:300:6.75:95; dp::5:22.6:6.625")[1]["length_ft"] is None,
11958
+ }
11959
+ failed1 = [k for k, v in c1.items() if not v]
11960
+ ok(not failed1,
11961
+ "CV1 the soft-string model against closed forms: a vertical string weighs its buoyed weight; a straight 60-degree string "
11962
+ "pulled, run and rotated gives W(cos + mu sin), W(cos - mu sin), W cos and the torque mu W sin r exactly; a weightless string "
11963
+ "over a 90-degree bend, in the vertical plane or in azimuth, comes to the capstan's e^(mu theta) as the elements shrink; a "
11964
+ "string without sections, with two open-ended, or with a diameter, weight or contact diameter out of range refused"
11965
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
11966
+
11967
+ # -- CV2 a record made by the model, read back ---------------------------------------------------------------------------
11968
+ f = TND.selftest_files(str(d))
11969
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
11970
+ wid = ws.add_well_file(f["record"], display="TD-1", actor="tester")["id"]
11971
+ ws.set_well(wid, surface={"lat": 31.42, "lon": -104.10, "datum": "NAD83", "elevation": {"ft": 3000.0, "datum": "KB", "source": "test plat"}}, actor="tester")
11972
+ no_survey = TND.page(ws, "workspace", wid)["detail"]
11973
+ W.attach_trajectory(ws, wid, "s.csv", Path(f["survey"]).read_bytes(), "tester", "true", "ft", None, None, None, "test survey")
11974
+ no_string = TND.page(ws, "workspace", wid)["detail"]
11975
+ RS.set_casing(ws, "tester", wid, "9-5/8", 4000, "tally (test)")
11976
+ TND.set_well(ws, "tester", wid, "BHA report (test)", f["string"], 10.0, 0.25, 0.30)
11977
+ r = TND.page(ws, "workspace", wid)["result"]
11978
+ fi = r["fits"]
11979
+ m_ = r["measured"]
11980
+ c2 = {
11981
+ "needs": "survey" in no_survey and "string" in no_string,
11982
+ "measured": len(m_["out"]["depth_ft"]) >= 70 and len(m_["in"]["depth_ft"]) >= 70 and len(m_["rotate"]["depth_ft"]) == 9 and abs(r["block_klbf"] - 40.0) < 0.5 and m_["block_estimated"],
11983
+ "open": all(abs(fi[k]["mu"] - 0.24) <= 0.02 for k in ("out:open", "in:open", "torque:open")),
11984
+ "cased": all(abs(fi[k]["mu"] - 0.18) <= 0.02 for k in ("out:cased", "in:cased", "torque:cased")),
11985
+ "weights": abs(r["string_air_klbf"] - (300 * 95.0 + (r["td_ft"] - 300) * 22.6) / 1000.0) < 0.05 and r["shoe_ft"] == 4000.0,
11986
+ }
11987
+ failed2 = [k for k, v in c2.items() if not v]
11988
+ ok(not failed2,
11989
+ "CV2 a drilling record made by the model - a trip out and in stand by stand, the string rotated off bottom every 900 ft, "
11990
+ "friction 0.18 cased above a 4,000 ft shoe and 0.24 open, a 40 klbf block, noise - read back by the rig states: the block "
11991
+ "estimated to half a klbf, the pick-up, slack-off and torque each fitting 0.18 cased and 0.24 open to within 0.02; a well "
11992
+ "without a survey or a string says what it needs"
11993
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
11994
+
11995
+ # -- CV3 the worked example -----------------------------------------------------------------------------------------------
11996
+ ex = TND.example()
11997
+ de = ex["declared"]
11998
+ c3 = {
11999
+ "example": ex["well"] == "15/9-F-10" and abs(ex["td_ft"] - 5300.07 / 0.3048) < 5.0 and ex["max_inc_deg"] > 80
12000
+ and all(de["out"][i] >= de["rotate"][i] >= de["in"][i] for i in range(len(de["depth_ft"])))
12001
+ and de["torque"][-1] > de["torque"][len(de["torque"]) // 2] > 0 and "not from Volve" in ex["config"]["source"],
12002
+ "fan": sorted(ex["fan"]) == ["0.1", "0.2", "0.3", "0.4", "0.5"] and ex["fan"]["0.5"]["out"][-1] > ex["fan"]["0.1"]["out"][-1] and ex["fan"]["0.5"]["in"][-1] < ex["fan"]["0.1"]["in"][-1],
12003
+ }
12004
+ failed3 = [k for k, v in c3.items() if not v]
12005
+ ok(not failed3,
12006
+ "CV3 the worked example, Volve 15/9-F-10's survey to its 5,300 m TD with a string declared for the example: pick-up above "
12007
+ "rotating above slack-off at every depth, the torque growing with depth, the broomstick fanning out with the friction factor"
12008
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
12009
+
12010
+ # -- CV4 the API, the CLI, the report, the dossier, the tab and the help -------------------------------------------------
12011
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
12012
+ svc = Service(wsp, port=0, scheduler=False).start()
12013
+ base_url = svc.url.rstrip("/")
12014
+
12015
+ def opener(name, pw_):
12016
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
12017
+ rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
12018
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
12019
+ return o
12020
+
12021
+ def call(o, method, path, body=None):
12022
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
12023
+ if method == "POST":
12024
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
12025
+ try:
12026
+ with o.open(rq, timeout=120) as rr_:
12027
+ return rr_.status, json.loads(rr_.read())
12028
+ except urllib.error.HTTPError as e:
12029
+ return e.code, json.loads(e.read())
12030
+ try:
12031
+ V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
12032
+ s_vg, vg = call(V, "GET", "/api/torque-drag?well=" + wid)
12033
+ s_ve, ve = call(V, "GET", "/api/torque-drag?scene=example")
12034
+ s_vs, _ = call(V, "POST", "/api/torque-drag/settings", {"well_id": wid, "mud_ppg": 11, "source": "x"})
12035
+ s_os, os_ = call(O, "POST", "/api/torque-drag/settings", {"well_id": wid, "mud_ppg": "11", "string": "BHA:300:6.75:95:6.75; dp::5:22.6:6.625", "source": "mud report (test)"})
12036
+ s_bs, _ = call(O, "POST", "/api/torque-drag/settings", {"well_id": wid, "string": "BHA:300", "source": "x"})
12037
+ s_bm, _ = call(O, "POST", "/api/torque-drag/settings", {"well_id": wid, "mu_open": 0.9, "source": "x"})
12038
+ s_ns, _ = call(O, "POST", "/api/torque-drag/settings", {"well_id": wid, "mud_ppg": 12})
12039
+ s_vw, _ = call(V, "POST", "/api/torque-drag/write", {"well_id": wid})
12040
+ s_ow, ow = call(O, "POST", "/api/torque-drag/write", {"well_id": wid})
12041
+ finally:
12042
+ svc.stop()
12043
+ cli_x = _sp.run([sys.executable, "-m", "gea", "torque-drag", "--example"], capture_output=True, text=True, cwd=str(pkg.parent))
12044
+ cli_s = _sp.run([sys.executable, "-m", "gea", "torque-drag", "--workspace", wsp, "--well", wid, "--mu-open", "0.26", "--source", "offset well (test)"], capture_output=True, text=True, cwd=str(pkg.parent))
12045
+ cli_t = _sp.run([sys.executable, "-m", "gea", "torque-drag", "--workspace", wsp, "--well", wid], capture_output=True, text=True, cwd=str(pkg.parent))
12046
+ dz = DZ.build(ws, "tester", _ver, None)
12047
+ names = _zf.ZipFile(Path(ws.reports_dir, dz["path"])).namelist()
12048
+ audit = Path(ws.audit_path).read_text(encoding="utf-8")
12049
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
12050
+ c4 = {
12051
+ "api": s_vg == 200 and vg["result"]["fits"] and s_ve == 200 and ve["result"]["well"] == "15/9-F-10" and s_vs == 403 and s_os == 200 and os_["mud_ppg"] == 11.0
12052
+ and len(os_["string"]) == 2 and s_bs == 400 and s_bm == 400 and s_ns == 400 and s_vw == 403 and s_ow == 200 and ow["report_id"].startswith("TND-"),
12053
+ "cli": cli_x.returncode == 0 and "15/9-F-10" in cli_x.stdout and cli_s.returncode == 0 and "open 0.26" in cli_s.stdout and cli_t.returncode == 0
12054
+ and "fits mu" in cli_t.stdout and "mud 11 ppg" in cli_t.stdout,
12055
+ "report": PR_read_ok(Path(ws.reports_dir, "wells", wid, "torque_drag.pdf")) and "torque_drag.declare" in audit and "report.torque_drag" in audit,
12056
+ "dossier": "declarations/drillstring/torque_drag.json" in names and f"results/torque_drag/{wid}.json" in names,
12057
+ "page": all(x in page_src for x in ("VIEWS.torquedrag", "['#/torquedrag', 'Torque & drag']", "/api/torque-drag/settings", "tdChart")),
12058
+ "help": not H.check("torque-drag") and H.VIEW_TOPIC.get("torquedrag") == "torque-drag",
12059
+ }
12060
+ failed4 = [k for k, v in c4.items() if not v]
12061
+ ok(not failed4,
12062
+ "CV4 on the API (anyone reads a well's model and the worked example; only an operator declares the string, the mud and the "
12063
+ "friction factors - a section without its figures, a friction factor of 0.9 or a change without its source refused - or "
12064
+ "writes the report), the CLI, the report as PDF, a signed dossier carrying the string and the model, the tab and the help"
12065
+ + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
12066
+
12067
+
12068
+ def section_cw_decline(tmp: str) -> None:
12069
+ """Section CW - decline curves: Arps's rate and cumulative and the modified hyperbolic against their closed forms
12070
+ and the decline conversions; a production table built on known curves fitted back and forecast; the economic limit,
12071
+ the discounted value and the SEC 12-month price; the API, the CLI, the report, the dossier, the tab and the help."""
12072
+ import http.cookiejar
12073
+ import math as _m
12074
+ import subprocess as _sp
12075
+ import urllib.request
12076
+ import urllib.error
12077
+ import zipfile as _zf
12078
+ from datetime import date as _date
12079
+ import numpy as _np
12080
+ from . import __version__ as _ver
12081
+ from . import decline as DCA
12082
+ from . import dossier as DZ
12083
+ from . import edr as E
12084
+ from . import helplib as H
12085
+ from . import xlsx_read as XR
12086
+ from .workspace import Workspace
12087
+ from .service import Service, Users
12088
+ pkg = Path(__file__).parent
12089
+ d = Path(tmp, "decline"); d.mkdir()
12090
+
12091
+ # -- CW1 the curves by hand --------------------------------------------------------------------------------------------
12092
+ qi, Di = 1000.0, 0.002
12093
+ t = _np.array([0.0, 100.0, 1000.0])
12094
+ e_ok = _np.allclose(DCA.rate(t, qi, Di, 0.0), qi * _np.exp(-Di * t)) and _np.allclose(DCA.cum(t, qi, Di, 0.0), qi / Di * (1 - _np.exp(-Di * t)))
12095
+ h_ok = _np.allclose(DCA.rate(t, qi, Di, 0.5), qi / (1 + 0.5 * Di * t) ** 2) and _np.allclose(DCA.cum(t, qi, Di, 0.5), qi / (0.5 * Di) * (1 - (1 + 0.5 * Di * t) ** -1))
12096
+ a_ok = _np.allclose(DCA.cum(t, qi, Di, 1.0), qi / Di * _np.log(1 + Di * t))
12097
+ tt = _np.linspace(0, 20000, 200001)
12098
+ q_mh = DCA.rate(tt, qi, Di, 1.5, 0.0002)
12099
+ trap = float(_np.sum(0.5 * (q_mh[1:] + q_mh[:-1]) * _np.diff(tt)))
12100
+ ts = (Di / 0.0002 - 1) / (1.5 * Di)
12101
+ i_s = int(_np.searchsorted(tt, ts))
12102
+ dq = _np.diff(_np.log(q_mh))
12103
+ c1 = {
12104
+ "arps": e_ok and h_ok and a_ok,
12105
+ "modified": abs(float(DCA.cum(_np.array([20000.0]), qi, Di, 1.5, 0.0002)[0]) / trap - 1) < 1e-6 and abs(dq[i_s + 5] / 0.1 + 0.0002) < 1e-9 and q_mh[i_s - 1] > q_mh[i_s],
12106
+ "conversions": abs(DCA.effective_annual(DCA.nominal_daily(0.65, 1.1), 1.1) - 0.65) < 1e-12 and abs(DCA.effective_annual(DCA.nominal_daily(0.08, 0.0), 0.0) - 0.08) < 1e-12
12107
+ and abs(DCA.effective_annual(0.001, 0.0) - (1 - _m.exp(-0.36525))) < 1e-12,
12108
+ "sec": DCA.sec_price([f"2025-{m:02d}-0{k}" for m in range(1, 13) for k in (2, 3)] + ["2024-12-31"], [float(m * 10 + k) for m in range(1, 13) for k in (2, 3)] + [999.0],
12109
+ _date(2026, 1, 1))["price"] == round(sum(m * 10 + 2 for m in range(1, 13)) / 12.0, 4)
12110
+ and DCA.sec_price(["2025-06-01"], [50.0], _date(2026, 1, 1)) is None,
12111
+ }
12112
+ failed1 = [k for k, v in c1.items() if not v]
12113
+ ok(not failed1,
12114
+ "CW1 Arps's curves by hand: the exponential, hyperbolic and harmonic rates and cumulatives in closed form; the modified "
12115
+ "hyperbolic's cumulative equal to the integral of its rate, exponential at the terminal decline from the switch; the "
12116
+ "effective and nominal declines inverse to each other; the SEC price the average of each month's first quote of the 12 "
12117
+ "months before, and none when a month is missing"
12118
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
12119
+
12120
+ # -- CW2 a table built on known curves, fitted back ---------------------------------------------------------------------
12121
+ f = DCA.selftest_files(str(d))
12122
+ prod = DCA.read_production(Path(f["path"]).read_bytes())
12123
+ so = DCA.analyse_stream(prod, "oil", {**DCA.DEFAULTS})
12124
+ Dn, Dm = DCA.nominal_daily(f["De"], f["b"]), DCA.nominal_daily(f["Dterm"], 0.0)
12125
+ th = _np.array(so["history"]["t"])
12126
+ q_true = DCA.rate(th, f["qi"], Dn, f["b"], Dm)
12127
+ q_fit = DCA.rate(th - so["t0_days"], so["fit"]["qi"], so["fit"]["Di"], so["fit"]["b"], so["Dmin"])
12128
+ t_end = th[-1] + 30 * 365.25
12129
+ eur_true = float(DCA.cum(_np.array([t_end]), f["qi"], Dn, f["b"], Dm)[0])
12130
+ eur_fit = float(DCA.cum(_np.array([t_end - so["t0_days"]]), so["fit"]["qi"], so["fit"]["Di"], so["fit"]["b"], so["Dmin"])[0])
12131
+ days_ok = all(abs(so["history"]["q"][i] - so["history"]["v"][i] / prod[i]["days"]) < 1e-3 for i in (7, 19))
12132
+ XR.write_simple(str(d / "p.xlsx"), [["Production Month", "Oil Volume", "Gas Volume", "Days On"], ["2024-01", 3000, 6000, 31], ["2024-02", 2700, 5500, 29]])
12133
+ px = DCA.read_production((d / "p.xlsx").read_bytes())
12134
+ refused = 0
12135
+ for bad in ("Month,Oil\n2024-01,-5\n", "Month,Oil\n2024-01,5\n2024-01,6\n", "Oil,Gas\n1,2\n", "Month,Oil,Days\n2024-01,5,40\n", "Month,Note\n2024-01,x\n"):
12136
+ try:
12137
+ DCA.read_production(bad.encode())
12138
+ except ValueError:
12139
+ refused += 1
12140
+ c2 = {
12141
+ "fit": abs(so["fit"]["b"] - f["b"]) <= 0.15 and float(_np.max(_np.abs(q_fit - q_true) / q_true)) < 0.04 and so["months_fitted"] == 48,
12142
+ "eur": abs(eur_fit / eur_true - 1) < 0.05,
12143
+ "days": days_ok,
12144
+ "table": len(prod) == 48 and prod[0]["month"] == "2022-01" and px[1]["days"] == 29 and px[0]["oil"] == 3000 and refused == 5,
12145
+ }
12146
+ failed2 = [k for k, v in c2.items() if not v]
12147
+ ok(not failed2,
12148
+ "CW2 a well's 48 months built on a known curve (900 bbl/d, b 1.1, 65 % a year, 8 % terminal, 3 % noise, four months part "
12149
+ "shut in) fitted back: b within 0.15, the rate within 4 % over the history, the 30-year recovery within 5 %; a part month's "
12150
+ "rate its volume over its producing days; a table read from Excel by its names; a negative volume, a month twice, no month, "
12151
+ "more than 31 days or no stream refused"
12152
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
12153
+
12154
+ # -- CW3 the forecast, the limit and the value ---------------------------------------------------------------------------
12155
+ one = [{"month": f"2020-{m:02d}", "oil": 3000.0 * _m.exp(-0.01 * 30.4375 * (m - 1)), "gas": None, "water": None, "days": None} for m in range(1, 13)]
12156
+ cfg = {**DCA.DEFAULTS, "terminal_decline": None, "nri": 1.0, "severance": 0.0, "opex_month": 0.0, "discount": 0.0, "years": 1.0}
12157
+ r1 = DCA.analyse(one, cfg, {"oil": 50.0, "gas": 0.0})
12158
+ vol12 = sum(m_["oil"] for m_ in r1["forecast"]["months"])
12159
+ cfg2 = {**cfg, "opex_month": 500.0, "years": 20.0}
12160
+ r2 = DCA.analyse(one, cfg2, {"oil": 50.0, "gas": 0.0})
12161
+ last_net = r2["forecast"]["months"][-1]["net_usd"]
12162
+ cfg3 = {**cfg, "discount": 0.10, "years": 1.0}
12163
+ r3 = DCA.analyse(one, cfg3, {"oil": 50.0, "gas": 0.0})
12164
+ pv_hand = sum(m_["net_usd"] * 1.1 ** (-((i + 0.5) / 12)) for i, m_ in enumerate(r3["forecast"]["months"]))
12165
+ c3 = {
12166
+ "exponential": r1["streams"]["oil"]["fit"]["b"] == 0.0 and abs(r1["streams"]["oil"]["fit"]["Di"] / (0.01) - 1) < 0.01 and len(r1["forecast"]["months"]) == 12
12167
+ and abs(r1["forecast"]["undiscounted_usd"] / (50.0 * vol12) - 1) < 1e-3,
12168
+ "limit": r2["forecast"]["limit_month"] is not None and r2["forecast"]["stopped_by"] == "the economic limit" and last_net > 0
12169
+ and 3 <= len(r2["forecast"]["months"]) <= 12 and 50.0 * r2["forecast"]["months"][-1]["oil"] > 500.0,
12170
+ "pv": abs(r3["forecast"]["pv_usd"] - pv_hand) < 2.0 and r3["forecast"]["pv_usd"] < r3["forecast"]["undiscounted_usd"],
12171
+ }
12172
+ failed3 = [k for k, v in c3.items() if not v]
12173
+ ok(not failed3,
12174
+ "CW3 the forecast: a year of exponential decline at 1 % a day fitted as exponential and carried a year forward, its revenue "
12175
+ "the volume times the price; an operating cost stopping it at the economic limit on the last month that still pays; the "
12176
+ "value discounted mid-month as by hand"
12177
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
12178
+
12179
+ # -- CW4 the API, the CLI, the report, the dossier, the tab and the help -------------------------------------------------
12180
+ ev = E.selftest_events(str(d))
12181
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
12182
+ wid = ws.add_well_file(ev["path"], display="PROD-1", actor="tester")["id"]
12183
+ nothing = DCA.page(ws, wid)["detail"]
12184
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
12185
+ svc = Service(wsp, port=0, scheduler=False).start()
12186
+ base_url = svc.url.rstrip("/")
12187
+
12188
+ def opener(name, pw_):
12189
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
12190
+ rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
12191
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
12192
+ return o
12193
+
12194
+ def call(o, method, path, body=None):
12195
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
12196
+ if method == "POST":
12197
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
12198
+ try:
12199
+ with o.open(rq, timeout=120) as rr_:
12200
+ return rr_.status, json.loads(rr_.read())
12201
+ except urllib.error.HTTPError as e:
12202
+ return e.code, json.loads(e.read())
12203
+ content = Path(f["path"]).read_text(encoding="utf-8")
12204
+ try:
12205
+ V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
12206
+ s_va, _ = call(V, "POST", "/api/decline/attach", {"well_id": wid, "filename": "p.csv", "content": content, "source": "x"})
12207
+ s_ns, _ = call(O, "POST", "/api/decline/attach", {"well_id": wid, "filename": "p.csv", "content": content, "source": ""})
12208
+ s_oa, oa = call(O, "POST", "/api/decline/attach", {"well_id": wid, "filename": "p.csv", "content": content, "source": "monthly report (test)"})
12209
+ s_vg, vg = call(V, "GET", "/api/decline?well=" + wid + "&as_of=2026-01-01")
12210
+ s_bt, _ = call(O, "POST", "/api/decline/settings", {"well_id": wid, "nri": 1.5, "source": "x"})
12211
+ s_bp, _ = call(O, "POST", "/api/decline/settings", {"well_id": wid, "price_basis": "spot", "source": "x"})
12212
+ s_ot, ot = call(O, "POST", "/api/decline/settings", {"well_id": wid, "price_basis": "flat", "oil_price": "70", "gas_price": "3", "source": "budget deck (test)"})
12213
+ s_fg, fg = call(V, "GET", "/api/decline?well=" + wid)
12214
+ s_vw, _ = call(V, "POST", "/api/decline/write", {"well_id": wid})
12215
+ s_ow, ow = call(O, "POST", "/api/decline/write", {"well_id": wid})
12216
+ finally:
12217
+ svc.stop()
12218
+ cli_t = _sp.run([sys.executable, "-m", "gea", "decline", "--workspace", wsp, "--well", wid], capture_output=True, text=True, cwd=str(pkg.parent))
12219
+ cli_s = _sp.run([sys.executable, "-m", "gea", "decline", "--workspace", wsp, "--well", wid, "--opex", "9000", "--source", "LOE (test)"], capture_output=True, text=True, cwd=str(pkg.parent))
12220
+ dz = DZ.build(ws, "tester", _ver, None)
12221
+ names = _zf.ZipFile(Path(ws.reports_dir, dz["path"])).namelist()
12222
+ audit = Path(ws.audit_path).read_text(encoding="utf-8")
12223
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
12224
+ c4 = {
12225
+ "api": "production" in nothing and s_va == 403 and s_ns == 400 and s_oa == 200 and oa["months"] == 48 and s_vg == 200 and vg["result"]["as_of"] == "2026-01-01"
12226
+ and "SEC average" in vg["result"]["price_how"]["oil"] and s_bt == 400 and s_bp == 400 and s_ot == 200 and ot["oil_price"] == 70.0
12227
+ and s_fg == 200 and fg["result"]["prices"]["oil"] == 70.0 and s_vw == 403 and s_ow == 200 and ow["report_id"].startswith("DCA-"),
12228
+ "cli": cli_t.returncode == 0 and "EUR" in cli_t.stdout and "70 USD/bbl declared" in cli_t.stdout and cli_s.returncode == 0 and "opex 9,000/month" in cli_s.stdout,
12229
+ "report": PR_read_ok(Path(ws.reports_dir, "wells", wid, "decline_curves.pdf")) and all(x in audit for x in ("production.attach", "production.terms", "report.decline_curves")),
12230
+ "dossier": "declarations/economics/production/production.json" in names and f"declarations/economics/production/{wid}/p.csv" in names and f"results/decline/{wid}.json" in names,
12231
+ "page": all(x in page_src for x in ("VIEWS.decline", "['#/decline', 'Decline curves']", "/api/decline/attach", "dcChart")),
12232
+ "help": not H.check("decline") and H.VIEW_TOPIC.get("decline") == "decline",
12233
+ }
12234
+ failed4 = [k for k, v in c4.items() if not v]
12235
+ ok(not failed4,
12236
+ "CW4 on the API (anyone reads a well's curves at a chosen date, valued at the SEC price; only an operator attaches production "
12237
+ "- with its source - or declares terms - a net revenue interest of 1.5 or an unknown price basis refused - or writes the "
12238
+ "report), the CLI, the report as PDF, a signed dossier carrying the production and the curves, the tab and the help"
12239
+ + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
12240
+
12241
+
11908
12242
  def PR_read_ok(path) -> bool:
11909
12243
  from . import pdf_report as PR
11910
12244
  try:
@@ -12015,6 +12349,8 @@ def main() -> int:
12015
12349
  section_cs_plan_and_tools(tmp)
12016
12350
  section_ct_frac_hits(tmp)
12017
12351
  section_cu_pore_pressure(tmp)
12352
+ section_cv_torque_drag(tmp)
12353
+ section_cw_decline(tmp)
12018
12354
  if _FAILS:
12019
12355
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
12020
12356
  for f in _FAILS: