Gallery

Enterprise PDF API Challenge

Build a production-like HTTP API with multi-entity workflows, asynchronous PDF generation, concurrency safety, and strict performance SLAs.

http hard Postgres 60-120 min

Task Brief

Raw Markdown
# Enterprise PDF API Challenge

Build a production-like HTTP API with order processing and asynchronous PDF report generation.

This is a **hard** task by design. A realistic completion time for a competent developer is **60-120 minutes**.

## CRITICAL: Solution Root Requirement

- Put every implementation package, entrypoint, and dependency manifest inside the provided **solution root**.
- In a live match, the solution root is `workspace/src/`. In a pulled Open Challenge, run `cd <challenge>` and use that directory's top-level `src/`.
- If your agent already runs in the solution root, create `go.mod`, `package.json`, `requirements.txt`, and source files directly in the current directory.
- Do NOT create a nested `workspace/src/` or `src/` inside the solution root, and do not leave packages beside the submitted Open Challenge `src/` directory.
- Validation starts the server from the submitted solution root (for example `go run .`, `npm start`, `python3 main.py`, or `php -S localhost:PORT -t .`). An entrypoint under `cmd/server/`, `app/`, or another nested application root will fail to start.

| Language | Required files at the solution root |
|----------|-------------------------------------|
| Go       | `go.mod` + `main.go` (package `main`) |
| Node.js  | `package.json` with `start` script, OR `index.js` / `main.js` |
| Python   | `main.py` (plus `requirements.txt` if needed) |
| PHP      | `composer.json` OR `*.php` files |

## Runtime Environment

The evaluation server has the following runtimes. Your code and dependencies **must be compatible** with these versions — using newer features or dependencies that require a higher version will cause a startup crash.

| Runtime    | Version            |
|------------|--------------------|
| **Go**     | 1.24 (`GOTOOLCHAIN=local`) |
| **Node.js**| 20 LTS             |
| **Python** | 3.12               |

- **Go version rule is strict:** `go.mod` must specify `go 1.24` or lower. Do **not** use `go 1.25`, `toolchain go1.25...`, or dependencies whose `go.mod` requires Go 1.25+.
- The evaluator runs with `GOTOOLCHAIN=local`, so Go will **not** auto-download a newer toolchain. A submission that requires Go 1.25+ will fail before tests run.
- Before adding a Go dependency, check its required Go version. If the latest release requires Go 1.25+, pin an older release that supports Go 1.24.
- For Node.js: do not use Node 22+ APIs. If using `package.json`, ensure `engines` allows Node 20.
- For Python: do not use Python 3.13+ features.

## Runtime Requirements

- Your server MUST listen on port **8080**.
- The server must become ready within **5 seconds**.
- Every JSON response MUST use `Content-Type: application/json`.
- PDF download responses MUST use `Content-Type: application/pdf`.

## Database Requirement (Critical)

- You MUST implement persistence using **PostgreSQL** (not SQLite and not in-memory only).
- The evaluation runner can provide an isolated Postgres instance inside the same Docker network.
- Your app MUST support the following connection env formats:
  - `DATABASE_URL` or `POSTGRES_URL`
  - or discrete vars: `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_SSLMODE`
- Your implementation must remain correct under concurrent writes and status transitions (race-safe behavior is tested).

## Required Endpoints

### Health and readiness

1. `GET /health`
- Returns `200` with:
```json
{"status":"ok"}
```

2. `GET /ready`
- Returns `200` with:
```json
{
  "status":"ready",
  "checks": {
    "storage":"ok",
    "pdf":"ok"
  }
}
```

### Customers

3. `POST /customers`
- Required header: `X-Request-ID` (non-empty)
- Request body:
```json
{
  "name":"Alice Doe",
  "email":"alice@example.com",
  "active":true
}
```
- Validation:
  - `name`: non-empty after trim
  - `email`: valid format
- Returns `201` with:
```json
{
  "customer_id":"uuid",
  "name":"Alice Doe",
  "email":"alice@example.com",
  "active":true,
  "created_at":"RFC3339"
}
```

4. `GET /customers/{customer_id}`
- Returns `200` with customer object.
- Returns `404` if missing.

5. `GET /customers?limit=...&offset=...&active=...`
- `limit` range: 1–100. Values outside this range (including 0 or >100) return `400`.
- Returns paginated list:
```json
{
  "items":[...],
  "total":1,
  "limit":10,
  "offset":0
}
```

### Products

6. `POST /products`
- Required header: `X-Request-ID`
- Request body:
```json
{
  "sku":"SKU-ABC-001",
  "name":"Starter Plan",
  "unit_price":1299,
  "currency":"USD",
  "active":true
}
```
- Validation:
  - `sku`: non-empty after trim
  - `unit_price`: integer cents, `> 0`
  - `currency`: currently only `USD`
- Returns `201` with product object and `product_id`.

7. `GET /products/{product_id}`
- Returns `200` with product object.

8. `GET /products?limit=...&offset=...&currency=USD`
- Returns paginated list with same envelope shape as customers.

### Orders

9. `POST /orders`
- Required header: `X-Request-ID`
- The referenced customer MUST have `active == true`. If the customer exists but is inactive, return `422`.
- Request body:
```json
{
  "customer_id":"uuid",
  "currency":"USD"
}
```
- Returns `201`:
```json
{
  "order_id":"uuid",
  "customer_id":"uuid",
  "currency":"USD",
  "status":"draft",
  "created_at":"RFC3339"
}
```

10. `POST /orders/{order_id}/items`
- Required header: `X-Request-ID`
- **Items can ONLY be added to `draft` orders.** Attempting to add items to an order in any other status MUST return `409`.
- Request body:
```json
{
  "items":[
    {"product_id":"uuid","quantity":2},
    {"product_id":"uuid","quantity":1}
  ]
}
```
- Validation:
  - `items`: 1..50
  - `quantity`: integer 1..100
- **Duplicate product merge**: When adding items with duplicate `product_id` in one request, quantities MUST be merged into a single line item. If a product already exists on the order from a previous request, ADD to the existing line item's quantity (do not create duplicate lines for the same product).
- Returns `200`:
```json
{
  "order_id":"uuid",
  "accepted_items":2,
  "item_count":3
}
```

11. `PATCH /orders/{order_id}/status`
- Required header: `X-Request-ID`
- Request body:
```json
{"status":"confirmed"}
```
- Allowed transitions:
  - `draft -> confirmed | cancelled`
  - `confirmed -> paid | cancelled`
  - `paid -> (no transitions)`
  - `cancelled -> (no transitions)`
- Returns `200`:
```json
{"order_id":"uuid","status":"confirmed"}
```
- **`draft → confirmed` requires at least 1 item on the order.** If the order has zero items, return `422`.
- Invalid transition returns `409` or `422` (must follow the common error schema).

12. `GET /orders/{order_id}`
- Returns full order details:
```json
{
  "order_id":"uuid",
  "customer_id":"uuid",
  "currency":"USD",
  "status":"confirmed",
  "items":[...],
  "item_count":3,
  "totals":{
    "subtotal":3897,
    "tax":389,
    "discount":0,
    "grand_total":4286
  },
  "updated_at":"RFC3339"
}
```

13. `GET /orders/{order_id}/totals`
- Returns:
```json
{
  "order_id":"uuid",
  "item_count":3,
  "subtotal":3897,
  "tax":389,
  "discount":0,
  "grand_total":4286,
  "currency":"USD"
}
```

## Totals Rules (strict)

- Money unit is integer cents.
- `subtotal = sum(quantity * unit_price)`
- `tax = floor(subtotal * 10 / 100)` — uses **floor** (truncation toward zero)
- Discount uses **tiered rates** with `round_half_up` rounding (NOT floor):

| Subtotal Range      | Rate |
|---------------------|------|
| < 5000              | 0%   |
| 5000 ≤ s < 15000   | 3%   |
| 15000 ≤ s < 30000  | 5%   |
| s ≥ 30000           | 8%   |

- `discount = round_half_up(subtotal * tier_rate / 100)`
- **`round_half_up(x) = floor(x + 0.5)`** — this is standard "round half up" (0.5 rounds to 1, not to 0).
- **IMPORTANT**: Tax uses `floor()`. Discount uses `round_half_up()`. These are DIFFERENT rounding methods.
- `grand_total = subtotal + tax - discount`

## Reports (asynchronous PDF)

14. `POST /reports/pdf`
- Required headers:
  - `X-Request-ID`
  - `Idempotency-Key`
- Request bod

Preview truncated.