import { type ExtractionEnvelopeImportDiagnostic, type ExtractionEnvelopeImportResult, type ResolvedExtractionArtifact } from "../extraction-envelope.js"; import { type PortablePdfRegionContext } from "../pdf-layout.js"; export type ExtractionAlignmentState = "aligned" | "excerpt-mismatch" | "artifact-unavailable" | "digest-mismatch"; export type { ArtifactUnavailableCode, ResolvedExtractionArtifact } from "../extraction-envelope.js"; export interface ExtractionInspectorEntry { /** Result returned by importExtractionEnvelope; includes authoritative ReviewItem identities. */ importResult: ExtractionEnvelopeImportResult; artifact: ResolvedExtractionArtifact; /** Optional producer-declared pass label, kept outside the upstream envelope. */ pass?: string; } export type ExtractionInspectorInput = ExtractionInspectorEntry | { imports: ExtractionInspectorEntry[]; }; export interface ExtractionInspectorCandidate { id: string; /** * PUBLIC CONTRACT — the element id of the source-highlight anchor that * {@link mountExtractionInspector} renders for this candidate. * * A host that wants to link a fact back to the sentence it came from uses this * value directly (`href="#" + highlightElementId`, or * `document.getElementById(highlightElementId)`); it must never re-derive an id * from {@link ExtractionInspectorCandidate.id}. The renderer reads this same * field, so the value a host reads and the id in the DOM cannot drift apart. * * Optional **here** only because this type is also the shape a caller may hand * to {@link mountExtractionInspector} directly; a model that came from * {@link buildExtractionInspectorModel} is a * {@link BuiltExtractionInspectorModel}, where it is always present. Take the * builder's type if you want to link, and the guarantees below are yours. * Mount fills in an id for any candidate that arrives without one, so a * hand-authored model still renders — it just has nothing published to link to. * * Resolvable for **every** candidate in the model for as long as the inspector * is mounted, in every posture. Anchors are deliberately exempt from the * candidate list's paging and filtering (see * {@link ExtractionInspectorMountOptions.pageSize}), and are rendered even when * the prepared artifact is unavailable, digest-mismatched, or excerpt- * mismatched — there is no highlighted span to land on then, so the anchor sits * with the non-grounded posture message that explains why. A link that dies * when a reviewer pages, filters, or opens a source that failed verification is * the same broken promise as an id that drifted. * * Guaranteed unique across every candidate in one * {@link BuiltExtractionInspectorModel}, and a valid CSS/HTML identifier. * * The reverse lookup — DOM node to candidate — is the equally public * `data-highlight-candidate-id=""` attribute on the same anchor. * * Deliberately omitted from {@link exportExtractionInspector}: the canonical * export records extraction evidence, and a DOM binding is not evidence. */ highlightElementId?: string; sourceKey: string; reviewItemName: string; proposalIndex: number; field: string; provider: string; model?: string; attempt: string; pass?: string; valueType: string; inferenceType: "explicit" | "inferred"; start: number; end: number; excerpt: string; alignment: ExtractionAlignmentState; pdfRegion?: PortablePdfRegionContext; ocrDerived?: true; } export interface ExtractionInspectorSource { key: string; importName: string; artifactRef?: string; expectedDigest?: string; actualDigest?: string; artifactText?: string; ocrDerived?: true; alignment: ExtractionAlignmentState; /** * Present when the extraction failed, or stopped short without proposing * anything: the import's `extraction-failed` / `extraction-incomplete` * diagnostic. An empty source with this set is not a run that found nothing. */ extractionDiagnostic?: Extract; /** * Whether the import checked excerpts against the prepared artifact * (`status.provenance`, required on every import record). Always set by * {@link buildExtractionInspectorModel}; optional for hand-authored models. */ importProvenance?: "verified" | "unverified"; /** * Proposals the import left out because the prepared text at their span is * not their excerpt. They have no candidate row; this is where they are shown. */ excludedProposals?: Array<{ proposalIndex: number; field: string; locator: string; }>; message: string; } export interface ExtractionInspectorModel { sources: ExtractionInspectorSource[]; candidates: ExtractionInspectorCandidate[]; } /** * A candidate from {@link buildExtractionInspectorModel}, where the DOM binding * is resolved rather than optional. * * The split exists so the linking guarantee can be unconditional without making * the *input* shape unbuildable by hand. `ExtractionInspectorModel` stays what a * caller may author and pass to mount; this is what the builder hands back, and * only the builder can promise the id is present, unique, and the one the * renderer will use. */ export interface BuiltExtractionInspectorCandidate extends ExtractionInspectorCandidate { highlightElementId: string; } /** The model {@link buildExtractionInspectorModel} returns. Assignable anywhere an {@link ExtractionInspectorModel} is accepted. */ export interface BuiltExtractionInspectorModel extends ExtractionInspectorModel { candidates: BuiltExtractionInspectorCandidate[]; } export interface ExtractionInspectorFilters { field?: string; provider?: string; model?: string; attempt?: string; pass?: string; inferenceType?: "explicit" | "inferred"; alignment?: ExtractionAlignmentState; query?: string; } export interface ExtractionInspectorExportOptions { includePreparedText?: boolean; includeExcerpts?: boolean; } export interface ExtractionInspectorMountOptions { /** * Maximum candidate rows and painted source highlights mounted at once. * Defaults to 100 and is capped at 500. * * This governs the candidate list and the `` painting only. The * highlight *anchors* — the elements * {@link ExtractionInspectorCandidate.highlightElementId} names — are mounted * for every candidate in the model regardless of page or filter, because a * host's link to a span must not go dead when a reviewer types in the filter * box. Anchors are empty and inert; the per-candidate cost is one element. */ pageSize?: number; } /** * Build a read-only view from one or more results already produced by Survey's * public import boundary. The function rechecks the result/ReviewItem binding * and fails closed if mutable caller data has drifted since import. */ export declare function buildExtractionInspectorModel(input: ExtractionInspectorInput): BuiltExtractionInspectorModel; /** * The posture a source is shown with: the extraction's own failure or early * stop when there is one, otherwise the artifact alignment. A failed * extraction over an aligned artifact must not show the aligned posture. */ export declare function inspectorSourcePosture(source: Pick): ExtractionAlignmentState | "extraction-failed" | "extraction-incomplete" | "proposals-excluded"; export declare function filterExtractionInspectorCandidates(model: ExtractionInspectorModel, filters: ExtractionInspectorFilters): ExtractionInspectorCandidate[]; export declare function exportExtractionInspector(model: ExtractionInspectorModel, options?: ExtractionInspectorExportOptions): string; export declare function mountExtractionInspector(container: HTMLElement, model: ExtractionInspectorModel, options?: ExtractionInspectorMountOptions): () => void;