HealthcareParse → Extract

Lab Results Extractor

Extracts patient information, test results, and reference ranges from lab reports.

Ship it with Extend

Live pipeline

a real document, processed end to end · view only
Source documentblood_test.png

Step-by-step

A lab results report is a clinical document issued by a healthcare laboratory that contains a patient's demographic information, specimen details, individual test results with values and reference ranges, and the report status and timestamps. This template takes in laboratory results, such as blood tests, and outputs markdown (.md) capturing the lab report's full text and layout, and JSON (.json) with structured patient and test data fields per the extraction schema by using Extend's Parse, Extract primitives.

Input
laboratory results, such as blood tests,
Compatible document types (full list)
.pdf.docx.xlsx.png.jpg.jpeg.tiff.tif.svg.heic.heif.bmp.gif.webp.psd.xls.xltm.xltx.ods.doc.wpd.dotx.odt.pptx.ppt.ppm.csv.txt.html.xml.rtf.lis.md.eml.pcx
Step 1

Parse

Parse lab results

InputSource document (PDF, image, or scan)
Config
blockOptions.figures.enabledtruechanged
blockOptions.tables.agentic.enabledtruechanged
blockOptions.tables.tableHeaderContinuationEnabledtruechanged
blockOptions.text.agentic.enabledtruechanged
blockOptions.text.signatureDetectionEnabledtruechanged
chunkingStrategy.type"page"changed
Outputmarkdown (.md) capturing the document's full text, tables, and layout

You can learn more about Parse configuration in Extend's Parse documentation.

Step 2

Extract

Extract lab results extractor fields

InputOutput of the previous step
Config
schemacustom schema — 11 fieldschanged
additionalPropertiesfalsechanged
properties.age.description"The age of the patient at the time of the test. May be explicitly stated or derived from the date of birth and collection date."changed
properties.age.type["number","null"]changed
properties.date_of_birth.description"The patient's date of birth. This is used for identification and to interpret age-dependent reference ranges. May be labeled as 'DOB', 'Date of Birth', or simi…changed
properties.date_of_birth.extend:type"date"changed
properties.date_of_birth.type["string","null"]changed
properties.fasting_status.description"Indicates whether the patient was fasting at the time of specimen collection. May be labeled as 'Fasting', 'Fasting Status', or similar, with values such as 'Y…changed
properties.fasting_status.type["string","null"]changed
properties.gender.description"The gender or sex of the patient. May be labeled as 'Gender', 'Sex', or indicated by 'M', 'F', or other descriptors."changed
properties.gender.type["string","null"]changed
properties.hospital_address.description"The address of the hospital, clinic, or healthcare facility associated with the test. May include street, city, state, and postal code."changed
properties.hospital_address.type["string","null"]changed
properties.hospital_name.description"The name of the hospital, clinic, or healthcare facility associated with the test. May be labeled as 'Facility', 'Client', or appear in the client information …changed
properties.hospital_name.type["string","null"]changed
properties.hospital_phone.description"The contact phone number for the hospital or healthcare facility, if provided."changed
properties.hospital_phone.type["string","null"]changed
properties.lab_address.description"The address of the laboratory that performed the analysis, if provided."changed
properties.lab_address.type["string","null"]changed
properties.lab_clia_number.description"The CLIA (Clinical Laboratory Improvement Amendments) number or other accreditation identifier for the laboratory, if present."changed
properties.lab_clia_number.type["string","null"]changed
properties.lab_director.description"The name of the laboratory director or responsible scientist, if listed."changed
properties.lab_director.type["string","null"]changed
properties.lab_name.description"The name of the laboratory that performed the analysis. May be labeled as 'Lab', 'Performing Site', or appear in the laboratory information section."changed
properties.lab_name.type["string","null"]changed
properties.lab_results.description"A list of all individual laboratory test results included in this report. Each entry represents a single analyte or measurement, with its value, reference rang…changed
properties.lab_results.items.additionalPropertiesfalsechanged
properties.lab_results.items.properties.flag.description"An indicator of whether the result is outside the reference range or otherwise notable. Common values include 'H' (high), 'L' (low), 'Normal', or may be blank …changed
properties.lab_results.items.properties.flag.type["string","null"]changed
properties.lab_results.items.properties.interpretation.description"Any additional interpretation, comments, or notes provided for this result, such as 'Fasting reference interval', 'calc', or other remarks."changed
properties.lab_results.items.properties.interpretation.type["string","null"]changed
properties.lab_results.items.properties.lab_code.description"A code or abbreviation for the laboratory or testing site associated with this result, if provided."changed
properties.lab_results.items.properties.lab_code.type["string","null"]changed
properties.lab_results.items.properties.panel_name.description"The name of the test panel or group this result belongs to, if applicable (e.g., 'COMPREHENSIVE METABOLIC PANEL', 'CBC')."changed
properties.lab_results.items.properties.panel_name.type["string","null"]changed
properties.lab_results.items.properties.reference_range.description"The normal or expected range for this test result, as provided by the laboratory. May include lower and upper limits, or descriptive text (e.g., '65-99 mg/dL',…changed
properties.lab_results.items.properties.reference_range.type["string","null"]changed
properties.lab_results.items.properties.result_value.description"The reported value or measurement for this test. May be numeric or text (e.g., 'NOT APPLICABLE', 'Pending')."changed
properties.lab_results.items.properties.result_value.type["string","null"]changed
properties.lab_results.items.properties.test_name.description"The name of the laboratory test or analyte measured. May include abbreviations, full names, or panel names (e.g., 'GLUCOSE', 'HEMOGLOBIN', 'CBC')."changed
properties.lab_results.items.properties.test_name.type["string","null"]changed
properties.lab_results.items.properties.units.description"The measurement units for the result value, such as 'mg/dL', 'mmol/L', '%', or other units. May be omitted for qualitative results."changed
properties.lab_results.items.properties.units.type["string","null"]changed
properties.lab_results.items.required["flag","units","lab_code","test_name","panel_name","result_value","interpretation","reference_range"]changed
properties.lab_results.items.type"object"changed
properties.lab_results.type"array"changed
properties.patient_id.description"A unique identifier assigned to the patient by the healthcare provider or laboratory. This may be labeled as 'Patient ID', 'MRN', or similar, and is used to di…changed
properties.patient_id.type["string","null"]changed
properties.patient_name.description"The full name of the patient whose blood test results are reported. This is the individual for whom the laboratory analysis was performed. May appear with labe…changed
properties.patient_name.type["string","null"]changed
properties.patient_phone.description"The contact phone number for the patient, if provided. May appear in the patient information section."changed
properties.patient_phone.type["string","null"]changed
properties.report_date.description"The date when the laboratory report was generated or finalized. May be labeled as 'Report Date', 'Reported', or similar."changed
properties.report_date.extend:type"date"changed
properties.report_date.type["string","null"]changed
properties.specimen_collection_date.description"The date when the specimen was collected from the patient. May be labeled as 'Collected', 'Collection Date', or similar."changed
properties.specimen_collection_date.extend:type"date"changed
properties.specimen_collection_date.type["string","null"]changed
properties.specimen_id.description"A unique identifier for the specimen or sample tested. May be labeled as 'Specimen', 'Sample ID', or similar."changed
properties.specimen_id.type["string","null"]changed
properties.specimen_received_date.description"The date when the laboratory received the specimen. May be labeled as 'Received', 'Specimen Received', or similar."changed
properties.specimen_received_date.extend:type"date"changed
properties.specimen_received_date.type["string","null"]changed
required["age","gender","lab_name","patient_id","lab_address","lab_results","report_date","specimen_id","lab_director","patient_name","date_of_birth","hospital_name","p…changed
type"object"changed
OutputJSON (.json) with structured fields per the extraction schema

You can learn more about Extract configuration in Extend's Extract documentation.

Example code

{
  "name": "Lab Results Extractor Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": true
              },
              "signatureDetectionEnabled": true
            },
            "tables": {
              "agentic": {
                "enabled": true
              },
              "tableHeaderContinuationEnabled": true
            },
            "figures": {
              "enabled": true
            }
          },
          "chunkingStrategy": {
            "type": "page",
            "options": {}
          }
        }
      },
      "next": [
        {
          "step": "extraction2"
        }
      ]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "properties": {
              "age": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Patient's age in years"
              },
              "gender": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Patient gender (M/F)"
              },
              "patient_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Unique patient identifier"
              },
              "lab_results": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "test_name": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Name of the lab test"
                    },
                    "result_value": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Numerical or descriptive result value"
                    },
                    "reference_range": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Normal reference range for the test"
                    }
                  }
                },
                "description": "Array of individual lab test results"
              },
              "specimen_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Unique specimen identifier"
              },
              "patient_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full name of the patient"
              },
              "date_of_birth": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Patient's date of birth in MM/DD/YYYY format"
              },
              "report_status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Status of the report (e.g., Final, Preliminary)"
              },
              "fasting_status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Whether patient was fasting (Y/N)"
              },
              "report_datetime": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Date and time report was finalized in MM/DD/YYYY / HH:MM format"
              },
              "collection_datetime": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Date and time specimen was collected in MM/DD/YYYY / HH:MM TZ format"
              }
            }
          }
        }
      }
    }
  ]
}
# Lab Results Extractor — Extend AI Skill

## What this pipeline does

This pipeline ingests clinical laboratory result PDFs from diagnostic facilities (Quest Diagnostics, LabCorp, etc.) and extracts structured patient demographics, specimen details, and comprehensive test results with reference ranges into JSON. It parses complex tabular layouts and handwritten annotations using agentic OCR, then extracts 20+ fields including patient name, DOB, age, gender, specimen ID, report status, fasting status, and a fully populated array of individual lab tests (test name, result value, reference range, units, flags, panel grouping, and interpretation notes).

## When to use this

- **EHR integration**: Import lab results directly into electronic health record systems, eliminating manual data entry and transcription errors.
- **Clinical decision support**: Feed structured lab data into diagnostic algorithms that flag abnormal results or suggest follow-up tests.
- **Patient engagement portals**: Automatically populate patient-facing web interfaces with lab results, historical trends, and actionable insights.
- **Compliance & audit**: Extract CLIA-required fields (lab director, CLIA number, facility accreditation) for regulatory reporting and audit trails.
- **Batch processing**: Handle high-volume lab result ingestion from multiple facilities in a single workflow, with consistent schema across all sources.

## Processor pipeline

### Step 1: Parse (agentic OCR with table & signature detection)
- **Processor**: `parseRuns.createAndPoll()`
- **Purpose**: Convert PDF pages into structured markdown, preserving table rows, column alignment, and metadata.
- **Key config**:
  - `blockOptions.text.agentic.enabled: true` — enables intelligent text recognition for variable layouts, handwriting, and anomalies common in lab reports.
  - `blockOptions.tables.agentic.enabled: true` — instructs the parser to recognize table structure even if columns shift between pages or headers repeat.
  - `blockOptions.tables.tableHeaderContinuationEnabled: true` — critical for multi-page reports where lab results tables span pages; ensures column headers are inferred for continuation sections.
  - `blockOptions.figures.enabled: true` — captures any charts or graphs embedded in the report.
  - `chunkingStrategy.type: "page"` — outputs one markdown chunk per page, making downstream extraction aligned to document structure.
- **Why this config**: Lab reports have highly variable layouts (different labs, different test panels). Agentic OCR handles handwritten notes, non-standard fonts, and shifted table alignments. Table header continuation is essential because CMP (Comprehensive Metabolic Panel) results often span multiple pages with implicit column headers.

### Step 2: Extract (structured JSON with Zod schema)
- **Processor**: `extractRuns.createAndPoll()`
- **Purpose**: Parse the markdown output and extract 20 patient/specimen/facility fields and a dynamic array of test results.
- **Key config**:
  - **Schema structure**: Root object with nested properties for demographics (name, DOB, age, gender, patient_id), facility info (lab_name, lab_address, lab_director, lab_clia_number, hospital_name, hospital_address, hospital_phone, patient_phone), specimen metadata (specimen_id, specimen_collection_date, specimen_received_date), report metadata (report_status, report_datetime, fasting_status), and a `lab_results` array.
  - **lab_results array**: Each item is an object with test_name, result_value, reference_range, units, panel_name, lab_code, flag, and interpretation — capturing the full context of each analyte.
  - **Field descriptions**: Ultra-specific guidance (e.g., "may be labeled as 'CLIA', 'CMS', or appear in footer") helps the model find fields in non-standard positions.
  - **Nullability**: All string/number fields are nullable (`type: ["string", "null"]`) to gracefully handle missing fields; arrays are not nullable but may be empty.
- **Why this config**: Lab result PDFs are dense with clinical terminology and variable field positions. Explicit descriptions tie each schema field to all known label variations across labs, ensuring high recall. The nested array structure mirrors the semantic unit of a lab report: one patient, one specimen, multiple tests.

---

## TypeScript implementation



---

## CLI equivalent

```bash
# Set API key
export EXTEND_API_KEY="sk_..."

# Step 1: Parse the lab results PDF with agentic OCR and table detection
extend parse lab_results.pdf \
  --mode agentic_ocr \
  --block-option text.agentic.enabled=true \
  --block-option text.signatureDetectionEnabled=true \
  --block-option tables.agentic.enabled=true \
  --block-option tables.tableHeaderContinuationEnabled=true \
  --block-option figures.enabled=true \
  --chunking-strategy page \
  > parsed_output.md

# Step 2: Extract structured fields using the schema (save schema to file first)
cat > lab_schema.json << 'EOF'
{
  "type": "object",
  "properties": {
    "patient_name": { "type": ["string", "null"], "description": "Full name of the patient" },
    "date_of_birth": { "type": ["string", "null"], "description": "Patient's date of birth in MM/DD/YYYY format" },
    "age": { "type": ["integer", "null"], "description": "Patient's age in years" },
    "gender": { "type": ["string", "null"], "description": "Patient gender: M/F" },
    "patient_id": { "type": ["string", "null"], "description": "Unique patient identifier or MRN" },
    "patient_phone": { "type": ["string", "null"], "description": "Patient contact phone" },
    "specimen_id": { "type": ["string", "null"], "description": "Unique specimen identifier" },
    "specimen_collection_date": { "type": ["string", "null"], "description": "Date specimen was collected" },
    "specimen
import { ExtendClient } from "extend-ai";
import { z } from "zod";
import fs from "fs";

const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });

// Zod schema matching the lab results extractor pipeline
const labResultItemSchema = z.object({
  test_name: z.string().nullable().describe("Name of the lab test (e.g., 'GLUCOSE', 'HEMOGLOBIN')"),
  result_value: z.string().nullable().describe("Numerical or descriptive result value"),
  reference_range: z.string().nullable().describe("Normal reference range for the test (e.g., '65-99 mg/dL')"),
  units: z.string().nullable().describe("Measurement units (e.g., 'mg/dL', 'mmol/L')"),
  panel_name: z.string().nullable().describe("Name of test panel (e.g., 'COMPREHENSIVE METABOLIC PANEL')"),
  lab_code: z.string().nullable().describe("Lab code or abbreviation for this test"),
  flag: z.string().nullable().describe("Flag if outside range: 'H' (high), 'L' (low), 'Normal', or blank"),
  interpretation: z.string().nullable().describe("Additional notes or interpretation (e.g., 'Fasting reference interval')"),
});

const labResultsExtractorSchema = z.object({
  patient_name: z.string().nullable().describe("Full name of the patient whose test results are reported"),
  date_of_birth: z.string().nullable().describe("Patient's date of birth in MM/DD/YYYY format"),
  age: z.number().nullable().describe("Patient's age in years, computed from DOB or explicitly stated"),
  gender: z.string().nullable().describe("Patient gender: 'M', 'F', or full descriptor"),
  patient_id: z.string().nullable().describe("Unique patient identifier, MRN, or account number"),
  patient_phone: z.string().nullable().describe("Contact phone number for the patient"),
  
  specimen_id: z.string().nullable().describe("Unique specimen or sample identifier"),
  specimen_collection_date: z.string().nullable().describe("Date specimen was collected (MM/DD/YYYY or similar)"),
  specimen_received_date: z.string().nullable().describe("Date lab received the specimen"),
  fasting_status: z.string().nullable().describe("Whether patient was fasting: 'Y', 'N', 'Yes', 'No', or descriptive"),
  
  report_status: z.string().nullable().describe("Report status: 'Final', 'Preliminary', 'Amended', or similar"),
  report_datetime: z.string().nullable().describe("Date/time report was finalized (MM/DD/YYYY / HH:MM format)"),
  collection_datetime: z.string().nullable().describe("Date/time specimen collected (MM/DD/YYYY / HH:MM TZ format)"),
  
  lab_name: z.string().nullable().describe("Name of the laboratory that performed the analysis"),
  lab_address: z.string().nullable().describe("Address of the laboratory"),
  lab_director: z.string().nullable().describe("Name of laboratory director or responsible scientist"),
  lab_clia_number: z.string().nullable().describe("CLIA number or accreditation identifier for the lab"),
  
  hospital_name: z.string().nullable().describe("Name of hospital or healthcare facility associated with the test"),
  hospital_address: z.string().nullable().describe("Address of the hospital or healthcare facility"),
  hospital_phone: z.string().nullable().describe("Contact phone number for the hospital or facility"),
  
  lab_results: z.array(labResultItemSchema).describe("Array of individual lab test results with values, ranges, and interpretations"),
});

export async function processLabResultsExtractor(filePath: string) {
  console.log(`Processing lab results from: ${filePath}`);
  
  // Convert local file to data URL (base64) for SDK compatibility
  const fileBuffer = fs.readFileSync(filePath);
  const base64Data = fileBuffer.toString("base64");
  const dataUrl = `data:application/octet-stream;base64,${base64Data}`;
  
  // Step 1: Parse with agentic OCR, table detection, and signature detection
  console.log("Step 1: Parsing lab results document...");
  const parseRun = await client.parseRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      mode: "agentic_ocr",
      blockOptions: {
        text: {
          agentic: { enabled: true },
          signatureDetectionEnabled: true,
        },
        tables: {
          agentic: { enabled: true },
          tableHeaderContinuationEnabled: true,
        },
        figures: { enabled: true },
      },
      chunkingStrategy: {
        type: "page",
        options: {},
      },
    },
  });
  
  if (parseRun.status !== "PROCESSED") {
    throw new Error(`Parse failed with status: ${parseRun.status}`);
  }
  
  console.log(`✓ Parsed ${parseRun.output.chunks.length} pages`);
  
  // Log the first chunk as a sanity check
  if (parseRun.output.chunks.length > 0) {
    console.log(`First chunk preview:\n${parseRun.output.chunks[0].content.substring(0, 300)}...\n`);
  }
  
  // Step 2: Extract structured fields using Zod schema
  console.log("Step 2: Extracting lab results fields...");
  const extractRun = await client.extractRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      schema: labResultsExtractorSchema,
    },
  });
  
  if (extractRun.status !== "PROCESSED") {
    throw new Error(`Extraction failed with status: ${extractRun.status}`);
  }
  
  const result = extractRun.output.value;
  
  console.log("✓ Extraction complete\n");
  console.log("=== EXTRACTED LAB RESULTS ===\n");
  
  // Display patient demographics
  console.log("PATIENT INFORMATION:");
  console.log(`  Name: ${result.patient_name || "N/A"}`);
  console.log(`  DOB: ${result.date_of_birth || "N/A"}`);
  console.log(`  Age: ${result.age || "N/A"}`);
  console.log(`  Gender: ${result.gender || "N/A"}`);
  console.log(`  Patient ID: ${result.patient_id || "N/A"}`);
  console.log(`  Phone: ${result.patient_phone || "N/A"}\n`);
  
  // Display specimen and report metadata
  console.log("SPECIMEN & REPORT:");
  console.log(`  Specimen ID: ${result.specimen_id || "N/A"}`);
  console.log(`  Collection Date/Time: ${result.collection_datetime || "N/A"}`);
  console.log(`  Received Date: ${result.specimen_received_date || "N/A"}`);
  console.log(`  Fasting Status: ${result.fasting_status || "N/A"}`);
  console.log(`  Report Status: ${result.report_status || "N/A"}`);
  console.log(`  Report Date/Time: ${result.report_datetime || "N/A"}\n`);
  
  // Display facility information
  console.log("FACILITY INFORMATION:");
  console.log(`  Lab Name: ${result.lab_name || "N/A"}`);
  console.log(`  Lab Address: ${result.lab_address || "N/A"}`);
  console.log(`  Lab Director: ${result.lab_director || "N/A"}`);
  console.log(`  CLIA Number: ${result.lab_clia_number || "N/A"}`);
  console.log(`  Hospital: ${result.hospital_name || "N/A"}`);
  console.log(`  Hospital Address: ${result.hospital_address || "N/A"}`);
  console.log(`  Hospital Phone: ${result.hospital_phone || "N/A"}\n`);
  
  // Display lab results
  console.log("LAB TEST RESULTS:");
  if (result.lab_results && result.lab_results.length > 0) {
    console.log(`Total Tests: ${result.lab_results.length}\n`);
    
    // Group by panel if available
    const byPanel = new Map<string | null, typeof result.lab_results>();
    for (const test of result.lab_results) {
      const panel = test.panel_name || "OTHER";
      if (!byPanel.has(panel)) byPanel.set(panel, []);
      byPanel.get(panel)!.push(test);
    }
    
    for (const [panel, tests] of byPanel) {
      console.log(`  ${panel}:`);
      for (const test of tests) {
        const flag = test.flag ? ` [${test.flag}]` : "";
        const units = test.units ? ` ${test.units}` : "";
        const refRange = test.reference_range ? ` (ref: ${test.reference_range})` : "";
        console.log(`    • ${test.test_name}: ${test.result_value}${units}${refRange}${flag}`);
        if (test.interpretation) {
          console.log(`      Note: ${test.interpretation}`);
        }
      }
      console.log();
    }
  } else {
    console.log("  No lab results extracted.\n");
  }
  
  // Return the full structured result for programmatic use
  return {
    status: "success",
    data: result,
    parseChunks: parseRun.output.chunks.map(c => c.content),
  };
}

// For direct execution (e.g., npm run or node script)
const filePath = process.argv[2];
if (!filePath) {
  console.error("Usage: node solution.ts <path-to-pdf>");
  process.exit(1);
}

processLabResultsExtractor(filePath)
  .then(result => {
    console.log("\n=== RAW JSON OUTPUT ===");
    console.log(JSON.stringify(result.data, null, 2));
  })
  .catch(err => {
    console.error("Error:", err);
    process.exit(1);
  });
from extend_ai import Extend
from pydantic import BaseModel, Field
from typing import Optional, List
import base64
import os
import sys
import json


client = Extend(token=os.environ["EXTEND_API_KEY"])


class LabResultItem(BaseModel):
    test_name: Optional[str] = Field(None, description="Name of the lab test (e.g., 'GLUCOSE', 'HEMOGLOBIN')")
    result_value: Optional[str] = Field(None, description="Numerical or descriptive result value")
    reference_range: Optional[str] = Field(None, description="Normal reference range for the test (e.g., '65-99 mg/dL')")
    units: Optional[str] = Field(None, description="Measurement units (e.g., 'mg/dL', 'mmol/L')")
    panel_name: Optional[str] = Field(None, description="Name of test panel (e.g., 'COMPREHENSIVE METABOLIC PANEL')")
    lab_code: Optional[str] = Field(None, description="Lab code or abbreviation for this test")
    flag: Optional[str] = Field(None, description="Flag if outside range: 'H' (high), 'L' (low), 'Normal', or blank")
    interpretation: Optional[str] = Field(None, description="Additional notes or interpretation (e.g., 'Fasting reference interval')")


class LabResultsExtractor(BaseModel):
    patient_name: Optional[str] = Field(None, description="Full name of the patient whose test results are reported")
    date_of_birth: Optional[str] = Field(None, description="Patient's date of birth in MM/DD/YYYY format")
    age: Optional[int] = Field(None, description="Patient's age in years, computed from DOB or explicitly stated")
    gender: Optional[str] = Field(None, description="Patient gender: 'M', 'F', or full descriptor")
    patient_id: Optional[str] = Field(None, description="Unique patient identifier, MRN, or account number")
    patient_phone: Optional[str] = Field(None, description="Contact phone number for the patient")
    
    specimen_id: Optional[str] = Field(None, description="Unique specimen or sample identifier")
    specimen_collection_date: Optional[str] = Field(None, description="Date specimen was collected (MM/DD/YYYY or similar)")
    specimen_received_date: Optional[str] = Field(None, description="Date lab received the specimen")
    fasting_status: Optional[str] = Field(None, description="Whether patient was fasting: 'Y', 'N', 'Yes', 'No', or descriptive")
    
    report_status: Optional[str] = Field(None, description="Report status: 'Final', 'Preliminary', 'Amended', or similar")
    report_datetime: Optional[str] = Field(None, description="Date/time report was finalized (MM/DD/YYYY / HH:MM format)")
    collection_datetime: Optional[str] = Field(None, description="Date/time specimen collected (MM/DD/YYYY / HH:MM TZ format)")
    
    lab_name: Optional[str] = Field(None, description="Name of the laboratory that performed the analysis")
    lab_address: Optional[str] = Field(None, description="Address of the laboratory")
    lab_director: Optional[str] = Field(None, description="Name of laboratory director or responsible scientist")
    lab_clia_number: Optional[str] = Field(None, description="CLIA number or accreditation identifier for the lab")
    
    hospital_name: Optional[str] = Field(None, description="Name of hospital or healthcare facility associated with the test")
    hospital_address: Optional[str] = Field(None, description="Address of the hospital or healthcare facility")
    hospital_phone: Optional[str] = Field(None, description="Contact phone number for the hospital or facility")
    
    lab_results: List[LabResultItem] = Field(default_factory=list, description="Array of individual lab test results with values, ranges, and interpretations")


def process_lab_results_extractor(file_path: str):
    print(f"Processing lab results from: {file_path}")
    
    # Convert local file to data URL (base64) for SDK compatibility
    with open(file_path, "rb") as f:
        file_buffer = f.read()
    base64_data = base64.b64encode(file_buffer).decode("utf-8")
    data_url = f"data:application/octet-stream;base64,{base64_data}"
    
    # Step 1: Parse with agentic OCR, table detection, and signature detection
    print("Step 1: Parsing lab results document...")
    parse_run = client.parse_runs.create_and_poll(
        file={"url": data_url},
        config={
            "mode": "agentic_ocr",
            "block_options": {
                "text": {
                    "agentic": {"enabled": True},
                    "signature_detection_enabled": True,
                },
                "tables": {
                    "agentic": {"enabled": True},
                    "table_header_continuation_enabled": True,
                },
                "figures": {"enabled": True},
            },
            "chunking_strategy": {
                "type": "page",
                "options": {},
            },
        },
    )
    
    if parse_run.status != "PROCESSED":
        raise Exception(f"Parse failed with status: {parse_run.status}")
    
    print(f"✓ Parsed {len(parse_run.output.chunks)} pages")
    
    # Log the first chunk as a sanity check
    if parse_run.output.chunks:
        print(f"First chunk preview:\n{parse_run.output.chunks[0].content[:300]}...\n")
    
    # Step 2: Extract structured fields using Pydantic schema
    print("Step 2: Extracting lab results fields...")
    extract_run = client.extract_runs.create_and_poll(
        file={"url": data_url},
        config={
            "schema": LabResultsExtractor.model_json_schema(),
        },
    )
    
    if extract_run.status != "PROCESSED":
        raise Exception(f"Extraction failed with status: {extract_run.status}")
    
    result = LabResultsExtractor(**extract_run.output.value)
    
    print("✓ Extraction complete\n")
    print("=== EXTRACTED LAB RESULTS ===\n")
    
    # Display patient demographics
    print("PATIENT INFORMATION:")
    print(f"  Name: {result.patient_name or 'N/A'}")
    print(f"  DOB: {result.date_of_birth or 'N/A'}")
    print(f"  Age: {result.age or 'N/A'}")
    print(f"  Gender: {result.gender or 'N/A'}")
    print(f"  Patient ID: {result.patient_id or 'N/A'}")
    print(f"  Phone: {result.patient_phone or 'N/A'}\n")
    
    # Display specimen and report metadata
    print("SPECIMEN & REPORT:")
    print(f"  Specimen ID: {result.specimen_id or 'N/A'}")
    print(f"  Collection Date/Time: {result.collection_datetime or 'N/A'}")
    print(f"  Received Date: {result.specimen_received_date or 'N/A'}")
    print(f"  Fasting Status: {result.fasting_status or 'N/A'}")
    print(f"  Report Status: {result.report_status or 'N/A'}")
    print(f"  Report Date/Time: {result.report_datetime or 'N/A'}\n")
    
    # Display facility information
    print("FACILITY INFORMATION:")
    print(f"  Lab Name: {result.lab_name or 'N/A'}")
    print(f"  Lab Address: {result.lab_address or 'N/A'}")
    print(f"  Lab Director: {result.lab_director or 'N/A'}")
    print(f"  CLIA Number: {result.lab_clia_number or 'N/A'}")
    print(f"  Hospital: {result.hospital_name or 'N/A'}")
    print(f"  Hospital Address: {result.hospital_address or 'N/A'}")
    print(f"  Hospital Phone: {result.hospital_phone or 'N/A'}\n")
    
    # Display lab results
    print("LAB TEST RESULTS:")
    if result.lab_results:
        print(f"Total Tests: {len(result.lab_results)}\n")
        
        # Group by panel if available
        by_panel = {}
        for test in result.lab_results:
            panel = test.panel_name or "OTHER"
            if panel not in by_panel:
                by_panel[panel] = []
            by_panel[panel].append(test)
        
        for panel, tests in by_panel.items():
            print(f"  {panel}:")
            for test in tests:
                flag = f" [{test.flag}]" if test.flag else ""
                units = f" {test.units}" if test.units else ""
                ref_range = f" (ref: {test.reference_range})" if test.reference_range else ""
                print(f"    • {test.test_name}: {test.result_value}{units}{ref_range}{flag}")
                if test.interpretation:
                    print(f"      Note: {test.interpretation}")
            print()
    else:
        print("  No lab results extracted.\n")
    
    # Return the full structured result for programmatic use
    return {
        "status": "success",
        "data": result.model_dump(),
        "parse_chunks": [c.content for c in parse_run.output.chunks],
    }


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python solution.py <path-to-pdf>")
        sys.exit(1)
    
    file_path = sys.argv[1]
    
    result = process_lab_results_extractor(file_path)
    print("\n=== RAW JSON OUTPUT ===")
    print(json.dumps(result["data"], indent=2))
import com.extend.ai.ExtendClient;
import com.extend.ai.models.ParseRunsCreateAndPollRequest;
import com.extend.ai.models.ExtractRunsCreateAndPollRequest;
import com.extend.ai.models.ParseRun;
import com.extend.ai.models.ExtractRun;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

import java.io.File;
import java.nio.file.Files;
import java.util.*;

public class LabResultsExtractor {

  private static final ExtendClient client = new ExtendClient(
    System.getenv("EXTEND_API_KEY")
  );
  private static final ObjectMapper objectMapper = new ObjectMapper();

  public static void main(String[] args) throws Exception {
    if (args.length == 0) {
      System.err.println("Usage: java LabResultsExtractor <path-to-pdf>");
      System.exit(1);
    }

    String filePath = args[0];
    try {
      Map<String, Object> result = processLabResultsExtractor(filePath);
      System.out.println("\n=== RAW JSON OUTPUT ===");
      System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.get("data")));
    } catch (Exception err) {
      System.err.println("Error: " + err.getMessage());
      err.printStackTrace();
      System.exit(1);
    }
  }

  public static Map<String, Object> processLabResultsExtractor(String filePath) throws Exception {
    System.out.println("Processing lab results from: " + filePath);

    // Convert local file to data URL (base64) for SDK compatibility
    byte[] fileBuffer = Files.readAllBytes(new File(filePath).toPath());
    String base64Data = Base64.getEncoder().encodeToString(fileBuffer);
    String dataUrl = "data:application/octet-stream;base64," + base64Data;

    // Step 1: Parse with agentic OCR, table detection, and signature detection
    System.out.println("Step 1: Parsing lab results document...");
    
    ObjectNode parseConfig = objectMapper.createObjectNode();
    parseConfig.put("mode", "agentic_ocr");
    
    ObjectNode blockOptions = parseConfig.putObject("blockOptions");
    ObjectNode textBlock = blockOptions.putObject("text");
    textBlock.putObject("agentic").put("enabled", true);
    textBlock.put("signatureDetectionEnabled", true);
    
    ObjectNode tablesBlock = blockOptions.putObject("tables");
    tablesBlock.putObject("agentic").put("enabled", true);
    tablesBlock.put("tableHeaderContinuationEnabled", true);
    
    blockOptions.putObject("figures").put("enabled", true);
    
    ObjectNode chunkingStrategy = parseConfig.putObject("chunkingStrategy");
    chunkingStrategy.put("type", "page");
    chunkingStrategy.putObject("options");

    ParseRunsCreateAndPollRequest parseRequest = new ParseRunsCreateAndPollRequest.Builder()
      .file(new ParseRunsCreateAndPollRequest.File(dataUrl))
      .config(parseConfig)
      .build();

    ParseRun parseRun = client.parseRuns().createAndPoll(parseRequest);

    if (!"PROCESSED".equals(parseRun.getStatus())) {
      throw new Exception("Parse failed with status: " + parseRun.getStatus());
    }

    System.out.println("✓ Parsed " + parseRun.getOutput().getChunks().size() + " pages");

    if (!parseRun.getOutput().getChunks().isEmpty()) {
      String firstChunk = parseRun.getOutput().getChunks().get(0).getContent();
      int previewLength = Math.min(300, firstChunk.length());
      System.out.println("First chunk preview:\n" + firstChunk.substring(0, previewLength) + "...\n");
    }

    // Step 2: Extract structured fields using schema
    System.out.println("Step 2: Extracting lab results fields...");
    
    ObjectNode schema = createLabResultsSchema();

    ExtractRunsCreateAndPollRequest extractRequest = new ExtractRunsCreateAndPollRequest.Builder()
      .file(new ExtractRunsCreateAndPollRequest.File(dataUrl))
      .config(new ExtractRunsCreateAndPollRequest.Config(schema))
      .build();

    ExtractRun extractRun = client.extractRuns().createAndPoll(extractRequest);

    if (!"PROCESSED".equals(extractRun.getStatus())) {
      throw new Exception("Extraction failed with status: " + extractRun.getStatus());
    }

    JsonNode result = (JsonNode) extractRun.getOutput().getValue();

    System.out.println("✓ Extraction complete\n");
    System.out.println("=== EXTRACTED LAB RESULTS ===\n");

    // Display patient demographics
    System.out.println("PATIENT INFORMATION:");
    System.out.println("  Name: " + getStringOrNA(result, "patient_name"));
    System.out.println("  DOB: " + getStringOrNA(result, "date_of_birth"));
    System.out.println("  Age: " + getStringOrNA(result, "age"));
    System.out.println("  Gender: " + getStringOrNA(result, "gender"));
    System.out.println("  Patient ID: " + getStringOrNA(result, "patient_id"));
    System.out.println("  Phone: " + getStringOrNA(result, "patient_phone") + "\n");

    // Display specimen and report metadata
    System.out.println("SPECIMEN & REPORT:");
    System.out.println("  Specimen ID: " + getStringOrNA(result, "specimen_id"));
    System.out.println("  Collection Date/Time: " + getStringOrNA(result, "collection_datetime"));
    System.out.println("  Received Date: " + getStringOrNA(result, "specimen_received_date"));
    System.out.println("  Fasting Status: " + getStringOrNA(result, "fasting_status"));
    System.out.println("  Report Status: " + getStringOrNA(result, "report_status"));
    System.out.println("  Report Date/Time: " + getStringOrNA(result, "report_datetime") + "\n");

    // Display facility information
    System.out.println("FACILITY INFORMATION:");
    System.out.println("  Lab Name: " + getStringOrNA(result, "lab_name"));
    System.out.println("  Lab Address: " + getStringOrNA(result, "lab_address"));
    System.out.println("  Lab Director: " + getStringOrNA(result, "lab_director"));
    System.out.println("  CLIA Number: " + getStringOrNA(result, "lab_clia_number"));
    System.out.println("  Hospital: " + getStringOrNA(result, "hospital_name"));
    System.out.println("  Hospital Address: " + getStringOrNA(result, "hospital_address"));
    System.out.println("  Hospital Phone: " + getStringOrNA(result, "hospital_phone") + "\n");

    // Display lab results
    System.out.println("LAB TEST RESULTS:");
    JsonNode labResultsNode = result.get("lab_results");
    if (labResultsNode != null && labResultsNode.isArray() && labResultsNode.size() > 0) {
      System.out.println("Total Tests: " + labResultsNode.size() + "\n");

      Map<String, List<JsonNode>> byPanel = new LinkedHashMap<>();
      for (JsonNode test : labResultsNode) {
        String panel = test.has("panel_name") && !test.get("panel_name").isNull()
          ? test.get("panel_name").asText()
          : "OTHER";
        byPanel.computeIfAbsent(panel, k -> new ArrayList<>()).add(test);
      }

      for (Map.Entry<String, List<JsonNode>> entry : byPanel.entrySet()) {
        System.out.println("  " + entry.getKey() + ":");
        for (JsonNode test : entry.getValue()) {
          String flag = test.has("flag") && !test.get("flag").isNull()
            ? " [" + test.get("flag").asText() + "]"
            : "";
          String units = test.has("units") && !test.get("units").isNull()
            ? " " + test.get("units").asText()
            : "";
          String refRange = test.has("reference_range") && !test.get("reference_range").isNull()
            ? " (ref: " + test.get("reference_range").asText() + ")"
            : "";
          String testName = test.has("test_name") && !test.get("test_name").isNull()
            ? test.get("test_name").asText()
            : "N/A";
          String resultValue = test.has("result_value") && !test.get("result_value").isNull()
            ? test.get("result_value").asText()
            : "N/A";

          System.out.println("    • " + testName + ": " + resultValue + units + refRange + flag);

          if (test.has("interpretation") && !test.get("interpretation").isNull()) {
            System.out.println("      Note: " + test.get("interpretation").asText());
          }
        }
        System.out.println();
      }
    } else {
      System.out.println("  No lab results extracted.\n");
    }

    // Return the full structured result for programmatic use
    Map<String, Object> response = new HashMap<>();
    response.put("status", "success");
    response.put("data", result);
    List<String> chunks = new ArrayList<>();
    for (int i = 0; i < parseRun.getOutput().getChunks().size(); i++) {
      chunks.add(parseRun.getOutput().getChunks().get(i).getContent());
    }
    response.put("parseChunks", chunks);

    return response;
  }

  private static String getStringOrNA(JsonNode node, String field) {
    if (node.has(field) && !node.get(field).isNull()) {
      return node.get(field).asText();
    }
    return "N/A";
  }

  private static ObjectNode createLabResultsSchema() {
    ObjectNode schema = objectMapper.createObjectNode();
    schema.put("type", "object");

    ObjectNode properties = schema.putObject("properties");

    // Patient name
    ObjectNode patientName = properties.putObject("patient_name");
    ArrayNode patientNameType = patientName.putArray("type");
    patientNameType.add("string");
    patientNameType.add("null");
    patientName.put("description", "Full name of the patient whose test results are reported");

    // Date of birth
    ObjectNode dob = properties.putObject("date_of_birth");
    ArrayNode dobType = dob.putArray("type");
    dobType.add("string");
    dobType.add("null");
    dob.put("description", "Patient's date of birth in MM/DD/YYYY format");

    // Age
    ObjectNode age = properties.putObject("age");
    ArrayNode ageType = age.putArray("type");
    ageType.add("integer");
    ageType.add("null");
    age.put("description", "Patient's age in years, computed from DOB or explicitly stated");

    // Gender
    ObjectNode gender = properties.putObject("gender");
    ArrayNode genderType = gender.putArray("type");
    genderType.add("string");
    genderType.add("null");
    gender.put("description", "Patient gender: 'M', 'F', or full descriptor");

    // Patient ID
    ObjectNode patientId = properties.putObject("patient_id");
    ArrayNode patientIdType = patientId.putArray("type");
    patientIdType.add("string");
    patientIdType.add("null");
    patientId.put("description", "Unique patient identifier, MRN, or account number");

    // Patient phone
    ObjectNode patientPhone = properties.putObject("patient_phone");
    ArrayNode patientPhoneType = patientPhone.putArray("type");
    patientPhoneType.add("string");
    patientPhoneType.add("null");
    patientPhone.put("description", "Contact phone number for the patient");

    // Specimen ID
    ObjectNode specimenId = properties.putObject("specimen_id");
    ArrayNode specimenIdType = specimenId.putArray("type");
    specimenIdType.add("string");
    specimenIdType.add("null");
    specimenId.put("description", "Unique specimen or sample identifier");

    // Specimen collection date
    ObjectNode specimenCollectionDate = properties.putObject("specimen_collection_date");
    ArrayNode specimenCollectionDateType = specimenCollectionDate.putArray("type");
    specimenCollectionDateType.add("string");
    specimenCollectionDateType.add("null");
    specimenCollectionDate.put("description", "Date specimen was collected (MM/DD/YYYY or similar)");

    // Specimen received date
    ObjectNode specimenReceivedDate = properties.putObject("specimen_received_date");
    ArrayNode specimenReceivedDateType = specimenReceivedDate.putArray("type");
    specimenReceivedDateType.add("string");
    specimenReceivedDateType.add("null");
    specimenReceivedDate.put("description", "Date lab received the specimen");

    // Fasting status
    ObjectNode fastingStatus = properties.putObject("fasting_status");
    ArrayNode fastingStatusType = fastingStatus.putArray("type");
    fastingStatusType.add("string");
    fastingStatusType.add("null");
    fastingStatus.put("description", "Whether patient was fasting: 'Y', 'N', 'Yes', 'No', or descriptive");

    // Report status
    ObjectNode reportStatus = properties.putObject("report_status");
    ArrayNode reportStatusType = reportStatus.putArray("type");
    reportStatusType.add("string");
    reportStatusType.add("null");
    reportStatus.put("description", "Report status: 'Final', 'Preliminary', 'Amended', or similar");

    // Report datetime
    ObjectNode reportDatetime = properties.putObject("report_datetime");
    ArrayNode reportDatetimeType = reportDatetime.putArray("type");
    reportDatetimeType.add("string");
    reportDatetimeType.add("null");
    reportDatetime.put("description", "Date/time report was finalized (MM/DD/YYYY / HH:MM format)");

    // Collection datetime
    ObjectNode collectionDatetime = properties.putObject("collection_datetime");
    ArrayNode collectionDatetimeType = collectionDatetime.putArray("type");
    collectionDatetimeType.add("string");
    collectionDatetimeType.add("null");
    collectionDatetime.put("description", "Date/time specimen collected (MM/DD/YYYY / HH:MM TZ format)");

    // Lab name
    ObjectNode labName = properties.putObject("lab_name");
    ArrayNode labNameType = labName.putArray("type");
    labNameType.add("string");
    labNameType.add("null");
    labName.put("description", "Name of the laboratory that performed the analysis");

    // Lab address
    ObjectNode labAddress = properties.putObject("lab_address");
    ArrayNode labAddressType = labAddress.putArray("type");
    labAddressType.add("string");
    labAddressType.add("null");
    labAddress.put("description", "Address of the laboratory");

    // Lab director
    ObjectNode labDirector = properties.putObject("lab_director");
    ArrayNode labDirectorType = labDirector.putArray("type");
    labDirectorType.add("string");
    labDirectorType.add("null");
    labDirector.put("description", "Name of laboratory director or responsible scientist");

    // Lab CLIA number
    ObjectNode labCliaNumber = properties.putObject("lab_clia_number");
    ArrayNode labCliaNumberType = labCliaNumber.putArray("type");
    labCliaNumberType.add("string");
    labCliaNumberType.add("null");
    labCliaNumber.put("description", "CLIA number or accreditation identifier for the lab");

    // Hospital name
    ObjectNode hospitalName = properties.putObject("hospital_name");
    ArrayNode hospitalNameType = hospitalName.putArray("type");
    hospitalNameType.add("string");
    hospitalNameType.add("null");
    hospitalName.put("description", "Name of hospital or healthcare facility associated with the test");

    // Hospital address
    ObjectNode hospitalAddress = properties.putObject("hospital_address");
    ArrayNode hospitalAddressType = hospitalAddress.putArray("type");
    hospitalAddressType.add("string");
    hospitalAddressType.add("null");
    hospitalAddress.put("description", "Address of the hospital or healthcare facility");

    // Hospital phone
    ObjectNode hospitalPhone = properties.putObject("hospital_phone");
    ArrayNode hospitalPhoneType = hospitalPhone.putArray("type");
    hospitalPhoneType.add("string");
    hospitalPhoneType.add("null");
    hospitalPhone.put("description", "Contact phone number for the hospital or facility");

    // Lab results array
    ObjectNode labResults = properties.putObject("lab_results");
    labResults.put("type", "array");
    
    ObjectNode labResultItem = labResults.putObject("items");
    labResultItem.put("type", "object");
    
    ObjectNode labResultItemProperties = labResultItem.putObject("properties");
    
    // Test name
    ObjectNode testName = labResultItemProperties.putObject("test_name");
    ArrayNode testNameType = testName.putArray("type");
    testNameType.add("string");
    testNameType.add("null");
    testName.put("description", "Name of the lab test (e.g., 'GLUCOSE', 'HEMOGLOBIN')");
    
    // Result value
    ObjectNode resultValue = labResultItemProperties.putObject("result_value");
    ArrayNode resultValueType = resultValue.putArray("type");
    resultValueType.add("string");
    resultValueType.add("null");
    resultValue.put("description", "Numerical or descriptive result value");
    
    // Reference range
    ObjectNode referenceRange = labResultItemProperties.putObject("reference_range");
    ArrayNode referenceRangeType = referenceRange.putArray("type");
    referenceRangeType.add("string");
    referenceRangeType.add("null");
    referenceRange.put("description", "Normal reference range for the test (e.g., '65-99 mg/dL')");
    
    // Units
    ObjectNode units = labResultItemProperties.putObject("units");
    ArrayNode unitsType = units.putArray("type");
    unitsType.add("string");
    unitsType.add("null");
    units.put("description", "Measurement units (e.g., 'mg/dL', 'mmol/L')");
    
    // Panel name
    ObjectNode panelName = labResultItemProperties.putObject("panel_name");
    ArrayNode panelNameType = panelName.putArray("type");
    panelNameType.add("string");
    panelNameType.add("null");
    panelName.put("description", "Name of test panel (e.g., 'COMPREHENSIVE METABOLIC PANEL')");
    
    // Lab code
    ObjectNode labCode = labResultItemProperties.putObject("lab_code");
    ArrayNode labCodeType = labCode.putArray("type");
    labCodeType.add("string");
    labCodeType.add("null");
    labCode.put("description", "Lab code or abbreviation for this test");
    
    // Flag
    ObjectNode flag = labResultItemProperties.putObject("flag");
    ArrayNode flagType = flag.putArray("type");
    flagType.add("string");
    flagType.add("null");
    flag.put("description", "Flag if outside range: 'H' (high), 'L' (low), 'Normal', or blank");
    
    // Interpretation
    ObjectNode interpretation = labResultItemProperties.putObject("interpretation");
    ArrayNode interpretationType = interpretation.putArray("type");
    interpretationType.add("string");
    interpretationType.add("null");
    interpretation.put("description", "Additional notes or interpretation (e.g., 'Fasting reference interval')");
    
    labResults.put("description", "Array of individual lab test results with values, ranges, and interpretations");

    return schema;
  }
}
package main

import (
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"os"

	"github.com/extend-ai/extend-go"
)

type LabResultItem struct {
	TestName       *string `json:"test_name"`
	ResultValue    *string `json:"result_value"`
	ReferenceRange *string `json:"reference_range"`
	Units          *string `json:"units"`
	PanelName      *string `json:"panel_name"`
	LabCode        *string `json:"lab_code"`
	Flag           *string `json:"flag"`
	Interpretation *string `json:"interpretation"`
}

type LabResultsExtractor struct {
	PatientName           *string          `json:"patient_name"`
	DateOfBirth           *string          `json:"date_of_birth"`
	Age                   *int             `json:"age"`
	Gender                *string          `json:"gender"`
	PatientID             *string          `json:"patient_id"`
	PatientPhone          *string          `json:"patient_phone"`
	SpecimenID            *string          `json:"specimen_id"`
	SpecimenCollectionDate *string         `json:"specimen_collection_date"`
	SpecimenReceivedDate  *string          `json:"specimen_received_date"`
	FastingStatus         *string          `json:"fasting_status"`
	ReportStatus          *string          `json:"report_status"`
	ReportDatetime        *string          `json:"report_datetime"`
	CollectionDatetime    *string          `json:"collection_datetime"`
	LabName               *string          `json:"lab_name"`
	LabAddress            *string          `json:"lab_address"`
	LabDirector           *string          `json:"lab_director"`
	LabCliaNumber         *string          `json:"lab_clia_number"`
	HospitalName          *string          `json:"hospital_name"`
	HospitalAddress       *string          `json:"hospital_address"`
	HospitalPhone         *string          `json:"hospital_phone"`
	LabResults            []LabResultItem  `json:"lab_results"`
}

func processLabResultsExtractor(filePath string) (map[string]interface{}, error) {
	fmt.Printf("Processing lab results from: %s\n", filePath)

	client := extend.NewClient(os.Getenv("EXTEND_API_KEY"))

	// Convert local file to data URL (base64) for SDK compatibility
	fileBuffer, err := os.ReadFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read file: %w", err)
	}

	base64Data := base64.StdEncoding.EncodeToString(fileBuffer)
	dataURL := fmt.Sprintf("data:application/octet-stream;base64,%s", base64Data)

	// Step 1: Parse with agentic OCR, table detection, and signature detection
	fmt.Println("Step 1: Parsing lab results document...")

	parseConfig := extend.ParseConfig{
		Mode: "agentic_ocr",
		BlockOptions: extend.BlockOptions{
			Text: extend.TextBlockOptions{
				Agentic: extend.AgenticOptions{
					Enabled: true,
				},
				SignatureDetectionEnabled: true,
			},
			Tables: extend.TableBlockOptions{
				Agentic: extend.AgenticOptions{
					Enabled: true,
				},
				TableHeaderContinuationEnabled: true,
			},
			Figures: extend.FigureBlockOptions{
				Enabled: true,
			},
		},
		ChunkingStrategy: extend.ChunkingStrategy{
			Type:    "page",
			Options: map[string]interface{}{},
		},
	}

	parseRun, err := client.ParseRuns.CreateAndPoll(&extend.ParseRunRequest{
		File: extend.FileReference{
			URL: dataURL,
		},
		Config: parseConfig,
	})
	if err != nil {
		return nil, fmt.Errorf("parse request failed: %w", err)
	}

	if parseRun.Status != "PROCESSED" {
		return nil, fmt.Errorf("parse failed with status: %s", parseRun.Status)
	}

	fmt.Printf("✓ Parsed %d pages\n", len(parseRun.Output.Chunks))

	if len(parseRun.Output.Chunks) > 0 {
		chunkPreview := parseRun.Output.Chunks[0].Content
		if len(chunkPreview) > 300 {
			chunkPreview = chunkPreview[:300]
		}
		fmt.Printf("First chunk preview:\n%s...\n\n", chunkPreview)
	}

	// Step 2: Extract structured fields using JSON schema
	fmt.Println("Step 2: Extracting lab results fields...")

	schema := map[string]interface{}{
		"type": "object",
		"properties": map[string]interface{}{
			"patient_name": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Full name of the patient whose test results are reported",
			},
			"date_of_birth": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Patient's date of birth in MM/DD/YYYY format",
			},
			"age": map[string]interface{}{
				"type": []interface{}{"integer", "null"},
				"description": "Patient's age in years, computed from DOB or explicitly stated",
			},
			"gender": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Patient gender: 'M', 'F', or full descriptor",
			},
			"patient_id": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Unique patient identifier, MRN, or account number",
			},
			"patient_phone": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Contact phone number for the patient",
			},
			"specimen_id": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Unique specimen or sample identifier",
			},
			"specimen_collection_date": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Date specimen was collected (MM/DD/YYYY or similar)",
			},
			"specimen_received_date": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Date lab received the specimen",
			},
			"fasting_status": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Whether patient was fasting: 'Y', 'N', 'Yes', 'No', or descriptive",
			},
			"report_status": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Report status: 'Final', 'Preliminary', 'Amended', or similar",
			},
			"report_datetime": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Date/time report was finalized (MM/DD/YYYY / HH:MM format)",
			},
			"collection_datetime": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Date/time specimen collected (MM/DD/YYYY / HH:MM TZ format)",
			},
			"lab_name": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Name of the laboratory that performed the analysis",
			},
			"lab_address": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Address of the laboratory",
			},
			"lab_director": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Name of laboratory director or responsible scientist",
			},
			"lab_clia_number": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "CLIA number or accreditation identifier for the lab",
			},
			"hospital_name": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Name of hospital or healthcare facility associated with the test",
			},
			"hospital_address": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Address of the hospital or healthcare facility",
			},
			"hospital_phone": map[string]interface{}{
				"type": []string{"string", "null"},
				"description": "Contact phone number for the hospital or facility",
			},
			"lab_results": map[string]interface{}{
				"type": "array",
				"items": map[string]interface{}{
					"type": "object",
					"properties": map[string]interface{}{
						"test_name": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Name of the lab test (e.g., 'GLUCOSE', 'HEMOGLOBIN')",
						},
						"result_value": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Numerical or descriptive result value",
						},
						"reference_range": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Normal reference range for the test (e.g., '65-99 mg/dL')",
						},
						"units": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Measurement units (e.g., 'mg/dL', 'mmol/L')",
						},
						"panel_name": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Name of test panel (e.g., 'COMPREHENSIVE METABOLIC PANEL')",
						},
						"lab_code": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Lab code or abbreviation for this test",
						},
						"flag": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Flag if outside range: 'H' (high), 'L' (low), 'Normal', or blank",
						},
						"interpretation": map[string]interface{}{
							"type": []string{"string", "null"},
							"description": "Additional notes or interpretation (e.g., 'Fasting reference interval')",
						},
					},
				},
				"description": "Array of individual lab test results with values, ranges, and interpretations",
			},
		},
	}

	extractRun, err := client.ExtractRuns.CreateAndPoll(&extend.ExtractRunRequest{
		File: extend.FileReference{
			URL: dataURL,
		},
		Config: extend.ExtractConfig{
			Schema: schema,
		},
	})
	if err != nil {
		return nil, fmt.Errorf("extraction request failed: %w", err)
	}

	if extractRun.Status != "PROCESSED" {
		return nil, fmt.Errorf("extraction failed with status: %s", extractRun.Status)
	}

	var result LabResultsExtractor
	resultBytes, err := json.Marshal(extractRun.Output.Value)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal extraction result: %w", err)
	}

	if err := json.Unmarshal(resultBytes, &result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal extraction result: %w", err)
	}

	fmt.Println("✓ Extraction complete\n")
	fmt.Println("=== EXTRACTED LAB RESULTS ===\n")

	// Display patient demographics
	fmt.Println("PATIENT INFORMATION:")
	fmt.Printf("  Name: %s\n", stringOrNA(result.PatientName))
	fmt.Printf("  DOB: %s\n", stringOrNA(result.DateOfBirth))
	fmt.Printf("  Age: %s\n", intOrNA(result.Age))
	fmt.Printf("  Gender: %s\n", stringOrNA(result.Gender))
	fmt.Printf("  Patient ID: %s\n", stringOrNA(result.PatientID))
	fmt.Printf("  Phone: %s\n\n", stringOrNA(result.PatientPhone))

	// Display specimen and report metadata
	fmt.Println("SPECIMEN & REPORT:")
	fmt.Printf("  Specimen ID: %s\n", stringOrNA(result.SpecimenID))
	fmt.Printf("  Collection Date/Time: %s\n", stringOrNA(result.CollectionDatetime))
	fmt.Printf("  Received Date: %s\n", stringOrNA(result.SpecimenReceivedDate))
	fmt.Printf("  Fasting Status: %s\n", stringOrNA(result.FastingStatus))
	fmt.Printf("  Report Status: %s\n", stringOrNA(result.ReportStatus))
	fmt.Printf("  Report Date/Time: %s\n\n", stringOrNA(result.ReportDatetime))

	// Display facility information
	fmt.Println("FACILITY INFORMATION:")
	fmt.Printf("  Lab Name: %s\n", stringOrNA(result.LabName))
	fmt.Printf("  Lab Address: %s\n", stringOrNA(result.LabAddress))
	fmt.Printf("  Lab Director: %s\n", stringOrNA(result.LabDirector))
	fmt.Printf("  CLIA Number: %s\n", stringOrNA(result.LabCliaNumber))
	fmt.Printf("  Hospital: %s\n", stringOrNA(result.HospitalName))
	fmt.Printf("  Hospital Address: %s\n", stringOrNA(result.HospitalAddress))
	fmt.Printf("  Hospital Phone: %s\n\n", stringOrNA(result.HospitalPhone))

	// Display lab results
	fmt.Println("LAB TEST RESULTS:")
	if len(result.LabResults) > 0 {
		fmt.Printf("Total Tests: %d\n\n", len(result.LabResults))

		// Group by panel if available
		byPanel := make(map[string][]LabResultItem)
		for _, test := range result.LabResults {
			panel := "OTHER"
			if test.PanelName != nil {
				panel = *test.PanelName
			}
			byPanel[panel] = append(byPanel[panel], test)
		}

		for panel, tests := range byPanel {
			fmt.Printf("  %s:\n", panel)
			for _, test := range tests {
				flag := ""
				if test.Flag != nil {
					flag = fmt.Sprintf(" [%s]", *test.Flag)
				}
				units := ""
				if test.Units != nil {
					units = fmt.Sprintf(" %s", *test.Units)
				}
				refRange := ""
				if test.ReferenceRange != nil {
					refRange = fmt.Sprintf(" (ref: %s)", *test.ReferenceRange)
				}
				fmt.Printf("    • %s: %s%s%s%s\n", stringOrNA(test.TestName), stringOrNA(test.ResultValue), units, refRange, flag)
				if test.Interpretation != nil {
					fmt.Printf("      Note: %s\n", *test.Interpretation)
				}
			}
			fmt.Println()
		}
	} else {
		fmt.Println("  No lab results extracted.\n")
	}

	// Return the full structured result for programmatic use
	parseChunks := make([]string, 0)
	for _, chunk := range parseRun.Output.Chunks {
		parseChunks = append(parseChunks, chunk.Content)
	}

	return map[string]interface{}{
		"status":      "success",
		"data":        result,
		"parseChunks": parseChunks,
	}, nil
}

func stringOrNA(s *string) string {
	if s == nil {
		return "N/A"
	}
	return *s
}

func intOrNA(i *int) string {
	if i == nil {
		return "N/A"
	}
	return fmt.Sprintf("%d", *i)
}

func main() {
	flag.Parse()
	args := flag.Args()

	if len(args) == 0 {
		fmt.Fprintln(os.Stderr, "Usage: solution <path-to-pdf>")
		os.Exit(1)
	}

	filePath := args[0]

	result, err := processLabResultsExtractor(filePath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}

	fmt.Println("\n=== RAW JSON OUTPUT ===")
	jsonBytes, err := json.MarshalIndent(result["data"], "", "  ")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error marshaling JSON: %v\n", err)
		os.Exit(1)
	}
	fmt.Println(string(jsonBytes))
}
// Deploy the "Lab Results Extractor" pipeline to YOUR Extend account.
//
// The workflow below is fully self-contained — every EXTRACT/CLASSIFY/SPLIT
// step carries its extractor/classifier/splitter config INLINE, so this is a
// single API call. No processors to create or wire up beforehand.
// Idempotent: the created workflow id is cached in .extend/lab-results-extractor.json,
// so re-running updates the existing workflow instead of duplicating it.
//
// Usage:
//   export EXTEND_API_KEY=sk_...   (from https://dashboard.extend.ai → API Keys)
//   npx tsx provision.ts
//
// Generated by doc1 (template: lab-results-extractor).

import fs from "node:fs";
import path from "node:path";

const API = "https://api.extend.ai";
const VERSION = "2026-02-09";
const API_KEY = process.env.EXTEND_API_KEY;
if (!API_KEY) { console.error("Set EXTEND_API_KEY first."); process.exit(1); }

const STATE_DIR = path.join(process.cwd(), ".extend");
const STATE_FILE = path.join(STATE_DIR, "lab-results-extractor.json");

type State = { workflowId?: string };
const state: State = fs.existsSync(STATE_FILE)
  ? JSON.parse(fs.readFileSync(STATE_FILE, "utf8"))
  : {};
function saveState() {
  fs.mkdirSync(STATE_DIR, { recursive: true });
  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}

async function api(method: string, pathName: string, body?: unknown) {
  const res = await fetch(API + pathName, {
    method,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "x-extend-api-version": VERSION,
      ...(body ? { "Content-Type": "application/json" } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(`${method} ${pathName} failed (${res.status}): ${JSON.stringify(data).slice(0, 300)}`);
  return data;
}

// ── Workflow definition — extractor/classifier/splitter configs inline ──────
const WORKFLOW = {
  "name": "Lab Results Extractor Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": true
              },
              "signatureDetectionEnabled": true
            },
            "tables": {
              "agentic": {
                "enabled": true
              },
              "tableHeaderContinuationEnabled": true
            },
            "figures": {
              "enabled": true
            }
          },
          "chunkingStrategy": {
            "type": "page",
            "options": {}
          }
        }
      },
      "next": [
        {
          "step": "extraction2"
        }
      ]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "properties": {
              "age": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Patient's age in years"
              },
              "gender": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Patient gender (M/F)"
              },
              "patient_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Unique patient identifier"
              },
              "lab_results": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "test_name": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Name of the lab test"
                    },
                    "result_value": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Numerical or descriptive result value"
                    },
                    "reference_range": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Normal reference range for the test"
                    }
                  }
                },
                "description": "Array of individual lab test results"
              },
              "specimen_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Unique specimen identifier"
              },
              "patient_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full name of the patient"
              },
              "date_of_birth": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Patient's date of birth in MM/DD/YYYY format"
              },
              "report_status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Status of the report (e.g., Final, Preliminary)"
              },
              "fasting_status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Whether patient was fasting (Y/N)"
              },
              "report_datetime": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Date and time report was finalized in MM/DD/YYYY / HH:MM format"
              },
              "collection_datetime": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Date and time specimen was collected in MM/DD/YYYY / HH:MM TZ format"
              }
            }
          }
        }
      }
    }
  ]
};

async function main() {
  console.log(`Deploying "${WORKFLOW.name}"…`);

  if (state.workflowId) {
    console.log(`✓ workflow already provisioned (${state.workflowId}) — updating steps`);
    await api("POST", `/workflows/${state.workflowId}`, { steps: WORKFLOW.steps });
  } else {
    // Reuse an existing workflow with the same name if one exists (e.g. a
    // previous run's state file was lost) instead of creating a duplicate.
    try {
      const list = await api("GET", `/workflows?name=${encodeURIComponent(WORKFLOW.name)}`);
      const items = (list.data ?? list.items ?? []) as Array<{ name?: string; id?: string }>;
      const existing = items.find((x) => x.name === WORKFLOW.name);
      if (existing?.id) {
        state.workflowId = existing.id; saveState();
        console.log(`✓ workflow "${WORKFLOW.name}" found in your account (${existing.id}) — updating steps`);
        await api("POST", `/workflows/${existing.id}`, { steps: WORKFLOW.steps });
      }
    } catch { /* lookup is best-effort; fall through to create */ }

    if (!state.workflowId) {
      const created = await api("POST", "/workflows", WORKFLOW);
      const wfId = created.id ?? created.workflow?.id;
      if (!wfId) throw new Error("Could not read created workflow id from response");
      state.workflowId = wfId; saveState();
      console.log(`+ created workflow (${wfId})`);
    }
  }

  // Deploy the current draft as a new version so the workflow is runnable —
  // best-effort: some accounts/plans may not require this explicit step.
  await api("POST", `/workflows/${state.workflowId}/versions`, {}).catch(() => {});

  console.log("\nDone. Run documents through it with:");
  console.log(`  POST ${API}/workflow_runs  { workflow: { id: "${state.workflowId}" }, file: { url: "https://…" } }`);
  console.log("Or open the workflow in the Extend dashboard to review and deploy it.");
}

main().catch((e) => { console.error(e.message ?? e); process.exit(1); });
import json
import os
import sys
from pathlib import Path
from typing import Any, Optional, TypedDict
import httpx

API = "https://api.extend.ai"
VERSION = "2026-02-09"
API_KEY = os.environ.get("EXTEND_API_KEY")
if not API_KEY:
    print("Set EXTEND_API_KEY first.", file=sys.stderr)
    sys.exit(1)

STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "lab-results-extractor.json"


class State(TypedDict, total=False):
    workflowId: Optional[str]


state: State = {}
if STATE_FILE.exists():
    state = json.loads(STATE_FILE.read_text())


def save_state() -> None:
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, indent=2))


def api(method: str, path_name: str, body: Optional[Any] = None) -> Any:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "x-extend-api-version": VERSION,
    }
    if body:
        headers["Content-Type"] = "application/json"

    response = httpx.request(
        method,
        API + path_name,
        headers=headers,
        json=body if body else None,
    )
    try:
        data = response.json()
    except Exception:
        data = {}

    if not response.is_success:
        error_msg = json.dumps(data)[:300]
        raise RuntimeError(f"{method} {path_name} failed ({response.status_code}): {error_msg}")

    return data


WORKFLOW = {
    "name": "Lab Results Extractor Processing Pipeline",
    "steps": [
        {
            "name": "startTrigger1",
            "type": "TRIGGER",
            "next": [{"step": "parse1"}],
        },
        {
            "name": "parse1",
            "type": "PARSE",
            "config": {
                "parseConfig": {
                    "blockOptions": {
                        "text": {
                            "agentic": {"enabled": True},
                            "signatureDetectionEnabled": True,
                        },
                        "tables": {
                            "agentic": {"enabled": True},
                            "tableHeaderContinuationEnabled": True,
                        },
                        "figures": {"enabled": True},
                    },
                    "chunkingStrategy": {"type": "page", "options": {}},
                }
            },
            "next": [{"step": "extraction2"}],
        },
        {
            "name": "extraction2",
            "type": "EXTRACT",
            "config": {
                "extractorConfig": {
                    "schema": {
                        "type": "object",
                        "properties": {
                            "age": {
                                "type": ["integer", "null"],
                                "description": "Patient's age in years",
                            },
                            "gender": {
                                "type": ["string", "null"],
                                "description": "Patient gender (M/F)",
                            },
                            "patient_id": {
                                "type": ["string", "null"],
                                "description": "Unique patient identifier",
                            },
                            "lab_results": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "test_name": {
                                            "type": ["string", "null"],
                                            "description": "Name of the lab test",
                                        },
                                        "result_value": {
                                            "type": ["string", "null"],
                                            "description": "Numerical or descriptive result value",
                                        },
                                        "reference_range": {
                                            "type": ["string", "null"],
                                            "description": "Normal reference range for the test",
                                        },
                                    },
                                },
                                "description": "Array of individual lab test results",
                            },
                            "specimen_id": {
                                "type": ["string", "null"],
                                "description": "Unique specimen identifier",
                            },
                            "patient_name": {
                                "type": ["string", "null"],
                                "description": "Full name of the patient",
                            },
                            "date_of_birth": {
                                "type": ["string", "null"],
                                "description": "Patient's date of birth in MM/DD/YYYY format",
                            },
                            "report_status": {
                                "type": ["string", "null"],
                                "description": "Status of the report (e.g., Final, Preliminary)",
                            },
                            "fasting_status": {
                                "type": ["string", "null"],
                                "description": "Whether patient was fasting (Y/N)",
                            },
                            "report_datetime": {
                                "type": ["string", "null"],
                                "description": "Date and time report was finalized in MM/DD/YYYY / HH:MM format",
                            },
                            "collection_datetime": {
                                "type": ["string", "null"],
                                "description": "Date and time specimen was collected in MM/DD/YYYY / HH:MM TZ format",
                            },
                        },
                    }
                }
            },
        },
    ],
}


def main() -> None:
    print(f'Deploying "{WORKFLOW["name"]}…')

    if state.get("workflowId"):
        print(f'✓ workflow already provisioned ({state["workflowId"]}) — updating steps')
        api("POST", f'/workflows/{state["workflowId"]}', {"steps": WORKFLOW["steps"]})
    else:
        try:
            list_response = api("GET", f'/workflows?name={WORKFLOW["name"]}')
            items = list_response.get("data") or list_response.get("items") or []
            existing = next(
                (x for x in items if x.get("name") == WORKFLOW["name"]),
                None,
            )
            if existing and existing.get("id"):
                state["workflowId"] = existing["id"]
                save_state()
                print(f'✓ workflow "{WORKFLOW["name"]}" found in your account ({existing["id"]}) — updating steps')
                api("POST", f'/workflows/{existing["id"]}', {"steps": WORKFLOW["steps"]})
        except Exception:
            pass

        if not state.get("workflowId"):
            created = api("POST", "/workflows", WORKFLOW)
            wf_id = created.get("id") or created.get("workflow", {}).get("id")
            if not wf_id:
                raise RuntimeError("Could not read created workflow id from response")
            state["workflowId"] = wf_id
            save_state()
            print(f"+ created workflow ({wf_id})")

    try:
        api("POST", f'/workflows/{state["workflowId"]}/versions', {})
    except Exception:
        pass

    print("\nDone. Run documents through it with:")
    print(f'  POST {API}/workflow_runs  {{ "workflow": {{ "id": "{state["workflowId"]}" }}, "file": {{ "url": "https://…" }} }}')
    print("Or open the workflow in the Extend dashboard to review and deploy it.")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(str(e), file=sys.stderr)
        sys.exit(1)
import java.io.*;
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.util.*;
import com.google.gson.*;

public class ProvisionLabResultsExtractor {
    private static final String API = "https://api.extend.ai";
    private static final String VERSION = "2026-02-09";
    private static final String API_KEY = System.getenv("EXTEND_API_KEY");
    private static final Path STATE_DIR = Paths.get(System.getProperty("user.dir"), ".extend");
    private static final Path STATE_FILE = STATE_DIR.resolve("lab-results-extractor.json");
    private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
    private static final HttpClient httpClient = HttpClient.newHttpClient();

    static class State {
        String workflowId;
    }

    static {
        if (API_KEY == null || API_KEY.isEmpty()) {
            System.err.println("Set EXTEND_API_KEY first.");
            System.exit(1);
        }
    }

    private static State loadState() throws IOException {
        if (Files.exists(STATE_FILE)) {
            String json = Files.readString(STATE_FILE);
            return gson.fromJson(json, State.class);
        }
        return new State();
    }

    private static void saveState(State state) throws IOException {
        Files.createDirectories(STATE_DIR);
        Files.writeString(STATE_FILE, gson.toJson(state));
    }

    private static JsonElement api(String method, String pathName, JsonElement body) throws Exception {
        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(API + pathName))
                .header("Authorization", "Bearer " + API_KEY)
                .header("x-extend-api-version", VERSION);

        if (body != null) {
            requestBuilder.method(method, HttpRequest.BodyPublishers.ofString(gson.toJson(body)))
                    .header("Content-Type", "application/json");
        } else {
            requestBuilder.method(method, HttpRequest.BodyPublishers.noBody());
        }

        HttpResponse<String> response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
        JsonElement data = JsonParser.parseString(response.body().isEmpty() ? "{}" : response.body());

        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            String errorMsg = data.toString();
            if (errorMsg.length() > 300) errorMsg = errorMsg.substring(0, 300);
            throw new Exception(method + " " + pathName + " failed (" + response.statusCode() + "): " + errorMsg);
        }
        return data;
    }

    private static JsonElement buildWorkflow() {
        JsonObject workflow = new JsonObject();
        workflow.addProperty("name", "Lab Results Extractor Processing Pipeline");

        JsonArray steps = new JsonArray();

        JsonObject trigger = new JsonObject();
        trigger.addProperty("name", "startTrigger1");
        trigger.addProperty("type", "TRIGGER");
        JsonArray triggerNext = new JsonArray();
        JsonObject triggerNextItem = new JsonObject();
        triggerNextItem.addProperty("step", "parse1");
        triggerNext.add(triggerNextItem);
        trigger.add("next", triggerNext);
        steps.add(trigger);

        JsonObject parse = new JsonObject();
        parse.addProperty("name", "parse1");
        parse.addProperty("type", "PARSE");
        JsonObject parseConfig = new JsonObject();
        JsonObject parseOptions = new JsonObject();
        JsonObject blockOptions = new JsonObject();
        JsonObject textBlock = new JsonObject();
        JsonObject textAgentic = new JsonObject();
        textAgentic.addProperty("enabled", true);
        textBlock.add("agentic", textAgentic);
        textBlock.addProperty("signatureDetectionEnabled", true);
        blockOptions.add("text", textBlock);
        JsonObject tablesBlock = new JsonObject();
        JsonObject tablesAgentic = new JsonObject();
        tablesAgentic.addProperty("enabled", true);
        tablesBlock.add("agentic", tablesAgentic);
        tablesBlock.addProperty("tableHeaderContinuationEnabled", true);
        blockOptions.add("tables", tablesBlock);
        JsonObject figuresBlock = new JsonObject();
        figuresBlock.addProperty("enabled", true);
        blockOptions.add("figures", figuresBlock);
        parseOptions.add("blockOptions", blockOptions);
        JsonObject chunkingStrategy = new JsonObject();
        chunkingStrategy.addProperty("type", "page");
        chunkingStrategy.add("options", new JsonObject());
        parseOptions.add("chunkingStrategy", chunkingStrategy);
        parseConfig.add("parseConfig", parseOptions);
        parse.add("config", parseConfig);
        JsonArray parseNext = new JsonArray();
        JsonObject parseNextItem = new JsonObject();
        parseNextItem.addProperty("step", "extraction2");
        parseNext.add(parseNextItem);
        parse.add("next", parseNext);
        steps.add(parse);

        JsonObject extraction = new JsonObject();
        extraction.addProperty("name", "extraction2");
        extraction.addProperty("type", "EXTRACT");
        JsonObject extractionConfig = new JsonObject();
        JsonObject extractorConfig = new JsonObject();
        JsonObject schema = new JsonObject();
        schema.addProperty("type", "object");
        JsonObject properties = new JsonObject();

        properties.add("age", buildProperty("integer", "Patient's age in years"));
        properties.add("gender", buildProperty("string", "Patient gender (M/F)"));
        properties.add("patient_id", buildProperty("string", "Unique patient identifier"));

        JsonObject labResults = new JsonObject();
        labResults.add("type", gson.toJsonTree(new String[]{"array"}));
        JsonObject labResultsItems = new JsonObject();
        labResultsItems.addProperty("type", "object");
        JsonObject labResultsProperties = new JsonObject();
        labResultsProperties.add("test_name", buildProperty("string", "Name of the lab test"));
        labResultsProperties.add("result_value", buildProperty("string", "Numerical or descriptive result value"));
        labResultsProperties.add("reference_range", buildProperty("string", "Normal reference range for the test"));
        labResultsItems.add("properties", labResultsProperties);
        labResults.add("items", labResultsItems);
        labResults.addProperty("description", "Array of individual lab test results");
        properties.add("lab_results", labResults);

        properties.add("specimen_id", buildProperty("string", "Unique specimen identifier"));
        properties.add("patient_name", buildProperty("string", "Full name of the patient"));
        properties.add("date_of_birth", buildProperty("string", "Patient's date of birth in MM/DD/YYYY format"));
        properties.add("report_status", buildProperty("string", "Status of the report (e.g., Final, Preliminary)"));
        properties.add("fasting_status", buildProperty("string", "Whether patient was fasting (Y/N)"));
        properties.add("report_datetime", buildProperty("string", "Date and time report was finalized in MM/DD/YYYY / HH:MM format"));
        properties.add("collection_datetime", buildProperty("string", "Date and time specimen was collected in MM/DD/YYYY / HH:MM TZ format"));

        schema.add("properties", properties);
        extractorConfig.add("schema", schema);
        extractionConfig.add("extractorConfig", extractorConfig);
        extraction.add("config", extractionConfig);
        steps.add(extraction);

        workflow.add("steps", steps);
        return workflow;
    }

    private static JsonObject buildProperty(String type, String description) {
        JsonObject prop = new JsonObject();
        prop.add("type", gson.toJsonTree(new String[]{type, "null"}));
        prop.addProperty("description", description);
        return prop;
    }

    public static void main(String[] args) {
        try {
            State state = loadState();
            JsonElement workflow = buildWorkflow();
            String workflowName = workflow.getAsJsonObject().get("name").getAsString();
            JsonArray steps = workflow.getAsJsonObject().getAsJsonArray("steps");

            System.out.println("Deploying \"" + workflowName + "\"…");

            if (state.workflowId != null && !state.workflowId.isEmpty()) {
                System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
                JsonObject updateBody = new JsonObject();
                updateBody.add("steps", steps);
                api("POST", "/workflows/" + state.workflowId, updateBody);
            } else {
                try {
                    String listUrl = "/workflows?name=" + java.net.URLEncoder.encode(workflowName, java.nio.charset.StandardCharsets.UTF_8);
                    JsonElement listResponse = api("GET", listUrl, null);
                    JsonArray items = listResponse.getAsJsonObject().has("data")
                            ? listResponse.getAsJsonObject().getAsJsonArray("data")
                            : listResponse.getAsJsonObject().getAsJsonArray("items");

                    for (JsonElement item : items) {
                        if (item.getAsJsonObject().has("name") &&
                                item.getAsJsonObject().get("name").getAsString().equals(workflowName) &&
                                item.getAsJsonObject().has("id")) {
                            String existingId = item.getAsJsonObject().get("id").getAsString();
                            state.workflowId = existingId;
                            saveState(state);
                            System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + existingId + ") — updating steps");
                            JsonObject updateBody = new JsonObject();
                            updateBody.add("steps", steps);
                            api("POST", "/workflows/" + existingId, updateBody);
                            break;
                        }
                    }
                } catch (Exception e) {
                    // lookup is best-effort; fall through to create
                }

                if (state.workflowId == null || state.workflowId.isEmpty()) {
                    JsonElement created = api("POST", "/workflows", workflow);
                    String wfId = null;
                    if (created.getAsJsonObject().has("id")) {
                        wfId = created.getAsJsonObject().get("id").getAsString();
                    } else if (created.getAsJsonObject().has("workflow")) {
                        wfId = created.getAsJsonObject().getAsJsonObject("workflow").get("id").getAsString();
                    }
                    if (wfId == null) {
                        throw new Exception("Could not read created workflow id from response");
                    }
                    state.workflowId = wfId;
                    saveState(state);
                    System.out.println("+ created workflow (" + wfId + ")");
                }
            }

            try {
                api("POST", "/workflows/" + state.workflowId + "/versions", new JsonObject());
            } catch (Exception e) {
                // best-effort: some accounts/plans may not require this explicit step
            }

            System.out.println("\nDone. Run documents through it with:");
            System.out.println("  POST " + API + "/workflow_runs  { workflow: { id: \"" + state.workflowId + "\" }, file: { url: \"https://…\" } }");
            System.out.println("Or open the workflow in the Extend dashboard to review and deploy it.");
        } catch (Exception e) {
            System.err.println(e.getMessage() != null ? e.getMessage() : e);
            System.exit(1);
        }
    }
}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
)

const (
	API     = "https://api.extend.ai"
	VERSION = "2026-02-09"
)

type State struct {
	WorkflowID string `json:"workflowId,omitempty"`
}

var (
	apiKey   string
	stateDir string
	stateFile string
	state     State
)

func init() {
	apiKey = os.Getenv("EXTEND_API_KEY")
	if apiKey == "" {
		fmt.Fprintf(os.Stderr, "Set EXTEND_API_KEY first.\n")
		os.Exit(1)
	}

	wd, err := os.Getwd()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to get working directory: %v\n", err)
		os.Exit(1)
	}

	stateDir = filepath.Join(wd, ".extend")
	stateFile = filepath.Join(stateDir, "lab-results-extractor.json")

	// Load existing state if available
	if data, err := os.ReadFile(stateFile); err == nil {
		json.Unmarshal(data, &state)
	}
}

func saveState() error {
	if err := os.MkdirAll(stateDir, 0755); err != nil {
		return err
	}
	data, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(stateFile, data, 0644)
}

func apiCall(method, pathName string, body interface{}) (map[string]interface{}, error) {
	url := API + pathName
	var bodyReader io.Reader

	if body != nil {
		bodyBytes, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		bodyReader = bytes.NewReader(bodyBytes)
	}

	req, err := http.NewRequest(method, url, bodyReader)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
	req.Header.Set("x-extend-api-version", VERSION)
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var data map[string]interface{}
	json.Unmarshal(respBody, &data)

	if !resp.Ok {
		errMsg := string(respBody)
		if len(errMsg) > 300 {
			errMsg = errMsg[:300]
		}
		return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, errMsg)
	}

	return data, nil
}

var workflow = map[string]interface{}{
	"name": "Lab Results Extractor Processing Pipeline",
	"steps": []map[string]interface{}{
		{
			"name": "startTrigger1",
			"type": "TRIGGER",
			"next": []map[string]interface{}{
				{"step": "parse1"},
			},
		},
		{
			"name": "parse1",
			"type": "PARSE",
			"config": map[string]interface{}{
				"parseConfig": map[string]interface{}{
					"blockOptions": map[string]interface{}{
						"text": map[string]interface{}{
							"agentic": map[string]interface{}{
								"enabled": true,
							},
							"signatureDetectionEnabled": true,
						},
						"tables": map[string]interface{}{
							"agentic": map[string]interface{}{
								"enabled": true,
							},
							"tableHeaderContinuationEnabled": true,
						},
						"figures": map[string]interface{}{
							"enabled": true,
						},
					},
					"chunkingStrategy": map[string]interface{}{
						"type":    "page",
						"options": map[string]interface{}{},
					},
				},
			},
			"next": []map[string]interface{}{
				{"step": "extraction2"},
			},
		},
		{
			"name": "extraction2",
			"type": "EXTRACT",
			"config": map[string]interface{}{
				"extractorConfig": map[string]interface{}{
					"schema": map[string]interface{}{
						"type": "object",
						"properties": map[string]interface{}{
							"age": map[string]interface{}{
								"type":        []string{"integer", "null"},
								"description": "Patient's age in years",
							},
							"gender": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Patient gender (M/F)",
							},
							"patient_id": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Unique patient identifier",
							},
							"lab_results": map[string]interface{}{
								"type": "array",
								"items": map[string]interface{}{
									"type": "object",
									"properties": map[string]interface{}{
										"test_name": map[string]interface{}{
											"type":        []string{"string", "null"},
											"description": "Name of the lab test",
										},
										"result_value": map[string]interface{}{
											"type":        []string{"string", "null"},
											"description": "Numerical or descriptive result value",
										},
										"reference_range": map[string]interface{}{
											"type":        []string{"string", "null"},
											"description": "Normal reference range for the test",
										},
									},
								},
								"description": "Array of individual lab test results",
							},
							"specimen_id": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Unique specimen identifier",
							},
							"patient_name": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Full name of the patient",
							},
							"date_of_birth": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Patient's date of birth in MM/DD/YYYY format",
							},
							"report_status": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Status of the report (e.g., Final, Preliminary)",
							},
							"fasting_status": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Whether patient was fasting (Y/N)",
							},
							"report_datetime": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Date and time report was finalized in MM/DD/YYYY / HH:MM format",
							},
							"collection_datetime": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Date and time specimen was collected in MM/DD/YYYY / HH:MM TZ format",
							},
						},
					},
				},
			},
		},
	},
}

func main() {
	workflowName := workflow["name"].(string)
	fmt.Printf("Deploying \"%s\"…\n", workflowName)

	if state.WorkflowID != "" {
		fmt.Printf("✓ workflow already provisioned (%s) — updating steps\n", state.WorkflowID)
		steps := workflow["steps"]
		_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", state.WorkflowID), map[string]interface{}{"steps": steps})
		if err != nil {
			fmt.Fprintf(os.Stderr, "%v\n", err)
			os.Exit(1)
		}
	} else {
		// Try to reuse an existing workflow with the same name
		listURL := fmt.Sprintf("/workflows?name=%s", url.QueryEscape(workflowName))
		list, err := apiCall("GET", listURL, nil)

		if err == nil {
			var items []map[string]interface{}
			if data, ok := list["data"].([]interface{}); ok {
				for _, item := range data {
					items = append(items, item.(map[string]interface{}))
				}
			} else if data, ok := list["items"].([]interface{}); ok {
				for _, item := range data {
					items = append(items, item.(map[string]interface{}))
				}
			}

			for _, item := range items {
				if name, ok := item["name"].(string); ok && name == workflowName {
					if id, ok := item["id"].(string); ok {
						state.WorkflowID = id
						saveState()
						fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", workflowName, id)
						steps := workflow["steps"]
						_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", id), map[string]interface{}{"steps": steps})
						if err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
							os.Exit(1)
						}
						break
					}
				}
			}
		}

		if state.WorkflowID == "" {
			created, err := apiCall("POST", "/workflows", workflow)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%v\n", err)
				os.Exit(1)
			}

			wfID := ""
			if id, ok := created["id"].(string); ok {
				wfID = id
			} else if wf, ok := created["workflow"].(map[string]interface{}); ok {
				if id, ok := wf["id"].(string); ok {
					wfID = id
				}
			}

			if wfID == "" {
				fmt.Fprintf(os.Stderr, "Could not read created workflow id from response\n")
				os.Exit(1)
			}

			state.WorkflowID = wfID
			saveState()
			fmt.Printf("+ created workflow (%s)\n", wfID)
		}
	}

	// Deploy the current draft as a new version
	apiCall("POST", fmt.Sprintf("/workflows/%s/versions", state.WorkflowID), map[string]interface{}{})

	fmt.Println("\nDone. Run documents through it with:")
	fmt.Printf("  POST %s/workflow_runs  { workflow: { id: \"%s\" }, file: { url: \"https://…\" } }\n", API, state.WorkflowID)
	fmt.Println("Or open the workflow in the Extend dashboard to review and deploy it.")
}

Frequently Asked Questions (FAQ)

Lab reports often mix digital headers with handwritten provider notes; use `mode: "agentic_ocr"` in the parse step to enable handwriting recognition, then extract from the resulting markdown. If confidence drops below 0.85 on critical fields, add human review gates before downstream systems consume the data.
Define your extraction schema with nullable string fields for `result_value`, `result_status` (e.g., 'high', 'normal', 'low'), and `reference_range` as separate properties—this lets the model map each value to its corresponding status and range independently. Write descriptions like: `result_status: 'whether the result falls within normal, high, or low range based on the reference interval shown'`.
Use async (`parseRuns.createAndPoll`) for batches >10 documents or reports >20 pages, since lab result PDFs often contain multi-page panels with dense tables; For real-time single-document APIs, sync is acceptable if you set a 30-second timeout and fall back to async on failure.
In your extraction schema, use type hints like `{ type: "string", description: "Numeric result value, e.g. '7.2' or '142 mg/dL'" }` to guide the model.
Tags
Laboratory ResultsPatient DataClinical TestingMedical RecordsDiagnostic Report
About this template

This template processes clinical laboratory results from diagnostic facilities like Quest Diagnostics, Labcorp, BiorReference, etc. It captures patient demographics, specimen details, and comprehensive metabolic panel results with reference ranges. Ideal for engineering teams building tools that require extracting patient test data and medical records.

Document formats
  • PDF
  • Images & Scans
Requirements
  • Long tables
  • Scanned documents
  • Complex layouts