Skip to navigation

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

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

{
  clientApplicationId: string;
  clientApplicationClassifierId: string;
  applicationId: string;
  internalUrl: string;
  classifiedOutputUrl: string;
  fileId: string;
  timestamp: Date;
}

Output message - FileSplitFileEvent (one per split file)

{
  clientApplicationId: string;
  internalUrl: string;
  fileLocationUrl: string;
  timestamp: Date;
}

Output message - FileSplitFileResultEvent (one per job)

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

splits/
  <clientApplicationId>/
    splitResults/
      <clientApplicationClassifierId>/
        <timestamp>_<baseName>_splitResult.json

Manifest Schema

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

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

Example:

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