import type { SampleGroup } from './components' import type { TreeNode } from './componentLabTypes' // ---- Legacy (kept for source-compatibility) ---- export type OutlierAction = 'include' | 'exclude' | 'qc' export type InputMode = 'paste' | 'csv' | 'experiment' export interface OutlierInfo { sample: string index: number fieldCount: number action: OutlierAction } export interface MetadataRow { sampleName: string fields: Record group: string } export interface ParsedCsvData { columns: string[] rows: Record[] sampleColumn: string delimiter: string } // ---- Class detection ---- export type SampleClassKind = | 'biological' | 'iqc' | 'eqc' | 'standard' | 'spike-in' | 'stock' | 'blank' | 'unknown' export type ClassDisposition = 'group' | 'overlay' | 'exclude' export interface SampleClass { kind: SampleClassKind label: string subKind?: string members: number[] classTagPositions: number[] disposition: ClassDisposition /** * Token count shared by every member of this class. Set by `splitByTokenLength` * after type classification so that samples with different field counts never * share a schema — column index N then means the same field for every member. */ tokenLength?: number } // ---- Column / role classifier ---- export type ColumnRole = | 'constant' | 'factor' | 'replicate' | 'run-order' | 'id' | 'numeric' | 'class-tag' | 'ignore' export interface NumericParse { value: number unit: string raw: string } export interface NumericBinning { mode: 'discrete' | 'binned' edges?: number[] labels?: string[] } export interface ValueOps { alias?: Record exclude?: string[] } export interface ColumnInfo { index: number name: string sourceIndices: number[] uniqueValues: string[] cardinality: number /** Legacy field, retained alongside `role` during the shim window. */ type?: 'prefix' | 'suffix' role?: ColumnRole numeric?: { byValue: Record } binning?: NumericBinning ops?: ValueOps /** Original CSV column header — used for row lookups when name is renamed */ originalName?: string displayName?: string } export interface ClassSchema { classKind: SampleClassKind subKind?: string /** Token count of the class this schema describes. Distinguishes schemas that * share a (kind, subKind) but were split apart by token-length. */ tokenLength?: number columns: ColumnInfo[] groupBy: number[] replicateColumn?: number } export interface MergeSuggestion { classKind: SampleClassKind subKind?: string columnIndices: number[] proposedName: string confidence: number } export interface AutoGroupResult { /** All emitted groups, experimental + overlay concatenated for source compat. */ groups: SampleGroup[] /** Class disposition === 'group' only. */ experimentalGroups?: SampleGroup[] /** Class disposition === 'overlay' only. */ qcGroups?: SampleGroup[] metadata: MetadataRow[] excludedSamples: string[] schemas?: ClassSchema[] fingerprint?: SchemaFingerprint /** * Experimental (disposition === 'group') classes as a nested hierarchy: * one root per class, then one level per enabled groupBy column, then the * sample leaves. Parent nodes carry a sample-count `badge`; leaves carry the * sample name plus the detected injection number (`metadata.injection` and as * the leaf badge). Renders directly in `SampleHierarchyTree`. */ groupTree?: TreeNode[] } export interface SchemaFingerprint { version: 1 classes: Array<{ kind: SampleClassKind subKind?: string tokenLength?: number columns: Array<{ name: string role: ColumnRole sourceIndices: number[] binning?: NumericBinning ops?: ValueOps }> groupBy: number[] }> }