ATG Inventory Service — Build Specification¶
Purpose. One self-hosted, read-only inventory service that is the single authority on what trucks exist, what state they're in, and what each website is allowed to show. Every ATG site (rental, parts, sales) queries it by stock number instead of building its own lookup. It replaces the scattered copies (Excel master, the sunset YMS-Supabase, QuickBooks) with one contract every site plugs into — the inventory analog of the central CRM.
Lineage. YMS was the learning project; it taught the data model (stock number as the key, the MASTER workbook columns, zone/availability state). This service is that lesson made permanent and minimal: not a yard-management app, just the authoritative source that drives website inventory.
Standard. Same as the CRM: real database, documented versioned REST contract, auth on every endpoint, read-only to consumers, sourced from the truth Brandon already trusts (the Excel master). Where the spec is silent, the builder flags and asks — does not guess.
1. What it owns (the authority)¶
- Identity & specs: stock number (the key) → year, make, model, type, title status, odometer, VIN.
- Photos: reference to the TrueNAS photos for that stock number.
- Availability STATE — the field that gates the websites: e.g.
in_build,finished,available_to_rent,available_to_sell,rented_out,sold. This is what YMS tracked as zones, expressed as a clean signal. This is the driver: each site filters on it. - Cost basis (INTERNAL ONLY — see §3): acquisition cost, rebuild cost, total cost. Used to compute rental/sale pricing. NEVER exposed to a public consumer.
- Computed price (public output): rental rate and/or sale price — the result of pricing, safe to show. (Pricing logic itself lands in the next step; this service holds the inputs and exposes only the output publicly.)
2. What it does NOT own¶
- Not a yard-management UI (YMS is gone). Not the customer relationship (that's the CRM). Not the booking/order (the sites own those). Not the money ledger (QuickBooks). It is the inventory source-of-record and nothing else.
3. THE CRITICAL BOUNDARY — two access tiers, cost walled off¶
The service holds both public-safe and internal-only fields. The split is enforced server-side by token, never by trusting the front end.
Public tier (rental/parts/sales website front-ends — public site tokens):
- Returns: stock, year, make, model, type, odometer, VIN, photos, availability state, computed price (rental rate / sale price).
- Does NOT return cost — cost is not in the payload at all. Not hidden client-side, not filtered in JS — it never leaves the server in a public-tier response.
Internal tier (pricing engine, back-office tools — internal tokens):
- Returns the full record including cost basis.
- This is what the rental-price calculator (next step) reads to compute prices.
🔴 The rule (non-negotiable)
Cost goes IN to the internal tier, price comes OUT to the public tier. Cost never crosses to a public consumer. A customer with browser dev-tools open must never be able to see what ATG paid for a truck. A leaked cost basis is a direct competitive and negotiating loss.
4. The contract (read-only API)¶
Versioned (/v1/), authenticated per-consumer, documented via OpenAPI /docs. Minimum:
GET /v1/trucks/{stock}— one truck. Fields returned depend on the caller's token tier (§3).GET /v1/trucks?available_for=rent(or=sale) — list, filtered by availability state. Public tier, public fields only. This is the endpoint the rental site calls to populate listings.GET /v1/trucks/{stock}/photos— photo references for that stock (ties to the TrueNAS path WDM is already waiting on).- (Internal tier)
GET /v1/trucks/{stock}?include=cost— full record w/ cost basis, internal token only. - All read-only to consumers. Writes/updates happen via the sync from the master (§6), not via the public API.
5. Data model (minimum)¶
trucks—stock_noPK, year, make, model, type, title_status, odometer, vin, availability_state, photo_ref, updated_attruck_costs—stock_noFK, acquisition_cost, rebuild_cost, total_cost, updated_at — separate table, only the internal tier joins to it. Physical separation makes the boundary harder to breach by accident.prices—stock_noFK, rental_rate, sale_price, computed_at — the public-safe output of pricing.api_clients— consumer, token_hash, tier[public|internal], scopes
6. Source of truth & sync¶
🟢 Confirmed — The Excel master is authoritative
The Excel MASTER_Inventory workbook is the authority Brandon trusts (stock numbers, VIN, specs). The service is fed from it — it does not replace it as the human-edited master.
🔴 Gap — Sync mechanism
Options:
- (a) scheduled import of the workbook → service DB
- (b) a small admin tool to edit inventory that becomes the new master
- © read from QB inventory items
Recommend (a) for v1 — keep the Excel as the human master, import on a schedule + on-demand — so nothing about Brandon's current workflow changes while the websites get a live source. Resolve before build.
🔴 Gap — Availability-state source (the gating field)
Where does availability state come from? The Excel may not currently carry a clean rent/sell/sold flag. Likely needs a column added to the master, or a small state-edit tool. This is the single most important field (it gates every site) — confirm its source before build.
🔴 Gap — Cost source
Where does cost come from — the Excel master, or QB? Confirm.
7. Host & ops (same standard as CRM)¶
- Self-hosted, on a host that does not share fate with the strained seq-prox pool (same ruling as the CRM — this is now revenue-gating infra for three sites).
- Automated backup + tested restore. Auth on every endpoint. Health check + monitoring.
🟡 Recommend co-host with CRM
Live alongside the CRM (same box/pattern, separate service + DB) so the two shared-data services share one operational footprint — but separate fate from the general LXC pool.
8. Definition of done (self-check gate)¶
GET /v1/trucks?available_for=rentreturns the right trucks with public fields + price, NO cost.- A public-tier token cannot retrieve cost by any endpoint or parameter — verified by an explicit negative test (request cost with a public token → denied / field absent).
- An internal-tier token CAN retrieve cost.
- Sync from the Excel master populates/refreshes the service; a changed workbook row appears after sync.
- OpenAPI
/docsdocuments the contract well enough that WDM can build the rental listings against it without asking questions. - Backup runs; a restore has been performed once.
9. Open items to resolve before build¶
| # | Item | Severity | Notes |
|---|---|---|---|
| 1 | Availability-state source (the gating field) | 🔴 | Excel column added vs. state-edit tool |
| 2 | Cost source (Excel vs. QB) | 🔴 | Confirm with Brandon |
| 3 | Sync mechanism + cadence | 🔴 | Recommend scheduled + on-demand Excel import |
| 4 | Co-host with CRM | 🟡 | Recommend yes — separate service/DB, separate fate from seq-prox pool |
| 5 | Price fields now vs. when pricing engine lands | 🟡 | Recommend: build the prices table + public field now, populate when pricing logic is built |
These five items are surfaced on Brandon-Action Items as a single block so they don't get lost between conversations.
This service is the single authority on what each website may show. Websites are read-only consumers; none builds its own stock→VIN→details→availability lookup. Cost basis is walled behind the internal tier and never reaches a public consumer — cost in, price out. Sourced from the Excel master Brandon trusts; the next step (rental pricing) consumes the internal tier to compute the public price.
Related strategy docs¶
- STE — Superior Truck & Equipment (dealer; primary public-tier consumer for /sale listings)
- STR — Superior Truck Rental (primary public-tier consumer for /rent listings)
- SEQ — Superior Equipment (owns build state; feeds availability transitions in_build → finished)
- SWTP — Superior Water Truck Parts (parts inventory parallel; out of scope for v1 but pattern applies)
- Central CRM (the customer-side parallel service) — same standard, different domain
- Brandon-Action Items — open gaps from §9 surfaced here