Surfaces

Requires the viz extra for the widgets (pip install "longeron[viz]"); the measure runners reach their engines lazily (the mdao extra for the sizing what-if, the mesh engine ships with the core).

Design: Model-defined analysis surfaces. The declaration the engine derives from is model content: see examples/deepscout/surfaces.sysml (the ScoutSurfaces package) and the LongeronSurfaces rendering vocabulary shipped beside the vendored standard library.

Model-declared analysis surfaces: derive the dashboard from the model.

Design: docs/design/surfaces.md (adopted 2026-08-30), phase 1. A dashboard is a standard view usage: its expose members select the content – a subject plus analysis and verification cases – and each panel is a nested view usage whose render reference names a LongeronSurfaces rendering (the vendored-stdlib-adjacent extension library). surface() turns that declaration into a composed ipywidgets surface:

  • each analysis case becomes a what-if card: its in parameters are sliders whose bounds are MINED from the model’s own constraints (longeron.analysis.verify.attribute_domains()assume constraints in the case objective, or the subject’s own constraint bounds reached through the parameter’s default binding), the @ToolExecution annotation names the engine that re-measures on every move, and the named returns are the readout;

  • each verification case becomes a verdict panel: the verify members name the requirement rows, evaluated through the interpreter (check_requirement()), with the verdict stated in the standard’s VerdictKind vocabulary (pass / fail / inconclusive / error);

  • results flow between panels ONLY through the model’s explicit result bindings (attribute :>> measured = case.result; – the corpus spelling, and the one spelling recognized); a case result no binding names is listed in the wiring map as an unbound diagnostic, so a forgotten coupling is loud;

  • subject typing is the applicability test: a case applies to the surface’s subject when the subject’s specialization chain reaches the case’s declared subject type. A case that does not apply renders as HONEST ABSENCE – the panel stays in the layout, dimmed, stating the subject type it needs – and swapping the subject (surface(..., subject=...), the picker dropdown, or box.swap(...)) re-derives every panel.

The returned widget carries the derivation as data: box.panels (one Panel per declared subview) and box.wiring – the printable WiringMap recording which binding fired for every coupling, the range source per slider, unbound results, and honest absences.

Renderings bind to widget builders through RENDERINGS, a Python registry keyed by rendering qualified name (the longeron.views.VIEW_DEFINITIONS mapping-table precedent). Phase 1 registers the two engine-built panels the proof needs (the what-if card and the verdict cards); the remaining vocabulary entries are declared and render as honest absence until their builders land.

Requires the viz extra for the widgets: pip install "longeron[viz]".

longeron.analysis.surfaces.RANGE_MINED = 'mined-from-constraint'

the wiring map’s range-source vocabulary (honesty requirement: the printable map states, per slider, where its bounds came from)

longeron.analysis.surfaces.RENDERINGS: dict[str, str] = {'LongeronSurfaces::asActionDiagram': 'action_diagram', 'LongeronSurfaces::asMeshViewer': 'mesh_viewer', 'LongeronSurfaces::asMissionGlobe': 'mission_viewer', 'LongeronSurfaces::asReplayPlayer': 'replay_widget', 'LongeronSurfaces::asScoreboard': 'scoreboard', 'LongeronSurfaces::asSizingCards': 'sizing cards', 'LongeronSurfaces::asStateDiagram': 'state_diagram', 'LongeronSurfaces::asStructureDiagram': 'structure_diagram', 'LongeronSurfaces::asVerdictCards': 'verdict cards', 'LongeronSurfaces::asWhatIfCard': 'what-if card'}

rendering qualified name -> the widgets-catalog entry (or engine-built panel) it names; the registry the render references dispatch through. A rendering with no phase-1 builder renders as honest absence naming this table.

class longeron.analysis.surfaces.Coupling(source, target, binding)[source]

Bases: object

One explicit result binding that fired: source -> target.

source: str

<analysis case qname>.<result name>

target: str

<verification case qname> :>> <attribute>

binding: str

where the model states it

class longeron.analysis.surfaces.Panel(name, rendering, builder, case='', absent=False, reason='', sliders=<factory>, ranges=<factory>, returns=(), results=<factory>, verdict='', rows=<factory>, tool=None, readout=None, widget=None, repaint=None)[source]

Bases: object

One declared subview, derived (or honestly absent).

rendering: str

rendering qualified name (’’ when the panel names none)

builder: str

the RENDERINGS entry (’’ when unregistered)

case: str = ''

the case usage’s qualified name, when the panel has one

reason: str = ''

why the panel is absent (always stated, never silent)

sliders: dict[str, Any]

parameter -> FloatSlider

returns: tuple[str, ...] = ()

the case’s declared result names

results: dict[str, float]

last run’s returns

verdict: Literal['pass', 'fail', 'inconclusive', 'error', ''] = ''

VerdictKind for verdict panels (’’ elsewhere)

tool: tuple[str, str] | None = None

the @ToolExecution (toolName, uri)

repaint: Any = None

verdict panels: re-evaluate + repaint (engine wiring)

class longeron.analysis.surfaces.RangeInfo(case, parameter, lo, hi, source, mined_lo=None, mined_hi=None, mined_from=())[source]

Bases: object

One derived slider’s bounds, with their provenance.

source is one of RANGE_MINED (suffixed with the mined constraints’ qualified names), RANGE_OVERRIDDEN, or RANGE_FALLBACK. An override never erases what the model states: mined_lo / mined_hi keep the constraint bounds, and the case evaluation still tells the truth about values outside them.

case: str

the case usage’s qualified name

parameter: str

the in-parameter name

mined_lo: float | None = None

the model’s own bound, kept under an override

mined_from: tuple[str, ...] = ()

the domain ladder’s provenance, verbatim

class longeron.analysis.surfaces.SurfaceWidget(*args, **kwargs)[source]

Bases: Protocol

What surface() returns: an ipywidgets VBox (displayable as usual) carrying the derivation as data. The attributes below are the composite’s kernel-side surface – everything a notebook or test reads and scripts; swap re-derives every panel for another subject (the picker’s programmatic twin).

panels: list[Panel]

one per declared subview, derived or absent

wiring: WiringMap

the printable derivation record

view: str

the view usage’s qualified name

subject: str

the current subject definition’s qualified name

subjects: list[str]

every definition the exposed cases admit

picker: Any

the subject dropdown (an ipywidgets Dropdown)

header: Any

the title bar (an ipywidgets HTML)

children: Any

the VBox composition seam

swap: Callable[[str | Definition], None]

re-derive for a subject

longeron.analysis.surfaces.VerdictKind

the standard VerdictKind vocabulary a verdict panel aggregates to ("error" is the honest extra state: the evaluation itself failed), cross-asserted against the tone table by the test suite

alias of Literal[‘pass’, ‘fail’, ‘inconclusive’, ‘error’]

class longeron.analysis.surfaces.WiringMap(view, subject, panels=<factory>, ranges=<factory>, couplings=<factory>, unbound=<factory>, absences=<factory>, notes=<factory>)[source]

Bases: object

The derived wiring, printable: print(box.wiring).

Everything the surface wired – and everything it honestly did not: which binding fired for every coupling, the range source per slider, case results no binding names (unbound), and the panels that do not apply to the subject (absences).

unbound: list[str]

case results nothing binds

longeron.analysis.surfaces.surface(model, view, *, subject=None, ranges=None)[source]

Derive the composed dashboard a view usage declares.

view names the surface’s view usage (qualified name or element). Its own expose names the home subject; each nested view usage is one panel, deriving through its own expose (the content: an analysis or verification case) and render (the presentation: a rendering the RENDERINGS registry maps to a builder).

subject re-targets the whole surface at another definition (a qualified name or the element). Applicability re-derives: cases whose subject type the new subject does not reach render as honest absence, stating the type they need.

ranges overrides the mined slider bounds per parameter – ranges={"loiterSpeed": (8.0, 30.0)} – keyed by the in-parameter’s name or qualified spelling (<case qname>::<name> or the parameter’s own qualified name). A key matching no parameter, a key matching more than one, or bounds with lo >= hi are refused loudly. An override REPLACES the mined range for that slider only; every other slider stays mined. It is a UI freedom, not a model edit: the mined bounds stay recorded (RangeInfo.mined_lo / mined_hi, and on the slider’s provenance marker), and when an override widens past a model constraint the case evaluation still tells the truth – the interpreter and the verdict panels report the violation the model states.

Returns an ipywidgets VBox carrying the derivation as data (the SurfaceWidget protocol): .panels (Panel per subview), .wiring (the printable WiringMap), .subject, .subjects (every definition the exposed cases admit), .picker (a dropdown that re-derives on change), and .swap(subject) (the same re-derivation, scriptable).

Return type:

SurfaceWidget