{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "split-consensus-lukas-viewer-block",
  "title": "Split Consensus Lukas",
  "description": "The split benchmark diagram from Lukas Bjorkland's article, with GT, Split v1, Split v2, the PDF pane, and the same dark metrics table treatment.",
  "registryDependencies": [
    "@retab/file-viewer",
    "@retab/pdf-viewer",
    "@retab/use-keyed-layout-effect",
    "@retab/effect-key"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/blocks/split-consensus-lukas-viewer-block.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  FileViewer,\n  FileViewerContent,\n  FileViewerControls,\n  FileViewerHeader,\n  FileViewerInset,\n  FileViewerProvider,\n  FileViewerSidebar,\n  FileViewerSidebarContent,\n  FileViewerSidebarTrigger,\n  FileViewerTitle,\n  FileViewerViewport,\n} from \"@/components/ui/file-viewer\";\nimport {\n  PdfViewerPages,\n  PdfViewerProvider,\n  type PdfViewerHandle,\n} from \"@/components/ui/pdf-viewer\";\nimport {\n  SHOWCASE_DOC,\n  type ShowcaseSeg,\n} from \"@/components/blocks/split-consensus-lukas-data\";\n\nconst POLITAX_ASSET_BASE_URL =\n  \"https://storage.googleapis.com/retab-public-assets/politaxsplit\";\n\nconst DOC = SHOWCASE_DOC;\n\nconst SOURCE = {\n  kind: \"url\" as const,\n  downloadUrl: remotePdfUrl(DOC.document),\n  fileName: DOC.document,\n  url: proxiedPdfUrl(DOC.document),\n};\n\n// Vertical strip layout copied from the Lukas Bjorkland split benchmark post.\n// Three columns (GT, Split v1, Split v2). Pages run top to bottom. The SVG is\n// sized to its measured container so the bars fill the full sidebar height and\n// width without distorting the labels.\nconst DEFAULT_W = 320;\nconst DEFAULT_H = 560;\nconst PAD = { top: 44, right: 10, bottom: 28, left: 48 };\nconst NUM_COLS = 3;\nconst COL_GAP = 12;\n\nconst TYPE_COLORS: Record<string, string> = {\n  \"Form 1040\": \"#4e79a7\",\n  \"Schedule 1 (Form 1040)\": \"#f28e2b\",\n  \"Schedule 2 (Form 1040)\": \"#e15759\",\n  \"Schedule 3 (Form 1040)\": \"#ff9da7\",\n  \"Schedule 4 (Form 1040)\": \"#86bcb6\",\n  \"Schedule 5 (Form 1040)\": \"#bab0ac\",\n  \"Form 2210\": \"#9c755f\",\n  \"Schedule A (Form 1040)\": \"#76b7b2\",\n  \"Schedule B (Form 1040)\": \"#59a14f\",\n  \"Schedule C (Form 1040)\": \"#edc948\",\n  \"Schedule D (Form 1040)\": \"#b07aa1\",\n  \"Schedule E (Form 1040)\": \"#fabfd2\",\n  \"Schedule H (Form 1040)\": \"#8cd17d\",\n  \"Schedule SE (Form 1040)\": \"#d4a6c8\",\n  \"Schedule K-1 (Form 1065)\": \"#ff7f50\",\n  \"Schedule K-1 (Form 1120-S)\": \"#ffb347\",\n  \"Schedule K (Form 1065)\": \"#c49c94\",\n  \"Schedule L (Form 1065)\": \"#dbdb8d\",\n  \"Schedule M-2 (Form 1065)\": \"#8d6e63\",\n  \"Schedule M-3 (Form 1120/1065)\": \"#9edae5\",\n  \"Form 1065\": \"#17becf\",\n  \"Form 1120-S\": \"#aec7e8\",\n  \"Form 4562\": \"#ffbb78\",\n  \"Form 4136\": \"#98df8a\",\n  \"Form 4797\": \"#f7b6d2\",\n  \"Form 5471\": \"#c5b0d5\",\n  \"Form 6251\": \"#a0cbe8\",\n  \"Form 709\": \"#c68a89\",\n  \"Form 8283\": \"#e7cb94\",\n  \"Form 8582\": \"#e7ba52\",\n  \"Form 8858\": \"#a65628\",\n  \"Form 8865\": \"#ff9896\",\n  \"Form 8886\": \"#bd7ebe\",\n  \"Form 8938\": \"#c7c7c7\",\n  \"Form 8949\": \"#9467bd\",\n  \"Form 8960\": \"#d7b5a6\",\n  \"Form IL-1040\": \"#bcbd22\",\n  \"Form IL-2210\": \"#2ca02c\",\n  misc_form: \"#aab4bd\",\n  supplement: \"#499894\",\n  other: \"#6b7280\",\n};\n\ntype Col = {\n  key: string;\n  label: string;\n  labelClassName: string;\n  segs: ShowcaseSeg[];\n};\n\ntype Hovered = {\n  colKey: string;\n  seg: ShowcaseSeg;\n  cx: number;\n  cy: number;\n} | null;\n\nexport function SplitConsensusLukasViewerBlock() {\n  const [currentPage, setCurrentPage] = React.useState(1);\n  const pdfRef = React.useRef<PdfViewerHandle | null>(null);\n\n  const jumpToPage = React.useCallback((page: number) => {\n    setCurrentPage(page);\n    pdfRef.current?.scrollToPage(page, { behavior: \"smooth\" });\n  }, []);\n\n  return (\n    <div className=\"bg-background flex h-full min-h-[760px] flex-col\">\n      <FileViewerProvider source={SOURCE} defaultSidebarOpen>\n        <FileViewer className=\"bg-background\">\n          <PdfViewerProvider>\n            <FileViewerHeader>\n              <FileViewerSidebarTrigger className=\"-ms-1\" />\n              <FileViewerTitle />\n              <FileViewerControls />\n            </FileViewerHeader>\n            <FileViewerContent>\n              <SplitConsensusSidebar\n                currentPage={currentPage}\n                onJump={jumpToPage}\n              />\n              <FileViewerInset>\n                <FileViewerViewport>\n                  <PdfViewerPages\n                    ref={pdfRef}\n                    bare\n                    className=\"h-full\"\n                    onVisiblePageChange={(page) => setCurrentPage(page)}\n                  />\n                </FileViewerViewport>\n              </FileViewerInset>\n            </FileViewerContent>\n          </PdfViewerProvider>\n        </FileViewer>\n      </FileViewerProvider>\n    </div>\n  );\n}\n\nfunction SplitConsensusSidebar({\n  currentPage,\n  onJump,\n}: {\n  currentPage: number;\n  onJump: (page: number) => void;\n}) {\n  return (\n    <FileViewerSidebar\n      aria-label=\"Split consensus segments\"\n      width=\"23rem\"\n      className=\"border-r\"\n    >\n      <FileViewerSidebarContent>\n        <SegmentStripChart currentPage={currentPage} onJump={onJump} />\n      </FileViewerSidebarContent>\n    </FileViewerSidebar>\n  );\n}\n\nfunction SegmentStripChart({\n  currentPage,\n  onJump,\n}: {\n  currentPage: number;\n  onJump: (page: number) => void;\n}) {\n  const [hovered, setHovered] = React.useState<Hovered>(null);\n  const [ref, size] = useElementSize<HTMLDivElement>();\n\n  const W = size.width || DEFAULT_W;\n  const H = size.height || DEFAULT_H;\n  const innerW = Math.max(W - PAD.left - PAD.right, 1);\n  const innerH = Math.max(H - PAD.top - PAD.bottom, 1);\n  const colW = Math.max((innerW - COL_GAP * (NUM_COLS - 1)) / NUM_COLS, 1);\n\n  const cols: Col[] = [\n    {\n      key: \"gt\",\n      label: \"Ground truth\",\n      labelClassName: \"fill-foreground font-semibold\",\n      segs: DOC.ground_truth,\n    },\n    {\n      key: \"v1\",\n      label: \"Split v1\",\n      labelClassName: \"fill-muted-foreground\",\n      segs: DOC.v1,\n    },\n    {\n      key: \"v2\",\n      label: \"Split v2\",\n      labelClassName: \"fill-emerald-500\",\n      segs: DOC.v2,\n    },\n  ];\n\n  const pages = DOC.page_count;\n  const colX = (index: number) => PAD.left + index * (colW + COL_GAP);\n  const yFor = (page: number) => PAD.top + ((page - 1) / pages) * innerH;\n  const hFor = (start: number, end: number) =>\n    Math.max(((end - start + 1) / pages) * innerH, 1.4);\n\n  const tickStep = Math.max(10, Math.round(pages / 10 / 5) * 5);\n  const ticks: number[] = [1];\n  for (let page = tickStep; page <= pages; page += tickStep) ticks.push(page);\n  if (ticks[ticks.length - 1] !== pages) ticks.push(pages);\n\n  const cursorY = yFor(Math.max(1, Math.min(pages + 1, currentPage)));\n\n  return (\n    <div\n      ref={ref}\n      className=\"flex min-h-0 w-full min-w-0 flex-1 flex-col overflow-hidden px-3 py-3\"\n    >\n      <svg\n        viewBox={`0 0 ${W} ${H}`}\n        width={W}\n        height={H}\n        className=\"text-muted-foreground block\"\n        aria-label={`Ground truth vs Split v1 vs Split v2 - ${DOC.pretty_name}`}\n      >\n        {cols.map((col, index) => (\n          <g key={`hdr-${col.key}`}>\n            <text\n              x={colX(index) + colW / 2}\n              y={PAD.top - 20}\n              textAnchor=\"middle\"\n              fontFamily=\"ui-monospace,monospace\"\n              fontSize={11}\n              className={col.labelClassName}\n            >\n              {col.label}\n            </text>\n            <text\n              x={colX(index) + colW / 2}\n              y={PAD.top - 7}\n              textAnchor=\"middle\"\n              fontFamily=\"ui-monospace,monospace\"\n              fontSize={9}\n              className=\"fill-muted-foreground\"\n            >\n              {col.segs.length} segs\n            </text>\n          </g>\n        ))}\n\n        {cols.map((col, index) => {\n          const cx = colX(index);\n          return (\n            <g key={col.key}>\n              <rect\n                x={cx}\n                y={PAD.top}\n                width={colW}\n                height={innerH}\n                rx={3}\n                className=\"fill-muted\"\n              />\n              {col.segs.map((seg, segmentIndex) => {\n                const sy = yFor(seg.s);\n                const sh = hFor(seg.s, seg.e);\n                const isHovered =\n                  hovered?.colKey === col.key && hovered.seg === seg;\n                const isActive =\n                  currentPage >= seg.s && currentPage < seg.e + 1;\n                return (\n                  <rect\n                    key={segmentIndex}\n                    x={cx + 1}\n                    y={sy}\n                    width={colW - 2}\n                    height={sh}\n                    fill={colorForType(seg.t)}\n                    className={cn(\n                      isHovered\n                        ? \"stroke-foreground\"\n                        : isActive\n                          ? \"stroke-foreground\"\n                          : \"stroke-background\",\n                    )}\n                    strokeWidth={isHovered ? 1.5 : isActive ? 1.5 : 0.4}\n                    opacity={\n                      hovered ? (isHovered ? 1 : 0.5) : isActive ? 1 : 0.85\n                    }\n                    rx={1}\n                    style={{ cursor: \"pointer\" }}\n                    onClick={() => onJump(seg.s)}\n                    onMouseEnter={() =>\n                      setHovered({\n                        colKey: col.key,\n                        seg,\n                        cx: cx + colW / 2,\n                        cy: sy + sh / 2,\n                      })\n                    }\n                    onMouseLeave={() => setHovered(null)}\n                  />\n                );\n              })}\n            </g>\n          );\n        })}\n\n        {ticks.map((page) => (\n          <g key={page}>\n            <line\n              x1={PAD.left - 5}\n              y1={yFor(page)}\n              x2={PAD.left - 2}\n              y2={yFor(page)}\n              className=\"stroke-muted-foreground\"\n              strokeWidth={0.75}\n            />\n            <text\n              x={PAD.left - 8}\n              y={yFor(page) + 3}\n              textAnchor=\"end\"\n              fontFamily=\"ui-monospace,monospace\"\n              fontSize={9}\n              className=\"fill-muted-foreground\"\n            >\n              {page}\n            </text>\n          </g>\n        ))}\n\n        <text\n          x={12}\n          y={PAD.top + innerH / 2}\n          textAnchor=\"middle\"\n          fontFamily=\"ui-monospace,monospace\"\n          fontSize={9}\n          className=\"fill-muted-foreground uppercase\"\n          transform={`rotate(-90, 12, ${PAD.top + innerH / 2})`}\n        >\n          page\n        </text>\n\n        <line\n          x1={PAD.left - 2}\n          y1={cursorY}\n          x2={PAD.left + innerW + 2}\n          y2={cursorY}\n          className=\"stroke-foreground\"\n          strokeWidth={1.25}\n          pointerEvents=\"none\"\n        />\n        <polygon\n          points={`${PAD.left - 8},${cursorY - 4} ${PAD.left - 2},${cursorY} ${PAD.left - 8},${cursorY + 4}`}\n          className=\"fill-foreground\"\n          pointerEvents=\"none\"\n        />\n\n        {hovered ? <HoverTooltip hovered={hovered} chartWidth={W} /> : null}\n      </svg>\n    </div>\n  );\n}\n\nfunction HoverTooltip({\n  hovered,\n  chartWidth,\n}: {\n  hovered: NonNullable<Hovered>;\n  chartWidth: number;\n}) {\n  const { seg } = hovered;\n  const label = seg.t;\n  const range =\n    seg.s === seg.e\n      ? `page ${seg.s}`\n      : `pages ${seg.s}-${seg.e} (${seg.e - seg.s + 1})`;\n  const textW = Math.max(label.length, range.length) * 6 + 28;\n  let tx = hovered.cx - textW / 2;\n  if (tx < PAD.left) tx = PAD.left;\n  if (tx + textW > chartWidth - 4) tx = chartWidth - 4 - textW;\n  const showBelow = hovered.cy < PAD.top + 40;\n  const ty = showBelow ? hovered.cy + 8 : hovered.cy - 40;\n  const color = colorForType(seg.t);\n\n  return (\n    <g pointerEvents=\"none\">\n      <rect\n        x={tx}\n        y={ty}\n        width={textW}\n        height={32}\n        rx={4}\n        className=\"fill-popover stroke-border\"\n        strokeWidth={1}\n      />\n      <circle cx={tx + 12} cy={ty + 10} r={4} fill={color} />\n      <text\n        x={tx + 20}\n        y={ty + 13}\n        fontFamily=\"ui-monospace,monospace\"\n        fontSize={10}\n        fill={color}\n        fontWeight=\"bold\"\n      >\n        {label}\n      </text>\n      <text\n        x={tx + 20}\n        y={ty + 25}\n        fontFamily=\"ui-monospace,monospace\"\n        fontSize={9}\n        className=\"fill-muted-foreground\"\n      >\n        {range}\n      </text>\n    </g>\n  );\n}\n\nfunction useElementSize<T extends HTMLElement>() {\n  const [element, setElement] = React.useState<T | null>(null);\n  const [size, setSize] = React.useState({ width: 0, height: 0 });\n\n  useKeyedLayoutEffect(element ? joinEffectKey([element]) : null, () => {\n    if (!element) return;\n\n    const observer = new ResizeObserver((entries) => {\n      const box = entries[0]?.contentRect;\n      if (!box) return;\n      setSize((prev) =>\n        prev.width === box.width && prev.height === box.height\n          ? prev\n          : { width: box.width, height: box.height },\n      );\n    });\n    observer.observe(element);\n    return () => observer.disconnect();\n  });\n\n  return [setElement, size] as const;\n}\n\nfunction remotePdfUrl(document: string): string {\n  const prefix = document.split(\"_\")[0]?.toLowerCase();\n  return `${POLITAX_ASSET_BASE_URL}/pdfs/${encodeURIComponent(prefix)}/${encodeURIComponent(document)}`;\n}\n\nfunction proxiedPdfUrl(document: string): string {\n  const prefix = document.split(\"_\")[0]?.toLowerCase();\n  return `/api/politax-pdf/${encodeURIComponent(prefix)}/${encodeURIComponent(document)}`;\n}\n\nfunction colorForType(type: string): string {\n  return TYPE_COLORS[type] ?? \"#a1a1aa\";\n}\n",
      "type": "registry:component",
      "target": "@components/blocks/split-consensus-lukas-viewer-block.tsx"
    },
    {
      "path": "registry/new-york-v4/blocks/split-consensus-lukas-data.ts",
      "content": "// One sample PoliTax-Split document: GT / Split v1 / Split v2 segments\n// plus per-pipeline Pages Labelled Correctly (seg_accuracy) and Total F1 (lenient).\n// Source row extracted from handmade-tests/benchmark/gen_showcase_data.py output.\n\nexport type ShowcaseSeg = { t: string; s: number; e: number };\n\nexport type ShowcaseDoc = {\n  document: string;\n  pretty_name: string;\n  page_count: number;\n  ground_truth: ShowcaseSeg[];\n  v1: ShowcaseSeg[];\n  v2: ShowcaseSeg[];\n  v1_seg_accuracy: number;\n  v1_lenient_f1: number;\n  v2_seg_accuracy: number;\n  v2_lenient_f1: number;\n};\n\nexport const SHOWCASE_DOC: ShowcaseDoc = {\n    document: \"harris_2020_federal_state_returns.pdf\",\n    pretty_name: \"Harris 2020 \\u2014 Federal & State Returns\",\n    page_count: 100,\n    ground_truth: [\n      {\n        t: \"Form 1040\",\n        s: 1,\n        e: 2,\n      },\n      {\n        t: \"Schedule 1 (Form 1040)\",\n        s: 3,\n        e: 3,\n      },\n      {\n        t: \"Schedule 2 (Form 1040)\",\n        s: 4,\n        e: 4,\n      },\n      {\n        t: \"Schedule 3 (Form 1040)\",\n        s: 5,\n        e: 5,\n      },\n      {\n        t: \"Form 2210\",\n        s: 6,\n        e: 8,\n      },\n      {\n        t: \"Schedule A (Form 1040)\",\n        s: 9,\n        e: 9,\n      },\n      {\n        t: \"Schedule B (Form 1040)\",\n        s: 10,\n        e: 10,\n      },\n      {\n        t: \"Schedule C (Form 1040)\",\n        s: 11,\n        e: 11,\n      },\n      {\n        t: \"Schedule D (Form 1040)\",\n        s: 12,\n        e: 13,\n      },\n      {\n        t: \"Schedule E (Form 1040)\",\n        s: 14,\n        e: 14,\n      },\n      {\n        t: \"Schedule SE (Form 1040)\",\n        s: 15,\n        e: 16,\n      },\n      {\n        t: \"Schedule SE (Form 1040)\",\n        s: 17,\n        e: 18,\n      },\n      {\n        t: \"misc_form\",\n        s: 19,\n        e: 20,\n      },\n      {\n        t: \"misc_form\",\n        s: 21,\n        e: 23,\n      },\n      {\n        t: \"Form 6251\",\n        s: 24,\n        e: 25,\n      },\n      {\n        t: \"misc_form\",\n        s: 26,\n        e: 27,\n      },\n      {\n        t: \"Schedule H (Form 1040)\",\n        s: 28,\n        e: 29,\n      },\n      {\n        t: \"misc_form\",\n        s: 30,\n        e: 30,\n      },\n      {\n        t: \"misc_form\",\n        s: 31,\n        e: 31,\n      },\n      {\n        t: \"Form 8960\",\n        s: 32,\n        e: 33,\n      },\n      {\n        t: \"supplement\",\n        s: 34,\n        e: 41,\n      },\n      {\n        t: \"misc_form\",\n        s: 42,\n        e: 46,\n      },\n      {\n        t: \"misc_form\",\n        s: 47,\n        e: 47,\n      },\n      {\n        t: \"misc_form\",\n        s: 48,\n        e: 50,\n      },\n      {\n        t: \"supplement\",\n        s: 51,\n        e: 51,\n      },\n      {\n        t: \"misc_form\",\n        s: 52,\n        e: 53,\n      },\n      {\n        t: \"misc_form\",\n        s: 54,\n        e: 54,\n      },\n      {\n        t: \"misc_form\",\n        s: 55,\n        e: 55,\n      },\n      {\n        t: \"misc_form\",\n        s: 56,\n        e: 56,\n      },\n      {\n        t: \"misc_form\",\n        s: 57,\n        e: 57,\n      },\n      {\n        t: \"misc_form\",\n        s: 58,\n        e: 58,\n      },\n      {\n        t: \"misc_form\",\n        s: 59,\n        e: 59,\n      },\n      {\n        t: \"misc_form\",\n        s: 60,\n        e: 60,\n      },\n      {\n        t: \"misc_form\",\n        s: 61,\n        e: 61,\n      },\n      {\n        t: \"misc_form\",\n        s: 62,\n        e: 62,\n      },\n      {\n        t: \"misc_form\",\n        s: 63,\n        e: 63,\n      },\n      {\n        t: \"misc_form\",\n        s: 64,\n        e: 64,\n      },\n      {\n        t: \"misc_form\",\n        s: 65,\n        e: 65,\n      },\n      {\n        t: \"misc_form\",\n        s: 66,\n        e: 66,\n      },\n      {\n        t: \"misc_form\",\n        s: 67,\n        e: 67,\n      },\n      {\n        t: \"misc_form\",\n        s: 68,\n        e: 68,\n      },\n      {\n        t: \"misc_form\",\n        s: 69,\n        e: 69,\n      },\n      {\n        t: \"misc_form\",\n        s: 70,\n        e: 70,\n      },\n      {\n        t: \"misc_form\",\n        s: 71,\n        e: 71,\n      },\n      {\n        t: \"misc_form\",\n        s: 72,\n        e: 72,\n      },\n      {\n        t: \"misc_form\",\n        s: 73,\n        e: 73,\n      },\n      {\n        t: \"misc_form\",\n        s: 74,\n        e: 75,\n      },\n      {\n        t: \"misc_form\",\n        s: 76,\n        e: 77,\n      },\n      {\n        t: \"supplement\",\n        s: 78,\n        e: 78,\n      },\n      {\n        t: \"misc_form\",\n        s: 79,\n        e: 83,\n      },\n      {\n        t: \"supplement\",\n        s: 84,\n        e: 84,\n      },\n      {\n        t: \"supplement\",\n        s: 85,\n        e: 85,\n      },\n      {\n        t: \"supplement\",\n        s: 86,\n        e: 87,\n      },\n      {\n        t: \"misc_form\",\n        s: 88,\n        e: 90,\n      },\n      {\n        t: \"misc_form\",\n        s: 91,\n        e: 91,\n      },\n      {\n        t: \"supplement\",\n        s: 92,\n        e: 92,\n      },\n      {\n        t: \"supplement\",\n        s: 93,\n        e: 97,\n      },\n      {\n        t: \"Schedule D (Form 1040)\",\n        s: 98,\n        e: 99,\n      },\n      {\n        t: \"supplement\",\n        s: 100,\n        e: 100,\n      },\n    ],\n    v1: [\n      {\n        t: \"Form 1040\",\n        s: 1,\n        e: 2,\n      },\n      {\n        t: \"Schedule 1 (Form 1040)\",\n        s: 3,\n        e: 3,\n      },\n      {\n        t: \"Schedule 2 (Form 1040)\",\n        s: 4,\n        e: 4,\n      },\n      {\n        t: \"Schedule 3 (Form 1040)\",\n        s: 5,\n        e: 5,\n      },\n      {\n        t: \"Form 2210\",\n        s: 6,\n        e: 8,\n      },\n      {\n        t: \"Schedule A (Form 1040)\",\n        s: 9,\n        e: 9,\n      },\n      {\n        t: \"Schedule B (Form 1040)\",\n        s: 10,\n        e: 10,\n      },\n      {\n        t: \"Schedule C (Form 1040)\",\n        s: 11,\n        e: 11,\n      },\n      {\n        t: \"Schedule D (Form 1040)\",\n        s: 12,\n        e: 13,\n      },\n      {\n        t: \"Schedule E (Form 1040)\",\n        s: 14,\n        e: 14,\n      },\n      {\n        t: \"Schedule SE (Form 1040)\",\n        s: 15,\n        e: 18,\n      },\n      {\n        t: \"misc_form\",\n        s: 19,\n        e: 20,\n      },\n      {\n        t: \"misc_form\",\n        s: 21,\n        e: 23,\n      },\n      {\n        t: \"Form 6251\",\n        s: 24,\n        e: 25,\n      },\n      {\n        t: \"misc_form\",\n        s: 26,\n        e: 27,\n      },\n      {\n        t: \"Schedule H (Form 1040)\",\n        s: 28,\n        e: 29,\n      },\n      {\n        t: \"misc_form\",\n        s: 30,\n        e: 30,\n      },\n      {\n        t: \"misc_form\",\n        s: 31,\n        e: 31,\n      },\n      {\n        t: \"Form 8960\",\n        s: 32,\n        e: 33,\n      },\n      {\n        t: \"supplement\",\n        s: 34,\n        e: 34,\n      },\n      {\n        t: \"supplement\",\n        s: 35,\n        e: 35,\n      },\n      {\n        t: \"supplement\",\n        s: 35,\n        e: 35,\n      },\n      {\n        t: \"supplement\",\n        s: 35,\n        e: 35,\n      },\n      {\n        t: \"supplement\",\n        s: 35,\n        e: 35,\n      },\n      {\n        t: \"supplement\",\n        s: 36,\n        e: 36,\n      },\n      {\n        t: \"supplement\",\n        s: 37,\n        e: 37,\n      },\n      {\n        t: \"supplement\",\n        s: 38,\n        e: 38,\n      },\n      {\n        t: \"supplement\",\n        s: 39,\n        e: 39,\n      },\n      {\n        t: \"supplement\",\n        s: 40,\n        e: 40,\n      },\n      {\n        t: \"supplement\",\n        s: 41,\n        e: 41,\n      },\n      {\n        t: \"supplement\",\n        s: 41,\n        e: 41,\n      },\n      {\n        t: \"misc_form\",\n        s: 42,\n        e: 46,\n      },\n      {\n        t: \"misc_form\",\n        s: 47,\n        e: 47,\n      },\n      {\n        t: \"misc_form\",\n        s: 48,\n        e: 50,\n      },\n      {\n        t: \"supplement\",\n        s: 51,\n        e: 51,\n      },\n      {\n        t: \"misc_form\",\n        s: 52,\n        e: 53,\n      },\n      {\n        t: \"misc_form\",\n        s: 54,\n        e: 73,\n      },\n      {\n        t: \"misc_form\",\n        s: 74,\n        e: 77,\n      },\n      {\n        t: \"supplement\",\n        s: 78,\n        e: 78,\n      },\n      {\n        t: \"misc_form\",\n        s: 79,\n        e: 83,\n      },\n      {\n        t: \"supplement\",\n        s: 84,\n        e: 87,\n      },\n      {\n        t: \"misc_form\",\n        s: 88,\n        e: 91,\n      },\n      {\n        t: \"misc_form\",\n        s: 89,\n        e: 91,\n      },\n      {\n        t: \"misc_form\",\n        s: 91,\n        e: 91,\n      },\n      {\n        t: \"supplement\",\n        s: 92,\n        e: 92,\n      },\n      {\n        t: \"supplement\",\n        s: 93,\n        e: 97,\n      },\n      {\n        t: \"Schedule D (Form 1040)\",\n        s: 98,\n        e: 99,\n      },\n      {\n        t: \"supplement\",\n        s: 100,\n        e: 100,\n      },\n      {\n        t: \"supplement\",\n        s: 100,\n        e: 100,\n      },\n    ],\n    v2: [\n      {\n        t: \"Form 1040\",\n        s: 1,\n        e: 2,\n      },\n      {\n        t: \"Schedule 1 (Form 1040)\",\n        s: 3,\n        e: 3,\n      },\n      {\n        t: \"Schedule 2 (Form 1040)\",\n        s: 4,\n        e: 4,\n      },\n      {\n        t: \"Schedule 3 (Form 1040)\",\n        s: 5,\n        e: 5,\n      },\n      {\n        t: \"Form 2210\",\n        s: 6,\n        e: 8,\n      },\n      {\n        t: \"Schedule A (Form 1040)\",\n        s: 9,\n        e: 9,\n      },\n      {\n        t: \"Schedule B (Form 1040)\",\n        s: 10,\n        e: 10,\n      },\n      {\n        t: \"Schedule C (Form 1040)\",\n        s: 11,\n        e: 11,\n      },\n      {\n        t: \"Schedule D (Form 1040)\",\n        s: 12,\n        e: 13,\n      },\n      {\n        t: \"Schedule E (Form 1040)\",\n        s: 14,\n        e: 14,\n      },\n      {\n        t: \"Schedule SE (Form 1040)\",\n        s: 15,\n        e: 16,\n      },\n      {\n        t: \"Schedule SE (Form 1040)\",\n        s: 17,\n        e: 18,\n      },\n      {\n        t: \"misc_form\",\n        s: 19,\n        e: 20,\n      },\n      {\n        t: \"misc_form\",\n        s: 21,\n        e: 23,\n      },\n      {\n        t: \"Form 6251\",\n        s: 24,\n        e: 25,\n      },\n      {\n        t: \"misc_form\",\n        s: 26,\n        e: 27,\n      },\n      {\n        t: \"Schedule H (Form 1040)\",\n        s: 28,\n        e: 29,\n      },\n      {\n        t: \"misc_form\",\n        s: 30,\n        e: 30,\n      },\n      {\n        t: \"misc_form\",\n        s: 31,\n        e: 31,\n      },\n      {\n        t: \"Form 8960\",\n        s: 32,\n        e: 32,\n      },\n      {\n        t: \"Form 8960\",\n        s: 33,\n        e: 33,\n      },\n      {\n        t: \"supplement\",\n        s: 34,\n        e: 41,\n      },\n      {\n        t: \"misc_form\",\n        s: 42,\n        e: 46,\n      },\n      {\n        t: \"misc_form\",\n        s: 47,\n        e: 47,\n      },\n      {\n        t: \"misc_form\",\n        s: 48,\n        e: 50,\n      },\n      {\n        t: \"supplement\",\n        s: 51,\n        e: 51,\n      },\n      {\n        t: \"misc_form\",\n        s: 52,\n        e: 53,\n      },\n      {\n        t: \"misc_form\",\n        s: 54,\n        e: 54,\n      },\n      {\n        t: \"misc_form\",\n        s: 55,\n        e: 55,\n      },\n      {\n        t: \"misc_form\",\n        s: 56,\n        e: 56,\n      },\n      {\n        t: \"misc_form\",\n        s: 57,\n        e: 57,\n      },\n      {\n        t: \"misc_form\",\n        s: 58,\n        e: 58,\n      },\n      {\n        t: \"misc_form\",\n        s: 59,\n        e: 59,\n      },\n      {\n        t: \"misc_form\",\n        s: 60,\n        e: 60,\n      },\n      {\n        t: \"misc_form\",\n        s: 61,\n        e: 61,\n      },\n      {\n        t: \"misc_form\",\n        s: 62,\n        e: 62,\n      },\n      {\n        t: \"misc_form\",\n        s: 63,\n        e: 63,\n      },\n      {\n        t: \"misc_form\",\n        s: 64,\n        e: 64,\n      },\n      {\n        t: \"misc_form\",\n        s: 65,\n        e: 65,\n      },\n      {\n        t: \"misc_form\",\n        s: 66,\n        e: 66,\n      },\n      {\n        t: \"misc_form\",\n        s: 67,\n        e: 67,\n      },\n      {\n        t: \"misc_form\",\n        s: 68,\n        e: 68,\n      },\n      {\n        t: \"misc_form\",\n        s: 69,\n        e: 69,\n      },\n      {\n        t: \"misc_form\",\n        s: 70,\n        e: 70,\n      },\n      {\n        t: \"misc_form\",\n        s: 71,\n        e: 71,\n      },\n      {\n        t: \"misc_form\",\n        s: 72,\n        e: 72,\n      },\n      {\n        t: \"misc_form\",\n        s: 73,\n        e: 73,\n      },\n      {\n        t: \"misc_form\",\n        s: 74,\n        e: 75,\n      },\n      {\n        t: \"misc_form\",\n        s: 76,\n        e: 77,\n      },\n      {\n        t: \"supplement\",\n        s: 78,\n        e: 78,\n      },\n      {\n        t: \"misc_form\",\n        s: 79,\n        e: 83,\n      },\n      {\n        t: \"supplement\",\n        s: 84,\n        e: 87,\n      },\n      {\n        t: \"misc_form\",\n        s: 88,\n        e: 90,\n      },\n      {\n        t: \"misc_form\",\n        s: 91,\n        e: 91,\n      },\n      {\n        t: \"supplement\",\n        s: 92,\n        e: 97,\n      },\n      {\n        t: \"Schedule D (Form 1040)\",\n        s: 98,\n        e: 99,\n      },\n      {\n        t: \"supplement\",\n        s: 100,\n        e: 100,\n      },\n    ],\n    v1_seg_accuracy: 1.0,\n    v1_lenient_f1: 0.6296,\n    v2_seg_accuracy: 1.0,\n    v2_lenient_f1: 0.9655,\n  };\n",
      "type": "registry:component",
      "target": "@components/blocks/split-consensus-lukas-data.ts"
    },
    {
      "path": "app/api/politax-pdf/[prefix]/[document]/route.ts",
      "content": "import { type NextRequest } from \"next/server\";\n\nconst POLITAX_ASSET_BASE_URL =\n  \"https://storage.googleapis.com/retab-public-assets/politaxsplit\";\nconst SAFE_PREFIX_RE = /^[a-z0-9-]+$/;\nconst SAFE_DOCUMENT_RE = /^[A-Za-z0-9_.-]+\\.pdf$/;\n\nexport const dynamic = \"force-dynamic\";\n\nexport async function GET(\n  request: NextRequest,\n  { params }: { params: Promise<{ document: string; prefix: string }> },\n) {\n  return proxyPolitaxPdf(request, await params);\n}\n\nexport async function HEAD(\n  request: NextRequest,\n  { params }: { params: Promise<{ document: string; prefix: string }> },\n) {\n  return proxyPolitaxPdf(request, await params, { method: \"HEAD\" });\n}\n\nasync function proxyPolitaxPdf(\n  request: NextRequest,\n  params: { document: string; prefix: string },\n  options: { method?: \"GET\" | \"HEAD\" } = {},\n) {\n  const { document, prefix } = params;\n  if (!SAFE_PREFIX_RE.test(prefix) || !SAFE_DOCUMENT_RE.test(document)) {\n    return Response.json({ error: \"Invalid PDF path.\" }, { status: 400 });\n  }\n\n  const upstreamUrl = `${POLITAX_ASSET_BASE_URL}/pdfs/${encodeURIComponent(prefix)}/${encodeURIComponent(document)}`;\n  const range = request.headers.get(\"range\");\n  const upstream = await fetch(upstreamUrl, {\n    headers: range ? { range } : undefined,\n    method: options.method ?? \"GET\",\n  });\n\n  if (!upstream.ok && upstream.status !== 206) {\n    return Response.json(\n      { error: `Failed to load PDF: ${upstream.status}` },\n      { status: upstream.status },\n    );\n  }\n\n  const headers = proxiedPdfHeaders(upstream.headers);\n  return new Response(options.method === \"HEAD\" ? null : upstream.body, {\n    headers,\n    status: upstream.status,\n  });\n}\n\nfunction proxiedPdfHeaders(upstreamHeaders: Headers) {\n  const headers = new Headers();\n  for (const name of [\n    \"accept-ranges\",\n    \"cache-control\",\n    \"content-length\",\n    \"content-range\",\n    \"content-type\",\n    \"etag\",\n    \"last-modified\",\n  ]) {\n    const value = upstreamHeaders.get(name);\n    if (value) headers.set(name, value);\n  }\n  headers.set(\"content-disposition\", \"inline\");\n  return headers;\n}\n",
      "type": "registry:file",
      "target": "app/api/politax-pdf/[prefix]/[document]/route.ts"
    }
  ],
  "categories": [
    "primitives"
  ],
  "type": "registry:block"
}
