Skip to navigation

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:

{
"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:

{
"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:

{
"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:

{
"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:

{
"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:

{
"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)