Files · Supplier Order Optimizer for Small Restaurant Groups
66 (1 binary, 561.0 kB total)attempt 1
README.md·3950 B·markdown
markdown
# Supplier Order Optimizer for Small Restaurant Groups
Auto-calculate order quantities from current pars and rolling usage history.
The chef or kitchen manager manually compares a spreadsheet of past supplier orders against current inventory pars every week. This takes 2-3 hours and often leads to over-ordering (waste) or under-ordering (emergency runs). This project automates that workflow: upload a spreadsheet of inventory pars, usage history, and past orders — the system uses LLM reasoning to generate optimised order quantities, reducing food waste and emergency runs.
## How it works
1. **Upload** — Upload an XLSX spreadsheet with three sheets: `par_levels`, `usage_history`, `past_orders`
2. **Parse** — The spreadsheet service reads and validates each sheet with Zod schemas
3. **Analyse** — The LLM service (Vercel AI SDK + OpenAI) generates optimised order suggestions with explanations
4. **Cache** — Responses are cached in Redis via `@reaatech/llm-cache` to reduce costs on repeated runs
5. **Persist** — Order history is stored in memory via `@reaatech/agent-memory-*` for future reference
6. **Export** — Results can be exported as a new XLSX spreadsheet
## API Reference
### `POST /api/optimize-order`
Upload an XLSX file (multipart/form-data) or send a JSON body:
**Multipart:**
```
Content-Type: multipart/form-data
file: <xlsx buffer>
supplierId: "supplier-1"
```
**JSON:**
```json
{
"supplierId": "supplier-1",
"parLevels": [{ "itemId": "item-1", "name": "Tomatoes", "targetQuantity": 50, "reorderPoint": 10 }],
"usageHistory": [{ "itemId": "item-1", "date": "2025-01-01", "quantityUsed": 15 }],
"pastOrders": [{ "itemId": "item-1", "supplierId": "supplier-1", "quantity": 50, "orderDate": "2025-01-01", "unitCost": 0.5 }]
}
```
Returns 200 with `OptimizationResult`:
```json
{
"supplierId": "supplier-1",
"supplierName": "supplier-1",
"suggestions": [{ "itemId": "item-1", "itemName": "Tomatoes", "currentStock": 20, "parLevel": 50, "suggestedOrderQuantity": 35, "reason": "Expected demand based on past usage", "urgency": "high" }],
"totalCost": 17.5,
"orderByDate": "2025-01-08",
"generatedAt": "2025-01-01T12:00:00.000Z"
}
```
### `GET /api/par-levels?tenant=default`
Returns stored par levels for the given tenant.
### `POST /api/par-levels?tenant=default`
```json
[{ "itemId": "item-1", "name": "Tomatoes", "targetQuantity": 50, "reorderPoint": 10 }]
```
Returns 201 with `{ "stored": 1 }`.
### `GET /api/suppliers?tenant=default`
Returns stored suppliers.
### `POST /api/suppliers?tenant=default`
```json
[{ "id": "supplier-1", "name": "Produce Co.", "leadTimeDays": 3, "minimumOrder": 20 }]
```
Returns 201 with `{ "stored": 1 }`.
## Environment variables
| Variable | Description | Default |
|---|---|---|
| `OPENAI_API_KEY` | OpenAI API key for LLM | (required) |
| `LANGFUSE_PUBLIC_KEY` | Langfuse public key for tracing | (required) |
| `LANGFUSE_SECRET_KEY` | Langfuse secret key | (required) |
| `LANGFUSE_BASE_URL` | Langfuse base URL | `https://cloud.langfuse.com` |
| `REDIS_URL` | Redis connection URL | `redis://localhost:6379` |
| `LLM_MODEL` | OpenAI model ID | `gpt-5.2-mini` |
| `MAX_RETRY_ATTEMPTS` | Max retries for LLM calls | `3` |
| `CACHE_TTL_SECONDS` | Cache TTL for LLM responses | `3600` |
## Tech stack
- **Framework:** Next.js 16+ (App Router)
- **LLM:** Vercel AI SDK (`ai`) + OpenAI (`@ai-sdk/openai`)
- **Persistence:** `@reaatech/agent-memory-core`, `@reaatech/agent-memory-storage`, `@reaatech/agent-memory-retrieval`
- **Task tracking:** `@reaatech/a2a-reference-persistence`
- **Caching:** `@reaatech/llm-cache`, `@reaatech/llm-cache-adapters-redis`
- **Spreadsheets:** `xlsx` (SheetJS)
- **Validation:** `zod`
- **Observability:** `langfuse`
## Development
```bash
pnpm dev # Start the dev server
pnpm test # Run tests with coverage
pnpm typecheck # TypeScript type checking
pnpm lint # ESLint checks
```