hydropd / website /src /data /mockData.ts
rikardsaqe's picture
Pfly via feature set, condition Other, Data dropdowns
367367f verified
Raw
History Blame Contribute Delete
7.85 kB
// 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',
}