Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
^treescan_project$
^.*\.docx$
^README\.qmd$
^\.claude$
^.*\.Rproj$
^\.Rproj\.user$
^\.quarto$
^data-raw$
^README_files$
^\.devcontainer$
^\.github$
^r-package-port\.md$
34 changes: 34 additions & 0 deletions .devcontainer/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Development / CI image for treescanr: rocker devcontainer + Quarto + TreeScan.
#
# TreeScan(TM) cannot be downloaded without an account, so the binary is NOT
# in the repository. Build with .devcontainer/build.sh, which copies the Linux
# tarball (treescan.X.Y.Z.tar.gz, from https://www.treescan.org) into the
# build context. The resulting image must stay PRIVATE: users of TreeScan(TM)
# must read and agree to its license (/opt/treescan/documents/eula).
FROM ghcr.io/rocker-org/devcontainer/r-ver:4.6

LABEL org.opencontainers.image.source="https://github.com/EpiForeSITE/TreeScan" \
org.opencontainers.image.description="treescanr development image (includes TreeScan(TM), private)"

# Architecture-specific variable (the TreeScan Linux binary is amd64 only)
ARG TARGETARCH

# Install quarto-cli
RUN wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.9.35/quarto-1.9.35-linux-${TARGETARCH}.deb && \
dpkg -i quarto-1.9.35-linux-${TARGETARCH}.deb && \
rm quarto-1.9.35-linux-${TARGETARCH}.deb

# Adding R packages
RUN install2.r --error data.table tinytest roxygen2 quarto knitr rmarkdown

# TreeScan(TM) command-line binary
COPY treescan.tar.gz /tmp/treescan.tar.gz
RUN mkdir -p /opt/treescan && \
tar xzf /tmp/treescan.tar.gz -C /opt/treescan && \
rm /tmp/treescan.tar.gz && \
chmod 0755 /opt/treescan/treescan64 && \
ln -s /opt/treescan/treescan64 /usr/local/bin/treescan64

ENV TREESCAN_BIN=/opt/treescan/treescan64

CMD ["bash"]
25 changes: 25 additions & 0 deletions .devcontainer/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh
# Build and push the private treescanr dev image to ghcr.io.
#
# Usage: .devcontainer/build.sh <path/to/treescan.X.Y.Z.tar.gz> [tag]
#
# Requires docker or podman, and a login to ghcr.io with a token that has the
# write:packages scope, e.g.:
# gh auth refresh -s write:packages,read:packages
# gh auth token | podman login ghcr.io -u <github-user> --password-stdin
set -eu

TARBALL=${1:?"path to the TreeScan Linux tarball"}
TAG=${2:-latest}
IMAGE=ghcr.io/epiforesite/treescanr-dev
ENGINE=$(command -v docker || command -v podman)
DIR=$(cd "$(dirname "$0")" && pwd)

cp "$TARBALL" "$DIR/treescan.tar.gz"
trap 'rm -f "$DIR/treescan.tar.gz"' EXIT

# The TreeScan Linux binary is x86-64 only
"$ENGINE" build --platform linux/amd64 -f "$DIR/Containerfile" \
-t "$IMAGE:$TAG" "$DIR"

"$ENGINE" push "$IMAGE:$TAG"
19 changes: 19 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Private image built from .devcontainer/Containerfile with .devcontainer/build.sh
// (it bundles TreeScan, which cannot be downloaded anonymously).
{
"name": "treescanr",
"image": "ghcr.io/epiforesite/treescanr-dev:latest",
"runArgs": ["--platform=linux/amd64"],
"customizations": {
"vscode": {
"extensions": [
"reditorsupport.r",
"rdebugger.r-debugger",
"quarto.quarto",
"github.vscode-github-actions"
]
}
},
"postCreateCommand": "R CMD INSTALL .",
"remoteUser": "root"
}
43 changes: 43 additions & 0 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# R CMD check inside the private dev image, which includes TreeScan(TM) so the
# full pipeline test (ts_run) is exercised. Pull requests from forks cannot
# pull the private image.
name: R-CMD-check

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read
packages: read

jobs:
R-CMD-check:
runs-on: ubuntu-latest
container:
image: ghcr.io/epiforesite/treescanr-dev:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4

- name: Build
run: R CMD build .

- name: Check
env:
_R_CHECK_CRAN_INCOMING_: false
run: R CMD check --no-manual --as-cran treescanr_*.tar.gz

- name: Show test output
if: always()
run: find treescanr.Rcheck -name '*.Rout*' -exec cat {} \;

- uses: actions/upload-artifact@v4
if: failure()
with:
name: R-CMD-check-results
path: treescanr.Rcheck
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.Rcheck/
treescanr_*.tar.gz
README_files/
.Rproj.user/
.quarto/
.devcontainer/*.tar.gz
.devcontainer/*.bz2
23 changes: 23 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Package: treescanr
Title: Tree-Based Scan Statistics for Syndromic Surveillance with 'TreeScan'
Version: 0.0.1
Authors@R:
person("George G.", "Vega Yon", , "g.vegayon@gmail.com", role = c("aut", "cre"))
Description: A lightweight pipeline to prepare emergency department visit data,
build incident-diagnosis count files, write parameter files, and run the
'TreeScan' command-line software on ICD-10-CM trees. Ported from the
epiENGAGE TreeScan implementation scripts.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
Depends: R (>= 4.1.0)
Imports:
data.table,
utils
Suggests:
tinytest,
quarto
VignetteBuilder: quarto
SystemRequirements: TreeScan (>= 2.4.1) command-line binary
(https://www.treescan.org), Quarto (for vignettes)
Config/roxygen2/version: 8.0.0
2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
YEAR: 2026
COPYRIGHT HOLDER: treescanr authors
17 changes: 17 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by roxygen2: do not edit by hand

S3method(print,ts_prm)
S3method(print,ts_result)
export(ts_binary)
export(ts_counts)
export(ts_ineligible_pattern)
export(ts_prm_get)
export(ts_prm_read)
export(ts_prm_set)
export(ts_prm_template)
export(ts_prm_write)
export(ts_results)
export(ts_run)
export(ts_visits)
export(ts_visits_nssp)
import(data.table)
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# treescanr 0.0.1

* Initial MVP: `ts_visits()`, `ts_visits_nssp()`, `ts_counts()`, `ts_prm_*()`,
`ts_binary()`, `ts_run()`, and `ts_results()`, ported from the scripts in
`treescan_project/code/` (steps 2 to 5).

* Private devcontainer image (`ghcr.io/epiforesite/treescanr-dev`) with
TreeScan 2.4.1, used by the `R-CMD-check` GitHub Action.
36 changes: 36 additions & 0 deletions R/binary.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#' Locate the TreeScan command-line binary
#'
#' Checks that the TreeScan binary exists and is executable. The non-graphical
#' (command-line) version of TreeScan can be downloaded from
#' <https://www.treescan.org/download_treescan.html>.
#'
#' @param path Path to the binary (e.g., `treescan64` or `treescan64.exe`).
#' Defaults to the `treescanr.binary` option, then the `TREESCAN_BIN`
#' environment variable, then `treescan64` on the `PATH`.
#' @return The normalized path to the binary.
#' @export
#' @examples
#' \dontrun{
#' options(treescanr.binary = "~/TreeScan/treescan64")
#' ts_binary()
#' }
ts_binary <- function(
path = getOption("treescanr.binary", Sys.getenv("TREESCAN_BIN"))
) {
if (is.null(path) || !nzchar(path))
path <- Sys.which(c("treescan64", "treescan64.exe"))[1L]

if (is.na(path) || !nzchar(path) || !file.exists(path))
stop(
"TreeScan binary not found. Download the command-line version from ",
"https://www.treescan.org/download_treescan.html and set ",
"`options(treescanr.binary = <path>)` or the TREESCAN_BIN environment ",
"variable.", call. = FALSE
)

path <- normalizePath(path, winslash = "/", mustWork = TRUE)
if (.Platform$OS.type == "unix" && file.access(path, 1L) != 0L)
Sys.chmod(path, mode = "0755")

path
}
Loading
Loading