gea-program 0.2.0__tar.gz → 0.4.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 (207) hide show
  1. gea_program-0.4.0/PKG-INFO +381 -0
  2. gea_program-0.4.0/README.md +342 -0
  3. {gea_program-0.2.0 → gea_program-0.4.0}/gea/__init__.py +3 -1
  4. {gea_program-0.2.0 → gea_program-0.4.0}/gea/__main__.py +471 -8
  5. gea_program-0.4.0/gea/acceptance_tests.py +2733 -0
  6. {gea_program-0.2.0 → gea_program-0.4.0}/gea/alarm_engine.py +96 -9
  7. gea_program-0.4.0/gea/certificates.py +130 -0
  8. {gea_program-0.2.0 → gea_program-0.4.0}/gea/cli.py +37 -10
  9. {gea_program-0.2.0 → gea_program-0.4.0}/gea/client_reports.py +120 -7
  10. {gea_program-0.2.0 → gea_program-0.4.0}/gea/dashboard.py +116 -5
  11. gea_program-0.4.0/gea/doctor.py +214 -0
  12. {gea_program-0.2.0 → gea_program-0.4.0}/gea/fat_sat.py +8 -0
  13. gea_program-0.4.0/gea/files.py +367 -0
  14. gea_program-0.4.0/gea/help/TESTER_GUIDE.md +163 -0
  15. gea_program-0.4.0/gea/help/alarms.md +13 -0
  16. gea_program-0.4.0/gea/help/data-in.md +13 -0
  17. gea_program-0.4.0/gea/help/doctor.md +13 -0
  18. gea_program-0.4.0/gea/help/drift.md +13 -0
  19. gea_program-0.4.0/gea/help/files.md +13 -0
  20. gea_program-0.4.0/gea/help/instruments.md +13 -0
  21. gea_program-0.4.0/gea/help/notifications.md +13 -0
  22. gea_program-0.4.0/gea/help/patches.md +13 -0
  23. gea_program-0.4.0/gea/help/quality.md +13 -0
  24. gea_program-0.4.0/gea/help/site.md +15 -0
  25. gea_program-0.4.0/gea/help/start.md +13 -0
  26. gea_program-0.4.0/gea/help/transients.md +13 -0
  27. gea_program-0.4.0/gea/help/upkeep.md +13 -0
  28. gea_program-0.4.0/gea/help/well-tests.md +13 -0
  29. gea_program-0.4.0/gea/helplib.py +166 -0
  30. gea_program-0.4.0/gea/housekeeping.py +153 -0
  31. gea_program-0.4.0/gea/jobs.py +297 -0
  32. {gea_program-0.2.0 → gea_program-0.4.0}/gea/live_ports.py +56 -4
  33. gea_program-0.4.0/gea/loadtest.py +115 -0
  34. gea_program-0.4.0/gea/notify.py +348 -0
  35. gea_program-0.4.0/gea/patches.py +462 -0
  36. {gea_program-0.2.0 → gea_program-0.4.0}/gea/ports.py +6 -1
  37. {gea_program-0.2.0 → gea_program-0.4.0}/gea/sbom.py +1 -1
  38. gea_program-0.4.0/gea/sensor_swap.py +221 -0
  39. gea_program-0.4.0/gea/service.py +1586 -0
  40. gea_program-0.4.0/gea/shut_in.py +201 -0
  41. gea_program-0.4.0/gea/transient.py +247 -0
  42. gea_program-0.4.0/gea/web/app.html +569 -0
  43. gea_program-0.4.0/gea/wits0.py +397 -0
  44. gea_program-0.4.0/gea/witsml.py +402 -0
  45. gea_program-0.4.0/gea/workspace.py +359 -0
  46. gea_program-0.4.0/gea_program.egg-info/PKG-INFO +381 -0
  47. {gea_program-0.2.0 → gea_program-0.4.0}/gea_program.egg-info/SOURCES.txt +32 -0
  48. {gea_program-0.2.0 → gea_program-0.4.0}/gea_program.egg-info/requires.txt +4 -0
  49. {gea_program-0.2.0 → gea_program-0.4.0}/pyproject.toml +4 -3
  50. gea_program-0.2.0/PKG-INFO +0 -189
  51. gea_program-0.2.0/README.md +0 -153
  52. gea_program-0.2.0/gea/acceptance_tests.py +0 -1478
  53. gea_program-0.2.0/gea_program.egg-info/PKG-INFO +0 -189
  54. {gea_program-0.2.0 → gea_program-0.4.0}/LICENSE +0 -0
  55. {gea_program-0.2.0 → gea_program-0.4.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  56. {gea_program-0.2.0 → gea_program-0.4.0}/gea/accuracy_statement.py +0 -0
  57. {gea_program-0.2.0 → gea_program-0.4.0}/gea/bench.py +0 -0
  58. {gea_program-0.2.0 → gea_program-0.4.0}/gea/blind_harness.py +0 -0
  59. {gea_program-0.2.0 → gea_program-0.4.0}/gea/case_study.py +0 -0
  60. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  61. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  62. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  63. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  64. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  65. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  66. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  67. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  68. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  69. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  70. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  71. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  72. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  73. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  74. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  75. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  76. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  77. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  78. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  79. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  80. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  81. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  82. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  83. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  84. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  85. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  86. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  87. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  88. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  89. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  90. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  91. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  92. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  93. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  94. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  95. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  96. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  97. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  98. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  99. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  100. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  101. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  102. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  103. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  104. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  105. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  106. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  107. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  108. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  109. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  110. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  111. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  112. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  113. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  114. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  115. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  116. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  117. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  118. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  119. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  120. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  121. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  122. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  123. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  124. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  125. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  126. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  127. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/med_160_sapropels.txt +0 -0
  128. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  129. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  130. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  131. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  132. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  133. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  134. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  135. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  136. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  137. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  138. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  139. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  140. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  141. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  142. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  143. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  144. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  145. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  146. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  147. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  148. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  149. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  150. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  151. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  152. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  153. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  154. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  155. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  156. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  157. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  158. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  159. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  160. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  161. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  162. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  163. {gea_program-0.2.0 → gea_program-0.4.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  164. {gea_program-0.2.0 → gea_program-0.4.0}/gea/config_versioning.py +0 -0
  165. {gea_program-0.2.0 → gea_program-0.4.0}/gea/correlation.py +0 -0
  166. {gea_program-0.2.0 → gea_program-0.4.0}/gea/deviation.py +0 -0
  167. {gea_program-0.2.0 → gea_program-0.4.0}/gea/downhole_engine.py +0 -0
  168. {gea_program-0.2.0 → gea_program-0.4.0}/gea/drift_monitor.py +0 -0
  169. {gea_program-0.2.0 → gea_program-0.4.0}/gea/earth_model.py +0 -0
  170. {gea_program-0.2.0 → gea_program-0.4.0}/gea/example_register_map.json +0 -0
  171. {gea_program-0.2.0 → gea_program-0.4.0}/gea/follower.py +0 -0
  172. {gea_program-0.2.0 → gea_program-0.4.0}/gea/forward_model.py +0 -0
  173. {gea_program-0.2.0 → gea_program-0.4.0}/gea/gamma.py +0 -0
  174. {gea_program-0.2.0 → gea_program-0.4.0}/gea/gauge_specs.py +0 -0
  175. {gea_program-0.2.0 → gea_program-0.4.0}/gea/gravity_reference.py +0 -0
  176. {gea_program-0.2.0 → gea_program-0.4.0}/gea/inverse_engine.py +0 -0
  177. {gea_program-0.2.0 → gea_program-0.4.0}/gea/matplotlib_demo.py +0 -0
  178. {gea_program-0.2.0 → gea_program-0.4.0}/gea/modbus.py +0 -0
  179. {gea_program-0.2.0 → gea_program-0.4.0}/gea/model_card.py +0 -0
  180. {gea_program-0.2.0 → gea_program-0.4.0}/gea/mqtt_port.py +0 -0
  181. {gea_program-0.2.0 → gea_program-0.4.0}/gea/opcua_port.py +0 -0
  182. {gea_program-0.2.0 → gea_program-0.4.0}/gea/operator_app.py +0 -0
  183. {gea_program-0.2.0 → gea_program-0.4.0}/gea/profile_catalog.py +0 -0
  184. {gea_program-0.2.0 → gea_program-0.4.0}/gea/project.py +0 -0
  185. {gea_program-0.2.0 → gea_program-0.4.0}/gea/qt6_downhole_app.py +0 -0
  186. {gea_program-0.2.0 → gea_program-0.4.0}/gea/quartz_hpht_extension.py +0 -0
  187. {gea_program-0.2.0 → gea_program-0.4.0}/gea/reconciler.py +0 -0
  188. {gea_program-0.2.0 → gea_program-0.4.0}/gea/rock_inventory.py +0 -0
  189. {gea_program-0.2.0 → gea_program-0.4.0}/gea/sample_record.py +0 -0
  190. {gea_program-0.2.0 → gea_program-0.4.0}/gea/sample_well_profile.csv +0 -0
  191. {gea_program-0.2.0 → gea_program-0.4.0}/gea/segy.py +0 -0
  192. {gea_program-0.2.0 → gea_program-0.4.0}/gea/service_life.py +0 -0
  193. {gea_program-0.2.0 → gea_program-0.4.0}/gea/shell.py +0 -0
  194. {gea_program-0.2.0 → gea_program-0.4.0}/gea/sla_report.py +0 -0
  195. {gea_program-0.2.0 → gea_program-0.4.0}/gea/store_forward.py +0 -0
  196. {gea_program-0.2.0 → gea_program-0.4.0}/gea/strata_join.py +0 -0
  197. {gea_program-0.2.0 → gea_program-0.4.0}/gea/survey_cmd.py +0 -0
  198. {gea_program-0.2.0 → gea_program-0.4.0}/gea/survey_view.py +0 -0
  199. {gea_program-0.2.0 → gea_program-0.4.0}/gea/telemetry.py +0 -0
  200. {gea_program-0.2.0 → gea_program-0.4.0}/gea/tool_library.py +0 -0
  201. {gea_program-0.2.0 → gea_program-0.4.0}/gea/well_assembler.py +0 -0
  202. {gea_program-0.2.0 → gea_program-0.4.0}/gea/well_test_validation.py +0 -0
  203. {gea_program-0.2.0 → gea_program-0.4.0}/gea_program.egg-info/dependency_links.txt +0 -0
  204. {gea_program-0.2.0 → gea_program-0.4.0}/gea_program.egg-info/entry_points.txt +0 -0
  205. {gea_program-0.2.0 → gea_program-0.4.0}/gea_program.egg-info/top_level.txt +0 -0
  206. {gea_program-0.2.0 → gea_program-0.4.0}/setup.cfg +0 -0
  207. {gea_program-0.2.0 → gea_program-0.4.0}/tests/test_acceptance.py +0 -0
@@ -0,0 +1,381 @@
1
+ Metadata-Version: 2.4
2
+ Name: gea-program
3
+ Version: 0.4.0
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
+ Author: Daniel T. Murphy
6
+ License: MPL-2.0
7
+ Project-URL: Homepage, https://github.com/Daniel8Murphy0007/GEA-Program
8
+ Project-URL: Source, https://github.com/Daniel8Murphy0007/GEA-Program
9
+ Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy>=1.24
18
+ Provides-Extra: plotting
19
+ Requires-Dist: matplotlib>=3.7; extra == "plotting"
20
+ Provides-Extra: desktop
21
+ Requires-Dist: PyQt6>=6.5; extra == "desktop"
22
+ Requires-Dist: matplotlib>=3.7; extra == "desktop"
23
+ Provides-Extra: modbus
24
+ Requires-Dist: pymodbus>=3.0; extra == "modbus"
25
+ Provides-Extra: opcua
26
+ Requires-Dist: asyncua>=1.0; extra == "opcua"
27
+ Provides-Extra: mqtt
28
+ Requires-Dist: paho-mqtt>=2.0; extra == "mqtt"
29
+ Provides-Extra: serial
30
+ Requires-Dist: pyserial>=3.5; extra == "serial"
31
+ Provides-Extra: live
32
+ Requires-Dist: asyncua>=1.0; extra == "live"
33
+ Requires-Dist: paho-mqtt>=2.0; extra == "live"
34
+ Requires-Dist: pymodbus>=3.0; extra == "live"
35
+ Requires-Dist: pyserial>=3.5; extra == "live"
36
+ Provides-Extra: xls
37
+ Requires-Dist: xlrd>=2.0; extra == "xls"
38
+ Dynamic: license-file
39
+
40
+ # GEA-Program
41
+
42
+ Downhole gauge monitoring for production operations: the program takes a well's
43
+ historian data and returns the reports a client engineer reads, in the outline
44
+ and language of a production-operations scope of work.
45
+
46
+ Every number in every report is recomputed from the source data at generation
47
+ time. Reports are gated for vocabulary: the program's internal register never
48
+ reaches a client document. Nothing unmeasured is ever reported as met.
49
+
50
+ ## What it does
51
+
52
+ - **Canonical measurement record** (`gea/sample_record.py`): one record shape for
53
+ every sample - tag, UTC timestamp, value, unit, quality flag, the rule and limit
54
+ that fired, source layer, ingest timestamp. Quality rules: RANGE / ROC /
55
+ FLATLINE / SPIKE / STALE / GAP; ranges from the gauge datasheet, cited.
56
+ - **Gauge drift and reconciliation** (`reconciler.py`, `drift_monitor.py`): the
57
+ live pressure series against the well baseline; classification with the
58
+ numbers beside it; scheduled evaluation log; CURRENT / STALE; re-fit change
59
+ log with before/after coefficients; approve -> apply -> re-evaluate; SLA clocks;
60
+ annual re-fit cap.
61
+ - **Accuracy statement** (`accuracy_statement.py`): MAPE with a seeded bootstrap
62
+ 90 % CI; the band read at the conservative end of the interval.
63
+ - **Well-test validation** (`well_test_validation.py`): stable-period detection
64
+ with the client-agreed criteria in a file (hashed on every report), reason
65
+ codes naming the failing value, virtual rates, two-level approval trail.
66
+ - **Alarm management** (`alarm_engine.py`): setpoint / deadband / on-delay state
67
+ machine, event log, ISA-18.2-style KPIs printed against their targets.
68
+ - **Model cards** (`model_card.py`): one card per model - inputs with sources,
69
+ settings with basis, calibration data with provenance, recomputed evaluation,
70
+ limitations, re-fit history, component hashes.
71
+ - **Store-and-forward** (`store_forward.py`): 72 h edge buffer, chronological
72
+ rate-controlled replay, duplicate suppression, per-record latency.
73
+ - **Configuration versioning, SBOM, monthly SLA, FAT/SAT**
74
+ (`config_versioning.py`, `sbom.py`, `sla_report.py`, `fat_sat.py`).
75
+ - **Live protocol ports** (`live_ports.py`, `wits0.py`, `witsml.py`, `opcua_port.py`,
76
+ `mqtt_port.py`, `modbus.py`): WITS Level 0 from the drill floor (TCP connect,
77
+ TCP listen, serial), WITSML 1.4.1 stores (read-only, polled), OPC UA (read and
78
+ subscribe), MQTT (number, JSON, Sparkplug B), Modbus TCP; every message becomes
79
+ a measurement record with its source timestamp, ingest timestamp and quality
80
+ flag; units declared on the map are converted; every session can be recorded
81
+ and replayed; in-package simulators for WITS0 and WITSML. See *Live data*.
82
+ - **The patch panel** (`patches.py`): supervised connections that stay up -
83
+ reconnect with backoff, heartbeat, latency, per-tag last values, records to
84
+ disk as they arrive, several sources folded into one stream by priority.
85
+ - **Dashboard** (`dashboard.py`): tiles, well ranking, alarm wall, drill-down to
86
+ every report; light and dark; status is always an icon with a label.
87
+ - **The dashboard as the door** (`workspace.py`, `jobs.py`, `service.py`,
88
+ `web/app.html`): one folder per site that the client owns; a standard-library
89
+ HTTP service that serves the page, the reports and a JSON API; every action
90
+ a job running the program's own command with its log kept; accounts with
91
+ roles; an audit log with the hash of every input. See *Serving the dashboard*.
92
+ - **Survey track** (`earth_model.py`, `strata_join.py`, `inverse_engine.py`,
93
+ `blind_harness.py`, `rock_inventory.py`, `forward_model.py`, ...): strata
94
+ property estimation over a public co-located library, blind-scored on every run.
95
+
96
+ ## Quick start
97
+
98
+ ```
99
+ pip install -e .
100
+ gea quickstart # a real catalogue well -> its reports -> the dashboard; the KTB strata survey
101
+ gea survey mywell.las # a LAS file in, one strata report out
102
+ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --td 10500 --out dashboard
103
+ gea client-report --report accuracy --out client_report
104
+ gea model-cards --out model_cards
105
+ gea sbom --out sbom
106
+ gea accept # the product gate (237 checks)
107
+ gea help drift # the help library, by the job (14 pages; the same text is on every dashboard page)
108
+ gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
109
+ gea gui # the desktop window (pip install "gea-program[desktop]")
110
+ gea workspace --path C:\site --action init --name "Pad 3" # a site folder
111
+ gea serve --workspace C:\site # the dashboard as the door: http://127.0.0.1:8765/
112
+ gea doctor --workspace C:\site # which code runs, can it serve, with every fix
113
+ gea files --workspace C:\site --action list --root historian # the import roots, detection by content, import, export, packs
114
+ gea transient --file historian.csv --params params.json --out pta # shut-ins and build-up analysis with a band
115
+ gea swaps / gea certificates / gea notify / gea housekeeping / gea loadtest # instruments, notification rules, site upkeep
116
+ ```
117
+
118
+ `gea` is the front door (`gea/cli.py`); every other subcommand passes through to
119
+ `python -m gea`. PATH-proof form: `python -m gea.cli ...`. Open `dashboard/index.html`.
120
+
121
+ ## Live data
122
+
123
+ The engine reads live data through five protocol ports. All are **read-only**,
124
+ all map only what the site declares (a tag map the client owns; an empty map
125
+ is declined), and all write every received message to a JSON-lines recording
126
+ that `--replay` turns back into records without a connection - which is how a
127
+ site session is reproduced offline and how the mapping logic is tested.
128
+
129
+ ```
130
+ pip install "gea-program[live]" # asyncua, paho-mqtt, pymodbus, pyserial (or one extra at a time)
131
+ gea wits0 --write-example-config wits0.json # drill floor: transport tcp | listen | serial; item codes -> tags, units
132
+ gea wits0-sim --port 5001 # a WITS0 sender to rehearse with (no rig needed)
133
+ gea wits0 --config wits0.json --seconds 60 --record floor/session.jsonl --out floor --stream-csv floor/stream.csv
134
+ gea witsml --write-example-config witsml.json # a WITSML 1.4.1 store: url, uids, mnemonics -> tags; credentials by env name
135
+ gea witsml --config witsml.json --seconds 60 --out store
136
+ gea opcua --write-example-config opcua.json # edit: endpoint, security, credential env names, nodes
137
+ gea opcua --config opcua.json --seconds 60 --record opc/session.jsonl --out opc --stream-csv opc/stream.csv
138
+ gea mqtt --write-example-config mqtt.json # edit: broker, TLS, credential env names, topics
139
+ gea mqtt --config mqtt.json --replay mq/session.jsonl --out mq_replay # no broker needed
140
+ gea ingest --file floor/stream.csv # a stream CSV feeds the historian port like any other
141
+ ```
142
+
143
+ | port | transport | quality | timestamps |
144
+ |---|---|---|---|
145
+ | `wits0` | WITS Level 0 frames (`&&` ... `!!`, four-digit item codes) over TCP connect, TCP listen or serial; record-01 dictionary built in | numeric GOOD; sentinels (-9999, -999.25) and non-numeric -> GAP with the reason | items 0105/0106 (date, time) when present, else arrival; ingest at arrival |
146
+ | `witsml` | WITSML 1.4.1 SOAP store: GetVersion, GetCap, GetFromStore on one log object, rows newer than the last seen at each poll | numeric GOOD; the store's nullValue and empty fields -> GAP | the index curve of a time log; arrival for a depth log |
147
+ | `opcua` | opc.tcp, read + subscribe; Basic256Sha256 SignAndEncrypt when a certificate and key are given | StatusCode severity bits: Good -> GOOD, Uncertain -> STALE, Bad -> GAP (value withheld) | source timestamp, else server timestamp, else arrival |
148
+ | `mqtt` | MQTT v5 / v3.1.1, TLS optional, QoS per topic; `+`/`#` wildcards | number: GOOD or GAP; JSON: value/time/quality paths; Sparkplug B: `is_null` -> GAP, `Quality` != 192 -> STALE | JSON time path, Sparkplug metric timestamp, else arrival |
149
+ | `modbus_g6` | Modbus TCP, user-supplied register map with a citation | read failures -> GAP | arrival |
150
+
151
+ Units: a mapping may declare `unit_in` (what the wire carries) beside `unit`
152
+ (what the tag catalogue uses); known pairs (pressure, temperature, length,
153
+ force, flow, density, torque, volume) are converted before scale and offset,
154
+ and a pair the program cannot convert is declined when the map loads.
155
+ Credentials never sit in a map: the map names the environment variables that
156
+ hold them. The example maps are in `docs/examples/`.
157
+
158
+ **Patches** keep a source connected: a patch names a protocol, its map, the
159
+ well it feeds, a priority and a staleness limit; the service keeps every
160
+ enabled patch up, reconnects with backoff (2 ... 60 s) when the source drops,
161
+ keeps a heartbeat (state, last sample, samples per minute, latency p50/p95,
162
+ reconnects, last error, the last value of every tag), and appends every record
163
+ to `wells/<id>/records/live/patch_<name>_<day>.records.csv`. The refresh folds
164
+ the record files of every patch on a well into one stream (higher priority
165
+ wins a tie) and reports it beside the well; a drill-floor stream with no
166
+ downhole gauge stations gets quality and alarms, and its drift report is
167
+ marked not applicable rather than invented. The Patch panel page adds,
168
+ starts, stops, enables and edits patches and shows their live values.
169
+
170
+ ## Serving the dashboard
171
+
172
+ The command line is the engine; the dashboard is the door. A client site is one
173
+ folder, the **workspace**, which the client owns and backs up like any project
174
+ folder:
175
+
176
+ ```
177
+ gea workspace --path C:\site --action init --name "Pad 3"
178
+ gea workspace --path C:\site --action add-catalog --entry volve_f12_f14_production_excerpt --well 15/9-F-12 --station-md 10000
179
+ gea workspace --path C:\site --action add-file --file exports\historian.csv --name "Well A"
180
+ gea workspace --path C:\site --action add-live --name "Pad 3 OPC" --port opcua --file opcua.json
181
+ gea workspace --path C:\site --action refresh --month 2026-09 # every report, into C:\site\reports
182
+ gea serve --workspace C:\site # http://127.0.0.1:8765/
183
+ ```
184
+
185
+ Open the address in a browser. The first visit asks for the first
186
+ administrator's name and password (once); everyone else is added under
187
+ Administration with a role: **viewer** reads everything; **operator** adds
188
+ wells, runs reports and the gate, starts and stops live taps, acknowledges
189
+ alarms and commits configuration; **approver** decides well tests and re-fits;
190
+ **admin** manages users, schedules and rollbacks. Pages: Home (tiles, well
191
+ ranking), Wells (add from a file, the catalogue or a live tag map; each well's
192
+ reports, quality, trends, tests and alarms), Patch panel (supervised sources,
193
+ their state and live values, their maps, live values), Alarms (the wall, acknowledge, definitions), Approvals (one queue),
194
+ Files (the import and export roots an administrator allows, browse with
195
+ detection by content, import into a well, "save to..." on every report, the
196
+ evidence pack, watch folders), Alarms (the wall: acknowledge one or all, shelve
197
+ with a reason and an expiry, unshelve; definitions), Approvals (one queue),
198
+ Configuration (versioned JSON with history, diff and rollback), Reports
199
+ (generate and open every report), Verification (the acceptance suite, FAT/SAT,
200
+ SBOM and the standalone check from the page), Survey (a LAS file in, a strata
201
+ report out), Jobs (every run with its log), Administration (users, live
202
+ sessions, file roots, schedule, site settings, notifications, audit log). A
203
+ search box in the header spans wells, alarms, reports, jobs, configuration,
204
+ patches and schedules; the navigation shows badge counts (unacknowledged
205
+ alarms, decisions waiting, failed jobs, patches down); Home opens with what
206
+ happened since your last sign-in; every page has a help panel (the Help
207
+ button hides them); every timestamp is shown in your time zone and every
208
+ value in your units (field or SI - "Your preferences", with site defaults
209
+ under Administration); a first-run guide walks a new site through its
210
+ settings, first well, people and first refresh; every page prints cleanly.
211
+
212
+ Every action the page takes is a job: `python -m gea <command>` run by the
213
+ service with its log kept under `jobs/`, so one code path serves the page, the
214
+ terminal and the acceptance gate, and a failed action shows its log instead of
215
+ disappearing. Every action is written to `records/audit.jsonl` with the
216
+ actor's name and the SHA-256 of the inputs it used. The service is written on
217
+ the standard library only (no new dependency, the standalone check still
218
+ passes), listens on the loopback interface by default (`--host 0.0.0.0` for a
219
+ site network, behind the site's TLS proxy), keeps sessions in HttpOnly
220
+ SameSite cookies, and requires the page's own header on every action so a
221
+ foreign form cannot act. Accounts live in `users.json` as salted PBKDF2-SHA256
222
+ hashes; `gea users --workspace C:\site --action add --name ... --role admin`
223
+ adds one from the terminal (the password comes from the `GEA_PASSWORD`
224
+ environment variable, never the command line).
225
+
226
+ The workspace layout: `wells/<id>/source` (the client's files, copied in
227
+ verbatim and hashed), `config/` (the version store), `monitor/` (drift
228
+ evaluations and change logs), `reports/` (the printed dashboard and every
229
+ report), `jobs/` (every run), `records/audit.jsonl`, `users.json`. All of it is
230
+ text: JSON, JSON lines, CSV, Markdown, HTML.
231
+
232
+ ### The help library
233
+
234
+ `gea help` lists fourteen pages indexed by the job a reader arrives with -
235
+ start, bring data in, quality rules, drift, well tests, alarms and the month,
236
+ the site, instruments, shut-ins and build-ups, patches, files, notifications,
237
+ which code is running, upkeep. Each page carries four lines and stops: the
238
+ command, what it writes, the one number to check, and what the page will not
239
+ call a measurement. The pages ship inside the package (`gea/help/`), and the
240
+ same text is what `/api/help` serves and what the dashboard shows under every
241
+ heading and under Help, so the terminal and the page cannot drift apart. The
242
+ click-by-click tester guide ships with them: `gea guide`.
243
+
244
+ ### Which code is running
245
+
246
+ `gea doctor` answers the question every stale-page report comes down to:
247
+ which Python, which copy of the package, does pip's record match the code
248
+ that runs (an editable checkout or an installed release), is another copy
249
+ shadowing it, is the page present, which dependencies are installed, is a
250
+ newer release on PyPI; with `--workspace` it also checks the site folder, the
251
+ accounts, the patches, a free port and write access. Every finding carries
252
+ its fix. `gea serve` runs the same checks and does not start on a blocking
253
+ finding, so a stale page is never served silently.
254
+
255
+ ### Notifications
256
+
257
+ Rules route events to channels: a webhook URL or an SMTP mailbox. Events:
258
+ `alarm.activated` (by priority), `alarm.shelved`, `job.failed`, `patch.down`
259
+ and `patch.up`, `approval.pending`, `file.imported`. A quiet window stops
260
+ repeats of the same key; every attempt and every suppression is in
261
+ `records/notifications.jsonl`; an SMTP password is read from the environment
262
+ variable the channel names, never from the configuration. `gea notify
263
+ --example` prints a configuration to commit as `notifications` (Administration
264
+ has the editor and a "send a test" button per channel).
265
+
266
+ ### Instruments and transients
267
+
268
+ A swapped gauge is a new instrument. Record the swap on the well page (or
269
+ confirm one the step detector proposes - it looks for a jump in a gauge's
270
+ offset against its peers, which a process change does not produce) and the
271
+ drift fit restarts at the swap, with the swap printed in the report. File the
272
+ calibration certificate of each instrument (serial, lab, dates, stated
273
+ accuracy; the document is kept and hashed) and its status - VALID, EXPIRING,
274
+ EXPIRED, MISSING - is on the well page, on Home, and beside the measured bias
275
+ in the drift report, so a reader sees whether a bias is inside the
276
+ instrument's own class.
277
+
278
+ Every shut-in in a record (found by the rate channel, the on-stream hours, or
279
+ the pressure signature alone when there is no rate - marked so) gets a
280
+ build-up analysis on the next refresh: Horner slope and p* on a middle-time
281
+ region chosen by the flat Bourdet derivative, wellbore storage, and - once the
282
+ rock and fluid parameters are saved on the well page - kh, k, skin and the
283
+ radius of investigation, each with a 90 % band from a residual bootstrap. The
284
+ Shut-in and Pressure Transient report prints the region and the rule that
285
+ chose it; an analyst can move the region (`gea transient --mtr 2:40`) and the
286
+ result follows. It is the first look every shut-in should get automatically,
287
+ not a replacement for a full interpretation, and it says so.
288
+
289
+ ### Before a site goes live
290
+
291
+ `deploy/README.md` is the checklist: keep the service on the loopback
292
+ address and put a TLS proxy in front (`deploy/Caddyfile`,
293
+ `deploy/nginx-gea.conf`), start with `--behind-proxy`. The service itself
294
+ rate-limits sign-ins (five failures lock the name and the address for 15
295
+ minutes, audited), lists and revokes live sessions, sends the security
296
+ headers on every response, and `gea housekeeping --workspace C:\site --apply`
297
+ (schedule it daily from Administration) segments the append-only logs rather
298
+ than truncating them and prunes finished job folders and old recordings. `gea
299
+ loadtest --patches 16 --seconds 60 --with-service` tells you whether the site
300
+ machine carries the patches it will be given, with the page still answering.
301
+
302
+ ## The standalone install kit
303
+
304
+ A client site often has no internet and no Python. The kit is one folder
305
+ (and one zip) that carries its own Python, the package and every dependency
306
+ as files, with the scripts a site needs; it installs without a network and
307
+ without administrator rights, and keeps the site's data in a separate
308
+ workspace folder it never deletes.
309
+
310
+ ```
311
+ python tools/build_installer.py # dist/gea-program-<version>-win64/ and .zip (needs internet here, none there)
312
+ python tools/build_installer.py --extras live,plotting,xls,desktop # add the PyQt6 desktop window (~100 MB)
313
+ python tools/build_installer.py --platform linux # a venv-based kit for a Linux site server
314
+ ```
315
+
316
+ At the site: unzip, `install.cmd`, `start-dashboard.cmd`; the browser opens
317
+ `http://127.0.0.1:8765/`. `verify.cmd` runs the acceptance gate from the
318
+ installed kit (the client's own evidence), `register-service.cmd` starts the
319
+ dashboard at logon, `uninstall.cmd` removes the kit and leaves the workspace.
320
+ `SHA256SUMS.txt` lets the site verify the kit after copying it. Every tag
321
+ builds the Windows and Linux kits in CI (`build-installer.yml`), installs
322
+ them the way a client does, runs the gate from the installed kit, and
323
+ attaches the zips to the GitHub release of that tag.
324
+
325
+ ## Layout
326
+
327
+ ```
328
+ gea/ the package: engines, record layer, reports, monitor, dashboard, cli, shell, acceptance suite,
329
+ workspace, jobs, service, patches and web/app.html (the served dashboard)
330
+ gea/catalog/ 52 public archive entries, each with a provenance file
331
+ docs/ TESTER_GUIDE.md, REQUIREMENTS_MATRIX.md (the scope-of-work mirror that shaped the reports), examples/ (port configs),
332
+ SESSION_LOG.md (the working record, session by session),
333
+ commercial/ (pilot proposal, bench readiness, commercial use), HISTORY.md (the ship-by-ship record)
334
+ tools/ standalone_check.py (the self-contained guard; run by ci and ship.ps1), build_installer.py (the kit)
335
+ CHANGELOG.md per-release summary (a tag ships only with its section); SHIP_LOG.md is written by ship.ps1
336
+ tests/ pytest wrapper around the acceptance suite and the standalone check
337
+ ```
338
+
339
+ ## Basis
340
+
341
+ GEA-Program is a self-contained package: every module imports only the
342
+ standard library, numpy, the declared optional extras and the package itself,
343
+ and `tools/standalone_check.py` (run by CI and by `ship.ps1`) fails the build
344
+ if that ever changes or if a tracked file names another program. The physics
345
+ is on standard constants - CODATA 2018 G, standard gravity 9.80665 m/s2, the
346
+ IUGG mean Earth radius - and the rock inventory carries seventeen published
347
+ density anchors and Vp ranges with their citations (Telford, Geldart and
348
+ Sheriff; Schon). Every catalogue entry under `gea/catalog/` has a provenance
349
+ file naming its public source.
350
+
351
+ Two statements the product carries on its own model cards: the gauge aging
352
+ envelope's lower bound is an engineering model with no field validation on
353
+ record, and five of the fourteen back-tested strata quantities are NOT
354
+ ACCEPTABLE at the 95 % target. Both are printed, never claimed otherwise.
355
+
356
+ ## Shipping
357
+
358
+ `.\ship.ps1` (PowerShell) gates, commits, tags and pushes in one screen: version in
359
+ `pyproject.toml` must equal `gea.__version__` (`-Bump x.y.z` sets both), the tag must
360
+ not exist anywhere, every version in `SHIP_LOG.md` must have its tag, `python -m gea
361
+ accept` must be green, `SHIP_MESSAGE.txt` must start with the tag; then commit, tag,
362
+ push, and the remote tag must be seen before SHIPPED is printed. `CHANGELOG.md` must carry a
363
+ section headed by the tag, and `tools/standalone_check.py` must report no findings. `-DryRun` runs every
364
+ check and changes nothing; `-NoPush` stops after the local tag.
365
+
366
+ ## Publishing to PyPI
367
+
368
+ The name `gea-program` is free on PyPI as of 2026-09-29; a PyPI project is created by
369
+ its first upload, there is nothing to "start" beforehand except the trusted publisher.
370
+ One-time setup, before the first tag is pushed: sign in to PyPI -> your account ->
371
+ Publishing -> "Add a new pending publisher": project name `gea-program`, owner
372
+ `Daniel8Murphy0007`, repository `GEA-Program`, workflow `release-to-pypi.yml`,
373
+ environment `pypi`. Then in GitHub -> Settings -> Environments create `pypi`. From then
374
+ on `.\ship.ps1` pushes the tag and `.github/workflows/release-to-pypi.yml` gates, builds,
375
+ verifies the wheel and publishes; the package page is
376
+ https://pypi.org/project/gea-program/ after the first successful run.
377
+
378
+ ## Licence
379
+
380
+ Mozilla Public License 2.0 (MPL-2.0); see `LICENSE`. Every source file carries the MPL-2.0
381
+ header (Exhibit A). Copyright (c) 2026 Daniel T. Murphy.