Back to Projects
Data Visualization2025

Repack Cause Dashboard

Manufacturing & Packaging Operation (proprietary, name withheld)

A Power BI Case Study in ETL, Data Modeling, DAX & Visualization

<3 months~70%End-to-End
to reduce the pace of new repack creationreduction in on-hold inventory within 6 monthssolution: source data → ETL → model → DAX → report

Executive Summary

The Repack Cause Dashboard is a Power BI solution I designed and built end-to-end to solve a persistent, expensive problem in a manufacturing and packaging environment: finished cartons that had to be reworked ("repacked") after a quality issue, with no visibility into which quality events were actually driving that rework. The source system destroyed the evidence every time it deleted the original carton record and created a new one as part of the repack process. I traced that lineage back from the system's own audit log, rebuilt it as a governed data model, and delivered a report that gave Operations and Quality leadership their first real view into repack root causes. Within three months, the pace of new repack creation had slowed. Within six months, on-hold inventory was down roughly 70%.

The Problem

What a Repack Is

The product is a precision-manufactured component, produced in batches and packed into cartons for shipment to downstream assembly customers. Each carton typically pools components from several different production batches. When quality testing on a batch comes back after the fact and it's downgraded, found not to meet first-grade quality, every carton containing components from that batch has to be opened back up: the affected components pulled, replacements inserted, and the carton re-packed and re-sealed. That rework cycle is a "repack," and it is expensive. It consumes packing capacity that would otherwise go toward new production.

Why It Was Getting Worse

The core challenge wasn't building charts. It was recovering data that the source system was actively destroying, before any of the reporting could happen.

Approach

1. Discovery & Business Case

I started by interviewing Operations leadership, the Quality team, and Supply Chain to confirm the pain points and understand how each group experienced the problem differently. Operations cared about shippable capacity, Quality cared about root-cause trends, and Supply Chain cared about backlog and cash. That input became the business case for the project. From there I worked with IT to identify where the relevant data actually lived across the source systems.

2. Recovering the Repack Lineage

The central technical challenge was reconstructing a link the source system was designed to erase. I found that every repack and every quality-hold action left an entry in the system's audit log, a free-text description field never intended for analytics, written for human operators, not for reporting. By studying the structure of that text, I identified a repeatable pattern: entries tagged as a quality hold embedded the event ID and its reason code directly in the sentence; entries tagged as a hold-and-repack embedded a human-written reason. I built logic to parse that text and extract those values reliably, effectively rebuilding the deleted connection between a carton and the event that caused its rework.

That alone wasn't enough, though. A single carton could be tied to more than one quality event, several components from several different problem batches, each logged separately. So the model had to resolve, per carton, the complete set of causes, and it had to still produce a result for repacked cartons where the cause couldn't be confidently matched from the text, so that no carton silently disappeared from the analysis. I addressed this with a dedicated summary layer that consolidates every matched cause per carton and falls back to an explicit "unresolved" placeholder when no match exists, guaranteeing complete coverage of every repacked carton.

3. Data Architecture

Below the reporting layer, I built a lean extraction pipeline pulling directly from the production database, with filtering and column selection pushed down into native SQL rather than done in Power BI, keeping refreshes fast and avoiding pulling years of irrelevant log history into memory. On top of that, I designed a relational model connecting five core entities: the carton master record, the audit log, the quality-event log, and a component-level table with its own event-linking bridge, allowing the analysis to trace impact all the way down to which specific components, and how much weight, were affected by a given quality event.

4. Semantic Layer & DAX

With the lineage-recovery text-parsing logic and the model relationships in place, I layered in the business logic: classification of repacks as manual vs. system-driven, production-line/asset groupings, turnaround-time calculations, and a measure quantifying how many cartons a single quality event typically affects, a useful signal for spotting events with outsized downstream impact. Full DAX and Power Query code for all of this is included in the Technical Appendix.

5. Visualization

I designed the report as five pages moving from operational to diagnostic: a primary landing page showing overall quality impact (including an AI-generated narrative summary of quality event notes), a "last 7 days" operational view for daily use, trend pages for repack creation and for non-quality-driven repacks specifically, and an event-trends page for spotting whether specific production lines were generating disproportionate quality issues versus disproportionate rework volume. Consistent slicers for date range, production line, and reason code tie the pages together, with a shared detail table available for drill-down.

6. Delivery

I presented the finished dashboard to Operations, Quality, and technical stakeholders, walking through how to read repack causes, how to use the trend pages to prioritize root-cause work, and how the tool removed the guesswork that previously drove over-repacking.

Impact

Skills & Tools

CapabilityDemonstrated By
ETL / Data EngineeringDesigning a production-database-to-Power-BI pipeline with server-side filtering and incremental extraction windows
Relational Database DesignModeling a multi-entity schema with bridge tables resolving many-to-many relationships between cartons, components, and quality events
Advanced DAXText-mining calculated columns to recover deleted data lineage; a fully DAX-authored calculated table with set-based logic and complete-coverage guarantees
Data Reverse-EngineeringIndependently identifying and exploiting a recoverable pattern in an undocumented system audit log
Data Visualization / UX DesignA five-page report structured to move users from operational triage to root-cause diagnosis
Stakeholder ManagementOwning the project from initial business-case interviews through final delivery to Operations, Quality, and technical audiences

Full DAX and Power Query source code referenced throughout this case study is provided in the Technical Appendix below. Table names, column names, and internal codes have been generalized throughout for external sharing.


Technical Appendix: Code Reference

This appendix contains the underlying Power Query (M) and DAX code referenced narratively above, for technical reviewers who want to inspect the implementation directly. Table/column names and internal codes have been generalized; the logic and structure are unchanged from the production implementation.

A.1 Power Query (M): Source Extraction

Carton

let
    Source = Sql.Database("LegacyMES", "ProductionHistoryDB", [Query =
        "SELECT release_code, blend_code, production_line, carton_id, repack_date,
         quality_grade, repack_required_flag, pack_date, earliest_component_date,
         latest_component_date, archived_date, ready_to_ship_date
         FROM dbo.Carton
         WHERE pack_date >= '2025-01-01 12:00:00 AM'
         AND total_tare_weight_kg > 0
         AND quality_grade IN ('Grade_A', 'Grade_B')"])
in
    Source

Carton_Audit_Log

let
    Source = Sql.Database("LegacyMES", "ProductionHistoryDB", [Query =
        "SELECT log_type, log_description, carton_id, log_date
         FROM dbo.Carton_Audit_Log
         WHERE log_date > '2024-07-01'
         AND log_type IN ('HoldAndReworkLogged','HoldLogged')"])
in
    Source

Quality_Event

let
    Source = Sql.Database("LegacyMES", "ProductionHistoryDB", [Query =
        "SELECT end_date, event_date, event_notes, insert_date, start_date,
         quality_event_id, sub_reason_code, update_date, event_status,
         event_ref_id, reason_code
         FROM dbo.Quality_Event
         WHERE start_date >= '2024-07-01 12:00:00 AM' AND current_flag = 'Y'"]),
    #"Changed Type" = Table.TransformColumnTypes(Source,
        {{"quality_event_id", type text}, {"event_ref_id", type text}})
in
    #"Changed Type"

Component

let
    Source = Sql.Database("LegacyMES", "ProductionHistoryDB", [Query =
        "SELECT component_id, production_date, carton_id, carton_grade,
         component_grade, original_grade
         FROM dbo.Component
         WHERE production_date >= '2024-07-01 12:00:00 AM'"])
in
    Source

Component_Quality_Link

let
    Source = Sql.Database("LegacyMES", "ProductionHistoryDB", [Query =
        "SELECT component_id, unit_weight, quality_event_id, event_ref_id
         FROM dbo.Component_Quality_Link
         WHERE event_date >= '2024-07-01'
         AND quality_event_id IS NOT NULL"]),
    #"Changed Type" = Table.TransformColumnTypes(Source,
        {{"quality_event_id", type text}, {"event_ref_id", type text}})
in
    #"Changed Type"

A.2 DAX: Repack Lineage Recovery (Carton_Audit_Log calculated columns)

Quality_Event_ID — extracting the quality event identifier

Quality_Event_ID =
IF (
    'Carton_Audit_Log'[log_type] = "HoldLogged",
    VAR StartPos = SEARCH("Event ", 'Carton_Audit_Log'[log_description], , 0)
    RETURN
        IF (
            StartPos > 0,
            MID('Carton_Audit_Log'[log_description], StartPos + 6, 6),
            BLANK()
        ),
    BLANK()
)

Quality_Reason_Code — extracting the reason code

Quality_Reason_Code =
IF (
    'Carton_Audit_Log'[log_type] = "HoldLogged",
    VAR StartPos = SEARCH("Reason Code", 'Carton_Audit_Log'[log_description], , 0)
    VAR WordStart = StartPos + LEN("Reason Code") + 1
    VAR RemainingText = MID('Carton_Audit_Log'[log_description], WordStart, LEN('Carton_Audit_Log'[log_description]))
    VAR NextSpace = SEARCH(".", RemainingText, , 0)
    VAR Word =
        IF (
            NextSpace > 0,
            LEFT(RemainingText, NextSpace - 1),
            RemainingText
        )
    RETURN Word,
    BLANK()
)

Repack_Reason — extracting the human-written repack reason

Repack_Reason =
IF (
    'Carton_Audit_Log'[log_type] = "HoldAndReworkLogged",
    VAR SourceText = 'Carton_Audit_Log'[log_description]
    VAR Tag = "reason: "
    VAR TagPos = SEARCH( Tag, SourceText, , 0 )
    VAR AfterTagPos = TagPos + LEN( Tag )
    VAR Tails = IF ( TagPos > 0, MID ( SourceText, AfterTagPos, LEN ( SourceText ) ), BLANK() )
    VAR AndPos = IF ( NOT ISBLANK ( Tails ), SEARCH ( " and", Tails, , 0 ), 0 )
    VAR Extracted = IF ( AndPos > 0, LEFT ( Tails, AndPos - 1 ), Tails )
    RETURN TRIM ( Extracted ),
    BLANK()
)

Grade_Override_Applied — flagging grade-mix overrides

Grade_Override_Applied =
IF (
    Carton_Audit_Log[log_type] = "HoldLogged"
        && SEARCH ( "grade override applied", LOWER ( Carton_Audit_Log[log_description] ), , 0 ) > 0,
    1,
    BLANK()
)

A.3 DAX: Carton_Repack_Summary (Calculated Table)

The bridge table that consolidates every matched cause per carton and guarantees a row for every repacked carton, even when no cause could be text-matched:

Carton_Repack_Summary =
VAR CartonsWithRepack =
    FILTER (
        SUMMARIZE ( Carton_Audit_Log, Carton_Audit_Log[carton_id] ),
        CALCULATE (
            COUNTROWS ( FILTER ( Carton_Audit_Log, Carton_Audit_Log[log_type] = "HoldAndReworkLogged" ) )
        ) > 0
    )
VAR OverrideRows =
    FILTER (
        Carton_Audit_Log,
        Carton_Audit_Log[log_type] = "HoldLogged"
            && Carton_Audit_Log[Grade_Override_Applied] = 1
    )
-- A) Matched cause rows, for cartons that also have a Repack
VAR A_WithOverride =
    ADDCOLUMNS (
        SELECTCOLUMNS (
            FILTER (
                OverrideRows,
                VAR Cart = Carton_Audit_Log[carton_id]
                RETURN
                    CALCULATE (
                        COUNTROWS (
                            FILTER ( ALL ( Carton_Audit_Log ),
                                Carton_Audit_Log[carton_id] = Cart
                                    && Carton_Audit_Log[log_type] = "HoldAndReworkLogged"
                            )
                        )
                    ) > 0
            ),
            "carton_id", Carton_Audit_Log[carton_id],
            "quality_event_id", Carton_Audit_Log[Quality_Event_ID],
            "quality_reason_code", Carton_Audit_Log[Quality_Reason_Code],
            "quality_event_date", Carton_Audit_Log[log_date]
        ),
        "first_repack_date",
            VAR C1 = [carton_id]
            RETURN
                CALCULATE (
                    MIN ( Carton_Audit_Log[log_date] ),
                    FILTER ( ALL ( Carton_Audit_Log ),
                        Carton_Audit_Log[carton_id] = C1
                            && Carton_Audit_Log[log_type] = "HoldAndReworkLogged" )
                ),
        "repack_cause_count",
            VAR C2 = [carton_id]
            RETURN
                CALCULATE (
                    COUNTROWS ( Carton_Audit_Log ),
                    FILTER ( ALL ( Carton_Audit_Log ),
                        Carton_Audit_Log[carton_id] = C2
                            && Carton_Audit_Log[log_type] = "HoldAndReworkLogged" )
                )
    )
-- B) Repacked cartons with NO matched cause, one placeholder row each
VAR CartonsWithOverride = SUMMARIZE ( OverrideRows, Carton_Audit_Log[carton_id] )
VAR CartonsNeedingPlaceholder = EXCEPT ( CartonsWithRepack, CartonsWithOverride )
VAR B_NoOverride_Placeholders =
    ADDCOLUMNS (
        CartonsNeedingPlaceholder,
        "quality_event_id", BLANK (),
        "quality_reason_code", BLANK (),
        "quality_event_date", BLANK (),
        "first_repack_date",
            VAR C3 = [carton_id]
            RETURN
                CALCULATE (
                    MIN ( Carton_Audit_Log[log_date] ),
                    FILTER ( ALL ( Carton_Audit_Log ),
                        Carton_Audit_Log[carton_id] = C3
                            && Carton_Audit_Log[log_type] = "HoldAndReworkLogged" )
                ),
        "repack_cause_count",
            VAR C4 = [carton_id]
            RETURN
                CALCULATE (
                    COUNTROWS ( Carton_Audit_Log ),
                    FILTER ( ALL ( Carton_Audit_Log ),
                        Carton_Audit_Log[carton_id] = C4
                            && Carton_Audit_Log[log_type] = "HoldAndReworkLogged" )
                )
    )
RETURN
    UNION ( A_WithOverride, B_NoOverride_Placeholders )

quality_reason_code_simplified — reason code normalization

quality_reason_code_simplified =
VAR Code = TRIM ( [quality_reason_code] )
VAR LastChar = RIGHT ( Code, 1 )
RETURN
    IF (
        Code <> BLANK ()
            && LastChar >= "0"
            && LastChar <= "9",
        LEFT ( Code, LEN ( Code ) - 1 ),
        Code
    )

A.4 DAX: Supporting Attributes & Measures

Asset_Group — production-line grouping

Asset_Group =
SWITCH(
    TRUE(),
    'Carton'[production_line] IN {"Line_01","Line_02","Line_03","Line_04","Line_05","Line_06","Line_07","Line_08","Line_09","Line_10"}, "Asset_1",
    'Carton'[production_line] IN {"Line_11","Line_12","Line_13","Line_14"}, "Asset_2",
    'Carton'[production_line] IN {"Line_15","Line_16","Line_17","Line_18","Line_19","Line_20","Line_21","Line_22"}, "Asset_3",
    'Carton'[production_line] IN {"Line_23"}, "Asset_4",
    BLANK()
)

Repack_Method — manual vs. system classification

Repack_Method =
IF (
    'Carton'[production_line] IN {"Line_15","Line_16", "Line_17", "Line_18"}
        || 'Carton'[release_code] IN {"Code_A", "Code_B"},
    "2.Manual",
    "1.System"
)

Days_ReadyToShip_to_Archived — turnaround time

Days_ReadyToShip_to_Archived = Carton[archived_date].[Date] - Carton[ready_to_ship_date].[Date]

Component grade-change flags

component_grade_changed = IF(NOT(Component[component_grade] = Component[original_grade]), 1, 0)
component_carton_grade_diff = IF(NOT(Component[component_grade] = Component[carton_grade]), 1, 0)
Is_Grade_A = IF((Component[component_grade] = "Grade_A"), 1, 0)
Is_Grade_B = IF((Component[component_grade] = "Grade_B"), 1, 0)
Is_Downgraded = 1 - (Component[Is_Grade_A] + Component[Is_Grade_B])

Repacks per Quality Event — measure

Repacks per Quality Event = DISTINCTCOUNT(Carton_Repack_Summary[carton_id]) / DISTINCTCOUNT(Carton_Repack_Summary[quality_event_id])

A.5 Relationship Reference

FromToCardinalityCross-filterActive
Component_Quality_Link[component_id]Component[component_id]Many : 1Both directionsYes
Component_Quality_Link[event_ref_id]Quality_Event[event_ref_id]Many : 1Both directionsYes
Component[carton_id]Carton[carton_id]Many : 1Both directionsNo (inactive)
Carton_Audit_Log[carton_id]Carton[carton_id]Many : 1Both directionsYes
Carton_Repack_Summary[carton_id]Carton[carton_id]Many : 1Both directionsYes
Carton_Repack_Summary[quality_event_id]Quality_Event[quality_event_id]Many : 1Both directionsYes