Skip to content

visualdynamics.report.check_blocks

check_blocks

Check blocks: the checks Visual Dynamics reads, carried in the report (report feature check_blocks).

The app reads a record or a spectrum in ways a test report has to carry: the time statistics of a random record (the drive's skewness, kurtosis and density, which MIL-STD-810H (2019), Method 514.8, in the text under Table 514.8-II, asks for whenever the drive departs from a Gaussian), whether a shock record's velocity returns to rest, the points rule of a shock response spectrum, a classical pulse against its tolerance, the rms a spectrum carries against its record's, a random channel's lines outside its band beside what chance puts there, a sine record's distortion and frequency, a sweep's rate against the modes it passes, two signatures compared mode by mode, and a replicated record judged segment by segment. Each is one function in core, which the reading's view, the script and this module all call; nothing here computes a check a second way.

With the feature on, each check the report can make becomes a block in its results: a heading with the check's verdict and its words, the criterion it was judged against, a table with a row per channel (or mode, pass or segment), and its figures. A check that judged nothing says "Not judged" and why, and is never counted a pass: a dead channel, a sample that is not a number and an input the check refuses each name their cause.

Which checks a report makes:

  • From what the report shows. A check is made when the report already binds every object the check reads (AUTOMATIC): the time statistics of a random test's record (each channel's samples on the rail counted against the level it clips at, ChannelTable.clip_ranges, when the project holds a channel table), the rms check of its spectrum against the record and its lines by chance against the specification, the velocity check of a shock record, the points rule of a shock response spectrum against its requirement, the distortion and the frequency of each tone of a sine record. The sweep rate is made when the project also holds a shape set, whose modes it is checked against.
  • From what the author states. Some checks need a fact no object holds: the nominal pulse, the modes a sweep must let build, which signature was taken before the test and which after, the segments of a replicated record. Those are made from a check's own block, which carries what it reads and its settings (add, Project.report_check_block), and saved with the report and its template.

The readings can take a while (the distortion check rebuilds every tone on every channel), so they are made once and kept (read, keyed on the objects they read and the settings), and the app makes them on its worker before it draws the page (prepare, pending). A script that renders a report makes them as it renders.

The blocks are laid out on the page being rendered (page), under a Checks heading after the last figure of the results, so the report's own blocks keep their order and turning the feature off gives back the page it was. With skeleton sections on, the Checks fall under Results.

Classes:

Name Description
Check

One check a report can carry.

Item

One check a report will carry: the check, the names of the objects

Refusal

What a reading said instead of a result: why it could not be read.

Shown

A reading laid out for the page: its verdict and words, the

Functions:

Name Description
check

The check called key; ValueError naming the checks for any

add

Give report a check of its own, and return its block's index.

items

The checks report carries, in CHECKS order: each check of its

kept

The reading kept for check key over the objects found (each

forget

Drop every kept reading, so the next render reads again.

read

The reading of one item: what its check's core function returns,

pending

The items of report with no kept reading, or whose reading was

prepare

Read every check report carries that has no kept reading, so the

status_of

One verdict from the items' own: True inside, False outside, None

show

One item's reading (read) laid out in the display units us.

title_of

What an item's block is headed: the check's title, and the tone

page

The page blocks of every check report carries, in order: the

origin

What a check's page block stands for when the skeleton places it:

position

Where the checks go among the page's built blocks: at the first

finish

Draw each check's head in place: the Checks heading, or a check's

Classes

Check dataclass

Check(key: str, title: str, description: str, fields: tuple[tuple[str, str], ...], needs: tuple[str, ...], automatic: bool, settings: tuple[str, ...] = (), required: tuple[str, ...] = ())

One check a report can carry.

fields are the binding fields of the check's block, each with the symbolic binding it takes by default (core.report.resolve_binding); needs are the fields that must resolve for the check to be made. automatic checks are made whenever the report binds what they read; the others only from a block of their own. settings are the keyword arguments a block may carry to the reading, and required those it must.

Item dataclass

Item(check: Check, bound: tuple[tuple[str, str], ...], settings: Mapping[str, Any] = dict(), index: int | None = None)

One check a report will carry: the check, the names of the objects its fields resolved to (bound), its settings, and the index of its own block in the report (None for a check made from what the report shows).

Methods:

Name Description
objects

The objects each field names, by field.

Methods:
objects
objects(objects: Mapping[str, Any]) -> dict[str, Any]

The objects each field names, by field.

Source code in src/visualdynamics/report/check_blocks.py
def objects(self, objects: Mapping[str, Any]) -> dict[str, Any]:
    """The objects each field names, by field."""
    return {key: objects.get(name) for key, name in self.bound}

Refusal dataclass

Refusal(reason: str)

What a reading said instead of a result: why it could not be read.

Shown dataclass

Shown(status: str, words: str, criterion: str = '', headers: list[str] = list(), rows: list[list[str]] = list(), marks: list[list[str | None]] = list(), figures: list[dict[str, Any]] = list())

A reading laid out for the page: its verdict and words, the criterion, the table and the figures (each a built 'plot' block).

Functions:

check

check(key: Any) -> Check

The check called key; ValueError naming the checks for any other key.

Source code in src/visualdynamics/report/check_blocks.py
def check(key: Any) -> Check:
    """The check called `key`; `ValueError` naming the checks for any
    other key."""
    if not isinstance(key, str):
        raise TypeError(f'a check is named by its key, a str, not '
                        f'{type(key).__name__}')
    found = CHECKS.get(key)
    if found is None:
        raise ValueError(f'unknown check {key!r}; the checks are: '
                         f'{", ".join(CHECKS)}')
    return found

add

add(report: Any, key: str, bindings: Mapping[str, str] | None = None, settings: Mapping[str, Any] | None = None) -> int

Give report a check of its own, and return its block's index.

The block is a text block carrying the check's key, its bindings (each field the check takes, by default its symbolic binding) and its settings, the keyword arguments the check's reading takes. It is drawn, with the other checks, only while the feature is on, and is saved with the report and its template. A check that is made from what the report shows, given a block of its own, is made from the block alone, so its settings hold.

Raises ValueError for an unknown check, a field the check does not take, a setting it does not take, a required setting left out, a field with nothing bound to it, and settings that cannot be saved (anything but numbers, strings, booleans, None and lists or mappings of them). Nothing is changed when it raises.

Source code in src/visualdynamics/report/check_blocks.py
def add(report: Any, key: str, bindings: Mapping[str, str] | None = None,
        settings: Mapping[str, Any] | None = None) -> int:
    """Give `report` a check of its own, and return its block's index.

    The block is a text block carrying the check's key, its bindings
    (each field the check takes, by default its symbolic binding) and its
    settings, the keyword arguments the check's reading takes. It is
    drawn, with the other checks, only while the feature is on, and is
    saved with the report and its template. A check that is made from
    what the report shows, given a block of its own, is made from the
    block alone, so its settings hold.

    Raises `ValueError` for an unknown check, a field the check does not
    take, a setting it does not take, a required setting left out, a
    field with nothing bound to it, and settings that cannot be saved
    (anything but numbers, strings, booleans, None and lists or
    mappings of them). Nothing is changed when it raises.
    """
    found = check(key)
    given = dict(bindings or {})
    chosen = dict(settings or {})
    takes = dict(found.fields)
    for name in given:
        if name not in takes:
            raise ValueError(f'the {found.title.lower()} check reads '
                             f'{", ".join(takes)}, not {name!r}')
    for name in chosen:
        if name not in found.settings:
            raise ValueError(
                f'the {found.title.lower()} check takes the settings '
                f'{", ".join(found.settings)}, not {name!r}')
    missing = [name for name in found.required if name not in chosen]
    if missing:
        raise ValueError(f'the {found.title.lower()} check needs '
                         f'{", ".join(missing)}')
    bound = {name: str(given.get(name) or default)
             for name, default in found.fields}
    empty = [name for name in found.needs if not bound[name]]
    if empty:
        raise ValueError(f'the {found.title.lower()} check reads '
                         f'{", ".join(empty)}; name what it reads')
    try:
        stored = json.loads(json.dumps(chosen, allow_nan=False))
    except (TypeError, ValueError):
        raise ValueError('a check block is saved with the report, so its '
                         'settings are numbers, strings, booleans, None and '
                         'lists of them') from None
    block = {'kind': 'text', 'text': '', 'check': found.key,
             **{name: value for name, value in bound.items() if value},
             'settings': stored}
    report.blocks.append(block)
    return len(report.blocks) - 1

items

items(report: Any, objects: Mapping[str, Any], object_groups: Sequence[Mapping[str, Any]] | None = None) -> list[Item]

The checks report carries, in CHECKS order: each check of its own (a block carrying 'check') whose needed fields resolve, and each check in AUTOMATIC that the report has no block of and whose every needed object the report already draws. The distortion check is made once per tone of the level set it reads.

Source code in src/visualdynamics/report/check_blocks.py
def items(report: Any, objects: Mapping[str, Any],
          object_groups: Sequence[Mapping[str, Any]] | None = None
          ) -> list[Item]:
    """The checks `report` carries, in `CHECKS` order: each check of its
    own (a block carrying 'check') whose needed fields resolve, and each
    check in `AUTOMATIC` that the report has no block of and whose every
    needed object the report already draws. The distortion check is made
    once per tone of the level set it reads.
    """
    from ..core.sine import SineLevelSet

    own: list[Item] = []
    keys = set()
    for index, block in enumerate(report.blocks):
        key = block.get('check')
        if key not in CHECKS:
            continue
        keys.add(key)
        found = CHECKS[key]
        # a field a block was saved without (one added to the check
        # since) is bound as the check binds it by default
        bound = tuple((name, _resolved(block.get(name) or default, objects,
                                       object_groups))
                      for name, default in found.fields)
        if not all(dict(bound)[name] for name in found.needs):
            continue
        settings = block.get('settings') or {}
        own.append(Item(found, bound, dict(settings), index))
    shown = _shown(report, objects, object_groups)
    shown_types = {type(objects[name]) for name in shown}
    made: list[Item] = []
    for key in AUTOMATIC:
        if key in keys:
            continue
        found = CHECKS[key]
        bound = tuple((name, _resolved(default, objects, object_groups))
                      for name, default in found.fields)
        names = dict(bound)
        if not all(names[name] in shown for name in found.needs):
            continue
        if key == 'sweep_rate' and not names['shapes']:
            # the modes a sweep is checked against are a fact about the
            # article; without a shape set the project holds none
            continue
        if not _applies(key, {name: objects[value]
                              for name, value in bound if value},
                        shown_types):
            continue
        if key in ('distortion', 'frequency'):
            levels = objects[names['measured']]
            tones = (levels.tone_names if isinstance(levels, SineLevelSet)
                     else [])
            made.extend(Item(found, bound, {'tone': tone}) for tone in tones)
            continue
        made.append(Item(found, bound))
    order = list(CHECKS)
    return sorted(made + own, key=lambda item: order.index(item.check.key))

kept

kept(key: str, found: Mapping[str, Any], settings: Mapping[str, Any] | None = None) -> Any

The reading kept for check key over the objects found (each of the check's fields by name) with settings, or None when none is kept or it was canceled. Never reads: a caption states what a check has found, and the slow checks are made on the app's worker (prepare), not where a caption is read.

Parameters:

Name Type Description Default
key str

The check, one of CHECKS.

required
found mapping

The object each of the check's fields reads, by field name.

required
settings mapping

The settings it was read with; none when omitted.

None

Returns:

Type Description
object or None

What the check's core function returned, a Refusal, or None.

Source code in src/visualdynamics/report/check_blocks.py
def kept(key: str, found: Mapping[str, Any],
         settings: Mapping[str, Any] | None = None) -> Any:
    """The reading kept for check `key` over the objects `found` (each
    of the check's fields by name) with `settings`, or None when none is
    kept or it was canceled. Never reads: a caption states what a check
    has found, and the slow checks are made on the app's worker
    (`prepare`), not where a caption is read.

    Parameters
    ----------
    key : str
        The check, one of `CHECKS`.
    found : mapping
        The object each of the check's fields reads, by field name.
    settings : mapping, optional
        The settings it was read with; none when omitted.

    Returns
    -------
    object or None
        What the check's core function returned, a `Refusal`, or None.
    """
    ordered = {name: found.get(name) for name, _default in check(key).fields}
    if any(obj is None for obj in ordered.values()):
        return None
    reading = _kept(_found_key(key, ordered, settings or {}), ordered)
    return None if reading is _CANCELED else reading

forget

forget() -> None

Drop every kept reading, so the next render reads again.

Source code in src/visualdynamics/report/check_blocks.py
def forget() -> None:
    """Drop every kept reading, so the next render reads again."""
    _KEPT.clear()

read

read(item: Item, objects: Mapping[str, Any]) -> Any

The reading of one item: what its check's core function returns, or a Refusal with the reason when the function refuses. Made once and kept while the objects it read and its settings stay the same.

Source code in src/visualdynamics/report/check_blocks.py
def read(item: Item, objects: Mapping[str, Any]) -> Any:
    """The reading of one item: what its check's core function returns,
    or a `Refusal` with the reason when the function refuses. Made once
    and kept while the objects it read and its settings stay the same."""
    found = item.objects(objects)
    key = _key(item, objects)
    reading = _kept(key, found)
    if reading is not None:
        return reading
    unknown = [name for name in item.settings
               if name not in item.check.settings]
    missing = [name for name in item.check.required
               if name not in item.settings]
    if unknown or missing:
        # a block written by hand, or by a later version: said, not read
        reading = Refusal(
            f'the block\'s settings {", ".join(map(repr, unknown))} are not '
            f'the check\'s' if unknown else
            f'the block does not say {", ".join(missing)}')
    else:
        try:
            reading = _READERS[item.check.key](found, dict(item.settings))
        except (ValueError, TypeError) as refusal:
            # a value the reading cannot take, such as a word where a
            # number belongs, is refused as the reading refuses its own
            reading = Refusal(_reason(refusal))
    _keep(key, found, reading)
    return reading

pending

pending(report: Any, objects: Mapping[str, Any], object_groups: Sequence[Mapping[str, Any]] | None = None) -> list[Item]

The items of report with no kept reading, or whose reading was canceled: what prepare would read. Empty when the feature is off.

Source code in src/visualdynamics/report/check_blocks.py
def pending(report: Any, objects: Mapping[str, Any],
            object_groups: Sequence[Mapping[str, Any]] | None = None
            ) -> list[Item]:
    """The items of `report` with no kept reading, or whose reading was
    canceled: what `prepare` would read. Empty when the feature is off."""
    from .features import enabled

    if FEATURE not in enabled(report):
        return []
    out = []
    for item in items(report, objects, object_groups):
        kept = _kept(_key(item, objects), item.objects(objects))
        if kept is None or kept is _CANCELED:
            out.append(item)
    return out

prepare

prepare(report: Any, objects: Mapping[str, Any], object_groups: Sequence[Mapping[str, Any]] | None = None, *, progress: Callable[[int, int], Any] | None = None) -> int

Read every check report carries that has no kept reading, so the page can be drawn without waiting on any. progress(done, total) is told before each and at the end; what it raises (a cancel) stops the reading there, and every item not yet read is kept as not judged, the reading canceled, so the page can still be drawn; pending offers those again. Returns how many were read.

Source code in src/visualdynamics/report/check_blocks.py
def prepare(report: Any, objects: Mapping[str, Any],
            object_groups: Sequence[Mapping[str, Any]] | None = None, *,
            progress: Callable[[int, int], Any] | None = None) -> int:
    """Read every check `report` carries that has no kept reading, so the
    page can be drawn without waiting on any. `progress(done, total)` is
    told before each and at the end; what it raises (a cancel) stops the
    reading there, and every item not yet read is kept as not judged,
    the reading canceled, so the page can still be drawn; `pending`
    offers those again. Returns how many were read."""
    waiting = pending(report, objects, object_groups)
    for done, item in enumerate(waiting):
        try:
            if progress is not None:
                progress(done, len(waiting))
        except BaseException:
            for rest in waiting[done:]:
                _keep(_key(rest, objects), rest.objects(objects), _CANCELED)
            raise
        _KEPT.pop(_key(item, objects), None)
        read(item, objects)
    if progress is not None:
        progress(len(waiting), len(waiting))
    return len(waiting)

status_of

status_of(verdicts: Sequence[bool | None]) -> str

One verdict from the items' own: True inside, False outside, None not judged. Any outside is flagged; nothing judged is not judged; some judged and some not, with none outside, is partly judged; and only every item judged and inside is passed.

Source code in src/visualdynamics/report/check_blocks.py
def status_of(verdicts: Sequence[bool | None]) -> str:
    """One verdict from the items' own: True inside, False outside, None
    not judged. Any outside is flagged; nothing judged is not judged;
    some judged and some not, with none outside, is partly judged; and
    only every item judged and inside is passed."""
    if any(verdict is False for verdict in verdicts):
        return FLAGGED
    if not any(verdict is True for verdict in verdicts):
        return NOT_JUDGED
    if any(verdict is None for verdict in verdicts):
        return PARTLY
    return PASSED

show

show(item: Item, objects: Mapping[str, Any], us: Any) -> Shown

One item's reading (read) laid out in the display units us.

Source code in src/visualdynamics/report/check_blocks.py
def show(item: Item, objects: Mapping[str, Any], us: Any) -> Shown:
    """One item's reading (`read`) laid out in the display units `us`."""
    reading = read(item, objects)
    if isinstance(reading, Refusal):
        return _not_judged(reading.reason)
    return _SHOWERS[item.check.key](reading, item.objects(objects), us)

title_of

title_of(item: Item) -> str

What an item's block is headed: the check's title, and the tone for a distortion check of one tone.

Source code in src/visualdynamics/report/check_blocks.py
def title_of(item: Item) -> str:
    """What an item's block is headed: the check's title, and the tone
    for a distortion check of one tone."""
    tone = (item.settings.get('tone')
            if item.check.key in ('distortion', 'frequency') else None)
    return f'{item.check.title}, {tone}' if tone else item.check.title

page

page(report: Any, objects: Mapping[str, Any], us: Any, object_groups: Sequence[Mapping[str, Any]] | None = None, edit: bool = False) -> list[dict[str, Any]]

The page blocks of every check report carries, in order: the Checks heading, then for each check its head (verdict, words and criterion), its table and its figures. Empty when it carries none.

A head is a text block carrying 'check_head' for finish to draw once the page's text is rendered. On the edit page, the blocks of a check of its own carry its block's index, so a click selects it.

Source code in src/visualdynamics/report/check_blocks.py
def page(report: Any, objects: Mapping[str, Any], us: Any,
         object_groups: Sequence[Mapping[str, Any]] | None = None,
         edit: bool = False) -> list[dict[str, Any]]:
    """The page blocks of every check `report` carries, in order: the
    Checks heading, then for each check its head (verdict, words and
    criterion), its table and its figures. Empty when it carries none.

    A head is a text block carrying 'check_head' for `finish` to draw
    once the page's text is rendered. On the edit page, the blocks of a
    check of its own carry its block's index, so a click selects it.
    """
    found = items(report, objects, object_groups)
    if not found:
        return []
    out: list[dict[str, Any]] = [
        {'kind': 'text', 'text': '', 'check_head': {'heading': HEADING}}]
    for item in found:
        shown = show(item, objects, us)
        head: dict[str, Any] = {
            'kind': 'text', 'text': '',
            'check_head': {'title': title_of(item), 'status': shown.status,
                           'words': shown.words,
                           'criterion': shown.criterion}}
        blocks = [head]
        if shown.rows:
            table = {'kind': 'table',
                     'caption': f'{title_of(item)}: {shown.status}',
                     'headers': list(shown.headers),
                     'rows': [list(row) for row in shown.rows]}
            if any(any(mark for mark in row) for row in shown.marks):
                table['marks'] = shown.marks
            blocks.append(table)
        blocks.extend(shown.figures)
        if edit and item.index is not None:
            for block in blocks:
                block['index'] = item.index
        out.extend(blocks)
    return out

origin

origin(block: Mapping[str, Any]) -> dict[str, Any]

What a check's page block stands for when the skeleton places it: the Checks heading as a level-two heading, and the rest as figures of no instrumentation, which fall under Results.

Source code in src/visualdynamics/report/check_blocks.py
def origin(block: Mapping[str, Any]) -> dict[str, Any]:
    """What a check's page block stands for when the skeleton places it:
    the Checks heading as a level-two heading, and the rest as figures of
    no instrumentation, which fall under Results."""
    meta = block.get('check_head') or {}
    if 'heading' in meta:
        return {'kind': 'text', 'text': f'## {meta["heading"]}'}
    return {'kind': block.get('kind'), 'check': True}

position

position(blocks: Sequence[Mapping[str, Any]]) -> int

Where the checks go among the page's built blocks: at the first heading after the last figure (the section that closes the report), or at the end when no heading follows it.

Source code in src/visualdynamics/report/check_blocks.py
def position(blocks: Sequence[Mapping[str, Any]]) -> int:
    """Where the checks go among the page's built blocks: at the first
    heading after the last figure (the section that closes the report),
    or at the end when no heading follows it."""
    figures = [k for k, block in enumerate(blocks)
               if block.get('kind') not in ('text', 'unbound', 'verdict')]
    last = figures[-1] if figures else -1
    for k in range(last + 1, len(blocks)):
        block = blocks[k]
        if block.get('kind') == 'text' and \
                str(block.get('text', '')).lstrip().startswith('## '):
            return k
    return len(blocks)

finish

finish(blocks: Sequence[dict[str, Any]]) -> None

Draw each check's head in place: the Checks heading, or a check's title with its verdict in words, its words, and its criterion.

Source code in src/visualdynamics/report/check_blocks.py
def finish(blocks: Sequence[dict[str, Any]]) -> None:
    """Draw each check's head in place: the Checks heading, or a check's
    title with its verdict in words, its words, and its criterion."""
    for block in blocks:
        meta = block.pop('check_head', None)
        if meta is None:
            continue
        if 'heading' in meta:
            block['html'] = f'<h3>{html.escape(meta["heading"])}</h3>'
            continue
        status = meta['status']
        parts = [f'<h4 class="check">{html.escape(meta["title"])}</h4>',
                 (f'<p class="check-verdict check-{status.replace(" ", "-")}">'
                  f'<strong>{html.escape(VERDICTS[status])}.</strong> '
                  f'{html.escape(meta["words"])}</p>')]
        if meta['criterion']:
            parts.append(f'<p class="check-criterion">'
                         f'{html.escape(meta["criterion"])}</p>')
        block['html'] = ''.join(parts)