Ndewo β€” Welcome to your data story

{{ metadata.pipeline_name }}

{{ metadata.run_id }}
{{ metadata.started_at }}
{{ "%.2f"|format(metadata.duration) }}s
{% if metadata.project %}{{ metadata.project }}{% endif %} {% if metadata.plant %} / {{ metadata.plant }}{% endif %}
{% if metadata.git_info and metadata.git_info.commit != 'unknown' %}
{{ metadata.git_info.branch }} @ {{ metadata.git_info.commit }}
{% endif %}
{{ metadata.total_nodes }}
Nodes
{{ metadata.completed_nodes }}
Completed
{{ metadata.failed_nodes }}
Failed
{{ "{:,}".format(metadata.get_total_rows_processed()) }}
Total Rows
{{ "%.1f"|format(metadata.get_success_rate()) }}%
Success Rate
{% set health = metadata.get_run_health_summary() %} {% if health.has_failures %}

⚠️ Run Failed - {{ health.failed_count }} node{% if health.failed_count > 1 %}s{% endif %} failed

{% for node_name in health.failed_nodes %} {{ node_name }} {% endfor %}
{% if health.first_failure_error %}
{{ health.first_failure_type }}: {{ health.first_failure_node }}
{{ health.first_failure_error }}
{% endif %}
{% elif health.anomaly_count > 0 %}

⚑ Run Succeeded with {{ health.anomaly_count }} Anomal{% if health.anomaly_count > 1 %}ies{% else %}y{% endif %}

{% for node_name in health.anomalous_nodes %} {{ node_name }} {% endfor %}
{% else %}

βœ… Run Completed Successfully

{% endif %} {% if metadata.change_summary and metadata.change_summary.has_changes %}

πŸ“Š Changes vs Last Success

Compared to previous run ({{ metadata.compared_to_run_id | format_run_id }})
{% if metadata.change_summary.newly_failing_count > 0 %}
{{ metadata.change_summary.newly_failing_count }}
Newly Failing
{{ metadata.change_summary.newly_failing_nodes | join(', ') }}
{% endif %} {% if metadata.change_summary.sql_changed_count > 0 %}
{{ metadata.change_summary.sql_changed_count }}
SQL Changed
{{ metadata.change_summary.sql_changed_nodes | join(', ') }}
{% endif %} {% if metadata.change_summary.schema_changed_count > 0 %}
{{ metadata.change_summary.schema_changed_count }}
Schema Changed
{{ metadata.change_summary.schema_changed_nodes | join(', ') }}
{% endif %} {% if metadata.change_summary.rows_changed_count > 0 %}
{{ metadata.change_summary.rows_changed_count }}
Row Count Changed
{{ metadata.change_summary.rows_changed_nodes | join(', ') }}
{% endif %}
{% elif metadata.compared_to_run_id %}
βœ“ No significant changes vs previous run ({{ metadata.compared_to_run_id | format_run_id }})
{% endif %} {% set quality = metadata.get_data_quality_summary() %} {% set freshness = metadata.get_freshness_info() %} {% if quality.has_quality_issues or quality.top_null_columns %}

πŸ” Data Quality Summary

{% if freshness %} πŸ“… Data as of: {{ freshness.formatted }} ({{ freshness.column }} in {{ freshness.node }}) {% endif %}
{% if quality.total_validations_failed > 0 %}
{{ quality.total_validations_failed }}
Validations Failed
{% endif %} {% if quality.total_failed_rows > 0 %}
{{ "{:,}".format(quality.total_failed_rows) }}
Total Failed Rows
{% endif %} {% if quality.nodes_with_warnings %}
{{ quality.nodes_with_warnings|length }}
Nodes with Warnings
{{ quality.nodes_with_warnings[:3] | join(', ') }}{% if quality.nodes_with_warnings|length > 3 %}...{% endif %}
{% endif %}
{% if quality.top_null_columns %}

Top Columns by Null %

{% for col in quality.top_null_columns[:5] %} {{ col.column }} {{ "%.0f"|format(col.null_pct * 100) }}% ({{ col.node }}) {% endfor %}
{% endif %}
{% elif freshness %}
βœ“ Data Quality: No issues detected πŸ“… Data as of: {{ freshness.formatted }}
{% endif %} {% set changed_count = metadata.change_summary.sql_changed_count + metadata.change_summary.schema_changed_count if metadata.change_summary else 0 %}
{% if changed_count > 0 %} {% endif %}
{% set node_count = metadata.nodes|length %}

Pipeline Flow ({{ node_count }} nodes)

Click node for details
β–Ό
{% for node in metadata.nodes %}
{% if node.status == 'success' %}βœ“{% elif node.status == 'failed' %}!{% else %}-{% endif %}
{{ node.node_name }} {% if node.description %}
{{ node.description }}
{% endif %}
{{ node.operation }} {% if node.is_anomaly %} {% if node.is_slow %} 🐒 Slow {% endif %} {% if node.has_row_anomaly %} πŸ“Š Row Ξ” {% endif %} {% endif %} {% if node.changed_from_last_success %} Ξ” {{ node.changes_detected|join(', ') }} {% endif %} {% if node.runbook_url and node.status == 'failed' %} Troubleshooting guide β†’ {% endif %}
{% if node.rows_in is not none %} πŸ“₯ {{ "{:,}".format(node.rows_in) }} {% endif %} {% if node.rows_written is not none %} πŸ“€ {{ "{:,}".format(node.rows_written) }}{% if node.rows_in is not none and node.rows_written == 0 and node.rows_in > 0 %} (no changes){% endif %} {% elif node.rows_out is not none %} {{ "{:,}".format(node.rows_out) }} rows {% endif %} {% if node.rows_change is not none and node.rows_change != 0 %} {% if node.rows_change > 0 %}+{% endif %}{{ "{:,}".format(node.rows_change) }} {% endif %} {{ "%.4f"|format(node.duration) }}s {% if node.duration_history and node.duration_history|length > 1 %} {% endif %}
{% if node.executed_sql %} {% endif %} {% if node.sample_data or node.sample_in or node.data_diff %} {% endif %} {% if node.config_snapshot %} {% endif %}
{% if node.explanation %}
β–Ό πŸ“ Transformation Explanation
{{ node.explanation | markdown | safe }}
{% endif %} {% if node.column_drop_warning %}
⚠️ {{ node.column_drop_warning }}
{% endif %} {% if node.validation_warnings %}
Validation Warnings:
    {% for warning in node.validation_warnings %}
  • {{ warning }}
  • {% endfor %}
{% endif %} {% if node.error_message %}
Error: {{ node.error_type }}
{{ node.error_message }}
{% if node.error_traceback_cleaned %}
Show Traceback (Cleaned)
{{ node.error_traceback_cleaned }}
{% endif %} {% if node.error_traceback %}
Show Full Traceback (Raw)
{{ node.error_traceback }}
{% endif %}
{% endif %} {% if node.execution_steps %}
πŸ”§ Execution Steps ({{ node.execution_steps|length }})
    {% for step in node.execution_steps %}
  1. {{ step }}
  2. {% endfor %}
{% endif %} {% if node.retry_history and node.retry_history|length > 1 %}
πŸ”„ Retry History ({{ node.retry_history|length }} attempts)
{% for attempt in node.retry_history %} {% endfor %}
Attempt Status Duration Error
#{{ attempt.attempt }} {% if attempt.success %} βœ“ Success {% else %} βœ— Failed {% endif %} {{ attempt.duration }}s {% if attempt.error %}
{{ attempt.error|truncate(80) }}
{{ attempt.error_traceback or attempt.error }}
{% else %}-{% endif %}
{% endif %} {% if node.failed_rows_samples %}
❌ Failed Rows Samples ({{ node.failed_rows_samples|length }} validation{% if node.failed_rows_samples|length > 1 %}s{% endif %}) {% if node.failed_rows_truncated %}(truncated){% endif %}
{% for validation_name, rows in node.failed_rows_samples.items() %}
{{ validation_name }} {% if node.failed_rows_counts and node.failed_rows_counts[validation_name] %} ({{ node.failed_rows_counts[validation_name] }} total failed) {% endif %}
{% if rows|length > 0 %}
{% for key in rows[0].keys() %}{% endfor %} {% for row in rows %} {% for val in row.values() %}{% endfor %} {% endfor %}
{{ key }}
{{ val }}
{% else %}

No sample data available

{% endif %}
{% endfor %} {% if node.truncated_validations %}
{{ node.truncated_validations|length }} more validation{% if node.truncated_validations|length > 1 %}s{% endif %} failed (showing counts only):
    {% for val_name in node.truncated_validations %}
  • {{ val_name }}: {{ node.failed_rows_counts.get(val_name, 'N/A') }} failed rows
  • {% endfor %}
{% endif %}
{% endif %} {% if node.source_files %}

Source Files ({{ node.source_files|length }})

{% for file in node.source_files %}
{{ file }}
{% endfor %}
{% endif %}

Input Schema ({% if node.schema_in %}{{ node.schema_in|length }}{% else %}0{% endif %})

    {% if node.schema_in %} {% if node.schema_in is mapping %} {% for col, dtype in node.schema_in.items() %}
  • {{ col }} {{ dtype }}
  • {% endfor %} {% else %} {% for col in node.schema_in %}
  • {{ col }}
  • {% endfor %} {% endif %} {% else %}
  • No input schema
  • {% endif %}

Output Schema ({% if node.schema_out %}{{ node.schema_out|length }}{% else %}0{% endif %})

    {% if node.schema_out %} {% if node.schema_out is mapping %} {% for col, dtype in node.schema_out.items() %}
  • {{ col }} {% if col in node.columns_added %}(NEW){% endif %} {% if node.null_profile %} {% set null_pct = node.null_profile.get(col, 0.0) or 0.0 %} {% if null_pct > 0 %} {{ "%.0f"|format(null_pct * 100) }}% Null {% endif %} {% endif %}
    {{ dtype }}
  • {% endfor %} {% else %} {% for col in node.schema_out %}
  • {{ col }} {% if col in node.columns_added %}(NEW){% endif %} {% if node.null_profile %} {% set null_pct = node.null_profile.get(col, 0.0) or 0.0 %} {% if null_pct > 0 %} {{ "%.0f"|format(null_pct * 100) }}% Null {% endif %} {% endif %}
  • {% endfor %} {% endif %} {% else %}
  • No output schema
  • {% endif %}
{% if node.column_statistics %}
πŸ“Š Column Statistics ({{ node.column_statistics|length }} columns)
{% for col_name, stats in node.column_statistics.items() %} {% endfor %}
Column Min Max Mean Std Dev
{{ col_name }} {{ stats.min if stats.min is not none else '-' }} {{ stats.max if stats.max is not none else '-' }} {{ "%.2f"|format(stats.mean) if stats.mean is not none else '-' }} {{ "%.2f"|format(stats.stddev) if stats.stddev is not none else '-' }}
{% endif %} {% if node.delta_info %}

Delta Lake Write

v{{ node.delta_info.version }}
Operation
{{ node.delta_info.operation }}
Timestamp
{{ node.delta_info.timestamp }}
{% if node.delta_info.operation_metrics %} {% set metrics = node.delta_info.operation_metrics %} {% set inserted = metrics.numTargetRowsInserted or metrics.numOutputRows or metrics.num_added_rows %} {% set updated = metrics.numTargetRowsUpdated or metrics.num_updated_rows %} {% set deleted = metrics.numTargetRowsDeleted or metrics.num_deleted_rows %} {% set files = metrics.numAddedFiles or metrics.numFilesAdded or metrics.num_added_files %} {% if inserted %}
Rows Inserted
+{{ inserted }}
{% endif %} {% if updated %}
Rows Updated
~{{ updated }}
{% endif %} {% if deleted %}
Rows Deleted
-{{ deleted }}
{% endif %} {% if not inserted and not updated and not deleted %}
Files Added
{{ files }}
{% endif %} {% endif %}
{% endif %}
{% if node.executed_sql %}

πŸ’Ύ Executed SQL ({{ node.executed_sql|length }} statement{% if node.executed_sql|length > 1 %}s{% endif %})

{% for sql in node.executed_sql %}
Statement #{{ loop.index }} {% if sql|length > 100 %} - {{ sql[:50]|replace('\n', ' ') }}... {% endif %}
{{ sql }}
{% endfor %} {% if node.sql_hash %}
SQL Hash: {{ node.sql_hash }}
{% endif %}
{% endif %} {% if node.sample_data or node.sample_in or node.data_diff %}
{% if node.data_diff %}

πŸ“‰ Changes vs Previous Version {% if node.delta_info and node.delta_info.read_version is not none %} (v{{ node.delta_info.version }} vs v{{ node.delta_info.version - 1 }}) {% endif %}

Net Change: {{ node.data_diff.rows_change }}
{% if node.data_diff.rows_added is defined and node.data_diff.rows_added is not none %}
Added: {{ node.data_diff.rows_added }}
{% endif %} {% if node.data_diff.rows_updated is defined and node.data_diff.rows_updated is not none %}
Updated: {{ node.data_diff.rows_updated }}
{% endif %} {% if node.data_diff.rows_removed is defined and node.data_diff.rows_removed is not none %}
Removed: {{ node.data_diff.rows_removed }}
{% endif %} {% if node.data_diff.rows_added is not defined and node.data_diff.rows_updated is not defined and node.data_diff.rows_removed is not defined %}
(Enable deep diff for detailed breakdown)
{% endif %}
{% if node.data_diff.schema_previous is not none %}
πŸ“‹ Schema Evolution
{% if not node.data_diff.schema_added and not node.data_diff.schema_removed %}
No schema changes detected vs previous run.
{% else %}
{% if node.data_diff.schema_added %}
+ ADDED {{ node.data_diff.schema_added|join(', ') }}
{% endif %} {% if node.data_diff.schema_removed %}
- REMOVED {{ node.data_diff.schema_removed|join(', ') }}
{% endif %}
{% endif %}
{% endif %} {% if node.data_diff.sample_updated %}
UPDATED ROWS (Values changed from previous run)
{% for row in node.data_diff.sample_updated %} {% endfor %}
Key(s) Changes
{% for k, v in row.items() if k != '_changes' %}
{{ k }}: {{ v }}
{% endfor %}
{% for col, change in row._changes.items() %}
{{ col }}: {{ change.old }}{{ change.new }}
{% endfor %}
{% endif %} {% if node.data_diff.sample_added %}
ADDED ROWS (New in this run)
{% for key in node.data_diff.sample_added[0].keys() %}{% endfor %} {% for row in node.data_diff.sample_added %} {% for val in row.values() %}{% endfor %} {% endfor %}
{{ key }}
{{ val }}
{% endif %} {% if node.data_diff.sample_removed %}
REMOVED ROWS (Existed in previous run, gone now)
{% for key in node.data_diff.sample_removed[0].keys() %}{% endfor %} {% for row in node.data_diff.sample_removed %} {% for val in row.values() %}{% endfor %} {% endfor %}
{{ key }}
{{ val }}
{% endif %}
{% endif %} {% if node.sample_in and node.sample_data %} {% set in_cols = node.sample_in[0].keys()|list|length if node.sample_in|length > 0 else 0 %} {% set out_cols = node.sample_data[0].keys()|list|length if node.sample_data|length > 0 else 0 %} {% set max_cols = [in_cols, out_cols]|max %} {% if max_cols <= 6 %} {% set layout_style = "display: grid; grid-template-columns: 1fr 1fr; gap: 20px;" %} {% else %} {% set layout_style = "display: flex; flex-direction: column; gap: 20px;" %} {% endif %}

BEFORE Input Sample ({{ node.sample_in|length }} rows)

{% if node.sample_in|length > 0 %} {% for key in node.sample_in[0].keys() %} {% endfor %} {% endif %} {% for row in node.sample_in %} {% for key, val in row.items() %}{% endfor %} {% endfor %}
{{ key }}{% if key in node.columns_removed %} (DROPPED){% endif %}
{{ val }}

AFTER Output Sample ({{ node.sample_data|length }} rows)

{% if node.sample_data|length > 0 %} {% for key in node.sample_data[0].keys() %} {% endfor %} {% endif %} {% for row in node.sample_data %} {% for key, val in row.items() %}{% endfor %} {% endfor %}
{{ key }}{% if key in node.columns_added %} (NEW){% endif %}
{{ val }}
{% elif node.sample_in %}

Input Sample

{% if node.sample_in|length > 0 %} {% for key in node.sample_in[0].keys() %} {% endfor %} {% endif %} {% for row in node.sample_in %} {% for key, val in row.items() %}{% endfor %} {% endfor %}
{{ key }}
{{ val }}
{% elif node.sample_data %}

Output Sample

{% if node.sample_data|length > 0 %} {% for key in node.sample_data[0].keys() %} {% endfor %} {% endif %} {% for row in node.sample_data %} {% for key, val in row.items() %}{% endfor %} {% endfor %}
{{ key }}
{{ val }}
{% else %}

No sample data available.

{% endif %}
{% endif %} {% if node.config_snapshot %}
{% if node.environment %}
Execution Environment
Host: {{ node.environment.host }}
User: {{ node.environment.user }}
Python: {{ node.environment.python }}
Platform: {{ node.environment.platform }}
{% if node.environment.pandas %}
Pandas: {{ node.environment.pandas }}
{% endif %} {% if node.environment.pyspark %}
PySpark: {{ node.environment.pyspark }}
{% endif %} {% if node.environment.odibi %}
Odibi: {{ node.environment.odibi }}
{% endif %}
{% endif %} {% if node.previous_config_snapshot %}

πŸ“‹ Config Changes vs Last Run


                        

Current Configuration

{% endif %}
{{ node.config_snapshot | to_yaml }}
{% endif %}
{% endfor %}
"Where others saw gaps, I built bridges."
Odibi v{{ odibi_version }} Β· Henry Odibi