{#- Brand mark: a beam enters an equilateral prism and the spectrum emerges staggered along the right face, each ray fanning out at its own angle. -#} {# The mark is base64-embedded rather than linked, so a report stays a single offline file. If the packaged asset is missing the masthead falls back to the wordmark alone rather than showing a broken image. #} {% if favicon_uri %} {% endif %} {% set verdict = result.metadata.get('verdict') %} {% set consensus = result.evidence | selectattr('kind', 'equalto', 'anomaly_consensus_review') | list %} {% set review_count = consensus[0].value.review_row_count if consensus else 0 %}
{% if logo_uri %}{% endif %}
PrismEDA
Exploratory data analysis
{% if result.goal == 'schema_discovery' %}Schema discovery{% elif result.goal == 'anomaly_detection' %}Anomaly review{% elif result.goal == 'classification' %}Classification readiness{% elif result.goal == 'image_profile' %}Image dataset profile{% else %}Data profile{% endif %}
Recipe  {{ result.goal }} {% if source_location %} Source  {{ source_location }} {% endif %}
What we found

{%- if verdict -%} {{ verdict }} {%- elif result.goal == 'schema_discovery' -%} Candidate schema map {%- elif result.goal == 'anomaly_detection' -%} Anomaly candidate review {%- elif result.goal == 'classification' -%} Classification readiness map {%- elif result.goal == 'image_profile' -%} Image dataset quality profile {%- else -%} {{ dataset_title }} {%- endif -%}

{{ result.summary }}

{{ result.catalog.table_count | format_value }}
Table{% if result.catalog.table_count != 1 %}s{% endif %}
{{ result.catalog.row_count | format_value }}
{% if result.goal == 'image_profile' %}Images{% else %}Rows{% endif %}
{{ result.catalog.column_count | format_value }}
{% if result.goal == 'image_profile' %}Manifest fields{% else %}Columns{% endif %}
{% if result.goal == 'schema_discovery' %}
{{ result.metadata.get('candidate_relationships', result.findings | length) | format_value }}
Candidate relationships
{% elif result.goal == 'profile' %}
{{ issues | length | format_value }}
Issue{% if issues | length != 1 %}s{% endif %}
{{ observations | length | format_value }}
Alert{% if observations | length != 1 %}s{% endif %}
{% else %}
{{ result.findings | length | format_value }}
Finding{% if result.findings | length != 1 %}s{% endif %}
{% if result.goal == 'image_profile' and result.metadata.get('invalid_image_count') %}
{{ result.metadata.get('invalid_image_count') | format_value }}
Unreadable files
{% endif %} {% endif %} {% if review_count %}
{{ review_count | format_value }}
Rows to review
{% endif %}
{# Plain anchors, so the bar still navigates with JavaScript disabled; the script below only adds the active-section highlight. #} {% if sections | length > 1 %} {% endif %} {% set ai = result.metadata.get('ai_interpretation') %} {% if ai %}
AI Interpretation
{% if ai.narrative %}

{{ ai.narrative }}

{% endif %} {% if ai.column_reads %}
What the key columns mean
{% for read in ai.column_reads %}
{{ read.column }}{{ read.meaning }}
{% endfor %}
{% endif %} {% if ai.relationship_reads %}
What the relationships mean
{% for read in ai.relationship_reads %}
{{ read.child }} → {{ read.parent }}{{ read.reading }}
{% endfor %}
{% endif %} {% if ai.next_steps %}
Suggested next steps
{% endif %}

Interpretation by the AI investigator ({{ result.metadata.get('model', 'model') }}) over the deterministic evidence{% if ai.shared_labels %}, with column value samples shared per your privacy setting{% endif %} — judgment layered on the measurements above, not new measurements. Verify against the evidence.

{% endif %} {% if result.warnings %}
{{ sections.number('warnings') }}

Evidence warnings

What this run could not cover, and why.

{% for warning in result.warnings %}

{{ warning.code }}: {{ warning.message }}

{% endfor %} {% if result.sampling %} {% for record in result.sampling %}

{{ record.operation }}: {{ record.sampled_rows | format_value }} of {{ record.source_rows | format_value }} rows, sampled with seed {{ record.seed }} ({{ record.strategy }}).{% for limitation in record.limitations %} {{ limitation }}{% endfor %}

{% endfor %} {% endif %}
{% endif %} {% if result.goal == 'schema_discovery' %} {% for artifact in result.artifacts %} {% if artifact.kind == 'schema_graph' %} {% include "_erd_section.html" %} {% endif %} {% endfor %}
{{ sections.number('keys') }}

Candidate keys

Minimal combinations only; business confirmation is required.

{% for item in result.evidence %} {% if item.kind == 'candidate_key' %} {% endif %} {% endfor %}
TableColumnsUniqueCompleteConfidence
{{ item.scope.table }}{{ item.scope.columns | join(' + ') }}{{ "%.1f%%" | format(item.value.uniqueness_rate * 100) }}{% if item.value.sampled %}
sampled{% endif %}
{{ "%.1f%%" | format(item.value.completeness_rate * 100) }}{{ "%.1f%%" | format(item.confidence * 100) }}
{% set relationships = result.evidence | selectattr('kind', 'equalto', 'candidate_relationship') | sort(attribute='confidence', reverse=true) | list %}
{{ sections.number('relationships') }}

Candidate relationships

Direction runs from the unique parent key to the referencing child columns. Sorted by confidence — filter by table to focus a pair.

{% for item in relationships %} {% set bin = 'high' if item.confidence >= 0.85 else 'medium' if item.confidence >= 0.7 else 'low' %} {% endfor %}
ParentChildCardinalityIncludedOrphan rowsConfidence
{{ item.value.parent_table }}.{{ item.value.parent_columns | join(' + ') }} {{ item.value.child_table }}.{{ item.value.child_columns | join(' + ') }} {{ item.value.cardinality | replace('_', '–') }} {{ "%.1f%%" | format(item.value.inclusion_rate * 100) }} {{ item.value.orphan_row_count }} {{ "%.0f%%" | format(item.confidence * 100) }}{% if item.value.sampled %}
sampled{% endif %}
{% endif %} {# Image profiles defer their tables until after the findings and the contact sheets: the decision comes first, the reference tables back it up. #} {% if result.goal not in ('anomaly_detection', 'image_profile') %} {% for artifact in result.artifacts %} {% if artifact.kind == 'metric_table' %}
{{ sections.number('metrics-' ~ loop.index0) }}

{{ artifact.title }}

{% if artifact.metadata.description %}

{{ artifact.metadata.description }}

{% endif %}
{% for column in artifact.data.columns %}{% endfor %} {% for row in artifact.data.rows %} {% for column in artifact.data.columns %}{% endfor %} {% endfor %}
{{ column.label }}
{{ row[column.key] | format_value }}
{% endif %} {% endfor %} {% endif %}
{{ sections.number('issues') }}
{% if result.goal == 'profile' %}

Issues

Things wrong with the data: values that are missing, duplicated, mistyped, or standing in for something else. Ranked by severity.

{% else %}

Prioritized findings

Ranked by severity so the most important issues are reviewed first.

{% endif %}
{% if issues %} {% for finding in issues %} {% set sev = finding.severity if finding.severity in ['high', 'medium', 'low'] else 'high' %} {% set related_tables = namespace(items=[]) %} {% if result.goal == 'image_profile' %} {% for artifact in result.artifacts %} {% if artifact.kind == 'metric_table' %} {% for evidence_id in artifact.evidence_ids %} {% if evidence_id in finding.evidence_ids %} {% set related_tables.items = related_tables.items + [artifact] %} {% endif %} {% endfor %} {% endif %} {% endfor %} {% endif %}
{{ finding.severity }}

{{ finding.title }}

{{ finding.summary }}

{% if finding.recommendation %}

{{ finding.recommendation }}

{% endif %}
{% if related_tables.items %}
View details
{% for artifact in related_tables.items %}

{{ artifact.title }}

{% if artifact.metadata.description %}{{ artifact.metadata.description }}{% endif %}
{% for column in artifact.data.columns %}{% endfor %} {% for row in artifact.data.rows %} {% for column in artifact.data.columns %}{% endfor %} {% endfor %}
{{ column.label }}
{{ row[column.key] | format_value }}
{% endfor %}
{% endif %}
{% endfor %} {% else %}
{% if result.goal == 'schema_discovery' %}No candidate relationships met the configured evidence thresholds.{% elif result.goal == 'anomaly_detection' %}No anomaly candidate signals met the deterministic review thresholds.{% elif result.goal == 'classification' %}No prioritized classification readiness issues were found.{% elif result.goal == 'image_profile' %}No prioritized image dataset quality issues were found.{% else %}No data-quality issues were found. Every check that ran is listed in the guide; none of them fired.{% endif %}
{% endif %}
{% if observations %}
{{ sections.number('alerts') }}

Alerts

True of this data, and worth knowing — but not defects. Nothing here needs fixing; some of it changes how you model.

{% for finding in observations %}
note

{{ finding.title }}

{{ finding.summary }}

{% if finding.recommendation %}

{{ finding.recommendation }}

{% endif %}
{% endfor %}
{% endif %} {% for item in result.evidence if item.kind == 'anomaly_consensus_review' and item.value.rows %}
{{ sections.number('rows') }}

Rows to review

{{ item.value.review_row_count }} of {{ item.value.evaluated_row_count }} row{% if item.value.evaluated_row_count != 1 %}s{% endif %}, ranked by how many independent checks flagged them. Highlighted cells are the values that stand out; expand a row to see how each check flagged it.

Agreement
{% for column in item.value.columns %}{% endfor %} {% for row in item.value.rows %} {% set flagged_columns = row.contributors | map(attribute='column') | list %} {% set ex = row.explanations %} {% set mv = ex.get('multivariate') %} {% set cond = ex.get('conditional') %} {% for column in item.value.columns %} {% endfor %} {% endfor %}
Agreement{{ column }}Why it stands out
{{ row.method_count }}/{{ item.value.total_detectors }} {{ row.methods | join(' · ') }} {{ row['values'][column] | format_cell }} {{ row.why }} {% if ex.get('univariate') %}{{ ex.univariate.contributors | why_bars_svg }}{% endif %} {% if mv or cond %}
How each check flagged this {% if mv %}
Multivariate — joint deviation score {{ mv.score | round(1) }} (flags at ≥ {{ mv.threshold | round(1) }}). Every column's distance from its own typical value:
{{ mv['columns'] | why_bars_svg }}
{% endif %} {% if cond %}
Conditional — {{ cond.value_column }} is unusual for the {{ cond.condition_column }} ≈ {{ cond.condition_value | format_cell }} peer group. Where this row sits vs its peers:
{{ cond | peer_group_svg }}
{% endif %}
{% endif %}
{% endfor %} {% set sheets = result.artifacts | selectattr('kind', 'equalto', 'image_contact_sheet') | list %} {% if sheets %}
{{ sections.number('flagged-images') }}

The flagged images

Every image the checks above called out, shown rather than listed. Duplicate candidates are paired side by side so you can confirm or dismiss them without opening a file browser.

{% for sheet in sheets %}

{{ sheet.title }}

{% if sheet.metadata.description %}{{ sheet.metadata.description }}{% endif %}
{% if sheet.data.layout == 'pairs' %} {% for group in sheet.data.groups %}
{% for item in group['items'] %}
{{ item.caption }}
{{ item.meta }}
{% endfor %}
{{ group.note }}
{% endfor %} {% else %} {% for group in sheet.data.groups %}

{{ group.caption }}

{{ group.note }}
{% for item in group['items'] %}
{{ item.caption }}
{{ item.meta }}
{% endfor %}
{% endfor %} {% endif %}
{% endfor %}
{% endif %} {% if result.goal == 'image_profile' and result.metadata.get('image_count') %} {% set dimension = result.evidence | selectattr('kind', 'equalto', 'image_dimension_distribution') | first %} {% set brightness = result.evidence | selectattr('kind', 'equalto', 'image_brightness_distribution') | first %} {% set label_evidence = result.evidence | selectattr('kind', 'equalto', 'image_label_distribution') | first %}
{{ sections.number('image-shape') }}

Shape, exposure, and balance

One dot per distinct image size — a single dot means the dataset already agrees on a shape, a spread means your resize policy has to make a decision.

{% if dimension and dimension.value.scatter_points %}

Width against height

{{ dimension.value.unique_dimensions | format_value }} distinct size{% if dimension.value.unique_dimensions != 1 %}s{% endif %} · dot size = image count

{{ dimension.value | image_dimension_svg }}
{% endif %} {% if brightness and brightness.value.get('histogram') %}

Brightness

median {{ brightness.value.box.median | format_cell }} · 0 is black, 1 is white

{{ brightness.value | histogram_svg }}
{% endif %}
{% if label_evidence and label_evidence.value.labels | length > 1 %}

Images per label

a class starved 5x below the largest is marked
{{ label_evidence.value.labels | label_bars_svg }}
{% endif %}
typicalflagged
{% for artifact in result.artifacts %} {% if artifact.kind == 'metric_table' %} {% set linked_to_finding = namespace(value=false) %} {% for finding in result.findings %} {% for evidence_id in artifact.evidence_ids %} {% if evidence_id in finding.evidence_ids %}{% set linked_to_finding.value = true %}{% endif %} {% endfor %} {% endfor %} {% if not linked_to_finding.value %}
{{ sections.number('metrics-' ~ loop.index0) }}

{{ artifact.title }}

{% if artifact.metadata.description %}

{{ artifact.metadata.description }}

{% endif %}
{% for column in artifact.data.columns %}{% endfor %} {% for row in artifact.data.rows %} {% for column in artifact.data.columns %}{% endfor %} {% endfor %}
{{ column.label }}
{{ row[column.key] | format_value }}
{% endif %} {% endif %} {% endfor %} {% endif %} {% set distributions = result.evidence | selectattr('kind', 'equalto', 'anomaly_distribution_shape') | list %} {% if distributions %}
{{ sections.number('distributions') }}

Distributions

Where each numeric column's values sit. Bars are row counts; the strip below each shows the median, the middle 50%, and the whiskers. Red marks the flagged values.

{% for item in distributions %}

{{ item.value.column }}

{% if item.value.modality.is_multimodal %}

Two populations — {{ item.value.modality.clusters[0].count }} row(s) up to {{ item.value.modality.clusters[0].max | format_cell }}, then {{ item.value.modality.clusters[1].count }} row(s) from {{ item.value.modality.clusters[1].min | format_cell }} up.

{% else %}

median {{ item.value.box.median | format_cell }} · middle 50% {{ item.value.box.q1 | format_cell }}–{{ item.value.box.q3 | format_cell }}

{% endif %} {{ item.value | histogram_svg }}
{% endfor %}
all rowsflagged rows
{% endif %} {% for item in result.evidence if item.kind == 'anomaly_scatter_pair' and item.value.points %}
{{ sections.number('scatter') }}

{{ item.value.y_column }} vs {{ item.value.x_column }}

Each point is a row; red points are the flagged rows. Shows whether a flagged row is also unusual in context, not just on one column.

{{ item.value | scatter_svg }}
all rowsflagged rows
{% endfor %} {% if result.goal == 'anomaly_detection' %} {% for artifact in result.artifacts %} {% if artifact.kind == 'metric_table' %}
{{ sections.number('metrics-' ~ loop.index0) }}

{{ artifact.title }}

{% if artifact.metadata.description %}

{{ artifact.metadata.description }}

{% endif %}
{% for column in artifact.data.columns %}{% endfor %} {% for row in artifact.data.rows %} {% for column in artifact.data.columns %}{% endfor %} {% endfor %}
{{ column.label }}
{{ row[column.key] | format_value }}
{% endif %} {% endfor %} {% endif %} {% for table in result.catalog.tables %}
{{ sections.number('columns-' ~ loop.index0) }}

{{ table.name }}

{% if result.goal == 'image_profile' %}

{{ table.row_count | format_value }} image files · {{ table.column_count | format_value }} manifest fields

{% else %}

{{ table.row_count | format_value }} rows · {{ table.column_count | format_value }} columns · {{ table.duplicate_row_count | format_value }} duplicate row{% if table.duplicate_row_count != 1 %}s{% endif %}

{% endif %}
{# Filter box is progressive enhancement: with JS off every card shows. #}
{{ table.columns | length }} columns
{% for column in table.columns %} {% set stats = column.statistics %} {% set distribution = column_charts.get((table.name, column.name)) %} {% set frequency = column_frequencies.get((table.name, column.name)) %} {% set timeline = column_timelines.get((table.name, column.name)) %} {% set shape = distribution.shape if distribution else none %} {% set fit = distribution.fit if distribution else none %}

{{ column.name }}

{{ column.semantic_type }} · {{ column.physical_type }}
{% if shape %}{{ shape.label }}{% endif %} {% if fit and fit.family %}fits {{ fit.label }}{% endif %} {% for role in column.roles %}{{ role | replace('_candidate', '') | replace('_', ' ') }}{% endfor %} {% if column.missing_rate >= 0.2 %}{{ "%.0f%%" | format(column.missing_rate * 100) }} missing{% endif %} {% if column.unique_count == 1 %}constant{% endif %} {% if column.unique_count is not none and column.row_count and column.unique_count == column.row_count %}all unique{% endif %}
Distinct{{ column.unique_count | format_value }}{% if column.unique_rate is not none %} · {{ "%.1f%%" | format(column.unique_rate * 100) }}{% endif %}
Missing{{ column.missing_count | format_value }} · {{ "%.1f%%" | format(column.missing_rate * 100) }}
{% if column.semantic_type == 'numeric' %}
Minimum{{ stats.get('min') | format_value }}
Maximum{{ stats.get('max') | format_value }}
Mean{{ stats.get('mean') | format_value }}
Median{{ stats.get('median') | format_value }}
Zeros{{ stats.get('zero_count') | format_value }}
Negative{{ stats.get('negative_count') | format_value }}
{% if stats.get('infinite_count') %}
Infinite{{ stats.get('infinite_count') | format_value }}
{% endif %} {% elif column.semantic_type == 'datetime' %}
Earliest{{ stats.get('min') | format_value }}
Latest{{ stats.get('max') | format_value }}
{% elif column.semantic_type == 'text' %}
Shortest{{ stats.get('min_length') | format_value }}
Longest{{ stats.get('max_length') | format_value }}
Mean length{{ stats.get('mean_length') | format_value }}
{% elif column.top_values %}
Most common{% if column.top_values[0].value is none %}(missing){% else %}{{ column.top_values[0].value | string | truncate(18, true, '…') }}{% endif %}
Its share{{ "%.1f%%" | format(column.top_values[0].count / column.row_count * 100) }}
{% endif %} {% if column.memory_bytes is not none %}
In memory{{ column.memory_bytes | filesizeformat }}
{% endif %}
{% if distribution %} {{ distribution | histogram_svg(true) }} {% if shape and shape.descriptors %}

{{ shape.descriptors | join('; ') }}

{% endif %} {% elif frequency %} {{ frequency | category_bars_svg }} {% elif timeline %} {{ timeline | timeline_svg }} {% elif column.top_values and column.row_count %}
{% for item in column.top_values[:6] %}{% if item.value is none %}(missing){% else %}{{ item.value | string | truncate(24, true, '…') }}{% endif %} {{ "%.0f%%" | format(item.count / column.row_count * 100) }}{% endfor %}
{% else %}

No chart for this column.

{% endif %}
{% for warning in column.warnings %}{{ warning }}{% endfor %}
More details
rows{{ column.row_count | format_value }} non-null{{ column.non_null_count | format_value }} {% for key, value in stats.items() %} {{ key | replace('_', ' ') }}{{ value | format_value }} {% endfor %}
{% if fit %}

{% if fit.family %} Closest standard distribution: {{ fit.label }}{% for name, value in fit.parameters.items() %}{% if loop.first %} ({% endif %}{{ name }}={{ value | format_value }}{% if loop.last %}){% else %}, {% endif %}{% endfor %} — {{ fit.quality }} fit, KS distance {{ "%.3f" | format(fit.ks_distance) }}. {% elif fit.reason == 'no_family_fits_well' %} No standard distribution fits this column well (closest is {{ fit.ranked[0].label }} at KS distance {{ "%.3f" | format(fit.best_distance) }}). {% elif fit.reason == 'too_few_values' %} Too few distinct values to fit a distribution. {% endif %} The KS distance is measured with parameters estimated from this same column, so it describes how close the fit is — it is not a hypothesis test, and no p-value is implied.

{% endif %} {% if frequency and frequency.other_category_count %}

{{ frequency.other_category_count | format_value }} further label(s) are folded into “other”.

{% endif %}
{% endfor %}
{% endfor %} {% if 'correlations' in sections %} {% set matrix = (result.evidence | selectattr('kind', 'equalto', 'profile_association_matrix') | first).value %}
{{ sections.number('correlations') }}

Correlations

How strongly each pair of columns moves together, on a 0–1 scale. Three statistics share that scale because the columns are of different types: Spearman ρ between two numbers (signed, so the cell shows direction), Cramér's V between two categories, and the correlation ratio between a category and a number. Hover a cell for the exact figure and the statistic used. Association is not causation.

{{ matrix | association_heatmap_svg }}
{% set strong = matrix.pairs | selectattr('strength', 'ge', 0.8) | list %} {% if strong %}
{% for pair in strong[:12] %} {% endfor %}
PairStrengthStatisticReading
{{ pair.left }} & {{ pair.right }} {% if pair.method == 'spearman' %}{{ "%+.2f" | format(pair.spearman) }}{% else %}{{ "%.2f" | format(pair.strength) }}{% endif %} {% if pair.method == 'spearman' %}Spearman ρ{% if pair.pearson is not none %} (Pearson {{ "%+.2f" | format(pair.pearson) }}){% endif %}{% elif pair.method == 'cramers_v' %}Cramér's V{% else %}correlation ratio{% endif %} {% if pair.strength >= 0.99 %}Effectively one variable.{% elif pair.method == 'spearman' %}Move {% if pair.spearman > 0 %}together{% else %}in opposition{% endif %}.{% else %}One largely determines the other.{% endif %}
{% else %}

No pair reaches 0.80, so no column here is a restatement of another.

{% endif %}
{% endif %} {% if 'interactions' in sections %} {% set scatters = result.evidence | selectattr('kind', 'equalto', 'profile_scatter') | list %} {% set highlights = scatters | selectattr('value.role', 'equalto', 'highlight') | list %} {% set explorer = scatters | selectattr('value.role', 'equalto', 'explorer') | list %}
{{ sections.number('interactions') }}

Interactions

Each point is a row. The pairs below are the most strongly associated ones; use the selector to plot any other pair. A correlation number says how much two columns move together — a scatter says how, which is where curves, clusters, and ceilings show up.

{% for item in highlights %}

{{ item.value.y_column }} against {{ item.value.x_column }}

association {{ "%.2f" | format(item.value.association) }}{% if item.value.point_count < item.value.available_point_count %} · {{ item.value.point_count | format_value }} of {{ item.value.available_point_count | format_value }} rows drawn{% endif %}

{{ item.value | scatter_svg(true) }}
{% endfor %}
{% if explorer %}
{% for item in explorer %}

{{ item.value.y_column }} against {{ item.value.x_column }}

association {{ "%.2f" | format(item.value.association) }} · {{ item.value.point_count | format_value }} rows drawn

{{ item.value | scatter_svg(true) }}
{% endfor %}
{% endif %}
{% endif %} {% if 'missing' in sections %} {% set missingness = (result.evidence | selectattr('kind', 'equalto', 'profile_missingness') | first).value %} {% set co_missing = result.evidence | selectattr('kind', 'equalto', 'profile_co_missingness') | list %}
{{ sections.number('missing') }}

Missing values

{{ missingness.complete_row_count | format_value }} of {{ missingness.row_count | format_value }} rows are complete. Bars past 20% are the ones the profile promotes to an issue.

{{ missingness | missing_bars_svg }}
{% if co_missing %}

Missing together

How often two columns are blank on the same row. A high overlap usually means one optional section of a form, or a field added partway through collection — which is a different problem from values lost at random.

{{ co_missing[0].value | co_missing_heatmap_svg }}
{% endif %}
{% endif %} {% if 'sample' in sections %} {% set sample = (result.evidence | selectattr('kind', 'equalto', 'profile_sample_rows') | first).value %} {% set duplicates = result.evidence | selectattr('kind', 'equalto', 'profile_duplicate_groups') | list %}
{{ sections.number('sample') }}

Sample rows

The first and last rows as they were loaded{% if sample.hidden_column_count %}, showing {{ sample.columns | length }} of {{ sample.columns | length + sample.hidden_column_count }} columns{% endif %}. Every summary above is a claim about these values; this is what they actually look like.

{% for name in sample.columns %}{% endfor %} {% for row in sample.head %} {% for value in row['values'] %}{% endfor %} {% endfor %} {% if sample.tail %} {% for name in sample.columns %}{% endfor %} {% for row in sample.tail %} {% for value in row['values'] %}{% endfor %} {% endfor %} {% endif %}
#{{ name }}
{{ row.index }}{{ value | format_cell }}
{{ row.index }}{{ value | format_cell }}
{% if duplicates %}

Duplicated rows

{{ duplicates[0].value.duplicate_row_count | format_value }} row(s) repeat an earlier row exactly. Examples:

{% for name in duplicates[0].value.columns %}{% endfor %} {% for group in duplicates[0].value.groups %} {% for value in group['values'] %}{% endfor %} {% endfor %}
CopiesRow indexes{{ name }}
{{ group.row_count }}{{ group.indexes | join(', ') }}{{ value | format_cell }}
{% endif %}
{% endif %} {% if result.transformation_plan.steps %}
{{ sections.number('plan') }}

Reviewable transformation plan

These are recommendations only. Prism EDA has not modified the data.

{% for step in result.transformation_plan.steps %} {% set risk = step.risk if step.risk in ['high', 'medium', 'low'] else 'medium' %}
{{ step.risk }}

{{ step.operation | replace('_', ' ') | title }}

{{ step.table }}{% if step.columns %}.{{ step.columns | join(', ') }}{% endif %}: {{ step.rationale }}

{% endfor %}
{% endif %}