WorkforceIQ · build walkthrough

How this was built

Every step, every tool, and the real output each command produced. The terminal blocks below are captured stdout from actually running the pipeline — not prose describing what it would print.

10 pipeline steps
8 analytical SQL views
22 DAX measures
13 bugs found and fixed
28 commits
00

What was installed, and why each one

Eight libraries plus PostgreSQL and Power BI Desktop.

Chosen for a reason, not by habit
PostgreSQL 17The production database, hosted on Supabase. The brief called for a real relational DB.
DuckDBRuns the same .sql files with no server. This is what made the SQL testable in a fast loop, and later proved the two engines agree.
pandas + numpyDeterministic transforms. The whole dataset rebuilds byte-identically from a seed.
scikit-learnPipelines keep preprocessing inside cross-validation, which is what prevents the classic scaling leak.
matplotlibStatic PNGs that render on GitHub without a JS runtime.
nbformat + nbclientThe notebook is generated and executed by a script, so the committed .ipynb carries real outputs and can never drift from the training code.
psycopg2Runs the shipped .sql files against the live database, so the files themselves are what gets tested.
Power BI DesktopPBIP/TMDL is Microsoft's text project format: diffable and reviewable. A .pbix is an opaque binary.
Vanilla HTML/CSS/JSNo framework, no build step, no CDN. One self-contained file GitHub Pages serves directly.
PowerShellPower BI Desktop has no CLI, so a Win32 window capture was the only way to get screenshots.
Actual versions used
Output of importing every dependency.
Python       3.13.14
pandas       3.0.5
numpy        2.5.2
scikit-learn 1.9.0
duckdb       1.5.5
matplotlib   3.11.1
psycopg2     2.9.12
nbformat     5.11.1
01

The pipeline, end to end

One CSV in, four deliverables out.

data/raw/ibm_hr_attrition.csv 1,470 real employees, one dateless snapshot | | build_dataset.py normalise + synthesise the time dimension v data/processed/*.csv 6 tables, 23,645 rows, deterministic | +--> schema.sql + seed_data.sql ------> PostgreSQL (Supabase, live) | 7 tables, FKs, checks + rls_policies.sql | +--> sql/views/*.sql ------------------> 8 analytical views | window functions, CTEs, verified identical on | indirect standardisation Postgres AND DuckDB | +--> train_attrition_model.py ---------> attrition_risk_scores | features read FROM the views 1,233 employees scored | +--> build_powerbi.py -----------------> powerbi/ (PBIP + TMDL) | 14 tables, 22 DAX measures 4 pages, 23 visuals | +--> build_dashboard.py ---------------> docs/index.html results inlined GitHub Pages
02

Every step, and what it printed

Run in this order — the model must be scored before the seed file is generated, or the seed ships without the watchlist.

1 · Build the datasetexit 0
Normalises the flat 1,470-row IBM snapshot into six tables and synthesises the time dimension the source has no trace of. Deterministic: a fixed seed plus a per-employee SHA-256 hash means this reproduces byte-for-byte.
python generators/build_dataset.py
departments                    6 rows
employees                  1,470 rows
compensation_history       7,863 rows
performance_reviews        7,147 rows
attrition_events             237 rows
dim_date                   4,383 rows

base attrition rate  : 0.1612
departments          : 6
distinct managers    : 121
employees w/o manager: 6
comp rows / employee : 5.35
review rows / emp    : 4.86
2 · Train and score the modelexit 0
Trains logistic regression and a random forest, compares them on cross-validated PR-AUC, then scores every active employee. Runs BEFORE the seed file, because the seed embeds the scores it produces.
python generators/train_attrition_model.py
rows 1470   leavers 237   base rate 0.1612
train 1102   test 368   test leavers 59

--- Logistic Regression ---
              precision    recall  f1-score   support

      Stayed       0.92      0.77      0.84       309
        Left       0.35      0.66      0.46        59

    accuracy                           0.75       368
   macro avg       0.64      0.72      0.65       368
weighted avg       0.83      0.75      0.78       368

ROC-AUC 0.7392   PR-AUC 0.4797
confusion matrix [[tn fp][fn tp]]:
[[238  71]
 [ 20  39]]

--- Random Forest ---
              precision    recall  f1-score   support

      Stayed       0.87      0.97      0.91       309
        Left       0.57      0.22      0.32        59

    accuracy                           0.85       368
   macro avg       0.72      0.59      0.62       368
weighted avg       0.82      0.85      0.82       368

ROC-AUC 0.7494   PR-AUC 0.4058
confusion matrix [[tn fp][fn tp]]:
[[299  10]
 [ 46  13]]
Logistic Regression 5-fold PR-AUC 0.5069 +/- 0.0646
Random Forest 5-fold PR-AUC 0.5082 +/- 0.0351

better cross-validated PR-AUC: Random Forest

Top 12 logistic-regression coefficients (standardised features):
                          feature  coefficient  odds_ratio
business_travel_Travel_Frequently        1.301       3.672
             job_role_QA Engineer        0.991       2.695
                 tenure_years_log       -0.970       0.379
                     tenure_years        0.867       2.380
  job_role_Support Representative       -0.831       0.436
            marital_status_Single        0.819       2.268
                         overtime        0.779       2.180
            department_name_Sales        0.771       2.161
    job_role_Sales Representative        0.646       1.908
                   monthly_income       -0.563       0.570
    business_travel_Travel_Rarely        0.552       1.736
          income_pct_rank_in_role        0.529       1.698

Permutation importance (drop in PR-AUC when shuffled), top 10:
                 feature  importance    std
        tenure_years_log      0.2199 0.0298
                overtime      0.1784 0.0411
          monthly_income      0.1022 0.0233
            tenure_years      0.0922 0.0449
                job_role      0.0598 0.0234
     pct_vs_role_average      0.0439 0.0157
         department_name      0.0401 0.0243
        job_satisfaction      0.0360 0.0164
environment_satisfaction      0.0332 0.0166
                     age      0.0304 0.0113

wrote 4 charts to D:\Projects\WorkForceIq\docs\charts
logistic_regression: High>=0.677  Medium>=0.449  High tier n=124
random_forest: High>=0.287  Medium>=0.174  High tier n=124
wrote 2466 rows to attrition_risk_scores.csv

rule-based heuristic vs model: r=0.465, top-decile overlap=0.476
wrote docs/model_results.json
3 · Emit the seed SQLexit 0
Writes a dependency-free INSERT script so anyone with psql and no Python can build the whole database.
python generators/build_seed_sql.py
wrote D:\Projects\WorkForceIq\sql\seed_data.sql  (1122 KB)
4 · Run the eight viewsexit 0
Executes the exact .sql files that ship to PostgreSQL against DuckDB. This is where every documented figure comes from -- nothing in the docs is hand-typed.
python generators/run_views_local.py
Loading tables and creating views...
  created views from 01_vw_attrition_by_department.sql
  created views from 02_vw_tenure_cohort_attrition.sql
  created views from 03_vw_compensation_percentile.sql
  created views from 04_vw_manager_span_attrition.sql
  created views from 05_vw_overtime_satisfaction_attrition.sql
  created views from 06_vw_department_attrition_controlled.sql
  created views from 07_vw_attrition_risk_watchlist.sql
  created views from 08_vw_dim_employee.sql

==============================================================================
COMPANY HEADLINE
==============================================================================
 total_employees  active_headcount  leavers  crude_attrition_rate
            1470            1233.0    237.0                0.1612

==============================================================================
V1  vw_attrition_by_department -- rolling 4q rate, latest quarter
==============================================================================
 department_name year_quarter  headcount_end  terminations  attrition_rate_qtr  rolling_4q_terminations  rolling_4q_attrition_rate
              HR      2025 Q4             40             1              0.0247                      8.0                     0.1818
           Sales      2025 Q4            319            13              0.0400                     46.0                     0.1349
     Engineering      2025 Q4            520            21              0.0397                     59.0                     0.1082
Customer Support      2025 Q4            122             0              0.0000                      4.0                     0.0329
      Operations      2025 Q4            135             0              0.0000                      4.0                     0.0295
         Finance      2025 Q4             97             0              0.0000                      1.0                     0.0103

==============================================================================
V1  company-wide quarterly trend
==============================================================================
year_quarter  terminations  headcount_end  attrition_rate_qtr
     2024 Q1          18.0         1327.0              0.0137
     2024 Q2          33.0         1325.0              0.0249
     2024 Q3          35.0         1317.0              0.0265
     2024 Q4          29.0         1325.0              0.0220
     2025 Q1          22.0         1312.0              0.0167
     2025 Q2          31.0         1285.0              0.0239
     2025 Q3          34.0         1261.0              0.0267
     2025 Q4          35.0         1233.0              0.0281

==============================================================================
V2  vw_tenure_cohort_attrition
==============================================================================
tenure_cohort  employees  leavers  attrition_rate  share_of_all_leavers  lift_vs_company  avg_tenure_years
       0-1 yr         44     16.0          0.3636                0.0675             2.26              0.50
       1-3 yr        298     86.0          0.2886                0.3629             1.79              1.94
       3-5 yr        238     39.0          0.1639                0.1646             1.02              3.97
      5-10 yr        524     58.0          0.1107                0.2447             0.69              7.07
       10 yr+        366     38.0          0.1038                0.1603             0.64             15.83

==============================================================================
V3  vw_compensation_percentile -- attrition by in-role pay quartile
==============================================================================
 income_quartile_in_role     income_quartile_label  employees  leavers  attrition_rate  avg_monthly_income
                       1  Q1 (lowest paid in role)        370     72.0          0.1946              4481.0
                       2                        Q2        369     62.0          0.1680              5568.0
                       3                        Q3        367     50.0          0.1362              6868.0
                       4 Q4 (highest paid in role)        364     53.0          0.1456              9139.0

==============================================================================
V3  bottom vs top decile of in-role pay
==============================================================================
              band  employees  leavers  attrition_rate
Bottom 10% in role        152     31.0          0.2039
        Middle 80%       1166    185.0          0.1587
   Top 10% in role        152     21.0          0.1382

==============================================================================
V4  vw_attrition_by_span_band
==============================================================================
    span_band  span_band_sort  managers  employees_covered  leavers  attrition_rate  avg_span
  1-5 reports               1        19               79.0     16.0          0.2025       4.2
 6-10 reports               2        39              286.0     40.0          0.1399       7.3
11-15 reports               3        26              343.0     57.0          0.1662      13.2
  16+ reports               4        37              756.0    123.0          0.1627      20.4

==============================================================================
V4  worst-performing managers with a usable sample
==============================================================================
     manager_name department_name  direct_reports  reports_lost  team_attrition_rate     span_band
Soren Castellanos     Engineering              20          11.0               0.5500   16+ reports
  Rosa Fitzgerald              HR              13           7.0               0.5385 11-15 reports
      Diego Ramos           Sales              15           8.0               0.5333 11-15 reports
     Claire Mehta           Sales              19          10.0               0.5263   16+ reports
     Claire Reyes     Engineering               9           4.0               0.4444  6-10 reports
      Andre Nowak     Engineering              14           6.0               0.4286 11-15 reports
    Soren Bergman     Engineering              22           9.0               0.4091   16+ reports
   Leila Krishnan     Engineering              15           6.0               0.4000 11-15 reports

==============================================================================
V5  vw_overtime_satisfaction_attrition -- THE HEADLINE
==============================================================================
overtime_flag satisfaction_bucket  employees  leavers  attrition_rate  company_base_rate  lift_vs_base  has_reliable_sample
          Yes           Low (1-2)        153     56.0          0.3660             0.1612          2.27                    1
          Yes          Medium (3)        121     41.0          0.3388             0.1612          2.10                    1
          Yes            High (4)        142     30.0          0.2113             0.1612          1.31                    1
           No           Low (1-2)        416     56.0          0.1346             0.1612          0.83                    1
           No          Medium (3)        321     32.0          0.0997             0.1612          0.62                    1
           No            High (4)        317     22.0          0.0694             0.1612          0.43                    1

==============================================================================
V5  three-way: overtime x satisfaction x work-life balance
==============================================================================
overtime_flag satisfaction_flag wlb_flag  employees  leavers  attrition_rate
          Yes               Low     Poor         39     16.0          0.4103
          Yes               Low       OK        114     40.0          0.3509
          Yes                OK     Poor         87     26.0          0.2989
          Yes                OK       OK        176     45.0          0.2557
           No               Low     Poor        115     17.0          0.1478
           No                OK     Poor        183     24.0          0.1311
           No               Low       OK        301     39.0          0.1296
           No                OK       OK        455     30.0          0.0659

==============================================================================
V6  standard schedule -- company attrition rate by tenure cohort
==============================================================================
tenure_cohort  employees  leavers  company_cohort_rate
       0-1 yr         44     16.0               0.3636
       1-3 yr        298     86.0               0.2886
       10 yr+        366     38.0               0.1038
       3-5 yr        238     39.0               0.1639
      5-10 yr        524     58.0               0.1107

==============================================================================
V6  vw_department_attrition_controlled -- crude vs tenure-adjusted
==============================================================================
 department_name  headcount  avg_tenure_years  observed_leavers  expected_leavers  crude_attrition_rate  standardised_attrition_ratio  tenure_adjusted_rate  mix_effect                     verdict  has_reliable_sample
              HR         52              5.83              12.0               8.6                0.2308                         1.396                0.2250      0.0058  Worse than tenure predicts                    0
           Sales        409              7.08              90.0              66.1                0.2200                         1.361                0.2194      0.0006  Worse than tenure predicts                    1
     Engineering        631              6.32             111.0             109.7                0.1759                         1.012                0.1632      0.0127     In line with tenure mix                    1
      Operations        145              8.10              10.0              20.4                0.0690                         0.490                0.0791     -0.0101 Better than tenure predicts                    1
Customer Support        131              8.85               9.0              18.5                0.0687                         0.486                0.0783     -0.0096 Better than tenure predicts                    1
         Finance        102             14.95               5.0              13.7                0.0490                         0.366                0.0589     -0.0099 Better than tenure predicts                    1

==============================================================================
V7  vw_attrition_risk_watchlist -- top 15 active employees at risk
==============================================================================
  employee_name department_name             job_role      manager_name  risk_score risk_tier  risk_flag_count  tenure_years     income_quartile_label overtime_flag  job_satisfaction
  Nina Andersen           Sales Sales Representative       Diego Ramos       0.964      High                3          2.82                        Q2           Yes                 1
 Idris Ferreira           Sales Sales Representative     Anika Bergman       0.940      High                3          1.52                        Q3            No                 1
   Nadia Hassan     Engineering          QA Engineer     Tobias Moreau       0.922      High                2          3.91 Q4 (highest paid in role)           Yes                 1
 Sofia Kowalski           Sales      Sales Executive  Soren Fitzgerald       0.913      High                2          1.10                        Q3           Yes                 4
   Diego Okafor           Sales Sales Representative      Marcus Silva       0.904      High                1          0.18 Q4 (highest paid in role)            No                 3
    Elena Varga     Engineering          QA Engineer       Aisha Rossi       0.899      High                2         10.87 Q4 (highest paid in role)           Yes                 1
    Nadia Silva     Engineering    Software Engineer   Andre Delacroix       0.893      High                2         20.72 Q4 (highest paid in role)           Yes                 3
 Viktor Ibrahim      Operations  Operations Director    Amara Grimaldi       0.891      High                2         15.93                        Q2           Yes                 1
    Jonas Mbeki     Engineering    Software Engineer Soren Castellanos       0.884      High                3          1.80                        Q3           Yes                 4
  Sofia Cardoso           Sales      Sales Executive      Tobias Varga       0.883      High                3         21.62                        Q3            No                 1
    Samir Reyes     Engineering    Software Engineer   Idris Lindqvist       0.878      High                4          9.04  Q1 (lowest paid in role)           Yes                 1
  Tariq Eriksen     Engineering          QA Engineer Bruno Castellanos       0.873      High                4          2.12 Q4 (highest paid in role)           Yes                 1
Elena Delacroix     Engineering    Software Engineer   Diego Whitfield       0.873      High                4          8.31                        Q3           Yes                 1
Oscar Delacroix           Sales Sales Representative     Divya Navarro       0.872      High                2          3.63                        Q3           Yes                 4
    Elena Nowak     Engineering          QA Engineer         Lena Osei       0.867      High                2         18.85 Q4 (highest paid in role)           Yes                 1

==============================================================================
V7  watchlist tier distribution
==============================================================================
         model_name risk_tier  employees  avg_score
logistic_regression      High        124     0.7715
logistic_regression    Medium        246     0.5442
logistic_regression       Low        863     0.2137
      random_forest      High        124     0.3754
      random_forest    Medium        246     0.2184
      random_forest       Low        863     0.0946
5 · Export for Power BIexit 0
Runs each view and writes the result to CSV. These are not hand-made extracts: the SQL layer still computes every number the report displays.
python generators/export_for_powerbi.py
DimDate                     4383 rows  <- dim_date
  DimDepartment                  6 rows  <- departments
  DimEmployee                 1470 rows  <- vw_dim_employee
  FactAttrition                237 rows  <- attrition_events
  FactCompensation            7863 rows  <- compensation_history
  FactReview                  7147 rows  <- performance_reviews
  RiskScores                  2466 rows  <- attrition_risk_scores
  AttritionByDepartment         48 rows  <- vw_attrition_by_department
  TenureCohort                   5 rows  <- vw_tenure_cohort_attrition
  CompQuartile                   4 rows  <- vw_attrition_by_comp_quartile
  OvertimeSatisfaction           6 rows  <- vw_overtime_satisfaction_attrition
  SpanBand                       4 rows  <- vw_attrition_by_span_band
  DepartmentControlled           6 rows  <- vw_department_attrition_controlled

  13 tables, 23,645 rows -> data/powerbi
6 · Generate the Power BI projectexit 0
Introspects column types from the live views and emits the TMDL semantic model plus the report visuals. The model cannot drift from the schema -- change a view, re-run, and it follows.
python generators/build_powerbi.py
DimDate.tmdl  (8 columns from dim_date)
  DimDepartment.tmdl  (3 columns from departments)
  DimEmployee.tmdl  (38 columns from vw_dim_employee)
  FactAttrition.tmdl  (5 columns from attrition_events)
  FactCompensation.tmdl  (6 columns from compensation_history)
  FactReview.tmdl  (9 columns from performance_reviews)
  RiskScores.tmdl  (5 columns from attrition_risk_scores)
  AttritionByDepartment.tmdl  (14 columns from vw_attrition_by_department)
  TenureCohort.tmdl  (8 columns from vw_tenure_cohort_attrition)
  CompQuartile.tmdl  (6 columns from vw_attrition_by_comp_quartile)
  OvertimeSatisfaction.tmdl  (9 columns from vw_overtime_satisfaction_attrition)
  SpanBand.tmdl  (7 columns from vw_attrition_by_span_band)
  DepartmentControlled.tmdl  (14 columns from vw_department_attrition_controlled)
  Measures.tmdl  (22 DAX measures)
wrote report.json: 4 pages, 23 visuals

wrote PBIP semantic model to powerbi\WorkforceIQ.SemanticModel
wrote powerbi/measures.dax (22 measures)
7 · Build the web dashboardexit 0
Inlines the computed results into a single self-contained HTML file. No fetch, no API dependency, works even when the database is paused.
python generators/build_dashboard.py
wrote web/index.html  (71 KB)
wrote docs/index.html  (71 KB)
8 · Load PostgreSQLexit 0
Runs schema → seed → views → RLS against the live database. The security step is never skipped, because schema.sql opens with DROP TABLE ... CASCADE and that takes the RLS policies with it.
python generators/load_to_postgres.py
connected

[1/4] schema
  running sql/schema.sql ... ok

[2/4] seed data (this one takes a moment)
  running sql/seed_data.sql ... ok

[3/4] analytical views
  running sql/views/01_vw_attrition_by_department.sql ... ok
  running sql/views/02_vw_tenure_cohort_attrition.sql ... ok
  running sql/views/03_vw_compensation_percentile.sql ... ok
  running sql/views/04_vw_manager_span_attrition.sql ... ok
  running sql/views/05_vw_overtime_satisfaction_attrition.sql ... ok
  running sql/views/06_vw_department_attrition_controlled.sql ... ok
  running sql/views/07_vw_attrition_risk_watchlist.sql ... ok
  running sql/views/08_vw_dim_employee.sql ... ok

[4/4] row-level security and grants
  running sql/rls_policies.sql ... ok

--- verification ---

row counts
  attrition_events  237
  attrition_risk_scores  2466
  compensation_history  7863
  departments  6
  dim_date  4383
  employees  1470
  performance_reviews  7147

department attrition, tenure-controlled
  HR  0.2308  1.396  Worse than tenure predicts
  Sales  0.2200  1.361  Worse than tenure predicts
  Engineering  0.1759  1.012  In line with tenure mix
  Operations  0.0690  0.490  Better than tenure predicts
  Customer Support  0.0687  0.486  Better than tenure predicts
  Finance  0.0490  0.366  Better than tenure predicts

overtime x satisfaction
  Yes  Low (1-2)  153  0.3660  2.27
  Yes  Medium (3)  121  0.3388  2.10
  Yes  High (4)  142  0.2113  1.31
  No  Low (1-2)  416  0.1346  0.83
  No  Medium (3)  321  0.0997  0.62
  No  High (4)  317  0.0694  0.43

watchlist tiers
  logistic_regression  High  124
  logistic_regression  Low  863
  logistic_regression  Medium  246
  random_forest  High  124
  random_forest  Low  863
  random_forest  Medium  246

done
9 · Prove both engines agreeexit 0
Thirteen checks -- including row-level comparison across all 1,470 employees -- run against PostgreSQL and DuckDB. This caught two silent wrong-answer bugs that raised no error on either engine.
python generators/verify_parity.py
connecting ...
postgres + duckdb ready

  v1_dept_quarter        48 rows   ok
  v2_cohort               5 rows   ok
  v2_cohort_dept         29 rows   ok
  v3_percentile        1470 rows   ok
  v3_quartile             4 rows   ok
  v4_manager            121 rows   ok
  v4_span_band            4 rows   ok
  v5_ot_sat               6 rows   ok
  v5_threeway             8 rows   ok
  v6_controlled           6 rows   ok
  v6_schedule             5 rows   ok
  v7_watchlist         1233 rows   ok
  v8_dim_employee      1470 rows   ok

==================================================================
PARITY OK: all 13 checks identical on PostgreSQL and DuckDB
==================================================================
03

What the Power BI report looks like

Four pages, 23 visuals, generated as text and rendered in Desktop.

Executive Overview
Headline KPIs, crude vs tenure-adjusted attrition by department, the quarterly trend against its rolling 4-quarter window, and the department scorecard showing observed against expected leavers.
Executive Overview
Tenure & Cohort Analysis
Headcount and attrition rate per cohort, each cohort's share of total outflow, and the department × cohort matrix. The highest rate and the largest share of leavers are different cohorts.
Tenure &amp; Cohort Analysis
Compensation & Satisfaction
Attrition by in-role pay quartile, the overtime × satisfaction cross-segment with lift against the base rate, and the span-of-control chart reporting a negative result.
Compensation &amp; Satisfaction
Attrition Risk Watchlist
Every active employee ranked by modelled flight risk, with the rule-based heuristic beside the model score, plus department and tier slicers.
Attrition Risk Watchlist
04

What we got out of it

The findings the pipeline actually produced.

The three findings that matter
Overtime is the lever. Overtime combined with low satisfaction runs at 36.6% attrition — 2.27× the company base rate and 5.3× the 6.9% of employees with neither factor. The asymmetry is the real insight: overtime alone (21.1%, even among the highly satisfied) is worse than low satisfaction alone (13.5%). High satisfaction does not protect someone being worked too hard.
Engineering does not have a retention problem. Its crude rate is 17.6% and it accounts for 111 of 237 departures — 47% of all outflow. But its standardised attrition ratio is 1.01: it loses almost exactly what its tenure mix predicts. That is a hiring-volume consequence, not a retention failure. Sales, at 1.36 across 409 people, is where a retention task force belongs.
Span of control explains nothing. Flat at 14.0% / 16.6% / 16.3% across the 6–10, 11–15 and 16+ bands. The apparent spike in the smallest band is small-sample noise across 79 people. A negative result, reported as one rather than dressed up.
Deliverables
Everything the pipeline produced.
DatabaseLive PostgreSQL — 1,470 employees, 2,466 risk scores, 16 views, RLS on 7/7 tables, zero security lints
SQL layer8 analytical views, 13/13 parity checks identical across PostgreSQL and DuckDB
ModelLogistic regression, recall 0.66 on leavers, 5-fold PR-AUC 0.507 ± 0.065; 1,233 active employees scored
Power BIPBIP/TMDL — 14 tables, 22 DAX measures, 4 pages, 23 visuals
Web dashboardSelf-contained single file on GitHub Pages
DocsFindings write-up, ER diagram, executed notebook, this page
05

Thirteen bugs, found and fixed

Three of these produced wrong numbers while rendering without any error at all.

The list
This is what the commit history actually documents.
01
Tenure jitter applied to active employees onlyClass-dependent leakage. Put every leaver on an exact integer tenure and every stayer just above one, faking a 71% attrition rate in the under-1-year band that the model happily exploited.
02
Non-deterministic NTILE(4)Tied salaries straddling a quartile boundary must be split, and which row went where was arbitrary. PostgreSQL and DuckDB disagreed by one employee.
03
Bare ::NUMERIC castArbitrary precision on PostgreSQL, DECIMAL(18,3) on DuckDB. Pay percentiles silently truncated to three decimals on one engine only.
04
RLS destroyed by every rebuildschema.sql drops tables, which drops their policies. All seven tables were briefly exposed with default write grants.
05
Dashboard bars rendered emptyBar width was applied from a requestAnimationFrame callback, which never fires in a backgrounded or zero-size viewport. Correctness must not depend on an animation frame.
06
Mermaid ER diagram would not parseFK UK and an invented PK_FK. Mermaid allows one key token per attribute.
07
Wrong .pbip schema URL, missing report folderThe project would not open at all.
08
Table named MeasuresReserved name in the Tabular object model.
09
CompatibilityLevel downgrade 1606 → 1567Tabular rejects downgrades outright, so every regeneration broke a project Desktop had already opened.
10
DECIMAL columns imported as textAVERAGE() and MAX() over text broke two visuals, surfacing as a misleading “capacity or license issue”.
11
Shared DataFolder parameterThe model's only cross-query dependency. Power Query failed all 13 loads with “a cyclic reference was encountered”.
12
Aggregation enum shiftedCount is 2, not 4. The risk-tier donut plotted maximum employee_id per tier instead of counting employees -- and rendered without error.
13
Misleading grand totalsAveraged six departmental rates into 0.14 against a true 0.1612.
Two structural changes were made so that class cannot recur silently. An unmapped column type now raises instead of defaulting to string, so a new type fails the build rather than shipping a silently wrong model. And every Power BI projection reference is validated against its query before commit.