Files

85 lines
3.9 KiB
TypeScript

// What an extracted line is, as the proposal that carries it and as the reviewer sees it.
import { z } from "zod"
import { LINE_NOTES, LINE_STATUSES, LINE_VERIFICATIONS } from "./constants.js"
/** The proposal target this module contributes to `proposals.targets`: the writer of approved lines. */
export const LINE_TARGET_KEY = "documents.line"
/** The `files.processors` key and the job it names. */
export const PROCESSOR_KEY = "documents.ocr"
export const OCR_JOB = "documents.ocr"
/** The audit events of this module, declared in its manifest. */
export const EXPORT_CREATED = "documents.export_created"
export const LINE_EDITED = "documents.line_edited"
/** The content types the module reads. Spreadsheets are the customer's own extraction, not the template's. */
export const READABLE_CONTENT_TYPES = ["application/pdf", "image/png", "image/jpeg"] as const
/**
* The change a proposal carries and approval writes. `lineKey` is derived from the document and the
* line's position, so an extraction retried after a crash adds nothing a person already decided.
*/
export const lineChangeSchema = z
.object({
lineKey: z.string().regex(/^[0-9a-f]{64}$/, "a lower-case hex SHA-256"),
label: z.string().min(1).max(200),
value: z.string().min(1).max(2_000),
page: z.number().int().min(1),
/** Where the line sits in the document's reading order: what a review and an export sort by. */
position: z.number().int().min(0),
quote: z.string().min(1).max(2_000),
/** The reader's confidence in the words the line was read from, 0 to 1; a reviewer sorts by it. */
confidence: z.number().min(0).max(1),
/** `needs_review` when the quote was not found on the page it cites (see `LINE_VERIFICATIONS`). */
verification: z.enum(LINE_VERIFICATIONS),
/** Why a reviewer should look at the line besides `verification`; absent when there is nothing to add. */
note: z.enum(LINE_NOTES).optional(),
/** True once a reviewer has corrected the label or the value (the proposals module records who and when). */
edited: z.boolean(),
})
.strict()
export type LineChange = z.output<typeof lineChangeSchema>
/**
* What a model is asked to return for one page: the line schema above, cut to the fields a model can
* know. The page is the question's, not the answer's (a model that cites a page may cite the wrong
* one), the position and the key are the code's, and a confidence is measured from the reader, not
* asked of the model. The JSON Schema sent to the gateway is derived from this.
*/
export const modelLineSchema = lineChangeSchema.pick({ label: true, value: true, quote: true }).strict()
export const MAX_MODEL_LINES = 200
export const modelLinesSchema = z.object({ lines: z.array(modelLineSchema).max(MAX_MODEL_LINES) }).strict()
export type ModelLines = z.output<typeof modelLinesSchema>
/** One line as the review screen shows it: the change, where it was read, and what was decided. */
export const lineViewSchema = z.object({
/** The proposal's id: it is what approve, reject and edit are called with. */
lineId: z.string(),
/** The proposal version the reviewer saw; approval pins to it to prevent approving a later edit. */
expectedVersion: z.number().int().min(1),
status: z.enum(LINE_STATUSES),
label: z.string(),
value: z.string(),
page: z.number().int(),
quote: z.string(),
box: z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() }).nullable(),
confidence: z.number(),
verification: z.enum(LINE_VERIFICATIONS),
note: z.enum(LINE_NOTES).nullable(),
/** The reader's stable id of the block the quote sits in (`p{page}-b{n}`), when the reader gave one. */
blockId: z.string().nullable(),
edited: z.boolean(),
/** The model and version that produced the line: the disclosure Art. 50 asks for sits beside it. */
model: z.string(),
promptVersion: z.string(),
decidedAt: z.string().nullable(),
})
export type LineView = z.output<typeof lineViewSchema>