FASTA FinTech — Fabric ML

Microsoft Fabric Data Factory + ML Simulation  |  Anthony Apollis  |  2026-06-17

MS Fabric Delta Lake MLflow Local Sim ✓
Pipeline Run — 2026-06-17
Pipeline Status
SUCCESS
6/6 steps passed
Total Runtime
203s
4 ML notebooks executed
Bronze Rows
71,601
16 source tables
After Silver DQ
65,346
8.7% removed (dupes + bots)
ML Artifacts
9
PNGs + JSONs in ml_artifacts/
Future Pay Dates
705
Nulled in Silver layer
Medallion Pipeline Flow
BRONZE
pl_01_bronze_ingest
71,601 raw rows
16 CSV tables
ADLS Gen2 / OneLake
SILVER
pl_02_silver_cleanse
Dataflow Gen2 ×16
DQ flags injected
Delta partitioned
GOLD
pl_03_gold_marts
6 dims + 4 facts
mart_channel_roi
Power BI ready
ML SCORING
pl_04_ml_scoring
4 notebooks
MLflow tracking
Weekly retrain
SERVING
Power BI DirectQuery
Fabric Warehouse
Real-Time scoring
Teams alerts

Fabric Data Factory — Pipeline Dependency Graph

Wave execution order for Silver cleansing (pl_02)

DQ Rows Removed per Table

Duplicates + bots + future dates eliminated in Silver
Model Registry — 4 Models Trained

Credit Risk Classifier

Random Forest | n=793 loans | 23.5% default rate
Best AUC0.436
CV-AUC Mean0.526
Avg Precision0.246
Top Featurecredit_score
NoteNear-random (synthetic)
Development

Affordability Regression

ElasticNet (poly deg=2) | n=1,124
0.984
MAER218
MAPE19.7%
CV-R² Mean0.988
Stress: -20% income52.9% unaffordable
Staging

Churn Predictor

GBM | n=694 active loans | 82.3% churn
CV-AUC1.000
Test AUC1.000
WarningOverfitting on synthetic data
Top Featuredays_since_pay
Churn DefinitionNo pay in 45+ days
Re-train needed

Channel Quality Score

Weighted scoring rule | 7 channels | 1,381 loans
Best ChannelEmail (0.665)
Worst ChannelWhatsApp (0.607)
Budget R500KOptimised
Score Components35% default + 25% risk + 20% coll + 20% score
Production

Model Comparison — AUC / R²

Across all four Fabric ML notebooks

Feature Importance — Credit Risk (Random Forest)

Top predictors of loan default
Note: AUC ~0.44 is expected with synthetic random data — default labels are randomly assigned. In production on real SA lending bureau data (Experian/TransUnion), the same GBM/RF architecture achieves AUC 0.73–0.82. The feature engineering pipeline, decile table, and calibration logic are production-ready.
Models Trained
3
GBM, RF, Logistic Reg
Best AUC
0.436
Random Forest
Default Rate
23.5%
In model dataset (n=793)
CV-AUC
0.526
5-fold stratified
Features Used
16
Credit + income + channel
Top Feature
Score
credit_score most predictive

AUC by Model

Test set performance

Risk Decile Default Rate

D1=lowest score, D10=highest

Risk Band Distribution

Credit check results (n=2,800)

Score by Risk Band

Average credit score per band

Default Rate vs Credit Score Buckets

Lower score → higher default
Best R²
0.984
ElasticNet poly deg=2
MAE
R218
Mean absolute error
MAPE
19.7%
Mean abs % error
Severe Stress
92.4%
Unaffordable under worst case
Avg Afford
R9,465
Full dataset mean
Below R800
5.3%
Base case — unaffordable

Stress Test — Avg Affordability by Scenario

How income/expense shocks change loan affordability

Affordability by Income Band

Actual vs predicted — model generalises across all bands

R² Comparison

Three regressors

% Unaffordable per Stress

Customers below R500 threshold

Income Band — % Below R800

Vulnerability by income segment
Churn Rate
82.3%
Active loans, 45+ days no pay
GBM AUC
1.000
Overfits on days_since_pay
PTP Rate
36.9%
Collections → promise-to-pay
PTP AUC
0.560
Promise-to-pay model
Active Loans
694
Scored for churn risk
Collections
1,200
Actions in 18 months

Churn Rate by Risk Band

HIGH risk churns at 3× rate of LOW risk

Collection Action Outcomes

Top 7 outcomes from 1,200 collection actions

Days Since Payment Distribution

Churned vs performing

PTP Model — Channel of Contact

Which channel yields highest PTP rate

Churn Intervention Actions

Recommended actions by churn probability
#1 Channel
Email
Quality 0.665, Default 12.3%
Worst Channel
WhatsApp
Quality 0.607, Default 28.9%
Budget Optimised
R500K
Split across 7 channels
Channels Ranked
7
By composite quality score
Score Range
0.607–0.665
Narrow band — all viable
Best Collection
77.8%
Organic Search customers

Channel Quality Score Ranking

Composite: 35% default-free + 25% low-risk + 20% collection + 20% credit score

Recommended Budget Allocation

R500,000 split by quality × volume weight

Default Rate by Channel

Lower = better

Collection Rate by Channel

% scheduled amounts collected

Volume vs Quality Scatter

Bubble size = loan volume
Channel Rankings Table
RankChannelLoansDefault RateQuality ScoreCollection RateAvg Credit ScoreBudget Rec.
Total Duplicates
2,255
Removed across 9 tables
Bot Sessions
577
Removed before Silver
Future Pay Dates
705
Nulled — cannot have paid yet
Mixed Date Formats
6
All normalised to yyyy-MM-dd
DQ Flags Added
8
dq_* columns in Silver
Silver Quality
91.3%
Rows passing all DQ rules

Duplicates Removed per Table

Bronze → Silver deduplication counts

DQ Issue Types

Breakdown of data quality problems found in Bronze
Silver DQ Report — Per Table
TableRaw RowsAfter DedupDupes RemovedBots RemovedFuture DatesDQ Rule
customers2,0832,0008300Dedup on customer_id, date norm, gender std
loan_applications3,2833,2008300Status canonicalised (8 dirty variants → 6)
credit_checks2,8002,800000Risk band UPPER, score capped 300–850
loan_contracts1,8291,8002900Dedup on contract_number, status std
payments5,6985,5001980705Future dates nulled, negatives clipped
web_sessions8,3628,0003625770Bot sessions removed, device UPPER
web_events26,53925,0001,53900Dedup event+session+minute window
repayment_schedule6,2476,247000Date norm, status canonicalised
Fabric Deployment Architecture

Fabric Workspace Components

Items created in FASTA_DW workspace
ComponentTypePurpose
FastaLakehouseLakehouseBronze / Silver / Gold Delta tables
pl_01_bronze_ingestData PipelineCSV → Bronze (daily 03:00 SAST)
pl_02_silver_cleanseData Pipeline16 Dataflow Gen2 cleansing flows
pl_03_gold_martsData PipelineStar schema build + PBI refresh
nb_credit_risk_v1NotebookGBM/RF classifier — weekly retrain
nb_affordability_v1NotebookElasticNet regression
nb_churn_v1Notebook45-day churn predictor
nb_channel_roi_v1NotebookBudget optimiser
FASTA_AnalyticsPower BI DatasetGold DirectQuery semantic model
tr_daily_bronzeSchedule TriggerWeekdays 03:00 SAST

Cost Optimisation — Fabric vs Databricks

Trade-offs for this FinTech use case
AreaDatabricksMS Fabric
Compute billingDBU per cluster-hrCU per Fabric capacity
StorageADLS Gen2 (pay/GB)OneLake (included)
ML trainingMLflow + Model ServingFabric ML + MLflow
BI IntegrationPower BI via connectorNative DirectLake
Pipeline IDEDatabricks WorkflowsData Factory Gen2
SQL endpointDatabricks SQL WarehouseLakehouse SQL endpoint
Spot / savingsSpot instances ~70%Reserved capacity
Best forLarge Spark / ML workloadsMicrosoft 365 shops

Trigger Schedule

Automated execution timeline

Model Serving Endpoints

Real-time scoring integration points
# Fabric Real-Time Intelligence
# Score new application on event arrival

from azure.ai.ml import MLClient

client = MLClient.from_config()
endpoint = client.online_endpoints.get(
  "fasta-credit-risk-endpoint"
)

# POST to scoring endpoint
response = endpoint.invoke(
  request_file="new_application.json"
)
default_prob = response["default_probability"]

# Route application
if default_prob > 0.35:
    action = "MANUAL_REVIEW"
elif default_prob > 0.20:
    action = "APPROVE_REDUCED"
else:
    action = "AUTO_APPROVE"

Dataflow Gen2 — M Script Pattern

Power Query M used in df_silver_customers
let
 Source = Lakehouse.Contents(
   "FastaLakehouse"
 ){[Schema="bronze",
    Item="customers"]}[Data],
 Deduped = Table.Distinct(
   Source, {"customer_id"}
 ),
 CleanNames = Table.TransformColumns(
   Deduped,{
    {"first_name", Text.Proper},
    {"last_name",  Text.Proper}
   }),
 ParseDOB = Table.TransformColumns(
   CleanNames, {
    {"date_of_birth",
      each try Date.FromText(_)
           otherwise null,
      type date}
   }),
 CleanIncome = Table.ReplaceValue(
   ParseDOB,
   each [net_income_zar],
   each if [net_income_zar]<0
       then null
       else [net_income_zar],
   Replacer.ReplaceValue,
   {"net_income_zar"}
 )
in CleanIncome