gea-program 0.49.0__tar.gz → 0.50.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 (353) hide show
  1. {gea_program-0.49.0 → gea_program-0.50.0}/PKG-INFO +16 -1
  2. {gea_program-0.49.0 → gea_program-0.50.0}/README.md +15 -0
  3. {gea_program-0.49.0 → gea_program-0.50.0}/gea/__init__.py +1 -1
  4. {gea_program-0.49.0 → gea_program-0.50.0}/gea/__main__.py +61 -0
  5. {gea_program-0.49.0 → gea_program-0.50.0}/gea/acceptance_tests.py +252 -2
  6. {gea_program-0.49.0 → gea_program-0.50.0}/gea/collision.py +59 -11
  7. {gea_program-0.49.0 → gea_program-0.50.0}/gea/dossier.py +22 -3
  8. gea_program-0.50.0/gea/help/collision.md +19 -0
  9. gea_program-0.50.0/gea/help/plan.md +19 -0
  10. {gea_program-0.49.0 → gea_program-0.50.0}/gea/helplib.py +3 -2
  11. gea_program-0.50.0/gea/reference/iscwsa_rev5_example.json +2015 -0
  12. gea_program-0.50.0/gea/reference/owsg_tool_models.json +4611 -0
  13. {gea_program-0.49.0 → gea_program-0.50.0}/gea/service.py +28 -0
  14. {gea_program-0.49.0 → gea_program-0.50.0}/gea/web/app.html +108 -3
  15. {gea_program-0.49.0 → gea_program-0.50.0}/gea/well3d.py +50 -18
  16. gea_program-0.50.0/gea/wellplan.py +388 -0
  17. {gea_program-0.49.0 → gea_program-0.50.0}/gea/wellpos.py +169 -5
  18. {gea_program-0.49.0 → gea_program-0.50.0}/gea_program.egg-info/PKG-INFO +16 -1
  19. {gea_program-0.49.0 → gea_program-0.50.0}/gea_program.egg-info/SOURCES.txt +4 -0
  20. {gea_program-0.49.0 → gea_program-0.50.0}/pyproject.toml +1 -1
  21. gea_program-0.49.0/gea/help/collision.md +0 -19
  22. {gea_program-0.49.0 → gea_program-0.50.0}/LICENSE +0 -0
  23. {gea_program-0.49.0 → gea_program-0.50.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  24. {gea_program-0.49.0 → gea_program-0.50.0}/gea/accuracy_statement.py +0 -0
  25. {gea_program-0.49.0 → gea_program-0.50.0}/gea/alarm_engine.py +0 -0
  26. {gea_program-0.49.0 → gea_program-0.50.0}/gea/alerts.py +0 -0
  27. {gea_program-0.49.0 → gea_program-0.50.0}/gea/backup.py +0 -0
  28. {gea_program-0.49.0 → gea_program-0.50.0}/gea/bench.py +0 -0
  29. {gea_program-0.49.0 → gea_program-0.50.0}/gea/blind_harness.py +0 -0
  30. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  31. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  32. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  33. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  34. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  35. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  36. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  37. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  38. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  39. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  40. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  41. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  42. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  43. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  44. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  45. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  46. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  47. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  48. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  49. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  50. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  51. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  52. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  53. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  54. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  55. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  56. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  57. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  58. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  59. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  60. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  61. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  62. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  63. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  64. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  65. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  66. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  67. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  68. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  69. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  70. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  71. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  72. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  73. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  74. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  75. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  76. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  77. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  78. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  79. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  80. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  81. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  82. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  83. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  84. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  85. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  86. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  87. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  88. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  89. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  90. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  91. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  92. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  93. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  94. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  95. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  96. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  97. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/med_160_sapropels.txt +0 -0
  98. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  99. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  100. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  101. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  102. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  103. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  104. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  105. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  106. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  107. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  108. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  109. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  110. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  111. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  112. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  113. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  114. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  115. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  116. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  117. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  118. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  119. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  120. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  121. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  122. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  123. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  124. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  125. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  126. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  127. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  128. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  129. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  130. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  131. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  132. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  133. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  134. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog_map.py +0 -0
  135. {gea_program-0.49.0 → gea_program-0.50.0}/gea/catalog_register.py +0 -0
  136. {gea_program-0.49.0 → gea_program-0.50.0}/gea/certificates.py +0 -0
  137. {gea_program-0.49.0 → gea_program-0.50.0}/gea/cli.py +0 -0
  138. {gea_program-0.49.0 → gea_program-0.50.0}/gea/client_reports.py +0 -0
  139. {gea_program-0.49.0 → gea_program-0.50.0}/gea/config_versioning.py +0 -0
  140. {gea_program-0.49.0 → gea_program-0.50.0}/gea/conformance.py +0 -0
  141. {gea_program-0.49.0 → gea_program-0.50.0}/gea/control_room.py +0 -0
  142. {gea_program-0.49.0 → gea_program-0.50.0}/gea/correlation.py +0 -0
  143. {gea_program-0.49.0 → gea_program-0.50.0}/gea/dashboard.py +0 -0
  144. {gea_program-0.49.0 → gea_program-0.50.0}/gea/declarations.py +0 -0
  145. {gea_program-0.49.0 → gea_program-0.50.0}/gea/deviation.py +0 -0
  146. {gea_program-0.49.0 → gea_program-0.50.0}/gea/dlis.py +0 -0
  147. {gea_program-0.49.0 → gea_program-0.50.0}/gea/dnp3.py +0 -0
  148. {gea_program-0.49.0 → gea_program-0.50.0}/gea/doctor.py +0 -0
  149. {gea_program-0.49.0 → gea_program-0.50.0}/gea/dossier_verify.py +0 -0
  150. {gea_program-0.49.0 → gea_program-0.50.0}/gea/downhole_engine.py +0 -0
  151. {gea_program-0.49.0 → gea_program-0.50.0}/gea/drift_monitor.py +0 -0
  152. {gea_program-0.49.0 → gea_program-0.50.0}/gea/drill_string.py +0 -0
  153. {gea_program-0.49.0 → gea_program-0.50.0}/gea/earth_model.py +0 -0
  154. {gea_program-0.49.0 → gea_program-0.50.0}/gea/economics.py +0 -0
  155. {gea_program-0.49.0 → gea_program-0.50.0}/gea/ed25519.py +0 -0
  156. {gea_program-0.49.0 → gea_program-0.50.0}/gea/edr.py +0 -0
  157. {gea_program-0.49.0 → gea_program-0.50.0}/gea/elevation.py +0 -0
  158. {gea_program-0.49.0 → gea_program-0.50.0}/gea/etp.py +0 -0
  159. {gea_program-0.49.0 → gea_program-0.50.0}/gea/etp12_schemas.json +0 -0
  160. {gea_program-0.49.0 → gea_program-0.50.0}/gea/example_register_map.json +0 -0
  161. {gea_program-0.49.0 → gea_program-0.50.0}/gea/fat_sat.py +0 -0
  162. {gea_program-0.49.0 → gea_program-0.50.0}/gea/files.py +0 -0
  163. {gea_program-0.49.0 → gea_program-0.50.0}/gea/fixtures/README.md +0 -0
  164. {gea_program-0.49.0 → gea_program-0.50.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  165. {gea_program-0.49.0 → gea_program-0.50.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  166. {gea_program-0.49.0 → gea_program-0.50.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  167. {gea_program-0.49.0 → gea_program-0.50.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  168. {gea_program-0.49.0 → gea_program-0.50.0}/gea/follower.py +0 -0
  169. {gea_program-0.49.0 → gea_program-0.50.0}/gea/forward_model.py +0 -0
  170. {gea_program-0.49.0 → gea_program-0.50.0}/gea/friction_priors.py +0 -0
  171. {gea_program-0.49.0 → gea_program-0.50.0}/gea/gamma.py +0 -0
  172. {gea_program-0.49.0 → gea_program-0.50.0}/gea/gauge_aging.py +0 -0
  173. {gea_program-0.49.0 → gea_program-0.50.0}/gea/gauge_specs.py +0 -0
  174. {gea_program-0.49.0 → gea_program-0.50.0}/gea/geodesy.py +0 -0
  175. {gea_program-0.49.0 → gea_program-0.50.0}/gea/geomag.py +0 -0
  176. {gea_program-0.49.0 → gea_program-0.50.0}/gea/gravity_reference.py +0 -0
  177. {gea_program-0.49.0 → gea_program-0.50.0}/gea/hartip.py +0 -0
  178. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/TESTER_GUIDE.md +0 -0
  179. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/alarms.md +0 -0
  180. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/alerts.md +0 -0
  181. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/audit-update.md +0 -0
  182. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/backup.md +0 -0
  183. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/catalog.md +0 -0
  184. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/charts.md +0 -0
  185. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/conformance.md +0 -0
  186. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/control-room.md +0 -0
  187. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/data-in.md +0 -0
  188. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/declare.md +0 -0
  189. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/dlis.md +0 -0
  190. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/doctor.md +0 -0
  191. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/dossier.md +0 -0
  192. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/drift.md +0 -0
  193. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/drillstring.md +0 -0
  194. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/economics.md +0 -0
  195. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/edr.md +0 -0
  196. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/etp.md +0 -0
  197. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/field-ports.md +0 -0
  198. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/files.md +0 -0
  199. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/historian.md +0 -0
  200. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/instruments.md +0 -0
  201. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/interval.md +0 -0
  202. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/invoice-check.md +0 -0
  203. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/map.md +0 -0
  204. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/month-end.md +0 -0
  205. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/notifications.md +0 -0
  206. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/opseis.md +0 -0
  207. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/osdu.md +0 -0
  208. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/pad-summary.md +0 -0
  209. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/patches.md +0 -0
  210. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/penetrator.md +0 -0
  211. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/ppdm.md +0 -0
  212. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/quakeml.md +0 -0
  213. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/quality.md +0 -0
  214. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/report-history.md +0 -0
  215. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/rig-time.md +0 -0
  216. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/seedlink.md +0 -0
  217. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/seismic.md +0 -0
  218. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/setup.md +0 -0
  219. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/simulation.md +0 -0
  220. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/site.md +0 -0
  221. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/sites.md +0 -0
  222. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/slip.md +0 -0
  223. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/soak.md +0 -0
  224. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/sra.md +0 -0
  225. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/start.md +0 -0
  226. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/structure.md +0 -0
  227. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/tools.md +0 -0
  228. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/transients.md +0 -0
  229. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/upkeep.md +0 -0
  230. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/vibration.md +0 -0
  231. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/well-report.md +0 -0
  232. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/well-tests.md +0 -0
  233. {gea_program-0.49.0 → gea_program-0.50.0}/gea/help/well3d.md +0 -0
  234. {gea_program-0.49.0 → gea_program-0.50.0}/gea/housekeeping.py +0 -0
  235. {gea_program-0.49.0 → gea_program-0.50.0}/gea/interval.py +0 -0
  236. {gea_program-0.49.0 → gea_program-0.50.0}/gea/inverse_engine.py +0 -0
  237. {gea_program-0.49.0 → gea_program-0.50.0}/gea/invoice_check.py +0 -0
  238. {gea_program-0.49.0 → gea_program-0.50.0}/gea/jobs.py +0 -0
  239. {gea_program-0.49.0 → gea_program-0.50.0}/gea/live_ports.py +0 -0
  240. {gea_program-0.49.0 → gea_program-0.50.0}/gea/loadtest.py +0 -0
  241. {gea_program-0.49.0 → gea_program-0.50.0}/gea/matplotlib_demo.py +0 -0
  242. {gea_program-0.49.0 → gea_program-0.50.0}/gea/modbus.py +0 -0
  243. {gea_program-0.49.0 → gea_program-0.50.0}/gea/modbus_rtu.py +0 -0
  244. {gea_program-0.49.0 → gea_program-0.50.0}/gea/model_card.py +0 -0
  245. {gea_program-0.49.0 → gea_program-0.50.0}/gea/month_end.py +0 -0
  246. {gea_program-0.49.0 → gea_program-0.50.0}/gea/mqtt_port.py +0 -0
  247. {gea_program-0.49.0 → gea_program-0.50.0}/gea/notify.py +0 -0
  248. {gea_program-0.49.0 → gea_program-0.50.0}/gea/op_seismicity.py +0 -0
  249. {gea_program-0.49.0 → gea_program-0.50.0}/gea/opcua_port.py +0 -0
  250. {gea_program-0.49.0 → gea_program-0.50.0}/gea/operator_app.py +0 -0
  251. {gea_program-0.49.0 → gea_program-0.50.0}/gea/osdu.py +0 -0
  252. {gea_program-0.49.0 → gea_program-0.50.0}/gea/pad_summary.py +0 -0
  253. {gea_program-0.49.0 → gea_program-0.50.0}/gea/patches.py +0 -0
  254. {gea_program-0.49.0 → gea_program-0.50.0}/gea/pdf_report.py +0 -0
  255. {gea_program-0.49.0 → gea_program-0.50.0}/gea/penetrator.py +0 -0
  256. {gea_program-0.49.0 → gea_program-0.50.0}/gea/permits.py +0 -0
  257. {gea_program-0.49.0 → gea_program-0.50.0}/gea/piwebapi.py +0 -0
  258. {gea_program-0.49.0 → gea_program-0.50.0}/gea/ports.py +0 -0
  259. {gea_program-0.49.0 → gea_program-0.50.0}/gea/ppdm.py +0 -0
  260. {gea_program-0.49.0 → gea_program-0.50.0}/gea/profile_catalog.py +0 -0
  261. {gea_program-0.49.0 → gea_program-0.50.0}/gea/project.py +0 -0
  262. {gea_program-0.49.0 → gea_program-0.50.0}/gea/qt6_downhole_app.py +0 -0
  263. {gea_program-0.49.0 → gea_program-0.50.0}/gea/quakeml.py +0 -0
  264. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reconciler.py +0 -0
  265. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  266. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  267. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/WMM2025.COF +0 -0
  268. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  269. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/eia_prices.provenance.json +0 -0
  270. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  271. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/iscwsa_rev4_example.json +0 -0
  272. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  273. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  274. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  275. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  276. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/mahoning_example.json +0 -0
  277. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/mud_motor_specs.json +0 -0
  278. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/penetrator_ratings.json +0 -0
  279. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/seismicity_protocols.json +0 -0
  280. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/tool_sources.json +0 -0
  281. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/us_outline.json +0 -0
  282. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/us_outline.provenance.json +0 -0
  283. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_3d.provenance.json +0 -0
  284. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  285. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  286. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  287. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_surfaces.json.gz +0 -0
  288. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  289. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  290. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  291. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  292. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  293. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  294. {gea_program-0.49.0 → gea_program-0.50.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  295. {gea_program-0.49.0 → gea_program-0.50.0}/gea/report_history.py +0 -0
  296. {gea_program-0.49.0 → gea_program-0.50.0}/gea/rig_state.py +0 -0
  297. {gea_program-0.49.0 → gea_program-0.50.0}/gea/rock_inventory.py +0 -0
  298. {gea_program-0.49.0 → gea_program-0.50.0}/gea/sample_record.py +0 -0
  299. {gea_program-0.49.0 → gea_program-0.50.0}/gea/sample_well_profile.csv +0 -0
  300. {gea_program-0.49.0 → gea_program-0.50.0}/gea/sbom.py +0 -0
  301. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seedlink.py +0 -0
  302. {gea_program-0.49.0 → gea_program-0.50.0}/gea/segy.py +0 -0
  303. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic.py +0 -0
  304. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_array.py +0 -0
  305. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_assoc.py +0 -0
  306. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_detect.py +0 -0
  307. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_film.py +0 -0
  308. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_harmonic.py +0 -0
  309. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_qc.py +0 -0
  310. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_response.py +0 -0
  311. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_signature.py +0 -0
  312. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_track.py +0 -0
  313. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_uncertainty.py +0 -0
  314. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_unlisted.py +0 -0
  315. {gea_program-0.49.0 → gea_program-0.50.0}/gea/seismic_watch.py +0 -0
  316. {gea_program-0.49.0 → gea_program-0.50.0}/gea/sensor_swap.py +0 -0
  317. {gea_program-0.49.0 → gea_program-0.50.0}/gea/series_cache.py +0 -0
  318. {gea_program-0.49.0 → gea_program-0.50.0}/gea/service_life.py +0 -0
  319. {gea_program-0.49.0 → gea_program-0.50.0}/gea/setup_checklist.py +0 -0
  320. {gea_program-0.49.0 → gea_program-0.50.0}/gea/shapefile.py +0 -0
  321. {gea_program-0.49.0 → gea_program-0.50.0}/gea/shell.py +0 -0
  322. {gea_program-0.49.0 → gea_program-0.50.0}/gea/shut_in.py +0 -0
  323. {gea_program-0.49.0 → gea_program-0.50.0}/gea/simulation.py +0 -0
  324. {gea_program-0.49.0 → gea_program-0.50.0}/gea/site_map.py +0 -0
  325. {gea_program-0.49.0 → gea_program-0.50.0}/gea/sla_report.py +0 -0
  326. {gea_program-0.49.0 → gea_program-0.50.0}/gea/slip.py +0 -0
  327. {gea_program-0.49.0 → gea_program-0.50.0}/gea/soak.py +0 -0
  328. {gea_program-0.49.0 → gea_program-0.50.0}/gea/sra.py +0 -0
  329. {gea_program-0.49.0 → gea_program-0.50.0}/gea/store_forward.py +0 -0
  330. {gea_program-0.49.0 → gea_program-0.50.0}/gea/strata_join.py +0 -0
  331. {gea_program-0.49.0 → gea_program-0.50.0}/gea/structure.py +0 -0
  332. {gea_program-0.49.0 → gea_program-0.50.0}/gea/supervisor.py +0 -0
  333. {gea_program-0.49.0 → gea_program-0.50.0}/gea/survey_cmd.py +0 -0
  334. {gea_program-0.49.0 → gea_program-0.50.0}/gea/survey_view.py +0 -0
  335. {gea_program-0.49.0 → gea_program-0.50.0}/gea/telemetry.py +0 -0
  336. {gea_program-0.49.0 → gea_program-0.50.0}/gea/tool_catalog.py +0 -0
  337. {gea_program-0.49.0 → gea_program-0.50.0}/gea/tool_library.py +0 -0
  338. {gea_program-0.49.0 → gea_program-0.50.0}/gea/tool_taxonomy.py +0 -0
  339. {gea_program-0.49.0 → gea_program-0.50.0}/gea/transient.py +0 -0
  340. {gea_program-0.49.0 → gea_program-0.50.0}/gea/vibration.py +0 -0
  341. {gea_program-0.49.0 → gea_program-0.50.0}/gea/well_assembler.py +0 -0
  342. {gea_program-0.49.0 → gea_program-0.50.0}/gea/well_report.py +0 -0
  343. {gea_program-0.49.0 → gea_program-0.50.0}/gea/well_test_validation.py +0 -0
  344. {gea_program-0.49.0 → gea_program-0.50.0}/gea/wits0.py +0 -0
  345. {gea_program-0.49.0 → gea_program-0.50.0}/gea/witsml.py +0 -0
  346. {gea_program-0.49.0 → gea_program-0.50.0}/gea/workspace.py +0 -0
  347. {gea_program-0.49.0 → gea_program-0.50.0}/gea/xlsx_read.py +0 -0
  348. {gea_program-0.49.0 → gea_program-0.50.0}/gea_program.egg-info/dependency_links.txt +0 -0
  349. {gea_program-0.49.0 → gea_program-0.50.0}/gea_program.egg-info/entry_points.txt +0 -0
  350. {gea_program-0.49.0 → gea_program-0.50.0}/gea_program.egg-info/requires.txt +0 -0
  351. {gea_program-0.49.0 → gea_program-0.50.0}/gea_program.egg-info/top_level.txt +0 -0
  352. {gea_program-0.49.0 → gea_program-0.50.0}/setup.cfg +0 -0
  353. {gea_program-0.49.0 → gea_program-0.50.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.49.0
3
+ Version: 0.50.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,21 @@ 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
+ ### Plan vs actual and the survey tools
652
+
653
+ Each well against its directional plan: at every survey station, how far
654
+ it is from the plan - high or low, right or left, in the plan's own frame -
655
+ against the survey's own uncertainty in that direction; the bit projected
656
+ below the last survey; the one arc back to the plan, with its dogleg
657
+ severity and toolface. Each well's survey program names the tool that
658
+ surveyed each run, weighed by the ISCWSA MWD Rev 5.11 model (checked
659
+ against all 144 rows of the ISCWSA's own Rev 5 example) or one of 21 OWSG
660
+ tool-code models on its weighting functions - MWD and EMS with the standard,
661
+ high- or low-resolution geomagnetic model or in-field referencing. Wells not
662
+ yet drilled are checked for collision along their plans, a well being
663
+ drilled along its survey and then its plan, and the Collision tab draws the
664
+ travelling cylinder. The **Plan vs actual** tab, `gea plan`, `gea help plan`.
665
+
651
666
  ### Well collision checks
652
667
 
653
668
  Every well scanned against every other: at each point of one well, the
@@ -609,6 +609,21 @@ 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
+ ### Plan vs actual and the survey tools
613
+
614
+ Each well against its directional plan: at every survey station, how far
615
+ it is from the plan - high or low, right or left, in the plan's own frame -
616
+ against the survey's own uncertainty in that direction; the bit projected
617
+ below the last survey; the one arc back to the plan, with its dogleg
618
+ severity and toolface. Each well's survey program names the tool that
619
+ surveyed each run, weighed by the ISCWSA MWD Rev 5.11 model (checked
620
+ against all 144 rows of the ISCWSA's own Rev 5 example) or one of 21 OWSG
621
+ tool-code models on its weighting functions - MWD and EMS with the standard,
622
+ high- or low-resolution geomagnetic model or in-field referencing. Wells not
623
+ yet drilled are checked for collision along their plans, a well being
624
+ drilled along its survey and then its plan, and the Collision tab draws the
625
+ travelling cylinder. The **Plan vs actual** tab, `gea plan`, `gea help plan`.
626
+
612
627
  ### Well collision checks
613
628
 
614
629
  Every well scanned against every other: at each point of one well, the
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.49.0"
126
+ __version__ = "0.50.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -604,6 +604,24 @@ def main(argv=None) -> int:
604
604
  p_co.add_argument("--actor", type=str, default="cli")
605
605
  p_co.add_argument("--json", action="store_true")
606
606
 
607
+ p_pl = sub.add_parser("plan", help="a well against its plan: the distance high/low and right/left at each survey, the bit projected, the arc back to the plan; the survey program's tool models")
608
+ p_pl.add_argument("--workspace", type=str, default=None)
609
+ p_pl.add_argument("--well", type=str, default=None)
610
+ p_pl.add_argument("--attach", type=str, default=None, help="attach FILE as the well's plan (CSV, Excel or WITSML)")
611
+ p_pl.add_argument("--azimuth-reference", type=str, default=None, help="with --attach: true, grid or magnetic")
612
+ p_pl.add_argument("--md-unit", type=str, default="ft", help="with --attach: ft or m")
613
+ p_pl.add_argument("--grid", type=str, default=None, help="with a grid azimuth reference: the grid (e.g. UTM14N)")
614
+ p_pl.add_argument("--declination", type=str, default=None, help="with a magnetic azimuth reference: the declination used")
615
+ p_pl.add_argument("--remove", action="store_true", help="take the well's plan off it")
616
+ p_pl.add_argument("--program", type=str, default=None, help="the survey program: 'MWD+SRGM' or '0-1500:MWD+SRGM, 1500-:MWD+IFR1' (metres)")
617
+ p_pl.add_argument("--bit-offset", type=str, default=None, help="survey sensor to bit, m")
618
+ p_pl.add_argument("--lookahead", type=str, default=None, help="how far ahead the arc back rejoins the plan, m")
619
+ p_pl.add_argument("--source", type=str, default="", help="where the plan or the program came from")
620
+ p_pl.add_argument("--models", action="store_true", help="list the tool models")
621
+ p_pl.add_argument("--write", action="store_true", help="write the well's report")
622
+ p_pl.add_argument("--actor", type=str, default="cli")
623
+ p_pl.add_argument("--json", action="store_true")
624
+
607
625
  p_rt = sub.add_parser("rig-time", help="what the rig did each second of the drilling record - drilling, connections, trips, the slips - and the time lost against the pad's own best")
608
626
  p_rt.add_argument("--workspace", type=str, required=True)
609
627
  p_rt.add_argument("--well", type=str, default=None, help="with --write: one well's report (without it, the pad's)")
@@ -2422,6 +2440,49 @@ def main(argv=None) -> int:
2422
2440
  raise SystemExit(f"collision: {e.args[0] if e.args else e}")
2423
2441
  print(_json.dumps({k: v for k, v in r.items()}, default=str) if a.json else _CO.text(r))
2424
2442
  return 0
2443
+ elif a.cmd == "plan":
2444
+ import json as _json
2445
+ from . import wellplan as _PL
2446
+ from . import wellpos as _WP
2447
+ from .cli import _safe_stdout
2448
+ from .workspace import Workspace, WorkspaceError
2449
+ _safe_stdout()
2450
+ try:
2451
+ if a.models:
2452
+ for m_ in _WP.model_list():
2453
+ print(f"{m_['name']:<18} {m_['id']:<14} {m_['terms']:>2} terms {m_['family']:<10} {m_['long_name']}" + (" (default)" if m_['default'] else ""))
2454
+ return 0
2455
+ if not a.workspace:
2456
+ raise ValueError("--workspace is needed")
2457
+ ws = Workspace(a.workspace)
2458
+ if (a.attach or a.remove or a.program or a.bit_offset is not None or a.lookahead is not None or a.write) and not a.well:
2459
+ raise ValueError("--well is needed")
2460
+ if a.attach:
2461
+ with open(a.attach, "rb") as f_:
2462
+ r_ = _PL.attach(ws, a.well, os.path.basename(a.attach), f_.read(), a.actor, a.azimuth_reference or "", a.md_unit, a.grid,
2463
+ float(a.declination) if a.declination else None, a.source)
2464
+ print(f"plan attached to {a.well}: {r_['stations']} stations, {r_['md_range_m'][0]:,.1f} to {r_['md_range_m'][1]:,.1f} m MD ({r_['azimuth_reference']} north)")
2465
+ return 0
2466
+ if a.remove:
2467
+ _PL.remove(ws, a.well, a.actor)
2468
+ print(f"plan removed from {a.well}")
2469
+ return 0
2470
+ if a.program or a.bit_offset is not None or a.lookahead is not None:
2471
+ s_ = _PL.set_well(ws, a.actor, a.well, _PL.parse_program(a.program) if a.program else None, a.bit_offset, a.lookahead, a.source)
2472
+ print(f"{a.well}: " + ", ".join(f"{p_['model']} to {p_['to_md_m']:,.0f} m" if p_['to_md_m'] is not None else f"{p_['model']} to TD" for p_ in s_['program'])
2473
+ + f"; the bit {s_['bit_offset_m']:g} m below the survey sensor; back to the plan {s_['lookahead_m']:g} m ahead")
2474
+ return 0
2475
+ if a.write:
2476
+ r_ = _PL.write_report(ws, __version__, a.well, a.actor)
2477
+ print(f"wrote {r_['report_id']}: {r_['paths']['html']}")
2478
+ if r_['paths'].get('pdf'):
2479
+ print(f" printable: {r_['paths']['pdf']}")
2480
+ return 0
2481
+ r = _PL.page(ws, a.well)
2482
+ except (KeyError, ValueError, OSError, WorkspaceError) as e:
2483
+ raise SystemExit(f"plan: {e.args[0] if e.args else e}")
2484
+ print(_json.dumps(r, default=str) if a.json else _PL.text(r))
2485
+ return 0
2425
2486
  elif a.cmd == "rig-time":
2426
2487
  import json as _json
2427
2488
  from . import rig_state as _RS
@@ -11165,7 +11165,8 @@ def section_cr_collision(tmp: str) -> None:
11165
11165
  fld = {"declination_deg": 6.0, "dip_deg": 60.0, "total_nT": 48000.0}
11166
11166
  md = [0.0, 500.0, 1000.0, 2000.0]
11167
11167
  mk = lambda i, x, inc=(0, 0, 0, 0), az=(0, 0, 0, 0), y=0.0: {"id": i, "display": i, "md": md, "inc": list(inc), "az": list(az), "x0": x, "y0": y,
11168
- "ref_elev_m": 0.0, "field": fld, "hole_in": 8.5}
11168
+ "ref_elev_m": 0.0, "field": fld, "hole_in": 8.5,
11169
+ "program": [{"to_md_m": None, "model": WP.REV4}]}
11169
11170
  rule = {**CO.DEFAULTS}
11170
11171
  A, B = CO.prepare(mk("A", 0.0)), CO.prepare(mk("B", 10.0))
11171
11172
  sAB, sBA = CO.scan(A, B, rule), CO.scan(B, A, rule)
@@ -11207,7 +11208,7 @@ def section_cr_collision(tmp: str) -> None:
11207
11208
  c3 = {
11208
11209
  "wells": len(v["wells"]) == 3 and len(v["pairs"]) == 6 and abs(wl["15/9-F-10"]["td_md_m"] - 5300.1) < 0.1
11209
11210
  and all(abs(x["field"]["declination_deg"] - 1.12) < 0.05 for x in v["wells"]) and all("grid convergence" in x["azimuth_note"] for x in v["wells"]),
11210
- "worst": w_["level"] == "BELOW 1.0" and abs(w_["min_sf"] - 0.29) < 0.01 and {w_["reference_name"], w_["offset_name"]} == {"15/9-F-5", "15/9-F-10"}
11211
+ "worst": w_["level"] == "BELOW 1.0" and abs(w_["min_sf"] - 0.062) < 0.01 and {w_["reference_name"], w_["offset_name"]} == {"15/9-F-5", "15/9-F-10"}
11211
11212
  and abs(w_["at_md_m"] - 250) < 15 and abs(w_["distance_m"] - 1.20) < 0.05,
11212
11213
  "order": [p["min_sf"] for p in v["pairs"]] == sorted(p["min_sf"] for p in v["pairs"]) and all(len(p["profile"]["md_m"]) == len(p["profile"]["sf"]) for p in v["pairs"]),
11213
11214
  "ellipses": wl["15/9-F-10"]["td_ellipse_1sigma"]["major_m"] > wl["15/9-F-5"]["td_ellipse_1sigma"]["major_m"] > wl["NO 15/9-F-4"]["td_ellipse_1sigma"]["major_m"] > 1.0,
@@ -11296,6 +11297,254 @@ def section_cr_collision(tmp: str) -> None:
11296
11297
  + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
11297
11298
 
11298
11299
 
11300
+ def section_cs_plan_and_tools(tmp: str) -> None:
11301
+ """Section CS - plan against actual, the survey tools and the travelling cylinder: the ISCWSA MWD Rev 5.11 model
11302
+ checked against all 144 rows of the ISCWSA's own Rev 5 example, with the OWSG tool-code models on its weighting
11303
+ functions; survey programs weighing each run by its tool; a well against its plan - high or low, right or left,
11304
+ against the survey's own uncertainty - the bit projected and the arc back, each checked by construction; planned
11305
+ wells in the collision check and the travelling cylinder's directions; the API, the CLI, the report, the dossier,
11306
+ the tabs and the help."""
11307
+ import base64 as _b64
11308
+ import http.cookiejar
11309
+ import math as _m
11310
+ import subprocess as _sp
11311
+ import urllib.request
11312
+ import urllib.error
11313
+ import zipfile as _zf
11314
+ import numpy as _np
11315
+ from . import collision as CO
11316
+ from . import dossier as DZ
11317
+ from . import edr as E
11318
+ from . import helplib as H
11319
+ from . import well3d as W
11320
+ from . import wellplan as WPL
11321
+ from . import wellpos as WP
11322
+ from . import xlsx_read as XR
11323
+ from .workspace import Workspace
11324
+ from .service import Service, Users
11325
+ pkg = Path(__file__).parent
11326
+ d = Path(tmp, "planvs"); d.mkdir()
11327
+
11328
+ # -- CS1 the Rev 5.11 model against the ISCWSA's example; the OWSG models ---------------------------------------------
11329
+ ex5 = WP.check_example_rev5()
11330
+ ex4 = WP.check_example()
11331
+ t8 = ex5["totals"]["8000"]
11332
+ ms = WP.models()
11333
+ ml = WP.model_list()
11334
+ sv = json.loads((pkg / "reference" / "iscwsa_rev5_example.json").read_text(encoding="utf-8"))["survey"]
11335
+ md_, inc_, az_ = (_np.array(sv[k], float) for k in ("md", "inc", "azi"))
11336
+ tvd_ = WP.min_curve_tvd(md_, inc_, az_)
11337
+ at = lambda name: WP.program_uncertainty(md_, inc_, az_, tvd_, -4.0, 72.0, 50000.0, [{"to_md_m": None, "model": name}])["cov"][-1]
11338
+ c_s, c_i, c_h = at("MWD+SRGM"), at("MWD+IFR1"), at("MWD+HRGM")
11339
+ owsg = json.loads((pkg / "reference" / "owsg_tool_models.json").read_text(encoding="utf-8"))
11340
+ c1 = {
11341
+ "example": ex5["rows"] == 144 and ex5["sources"] == 35 and not ex5["disagree"] and ex5["worst_relative"] < 5e-5 and ex5["depths_m"] == [1200, 2100, 5100, 8000],
11342
+ "totals": abs(t8[0] - 8518.67) < 0.01 and abs(t8[1] - 711.20) < 0.01 and abs(t8[2] - 513.29) < 0.01 and abs(t8[3] + 2244.33) < 0.01,
11343
+ "rev4_kept": ex4["rows"] == 112 and not ex4["disagree"],
11344
+ "models": len(ms) == 22 and WP.DEFAULT_MODEL == "MWD+SRGM" and sum(1 for m in ml if m["default"]) == 1 and WP.REV4 in ms
11345
+ and all(len(m["terms"]) >= 27 for m in ms.values()) and {"MWD+IFR1", "MWD+IFR2+SAG", "MWD+HRGM", "EMS+SRGM", "MWD+LRGM+SAG"} <= set(ms),
11346
+ "magnitudes": ms["MWD+SRGM"]["terms"]["DEC-OS"][1] == 0.24 * _m.pi / 180 and ms["MWD+SRGM"]["terms"]["DEC-OS"][2] == "global"
11347
+ and ms["MWD+IFR1"]["terms"]["DEC-CI"][2] == "global" and ms["MWD+SRGM"]["terms"]["DEC-U"][2] == "well",
11348
+ "grades": c_i[0, 0] + c_i[1, 1] < c_h[0, 0] + c_h[1, 1] < c_s[0, 0] + c_s[1, 1],
11349
+ "attribution": "ISCWSA" in ex5["source"] and "Apache" in ex5["license"] and "OWSG" in owsg["source"] and "Apache" in owsg["license"] and len(owsg["models"]) == 21,
11350
+ }
11351
+ failed1 = [k for k, v in c1.items() if not v]
11352
+ ok(not failed1,
11353
+ "CS1 the ISCWSA MWD Rev 5.11 model - the surface tie-on, the course-length terms, the enhanced sag and misalignments, the "
11354
+ "declination split well-wide and field-wide - recomputing all 144 rows of the ISCWSA's own Rev 5 example (35 sources and the "
11355
+ "total at four depths) to five significant figures; the Rev 4 example still whole; 21 OWSG tool-code models on the same "
11356
+ "weighting functions with their magnitudes and propagation as published, in-field referencing tighter than the high-resolution "
11357
+ "model and that tighter than the standard; the source and licence of both carried"
11358
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
11359
+
11360
+ # -- CS2 survey programs ------------------------------------------------------------------------------------------------
11361
+ one = WP.program_terms(md_, inc_, az_, tvd_, -4.0, 72.0, 50000.0, [{"to_md_m": None, "model": "MWD+SRGM"}])
11362
+ two = WP.program_terms(md_, inc_, az_, tvd_, -4.0, 72.0, 50000.0, [{"to_md_m": 2100.0, "model": "MWD+SRGM"}, {"to_md_m": None, "model": "MWD+SRGM"}])
11363
+ mix = WP.program_uncertainty(md_, inc_, az_, tvd_, -4.0, 72.0, 50000.0, [{"to_md_m": 2100.0, "model": "MWD+SRGM"}, {"to_md_m": None, "model": "MWD+IFR1"}])
11364
+ p1, P1, _ = WP.covariances(one)
11365
+ p2, P2, _ = WP.covariances(two)
11366
+ k21 = int(_np.flatnonzero(md_ == 2100)[0])
11367
+ refused = 0
11368
+ for bad in ([{"to_md_m": None, "model": "NOPE"}], [{"to_md_m": 900, "model": "MWD+SRGM"}, {"to_md_m": 800, "model": "MWD+SRGM"}],
11369
+ [{"to_md_m": None, "model": "MWD+SRGM"}, {"to_md_m": None, "model": "MWD+IFR1"}], []):
11370
+ try:
11371
+ WPL.check_program([dict(x) for x in bad])
11372
+ except ValueError:
11373
+ refused += 1
11374
+ c2 = {
11375
+ "same_above": _np.allclose(P1[: k21 + 1], P2[: k21 + 1], rtol=1e-12, atol=1e-12),
11376
+ "keys": "DEC-OS" in two and "DEC-U" in two and "ABZ@1" in two and "ABZ@2" in two and "ABZ" not in two and two["DEC-OS"]["shared"] and not two["DEC-U"]["shared"],
11377
+ "runs_independent": not _np.allclose(P1[-1], P2[-1]),
11378
+ "ifr_below": mix["cov"][-1][0, 0] + mix["cov"][-1][1, 1] < P1[-1][0, 0] + P1[-1][1, 1] and mix["model"] == "MWD+SRGM then MWD+IFR1",
11379
+ "parse": WPL.parse_program("0-1500:MWD+SRGM, 1500-:MWD+IFR1") == [{"to_md_m": 1500.0, "model": "MWD+SRGM"}, {"to_md_m": None, "model": "MWD+IFR1"}]
11380
+ and WPL.parse_program("MWD+HRGM") == [{"to_md_m": None, "model": "MWD+HRGM"}],
11381
+ "refused": refused == 4,
11382
+ }
11383
+ failed2 = [k for k, v in c2.items() if not v]
11384
+ ok(not failed2,
11385
+ "CS2 survey programs: each station weighed by the run that surveyed it - above the change, two runs of one tool read as one; "
11386
+ "below it a run's own errors start again while the well's and the field's run through; in-field referencing below the "
11387
+ "change tightens the well at TD; a program with an unknown tool, depths out of order or an open run before the last refused"
11388
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
11389
+
11390
+ # -- CS3 a well against its plan, by construction -----------------------------------------------------------------------
11391
+ fld = {"declination_deg": 6.0, "dip_deg": 60.0, "total_nT": 48000.0}
11392
+ prog = [{"to_md_m": None, "model": "MWD+SRGM"}]
11393
+ mds = [float(x) for x in range(0, 1501, 30)]
11394
+ inc30 = [0.0 if m_ < 300 else min(30.0, (m_ - 300) * 0.1) for m_ in mds] # builds 3 deg/30 m from 300 m, holds 30 from 600 m
11395
+ az90 = [90.0] * len(mds)
11396
+ plan = {"md": mds, "incl": inc30, "azi": az90, "slot": [0.0, 0.0], "ref_elev_m": 0.0}
11397
+ act = {"md": mds[:41], "incl": inc30[:41], "azi": az90[:41], "slot": [0.0, 0.0], "ref_elev_m": 0.0} # drilled to 1,200 m of a 1,500 m plan
11398
+ left = WPL.compare({**act, "slot": [0.0, 5.0]}, plan, fld, prog, 15.0, 30.0) # the well 5 m north of a plan heading east
11399
+ up = WPL.compare({**act, "ref_elev_m": 3.0}, plan, fld, prog, 15.0, 30.0) # the well 3 m above the plan
11400
+ same = WPL.compare(act, plan, fld, prog, 15.0, 30.0)
11401
+ done = WPL.compare(plan, plan, fld, prog, 15.0, 30.0)
11402
+ bld = WPL.compare({"md": mds[:15], "incl": inc30[:15], "azi": az90[:15], "slot": [0.0, 0.0], "ref_elev_m": 0.0}, plan, fld, prog, 15.0, 30.0)
11403
+ la, lu = left["last"], up["last"]
11404
+ phi = _m.atan2(5.0, 30.0)
11405
+ dls_hand = _m.degrees(2 * phi) / (_m.hypot(30.0, 5.0) * phi / _m.sin(phi)) * 30.0
11406
+ c3 = {
11407
+ "left": abs(la["right_m"] + 5.0) < 0.01 and abs(la["high_m"]) < 0.01 and abs(la["distance_m"] - 5.0) < 0.01 and abs(la["d_inc_deg"]) < 0.01,
11408
+ "high": abs(lu["high_m"] - 1.5) < 0.01 and abs(lu["right_m"]) < 0.01,
11409
+ "sigma": la["sigma_right_m"] > 0 and abs(la["n_sigma"] - 5.0 / la["sigma_right_m"]) < 0.02 and same["worst"]["distance_m"] < 0.01,
11410
+ "bit": abs(bld["bit"]["inc_deg"] - (inc30[14] + 1.5)) < 1e-6 and abs(bld["bit"]["build_deg_30m"] - 3.0) < 1e-6 and bld["bit"]["md_m"] == mds[14] + 15.0
11411
+ and bld["bit"]["distance_m"] < 0.05,
11412
+ "steer": abs(left["steer"]["toolface_deg"] - 90.0) < 0.5 and abs(left["steer"]["dls_deg_30m"] - dls_hand) < 0.05 and same["steer"]["dls_deg_30m"] < 0.05
11413
+ and done["steer"]["dls_deg_30m"] is None and "past the plan" in done["steer"]["note"] and done["bit"]["past_plan_td"],
11414
+ "views": len(left["views"]["actual"]["n"]) == 41 and abs(left["views"]["actual"]["n"][0] - 5.0) < 1e-6,
11415
+ }
11416
+ failed3 = [k for k, v in c3.items() if not v]
11417
+ ok(not failed3,
11418
+ "CS3 a well against its plan, each figure by construction (drilled to 1,200 m of a 1,500 m plan): a well 5 m north of a plan heading east is 5 m left; 3 m above a "
11419
+ "30-degree hold is 1.5 m high; each set against the survey's standard deviation in its direction; the bit projected 15 m "
11420
+ "holding a 3 deg/30 m build lands on the plan that builds the same; the arc back from 5 m left to the plan 30 m ahead turns "
11421
+ "right (toolface 90) at the severity computed by hand, a well on its plan needs none, and a well at the plan's TD has no plan ahead to steer to"
11422
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
11423
+
11424
+ # -- CS4 planned wells in the collision check; the travelling cylinder --------------------------------------------------
11425
+ mk = lambda i, x, inc, az, prog_=None: {"id": i, "display": i, "md": [0.0, 500.0, 1000.0, 2000.0], "inc": list(inc), "az": list(az), "x0": x, "y0": 0.0,
11426
+ "ref_elev_m": 0.0, "field": fld, "hole_in": 8.5, **({"program": prog_} if prog_ else {})}
11427
+ R1, O1 = CO.prepare(mk("R", 0.0, (0, 0, 0, 0), (0, 0, 0, 0))), CO.prepare(mk("O", 10.0, (0, 0, 0, 0), (0, 0, 0, 0)))
11428
+ R2, O2 = CO.prepare(mk("R", 0.0, (45, 45, 45, 45), (0, 0, 0, 0))), CO.prepare(mk("O", 10.0, (45, 45, 45, 45), (0, 0, 0, 0), [{"to_md_m": None, "model": WP.REV4}]))
11429
+ s1, s2 = CO.scan(R1, O1, CO.DEFAULTS), CO.scan(R2, O2, CO.DEFAULTS)
11430
+ ev = E.selftest_events(str(d))
11431
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
11432
+ ids = []
11433
+ for i, n in enumerate(("Well A", "Well B", "Well C")):
11434
+ w = ws.add_well_file(ev["path"], display=n, actor="tester"); ids.append(w["id"])
11435
+ ws.set_well(w["id"], surface={"lat": 31.42, "lon": -104.10 + 0.00008 * i, "datum": "NAD83", "elevation": {"ft": 3000.0, "datum": "KB", "source": "test plat"}}, actor="tester")
11436
+ tab = lambda pts: "MD (ft),Inc (deg),Azi (deg)\n" + "\n".join(f"{a},{b},{c}" for a, b, c in pts) + "\n"
11437
+ sur_a = [(m_, 0.0 if m_ < 1500 else min(40.0, (m_ - 1500) * 0.02), 95.0) for m_ in range(0, 4001, 100)]
11438
+ plan_a = [(m_, 0.0 if m_ < 1500 else min(45.0, (m_ - 1500) * 0.02), 95.0) for m_ in range(0, 7001, 250)]
11439
+ W.attach_trajectory(ws, ids[0], "a.csv", tab(sur_a).encode(), "tester", "true", "ft", None, None, None, "MWD report (test)")
11440
+ W.attach_trajectory(ws, ids[1], "b.csv", tab([(m_, 0.0 if m_ < 1200 else min(50.0, (m_ - 1200) * 0.025), 265.0) for m_ in range(0, 5001, 100)]).encode(),
11441
+ "tester", "true", "ft", None, None, None, "MWD report (test)")
11442
+ XR.write_simple(str(d / "planC.xlsx"), [["MD (ft)", "Inc (deg)", "Azi (deg)"]] + [[m_, 0.0 if m_ < 800 else min(40.0, (m_ - 800) * 0.02), 5.0] for m_ in range(0, 6001, 500)])
11443
+ WPL.attach(ws, ids[0], "planA.csv", tab(plan_a).encode(), "tester", "true", "ft", None, None, "well plan rev 1 (test)")
11444
+ WPL.attach(ws, ids[2], "planC.xlsx", (d / "planC.xlsx").read_bytes(), "tester", "true", "ft", None, None, "well plan rev 0 (test)")
11445
+ pg = CO.page(ws)
11446
+ wl = {w_["id"]: w_ for w_ in pg["wells"]}
11447
+ sc3 = W.workspace_scene(ws)
11448
+ c4 = {
11449
+ "vertical_north": all(a_ == 90.0 for a_ in s1["profile"]["tc_deg"]) and all(s1["profile"]["tc_north"]),
11450
+ "high_side": all(abs(a_ - 90.0) < 0.5 for a_, nn in zip(s2["profile"]["tc_deg"], s2["profile"]["tc_north"]) if not nn) and not s2["profile"]["tc_north"][-1]
11451
+ and s2["min_sf"] > 0 and abs(s2["distance_m"] - 10.0) < 1e-6,
11452
+ "planned": len(pg["wells"]) == 3 and wl[ids[2]]["basis"] == "plan" and wl[ids[2]]["display"].endswith("(plan)") and "30 m" in wl[ids[2]]["basis_note"]
11453
+ and wl[ids[0]]["basis"] == "survey + plan" and abs(wl[ids[0]]["td_md_m"] - 7000 * 0.3048) < 0.5 and len(pg["pairs"]) == 6 and not pg["left_out"],
11454
+ "scene": [p_["id"] for p_ in sc3["planned"]] == [ids[0], ids[2]] and [w_["id"] for w_ in sc3["wells"]] == ids[:2]
11455
+ and any(x["id"] == ids[2] and "a plan" in x["why"] for x in sc3["not_placed"]),
11456
+ "models": all(w_["model"] == "MWD+SRGM" for w_ in pg["wells"]),
11457
+ }
11458
+ failed4 = [k for k, v in c4.items() if not v]
11459
+ ok(not failed4,
11460
+ "CS4 the travelling cylinder's directions - a neighbour due east of a vertical well at 90 from north, of a well heading north "
11461
+ "at 45 degrees at 90 from its high side (with one well on the Rev 4 model and one on Rev 5.11, their shared errors those both "
11462
+ "carry); a well not yet drilled checked along its plan (an Excel plan), weighed as surveyed every 30 m; a well being drilled "
11463
+ "along its survey and then its plan to TD; the 3D scene carrying the plans"
11464
+ + (f" [failed: {', '.join(failed4)}]" if failed4 else ""))
11465
+
11466
+ # -- CS5 the API, the CLI, the report, the dossier, the tabs and the help ----------------------------------------------
11467
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
11468
+ svc = Service(wsp, port=0, scheduler=False).start()
11469
+ base_url = svc.url.rstrip("/")
11470
+
11471
+ def opener(name, pw_):
11472
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
11473
+ rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
11474
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
11475
+ return o
11476
+
11477
+ def call(o, method, path, body=None):
11478
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
11479
+ if method == "POST":
11480
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
11481
+ try:
11482
+ with o.open(rq, timeout=120) as rr_:
11483
+ return rr_.status, json.loads(rr_.read())
11484
+ except urllib.error.HTTPError as e:
11485
+ return e.code, json.loads(e.read())
11486
+ plan_b = _b64.b64encode(tab([(m_, 0.0 if m_ < 1200 else min(55.0, (m_ - 1200) * 0.025), 262.0) for m_ in range(0, 6001, 300)]).encode()).decode()
11487
+ try:
11488
+ V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
11489
+ s_vp, vp = call(V, "GET", "/api/plan?well=" + ids[0])
11490
+ s_tm, tm = call(V, "GET", "/api/tool-models")
11491
+ s_va, _ = call(V, "POST", "/api/plan/attach", {"well_id": ids[1], "filename": "b.csv", "content_b64": plan_b, "azimuth_reference": "true", "source": "x"})
11492
+ s_ns, _ = call(O, "POST", "/api/plan/attach", {"well_id": ids[1], "filename": "b.csv", "content_b64": plan_b, "azimuth_reference": "true", "source": ""})
11493
+ s_nr, _ = call(O, "POST", "/api/plan/attach", {"well_id": ids[1], "filename": "b.csv", "content_b64": plan_b, "azimuth_reference": "", "source": "plan"})
11494
+ s_oa, oa = call(O, "POST", "/api/plan/attach", {"well_id": ids[1], "filename": "b.csv", "content_b64": plan_b, "azimuth_reference": "true", "md_unit": "ft", "source": "plan rev 1 (test)"})
11495
+ s_bp, _ = call(O, "POST", "/api/plan/settings", {"well_id": ids[1], "program": "0-500:NOPE", "source": "p"})
11496
+ s_op, op_ = call(O, "POST", "/api/plan/settings", {"well_id": ids[1], "program": "0-500:MWD+SRGM, 500-:MWD+IFR1", "bit_offset_m": "18", "source": "survey program rev 1 (test)"})
11497
+ s_bo, _ = call(O, "POST", "/api/plan/settings", {"well_id": ids[1], "bit_offset_m": 500})
11498
+ s_pb, pb = call(V, "GET", "/api/plan?well=" + ids[1])
11499
+ s_vw, _ = call(V, "POST", "/api/plan/write", {"well_id": ids[1]})
11500
+ s_ow, ow = call(O, "POST", "/api/plan/write", {"well_id": ids[1]})
11501
+ s_rm, _ = call(O, "POST", "/api/plan/remove", {"well_id": ids[1]})
11502
+ s_r2, _ = call(O, "POST", "/api/plan/remove", {"well_id": ids[1]})
11503
+ s_co, co = call(V, "GET", "/api/collision")
11504
+ finally:
11505
+ svc.stop()
11506
+ cli_m = _sp.run([sys.executable, "-m", "gea", "plan", "--models"], capture_output=True, text=True, cwd=str(pkg.parent))
11507
+ cli_p = _sp.run([sys.executable, "-m", "gea", "plan", "--workspace", wsp, "--well", ids[0], "--program", "MWD+HRGM", "--source", "program (test)"], capture_output=True, text=True, cwd=str(pkg.parent))
11508
+ cli_t = _sp.run([sys.executable, "-m", "gea", "plan", "--workspace", wsp, "--well", ids[0]], capture_output=True, text=True, cwd=str(pkg.parent))
11509
+ cli_w = _sp.run([sys.executable, "-m", "gea", "plan", "--workspace", wsp, "--well", ids[0], "--write"], capture_output=True, text=True, cwd=str(pkg.parent))
11510
+ from . import __version__ as _ver
11511
+ dz = DZ.build(ws, "tester", _ver, None)
11512
+ names = _zf.ZipFile(Path(ws.reports_dir, dz["path"])).namelist()
11513
+ audit = Path(ws.audit_path).read_text(encoding="utf-8")
11514
+ pdf = Path(ws.reports_dir, "wells", ids[0], "plan_vs_actual.pdf")
11515
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
11516
+ c5 = {
11517
+ "api": s_vp == 200 and vp["comparison"] is not None and s_tm == 200 and len(tm["models"]) == 22 and tm["default"] == "MWD+SRGM"
11518
+ and s_va == 403 and s_ns == 400 and s_nr == 400 and s_oa == 200 and oa["stations"] == 21 and s_bp == 400 and s_op == 200
11519
+ and op_["program"][1]["model"] == "MWD+IFR1" and op_["bit_offset_m"] == 18.0 and s_bo == 400 and s_pb == 200 and pb["comparison"]["bit"]["offset_m"] == 18.0
11520
+ and pb["comparison"]["model"] == "MWD+SRGM then MWD+IFR1" and s_vw == 403 and s_ow == 200 and ow["report_id"].startswith("PVA-")
11521
+ and s_rm == 200 and s_r2 == 404 and s_co == 200 and any(w_["model"] == "MWD+SRGM then MWD+IFR1" for w_ in co["wells"]),
11522
+ "cli": cli_m.returncode == 0 and "MWD+SRGM" in cli_m.stdout and "(default)" in cli_m.stdout and "ISCWSA MWD Rev4" in cli_m.stdout
11523
+ and cli_p.returncode == 0 and "MWD+HRGM to TD" in cli_p.stdout and cli_t.returncode == 0 and "the bit projected" in cli_t.stdout
11524
+ and "tools: MWD+HRGM" in cli_t.stdout and cli_w.returncode == 0 and "plan_vs_actual" in cli_w.stdout,
11525
+ "report": pdf.exists() and PR_read_ok(pdf) and "report.plan_vs_actual" in audit and "well.plan" in audit and "well.plan_removed" in audit and "survey_program.declare" in audit,
11526
+ "dossier": bool(names) and f"declarations/wells/{ids[0]}/plan/planA.csv" in names and "declarations/wellpath/survey_program.json" in names
11527
+ and "results/collision.json" in names and f"results/plan_vs_actual/{ids[0]}.json" in names,
11528
+ "page": all(x in page_src for x in ("VIEWS.plan", "['#/plan', 'Plan vs actual']", "/api/plan/attach", "/api/plan/settings", "coCylinder", "#coTcRef", "pvDev")),
11529
+ "help": not H.check("plan") and not H.check("collision") and H.VIEW_TOPIC.get("plan") == "plan" and "travelling cylinder" in (pkg / "help" / "collision.md").read_text(encoding="utf-8"),
11530
+ }
11531
+ failed5 = [k for k, v in c5.items() if not v]
11532
+ ok(not failed5,
11533
+ "CS5 on the API (anyone reads a well against its plan and the tool models; only an operator attaches a plan - its source and "
11534
+ "azimuth reference required - declares a survey program - an unknown tool or a bit offset past 100 m refused - writes the "
11535
+ "report or takes the plan off), the CLI (the models, a program, the comparison, the report), the report as PDF, a signed "
11536
+ "dossier carrying the plans, the programs and both checks, the tabs and the help"
11537
+ + (f" [failed: {', '.join(failed5)}]" if failed5 else ""))
11538
+
11539
+
11540
+ def PR_read_ok(path) -> bool:
11541
+ from . import pdf_report as PR
11542
+ try:
11543
+ return bool(PR.read(Path(path).read_bytes())["ok"])
11544
+ except Exception:
11545
+ return False
11546
+
11547
+
11299
11548
  def main() -> int:
11300
11549
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
11301
11550
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -11395,6 +11644,7 @@ def main() -> int:
11395
11644
  section_cp_rig_time(tmp)
11396
11645
  section_cq_tied_together(tmp)
11397
11646
  section_cr_collision(tmp)
11647
+ section_cs_plan_and_tools(tmp)
11398
11648
  if _FAILS:
11399
11649
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
11400
11650
  for f in _FAILS:
@@ -1,7 +1,9 @@
1
1
  """Well collision checks: how close each well's path comes to its neighbours, against how well both are known.
2
2
 
3
3
  Two wells drilled from one pad start metres apart. Each well's position is known only as well as its survey: the
4
- error model (`wellpos`, ISCWSA MWD Rev 4) gives each station's covariance, growing with depth. This module scans every
4
+ error model (`wellpos`: the tool models of the well's survey program, ISCWSA MWD Rev 5.11 unless declared) gives each
5
+ station's covariance, growing with depth. A well not yet drilled is checked along its plan (`wellplan`), weighed as if
6
+ surveyed every 30 m; a well being drilled along its survey and then its plan below it. This module scans every
5
7
  well against every other in three dimensions and answers, along the reference well, with the separation factor of the
6
8
  ISCWSA collision-avoidance rule (Sawaryn et al., "Well-Collision-Avoidance Separation Rule", SPE Drilling &
7
9
  Completion 34(1), 2019):
@@ -46,6 +48,8 @@ RULE_SOURCE = ('the ISCWSA separation rule (Sawaryn et al., Well-Collision-Avoid
46
48
  'surface margin 0.3 m and project-ahead 0.5 m as recommended) and the ISCWSA eBook\'s levels (Separation Rules: report below 4.0, '
47
49
  'approach limit 1.5, revise below 1.25, dispensation below 1.0)')
48
50
  _LOCK = threading.Lock()
51
+ TC_NORTH_BELOW_DEG = 5.0 # the travelling cylinder's angle from north while the reference is within this of vertical
52
+ PLANNED_STATION_M = 30.0 # a plan weighed as if surveyed every 30 m (one stand)
49
53
 
50
54
 
51
55
  def _path(ws) -> str:
@@ -113,7 +117,7 @@ def prepare(w: dict, step_m: float = 10.0) -> dict:
113
117
  p = W3.Path(md, inc, az, w['x0'], w['y0'], w.get('ref_elev_m') or 0.0, 0.0)
114
118
  az_true = (az + w.get('az_true_offset_deg', 0.0)) % 360.0
115
119
  f = w['field']
116
- u = WP.well_uncertainty(md, inc, az_true, p.tvd, f['declination_deg'], f['dip_deg'], f['total_nT'])
120
+ u = WP.program_uncertainty(md, inc, az_true, p.tvd, f['declination_deg'], f['dip_deg'], f['total_nT'], w.get('program'))
117
121
  r = math.radians(-w.get('az_true_offset_deg', 0.0)) # true north -> the frame's north
118
122
  R = np.array([[math.cos(r), -math.sin(r), 0.0], [math.sin(r), math.cos(r), 0.0], [0.0, 0.0, 1.0]])
119
123
  C = np.einsum('ij,kjl,ml->kim', R, u['cov'], R)
@@ -125,7 +129,14 @@ def prepare(w: dict, step_m: float = 10.0) -> dict:
125
129
  t = ((grid - md[j]) / np.where(md[j + 1] > md[j], md[j + 1] - md[j], 1.0)).clip(0.0, 1.0)
126
130
  Ci = C[j] * (1 - t)[:, None, None] + C[j + 1] * t[:, None, None]
127
131
  Gi = {k: v[j] * (1 - t)[:, None] + v[j + 1] * t[:, None] for k, v in G.items()}
132
+ tng = np.gradient(pos, grid, axis=0) if len(grid) > 1 else np.array([[0.0, 0.0, 1.0]])
133
+ tng = tng / np.maximum(np.linalg.norm(tng, axis=1), 1e-12)[:, None]
134
+ inc_g = np.degrees(np.arccos(np.clip(tng[:, 2], -1.0, 1.0)))
135
+ az_g = np.arctan2(tng[:, 1], tng[:, 0])
136
+ hs = np.stack([np.cos(np.radians(inc_g)) * np.cos(az_g), np.cos(np.radians(inc_g)) * np.sin(az_g), -np.sin(np.radians(inc_g))], axis=1)
137
+ rt = np.stack([-np.sin(az_g), np.cos(az_g), np.zeros(len(grid))], axis=1)
128
138
  return {**w, 'grid_md': grid, 'pos': pos, 'cov': Ci, 'glob': Gi, 'radius_m': w.get('hole_in', DEFAULTS['hole_in']) * 0.0254 / 2.0,
139
+ 'grid_inc': inc_g, 'hs': hs, 'rt': rt, 'model': u['model'],
129
140
  'stations_cov': C, 'stations_md': md, 'td_ellipse': WP.ellipse(C[-1], 1.0)}
130
141
 
131
142
 
@@ -157,13 +168,19 @@ def scan(ref: dict, off: dict, rule: dict) -> dict:
157
168
  Cr = ref['cov'][i]
158
169
  X = np.zeros((3, 3))
159
170
  for g_, sr in ref['glob'].items(): # what both wells share moves them together
171
+ if g_ not in off['glob']:
172
+ continue
160
173
  so = off['glob'][g_][jj] * (1 - f) + off['glob'][g_][jj + 1] * f
161
174
  X += np.outer(sr[i], so)
162
175
  var = float(n @ (Cr + Co - X - X.T) @ n)
163
176
  sig = math.sqrt(max(var, 0.0))
164
177
  sf = (D - ref['radius_m'] - off['radius_m'] - sm) / (k * math.sqrt(sig * sig + spa * spa))
165
178
  md_o = float(off['grid_md'][jj] * (1 - f) + off['grid_md'][jj + 1] * f)
166
- out.append((float(ref['grid_md'][i]), float(a[2]), md_o, D, sig, sf))
179
+ if ref['grid_inc'][i] < TC_NORTH_BELOW_DEG: # near vertical: the bearing from north
180
+ ang = math.degrees(math.atan2(float(n[1]), float(n[0]))) % 360.0
181
+ else: # clockwise from the reference's high side
182
+ ang = math.degrees(math.atan2(float(n @ ref['rt'][i]), float(n @ ref['hs'][i]))) % 360.0
183
+ out.append((float(ref['grid_md'][i]), float(a[2]), md_o, D, sig, sf, ang))
167
184
  arr = np.array(out)
168
185
  i_min = int(np.argmin(arr[:, 5]))
169
186
  low = arr[:, 5] < float(rule.get('report_below', 4.0))
@@ -180,7 +197,9 @@ def scan(ref: dict, off: dict, rule: dict) -> dict:
180
197
  'distance_m': round(float(mn[3]), 3), 'sigma_m': round(float(mn[4]), 3), 'level': lv, 'why': why, 'sections': sections,
181
198
  'closest_m': round(float(arr[:, 3].min()), 3), 'closest_at_md_m': round(float(arr[int(np.argmin(arr[:, 3])), 0]), 1),
182
199
  'profile': {'md_m': [round(float(x), 1) for x in arr[:, 0]], 'sf': [round(float(min(x, 99.0)), 3) for x in arr[:, 5]],
183
- 'distance_m': [round(float(x), 2) for x in arr[:, 3]], 'sigma_m': [round(float(x), 3) for x in arr[:, 4]]}}
200
+ 'distance_m': [round(float(x), 2) for x in arr[:, 3]], 'sigma_m': [round(float(x), 3) for x in arr[:, 4]],
201
+ 'offset_md_m': [round(float(x), 1) for x in arr[:, 2]], 'tc_deg': [round(float(x), 1) for x in arr[:, 6]],
202
+ 'tc_north': [bool(x < TC_NORTH_BELOW_DEG) for x in ref['grid_inc']]}}
184
203
 
185
204
 
186
205
  def _runs(mask):
@@ -199,16 +218,18 @@ def analyse(wells: List[dict], rule: dict, title: str = '') -> dict:
199
218
  pairs.sort(key=lambda p: p['min_sf'])
200
219
  wl = [{'id': w['id'], 'display': w['display'], 'td_md_m': round(float(w['stations_md'][-1]), 1), 'n_stations': int(len(w['stations_md'])),
201
220
  'hole_in': w.get('hole_in', DEFAULTS['hole_in']), 'field': w['field'], 'td_ellipse_1sigma': w['td_ellipse'],
202
- 'slot': [round(float(w['x0']), 2), round(float(w['y0']), 2)], 'field_note': w.get('field_note', ''), 'azimuth_note': w.get('azimuth_note', '')}
221
+ 'slot': [round(float(w['x0']), 2), round(float(w['y0']), 2)], 'field_note': w.get('field_note', ''), 'azimuth_note': w.get('azimuth_note', ''),
222
+ 'model': w['model'], 'basis': w.get('basis', 'survey'), 'basis_note': w.get('basis_note', '')}
203
223
  for w in prep]
204
224
  worst = pairs[0] if pairs else None
205
225
  return {'title': title, 'rule': {k: rule[k] for k in ('k', 'surface_margin_m', 'sigma_pa_m') if k in rule}, 'rule_source': rule.get('rule_source') or RULE_SOURCE,
206
226
  'wells': wl, 'pairs': pairs, 'worst': {k: v for k, v in (worst or {}).items() if k != 'profile'} or None,
207
227
  'levels': [{'below': lim, 'level': name, 'meaning': why} for lim, name, why in LEVELS],
208
- 'model': 'ISCWSA MWD Rev 4 (every well; a tool of another grade is another model)',
228
+ 'model': 'each well by its survey program\'s tool models (ISCWSA MWD Rev 5.11, MWD+SRGM, unless declared)',
209
229
  'not_a_measurement': ['the wells\' true positions: an uncertainty is what the survey tool\'s published error model allows, not a measured miss',
210
230
  'a probability of collision: the separation factor is a rule\'s ratio, and the operator\'s standard says what it permits',
211
- 'the hole size when it is not declared (8-1/2 in by default), and the casing beyond it']}
231
+ 'the hole size when it is not declared (8-1/2 in by default), and the casing beyond it',
232
+ 'a plan\'s position: a well not yet drilled is checked along its plan, weighed as if surveyed every 30 m by its program\'s tools']}
212
233
 
213
234
 
214
235
  # -- the scenes -----------------------------------------------------------------------------------------------------------
@@ -243,8 +264,11 @@ def workspace_wells(ws) -> Tuple[List[dict], List[dict]]:
243
264
  sc = W3.workspace_scene(ws)
244
265
  s = settings(ws)
245
266
  left = list(sc.get('not_placed') or [])
267
+ from . import wellplan as WPL
246
268
  out = []
247
- for w in sc['wells']:
269
+ plans = {p['id']: p for p in sc.get('planned') or []}
270
+ entries = [(w, 'survey') for w in sc['wells']] + [(p, 'plan') for p in plans.values() if not p['surveyed']]
271
+ for w, basis in entries:
248
272
  wid = w['id']
249
273
  wr = ws.well(wid)
250
274
  surf = wr.get('surface') or {}
@@ -256,16 +280,40 @@ def workspace_wells(ws) -> Tuple[List[dict], List[dict]]:
256
280
  except Exception:
257
281
  pass
258
282
  f = GM.field(float(surf.get('lat')), float(surf.get('lon')), 0.0, when or datetime.now(timezone.utc).date())
259
- st = w['stations']
260
- out.append({'id': wid, 'display': w['name'], 'md': st['md'], 'inc': st['incl'], 'az': st['azi'], 'az_true_offset_deg': 0.0,
283
+ st = dict(w['stations'])
284
+ note = ''
285
+ if basis == 'plan': # a well not yet drilled: its plan, weighed as surveyed every stand
286
+ st = _densify(st, PLANNED_STATION_M)
287
+ note = f'the plan ({w["source"]}), weighed as surveyed every {PLANNED_STATION_M:g} m'
288
+ elif wid in plans and plans[wid]['td_md'] > float(st['md'][-1]) + 1.0: # drilling: the survey, then the plan below it
289
+ pl = _densify(plans[wid]['stations'], PLANNED_STATION_M)
290
+ below = [i for i, m in enumerate(pl['md']) if m > float(st['md'][-1]) + 1.0]
291
+ st = {k: list(st[k]) + [pl[k][i] for i in below] for k in ('md', 'incl', 'azi')}
292
+ basis = 'survey + plan'
293
+ note = f'surveyed to {w["stations"]["md"][-1]:,.1f} m, the plan below it'
294
+ prog = WPL.well_settings(ws, wid)['program']
295
+ out.append({'id': wid, 'display': w['name'] + (' (plan)' if basis == 'plan' else ''), 'md': st['md'], 'inc': st['incl'], 'az': st['azi'], 'az_true_offset_deg': 0.0,
296
+ 'program': prog, 'basis': basis, 'basis_note': note,
261
297
  'x0': float(w['slot'][0]), 'y0': float(w['slot'][1]), 'ref_elev_m': float(w['ref_elev_m']),
262
298
  'field': {'declination_deg': round(f['declination_deg'], 3), 'dip_deg': round(f['dip_deg'], 3), 'total_nT': round(f['total_nT'], 1)},
263
299
  'hole_in': s['holes_in'].get(wid, DEFAULTS['hole_in']),
264
300
  'field_note': f"WMM2025 at the well for {f['decimal_year']:.2f}" + (f" ({f['note']})" if f['note'] else ''),
265
301
  'azimuth_note': w['azimuth']['applied']})
302
+ placed = {x['id'] for x in out}
303
+ left = [x for x in left if x['id'] not in placed]
266
304
  return out, left
267
305
 
268
306
 
307
+ def _densify(st: dict, step: float) -> dict:
308
+ """A plan's stations with one added every `step` metres between them, on its arcs (as a survey would see it)."""
309
+ from . import well3d as W3
310
+ from . import wellplan as WPL
311
+ p = W3.Path(st['md'], st['incl'], st['azi'], 0.0, 0.0, 0.0, 0.0)
312
+ mds = sorted(set(np.round(np.concatenate([p.md, np.arange(p.md[0], p.md[-1], step)]), 3).tolist()))
313
+ dirs = [WPL._interp_dir(p, m) for m in mds]
314
+ return {'md': mds, 'incl': [d[0] for d in dirs], 'azi': [d[1] for d in dirs]}
315
+
316
+
269
317
  def page(ws, scene: str = 'workspace') -> dict:
270
318
  rule = settings(ws)
271
319
  if scene == 'volve' or ws is None:
@@ -277,7 +325,7 @@ def page(ws, scene: str = 'workspace') -> dict:
277
325
  res = analyse(wells, rule, ws.manifest.get('name') or 'this workspace') if len(wells) >= 2 else {
278
326
  'title': ws.manifest.get('name') or 'this workspace', 'wells': [], 'pairs': [], 'worst': None, 'rule': {k: rule[k] for k in ('k', 'surface_margin_m', 'sigma_pa_m')},
279
327
  'rule_source': rule.get('rule_source') or RULE_SOURCE, 'levels': [{'below': lim, 'level': name, 'meaning': why} for lim, name, why in LEVELS],
280
- 'model': 'ISCWSA MWD Rev 4', 'not_a_measurement': [],
328
+ 'model': 'each well by its survey program\'s tool models', 'not_a_measurement': [],
281
329
  'detail': f"{len(wells)} well(s) have a surface position, a reference elevation and a survey; a check needs two"}
282
330
  res['left_out'] = left
283
331
  else: