Overview / Technical Docs / Technical Design
Freight Quote Tool · Technical Design

Technical Design

The architecture in detail — the shape of the system, each component, the UPS integration, and the security model. Written so a developer could pick it up cold.

Shape

One Cloudflare Worker serving a small static UI plus a JSON API, behind Cloudflare Access. No installs, one URL.

Browser — single page, two input modes (item lookup | manual carton specs)
   │  JSON
   ▼
Cloudflare Access (email OTP, ~14-day session, named addresses)
   ▼
Cloudflare Worker
   ├─ product lookup ← D1 table loaded from the SAGE export
   ├─ carton math (ceil(qty ÷ units/carton), weights)
   ├─ UPS OAuth (client credentials, token cached until expiry)
   └─ UPS Rating API — "Shoptimeintransit" → all services, negotiated rates + transit
Secrets: UPS client id/secret + account number as server-side Worker secrets — never in source or browser

The design deliberately reuses the in-house prototype's module boundaries (lookup → carton math → rate → display) and removes the parts that had stalled it: web scraping, Windows packaging, and per-machine credentials are structurally absent.

Components

1. Front end. Single page, plain HTML/JS. Two entry modes:

  • Item lookup: part #, quantity, ZIP, residential/commercial toggle. Shows the carton specs it found, with the option to override them (which is just manual mode pre-filled).
  • Manual: carton weight + dimensions (+ carton count), ZIP, residential/commercial.

Output: one table sorted by cost — service name, negotiated cost, transit days / expected delivery. Items without dimensions in the catalog get a clear nudge into manual mode.

2. API routes.

  • GET /api/item/{part} → carton specs (drives the show-what-we-found UI).
  • POST /api/quote{part | specs, qty, zip, residential} → rate table.
  • POST /api/catalog (admin-gated) → upload SAGE .xlsx; parsed server-side → database.

3. Product table. From the SAGE export: col B = ItemNum; DH/DI/DJ = carton L/W/H; DK = weight/carton; DL = units/carton. ~465 rows — trivial at this scale. A relational store (D1) was chosen over a key-value store so a future quote log is a table away, not a re-architecture.

Load/refresh mechanics: initial seed on build day via the same POST /api/catalog path the button uses. Each upload is parsed server-side, header-validated against the expected column map (a clear error names the missing/moved column, nothing is ingested on failure), then swapped in atomically — stage into a fresh table, verify row count, rename. Catalog metadata (uploaded-at, by whom, row count, items missing dimensions) is stored alongside and surfaced as "catalog last updated."

4. UPS integration.

  • OAuth 2 client-credentials against the UPS production environment; token cached until expiry.
  • Rating API with the request option Shoptimeintransit — one call returns all available services with cost and time-in-transit, exactly matching the requirement of "transit time and cost for all available service options."
  • Negotiated-rates indicator + the shipper account; ship-from ZIP fixed; residential-address indicator when the toggle is set.
  • If negotiated rates come back absent, show published rates with a visible marker rather than failing silently.
  • The known gate: negotiated rates require the developer app to have Rating-product access and the account linked — this is the permission the original prototype's rating call hadn't yet cleared. Validated in hour 1 of build day with a bare-bones script before anything else is built.

5. Auth. A Cloudflare Access policy on the hostname: named emails, one-time PIN, ~2-week session. The admin route (catalog upload) sits behind a second, tighter policy (an Access group containing the admin emails).

User load/refresh mechanics: the allow-list lives in the Access policy, not in app code. The initial list is seeded from the ~5 emails on build day. Adds/removes are made via the Cloudflare dashboard or API — same-day turnaround; no self-serve user admin in v1. The app itself never sees passwords or manages identity; it trusts the signed Access assertion header for "who is this" (used for the admin gate and the uploaded-by stamp).

6. No logging in v1 (a Product Requirements decision). If revived: a quotes table + a "download CSV" button — about an hour of work.

Credentials & security

  • UPS client id/secret and account number live as server-side Worker secrets, set at deploy time; nothing in source control, nothing client-side.
  • No Azure/SharePoint surface in v1 at all.
  • Credentials are provisioned fresh at handoff and held server-side only — never in the browser, never in the code.

Repo & ownership

  • Private Git repository; deployed via wrangler. The stack is intentionally standard and boring, so any competent shop — or your own team — can pick it up and keep it running. You get the source, and you're never locked in.
  • The service is set up to be client-owned and RA-configured: moving it to a client-owned account later is a DNS + secrets change, not a rebuild.

Assumptions

  • One product table serves both brands.
  • SAGE export dimensions are inches / pounds.
  • A partial last carton is rated as a full carton (conservative; matches the original carton-count math). Flag if pro-rating is preferred.
  • A single fixed origin; no multi-origin or Saturday-delivery options surfaced as needs at design time.
  • UPS Rating API volume (~25–30 calls/day) is negligible for every quota involved.
  • ~5 named users; the Cloudflare Access free tier (50) is years of headroom.