Clients
Complete examples of using the QC Analytical Results API from R and from Python: send a dataset, ask for a chart, handle an expired dataset.
The API is plain HTTPS and JSON, so any language will do. Two complete examples. Both read the key from the environment (QC_API_KEY), send a CSV gzipped, ask for a chart, and send the data again if the dataset has expired.
R
Uses httr and jsonlite.
library(httr)
library(jsonlite)
API <- "https://api.geoscience-analytics.com"
auth <- add_headers(Authorization = paste("Bearer", Sys.getenv("QC_API_KEY")))
call <- function(method, path, ...) {
r <- VERB(method, paste0(API, path), auth, ...)
body <- fromJSON(content(r, "text", encoding = "UTF-8"), simplifyVector = TRUE)
if (status_code(r) >= 400) stop(body$error$code, ": ", body$error$message, " (", body$error$request_id, ")")
body
}
# send the CRM results once (gzipped CSV)
send <- function(file, kind) {
gz <- tempfile(fileext = ".gz")
con <- gzfile(gz, "wb"); writeBin(readBin(file, "raw", file.size(file)), con); close(con)
call("POST", paste0("/v1/datasets?kind=", kind), content_type("text/csv"),
add_headers(`Content-Encoding` = "gzip"), body = upload_file(gz, type = "text/csv"))
}
ds <- send("crms.csv", "accuracy")
ds$facets$crms # the CRM ids you can ask for
# a chart; the dataset is sent again if it has expired
chart <- function(type, params) {
ask <- function() call("POST", paste0("/v1/charts/", type), content_type_json(), body = toJSON(params, auto_unbox = TRUE))
tryCatch(ask(), error = function(e) {
if (!grepl("^dataset_expired", conditionMessage(e))) stop(e)
ds <<- send("crms.csv", "accuracy"); params$dataset_id <<- ds$dataset_id
ask()
})
}
sh <- chart("shewhart", list(dataset_id = ds$dataset_id, lab = "ALS", analyte = "Au_AA25_ppm",
crm_type = "LAB_STD", crms = I("ALS-26"), nelson_rules = TRUE))
sh$spec$limits # centre, warning and action limits
head(sh$spec$points) # one row per result, with its flags
sh$spec$nelson$table # the eight rules, PASS or FAIL(I("ALS-26") keeps a single CRM as a JSON array, ["ALS-26"], which crms expects.)
Python
Uses requests.
import gzip
import os
import requests
API = "https://api.geoscience-analytics.com"
HEADERS = {"Authorization": "Bearer " + os.environ["QC_API_KEY"]}
class ApiError(Exception):
def __init__(self, status, body):
self.status = status
self.code = body["error"]["code"]
super().__init__("%s: %s (%s)" % (self.code, body["error"]["message"], body["error"].get("request_id")))
def call(method, path, **kw):
headers = dict(HEADERS, **kw.pop("headers", {}))
r = requests.request(method, API + path, headers=headers, timeout=120, **kw)
body = r.json()
if r.status_code >= 400:
raise ApiError(r.status_code, body)
return body
def send(path, kind):
with open(path, "rb") as f:
data = gzip.compress(f.read())
return call("POST", "/v1/datasets?kind=" + kind, data=data,
headers={"Content-Type": "text/csv", "Content-Encoding": "gzip"})
ds = send("duplicates.csv", "precision")
print(ds["facets"]["stages"])
def chart(chart_type, params):
try:
return call("POST", "/v1/charts/" + chart_type, json=params)
except ApiError as e:
if e.code != "dataset_expired":
raise
params["dataset_id"] = send("duplicates.csv", "precision")["dataset_id"]
return call("POST", "/v1/charts/" + chart_type, json=params)
sc = chart("scatter", {"dataset_id": ds["dataset_id"], "lab": "ALS", "analyte": "Au_AA25_ppm",
"stage": "Pulverising Stage", "rd_formula": "MPRD"})
print(sc["rows_used"], "pairs,", sc["spec"]["stats"]["outliers"], "outliers")
for p in sc["spec"]["points"][:3]:
print(p["ASSAYVALUE_OR"], p["ASSAYVALUE_CK"], p["RD"], p["outlier"])Patterns worth copying
- Send on every run, keep the id for the run. The same file gives the same id back, so there is no state to keep between runs.
- Retry once on
dataset_expired. Send the data again and repeat the call. - Wait and retry on
429, a503or no answer, a few times with a growing pause (5 s, 30 s, 2 min). Nothing else needs a retry: a4xxother than429will fail the same way again. - Keep
parametersfrom the answer with anything you store: it records the defaults that were in force. - Never log the key. Log the
request_idinstead; it is what support needs.