// Mock "compute" for the HydroPD UI (Milestone 1). Model metadata is REAL // (see data/models.ts + generated JSON); the per-peptide scores and training // runs here are deterministic stand-ins pending Milestone 2 (backend inference). import { parseSequences, type ParsedSequence } from '../lib/parseSequences' import type { RealModel } from './models' export const CONTACT_EMAIL = 'rsaqe@uwaterloo.ca' // --------------------------------------------------------------------------- // Sample sequences (for "load example" buttons) // --------------------------------------------------------------------------- export const SAMPLE_PEPTIDES = `VLPVPQK IPP NALKPDNR FEEPQQPQQR GQEIYIQQGK VPP SYTNGPQEIYIQQGK AGVALSR LLPHH CTLNR IYIVQGR FESSSQQFQG PLVHLLAQNIR AQQLQQLVLANLAAYSQEQQ EPQQPQQRG FAQPQQLFPE AADSFKHK` // Example protein for the allergen-origin screen: Tri a 39.0101, a recognized wheat // allergen (Triticum aestivum; WHO/IUIS, AllergenOnline). Matches the allergen index exactly. export const SAMPLE_PROTEIN = `>Tri_a_39.0101 wheat allergen (Triticum aestivum) MSPVVKKPEGRNTDTSDHHNQKTEWPELVGKSVEEAKKVILQDKSEAQIVVLPVGTIVTMEYRIDRVRLFVDSLDKIAQVPRVG` // --------------------------------------------------------------------------- // Deterministic pseudo-randomness (stable across renders) // --------------------------------------------------------------------------- function hashString(s: string): number { let h = 2166136261 for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i) h = Math.imul(h, 16777619) } return (h >>> 0) / 4294967295 // -> [0, 1) } // --------------------------------------------------------------------------- // Mock detectability prediction (illustrative, Milestone 2 will run the .joblib) // --------------------------------------------------------------------------- export interface DetectabilityRow { peptide: string length: number score: number prediction: 'Detectable' | 'Not detectable' model: string // Was this peptide in the selected model's training set? Flag so a memorised // high-confidence call isn't mistaken for generalisation. null = held out / unknown. inTraining?: 'positive' | 'negative' | null // Known bioactivity classes for this peptide (';'-joined), merged from the // bioactivity screen so the prediction output carries function too. '' = none found. bioactivities?: string } const DETECT_THRESHOLD = 0.5 export function runDetectability( input: string, model: RealModel, ): DetectabilityRow[] { const peptides = parseSequences(input) return peptides.map((p: ParsedSequence) => { const base = hashString(p.seq + '|' + model.code) const lengthBias = p.seq.length >= 7 && p.seq.length <= 20 ? 0.12 : -0.08 const hydrophobic = (p.seq.match(/[FLIVWY]/g)?.length ?? 0) / p.seq.length const score = Math.min( 0.99, Math.max(0.01, base * 0.7 + lengthBias + hydrophobic * 0.25), ) return { peptide: p.seq, length: p.seq.length, score: Number(score.toFixed(3)), prediction: score >= DETECT_THRESHOLD ? 'Detectable' : 'Not detectable', model: `${model.code} · ${model.species}`, } }) } // --------------------------------------------------------------------------- // Bioactivity / allergen screening (exact-DB match, no offline compute possible) // --------------------------------------------------------------------------- export interface BioactivityRow { peptide: string bioactivities: string // ';'-joined canonical activity classes ('' = no hit) sources: string // ';'-joined DBs the hit came from igeEpitope: boolean // retains a documented linear IgE epitope (Q2) } export interface AllergenRow { protein: string isAllergen: boolean // recognized-allergen protein match (Q1) allergenName: string organism: string source: string } // Screening is an exact match against a static database, it cannot be faked offline. // The mocks parse the input and return empty hits; the page's offline badge makes clear // these are not real screening calls (no fabricated bioactivity/allergen claims). export function runBioactivityPeptides(input: string): BioactivityRow[] { return parseSequences(input).map((p: ParsedSequence) => ({ peptide: p.seq, bioactivities: '', sources: '', igeEpitope: false, })) } export function runAllergenOrigin(input: string): AllergenRow[] { return parseSequences(input).map((p: ParsedSequence) => ({ protein: p.seq.length > 60 ? p.seq.slice(0, 57) + '…' : p.seq, isAllergen: false, allergenName: '', organism: '', source: '', })) } // --------------------------------------------------------------------------- // Mock training result (illustrative) // --------------------------------------------------------------------------- export interface FoldResult { fold: number rocAuc: number prAuc: number nTrain: number nTest: number } export interface TrainingResult { rocAuc: number prAuc: number folds: FoldResult[] nPositives: number nNegatives: number } export function runTraining(input: string, seed: string): TrainingResult { const peptides = parseSequences(input) const nPos = peptides.length const nNeg = Math.round(nPos * 1.0) const base = 0.72 + hashString(seed + nPos) * 0.18 // 0.72 - 0.90 const folds: FoldResult[] = Array.from({ length: 5 }, (_, i) => { const jitter = (hashString(seed + 'fold' + i) - 0.5) * 0.06 const roc = Math.min(0.97, Math.max(0.55, base + jitter)) return { fold: i + 1, rocAuc: Number(roc.toFixed(3)), prAuc: Number((roc - 0.06).toFixed(3)), nTrain: Math.round((nPos + nNeg) * 0.8), nTest: Math.round((nPos + nNeg) * 0.2), } }) const mean = folds.reduce((s, f) => s + f.rocAuc, 0) / folds.length return { rocAuc: Number(mean.toFixed(3)), prAuc: Number((mean - 0.06).toFixed(3)), folds, nPositives: nPos, nNegatives: nNeg, } } // --------------------------------------------------------------------------- // Training-spec option sets, REAL vocabulary from the Step-4 pipeline // --------------------------------------------------------------------------- export const FEATURE_SET_OPTIONS = [ { id: 'phys', label: 'Physicochemical (14 descriptors)', desc: 'PeptideRanker descriptors' }, { id: 'pepbert', label: 'PepBERT', desc: 'PepBERT-large-UniParc embeddings' }, { id: 'esm2', label: 'ESM2-t12-35M', desc: 'ESM2 protein language model' }, { id: 'pep_phys', label: 'PepBERT ⊕ physicochemical', desc: 'Embedding + descriptors' }, { id: 'esm2_phys', label: 'ESM2 ⊕ physicochemical', desc: 'Top-ranked feature set (default)' }, { id: 'pfly', label: 'Pfly (sequence-only)', desc: 'dlomix DetectabilityModel, reads the sequence directly' }, ] export const CLASSIFIER_OPTIONS = [ { id: 'xgb', label: 'XGBoost', desc: 'Gradient-boosted trees (most consistent winner)' }, { id: 'rf', label: 'Random forest', desc: 'Bagged trees, balanced' }, { id: 'mlp', label: 'Neural net (MLP)', desc: 'MLP over features, standardized' }, { id: 'pfly', label: 'Pfly (fine-tuned)', desc: 'dlomix BiGRU, sequence-only (feature set ignored)' }, ] export const SPLIT_OPTIONS = [ { id: 'protein', label: 'Group by protein', desc: 'Production default, avoids peptide leakage' }, { id: 'cluster90', label: 'Cluster (90% id)', desc: 'MMseqs2 sequence-identity clusters' }, { id: 'cluster50', label: 'Cluster (50% id)', desc: 'Stricter homology grouping' }, { id: 'peptide', label: 'By peptide', desc: 'Leakage baseline (inflates AUROC)' }, ] export interface TrainingConfig { featureSet: string classifier: string split: string folds: number balance: string } export const DEFAULT_TRAINING_CONFIG: TrainingConfig = { featureSet: 'esm2_phys', classifier: 'xgb', split: 'protein', folds: 5, balance: '1:1', }