Source code for vibesketch.chem.report

"""What the properties panel shows for each species of a document."""

from __future__ import annotations

from dataclasses import asdict, dataclass, field
from typing import Any

from vibesketch.document.model import Document

__all__ = ["SpeciesReport", "report"]


[docs] @dataclass class SpeciesReport: id: str label: str | None smiles: str | None inchikey: str | None inchi: str | None formula: str | None average_mass: float | None monoisotopic_mass: float | None charge: int multiplicity: int #: The checked name (see :mod:`vibesketch.naming`), or ``None`` if not asked for. name: dict[str, Any] | None #: Mass percent per element and the "Anal. Calcd for" line (:mod:`vibesketch.chem.analysis`). analysis: dict[str, Any] | None = None #: Calculated estimates: logP, molar refractivity, TPSA, donors, acceptors, counts. descriptors: dict[str, Any] | None = None #: CIP descriptors, undefined stereo and enhanced-stereo groups (:mod:`vibesketch.chem.cip`). stereo: dict[str, Any] | None = None #: Atoms marked in colour: ``{"colour", "atoms": ["C2", ...]}`` per highlight. highlights: list[dict[str, Any]] = field(default_factory=list) #: Abbreviations drawn contracted in it, such as ``["Boc", "Ph"]``. abbreviations: list[str] = field(default_factory=list) #: A generated nanostructure's key, description and size (see document.nanostructures). nanostructure: dict[str, Any] | None = None warnings: list[str] = field(default_factory=list)
[docs] def to_dict(self) -> dict[str, Any]: return asdict(self)
[docs] def report(document: Document, *, names: bool = True) -> list[SpeciesReport]: """One report per species, in document order.""" from vibesketch.chem.analysis import descriptors, elemental_analysis from vibesketch.chem.species import TRANSITION_METALS, identity, properties from vibesketch.document.abbreviations import abbreviations_of from vibesketch.document.formats import species_mols from vibesketch.document.highlights import marks_for from vibesketch.document.stereo import described, stale_labels from vibesketch.naming import name_for mols = species_mols(document) stale = set(stale_labels(document)) contracted = abbreviations_of(document) rows: list[SpeciesReport] = [] for species in document.species: mol = mols.get(species.id) warnings: list[str] = [] props = properties(mol) if mol is not None else None name: dict[str, Any] | None = None if species.name is not None: name = { "status": "set", "value": species.name.value, "source": species.name.source, "confidence": species.name.confidence, "note": None, } elif names and species.smiles: result = name_for(species.smiles, species.charge, species.multiplicity) name = asdict(result) if mol is not None: numbers = {int(a.GetAtomicNum()) for a in mol.GetAtoms()} if numbers & TRANSITION_METALS: warnings.append( "contains a transition metal: set the multiplicity explicitly, the drawing " "cannot know its spin state" ) drawn = identity(mol) if species.id in stale: warnings.append( "the stereo labels drawn on it no longer match its structure: label again" ) if drawn.charge != species.charge: warnings.append( f"the drawing's charge is {drawn.charge}, the document says {species.charge}" ) formula = props.formula if props else None average = round(props.average_mass, 4) if props else None monoisotopic = round(props.monoisotopic_mass, 6) if props else None made: dict[str, Any] | None = None if species.solid is not None: from vibesketch.chem.formula import parse_formula formula = species.solid.text average = species.solid.molar_mass_g_mol mono = parse_formula(species.solid.text).monoisotopic_mass() monoisotopic = round(mono, 6) if mono is not None else None if average is None: warnings.append( f"{species.solid.text} has a variable composition: it has no molar mass" ) if species.nanostructure is not None: from vibesketch.document.nanostructures import masses, summary made = summary(species) formula = species.nanostructure.formula average, monoisotopic = masses(species.conformers[0].symbols) rows.append( SpeciesReport( id=species.id, label=species.label, smiles=species.smiles, inchikey=species.inchikey, inchi=props.inchi if props else None, formula=formula, average_mass=average, monoisotopic_mass=monoisotopic, charge=species.charge, multiplicity=species.multiplicity, name=name, analysis=elemental_analysis(mol).to_dict() if mol is not None else None, descriptors=descriptors(mol).to_dict() if mol is not None else None, stereo=described(mol) if mol is not None else None, highlights=[ { "colour": colour, "atoms": [f"{mol.GetAtomWithIdx(i).GetSymbol()}{i + 1}" for i in atoms], } for atoms, colour in marks_for(document, species.id, mol) ] if mol is not None else [], abbreviations=contracted.get(species.id, []), nanostructure=made, warnings=warnings, ) ) return rows