70 lines
3.3 KiB
TypeScript
70 lines
3.3 KiB
TypeScript
// The OCR job: what `files.processors` enqueues once per matching upload.
|
|
//
|
|
// The tenant comes from the container, never from the payload (a module cannot mint a system
|
|
// actor, decision AT-F6), so a payload that named another organization would change nothing.
|
|
|
|
import { internal, type JobDef, serviceRefusal } from "@conusai-kit/core"
|
|
import { OBJECT_STORE } from "@conusai-kit/data"
|
|
import { type ProcessorJobPayload, processorJobPayloadSchema } from "@conusai-kit/files"
|
|
import { NATIVE_JOB_ADMISSION } from "@conusai-kit/platform"
|
|
import { PLATFORM_ITEMS_FLAG } from "../contract/index.js"
|
|
import {
|
|
DOCUMENT_OCR,
|
|
DOCUMENTS_FLAGS,
|
|
DOCUMENTS_SCOPE,
|
|
defaultDocumentsFlags,
|
|
type LineExtractor,
|
|
} from "../core/ports.js"
|
|
import { processDocument } from "../core/process.js"
|
|
import { DOCUMENT_EXCLUDED_PAGE_CLASSES, DOCUMENT_MODEL, MODEL_GATEWAY, modelLineExtractor } from "./model-extractor.js"
|
|
import { notifyDocumentResult } from "./notifications.js"
|
|
import { DOCUMENT_ITEMS, DOCUMENT_ITEMS_PROFILE, platformItemsExtractor } from "./platform-items-extractor.js"
|
|
import { documentsDeps } from "./procedures.js"
|
|
|
|
export const ocrJob: JobDef<ProcessorJobPayload> = {
|
|
payload: processorJobPayloadSchema,
|
|
async handler(payload, ctx) {
|
|
const scope = ctx.container.resolve(DOCUMENTS_SCOPE)
|
|
if (scope.level !== "project") throw internal(`a documents job runs in a project scope, not a ${scope.level} one`)
|
|
const excludedPageClasses = ctx.container.has(DOCUMENT_EXCLUDED_PAGE_CLASSES)
|
|
? { excludedPageClasses: ctx.container.resolve(DOCUMENT_EXCLUDED_PAGE_CLASSES) }
|
|
: {}
|
|
const flags = ctx.container.has(DOCUMENTS_FLAGS) ? ctx.container.resolve(DOCUMENTS_FLAGS) : defaultDocumentsFlags
|
|
// The platform's Document Items run when the flag is on and the worker bound it; otherwise the model
|
|
// behind the platform's AI gateway, which the composition root binds with its model id.
|
|
const extractor: LineExtractor =
|
|
ctx.container.has(DOCUMENT_ITEMS) && (await flags.enabled(PLATFORM_ITEMS_FLAG, scope))
|
|
? platformItemsExtractor({
|
|
items: ctx.container.resolve(DOCUMENT_ITEMS),
|
|
...(ctx.container.has(DOCUMENT_ITEMS_PROFILE)
|
|
? { profile: ctx.container.resolve(DOCUMENT_ITEMS_PROFILE) }
|
|
: {}),
|
|
...excludedPageClasses,
|
|
})
|
|
: modelLineExtractor({
|
|
gateway: ctx.container.resolve(MODEL_GATEWAY),
|
|
model: ctx.container.resolve(DOCUMENT_MODEL),
|
|
...excludedPageClasses,
|
|
})
|
|
await processDocument(
|
|
{
|
|
...documentsDeps(ctx.container),
|
|
...(ctx.finalAttempt === undefined ? {} : { finalAttempt: ctx.finalAttempt }),
|
|
beforeProvider: async (subject) => {
|
|
if (!ctx.signal || !ctx.container.has(NATIVE_JOB_ADMISSION)) throw serviceRefusal("observation_missing")
|
|
ctx.signal.throwIfAborted()
|
|
await ctx.container.resolve(NATIVE_JOB_ADMISSION).authorize(scope, "documents.ocr", subject, ctx.signal)
|
|
},
|
|
store: ctx.container.resolve(OBJECT_STORE),
|
|
ocr: ctx.container.resolve(DOCUMENT_OCR),
|
|
extractor,
|
|
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
},
|
|
scope,
|
|
payload,
|
|
)
|
|
ctx.signal?.throwIfAborted()
|
|
await notifyDocumentResult(ctx.container, scope, payload.fileId)
|
|
},
|
|
}
|