// 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 /** * 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 /** 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