Work

What I have actually built

Six pieces of work, then the open projects and the live dashboards. The employer cases are described generically — the approach and the trade-offs are the interesting part anyway.

On confidentiality. Employer work is written up generically: no customer names, no internal figures, and no detail that would be useful to a competitor. Happy to go a level deeper in conversation, within what is mine to share.

Applied AI · in production

Context
Nordic B2B distribution · ~50 bn SEK, 7,000+ staff
Years
2025 – present
Role
Owner — modelling, backend, cloud, DevOps, front end
Stack
Azure OpenAI, Python, Terraform, Kubernetes, Azure DevOps, REST APIs, EDI & ERP

A pilot that had defeated four years of attempts

The problem

A large share of customer orders never arrives as clean EDI. They arrive as PDFs — exported from a customer's own ERP, scanned, screenshotted, forwarded, or assembled by hand in Excel and printed. Every one of them has to become structured order lines in the ERP, and until recently a person did that by reading the document and typing.

The idea was not new. The project had been open for roughly four years, and capable people had taken a run at it before me. The technology was not the blocker — by the end there were capable models available to anyone. The blocker was that nobody had defined the problem tightly enough for a solution to be finishable.

What I changed

I started by narrowing it. Not ‘parse any document’ but: which fields, at what confidence, for which document families, failing how, and into which existing order path. Once that was written down, most of the work became ordinary engineering.

  • Extraction against a strict output schema rather than free text, so a bad response is detectable instead of plausible
  • Defaulting and enrichment for the fields customers routinely omit — ship-to, order class, whether the order must ship complete
  • Throttling, back-off and retry handling; the difference between a demo and something that runs unattended at volume
  • Infrastructure as code with Terraform, deployment on Kubernetes, pipelines in Azure DevOps
  • New API endpoints and the front end for the human-review path, because the parts a person touches decide whether the automated parts get trusted
  • An evaluation set of real documents, scored per field, so improvement is a measurement rather than an opinion

Where it landed

It runs. Over recent months the volume of AI-parsed billed orders has grown close to exponentially, and the ceiling now is adoption and document supply rather than the system.

The honest lesson is not a flattering one about me. Several capable people had worked on this. What I brought was not better modelling — it was being willing to spend the first weeks arguing about scope instead of building.

The interesting engineering in an LLM feature is almost never the prompt. It is the schema, the retries, the review path and the evaluation set.

Commercial ML · measured

Context
Nordic B2B distribution · ~50 bn SEK, 7,000+ staff
Years
2024 – present
Role
Designed and built the framework
Stack
Python, Azure, dbt, Databricks, holdout design

Measured against a holdout, not against last year

The problem

A distributor generates an enormous number of small commercial signals every day: a basket left behind, an order that stops repeating, a customer who has quietly moved a category to someone else. Each one is individually trivial and collectively enormous, and almost none of them were being acted on in any systematic way.

The trap is to build a one-off campaign for each. That produces something that works once, cannot be evaluated, and nobody maintains.

What I built

A reusable framework for triggered commercial flows — the shared machinery for detecting a signal, deciding whether it warrants contact, executing, and measuring. Individual flows become configuration rather than new projects.

  • Signal definitions modelled in dbt so the meaning of a trigger is in version control, not in someone's head
  • Eligibility and suppression rules, so the business never has to choose between acting and annoying its customers
  • A holdout group built in from the first flow — a randomly withheld population that receives nothing, so incremental effect is measurable rather than inferred from a before/after chart
  • Reporting that reads the holdout comparison continuously rather than at launch

Where it landed

The first flow built on it was abandoned cart. Compared against its holdout group it produces a material and sustained lift in incremental conversion — enough on its own to justify building the framework rather than another campaign. Several further flows have since been built on the same framework, which was the actual objective — the first flow was the proof, not the product.

The holdout is the part I would defend hardest. Attribution in commercial analytics is mostly wishful, and the standard alternative — comparing to last year — would have credited this with a far larger and completely unreliable number.

Any flow can look like it worked. A holdout group is the cheapest honest answer to “compared to what?”

MLOps · production models

Context
Nordic B2B distribution · ~50 bn SEK, 7,000+ staff
Years
2023 – present
Role
Maintain, modify and extend
Stack
Azure ML, Python, Databricks, dbt, CI/CD

Keeping load-bearing models honest

The work nobody writes case studies about

Several commercial scoring models were already in production when I arrived and are relied on by people who will never read a model card: share of wallet, cross-sales propensity, customer onboarding. My job across these has been maintaining, modifying and extending the Azure ML pipelines behind them.

This is less glamorous than building something new and considerably more consequential. A model that has been quietly wrong for two quarters has already changed how a sales organisation spends its time.

What that involves in practice

  • Reworking pipelines as the upstream data model changes — new source systems, changed definitions, categories that split or merge
  • Investigating drift and deciding whether it is the world changing or the pipeline breaking, which are not the same problem and have opposite fixes
  • Retraining and validation runs that are repeatable rather than heroic
  • Making sure the features a model consumes mean the same thing they did when it was trained

The expensive failures are not models that break. They are models that keep answering.

Machine learning · commercial

Context
Nordic B2B distribution · ~50 bn SEK, 7,000+ staff
Years
2025 – 2026
Role
Data scientist, end to end
Stack
Databricks, dbt, SQL, Power BI & DAX, React / Vite

Scoring which customers deserve the next hour

The problem

A sales organisation has a finite number of hours and a very long customer list. The implicit ranking — who gets called, who gets a visit, who gets forgotten — is usually a mix of history, habit and whoever shouted most recently.

The question was whether an explicit, defensible score could do better: which accounts look most like the ones we serve well and profitably, and which are furthest from their own potential.

What I built

  • A modelled customer table in Databricks, built through dbt so the definitions live in version control instead of in six people's spreadsheets
  • A scoring approach combining fit against an ideal-customer profile with signals of unrealised potential, so a high score means something a salesperson can act on
  • Delivery in two shapes: a Power BI template with the measures written in DAX, so any region can drop in its own data, and a React application for the interactive view — six report pages in total

Where it landed

The part I would defend hardest is the delivery decision. A score that lives in a notebook changes nothing. Shipping the same logic as a template someone can open, filter and argue with is what turns it from an analysis into a tool — and the arguing is the point, because that is where the definition gets sharpened.

A model nobody opens is not a model. It is a file.

Data engineering

Context
Nordic B2B distribution · ~50 bn SEK, 7,000+ staff
Years
2023 – present
Role
Data scientist, platform-adjacent
Stack
Databricks, dbt, SQL, Kafka, Azure, Kubernetes, Power BI, Streamlit

Keeping the platform honest

The unglamorous half of the job

None of the work above exists without a platform that behaves. A representative slice:

  • Deduplication and grain defects in warehouse mart tables — the class of bug that propagates silently into every number downstream and only surfaces when two reports disagree
  • Identity, conditional-access and token-lifetime failures that block cluster authentication — the kind of cross-system error that halts a team until somebody reads the actual trace instead of guessing
  • Semantic-model regressions traced to an upstream connector change rather than to our own code — and being able to demonstrate which it was
  • Building a Streamlit upload path so business users can get their own data into the Databricks/dbt world without a ticket and a two-week wait
  • Sales forecasting treated as a product rather than a report: modelled history, repeatable scoring, and actual-versus-target reporting so accuracy is visible instead of assumed

Why it is on this page

Because it is roughly half of what a working data scientist does, and a CV that hides it is describing a job that does not exist.

“The pipeline is fine” is the single most expensive sentence in data work.

Personal project · open data

Context
Personal project
Role
Sole author
Stack
Python, pandas, scikit-learn, Power BI
Data
Västtrafik open data

Where a tram network actually jams

The question

Public transport operators know when a service is late. It is harder to say where a network is structurally congested — which stops and time bands consistently absorb delay, as opposed to being unlucky on a particular Tuesday.

The approach

  • Västtrafik open data for the Gothenburg region, cleaned and reshaped into stop-level observations across time bands
  • Unsupervised clustering with k-means and DBSCAN, used together deliberately: k-means for structured groupings, DBSCAN because the interesting cases are often the ones that refuse to join a cluster
  • A composite congestion score, so results are comparable across stops rather than only interpretable within one
  • A published Power BI dashboard, because analysis that ends as a PDF gets read once

See it

Open the live dashboard

DBSCAN earns its place here precisely because it is allowed to say “this one belongs nowhere”.

Open projects

Code and reports you can read

Personal and academic projects, every one of them open on GitHub with the written report where there is one. They are studies rather than products, and they are here because reading someone's code and their write-up tells you more than a skills list does.

Customer churn & retention in banking

Logistic regression on complaint and retention behaviour, with the statistical work to check whether the regional differences were real. Written up as a full analysis report.

PythonLogistic regressionStatistics

New York yellow-taxi demand

Descriptive and predictive analysis of demand, peak periods and fare revenue — including what a record blizzard does to a demand model. Linear, polynomial and random-forest regression compared.

PythonRegressionRandom forest

Garment factory productivity

Production performance across departments and seasons, working through outliers, missing values and input errors to something a plant manager could act on.

PythonEDAData quality

Fake-news detection with BERT

Group project on classifying article legitimacy. BERT performed best, but generalised poorly to unseen datasets — which turned out to be the more interesting finding.

NLPBERTTransformers

Modular imputer — strategy & factory patterns

A custom imputer where the imputation strategy (mean, mode, median) is swappable at runtime. An exercise in design patterns applied to something genuinely useful.

PythonOOPDesign patterns

CPU scheduling simulator

FIFO, shortest-job-first, priority and round-robin scheduling implemented side by side, so the trade-offs between average wait and context-switch overhead are visible rather than asserted.

PythonOperating systemsAlgorithms

Bank prototype — object-oriented design

A teaching prototype demonstrating encapsulation, inheritance, polymorphism and abstraction on a domain where getting the abstractions wrong is expensive.

PythonOOPUML

Live

Dashboards you can click

Four published Power BI reports — the congestion model, plus a retail analysis and two visualisation studies from 2024. They are embedded and public, so you can filter and drill rather than take my word for it.

Open the dashboards

A long pier stretching out to sea at sunset

Next step

Want the version with the numbers in it?

Most of what is generalised above has a concrete story behind it. I am glad to walk through any of it — what worked, what did not, and what I would do differently.