africas2s 0.2.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 (253) hide show
  1. africas2s-0.2.0/.github/workflows/add-to-kanban.yml +14 -0
  2. africas2s-0.2.0/.github/workflows/publish-testpypi.yml +18 -0
  3. africas2s-0.2.0/.github/workflows/publish.yml +50 -0
  4. africas2s-0.2.0/.github/workflows/s2s_testbed.yml +341 -0
  5. africas2s-0.2.0/.github/workflows/test.yml +27 -0
  6. africas2s-0.2.0/.gitignore +54 -0
  7. africas2s-0.2.0/AGENTS.md +33 -0
  8. africas2s-0.2.0/LICENSE +21 -0
  9. africas2s-0.2.0/PKG-INFO +285 -0
  10. africas2s-0.2.0/README.md +237 -0
  11. africas2s-0.2.0/docs/specs/2026-07-09-analog-completion-design.md +98 -0
  12. africas2s-0.2.0/docs/specs/2026-07-09-index-generalization-design.md +79 -0
  13. africas2s-0.2.0/examples/README.md +54 -0
  14. africas2s-0.2.0/examples/demo_checkpoint_roundtrip.py +76 -0
  15. africas2s-0.2.0/examples/demo_chelsa.py +311 -0
  16. africas2s-0.2.0/examples/demo_corrdiff.py +307 -0
  17. africas2s-0.2.0/examples/demo_delta_scaling.py +56 -0
  18. africas2s-0.2.0/examples/demo_detrended_qm.py +61 -0
  19. africas2s-0.2.0/examples/demo_ensemble_regression.py +186 -0
  20. africas2s-0.2.0/examples/demo_forecast.py +355 -0
  21. africas2s-0.2.0/examples/demo_logistic_wvg.py +185 -0
  22. africas2s-0.2.0/examples/demo_probabilistic_method.py +102 -0
  23. africas2s-0.2.0/examples/demo_quantile_mapping.py +61 -0
  24. africas2s-0.2.0/examples/demo_realdata_comparison.py +142 -0
  25. africas2s-0.2.0/examples/demo_realdata_skill.py +117 -0
  26. africas2s-0.2.0/examples/demo_seasonal_mme.py +206 -0
  27. africas2s-0.2.0/examples/demo_seasonal_mme_multimodel.py +242 -0
  28. africas2s-0.2.0/examples/demo_smoothed_forecast.py +104 -0
  29. africas2s-0.2.0/examples/demo_train_inference.py +106 -0
  30. africas2s-0.2.0/examples/onset_gha_mam.py +82 -0
  31. africas2s-0.2.0/examples/seasonal_forecast_eastafrica_mam.py +360 -0
  32. africas2s-0.2.0/notebooks/experimentB1.ipynb +1061 -0
  33. africas2s-0.2.0/pyproject.toml +105 -0
  34. africas2s-0.2.0/scripts/DWN_CPT_SRC.sh +32 -0
  35. africas2s-0.2.0/scripts/benchmark_pycpt.py +544 -0
  36. africas2s-0.2.0/scripts/install_pycpt.sh +80 -0
  37. africas2s-0.2.0/scripts/nightly/__init__.py +5 -0
  38. africas2s-0.2.0/scripts/nightly/config.py +74 -0
  39. africas2s-0.2.0/scripts/nightly/nightly.yml +150 -0
  40. africas2s-0.2.0/scripts/nightly/output_writer.py +73 -0
  41. africas2s-0.2.0/scripts/nightly/publish.py +181 -0
  42. africas2s-0.2.0/scripts/nightly/run_country.py +230 -0
  43. africas2s-0.2.0/scripts/nightly/select_targets.py +56 -0
  44. africas2s-0.2.0/scripts/nightly/site/app.js +299 -0
  45. africas2s-0.2.0/scripts/nightly/site/index.html +74 -0
  46. africas2s-0.2.0/scripts/nightly/site/style.css +43 -0
  47. africas2s-0.2.0/scripts/reproduce.py +559 -0
  48. africas2s-0.2.0/scripts/reproduce_batch.py +657 -0
  49. africas2s-0.2.0/scripts/s2s/__init__.py +0 -0
  50. africas2s-0.2.0/scripts/s2s/config.py +72 -0
  51. africas2s-0.2.0/scripts/s2s/dekads.py +14 -0
  52. africas2s-0.2.0/scripts/s2s/detect_new_issuances.py +69 -0
  53. africas2s-0.2.0/scripts/s2s/init_issuance_store.md +50 -0
  54. africas2s-0.2.0/scripts/s2s/issuance_store.py +86 -0
  55. africas2s-0.2.0/scripts/s2s/plotting.py +232 -0
  56. africas2s-0.2.0/scripts/s2s/publish_pages.py +56 -0
  57. africas2s-0.2.0/scripts/s2s/render_dashboard.py +310 -0
  58. africas2s-0.2.0/scripts/s2s/run_issuance.py +269 -0
  59. africas2s-0.2.0/scripts/s2s/s2s.yml +47 -0
  60. africas2s-0.2.0/scripts/s2s/scoring.py +121 -0
  61. africas2s-0.2.0/scripts/s2s/verify.py +205 -0
  62. africas2s-0.2.0/site/index.html +29 -0
  63. africas2s-0.2.0/site/theme.css +55 -0
  64. africas2s-0.2.0/skills/africas2s/README.md +57 -0
  65. africas2s-0.2.0/skills/africas2s/SKILL.md +161 -0
  66. africas2s-0.2.0/skills/africas2s/examples/analog_completion.py +82 -0
  67. africas2s-0.2.0/skills/africas2s/examples/calibration.py +131 -0
  68. africas2s-0.2.0/skills/africas2s/examples/end_to_end_forecast.py +62 -0
  69. africas2s-0.2.0/skills/africas2s/examples/ensemble_and_reporting.py +58 -0
  70. africas2s-0.2.0/skills/africas2s/examples/seasonal_mme_pipeline.py +56 -0
  71. africas2s-0.2.0/skills/africas2s/examples/smoothed_calibration.py +72 -0
  72. africas2s-0.2.0/skills/africas2s/references/aggregations.md +281 -0
  73. africas2s-0.2.0/skills/africas2s/references/analog-completion.md +304 -0
  74. africas2s-0.2.0/skills/africas2s/references/api.md +267 -0
  75. africas2s-0.2.0/skills/africas2s/references/methods.md +141 -0
  76. africas2s-0.2.0/skills/africas2s/references/metrics-and-terciles.md +112 -0
  77. africas2s-0.2.0/skills/africas2s/references/plotting-reporting.md +184 -0
  78. africas2s-0.2.0/skills/africas2s/references/troubleshooting.md +53 -0
  79. africas2s-0.2.0/src/africas2s/__init__.py +100 -0
  80. africas2s-0.2.0/src/africas2s/_optional.py +10 -0
  81. africas2s-0.2.0/src/africas2s/_spatial.py +30 -0
  82. africas2s-0.2.0/src/africas2s/_warnings.py +30 -0
  83. africas2s-0.2.0/src/africas2s/aggregations/__init__.py +21 -0
  84. africas2s-0.2.0/src/africas2s/aggregations/_runs.py +73 -0
  85. africas2s-0.2.0/src/africas2s/aggregations/spells.py +93 -0
  86. africas2s-0.2.0/src/africas2s/aggregations/timing.py +446 -0
  87. africas2s-0.2.0/src/africas2s/analog.py +457 -0
  88. africas2s-0.2.0/src/africas2s/calibrate.py +1025 -0
  89. africas2s-0.2.0/src/africas2s/climate.py +440 -0
  90. africas2s-0.2.0/src/africas2s/combine.py +223 -0
  91. africas2s-0.2.0/src/africas2s/compare.py +162 -0
  92. africas2s-0.2.0/src/africas2s/completion.py +409 -0
  93. africas2s-0.2.0/src/africas2s/cv.py +145 -0
  94. africas2s-0.2.0/src/africas2s/downscale.py +85 -0
  95. africas2s-0.2.0/src/africas2s/ensemble.py +454 -0
  96. africas2s-0.2.0/src/africas2s/flex_forecast.py +129 -0
  97. africas2s-0.2.0/src/africas2s/indices.py +524 -0
  98. africas2s-0.2.0/src/africas2s/io.py +94 -0
  99. africas2s-0.2.0/src/africas2s/logistic.py +337 -0
  100. africas2s-0.2.0/src/africas2s/methods/__init__.py +6 -0
  101. africas2s-0.2.0/src/africas2s/methods/_qm_kernel.py +118 -0
  102. africas2s-0.2.0/src/africas2s/methods/base.py +84 -0
  103. africas2s-0.2.0/src/africas2s/methods/bcsd.py +70 -0
  104. africas2s-0.2.0/src/africas2s/methods/cca.py +525 -0
  105. africas2s-0.2.0/src/africas2s/methods/chelsa.py +337 -0
  106. africas2s-0.2.0/src/africas2s/methods/climatology.py +45 -0
  107. africas2s-0.2.0/src/africas2s/methods/corrdiff.py +367 -0
  108. africas2s-0.2.0/src/africas2s/methods/delta.py +39 -0
  109. africas2s-0.2.0/src/africas2s/methods/dqm.py +86 -0
  110. africas2s-0.2.0/src/africas2s/methods/ensemble_regression.py +255 -0
  111. africas2s-0.2.0/src/africas2s/methods/qm.py +101 -0
  112. africas2s-0.2.0/src/africas2s/methods/rank_analog.py +136 -0
  113. africas2s-0.2.0/src/africas2s/methods/smoothed_regression.py +170 -0
  114. africas2s-0.2.0/src/africas2s/metrics/__init__.py +12 -0
  115. africas2s-0.2.0/src/africas2s/metrics/base.py +6 -0
  116. africas2s-0.2.0/src/africas2s/metrics/cross_validation.py +71 -0
  117. africas2s-0.2.0/src/africas2s/metrics/crpss.py +45 -0
  118. africas2s-0.2.0/src/africas2s/metrics/generalized_roc.py +126 -0
  119. africas2s-0.2.0/src/africas2s/metrics/heidke.py +70 -0
  120. africas2s-0.2.0/src/africas2s/metrics/msss.py +30 -0
  121. africas2s-0.2.0/src/africas2s/metrics/pearson.py +27 -0
  122. africas2s-0.2.0/src/africas2s/metrics/reliability.py +71 -0
  123. africas2s-0.2.0/src/africas2s/metrics/rmse.py +23 -0
  124. africas2s-0.2.0/src/africas2s/metrics/roc.py +79 -0
  125. africas2s-0.2.0/src/africas2s/metrics/rpss.py +144 -0
  126. africas2s-0.2.0/src/africas2s/metrics/significance.py +107 -0
  127. africas2s-0.2.0/src/africas2s/metrics/spearman.py +64 -0
  128. africas2s-0.2.0/src/africas2s/metrics/spread_error.py +117 -0
  129. africas2s-0.2.0/src/africas2s/metrics/two_afc.py +61 -0
  130. africas2s-0.2.0/src/africas2s/optimize.py +103 -0
  131. africas2s-0.2.0/src/africas2s/pev.py +66 -0
  132. africas2s-0.2.0/src/africas2s/pipelines/__init__.py +3 -0
  133. africas2s-0.2.0/src/africas2s/pipelines/seasonal.py +798 -0
  134. africas2s-0.2.0/src/africas2s/plotting/__init__.py +47 -0
  135. africas2s-0.2.0/src/africas2s/plotting/domains.py +81 -0
  136. africas2s-0.2.0/src/africas2s/plotting/forecasts.py +733 -0
  137. africas2s-0.2.0/src/africas2s/plotting/maps.py +435 -0
  138. africas2s-0.2.0/src/africas2s/plotting/modes.py +229 -0
  139. africas2s-0.2.0/src/africas2s/plotting/reliability.py +73 -0
  140. africas2s-0.2.0/src/africas2s/plotting/scenarios.py +339 -0
  141. africas2s-0.2.0/src/africas2s/plotting/skill.py +83 -0
  142. africas2s-0.2.0/src/africas2s/plotting/style.py +41 -0
  143. africas2s-0.2.0/src/africas2s/registry.py +55 -0
  144. africas2s-0.2.0/src/africas2s/reporting/__init__.py +8 -0
  145. africas2s-0.2.0/src/africas2s/reporting/_pages.py +541 -0
  146. africas2s-0.2.0/src/africas2s/reporting/svslrf.py +57 -0
  147. africas2s-0.2.0/src/africas2s/series.py +202 -0
  148. africas2s-0.2.0/src/africas2s/skill.py +192 -0
  149. africas2s-0.2.0/src/africas2s/strategies/__init__.py +10 -0
  150. africas2s-0.2.0/src/africas2s/strategies/base.py +25 -0
  151. africas2s-0.2.0/src/africas2s/strategies/bma.py +132 -0
  152. africas2s-0.2.0/src/africas2s/strategies/drop_worst.py +103 -0
  153. africas2s-0.2.0/src/africas2s/strategies/skill_weighted.py +62 -0
  154. africas2s-0.2.0/src/africas2s/strategies/uniform.py +25 -0
  155. africas2s-0.2.0/src/africas2s/tercile.py +335 -0
  156. africas2s-0.2.0/src/africas2s/terrain.py +255 -0
  157. africas2s-0.2.0/src/africas2s/time.py +346 -0
  158. africas2s-0.2.0/src/africas2s/training/__init__.py +53 -0
  159. africas2s-0.2.0/tests/__init__.py +0 -0
  160. africas2s-0.2.0/tests/conftest.py +109 -0
  161. africas2s-0.2.0/tests/s2s/__init__.py +0 -0
  162. africas2s-0.2.0/tests/s2s/test_config.py +87 -0
  163. africas2s-0.2.0/tests/s2s/test_dekads.py +51 -0
  164. africas2s-0.2.0/tests/s2s/test_detect_new_issuances.py +32 -0
  165. africas2s-0.2.0/tests/s2s/test_issuance_store.py +56 -0
  166. africas2s-0.2.0/tests/s2s/test_obs_lookup.py +85 -0
  167. africas2s-0.2.0/tests/s2s/test_plotting.py +58 -0
  168. africas2s-0.2.0/tests/s2s/test_plotting_dark.py +39 -0
  169. africas2s-0.2.0/tests/s2s/test_publish_pages.py +85 -0
  170. africas2s-0.2.0/tests/s2s/test_render_dashboard.py +205 -0
  171. africas2s-0.2.0/tests/s2s/test_run_issuance_integration.py +258 -0
  172. africas2s-0.2.0/tests/s2s/test_scoring.py +90 -0
  173. africas2s-0.2.0/tests/s2s/test_verify_integration.py +225 -0
  174. africas2s-0.2.0/tests/test_aggregations_integration.py +65 -0
  175. africas2s-0.2.0/tests/test_aggregations_parity.py +102 -0
  176. africas2s-0.2.0/tests/test_aggregations_runs.py +52 -0
  177. africas2s-0.2.0/tests/test_aggregations_spells.py +80 -0
  178. africas2s-0.2.0/tests/test_aggregations_timing.py +414 -0
  179. africas2s-0.2.0/tests/test_agreement.py +90 -0
  180. africas2s-0.2.0/tests/test_analog.py +374 -0
  181. africas2s-0.2.0/tests/test_calibrate.py +377 -0
  182. africas2s-0.2.0/tests/test_calibrate_components.py +142 -0
  183. africas2s-0.2.0/tests/test_calibrate_deterministic.py +106 -0
  184. africas2s-0.2.0/tests/test_calibrate_ereg_native_years.py +111 -0
  185. africas2s-0.2.0/tests/test_calibrate_probabilistic.py +54 -0
  186. africas2s-0.2.0/tests/test_calibrate_smoothed_forecast.py +238 -0
  187. africas2s-0.2.0/tests/test_cca_cpt_args.py +265 -0
  188. africas2s-0.2.0/tests/test_cca_robustness.py +90 -0
  189. africas2s-0.2.0/tests/test_chelsa.py +210 -0
  190. africas2s-0.2.0/tests/test_climate.py +311 -0
  191. africas2s-0.2.0/tests/test_climatology_integration.py +54 -0
  192. africas2s-0.2.0/tests/test_combine_terciles.py +161 -0
  193. africas2s-0.2.0/tests/test_completion.py +501 -0
  194. africas2s-0.2.0/tests/test_corrdiff.py +383 -0
  195. africas2s-0.2.0/tests/test_corrdiff_integration.py +97 -0
  196. africas2s-0.2.0/tests/test_crpss.py +39 -0
  197. africas2s-0.2.0/tests/test_cv.py +285 -0
  198. africas2s-0.2.0/tests/test_delta.py +87 -0
  199. africas2s-0.2.0/tests/test_downscale.py +278 -0
  200. africas2s-0.2.0/tests/test_dqm.py +92 -0
  201. africas2s-0.2.0/tests/test_ensemble.py +692 -0
  202. africas2s-0.2.0/tests/test_ensemble_diagnostics.py +157 -0
  203. africas2s-0.2.0/tests/test_ensemble_regression.py +221 -0
  204. africas2s-0.2.0/tests/test_ensemble_safeguards_integration.py +121 -0
  205. africas2s-0.2.0/tests/test_example_chelsa.py +23 -0
  206. africas2s-0.2.0/tests/test_example_eastafrica_mam.py +42 -0
  207. africas2s-0.2.0/tests/test_example_logistic_ereg.py +37 -0
  208. africas2s-0.2.0/tests/test_flex_forecast.py +101 -0
  209. africas2s-0.2.0/tests/test_groc_integration.py +51 -0
  210. africas2s-0.2.0/tests/test_indices.py +143 -0
  211. africas2s-0.2.0/tests/test_indices_general.py +322 -0
  212. africas2s-0.2.0/tests/test_integration_reporting.py +96 -0
  213. africas2s-0.2.0/tests/test_load_terrain.py +273 -0
  214. africas2s-0.2.0/tests/test_logistic.py +199 -0
  215. africas2s-0.2.0/tests/test_mask_forecast.py +83 -0
  216. africas2s-0.2.0/tests/test_method_invariants.py +303 -0
  217. africas2s-0.2.0/tests/test_methods.py +853 -0
  218. africas2s-0.2.0/tests/test_metrics.py +958 -0
  219. africas2s-0.2.0/tests/test_mode_selection_north.py +115 -0
  220. africas2s-0.2.0/tests/test_msss.py +57 -0
  221. africas2s-0.2.0/tests/test_nightly.py +423 -0
  222. africas2s-0.2.0/tests/test_nightly_integration.py +66 -0
  223. africas2s-0.2.0/tests/test_percent_of_normal.py +57 -0
  224. africas2s-0.2.0/tests/test_pev.py +120 -0
  225. africas2s-0.2.0/tests/test_pev_integration.py +97 -0
  226. africas2s-0.2.0/tests/test_plotting.py +744 -0
  227. africas2s-0.2.0/tests/test_plotting_maps.py +212 -0
  228. africas2s-0.2.0/tests/test_plotting_scenarios.py +297 -0
  229. africas2s-0.2.0/tests/test_pool_ensembles.py +102 -0
  230. africas2s-0.2.0/tests/test_public_io_helpers.py +112 -0
  231. africas2s-0.2.0/tests/test_qm.py +116 -0
  232. africas2s-0.2.0/tests/test_rank_analog_integration.py +91 -0
  233. africas2s-0.2.0/tests/test_render_styled.py +36 -0
  234. africas2s-0.2.0/tests/test_reporting.py +564 -0
  235. africas2s-0.2.0/tests/test_seasonal_mme.py +314 -0
  236. africas2s-0.2.0/tests/test_seasonal_mme_integration.py +127 -0
  237. africas2s-0.2.0/tests/test_seasonal_native_years.py +166 -0
  238. africas2s-0.2.0/tests/test_seasonal_reduce.py +55 -0
  239. africas2s-0.2.0/tests/test_series.py +250 -0
  240. africas2s-0.2.0/tests/test_significance.py +122 -0
  241. africas2s-0.2.0/tests/test_smoothed_forecast_integration.py +94 -0
  242. africas2s-0.2.0/tests/test_smoothed_regression.py +76 -0
  243. africas2s-0.2.0/tests/test_smoothed_regression_prob.py +50 -0
  244. africas2s-0.2.0/tests/test_spatial.py +32 -0
  245. africas2s-0.2.0/tests/test_spec.md +353 -0
  246. africas2s-0.2.0/tests/test_spread_error_integration.py +59 -0
  247. africas2s-0.2.0/tests/test_tercile.py +102 -0
  248. africas2s-0.2.0/tests/test_tercile_cv.py +101 -0
  249. africas2s-0.2.0/tests/test_time.py +260 -0
  250. africas2s-0.2.0/tests/test_training.py +124 -0
  251. africas2s-0.2.0/uv.lock +3129 -0
  252. africas2s-0.2.0/validation/chelsa/saga_wind_effect.csv +9 -0
  253. africas2s-0.2.0/validation/chelsa/validate_saga_fixture.py +87 -0
@@ -0,0 +1,14 @@
1
+ name: Add issues to Kanban
2
+
3
+ on:
4
+ issues:
5
+ types: [opened, reopened]
6
+
7
+ jobs:
8
+ add-to-kanban:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/add-to-project@v1.0.2
12
+ with:
13
+ project-url: https://github.com/orgs/accord-research/projects/2
14
+ github-token: ${{ secrets.KANBAN_TOKEN }}
@@ -0,0 +1,18 @@
1
+ name: publish-testpypi
2
+ on:
3
+ workflow_dispatch:
4
+
5
+ permissions:
6
+ id-token: write
7
+
8
+ jobs:
9
+ build-publish-test:
10
+ runs-on: ubuntu-latest
11
+ environment: testpypi
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v6
15
+ - run: uv build
16
+ - uses: pypa/gh-action-pypi-publish@release/v1
17
+ with:
18
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,50 @@
1
+ name: publish
2
+
3
+ # PyPI releases are cut MANUALLY, never on a push to main.
4
+ #
5
+ # To release:
6
+ # 1. Bump `version` in pyproject.toml (in a commit to main).
7
+ # 2. Actions tab -> "publish" -> "Run workflow".
8
+ #
9
+ # The job publishes only if that version isn't already on PyPI, so re-running
10
+ # without a version bump is a safe no-op (it fails fast with a clear message).
11
+ # pyproject.toml's `version` is the single source of truth. Publishing uses
12
+ # PyPI Trusted Publishing (no tokens).
13
+
14
+ on:
15
+ workflow_dispatch:
16
+
17
+ jobs:
18
+ test:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v6
23
+ - run: uv sync --extra dev
24
+ - run: |
25
+ uv run pytest tests/ \
26
+ --ignore=tests/test_agreement.py \
27
+ --ignore=tests/test_corrdiff.py \
28
+ --ignore=tests/test_corrdiff_integration.py \
29
+ -m "not gpu and not integration" -q
30
+
31
+ publish:
32
+ needs: test
33
+ runs-on: ubuntu-latest
34
+ environment: pypi
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v6
40
+ - name: Refuse to republish an existing version
41
+ run: |
42
+ VERSION=$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
43
+ echo "pyproject.toml version: $VERSION"
44
+ if curl -sf "https://pypi.org/pypi/africas2s/$VERSION/json" > /dev/null; then
45
+ echo "::error::africas2s $VERSION is already on PyPI — bump 'version' in pyproject.toml first."
46
+ exit 1
47
+ fi
48
+ echo "$VERSION is new on PyPI; proceeding to publish."
49
+ - run: uv build
50
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,341 @@
1
+ name: s2s testbed
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 2 * * *' # 02:00 UTC daily
6
+ workflow_dispatch:
7
+ inputs:
8
+ lookback_days:
9
+ description: 'How many days back to scan for missed issuances'
10
+ required: false
11
+ default: '14'
12
+
13
+ permissions:
14
+ contents: write # We push to the issuance-store branch.
15
+
16
+ env:
17
+ CDSAPI_KEY: ${{ secrets.CDSAPI_KEY }}
18
+ MARS_API_KEY: ${{ secrets.MARS_API_KEY }}
19
+ MARS_API_EMAIL: ${{ secrets.MARS_API_EMAIL }}
20
+ COUNTRIES: '["kenya","ethiopia","nigeria"]'
21
+
22
+ jobs:
23
+
24
+ detect_issuances:
25
+ name: detect new issuances per country
26
+ runs-on: ubuntu-latest
27
+ outputs:
28
+ matrix: ${{ steps.build.outputs.matrix }}
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: actions/checkout@v4
32
+ with:
33
+ ref: issuance-store
34
+ path: issuance-store-checkout
35
+
36
+ - uses: astral-sh/setup-uv@v6
37
+ with:
38
+ enable-cache: true
39
+
40
+ - name: Install africas2s (no extras)
41
+ run: uv sync
42
+
43
+ - name: Build (country, issuance) matrix
44
+ id: build
45
+ run: |
46
+ set -euo pipefail
47
+ LOOKBACK=${{ github.event.inputs.lookback_days || '14' }}
48
+ MATRIX='[]'
49
+ for COUNTRY in $(echo "$COUNTRIES" | jq -r '.[]'); do
50
+ ISSUANCES=$(uv run python -m scripts.s2s.detect_new_issuances \
51
+ --store-root issuance-store-checkout \
52
+ --country "$COUNTRY" \
53
+ --lookback-days "$LOOKBACK")
54
+ MATRIX=$(jq --argjson m "$MATRIX" --arg c "$COUNTRY" --argjson is "$ISSUANCES" \
55
+ -n '$m + [$is[] | {country: $c, issuance: .}]')
56
+ done
57
+ echo "matrix=$(echo "$MATRIX" | jq -c '{include: .}')" >> "$GITHUB_OUTPUT"
58
+
59
+ forecast:
60
+ name: forecast (${{ matrix.country }} @ ${{ matrix.issuance }})
61
+ needs: detect_issuances
62
+ if: ${{ needs.detect_issuances.outputs.matrix != '{"include":[]}' }}
63
+ runs-on: ubuntu-latest
64
+ strategy:
65
+ fail-fast: false
66
+ # ECDS allows multiple concurrent requests per user (the legacy MARS
67
+ # WEB-API's 1-queued-per-user limit is gone now that we're off it).
68
+ # Three parallel shards keeps total cron wall-clock down without
69
+ # hammering ECDS or running out of GitHub-hosted runner concurrency.
70
+ max-parallel: 3
71
+ matrix: ${{ fromJSON(needs.detect_issuances.outputs.matrix) }}
72
+ steps:
73
+ - uses: actions/checkout@v4
74
+ - uses: actions/checkout@v4
75
+ with:
76
+ ref: issuance-store
77
+ path: issuance-store-checkout
78
+ - uses: actions/checkout@v4
79
+ with:
80
+ repository: accord-research/rosetta
81
+ path: rosetta
82
+
83
+ - uses: astral-sh/setup-uv@v6
84
+ with:
85
+ enable-cache: true
86
+
87
+ - name: Install africas2s + rosetta
88
+ run: |
89
+ uv sync
90
+ uv pip install -e ./rosetta
91
+
92
+ - name: Configure nuthatch cache location
93
+ run: |
94
+ mkdir -p ~/.cache/nuthatch
95
+ cat > ~/.nuthatch.toml <<EOF
96
+ [tool.nuthatch]
97
+ filesystem = "~/.cache/nuthatch"
98
+ skipped_filesystems = ["gs://sheerwater-datalake/caches"]
99
+ EOF
100
+
101
+ - name: Restore nuthatch cache
102
+ uses: actions/cache@v4
103
+ with:
104
+ path: ~/.cache/nuthatch
105
+ # Per-(country, issuance) cache keyed on the rosetta files whose
106
+ # contents define cache semantics (catalog units, normalization
107
+ # logic, _CACHE_VERSION). When those change, the cache invalidates.
108
+ key: nuthatch-${{ matrix.country }}-${{ matrix.issuance }}-${{ hashFiles('rosetta/src/rosetta/catalog.yaml', 'rosetta/src/rosetta/normalize.py', 'rosetta/src/rosetta/fetch.py') }}
109
+ restore-keys: |
110
+ nuthatch-${{ matrix.country }}-${{ matrix.issuance }}-
111
+ nuthatch-${{ matrix.country }}-
112
+ nuthatch-
113
+
114
+ - name: Write .cdsapirc
115
+ run: |
116
+ umask 077
117
+ cat > ~/.cdsapirc <<EOF
118
+ url: https://ecds.ecmwf.int/api
119
+ key: ${CDSAPI_KEY}
120
+ EOF
121
+
122
+ - name: Write .ecmwfapirc
123
+ run: |
124
+ umask 077
125
+ cat > ~/.ecmwfapirc <<EOF
126
+ {
127
+ "url": "https://api.ecmwf.int/v1",
128
+ "key": "${MARS_API_KEY}",
129
+ "email": "${MARS_API_EMAIL}"
130
+ }
131
+ EOF
132
+
133
+ - name: Run issuance
134
+ env:
135
+ S2S_STORE_ROOT: issuance-store-checkout/issuances
136
+ run: |
137
+ uv run python -m scripts.s2s.run_issuance \
138
+ --country "${{ matrix.country }}" \
139
+ --issuance "${{ matrix.issuance }}" \
140
+ --config scripts/s2s/s2s.yml
141
+
142
+ - name: Commit + push new issuance to issuance-store
143
+ working-directory: issuance-store-checkout
144
+ run: |
145
+ set -euo pipefail
146
+ git config user.name "github-actions[bot]"
147
+ git config user.email "github-actions[bot]@users.noreply.github.com"
148
+ git add issuances/
149
+ if git diff --cached --quiet; then
150
+ echo "Nothing to commit for ${{ matrix.country }} @ ${{ matrix.issuance }}"
151
+ exit 0
152
+ fi
153
+ git commit -m "forecast: ${{ matrix.country }} @ ${{ matrix.issuance }}"
154
+ # Retry-pull-rebase to absorb concurrent matrix shards.
155
+ for attempt in 1 2 3 4 5; do
156
+ git pull --rebase origin issuance-store && git push origin issuance-store && exit 0
157
+ sleep $((attempt * 5))
158
+ done
159
+ echo "Push failed after 5 attempts" >&2
160
+ exit 1
161
+
162
+ verify:
163
+ name: verification pass
164
+ needs: forecast
165
+ if: ${{ always() }} # Run even if some forecast shards failed; verify is independent.
166
+ runs-on: ubuntu-latest
167
+ steps:
168
+ - uses: actions/checkout@v4
169
+ - uses: actions/checkout@v4
170
+ with:
171
+ ref: issuance-store
172
+ path: issuance-store-checkout
173
+ - uses: actions/checkout@v4
174
+ with:
175
+ repository: accord-research/rosetta
176
+ path: rosetta
177
+
178
+ - uses: astral-sh/setup-uv@v6
179
+ with:
180
+ enable-cache: true
181
+
182
+ - name: Install africas2s + rosetta
183
+ run: |
184
+ uv sync
185
+ uv pip install -e ./rosetta
186
+
187
+ - name: Configure nuthatch cache location
188
+ run: |
189
+ mkdir -p ~/.cache/nuthatch
190
+ cat > ~/.nuthatch.toml <<EOF
191
+ [tool.nuthatch]
192
+ filesystem = "~/.cache/nuthatch"
193
+ skipped_filesystems = ["gs://sheerwater-datalake/caches"]
194
+ EOF
195
+
196
+ - name: Restore nuthatch cache
197
+ uses: actions/cache@v4
198
+ with:
199
+ path: ~/.cache/nuthatch
200
+ # Verify is a single job that needs obs for every country, so use one
201
+ # shared verify-scoped cache. Restores from the forecast shards' caches
202
+ # too (they fetched the same CHIRPS dates) via the `nuthatch-` prefix.
203
+ key: nuthatch-verify-${{ github.run_id }}-${{ hashFiles('rosetta/src/rosetta/catalog.yaml', 'rosetta/src/rosetta/normalize.py', 'rosetta/src/rosetta/fetch.py') }}
204
+ restore-keys: |
205
+ nuthatch-verify-
206
+ nuthatch-
207
+
208
+ - name: Write .cdsapirc
209
+ run: |
210
+ umask 077
211
+ cat > ~/.cdsapirc <<EOF
212
+ url: https://ecds.ecmwf.int/api
213
+ key: ${CDSAPI_KEY}
214
+ EOF
215
+
216
+ - name: Write .ecmwfapirc
217
+ run: |
218
+ umask 077
219
+ cat > ~/.ecmwfapirc <<EOF
220
+ {
221
+ "url": "https://api.ecmwf.int/v1",
222
+ "key": "${MARS_API_KEY}",
223
+ "email": "${MARS_API_EMAIL}"
224
+ }
225
+ EOF
226
+
227
+ - name: Run verification
228
+ run: |
229
+ uv run python -m scripts.s2s.verify \
230
+ --store-root issuance-store-checkout/issuances \
231
+ --verification-root issuance-store-checkout/verification \
232
+ --config scripts/s2s/s2s.yml
233
+
234
+ - name: Commit + push verification updates
235
+ working-directory: issuance-store-checkout
236
+ run: |
237
+ set -euo pipefail
238
+ git config user.name "github-actions[bot]"
239
+ git config user.email "github-actions[bot]@users.noreply.github.com"
240
+ git add verification/
241
+ if git diff --cached --quiet; then
242
+ echo "No new scores to commit"
243
+ exit 0
244
+ fi
245
+ git commit -m "verify: $(date -u +%Y-%m-%dT%H:%MZ)"
246
+ for attempt in 1 2 3 4 5; do
247
+ git pull --rebase origin issuance-store && git push origin issuance-store && exit 0
248
+ sleep $((attempt * 5))
249
+ done
250
+ exit 1
251
+
252
+ publish:
253
+ name: render + publish dashboard
254
+ needs: verify
255
+ if: ${{ always() }} # Publish whatever data made it through.
256
+ runs-on: ubuntu-latest
257
+ steps:
258
+ - uses: actions/checkout@v4 # main: code + site/ shell
259
+ - uses: actions/checkout@v4
260
+ with:
261
+ ref: issuance-store
262
+ path: issuance-store-checkout # data: issuances/ + verification/
263
+ - uses: actions/checkout@v4
264
+ with:
265
+ ref: gh-pages
266
+ path: gh-pages-checkout # publish target
267
+ - uses: actions/checkout@v4
268
+ with:
269
+ repository: accord-research/rosetta
270
+ path: rosetta
271
+
272
+ - uses: astral-sh/setup-uv@v6
273
+ with:
274
+ enable-cache: true
275
+
276
+ - name: Install africas2s + rosetta
277
+ run: |
278
+ uv sync
279
+ uv pip install -e ./rosetta
280
+
281
+ - name: Configure nuthatch cache location
282
+ run: |
283
+ mkdir -p ~/.cache/nuthatch
284
+ cat > ~/.nuthatch.toml <<EOF
285
+ [tool.nuthatch]
286
+ filesystem = "~/.cache/nuthatch"
287
+ skipped_filesystems = ["gs://sheerwater-datalake/caches"]
288
+ EOF
289
+
290
+ - name: Restore nuthatch cache
291
+ uses: actions/cache@v4
292
+ with:
293
+ path: ~/.cache/nuthatch
294
+ # Share with the verify job's cache — render fetches the same CHIRPS
295
+ # climatology for the comparison-grid obs panel.
296
+ key: nuthatch-publish-${{ github.run_id }}-${{ hashFiles('rosetta/src/rosetta/catalog.yaml', 'rosetta/src/rosetta/normalize.py', 'rosetta/src/rosetta/fetch.py') }}
297
+ restore-keys: |
298
+ nuthatch-verify-
299
+ nuthatch-
300
+
301
+ - name: Write .cdsapirc
302
+ run: |
303
+ umask 077
304
+ cat > ~/.cdsapirc <<EOF
305
+ url: https://ecds.ecmwf.int/api
306
+ key: ${CDSAPI_KEY}
307
+ EOF
308
+
309
+ - name: Render dashboard into a build dir
310
+ run: |
311
+ uv run python -m scripts.s2s.render_dashboard \
312
+ --store-root issuance-store-checkout/issuances \
313
+ --verification-root issuance-store-checkout/verification \
314
+ --dashboard-root build/s2s \
315
+ --config scripts/s2s/s2s.yml
316
+
317
+ - name: Assemble gh-pages tree (hub + theme + s2s/)
318
+ run: |
319
+ uv run python -m scripts.s2s.publish_pages \
320
+ --site-src site \
321
+ --s2s-build build/s2s \
322
+ --pages-root gh-pages-checkout
323
+
324
+ - name: Commit + push gh-pages
325
+ working-directory: gh-pages-checkout
326
+ run: |
327
+ set -euo pipefail
328
+ git config user.name "github-actions[bot]"
329
+ git config user.email "github-actions[bot]@users.noreply.github.com"
330
+ git add index.html theme.css
331
+ [ -d s2s ] && git add -A s2s || true
332
+ if git diff --cached --quiet; then
333
+ echo "No site changes to commit"
334
+ exit 0
335
+ fi
336
+ git commit -m "pages: $(date -u +%Y-%m-%dT%H:%MZ)"
337
+ for attempt in 1 2 3 4 5; do
338
+ git pull --rebase origin gh-pages && git push origin gh-pages && exit 0
339
+ sleep $((attempt * 5))
340
+ done
341
+ exit 1
@@ -0,0 +1,27 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - uses: astral-sh/setup-uv@v6
14
+ with:
15
+ enable-cache: true
16
+
17
+ - name: Install dependencies
18
+ run: uv sync --extra dev
19
+
20
+ - name: Run tests
21
+ run: |
22
+ uv run pytest tests/ \
23
+ --ignore=tests/test_agreement.py \
24
+ --ignore=tests/test_corrdiff.py \
25
+ --ignore=tests/test_corrdiff_integration.py \
26
+ -m "not gpu and not integration" \
27
+ -v
@@ -0,0 +1,54 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+
6
+ # Local virtual environments
7
+ .venv/
8
+ venv/
9
+
10
+ # Test and tooling caches
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ .coverage.*
16
+ htmlcov/
17
+
18
+ # Notebook and editor noise
19
+ .ipynb_checkpoints/
20
+ .DS_Store
21
+
22
+ # Local outputs/artifacts
23
+ demo_cache/
24
+ examples/output/
25
+ *.png
26
+ *.nc
27
+ *.zarr/
28
+
29
+ # PyCPT working directories
30
+ tmp_iri_*/
31
+
32
+ # Scripts cache and output
33
+ scripts/cache/
34
+ scripts/output/
35
+
36
+ # CPT Fortran source (downloaded by scripts/DWN_CPT_SRC.sh)
37
+ CPT_SRC/
38
+
39
+ # S2S pipeline outputs (regenerated by run_issuance / verify / render_dashboard;
40
+ # published to an orphan branch or external bucket, not tracked on the code branch)
41
+ dashboard/
42
+ verification/
43
+ issuances/
44
+
45
+ # Local session / tooling artifacts (Claude Code, superpowers, ad-hoc dumps)
46
+ .superpowers/
47
+ docs/superpowers/
48
+ .old_claude_env
49
+ github-issues.json
50
+
51
+ # Credential files (never commit)
52
+ .cdsapirc
53
+ .ecmwfapirc
54
+ .netrc
@@ -0,0 +1,33 @@
1
+ # Agent instructions for africas2s
2
+
3
+ Instructions for any AI coding agent (Claude Code, Codex, OpenCode, etc.) working in this repository.
4
+
5
+ ## Before you start
6
+
7
+ Read [`skills/africas2s/SKILL.md`](skills/africas2s/SKILL.md) before writing or modifying code that uses africas2s. It is the authoritative quick reference for the public API, data conventions, method/metric/strategy registries, and the statistical discipline rules (tercile leakage, grid rules, CV requirements), with deeper detail in `skills/africas2s/references/`. If your harness supports Agent Skills natively, load the skill instead of re-deriving the API from source.
8
+
9
+ ## Keep the docs and skill in sync — this is a hard requirement
10
+
11
+ The skill is a snapshot of the source. Any change that alters observable behavior MUST update the matching documentation **in the same PR**:
12
+
13
+ | If you change... | You must update... |
14
+ |---|---|
15
+ | Public verb signatures/behavior (`downscale`, `optimize`, `train`, `calibrate`, `ensemble`, `skill`, `skill_compare`, `seasonal_mme`, `flex_forecast`, ...) or result dataclasses | `skills/africas2s/SKILL.md` + `skills/africas2s/references/api.md` |
16
+ | `Index` (`indices.py`): named indices, `REGIONS`, `transform`/`weights`/`baseline`, `reduce` signature | `skills/africas2s/references/api.md` |
17
+ | Analog selection (`analog.py`), scenario completion (`completion.py`), climate positioning (`climate.py`), scalar-series calibration (`series.py`), calendar/season-step utilities (`time.py`) | `skills/africas2s/references/analog-completion.md` (+ `SKILL.md` "Analog completion & climate positioning") |
18
+ | Methods, calibrators, ensemble strategies, CV schemes, `pool_ensembles` (`ensemble.py`), tercile combination/masking (`combine.py`) — add/remove/rename, parameter changes | `skills/africas2s/references/methods.md` |
19
+ | Metrics, tercile conversion, boundaries, significance tools (`metrics/significance.py`, `metrics/cross_validation.py`) | `skills/africas2s/references/metrics-and-terciles.md` |
20
+ | Plotting/reporting functions or export formats — incl. `plotting/maps.py`, `plotting/scenarios.py`, `plotting/forecasts.py` | `skills/africas2s/references/plotting-reporting.md` |
21
+ | Error messages, extras, environment requirements | `skills/africas2s/references/troubleshooting.md` |
22
+ | Rosetta integration / input data shapes | `skills/africas2s/SKILL.md` ("Getting data in") |
23
+ | Anything user-facing | `README.md` if it covers the topic |
24
+
25
+ Also update `skills/africas2s/examples/` if a change breaks or obsoletes an example. If you are unsure whether a change is documented, grep `skills/` and `README.md` for the function, method name, or parameter you touched — stale docs are treated as bugs.
26
+
27
+ ## Repo conventions
28
+
29
+ - Package layout: `src/africas2s/` (import name `africas2s`, distribution `africas2s`). Python ≥ 3.10, `uv` for dependency management.
30
+ - Methods/metrics/strategies/calibrators are looked up by name via `africas2s/registry.py` — new capabilities register there rather than being hard-wired.
31
+ - Tests: bare `pytest` must stay fast (< 30 s) and green; markers `integration`, `agreement`, `gpu` gate slow/real-data suites. Coverage target > 85% on `src/africas2s`. New behavior needs tests.
32
+ - Statistical honesty is a design invariant: cross-validated outputs must never leak held-out years (use `to_tercile_cv`, nested CV safeguards). Do not weaken these paths for convenience.
33
+ - CCA numerics intentionally match CPT Fortran 17.8.3 — changes there must preserve parity (`scripts/reproduce.py`, `agreement` tests).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 accord-research
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.