ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
@@ -0,0 +1,145 @@
// The line extractor that asks the platform: Document Items (ADR-069) behind the kit's `DocumentReader`.
//
// The platform already read the document (the job this module submitted); this extractor runs a profile
// over that job (`items.submit` then `items.wait`) and maps each item of `conusai.documents.items/v1` to a
// `CandidateLine`: the item's name as the label, its quantity and unit as printed as the value, and the
// page, quote and box of its first citation (the box comes in the page's units and is kept as fractions of
// the page this module read). The platform verifies its quotes; this module checks them again against that
// page (core/verify.ts), so a line is `verified` only when the platform auto-accepted the item, no check
// failed and the quote is on the page; anything else is proposed as `needs_review`. Each line keeps a
// reference to its item (`itemRef`), so a reviewer's decision goes back as feedback (adapters/item-feedback.ts).
//
// It is chosen by the `documents.platform_items` flag (adapters/jobs.ts); `modelLineExtractor` stays the
// fallback and the offline default.
import { token } from "@conusai-kit/core"
import type { DocumentItems, Item, ItemCitation, ItemClass, OcrItems } from "@conusai-kit/platform"
import { DEFAULT_EXCLUDED_PAGE_CLASSES, type PageClass } from "../contract/index.js"
import {
type CandidateLine,
type Extraction,
ExtractionFailed,
type ExtractionInput,
type FractionBox,
type LineExtractor,
type ReadPage,
type SkippedPage,
} from "../core/ports.js"
import { quoteIsOnPage } from "../core/verify.js"
/** The platform's items run, as the worker binds it: the `items` member of the kit's `DocumentReader`. */
export const DOCUMENT_ITEMS = token<Pick<OcrItems, "submit" | "wait" | "feedback">>("documents.items", {
access: "effect",
})
/** The profile the run uses; without a binding, `DEFAULT_ITEMS_PROFILE`. */
export const DOCUMENT_ITEMS_PROFILE = token<string>("documents.items-profile")
export const DEFAULT_ITEMS_PROFILE = "invoice"
export interface PlatformItemsExtractorOptions {
readonly items: Pick<OcrItems, "submit" | "wait">
readonly profile?: string
/** Profiles the run links items to, `spec_sections`. */
readonly link?: string[]
/** Item classes kept, `material`. */
readonly keep?: ItemClass[]
readonly excludedPageClasses?: readonly PageClass[]
/** Poll interval of `wait`, in milliseconds. */
readonly intervalMs?: number
}
/** A citation's box, `[x0, y0, x1, y1]` in the page's units, as fractions of the page it was read on. */
export function boxOf(
bbox: readonly number[] | null | undefined,
page: Pick<ReadPage, "width" | "height"> | undefined,
): FractionBox | undefined {
if (bbox?.length !== 4 || !page || !(page.width > 0 && page.height > 0)) return undefined
const [x0, y0, x1, y1] = bbox as [number, number, number, number]
if (!(x1 > x0 && y1 > y0)) return undefined
const fraction = (value: number, whole: number) => Math.min(1, Math.max(0, value / whole))
const x = fraction(x0, page.width)
const y = fraction(y0, page.height)
return { x, y, width: fraction(x1, page.width) - x, height: fraction(y1, page.height) - y }
}
/** The label and value a reviewer sees: the item's name, and its quantity and unit as printed. */
export function labelAndValueOf(item: Item): { label: string; value: string } | undefined {
const label = item.name.trim()
const value = [item.quantity.text.trim(), item.unit.text.trim()].filter((part) => part !== "").join(" ")
if (label === "" || value === "") return undefined
return { label: label.slice(0, 200), value: value.slice(0, 2_000) }
}
/** A citation of a PDF page and its lines; the other kind cites a spreadsheet's cells. */
type PageCitation = Extract<ItemCitation, { kind: "pdf_page" }>
/**
* One item as the line a person reviews; undefined for an item without a PDF page citation (this module
* reads PDFs, so a sheet's cells are nothing it can show). It is `verified` only when the platform
* auto-accepted it (reader and model agree, every check passed) and its quote is on the page this module
* read.
*/
export function candidateOf(
item: Item,
result: Pick<DocumentItems, "run_id" | "models">,
pageOf: (page: number) => ReadPage | undefined,
): CandidateLine | undefined {
const cited = item.citations.filter((one): one is PageCitation => one.kind === "pdf_page")
const citation = cited[0]
const shown = labelAndValueOf(item)
if (!citation || !shown) return undefined
const quote = cited
.filter((one) => one.page === citation.page)
.map((one) => one.quote)
.join("\n")
const page = pageOf(citation.page)
const onPage = page !== undefined && quoteIsOnPage(page.text, quote)
const checked = item.checks.every((check) => check.status !== "failed")
const box = boxOf(citation.bbox, page)
const model = result.models[0]?.model
return {
...shown,
page: citation.page,
quote,
...(box ? { box } : {}),
confidence: Math.min(1, Math.max(0, item.confidence)),
...(model ? { model } : {}),
verification: onPage && checked && item.agreement.status === "auto_accepted" ? "verified" : "needs_review",
...(result.run_id === null ? {} : { itemRef: `${result.run_id}:${item.id}` }),
}
}
export function platformItemsExtractor(options: PlatformItemsExtractorOptions): LineExtractor {
const profile = options.profile ?? DEFAULT_ITEMS_PROFILE
const excluded = new Set<PageClass>(options.excludedPageClasses ?? DEFAULT_EXCLUDED_PAGE_CLASSES)
return {
name: `platform-items:${profile}`,
version: `items.${profile}.v1`,
async extract(input: ExtractionInput): Promise<Extraction> {
// The run reads the job this module submitted: without one there is nothing on the platform to read.
if (input.ocrJobId === undefined || input.ocrJobId === null) throw new ExtractionFailed("extraction_invalid")
const skipped: SkippedPage[] = input.document.pages
.filter((page) => excluded.has(page.class))
.map((page) => ({ page: page.index + 1, pageClass: page.class }))
const skippedPages = new Set(skipped.map((one) => one.page))
await input.beforeProvider?.()
const run = await options.items.submit(input.ocrJobId, {
profile,
...(options.link ? { link: options.link } : {}),
...(options.keep ? { keep: options.keep } : {}),
// One run per job and profile: a retried job resumes the run it already started.
idempotencyKey: `documents.items.${input.ocrJobId}.${profile}`,
...(input.signal ? { signal: input.signal } : {}),
})
const result = await options.items.wait(run, {
...(options.intervalMs === undefined ? {} : { intervalMs: options.intervalMs }),
...(input.signal ? { signal: input.signal } : {}),
})
const pageOf = (page: number) => input.document.pages.find((one) => one.index === page - 1)
const lines = result.items
.map((item) => candidateOf(item, result, pageOf))
.filter((line): line is CandidateLine => line !== undefined && !skippedPages.has(line.page))
return { lines, skipped }
},
}
}