04/Internal Tools

OrderExportforProductionPlanning

Removing the developer from a routine data request

Year
2026
Status
Internal production
Category
Internal Tools
Stack
Python 3.12 · FastAPI · Pydantic v2

01/Context

The business is made-to-order, but sometimes has matching finished stock sitting in spreadsheets maintained outside Shopify. Planners were manually cross-checking each order line against three separate Google Sheets.

The result was a developer-in-the-loop process for a routine request — the kind of dependency that looks small until you count how often it happens.

02/At a glance

Problem
Production planners needed one row per line item, each tagged with the warehouse that will actually ship it. Shopify's native export does not produce that shape, and Shopify exposes several non-equivalent 'location' values — so 'which warehouse' is a decision problem, not a field lookup.
Built
A stateless FastAPI service where the planning team picks a date range, previews the orders in-browser, and downloads Excel or CSV — behind company Google sign-in, with nothing customer-related written to disk.
Role
Sole developer: requirements analysis, Shopify schema investigation, architecture, implementation, tests, containerisation and the cloud deployment.
Changed
A non-technical team now self-serves data that previously required a developer to run local scripts on request.

03/Architecture

A stateless container behind Google sign-in. Identity is terminated at the proxy, the app holds no durable storage beyond a short-lived preview cache, and every external dependency is allowed to fail without taking the export with it.

Order Export for Production Planning architecture diagramData flows through 11 components: Planning team browser (Interface), TLS + Google sign-in (Service), FastAPI export service (Service), Preview cache (Store), Warehouse resolver (Decision logic), Secret Manager (Store), Structured logging (Output), Stock matcher (Decision logic), Shopify Admin GraphQL (External), Excel / CSV builders (Output), Stock workbooks ×3 (External). Connections: Planning team browser to TLS + Google sign-in; TLS + Google sign-in to FastAPI export service; FastAPI export service to Preview cache; FastAPI export service to Warehouse resolver; Warehouse resolver to Shopify Admin GraphQL; Warehouse resolver to Stock matcher; Stock matcher to Stock workbooks ×3; FastAPI export service to Secret Manager; Warehouse resolver to Excel / CSV builders; Excel / CSV builders to Planning team browser; FastAPI export service to Structured logging.Planning team browserServer-rendered page, no bu…TLS + Google sign-inoauth2-proxy · Workspace do…FastAPI export servi…Stateless, non-root contain…Preview cache10 min TTL, keyed by reques…Warehouse resolverfulfilled → assigned → Unkn…Secret ManagerClient secret → ~24h tokenStructured loggingPII-redacted by constructionStock matcherstyle + colour + size; cons…Shopify Admin GraphQLCost-aware pacing, read-onl…Excel / CSV buildersGenerated fully in memoryStock workbooks ×3Read-only service account, …

04/The system

Two passes, because one query is too expensive

A single fully-nested query for orders, line items and fulfillment data exceeds Shopify's 1000-point per-query cost cap. So the fetch runs in two passes: orders plus line items, then per-order fulfillment.

The HTTP client reads the throttle status from the cost extension on every response and paces against the real bucket, rather than guessing at a safe delay.

Warehouse attribution with provenance

A dedicated resolver assigns each unit to a location using the actual fulfillment location first, then the assigned fulfillment-order location — never inventory levels, which answer a different question.

A split line item fans out into multiple rows, each tagged with how the warehouse was determined: fulfilled, assigned, or Unknown. Planners can see how much to trust each row instead of receiving a confident guess.

A conservative stock recommendation

A separate module reads three stock workbooks via a read-only service account and indexes them on normalized style, colour and size — deliberately never on style ID, because season prefixes differ between systems.

It emits Yes / Partial / No / Review per line, and fails soft: if the sheets are unreachable, the export still ships without the stock column rather than failing entirely.

Stateless by design

Exports are generated in memory. Nothing customer-related is written to disk or to a database; structured logs carry order references, counts and statuses only. The GraphQL documents deliberately select no customer name, email, address, phone or payment field.

05/What was hard

'Which warehouse' has three plausible answers

Shopify exposes the actual fulfillment location, the assigned fulfillment-order location, and inventory levels — and they disagree. Picking an order of preference and then shipping the provenance alongside the answer turned an ambiguous field lookup into information a planner can reason about.

Matching stock across two naming systems

The stock workbooks and Shopify do not agree on style IDs, because season prefixes differ. Indexing on normalized style, colour and size instead is less elegant and considerably more correct.

A stale build shipped once

The deployment runbook records the incident and the grep freshness check added to prevent it. Writing down the pitfall that actually happened, in the runbook, is worth more than a process document nobody reads.

06/Automation

Replaces a developer running local scripts on request, and the manual cross-check of each order line against three stock spreadsheets. Line-item fan-out, warehouse attribution, split-shipment expansion, timezone conversion and the stock recommendation are all computed rather than hand-assembled.

08/Direction & delivery

Eleven numbered deliverable documents covering requirements analysis, architecture, data mapping, warehouse logic, GraphQL cost math, security controls, deployment, a deployment checklist, a non-technical end-user guide, and a prioritized roadmap that names the infrastructure each idea would add. The README carries an explicit 'caveats to verify before production' list. The operational timezone was deliberately reversed at one point, and the reason is recorded.

09/Scale

25
Application modules
90
Test functions
11
Numbered docs
33
Export columns
2
GraphQL passes

Objective size signals taken from the repository. No impact metrics are claimed that the source does not prove.

10/What I would tell the next person

  • Ship provenance with an inferred value. 'Warehouse: X (assigned)' is more useful than 'Warehouse: X'.
  • Let non-critical dependencies fail soft. A missing stock column beats a failed export.
  • Record the incident that actually happened, next to the check that now prevents it.

11/Technologies

  • Python 3.12
  • FastAPI
  • Pydantic v2
  • httpx
  • tenacity
  • Jinja2
  • XlsxWriter
  • gspread
  • Docker
  • Cloud Run
  • Cloud Build
  • IAP
  • oauth2-proxy
  • Caddy
  • pytest
  • ruff

Integrations

  • Shopify Admin GraphQL (orders, fulfillment orders, fulfillments)
  • Google Sheets API (three stock workbooks, read-only)
  • Google Cloud Secret Manager
  • Google Identity-Aware Proxy

Internal service behind company Google sign-in.