A client uploads four messy exports. Nothing that decides anything is a language model.
Two halves that never call each other. n8n takes the payment, issues a single use token and receives the files. A Python service reads what landed, computes every figure and every verdict arithmetically, then asks Claude to write about numbers that are already final. Supabase sits between them and holds the state.
The live workflow, laid out as it sits in the editor. Payment across the top, then three upload endpoints running in parallel below it. Node names and positions are the exported ones.
The last node above posts a job id to a Python service and the workflow ends. Everything below happens out of n8n's sight, against Supabase.
Reads the job status and stops silently unless it is pending, queued or waiting on upload. The intake workflow can fire the same job twice and only the first one runs.
Status and a start timestamp. The dashboard polls this rather than waiting on a response, because there is no response to wait on.
File rows out of Supabase, objects out of S3, then read into dataframes. Export names are remapped to internal ones and duplicate uploads of the same kind are concatenated rather than rejected.
Twelve steps, no network calls at all. SKU aggregates, cost of goods joined, fulfilment joined at SKU level with an order level fallback, ad spend allocated per channel by direct join or weighted by revenue share, contribution profit, overhead allocated by revenue share, true net profit and margin, then a scaling flag per SKU against fixed thresholds. Every number on the report exists by the end of this function.
The only outbound call in the pipeline. Renders the finished figures into text, sends them to Claude and asks for findings, three immediate fixes, three scaling opportunities, a structural weakness and a margin recovery range. The reply is parsed, validated against a required key set and stored.
One row into run results carrying the SKU analysis, channel analysis, scaling flags, raw metrics and the narrative, each in its own column. Then the job is marked complete.
Python, no model involved
Claude, narrative only
Every SKU already carries its flag when the prompt is built, set by comparing actual CAC against break even CAC with a fixed buffer. Nothing the model returns is read back into a calculation, a threshold or a ranking. It is stored in its own column, next to the numbers rather than inside them. If it were wired the other way the same report would come back different on a second run against the same files.
Auth failure, rate limit, connection drop, a reply that is not JSON, a reply missing a required key. All of them are caught and turned into an error string. The pipeline logs a warning, writes the run anyway and still marks the job complete. The client gets their figures with the commentary missing rather than a job stuck at processing.