> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://cla-demo.postmansolutions.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://cla-demo.postmansolutions.com/_mcp/server.

# Step by Step Walkthrough

## Event Flow

```
Azure Service Bus                Document Splitter                Azure Blob Storage
─────────────────                ─────────────────                ──────────────────
                                 ┌─────────────────┐
classified-file topic ──────────►│ messageReceiver │
                                 └────────┬────────┘
                                          │ routes to handler
                                          ▼
                                 ┌─────────────────────────────┐
                                 │   classifiedFileHandler     │
                                 │                             │
                                 │ 1. validate event fields    │
                                 │ 2. create job (SQL)         │
                                 │ 3. download classification ─┼────────────────────► [GET classifiedOutputUrl]
                                 │ 4. download source PDF ─────┼────────────────────► [GET internalUrl]
                                 │ 5. validate page ranges     │
                                 │ 6. split PDF (pdf-lib)      │
                                 │ 7. upload split files ──────┼──── HTTP POST ──────► File Intake Service
                                 │    (parallel)               │                           │
                                 │                             │◄── blob URLs ─────────────┘
                                 │ 8. publish split-file ──────┼──► split-file topic (one per file)
                                 │    events                   │
                                 │ 9. upload manifest JSON ────┼────────────────────► [PUT splits container]
                                 │ 10. publish result event ───┼──► split-file-result topic
                                 │ 11. complete job (SQL)      │
                                 └─────────────────────────────┘
                                          │ on any error
                                          ▼
                                 problem topic ──────────────────► Status / Alert service
```

---

## Step-by-Step Walkthrough

### Step 1 , Message Arrives

**Trigger:** The classifier service publishes a `FileClassifiedEvent` to the `classified-file` topic.

**Message shape:**

```json
{
  "clientApplicationId": "app-001",
  "clientApplicationClassifierId": "classifier-xyz",
  "applicationId": "tenant-abc",
  "fileId": "file-99999",
  "internalUrl": "https://storage.blob.core.windows.net/docs/original/myfile.pdf",
  "classifiedOutputUrl": "https://storage.blob.core.windows.net/docs/classified/myfile.json",
  "timestamp": "2026-07-29T10:00:00.000Z"
}
```

**Code:** `src/messaging/subscribers/messageReceiver.ts` receives the message and routes it to `classifiedFileHandler`.

---

### Step 2 , Validate the Event

The handler checks that all required fields are present and non-empty.

**If invalid:** Publishes a `ProblemEvent` with HTTP status `400` to the `problem` topic, then stops processing.

**Code:** `src/messaging/handlers/classifiedFileHandler.ts`

---

### Step 3 , Create Job in SQL

Inserts (or upserts) a row in `[splitter].[Job]`:

```
jobId           = auto-generated UUID
fileId          = "file-99999"
status          = "Started"
createdAt       = now()
```

This record lets operators query which files are in-flight and which have failed.

**Code:** `src/repositories/jobRepository.ts`

---

### Step 4 , Download Classification JSON

Parses `classifiedOutputUrl` → extracts container `docs` and blob path `classified/myfile.json`.

Downloads and deserialises the classification output:

```json
{
  "is_multi_form": true,
  "num_pages": 6,
  "forms": [
    { "form_type": "W2",     "page_start": 1, "page_end": 2, "confidence": 0.98 },
    { "form_type": "1099",   "page_start": 3, "page_end": 4, "confidence": 0.95 },
    { "form_type": "ExtraForm", "page_ranges": [{ "start": 5, "end": 6 }], "confidence": 0.72 }
  ]
}
```

**Code:** `src/internal/storage/blobStorageService.ts` → `downloadBuffer()`

---

### Step 5 , Download Source PDF

Parses `internalUrl` → downloads the PDF bytes as a `Buffer`.

**Code:** `src/internal/storage/blobStorageService.ts` → `downloadBuffer()`

---

### Step 6 , Validate Page Ranges

Runs these checks against the classification + actual PDF:

```
✓ PDF has exactly 6 pages (matches num_pages)
✓ All page_start / page_end are integers
✓ All pages are within bounds (1–6)
✓ page_start <= page_end for each form
✓ No two forms share the same page (no overlaps)
```

**If any check fails:** Publishes `ProblemEvent` (400), marks job `Failed`, stops processing.

**Code:** `src/internal/pdf/pdfSplitter.ts` → `buildValidatedPageRanges()`

---

### Step 7 , Split the PDF

For each form, `pdf-lib` extracts the relevant pages into a new `Buffer`:

```
Form 1: W2      → pages 1–2 → Buffer A
Form 2: 1099    → pages 3–4 → Buffer B
Form 3: ExtraForm → pages 5–6 → Buffer C
```

Generated filenames:

```
20260729T100000_myfile_pg1-2_W2.pdf
20260729T100000_myfile_pg3-4_1099.pdf
20260729T100000_myfile_pg5-6_ExtraForm.pdf
```

**Code:** `src/internal/pdf/pdfSplitter.ts` → `splitPdfByPageRanges()`

---

### Step 8 , Upload Split Files (Parallel)

For each split buffer, an HTTP `POST` is made to the File Intake service:

```
POST {FILE_INTAKE_BASE_URL}/v1/upload/tenant-abc
  ?parentFileId=file-99999
  &clientApplicationClassifierId=classifier-xyz

Body: multipart/form-data
  file = <pdf buffer>
  filename = "20260729T100000_myfile_pg1-2_W2.pdf"
```

All three uploads run in **parallel** (`Promise.allSettled`).

Response per upload:

```json
{
  "fileId": "child-file-001",
  "filePath": "tenant-abc/uploads/20260729T100000_myfile_pg1-2_W2.pdf",
  "blobUrl": "https://storage.blob.core.windows.net/uploads/tenant-abc/…/W2.pdf"
}
```

**Code:** `src/services/fileIntake/fileIntakeService.ts` → `uploadChildFile()`

---

### Step 9 , Publish `FileSplitFileEvent` (per split)

One Service Bus message is published per successfully uploaded split:

```json
{
  "clientApplicationId": "app-001",
  "internalUrl": "https://storage.blob.core.windows.net/uploads/…/W2.pdf",
  "fileLocationUrl": "tenant-abc/uploads/…/W2.pdf",
  "timestamp": "2026-07-29T10:00:05.000Z"
}
```

**Topic:** `split-file`

**Code:** `src/messaging/publishers/createMessageSender.ts`

---

### Step 10 , Upload Split Manifest JSON

A JSON document summarising all splits is uploaded to blob storage:

```json
{
  "clientApplicationId": "app-001",
  "fileId": "file-99999",
  "parentFileBlobUrl": "https://…/original/myfile.pdf",
  "classificationUrl": "https://…/classified/myfile.json",
  "totalSplitFiles": 3,
  "splitFiles": [
    { "fileName": "…_W2.pdf",        "formType": "W2",        "formIndex": 0, "pageStart": 1, "pageEnd": 2, "blobUrl": "…" },
    { "fileName": "…_1099.pdf",      "formType": "1099",      "formIndex": 1, "pageStart": 3, "pageEnd": 4, "blobUrl": "…" },
    { "fileName": "…_ExtraForm.pdf", "formType": "ExtraForm", "formIndex": 2, "pageStart": 5, "pageEnd": 6, "blobUrl": "…" }
  ],
  "timestamp": "2026-07-29T10:00:06.000Z"
}
```

**Blob path:** `splits/app-001/splitResults/classifier-xyz/20260729T100000_myfile_splitResult.json`

**Code:** `src/internal/storage/blobStorageService.ts` → `uploadBuffer()`

---

### Step 11 , Publish `FileSplitFileResultEvent`

The manifest is also published as a single Service Bus message:

```json
{
  "clientApplicationId": "app-001",
  "fileId": "file-99999",
  "parentFileBlobUrl": "…",
  "classificationUrl": "…",
  "totalSplitFiles": 3,
  "splitFiles": [ … ],
  "timestamp": "…"
}
```

**Topic:** `split-file-result`

---

### Step 12 , Complete the Job

Updates `[splitter].[Job]` row:

```
status              = "Completed"
splitManifestBlobUrl = "https://…/splitResult.json"
completedAt         = now()
```

**Code:** `src/repositories/jobRepository.ts` → `completeJob()`

---

## Error Paths

```
                        ┌────────────────────┐
  Any step fails ──────►│   Error handler    │
                        │                    │
                        │ 1. publish         │──► problem topic
                        │    ProblemEvent    │
                        │                    │
                        │ 2. update job      │──► SQL [status = "Failed"]
                        │    status          │
                        │                    │
                        │ 3. re-throw        │──► Service Bus abandons message
                        └────────────────────┘         │
                                                        ▼
                                              Retry (up to 3×)
                                                        │
                                              (still fails?)
                                                        ▼
                                              Dead-letter queue
                                              (manual intervention)
```