MODULE 08 OF 10 · ~45 MIN · ADVANCED · LIGHT CODE

Treasury Data for AI

Garbage in, garbage out. Clean a messy treasury dataset before it touches a model.

What you'll cover
  1. 01Treasury data sources, 5 SQL patterns and the EXTRACT > CLEAN > TRANSFORM > VALIDATE > LOAD pipeline
  2. 026 data quality checks and the 10 pandas commands that cover most treasury data prep
  3. 03Live build: clean 52 weeks of messy cash flow data and engineer 12 features
You walk away with
  • A Python setup guide (or run it all in Claude)
  • The messy and the clean dataset (CSV)
  • Tested check and cleaning scripts

Garbage in, garbage out

The best model in the world cannot fix bad data. The often-quoted rule of thumb: around 80% of an AI project's time goes into data preparation, not modeling. Five ways bad treasury data breaks a model:

Missing values. 3 weeks of cash flow data missing. The model fills the gap with a guess. The forecast becomes fiction.

Duplicate entries. The same payment booked twice in the ERP. The model learns the duplicate as normal and over-forecasts payments.

Inconsistent formats. One bank sends DD/MM/YYYY, another MM/DD/YYYY. The model confuses 4 March with 3 April.

Outliers. A one-time EUR 5M acquisition payment. If it is not flagged, the model expects EUR 5M every quarter.

Stale data. Yesterday's FX rates used for today's exposure. The position is off by thousands.

Before you start: two ways to run the code

Modules 8 and 9 use Python. You do not need to become a programmer: AI writes the code, you check the results. Pick one route.

Route A · Zero install

Use an AI assistant with code execution (for example Claude's code execution / analysis tool). Upload the CSV, paste the prompt, the AI writes and runs the Python and shows you the result.

Good for learning. Limits: nothing is stored between sessions, no connection to your systems, nothing can be scheduled. Use the course data, not your company's.

Route B · Local Python (15–20 min)
  1. Install Python 3.11 or later from python.org. On Windows, tick Add Python to PATH.
  2. Open a terminal (Command Prompt on Windows) and run the two commands below. If pip is not recognized, use py -m pip install …
  3. In Jupyter: New › Python 3, paste the test cell, press Shift + Enter.
Terminal · install, then start Jupyter
pip install pandas numpy matplotlib jupyter prophet scikit-learn openpyxl pytest
jupyter notebook
First Jupyter cell · test
import pandas as pd
print('Setup complete. Pandas version:', pd.__version__)

Prefer Anaconda? It bundles Python, pandas and Jupyter, but not Prophet: add it with conda install -c conda-forge prophet. Check with IT first: Anaconda's terms require a paid licence for larger organizations. Miniforge is the free conda-forge alternative.

A new install today gives you pandas 3. Every script in this module is tested on pandas 2.3 and 3.0.

Five treasury data sources

Each source has its own format, refresh rate and quality issues. Your job is to turn all of them into one clean dataset.

Bank feeds

MT940, MT942, CAMT.053, CAMT.054
SWIFT / EBICS / host-to-host / API

ERP systems

SAP FI tables (BKPF, BSEG), Oracle GL
SQL queries / RFC / OData API

TMS

Deal records, cash positions, exposures
API / CSV export / database views

Market data

FX rates, interest rates, yield curves
Bloomberg / ECB / Refinitiv feeds

Internal

Budgets, forecasts, AR/AP aging, payroll
Excel / SharePoint / Power Query

SQL for treasury: 5 patterns

You do not need to be a database administrator. These five patterns cover most treasury data extraction. Each one below was run against a test database.

1
SELECT + WHERE
One entity, one date range
2
GROUP BY + SUM
Daily into weekly totals
3
JOIN
Bank lines to ERP entries
4
CASE WHEN
Categorize: payroll, supplier…
5
LAG / LEAD
Previous and next period
SQL · the 5 patterns with treasury examples
-- 1. SELECT + WHERE: cash flows for one entity from January 2025 onward
SELECT date, amount, type
FROM cash_flows
WHERE entity = 'Entity_NL'
  AND date >= '2025-01-01';

-- 2. GROUP BY + SUM: daily transactions into weekly totals
SELECT week_number, SUM(amount) AS total
FROM cash_flows
GROUP BY week_number
ORDER BY week_number;

-- 3. JOIN: bank lines matched to ERP entries by reference
SELECT b.date, b.amount, e.gl_account
FROM bank_statements b
JOIN erp_ledger e ON b.reference = e.reference;

-- 3b. LEFT JOIN: the bank lines with NO ERP match (your reconciliation breaks)
SELECT b.date, b.amount, b.reference
FROM bank_statements b
LEFT JOIN erp_ledger e ON b.reference = e.reference
WHERE e.reference IS NULL;

-- 4. CASE WHEN: categorize by description (LOWER makes it case-insensitive everywhere)
SELECT date, amount,
  CASE
    WHEN LOWER(description) LIKE '%payroll%' THEN 'Payroll'
    WHEN LOWER(description) LIKE '%supplier%' THEN 'Supplier'
    ELSE 'Other'
  END AS category
FROM cash_flows;

-- 5. LAG: previous-day balance and the daily change
SELECT date, amount,
  LAG(amount, 1) OVER (ORDER BY date) AS prev_day,
  amount - LAG(amount, 1) OVER (ORDER BY date) AS daily_change
FROM cash_positions;

Two treasury traps. A plain JOIN silently drops bank lines with no ERP match, and those are exactly the items your reconciliation needs. Use the LEFT JOIN in 3b to list them.

LIKE is case-sensitive in some databases (PostgreSQL, for example), so "PAYROLL January" would fall into Other. Wrapping the column in LOWER() makes the rule behave the same everywhere.

The treasury data pipeline

Every dataset goes through the same five stages. Learn it once, apply it to every dataset.

EXTRACT
Pull raw data from source systems
CLEAN
Fix missing values, duplicates, formats
TRANSFORM
Create features, aggregate, categorize
VALIDATE
Check quality, completeness, accuracy
LOAD
Feed into a model or storage

This is ETL plus validation. VALIDATE is the step most teams skip, and it is the one an auditor asks about.

The 6 data quality checks

Run these on every dataset before it touches a model.

01Completeness

Missing values? Gaps in dates? Empty fields?

df.isnull().sum() · date range check

02Uniqueness

Duplicate rows? The same transaction twice?

df.duplicated().sum() · reference check

03Consistency

Mixed date formats? Currency codes vary?

df['date'].dtype · df['ccy'].unique()

04Accuracy

Opening + transactions = closing? Totals match the source?

Balance validation · cross-reference

05Outliers

Extreme values? One-time items skewing the pattern?

df.describe() · IQR · z-score

06Timeliness

How old is this data? When was the last refresh?

max(date) · data freshness

Python + pandas: the 10 commands you need

You are not becoming a data scientist. These 10 commands cover most treasury data preparation.

Python · the 10 commands
df = pd.read_csv('cash_flows.csv')      # 1. load (pd.read_excel for .xlsx)
df.head()                                # 2. preview the first 5 rows
df.info()                                # 3. column types and non-null counts
df.describe()                            # 4. min, max, mean, std per column
df.isnull().sum()                        # 5. missing values per column
df = df.ffill()                          # 6. forward fill (or df.dropna())
df = df.drop_duplicates()                # 7. remove exact duplicate rows
df['date'] = pd.to_datetime(df['date'], format='%d/%m/%Y')   # 8. fix dates
weekly = df.groupby('week')['amount'].sum()                  # 9. aggregate
df['prev_week'] = df['amount'].shift(1)                      # 10. lag feature

Watch out for old tutorials. df.fillna(method='ffill') appears in many guides. It was deprecated in pandas 2 and raises an error in pandas 3. Use df.ffill().

Command 8 names the format explicitly. dayfirst=True alone is only a hint. When one file mixes formats, pandas either stops with an error or guesses, and a guess is how 4 March becomes 3 April. The live build shows it.

Feature engineering for treasury

Features are the signals a model learns from. Better features, better forecast, even with a simple model.

TIME

Month, quarter, week of year, "does this week contain a month end or quarter end".

LAG

Previous week, previous 4 weeks, same week last year, rolling 4-week average.

CATEGORY

Transaction type (payroll, supplier, customer, IC, tax, fees), one-hot encoded for ML models.

BUSINESS

Payroll week, tax week, holiday, budget period. Events you already know about in advance.

DERIVED

Cumulative balance, net flow, inflow/outflow ratio, rolling volatility, deviation from the mean.

Match features to the grain of your data. On weekly data, "day of week" is the same for every row, and "is month end" is almost never true, so use "week contains a month end" instead. A feature that never changes teaches the model nothing.

LIVE BUILD

The dataset: 52 weeks of messy cash flow data

Synthetic weekly cash flows for 2025, in EUR, with six problems planted on purpose. Download it and use it in both routes.

1

Receipts missing for 3 weeks. Weeks 15, 16, 17 are empty.

2

A duplicate row. Week 22 (a payroll week) appears twice.

3

Mixed date formats. Weeks 1–26 are DD/MM/YYYY, weeks 27–52 are YYYY-MM-DD.

4

A one-off. Week 38: a EUR 5.2M acquisition payment, funded the same week by a EUR 5.0M loan from the parent.

5

Inconsistent currency codes. EUR, eur and Euro, at random.

6

A sign error. Week 29: supplier payments booked as positive.

STEP 1 · DIAGNOSE

Run the 6 checks

Upload the CSV and paste this prompt (Route A), or run the script in Jupyter (Route B). Diagnose first, fix later.

Prompt · 6 quality checks
I have uploaded treasury_cash_flows_messy.csv: 52 weeks of weekly cash flows in EUR. Columns: week_number, date, receipts, supplier_payments, payroll, tax, intercompany, other, net_flow, currency. Outflows are negative.

Use Python (pandas) to run these 6 data quality checks and show the output of each. Do not fix anything yet.
1. Completeness: df.isnull().sum(). Which columns and weeks have missing values?
2. Uniqueness: df.duplicated().sum(). Which rows are duplicated?
3. Consistency: what type is the date column, which date formats appear, and what does df['currency'].unique() show?
4. Accuracy: add up the six flow columns per week and compare with net_flow. List the weeks that do not reconcile.
5. Outliers: flag any value more than 3 standard deviations from its column mean. List week and column.
6. Timeliness: what is the date range? Any gaps or repeated weeks in the weekly sequence?
Python script · tested on pandas 2.3 and 3.0
m8_checks.py
import pandas as pd

df = pd.read_csv('treasury_cash_flows_messy.csv')
FLOWS = ['receipts', 'supplier_payments', 'payroll', 'tax', 'intercompany', 'other']

# 1. Completeness
print(df.isnull().sum())
# 2. Uniqueness
print('Duplicate rows:', df.duplicated().sum())
# 3. Consistency
print('Date column type:', df['date'].dtype, '| sample:', df['date'].iloc[[0, -1]].tolist())
print('Currency codes:', df['currency'].unique())
# 4. Accuracy: do the flows add up to net_flow?
diff = df[FLOWS].sum(axis=1) - df['net_flow']
print('Weeks that do not reconcile:', df.loc[diff.abs() > 1, 'week_number'].tolist())
# 5. Outliers: more than 3 standard deviations from the column mean
z = (df[FLOWS] - df[FLOWS].mean()) / df[FLOWS].std()
for col in FLOWS:
    weeks = df.loc[z[col].abs() > 3, 'week_number'].tolist()
    if weeks:
        print(f'Outliers in {col}: weeks {weeks}')
# 6. Timeliness: date range (after parsing, see the fix script)
What you should see · open after you run it
  • Completeness: 3 missing values, all in receipts (weeks 15, 16, 17).
  • Uniqueness: 1 duplicate row (week 22). The file has 53 rows, not 52.
  • Consistency: dates are stored as text, in two formats. Three spellings of the currency: EUR, eur, Euro.
  • Accuracy: weeks 15, 16, 17 (missing receipts) and 29 (sign error) do not reconcile to net_flow.
  • Outliers: week 29 (supplier), week 38 (intercompany and other). Weeks 13, 26, 39 and 52 are flagged too: quarterly tax. That is a false positive. The test flags, the treasurer decides.
  • Timeliness: 6 January to 29 December 2025. The duplicate shows up as a repeated week, not as a gap. Once it is removed, the sequence has no gaps.
STEP 2 · FIX

Fix all six, then prove it

The prompt spells out how to fix each issue. Left to decide on its own, the AI tends to pick the two fixes that quietly corrupt the data.

Prompt · fix and validate
Now fix the issues. Follow these rules exactly:
1. Duplicates: drop exact duplicate rows, keep the first.
2. Dates: the file mixes DD/MM/YYYY and YYYY-MM-DD. Parse each format explicitly (format='%d/%m/%Y' and format='%Y-%m-%d'). Never let pandas guess day versus month. Confirm every date parsed and the weeks are exactly 7 days apart.
3. Missing receipts (weeks 15-17): net_flow comes from the bank statement, so rebuild receipts = net_flow minus the other five flow columns. Do not interpolate.
4. Currency: standardize to 'EUR'.
5. Supplier payments are outflows: make every value negative.
6. Week 38 holds a EUR 5.2M acquisition payment and its EUR 5.0M intercompany funding. Add non_recurring=True for that week. Flag it, do not delete it.
Then prove it: every week must now satisfy sum of flows = net_flow. Show df.info() and df.describe().
Trap 1 · Dates

We tested the tempting shortcut: pd.to_datetime(df['date'], format='mixed', dayfirst=True). On pandas 3.0 it reads 2025-12-01 as 12 January. No error, no warning. Parse each format explicitly.

Trap 2 · Missing receipts

Interpolation missed the real receipts by EUR 33K–81K a week, and those weeks no longer reconciled. The net flow comes from the bank, so the gap can be rebuilt exactly: receipts = net_flow − the other flows. Estimate only what you cannot rebuild.

Python script · fixes, validation and features · tested
m8_clean.py
import pandas as pd

df = pd.read_csv('treasury_cash_flows_messy.csv')
FLOWS = ['receipts', 'supplier_payments', 'payroll', 'tax', 'intercompany', 'other']

# 1. Duplicates: drop exact duplicate rows, keep the first
df = df.drop_duplicates(keep='first').reset_index(drop=True)

# 2. Dates: parse each format explicitly. Never let pandas guess day vs month.
iso = pd.to_datetime(df['date'], format='%Y-%m-%d', errors='coerce')
dmy = pd.to_datetime(df['date'], format='%d/%m/%Y', errors='coerce')
df['date'] = iso.fillna(dmy)
assert df['date'].notna().all(), 'Some dates match neither format'
assert (df['date'].diff().dropna() == pd.Timedelta(days=7)).all(), 'Gap in the weekly sequence'

# 3. Missing receipts: net_flow comes from the bank, so rebuild receipts exactly
gap = df['receipts'].isna()
df.loc[gap, 'receipts'] = df.loc[gap, 'net_flow'] - df.loc[gap, FLOWS[1:]].sum(axis=1)

# 4. Currency codes
df['currency'] = df['currency'].str.strip().str.upper().replace({'EURO': 'EUR'})

# 5. Sign error: supplier payments are always outflows
df['supplier_payments'] = -df['supplier_payments'].abs()

# 6. One-off: flag, do not delete
df['non_recurring'] = df['week_number'] == 38

# Validate: every week must reconcile again
assert ((df[FLOWS].sum(axis=1) - df['net_flow']).abs() < 1).all(), 'Flows do not add up to net_flow'

# Features
week_end = df['date'] + pd.Timedelta(days=6)
df['month'] = df['date'].dt.month
df['quarter'] = df['date'].dt.quarter
df['week_of_year'] = df['date'].dt.isocalendar().week.astype(int)
df['contains_month_end'] = week_end.dt.month != df['date'].dt.month
df['contains_quarter_end'] = df['contains_month_end'] & df['month'].isin([3, 6, 9, 12])
df['prev_week_net'] = df['net_flow'].shift(1)
df['prev_4week_net'] = df['net_flow'].shift(4)
df['rolling_4week_avg'] = df['net_flow'].rolling(4).mean()
df['is_payroll_week'] = df['payroll'] != 0
df['is_tax_week'] = df['tax'] != 0
df['cumulative_balance'] = 2_450_000 + df['net_flow'].cumsum()
inflows = df['receipts'] + df[FLOWS[1:]].clip(lower=0).sum(axis=1)
outflows = -df[FLOWS[1:]].clip(upper=0).sum(axis=1)
df['inflow_outflow_ratio'] = (inflows / outflows).round(3)

df.info()
df.to_csv('treasury_cash_flows_clean.csv', index=False)
STEP 3 · FEATURES

Add 12 features and save a model-ready dataset

The outlier is flagged, not deleted: the model should know week 38 happened, but not learn from it.

Prompt · feature engineering
Now add 12 features for a forecasting model:
1. Time: month, quarter, week_of_year, contains_month_end, contains_quarter_end (does the week, Monday to Sunday, include a month end or quarter end?)
2. Lag: prev_week_net (shift 1), prev_4week_net (shift 4), rolling_4week_avg
3. Business: is_payroll_week (payroll is not zero), is_tax_week (tax is not zero)
4. Derived: cumulative_balance (starting from EUR 2,450,000), inflow_outflow_ratio
Show the final DataFrame and save it as treasury_cash_flows_clean.csv.

Result: 52 rows, 10 original columns, the non_recurring flag and 12 features. Every week reconciles. The lag features are empty in the first rows (there is no week 0), which is expected.

This file feeds Module 9. If your AI output differs, compare it with ours:

↓ treasury_cash_flows_clean.csv

The Microsoft ecosystem for treasury data

For when you scale. Start with Python and pandas for cleaning, Power Query for extraction, Power BI for visuals. Add the rest as you grow.

Power Query. Extraction and transformation, built into Excel and Power BI. No code for basic ETL.

Beginner

Power BI. Dashboards on any data source. Scheduled refreshes. Share with the team.

Beginner

Power Automate. Workflow automation: run data pipelines on a schedule or an event, email alerts, file handling.

Intermediate

Microsoft Fabric. The unified data platform: lakehouse, data engineering, notebooks, ML. Enterprise scale.

Advanced

Copilot. The AI assistant inside Excel, Power BI and Power Automate. Data analysis in plain language.

Beginner

What you take away

01

Data quality is most of the work. The model is the easy part.

02

5 treasury data sources: banks, ERP, TMS, market data, internal.

03

5 SQL patterns, plus the LEFT JOIN that finds what did not match.

04

The EXTRACT > CLEAN > TRANSFORM > VALIDATE > LOAD pipeline.

05

6 data quality checks on every dataset. A flag is a question, not a verdict.

06

10 pandas commands, written for pandas 3.

07

Rebuild what you can, estimate only what you cannot. Parse dates explicitly.

08

Features that match the grain of your data: time, lag, category, business, derived.

✓ Module 8 complete

You cleaned a treasury dataset from scratch.

Same six checks, same fixes, on every dataset you will ever feed a model. With real data it takes longer. The process is identical.

Next: Module 9, Building Cash Flow Forecasting Models. The clean dataset becomes a backtested 13-week forecast with three scenarios.

Built by a treasurer, for treasurers. · treasuryease.com