diff --git a/plate-tool-lib/src/csv/alternative_formats.rs b/plate-tool-lib/src/csv/alternative_formats.rs index 7059760..366dc47 100644 --- a/plate-tool-lib/src/csv/alternative_formats.rs +++ b/plate-tool-lib/src/csv/alternative_formats.rs @@ -2,9 +2,10 @@ use serde::Serialize; use super::TransferRecord; -// Format preferred by the Echo Client when using the Pick-List function. -// Note that this does not export plate info! -// Exports of this type should combine all transfers between the two actively selected plates. +/// Format preferred by the Echo Client when using the Pick-List function. +/// +/// Note that this does not export plate info! +/// Exports of this type should combine all transfers between the two actively selected plates. #[derive(Serialize, Debug)] pub struct EchoClientTransferRecord { #[serde(rename = "SrcWell")] diff --git a/plate-tool-lib/src/csv/auto.rs b/plate-tool-lib/src/csv/auto.rs index e330b20..5d08b6c 100644 --- a/plate-tool-lib/src/csv/auto.rs +++ b/plate-tool-lib/src/csv/auto.rs @@ -1,3 +1,8 @@ +//! Auto module for importing picklists without creating plates first +//! +//! Before the auto module, it was necessary for a user to create the plates +//! used in an imported transfer before import. + use super::{string_well_to_pt, TransferRecord, read_csv}; use crate::plate::{PlateFormat, PlateType}; use crate::plate_instances::PlateInstance; @@ -12,6 +17,7 @@ pub struct AutoOutput { pub transfers: Vec, } +/// Two lists of plates that are guaranteed to be unique (no duplicate plates) struct UniquePlates { sources: Vec, destinations: Vec, @@ -24,6 +30,12 @@ const W384_ROW_MAX: u8 = 16; const W1536_COL_MAX: u8 = 48; const W1536_ROW_MAX: u8 = 32; +/// Main function for the auto module +/// Takes a list of pre-deserialized transfer records and generates output +/// This output is 3 lists of: +/// 1. The source plates used in the picklist +/// 2. The destination plates used in the picklist +/// 3. All of the transfers between those plates pub fn auto(records: &[TransferRecord]) -> AutoOutput { let unique_plates = find_unique_plates(records); let transfers = get_transfer_for_all_pairs(records, &unique_plates); @@ -31,11 +43,18 @@ pub fn auto(records: &[TransferRecord]) -> AutoOutput { AutoOutput { sources: unique_plates.sources, destinations: unique_plates.destinations, transfers } } +/// Helper function that reads a CSV and immediately dumps the resulting transfer records +/// into the auto function defined in this module. pub fn read_csv_auto(data: &str) -> AutoOutput { let transfer_records = read_csv(data); auto(&transfer_records) } +/// Looks through a list of transfer records and generates two lists +/// of all of the unique plates found in those transfers. +/// Worth noting that the Transfer struct requires two associated PlateInstances; +/// these PlateInstance structs are generated here---they are not present in a TransferRecord. +/// See notes on the UniquePlates struct fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates { let mut source_names: HashSet<&str> = HashSet::new(); let mut destination_names: HashSet<&str> = HashSet::new(); @@ -79,6 +98,12 @@ fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates { } } +/// Tries to guess the format of a plate given the southeastern-est well used. +/// A picklist does not necessarily contain plate format info; this method guesses +/// based on the well furthest from A1 that exists in a transfer. +/// It's possible for a 1536 well plate to be seen as a 384 well if all of the wells +/// used could have fit in the top-left quadrant of a 384 well plate +/// (i.e. all lower than P24 in this case) fn guess_plate_size(plate_filtered_records: &[&TransferRecord], which: PlateType) -> PlateFormat { let mut guess = PlateFormat::W96; // Never guess smaller than 96 for record in plate_filtered_records { @@ -116,6 +141,17 @@ fn get_transfer_for_all_pairs( transfers } +/// Given a source and destination plate, get all of the wells transferred +/// from source to destination and turn this into a Transfer. +/// +/// Note that even if you have what looks like two separate transfers, +/// this will join all transfers for a given source-dest pair. +/// For example: +/// Consider expanding all of src:A1 to the column dst:A +/// and src:B1 -> row dst:1 +/// If you were making this transfer in plate tool, it would be easiest +/// to do this as two separate transfers. +/// On import, this would be seen as one transfer. fn get_transfer_for_pair( records: &[TransferRecord], source: &PlateInstance, diff --git a/plate-tool-lib/src/csv/conversion.rs b/plate-tool-lib/src/csv/conversion.rs index 903d95b..48a9e37 100644 --- a/plate-tool-lib/src/csv/conversion.rs +++ b/plate-tool-lib/src/csv/conversion.rs @@ -52,9 +52,12 @@ pub fn records_to_echo_client_csv(trs: Vec) -> Result Option<(u8, u8)> { lazy_static! { static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap(); + + // Can this be removed? static ref REGEX_ALT: Regex = Regex::new(r"(\d+)").unwrap(); } if let Some(c1) = REGEX.captures(input) { diff --git a/plate-tool-lib/src/csv/mangle_headers.rs b/plate-tool-lib/src/csv/mangle_headers.rs index 6c1d9ad..8ac4fdf 100644 --- a/plate-tool-lib/src/csv/mangle_headers.rs +++ b/plate-tool-lib/src/csv/mangle_headers.rs @@ -1,3 +1,12 @@ +//! This contains the `mangle_headers` function and its helpers +//! +//! Mangling headers is necessary to normalize CSV column names before +//! deserialization can be done by Serde. +//! Field detection occurs in `detect_field`. +//! +//! As of lib 0.4.0, unrecognized headers are ignored and left untouched by mangling. +//! However, they are not passed through to any subsequent exports from plate-tool. + pub fn mangle_headers(data: &str) -> String { let (header, rows) = data.split_at(data.find('\n').unwrap()); let fields = header.trim().split(","); diff --git a/plate-tool-lib/src/csv/transfer_record.rs b/plate-tool-lib/src/csv/transfer_record.rs index 71066a2..a845a1c 100644 --- a/plate-tool-lib/src/csv/transfer_record.rs +++ b/plate-tool-lib/src/csv/transfer_record.rs @@ -2,6 +2,10 @@ 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")] @@ -18,6 +22,12 @@ pub struct TransferRecord { pub concentration: Option, } +/// 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")] @@ -78,6 +88,7 @@ impl From for TransferRecord { } 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() @@ -95,6 +106,7 @@ fn numeric_well_to_alphanumeric(input: u16, pformat: PlateFormat) -> Option f32 { Transfer::default().volume }