Spaces:
Sleeping
Sleeping
File size: 4,585 Bytes
2e818da | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | from __future__ import annotations
import hashlib
import math
from app.schemas.visual_lesson import NucleicAcidSpec
from app.services.nucleic_compiler import NucleicCompiler
from app.services.nucleic_resolver import DNA_ALPHABET, RNA_ALPHABET
from app.services.visual_lesson_store import VisualLessonStore
class NucleicSpecValidationError(ValueError):
pass
class NucleicSpecValidator:
def __init__(self, store: VisualLessonStore, compiler: NucleicCompiler) -> None:
self.store = store
self.compiler = compiler
def validate(self, spec: NucleicAcidSpec) -> None:
if spec.mode != "structure":
if not 1 <= len(spec.sequence) <= 60:
raise NucleicSpecValidationError("The displayed sequence must contain 1–60 nucleotides")
alphabet = RNA_ALPHABET if spec.molecule == "rna" else DNA_ALPHABET
if not set(spec.sequence) <= alphabet or ("T" in spec.sequence and "U" in spec.sequence):
raise NucleicSpecValidationError("The displayed sequence is not a valid DNA or RNA IUPAC sequence")
feature_ids = {feature.feature_id for feature in spec.features}
claim_ids = {claim.claim_id for claim in spec.evidence_claims}
if any(feature_id not in feature_ids for feature_id in spec.composition.visible_feature_ids):
raise NucleicSpecValidationError("The composition references an unknown visual feature")
for feature in spec.features:
if not feature.claim_ids or any(claim_id not in claim_ids for claim_id in feature.claim_ids):
raise NucleicSpecValidationError(f"Feature {feature.feature_id} is not linked to evidence")
if spec.mode == "structure":
if not spec.structure or spec.composition.primary_view != "structure":
raise NucleicSpecValidationError("A molecular structure visualization requires official coordinates")
path = self.store.asset_path(spec.project_id, spec.structure.coordinate_asset_id, spec.structure.coordinate_format)
digest = hashlib.sha256(path.read_bytes()).hexdigest()
if digest != spec.structure.coordinate_sha256 or digest != spec.structure.coordinate_asset_id:
raise NucleicSpecValidationError("The molecular coordinate checksum does not match the saved asset")
chain_ids = {chain.chain_id for chain in spec.structure.chains}
if any(feature.chain_id and feature.chain_id not in chain_ids for feature in spec.features):
raise NucleicSpecValidationError("A molecular feature references an unknown chain")
elif spec.structure is not None:
raise NucleicSpecValidationError("Generic nucleic geometry cannot claim an official coordinate structure")
molecule = spec.nucleobase_molecule
if spec.composition.primary_view == "molecule_3d" and molecule is None:
raise NucleicSpecValidationError("A 3D molecule view requires verified compound coordinates")
if molecule is not None:
if spec.focus_base != molecule.symbol:
raise NucleicSpecValidationError("The resolved nucleobase does not match the requested base")
atom_ids = {atom.atom_id for atom in molecule.atoms}
if not atom_ids or len(atom_ids) != len(molecule.atoms):
raise NucleicSpecValidationError("The molecular atom catalogue is empty or contains duplicate IDs")
if any(not all(math.isfinite(value) for value in (atom.x, atom.y, atom.z)) for atom in molecule.atoms):
raise NucleicSpecValidationError("The molecular coordinates contain a non-finite value")
if any(
bond.atom_a not in atom_ids
or bond.atom_b not in atom_ids
or bond.atom_a == bond.atom_b
or bond.order not in {1, 2, 3}
for bond in molecule.bonds
):
raise NucleicSpecValidationError("The molecular bond graph references invalid atoms")
if "nucleobase-structure" not in claim_ids:
raise NucleicSpecValidationError("The 3D molecule is not linked to official compound evidence")
elif spec.focus_base and spec.composition.primary_view == "molecule_3d":
raise NucleicSpecValidationError("The requested nucleobase has no verified 3D conformer")
compiled = self.compiler.compile_spec(spec)
if not compiled.assertions_passed:
raise NucleicSpecValidationError("The nucleic geometry compiler assertions failed")
|