ConusAI vendor v0.1.0-dev.20261027+9b1131fb
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-dev.20261027+9b1131fb
2026-10-27 18:05:17 +00:00
commit 3102273ec7
538 changed files with 80203 additions and 0 deletions
+175
View File
@@ -0,0 +1,175 @@
// The ports the documents module's use cases are written against. Core imports no SDK (spec §2), so
// the shapes of an OCR result and of an extractor are declared here, structurally: the SDK's own
// `OcrDocument` is assignable to `ReadDocument`, and the adapter in adapters/ocr.ts is the one place
// that knows the SDK exists.
import { type TenantScope, token } from "@conusai-kit/core"
import type { FailureCode, LineNote, LineVerification, PageClass } from "../contract/index.js"
import type { StageCache } from "./stage-cache.js"
/** `[x0, y0, x1, y1]` in pixels of the page image, origin top-left. */
export type PixelBox = readonly [number, number, number, number]
export interface ReadLine {
/** The line as the reader returned it. A quote is this string, unchanged. */
readonly text: string
readonly bbox: PixelBox
/** 0 to 1. */
readonly confidence: number
/** Reading order within the page. */
readonly order: number
/** True when the line's text is on most pages of the document: a running header, footer or stamp, not the document's content. */
readonly repeated: boolean
}
/** A run of lines the reader grouped, with the stable id a citation points at. */
export interface ReadBlock {
/** `p{page}-b{n}`: the 1-based page of the source document and the block's place in reading order. */
readonly id: string
readonly bbox: PixelBox
readonly order: number
/** Indexes into the page's `lines`. */
readonly lines: readonly number[]
}
export interface ReadPage {
/** 0-based position in the source document. */
readonly index: number
readonly width: number
readonly height: number
/** What the page is mostly made of; decides whether the extraction reads it, and whether it is shown the image. */
readonly class: PageClass
/** `text_layer`: read from the file's own text; `ocr`: recognised from the page image (a scan). */
readonly source?: "ocr" | "text_layer" | "spreadsheet" | undefined
/** The page's text in reading order, one row per line. */
readonly text: string
readonly lines: readonly ReadLine[]
readonly blocks: readonly ReadBlock[]
}
export interface ReadDocument {
readonly engine: { readonly name: string; readonly version: string }
readonly page_count: number
readonly pages: readonly ReadPage[]
}
/** A box as fractions of the page, so it survives a render at any width (kit-proposals AT-D27). */
export interface FractionBox {
readonly x: number
readonly y: number
readonly width: number
readonly height: number
}
/** What an extractor proposes: a value, where it was read, and the words it was read from. */
export interface CandidateLine {
readonly label: string
readonly value: string
/** 1-based. */
readonly page: number
/** What the extractor quoted: verbatim from the page text, unless `verification` says it was not found there. */
readonly quote: string
/** The reader's id of the block the quote sits in, when the quote was found on the page's lines. */
readonly blockId?: string
/** The block's box (or, without a block, the box of the lines the quote falls on). */
readonly box?: FractionBox
/** The reader's confidence in the words, 0 to 1. */
readonly confidence: number
/** Set by code, never by a model: was the quote found on the page it cites. */
readonly verification: LineVerification
/** Why a reviewer should look at the line besides `verification`. */
readonly note?: LineNote
}
/** The image of one page, for a model that must read what the text layer could not. */
export interface PageImage {
readonly bytes: Uint8Array
/** `image/png` or `image/jpeg`. */
readonly mediaType: string
}
/** What an extractor is given: the read document, and what it needs to avoid paying twice. */
export interface ExtractionInput {
readonly document: ReadDocument
/** The image of a 0-based page, or undefined when there is none to give. */
readonly pageImage: (pageIndex: number) => Promise<PageImage | undefined>
/** Model replies are cached here by content hash, so a retried job pays for nothing twice. */
readonly cache: StageCache
readonly signal?: AbortSignal
}
/** The failure codes an extraction can end a document with. */
export type ExtractionFailureCode = Extract<FailureCode, `extraction_${string}`>
/**
* A permanent failure of the extraction, as a code the document records: the model declined
* (`extraction_refused`), kept running into its token ceiling (`extraction_truncated`) or never
* answered in the schema (`extraction_invalid`), each after one retry. Anything else an extractor
* throws is transient, and the queue retries the job.
*/
export class ExtractionFailed extends Error {
constructor(public readonly code: ExtractionFailureCode) {
super(`the extraction failed: ${code}`)
this.name = "ExtractionFailed"
}
}
/** A page the extraction did not read, and the class that left it out. */
export interface SkippedPage {
/** 1-based. */
readonly page: number
readonly pageClass: PageClass
}
/** What an extractor produced: the lines it proposes, and the pages it left out on purpose. */
export interface Extraction {
readonly lines: readonly CandidateLine[]
readonly skipped: readonly SkippedPage[]
}
/**
* Turns a read document into candidate lines. The template ships one behind the `ModelGateway`
* port (adapters/model-extractor.ts); a product replaces it -- a table reader, a domain's own rules,
* a different prompt -- by passing its own to the job, and nothing else in the module changes.
*/
export interface LineExtractor {
/** Recorded on every proposal as its prompt version: `<name>.v<n>`. */
readonly version: string
/** Recorded on every proposal as its producing model, besides the OCR engine. */
readonly name: string
/** Throws `ExtractionFailed` for a failure no retry would change. */
extract(input: ExtractionInput): Promise<Extraction>
}
/**
* Asynchronous OCR: `submit` returns when the platform has accepted the document, `wait` when it
* has read it. Two calls, because a retried job resumes a job it already submitted instead of
* paying for the document twice.
*/
export interface DocumentOcr {
submit(bytes: Uint8Array, options: { filename: string; idempotencyKey: string }): Promise<{ jobId: number }>
/** Throws `OcrJobFailed` when the platform says the job failed, which no retry will change. */
wait(jobId: number, options?: { signal?: AbortSignal }): Promise<ReadDocument>
/**
* The image of a 1-based page of a finished job, drawn by the platform. Undefined when the platform
* has none to give (the job's input expired, the page is past the end): the page is then read from its text.
*/
page(jobId: number, page: number, options?: { signal?: AbortSignal }): Promise<PageImage | undefined>
}
/** The platform read the document and could not: permanent, so the document is marked failed. */
export class OcrJobFailed extends Error {
constructor(message = "the OCR job failed") {
super(message)
this.name = "OcrJobFailed"
}
}
// An OCR read is metered and leaves the process: only an effect (a job) may resolve it.
export const DOCUMENT_OCR = token<DocumentOcr>("documents.ocr", { access: "effect" })
/**
* The project a job runs in, with a system actor. The worker binds it per job from the enqueueing
* scope (the same arrangement as `FILES_SCOPE`, decision AT-F6): a payload never names a tenant.
*/
export const DOCUMENTS_SCOPE = token<TenantScope>("documents.scope")