ERP case study
ENRU
Case study - Custom ERP

The ERP behind a $4M-a-month marketplace business

A custom back office for an apparel seller on Wildberries, a major e-commerce marketplace (600M+ monthly visits). The company runs $4M in monthly revenue, a catalog of 600+ SKUs, 12 seller accounts and about 30 users. One system now covers analytics, customer replies, product card creation and supply costing.

FastAPIPostgreSQL 16CeleryReact 18 LLM APIGoogle Sheets APIBitrix24
61
schema migrations
~298
API endpoints, 100 of them in the supply module
110
database tables, 15 GB production DB
~37
metrics per SKU recalculated nightly (T1..T37 map)
12 / 30
seller accounts / daily users
~75K
lines of code: Python + TypeScript
01 Context and pain

How the business lived before the system

A fast-growing seller with 12 marketplace accounts. Each account had its own dashboard, its own API keys and its own spreadsheet. The numbers that ran the business were assembled by hand.

12 dashboards, zero big picture

Every seller account is a separate cabinet in the marketplace. Orders, ads, stock and finance lived in 12 places. Nobody saw the whole company on one screen.

Manual spreadsheets, every day

Managers copied figures from 5 or 6 marketplace screens into a daily control spreadsheet. Realistically 1 to 2 minutes per SKU. With over a thousand active SKUs in a single account, full manual coverage was physically impossible. The long tail of the catalog went unwatched.

Hours to answer "what is our margin?"

The marketplace closes its finance report weekly, with about a 3-day delay. Real margin per SKU per day was unknown. Reports took hours and were already stale when finished.

A queue of customers waiting

At the starting point: 210 unanswered reviews and 1,023 unanswered customer questions across the accounts. New product cards took 15 to 30 minutes of manual work each, across 3 different systems.

02 System architecture

One data backbone, four working surfaces

Nightly Celery jobs pull 6 domains of the Wildberries API into PostgreSQL. A calculation layer turns raw rows into margins, alerts and cost. People work through the React SPA, through familiar Google Sheets, or simply receive alerts. Two automation pipelines push work back into the marketplace.

ANALYTICS LOOP · NIGHTLY Wildberries API 6 domains: statistics · content adverts · feedbacks supplies · analytics rate limits per domain Nightly sync Celery Beat, 38 jobs 01:00 MSK, 11 steps 429 retries via Redis watchdog for stuck runs PostgreSQL 16 15 GB · 110 tables largest table: 8.2M rows Redis 7: cache + broker Calculation layer FIFO cost · margins fact vs forecast gate 11 alert types · snapshots React SPA 54 screens · ~30 users Google Sheets bridge 200+ values per SKU two-way Alerts + Excel 29 report exports AUTOMATION PIPELINES Bitrix24 purchasing task boards Product card pipeline regex parser, ~480 lines approve queue, no LLM Wildberries new product cards up to 62 per day LLM API Haiku · Sonnet · Opus Review + Q&A bot 14 intents, style guide drafts, never auto-sends Manager approves safe send 4 SQL guard layers Wildberries review + question replies state + alerts in DB
External systems ERP services Data store Human control point
38scheduled Celery jobs
11steps in the nightly sync
8.2Mrows in the largest table
61Alembic migrations
7Docker containers behind Nginx

Engineering details that keep it alive: per-domain rate limits on marketplace calls, idempotent upserts everywhere, advisory locks around recalculations, a watchdog that unsticks hung syncs, and an audit log on every mutation in the money-critical modules.

03 Flagship module

The daily control panel: ~37 metrics per SKU, every morning

The heart of the system is a daily per-SKU panel the team calls the pulse board. For every SKU and every date it shows about 37 indicators (an internal T1..T37 map): the full sales funnel, advertising economics, confirmed and forecast margin, stock and transit. All of it recalculated overnight, with zero manual input except unit cost and China transit.

Funnel

Views, add-to-cart, orders, buyouts. Conversion at each step. Cancellations and returns per date. Buyout rate computed three ways, each for its own job.

Advertising

Spend, DRR (ad cost share of revenue), CTR, CPC, CPM, CPO per SKU per day. Data pulled in 31-day windows with retry queues for marketplace 429s.

Margin and economics

Fact margin from the closed finance report. Plan margin from an 8-component model that prices in non-redeemed orders. Effective commission instead of the nominal rate. ROI with one formula everywhere.

Stock and turnover

Remains per warehouse and size, goods in transit to and from customers, manual China transit. Zero-stock and low-stock alerts instead of eyeballing a thousand rows.

How the numbers are made

  1. 01:00 MSK. The nightly sync walks 11 steps across 6 marketplace API domains: orders, sales, stock, warehouse remains, product cards, prices, adverts, funnel, paid storage, the finance report, then averages. Soft timeout 180 minutes, watchdog every 5.
  2. Recalculation. Average logistics and effective commission are rebuilt from the last 3 closed finance weeks. Buyout rate is computed per order cohort: every order in a 30-day window is traced to its final outcome.
  3. 00:05 plan freeze. Yesterday's plan margin is snapshotted so past days never drift when averages update. A classic spreadsheet disease, cured by design.
  4. Fact vs forecast gate. The marketplace confirms finances weekly with a 3-day lag. For every single date the system decides: is this day confirmed by the finance report, or a forecast from the funnel? The UI marks which numbers you can already trust.
  5. Daily history. A 40-metrics-by-14-days history is kept per SKU and served to the spreadsheet bridge as a 20-30 MB gzipped response. Hourly, 11 alert types are re-evaluated with auto-resolve.

Sample metrics and their formulas

MetricQuestion it answersHow it is computed
Buyout rate (cohort) How many orders end as paid sales? Each order in a 30-day window (7-day lag) is traced to its fate. clean / (clean + returned + cancelled), minimum 30 completed orders.
DRR Is advertising paying for itself? ad spend / order revenue x 100, counted the same way the marketplace dashboard counts it.
Effective commission What does the marketplace really take? (retail - payout - acquiring) / retail price x 100 over finance report sale rows. The nominal category rate is shown only as a reference chip.
Fact margin Real profit per day net revenue - commission - logistics - storage - penalties - acquiring - unit cost - ads. Computed only for days confirmed by the finance report, otherwise shown as a dash.
Plan margin Expected profit before the report closes 8 components. expected sales = orders x buyout rate. A non-redeemed order still pays two legs of logistics. Frozen daily by snapshot.
ROI Return on money in the game margin / (unit cost + ad spend) x 100. One formula across list, card and spreadsheet.
Estimated impact

The manual version of this table cannot exist

Collecting one daily row per SKU by hand takes 1 to 2 minutes across 5 or 6 marketplace screens. One account alone holds over 1,100 active SKUs. That is 19 to 38 person-hours per day for one account, so sellers usually track only the top 100 SKUs and lose the tail. A conservative estimate of the replaced routine: 250 to 350 person-hours per month, roughly 2 to 4 full-time analysts. The system does it every night and the tail of the catalog stays watched.

Pulse board: SKU list with alerts, orders, margin, ROI and DRR
SKU list: alert traffic lights, orders, margin, ROI and DRR per row. Problem SKUs float to the top.
Expanded SKU card: 33 metric rows in 8 groups, dates as columns
Expanded SKU card: dates as columns, 33 metric rows in 8 collapsible groups, fact in black, plan in blue.
~37metrics per SKU (T1..T37 map)
1,100+active SKUs in one account
40 x 14daily-history metrics x days
11alert types with auto-resolve
18REST endpoints of the module
04 AI module

AI assistant for reviews and customer questions

Marketplace sellers live or die by response speed and tone. The bot is a three-model LLM pipeline that drafts every reply in the brand voice. A human always presses the send button.

Incoming reviews + questions synced 3x per day 12 accounts Haiku intent classifier 14 intents 30-day answer cache Sonnet writes one reply tool loop: product card confidence score Manager approve or fix low confidence is never auto-sent WB reply API 1 req/sec chunks 40-70 Brand style guide 24 rules · 12 templates from 161 gold references Opus style trainer nightly distillation a rejected draft + the manager's fix become a gold training example
External systems LLM models in the pipeline Learned asset Human control point

1. Haiku classifies

The cheapest model sorts every message into 14 intents: defect return, sizing help, restock, care instructions, delivery, wrong item and more. Identical texts hit a 30-day cache and cost nothing. Star-only reviews skip the LLM entirely and go to a template.

2. Sonnet writes

One reply, not ten options. A tool loop lets the model pull the product card when the question is about fabric or care. Hard business rules sit above everything: no promised discounts, no canned phrases, always the brand signature. Every reply carries a confidence score.

3. Opus trains the voice

Every manager correction is stored as a gold reference. From 161 of them Opus distilled a brand style guide: 24 numbered rules plus 12 per-intent templates. Business bans rank above learned patterns, by explicit design.

Human control points, by design

  1. The bot never sends on its own. Every draft waits in the UI with its intent, confidence and reasoning. The manager approves or rejects.
  2. A rejection teaches the bot. The fix modal saves the triple: customer text, bad draft, corrected reply. It becomes training data for the nightly style distillation.
  3. Low confidence never ships. Drafts under the confidence threshold are flagged, sorted to the top and excluded from any mass send. No override switch exists.
  4. Mass send has 4 SQL guard layers plus a rating floor agreed with the business. Always a dry run first.
  5. Honest failure. If the AI API is down the UI shows a red banner and an empty manual field. No silent template replies to customers.
  6. Marketplace-friendly sending. Random chunks of 40 to 70 replies, pauses of 7 to 29 seconds between replies and about 20 minutes between chunks, 1 request per second.

What it cleared

The starting queue: 210 unanswered reviews and 1,023 questions across the accounts. A single offline generation session produced and loaded 60 review replies plus 661 question replies for the most backlogged account. Per the client's estimate, the work of about 6 employees was taken off review duty.

What it costs

Prompt engineering with a measurable result: the input prompt shrank from 6-8K tokens (80 raw examples) to 1.5-2K tokens (distilled style guide plus 7 references per intent). Plus caches at every step and short-circuits for empty reviews.

Review assistant: queue with AI drafts, intent, confidence and approve or fix buttons
The review workspace: queue on the left, full review with the AI draft, intent, confidence and approve / fix buttons on the right.
05 Automation module

Product card pipeline: from a Bitrix24 task to a live marketplace card

The purchasing team plans new products as tasks on Bitrix24 kanban boards. The pipeline turns a finished purchasing task into a complete marketplace product card: parsed attributes, barcodes, category, brand, manager, photo. What took 15 to 30 minutes of manual work across 3 systems became a supervised conveyor.

Bitrix24 kanban boards trigger stage: goods purchased Scan + parse regex, ~480 lines, no LLM price, size grid, color, composition, package dims Enrichment category + HS code from the nomenclature sheet, brand + manager routing WB Content API barcodes per size card upload + photos 900 x 1200 crop 3 verifications card in WB, task staged, sheet row confirmed Write-back sheet row updated, task renamed, staged, commented 26+ alert codes skip on alert no silent defaults missing data raises a typed alert instead of a broken card
External systems Pipeline steps Consistency checks

What flows through

The trigger is a kanban stage: goods purchased. The pipeline pulls the task title, description, comments and attachments. A regex parser of about 480 lines extracts price, cost, fabric composition, color, the full size grid with quantities and package dimensions. Category and customs HS code come from the nomenclature Google Sheet the managers maintain. Brand and responsible manager are assigned by round-robin rules. Then: barcodes per size, card upload, photo cropped to 900 x 1200, and write-backs to the sheet and the original task.

No LLM at runtime. On purpose.

The parser is deterministic regex, not a model. Same input, same output, testable with regression tests pinned to real incidents, zero inference cost and no hallucinated prices. The module was built from detailed written specs: data contract, formulas, edge cases and acceptance checks. The rule of the module: hard alerts instead of silent defaults.

~62cards created on the peak day
142tasks scanned in one audit run
141data blockers auto-classified in that run
26+typed alert codes
44REST endpoints, 15 dedicated tables

The pipeline doubled as a data quality audit

On its first full run over 142 tasks the pipeline created zero cards. Instead it produced an exact classification of 141 data blockers: 42 tasks without package dimensions, 21 without price, 38 without category rows. Managers received precise to-do lists. Within days the same pipeline was producing up to 62 cards per day. Idempotency at 4 levels means a rerun never creates duplicates, and the invariant is strict: one task equals one full cycle across all 3 systems, verified 3 ways.

Approve queue of the product card pipeline with parsed drafts
The approve queue: parsed drafts with extracted attributes, photo preview and Apply / Edit / Reject actions.
06 Accounting module

Supply and true unit cost: a FIFO engine over the whole chain

Factory orders, production, international and domestic logistics, fulfillment receipts, debts to suppliers and true FIFO unit cost. The module went from zero to production in about 3 weeks and became the financial backbone of the system.

FIFO landed cost

Every receipt creates an immutable cost lot: goods in yuan, international logistics in dollars, domestic legs in rubles, fulfillment handling. Currencies are converted at the central bank rate on the payment date. Each sold unit consumes the oldest lot, returns restore into the newest layer, and everything is idempotent per sale line.

Debts and cash-need

A debt ledger per payer with limits, planned payments and weekly snapshots. The dashboard answers the owner's question directly: who do we owe, how much cash does next week need, and where are the goods right now, from factory to warehouse shelf.

Control points

13 deterministic SQL alert rules watch the chain: an order stuck in planning, production not started in time, payment sums that do not match order sums. Severity levels, auto-resolve, and an audit log on every mutation. 29 Excel report exports for the finance team.

3,256FIFO cost lots
608Kunits tracked through lots
~₽ tens of millionsof COGS computed by FIFO for the period
100endpoints under /api/supply
24dedicated tables, 14 UI screens
~3 weeksfrom zero to production
Supply dashboard: KPI tiles, order funnel, alerts and planned payments
Supply dashboard: debt and cash-need KPIs, a 7-stage order funnel, top alerts and upcoming payments.
Factory order card with items, payments and stage timeline
A factory order card: items, payments, stage timeline and fulfillment stock in one place.
FIFO costing screen comparing computed unit cost with legacy figures
FIFO costing: computed landed cost per lot and per unit, side by side with the legacy manual figures.
07 Adoption trick

Google Sheets bridge: the team never had to migrate

Managers loved their spreadsheet and hated the idea of a new tool. So the spreadsheet stayed. The backend now fills it.

Every morning the bridge bootstraps 200+ values per SKU into the familiar sheet: prices, commissions, funnel conversions, stock, buyout rates, yesterday's ads. Managers type only their action plan and notes, and those edits flow back into the database. A 40-metrics-by-14-days daily history ships as a 20-30 MB gzipped response and renders as the archive tabs they already knew.

Security is strict: hashed service tokens plus a spreadsheet whitelist. A sheet is mapped to exactly one seller account and physically cannot read another account's data.

Why it matters

Zero retraining, zero adoption fight, zero parallel-tool period. The team kept its habits while the data under those habits became a database instead of copy-paste. This is the cheapest change management there is: move the system to the people, not the people to the system.

08 How it was built

Business analysis first, then engineering delivery

One analyst-developer, changes shipped as small, reviewable steps. That is the methodology fact, not the headline: the headline is that the business runs on the result.

  1. Spec first. Every module started as a written specification: data contract, formulas, edge cases, acceptance checks. Several specs were taken all the way down to a step-by-step implementation plan.
  2. Documentation as part of the system. A repository-level runbook defines health checks, operational rules and hard prohibitions, so the system can be operated safely by a team.
  3. Incident-driven hardening. Fixes are pinned with dated comments and regression tests. Idempotency, kill switches and audit logs are default practice, not afterthoughts.
  4. Honest numbers. Every figure in this case study is verified against the repository and production documentation. Estimates are labeled as estimates.

Stack

PythonFastAPISQLAlchemy 2 asyncAlembic Celery + BeatPostgreSQL 16Redis 7 Docker ComposeNginx React 18TypeScriptViteTailwind Radix UIZustandTanStack Query

Integrations

Wildberries API, 6 domainsGoogle Sheets API Bitrix24 RESTLLM APICentral bank FX rates

Delivery pace

110 database tables61 migrations~298 endpoints
09 Outcome

Before and after

AreaBeforeAfter
Daily analytics Manual copy-paste from 5-6 marketplace screens. Only the top SKUs tracked. An estimated 250-350 person-hours per month of routine. Nightly recalculation of ~37 metrics for 1,100+ SKUs per account. The whole catalog watched, alerts surface problems.
Margin visibility Some margin, once a week, after the finance report closed. Fact and plan margin per SKU per day, with an explicit marker of which days are confirmed.
Reviews and questions A queue of 210 reviews and 1,023 questions. Managers wrote each reply by hand. AI drafts in the brand voice, human approves. Per the client's estimate, work of ~6 employees taken off this duty.
New product cards 15-30 minutes of manual work per card across 3 systems. A supervised pipeline, up to 62 cards per day at peak, with typed alerts on bad input data.
Unit economics Approximate unit cost in spreadsheets, no landed-cost view. FIFO landed cost across 3,256 lots and 608K units, debts and cash-need on one dashboard.
Tooling 12 separate marketplace dashboards plus disconnected spreadsheets. One ERP: ~298 endpoints, 110 tables, a web app and the familiar Sheets, all on the same database.

Kirill Sedov

Business analyst and systems builder. I start with the business process and the data contract, then automate. This system was analyzed, specified and shipped by one person for a live $4M-a-month business.

Connect on LinkedIn