Skip to content
All work

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

Interactive
Inter-store transfer plannermercaldas-data · demo buildSimulation · synthetic data
target cover · 30 days

Current position

StoreOn handCoverAfter
Centro94085d31d
Palermo1285d30d
La Enea61068d30d
Versalles744d4d
Chipre30523d23d
Fátima1567d24d

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 API where separate scripts used to beThe operations team works in a plain-JavaScript web UI backed by a single FastAPI core that holds the transfer, buying-factor and reporting logic previously spread across standalone scripts. Dagster drives the recurring report runs, delivery goes out over the Gmail API with OAuth2, and SQL Server remains the transactional source.USERSINTERFACEPLATFORMDELIVERYSYSTEMSmigratedOperations & planningused to queue behind meDagsterrecurring schedulesWeb UIplain JS, no build stepFastAPI coretransfers · factors ·reportsStandalone scriptsabsorbed one at a timeGmail APIOAuth2, not SMTPInboxesno human in the loopSQL Servertransactional source
One API where separate scripts used to be

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.