Work

Selected projects from consultancy practice. Client names and figures are generalised where the work is confidential; the methods and outcomes are real.

SAF allocation optimisation for international airlines

Context. Monthly allocation of sustainable aviation fuel across tens of airports for airline clients, matching constrained supply against constrained demand to maximise emissions reduction.

The problem. The allocation was built by hand in Excel every month. A single cycle took two weeks of consultant time, which made it expensive to run and effectively impossible to explore alternatives .. by the time one scenario was finished, there was no appetite to test a second.

What I built. An end-to-end pipeline: automated ingestion of supply and demand data, the allocation formulated as a convex optimisation problem and solved with cvxpy, and results published to a Power BI dashboard where a Sankey diagram shows fuel flowing from each supply source to each airport.

Convex formulation was the right call over a metaheuristic: it returns a provably optimal solution rather than a good one, and provable optimality is what makes the result defensible to a client committing fuel volumes against it.

The Sankey mattered more than it sounds. An optimiser’s output is a table of numbers with no intuition attached, and clients don’t sign off on tables they can’t interrogate. Seeing the allocation as flow (which sources feed which airports, where the volume concentrates) is what let them challenge the result, and being able to challenge it is what let them trust it.

What changed. The allocation calculation went from two weeks of manual work to two hours .. a 98% reduction on that step. Certification, QC and client reporting still need a consultant, so the saving across the full engagement is closer to 70%. The system has run in production every month for over two years.

Stack. Python, cvxpy, pandas, Power BI.


Uncertainty quantification for heating networks

What I built. A Monte Carlo simulation over tenant connection scenarios. Each iteration produced a viable network configuration and its cost, propagating through to a cost distribution rather than a point estimate, with convergence monitored so the simulation stopped once the distribution stabilised rather than running a fixed count regardless.

Then the geometry rather than just the numbers: all simulated networks overlaid in ArcGIS Pro, so each pipe segment carried the frequency with which it appeared across thousands of scenarios. That turns a cost distribution into a map. Segments present in nearly every scenario are structural .. they get built almost regardless of who connects. Segments appearing in a minority are where the uncertainty actually lives.

Results went to Power BI, where the client could read P50 against P80 and see what the difference cost them.

What changed. The scheme could be discussed in terms of exposure rather than a single number, and the frequency map gave the design team somewhere to point: build out the high-frequency spine with confidence, treat the low-frequency branches as contingent on securing connections.

Stack. Python, SciPy, ArcGIS Pro, Power BI.


Spatially-aware GraphRAG over engineering project archives

The constraint. Conventional GraphRAG extracts entities and relationships from text. That handles reports and correspondence and has nothing to say about a shapefile (yet in engineering consultancy much of what a project knows is geometric). Two schemes are related because they share a catchment, not because a document mentions both.

What I built. A knowledge-graph backend on Azure Cosmos DB (Gremlin API), with entity-relationship triples extracted from documents by LLMs and Docling handling PDF parsing.

Spatial data is brought into the same graph rather than left beside it: geometries are converted to network structure with city2graph, so catchment adjacency and network connectivity become traversable edges alongside the text-derived entities. Uploaded project boundaries also trigger enrichment from open environmental datasets, so the graph gains context about a location that nobody in the firm ever typed into a document.

Retrieval runs three ways over the same corpus ..vector similarity and keyword matching through Azure AI Search hybrid, plus graph traversal in Cosmos. Text search finds documents that talk about a problem; the graph finds projects that are structurally or spatially related to it, which is usually the more useful question.

Stack. Python, Azure Cosmos DB (Gremlin), Azure AI Search, OpenAI API, Docling, city2graph.


Open-source spatial prioritisation for JNCC

Context. Technical lead on the multi-criteria analysis underpinning JNCC’s Spatial Prioritisation programme, which ranks land management interventions by their likely delivery of ecosystem services across England. I led development of the analytical tooling; domain expertise came from Ramboll’s water team. Each analysis produced a priority map for one ecosystem service (river water quality, river flow variability, coastal water quality and coastal erosion) identifying where delivering that service matters most. Those maps become the weighting layer for the programme’s wider decision analysis.

The problem. Land management decisions balance competing environmental priorities against finite land and a limited budget. Producing a defensible, parcel-level view of where each service matters means combining many spatial criteria consistently at national scale (and doing it so a policy team can revisit it as priorities shift).

The constraint. Two, and they pulled against each other. The analysis had to cover England at parcel level using only open data, which is a lot of data. And the client required the entire toolchain to be open source with all configuration driven from Excel (not a preference but a handover requirement, since the JNCC team had to re-run the analysis, change weightings and interrogate results without a Python developer). That shaped the architecture: anything a non-programmer might reasonably want to change had to live outside the code.

What I built. Every parameter (criteria, weights, thresholds, input paths) read from a structured Excel workbook, making the analysis reconfigurable by the people who owned it. Built on GeoPandas, rasterio and Fiona, with QGIS for inspection and cartography.

What changed. JNCC ran and reconfigured the analysis themselves after handover

Stack. Python, GeoPandas, rasterio, Fiona, pandas, openpyxl, QGIS.


Nature-based solutions prioritisation for National Highways

Context. A methodology for identifying where nature-based solutions should be deployed across the strategic road network in England.

The problem. NBS interventions tend to get placed where an opportunity is noticed rather than where the case is strongest. Across a national network that spreads budget thinly, and it means the sites where an engineering need and an environmental gain coincide aren’t systematically found.

What I built. A spatial framework crossing two axes: whether the network causes a problem at that location — flooding, drainage failure, noise, severance — and whether the surrounding environment is in good or poor condition.

The four quadrants imply four different responses. Where a network problem meets a degraded environment, an intervention does two jobs at once: highest priority. Where a functioning network sits beside a healthy environment, the answer is to maintain, not to spend. The two mixed cases fall in the middle. Applied across the network, that produces a prioritised map rather than a list — and a logic an engineer, an ecologist or a director can follow without the model being explained to them.

Stack. Python, Arcpy