Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
28ee690
Add CLAUDE.md with architecture and development guidance
Jun 8, 2026
c36aa55
Add analysis/ HPC layer: config-driven SLURM parallelization over cou…
Jun 9, 2026
eb15b96
Make GeoParquet optional via use_geoparquet config flag
Jun 9, 2026
3a21cac
make test config only 1 month of data
Jun 9, 2026
2c66c49
add module load for yggdrasil
Jun 9, 2026
ce5d5cd
update model load for yggdrasil to allow abseil
Jun 9, 2026
56bde24
add taxonomy credentials call
Jun 9, 2026
cf01ff0
add script to install packages on yggdrasil
Jun 9, 2026
3efda61
update modules
Jun 9, 2026
80a17d2
fix to add population using get_pop
Jun 9, 2026
afa967c
update module loading for hpc
Jun 9, 2026
ec08316
use source to apply env variable
Jun 9, 2026
6a5cd17
fix call to taxdat rename_database_fields
Jun 9, 2026
e8e28b2
patch rename_database_fields
Jun 9, 2026
bdb42d0
temp hack to turn off ssl verification to pull api
Jun 9, 2026
91f7a1e
add rjson to library loads
Jun 9, 2026
925f664
replace rjson with jsonlite
Jun 9, 2026
70f1fa8
add missing packages
Jun 9, 2026
b1d8046
add WorldPop population estimation to Stage 1 pipeline
Jun 9, 2026
5e335bd
use hack to get around ssl
Jun 9, 2026
d16d480
wire taxdat:: namespace and normalize API column names
Jun 9, 2026
434c406
fix 404: use opt\$api_website instead of hardcoded URL
Jun 9, 2026
8c8bd19
bypass ssl_verifypeer for API call (server cert covers base domain only)
Jun 9, 2026
f845765
fix ssl bypass: use set_config/reset_config instead of with_config
Jun 9, 2026
7d46bb7
fix ssl: disable ssl_verifyhost (hostname mismatch, not CA trust)
Jun 9, 2026
99c7bb4
switch to taxdat::read_taxonomy_data_api (drop pull_taxonomy_data wra…
Jun 9, 2026
d80aa3c
fix HPC 500 and clean_columns parse error
Jun 9, 2026
a6262c1
fixes for step 1
Jun 9, 2026
d0f3450
update descdiption
Jun 9, 2026
ef86ed6
remove dependency on tidyverse
Jun 9, 2026
54b2739
add sf to 01
Jun 9, 2026
aee1d53
update configs to 4-monthly time bounds
Jun 9, 2026
8a8ffe7
date time bounds for configs
Jun 9, 2026
9692d0f
update for first test run
Jun 9, 2026
586e31c
fix dplry in 00
Jun 9, 2026
fd6cde8
make time bounds characters
Jun 9, 2026
9ec7091
guard against dropping cCh
Jun 9, 2026
705e1a4
dont use s2
Jun 9, 2026
8cffb30
fix pull_set errors 1-3 from 2026-06-09 run
Jun 9, 2026
eb02e53
Fix pull_set errors 4-6 and clean up add_population geometry guards
Jun 9, 2026
1812eb4
expand pull set to all countries
Jun 9, 2026
21107e8
add full country list jobs
Jun 9, 2026
67ba252
Fix pull_set topology and empty-response crashes
Jun 10, 2026
1aba099
Fix positional arg bug in identify_outbreaks() call to get_outbreak_t…
Jun 10, 2026
215d9ba
Fix identify_outbreaks() crash when last outbreak has no epidemic tail
Jun 10, 2026
cc64609
redo outbreak detection by default
Jun 10, 2026
ccf82ce
Fix NA risk propagation crash in outbreak detection
Jun 10, 2026
607b312
Add stage2 summary scratchpad with coverage and outbreak descriptive …
Jun 10, 2026
92a3c03
update config defaults to retained outbreak definition
Jun 10, 2026
d3fbed1
Fix population lookup for compound ISO3 codes (TZA::Mainland, TZA::Za…
Jun 10, 2026
c59eb10
Add Shiny outbreak explorer and fix centroid coverage for COD/TZA
Jun 11, 2026
b3b6906
Fix outbreak period shading in weekly time series
Jun 11, 2026
7e1eb2d
Switch app data source to parquet; add shinyapps.io deploy config
Jun 11, 2026
de06dd6
Replace parquet with RDS to fix shinyapps.io build failure
Jun 11, 2026
f781b3c
Add verify_outbreak_definitions() for post-hoc consistency checking
Jun 12, 2026
5f3151e
Align analysis pipeline with reference Step2_Extract_outbreak.R
Jun 12, 2026
f473047
Update parse_detect_logs.R for new single-pass pipeline format
Jun 12, 2026
1956f7e
Fix mixed-geometry crash in add_population() for large countries (COD)
Jun 15, 2026
81e3463
Add post-detection outbreak size filter; fix hardcoded tail extension…
Jun 19, 2026
98fb9c3
fix(detect): match geo files by raw country_iso3 so pop attaches for …
Jun 20, 2026
92a5e00
feat(detection_set): add filter_outbreaks_by_size + extend TZA to 2024
Jun 22, 2026
9627e07
fix(pull_data): patch taxdat::read_taxonomy_data_api to handle missin…
Jun 23, 2026
4a3ab3d
fix(add_population): st_make_valid() before st_union() in geoboundari…
Jun 23, 2026
08bd97e
feat(detect): restore composite-location outbreak detection (Stage 2)
Jun 23, 2026
537d6ba
fix(shiny): remove stale run_id from group_by in outbreak summary
Jul 9, 2026
faadb7a
feat(composite): geometry-derived population + child resolution for c…
Jul 24, 2026
53f91c0
fix(population): eliminate zero-population denominators, detect dupli…
Aug 6, 2026
c0142a9
feat(detect): wire keep_nonoutbreak_locations into Stage 2 config
Aug 6, 2026
497074d
fix(population): compare each LP to its own year's national total in …
Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
^.*\.Rproj$
^\.Rproj\.user$
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.Rproj.user
.Rhistory
.RData
.Ruserdata

# R check artifacts
*.Rcheck/

# Generated analysis outputs — large binaries, not source
analysis/generated_data/
analysis/worldpop/

# SLURM logs
logs/
141 changes: 141 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Package Overview

**OutbreakExtractR** is an R package for epidemiological cholera outbreak surveillance. It extracts and identifies cholera outbreaks from surveillance data (sourced from a PostgreSQL/Global Cholera Taxonomy Database) using a configurable operational outbreak definition.

## Development Commands

```r
# Regenerate documentation (run before installing after any @param/@export changes)
devtools::document()

# Install from source
install.packages('.', repo=NULL, type="source")

# Run all tests
devtools::test()

# Run a single test file
testthat::test_file("tests/testthat/test-clean_psql_data.R")

# Load package interactively without reinstalling
devtools::load_all()

# Check the full package (CRAN-style)
devtools::check()
```

NAMESPACE is auto-generated by roxygen2 — never edit it by hand.

## Architecture

The pipeline has five sequential layers:

### 1. Ingestion & Cleaning (`R/clean_*.R`, `R/observation_filter.R`)
- `clean_psql_data()` — entry point for raw PostgreSQL CSV exports; standardizes types, identifies spatial/temporal scales, flags primary records
- `clean_location_names()` — normalizes `::`-delimited location hierarchy strings (`country::admin1::admin2::admin3::admin4`)
- `observation_filter()` — subsets by date range, WHO region, scale, and minimum case count

### 2. Normalization (`R/observation_aggregator.R`, `R/fill_*.R`, `R/set_uniform_*.R`)
- Aggregates daily data to weekly; aligns week-start days; fills phantom zero-case weeks (±8 weeks padding) and missing location-periods from linked records
- Time periods are always represented as `TL` (start) / `TR` (end) date pairs

### 3. Threshold Calculation (`R/get_outbreak_threshold.R`, `R/get_pop.R`, `R/calculate_population_density.R`)
- Supports four threshold modes: `fixed`, `mean_weekly_incidence`, `outbreak_dependent`, `time_restricted`
- Population data comes from bundled `WPP2022.rda` / `WPP2024.rda` (WorldPop) and `endemic_locs_gte50nz_3ydata.rda`

### 4. Outbreak Detection (`R/identify_*.R`)
`identify_outbreaks()` is the main orchestrator. It calls:
- `identify_epidemic_start()` — two modes: `"consecutive"` (N weeks above threshold) or `"dual_window"` (sliding window + cumulative case trigger); key params: `min_weeks_above`, `window_weeks`, `cumulative_case_threshold_ratio`
- `identify_epidemic_tail()` — marks outbreak end after `tail_period` (default 6) consecutive weeks below threshold
- `identify_consecutive_outbreak_data()` / `identify_epidemic_period()` — group and extract bounded outbreak windows
- `identify_washout_period()` — labels inter-epidemic gaps

### 5. Alert Generation & Post-Processing (`R/trigger_alert*.R`, `R/format_alerts.R`, `R/label_*.R`)
`trigger_alert()` fires 17 alert types across three families:
- **Trend**: single/double/triple week exceeding 4-week rolling mean
- **Case count**: 2/5/10/25/50/100/250 cases in 3 consecutive weeks
- **Cumulative**: 5/10/25/50/100/500/1000 cumulative cases in 3 weeks

Functions suffixed `_obs` operate on observatory (observed) data; others operate on modeled/threshold data.

## Key Conventions

- **Location strings** use `::` as hierarchy delimiter (e.g., `"Chad::Borkou"`). Helper `custom_paste()` handles safe concatenation.
- **Spatial scales**: `country`, `admin1`–`admin4` (and `admin4+`)
- **Temporal scales**: `daily`, `multiday`, `weekly`, `multiweek`, `monthly`, `multimonth`, `yearly`, `multiyear`
- **WHO regions**: `AFR`, `EMR`, `AMR`, `SEAR`, `EUR`, `WPR`
- Code uses R 4.1+ lambda syntax (`\(x)`) throughout; pipe is `%>%` (magrittr)
- Rolling windows via `slider`; RLE-based grouping via `zoo`; per-location processing via `purrr::map`

## Testing

Test files live in `tests/testthat/` with CSV fixtures (`outbreak_testing_data.csv`, `clean_outbreak_testing_data.csv`). Tests use **testthat edition 3**. Coverage focuses on filtering boundaries, type coercion, NA handling, aggregation correctness, and de-duplication logic.

---

## HPC / SLURM Analysis Layer (`analysis/`)

A config-driven pipeline that parallelizes over **countries × time windows** on Yggdrasil (University of Geneva HPC). Built on the same pattern as `mpox-uvira-sprint`.

### Quick start

```bash
# 1. Generate YAML configs
Rscript analysis/00_make_configs.R

# 2. Set API credentials (these propagate to SLURM tasks via --export=ALL)
export CHOLERA_API_USERNAME=<username>
export CHOLERA_API_KEY=<api_key>

# 3. Submit Batch 1 (data pull), chain Batch 2 (outbreak detection) after it
BATCH1=$(sbatch --parsable analysis/bash/submit_01_pull_data.sh)
sbatch --dependency=afterok:$BATCH1 analysis/bash/submit_02_detection.sh

# 4. After all jobs finish, aggregate results locally
Rscript analysis/03_aggregate_results.R
```

### Architecture

**Two SLURM batches:**
- **Batch 1** (`submit_01_pull_data.sh`): array over `analysis/configs/pull_set/` — one task per country × time window. Pulls via `taxdat::pull_taxonomy_data(source="api")`, runs the full normalization pipeline, writes:
- `stage1_geo_{run_id}.parquet` — GeoParquet with geometry (via `sfarrow`)
- `stage1_flat_{run_id}.parquet` — flat Parquet without geometry (input to Batch 2)
- **Batch 2** (`submit_02_detection.sh`): array over `analysis/configs/detection_set/` — one task per country. Globs all Stage 1 flat files for that country, runs `identify_outbreaks()` + `trigger_alert()` per time window, writes `stage2_{region}_{iso3}.parquet`.

### Config system

- `analysis/config_defaults.yml` — full parameter schema with defaults
- `analysis/00_make_configs.R` — editable country list and time windows; generates `pull_set/` and `detection_set/` configs
- `analysis/utils.R` — shared helpers: `write_configs()`, `make_options_from_config()`, `make_taxdat_location()`, filename functions

**taxdat location format:** `"CT-World::{WHO_REGION}::{ISO3}"` (e.g. `"CT-World::AFR::COD"`).
**Credentials:** always via env vars `CHOLERA_API_USERNAME` / `CHOLERA_API_KEY` — never in config files.

### Local dry-run workflow

```bash
# Generate a 1-country test config
# (already created by 00_make_configs.R as configs/test_pull/ and test_detection/)

# Test Batch 1
Rscript analysis/01_pull_data.R -c analysis/configs/test_pull/test_pull_1.yml

# Test Batch 2
Rscript analysis/02_run_outbreak_detection.R -c analysis/configs/test_detection/test_detection_1.yml

# SLURM dry run (no submission)
SLURM_ARRAY_TASK_ID=0 bash analysis/bash/submit_01_pull_data.sh
```

### SLURM array bounds

After running `00_make_configs.R`, it prints the exact `--array` bounds to set in the submission scripts. **Edit the `#SBATCH --array=` lines** in `submit_01_pull_data.sh` and `submit_02_detection.sh` before submitting.

### GeoParquet

`get_shp()` now accepts an `output_parquet` parameter — when provided, it saves the returned sf object as GeoParquet via `sfarrow::st_write_parquet()`. All `analysis/` layer outputs use `.parquet` format (`sfarrow` for spatial, `arrow` for tabular).
38 changes: 34 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,40 @@ Description: This package
License: GLP-2
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Suggests:
testthat (>= 3.0.0)
Imports:
DBI,
RPostgres,
glue,
sf,
dplyr,
purrr,
tidyr,
stringr,
tibble,
lubridate,
zoo,
tidyselect,
rlang,
magrittr,
slider,
curl,
raster,
exactextractr
Suggests:
testthat (>= 3.0.0),
sfarrow,
arrow,
yaml,
optparse,
furrr,
future,
here,
taxdat,
rgeoboundaries,
digest,
withr
Config/testthat/edition: 3
Depends:
Depends:
R (>= 2.10)
LazyData: true
Config/roxygen2/version: 8.1.0
10 changes: 10 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ export(add_outcome_bin)
export(add_pop3_adm_columns)
export(add_pop3_adm_columns_outbreak)
export(add_pop6_adm_columns)
export(add_population)
export(add_unique_alert_ids)
export(average_duplicate_observations)
export(band_adj_factor)
export(build_composite_locations)
export(calculate_cases)
export(calculate_population_density)
export(clean_location_names)
Expand All @@ -19,13 +22,17 @@ export(create_alert_groups)
export(create_alert_groups2)
export(custom_paste)
export(define_postprocessed_alerts)
export(detect_duplicate_geometries)
export(estimate_pop_for_geometries)
export(extract_agroup_case_outcomes)
export(extract_agroup_pers_outcomes)
export(extract_alert_outcomes)
export(extract_binary_case_outcomes)
export(fill_missing_lps)
export(fill_phantom_zeroes)
export(filter_ms_data)
export(format_alerts)
export(get_country_boundary)
export(get_epiweek)
export(get_ghs_pop)
export(get_outbreak_threshold)
Expand All @@ -46,11 +53,14 @@ export(label_dimensions)
export(observation_aggregator)
export(observation_filter)
export(remove_consecutive_alerts)
export(resolve_composite_children)
export(set_uniform_wday_start)
export(trigger_alert)
export(trigger_alert_caseratio)
export(trigger_alert_cases)
export(trigger_alert_rate)
export(trigger_alert_trends)
export(validate_population)
export(verify_outbreak_definitions)
import(magrittr)
importFrom(magrittr,"%>%")
20 changes: 20 additions & 0 deletions OutbreakExtractR.Rproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Version: 1.0

RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8

RnwWeave: Sweave
LaTeX: pdfLaTeX

AutoAppendNewline: Yes

BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace
Loading