{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "document-source",
  "title": "Document source model",
  "description": "Viewer-agnostic model for extraction sources (the Retab /v1/extractions/{id}/sources shape): SourceAnchor union (pdf_bbox, image_bbox, spreadsheet/csv cell, docx/text span), Source, ExtractionSourcesResponse, and extractionSourcesToSourceMap to flatten the sources tree into a path-keyed SourceMap.",
  "files": [
    {
      "path": "registry/new-york-v4/lib/document-source.ts",
      "content": "// Viewer-agnostic model for \"sources\" — the provenance of an extracted value:\n// where in a source document a field came from. Mirrors the Retab\n// `GET /v1/extractions/{id}/sources` response (`GetSourcesResponse`): a value\n// tree plus a parallel `sources` tree whose leaves locate each value with a\n// format-specific anchor.\n//\n// This module has no viewer/React imports so every viewer (PDF today; image,\n// xlsx, csv, docx, text later) shares one source model. Each viewer's adapter\n// turns an `anchor` into something it can render (see e.g. the PDF adapter).\n\n// ── Anchors — discriminated union on `kind`, one per document format ──────────\n\n/** A region on one PDF page, normalized to the page box (each value in [0, 1]). */\nexport interface PdfBboxAnchor {\n  kind: \"pdf_bbox\";\n  /** 1-based page number. */\n  page: number;\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\n/** A region on an image, normalized to the image box (each value in [0, 1]). */\nexport interface ImageBboxAnchor {\n  kind: \"image_bbox\";\n  /**\n   * 1-based frame index for multi-page rasters — a multi-frame TIFF page, or a\n   * rasterized slide in a deck. Omitted (or 1) for a single-frame image.\n   */\n  page?: number;\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\n/** A cell in a CSV. */\nexport interface CsvCellAnchor {\n  kind: \"csv_cell\";\n  /** 1-based row number. */\n  row: number;\n  /** Column letter (A, B, … AA). */\n  column: string;\n  /** Cell coordinate (e.g. A12). */\n  coordinate?: string;\n}\n\n/** A cell in a spreadsheet (xlsx), on a given sheet. */\nexport interface SpreadsheetCellAnchor {\n  kind: \"spreadsheet_cell\";\n  /** 1-based row number. */\n  row: number;\n  /** Column letter (A, B, … AA). */\n  column: string;\n  coordinate?: string;\n  /** 0-based sheet index. */\n  sheet_index: number;\n  sheet_name?: string;\n}\n\n/** A character span within a docx paragraph. */\nexport interface DocxTextSpanAnchor {\n  kind: \"docx_text_span\";\n  /** 0-based paragraph index. */\n  paragraph: number;\n  char_start?: number;\n  char_end?: number;\n  /** Raw OOXML of the matched paragraph. */\n  xml?: string;\n}\n\n/** A character span within a docx table cell. */\nexport interface DocxTableCellAnchor {\n  kind: \"docx_table_cell\";\n  /** 0-based table index. */\n  table: number;\n  /** 0-based row index. */\n  row: number;\n  /** 0-based column index. */\n  column: number;\n  char_start?: number;\n  char_end?: number;\n  xml?: string;\n}\n\n/** A line/character span within plain text. */\nexport interface TextSpanAnchor {\n  kind: \"text_span\";\n  /** 1-based start line. */\n  line_start: number;\n  /** 1-based end line. */\n  line_end: number;\n  char_start?: number;\n  char_end?: number;\n}\n\nexport type SourceAnchor =\n  | PdfBboxAnchor\n  | ImageBboxAnchor\n  | CsvCellAnchor\n  | SpreadsheetCellAnchor\n  | DocxTextSpanAnchor\n  | DocxTableCellAnchor\n  | TextSpanAnchor;\n\n/** Where one extracted value came from: its quoted text + a format-specific anchor. */\nexport interface Source {\n  /** Minimal source text corresponding to the extracted value. */\n  content: string;\n  anchor: SourceAnchor;\n}\n\n// ── The `GET /v1/extractions/{id}/sources` response ───────────────────────────\n\nexport type DocumentType = \"pdf\" | \"image\" | \"csv\" | \"xlsx\" | \"docx\" | \"txt\";\n\nexport interface SourceFileRef {\n  id: string;\n  filename?: string;\n  mime_type?: string;\n}\n\n/** A leaf of the `sources` tree: the value plus the source backing it (or null). */\nexport interface SourcedLeaf {\n  value: unknown;\n  source: Source | null;\n}\n\n/** Response of `GET /v1/extractions/{id}/sources`. */\nexport interface ExtractionSourcesResponse {\n  object: \"extraction.sources\";\n  extraction_id: string;\n  document_type: DocumentType;\n  file: SourceFileRef;\n  /** Original extraction output (the value tree). */\n  extraction: Record<string, unknown>;\n  /** Same shape as `extraction`, but every leaf is a `{ value, source }`. */\n  sources: Record<string, unknown>;\n}\n\n// ── SourceMap — the flat join structure ──────────────────────────────────────\n\n/**\n * The join key between a field-rendering component and its source. Dotted path,\n * matching both Retab's field-location keys and react-hook-form's field names\n * (`owner.name`, `properties.0.gas_volume`) — so json-form field-anchor links\n * plug straight in with no conversion.\n */\nexport type SourcePath = string;\n\n/** Flat lookup from field path to its source. */\nexport type SourceMap = Record<SourcePath, Source>;\n\nconst SOURCE_ANCHOR_KINDS = new Set<string>([\n  \"pdf_bbox\",\n  \"image_bbox\",\n  \"csv_cell\",\n  \"spreadsheet_cell\",\n  \"docx_text_span\",\n  \"docx_table_cell\",\n  \"text_span\",\n]);\n\nfunction isSourcedLeaf(node: unknown): node is SourcedLeaf {\n  if (typeof node !== \"object\" || node === null) return false;\n  if (!(\"value\" in node) || !(\"source\" in node)) return false;\n  const sourceSlot = (node as Record<string, unknown>).source;\n\n  // A schema may legitimately have sibling fields named `value` and `source`.\n  // In the source tree those field values are sourced leaves themselves, while\n  // a wrapper's `source` slot is either null or a source-shaped payload.\n  return !isSourcedLeaf(sourceSlot);\n}\n\nfunction isSource(source: unknown): source is Source {\n  if (typeof source !== \"object\" || source === null) return false;\n  const record = source as Record<string, unknown>;\n  const anchor = record.anchor;\n  if (typeof record.content !== \"string\") return false;\n  if (typeof anchor !== \"object\" || anchor === null) return false;\n  return isSourceAnchor(anchor);\n}\n\nfunction isSourceAnchor(anchor: object): boolean {\n  const record = anchor as Record<string, unknown>;\n  const kind = record.kind;\n  if (typeof kind !== \"string\" || !SOURCE_ANCHOR_KINDS.has(kind)) return false;\n\n  if (kind === \"pdf_bbox\") {\n    return isPositiveInteger(record.page) && isValidNormalizedBox(record);\n  }\n  if (kind === \"image_bbox\") {\n    return (\n      (record.page == null || isPositiveInteger(record.page)) &&\n      isValidNormalizedBox(record)\n    );\n  }\n  if (kind === \"csv_cell\") {\n    return isPositiveInteger(record.row) && isColumnLetters(record.column);\n  }\n  if (kind === \"spreadsheet_cell\") {\n    return (\n      isNonNegativeInteger(record.sheet_index) &&\n      isPositiveInteger(record.row) &&\n      isColumnLetters(record.column)\n    );\n  }\n  if (kind === \"docx_text_span\") {\n    return (\n      isNonNegativeInteger(record.paragraph) &&\n      isValidOptionalRange(record.char_start, record.char_end)\n    );\n  }\n  if (kind === \"docx_table_cell\") {\n    return (\n      isNonNegativeInteger(record.table) &&\n      isNonNegativeInteger(record.row) &&\n      isNonNegativeInteger(record.column) &&\n      isValidOptionalRange(record.char_start, record.char_end)\n    );\n  }\n  if (kind === \"text_span\") {\n    return (\n      isPositiveInteger(record.line_start) &&\n      isPositiveInteger(record.line_end) &&\n      record.line_end >= record.line_start &&\n      isValidOptionalRange(record.char_start, record.char_end)\n    );\n  }\n  return false;\n}\n\nfunction isPositiveInteger(value: unknown): value is number {\n  return Number.isInteger(value) && Number(value) >= 1;\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n  return Number.isInteger(value) && Number(value) >= 0;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n  return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction isColumnLetters(value: unknown): value is string {\n  if (typeof value !== \"string\" || !/^[A-Za-z]+$/.test(value)) return false;\n  let index = 0;\n  for (const character of value.toUpperCase()) {\n    index = index * 26 + (character.charCodeAt(0) - 64);\n    if (!Number.isSafeInteger(index)) return false;\n  }\n  return true;\n}\n\nfunction isValidOptionalRange(start: unknown, end: unknown) {\n  if (start == null && end == null) return true;\n  if (start == null || end == null) return false;\n  return (\n    isNonNegativeInteger(start) && isNonNegativeInteger(end) && end >= start\n  );\n}\n\nfunction isValidNormalizedBox(record: Record<string, unknown>) {\n  if (\n    !isFiniteNumber(record.left) ||\n    !isFiniteNumber(record.top) ||\n    !isFiniteNumber(record.width) ||\n    !isFiniteNumber(record.height)\n  ) {\n    return false;\n  }\n  return (\n    record.left >= 0 &&\n    record.top >= 0 &&\n    record.width > 0 &&\n    record.height > 0 &&\n    record.left + record.width <= 1 &&\n    record.top + record.height <= 1\n  );\n}\n\n/**\n * Flatten the nested `sources` tree (from `ExtractionSourcesResponse.sources`)\n * into a flat `SourceMap` keyed by dotted path. Leaves without a source are\n * skipped. Arrays use numeric path segments (`items.0.amount`).\n */\nexport function extractionSourcesToSourceMap(sources: unknown): SourceMap {\n  const map: SourceMap = {};\n\n  const walk = (node: unknown, prefix: string) => {\n    if (isSourcedLeaf(node)) {\n      if (isSource(node.source)) map[prefix] = node.source;\n      if (node.value != null && typeof node.value === \"object\") {\n        walk(node.value, prefix);\n      }\n      return;\n    }\n    if (Array.isArray(node)) {\n      node.forEach((item, i) => walk(item, prefix ? `${prefix}.${i}` : `${i}`));\n      return;\n    }\n    if (typeof node === \"object\" && node !== null) {\n      for (const [key, value] of Object.entries(node)) {\n        walk(value, prefix ? `${prefix}.${key}` : key);\n      }\n    }\n  };\n\n  walk(sources, \"\");\n  return map;\n}\n\n// ── Viewer-ready geometry — a normalized 2D region on a page ──────────────────\n\n/** A box on a page, each field a percentage [0, 100] of the page. */\nexport interface SourceArea {\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n}\n\n/** A normalized, viewer-ready location: which page, and where on it (in %). */\nexport interface SourceLocation {\n  page: number;\n  area: SourceArea;\n}\n\n/**\n * A stable string key for a location — used to dedupe repeated hover/select\n * events that resolve to the same box before an imperative scroll.\n */\nexport function sourceLocationKey(location: SourceLocation | undefined) {\n  if (!location) return null;\n  const { area } = location;\n  return [location.page, area.left, area.top, area.width, area.height].join(\n    \":\",\n  );\n}\n",
      "type": "registry:lib",
      "target": "@lib/document-source.ts"
    }
  ],
  "type": "registry:lib"
}