How I turn engineering questions into data analysis.
This portfolio is deliberately detailed. Instead of listing software and claiming skills, I show what I did: the question I started with, what the source looked like, how I cleaned and modelled it, the SQL or reporting logic I used, how I tested the result and what decision the output supports.
The same process across every case study.
The tools change from project to project. The analytical discipline does not.
Start with the decision
I write the question before touching the dashboard. If I cannot explain what somebody should be able to decide differently after the analysis, I do not have a useful project yet.
Understand the source
I check who produced the data, what one row represents, the period covered, units, revision notes, missing values and anything in the source definition that could change the interpretation.
Reshape the raw information
I separate fields that have been buried inside labels, convert dates and numbers to controlled types, create keys and dimensions, remove duplicates only when I can explain why they are duplicates and preserve missing data as missing.
Build the analytical model
I organise the cleaned data around the question: activity + month for price analysis, activity + reporting snapshot for project controls, or information event + package + discipline for RFI/submittal reporting.
Query, calculate and visualise
I use SQL, Power Query, DAX or browser visualisation depending on the job. The calculation stays visible so a result can be traced back to the source rows.
Validate before interpreting
I test row counts, keys, date logic, expected ranges and source totals. Only after the checks pass do I write the finding and explain the limitation.
The Approval Gap
Why plans approved and buildings completed can tell very different stories about the construction pipeline — and why I refused to call a same-period ratio a conversion rate.
A building plan approved today is not a building completed today. The two statistics sit at different stages of the construction pipeline, so comparing them without thinking about time can create a false “conversion” story.
I wanted to measure how the upstream approval side and downstream completion side were moving, then test whether the available monthly history was strong enough to support a lag conclusion.
The useful result is partly what I did not claim.
The same-period completed/plans value for Jan–May 2026 is 47.7%, but I do not call that a conversion rate. The completed buildings generally come from approvals in earlier periods. I tested lags from 0 to 6 months on the 17-month real series; the short window does not show a convincing delayed peak, so the next correct step is to import longer history rather than invent a 9- or 12-month lag.
The Bitumen Effect
Why two legitimate construction-material series from the same Stats SA release can give a very different road-cost signal.
I started from the official release, not from a chart.
The source publishes civil-engineering material indices by activity. For several road activities there are paired series — one includes bitumen and the other excludes it. That immediately creates an analytical problem: if somebody says “road materials increased by X%”, which of those legitimate series are they actually using?
I converted the publication into an analysis-ready table.
The raw publication is designed for reading, not querying. Activity names carry analytical meaning inside the text, decimal-comma values need numeric conversion, and some historical values are unavailable. I therefore created separate fields for activity, period, index, pct_yoy and bitumen_treatment.
Roads - reseal [series including bitumen]activity = Roads - reseal
bitumen_treatment = included
pct_yoy = 0.1I did not fill missing historical observations with zero because zero would mean “no movement”; missing means the comparison value is not available.
I paired the two versions of each activity with a SQL self-join.
Once the table was in long format, I could pair the included and excluded versions of the same road activity and calculate the difference directly.
SELECT
i.activity,
i.pct_yoy AS incl_bitumen,
e.pct_yoy AS excl_bitumen,
ROUND(e.pct_yoy - i.pct_yoy, 1) AS gap_pp
FROM civil_material_index i
JOIN civil_material_index e
ON e.activity = i.activity
AND e.period = i.period
WHERE i.bitumen_treatment = 'included'
AND e.bitumen_treatment = 'excluded'
AND i.period = '2025-10'
ORDER BY gap_pp DESC;
The point of the join is not to make the SQL look complicated. It makes the comparison explicit: same activity, same month, only the bitumen treatment changes.
The result showed where the choice of series matters most.
Road reseal is the clearest example: the October 2025 year-on-year signal is 0.1% with bitumen included and 8.9% with bitumen excluded — an 8.8 percentage-point gap. Roads general and refurbishment each show a 2.6-point gap, while bulk earthworks is only 0.5 points apart.
I then checked contribution, not only the biggest percentage move.
A material can have a dramatic percentage increase but a small effect on the total basket if its weight is small. So the second part of the analysis ranks weight × price movement, rather than ranking percentages alone.
SELECT
product,
weight,
pct_yoy,
ROUND(weight * pct_yoy / 100, 2) AS contribution_pp
FROM construction_input_index
WHERE period = '2025-10'
ORDER BY ABS(contribution_pp) DESC;
This is the practical difference between reporting “the biggest price increase” and explaining “what actually pushed the overall construction-input index”.
I validated the extract before writing the conclusion.
The current validation file checks that all 13 activity rows are present, the October 2025 index is populated, the bitumen treatment field contains only the expected values, and the two known historical comparison gaps remain visible as warnings rather than being silently imputed.
For road-material exposure I would not substitute one generic construction headline. I would document which activity-specific series is being used, whether bitumen is included, and why that series matches the exposure being assessed.
How the road signal changes when bitumen treatment changes
The charts below use the same October 2025 extract. No file download is required to understand the result.
Project Controls Performance
Turning activity-level schedule snapshots into a report that shows where the programme is slipping and which packages need attention.
I treated the programme as a time series, not as one static schedule.
A single activity table tells me the current state, but it does not tell me how that state changed. I therefore used a monthly snapshot fact table: the same activity can appear once per reporting period with the planned progress, actual progress, current forecast finish, remaining duration, float and resource hours recorded for that month.
I separated baseline, forecast and actual fields.
Those fields are not interchangeable. Baseline finish is the approved reference. Forecast finish is where the activity is currently expected to finish. Actual finish is evidence that the work has finished. The reporting model keeps all three so slippage can be measured rather than overwritten.
I used window functions to get the latest activity state while retaining history.
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER(
PARTITION BY activity_id
ORDER BY snapshot_date DESC
) AS rn
FROM project_control_snapshot
)
SELECT *
FROM ranked
WHERE rn = 1;
The historical snapshots remain available for trend analysis. The row-number query simply produces the latest-state table used for exception reporting.
I built an S-curve view so the direction of travel is visible.
For example, at the August 2025 snapshot planned progress is 39.9% and actual is 37.0%, a 2.9 percentage-point shortfall. By December 2025 the gap is 2.0 points. That trend is more useful than waiting until completion to discover that the forecast moved.
I added exception reporting for float, milestones and resources.
The purpose is not to claim that every negative-float activity is equally important. The report gives the project controller a shortlist: which package, which activity, how the forecast changed and whether the issue is becoming more critical over time.
I tested the model before trusting the dashboard.
The validation checks confirm 19,200 snapshot rows, 800 unique activities, 24 reporting periods, no orphan activity keys, no progress outside 0–100%, no missing WBS keys and no forecast finish before the activity start.
I would use the dashboard to move the conversation from “the project is 37% complete” to “these packages are below plan, these activities are losing float, these milestones are slipping and this is how the forecast has changed since the previous report.”
From one progress percentage to a project-controls exception report
Reference snapshot: 2025-12-31.
Engineering Information Performance
Using RFI, submittal and deliverable records to identify ageing, approval bottlenecks and packages where information flow is becoming a project risk.
I defined one information-event structure.
RFIs, submittals and deliverables have different workflows, but the reporting questions overlap: when was it created, when was it due, who owns it, is it still open, how old is it, how long did it take to close and which package or discipline is affected?
I separated open ageing from closed cycle time.
An open RFI does not yet have a completed cycle time. For open items I calculate age from the creation date to the reporting cut-off. For closed items I use the elapsed time between creation and closure. Mixing those two measures would make the KPI difficult to interpret.
SELECT
package,
COUNT(*) FILTER (
WHERE record_type='RFI' AND status='Open'
) AS open_rfis,
PERCENTILE_CONT(0.5) WITHIN GROUP (
ORDER BY CURRENT_DATE-created_date::date
) FILTER (
WHERE record_type='RFI' AND status='Open'
) AS median_open_age_days
FROM engineering_information_event
GROUP BY package;
I compared packages instead of reporting one project-wide average.
The Utilities package has only 18.7% of deliverables on time and a 61.2% first-pass submittal approval rate. The drainage package also has a low 60.8% first-pass approval rate and 26.1% on-time deliverables. Those numbers do not diagnose the cause by themselves, but they tell me where to drill into revisions, responsible parties and overdue records.
I designed measures that answer operational questions.
Open RFIsHow much unresolved technical information is sitting in the workflow?
Median Open RFI AgeIs the current backlog recent, or has it been sitting unresolved for months?
First Pass Approval %How often are submittals accepted without another revision cycle?
Deliverables On Time %Are required engineering outputs arriving by their due dates?
The full evidence pack includes the DAX versions of these measures so the reporting logic can be inspected rather than inferred from the chart.
I validated the event structure before analysing performance.
The checks confirm 52,000 rows, 52,000 unique event IDs, only the three expected record types, no missing created/due dates, no negative cycle days and no closed records missing their closure date.
The report should not simply say “there are 203 open RFIs”. It should show which package owns the backlog, how old it is, whether approval performance is deteriorating and where the project team should investigate first.
Where engineering information is accumulating, ageing or cycling through revisions
Reporting cut-off: 2026-12-18.
How I test a report before I trust it.
A dashboard can be visually perfect and still be wrong. I keep the data-quality evidence next to the analysis.
I write the expected result before I run the test.
Each validation record contains the area being tested, the rule, the expected result, the actual result, a PASS/WARN/FAIL status, severity and the evidence file. That prevents a quality check from becoming an informal “it looks okay” review.
The current validation run is inspectable row by row.
The one warning in the full run is deliberate: the published Stats SA case has two historical comparison gaps. I leave those as a documented warning instead of converting them to zero and pretending the history is complete.
I use severity to decide whether publication should stop.
Data quality is part of the analytical deliverable. If a management number cannot be reconciled back to its source rows and validation rules, I do not consider the report complete.
Quality evidence before a management number is published
The detailed test rows remain downloadable, but the page shows the quality state first.
Civil Infrastructure Tender Intelligence
The engineering work behind turning a public procurement API into a controlled dataset instead of manually searching tender notices.
I separated ingestion from publication.
A source being reachable does not mean its new values should immediately replace a trusted dashboard. The pipeline records the raw payload first, then checks the structure and completeness before promoting anything.
I kept classification explainable.
For road/asphalt analysis I retain the matched words that caused the classification. Explicit terms such as asphalt, hot mix, premix, overlay or milling and overlay are stronger evidence than a generic “roadworks” label. Seal/reseal/slurry/chip-seal work is kept separate from hot-mix asphalt.
Professional consulting/design tenders can still belong in civil procurement intelligence, but they do not automatically contribute to the asphalt works-demand signal.
The next validation is classifier precision and recall.
I do not publish an accuracy percentage until there is a manually reviewed validation sample. The planned test is straightforward: manually label a sample of real tender descriptions, compare the human label with the Yatify result, then calculate precision, recall, false positives and false negatives.
A recruiter can see the difference between “I can call an API” and “I can build a data pipeline with raw retention, schema checks, explainable classification, validation and a publication gate.”
The pipeline is shown honestly before market charts are published
Production market charts are added only after real imported records pass validation.
The transfer is practical, not decorative.
On site I compare test evidence with a specification before accepting a layer. In analytics the equivalent is defining the expected result, running the validation and refusing to publish a failed high-severity check.
Engineering measurement teaches me to ask where a total came from, which source record supports it and why two records do not reconcile. That same habit is useful in SQL and management reporting.
Construction work is already structured by areas, packages, disciplines and activities. That makes WBS/project-control and engineering-information models intuitive rather than abstract.
A monthly or weekly report has to be repeatable. That is exactly why I am interested in Power Query, SQL pipelines and controlled definitions instead of rebuilding a spreadsheet manually every reporting cycle.
This is the level of work I want the portfolio to be judged on.
For employment opportunities, reporting/data roles or engineering-data collaboration, contact me directly. The individual case-study pages contain additional source notes, SQL and downloadable evidence.