{#
The document says what produced it, beside what it is of. "VNtyper 2.0.21" was
in a provenance panel and is now at the foot of the document with the rest of
the run's provenance - which is the right place for the run's *settings* and
the wrong one for the name of the tool, because a reader who has been handed
this file, or found it in a folder years later, needs to know what made it
before they need to know its schema version.
It is a byline, not a second heading: smaller than the sample name, in the
muted ink, on its own line. The version is monospaced because it is an exact
string somebody will quote in a methods section.
#}
{# The gene symbol in italics, as HGNC has it. `` and not ``: this is a
typographic convention for a technical term, not emphasis, which is exactly
the distinction the element exists for. The symbol and the rest of the title
are two autoescaped values rather than one string with markup in it. #}
{#
THE MASTHEAD. Scan order is who this is about, what state the pipeline
computed, then the sentence the configuration wrote for that state.
`data-state` drives emphasis and nothing else. None of the three values
it takes - `finding`, `no-finding`, `indeterminate` - is ever rendered as
text: they are colour selectors, not a verdict vocabulary, and
`tests/unit/test_generate_report.py` asserts that no report prints one.
Every word here is either the pipeline's own or the configuration's.
#}
{#
One `
` per label/value pair, so a pair is one flex item and can
never be split across a line break. A value the run did not record is
marked as absent rather than drawn like a fact.
#}
Sample
{{ sample_name }}
Assay
{{ assay_name }}
Assembly
{{- assembly_declared }}
Region
{{- region_resolved }}
{#
The state, in words. The three algorithm chips are built in
`screening_summary.state_chips` - a chip is the most compressed thing
this report says about a stage, so it is the one most easily misread
as a verdict, and choosing its wording is presentation logic that
belongs in the pure module (AGENTS.md trap 11).
The last two are here because they read booleans the computed state
carries: coverage QC has a displayed verdict *and* a screening axis,
and they are not the same fact - a run with no coverage step at all
passes the axis while its verdict says it was never evaluated. The
rule chip appears only when the configuration had no message for the
computed state, which is what tells the reader that the sentence
below is a fallback rather than the one written for this state.
#}
{#
THE VERDICT AND THE STATE, SIDE BY SIDE.
Prose wants a measure and a masthead is 1,132px wide, so a column of
sentences capped at 44 and 68 characters left half the panel empty while
the chips sat in a row above it using a third of theirs. Neither is a
layout: they are two stacks that happened to be written in that order.
The sentence keeps its measure - that is what makes it readable - and the
chips take the width beside it, where a reader scanning for state finds
them on one edge instead of reading across four boxes. Below 60rem the
two become one column again and the original order returns.
#}
{#
The configured message, as the ordered parts it was authored in.
Every part is a separate autoescaped element: it used to be one
`summary_text` interpolation marked safe, and the only reason it
was marked safe was the line-break separators inside it - the
whole sentence was exempt from escaping to get a line break.
Nothing is reworded here; the split lives in `report_config.json`
beside the verbatim message, and `screening_summary.render_segments`
pins that the two still agree.
#}
{{ screening_segments[0] }}
{% for segment in screening_segments[1:] %}
{{ segment }}
{% endfor %}
{% if coverage_not_measured_note %}
{{ coverage_not_measured_note }}
{% endif %}
{#
WHAT THE RUN NAMED. The second question anybody opens this report with,
beside the first answer instead of in column eleven of a nineteen-column
table, 1,600px down the page and 700px off its right edge.
It is a repetition of rows that stay below it, never a replacement for
them: the tables remain the record. Where the rows carry more than one
distinct name, `variant_identity` returns every one of them and qualifies
none - choosing which name to show at the top would be the report deciding
which variant the reader gets, which is the defect this issue exists to
remove.
#}
{% if variant_identity %}
{{ variant_identity.name }}
{%- for other in variant_identity.other_names %}
{{ other }}
{%- endfor %}
{% if variant_identity.tier %}
{# The letter and the words for it, together and always. The
letter is what the tables print and what a consumer
matches on; the words are what make it mean anything to
somebody meeting it for the first time. The full
definition is in the reading key under the tables and in
this element's `title`, so the hover is a shortcut to
something already printed rather than the only route. #}
{# The badge carries both call-specific sentences as its
hover text: they explain something already stated, and
four lines of prose under a two-character badge is the
badge losing an argument with its own footnote. Both are
printed by the rule below, because the archived PDF is
what gets filed and a reader with no pointer has no
hover - a hover here is a shortcut, never the only
route. #}
Tier {{ variant_identity.tier }}{{ variant_identity.tier_label }}
{% endif %}
{% if variant_identity.ambiguity %}
Ambiguity interval
{{ variant_identity.ambiguity }}
{% endif %}
{% if variant_identity.repeat_form %}
Repeat form
{{ variant_identity.repeat_form }}
{% endif %}
{% if variant_identity.kestrel_name %}
Kestrel name
{{ variant_identity.kestrel_name }}
{% endif %}
{% if variant_identity.advntr_name %}
adVNTR name
{{ variant_identity.advntr_name }}
{% endif %}
{% if variant_identity.flags %}
Flags
{% for flag in variant_identity.flags %}
{{ flag.token }}
{% endfor %}
{% endif %}
{#
Which tier-A condition this call actually missed. "At least one
tier-A condition is unmet" is a definition, not an answer: the
report printed the letter, printed the flags, and left the reader
to know that `motif-context-diverges` is one of the five names
`reconcile` tests before it promotes a call. `tier_reason` states
the connection, and says nothing at all when the blocker left no
flag behind rather than guessing at one.
#}
{% if variant_identity.tier_reason %}
{{ variant_identity.tier_reason }}
{% endif %}
{% if variant_identity.note %}
{{ variant_identity.note }}
{% endif %}
{% endif %}
{% for chip in state_chips %}
{{ chip.label }}{{ chip.value }}
{% endfor %}
Coverage QC{{ coverage_qc_text }}
{% if not screening_state.matched_rule %}
Screening ruleNot configured
{% endif %}
{#
The two depth figures, beside the state they qualify.
"How confident should I be" is the third question this report is
opened with, and answering it meant holding a depth score from the
table and two coverage figures from a section 250px below it. They
are numbers, not states, so they carry no tone - a chip's colour
means a computed state here and a depth is not one. The caveat on
each is what the coverage section below still exists to state, and
the reason the two figures are labelled differently: the window
mean is not comparable between assemblies and the flank depth is.
#}
Mean coverage{{ mean_vntr_coverage }}
Flank depth{{ vntr_flank_mean_depth }}
{#
The provenance line: the raw state the message above was selected by,
spelled out in existing pipeline vocabulary so I2 is visible to a
reader, not only to a test.
Each algorithm's word comes from `screening_summary.algorithm_state_text`,
because the computed result is only a result when the stage produced
one: an absent or unreadable stage reduces to the block's `default`,
so printing `kestrel_result` here read "Kestrel: negative" for a stage
that never called anything.
#}
{#
THE NOTICES, AS ONE BLOCK.
Two things this report must say that are neither a result nor evidence:
what the artefact may be used for, and that the alignment under every
number in it could not be identified. They were a grey box inside the
masthead and a red box outside it, in two type sizes, with a gap between
them - two boxes, two treatments and about 150px for four lines of text.
One block, one treatment, one row each, distinguished by the marker and
the accent rather than by having a frame of its own. Both are always
readable; neither shouts over the result above them.
The warning is *first* and is the one that carries an accent: an alignment
the pipeline cannot vouch for changes how every number below it should be
read, which is a stronger claim than the standing use restriction. It used
to be the last line of the fourth provenance panel, 1,000px down the page,
where a reader who did not see the red was told nothing. `role="alert"`
puts it in front of a screen-reader user and "Warning:" carries the same
meaning in words, so neither depends on the colour.
#}
{# Each notice's text is one element, so the marker and the whole sentence
are two grid items rather than the label and its text landing on
separate rows. #}
{% if header_warning %}
Warning: {{ header_warning }}
{% endif %}
{{ research_use_statement }}.
{#
THE EVIDENCE, FIRST. Until now the first variant table began at y=1580 of a
3142px document - 50% of the way down, 1.6 viewport-scrolls, behind 536px of
provenance panels and a coverage section. On a 390px phone it began at 60%.
It is what the reader came for, so it is the first thing under the answer.
Coverage, cross-match and the alignment browser follow it: each of them
qualifies these rows, and a qualifier is read after the thing it qualifies.
#}
Kestrel Identified Variants
{#
Four states, and they are four different facts (#242). A table of calls; a
run that genotyped and called nothing; a run whose Kestrel step is recorded
but produced no readable result (#212); and a summary with no Kestrel step in
it at all, which `vntyper report` can legitimately be handed.
The unreadable case is checked before the negative and says in words that it
is not one. A stage that failed has established nothing about the sample, so
a report that renders it as "no variant detected" states a negative the run
never reached - which is the defect this whole issue exists to remove, in the
one place a reader would never think to doubt it.
`escaped_table_html` returns "" for an empty frame, and that empty string is
what this branches on - `to_html` on one produces a headerless, bodyless
table that renders as a stray empty box under the heading.
The middle state was the commonest report in any cohort and it looked like a
crashed pipeline: `output_empty_result` writes one row of the literal string
"None" so that `kestrel_result.tsv` has a body, and the report tabulated it
as `None None None None None None None None NaN Negative` with a count line
above it calling it one Kestrel row.
The count line is counted in Python from the frame that was rendered, so it
cannot disagree with the table beneath it. DataTables' own "Showing 1 to 3 of
3 entries" footer is switched off: it exists only when the CDNs resolve, and
it counted the rows left *after* the filter had removed the others.
#}
{#
The switch that emphasises flagged values, beside the tables it emphasises.
It used to sit in a strip of controls between the provenance panels and the
coverage heading, 450px above the table it acts on and above a second switch
with an unrelated scope - a control nobody looking at a table would find, and
two controls grouped by nothing but having been written next to each other.
It changes emphasis and nothing else. This switch used to drive a DataTables
row-visibility predicate that removed every flagged row from the DOM until the
reader ticked it: a single-patient report that narrates a flagged pathogenic
variant and then renders it invisible is issue #242, and there is no code path
in this file that can hide a results row. (The cohort report keeps its filter;
hiding flagged rows is defensible for triage across samples.)
Both switches were `display: none` inputs with a Bootstrap `