108 lines
3.8 KiB
Rust
108 lines
3.8 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{plate::PlateFormat, transfer::Transfer, util::num_to_letters};
|
|
|
|
/// Represents a single line of a CSV picklist.
|
|
/// In practice, this is generated from the deserialize intermediate.
|
|
/// A list of `TransferRecord`s can be used to create a `transfer::Transfer` struct;
|
|
/// see the `auto` module for this.
|
|
#[derive(Serialize, Debug, Clone)]
|
|
pub struct TransferRecord {
|
|
#[serde(rename = "Source Barcode")]
|
|
pub source_plate: String,
|
|
#[serde(rename = "Source Well")]
|
|
pub source_well: String,
|
|
#[serde(rename = "Dest Barcode")]
|
|
pub destination_plate: String,
|
|
#[serde(rename = "Destination Well")]
|
|
pub destination_well: String,
|
|
#[serde(rename = "Transfer Volume")]
|
|
pub volume: f32,
|
|
#[serde(rename = "Concentration")]
|
|
pub concentration: Option<f32>,
|
|
}
|
|
|
|
/// The deserialization intermediate is generated from a mangled CSV s.t.
|
|
/// the column headers are standardized to those given to the derive macro.
|
|
///
|
|
/// `TransferRecord` can *always* be generated from the intermediate.
|
|
/// Currently there's no reason why the inverse couldn't be true,
|
|
/// but there's no reason to convert back into the deserialize-only intermediate.
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
pub struct TransferRecordDeserializeIntermediate {
|
|
#[serde(rename = "sourceplate")]
|
|
source_plate: String,
|
|
#[serde(rename = "destinationplate")]
|
|
destination_plate: String,
|
|
#[serde(rename = "sourcewell")]
|
|
source_well: String,
|
|
#[serde(rename = "sourceformat")]
|
|
source_format: Option<String>,
|
|
#[serde(rename = "destinationwell")]
|
|
destination_well: String,
|
|
#[serde(rename = "destinationformat")]
|
|
destination_format: Option<String>,
|
|
#[serde(rename = "volume")]
|
|
volume: Option<f32>,
|
|
#[serde(rename = "concentration")]
|
|
concentration: Option<f32>,
|
|
}
|
|
|
|
impl From<TransferRecordDeserializeIntermediate> for TransferRecord {
|
|
fn from(value: TransferRecordDeserializeIntermediate) -> Self {
|
|
let mut source_well: String = value.source_well;
|
|
if let Some(pformat) = value
|
|
.source_format
|
|
.and_then(|x| PlateFormat::try_from(x.as_str()).ok())
|
|
{
|
|
if let Ok(well_number) = source_well.parse::<u16>() {
|
|
if let Some(alphanumeric) = numeric_well_to_alphanumeric(well_number, pformat) {
|
|
source_well = alphanumeric;
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut destination_well: String = value.destination_well;
|
|
if let Some(pformat) = value
|
|
.destination_format
|
|
.and_then(|x| PlateFormat::try_from(x.as_str()).ok())
|
|
{
|
|
if let Ok(well_number) = destination_well.parse::<u16>() {
|
|
if let Some(alphanumeric) = numeric_well_to_alphanumeric(well_number, pformat) {
|
|
destination_well = alphanumeric;
|
|
}
|
|
}
|
|
}
|
|
|
|
let volume = value.volume.unwrap_or(2.5f32);
|
|
|
|
TransferRecord {
|
|
source_plate: value.source_plate,
|
|
destination_plate: value.destination_plate,
|
|
source_well,
|
|
destination_well,
|
|
volume,
|
|
concentration: value.concentration,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TransferRecordDeserializeIntermediate {
|
|
/// Used to cull malformed picklist entries that lack required information
|
|
pub fn is_empty(&self) -> bool {
|
|
self.source_plate.is_empty()
|
|
|| self.destination_plate.is_empty()
|
|
|| self.source_well.is_empty()
|
|
|| self.destination_well.is_empty()
|
|
}
|
|
}
|
|
|
|
fn numeric_well_to_alphanumeric(input: u16, pformat: PlateFormat) -> Option<String> {
|
|
let column_height: u16 = pformat.size().0 as u16;
|
|
let column = input.div_ceil(column_height);
|
|
let row = input % column_height;
|
|
let row_str = num_to_letters(row as u8)?;
|
|
|
|
Some(format!("{}{}", row_str, column))
|
|
}
|