gea-program 0.32.0__tar.gz → 0.34.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 (287) hide show
  1. {gea_program-0.32.0/gea_program.egg-info → gea_program-0.34.0}/PKG-INFO +35 -2
  2. {gea_program-0.32.0 → gea_program-0.34.0}/README.md +34 -1
  3. {gea_program-0.32.0 → gea_program-0.34.0}/gea/__init__.py +1 -1
  4. {gea_program-0.32.0 → gea_program-0.34.0}/gea/acceptance_tests.py +280 -0
  5. {gea_program-0.32.0 → gea_program-0.34.0}/gea/backup.py +1 -1
  6. {gea_program-0.32.0 → gea_program-0.34.0}/gea/dashboard.py +11 -3
  7. {gea_program-0.32.0 → gea_program-0.34.0}/gea/edr.py +33 -8
  8. gea_program-0.34.0/gea/help/charts.md +11 -0
  9. gea_program-0.34.0/gea/help/map.md +11 -0
  10. {gea_program-0.32.0 → gea_program-0.34.0}/gea/helplib.py +3 -1
  11. gea_program-0.34.0/gea/reference/us_outline.json +1 -0
  12. gea_program-0.34.0/gea/reference/us_outline.provenance.json +22 -0
  13. gea_program-0.34.0/gea/series_cache.py +299 -0
  14. {gea_program-0.32.0 → gea_program-0.34.0}/gea/service.py +117 -0
  15. gea_program-0.34.0/gea/site_map.py +295 -0
  16. {gea_program-0.32.0 → gea_program-0.34.0}/gea/web/app.html +320 -9
  17. {gea_program-0.32.0 → gea_program-0.34.0}/gea/workspace.py +1 -1
  18. {gea_program-0.32.0 → gea_program-0.34.0/gea_program.egg-info}/PKG-INFO +35 -2
  19. {gea_program-0.32.0 → gea_program-0.34.0}/gea_program.egg-info/SOURCES.txt +6 -0
  20. {gea_program-0.32.0 → gea_program-0.34.0}/pyproject.toml +1 -1
  21. {gea_program-0.32.0 → gea_program-0.34.0}/LICENSE +0 -0
  22. {gea_program-0.32.0 → gea_program-0.34.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  23. {gea_program-0.32.0 → gea_program-0.34.0}/gea/__main__.py +0 -0
  24. {gea_program-0.32.0 → gea_program-0.34.0}/gea/accuracy_statement.py +0 -0
  25. {gea_program-0.32.0 → gea_program-0.34.0}/gea/alarm_engine.py +0 -0
  26. {gea_program-0.32.0 → gea_program-0.34.0}/gea/bench.py +0 -0
  27. {gea_program-0.32.0 → gea_program-0.34.0}/gea/blind_harness.py +0 -0
  28. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  29. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  30. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  31. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  32. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  33. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  34. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  35. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  36. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  37. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  38. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  39. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  40. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  41. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  42. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  43. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  44. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  45. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  46. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  47. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  48. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  49. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  50. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  51. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  52. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  53. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  54. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  55. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  56. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  57. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  58. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  59. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  60. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  61. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  62. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  63. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  64. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  65. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  66. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  67. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  68. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  69. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  70. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  71. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  72. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  73. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  74. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  75. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  76. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  77. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  78. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  79. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  80. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  81. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  82. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  83. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  84. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  85. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  86. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  87. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  88. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  89. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  90. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  91. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  92. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  93. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  94. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  95. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/med_160_sapropels.txt +0 -0
  96. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  97. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  98. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  99. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  100. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  101. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  102. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  103. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  104. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  105. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  106. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  107. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  108. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  109. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  110. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  111. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  112. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  113. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  114. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  115. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  116. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  117. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  118. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  119. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  120. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  121. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  122. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  123. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  124. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  125. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  126. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  127. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  128. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  129. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  130. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  131. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  132. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog_map.py +0 -0
  133. {gea_program-0.32.0 → gea_program-0.34.0}/gea/catalog_register.py +0 -0
  134. {gea_program-0.32.0 → gea_program-0.34.0}/gea/certificates.py +0 -0
  135. {gea_program-0.32.0 → gea_program-0.34.0}/gea/cli.py +0 -0
  136. {gea_program-0.32.0 → gea_program-0.34.0}/gea/client_reports.py +0 -0
  137. {gea_program-0.32.0 → gea_program-0.34.0}/gea/config_versioning.py +0 -0
  138. {gea_program-0.32.0 → gea_program-0.34.0}/gea/conformance.py +0 -0
  139. {gea_program-0.32.0 → gea_program-0.34.0}/gea/correlation.py +0 -0
  140. {gea_program-0.32.0 → gea_program-0.34.0}/gea/deviation.py +0 -0
  141. {gea_program-0.32.0 → gea_program-0.34.0}/gea/dlis.py +0 -0
  142. {gea_program-0.32.0 → gea_program-0.34.0}/gea/dnp3.py +0 -0
  143. {gea_program-0.32.0 → gea_program-0.34.0}/gea/doctor.py +0 -0
  144. {gea_program-0.32.0 → gea_program-0.34.0}/gea/downhole_engine.py +0 -0
  145. {gea_program-0.32.0 → gea_program-0.34.0}/gea/drift_monitor.py +0 -0
  146. {gea_program-0.32.0 → gea_program-0.34.0}/gea/earth_model.py +0 -0
  147. {gea_program-0.32.0 → gea_program-0.34.0}/gea/elevation.py +0 -0
  148. {gea_program-0.32.0 → gea_program-0.34.0}/gea/etp.py +0 -0
  149. {gea_program-0.32.0 → gea_program-0.34.0}/gea/etp12_schemas.json +0 -0
  150. {gea_program-0.32.0 → gea_program-0.34.0}/gea/example_register_map.json +0 -0
  151. {gea_program-0.32.0 → gea_program-0.34.0}/gea/fat_sat.py +0 -0
  152. {gea_program-0.32.0 → gea_program-0.34.0}/gea/files.py +0 -0
  153. {gea_program-0.32.0 → gea_program-0.34.0}/gea/fixtures/README.md +0 -0
  154. {gea_program-0.32.0 → gea_program-0.34.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  155. {gea_program-0.32.0 → gea_program-0.34.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  156. {gea_program-0.32.0 → gea_program-0.34.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  157. {gea_program-0.32.0 → gea_program-0.34.0}/gea/follower.py +0 -0
  158. {gea_program-0.32.0 → gea_program-0.34.0}/gea/forward_model.py +0 -0
  159. {gea_program-0.32.0 → gea_program-0.34.0}/gea/friction_priors.py +0 -0
  160. {gea_program-0.32.0 → gea_program-0.34.0}/gea/gamma.py +0 -0
  161. {gea_program-0.32.0 → gea_program-0.34.0}/gea/gauge_aging.py +0 -0
  162. {gea_program-0.32.0 → gea_program-0.34.0}/gea/gauge_specs.py +0 -0
  163. {gea_program-0.32.0 → gea_program-0.34.0}/gea/geodesy.py +0 -0
  164. {gea_program-0.32.0 → gea_program-0.34.0}/gea/gravity_reference.py +0 -0
  165. {gea_program-0.32.0 → gea_program-0.34.0}/gea/hartip.py +0 -0
  166. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/TESTER_GUIDE.md +0 -0
  167. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/alarms.md +0 -0
  168. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/audit-update.md +0 -0
  169. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/backup.md +0 -0
  170. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/catalog.md +0 -0
  171. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/conformance.md +0 -0
  172. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/data-in.md +0 -0
  173. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/dlis.md +0 -0
  174. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/doctor.md +0 -0
  175. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/drift.md +0 -0
  176. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/edr.md +0 -0
  177. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/etp.md +0 -0
  178. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/field-ports.md +0 -0
  179. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/files.md +0 -0
  180. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/historian.md +0 -0
  181. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/instruments.md +0 -0
  182. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/interval.md +0 -0
  183. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/month-end.md +0 -0
  184. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/notifications.md +0 -0
  185. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/osdu.md +0 -0
  186. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/patches.md +0 -0
  187. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/ppdm.md +0 -0
  188. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/quakeml.md +0 -0
  189. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/quality.md +0 -0
  190. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/seedlink.md +0 -0
  191. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/seismic.md +0 -0
  192. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/setup.md +0 -0
  193. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/site.md +0 -0
  194. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/sites.md +0 -0
  195. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/slip.md +0 -0
  196. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/soak.md +0 -0
  197. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/sra.md +0 -0
  198. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/start.md +0 -0
  199. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/structure.md +0 -0
  200. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/tools.md +0 -0
  201. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/transients.md +0 -0
  202. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/upkeep.md +0 -0
  203. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/vibration.md +0 -0
  204. {gea_program-0.32.0 → gea_program-0.34.0}/gea/help/well-tests.md +0 -0
  205. {gea_program-0.32.0 → gea_program-0.34.0}/gea/housekeeping.py +0 -0
  206. {gea_program-0.32.0 → gea_program-0.34.0}/gea/interval.py +0 -0
  207. {gea_program-0.32.0 → gea_program-0.34.0}/gea/inverse_engine.py +0 -0
  208. {gea_program-0.32.0 → gea_program-0.34.0}/gea/jobs.py +0 -0
  209. {gea_program-0.32.0 → gea_program-0.34.0}/gea/live_ports.py +0 -0
  210. {gea_program-0.32.0 → gea_program-0.34.0}/gea/loadtest.py +0 -0
  211. {gea_program-0.32.0 → gea_program-0.34.0}/gea/matplotlib_demo.py +0 -0
  212. {gea_program-0.32.0 → gea_program-0.34.0}/gea/modbus.py +0 -0
  213. {gea_program-0.32.0 → gea_program-0.34.0}/gea/modbus_rtu.py +0 -0
  214. {gea_program-0.32.0 → gea_program-0.34.0}/gea/model_card.py +0 -0
  215. {gea_program-0.32.0 → gea_program-0.34.0}/gea/month_end.py +0 -0
  216. {gea_program-0.32.0 → gea_program-0.34.0}/gea/mqtt_port.py +0 -0
  217. {gea_program-0.32.0 → gea_program-0.34.0}/gea/notify.py +0 -0
  218. {gea_program-0.32.0 → gea_program-0.34.0}/gea/opcua_port.py +0 -0
  219. {gea_program-0.32.0 → gea_program-0.34.0}/gea/operator_app.py +0 -0
  220. {gea_program-0.32.0 → gea_program-0.34.0}/gea/osdu.py +0 -0
  221. {gea_program-0.32.0 → gea_program-0.34.0}/gea/patches.py +0 -0
  222. {gea_program-0.32.0 → gea_program-0.34.0}/gea/permits.py +0 -0
  223. {gea_program-0.32.0 → gea_program-0.34.0}/gea/piwebapi.py +0 -0
  224. {gea_program-0.32.0 → gea_program-0.34.0}/gea/ports.py +0 -0
  225. {gea_program-0.32.0 → gea_program-0.34.0}/gea/ppdm.py +0 -0
  226. {gea_program-0.32.0 → gea_program-0.34.0}/gea/profile_catalog.py +0 -0
  227. {gea_program-0.32.0 → gea_program-0.34.0}/gea/project.py +0 -0
  228. {gea_program-0.32.0 → gea_program-0.34.0}/gea/qt6_downhole_app.py +0 -0
  229. {gea_program-0.32.0 → gea_program-0.34.0}/gea/quakeml.py +0 -0
  230. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reconciler.py +0 -0
  231. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  232. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  233. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  234. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  235. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  236. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  237. {gea_program-0.32.0 → gea_program-0.34.0}/gea/reference/tool_sources.json +0 -0
  238. {gea_program-0.32.0 → gea_program-0.34.0}/gea/rock_inventory.py +0 -0
  239. {gea_program-0.32.0 → gea_program-0.34.0}/gea/sample_record.py +0 -0
  240. {gea_program-0.32.0 → gea_program-0.34.0}/gea/sample_well_profile.csv +0 -0
  241. {gea_program-0.32.0 → gea_program-0.34.0}/gea/sbom.py +0 -0
  242. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seedlink.py +0 -0
  243. {gea_program-0.32.0 → gea_program-0.34.0}/gea/segy.py +0 -0
  244. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic.py +0 -0
  245. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_array.py +0 -0
  246. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_assoc.py +0 -0
  247. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_detect.py +0 -0
  248. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_film.py +0 -0
  249. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_harmonic.py +0 -0
  250. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_qc.py +0 -0
  251. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_response.py +0 -0
  252. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_signature.py +0 -0
  253. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_track.py +0 -0
  254. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_uncertainty.py +0 -0
  255. {gea_program-0.32.0 → gea_program-0.34.0}/gea/seismic_unlisted.py +0 -0
  256. {gea_program-0.32.0 → gea_program-0.34.0}/gea/sensor_swap.py +0 -0
  257. {gea_program-0.32.0 → gea_program-0.34.0}/gea/service_life.py +0 -0
  258. {gea_program-0.32.0 → gea_program-0.34.0}/gea/setup_checklist.py +0 -0
  259. {gea_program-0.32.0 → gea_program-0.34.0}/gea/shapefile.py +0 -0
  260. {gea_program-0.32.0 → gea_program-0.34.0}/gea/shell.py +0 -0
  261. {gea_program-0.32.0 → gea_program-0.34.0}/gea/shut_in.py +0 -0
  262. {gea_program-0.32.0 → gea_program-0.34.0}/gea/sla_report.py +0 -0
  263. {gea_program-0.32.0 → gea_program-0.34.0}/gea/slip.py +0 -0
  264. {gea_program-0.32.0 → gea_program-0.34.0}/gea/soak.py +0 -0
  265. {gea_program-0.32.0 → gea_program-0.34.0}/gea/sra.py +0 -0
  266. {gea_program-0.32.0 → gea_program-0.34.0}/gea/store_forward.py +0 -0
  267. {gea_program-0.32.0 → gea_program-0.34.0}/gea/strata_join.py +0 -0
  268. {gea_program-0.32.0 → gea_program-0.34.0}/gea/structure.py +0 -0
  269. {gea_program-0.32.0 → gea_program-0.34.0}/gea/supervisor.py +0 -0
  270. {gea_program-0.32.0 → gea_program-0.34.0}/gea/survey_cmd.py +0 -0
  271. {gea_program-0.32.0 → gea_program-0.34.0}/gea/survey_view.py +0 -0
  272. {gea_program-0.32.0 → gea_program-0.34.0}/gea/telemetry.py +0 -0
  273. {gea_program-0.32.0 → gea_program-0.34.0}/gea/tool_catalog.py +0 -0
  274. {gea_program-0.32.0 → gea_program-0.34.0}/gea/tool_library.py +0 -0
  275. {gea_program-0.32.0 → gea_program-0.34.0}/gea/tool_taxonomy.py +0 -0
  276. {gea_program-0.32.0 → gea_program-0.34.0}/gea/transient.py +0 -0
  277. {gea_program-0.32.0 → gea_program-0.34.0}/gea/vibration.py +0 -0
  278. {gea_program-0.32.0 → gea_program-0.34.0}/gea/well_assembler.py +0 -0
  279. {gea_program-0.32.0 → gea_program-0.34.0}/gea/well_test_validation.py +0 -0
  280. {gea_program-0.32.0 → gea_program-0.34.0}/gea/wits0.py +0 -0
  281. {gea_program-0.32.0 → gea_program-0.34.0}/gea/witsml.py +0 -0
  282. {gea_program-0.32.0 → gea_program-0.34.0}/gea_program.egg-info/dependency_links.txt +0 -0
  283. {gea_program-0.32.0 → gea_program-0.34.0}/gea_program.egg-info/entry_points.txt +0 -0
  284. {gea_program-0.32.0 → gea_program-0.34.0}/gea_program.egg-info/requires.txt +0 -0
  285. {gea_program-0.32.0 → gea_program-0.34.0}/gea_program.egg-info/top_level.txt +0 -0
  286. {gea_program-0.32.0 → gea_program-0.34.0}/setup.cfg +0 -0
  287. {gea_program-0.32.0 → gea_program-0.34.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.32.0
3
+ Version: 0.34.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
@@ -125,7 +125,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
125
125
  gea client-report --report accuracy --out client_report
126
126
  gea model-cards --out model_cards
127
127
  gea sbom --out sbom
128
- gea accept # the product gate (383 checks)
128
+ gea accept # the product gate (389 checks)
129
129
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
130
130
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
131
131
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -648,6 +648,39 @@ 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
+ ### The site map
652
+
653
+ The Map tab puts everything in the workspace that has a position in one
654
+ WGS84 frame: the wells at their declared surface positions (a NAD27
655
+ declaration moved by its datum shift, the shift shown), the structure layers
656
+ - faults and folds, formation contours, areas, points - each drawn at the
657
+ detail the view needs without a vertex moved, the World Stress Map's SHmax
658
+ directions, the seismic stations and the Seismicity Response Areas, over the
659
+ outline of the US states and counties. The outline ships with the program
660
+ (US Census Bureau 2017 cartographic boundaries by way of us-atlas, with its
661
+ provenance), so the map needs no network. A well without a declared position
662
+ is listed as not placed and is never drawn. Drag, the wheel or the keys pan
663
+ and zoom; click anywhere for what every layer says about that spot and the
664
+ distance to every placed well; rings draw distance circles around the wells.
665
+ `gea help map`.
666
+
667
+ ### The charts
668
+
669
+ A well's page carries the Charts card: up to four channels stacked on one
670
+ horizontal axis, a cursor that reads them all at the same moment, cut from
671
+ every sample of the well's file. Drag across a stretch to zoom into it, the
672
+ wheel zooms where you point, double-click or Reset shows everything; the
673
+ keyboard does the same (arrows, + and -, Page Up and Page Down, Home). A
674
+ month-wide view keeps a one-second spike - each bucket is the minimum, the
675
+ maximum and the mean of its samples - and a stretch with no samples is drawn
676
+ empty, never bridged. On a drilling recorder's export the axis switches
677
+ between time and depth (the drilling samples by bit depth, kept off hole a
678
+ later sidetrack abandoned), the depth events are marked where they
679
+ happened, and each section of the recording is a button. The Wells page
680
+ carries the drilling progress of every drilling-recorder well: hole depth
681
+ against days, one colour per well. The refresh writes a chart cache per well
682
+ from the read it already makes; `gea help charts`.
683
+
651
684
  ### The setup checklist
652
685
 
653
686
  The Setup tab (`#/setup`, `gea checklist --workspace C:\site`) says what a
@@ -86,7 +86,7 @@ gea dashboard --catalog-well volve_f12_f14_production_excerpt:15/9-F-12:10000 --
86
86
  gea client-report --report accuracy --out client_report
87
87
  gea model-cards --out model_cards
88
88
  gea sbom --out sbom
89
- gea accept # the product gate (383 checks)
89
+ gea accept # the product gate (389 checks)
90
90
  gea help drift # the help library, by the job (16 pages; the same text is on every dashboard page)
91
91
  gea guide # the click-by-click tester guide (docs/TESTER_GUIDE.md)
92
92
  gea gui # the desktop window (pip install "gea-program[desktop]")
@@ -609,6 +609,39 @@ 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
+ ### The site map
613
+
614
+ The Map tab puts everything in the workspace that has a position in one
615
+ WGS84 frame: the wells at their declared surface positions (a NAD27
616
+ declaration moved by its datum shift, the shift shown), the structure layers
617
+ - faults and folds, formation contours, areas, points - each drawn at the
618
+ detail the view needs without a vertex moved, the World Stress Map's SHmax
619
+ directions, the seismic stations and the Seismicity Response Areas, over the
620
+ outline of the US states and counties. The outline ships with the program
621
+ (US Census Bureau 2017 cartographic boundaries by way of us-atlas, with its
622
+ provenance), so the map needs no network. A well without a declared position
623
+ is listed as not placed and is never drawn. Drag, the wheel or the keys pan
624
+ and zoom; click anywhere for what every layer says about that spot and the
625
+ distance to every placed well; rings draw distance circles around the wells.
626
+ `gea help map`.
627
+
628
+ ### The charts
629
+
630
+ A well's page carries the Charts card: up to four channels stacked on one
631
+ horizontal axis, a cursor that reads them all at the same moment, cut from
632
+ every sample of the well's file. Drag across a stretch to zoom into it, the
633
+ wheel zooms where you point, double-click or Reset shows everything; the
634
+ keyboard does the same (arrows, + and -, Page Up and Page Down, Home). A
635
+ month-wide view keeps a one-second spike - each bucket is the minimum, the
636
+ maximum and the mean of its samples - and a stretch with no samples is drawn
637
+ empty, never bridged. On a drilling recorder's export the axis switches
638
+ between time and depth (the drilling samples by bit depth, kept off hole a
639
+ later sidetrack abandoned), the depth events are marked where they
640
+ happened, and each section of the recording is a button. The Wells page
641
+ carries the drilling progress of every drilling-recorder well: hole depth
642
+ against days, one colour per well. The refresh writes a chart cache per well
643
+ from the read it already makes; `gea help charts`.
644
+
612
645
  ### The setup checklist
613
646
 
614
647
  The Setup tab (`#/setup`, `gea checklist --workspace C:\site`) says what a
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.32.0"
126
+ __version__ = "0.34.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -8170,6 +8170,284 @@ def section_ca_setup_checklist(tmp: str) -> None:
8170
8170
  "request; the help page and its view mapping"
8171
8171
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
8172
8172
 
8173
+
8174
+ def section_cb_charts(tmp: str) -> None:
8175
+ """Section CB - the interactive charts: a window cut from every sample through the series cache, an envelope that
8176
+ keeps a one-second spike in a month-wide view and never bridges a gap, the depth axis for a drilling recorder from
8177
+ the same samples as the gamma log, the drilling progress of every well, the page's zoom, pan, cursor and keys."""
8178
+ import http.cookiejar
8179
+ import os as _os
8180
+ import urllib.request
8181
+ import urllib.error
8182
+ import numpy as _np
8183
+ from . import edr as E
8184
+ from . import series_cache as SCH
8185
+ from . import backup as BK
8186
+ from . import helplib as H
8187
+ from .workspace import Workspace
8188
+ from .service import Service, Users
8189
+ pkg = Path(__file__).parent
8190
+ d = Path(tmp, "cb"); d.mkdir()
8191
+
8192
+ ev = E.selftest_events(str(d))
8193
+ st = E.read(ev["path"])
8194
+ meta = SCH.build(st, str(d / "c"), extras=E.chart_masks(st))
8195
+ c = SCH.load(str(d / "c"), ev["path"])
8196
+ idx = _np.asarray(c.index); rop = _np.asarray(c.channel("Rate Of Penetration"), dtype=float)
8197
+ w = SCH.window(c, ["Rate Of Penetration", "Hole Depth"], points=300)
8198
+ edges = _np.linspace(idx[0], idx[-1], 301); b = _np.clip(_np.searchsorted(edges, idx, "right") - 1, 0, 299)
8199
+ brute_ok = True
8200
+ for k in range(300):
8201
+ m = (b == k) & ~_np.isnan(rop)
8202
+ got = w["channels"]["Rate Of Penetration"]
8203
+ if m.any():
8204
+ if got["max"][k] is None or abs(got["max"][k] - rop[m].max()) > 1e-3 or abs(got["min"][k] - rop[m].min()) > 1e-3 or abs(got["mean"][k] - rop[m].mean()) > 1e-2 or got["n"][k] != int(m.sum()):
8205
+ brute_ok = False; break
8206
+ elif got["max"][k] is not None:
8207
+ brute_ok = False; break
8208
+ gaps = sum(1 for x in w["channels"]["Rate Of Penetration"]["mean"] if x is None)
8209
+ spike = st.channels["Rate Of Penetration"].values.copy()
8210
+ w_small = SCH.window(c, ["Rate Of Penetration"], x0=float(idx[1300]), x1=float(idx[1300]) + 300)
8211
+ dw = SCH.depth_window(c, ["Gamma"], points=200)
8212
+ pr = SCH.progress(c)
8213
+ cons = SCH.connections(c, float(idx[0]), float(idx[-1]))
8214
+ stale_size = _os.path.getsize(ev["path"])
8215
+ with open(ev["path"], "a", encoding="utf-8") as fh:
8216
+ fh.write("")
8217
+ _os.utime(ev["path"], (1, 1))
8218
+ stale = SCH.load(str(d / "c"), ev["path"])
8219
+ c1 = {
8220
+ "cache": meta["n"] == 34720 and {"drilling", "keep_b", "keep_h", "excluded", "bottom", "flag_idle"} <= set(meta["extras"])
8221
+ and len(meta["sections"]) == 2 and _os.path.isfile(str(d / "c" / "index.npy")),
8222
+ "envelope": w["mode"] == "envelope" and len(w["x"]) == 300 and w["samples"] == 34720 and brute_ok and gaps > 200,
8223
+ "samples": w_small["mode"] == "samples" and len(w_small["x"]) == 301 and w_small["channels"]["Rate Of Penetration"]["value"][0] is not None,
8224
+ "depth": dw["axis"] == "Bit Depth" and dw["extent"] == [4800.0, 5750.0] and dw["samples"] == 27420
8225
+ and all(v is None or abs(v - 110.0) < 1e-6 for v in dw["channels"]["Gamma"]["mean"]) and "abandoned" in dw["how"],
8226
+ "progress": pr is not None and pr["first_utc"] == "2026-07-01T00:00:00" and max(v for v in pr["bottom_max"] if v is not None) == 5750.0
8227
+ and abs(pr["days"][-1] - 2.4) < 0.01,
8228
+ "connections": len(cons) == 3,
8229
+ "stale": stale is None and stale_size == _os.path.getsize(ev["path"]),
8230
+ }
8231
+ failed1 = [k for k, v in c1.items() if not v]
8232
+ ok(not failed1,
8233
+ "CB1 the series cache: every sample of a drilling recorder export with the depth pass's masks and the running bottom; a window of equal time "
8234
+ "buckets whose minimum, maximum, mean and count equal the samples' own, a bucket with none left empty (a gap, never bridged), the samples "
8235
+ "themselves when they fit; the depth axis the drilling samples by bit depth kept off the abandoned hole (4,800 to 5,750 ft, the gamma log's "
8236
+ "range); the drilling progress the running bottom against days; a cache whose source changed is not used"
8237
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
8238
+
8239
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
8240
+ w1 = ws.add_well_file(ev["path"], display="Well B", actor="tester")
8241
+ w2 = ws.add_well_catalog("volve_f12_f14_production_excerpt", "15/9-F-12", 10000.0, actor="tester")
8242
+ ws.refresh_dashboard(actor="tester")
8243
+ built = _os.path.isfile(_os.path.join(wsp, "cache", "series", w1["id"], "x_drilling.npy"))
8244
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer")
8245
+ svc = Service(wsp, port=0, scheduler=False).start()
8246
+ base_url = svc.url.rstrip("/")
8247
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
8248
+
8249
+ def call(method, path, body=None):
8250
+ req = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
8251
+ req.add_header("Content-Type", "application/json"); req.add_header("X-GEA-Action", "1")
8252
+ try:
8253
+ with o.open(req, timeout=120) as rr_:
8254
+ return rr_.status, json.loads(rr_.read())
8255
+ except urllib.error.HTTPError as e:
8256
+ return e.code, json.loads(e.read())
8257
+ q = lambda s_: urllib.parse.quote(s_)
8258
+ try:
8259
+ call("POST", "/api/login", {"name": "v", "password": "viewer-pass-1"})
8260
+ st_m, m1 = call("GET", f"/api/wells/{w1['id']}/chart/meta")
8261
+ st_w, wt = call("GET", f"/api/wells/{w1['id']}/chart/window?ch={q('Rate Of Penetration')}&ch={q('Hole Depth')}&points=400")
8262
+ st_z, wz = call("GET", f"/api/wells/{w1['id']}/chart/window?ch={q('Rate Of Penetration')}&x0=172800&x1=180000&points=400")
8263
+ st_d, wd = call("GET", f"/api/wells/{w1['id']}/chart/window?ch=Gamma&axis=depth&points=200")
8264
+ st_p, pp = call("GET", "/api/pad/progress")
8265
+ st_g, mg = call("GET", f"/api/wells/{w2['id']}/chart/meta")
8266
+ st_bad, bad = call("GET", f"/api/wells/{w1['id']}/chart/window?ch=Nothing")
8267
+ st_none, none_ = call("GET", f"/api/wells/{w1['id']}/chart/window")
8268
+ st_x, _ = call("GET", "/api/wells/no-such-well/chart/meta")
8269
+ finally:
8270
+ svc.stop()
8271
+ c2 = {
8272
+ "refresh_builds": built and not any(f.startswith("cache/") for f in BK.site_files(wsp)),
8273
+ "meta": st_m == 200 and m1["axes"] == ["time", "depth"] and m1["n"] == 34720 and len(m1["events"]) == 4 and m1["events"][2]["type"] == "SIDETRACK"
8274
+ and m1["events"][0]["x"] > 0 and len(m1["sections"]) == 2 and m1["default"] == ["Rate Of Penetration"],
8275
+ "window": st_w == 200 and wt["mode"] == "envelope" and set(wt["channels"]) == {"Rate Of Penetration", "Hole Depth"} and st_z == 200
8276
+ and wz["mode"] == "envelope" and "connections" in wz and wz["x0"] == 172800.0,
8277
+ "depth": st_d == 200 and wd["extent"] == [4800.0, 5750.0],
8278
+ "progress": st_p == 200 and [x["well_id"] for x in pp["wells"]] == [w1["id"]] and pp["wells"][0]["display"] == "Well B",
8279
+ "gauge_well": st_g == 200 and mg["axes"] == ["time_s"] and mg["n"] > 0 and mg["events"] == [],
8280
+ "errors": st_bad == 400 and "no channel 'Nothing'" in bad.get("error", "") and st_none == 400 and "name one or more channels" in none_.get("error", "") and st_x == 404,
8281
+ }
8282
+ failed2 = [k for k, v in c2.items() if not v]
8283
+ ok(not failed2,
8284
+ "CB2 in a site: the refresh writes each file well's chart cache from the read it already makes, and backups leave it out; the API gives a "
8285
+ "signed-in viewer the channels, the axes, the depth events at their times, the sections, a time window with the connections when zoomed in, "
8286
+ "the depth window and every drilling-recorder well's progress; a gauge well's cache is built on its first view; a channel the well does not "
8287
+ "have, no channel at all and an unknown well are named"
8288
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
8289
+
8290
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
8291
+ help_md = (pkg / "help" / "charts.md").read_text(encoding="utf-8")
8292
+ c3 = {
8293
+ "page": all(x in page_src for x in ("async function chartPanel", "async function padProgress", "/chart/meta", "/chart/window", "/api/pad/progress",
8294
+ 'data-axis="depth"', "data-sec=", "PageUp", "'Home' || ev.key === 'Escape'", "ArrowLeft", "dblclick", "'wheel'",
8295
+ "setPointerCapture", "ResizeObserver", "cp-tbl", 'aria-live="polite"', "role=\"application\"", "Drilling progress",
8296
+ "#/help/charts", "cp-loading")),
8297
+ "palette": page_src.count("--s1:") == 3 and page_src.count("--s3:") == 3,
8298
+ "help": any(t["topic"] == "charts" for t in H.topics()) and not H.check("charts") and "never bridged" in help_md.split("will not call a measurement")[1],
8299
+ }
8300
+ failed3 = [k for k, v in c3.items() if not v]
8301
+ ok(not failed3,
8302
+ "CB3 the page: up to four tracks on one axis, a cursor that reads every track at the same moment, drag to zoom, the wheel, double-click and "
8303
+ "Reset, pan, the keys (arrows, + and -, Page Up and Page Down, Home and Esc), time or depth, the section buttons, the table view, the frame "
8304
+ "kept while a window loads, the drilling progress chart with one colour per well in a fixed order in both themes; the help page"
8305
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
8306
+
8307
+
8308
+ def section_cc_site_map(tmp: str) -> None:
8309
+ """Section CC - the site map: the shipped state and county outline with its provenance, every positioned thing in one
8310
+ WGS84 frame (a NAD27 well moved by its datum shift and saying so, a well without a position never drawn), a layer
8311
+ thinned to the view's pixel without a vertex moved, what every layer says about a spot, the page."""
8312
+ import hashlib as _hl
8313
+ import http.cookiejar
8314
+ import math as _m
8315
+ import urllib.request
8316
+ import urllib.error
8317
+ from . import edr as E
8318
+ from . import helplib as H
8319
+ from . import shapefile as SF
8320
+ from . import site_map as SM
8321
+ from .workspace import Workspace
8322
+ from .service import Service, Users
8323
+ pkg = Path(__file__).parent
8324
+ d = Path(tmp, "cc"); d.mkdir()
8325
+
8326
+ ol = SM.outline_data()
8327
+ prov = json.loads((pkg / "reference" / "us_outline.provenance.json").read_text(encoding="utf-8"))
8328
+ cul = [c for c in ol["counties"] if c["name"] == "Culberson"]
8329
+ wide = SM.outline_view([-110.0, 25.0, -90.0, 37.0], 0.02)
8330
+ near = SM.outline_view([-105.2, 31.0, -104.0, 32.0], 0.002)
8331
+ c1 = {
8332
+ "outline": len(ol["counties"]) == 3231 and ol["states"].get("48") == "Texas" and cul and cul[0]["fips"] == "48109"
8333
+ and abs(cul[0]["label"][0] + 104.65) < 0.2 and abs(cul[0]["label"][1] - 31.47) < 0.2,
8334
+ "provenance": prov["sha256"] == _hl.sha256((pkg / "reference" / "us_outline.json").read_bytes()).hexdigest() and prov["package_license"] == "ISC"
8335
+ and "Copyright 2013-2019 Michael Bostock" in prov["package_license_text"] and "public domain" in prov["original_source"]
8336
+ and prov["counts"]["counties"] == 3231,
8337
+ "detail": all(a["s"] == 1 for a in wide["arcs"]) and not wide["labels"] and any(a["s"] == 0 for a in near["arcs"])
8338
+ and "Culberson" in [x["name"] for x in near["labels"]],
8339
+ }
8340
+ failed1 = [k for k, v in c1.items() if not v]
8341
+ ok(not failed1,
8342
+ "CC1 the outline ships with the program: the US Census Bureau's 2017 cartographic counties by way of us-atlas 3.0.1, 3,231 counties, the "
8343
+ "provenance with its hash, the package's ISC notice and the public-domain source; state lines only in a wide view, county lines and names "
8344
+ "once the view is close (Culberson, FIPS 48109)"
8345
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
8346
+
8347
+ lat1, lon1 = 31.42, -104.10
8348
+ kmlon = 1.0 / (111.195 * _m.cos(_m.radians(lat1)))
8349
+ faults = [([[(lon1 - 0.2 + 0.01 * i, lat1 - 0.3 + 0.0002 * j) for j in range(3001)]], {"TYPE": "normal fault", "NAME": f"F{i}"}) for i in range(0, 40, 8)]
8350
+ SF.write(str(d / "Faults"), "PolyLine", faults, [("TYPE", "C", 20, 0), ("NAME", "C", 10, 0)], SF.NAD83_GEOGCS)
8351
+ ctr = [([[(lon1 + (i - 1.5) * 4 * kmlon + 0.001 * k, lat1 - 0.3 + 0.006 * k) for k in range(100)]], {"ElevTop_ft": -8500.0 + 500.0 * i}) for i in range(4)]
8352
+ SF.write(str(d / "Top_A"), "PolyLine", ctr, [("ElevTop_ft", "F", 10, 1)], SF.NAD83_GEOGCS)
8353
+ SF.write(str(d / "Picks"), "Point", [([[(lon1 + 0.0001 * i, lat1 + 0.00005 * i)]], {"NAME": f"P{i}"}) for i in range(6000)], [("NAME", "C", 10, 0)], SF.NAD83_GEOGCS)
8354
+ with open(d / "wsm.csv", "w", encoding="utf-8") as f:
8355
+ f.write("ID,LAT,LON,AZI,TYPE,DEPTH,QUALITY,REGIME,LOCALITY\n")
8356
+ for i in range(40):
8357
+ f.write(f"W{i},{lat1 - 0.1 + 0.005 * i:.4f},{lon1 - 0.05:.4f},{60 + i},BO,2.5,{'DCBA'[i % 4]},NF,test\n")
8358
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad 3", actor="tester")
8359
+ empty_view = SM.overview(ws)["view"]
8360
+ ws.add_layer(str(d / "Faults.shp"), "Basement faults", actor="tester", citation="test")
8361
+ ws.add_layer(str(d / "Top_A.shp"), "Wolfcamp top", actor="tester", formation="Wolfcamp", role="top", citation="test")
8362
+ ws.add_layer(str(d / "Picks.shp"), "Picks", actor="tester", citation="test")
8363
+ ws.add_stress_map(str(d / "wsm.csv"), actor="tester", citation="test WSM")
8364
+ layer_view0 = SM.overview(ws)["view"]
8365
+ a = E.selftest(str(d)); b = E.selftest_events(str(d))
8366
+ wa = ws.add_well_file(a["path"], display="Well A", actor="tester"); wb = ws.add_well_file(b["path"], display="Well B", actor="tester")
8367
+ wc = ws.add_well_file(a["path"], display="Well C", actor="tester")
8368
+ ws.set_well(wa["id"], surface={"lat": 31.41, "lon": -104.12, "datum": "NAD27"}, actor="tester")
8369
+ ws.set_well(wb["id"], surface={"lat": 31.43, "lon": -104.08, "datum": "NAD83"}, actor="tester")
8370
+ o = SM.overview(ws)
8371
+ wl = {w["id"]: w for w in o["wells"]}
8372
+ view = o["view"]["bbox"]
8373
+ fine = SM.layer_view(ws, "Basement-faults", [-105, 30, -103, 32], 0.0)
8374
+ coarse = SM.layer_view(ws, "Basement-faults", [-105, 30, -103, 32], 0.01)
8375
+ raw = SM._layer(ws, "Basement-faults")
8376
+ src_pts = {(round(float(x), 5), round(float(y), 5)) for f in raw["feats"] for pp in f["parts"] for x, y in pp}
8377
+ moved = [p for f in coarse["features"] for pp in f["parts"] for p in pp if (p[0], p[1]) not in src_pts]
8378
+ ends_kept = all(f["parts"][0][0] == fine["features"][k]["parts"][0][0] and f["parts"][0][-1] == fine["features"][k]["parts"][0][-1] for k, f in enumerate(coarse["features"]))
8379
+ pts = SM.layer_view(ws, "Picks", [-105, 30, -103, 32], 0.0, max_points=1000)
8380
+ wh = SM.what_here(ws, 31.42, -104.10)
8381
+ sv = SM.stress_view(ws, [-104.3, 31.2, -103.9, 31.6])
8382
+ c2 = {
8383
+ "frame": "WGS84" in o["frame"] and o["view"]["why"] == "the placed wells" and empty_view["why"].startswith("nothing in the workspace")
8384
+ and layer_view0["why"].startswith("the area most of the layers cover (the median extent of 3 of the 3)"),
8385
+ "wells": wl[wa["id"]]["placed"] and 30.0 < wl[wa["id"]]["shift_m"] < 90.0 and wl[wa["id"]]["declared"]["datum"] == "NAD27"
8386
+ and wl[wb["id"]]["placed"] and wl[wb["id"]]["shift_m"] == 0.0 and not wl[wc["id"]]["placed"] and "lat" not in wl[wc["id"]]
8387
+ and view[0] < -104.12 < view[2] and view[1] < 31.43 < view[3],
8388
+ "layers": [(l["id"], l["kind"]) for l in o["layers"]] == [("Basement-faults", "structure"), ("Wolfcamp-top", "contours"), ("Picks", "points")]
8389
+ and o["layers"][0]["classes"] == ["fault"] and o["layers"][0]["n_points"] == 5 * 3001 and o["stress_map"]["n_records"] == 40,
8390
+ "thinning": fine["vertices"] == 5 * 3001 and coarse["vertices"] < fine["vertices"] / 10 and not moved and ends_kept
8391
+ and all(f.get("cls") == "fault" for f in coarse["features"]),
8392
+ "points": pts["in_view"] == 6000 and pts["shown"] == 1000 and pts["kind"] == "points",
8393
+ "what_here": wh["answered"] >= 2 and any("Nearest mapped fault" in ln for l_ in wh["layers"] for ln in (l_["lines"] or []))
8394
+ and any("contours" in ln for l_ in wh["layers"] for ln in (l_["lines"] or []))
8395
+ and [w["id"] for w in wh["wells"]] == sorted([wa["id"], wb["id"]], key=lambda i: next(x["distance_km"] for x in wh["wells"] if x["id"] == i))
8396
+ and len(wh["wells"]) == 2,
8397
+ "stress": sv["n_in_view"] == 40 and sv["records"][0]["quality"] == "A" and sv["records"][-1]["quality"] == "D" and sv["citation"] == "test WSM",
8398
+ }
8399
+ failed2 = [k for k, v in c2.items() if not v]
8400
+ ok(not failed2,
8401
+ "CC2 one frame: a well declared on NAD27 moved to WGS84 by its datum shift and the shift kept with it, a well declared on NAD83 not moved, a "
8402
+ "well with no position never placed; the map opens on the placed wells (else the area most layers cover, else Texas, said so); a layer "
8403
+ "thinned to the view's pixel keeps its ends and moves no vertex, points sampled evenly past the limit; each layer's own statement for a spot "
8404
+ "and the distance to every placed well; the stress records in view, best quality first"
8405
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
8406
+
8407
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer")
8408
+ svc = Service(wsp, port=0, scheduler=False).start()
8409
+ base_url = svc.url.rstrip("/")
8410
+ op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
8411
+
8412
+ def call(path):
8413
+ req = urllib.request.Request(base_url + path, method="GET")
8414
+ try:
8415
+ with op.open(req, timeout=120) as rr_:
8416
+ return rr_.status, json.loads(rr_.read())
8417
+ except urllib.error.HTTPError as e:
8418
+ return e.code, json.loads(e.read())
8419
+ try:
8420
+ lreq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": "v", "password": "viewer-pass-1"}).encode())
8421
+ lreq.add_header("Content-Type", "application/json"); lreq.add_header("X-GEA-Action", "1"); op.open(lreq, timeout=30)
8422
+ bb = ",".join(f"{x:.5f}" for x in view)
8423
+ st_o, ov = call("/api/map")
8424
+ st_l, lv = call(f"/api/map/layer/Wolfcamp-top?bbox={bb}&tol=0.001")
8425
+ st_ol, olv = call(f"/api/map/outline?bbox={bb}&tol=0.001")
8426
+ st_s, svv = call(f"/api/map/stress?bbox={bb}")
8427
+ st_a, at = call("/api/map/at?lat=31.42&lon=-104.10")
8428
+ st_bad, _ = call("/api/map/layer/Wolfcamp-top?bbox=1,2,3")
8429
+ st_nol, _ = call(f"/api/map/layer/no-such-layer?bbox={bb}")
8430
+ st_noll, _ = call("/api/map/at?lat=x")
8431
+ finally:
8432
+ svc.stop()
8433
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
8434
+ help_md = (pkg / "help" / "map.md").read_text(encoding="utf-8")
8435
+ c3 = {
8436
+ "api": st_o == 200 and len(ov["wells"]) == 3 and st_l == 200 and lv["kind"] == "contours" and all("value" in f for f in lv["features"])
8437
+ and st_ol == 200 and st_s == 200 and st_a == 200 and len(at["wells"]) == 2 and st_bad == 400 and st_nol == 404 and st_noll == 400,
8438
+ "page": all(x in page_src for x in ("VIEWS.map", "['#/map', 'Map']", "/api/map/layer/", "/api/map/outline", "/api/map/stress", "/api/map/at",
8439
+ "Not placed", "declare it", "data-fit", "mpRings", "ArrowUp", "'Home' || k === 'Escape'", "scale bar", "'wheel'",
8440
+ "What is at", "setPointerCapture")),
8441
+ "help": any(t["topic"] == "map" for t in H.topics()) and not H.check("map") and H.VIEW_TOPIC.get("map") == "map"
8442
+ and "never moved" in help_md.split("will not call a measurement")[1],
8443
+ }
8444
+ failed3 = [k for k, v in c3.items() if not v]
8445
+ ok(not failed3,
8446
+ "CC3 the API (the contents, a layer with its contour values, the outline, the stress records, what is at a spot; a malformed box, an unknown "
8447
+ "layer and a missing position named) and the Map tab: pan and zoom by pointer, wheel and keys, layer toggles and fit, distance rings, a scale "
8448
+ "bar, the not-placed wells with the way to place them, what is at a click; the help page"
8449
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
8450
+
8173
8451
  def main() -> int:
8174
8452
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
8175
8453
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -8252,6 +8530,8 @@ def main() -> int:
8252
8530
  section_by_edr(tmp)
8253
8531
  section_bz_tool_library(tmp)
8254
8532
  section_ca_setup_checklist(tmp)
8533
+ section_cb_charts(tmp)
8534
+ section_cc_site_map(tmp)
8255
8535
  if _FAILS:
8256
8536
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
8257
8537
  for f in _FAILS:
@@ -28,7 +28,7 @@ from datetime import datetime, timezone
28
28
  from typing import Dict, List, Optional
29
29
 
30
30
  STALE_AFTER_H = 26.0 # a daily backup that is more than a day and change old
31
- EXCLUDE_TOP = ('jobs',) # taken selectively: schedule.json only
31
+ EXCLUDE_TOP = ('jobs', 'cache') # jobs taken selectively (schedule.json only); cache is rebuilt from the sources
32
32
  EXCLUDE_NAMES = ('_uploads', '__pycache__')
33
33
  LOG_NAME = 'backups.jsonl' # records/backups.jsonl: one line per backup taken from this workspace
34
34
 
@@ -347,8 +347,9 @@ def render(data: dict, out_dir: str, site_name: str = 'site', program_version: s
347
347
  def orchestrate(out_dir: str, catalog_wells: List[dict], file_wells: List[dict], td_ft: float = 10500.0,
348
348
  gauge_spec=None, outages: Optional[List[str]] = None, month: Optional[str] = None,
349
349
  site_name: str = 'site', criteria_path: Optional[str] = None, monitor_root: Optional[str] = None,
350
- run_sat: bool = False) -> dict:
351
- """catalog_wells: [{'entry', 'well', 'md_ft'}]; file_wells: [{'path', 'name'}]."""
350
+ run_sat: bool = False, series_cache_root: Optional[str] = None) -> dict:
351
+ """catalog_wells: [{'entry', 'well', 'md_ft'}]; file_wells: [{'path', 'name'}]. With `series_cache_root`, each file
352
+ well's stream is also cached for the charts, from the same read."""
352
353
  from . import (production_live_stream, ingest, Reconciler, SimulatorConfig, CATALOG, __version__ as _v)
353
354
  from .sample_record import TagCatalogue, records_from_stream
354
355
  from .client_reports import (gauge_drift_report, well_test_report, alarm_event_report, data_resilience_report,
@@ -401,16 +402,23 @@ def orchestrate(out_dir: str, catalog_wells: List[dict], file_wells: List[dict],
401
402
  continue
402
403
  with open(os.path.join(d, 'well.json'), 'w', encoding='utf-8') as f:
403
404
  json.dump({'display': name, 'source': fw['path'], 'index_kind': stream.index_kind, 'source_format': stream.source_format}, f)
405
+ cache_dir = os.path.join(series_cache_root, name) if series_cache_root else None
404
406
  if stream.source_format == 'edr': # a drilling recorder's export: the drilling record, not the gauge pipeline
405
407
  from .edr import write_record
406
408
  try:
407
- write_record(stream, d, _v)
409
+ write_record(stream, d, _v, cache_dir=cache_dir)
408
410
  made.setdefault(name, []).append('drilling_record')
409
411
  except Exception as ex:
410
412
  with open(os.path.join(d, 'drilling_record_not_written.json'), 'w', encoding='utf-8') as f:
411
413
  json.dump({'well': name, 'reason': f'{type(ex).__name__}: {ex}'}, f, indent=1)
412
414
  made.setdefault(name, []).append('drilling_record_not_written')
413
415
  continue
416
+ if cache_dir:
417
+ try:
418
+ from . import series_cache as _SCH
419
+ _SCH.build(stream, cache_dir)
420
+ except Exception:
421
+ pass # the cache is a convenience; the page builds it on first view
414
422
  if stream.index_kind != 'time_s': # a depth log is a survey input: run the strata survey, not the gauge pipeline
415
423
  try:
416
424
  from .survey_cmd import run_survey
@@ -322,6 +322,32 @@ def _idle_flag(drilling: np.ndarray, hole: Optional[np.ndarray], rop: Optional[n
322
322
  return idle
323
323
 
324
324
 
325
+ def drilling_mask(stream: LiveStream, de: dict) -> Tuple[np.ndarray, np.ndarray]:
326
+ """The samples that are drilling - the on-bottom flag set at the bottom of the hole, not excluded, not the flag
327
+ left on with nothing drilled - and, separately, the flag-left-on samples. One definition for the record and the charts."""
328
+ hole, rop, onb = _ch(stream, 'hole'), _ch(stream, 'rop'), _ch(stream, 'onb')
329
+ n = len(stream.index)
330
+ on_flag = (onb == 1.0) if onb is not None else np.zeros(n, dtype=bool)
331
+ excl = de['excluded']; M = de['running_bottom']
332
+ if hole is not None and M is not None:
333
+ drilling = on_flag & ~excl & ~np.isnan(hole) & (hole >= np.nan_to_num(M, nan=np.inf) - 10.0)
334
+ else:
335
+ drilling = on_flag & ~excl
336
+ idle = _idle_flag(drilling, hole, rop)
337
+ return drilling & ~idle, idle
338
+
339
+
340
+ def chart_masks(stream: LiveStream, de: Optional[dict] = None) -> dict:
341
+ """What the charts need from the depth pass, as arrays the length of the stream: drilling, the samples a depth
342
+ table may use (by hole depth and by bit depth), the excluded samples, and the running bottom of the hole."""
343
+ de = de if de is not None else depth_events(stream)
344
+ drilling, idle = drilling_mask(stream, de)
345
+ keep_h, keep_b = _keep_masks(stream, de)
346
+ n = len(stream.index)
347
+ bottom = de['running_bottom'] if de['running_bottom'] is not None else np.full(n, np.nan)
348
+ return {'drilling': drilling, 'flag_idle': idle, 'keep_h': keep_h, 'keep_b': keep_b, 'excluded': de['excluded'], 'bottom': bottom}
349
+
350
+
325
351
  def _keep_masks(stream: LiveStream, de: dict) -> Tuple[np.ndarray, np.ndarray]:
326
352
  """Samples a depth table may use: not excluded, and not in a borehole abandoned by a later sidetrack
327
353
  (by hole depth for the TVD table, by bit depth for the gamma log)."""
@@ -358,12 +384,7 @@ def record(stream: LiveStream, de: Optional[dict] = None) -> dict:
358
384
  out['notes'].append('channels this record needs are not in the file: ' + ', '.join(missing))
359
385
  on_flag = (onb == 1.0) if onb is not None else np.zeros(n, dtype=bool)
360
386
  excl = de['excluded']; M = de['running_bottom']; inc = de['increments']
361
- if hole is not None and M is not None:
362
- drilling = on_flag & ~excl & ~np.isnan(hole) & (hole >= np.nan_to_num(M, nan=np.inf) - 10.0)
363
- else:
364
- drilling = on_flag & ~excl
365
- idle = _idle_flag(drilling, hole, rop)
366
- drilling = drilling & ~idle
387
+ drilling, idle = drilling_mask(stream, de)
367
388
  rot = drilling & (rpm > 0) if rpm is not None else drilling
368
389
  prev_end = None
369
390
  for si, (a, b) in enumerate(sections, 1):
@@ -679,10 +700,14 @@ def document(rec: dict, program_version: str):
679
700
  data={k: v for k, v in rec.items() if k != 'lines'} | {'_records': [], '_catalogue_obj': None})
680
701
 
681
702
 
682
- def write_record(stream: LiveStream, out_dir: str, program_version: str) -> dict:
683
- """The record beside the well's other reports: drilling_record.json / .md / .html, and the MD-TVD table as CSV."""
703
+ def write_record(stream: LiveStream, out_dir: str, program_version: str, cache_dir: Optional[str] = None) -> dict:
704
+ """The record beside the well's other reports: drilling_record.json / .md / .html, and the MD-TVD table as CSV;
705
+ with `cache_dir`, the charts' series cache from the same read and the same depth pass."""
684
706
  from .client_reports import write
685
707
  de = depth_events(stream)
708
+ if cache_dir:
709
+ from . import series_cache as SCH
710
+ SCH.build(stream, cache_dir, extras=chart_masks(stream, de))
686
711
  rec = record(stream, de=de)
687
712
  os.makedirs(out_dir, exist_ok=True)
688
713
  paths = write(document(rec, program_version), out_dir, basename='drilling_record')
@@ -0,0 +1,11 @@
1
+ # The charts
2
+
3
+ > A well's channels, stacked on one horizontal axis with a cursor that reads them all at the same moment, cut from every sample of the well's file - not from a thinned copy. Drag across a stretch to zoom into it, wheel to zoom where you point, double-click to see everything again; on a drilling recorder's export the axis switches between time and depth, the depth events are marked where they happened, the recording's sections are a click each, and the Wells page carries the drilling progress of every well on one chart.
4
+
5
+ **The command**: the Charts card on a well's page. Choose up to four channels (one per track); point at the chart for the values at that moment - each channel's mean in the bucket under the cursor, with its minimum and maximum and how many samples it holds, or the sample itself when the window is close enough to show every one. Drag across a stretch to zoom into it; the wheel zooms where you point; + and − zoom, ◀ and ▶ pan by half a window, Reset (or double-click) shows everything. The keyboard does the same once the chart has focus: ← → move the cursor (Shift for ten steps), + and − zoom, Page Up and Page Down pan, Home or Esc resets. Time or Depth switches the axis on a drilling recorder; the section buttons zoom to one stretch of recording. Table shows the window as rows. On the Wells page, Drilling progress plots each drilling-recorder well's hole depth against days since its first sample; point at it for every well's depth on that day.
6
+
7
+ **What it writes**: a chart cache per well under `cache\series\` in the workspace, written by the refresh from the same read that writes the reports - the index, each channel, which samples were flagged, and for a drilling recorder the drilling samples, the samples a depth table may use, the excluded samples and the running bottom of the hole. It is a copy: it carries the source file's size and time and is rebuilt when they no longer match, the first view of a well with no cache builds it, and backups leave it out.
8
+
9
+ **The number to check**: the sample count under the chart against the file's rows (`gea edr --file`); a bucket's minimum and maximum against the raw rows for a short stretch (zoom until the chart says every sample is shown); the depth axis's range against the gamma log's.
10
+
11
+ **What this page will not call a measurement**: a bucket's mean - it is arithmetic on the samples in the bucket, and the band around it is their range; a line across a gap - a stretch with no samples is drawn empty, never bridged; the depth axis as the hole - it is the drilling samples by bit depth, kept off hole a later sidetrack abandoned, the same samples the gamma log uses; the drilling progress as a plan - it is the running bottom the depth pass read from the recorder, stale and re-referenced depths left out.
@@ -0,0 +1,11 @@
1
+ # The site map
2
+
3
+ > Everything in the workspace that has a position, in one frame: the wells where their surface positions were declared, the structure layers the site brought (faults and folds, formation contours, areas, points), the World Stress Map's SHmax directions, the seismic stations, the Seismicity Response Areas, and the outline of the US states and counties underneath. A well without a declared position is listed as not placed and is never drawn anywhere. Click the map for what every layer says about that spot.
4
+
5
+ **The command**: the Map tab. Drag to pan, the wheel or + − to zoom, Home to return to where the map opened (the placed wells, else the response area, else the area most of the layers cover). Tick a layer to draw it, "fit" to zoom to it; Rings draws distance circles around every placed well. Point at the map for the position (WGS84) and the nearest placed well; click it - or press Enter for the centre - for each layer's own statement there: the nearest fault and its distance and bearing, the contours it lies between, the area it falls in, the nearest points, and the distance and bearing to every placed well. The keyboard does the same once the map has focus: arrows pan, + and − zoom, Home returns. A well's marker opens its page; "declare it" beside a not-placed well opens the setup checklist.
6
+
7
+ **What it writes**: nothing. The map reads the workspace: each well's declared surface position, each layer's shapefile (read once per run of the program and again when it changes), the stress map, the stations and the response areas. The outline is shipped with the program (`reference/us_outline.json`, from the US Census Bureau's 2017 cartographic boundary files by way of the us-atlas package, with its provenance beside it), so the map needs no network.
8
+
9
+ **The number to check**: a placed well against its survey plat - its marker is drawn at the declared latitude and longitude moved from the declared datum to WGS84, and the move is printed beside it (about fifty metres for NAD27 in West Texas); a layer's line against the map it came from, at the scale that map was made.
10
+
11
+ **What this page will not call a measurement**: a well's position - it is the operator's declaration, drawn where it was declared; a line drawn at this zoom as the line in the file - a vertex that would fall in the same screen cell as the one before it is left out of the drawing (never moved), and the full line is what "what is here" measures against; the outline as a survey - it is the Census Bureau's cartographic generalisation, good to a few hundred metres; a distance to a mapped fault as a hazard - it is a fact of the map.
@@ -43,6 +43,8 @@ INDEX: List[tuple] = [
43
43
  ('edr', 'The drilling recorder', 'the rig\'s one-second EDR export read as delivered - sections, depths, hours on bottom, the rate of penetration, the recorder\'s MD-TVD table - and what the record will not say'),
44
44
  ('dlis', 'The DLIS log', 'a well log as it is delivered (API RP66 V1), read as the standard lays it out: the frames, the channels, what was skipped; into the strata survey'),
45
45
  ('historian', 'The historian pull', 'an operator\'s gauge history out of the PI System through PI Web API, read-only, every value with its archive time and flags'),
46
+ ('map', 'The site map', 'everything with a position in one frame: the placed wells, the structure layers, the stress directions, the stations and response areas over the state and county outline; what every layer says about any spot'),
47
+ ('charts', 'The charts', 'a well\'s channels stacked on one axis with a linked cursor, zoom and pan from every sample, time or depth for a drilling recorder, the depth events marked, and the drilling progress of every well'),
46
48
  ('setup', 'The setup checklist', 'what this workspace still needs before its reports can be handed over - each well\'s location, elevation and number, the client site, the backup, the reports - read from the workspace as it stands, each with its form or button'),
47
49
  ('tools', 'Tools and ports', 'every class of downhole tool in use across the industry with a hover description and a pop-open tutorial, your own fleet with its datasheets, the gauges this program reads with their citations and ratings, the formats and protocols that bring readings in, and the rating check on a tool string'),
48
50
  ('field-ports', 'The field ports', 'Modbus RTU on the serial line, DNP3 from the pad RTU, HART-IP from the transmitter itself - read-only, polled, with the stand-ins to rehearse against'),
@@ -69,7 +71,7 @@ REQUIRED_LINES = ('**The command**', '**What it writes**', '**The number to chec
69
71
  VIEW_TOPIC: Dict[str, str] = {
70
72
  'home': 'start', 'wells': 'data-in', 'well': 'drift', 'live': 'patches', 'patch': 'patches', 'files': 'files', 'alarms': 'alarms',
71
73
  'approvals': 'well-tests', 'config': 'site', 'reports': 'drift', 'verify': 'start', 'survey': 'data-in', 'jobs': 'site',
72
- 'admin': 'site', 'prefs': 'site', 'search': 'start', 'welcome': 'start', 'seismic': 'seismic', 'seismic_station': 'seismic', 'track': 'seismic', 'sar': 'seismic', 'sites': 'sites', 'setup': 'setup', 'audit': 'audit-update', 'tools': 'tools', 'catalog': 'catalog',
74
+ 'admin': 'site', 'prefs': 'site', 'search': 'start', 'welcome': 'start', 'seismic': 'seismic', 'seismic_station': 'seismic', 'track': 'seismic', 'sar': 'seismic', 'sites': 'sites', 'setup': 'setup', 'map': 'map', 'audit': 'audit-update', 'tools': 'tools', 'catalog': 'catalog',
73
75
  }
74
76
 
75
77