import type { Rack, SlotPosition, Well, WellEditData, WellPlateFormat } from '../types' /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export type LcmsPlateType = '96well' | '54vial' /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export type LcmsControlSampleType = 'blank' | 'qc' | 'iqc' /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export interface LcmsPlateCell { row: string column: number sample_name: string sample_type?: string | null injection_volume?: number | null injection_count?: number | null custom_method?: string | null slot?: SlotPosition | null } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export interface LcmsPlateCellsToRackOptions { rackId?: string rackName?: string format?: WellPlateFormat plateType?: LcmsPlateType slot?: SlotPosition injectionVolume?: number } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export interface LcmsPlateCellsToRacksOptions extends Omit { defaultSlot?: SlotPosition rackId?: (slot: SlotPosition, index: number) => string rackName?: (slot: SlotPosition, index: number) => string } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export const LCMS_DEFAULT_CONTROL_POSITIONS: Record> = { '96well': { blank: { row: 'H', column: 12 }, qc: { row: 'H', column: 11 }, iqc: { row: 'H', column: 10 }, }, '54vial': { blank: { row: 'F', column: 9 }, qc: { row: 'F', column: 8 }, iqc: { row: 'F', column: 7 }, }, } const SLOT_ORDER: SlotPosition[] = ['R', 'G', 'B', 'Y'] /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function lcmsPlateTypeToRackFormat(plateType: LcmsPlateType): WellPlateFormat { return plateType === '96well' ? 96 : 54 } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function rackFormatToLcmsPlateType(format: WellPlateFormat): LcmsPlateType { if (format === 96) return '96well' if (format === 54) return '54vial' throw new Error(`LCMS plate cells support only 54-vial racks and 96-well plates; received format ${format}.`) } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function lcmsWellId(row: string, column: number): string { return `${row.toUpperCase()}${column}` } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function parseLcmsWellId(wellId: string): { row: string; column: number } { const match = /^([A-Za-z]+)(\d+)$/.exec(wellId.trim()) if (!match) { throw new Error(`Invalid well id "${wellId}". Expected a row letter followed by a column number, e.g. A1.`) } return { row: match[1].toUpperCase(), column: Number(match[2]), } } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function lcmsPlateCellsToRack(cells: LcmsPlateCell[], options: LcmsPlateCellsToRackOptions = {}): Rack { const format = options.format ?? (options.plateType ? lcmsPlateTypeToRackFormat(options.plateType) : inferRackFormat(cells)) const slot = options.slot ?? cells.find(cell => cell.slot)?.slot ?? 'R' const injectionVolume = options.injectionVolume ?? inferInjectionVolume(cells) ?? 5 return { id: options.rackId ?? `rack-${slot}`, name: options.rackName ?? `Rack ${slot}`, format, slot, injectionVolume, wells: Object.fromEntries( cells .filter(cell => cell.sample_name.trim()) .map((cell) => { const wellId = lcmsWellId(cell.row, cell.column) const well: Partial = { id: wellId, row: rowIndex(cell.row), col: cell.column - 1, state: 'filled', sampleType: cell.sample_type ?? 'sample', metadata: { label: cell.sample_name.trim(), injectionVolume: cell.injection_volume ?? injectionVolume, injectionCount: cell.injection_count ?? 1, customMethod: cell.custom_method ?? null, }, } return [wellId, well] }), ), } } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function lcmsPlateCellsToRacks(cells: LcmsPlateCell[], options: LcmsPlateCellsToRacksOptions = {}): Rack[] { const bySlot = new Map() const defaultSlot = options.defaultSlot ?? 'R' for (const cell of cells) { const slot = cell.slot ?? defaultSlot const slotCells = bySlot.get(slot) ?? [] slotCells.push(cell) bySlot.set(slot, slotCells) } return Array.from(bySlot.entries()) .sort(([a], [b]) => SLOT_ORDER.indexOf(a) - SLOT_ORDER.indexOf(b)) .map(([slot, slotCells], index) => lcmsPlateCellsToRack(slotCells, { ...options, slot, rackId: options.rackId?.(slot, index) ?? `rack-${slot}`, rackName: options.rackName?.(slot, index) ?? `Rack ${slot}`, })) } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function rackToLcmsPlateCells(rack: Rack): LcmsPlateCell[] { return Object.entries(rack.wells) .map(([wellId, well]) => wellToLcmsPlateCell(rack, wellId, well)) .filter((cell): cell is LcmsPlateCell => cell !== null) .sort(comparePlateCells) } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function racksToLcmsPlateCells(racks: Rack[]): LcmsPlateCell[] { return racks.flatMap(rackToLcmsPlateCells) } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function getLcmsDefaultControlWellId(plateType: LcmsPlateType, sampleType: LcmsControlSampleType): string { const position = LCMS_DEFAULT_CONTROL_POSITIONS[plateType][sampleType] return lcmsWellId(position.row, position.column) } /** @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */ export function createLcmsControlWellEditData( sampleType: LcmsControlSampleType, options: { wellId?: string plateType?: LcmsPlateType injectionVolume?: number } = {}, ): WellEditData { const plateType = options.plateType ?? '54vial' return { wellId: options.wellId ?? getLcmsDefaultControlWellId(plateType, sampleType), label: sampleType === 'blank' ? 'Blank' : sampleType.toUpperCase(), sampleType, injectionVolume: options.injectionVolume ?? 5, injectionCount: 1, customMethod: '', } } function wellToLcmsPlateCell(rack: Rack, wellId: string, well: Partial): LcmsPlateCell | null { const metadata = well.metadata ?? {} const label = getString(metadata.label) if (!label) return null const parsed = parseLcmsWellId(well.id ?? wellId) return { row: parsed.row, column: parsed.column, sample_name: label, sample_type: well.sampleType ?? null, injection_volume: getNumber(metadata.injectionVolume) ?? rack.injectionVolume, injection_count: getNumber(metadata.injectionCount) ?? 1, custom_method: getNullableString(metadata.customMethod), slot: rack.slot, } } function inferRackFormat(cells: LcmsPlateCell[]): WellPlateFormat { return cells.some(cell => rowIndex(cell.row) > 5 || cell.column > 9) ? 96 : 54 } function inferInjectionVolume(cells: LcmsPlateCell[]): number | undefined { return cells.find(cell => typeof cell.injection_volume === 'number')?.injection_volume ?? undefined } function rowIndex(row: string): number { return row.toUpperCase().charCodeAt(0) - 65 } function getString(value: unknown): string | undefined { return typeof value === 'string' && value.trim() ? value.trim() : undefined } function getNullableString(value: unknown): string | null { return typeof value === 'string' && value.trim() ? value.trim() : null } function getNumber(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined } function comparePlateCells(a: LcmsPlateCell, b: LcmsPlateCell): number { const rowDiff = rowIndex(a.row) - rowIndex(b.row) return rowDiff === 0 ? a.column - b.column : rowDiff }