bio-nexus-api / app /tools /alignment_stats.py
Samad14's picture
Upload folder using huggingface_hub
5d48842 verified
Raw History Blame Contribute Delete
2.35 kB
"""Alignment column statistics — single backend implementation.
This is the authoritative source for the MSA summary metrics that the
frontend renders. The frontend must not recompute scientific numbers; it
renders the values emitted here (the CLUSTAL-style display symbols stay a
pure presentation concern).
"""
from __future__ import annotations
GAP_CHARS = {"-", "."}
# The scientific summary classifies columns exactly as the old client helper did:
# matched — every non-gap residue identical (invariant column)
# gapped — at least one gap character present
# mismatched — >= 2 distinct residues and no gaps (variable column)
def alignment_stats(seqs: list[str]) -> dict:
"""Column-wise summary of an already-aligned set of equal-length strings."""
empty = {
"length": 0,
"matched": 0,
"mismatched": 0,
"gapped": 0,
"total_gaps": 0,
"identity_pct": 0.0,
}
if not seqs:
return empty
length = max(len(s) for s in seqs)
if length == 0:
return empty
matched = 0
mismatched = 0
gapped = 0
total_gaps = 0
for i in range(length):
distinct: set[str] = set()
col_has_gap = False
for s in seqs:
c = (s[i] if i < len(s) else "-").upper()
if c in GAP_CHARS:
col_has_gap = True
total_gaps += 1
else:
distinct.add(c)
if col_has_gap:
gapped += 1
elif len(distinct) <= 1:
matched += 1
else:
mismatched += 1
return {
"length": length,
"matched": matched,
"mismatched": mismatched,
"gapped": gapped,
"total_gaps": total_gaps,
"identity_pct": round(matched / length * 100, 1) if length else 0.0,
}
def parse_aligned_fasta(fasta: str) -> list[str]:
"""Return the aligned sequence bodies (lowercased headers removed, gaps kept)."""
if not fasta:
return []
seqs: list[str] = []
current = ""
for line in (fasta or "").splitlines():
t = line.strip()
if t.startswith(">"):
if current:
seqs.append(current)
current = ""
elif t:
current += t
if current:
seqs.append(current)
return seqs