was the UI for complex budget planning, with an AI agent handling the optimisation behind the scenes?
If I could just send ‘Run 2026 budget with XXX caps and Y% on Sustainability’ and get a decision back, that would change our budget review.
That’s how a logistics VP framed the problem when we discussed automating portfolio selection for their annual CAPEX approval cycle.
Example of CAPEX Application – (Image by Samir Saci)
Because companies invest in expensive logistics equipment, directors receive CAPEX applications from operations teams for short and long-term projects.
This is a complex exercise as you need to balance between return on investment and long-term strategy.
As a data scientist, how can we support this decision-making process?
Using linear programming, we can help decide which projects to fund to maximise ROI while respecting multi-year budget constraints.
In this article, we will build an AI Agent for Budget Planning that turns email requests into an optimised CAPEX portfolio.
This AI workflow is built using n8n for orchestration and LangGraph to create a reasoning agent connected to a FastAPI microservice that executes a linear programming model.
Objective of the Workflow – (Image by Samir Saci)
We will review the architecture and explore the results using a real request for budget optimisation with a constraint of a minimum 20% allocation for sustainability projects.
Budget Planning Optimisation with Python
Problem Statement: Operational Budget Planning
We are supporting the APAC director of a large Third-Party Logistics Service Provider (3PL) based in Singapore.
Their job is to manage warehousing and transportation operations for other companies in four countries in the Asia Pacific.
We are talking about operations supporting 48 customers grouped in over eight market verticals (Luxury, Cosmetics …).
For instance, they manage a 10,000 sqm warehouse for a large fast-fashion retailer in Beijing, delivering to 50 stores across North China.
Warehouse Manager: We need 150k euros for a new conveyor belt that will increase our receiving productivity by 20%.
Our director receives a list of projects that require capital expenditure (CAPEX) from his 17 warehouse managers across the APAC region.
Examples of CAPEX Application – (Image by Samir Saci)
For each project, the CAPEX application includes a brief description (e.g., renting 500 sqm), a three-year cost profile (Year 1: €115k; Year 2: €120k; Year 3: €150k), and the expected Return On Investment (e.g., +€50k).
These projects can also bring additional benefits:
Business Development: unlock new revenue (e.g., capacity for a new customer or product line)
Sustainability (CO₂ Reduction): lower emissions via energy-efficient equipment or layout changes
Digital Transformation: improve data visibility, automate repetitive tasks, and enable AI-driven decisions
Operational Excellence: increase throughput, reduce defects and rework, shorten changeovers, and stabilise processes.
HSE (Health, Safety & Environment): reduce incident risk and insurance exposure with safer equipment
CSR (Corporate Social Responsibility): strengthen community and workforce initiatives (e.g., training, accessibility)
For instance, a warehouse automation project can reduce packaging use (sustainability), decrease operator strain (CSR), and accelerate digital transformation.
Some of the additional benefits are linked to the top management guidelines that our director needs to follow.
APAC Director: “How should I allocate my budget of XX M€ to maximise the ROI while respecting my top management guidelines?“
As the director receives over 50 projects each session, we proposed to build a linear programming module to determine the optimal selection, taking into account external constraints.
Objective of the solution – (Image by Samir Saci)
As inputs, we get the available budget and the management objectives, along with the spreadsheet of CAPEX applications.
Example of CAPEX Application Spreadsheet – (Image by Samir Saci)
This will be part of the inputs of our solution.
FastAPI Microservice: 0–1 Mixed-Integer Optimiser for CAPEX Budget Planning
To address this problem, we utilised the modelling framework for Linear Programming (LP) and Integer Programming (IP) problems provided by the PuLP library of Python.
The solution is packaged in a FastAPI microservice deployed on the cloud.
Decision Variables
For each project i, we define a binary value to inform if we allocate a budget or not:
Decision Variables – (Image by Samir Saci)
Objective Function
The objective is to maximise the total return on investment of the portfolio of projects we selected:
Objective Function – (Image by Samir Saci)
Constraints
As we cannot spend more than what we have allocated per year, we must consider the budget constraints for the next three years.
Constraints – (Image by Samir Saci)
APAC Director: Our CEO want us to invest 20% of our budget on projects supporting our sustainability roadmap.
Moreover, we may also have constraints on the minimum budget for specific management objectives.
Example of Constraint for Sustainability – (Image by Samir Saci)
In the example above, we ensure that the total investment for sustainability projects is equal to or greater than S_min.
Now that we have defined our model, we can package it as a FastAPI microservice using the code shared in this article.
This overall workflow will accept a specific schema defined using Pydantic for validation and defaults.
from pydantic import BaseModel
from typing import Optional
class LaunchParamsBudget(BaseModel):
budget_year1: int = 1250000
budget_year2: int = 1500000
budget_year3: int = 1750000
set_min_budget: bool = False
min_budget_objective: Optional[str] = 'Sustainability'
min_budget_perc: float = 20
class EmailRequest(BaseModel):
email_text: str
LaunchParamsBudget captures the optimisation inputs:
The three annual budget caps (budget_year1/2/3 in euros)
The optional minimum-allocation rule toggled by set_min_budget with its target management objective min_budget_objective and the required share min_budget_perc in %.
We need to ensure that the Agent respects this schema; if not, it will be unable to query the API.
We include the parameters that will be used for the Graph State variables.
This block EmailParser turns a plain-text email body into typed, schema-valid parameters for our Budget Planning API using an LLM.
On initialisation, we load the chat model from the config and build a LangChain chat model.
The function parse() takes the raw email content sent by n8n HTTP node and the system prompt to invoke the model with structured outputs defined with the LaunchParamsBudget Pydantic schema.
The EmailParser system_prompt stored in the YAML config file (minimal version for concision):
budget_agent:
system_prompt_parser: |
You are a budget planning analyst for LogiGreen.
Your task is to extract structured input parameters from emails
requesting budget optimization runs.
Fields to return (match exactly the schema):
- budget_year1: integer (annual cap for Year 1)
- budget_year2: integer (annual cap for Year 2)
- budget_year3: integer (annual cap for Year 3)
- set_min_budget: boolean (true/false)
- min_budget_objective: string (e.g., "Sustainability")
- min_budget_perc: number (percentage between 0 and 100)
Output ONLY these fields; no extra keys.
It includes a list of fields to parse, along with concise explanations and strict format rules.
We can now call this function in the block BudgetPlanInterpreter defined below.
import logging
import requests
from langchain.chat_models import init_chat_model
from app.utils.functions.budagent_runner import run_budget_api
from app.models.budagent_models import LaunchParamsBudget
from app.utils.config_loader import load_config
logger = logging.getLogger(__name__)
config = load_config()
class BudgetPlanInterpreter:
def __init__(self,
model_name: str = "anthropic:claude-3-7-sonnet-latest",
session_id: str = "test_agent"):
self.llm = init_chat_model(model_name)
self.session_id = session_id
self.api_result: dict | None = None
self.html_summary: str | None = None
async def run_plan(self, params: dict) -> dict:
"""Run budget planning using FastAPI Microservice API"""
try:
launch_params = LaunchParamsBudget(**params)
self.api_result = await run_budget_api(launch_params, self.session_id)
return self.api_result
except Exception as e:
logger.error(f"[BudgetAgent] Direct API call failed: {e}")
return {"error": str(e)}
async def interpret(self, params: dict, api_output: dict | None = None) -> str:
"""Interpret API budget planning results into HTML summary for the Director"""
if api_output is None:
if not self.api_result:
raise ValueError("No API result available.")
api_output = self.api_result
messages = [
{
"role": "system",
"content": config["budget_agent"]["system_prompt_tool"]
},
{
"role": "user",
"content": f"Input parameters: {params}n
nModel results: {api_output}"
}
]
reply = self.llm.invoke(messages)
self.html_summary = reply.content
logger.info("[BudgetPlanAgent] Generated HTML summary")
return self.html_summary
def get_summary(self) -> str:
if not self.html_summary:
raise ValueError("No summary available.")
return self.html_summary
It runs the budget optimiser and turns its JSON into an “executive summary” in HTML format.
We use run_plan() to call the FastAPI microservice utilising the function run_budget_api(s) to retrieve the results
The function interpret() generates the analysis based on the API’s outputs, following the instructions of a system prompt.
We now have our three foundation blocks that can be used to build a graph with nodes and conditional edges.
LangGraph Builder with nodes and conditional edges
Now that we have the three blocks, we can build our graph.
LangGraph – (Image by Samir Saci)
This is a small experimental LangGraph state machine that includes the three steps (parsing, running, interpretation) with error handling at every step.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import logging
from app.utils.functions.budagent_parser import EmailParser
from app.utils.functions.budagent_functions import BudgetPlanInterpreter
from app.utils.config_loader import load_config
logger = logging.getLogger(__name__)
config = load_config()
class AgentState(TypedDict, total=False):
email_text: str
params: dict
budget_results: dict
html_summary: str
error: str
session_id: str
This block is fundamental as it defines the shared state passed between LangGraph nodes
session_id: included in the API call
email_text: raw email body received from the n8n node
params: structured inputs parsed from the email by EmailParser
budget_results: JSON output of the FastAPI microservice
html_summary: the analysis (in HTML format) generated by interpret() based on the output budget_results
error: storing an error message if needed
We should now define the functions of each node that will receive the current AgentState and return a partial update.
route_after_parse will direct the flow based on the output of the email parsing: if error in state → go to "error"; else → "run".
route_after_run will direct the flow based on the output of the FastAPI Microservice calling: if error in state → go to "error"; else → "summarize".
We are nearly done!
We just need to package this in a FastAPI endpoint:
@router.post("/graph_parse_and_run")
async def graph_parse_and_run(request: EmailRequest):
"""
Parse an email body, run Budget Planning, and return an HTML summary — orchestrated via a LangGraph StateGraph.
"""
try:
initial_state = {
"email_text": request.email_text,
"session_id": config.get("budget_agent", {}).get("session_id", "test_agent"),
}
final_state = await _graph.ainvoke(initial_state)
return {
"params": final_state.get("params"),
"budget_results": final_state.get("budget_results"),
"html_summary": final_state.get("html_summary"),
"error": final_state.get("error"),
}
except Exception as e:
logger.exception("[BudAgent] Graph run failed")
raise HTTPException(status_code=500, detail=f"Graph run failed: {e}")
It will be queried by the Query Agent API node of our n8n workflow to return input parameters in params, Budget Optimiser results in budget_results and the summary generated by the Agent Interpreter in html_summary.
A fully functional AI Workflow for Budget Planning
We can now activate the workflow on n8n and test the tool with different scenarios.
What if we don’t have a minimum budget for any management objectives?
I will try to adapt the email to have set_min_budget at False.
Example of email – (Image by Saci)
The email has been well parsed with now set_min_budget at the value False.
New summary with updated constraints – (Image by Samir Saci)
As we could expect, the performance is better:
Total ROI: €1,050,976 (vs. €1,024,051)
ROI/€: €0.26 (vs. €0.24)
Conclusion
This workflow has been presented to the APAC team, who started to “play with it”.
We learned that they use it to prepare slides with different scenarios of portfolio allocation for the board meetings.
This remains a “strategic tool” that is used only a couple of times per year.
However, we plan to reuse the same architecture for more “tactical” tools that supply chain departments can use for ABC Analysis, Stock Management, or Supply Chain Optimisation, as well as human resources for Workforce Planning or business controlling teams.
Can we go beyond this simple workflow?
I am still not satisfied with the contribution of the Agentic part of the workflow.
Indeed, it’s nice to have a tool that can be triggered by an email and provide a concise summary.
However, I would like to explore the idea of having multiple agents proposing different scenarios that would compete against each other.
What would be the impact on the ROI if we increase the minimum budget of sustainability by 15%?
For instance, we can ask agents to run multiple scenarios and provide a comparative study.
We are still experimenting with various types of orchestration to determine the most efficient approach.
This will be the topic of the following articles.
Other examples of Agentic Workflows?
This is not the first time I am trying to link an optimisation tool (packaged in a FastAPI Microservice) with an Agentic Workflow.
My initial attempt was to create a Production Planning Optimisation Agent.
Production Planning Agent – (Image by Samir Saci)
Like here, I packaged an optimisation algorithm in a FastAPI Microservice that I wanted to connect to an email workflow.
Production Planning FastAPI Microservices – (Image by Samir Saci)
Unlike here, the Agentic part of the workflow was handled in n8n with two Agent nodes.
Workflow – (Image by Samir Saci)
The results were quite satisfying, as you can see in the short video linked below.
The user experience was very smooth.
However, the maintenance proved to be challenging for the team.
This is why we wanted to explore the use of Python and TypeScript frameworks for the agentic workflow, like here.
What’s next? Agentic Approach of Business Planning
At our startup, LogiGreen, we are attempting (through experiments like this one) to extend beyond supply chain optimisation and encompass business decision-making.
On my roadmap, I have a tool that I have developed to help small and medium-sized company optimise their cash flow.
Value Chain of the business model of my friend – (Image by Samir Saci)
In another article published in Towards Data Science, I have introduced how I used Python to simulate the financial flows of a company selling renewable paper cups to coffee shops.
“We have to refuse orders as we don’t have enough cash to pay suppliers for stock replenishment.”
A close friend, who owns a small business, was complaining about cash flow issues limiting the development of his company.
I started by tackling the problem of inventory management with an optimised built-in solution using Python.
Inventory Management Rule – (Image by Samir Saci)
Then I enriched the model considering sales channel strategy, payment terms and many other strategic business parameters to help him find the optimal business plan to maximise profitability.
Example of scenarios generated – (Image by Samir Saci)
This solution is probably the next candidate in our experimentation with using agentic workflows to support business and operational decision-making.
For more information, you can have a look at this short presentation of the tool
Currently, it is packaged in a FastAPI microservice connected to a React frontend (and streamlit for the public demo).
UI of the tool available in LogiGreen Apps – (Image by Samir Saci)
I would like to implement an AI workflow that would
Take multiple scenarios (like the ones presented in the video)
Call the API for each scenario
Collect and process the results
Provide a comparative analysis to recommend the best decision
Basically, I would like to outsource to a single (or multiple agents) the complete study presented in the article and the video.
For that, we are exploring multiple approaches to agent orchestration.
We will share our findings in a future article. Stay tuned!
About Me
Let’s connect on Linkedin and Twitter. I am a Supply Chain Engineer who uses data analytics to improve logistics operations and reduce costs.
For consulting or advice on analytics and sustainable supply chain transformation, feel free to contact me via Logigreen Consulting.
If you are interested in Data Analytics and Supply Chain, look at my website.