Findings register: deviations, anomalies and nonconformances in four
parts (report feature findings).
With the feature on, the author enters each finding in a register:
its number and kind (D-1, A-1, NC-1), the run, channel and band it
concerns, and its four parts, fact, cause, effect, and approval or
disposition (visualdynamics.core.findings). The page draws the
register in section 9 of the skeleton, each finding as its four
labeled parts, and lists the findings by number under the Summary. A
part left blank is drawn as owed, and the export's check lists it; it
is never filled in and never left out.
The register is the author's section. The skeleton keeps the
deviations section as the author's to write, and the register lives
in that section's own block (the text block carrying section:
'deviations', under 'findings'), written only by what the author
does: entering a finding, editing it, accepting a draft. Nothing is
generated into it.
- The fact may be offered as a draft, read off a measurement the
project holds (
draft_from_comparison: the channel, the band, the
side and size of the excursion, the record). A draft is held beside
the finding, and its words are not drawn; the author accepts it,
edits it or discards it (accept, discard), and once accepted it
is the author's fact. Until then the fact stays owed, on the page
and in the check, marked as having a drafted fact waiting. Every
source of a draft feeds it through one function, offer.
- A run log brought into the project offers each deviation and
anomaly it holds as a draft (
offer_log): its kind, its fact as
typed at the console and the log's number, proposed, citing the log,
test and entry. The drafts are kept beside the register, never in
it: the register and section 9 do not change, the section stays
owed, and the editing page shows the drafts marked as drafted from
the run log. Accepting one (accept_log) is what enters the finding,
under the log's number when it is free, with its fact through
offer; its cause, effect and approval or disposition stay owed. A
fact the author has written is never offered over, and the log's
approvals and dispositions are never offered. A log with no deviation
or anomaly offers nothing, never "none".
- Cause, effect and approval are only ever typed.
- None is the skeleton's one-step None (
none), and it says only
what the author asks it to: with nothing entered it writes the
section's own None wording, exactly as the skeleton does; with
findings entered, it writes the "none" sentence of each kind that
has none, such as "No anomalies.". An empty register is never read
as "none": until the author says so, a kind with nothing entered is
owed.
The feature needs Skeleton sections, whose sections it fills.
Classes:
| Name |
Description |
FactDraft |
A fact offered for the author to accept, edit or discard.
|
Heads |
The skeleton heads the register draws into (prepare) and what
|
Functions:
| Name |
Description |
register |
The findings entered on report, each checked
|
find |
The finding numbered number; ValueError naming the numbers
|
add |
Enter a finding of kind and return its number.
|
edit |
Change the fields given of finding number (any of EDITABLE;
|
remove |
Take finding number out of the register. Its number is not
|
none |
The deviations section's one-step None, and the words it wrote.
|
offer |
Offer draft as the fact of finding number, for the author to
|
accept |
Make the draft offered on finding number its fact: as offered,
|
discard |
Drop the draft offered on finding number; refused when none is
|
log_drafts |
The findings a run log offers report and the author has not
|
offer_log |
Offer the deviations and anomalies a run log extract holds as
|
accept_log |
Accept the finding the run log offered as number, and return the
|
discard_log |
Drop the finding the run log offered as number; nothing enters
|
fact_text |
The fact a reading supports, in words: the channel, each stretch
|
fact_from_comparison |
A fact draft read off a comparison the project holds: measured
|
draft_from_comparison |
Read a fact draft off a comparison (fact_from_comparison) and
|
check |
The skeleton's owed list with section 9 read off the register.
|
finding_html |
One finding as the page draws it: its number and what it
|
register_html |
The register as section 9 draws it: each kind in turn, its
|
summary_html |
The Summary's line of findings by number: "Deviations: D-1, D-2.
|
offered_html |
The findings a run log offers, as the editing page draws them under
|
prepare |
Ready the page skeleton.arrange laid out for the register.
|
draw |
Add the register to section 9's head and the line of findings to
|
Classes
FactDraft
dataclass
FactDraft(text: str, source: str, run: str = '', channel: str = '', band: tuple[float, float] | None = None)
A fact offered for the author to accept, edit or discard.
text is the draft. source says where it came from, shown beside
it in the editor. run, channel and band are what the draft
concerns; on acceptance they fill the finding's own fields where
the author has left them blank.
Heads
dataclass
Heads(summary: dict[str, Any] | None, deviations: dict[str, Any] | None, summary_html: str, deviations_html: str, offered_html: str = '')
The skeleton heads the register draws into (prepare) and what
it adds to each once they are drawn (draw).
Functions:
register
register(report: Any) -> list[dict[str, Any]]
The findings entered on report, each checked
(core.findings.clean), by kind and then number. Empty when none
are.
Source code in src/visualdynamics/report/findings.py
| def register(report: Any) -> list[dict[str, Any]]:
"""The findings entered on `report`, each checked
(`core.findings.clean`), by kind and then number. Empty when none
are."""
block = _block(report)
return core.read(block.get('findings') if block is not None else None)
|
find
find(report: Any, number: str) -> dict[str, Any]
The finding numbered number; ValueError naming the numbers
entered when there is none.
Source code in src/visualdynamics/report/findings.py
| def find(report: Any, number: str) -> dict[str, Any]:
"""The finding numbered `number`; `ValueError` naming the numbers
entered when there is none."""
core.number(number)
for entry in register(report):
if entry['number'] == number:
return entry
taken = ', '.join(entry['number'] for entry in register(report))
raise ValueError(f'no finding {number}; entered: {taken or "none"}')
|
add
add(report: Any, kind: str, *, number: str | None = None, **fields: Any) -> str
Enter a finding of kind and return its number.
The number is the next of its kind (core.findings.next_number)
unless number is given, which must be of that kind and not yet
taken. fields are any of EDITABLE. Entering a finding of a
kind the author had said there were none of takes that kind's
none sentence out of the section's words: the finding is the later
statement.
Raises:
| Type |
Description |
ValueError
|
When the report does not have the feature enabled, for an
unknown kind, a number of another kind or already taken, an
unknown field, or a field core.findings.clean refuses.
Nothing is changed.
|
TypeError
|
When a text field is not a string.
|
Source code in src/visualdynamics/report/findings.py
| def add(report: Any, kind: str, *, number: str | None = None,
**fields: Any) -> str:
"""Enter a finding of `kind` and return its number.
The number is the next of its kind (`core.findings.next_number`)
unless `number` is given, which must be of that kind and not yet
taken. `fields` are any of `EDITABLE`. Entering a finding of a
kind the author had said there were none of takes that kind's
none sentence out of the section's words: the finding is the later
statement.
Raises
------
ValueError
When the report does not have the feature enabled, for an
unknown kind, a number of another kind or already taken, an
unknown field, or a field `core.findings.clean` refuses.
Nothing is changed.
TypeError
When a text field is not a string.
"""
_require(report)
core.kind_of(kind)
entries = register(report)
if number is None:
number = core.next_number(entries, kind, _issued(report))
else:
given, _n = core.number(number)
if given != kind:
raise ValueError(f'{number} is a {given} number, not a {kind}')
if any(entry['number'] == number for entry in entries):
raise ValueError(f'{number} is already entered')
if _n <= _issued(report).get(core.PREFIX[kind], 0):
raise ValueError(f'{number} was issued before and removed; a '
'number is not given to a second finding')
entry = core.clean({'number': number, 'kind': kind, **_fields(fields)})
words = _words(report)
_store(report, [*entries, entry])
if kind in core.declared(words):
skeleton.write(report, SECTION, core.withdraw(words, [kind]))
return number
|
edit
edit(report: Any, number: str, **fields: Any) -> None
Change the fields given of finding number (any of EDITABLE;
a field given as None is left alone, and an empty band clears the
band). Refused as add refuses, before anything changes; a
finding's kind is its number's and is not edited (enter the
finding again under the other kind, as a report does when an
anomaly is found to be a nonconformance).
Source code in src/visualdynamics/report/findings.py
| def edit(report: Any, number: str, **fields: Any) -> None:
"""Change the fields given of finding `number` (any of `EDITABLE`;
a field given as None is left alone, and an empty band clears the
band). Refused as `add` refuses, before anything changes; a
finding's kind is its number's and is not edited (enter the
finding again under the other kind, as a report does when an
anomaly is found to be a nonconformance)."""
_require(report)
entry = {**find(report, number), **_fields(fields)}
entry = core.clean(entry)
_store(report, [entry if other['number'] == number else other
for other in register(report)])
|
remove
remove(report: Any, number: str) -> None
Take finding number out of the register. Its number is not
given again (core.findings.next_number).
Source code in src/visualdynamics/report/findings.py
| def remove(report: Any, number: str) -> None:
"""Take finding `number` out of the register. Its number is not
given again (`core.findings.next_number`)."""
_require(report)
find(report, number)
_store(report, [entry for entry in register(report)
if entry['number'] != number])
|
none
The deviations section's one-step None, and the words it wrote.
With nothing entered, it is the skeleton's None exactly: the
section says "No deviations. No anomalies. No nonconformances.".
With findings entered, it says "none" only of each kind that has
nothing entered, keeping the rest of the section's words.
Raises ValueError when the feature is off, and when every kind
has a finding entered, so there is no kind to say "none" of.
Source code in src/visualdynamics/report/findings.py
| def none(report: Any) -> str:
"""The deviations section's one-step None, and the words it wrote.
With nothing entered, it is the skeleton's None exactly: the
section says "No deviations. No anomalies. No nonconformances.".
With findings entered, it says "none" only of each kind that has
nothing entered, keeping the rest of the section's words.
Raises `ValueError` when the feature is off, and when every kind
has a finding entered, so there is no kind to say "none" of.
"""
_require(report)
entries = register(report)
if not entries:
index = skeleton.write(report, SECTION, none=True)
return str(report.blocks[index]['text'])
entered = {entry['kind'] for entry in entries}
empty = [kind for kind in core.KINDS if kind not in entered]
if not empty:
raise ValueError(f'{skeleton.BY_KEY[SECTION].heading}: every kind has '
'a finding entered, so there is none to set to None')
kept = core.withdraw(_words(report), core.KINDS)
text = (f'{kept}\n\n' if kept else '') + core.declaration(empty)
skeleton.write(report, SECTION, text)
return text
|
offer
offer(report: Any, number: str, draft: FactDraft) -> None
Offer draft as the fact of finding number, for the author to
accept, edit or discard; it replaces any draft already offered.
This is the one way a fact draft reaches a finding, whatever made
it: draft_from_comparison reads one off a measurement, and any
later source (a run log's entry, say) builds a FactDraft and
offers it here. A draft is not drawn on the page and fills nothing
until it is accepted.
Source code in src/visualdynamics/report/findings.py
| def offer(report: Any, number: str, draft: FactDraft) -> None:
"""Offer `draft` as the fact of finding `number`, for the author to
accept, edit or discard; it replaces any draft already offered.
This is the one way a fact draft reaches a finding, whatever made
it: `draft_from_comparison` reads one off a measurement, and any
later source (a run log's entry, say) builds a `FactDraft` and
offers it here. A draft is not drawn on the page and fills nothing
until it is accepted.
"""
_require(report)
if not isinstance(draft, FactDraft):
raise TypeError(f'a fact draft is a FactDraft, not '
f'{type(draft).__name__}')
stored = asdict(draft)
stored['band'] = list(draft.band) if draft.band is not None else None
entry = core.clean({**find(report, number), 'draft': stored})
_store(report, [entry if other['number'] == number else other
for other in register(report)])
|
accept
accept(report: Any, number: str, text: str | None = None) -> None
Make the draft offered on finding number its fact: as offered,
or as the author edited it (text). The draft's run, channel and
band fill the finding's own where the author left them blank, and
never replace what the author entered. Refused when no draft is
waiting, or when the text accepted is blank.
Source code in src/visualdynamics/report/findings.py
| def accept(report: Any, number: str, text: str | None = None) -> None:
"""Make the draft offered on finding `number` its fact: as offered,
or as the author edited it (`text`). The draft's run, channel and
band fill the finding's own where the author left them blank, and
never replace what the author entered. Refused when no draft is
waiting, or when the text accepted is blank."""
_require(report)
entry = find(report, number)
draft = entry['draft']
if draft is None:
raise ValueError(f'{number} has no fact draft waiting to be accepted')
fact = draft['text'] if text is None else core.clean(
{'number': number, 'fact': text})['fact']
if not fact:
raise ValueError(f'{number}: a blank fact is not accepted; discard '
'the draft instead')
entry = {**entry, 'fact': fact, 'draft': None}
for key in ('run', 'channel', 'band'):
if not entry[key] and draft[key]:
entry[key] = draft[key]
_store(report, [core.clean(entry) if other['number'] == number else other
for other in register(report)])
|
discard
discard(report: Any, number: str) -> None
Drop the draft offered on finding number; refused when none is
waiting.
Source code in src/visualdynamics/report/findings.py
| def discard(report: Any, number: str) -> None:
"""Drop the draft offered on finding `number`; refused when none is
waiting."""
_require(report)
entry = find(report, number)
if entry['draft'] is None:
raise ValueError(f'{number} has no fact draft waiting to be discarded')
_store(report, [{**entry, 'draft': None} if other['number'] == number
else other for other in register(report)])
|
log_drafts
log_drafts(report: Any) -> list[dict[str, Any]]
The findings a run log offers report and the author has not
accepted or discarded, each {'number', 'kind', 'fact', 'source',
'run'}, in the order offered. The number is the log's, proposed;
none of them is in the register.
Source code in src/visualdynamics/report/findings.py
| def log_drafts(report: Any) -> list[dict[str, Any]]:
"""The findings a run log offers `report` and the author has not
accepted or discarded, each {'number', 'kind', 'fact', 'source',
'run'}, in the order offered. The number is the log's, proposed;
none of them is in the register."""
block = _block(report)
held = block.get(LOG_DRAFTS) if block is not None else None
return [dict(draft) for draft in held or ()]
|
offer_log
offer_log(report: Any, extract: Any) -> list[str]
Offer the deviations and anomalies a run log extract holds as
drafts, and return a line on each.
Each is offered with the log's number, proposed, its kind and its
fact as typed at the console, its source citing the log, test and
entry (with the extract's badge when the log has newer entries). A
draft is kept beside the register, never in it: the register and
the deviations section do not change, the section stays owed, and
the editing page shows the drafts marked FROM_LOG until the author
accepts one (accept_log, which enters the finding) or discards it
(discard_log). A new offer replaces the drafts waiting. Not
offered, each with its line: a finding whose number the register
holds with its fact written, and a finding with no fact logged. The
cause, the effect and the approval or disposition are never offered,
whatever the log holds.
A log holding no deviation or anomaly offers nothing; its line says
so, and leaves setting the section to None (none) to the author.
Raises ValueError when the feature is off.
Source code in src/visualdynamics/report/findings.py
| def offer_log(report: Any, extract: Any) -> list[str]:
"""Offer the deviations and anomalies a run log extract holds as
drafts, and return a line on each.
Each is offered with the log's number, proposed, its kind and its
fact as typed at the console, its source citing the log, test and
entry (with the extract's badge when the log has newer entries). A
draft is kept beside the register, never in it: the register and
the deviations section do not change, the section stays owed, and
the editing page shows the drafts marked `FROM_LOG` until the author
accepts one (`accept_log`, which enters the finding) or discards it
(`discard_log`). A new offer replaces the drafts waiting. Not
offered, each with its line: a finding whose number the register
holds with its fact written, and a finding with no fact logged. The
cause, the effect and the approval or disposition are never offered,
whatever the log holds.
A log holding no deviation or anomaly offers nothing; its line says
so, and leaves setting the section to None (`none`) to the author.
Raises `ValueError` when the feature is off.
"""
_require(report)
found = extract.findings()
if not found:
_keep_drafts(report, [])
return [(f'The run log holds no deviation or anomaly of test '
f'{extract.test} by entry #{extract.upto}; nothing is '
"offered, and whether there were none is the author's to "
'say (set the section to None).')]
cited = extract.cited()
written = {entry['number'] for entry in register(report) if entry['fact']}
drafts, lines = [], []
for finding in found:
number = finding.number
fact = (finding.fact or '').strip()
if number in written:
lines.append(f"{number}: the register holds it with its fact "
"written; the log's is not offered over it.")
continue
if not fact:
lines.append(f'{number}: the log holds no fact for it, so '
'nothing is offered; enter it in the register.')
continue
run = finding.entry.run
drafts.append({'number': number, 'kind': finding.kind, 'fact': fact,
'source': f'{cited}, entry #{finding.entry.id}',
'run': str(run) if run is not None else ''})
lines.append(f'{number}: offered as a draft from the run log, not '
'in the register until accepted.')
_keep_drafts(report, drafts)
return lines
|
accept_log
accept_log(report: Any, number: str, text: str | None = None) -> str
Accept the finding the run log offered as number, and return the
number it is entered under.
The finding is entered in the register then, and not before: under
the log's number when the register has not given that number, and
otherwise under the next number of its kind (add); a finding the
register already holds under that number with its fact blank takes
the fact instead. Its fact, as offered or as the author edited it
(text), reaches it through the register's one entry point for
drafts (offer) and is accepted (accept); its run fills the
finding's where blank. Its cause, effect and approval or disposition
stay owed. The draft is no longer offered afterwards.
Raises ValueError when the feature is off, no draft is offered as
number, or the fact accepted is blank; nothing is changed.
Source code in src/visualdynamics/report/findings.py
| def accept_log(report: Any, number: str, text: str | None = None) -> str:
"""Accept the finding the run log offered as `number`, and return the
number it is entered under.
The finding is entered in the register then, and not before: under
the log's number when the register has not given that number, and
otherwise under the next number of its kind (`add`); a finding the
register already holds under that number with its fact blank takes
the fact instead. Its fact, as offered or as the author edited it
(`text`), reaches it through the register's one entry point for
drafts (`offer`) and is accepted (`accept`); its run fills the
finding's where blank. Its cause, effect and approval or disposition
stay owed. The draft is no longer offered afterwards.
Raises `ValueError` when the feature is off, no draft is offered as
`number`, or the fact accepted is blank; nothing is changed.
"""
_require(report)
draft = _log_draft(report, number)
kind = draft['kind']
if text is not None and not core.clean({'number': number,
'fact': text})['fact']:
raise ValueError(f'{number}: a blank fact is not accepted; discard '
'the draft instead')
held = next((entry for entry in register(report)
if entry['number'] == number), None)
_n = core.number(number)[1]
if held is not None and not held['fact']:
entered = number
elif held is None and _n > _issued(report).get(core.PREFIX[kind], 0):
entered = add(report, kind, number=number)
else:
entered = add(report, kind)
offer(report, entered, FactDraft(text=draft['fact'],
source=draft['source'],
run=draft['run']))
accept(report, entered, text)
_keep_drafts(report, [other for other in log_drafts(report)
if other['number'] != number])
return entered
|
discard_log
discard_log(report: Any, number: str) -> None
Drop the finding the run log offered as number; nothing enters
the register. Refused when no draft is offered as number.
Source code in src/visualdynamics/report/findings.py
| def discard_log(report: Any, number: str) -> None:
"""Drop the finding the run log offered as `number`; nothing enters
the register. Refused when no draft is offered as `number`."""
_require(report)
_log_draft(report, number)
_keep_drafts(report, [other for other in log_drafts(report)
if other['number'] != number])
|
fact_text
fact_text(channel: str, reading: ExcursionReading, pair: str, record: str, band: Sequence[float] | None = None) -> str
The fact a reading supports, in words: the channel, each stretch
above the limit and then each below (a side with more than LISTED
said once, with its count, its reach and its worst stretch), the
record, and what was not judged. Refused when the reading holds no stretch: a
reading inside its limits is not a fact about a finding.
Source code in src/visualdynamics/report/findings.py
| def fact_text(channel: str, reading: core.ExcursionReading, pair: str,
record: str, band: Sequence[float] | None = None) -> str:
"""The fact a reading supports, in words: the channel, each stretch
above the limit and then each below (a side with more than `LISTED`
said once, with its count, its reach and its worst stretch), the
record, and what was not judged. Refused when the reading holds no stretch: a
reading inside its limits is not a fact about a finding."""
found = reading.excursions
if not found:
raise ValueError(f'{channel} is inside its {pair} limits wherever it '
f'was judged, {_hz(reading.low)} to '
f'{_hz(reading.high)} Hz; there is no excursion to '
'draft a fact from')
parts = []
for side in ('above', 'below'):
mine = [excursion for excursion in found if excursion.side == side]
if len(mine) <= LISTED:
parts.extend(_stretch(excursion, pair) for excursion in mine)
continue
worst = max(mine, key=lambda excursion: excursion.worst_db)
parts.append(
f'{side} its {pair} limit in {len(mine)} stretches from '
f'{_hz(mine[0].low)} to {_hz(mine[-1].high)} Hz, '
f'{_lines(sum(e.lines for e in mine))} in all, '
f'{_size(worst.worst_db)} ({_reach(worst.low, worst.high)})')
text = f'{channel} ' + '; '.join(parts) + f'; {record}'
if reading.scale_db:
text += f'; compared at {reading.scale_db:+g} dB'
if band is not None and (reading.low > band[0] or reading.high < band[1]):
text += (f'; judged from {_hz(reading.low)} to {_hz(reading.high)} Hz '
f'of the {_hz(band[0])} to {_hz(band[1])} Hz asked')
if reading.unjudged:
text += (f'; {_lines(reading.unjudged)} with no value, not '
'judged')
return text + '.'
|
fact_from_comparison
fact_from_comparison(objects: Mapping[str, Any], measured: str, specification: str, channel: str, *, band: Sequence[float] | None = None, pair: str = 'abort', provenance: Mapping[str, Mapping[str, Any]] | None = None) -> FactDraft
A fact draft read off a comparison the project holds: measured
(a spectrum, by name) against specification (by name), on
channel, over band if given, against the pair of limits.
The reading is core.findings.excursions, on the same cells and at
the same scale as the comparison figure. The record is named, and
the time history it was computed from where provenance (the
project's) says; that time history is offered as the run.
Raises:
| Type |
Description |
ValueError
|
A name that is not an object of the project or not of the
right type, a channel the two do not share, anything
excursions refuses, and a channel inside its limits wherever
it was judged, since that is no fact about a finding.
|
Source code in src/visualdynamics/report/findings.py
| def fact_from_comparison(objects: Mapping[str, Any], measured: str,
specification: str, channel: str, *,
band: Sequence[float] | None = None,
pair: str = 'abort',
provenance: Mapping[str, Mapping[str, Any]]
| None = None) -> FactDraft:
"""A fact draft read off a comparison the project holds: `measured`
(a spectrum, by name) against `specification` (by name), on
`channel`, over `band` if given, against the `pair` of limits.
The reading is `core.findings.excursions`, on the same cells and at
the same scale as the comparison figure. The record is named, and
the time history it was computed from where `provenance` (the
project's) says; that time history is offered as the run.
Raises
------
ValueError
A name that is not an object of the project or not of the
right type, a channel the two do not share, anything
`excursions` refuses, and a channel inside its limits wherever
it was judged, since that is no fact about a finding.
"""
from ..core.data import DataArray, Specification
def named(name, wanted, what):
obj = objects.get(name) if isinstance(name, str) else None
if obj is None:
raise ValueError(f'{name!r} is not an object of the project')
if not isinstance(obj, wanted) or (wanted is DataArray and
isinstance(obj, Specification)):
raise ValueError(f'{name!r} is a {type(obj).__name__}, not '
f'{what}')
return obj
spec = named(specification, Specification, 'a specification')
held = named(measured, DataArray, 'a measured spectrum')
band = core.band_of(band)
spec_record, held_record = core.pair_records(spec, held, channel)
reading = core.excursions(spec, held, spec_record, held_record,
pair=pair, band=band)
source = ((provenance or {}).get(measured) or {}).get('source')
kind = 'PSD' if type(held).__name__ == 'Psd' else type(held).__name__
record = (f'{kind} {measured!r}, from {source!r}' if source
else f'{kind} {measured!r}')
text = fact_text(channel, reading, pair, record, band)
stretches = reading.excursions
return FactDraft(
text=text,
source=(f'{measured} against {specification}, {channel}, '
f'{pair} limits'),
run=str(source or measured), channel=channel,
band=(stretches[0].low, stretches[-1].high))
|
draft_from_comparison
draft_from_comparison(report: Any, number: str, objects: Mapping[str, Any], measured: str, specification: str, channel: str | None = None, *, band: Sequence[float] | None = None, pair: str = 'abort', provenance: Mapping[str, Mapping[str, Any]] | None = None) -> FactDraft
Read a fact draft off a comparison (fact_from_comparison) and
offer it on finding number. The channel and band default to the
finding's own. Returns the draft. Refused, changing nothing, as
those two refuse, and when no channel is given or entered.
Source code in src/visualdynamics/report/findings.py
| def draft_from_comparison(report: Any, number: str,
objects: Mapping[str, Any], measured: str,
specification: str, channel: str | None = None,
*, band: Sequence[float] | None = None,
pair: str = 'abort',
provenance: Mapping[str, Mapping[str, Any]]
| None = None) -> FactDraft:
"""Read a fact draft off a comparison (`fact_from_comparison`) and
`offer` it on finding `number`. The channel and band default to the
finding's own. Returns the draft. Refused, changing nothing, as
those two refuse, and when no channel is given or entered."""
_require(report)
entry = find(report, number)
channel = channel if channel is not None else entry['channel']
if not channel:
raise ValueError(f'{number}: name the channel to read the fact off')
if band is None:
band = entry['band']
draft = fact_from_comparison(objects, measured, specification, channel,
band=band, pair=pair, provenance=provenance)
offer(report, number, draft)
return draft
|
check
check(report: Any, owed: Sequence[Owed]) -> tuple[Owed, ...]
The skeleton's owed list with section 9 read off the register.
With the feature off, owed as it is. With it on, the section is
owed, as the skeleton says, while nothing is entered and nothing
written; otherwise each of these is listed under it: a kind with
nothing entered that the author has not said is none, a kind both
entered and said to be none, and each finding's blank parts.
Source code in src/visualdynamics/report/findings.py
| def check(report: Any, owed: Sequence[skeleton.Owed]) -> tuple[skeleton.Owed,
...]:
"""The skeleton's `owed` list with section 9 read off the register.
With the feature off, `owed` as it is. With it on, the section is
owed, as the skeleton says, while nothing is entered and nothing
written; otherwise each of these is listed under it: a kind with
nothing entered that the author has not said is none, a kind both
entered and said to be none, and each finding's blank parts.
"""
from .features import enabled
if FEATURE not in enabled(report):
return tuple(owed)
found = skeleton.BY_KEY[SECTION]
entries = register(report)
words = skeleton.words(report, SECTION)
waiting = log_drafts(report)
# drafts from a run log are not findings: the section is owed as it
# was, and the check says what waits to be accepted
numbers = ', '.join(draft['number'] for draft in waiting)
extra = ([skeleton.Owed(
SECTION, found.number, found.title,
f'{numbers} offered from the run log, not yet accepted')]
if waiting else [])
if not entries and not words:
return (tuple(sorted([*owed, *extra], key=lambda item: item.number))
if extra else tuple(owed))
said = core.declared(words)
reasons = []
for kind in core.KINDS:
mine = [entry for entry in entries if entry['kind'] == kind]
plural = core.PLURAL[kind].lower()
if not mine and kind not in said:
reasons.append(f'{plural} neither entered nor set to None')
elif mine and kind in said:
reasons.append(f'{plural} entered and also said to be none')
for entry in entries:
missing = [core.FOURTH[entry['kind']].lower() if part == 'approval'
else part for part in core.owed(entry)]
if missing:
reasons.append(f'{entry["number"]}: {", ".join(missing)} not yet '
'written' + (f' ({DRAFTED})' if 'fact' in missing
and entry['draft'] is not None
else ''))
rest = [item for item in owed if item.key != SECTION]
mine = [skeleton.Owed(SECTION, found.number, found.title, reason)
for reason in reasons]
return tuple(sorted([*rest, *mine, *extra],
key=lambda item: item.number))
|
finding_html
finding_html(entry: Mapping[str, Any]) -> str
One finding as the page draws it: its number and what it
concerns, then its four labeled parts, each blank one owed.
Source code in src/visualdynamics/report/findings.py
| def finding_html(entry: Mapping[str, Any]) -> str:
"""One finding as the page draws it: its number and what it
concerns, then its four labeled parts, each blank one owed."""
where = _where(entry)
head = (f'<p class="finding-head"><strong>'
f'{html.escape(entry["number"])}.</strong> '
f'{entry["kind"].capitalize()}'
+ (f' <span class="finding-where">({html.escape(where)})</span>'
if where else '') + '</p>')
parts = []
for part in core.PARTS:
label = (core.FOURTH[entry['kind']] if part == 'approval'
else part.capitalize())
said = entry[part]
shown = _escape(said) if said else _owed_part()
if part == 'fact' and not said and entry['draft'] is not None:
# a draft is never the fact until accepted: the part stays
# owed, and says a drafted fact is waiting
shown += f' <span class="finding-drafted">{DRAFTED}</span>'
parts.append(f'<p class="finding-part"><strong>{label}:</strong> '
f'{shown}</p>')
return f'<div class="finding">{head}{"".join(parts)}</div>'
|
register_html
register_html(entries: Sequence[Mapping[str, Any]], said: set[str]) -> str
The register as section 9 draws it: each kind in turn, its
findings in number order, or its none sentence where the author
said so, or an owed line where neither.
Source code in src/visualdynamics/report/findings.py
| def register_html(entries: Sequence[Mapping[str, Any]], said: set[str]) -> str:
"""The register as section 9 draws it: each kind in turn, its
findings in number order, or its none sentence where the author
said so, or an owed line where neither."""
out = []
for kind in core.KINDS:
mine = [entry for entry in entries if entry['kind'] == kind]
if mine:
out.extend(finding_html(entry) for entry in mine)
elif kind in said:
out.append(f'<p class="finding-none">'
f'{html.escape(core.NONE_SENTENCES[kind])}</p>')
else:
out.append(f'<p class="finding-none">{_owed_part()} '
f'{core.PLURAL[kind]}: enter each in its four parts, '
'or set None.</p>')
return ''.join(out)
|
summary_html
summary_html(entries: Sequence[Mapping[str, Any]], said: set[str]) -> str
The Summary's line of findings by number: "Deviations: D-1, D-2.
Anomalies: A-1 (closed). Nonconformances: none." A kind the author
has neither entered nor said is none is owed.
Source code in src/visualdynamics/report/findings.py
| def summary_html(entries: Sequence[Mapping[str, Any]], said: set[str]) -> str:
"""The Summary's line of findings by number: "Deviations: D-1, D-2.
Anomalies: A-1 (closed). Nonconformances: none." A kind the author
has neither entered nor said is none is owed."""
out = []
for kind in core.KINDS:
mine = [entry for entry in entries if entry['kind'] == kind]
if mine:
listed = ', '.join(
html.escape(entry['number'])
+ (f' ({entry["status"]})' if entry['status'] else '')
for entry in mine)
elif kind in said:
listed = 'none'
else:
listed = '<span class="owed">not yet written</span>'
out.append(f'{core.PLURAL[kind]}: {listed}.')
return f'<p class="findings-summary">{" ".join(out)}</p>'
|
offered_html
offered_html(drafts: Sequence[Mapping[str, Any]]) -> str
The findings a run log offers, as the editing page draws them under
section 9: marked FROM_LOG, each with its proposed number, its kind,
where it came from and its fact. '' when none is offered.
Source code in src/visualdynamics/report/findings.py
| def offered_html(drafts: Sequence[Mapping[str, Any]]) -> str:
"""The findings a run log offers, as the editing page draws them under
section 9: marked `FROM_LOG`, each with its proposed number, its kind,
where it came from and its fact. '' when none is offered."""
if not drafts:
return ''
out = [(f'<p class="finding-head"><span class="finding-drafted">'
f'{FROM_LOG}</span></p>')]
for draft in drafts:
out.append(f'<p class="finding-part"><strong>Proposed '
f'{html.escape(draft["number"])}.</strong> '
f'{draft["kind"].capitalize()} <span class="finding-where">'
f'({html.escape(draft["source"])})</span>: '
f'{_escape(draft["fact"])}</p>')
return f'<div class="finding">{"".join(out)}</div>'
|
prepare
prepare(blocks: Sequence[dict[str, Any]], report: Any, edit: bool = False) -> Heads
Ready the page skeleton.arrange laid out for the register.
Section 9's head keeps the skeleton's owed card while nothing is
entered and nothing written. Otherwise the register fills the
section: its head shows the heading and the author's other words,
the none sentences being drawn in their kinds' places by draw.
Call before the text is drawn as HTML, and draw after
skeleton.finish. With edit, the findings a run log offers
(log_drafts) are drawn under section 9 as well, marked as drafted;
they are never on the reading page.
Source code in src/visualdynamics/report/findings.py
| def prepare(blocks: Sequence[dict[str, Any]], report: Any,
edit: bool = False) -> Heads:
"""Ready the page `skeleton.arrange` laid out for the register.
Section 9's head keeps the skeleton's owed card while nothing is
entered and nothing written. Otherwise the register fills the
section: its head shows the heading and the author's other words,
the none sentences being drawn in their kinds' places by `draw`.
Call before the text is drawn as HTML, and `draw` after
`skeleton.finish`. With `edit`, the findings a run log offers
(`log_drafts`) are drawn under section 9 as well, marked as drafted;
they are never on the reading page.
"""
def head(key):
return next((block for block in blocks
if (block.get('skeleton') or {}).get('key') == key), None)
entries = register(report)
summary, deviations = head('summary'), head(SECTION)
words = str(deviations.get('text', '')) if deviations is not None else ''
said = core.declared(words)
entered = {entry['kind'] for entry in entries}
filled = ''
if deviations is not None and (entries or words.strip()):
# a none sentence of a kind with findings stays where the author
# put it, so the contradiction is seen; the others are drawn in
# their kinds' places
deviations['text'] = core.withdraw(words, said - entered)
deviations['skeleton']['state'] = 'written'
filled = register_html(entries, said)
return Heads(summary, deviations, summary_html(entries, said), filled,
offered_html(log_drafts(report)) if edit else '')
|
draw
draw(heads: Heads) -> None
Add the register to section 9's head and the line of findings to
the Summary's, once skeleton.finish has drawn them.
Source code in src/visualdynamics/report/findings.py
| def draw(heads: Heads) -> None:
"""Add the register to section 9's head and the line of findings to
the Summary's, once `skeleton.finish` has drawn them."""
if heads.summary is not None:
heads.summary['html'] = heads.summary.get('html', '') \
+ heads.summary_html
if heads.deviations is not None and heads.deviations_html:
heads.deviations['html'] = heads.deviations.get('html', '') \
+ heads.deviations_html
if heads.deviations is not None and heads.offered_html:
heads.deviations['html'] = heads.deviations.get('html', '') \
+ heads.offered_html
|