Backend · Internal tooling
Internal operations platform
A central API that absorbed the recurring work operations used to do by hand: inter-store transfers, buying factors, scheduled reports.
- Repo
- mercaldas-data
- Context
- Mercaldas
- Role
- Sole engineer
- Status
- In production since 2025
The problem
Recurring operational work lived in individual scripts with no shared API and no interface. Planning a stock transfer meant asking the data person and waiting. Every task was a standing interruption.
Stack
- Python
- FastAPI
- pandas
- SQL Server
- Dagster
- Docker
- Gmail API
What it does
Self-service
Operations plans transfers in a web tool instead of queuing behind one engineer
Hours to minutes
Time to produce a transfer plan
Several per week
Recurring reports generated and delivered unattended
One API
Scripts consolidated without stopping the operation
Try it yourself
InteractiveCurrent position
| Store | On hand | Cover | After |
|---|---|---|---|
| Centro | 940 | 85d | 31d |
| Palermo | 128 | 5d | 30d |
| La Enea | 610 | 68d | 30d |
| Versalles | 74 | 4d | 4d |
| Chipre | 305 | 23d | 23d |
| Fátima | 156 | 7d | 24d |
Cover is on-hand units divided by the store’s daily rate. Red is under twelve days, amber over fifty-five. Those are the two states that cost money.
Suggested transfers
- CentroPalermo
- La EneaFátima
Not solvable from stock on hand: after every worthwhile move, the network is still 466 units short, and Versalles stays well under target. The tool says so rather than proposing moves that only spread the shortage around. This is a purchasing decision, not a transfer one.
The real tool runs against live inventory and sales, handles pack sizes and route constraints, and is operated by the purchasing team rather than by me. Stores, stock and sales rates here are invented, and nothing is submitted anywhere.
Context
The least glamorous project here, and the one that removed the most friction per line of code.
Operations and planning ran on a collection of scripts. Each solved a real problem: plan a stock transfer between stores, work out promotional buying factors, assemble a sales and inventory report. Each had its own conventions and no interface. Running any of them meant asking me.
The constraint that shaped the design: the operation could not stop while this was fixed. There is no window in which the stores agree to stop needing transfers. So it had to be a consolidation that absorbed one module at a time, with the old path alive throughout.
Architecture
One FastAPI core holding the logic the scripts held individually. Not a rewrite of each algorithm, those were mostly fine, but a single place for what every script had reimplemented: database access, environment handling, scheduling, delivery, error reporting.
No medallion architecture, no data lake, and the divergence is the point. Every other data project I run here is layered Bronze/Silver/Gold on Delta Lake. Applying that would have been consistent and wrong. Those pipelines build trustworthy history for models. This one moves data between transactional systems so a person can decide in the next ten minutes. Medallion buys reproducibility and lineage at the cost of latency and indirection. An operations tool wants the opposite trade. Using the house pattern where it does not fit is how a house pattern becomes a cargo cult.
Plain JavaScript UI, no framework. Not a preference, a maintenance calculation. React adds a build pipeline, a dependency tree and an upgrade cadence, all maintained by one person who also owns the forecast pipeline and the price platform. The transfer planner needs forms, a table and a few interactions. Server-rendered pages with vanilla JavaScript do that, and still work in three years.
Dagster for recurring reports, so schedule failures surface where every other schedule failure does. Gmail API with OAuth2 rather than SMTP: no password in a config file, revocable access, and it survives the policy changes that periodically break SMTP.
The hard part
Not technical. It was deciding that transfer planning should be self-service.
The easy version is an API I call on someone's behalf, or a report that lands in an inbox. That keeps the interface simple and keeps the work coming back to me forever. The harder version is a tool the operations team drives themselves, which means the interface has to encode enough of the domain that someone who does not know the data model gets a correct answer: sensible defaults, constraints that make invalid plans hard to express, output that is obviously right or obviously wrong.
Different design problem from pipeline work, and the one with the larger payoff. A transfer plan used to be a request in a queue behind whatever else I was doing. Now the ceiling on how many the company can make in a week is no longer me.
Trade-offs
Test coverage came second, and I will say so plainly. The modules people needed most shipped before an automated suite existed. Right call for the first weeks, since an operations team waiting on a tool is a cost paid daily and the manual path was the fallback. It is technical debt now, tracked as debt in the project's own backlog rather than discovered later by whoever inherits it. The difference between a decision and an oversight is whether it is written down.
A shared Postgres instance with another internal project. Cheap, and one failure domain covering two systems. Managed as a known risk: I know what falls over together and what recovery looks like.
Consolidation, not rewrite. Some absorbed logic still carries the shape of the script it came from. The alternative was a clean rewrite delivering nothing until finished. Operations gets more from an inconsistent platform in production than an elegant one in progress.
Results
Several recurring reports generate and deliver themselves, each previously assembled by hand. Transfer planning runs as a self-service web tool used directly by the operations team, cutting each plan from hours to minutes. Business logic consolidated behind one API, migrated module by module without an outage.
Business figures are published as ranges. The company is named, the exact numbers are not mine to publish.
Technical metrics
No medallion
A deliberate divergence: this problem is operational
Plain JS UI
No frontend framework, minimal build for a team of one
OAuth2
Delivery over the Gmail API rather than SMTP
Mercaldas is named as the employer, but business figures on this page are published as ranges, orders of magnitude or relative percentages. Exact revenue, margin and volume figures, source code and screenshots of real operating data are not published.