API

AssaySentinel.AssaySentinel — Module
AssaySentinel

Know when the measurement changed before the science does.

AssaySentinel.jl is a high-performance analytical quality and drift-detection framework for repeated quantitative measurements. It monitors the measurement process itself — instruments, reagent lots, calibrations, batches, and controls — rather than diagnosing patients.

Safety boundary

This software is intended for research, analytical-quality assessment, method development, and scientific decision support. It is not a diagnostic medical device and must not independently determine patient diagnosis or treatment.

Example

using AssaySentinel
using Dates

stream = AssayStream(analyte = :glucose, unit = "mg/dL", instrument = "Analyzer-A")
push!(stream, Measurement(value = 101.2, timestamp = now(), batch = "B104", lot = "R22", control = true))
report = analyze(stream)
explain(report)
source
AssaySentinel.Alert — Type
Alert

Analytical alert. Severity is one of :info, :watch, :warning, :critical. These are analytical-process severities, not clinical interpretations.

source
AssaySentinel.AttributionResult — Type
AttributionResult

Temporal association between a detected change and nearby operational events. Never claims causation from association alone.

source
AssaySentinel.Measurement — Type
Measurement{T}

A single quantitative observation with provenance metadata.

Keyword lot is accepted as an alias for reagent_lot. Missing values and NaN must be represented explicitly; they are never coerced to zero.

source
AssaySentinel.PanelReport — Type
PanelReport

Frozen multi-analyte product: per-analyte QualityReports plus a panel reconstruction. Schema version is stored for reload compatibility.

.panel is an alias of .name so older analyze(panel) NamedTuple accessors keep working.

source
AssaySentinel.Reconstruction — Type
Reconstruction

A defensible reconstruction of how the measurement system behaved: ordered story beats, uncertainty, lot/instrument evidence, SVG charts, and a provenance graph. Produced by analyze / reconstruct.

source
AssaySentinel.Sentinel — Type
Sentinel

Streaming monitor over a baseline. Incremental detectors, alert callbacks, persistence, and cooldown are first-class.

source
AssaySentinel.SentinelScore — Type
SentinelScore

Composite analytical-stability score on 0–100. This is not a patient-risk score. Components are always retained and never hidden.

source
AssaySentinel.StudyReport — Type
StudyReport

Frozen study-level product: per-site QualityReports plus the hierarchical combine. Schema version is stored for reload compatibility.

source
AssaySentinel.StudySentinel — Type
StudySentinel

Streaming monitor for several sites. A study-level alert fires when enough sites alarm inside the concordance window (default: 2 sites within 7 days). Concordance alerts themselves respect concordance_cooldown so a persistent shared signal is not re-emitted on every subsequent observation.

source
AssaySentinel.UncertaintyBudget — Type
UncertaintyBudget

How measurement uncertainty and analytical scatter combine. This is analytical-process uncertainty, not a clinical interval.

source
AssaySentinel.UnitMismatchError — Type
UnitMismatchError

Raised when two quantities are compared or combined without an explicit unit conversion. mg/dL is never treated as equivalent to mmol/L.

source
AssaySentinel.analyze — Method
analyze(panel::AssayPanel; parallel=false, rng)

Analyze each analyte stream independently and return a PanelReport with a panel reconstruction (status chart, story, provenance). Per-analyte RNGs are independent. Units are never pooled. .panel remains an alias of .name for older NamedTuple-style access.

source
AssaySentinel.analyze — Method
analyze(stream::AssayStream; rng, outlier_policy=:annotate, parallel=false)

Combine change-point, drift, QC, distribution, outlier, and attribution evidence into a QualityReport.

source
AssaySentinel.analyze — Method
analyze(study, streams; rng)

Analyze each site stream and combine them with hierarchical_sites. streams maps site name → AssayStream. The study reconstruction (forest plot, sharing narrative, uncertainty budget) is attached to the StudyReport.

source
AssaySentinel.assess_partitions — Method
assess_partitions(data; group, value)

Harris–Boyd style statistical partitioning evidence (Harris & Boyd 1990). Does not recommend clinical partitions.

source
AssaySentinel.calibrate — Method
calibrate(concentrations, responses; model=:linear, weights=nothing)

Fit a calibration curve.

Models: :linear, :weighted_linear, :polynomial, :robust, :spline, :fourpl.

source
AssaySentinel.calibration_diagnostics — Method
calibration_diagnostics(curve)

Residual, runs, relative-error, and (when replicates exist) lack-of-fit diagnostics for a fitted calibration. Does not claim clinical impact.

source
AssaySentinel.check_units — Method
check_units(a, b)

Throw UnitMismatchError unless a and b denote the same unit after alias normalization. Empty units are treated as unspecified and allowed.

source
AssaySentinel.compare_lots — Function
compare_lots(data, lotcol; value=:value)

Estimate whether a lot transition corresponds to location, variance, or distributional change.

source
AssaySentinel.compare_methods — Method
compare_methods(a, b; method=:ba)

Compare paired measurements from two methods.

Methods:

  • :ba — Bland–Altman (Bland & Altman 1986)
  • :deming — Deming regression (errors-in-variables)
  • :passing_bablok — Passing & Bablok (1983)
  • :ols — ordinary least squares
  • :robust — Theil–Sen
source
AssaySentinel.convert_unit — Method
convert_unit(value, from, to; molar_mass=nothing, factor=nothing)

Explicit conversion only. Common clinical-chemistry conversions require molar_mass (g/mol) when moving between mass and molar concentration.

This function never guesses an analyte.

source
AssaySentinel.correct_batch_effects — Method
correct_batch_effects(X, batch; method=:combat, design=nothing)

Multi-feature ComBat. X is observations × features. Empirical-Bayes priors for each batch are estimated across features (Johnson et al. 2007).

source
AssaySentinel.correct_batch_effects — Method
correct_batch_effects(dataset; method=:median, batch, value, biological_group=nothing)

Optional correction. Always returns a new table-like vector of named tuples and never mutates the original. The transformation is intended to be stored in provenance by the caller.

Methods:

  • :median — location centering to the grand mean
  • :quantile — batch-wise quantile matching to the pooled empirical distribution
  • :ruv — control-anchored unwanted-variation removal (RUV-lite)
  • :combat — parametric empirical-Bayes location/scale adjustment (Johnson, Li & Rabinovic 2007). Batch location γ and scale δ² are shrunk toward hyperparameters estimated from the batch-level moments. When biological_group is set, that design is protected (not removed).
source
AssaySentinel.detect_batch_effects — Method
detect_batch_effects(dataset; batch, value, biological_group=nothing)

Distinguish likely technical variation from variation associated with an experimental grouping. Does not correct data.

source
AssaySentinel.detect_changes — Method
detect_changes(data; method=:auto, timestamps=nothing, has_controls=false, rng)

Detect change points in a univariate series.

Methods:

  • :cusum — Page (1954) two-sided CUSUM plus CUSUM-path localization
  • :likelihood — Gaussian mean-change likelihood ratio / SIC scan
  • :pelt — PELT for piecewise mean (Killick, Fearnhead & Eckley 2012)
  • :robust_median — CUSUM on robustly standardized observations
  • :rolling — rolling two-sample Welch tests
  • :bayesian — Fearnhead (2006) product-partition posterior (multiple changes)
  • :turing — MCMC via the Turing.jl extension (model=:single|:multiple|:hierarchical, ncuts for piecewise means, sampler=:mh|:nuts)
  • :kernel — energy-distance scan (Székely & Rizzo)
  • :auto — choose from sample size, tail weight, missingness, cadence
source
AssaySentinel.detect_drift — Method
detect_drift(X::AbstractMatrix; method=:mahalanobis, timestamps=nothing)

X is observations × features. Methods:

  • :mahalanobis — Hotelling-style distance from baseline mean/covariance
  • :pca — Hotelling T² on leading principal components
  • :covariance — Frobenius shift of correlation matrices
  • :energy — multivariate energy distance between halves
source
AssaySentinel.detect_drift — Method
detect_drift(data; kind=:auto, timestamps=nothing, baseline=nothing, rng)

Detect analytical drift of a specified kind.

Kinds: :linear, :nonlinear, :sudden, :cyclic, :variance, :distribution, :calibration, :auto.

source
AssaySentinel.detect_outliers — Method
detect_outliers(x; method=:mad, k=3.5)

Detect outliers without removing them.

Methods:

  • :mad — robust z-score using normalized MAD (Rousseeuw & Croux 1993)
  • :iqr — Tukey fences at Q1 − k IQR, Q3 + k IQR (Tukey 1977); default k=1.5
  • :zscore — classical |z| > k
  • :robust_z — alias of :mad
source
AssaySentinel.evaluate_detector — Function
evaluate_detector(kind; nrep=20, n=800, rng)

Compare detection delay, false-positive rate, and sensitivity on simulated streams. Useful as a research-platform harness for new detectors.

source
AssaySentinel.explain — Method
explain(result)

Human-readable reconstruction of why a conclusion was reached. Leads with the dated analytical story, then the uncertainty budget, then the evidence that assigned status. Distinguishes observed facts, statistical results, algorithmic inference, and user annotations.

source
AssaySentinel.forest_chart — Function
forest_chart(result::HierarchicalSiteResult)

Makie forest plot of site means with a prediction-interval overlay. Requires the optional Makie.jl extension. The SVG counterpart svg_forest_chart is always available in core.

source
AssaySentinel.hierarchical_sites — Method
hierarchical_sites(data; site, value=:value, timestamps=:timestamp, method=:eb)

Random-effects site model (DerSimonian–Laird / empirical Bayes):

  1. Per-site mean, SD, and standard error
  2. Between-site τ² and within-site σ²
  3. Shrink site means toward the grand mean
  4. Higgins I² and a 95% prediction interval for a new site mean
  5. Per-site and pooled drift
  6. Cochran Q on site drift magnitudes → :global, :site_specific, :mixed, or :stable

method=:turing uses the Turing extension (hierarchical site intercepts + shared change). The default :eb path is stdlib-only.

Attribution is a statistical description of sharing, not a cause.

source
AssaySentinel.levey_jennings_data — Method
levey_jennings_data(values, spec) / control_chart_data

Structured series for Levey–Jennings charts (Levey & Jennings 1950). Plotting is provided by the optional Makie extension.

source
AssaySentinel.monitor — Method
monitor(control, measurements; rules=westgard_rules())

Evaluate control material against target ± SD and the configured rule set.

source
AssaySentinel.reconstruct — Function
reconstruct(hierarchy, site_reports; rng_seed, name)

Study-level reconstruction: sharing narrative, between/within uncertainty, forest plot, and provenance. Called by analyze(study, streams).

source
AssaySentinel.reconstruct — Method
reconstruct(stream, report_pieces; rng_seed)

Build the ordered analytical story, uncertainty budget, charts, and provenance graph from an already-run analysis. Called by analyze.

source
AssaySentinel.reconstruct — Method
reconstruct(reports; rng_seed, name)

Panel-level reconstruction: per-analyte status narrative, score chart, and provenance. Called by analyze(panel). Units are never pooled.

source
AssaySentinel.reference_curve — Method
reference_curve(covariate, values; quantiles=(0.025, 0.50, 0.975), span=0.3, method=:quantile)

Continuous reference curves. :quantile uses locally weighted empirical quantiles. :lms uses Cole LMS (global λ, local μ and σ) and back-transforms the requested probabilities. Not a clinical reference system.

source
AssaySentinel.reference_interval — Method
reference_interval(values; method=:nonparametric, rng, α=0.05, unit="")

Methods:

  • :nonparametric — 2.5th–97.5th percentiles (CLSI EP28-style)
  • :parametric — mean ± 1.96 SD
  • :robust — median ± 1.96 × normalized MAD
  • :transformed — log-scale parametric, back-transformed
  • :boxcox — Box–Cox profile-likelihood transform, then parametric limits
  • :horn — Tukey fences then nonparametric limits on the remaining sample
  • :lms — Cole LMS (λ, μ, σ) 2.5th–97.5th quantiles
source
AssaySentinel.result — Method
result(r::PanelReport)

Snapshot of per-analyte status and scores. Analytical process, not clinical risk.

source
AssaySentinel.result — Method
result(study::StudySentinel)

Snapshot of per-site sentinels and any study-level concordance alerts.

source
AssaySentinel.showcase_dataset — Method
showcase_dataset(; rng)

12 months of synthetic measurements: 3 reagent lots, 2 instruments, one calibration event, gradual drift, variance shift, and control failures.

source
AssaySentinel.simulate_assay — Method
simulate_assay(; n, drift=:none, rng, ...)

Generate a synthetic measurement stream.

Drift kinds: :none, :linear, :step, :variance, :lot, :batch, :periodic, :failure, :outliers.

source
AssaySentinel.svg_forest_chart — Method
svg_forest_chart(result::HierarchicalSiteResult; title)

Forest plot of per-site means with 95% CIs (raw SE), shrunk means as filled markers, a diamond at the grand mean, and dashed prediction-interval guides.

source
AssaySentinel.svg_panel_chart — Method
svg_panel_chart(reports; title)

Horizontal Sentinel Score bars for each analyte in a PanelReport. Scores describe analytical-process stability, not patient risk.

source
AssaySentinel.@qcrule — Macro
@qcrule name begin
    ...
end

Custom rule body sees values::Vector{Float64} and spec::QCSpec and must return a QCRuleResult.

source