← Files Biological Sequence & Alignment ViewerARCHIVED FILE

src/msa/molecule-inference.ts

3.15 KB · Sep 30, 2026 · 23:01 UTC

↓ Download file

import {
  classifyProteinResidue,
  isGapSymbol,
  isNucleotideSymbol,
  isProteinExclusiveSymbol,
  normalizeResidueSymbol,
} from "./residue-alphabet";
import type {
  MsaAnnotationTrack,
  MsaFormat,
  MsaMoleculeEvidence,
  MsaMoleculeInference,
} from "./types";

export function inferMsaMoleculeType({
  annotations,
  format,
  rows,
}: {
  annotations: Array<MsaAnnotationTrack>;
  format: MsaFormat;
  rows: Array<{ alignedSequence: string }>;
}): MsaMoleculeInference {
  const symbols = new Set<string>();
  for (const row of rows) {
    for (const symbol of row.alignedSequence) {
      const normalized = normalizeResidueSymbol(symbol);
      if (!isGapSymbol(normalized)) {
        symbols.add(normalized);
      }
    }
  }

  const evidence: Array<MsaMoleculeEvidence> = [];
  const warnings: Array<string> = [];
  const hasT = symbols.has("T");
  const hasU = symbols.has("U");
  const hasStructure = annotations.some(
    (track) => track.kind === "rna-secondary-structure",
  );
  const hasProteinExclusive = [...symbols].some(isProteinExclusiveSymbol);
  const allNucleotide = [...symbols].every(isNucleotideSymbol);
  const allProteinLike = [...symbols].every(
    (symbol) =>
      classifyProteinResidue(symbol) !== "unknown" ||
      ["A", "C", "G", "T", "U"].includes(symbol),
  );

  if (hasT) {
    evidence.push("dna-t-present");
  }
  if (hasU) {
    evidence.push("rna-u-present");
  }
  if (hasStructure) {
    evidence.push("stockholm-rna-structure-present");
  }
  if (hasProteinExclusive) {
    evidence.push("protein-exclusive-symbol-present");
  }
  if (format === "a2m" || format === "a3m") {
    evidence.push("a2m-a3m-profile-convention");
  }

  if (hasProteinExclusive) {
    return {
      confidence: "high",
      evidence,
      moleculeType: "protein",
      warnings,
    };
  }

  if (hasStructure) {
    return {
      confidence: "high",
      evidence,
      moleculeType: "rna",
      warnings,
    };
  }

  if (format === "a2m" || format === "a3m") {
    return {
      confidence: "medium",
      evidence,
      moleculeType: "protein",
      warnings,
    };
  }

  if (allNucleotide && hasT && !hasU) {
    return { confidence: "high", evidence, moleculeType: "dna", warnings };
  }

  if (allNucleotide && hasU && !hasT) {
    return { confidence: "high", evidence, moleculeType: "rna", warnings };
  }

  if (allNucleotide && hasT && hasU) {
    warnings.push("The alignment mixes T and U nucleotide symbols.");
    return {
      confidence: "medium",
      evidence: [...evidence, "ambiguous-symbol-set"],
      moleculeType: "nucleic-acid-ambiguous",
      warnings,
    };
  }

  if (allNucleotide) {
    return {
      confidence: "low",
      evidence: [...evidence, "ambiguous-symbol-set"],
      moleculeType: "nucleic-acid-ambiguous",
      warnings,
    };
  }

  if (allProteinLike) {
    return {
      confidence: "low",
      evidence: [...evidence, "ambiguous-symbol-set"],
      moleculeType: "protein",
      warnings,
    };
  }

  warnings.push("Some alignment symbols cannot be classified confidently.");
  return {
    confidence: "low",
    evidence: [...evidence, "ambiguous-symbol-set"],
    moleculeType: "unknown",
    warnings,
  };
}

SHA-256: 83e8c51dc0130234db00836be184b3c4a07b92b18dcf5b6712ecafbf62f1ca6a