Skip to navigation

FLUXX Splitter Service---Low Level Design

Document Version

  • Version: 1.0

  • Date: March 6, 2026

  • Author: System Design

  • Status: Design Phase

1. Overview

1.1 Purpose

The FLUXX Splitter Service is a standalone microservice that takes classified PDF documents and splits them into individual form files based on classification results. This service is extracted from the splitting logic in the existing ClassifyFileTool implementation to provide document splitting capabilities as an independent, reusable service.

1.2 Scope

This service handles:

  • Reading classification results from previous classification phase

  • Extracting page ranges for individual forms

  • Splitting multi-form PDFs into separate PDF files

  • Handling contiguous and non-contiguous page ranges

  • Creating file metadata records for split files

  • Uploading split files to blob storage

  • Generating sanitized filenames from form types

1.3 Reference Implementation

This design is based on the existing implementation in:

  • /backend/src/api/v1/agent/tools/ClassifyFileTool.ts (lines 237-368)

  • /backend/src/utils/pdfProcessor.ts (splitPDFByPageRanges function)

2. Architecture

2.1 System Context

┌─────────────────────────────────┐
│  FLUXX Classification Service   │
│  (Upstream)                     │
└────────┬────────────────────────┘
         │
         │ Classification Result
         ▼
┌─────────────────────────────────┐
│  FLUXX Splitter Service         │
│  ┌──────────────────────────┐  │
│  │  API Layer (Express)     │  │
│  ├──────────────────────────┤  │
│  │  Split Orchestrator      │  │
│  ├──────────────────────────┤  │
│  │  PDF Splitting Engine    │  │
│  ├──────────────────────────┤  │
│  │  File Management Layer   │  │
│  └──────────────────────────┘  │
└─────┬──────────────┬────────────┘
      │              
      ▼              
┌───────────┐
│ Azure Blob│  
│ Storage   │  
└───────────┘

2.2 Service Components

2.2.1 API Layer

  • Express.js REST API

  • Request validation

  • Authentication and authorization

  • Response formatting

2.2.2 Split Orchestrator

  • Orchestrates the splitting workflow

  • Handles classification result parsing

  • Builds page ranges from form classifications

  • Manages error recovery and rollback

2.2.3 PDF Splitting Engine

  • PDF page extraction using pdf-lib

  • Multi-range splitting for non-contiguous pages

  • PDF document assembly

  • Buffer management

2.2.4 File Management Layer

  • Blob storage integration

  • File metadata creation

  • Filename generation and sanitization

  • Parent-child file relationships

3. API Design

3.1 Endpoints

3.1.1 POST /api/v1/split

Request:

{
  "file_id": "string (optional, reference to original file)",
  "file_url": "string (blob storage URL of original PDF)",
  "classification_result": {
    "is_multi_form": true,
    "num_pages": 45,
    "forms": [
      {
        "page_start": 1,
        "page_end": 2,
        "form_type": "Form 1040",
        "confidence": 0.95
      },
      {
        "page_start": 3,
        "page_end": 5,
        "form_type": "Schedule C",
        "confidence": 0.92
      }
    ]
  },
  "user_id": "string",
  "client_id": "string",
  "original_filename": "tax_return_2024.pdf",
  "metadata": {
    "parent_file_id": "uuid",
    "classification_url": "https://..."
  }
}

Response (Success - 200):

{
  "success": true,
  "split_id": "string (UUID)",
  "original_file_id": "string",
  "files_created": 2,
  "split_files": [
    {
      "file_id": "string",
      "filename": "tax_return_2024_Form_1040.pdf",
      "blob_url": "https://storage.blob.core.windows.net/.../tax_return_2024_Form_1040.pdf",
      "form_type": "Form 1040",
      "page_range": { "start": 1, "end": 2 },
      "file_size": 524288,
      "form_index": 0
    },
    {
      "file_id": "string",
      "filename": "tax_return_2024_Schedule_C.pdf",
      "blob_url": "https://storage.blob.core.windows.net/.../tax_return_2024_Schedule_C.pdf",
      "form_type": "Schedule C",
      "page_range": { "start": 3, "end": 5 },
      "file_size": 786432,
      "form_index": 1
    }
  ],
  "processing_time_ms": 1234
}

Response (Error - 400/500):

{
  "success": false,
  "error": "string",
  "error_code": "string",
  "details": {
    "invalid_ranges": [],
    "failed_splits": []
  }
}

3.1.2 POST /api/v1/split-batch

Batch splitting for multiple documents.

Request:

{
  "split_requests": [
    { /* same as single split request */ },
    { /* same as single split request */ }
  ]
}

Response:

{
  "success": true,
  "total_requests": 10,
  "successful": 9,
  "failed": 1,
  "results": [
    { /* individual split results */ }
  ]
}

3.1.3 GET /api/v1/split/:split_id

Retrieves information about a previous split operation.

Response:

{
  "success": true,
  "split_id": "string",
  "created_at": "ISO8601 timestamp",
  "original_file_id": "string",
  "files_created": 2,
  "split_files": [...]
}

3.1.4 GET /health

Health check endpoint.

Response:

{
  "status": "healthy",
  "version": "1.0.0",
  "dependencies": {
    "blob_storage": "connected",
    "database": "connected"
  }
}

4. Core Algorithm

4.1 Splitting Workflow

START
  │
  ├─> Validate Input
  │     ├─> Check classification result structure
  │     ├─> Verify file_url is accessible
  │     └─> Validate page ranges
  │
  ├─> Determine if Splitting is Needed
  │     │
  │     ├─> Check is_multi_form flag
  │     ├─> Check forms array length
  │     │
  │     IF (NOT is_multi_form AND forms.length <= 1):
  │       └─> SKIP SPLITTING (return success without splits)
  │     ELSE:
  │       └─> PROCEED WITH SPLITTING
  │
  ├─> Download Original PDF
  │     └─> From blob storage (file_url)
  │
  ├─> Build Page Ranges
  │     │
  │     FOR each form in classification_result.forms:
  │       │
  │       ├─> Extract form_type
  │       │
  │       ├─> Check for non-contiguous ranges
  │       │     IF form.page_ranges EXISTS:
  │       │       FOR each range in form.page_ranges:
  │       │         └─> Add: {start, end, formType: "{form_type}_Part{N}"}
  │       │     ELSE:
  │       │       └─> Add: {start: page_start, end: page_end, formType: form_type}
  │       │
  │       └─> Store in pageRanges array
  │
  ├─> Validate Page Ranges
  │     │
  │     FOR each range in pageRanges:
  │       ├─> Check: range.start >= 1
  │       ├─> Check: range.end >= range.start
  │       ├─> Check: range.end <= total_pages
  │       │
  │       IF invalid:
  │         └─> THROW ERROR (invalid page ranges)
  │
  ├─> Split PDF Using pdf-lib
  │     │
  │     FOR each range in pageRanges:
  │       │
  │       ├─> Create new PDFDocument
  │       │
  │       ├─> Copy pages from original (0-indexed)
  │       │     FOR page = range.start-1 TO range.end-1:
  │       │       ├─> Copy page from original PDF
  │       │       └─> Add to new PDF
  │       │
  │       ├─> Save new PDF to buffer
  │       │
  │       └─> Store: {buffer, pageRange, formIndex, formType}
  │
  ├─> Build Response
  │     ├─> split_id: generated UUID
  │     ├─> files_created: splitResults.length
  │     ├─> split_files: array of file metadata
  │     └─> processing_time_ms: elapsed time
  │
  └─> Return Success Response

4.2 Page Range Building Logic

FUNCTION buildPageRanges(forms: FormClassification[]): PageRange[]
  pageRanges = []
  FOR idx = 0 TO forms.length - 1:
    form = forms[idx]
    formType = form.form_type || form.formType || form.type || "Form_{idx+1}"
    // Check for non-contiguous page ranges
    IF form.page_ranges EXISTS AND is_array(form.page_ranges):
      // Multiple non-contiguous ranges for this form
      FOR rangeIdx = 0 TO form.page_ranges.length - 1:
        range = form.page_ranges[rangeIdx]
        // If multiple ranges, append part number
        IF form.page_ranges.length > 1:
          actualFormType = "{formType}_Part{rangeIdx + 1}"
        ELSE:
          actualFormType = formType
        pageRanges.push({
          start: range.start,
          end: range.end,
          formType: actualFormType
        })
    ELSE:
      // Single contiguous range
      start = form.start_page || form.page_start || form.pageStart || form.start || 1
      end = form.end_page || form.page_end || form.pageEnd || form.end || 1
      pageRanges.push({
        start: start,
        end: end,
        formType: formType
      })
  RETURN pageRanges

4.3 PDF Splitting Logic (pdf-lib)

FUNCTION splitPDFByPageRanges(
  pdfBuffer: Buffer,
  pageRanges: PageRange[]
): SplitResult[]
  // Load original PDF
  originalPdf = await PDFDocument.load(pdfBuffer)
  results = []
  FOR idx = 0 TO pageRanges.length - 1:
    range = pageRanges[idx]
    // Create new PDF document
    newPdf = await PDFDocument.create()
    // Convert to 0-indexed
    startPage = range.start - 1
    endPage = range.end - 1
    // Copy pages
    FOR pageNum = startPage TO endPage:
      [copiedPage] = await newPdf.copyPages(originalPdf, [pageNum])
      newPdf.addPage(copiedPage)
    // Save to buffer
    pdfBytes = await newPdf.save()
    results.push({
      buffer: Buffer.from(pdfBytes),
      pageRange: range,
      formIndex: idx,
      formType: range.formType
    })
  RETURN results

5. Data Models

5.1 Split Request

interface SplitRequest {
  file_id?: string;
  file_url: string;
  classification_result: ClassificationResult;
  user_id: string;
  client_id: string;
  original_filename: string;
  metadata?: Record<string, any>;
}
interface ClassificationResult {
  is_multi_form: boolean;
  num_pages: number;
  forms: FormClassification[];
}
interface FormClassification {
  page_start?: number;
  page_end?: number;
  form_type: string;
  confidence: number;
  page_ranges?: Array<{start: number; end: number}>;
}

5.2 Split Response

interface SplitResponse {
  success: boolean;
  split_id: string;
  original_file_id?: string;
  files_created: number;
  split_files: SplitFileInfo[];
  processing_time_ms: number;
  metadata?: Record<string, any>;
}
interface SplitFileInfo {
  file_id: string;
  filename: string;
  blob_url: string;
  form_type: string;
  page_range: { start: number; end: number };
  file_size: number;
  form_index: number;
}

5.3 Internal Models

interface PageRange {
  start: number;
  end: number;
  formType: string;
}
interface SplitResult {
  buffer: Buffer;
  pageRange: PageRange;
  formIndex: number;
  formType: string;
}

6. Error Handling

6.1 Error Categories

| Error Code | HTTP Status | Description | Recovery |

|------------|-------------|-------------|----------|

| INVALID_CLASSIFICATION | 400 | Invalid classification result | Fix classification data |

| INVALID_PAGE_RANGES | 400 | Page ranges out of bounds | Reclassify document |

| FILE_NOT_ACCESSIBLE | 404 | Cannot download original PDF | Verify file_url |

| PDF_LOAD_ERROR | 500 | Failed to load PDF with pdf-lib | Check PDF integrity |

| SPLIT_FAILED | 500 | PDF splitting operation failed | Retry or manual review |

| STORAGE_ERROR | 503 | Blob storage failure | Retry with backoff |

| PARTIAL_SPLIT_SUCCESS | 207 | Some splits failed, others succeeded | Review partial results |

6.2 Error Response Format

interface ErrorResponse {
  success: false;
  error: string;
  error_code: string;
  details?: {
    invalid_ranges?: PageRange[];
    failed_splits?: number[];
    partial_results?: SplitFileInfo[];
  };
  timestamp: string;
  request_id: string;
}

6.3 Partial Failure Handling

When some splits succeed but others fail:

  • Return HTTP 207 (Multi-Status)

  • Include successful split files in response

  • Detail which splits failed and why

  • Do NOT delete successful splits (allow partial success)

  • Client can retry failed splits individually

7. Configuration

7.1 Environment Variables

# Service Configuration
PORT=3002
SERVICE_NAME=fluxx-splitter
VERSION=1.0.0
# Azure Blob Storage
AZURE_STORAGE_CONNECTION_STRING=xxx
AZURE_STORAGE_CONTAINER_NAME=fluxx-splits
# Splitter Settings
MAX_PDF_SIZE_MB=100
MAX_SPLIT_FILES=100
FILENAME_MAX_LENGTH=255
# Logging
LOG_LEVEL=info

7.2 Service Configuration

// config/splitter.ts
export const splitterConfig = {
  maxPdfSizeMB: parseInt(process.env.MAX_PDF_SIZE_MB || '100'),
  maxSplitFiles: parseInt(process.env.MAX_SPLIT_FILES || '100'),
  filenameMaxLength: parseInt(process.env.FILENAME_MAX_LENGTH || '255'),
  blobContainerName: process.env.AZURE_STORAGE_CONTAINER_NAME || 'paperboy',
};

8. Implementation Pseudocode

8.1 Main Split Function

async function splitDocument(request: SplitRequest): Promise<SplitResponse> {
  const startTime = Date.now();
  const splitId = generateUUID();
  try {
    // 1. Validate input
    validateSplitRequest(request);
    // 2. Check if splitting is needed
    const shouldSplit = request.classification_result.is_multi_form ||
                        request.classification_result.forms.length > 1;
    if (!shouldSplit || request.classification_result.forms.length === 0) {
      return {
        success: true,
        split_id: splitId,
        files_created: 0,
        split_files: [],
        processing_time_ms: Date.now() - startTime,
        message: 'Document does not require splitting'
      };
    }
    // 3. Download original PDF
    const pdfBuffer = await downloadFromBlobStorage(request.file_url);
    // 4. Build page ranges
    const pageRanges = buildPageRanges(request.classification_result.forms);
    // 5. Validate page ranges
    validatePageRanges(pageRanges, request.classification_result.num_pages);
    // 6. Split PDF
    const splitResults = await splitPDFByPageRanges(pdfBuffer, pageRanges);
    // 7. Upload and create file records
    const splitFiles: SplitFileInfo[] = [];
    const errors: Array<{index: number; error: string}> = [];
    for (let i = 0; i < splitResults.length; i++) {
      try {
        const splitResult = splitResults[i];
        const formType = pageRanges[i].formType;
        // Generate filename
        const baseName = request.original_filename.replace('.pdf', '');
        const sanitizedFormType = sanitizeForFilename(formType);
        const fileName = `${baseName}_${sanitizedFormType}.pdf`;
        // Upload to blob storage
        const blobUrl = await uploadToBlobStorage(
          request.user_id,
          request.client_id,
          fileName,
          splitResult.buffer
        );
        splitFiles.push({
          file_id: fileId,
          filename: fileName,
          blob_url: blobUrl,
          form_type: formType,
          page_range: splitResult.pageRange,
          file_size: splitResult.buffer.length,
          form_index: i
        });
      } catch (error) {
        console.error(`Failed to process split ${i}:`, error);
        errors.push({ index: i, error: error.message });
      }
    }
    // 8. Build response
    if (errors.length > 0 && splitFiles.length === 0) {
      // Total failure
      throw new Error(`All splits failed: ${JSON.stringify(errors)}`);
    }
    const response: SplitResponse = {
      success: true,
      split_id: splitId,
      original_file_id: request.file_id,
      files_created: splitFiles.length,
      split_files: splitFiles,
      processing_time_ms: Date.now() - startTime
    };
    if (errors.length > 0) {
      // Partial success
      response.partial_errors = errors;
      response.status_code = 207; // Multi-Status
    }
    return response;
  } catch (error) {
    throw classifySplitError(error, splitId);
  }
}

8.2 Filename Sanitization

function sanitizeForFilename(formType: string): string {
  // Remove special characters, keep alphanumeric and basic punctuation
  let sanitized = formType.replace(/[^a-zA-Z0-9\-_]/g, '_');
  // Remove consecutive underscores
  sanitized = sanitized.replace(/_+/g, '_');
  // Remove leading/trailing underscores
  sanitized = sanitized.replace(/^_+|_+$/g, '');
  // Limit length
  if (sanitized.length > 100) {
    sanitized = sanitized.substring(0, 100);
  }
  // Default if empty
  if (sanitized.length === 0) {
    sanitized = 'Form';
  }
  return sanitized;
}

8.3 Page Range Validation

function validatePageRanges(pageRanges: PageRange[], totalPages: number): void {
  const invalidRanges: PageRange[] = [];
  for (const range of pageRanges) {
    if (range.start < 1 ||
        range.end < range.start ||
        range.end > totalPages) {
      invalidRanges.push(range);
    }
  }
  if (invalidRanges.length > 0) {
    throw new ValidationError(
      'INVALID_PAGE_RANGES',
      `Invalid page ranges detected`,
      { invalid_ranges: invalidRanges, total_pages: totalPages }
    );
  }
  // Check for overlaps (optional, may be valid in some cases)
  for (let i = 0; i < pageRanges.length; i++) {
    for (let j = i + 1; j < pageRanges.length; j++) {
      const r1 = pageRanges[i];
      const r2 = pageRanges[j];
      if (rangesOverlap(r1, r2)) {
        console.warn(`Warning: Page ranges overlap: ${JSON.stringify(r1)} and ${JSON.stringify(r2)}`);
      }
    }
  }
}
function rangesOverlap(r1: PageRange, r2: PageRange): boolean {
  return !(r1.end < r2.start || r2.end < r1.start);
}

9. Deployment

9.1 Docker Container

FROM node:18-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy source
COPY . .
# Build TypeScript
RUN npm run build
EXPOSE 3002
CMD ["node", "dist/server.js"]

9.2 Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fluxx-splitter
  labels:
    app: fluxx-splitter
spec:
  replicas: 3
  selector:
    matchLabels:
      app: fluxx-splitter
  template:
    metadata:
      labels:
        app: fluxx-splitter
    spec:
      containers:
      - name: fluxx-splitter
        image: fluxx-splitter:1.0.0
        ports:
        - containerPort: 3002
        env:
        - name: PORT
          value: "3002"
        - name: AZURE_STORAGE_CONNECTION_STRING
          valueFrom:
            secretKeyRef:
              name: fluxx-secrets
              key: storage-connection-string
        - name: DATABASE_HOST
          valueFrom:
            secretKeyRef:
              name: fluxx-secrets
              key: database-host
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "4Gi"
            cpu: "2000m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3002
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3002
          initialDelaySeconds: 10
          periodSeconds: 5

10. Testing Strategy

10.1 Unit Tests

  • Page range building logic

  • Page range validation

  • Filename sanitization

  • Error handling for invalid inputs

10.2 Integration Tests

  • PDF splitting with pdf-lib

  • Blob storage upload

  • Database file metadata creation

  • End-to-end split workflow

10.3 Test Cases

| Test Case | Input | Expected Output |

|-----------|-------|-----------------|

| Single Form | Classification with 1 form | No splits created |

| Multi-Form (2 forms) | 1040 + Schedule C | 2 separate PDFs |

| Non-contiguous Pages | Form with page_ranges | Multiple PDFs for same form type |

| Invalid Page Range | page_start=10, page_end=5 | Error: INVALID_PAGE_RANGES |

| Page Range Out of Bounds | page_end=100, but PDF has 50 pages | Error: INVALID_PAGE_RANGES |

| Large Multi-Form | 50 forms | 50 separate PDFs |

| Special Characters in Form Type | “Form 1099-MISC” | Sanitized filename |

| Partial Split Failure | Valid data, but storage fails for 1 file | HTTP 207, partial results |

11. Performance Considerations

11.1 Expected Performance

  • Small PDF (2 forms, 10 pages): 1-2 seconds

  • Medium PDF (10 forms, 50 pages): 3-5 seconds

  • Large PDF (50 forms, 200 pages): 10-15 seconds

11.2 Performance Factors

  • PDF page copying with pdf-lib (CPU intensive)

  • Buffer creation for each split file

  • Blob storage upload time (network I/O)

  • Database inserts for file metadata

11.3 Optimization Opportunities

  1. Parallel uploads: Upload split files to blob storage concurrently

  2. Batch database inserts: Insert multiple file metadata records in single transaction

  3. Compression: Compress PDF buffers before upload

  4. Caching: Cache PDF documents for repeated split operations

11.4 Resource Requirements

  • Memory: ~50MB per split operation + PDF file size

  • CPU: Moderate (pdf-lib page copying)

  • Network: High (downloading original, uploading splits)

12. Monitoring & Observability

12.1 Metrics

  • Split requests per minute

  • Average split time

  • Files created per split

  • Blob upload latency

  • Database insert latency

  • Error rate by error code

  • Partial failure rate

12.2 Logging

  • Structured JSON logging

  • Input validation errors

  • Split operation details (page ranges, form types)

  • File creation success/failure

  • Error stack traces

12.3 Alerting

  • Error rate > 5%

  • Average split time > 30 seconds

  • Blob storage failures > 2%

  • Database connectivity issues

  • Partial failure rate > 10%

13. Security

13.1 Authentication

  • API key authentication

  • Azure AD integration (optional)

  • User/client authorization

13.2 Data Protection

  • TLS in transit

  • Encrypted blob storage

  • Temporary buffer cleanup

  • PII redaction in logs

13.3 Input Validation

  • Page range bounds checking

  • Form type sanitization

  • File size limits

  • PDF format validation

13.4 Rate Limiting

  • Per-user split limits

  • Global service rate limits

  • Maximum splits per request

14. Integration with Classification Service

14.1 Workflow Integration

┌─────────────────────────────┐
│  Client Application         │
└──────────┬──────────────────┘
           │
           │ 1. POST /classify
           ▼
┌─────────────────────────────┐
│  FLUXX Classification       │
│  Service                    │
└──────────┬──────────────────┘
           │
           │ 2. Classification Result
           ▼
┌─────────────────────────────┐
│  Client Application         │  
│  (Decision Logic)           │
└──────────┬──────────────────┘
           │
           │ 3. IF is_multi_form:
           │    POST /split
           ▼
┌─────────────────────────────┐
│  FLUXX Splitter Service     │
└──────────┬──────────────────┘
           │
           │ 4. Split Files
           ▼
┌─────────────────────────────┐
│  Client Application         │
│  (Process individual forms) │
└─────────────────────────────┘

14.2 Data Flow

  1. Classification Phase:

   - Client sends PDF to classification service

   - Receives classification result with form types and page ranges

  1. Decision Phase:

   - Client checks is_multi_form flag

   - If true, proceeds to splitting

  1. Splitting Phase:

   - Client sends classification result + file URL to splitter service

   - Receives array of split file references

  1. Processing Phase:

   - Client processes each split file individually

   - Each file is a single form type

14.3 API Contract

The splitter service expects the exact output format from the classification service:

// Classification Service Output = Splitter Service Input
{
  "is_multi_form": boolean,
  "num_pages": number,
  "forms": [
    {
      "page_start": number,
      "page_end": number,
      "form_type": string,
      "confidence": number,
      "page_ranges"?: Array<{start: number, end: number}>  // For non-contiguous
    }
  ]
}

15. Migration from Existing System

15.1 Migration Path

  1. Deploy FLUXX Splitter Service alongside existing system

  2. Update ClassifyFileTool to optionally call splitter service instead of inline splitting

  3. Add feature flag to control routing

  4. Run in parallel for validation period

  5. Gradually migrate all splitting to new service

  6. Remove inline splitting code from ClassifyFileTool

15.2 Backward Compatibility

  • Maintain same file metadata structure

  • Preserve parent_file_id relationships

  • Keep form_index and form_type fields

  • Support existing blob storage paths

16. Future Enhancements

16.1 Phase 2 Features

  • Parallel split file uploads

  • Batch database inserts

  • PDF compression options

  • Custom filename templates

  • Dry-run mode (validate without creating files)

16.2 Phase 3 Features

  • Merge operation (combine forms back into single PDF)

  • Re-ordering of forms before splitting

  • Watermarking of split files

  • PDF/A conversion for long-term archival

  • Split preview (return file sizes without creating)

17. Dependencies

17.1 NPM Packages

{
  "express": "^4.18.0",
  "pdf-lib": "^1.17.1",
  "@azure/storage-blob": "^12.15.0",
  "uuid": "^9.0.0",
  "zod": "^3.22.0"
}

17.2 External Services

  • Azure Blob Storage (for file storage)

  • Database (for file metadata - SQL Server or PostgreSQL)

18. Comparison with Current Implementation

18.1 Similarities

  • Same pdf-lib splitting logic

  • Same filename sanitization approach

  • Same file metadata structure

  • Same parent-child file relationships

18.2 Differences

| Aspect | Current (ClassifyFileTool) | FLUXX Splitter Service |

|--------|---------------------------|------------------------|

| Coupling | Tightly coupled with classification | Loosely coupled, independent service |

| Reusability | Only via tool invocation | Standard REST API, any client |

| Error Handling | Fails entire classification if split fails | Supports partial success, better error detail |

| Batch Support | One document at a time | Batch splitting supported |

| Monitoring | Limited logging | Comprehensive metrics and logging |

| Scalability | Scales with entire backend | Independent horizontal scaling |

| Testing | Integration tests only | Unit + integration tests |

19. Rollback Strategy

19.1 Rollback Triggers

  • Error rate > 10%

  • Data corruption detected

  • Performance degradation > 50%

  • Critical bug discovered

19.2 Rollback Process

  1. Switch feature flag to route traffic back to ClassifyFileTool

  2. Stop FLUXX Splitter Service

  3. Verify inline splitting is working

  4. Investigate and fix issue

  5. Re-deploy with fix

  6. Gradually re-enable new service

19.3 Data Cleanup

  • Split files created by service have traceable metadata

  • Can query by creation timestamp to identify service-created files

  • Rollback does not require deleting split files (backward compatible)

20. Conclusion

The FLUXX Splitter Service provides a robust, scalable solution for PDF document splitting based on classification results. By separating splitting from classification, we achieve:

Benefits:

  • Modularity: Each service has a single, well-defined responsibility

  • Reusability: Any client can use the splitter, not just Paperboy

  • Scalability: Split operations can scale independently from classification

  • Observability: Dedicated metrics and monitoring for splitting operations

  • Testability: Easier unit and integration testing

  • Flexibility: Clients can choose when and how to split

Key Design Decisions:

  • Stateless design: Enables horizontal scaling

  • Standard REST API: Easy integration

  • Partial failure support: Better error resilience

  • Comprehensive validation: Guards against invalid inputs

  • Cloud-native: Designed for Kubernetes with Azure services

Next Steps:

  1. Implement service following this design

  2. Create comprehensive test suite

  3. Deploy to staging environment

  4. Integration testing with Classification Service

  5. Performance benchmarking

  6. Production deployment with feature flag

  7. Gradual traffic migration

This service complements the FLUXX Classification Service to provide a complete document processing pipeline from classification to individual form extraction.