> 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.

# Document Splitter Service

## Overview

The **document-splitter** is a service that listens for file classification events from Azure Service Bus and automatically splits multi-form PDF documents into individual files based on the classified page ranges. For each split, it delegates file storage to the **file-intake** service and publishes downstream events so other services can process each form independently. It is designed to run as a background worker with no HTTP intake; all work is driven by messaging events.

When a **FileClassified** event is received from the Service Bus, the document-splitter service downloads the original PDF and its classification result, splits the PDF into separate files per classified form, uploads each split via the file-intake service, saves a manifest to blob storage, and publishes a **FileSplitFileEvent** per output file and a single **FileSplitFileResultEvent** for the full manifest.

## Flow

```text

Service Bus (FileClassified message) 



ClassifiedFileHandler

  1. Validate event fields (applicationId, clientApplicationId, clientApplicationClassifierId, internalUrl, classifiedOutputUrl, fileId)

  2. Create job record in DB (status: Started)

  3. Download classification JSON from blob (classifiedOutputUrl)

  4. Download original PDF from blob (internalUrl)

  5. Validate PDF page count matches classification num_pages

  6. Branch on is_multi_form:

     - false -> handleSingleForm (upload original buffer as single split)

     - true  -> handleMultiForm (split PDF by page_ranges or page_start/page_end per form)

  7. POST each split file to file-intake

       - Endpoint: v1/upload/<applicationId>?parentFileId=...&clientApplicationClassifierId=...

       - file-intake stores child file and returns file metadata

  8. Publish FileSplitFileEvent per split file -> split-file topic

  9. Upload manifest JSON to blob

       - splits/<clientApplicationId>/splitResults/<clientApplicationClassifierId>/

  10. Publish FileSplitFileResultEvent -> split-file-result topic

  11. Update job record in DB (status: Completed, splitManifestBlobUrl)

```

---

## Events

### Input message - FileClassifiedEvent

```ts

{

  clientApplicationId: string;

  clientApplicationClassifierId: string;

  applicationId: string;

  internalUrl: string;

  classifiedOutputUrl: string;

  fileId: string;

  timestamp: Date;

}

```

### Output message - FileSplitFileEvent (one per split file)

```ts

{

  clientApplicationId: string;

  internalUrl: string;

  fileLocationUrl: string;

  timestamp: Date;

}

```

### Output message - FileSplitFileResultEvent (one per job)

```ts

{

  clientApplicationId: string;

  fileId: string;

  parentFileBlobUrl: string;

  classificationUrl: string;

  totalSplitFiles: number;

  splitFiles: SplitFileItem[];

  timestamp: Date;

}

```

### Error - ProblemEvent

Published when processing fails.

* 400 for invalid input fields or invalid page range/page count mismatch

* 404 if blob content is not found

* 500 for all other processing errors

## Blob Storage Layout

Only the manifest is written directly by document-splitter. Split PDFs are stored by file-intake.

```text

splits/

  <clientApplicationId>/

    splitResults/

      <clientApplicationClassifierId>/

        <timestamp>_<baseName>_splitResult.json

```

## Manifest Schema

```json

{

  "clientApplicationId": "...",

  "fileId": "...",

  "parentFileBlobUrl": "...",

  "classificationUrl": "...",

  "timestamp": "...",

  "totalSplitFiles": 3,

  "splitFiles": [

    {

      "fileName": "...",

      "formType": "...",

      "formIndex": 0,

      "pageStart": 1,

      "pageEnd": 3,

      "blobUrl": "..."

    }

  ]

}

```

## Job Tracking

A SQL job record is upserted for each request lifecycle in splitter.Job.

* Started: after event validation, before processing

* Completed: after manifest upload and event publishing

* Failed: when any unhandled processing error occurs

On completion, splitManifestBlobUrl is stored in the job record.

## file-intake Integration

* Endpoint: POST v1/upload/\<applicationId>?parentFileId=\<fileId>\&clientApplicationClassifierId=\<clientApplicationClassifierId>

* Request: multipart/form-data with split PDF file content

* Response: array with fileId, filePath, filename, hashValue

* One request per split file, awaited before downstream publish

* Returned filePath is used for:

\- FileSplitFileEvent.internalUrl

\- FileSplitFileEvent.fileLocationUrl

\- manifest splitFiles\[].blobUrl

If file-intake upload fails or returns an invalid shape, a ProblemEvent is published and the error is rethrown.

## Split Filename Convention

```text

<timestamp>_<parentFileId>_pg<pageStart>-<pageEnd>_<formType>.<ext>

```

Example:

```text

20260722173629176_2B4C733C-9CD4-42C0-95C9-8A157AE63B57_pg7-8_Form-1040.pdf

```

* timestamp is derived from current ISO time with separators removed

* form type is sanitized to alphanumeric, underscore, and hyphen

## Edge Cases

* No forms in classification: throws bad request, publishes ProblemEvent

* Form with no valid page range in multi-form mode: skipped with warning

* is\_multi\_form = false: uploads original PDF buffer as a single split

* PDF page count mismatch: validation error (400)

* Any processing error: publishes ProblemEvent, marks job failed, rethrows for dead-letter handling

## Limitations

* File type supported: PDF only