Download R/bundle.R from fmegahed/decisions: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/spaces/fmegahed/decisions/resolve/main/R/bundle.R
- Command line
-
hf download hf://spaces/fmegahed/decisions/R/bundle.R
-
curl -L -o bundle.R https://huggingface.co/spaces/fmegahed/decisions/resolve/main/R/bundle.R
10.3 kB
| # Bundle and import. The bundle is one zip file with the code, the setup of | |
| # decisions, the answers and the references of a run. It holds no input image | |
| # of the visitor and no key. Import reads a setup of this app back, with our | |
| # own parser: it never calls decisions::read_setup() and never unserializes. | |
| # The text of the README of a bundle (80% ASD-STE100) | |
| bundle_readme <- function(spec, refs, name, has_data, date) { | |
| files <- c( | |
| sprintf("- `%s.R`: the R code of the run.", name), | |
| sprintf( | |
| "- `%s-setup.json`: the setup of the run. It holds the model, the question, the references and the image settings. It holds no key.", | |
| name | |
| ), | |
| sprintf("- `%s-answers.csv`: the answers of the run.", name), | |
| if (nrow(refs) > 0) "- `references/`: the reference images of the run. The code reads them.", | |
| if (has_data) "- `data.csv`: your texts. The code reads them." | |
| ) | |
| run <- c( | |
| "Put the files in one folder. Make this folder the working directory of R.", | |
| if (spec$mode == "image" && spec$source != "textiles") { | |
| 'Put your images in the folder "images", as the code lists them. This bundle holds no input image.' | |
| }, | |
| if (spec$mode == "text" && spec$source == "upload" && !has_data) { | |
| 'Save your file as "data.csv" in the same folder.' | |
| }, | |
| sprintf("Then run `%s.R`.", name) | |
| ) | |
| paste(c( | |
| sprintf("# %s", name), | |
| "", | |
| "This zip file holds one run of the decisions Space.", | |
| "", | |
| "## Files", | |
| "", | |
| files, | |
| "", | |
| "## Versions", | |
| "", | |
| sprintf("- App: %s", APP_VERSION), | |
| sprintf("- decisions: commit %s", DECISIONS_SHA), | |
| sprintf("- Model: %s", MODEL), | |
| sprintf("- Date: %s", format(date)), | |
| "", | |
| "## Install", | |
| "", | |
| "Install the package decisions in R:", | |
| "", | |
| ' pak::pak("fmegahed/decisions")', | |
| "", | |
| "## Key", | |
| "", | |
| "The code needs your TypeSafe key. Save the key in the environment variable `TYPESAFE_API_KEY`.", | |
| "For example, add the line `TYPESAFE_API_KEY=` followed by your key to the file `.Renviron` in your home folder. Then restart R.", | |
| "Never write the key in the script.", | |
| "", | |
| "## Run", | |
| "", | |
| run, | |
| "", | |
| "## Cite", | |
| "", | |
| "If you use this run, cite the package and the Space:", | |
| "", | |
| sprintf("- Megahed, F. M., & Chen, Y.-J. (2026). *decisions: Typed decisions from decision models* (R package version %s). https://github.com/fmegahed/decisions", as.character(utils::packageVersion("decisions"))), | |
| sprintf( | |
| "- Megahed, F. M., & Chen, Y.-J. (2026). *decisions Space* (Version %s) [Web application]. Hugging Face. https://huggingface.co/spaces/fmegahed/decisions", | |
| APP_VERSION | |
| ) | |
| ), collapse = "\n") | |
| } | |
| # Write the bundle of a run to `zipfile` and return its path. `table` is the | |
| # answers table of answers_table(). `rows` are the rows of Try on 5 for the | |
| # code (NULL for all inputs). | |
| write_bundle <- function(spec, table, zipfile, date = Sys.Date(), rows = NULL) { | |
| name <- run_name(spec) | |
| refs <- reference_files(spec) | |
| dir <- tempfile("bundle") | |
| dir.create(dir) | |
| on.exit(unlink(dir, recursive = TRUE)) | |
| readr::write_file(paste0(run_code(spec, date, rows), "\n"), file.path(dir, paste0(name, ".R"))) | |
| readr::write_file(answers_csv(table), file.path(dir, paste0(name, "-answers.csv"))) | |
| # The setup holds the references as the prepared images; the key stays out | |
| references <- if (nrow(refs) > 0) spec$references[unique(refs$answer)] | |
| setup <- decisions::decision_setup( | |
| make_decider(), stats::setNames(list(as_decisions_question(spec$question)), name), | |
| references = references, max_size = IMAGE_SETTINGS$max_size, | |
| upscale = IMAGE_SETTINGS$upscale | |
| ) | |
| decisions::write_setup(setup, file.path(dir, paste0(name, "-setup.json"))) | |
| # The references of a sample stay in the package (system.file()) | |
| if (nrow(refs) > 0 && spec$source != "textiles") { | |
| dest <- file.path(dir, "references", refs$folder, refs$file) | |
| purrr::walk(unique(dirname(dest)), dir.create, recursive = TRUE) | |
| file.copy(refs$path, dest) | |
| } | |
| # Pasted text goes in as data.csv; the file of a CSV upload stays with the visitor | |
| has_data <- spec$mode == "text" && spec$source == "upload" && | |
| is.null(spec$text_column) && nrow(spec$inputs) > 0 | |
| if (has_data) { | |
| readr::write_csv(tibble::tibble(text = spec$inputs$text), file.path(dir, "data.csv")) | |
| } | |
| readr::write_file( | |
| bundle_readme(spec, refs, name, has_data, date), file.path(dir, "README.md") | |
| ) | |
| zipfile <- normalizePath(zipfile, mustWork = FALSE) | |
| zip::zip(zipfile, files = list.files(dir), root = dir, mode = "mirror") | |
| zipfile | |
| } | |
| # The files of the bundle of a spec, as write_bundle() makes them (a folder | |
| # ends in "/"; test-bundle.R checks that the two agree) | |
| bundle_names <- function(spec) { | |
| name <- run_name(spec) | |
| has_refs <- nrow(reference_files(spec)) > 0 && spec$source != "textiles" | |
| has_data <- spec$mode == "text" && spec$source == "upload" && | |
| is.null(spec$text_column) && nrow(spec$inputs) > 0 | |
| c( | |
| paste0(name, c(".R", "-setup.json", "-answers.csv")), | |
| if (has_refs) "references/", if (has_data) "data.csv", "README.md" | |
| ) | |
| } | |
| # --- Import --------------------------------------------------------------- | |
| IMPORT_REFUSED <- "This file was not made by this app." | |
| # One text value: a string that is not NA | |
| is_one_string <- function(x) is.character(x) && length(x) == 1 && !is.na(x) | |
| # The path of the setup file: the file itself, or the `*-setup.json` of a zip | |
| # file, unpacked below `dir`. NULL when there is none. | |
| import_setup_path <- function(path, dir = tempfile("import")) { | |
| if (!utils::file_test("-f", path) || file.size(path) > mb_bytes(MAX_ZIP_MB)) return(NULL) | |
| if (tolower(tools::file_ext(path)) != "zip") return(path) | |
| entries <- tryCatch(zip::zip_list(path), error = function(e) NULL) | |
| if (is.null(entries)) return(NULL) | |
| found <- entries |> | |
| dplyr::filter( | |
| endsWith(filename, "-setup.json"), purrr::map_lgl(filename, safe_entry), | |
| !purrr::map_lgl(filename, is_junk), uncompressed_size <= mb_bytes(MAX_ZIP_MB) | |
| ) | |
| if (nrow(found) == 0) return(NULL) | |
| dest <- file.path(dir, "setup") | |
| zip::unzip(path, files = found$filename[1], exdir = dest) | |
| file.path(dest, found$filename[1]) | |
| } | |
| # TRUE when the parsed JSON has the layout and the settings of this app | |
| is_app_setup <- function(json) { | |
| question <- if (length(json$questions) == 1) json$questions[[1]] | |
| answers <- question$answers | |
| settings <- json$image_settings | |
| is.list(json) && | |
| identical(as.numeric(json$version), 1) && | |
| is_one_string(json$decider$model) && | |
| is.null(json$calibrator) && is.null(json$calibrator_hash) && | |
| is_one_string(question$type) && question$type %in% c("noul", "choice", "score") && | |
| is_one_string(question$instructions) && | |
| length(answers) > 0 && !is.null(names(answers)) && | |
| all(purrr::map_lgl(answers, \(x) is.null(x) || is_one_string(x))) && | |
| identical(as.numeric(settings$max_size), as.numeric(IMAGE_SETTINGS$max_size)) && | |
| identical(settings$upscale, IMAGE_SETTINGS$upscale) && | |
| identical(settings$crop, IMAGE_SETTINGS$crop) | |
| } | |
| # Decode the references of the file below `dir`, as | |
| # references/<answer>/ref_<i>.jpg. NULL when there is a bad reference. | |
| import_references <- function(json, answers, dir = tempfile("import")) { | |
| encoded <- json$references | |
| if (length(encoded) == 0) return(list(paths = NULL, problems = character())) | |
| # The references are an object keyed by answer: names that are all answers | |
| keys <- names(encoded) | |
| if (!is.list(encoded) || is.null(keys) || anyNA(keys) || anyDuplicated(keys) > 0 || | |
| !all(keys %in% names(answers))) { | |
| return(NULL) | |
| } | |
| folders <- answer_folders(names(answers)) | |
| paths <- tryCatch( | |
| purrr::imap(encoded, \(images, answer) { | |
| if (!is.list(images) || length(images) > MAX_REFERENCES) stop("bad references") | |
| purrr::imap_chr(unname(images), \(x, i) { | |
| bytes <- if (is_one_string(x)) jsonlite::base64_dec(x) else raw() | |
| jpeg <- length(bytes) > 3 && all(bytes[1:3] == as.raw(c(0xff, 0xd8, 0xff))) | |
| if (!jpeg || length(bytes) > mb_bytes(MAX_FILE_MB)) stop("bad reference") | |
| path <- file.path(dir, "references", folders[[answer]], sprintf("ref_%s.jpg", i)) | |
| dir.create(dirname(path), recursive = TRUE, showWarnings = FALSE) | |
| writeBin(bytes, path) | |
| path | |
| }) | |
| }), | |
| error = function(e) NULL | |
| ) | |
| if (is.null(paths)) return(NULL) | |
| list(paths = paths, problems = character()) | |
| } | |
| # Read a setup of this app: a bundle zip or the setup JSON. Returns the | |
| # question (in the format of spec.R), the references (answer to file paths) and | |
| # the problems. `question` is NULL when the file is refused. Every file of the | |
| # import is written below `dir` (the server gives the folder of the session). | |
| read_import_file <- function(path, dir = tempfile("import")) { | |
| refuse <- function(problems = IMPORT_REFUSED) { | |
| list(question = NULL, references = NULL, problems = problems) | |
| } | |
| setup_path <- import_setup_path(path, dir) | |
| if (is.null(setup_path)) return(refuse()) | |
| json <- tryCatch(jsonlite::read_json(setup_path), error = function(e) NULL) | |
| if (is.null(json) || !isTRUE(tryCatch(is_app_setup(json), error = function(e) FALSE))) { | |
| return(refuse()) | |
| } | |
| raw <- json$questions[[1]] | |
| answers <- purrr::map_chr(raw$answers, \(x) x %||% "") | |
| if (raw$type == "noul" && !setequal(names(answers), c("true", "false"))) return(refuse()) | |
| question <- list(type = raw$type, instructions = raw$instructions, answers = answers) | |
| problems <- check_question(question) | |
| if (length(problems) > 0) return(refuse(problems)) | |
| references <- import_references(json, answers, dir) | |
| if (is.null(references) || (raw$type == "score" && length(references$paths) > 0)) return(refuse()) | |
| model <- json$decider$model | |
| if (model != MODEL) { | |
| problems <- c(problems, sprintf("This file uses %s. The app uses %s.", model, MODEL)) | |
| } | |
| list( | |
| question = question, references = references$paths, | |
| problems = c(problems, references$problems) | |
| ) | |
| } | |
| # Read an import file. It never stops: any error refuses the file. | |
| read_import <- function(path, dir = tempfile("import")) { | |
| tryCatch( | |
| read_import_file(path, dir), | |
| error = function(e) list(question = NULL, references = NULL, problems = IMPORT_REFUSED) | |
| ) | |
| } | |