Skip to navigation

Classifier Service

Overview

The fluxx-classifier is a service that identifies the form type of each page in the PDF (e.g. Form 1040).

This service listens for file accepted events from Azure Service Bus and orchestrates the AI classification of documents. For each classifier registered to a client application, it creates and tracks a classification job in SQL, calls the external classification endpoint, saves the result to Azure Blob Storage, and publishes a downstream FileClassified event so other services (e.g. document-splitter) can process the output. It runs as a hybrid worker: a Fastify HTTP server hosts an OpenAPI spec endpoint, but all document processing is driven by messaging events.

When a FileAccepted event is received from the Service Bus, the classifier service verifies the file blob exists, retrieves all active classifiers registered for the client application, and runs each classifier concurrently. For each classifier, it creates a job record, calls the classification endpoint, saves the result JSON to blob storage, and publishes a FileClassified event and a ClassifierJobStatus event per status transition.


Flow

Service Bus (FileAccepted)
│
▼
AcceptedFileHandler
1. Verify blob exists at fileLocationUrl
2. Fetch active classifiers for clientApplicationId from RegistrationService
3. For each classifier
│
▼
Per Classifier:
a. upsertJob(Queued) → publish ClassifierJobStatus(Queued)
b. startClassification
c. updateJob(Started) → insertJobStatus(Started) → publish ClassifierJobStatus(Started)
d. updateJob(Succeeded) → insertJobStatus(Succeeded)
→ saveClassificationResult → blob storage (classifications container)
→ publish FileClassifiedEvent → (service bus)
→ publish ClassifierJobStatus(Succeeded)
on any error:
→ updateJob(Failed) → insertJobStatus(Failed) → publish ClassifierJobStatus(Failed)

Events

Input – FileAcceptedEvent

{
fileId: string;
fileHashValue: string;
applicationId: string;
internalUrl: string; // blob URL used to verify file existence
fileLocationUrl: string; // blob URL used to verify file existence
timestamp: Date;
clientApplicationId: string;
}

Output – FileClassifiedEvent (one per classifier that succeeds)

{
classifiedOutputUrl: string; // blob URL of the saved classification JSON
clientApplicationId: string;
fileId: string;
internalUrl: string;
timestamp: Date;
}

Output – ClassificationStatusEvent (one per status transition per classifier)

// Base shape
{
status: ClassificationStatus; // Queued | Started | Processing | Succeeded | Failed
jobId: string;
fileId: string;
clientApplicationClassifierId: string;
timestamp: Date;
}
// Variant fields by status:
// Started → classifiedEntityId: number
// Processing → pollAttemptCount: number, message?: string
// Failed → errorMessage: string
// Succeeded → resultSummary: string

Error – ProblemEvent

Published when processing fails. Returns 404 if the file blob is not found or no classifiers are registered, 500 for all other errors.


Blob Storage Layout

classifications/
<clientApplicationId>/
<clientApplicationClassifierId>/
<yyyyMMddHHmmss>_<fileId>.json ← classification result JSON

Database Schema

[classifier].[Job]

One record per classifier-per-file execution. Upserted on jobId.

ColumnDescription
JobIdUUID generated per classifier run
FileIdFrom the incoming event
ClientApplicationClassifierIdClassifier that was invoked
StatusCurrent status (Queued → Started → Succeeded / Failed)
StatusMessageError message on failure
ClassifierResponseRaw JSON result from the classifier
PollAttemptCountNumber of status poll attempts (for async classifiers)
CompletedAtISO timestamp when job reached a terminal state
CreatedAt / UpdatedAtManaged by the database

[classifier].[JobStatus]

Append-only status history. One row per status transition.

ColumnDescription
JobIdForeign key to [classifier].[Job]
StatusStatus at the time of the transition
MessageOptional message (e.g. error detail)
AttemptNumberPoll attempt index for Processing transitions

Limitations / Current State

  • Classification call is mocked - currently returns a hardcoded mock response ({ jobId, status: 'Started' }) without calling the real endpoint.
  • Mock result is used - Since the real classifier is not yet available, the team agreed to mock the result from classifier API.
  • Status polling not integrated - not yet called in the main flow; the job transitions directly from Started to Succeeded using the mock result.

TO-BE: Future Changes

Overview

The mocked classification flow will be replaced by real async integration with external classifiers. The orchestrator will call the actual classification endpoint, poll for status, and only publish the FileClassified event once a terminal result is confirmed.