The code that turns landing surveys and boat GPS tracks from Timor-Leste into the data behind Peskas Timor-Leste.
See the results at timor.peskas.org. Timor-Leste also appears on coasts.peskas.org, the Peskas Coasts regional comparison, which this pipeline feeds through a separate step.
This pipeline collects, checks and models small-scale fisheries data for Timor-Leste. It feeds Peskas Timor-Leste, the country’s official national fisheries monitoring system, used by fisheries managers, researchers and the public. It runs automatically every two days.
- Checks every landing survey and flags records that look wrong. The flags go to the Peskas Management Platform, and a weekly email lists the submissions with problems.
- Estimates the weight of each catch and the nutrients it provides.
- Estimates monthly catch and revenue for each municipality and for the whole country.
- Publishes the data files that timor.peskas.org reads, and a data report twice a week.
- Sends Timor-Leste landing records to the Peskas Fishery Data API, and summaries to Peskas Coasts.
- Publishes the checked landing records as an open, citable dataset on Harvard Dataverse, with a new version every month.
- Landing surveys: enumerators record landings around Timor-Leste with KoboToolbox. The survey form has changed twice since it started, and the pipeline reconciles the three versions.
- GPS trackers (Pelagic Data Systems) on part of the fleet, matched to the surveyed landings.
- Reference lists of boats, species and landing sites, including the number of registered boats in each area.
- Length-weight formulas from FishBase and SeaLifeBase, used to turn fish lengths into weights.
The data updates every two days. Known limits:
- National and municipal totals are estimates: models scale the surveyed landings up using the number of registered boats.
- GPS tracks cover only boats that carry a tracker.
Key terms:
- Landing: a boat’s return to shore with its catch, recorded by an enumerator.
- Enumerator: a trained data collector who records landings at landing sites.
- KoboToolbox: the free mobile survey app enumerators use to record landings.
- GPS trackers (Pelagic Data Systems): small solar-powered devices on boats that record where they travel.
Peskas Timor-Leste is a partnership, since 2016, between WorldFish and the Timor-Leste Ministry of Agriculture and Fisheries (MAF), through its Department of Fisheries, Aquaculture and Marine Resources. Since 2021 it has been funded by the Government of Timor-Leste, with technical support from WorldFish and Pelagic Data Systems Inc. Its development was supported by the Royal Norwegian Embassy in Jakarta, the Minderoo Foundation, the CGIAR Big Data Platform and the Schmidt Foundation.
For questions, write to peskas.platform@gmail.com.
Peskas is WorldFish’s open-source platform for monitoring small-scale fisheries (https://peskas.org).
- Peskas Zanzibar, Peskas Kenya, Peskas Mozambique: country dashboards
- Peskas Timor-Leste: Timor-Leste portal
- Peskas Coasts: regional comparison across countries
- Peskas Tracks: app for fishers to see their trips and log catches
- Peskas Kenya BMU dashboard: dashboard for Beach Management Units in Kenya
- Peskas Management Platform: data review and download for survey teams
- Peskas Fishery Data API: programmatic access to landing data
- Data pipelines: Kenya, Zanzibar, Mozambique, Coasts
Each step is an exported function, and the GitHub Actions workflow calls
them in order. Everything not specific to Timor-Leste (cloud storage,
KoboToolbox retrieval, GPS tracker ingestion, reference lists, MongoDB
access, species weights) lives in
peskas.coasts; call
it as coasts::fn(). This package keeps the survey reshaping, the
validators, the catch and revenue models, the nutrient calculations and
the portal file contract. The function reference is at
https://worldfishcenter.github.io/peskas.timor.data.pipeline/.
peskas.coasts is not on CRAN. It is declared in Remotes:, and the
production container installs the latest coasts release; see
DESCRIPTION for the minimum version.
# Copy .env.example to .env and fill in the values (ask the Peskas team).
remotes::install_deps(dependencies = TRUE) # installs peskas.coasts from GitHub
devtools::load_all()
devtools::document() # rebuild man/ after changing roxygen comments
devtools::check()inst/config.yml, read by read_config(), has two
profiles:
default: development. It uses the-devbuckets (timor-dev,pds-timor-dev,public-timor-dev,peskas-api-dev), so the whole pipeline can run without touching anything the portal reads. A local.Renvironsets it for local sessions (it is gitignored, so create it withR_CONFIG_ACTIVE=default), and every push to a branch other thanmainruns it in CI.production: the same code against the production buckets. The workflow selects it only onmain. Never write to a production bucket from a local session or an ad-hoc script.
Credentials come from the environment, never from a file in the
repository. Copy .env.example to .env for local
work; read_config() loads it. In CI the same variables come from
GitHub secrets. .env is gitignored.
The resolved config holds the service-account key and every token, and
GitHub Actions masks only exact matches of a secret. read_config()
therefore logs key names only; log single values you need, never the
whole config. Logging uses the
logger package: pass
log_threshold = logger::INFO to every coasts:: call in a workflow.
.github/workflows/data-pipeline.yaml
is the only workflow that produces data. It runs every two days and on
every push; read it for the exact needs: graph. In outline:
build-container
├── ingest-landings -> preprocess-landings -> merge-landings -> validate-landings
└── ingest-pds-data -> preprocess-pds-data -> validate-pds-data
validate-landings ├── export-api
└── merge-trips (also needs validate-pds-data)
├── model-indicators
└── export-trips (also needs model-indicators)
export-api + preprocess-pds-data -> summarize-model-data -> export-surveys-portal
export-trips writes the files timor.peskas.org reads.
summarize-model-data and export-surveys-portal are the shared coasts
steps that put Timor-Leste on Peskas Coasts; neither feeds
timor.peskas.org.
Three scheduled workflows run main’s code in the :latest container,
which only a run on main pushes (other branches push :dev):
data-report.yaml (Mondays and Thursdays),
validation-email-sender.yaml (Mondays) and dataverse-upload.yaml
(first of each month).
Files the jobs write are versioned as
<prefix>__<YYYYMMDDHHMMSS>_<git-sha7>__.<ext>, so each one traces back
to a single run.
upload_dataverse() publishes the table the Peskas Fishery Data API
serves for Timor-Leste (the trips-validated file that export-api
writes) to the Peskas
collection on Harvard
Dataverse. Each release is a new version of one dataset, so its DOI
stays the same.
- Files:
timor_landings.csvand aREADME.mdbuilt frominst/export/README.md. The column descriptions in it are read from the API (/metadata/landings) at release time, so they are maintained in peskas-api, not here. A column the API does not describe stops the release. - Dataset record (title, authors, licence, links):
inst/export/dataset-fields.json. It is sent on every release, so edit it there; a change made on the Dataverse website is overwritten. - Which dataset:
export_dataverse.dataset_doiin the config. While it is empty, a run creates the dataset as an unpublished draft and logs its DOI. Check the draft on Dataverse, put the DOI in the config, and the next run publishes it. - Safety: only the
productionprofile publishes. A manual run from another branch updates the draft and stops. - Secrets:
DATAVERSE_TOKEN. Reading the column descriptions needs no key.
The calls follow the Dataverse native API. Releases from March 2022 to June 2025 are older, separate datasets in the same collection, with different files.
Dockerfile.prod is the image the pipeline runs in.
The first workflow job builds it and every other job uses it. It
installs peskas.coasts at the COASTS_REF build argument, which has no
default:
docker build -f Dockerfile.prod --build-arg COASTS_REF=<coasts release tag> -t peskas-timor .tests/testthat/: unit tests over reshaping and schema logic, with no network or credentials. Rundevtools::test().inst/tinytest/: six assertion suites over the real files a run produces (validated landings, validated GPS trips, Peskas Coasts summaries, merged trips, the portal contract and public data). They run as steps inside the pipeline, right after the job that writes what they check, so a schema change stops the pipeline before it reaches the portal.
The portal finds its files by name, so a renamed or dropped file
disappears from the site without an error.
inst/tinytest/test_portal_contract.R guards this on every run; run it
before changing anything on the export path:
tinytest::run_test_file(system.file("tinytest/test_portal_contract.R",
package = "peskas.timor.data.pipeline"))Bump Version: in DESCRIPTION and add a block at the top of
NEWS.md. On a push to main, release.yaml turns that
block into a GitHub release.
See CLAUDE.md and .claude/rules/.
GPL-3. See LICENSE.md.
