Source code for vibeview.kinds

"""Kind registry — the four-rule partial-support contract.

Rule 1
    Supported kinds are declared explicitly in a single constant.
Rule 2
    Unknown kinds are classified as "skipped, unsupported"; kinds the writer
    emits but the viewer doesn't render yet are an explicit subset
    (DEFERRED_KINDS) surfaced as "skipped, not yet rendered".
Rule 3
    Vendor-namespace sections (``x_<vendor>.*``) get the vendor name extracted.
Rule 4
    Implemented in qvf.py (sha256 verify-before-use guard).
"""

from __future__ import annotations

# ── Rule 1: single explicit registry ──────────────────────────────────────
SUPPORTED_KINDS: frozenset[str] = frozenset(
    {
        "structure",
        "volume.density",
        "volume.orbital",
        "volume.spin",
        "volume.elf",
        "volume.difference",
        "volume.generic",
        "volume.potential",
        "volume.rdg",
        # The transition density of an excited state (qvf 0.2, spec § 5.2): a
        # signed field with the members of a density, drawn like a
        # difference density (#115).
        "volume.transition_density",
        # The Laplacian of the density, ∇²ρ (qvf 0.6, spec § 5.2): a signed
        # scalar grid with the members of a density (#154).
        "volume.laplacian",
        # A Cartesian vector per grid point (qvf 0.6, spec § 5.2), drawn as
        # arrows rather than an isosurface, so not a VOLUME_KINDS member (#156).
        "volume.vector",
        # A complex scalar per grid point (qvf 0.6, spec § 5.2): drawn as its
        # real or imaginary part, or |ψ| coloured by phase; not a VOLUME_KINDS
        # member, whose paths assume a real array (#157).
        "volume.complex",
        # n scalar fields on one grid with an axis (qvf 0.6, spec § 5.2),
        # stepped like trajectory frames; not a VOLUME_KINDS member (#158).
        "volume.series",
        # Energy derivatives (qvf 0.6, spec § 5.10–5.11): force arrows and a
        # Hessian frequency table (#159).
        "forces",
        "hessian",
        # Property tables (qvf 0.6, spec § 5.13), with a dipole arrow for the
        # electric properties (#161).
        "properties.electric",
        "properties.elastic",
        "properties.dielectric",
        # Crystalline orbitals at k-points (qvf 0.6, spec § 5.3.1), evaluated
        # on demand like wavefunction.gto (#162).
        "wavefunction.bloch",
        "basis.ao",
        "bands",
        "spectra.ir",
        "spectra.uvvis",
        "spectra.raman",
        "spectra.ecd",
        "spectra.vcd",
        "spectra.generic",
        "trajectory",
        "reaction.path",
        "reaction.waypoints",
        "scan.surface",
        "vibrations",
        "atom_properties",
        "wavefunction.gto",
        "citations",
        "scf_history",
        "structure.symmetry",
        "spectra.nmr",
        "spectra.epr",
        "dos.total",
        "dos.projected",
        "phonon_bands",
        "phonon_dos",
        "equation_of_state",
        "fermi_surface",
        "bond_orders",
        "topology.qtaim",
        "dos.coop",
        "dos.cohp",
        "run.record",
        "job.spec",
        # Descriptive kinds (qvf 0.2): a scheme relating the structures, and a
        # picture of the archive. Both show in the details panel; neither is
        # chemistry the 3D scene draws.
        "reaction.scheme",
        "preview",
        # The states of a TD-DFT (or similar) calculation, tabulated in a
        # details panel (#109).
        "excited_states",
    }
)

#: Supported kinds that describe the archive rather than hold computed data:
#: the qvf reference writers and vibe-sketch emit them, the vibe-qc writer
#: does not (tests/test_kind_drift.py).
DESCRIPTIVE_KINDS: frozenset[str] = frozenset({"reaction.scheme", "preview"})

# ── Deferred kinds: written + schema-defined, viewer renderer pending ──────
# Kinds the shared QVF schema defines, but which have no viewer panel yet:
# surfaced as "skipped, not yet rendered" instead of "unsupported" (#96).
# The excited-state table left this list with #109; the qvf 0.6 kinds below
# joined it (#153); volume.laplacian (#154), volume.vector (#156),
# volume.complex (#157), volume.series (#158), forces and hessian (#159),
# the properties kinds (#161) and wavefunction.bloch (#162) have since left
# it, so it is empty again; the category stays for the next new kind.
# tests/test_kind_drift.py ties every schema kind to a list.
DEFERRED_KINDS: frozenset[str] = frozenset()

# Volumetric kinds: a scalar field on a grid, drawn as an isosurface. The
# reader decodes their binary payload on first use, not at file-open time.
VOLUME_KINDS: frozenset[str] = frozenset(
    {
        "volume.density",
        "volume.orbital",
        "volume.spin",
        "volume.elf",
        "volume.difference",
        "volume.generic",
        "volume.potential",
        "volume.rdg",
        "volume.transition_density",
        "volume.laplacian",
        "basis.ao",
    }
)

# Kinds with no panel of their own whose content the structure view draws:
# the reader folds a standalone ``bonds`` section into ``read_structure`` and
# the structure renderer draws it. Reported as rendered "via structure", and
# never a reason to refuse a ``critical`` section.
RENDERED_VIA_STRUCTURE: frozenset[str] = frozenset({"bonds"})


# Stored volume kinds that are signed real fields and draw a +/- lobe pair.
# One list for the browser and the terminal, which used to decide by kind in
# one and by "any negative value" in the other (#38-18).
SIGNED_VOLUME_KINDS: frozenset[str] = frozenset(
    {
        "volume.difference",
        "volume.spin",
        "volume.orbital",
        "volume.potential",
        "volume.transition_density",
        "volume.laplacian",
        "basis.ao",
    }
)


[docs] def classify_section(kind: str) -> tuple[str, str | None]: """Return ``(status, detail)`` for a section kind. ``status`` is ``"rendered"`` or ``"skipped"``; a section that fails to read is reported as ``"error"`` by the caller, which knows about the archive, not by this kind-only lookup. ``detail`` is None for a kind with its own panel, ``"via structure"`` for :data:`RENDERED_VIA_STRUCTURE`, ``"via plugin"`` for a kind an installed renderer plugin claims, and the human-readable reason for a skipped kind. ── Rule 2 + 3: unknown kinds and vendor namespaces. """ if kind in SUPPORTED_KINDS: return ("rendered", None) # A standalone `bonds` section has no panel of its own, but the reader # folds its connectivity into the structure section (read_structure) and # the structure renderer draws it. Report it as rendered-via-structure so # the banner doesn't mislead the user into thinking their explicit bonds # were dropped (audit findings A6-01 / A3-03). if kind in RENDERED_VIA_STRUCTURE: return ("rendered", "via structure") # An installed `vibeview.renderers` plugin renders this kind (#113). After # the built-ins, which a plugin cannot claim, and before the skipped # categories: a vendor kind is the usual thing a plugin renders. from vibeview.renderer_plugins import PLUGIN_DETAIL, plugin_kinds if kind in plugin_kinds(): return ("rendered", PLUGIN_DETAIL) # Rule 2 (deferred subset): a kind the writer emits and the schema accepts # but the viewer has no renderer for yet. Honest, distinct from the generic # "unsupported" below so the banner doesn't imply the kind is unhandleable. if kind in DEFERRED_KINDS: return ("skipped", "not yet rendered") # Rule 3: vendor-namespace sections if kind.startswith("x_"): # kind = "x_<vendor>.<rest>" → extract vendor name prefix = kind.split(".", 1)[0] # "x_<vendor>" parts = prefix.split("_", 1) vendor = parts[1] if len(parts) > 1 else "unknown" return ("skipped", f"vendor namespace ({vendor})") return ("skipped", "unsupported")