{% from 'insights.html' import insight_card with context %} {% macro logo() -%} {%- endmacro %} {% macro icons() -%} {%- endmacro %} {% macro tool_search(id, label) -%} {%- endmacro %} {% macro tool_seg(id, label, options) -%}
{% for mode, text, n in options %}{% endfor %}
{%- endmacro %} {% macro table_head(label, kind='text', align=false, pill=false, filter=true) -%}
{{ label }}{% if filter %}{% endif %}
{%- endmacro %} {% macro column_name(column, dataset_id='') -%}{{ column.position }}{% if column_links.get((dataset_id, column.id)) %}{{ column.name or '(unnamed)' }} {% else %}{{ column.name or '(unnamed)' }}{% endif %}{%- endmacro %} {% macro percent_cell(value, count, color='var(--attn)', attention=false) -%} {% if value %}{{ value }}%{{ count | count }}{% else %}–{% endif %} {%- endmacro %} {% macro acc(kind) -%}{{ acc_words[kind] }}{%- endmacro %} {% macro status(word, cls, title) -%}{{ word }}{%- endmacro %} {% macro distinct_cell(column) -%} {% set d = column.distinct %}{% if d.kind == 'exact' %}{{ d.value | count }}{% if d.source == 'second_pass' %}2nd pass{% endif %}{% elif d.kind == 'at_least' %}≥ {{ d.value | count }}{% if d.estimate is not none %}≈ {{ d.estimate | round | int | count }} est.{% endif %}{% else %}limited{% endif %} {%- endmacro %} {% macro stage_cell(column, name) -%} {% set stage = column.normalization.stages | selectattr('stage', 'equalto', name) | first %}{% if stage.enabled %}{{ percent_cell(stage.changed_percent, stage.changed_count, 'var(--d2)') }}{% else %}off{% endif %} {%- endmacro %} {% macro key_distinct_cell(column) -%} {% set stage = column.normalization.stages | selectattr('enabled') | list | last %}{% if stage.distinct_status == 'complete' %}{{ stage.distinct_count | count }}{% elif stage.distinct_status == 'not_applicable' %}0{% else %}limited{% endif %} {%- endmacro %} {% macro variant_count_cell(column) -%} {% set n = column.normalization %}{% if n.variant_group_status == 'complete' %}{% if n.variant_group_count %}{{ n.variant_group_count | count }}{% else %}0{% endif %}{% elif n.variant_group_status == 'not_applicable' %}–{% else %}limited{% endif %} {%- endmacro %} {% macro value_examples(column, prefix='col', px='') -%} {% set ev = column.summary.values %}{% for value in ev.examples %}{{ value }}{% endfor %}{% if column.exposure in ['mask', 'hide'] %}{% elif column.distinct_count is none or column.distinct_count > ev.examples|length %}{% endif %} {%- endmacro %} {% macro variant_list(vd) -%} {% if vd.state == 'hidden' %}

Values hidden by the exposure setting of the column.

{% else %}{% if vd.state == 'masked' %}

Masked values: equal masks are merged.

{% endif %}
    {% for g in vd.groups %}
  1. {% for sp in g.spellings %}{{ sp.value }}×{{ sp.count | count }}{% endfor %}{% if g.more_spellings %}+{{ g.more_spellings | count }} more spellings{% endif %}
  2. {% endfor %}
{% if vd.more_groups %}

+{{ vd.more_groups | count }} more groups; the Transformations section lists them.

{% endif %}{% endif %} {%- endmacro %} {% macro variant_preview(vd) -%} {% if vd.state == 'hidden' %}{% else %}{% for sp in vd.groups[0].spellings[:2] %}{{ sp.value }}{% endfor %}{% if vd.total_groups is not none %}{{ vd.total_groups | count }} {{ 'group' if vd.total_groups == 1 else 'groups' }}{% else %}{{ vd.groups | length | count }}+ groups{% endif %}{% endif %} {%- endmacro %} {% macro detail_preview(fv) -%} {% for chip in fv.preview %}{{ chip }}{% if fv.preview_spoken %} ({{ fv.preview_spoken[loop.index0] }}){% endif %}{% endfor %}{% if fv.preview_figure is not none %}{{ fv.preview_figure | count }}{% endif %}{% if fv.preview_more %}{% if fv.preview_figure is not none %}, {% endif %}+{{ fv.preview_more | count }} more{% endif %} {%- endmacro %} {% macro detail_list(fv) -%} {% for note in fv.notes %}

{{ note }}

{% endfor %}{% for section in fv.sections %}

{{ section.title }}

{% for row in section.rows %}
{{ row.label }}{% if row.spoken %} ({{ row.spoken }}){% endif %}{% if row.values %}{% for value in row.values %}{{ value }}{% endfor %}{% endif %}{% if row.figure is not none %}({{ row.figure | count }}{% if row.note %}, {{ row.note }}{% endif %}){% endif %}
{% endfor %}
{% endfor %}{% for line in fv.footer %}

{{ line }}

{% endfor %} {%- endmacro %} {% macro kpi_columns(items, plural_name) -%} {%- endmacro %} {% macro pop_chevron() -%}{%- endmacro %} {% macro preview_body(p) -%}

{{ 'Error' if p.cls == 'error' else 'Issue' }}{{ p.title }}

{% if p.nature %}
What it is
{{ p.nature }}
{% endif %}
Counted in
{{ p.unit }}
{% if p.count_text %}
Count
{{ p.count_text }}
{% endif %}{% if p.population %}
Out of
{{ p.population }}
{% endif %}{% if p.share %}
Share
{{ p.share }}
{% endif %}
Accuracy
{{ p.accuracy }}: {{ p.accuracy_note }}
{% if p.examples %}
Examples
{% for value in p.examples %}{{ value }} {% endfor %}{% if p.examples_note %}{{ p.examples_note }}{% endif %}
{% elif p.examples_note %}
Examples
{{ p.examples_note }}
{% endif %}{% if p.limits %}
Limit
{% for limit in p.limits %}{{ limit }}{% endfor %}
{% endif %}
{%- endmacro %} {% macro check_list(checks) -%} {%- endmacro %} {% macro finding_cell(cell, kind, uid) -%} {% set noun = 'Error' if kind == 'error' else 'Issue' %}{% if cell.labels %}{% if cell.caveat %}{% set pid = uid ~ '-c' %}{% endif %}{% elif cell.checks %}{% set pid = uid ~ '-s' %}{% else %}{% if kind == 'error' and cell.state == 'not_applicable' %}{{ cell.state_label }}{% else %}{{ cell.state_label }}{% endif %}{% endif %} {%- endmacro %} {% macro details_cell(row, uid) -%} {% set d = row.details %}{% for item in d.examples %}{{ item.value }}{% endfor %}{% if d.state != 'shown' %}{% elif not d.exhaustive and d.note %}{% endif %} {%- endmacro %} {{ icons() }}
{% for v in views %}{% set ds = v.ds %}{% set table = v.table %}{% set finding_details = v.finding_details %}{% set finding_views = v.finding_views %}{% set px = v.px %}{% set numeric_columns = v.numeric_columns %}{% set date_columns = v.date_columns %}{% set string_columns = v.string_columns %}{% set detector_rows = v.detector_rows %}{% set variant_rows = v.variant_rows %}{% set has_limits = v.has_limits %}{% set pattern_columns = v.pattern_columns %}{% set pattern_rows = v.pattern_rows %}{% set pattern_missing = v.pattern_missing %}{% set pattern_grammar = v.pattern_grammar %}{% set period_columns = v.period_columns %}{% set sections = v.sections %}

Dataset report

{{ source_stem }}{{ source_suffix }}

  • {{ report.source.format | upper }}
  • {% if report.source.format in ('json', 'jsonl') %}
  • Dataset {{ ds.id }}
  • {{ ds.summary.row_count | count }} records
  • {{ ds.summary.column_count | count }} fields
  • {% else %}
  • {{ ds.summary.row_count | count }} rows
  • {{ ds.summary.column_count | count }} columns
  • {% endif %}
  • {{ report.source.size_bytes | size }}
  • {% if report.source.encoding %}
  • {{ report.source.encoding }}
  • {% endif %}{% if report.source.csv %}
  • Delimiter {{ report.source.csv.delimiter }}
  • {% endif %}
Analysis complete · {{ report.report.processing_seconds | seconds }} s

01 / Overview

Dataset overview

Size, completeness and the main signal to check first.

Rows
{{ ds.summary.row_count | count }}{% if ds.summary.empty_row_count %}{{ ds.summary.empty_row_count | count }} empty rows{% else %}No empty rows{% endif %}
Columns
{{ ds.summary.column_count | count }}{{ ds.summary.cell_count | count }} cells analyzed
Missing cells
{{ ds.summary.missing_percent }}%{{ ds.summary.missing_count | count }} cells
Duplicate rows
{% if ds.summary.duplicate_row_status == 'disabled' %}–Not checked{% elif ds.summary.duplicate_row_status == 'limited' %}≥ {{ ds.summary.duplicate_row_count | count }}At least, beyond the first occurrence{% else %}{{ ds.summary.duplicate_row_count | count }}Beyond the first occurrence{% endif %}
{% set qv = table.quality %}{% set sg = table.signals %}{% set dataset_notes = ds.quality.items | selectattr('field_name', 'none') | selectattr('observation.status', 'equalto', 'confirmed_issue') | list %}
Finding types · {{ qv.columns | count }} columns checked
{% for key, label, one, n, cols, items in [('error', 'Error types', 'Error type', qv.error_findings, qv.error_class_columns | length, qv.error_class_columns), ('issue', 'Issue types', 'Issue type', qv.issue_findings, qv.issue_class_columns | length, qv.issue_class_columns)] %}{% if n %}{% else %}
0{{ label }}0 of {{ qv.columns | count }} columns
{% endif %}{% endfor %}
{% if qv.top %}{% set fit = 8 %}

Affected columns

    {% for c in qv.top %}{% set n = c.errors + c.issues %} fit %} class="ov-x"{% endif %} title="{{ c.labels | join(' · ') }}">{{ c.name or '(unnamed)' }}{% if c.worst %}{{ signal_class_words[c.worst_cls] }} {{ c.worst }}{% if n > 1 %} · +{{ n - 1 }} other {{ 'type' if n == 2 else 'types' }}{% endif %}{% else %}{{ n }} {{ 'type' if n == 1 else 'types' }}{% endif %}{{ c.percent_text or '–' }}{% endfor %}
{% if qv.top | length > fit %}{% endif %}

Bar = largest finding share in the column

{% else %}

No Error and no Issue.

{{ qv.columns | count }} {{ 'column was' if qv.columns == 1 else 'columns were' }} checked and the checks that ran found nothing to report.

{% endif %}

Errors and Issues

What the checks found, by kind

{% if sg.shown or dataset_notes %}
    {% for s in sg.shown %}{% set pid = px ~ 'pop-s' ~ loop.index %}{% set nc = s.columns | length %} 8 %} class="ov-x"{% endif %}>{% endfor %}{% for item in dataset_notes %} 8 %} class="ov-x"{% endif %}>{{ item.title }}{{ item.observation.count | count }}Dataset · {{ item.noun }}{% endfor %}
{% if sg.more or sg.shown | length + dataset_notes | length > 8 %}{% endif %}{% else %}

No Error and no Issue was detected by the checks that ran.

{% endif %}{% if sg.incomplete_columns %}

{{ sg.incomplete_columns | count }} of {{ ds.summary.column_count | count }} columns were not completely checked.

{% endif %}

Column types

Classification: family, then its types

    {% for name, count in ds.summary.type_family_counts.items() %}
  • {{ family_labels[name] }}{{ count }}

      {% for key, kind_count in ds.summary.type_counts.items() if key.split('/')[0] == name %}
    • {{ type_labels[key] }} {{ kind_count }}
    • {% endfor %}
  • {% endfor %}

Technical type: what the values are made of

    {% for name, count in ds.summary.inferred_type_counts.items() %}
  • {{ name | replace('_',' ') | title }} {{ count }}
  • {% endfor %}
{% if 'mixed' in ds.summary.inferred_type_counts %}

Mixed: fewer than {{ (report.config.types.minimum_confidence * 100) | round(1) }}% of present values agree on one type.

{% endif %}

02 / Summary

Column summary({{ ds.summary.column_count }})

Type, overview and findings per column. Errors: values a rule proves invalid. Issues: ambiguities and deviations to inspect. Missing values are counted apart.{% if 'observations' not in hidden_sections %} Remarks are under Observations.{% endif %}

{{ tool_search(px ~ 'summary-table', 'columns') }}{{ tool_seg(px ~ 'summary-table', 'Show columns', [('all','All',ds.summary.column_count),('error','With errors',table.error_count),('issue','Quality issues',table.issue_count),('obs','With observations',table.observation_count)]) }}{{ ds.summary.column_count }} of {{ ds.summary.column_count }} columns
{{ table_head('Column','list') }}{{ table_head('Type family','list',false,true) }}{{ table_head('Type','list',false,true) }}{{ table_head('Overview',filter=false) }}{{ table_head('Missing','num',true) }}{{ table_head('Errors','list') }}{{ table_head('Issues','list') }}{{ table_head('Details',filter=false) }}{% for row in table.rows %}{{ percent_cell(row.missing.percent, row.missing.count) }}{{ finding_cell(row.errors, 'error', px ~ 'pv' ~ loop.index ~ 'e') }}{{ finding_cell(row.issues, 'issue', px ~ 'pv' ~ loop.index ~ 'i') }}{{ details_cell(row, px ~ loop.index) }}{% endfor %}
{{ row.position }}{{ row.name or '(unnamed)' }}{% if row.path != row.name %}{{ row.path }}{% endif %}{{ row.family_label }}{{ row.type_label }}{{ row.overview }}
{% if 'observations' not in hidden_sections %}

03 / Findings

Observations({{ ds.quality.items | length }})

What the scan found, by rule, from its facts and never from a guess. A candidate anomaly is worth a look, a remark may be perfectly valid. Counts are exact unless marked.

{% if not ds.quality.available %}

Not computed: the scan has no observations (scan.observations.enabled is false, or building them failed).

{% elif not ds.quality.items %}

No observations: the scan found nothing to report.

{% else %}{% set qc = ds.quality.counts %}
Confirmed issues
{{ qc.confirmed_issue | count }}Defects of the source
Candidate anomalies
{{ qc.candidate_anomaly | count }}Worth a look
Remarks
{{ qc.informational | count }}May be valid
Limits
{{ ds.quality.summary.limits | count if ds.quality.summary else '–' }}What the scan could not establish
{{ tool_search(px ~ 'observation-table', 'findings') }}{{ tool_seg(px ~ 'observation-table', 'Show findings', [('all','All',ds.quality.items | length),('issue','Issues',qc.confirmed_issue + qc.candidate_anomaly),('remark','Remarks',qc.informational)]) }}{{ ds.quality.items | length }} of {{ ds.quality.items | length }} findings
{{ table_head('Status','list',false,true) }}{{ table_head('Finding') }}{{ table_head('Column','list') }}{{ table_head('Count','num',true) }}{{ table_head('Out of','num',true) }}{{ table_head('Accuracy','list',false,true) }}{{ table_head('Detail',filter=false) }}{% for item in ds.quality.items[:display_limits.findings] %}{% set o = item.observation %}{% set vd = finding_details.get((o.field, o.rule)) %}{% set fv = finding_views.get((o.field, o.rule)) %}{% if vd %}{{ variant_preview(vd) }}{% elif fv %}{{ detail_preview(fv) }}{% endif %}{% if o.records %}Records {{ o.records[:5] | join(', ') }}{% if o.records | length > 5 %}…{% endif %}{% endif %}{% if o.patterns and not fv %}Patterns {{ o.patterns | join(', ') }}{% endif %}{% if o.limited_by %}Limited by {{ o.limited_by | join(', ') }}{% endif %}{% if o.diagnostics %}Diagnostics {{ o.diagnostics | join(', ') }}{% endif %}{% if not (vd or fv or o.records or o.patterns or o.limited_by or o.diagnostics) %}–{% endif %}{% endfor %}
{{ status(item.status_word, item.status_class, item.status_title) }}{{ item.title }}{{ o.rule }}{% if item.field_name %}{{ item.field_name }}{% else %}dataset{% endif %}{% if item.kind == 'at_least' %}≥ {% endif %}{{ o.count | count }}{% if o.of is not none %}{{ o.of | count }}{{ item.noun }}{% else %}–{% endif %}{{ acc(item.kind) }}
{% if ds.quality.items | length > display_limits.findings %}

Showing the first {{ display_limits.findings | count }} of {{ ds.quality.items | length | count }} findings; the report JSON lists them all.

{% endif %}{% if ds.quality.truncated %}

The scan listed only its first scan.observations.max_items findings; its summaries count all of them.

{% endif %}

Accuracy: exact counted on every value of the file; at least a scan limit stopped the count; sample counted on a sample of the distinct values. Remarks such as missing values are not quality issues.

{% endif %}
{% endif %}

04 / Structure

Columns({{ ds.summary.column_count }})

Inferred type and classification per column. Issues: confirmed issues and candidate anomalies found by the scan; remarks such as missing values are under Observations.

{{ tool_search(px ~ 'columns-table', 'columns') }}{{ tool_seg(px ~ 'columns-table', 'Show columns', [('all','All',ds.summary.column_count),('issue','Quality issues',ds.quality_issue_column_count),('obs','With observations',ds.observation_column_count)]) }}{{ ds.summary.column_count }} of {{ ds.summary.column_count }} columns
{{ table_head('Column','list') }}{{ table_head('Missing','num',true) }}{{ table_head('Distinct','num',true) }}{{ table_head('Inferred type','list',false,true) }}{{ table_head('Type','list',false,true) }}{{ table_head('Error','num',true) }}{{ table_head('Issues','num',true) }}{{ table_head('Examples',filter=false) }}{% for column in ds.columns %}{{ column_name(column, ds.id) }}{{ percent_cell(column.missing_percent,column.missing_count) }}{{ distinct_cell(column) }}{{ percent_cell(column.type_error_percent,column.type_error_count or 0,'var(--attn)',column.type_error_count != 0) }}{% set nq = column.quality | rejectattr('observation.status','equalto','informational') | list | length %}{% set nr = column.quality | length - nq %}{{ value_examples(column, px=px) }}{% endfor %}
{{ column.inferred_type }}{{ column.type_label }}{% if nq %}{{ nq | count }}{% else %}0{% endif %}{% if nr %}+{{ nr | count }} remarks{% endif %}

Distinct values are exact unless marked: ≥ at least (a scan limit stopped the count), ≈ estimated from a sample with its error.

{% if ds.structure %}{% set st = ds.structure %}

05 / Shape

JSON structure({{ st.fields | length }})

Every path of the records, containers included, with presence per parent and array lengths.

Records
{{ ds.summary.row_count | count }}{% for name, count in st.record_types.items() %}{{ count | count }} {{ name }}{% if not loop.last %}, {% endif %}{% else %}No records{% endfor %}
Paths
{% if st.path_count is none %}limited{% else %}{{ st.path_count | count }}{% endif %}{{ ds.summary.column_count | count }} with values
Maximum depth
{{ st.max_depth_seen }}Limit {{ report.config.limits.max_depth }}
{% if st.fields %}
{{ st.fields | length }} of {{ st.fields | length }} paths
{{ table_head('Path') }}{{ table_head('Depth','num',true) }}{{ table_head('Native types','list',false,true) }}{{ table_head('Present','num',true) }}{{ table_head('Absent','num',true) }}{{ table_head('Array length','num',true) }}{{ table_head('Empty arrays','num',true) }}{{ table_head('Role','list',false,true) }}{% for f in st.fields %}{% if f.present_percent is none %}{% else %}{% endif %}{% if f.absent_count is none %}{% else %}{% endif %}{% if f.arrays %}{% else %}{% endif %}{% set role = 'collection' if f.collection else ('column' if f.column else 'container') %}{% endfor %}
{{ f.path }}{{ f.depth }}{% for name, count in f.native_types.items() %}{{ name }}{% endfor %}items {{ f.present_count | count }}{{ f.present_percent }}%{{ f.present_count | count }}–{% if f.absent_count %}{{ f.absent_count | count }}{% else %}0{% endif %}{% if f.arrays.minimum_length == f.arrays.maximum_length %}{{ f.arrays.minimum_length | count }}{% else %}{{ f.arrays.minimum_length | count }}–{{ f.arrays.maximum_length | count }}mean {{ f.arrays.mean_length | number }}{% endif %}{{ f.arrays.empty_count | count }}––{% if f.collection %}collection {{ f.collection }}{% elif f.column %}column{% else %}container{% endif %}
{% else %}

No paths: the dataset has no records.

{% endif %}
{% endif %}

06 / Cleaning

Transformations({{ ds.summary.column_count }})

Occurrences changed by each normalization stage, and the spellings it groups. Raw preview values remain unchanged.

{% set tf = namespace(n=0) %}{% for c in ds.columns %}{% if c.normalization.stages | selectattr('changed_count') | list %}{% set tf.n = tf.n + 1 %}{% endif %}{% endfor %}
{{ tool_search(px ~ 'transformations-table', 'columns') }}{{ tool_seg(px ~ 'transformations-table', 'Show columns', [('all','All',ds.summary.column_count),('transform','With transform',tf.n)]) }}{{ ds.summary.column_count }} of {{ ds.summary.column_count }} columns
{{ table_head('Column','list') }}{{ table_head('Unicode composed','num',true) }}{{ table_head('Trimmed','num',true) }}{{ table_head('Whitespace collapsed','num',true) }}{{ table_head('Case folded','num',true) }}{{ table_head('Accents removed','num',true) }}{{ table_head('Distinct','num',true) }}{{ table_head('Compared distinct','num',true) }}{{ table_head('Variant groups','num',true) }}{% for column in ds.columns %}{{ column_name(column) }}{{ stage_cell(column,'nfc') }}{{ stage_cell(column,'trim') }}{{ stage_cell(column,'collapse_whitespace') }}{{ stage_cell(column,'casefold') }}{{ stage_cell(column,'strip_accents') }}{{ distinct_cell(column) }}{{ key_distinct_cell(column) }}{{ variant_count_cell(column) }}{% endfor %}
{% if variant_rows %}

Variant groups({{ variant_rows | length }})

Raw spellings that every normalization stage compares as equal: an analytical equivalence, not proof of identity. Whitespace is shown as written.

{% endif %}
{% if numeric_columns %}

07 / Numeric

Numeric analysis({{ ds.summary.numeric_column_count }})

Range and distribution statistics for accepted numeric values.

{{ ds.summary.numeric_column_count }} of {{ ds.summary.numeric_column_count }} columns
{{ table_head('Column','list') }}{{ table_head('Range','num',true) }}{{ table_head('Minimum','num',true) }}{{ table_head('Maximum','num',true) }}{{ table_head('Mean','num',true) }}{{ table_head('Median','num',true) }}{{ table_head('Distinct','num',true) }}{{ table_head('Examples',filter=false) }}{% for column in numeric_columns %}{{ column_name(column) }}{% if column.numeric.median is none %}{% set h = column.deep.histogram %}{% if h %}{% else %}{% endif %}{% else %}{% endif %}{{ distinct_cell(column) }}{{ value_examples(column,'num',px) }}{% endfor %}
{{ column.numeric.range | number }}{{ column.numeric.minimum | number }}{{ column.numeric.maximum | number }}{{ column.numeric.mean | number }}{{ h.median.value | number }}{{ 'exact' if h.median.accuracy == 'exact' else 'est.' }}limited{{ column.numeric.median | number }}
{% endif %} {% if date_columns %}

08 / Dates

Date analysis({{ ds.summary.date_column_count }})

Strict date parsing keeps ambiguous, invalid and non-date values separate. Ambiguous values are never resolved from the other values of the column.

{{ tool_search(px ~ 'date-table', 'columns') }}{{ tool_seg(px ~ 'date-table', 'Show date columns', [('all','All',date_columns | length),('mixed','Mixed',date_columns | selectattr('date_profile.status','equalto','mixed') | list | length)]) }}{{ ds.summary.date_column_count }} of {{ ds.summary.date_column_count }} date columns
{{ table_head('Column','list') }}{{ table_head('Status','list',false,true) }}{{ table_head('Breakdown',filter=false) }}{{ table_head('Valid','num',true) }}{{ table_head('Ambiguous','num',true) }}{{ table_head('Invalid','num',true) }}{{ table_head('Other','num',true) }}{{ table_head('Distinct','num',true) }}{{ table_head('Formats',filter=false) }}{% for column in date_columns %}{{ column_name(column) }}{{ percent_cell(column.date_profile.valid_percent,column.date_profile.valid_count,'var(--ok)') }}{{ percent_cell(column.date_profile.ambiguous_percent,column.date_profile.ambiguous_count,'var(--attn)',true) }}{{ percent_cell(column.date_profile.invalid_date_percent,column.date_profile.invalid_date_count,'var(--attn)',true) }}{{ percent_cell(column.date_profile.not_date_percent,column.date_profile.not_date_count,'var(--d3)') }}{{ distinct_cell(column) }}{% endfor %}
{{ column.date_profile.status | replace('_',' ') }}{{ column.date_profile.format_count }} variant{{ '' if column.date_profile.format_count == 1 else 's' }}
{% if period_columns %}

Distribution over time exact

Valid dates counted by the second pass per calendar period, as written: the offset of a datetime is not converted, and ambiguous dates are counted apart and never placed, unless the unambiguous dates of the column prove one order and the second pass reads them with it.

{% for column in period_columns %}{% set pc = column.deep.periods %}{% set top = (pc.periods | map(attribute='count') | max) %}
{{ column.name }} · {{ pc.count | count }} dates by {{ pc.granularity }}, {{ pc.first }} to {{ pc.last }}{% if pc.ambiguous %} · {{ pc.ambiguous | count }} ambiguous not placed{% endif %}{% if pc.resolution %} · {{ pc.resolved | count }} ambiguous read as {{ pc.resolution.order }} (proved by {{ pc.resolution.evidence.get(pc.resolution.order, 0) | count }} unambiguous dates){% endif %}{% if pc.unplaced %} · {{ pc.unplaced | count }} not dates{% endif %}
{% for period in pc.periods %}{% endfor %}
PeriodDates
{{ period.period }}{{ period.count | count }}
{% endfor %}{% endif %}
{% endif %} {% if string_columns %}

09 / Text

String analysis({{ ds.summary.string_column_count }})

Length classes, fixed widths and representative values.

{% set fx = string_columns | selectattr('string_profile.fixed_length', 'ne', none) | list | length %}{% set en = string_columns | selectattr('classification.type', 'equalto', 'enum') | list | length %}
{{ tool_search(px ~ 'string-table', 'columns') }}{{ tool_seg(px ~ 'string-table', 'Show by length', [('all','All',string_columns | length),('fixed','Fixed',fx),('notfixed','Not fixed',string_columns | length - fx)]) }}{{ tool_seg(px ~ 'string-table', 'Show by enumeration', [('all','All',string_columns | length),('enum','Enum',en)]) }}{{ ds.summary.string_column_count }} of {{ ds.summary.string_column_count }} string columns
{{ table_head('Column','list') }}{{ table_head('Class','list',false,true) }}{{ table_head('Fixed','num',true) }}{{ table_head('Min','num',true) }}{{ table_head('Max','num',true) }}{{ table_head('Mean','num',true) }}{{ table_head('Median','num',true) }}{{ table_head('Lengths',filter=false) }}{{ table_head('Distinct','num',true) }}{{ table_head('Examples',filter=false) }}{% for column in string_columns %}{{ column_name(column) }}{% if column.string_profile.fixed_length is not none %}{% else %}{% endif %}{{ distinct_cell(column) }}{% endfor %}
{{ column.string_profile.status | replace('_',' ') }}{% if column.string_profile.fixed_length is not none %}{{ column.string_profile.fixed_length }}{% else %}–{% endif %}––––{{ column.string_profile.minimum_length }}{{ column.string_profile.maximum_length }}{{ column.string_profile.mean_length }}{{ column.string_profile.median_length }}{% for item in column.string_profile.length_distribution %}{% endfor %}{% for value in column.string_profile.representative_examples %}{{ value }}{% endfor %}
{% endif %} {% if detector_rows %}

10 / Detectors

Detectors and formats({{ detector_rows | length }})

What each detector recognized per column, with the formats it found. Primary: the interpretation the classification starts from.

{{ tool_search(px ~ 'detector-table', 'columns') }}{{ detector_rows | length }} of {{ detector_rows | length }} detections
{{ table_head('Column','list') }}{{ table_head('Detector','list') }}{{ table_head('Matched','num',true) }}{{ table_head('Ambiguous','num',true) }}{{ table_head('Invalid','num',true) }}{{ table_head('Formats',filter=false) }}{% for column, detector in detector_rows %}{{ column_name(column) }}{% if detector.status == 'failed' %}{% else %}{{ percent_cell(detector.matched_percent, detector.matched_count, 'var(--ok)') }}{% if detector.formats %}{% else %}{% endif %}{% endif %}{% endfor %}
{{ detector.id }}{% if detector.primary %} primary{% endif %}failed–––{{ detector.ambiguous_count | count }}{{ detector.invalid_count | count }}{% for item in detector.formats %}{{ item.format }}{% endfor %}–
{% endif %} {% if pattern_columns %}

11 / Shapes

Patterns({{ pattern_columns | length }})

Structural patterns inferred from the distinct values the scan sampled, grouped by family (the values of one structure); the column page shows each family with its nodes and tables. 9 a digit, A a capital, a a lowercase letter, @ any letter, X a capital or a digit, x a lowercase letter or a digit, # a letter or a digit, other characters as written. Shares count distinct values, never occurrences in the file, and are not validated against the whole file.

Columns with patterns
{{ pattern_columns | length | count }}of {{ ds.summary.column_count | count }} columns
Without patterns
{{ pattern_missing | count }}Numbers, dates, masked or too few distinct values
Grammar
{{ pattern_grammar }}Version of the pattern rules
{{ tool_search(px ~ 'pattern-table', 'patterns') }}{{ pattern_rows | length }} of {{ pattern_rows | length }} patterns
{{ table_head('Column','list') }}{{ table_head('Family','list',false,true) }}{{ table_head('Pattern') }}{{ table_head('Role','list',false,true) }}{{ table_head('Distinct values','num',true) }}{{ table_head('Share','num',true) }}{{ table_head('Basis','list',false,true) }}{{ table_head('Validated on every value','num',true) }}{{ table_head('Length','num',true) }}{{ table_head('Examples',filter=false) }}{% for column, c, kind in pattern_rows[:display_limits.patterns] %}{% if c is none %}{% set o = column.patterns %}{{ column_name(column) }}{{ percent_cell((o.outside_share * 100) | round(2), o.outside_distinct, 'var(--d3)') }}{% set vp = column.deep.patterns %}{% if vp %}{{ percent_cell(((vp.outside_patterns() * 100 / vp.values) | round(2)) if vp.values else 0, vp.outside_patterns(), 'var(--d3)') }}{% else %}{% endif %}{% else %}{% set shown = column.patterns.examples[c.id] %}{{ column_name(column) }}{{ percent_cell((c.share * 100) | round(2), c.distinct, 'var(--d2)') }}{% set vp = column.deep.patterns %}{% if vp %}{% set tally = (vp.all_patterns() | selectattr('id', 'equalto', c.id) | first) %}{{ percent_cell(((tally.occurrences * 100 / vp.values) | round(2)) if vp.values else 0, tally.occurrences, 'var(--d2)') }}{% else %}{% endif %}{% if shown %}{% else %}{% endif %}{% endif %}{% endfor %}
–unmatched · {{ o.outside_structures | count }} structuresother{{ o.outside_distinct | count }}{{ acc(kind) }}–––
{{ c.family_key }}{{ c.family }}{{ c.render }}{{ c.role }}{{ c.distinct | count }}{{ acc(kind) }}{{ 'of every distinct value' if kind == 'exact' else 'of sampled distinct values' }}–{{ c.length }}{% for value in shown[:3] %}{{ value }}{% endfor %}not kept
{% if pattern_rows | length > display_limits.patterns %}

Showing the first {{ display_limits.patterns | count }} of {{ pattern_rows | length | count }} patterns; the report JSON lists them all.

{% endif %}

A share is the part of the distinct values a pattern takes: exact when the scan held every distinct value, sample when it held a sample of them. A value counts for one pattern: the one of its family that has its lengths. Quoted text is a constant the pattern folds. Validated on every value is the second pass: how many occurrences of the whole file the pattern reads, exactly; a value no pattern reads is an observation of structure, not an error.

{% endif %} {% if ds.relations.available and 'relationships' not in hidden_sections %}{% set rel = ds.relations %}

12 / Relationships

Relationships({{ rel.count }})

How one column goes with another, counted on every row by the second pass. The counts and the percentages of each category come first; a score only summarizes them. An association is not a cause, and a candidate rule is learned from the data and validated by nobody.

{% for g in rel.groups[:display_limits.relations] %}{% if g.mirror %}
The same pair the other way round: {{ g.category }} to {{ g.column }}{% endif %}

{{ g.category }} to {{ g.column }}({{ g.tables | length }} {{ 'feature' if g.tables | length == 1 else 'features' }})

{% for t in g.tables %}{% if t.leading %}
{% else %}
{{ t.feature_label | capitalize }}: {% if t.score is not none %}score {{ '%.2f' | format(t.score) }}{% else %}no score{% endif %}, purity {% if t.purity is not none %}{{ '%.1f' | format(t.purity * 100) }}%{% else %}n/a{% endif %}{% endif %}

By {{ t.feature_label }} {{ acc('exact') }} {{ t.sentence }}

Classified rows
{{ t.counted | count }}of {{ t.examined | count }}{% if t.left_out %}; {{ t.left_out }}{% endif %}
Purity
{% if t.purity is not none %}{{ '%.1f' | format(t.purity * 100) }}%{% else %}–{% endif %}Dominant class of each category{% if t.reverse_purity is not none %}; the other way round {{ '%.1f' | format(t.reverse_purity * 100) }}%{% endif %}
Association
{% if t.score is not none %}{{ '%.2f' | format(t.score) }}{% else %}–{% endif %}{% if t.strength == 'unstable' %}Unstable: low counts{% elif t.strength %}Cramér's V, bias-corrected: {{ t.strength }}{% else %}Not defined{% endif %}
Exceptions
{{ t.exception_count | count }}Rows outside the dominant class, in {{ t.exception_cells | count }} cells
{% if t.note %}

{{ t.note }}

{% endif %}
{{ table_head(g.category, filter=false) }}{% for c in t.classes %}{% endfor %}{{ table_head('Rows', align=true, filter=false) }}{{ table_head('Coverage', align=true, filter=false) }}{{ table_head('Purity', align=true, filter=false) }}{% for r in t.rows %}{% for cell in r.cells %}{% endfor %}{% endfor %}{% for n in t.column_totals %}{% endfor %}
{% if t.feature == 'pattern' and c != 'other' %}{{ c }}{% else %}{{ c }}{% endif %}
{{ r.label }}{% if cell.count %}{{ cell.count | count }}{{ '%.1f' | format(cell.share * 100) }}%{% else %}0{% endif %}{% if cell.dominant %} dominant class{% endif %}{{ r.classified | count }}{% if r.coverage is not none %}{{ '%.1f' | format(r.coverage * 100) }}%{% else %}–{% endif %}{% if r.purity is not none %}{{ '%.1f' | format(r.purity * 100) }}%{% else %}–{% endif %}
All{{ n | count }}{{ t.total | count }}
{% if t.exceptions %}

Exceptions({{ t.exception_count | count }} rows)

{{ table_head(g.category, filter=false) }}{{ table_head('Class', filter=false) }}{{ table_head('Rows', align=true, filter=false) }}{{ table_head('First rows', filter=false) }}{% for e in t.exceptions %}{% endfor %}
{{ e.category }}{% if t.feature == 'pattern' and e.label != 'other' %}{{ e.label }}{% else %}{{ e.label }}{% endif %}{{ e.count | count }}{% if e.examples %}{% for record, value in e.examples %}{{ value }}{% endfor %}Records {{ e.examples | map(attribute=0) | join(', ') }}{% else %}–{% endif %}
{% if t.exception_cells > t.exceptions | length %}

Showing the {{ t.exceptions | length | count }} largest of {{ t.exception_cells | count }} cells; the JSON lists them in its analyses.

{% endif %}{% endif %}{% if t.rules %}

Candidate rules({{ t.rules | length }})

    {% for r in t.rules %}
  • candidate When {{ g.category }} is {{ r.category }}, {{ g.column }} reads {% if t.feature == 'pattern' %}{{ r.label }}{% else %}{{ r.label }}{% endif %} in {{ (r.support - r.exceptions) | count }} of {{ r.support | count }} rows ({{ '%.1f' | format(r.confidence * 100) }}%).Learned from the data, not validated.{{ r.exceptions | count }}
  • {% endfor %}
{% endif %}{% if t.leading %}
{% else %}
{% endif %}{% endfor %}{% if g.mirror %}{% endif %}{% endfor %}{% if rel.groups | length > display_limits.relations %}

Showing the first {{ display_limits.relations | count }} of {{ rel.groups | length | count }} relations; the JSON lists them all.

{% endif %}{% for note in rel.notes %}

{{ note }}

{% endfor %}{% if rel.correlations %}

Correlations between numeric columns({{ rel.correlations | length }})

{{ table_head('Pair', filter=false) }}{{ table_head('Coefficient', align=true, filter=false) }}{{ table_head('Common rows', align=true, filter=false) }}{{ table_head('Note', filter=false) }}{{ table_head('Accuracy', filter=false) }}{% for c in rel.correlations %}{% endfor %}
{{ c.pair }}{% if c.coefficient is not none %}{{ '%+.3f' | format(c.coefficient) }}{% else %}–{% endif %}{{ c.pairs | count }}{{ c.note }}{{ acc('exact') }}
{% endif %}

Counts are exact: every row of the file is placed in one category and one class, or counted as left out. The dominant class of a category is the one that holds most of it; a tie goes to the first class listed. Purity is the share of the classified rows that sit in the dominant class of their category. The score is Cramér's V corrected for the number of rows and classes; when the expected counts are low it is marked unstable and given no strength. An empty other row or column is not printed. Correlations show a linear association between two columns, over the rows where both have a number.

{% endif %} {% if ds.insights and 'insights' not in hidden_sections %}

13 / Insights

Insights({{ ds.insights | length }})

What the second pass found in the values, with the accuracy of each figure: exact counts every value of the file, estimated comes with certain bounds. The same cards appear on the page of each column they concern.

{% set ns = namespace(family='') %}{% for card in v.insight_cards %}{% if card.family != ns.family %}{% set ns.family = card.family %}

{{ family_titles[card.family] }}

{% endif %}{{ insight_card(card, px) }}{% endfor %}{% if v.insight_cards | length < ds.insights | length %}

Showing {{ v.insight_cards | length | count }} of {{ ds.insights | length | count }} cards, at most {{ display_limits.insights | count }} of each family; the report JSON holds every result.

{% endif %}
{% endif %} {% if ds.second_pass.available and 'second-pass' not in hidden_sections %}{% set sp = ds.second_pass %}

14 / Second pass

Second pass({{ sp.rows | length }})

Exact analyses added by reading the source again. {{ mode_words[sp.mode] }} {{ outcome_words[sp.outcome] }}

{% if sp.reason %}

{{ sp.reason }}

{% endif %}{% if sp.metrics %}{% set m = sp.metrics %}
Computed
{{ m.computed | count }}Analyses with a result
Not computed
{{ (m.skipped + m.failed) | count }}{{ m.skipped | count }} skipped, {{ m.failed | count }} failed
Already answered
{{ m.already_answered | count }}By the scan, no reading needed
Not selected
{{ m.not_selected | count }}Left out by the planner
{% endif %}{% if sp.rows %}
{{ tool_search(px ~ 'second-pass-table', 'analyses') }}{{ sp.rows | length }} of {{ sp.rows | length }} analyses
{{ table_head('Column','list') }}{{ table_head('Analysis','list',false,true) }}{{ table_head('Status','list',false,true) }}{{ table_head('Result') }}{{ table_head('Accuracy','list',false,true) }}{% for row in sp.rows[:display_limits.findings] %}{% endfor %}
{{ row.column }}{{ row.analysis }}{{ row.status }}{{ row.summary }}{{ acc(row.kind) }}
{% else %}

No analysis was run for this dataset.

{% endif %}{% if sp.left_out %}

Not selected, by reason: {% for reason, count in sp.left_out %}{{ count | count }} {{ reason }}{% if not loop.last %}; {% endif %}{% endfor %}.

{% endif %}{% if sp.source_read %}

The source was read again{% if sp.source_verified %} and checked against the digest of the scan, so no result mixes two versions of the data{% endif %}{% if sp.seconds is not none %}, {{ sp.seconds | seconds }} s{% endif %}. This run computed {{ sp.executed | count }} analyses and reused {{ sp.reused | count }} stored ones.

{% elif sp.metrics %}

The source was not read for this report. Stored results and the facts of the scan were used.

{% endif %}
{% endif %} {% if has_limits and 'limits' not in hidden_sections %}{% set lim = ds.limits %}

15 / Limits

Limits and diagnostics

What the scan could not measure completely, and the technical events it recorded. Counters and statistics continue past a limit.

{% if lim.untracked_observations or lim.depth_truncated_observations %}
Untracked observations
{{ lim.untracked_observations | count }}Paths beyond scan.limits.max_fields
Truncated by depth
{{ lim.depth_truncated_observations | count }}Deeper than scan.limits.max_depth
{% endif %}{% if not lim.available %}

The limited measures are not computed: the scan has no observations (scan.observations.enabled is false, or building them failed).

{% endif %}{% if lim.items %}

Limited measures({{ lim.items | length }})

{{ lim.items | length }} of {{ lim.items | length }} measures
{{ table_head('Field','list') }}{{ table_head('Measure','list') }}{{ table_head('Reason','list',false,true) }}{{ table_head('Limit','num',true) }}{{ table_head('At least','num',true) }}{% for m in lim.items[:display_limits.limits] %}{% if m.limit is none %}{% else %}{% endif %}{% if m.lower_bound is none %}{% else %}{% endif %}{% endfor %}
{% if m.field_path is none %}dataset{% else %}{{ m.field_path }}{% endif %}{{ m.label }}{{ m.measure }}{{ m.reason }}{% if m.setting %}{{ m.setting }}{% elif m.note %}{{ m.note }}{% endif %}–{{ m.limit | count }}–{{ m.lower_bound | count }}
{% if lim.items | length > display_limits.limits %}

Showing the first {{ display_limits.limits | count }} of {{ lim.items | length | count }} limits; the report JSON lists them all.

{% endif %}{% endif %}{% if lim.diagnostics %}

Diagnostics({{ lim.diagnostics | length }})

    {% for d in lim.diagnostics %}
  • {{ d.level }} {{ d.code }} {{ d.message }}{% if d.dataset is none %} whole scan{% endif %}{% if d.locations %}Records {% for loc in d.locations %}{{ loc.record }}{% if loc.line is not none %} (line {{ loc.line }}){% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if d.count > d.locations | length %}…{% endif %}{% endif %}{{ d.count | count }}
  • {% endfor %}
{% endif %}
{% endif %}

16 / Raw values

Data sample({{ ds.rows.rows | length }})

{% set rs = ds.rows.sample %}{% if rs %}

Rows of the scan's row sample, with original row numbers and raw values: the first records, records drawn at random from the interior and the last records. The sampled rows are not uniform over the whole file.

{% else %}

Rows with original row numbers and raw values.

{% endif %}
{% if not rs %}

No row sample: scan.records.sample.rows is 0.

{% elif not ds.rows.rows %}

{% if rs.of %}This report keeps no row (report.row_sample asks for none).{% else %}No data records.{% endif %}

{% else %}{% set rc = ds.rows.counts %}

{{ ds.rows.rows | length | count }} of the {{ rs.of | count }} rows the scan sampled ({{ rc.start | count }} at the start, {{ rc.middle | count }} sampled, {{ rc.end | count }} at the end) out of {{ rs.population.records | count }} records. The sampled rows come from the interior, records {{ (rs.requested.edge + 1) | count }} to {{ (rs.population.records - rs.requested.edge) | count }}, where each record had the same chance by a seeded hash.{% if rs.reason %} The scan cut its sample to stay within its memory budget.{% endif %}

{{ tool_seg(px ~ 'sample-table', 'Show rows', [('all','All',ds.rows.rows | length),('start','Start',rc.start),('middle','Sampled',rc.middle),('end','End',rc.end)]) }}{{ ds.rows.rows | length }} of {{ ds.rows.rows | length }} sample rows
{% for column in ds.columns %}{% endfor %}{% set lists = [] %}{% if report.source.format in ('json', 'jsonl') %}{% for column in ds.columns %}{% if '[]' in column.path %}{% set _ = lists.append(loop.index0) %}{% endif %}{% endfor %}{% endif %}{% for row in ds.rows.rows[:display_limits.rows] %}{% for column in ds.columns %}{% if column.id not in row.values %}{% elif row.values[column.id] is none %}{% else %}{% set cell = row.values[column.id] | cell_text %}{% set cut = column.id in row.truncated %}{% if cell == '' %}empty{% else %}{{ cell }}{% endif %}{% if cut %}… cut{% endif %}{% endif %}{% endfor %}{% endfor %}
Row
Part
{{ (column.name or '(unnamed)') | breakable }}{{ column.inferred_type }}
{{ row.record }}{% for origin in row.origins %}{{ origin_words[origin] }}{% if not loop.last %} · {% endif %}{% endfor %}absenthidden
{% if ds.rows.rows | length > display_limits.rows %}

Showing the first {{ display_limits.rows | count }} of {{ ds.rows.rows | length | count }} rows kept; the report JSON lists them all.

{% endif %}{% endif %}

17 / Method

Analysis settings

Rules used for this analysis, so the result can be reproduced.

Scope
All {{ ds.summary.row_count | count }} records
{% set scan = report.config %}
Missing values
{{ scan.values.missing | join(', ') }}; markers: {% for marker in scan.values.null_markers %}{{ marker | tojson }} {% else %}none{% endfor %}
Marker comparison
Whitespace trimmed; {{ 'case-sensitive' if scan.values.null_markers_case_sensitive else 'case-insensitive' }}
Value normalization
Unicode composition (NFC): {{ 'enabled' if scan.normalization.nfc else 'disabled' }}; trim: {{ 'enabled' if scan.normalization.trim else 'disabled' }}; collapse internal whitespace: {{ 'enabled' if scan.normalization.collapse_whitespace else 'disabled' }}; comparison also with case folding: {{ 'enabled' if scan.normalization.casefold else 'disabled' }}, accents removed: {{ 'enabled' if scan.normalization.strip_accents else 'disabled' }}. Raw preview values are preserved.
Duplicate comparison
{% if scan.records.duplicates %}Exact raw values across every column; up to {{ scan.limits.max_tracked_records | count }} distinct records compared{% else %}Disabled{% endif %}
Type inference
At least {{ (scan.types.minimum_confidence * 100) | round(1) }}% agreement across present values; numbers with {{ scan.detectors.number.conventions | join(' or ') }} decimals, configured dates and true/false. Leading-zero identifiers remain text.
Date detection
{% if scan.detectors.date.enabled %}Orders: {{ scan.detectors.date.orders | join(', ') }}; separators: {% for separator in scan.detectors.date.separators %}{{ separator | tojson }} {% endfor %}; month names: {{ scan.detectors.date.month_languages | join(', ') or 'none' }}; ambiguous order: {{ scan.detectors.date.ambiguous_order or 'unresolved, column evidence shown but never applied' }}{% else %}Disabled{% endif %}
Column types
The technical type, a detector matching at least {{ (scan.detection.minimum_share * 100) | round(1) }}% of present values, the length of the texts, the name of the column and, when the second pass counted them, the exact distinct values
Adaptive detection
{% if scan.detection.warmup_values %}Every detector on the first {{ scan.detection.warmup_values | count }} distinct values of each column; detectors that recognized none of them{% if scan.detection.rare_share %}, or at most {{ (scan.detection.rare_share * 100) | round(2) }}% of them and none of the last {{ (scan.detection.warmup_values - scan.detection.warmup_values // 2) | count }},{% endif %} then skip the others{% if scan.detection.probe_interval %}, except about 1 value in {{ scan.detection.probe_interval | count }}{% endif %}. Number and date detection always test every value{% else %}Disabled: every detector tests every value{% endif %}
Enumerations
{% if scan.detectors.enumeration.enabled %}At most {{ scan.detectors.enumeration.maximum_distinct }} distinct values among at least {{ scan.detectors.enumeration.minimum_values | count }} present values{% else %}Disabled{% endif %}
Sensitive values
{{ {'mask': 'Masked', 'hide': 'Hidden', 'show': 'Shown'}[scan.exposure.sensitive_values] }} in examples and the data sample
String lengths
Very short through {{ report.config.classification.very_short_max_length }}; short through {{ report.config.classification.short_max_length }}; medium through {{ report.config.classification.medium_max_length }}; long through {{ report.config.classification.long_max_length }}; otherwise very long. Up to {{ report.report.config.string_analysis.examples_per_length }} examples per length are retained when the maximum is {{ report.report.config.string_analysis.length_distribution_max_length }} or less.
Value representation
Complete through {{ report.report.config.value_examples.full_distribution_max_distinct }} distinct values; otherwise a reproducible sample of up to {{ v.sample_size }} distinct values, the ones with the smallest hash priorities
Row numbering
Data records start at 1, excluding the header. Quoted multiline values count as one record.
Row sample
{% if ds.rows.sample %}The scan kept the first {{ ds.rows.sample.requested.edge }} records, up to {{ ds.rows.sample.requested.middle }} records drawn from the interior by a seeded hash, and the last {{ ds.rows.sample.requested.edge }}; this report shows {{ report.report.config.row_sample.start }} at the start, {{ report.report.config.row_sample.middle }} sampled and {{ report.report.config.row_sample.end }} at the end; raw values preserved{% else %}Disabled{% endif %}
Patterns
{% if scan.pattern_inference.enabled %}Inferred from the sampled distinct values: up to {{ scan.pattern_inference.max_patterns }} per column, values up to {{ scan.pattern_inference.max_value_length }} characters, at least {{ scan.pattern_inference.min_distinct }} distinct values per pattern{% else %}Not computed: disabled by scan.pattern_inference.enabled{% endif %}
Observations
{% if scan.observations.enabled %}Rules version {{ report.observations.version if report.observations else '–' }}; at most {{ scan.observations.max_items | count }} findings and {{ scan.observations.max_items | count }} limits listed{% else %}Not computed: disabled by scan.observations.enabled{% endif %}
JSON format
{{ report.format_version }} - revision {{ report.format_revision }} (experimental)
SOURCE SHA-256{{ report.source.sha256 }}
{% endfor %}