///|
/// Integer nanomolar bounds; TA uses nanoequivalents/L. All are inclusive.
pub(all) struct Bounds {
  lower : Int32
  upper : Int32
} derive(Debug, ToJson, FromJson)

///|
/// Exact rational output avoids rounding an assay boundary into agreement.
pub(all) struct Exact {
  numerator : String
  denominator : String
} derive(Debug, ToJson, FromJson)

///|
/// One Ca-carbonate-Na-Cl-water source, with no untracked strong ions.
pub(all) struct SourceWater {
  id : String
  volume_ul : Int32
  calcium_nmol_l : Bounds
  dic_nmol_l : Bounds
  ta_neq_l : Bounds
  sodium_nmol_l : Bounds
  chloride_nmol_l : Bounds
} derive(Debug, ToJson, FromJson)

///|
/// A child preparation state. take/dilute/evaporate use volume_ul and zero
/// amount; co2/calcite use signed amount_nmol and zero volume; hcl/naoh use
/// nonnegative reagent moles and added solution volume. Parents precede children.
pub(all) struct HandlingStep {
  id : String
  parent : String
  kind : String
  volume_ul : Int32
  amount_nmol : Bounds
} derive(Debug, ToJson, FromJson)

///|
/// An assay at its actual preparation state. analyte is Ca, DIC, TA, Na or Cl.
/// It is never silently attached to the original bottle; pH is not additive.
pub(all) struct BottleAssay {
  id : String
  node : String
  analyte : String
  bounds : Bounds
} derive(Debug, ToJson, FromJson)

///|
pub(all) struct AliquotStudy {
  schema : String
  source : SourceWater
  steps : Array[HandlingStep]
  assays : Array[BottleAssay]
} derive(Debug, ToJson, FromJson)

///|
pub(all) struct ParameterValue {
  parameter : String
  value : Exact
} derive(Debug, ToJson, FromJson)

///|
pub(all) struct PreparedSample {
  id : String
  volume_ul : Int32
  calcium_nmol_l : Exact
  dic_nmol_l : Exact
  ta_neq_l : Exact
  sodium_nmol_l : Exact
  chloride_nmol_l : Exact
} derive(Debug, ToJson, FromJson)

///|
pub(all) struct HandlingHistory {
  model : String
  parameters : Array[ParameterValue]
  samples : Array[PreparedSample]
} derive(Debug, ToJson, FromJson)

///|
priv struct Parameter {
  id : String
  bounds : Bounds
}

///|
priv struct Form {
  constant : R
  coefficients : Array[R]
}

///|
priv struct Bottle {
  id : String
  volume_ul : Int32
  amounts : Array[Form]
}

///|
priv struct Row {
  id : String
  coefficients : Array[R]
  upper : R
}

///|
priv struct Compiled {
  parameters : Array[Parameter]
  bottles : Array[Bottle]
  rows : Array[Row]
}

///|
let analytes : Array[String] = ["Ca", "DIC", "TA", "Na", "Cl"]