Government & Public SectorParse → Classify → Extract

Driver's License Classifier

Classifies if a document is a driver's license, and then extracts personal and license details.

Ship it with Extend

Live pipeline

a real document, processed end to end · view only
Source documentDriversLicensePicture.jpeg

Step-by-step

A driver's license is a government-issued identity document that verifies an individual's legal right to operate a motor vehicle and contains personal identification, physical characteristics, license class, restrictions, and expiration information. This template takes in Driver's License and outputs markdown (.md) capturing the document's full text and layout, and JSON (.json) with structured identity and licensing fields including name, license number, class, expiration date, and physical characteristics per the extraction schema by using Extend's Parse, Classify, Extract primitives.

Input
Driver's License
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

Converts the document into clean, layout-aware markdown plus structured blocks with spatial metadata.

InputSource document — PDF, image, spreadsheet, presentation, or scan
Config
blockOptions.text.agentic.enabledtruechanged
chunkingStrategy.type"document"
engine"parse_performance"
OutputMarkdown chunked by page or section, plus typed blocks (text, table, figure) with bounding boxes

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

Step 2

Classify

Assigns the document to one of a set of caller-defined categories.

InputOutput of the Parse step
Config
classifications2 custom categorieschanged
advancedOptions.advancedMultimodalEnabledtrue
baseProcessor"classification_performance"
OutputMatched category ID and type, a confidence score, and the reasoning behind the decision

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

Step 3

Extract

Pulls a defined set of fields from the document and returns them as structured JSON matching a schema.

InputOutput of the Classify step
Config
schemacustom schema — 12 fieldschanged
advancedOptions.advancedMultimodalEnabledtruechanged
advancedOptions.reviewAgent.enabledtruechanged
baseProcessor"extraction_performance"
OutputJSON shaped to the extraction schema, with per-field confidence scores and citations grounding each value to its source location

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

Example code

{
  "name": "Driver License Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": true
              }
            }
          },
          "chunkingStrategy": {
            "type": "document"
          }
        }
      },
      "next": [
        {
          "step": "classify2"
        }
      ]
    },
    {
      "name": "classify2",
      "type": "CLASSIFY",
      "config": {
        "classifierConfig": {
          "classifications": [
            {
              "id": "classification1",
              "type": "other",
              "description": "Use the `other` document type when the provided document can not clearly be classified into one of the described classifications."
            },
            {
              "id": "classification_dNv",
              "type": "drivers_license",
              "description": "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways"
            }
          ],
          "baseProcessor": "classification_performance",
          "advancedOptions": {
            "advancedMultimodalEnabled": true
          }
        }
      },
      "next": [
        {
          "step": "extraction3",
          "classificationId": "classification1"
        },
        {
          "step": "extraction3",
          "classificationId": "classification_dNv"
        }
      ]
    },
    {
      "name": "extraction3",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "properties": {
              "sex": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Gender (M/F)"
              },
              "class": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Driver license class or type"
              },
              "height": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Height of license holder"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full residential address"
              },
              "eye_color": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Eye color"
              },
              "full_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full name of the license holder"
              },
              "issue_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License issue date in MM/DD/YYYY format"
              },
              "endorsements": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License endorsements if any"
              },
              "restrictions": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License restrictions if any"
              },
              "date_of_birth": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Date of birth in MM/DD/YYYY format"
              },
              "license_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Unique driver license number"
              },
              "expiration_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License expiration date in MM/DD/YYYY format"
              }
            }
          },
          "baseProcessor": "extraction_performance",
          "advancedOptions": {
            "reviewAgent": {
              "enabled": true
            },
            "advancedMultimodalEnabled": true
          }
        }
      }
    }
  ]
}
# Driver License Processing — Extend AI Skill

## What this pipeline does

Converts a scanned or digital driver license into structured, verified data. The pipeline parses the document to markdown for readability, classifies it to confirm it's a valid driver license (vs. other ID types), then extracts 11 key fields (name, license number, dates, address, physical descriptors, endorsements, restrictions) using high-accuracy extraction with built-in review agent verification.

## When to use this

- **Identity verification workflows**: KYC (know your customer) onboarding, account creation, age verification
- **Licensing compliance**: Verifying driver qualifications before granting access to restricted services
- **Document intake at scale**: Processing batches of uploaded driver licenses for insurance, rental, or loan applications
- **Hybrid manual-AI review**: Extraction with automatic review agent flagging anomalies (expired, mismatched fields) for human sign-off
- **Multi-state license handling**: Standardizes output across all US state license formats and designs

## Processor pipeline

### Step 1: Parse (`parse_performance` + agentic OCR)
**Purpose**: Convert the driver license image/PDF into machine-readable markdown and capture spatial structure.

**Config rationale**:
- `baseProcessor: parse_performance` — handles both high-quality scans and lower-res phone photos without degradation
- `blockOptions.text.agentic.enabled: true` — enables optical character recognition optimized for small text, MRZ (machine-readable zones), and degraded images common in driver licenses
- `chunkingStrategy: document` — treats the entire license as one logical block (driver licenses are single-page, compact documents)

**Output**: Markdown text with all visible fields, ready for classification and extraction downstream.

---

### Step 2: Classify (`classification_performance` + multimodal)
**Purpose**: Verify the document is actually a driver license before expensive extraction; reject other ID types (passport, state ID, fake).

**Config rationale**:
- `baseProcessor: classification_performance` — high-accuracy classification balancing speed and confidence
- `advancedMultimodalEnabled: true` — uses both OCR text *and* visual layout cues (blue/gold background, license format) to classify; critical for distinguishing driver licenses from state IDs or other documents
- Two classifications:
  - `drivers_license` — official government ID authorizing vehicle operation
  - `other` — catch-all for non-driver-license IDs (passport, state ID, library card, etc.)

**Output**: Classification type and confidence score; router sends matched documents to extraction, rejects non-licenses.

---

### Step 3: Extract (`extraction_performance` + review agent)
**Purpose**: Populate 11 structured fields (name, license number, DOB, expiration, address, etc.) into JSON.

**Config rationale**:
- `baseProcessor: extraction_performance` — highest accuracy for this schema; uses multimodal context to resolve ambiguities (e.g., handwritten corrections, state-specific abbreviations)
- `advancedMultimodalEnabled: true` — cross-references OCR text with visual position on the card (ensures "DOB" field is read from the correct labeled section, not misread from endorsements)
- `reviewAgent.enabled: true` — automatic post-extraction validation:
  - Flags mismatched dates (issue date > expiration date, DOB inconsistent with age)
  - Detects expired licenses
  - Validates license number format (state-specific checksums where applicable)
  - Highlights missing or null-returned fields for manual review

**Output**: Fully typed JSON object with all 11 fields; review agent flags added to metadata.

---

## TypeScript implementation

```typescript
import { ExtendClient, extendDate } from "extend-ai";
import { z } from "zod";
import fs from "fs";
import path from "path";

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

// Zod schema for driver license extraction.
// Note: dates are ISO strings; review agent will flag if expiration < today or issue > expiration.
const driverLicenseSchema = z.object({
  full_name: z.string().nullable().describe(
    "Full legal name of the license holder as printed on the front of the card"
  ),
  license_number: z.string().nullable().describe(
    "Unique driver license number issued by the state DMV"
  ),
  date_of_birth: z.string().nullable().describe(
    "Date of birth in MM/DD/YYYY format (e.g., 03/15/1990)"
  ),
  expiration_date: z.string().nullable().describe(
    "License expiration date in MM/DD/YYYY format (e.g., 06/30/2028)"
  ),
  issue_date: z.string().nullable().describe(
    "License issue date in MM/DD/YYYY format (e.g., 07/01/2024)"
  ),
  address: z.string().nullable().describe(
    "Full residential address including street, city, state, and ZIP code"
  ),
  class: z.string().nullable().describe(
    "Driver license class or type (e.g., C, D, A, B) which determines vehicle weight and type restrictions"
  ),
  sex: z.string().nullable().describe(
    "Gender as listed on the license (M for male, F for female)"
  ),
  height: z.string().nullable().describe(
    "Physical height of the license holder (e.g., 5'11\", 180cm)"
  ),
  eye_color: z.string().nullable().describe(
    "Eye color (e.g., Blue, Brown, Green, Hazel)"
  ),
  endorsements: z.string().nullable().describe(
    "License endorsements if any (e.g., H for hazmat, P for passenger transport, M for motorcycle)"
  ),
  restrictions: z.string().nullable().describe(
    "License restrictions if any (e.g., B for corrective lenses, D for automatic transmission only)"
  ),
});

export async function processDriverLicense(filePath: string) {
  console.log(`[Driver License] Processing: ${filePath}`);

  // Convert local file to base64 data URL for SDK consumption.
  const fileBuffer = fs.readFileSync(filePath);
  const base64 = fileBuffer.toString("base64");
  const fileExt = path.extname(filePath).toLowerCase().slice(1) || "pdf";
  const mimeType =
    fileExt === "pdf"
      ? "application/pdf"
      : fileExt === "png"
        ? "image/png"
        : fileExt === "jpg" || fileExt === "jpeg"
          ? "image/jpeg"
          : "application/octet-stream";

  const dataUrl = `data:${mimeType};base64,${base64}`;

  // Step 1: Parse — extract markdown + layout structure.
  console.log("[1/3] Parsing driver license...");
  const parseRun = await client.parseRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      blockOptions: {
        text: {
          agentic: {
            enabled: true,
          },
        },
      },
      chunkingStrategy: {
        type: "document",
      },
    },
  });

  if (parseRun.status !== "PROCESSED") {
    throw new Error(
      `Parse failed with status: ${parseRun.status}. Error: ${parseRun.error?.message || "Unknown"}`
    );
  }

  const parsedMarkdown = parseRun.output.chunks
    .map((chunk) => chunk.content)
    .join("\n\n");
  console.log("[✓] Parse complete. Markdown length:", parsedMarkdown.length);

  // Step 2: Classify — confirm it's a driver license (not passport, state ID, etc.).
  console.log("[2/3] Classifying document...");
  const classifyRun = await client.classifyRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      classifications: [
        {
          id: "drivers_license",
          type: "drivers_license",
          description:
            "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways. It contains the holder's name, license number, DOB, address, and may include endorsements or restrictions.",
        },
        {
          id: "other",
          type: "other",
          description:
            "Use the `other` type when the provided document cannot be clearly classified as a driver's license (e.g., passport, state ID, military ID, library card).",
        },
      ],
      baseProcessor: "classification_performance",
      advancedOptions: {
        advancedMultimodalEnabled: true,
      },
    },
  });

  if (classifyRun.status !== "PROCESSED") {
    throw new Error(
      `Classify failed with status: ${classifyRun.status}. Error: ${classifyRun.error?.message || "Unknown"}`
    );
  }

  const classification = classifyRun.output;
  const isDriverLicense = classification.type === "drivers_license";
  const classificationConfidence = classification.confidence ?? 0;

  console.log(
    `[✓] Classification: ${classification.type} (confidence: ${(classificationConfidence * 100).toFixed(1)}%)`
  );

  if (!isDriverLicense) {
    console.warn(
      `[WARNING] Document classified as "${classification.type}" (not a driver license). Proceeding with extraction anyway.`
    );
  }

  // Step 3: Extract — pull all 11 fields into structured JSON.
  console.log("[3/3] Extracting driver license fields...");
  const extractRun = await client.extractRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      schema: driverLicenseSchema,
      baseProcessor: "extraction_performance",
      advancedOptions: {
        reviewAgent: {
          enabled: true, // Automatic post-extraction validation for date logic, format checks
        },
        advancedMultimodalEnabled: true,
      },
    },
  });

  if (extractRun.status !== "PROCESSED") {
    throw new Error(
      `Extract failed with status: ${extractRun.status}. Error: ${extractRun.error?.message || "Unknown"}`
    );
  }

  const extracted = extractRun.output.value;
  console.log("[✓] Extraction complete.");

  // Assemble final result.
  const result = {
    status: "success",
    classification: {
      type: classification.type,
      confidence: classificationConfidence,
    },
    extraction: extracted,
    review_agent: extractRun.output.review_agent || null, // Flags, warnings from review agent
    parsed_markdown: parsedMarkdown,
    timestamp: new Date().toISOString(),
  };

  console.log("\n=== DRIVER LICENSE EXTRACTION RESULT ===");
  console.log(JSON.stringify(result, null, 2));

  return result;
}

// Example invocation (uncomment to run standalone):
// const testFile = process.argv[2] || "./test_license.pdf";
// processDriverLicense(testFile).catch(console.error);
```

---

## CLI equivalent

Process a driver license file end-to-end using the Extend CLI (no code required):

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

# Step 1: Parse to markdown
extend parse driver_license.pdf > parsed.md
cat parsed.md

# Step 2: Classify (is it a driver license or other ID?)
extend classify driver_license.pdf --classifier-id cls_driver_license

# Step 3: Extract structured fields using the schema
extend extract driver_license.pdf \
  --schema schema.json \
  --advanced-options '{
    "reviewAgent": { "enabled": true },
    "advancedMultimodalEnabled": true
  }'

# Or run the entire pre-built workflow in one call:
extend run workflow_driver_license --file driver_license.pdf
```

**Schema file (`schema.json`) for CLI extraction:**

```json
{
  "type": "object",
  "properties": {
    "full_name": {
      "type": ["string", "null"],
      "description": "Full legal name of the license holder as printed on the front of the card"
    },
    "license_number": {
      "type": ["string", "null"],
      "description": "Unique driver license number issued by the state DMV"
    },
    "date_of_birth": {
      "type": ["string", "null"],
      "description": "Date of birth in MM/DD/YYYY format (e.g., 03/15/1990)"
    },
    "expiration_date": {
      "type": ["string", "null"],
      "description": "License expiration date in MM/DD/YYYY format (e.g., 06/30/2028)"
    },
    "issue_date": {
      "type": ["string", "null"],
      "description": "License issue date in MM/DD/YYYY format (e.g., 07/01/2024)"
    },
    "address": {
      "type": ["string", "null"],
      "description": "Full residential address including street, city, state, and ZIP code"
    },
    "class": {
      "type": ["string", "null"],
      "description": "Driver license class or type (e.g., C, D, A, B) which determines vehicle weight and type restrictions"
    },
    "sex": {
      "type": ["string", "null"],
      "description": "Gender as listed on the license (M for male, F for female)"
    },
    "height": {
      "type": ["string", "null"],
      "description": "Physical height of the license holder (e.g., 5'11\", 180cm)"
    },
    "eye_color": {
      "type": ["string", "null"],
      "description": "Eye color (e.g., Blue, Brown, Green, Hazel)"
    },
    "endorsements": {
      "type": ["string", "null"],
      "description": "License endorsements if any (e.g., H for hazmat, P for passenger transport, M for motorcycle)"
    },
    "restrictions": {
      "type": ["string", "null"],
      "description": "License restrictions if any (e.g., B for corrective lenses, D for automatic transmission only)"
    }
  }
}
```

---

## Schema

The extraction schema is purpose-built for driver licenses issued across all US states and territories. Each field uses nullable string to handle missing or illegible data gracefully.

```json
{
  "type": "object",
  "properties": {
    "full_name": {
      "type": ["string", "null"],
      "description": "Full legal name of the license holder as printed on the front of the card. Extract exactly as shown; do not abbreviate or reformat. Critical for identity matching."
    },
    "license_number": {
      "type": ["string", "null"],
      "description": "Unique driver license number issued by the state DMV. Format varies by state (may include letters, hyphens, check digits). Preserve exactly as printed."
    },
    "date_of_birth": {
      "type": ["string", "null"],
      "description": "Date of birth in MM/DD/YYYY format (e.g., 03/15/1990). Used for age verification and identity validation. Review agent will flag if inconsistent with license class rules (e.g., commercial class minimum age)."
    },
    "expiration_date": {
      "type": ["string", "null"],
      "description": "License expiration date in MM/DD/YYYY format (e.g., 06/30/2028). Review agent flags if date is in the past (expired license). Critical for compliance workflows."
    },
    "issue_date": {
      "type": ["string", "null"],
      "description": "License issue date in MM/DD/YYYY format (e.g., 07/01/2024). Review agent validates that issue_date <= expiration_date and issue_date <= today."
    },
    "address": {
      "type": ["string", "null"],
      "description": "Full residential address including street, city, state, and ZIP code. Some licenses abbreviate state; extract full address as printed. Used for fraud detection and address verification."
    },
    "class": {
      "type": ["string", "null"],
      "description": "Driver
import { ExtendClient, extendDate } from "extend-ai";
import { z } from "zod";
import fs from "fs";
import path from "path";

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

// Zod schema for driver license extraction.
// Note: dates are ISO strings; review agent will flag if expiration < today or issue > expiration.
const driverLicenseSchema = z.object({
  full_name: z.string().nullable().describe(
    "Full legal name of the license holder as printed on the front of the card"
  ),
  license_number: z.string().nullable().describe(
    "Unique driver license number issued by the state DMV"
  ),
  date_of_birth: z.string().nullable().describe(
    "Date of birth in MM/DD/YYYY format (e.g., 03/15/1990)"
  ),
  expiration_date: z.string().nullable().describe(
    "License expiration date in MM/DD/YYYY format (e.g., 06/30/2028)"
  ),
  issue_date: z.string().nullable().describe(
    "License issue date in MM/DD/YYYY format (e.g., 07/01/2024)"
  ),
  address: z.string().nullable().describe(
    "Full residential address including street, city, state, and ZIP code"
  ),
  class: z.string().nullable().describe(
    "Driver license class or type (e.g., C, D, A, B) which determines vehicle weight and type restrictions"
  ),
  sex: z.string().nullable().describe(
    "Gender as listed on the license (M for male, F for female)"
  ),
  height: z.string().nullable().describe(
    "Physical height of the license holder (e.g., 5'11\", 180cm)"
  ),
  eye_color: z.string().nullable().describe(
    "Eye color (e.g., Blue, Brown, Green, Hazel)"
  ),
  endorsements: z.string().nullable().describe(
    "License endorsements if any (e.g., H for hazmat, P for passenger transport, M for motorcycle)"
  ),
  restrictions: z.string().nullable().describe(
    "License restrictions if any (e.g., B for corrective lenses, D for automatic transmission only)"
  ),
});

export async function processDriverLicense(filePath: string) {
  console.log(`[Driver License] Processing: ${filePath}`);

  // Convert local file to base64 data URL for SDK consumption.
  const fileBuffer = fs.readFileSync(filePath);
  const base64 = fileBuffer.toString("base64");
  const fileExt = path.extname(filePath).toLowerCase().slice(1) || "pdf";
  const mimeType =
    fileExt === "pdf"
      ? "application/pdf"
      : fileExt === "png"
        ? "image/png"
        : fileExt === "jpg" || fileExt === "jpeg"
          ? "image/jpeg"
          : "application/octet-stream";

  const dataUrl = `data:${mimeType};base64,${base64}`;

  // Step 1: Parse — extract markdown + layout structure.
  console.log("[1/3] Parsing driver license...");
  const parseRun = await client.parseRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      blockOptions: {
        text: {
          agentic: {
            enabled: true,
          },
        },
      },
      chunkingStrategy: {
        type: "document",
      },
    },
  });

  if (parseRun.status !== "PROCESSED") {
    throw new Error(
      `Parse failed with status: ${parseRun.status}. Error: ${parseRun.error?.message || "Unknown"}`
    );
  }

  const parsedMarkdown = parseRun.output.chunks
    .map((chunk) => chunk.content)
    .join("\n\n");
  console.log("[✓] Parse complete. Markdown length:", parsedMarkdown.length);

  // Step 2: Classify — confirm it's a driver license (not passport, state ID, etc.).
  console.log("[2/3] Classifying document...");
  const classifyRun = await client.classifyRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      classifications: [
        {
          id: "drivers_license",
          type: "drivers_license",
          description:
            "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways. It contains the holder's name, license number, DOB, address, and may include endorsements or restrictions.",
        },
        {
          id: "other",
          type: "other",
          description:
            "Use the `other` type when the provided document cannot be clearly classified as a driver's license (e.g., passport, state ID, military ID, library card).",
        },
      ],
      baseProcessor: "classification_performance",
      advancedOptions: {
        advancedMultimodalEnabled: true,
      },
    },
  });

  if (classifyRun.status !== "PROCESSED") {
    throw new Error(
      `Classify failed with status: ${classifyRun.status}. Error: ${classifyRun.error?.message || "Unknown"}`
    );
  }

  const classification = classifyRun.output;
  const isDriverLicense = classification.type === "drivers_license";
  const classificationConfidence = classification.confidence ?? 0;

  console.log(
    `[✓] Classification: ${classification.type} (confidence: ${(classificationConfidence * 100).toFixed(1)}%)`
  );

  if (!isDriverLicense) {
    console.warn(
      `[WARNING] Document classified as "${classification.type}" (not a driver license). Proceeding with extraction anyway.`
    );
  }

  // Step 3: Extract — pull all 11 fields into structured JSON.
  console.log("[3/3] Extracting driver license fields...");
  const extractRun = await client.extractRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      schema: driverLicenseSchema,
      baseProcessor: "extraction_performance",
      advancedOptions: {
        reviewAgent: {
          enabled: true, // Automatic post-extraction validation for date logic, format checks
        },
        advancedMultimodalEnabled: true,
      },
    },
  });

  if (extractRun.status !== "PROCESSED") {
    throw new Error(
      `Extract failed with status: ${extractRun.status}. Error: ${extractRun.error?.message || "Unknown"}`
    );
  }

  const extracted = extractRun.output.value;
  console.log("[✓] Extraction complete.");

  // Assemble final result.
  const result = {
    status: "success",
    classification: {
      type: classification.type,
      confidence: classificationConfidence,
    },
    extraction: extracted,
    review_agent: extractRun.output.review_agent || null, // Flags, warnings from review agent
    parsed_markdown: parsedMarkdown,
    timestamp: new Date().toISOString(),
  };

  console.log("\n=== DRIVER LICENSE EXTRACTION RESULT ===");
  console.log(JSON.stringify(result, null, 2));

  return result;
}

// Example invocation (uncomment to run standalone):
// const testFile = process.argv[2] || "./test_license.pdf";
// processDriverLicense(testFile).catch(console.error);
import os
import base64
from pathlib import Path
from extend_ai import Extend

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

# Schema for driver license extraction.
# Note: dates are ISO strings; review agent will flag if expiration < today or issue > expiration.
driver_license_schema = {
    "type": "object",
    "properties": {
        "full_name": {
            "type": ["string", "null"],
            "description": "Full legal name of the license holder as printed on the front of the card",
        },
        "license_number": {
            "type": ["string", "null"],
            "description": "Unique driver license number issued by the state DMV",
        },
        "date_of_birth": {
            "type": ["string", "null"],
            "description": "Date of birth in MM/DD/YYYY format (e.g., 03/15/1990)",
        },
        "expiration_date": {
            "type": ["string", "null"],
            "description": "License expiration date in MM/DD/YYYY format (e.g., 06/30/2028)",
        },
        "issue_date": {
            "type": ["string", "null"],
            "description": "License issue date in MM/DD/YYYY format (e.g., 07/01/2024)",
        },
        "address": {
            "type": ["string", "null"],
            "description": "Full residential address including street, city, state, and ZIP code",
        },
        "class": {
            "type": ["string", "null"],
            "description": "Driver license class or type (e.g., C, D, A, B) which determines vehicle weight and type restrictions",
        },
        "sex": {
            "type": ["string", "null"],
            "description": "Gender as listed on the license (M for male, F for female)",
        },
        "height": {
            "type": ["string", "null"],
            "description": "Physical height of the license holder (e.g., 5'11\", 180cm)",
        },
        "eye_color": {
            "type": ["string", "null"],
            "description": "Eye color (e.g., Blue, Brown, Green, Hazel)",
        },
        "endorsements": {
            "type": ["string", "null"],
            "description": "License endorsements if any (e.g., H for hazmat, P for passenger transport, M for motorcycle)",
        },
        "restrictions": {
            "type": ["string", "null"],
            "description": "License restrictions if any (e.g., B for corrective lenses, D for automatic transmission only)",
        },
    },
}


def process_driver_license(file_path: str):
    print(f"[Driver License] Processing: {file_path}")

    # Convert local file to base64 data URL for SDK consumption.
    file_buffer = Path(file_path).read_bytes()
    base64_str = base64.b64encode(file_buffer).decode("utf-8")
    file_ext = Path(file_path).suffix.lower().lstrip(".") or "pdf"
    
    mime_type_map = {
        "pdf": "application/pdf",
        "png": "image/png",
        "jpg": "image/jpeg",
        "jpeg": "image/jpeg",
    }
    mime_type = mime_type_map.get(file_ext, "application/octet-stream")
    data_url = f"data:{mime_type};base64,{base64_str}"

    # Step 1: Parse — extract markdown + layout structure.
    print("[1/3] Parsing driver license...")
    parse_run = client.parse_runs.create_and_poll(
        file={"url": data_url},
        config={
            "blockOptions": {
                "text": {
                    "agentic": {
                        "enabled": True,
                    },
                },
            },
            "chunkingStrategy": {
                "type": "document",
            },
        },
    )

    if parse_run.status != "PROCESSED":
        raise Exception(
            f"Parse failed with status: {parse_run.status}. Error: {parse_run.error.message if parse_run.error else 'Unknown'}"
        )

    parsed_markdown = "\n\n".join(chunk.content for chunk in parse_run.output.chunks)
    print(f"[✓] Parse complete. Markdown length: {len(parsed_markdown)}")

    # Step 2: Classify — confirm it's a driver license (not passport, state ID, etc.).
    print("[2/3] Classifying document...")
    classify_run = client.classify_runs.create_and_poll(
        file={"url": data_url},
        config={
            "classifications": [
                {
                    "id": "drivers_license",
                    "type": "drivers_license",
                    "description": "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways. It contains the holder's name, license number, DOB, address, and may include endorsements or restrictions.",
                },
                {
                    "id": "other",
                    "type": "other",
                    "description": "Use the `other` type when the provided document cannot be clearly classified as a driver's license (e.g., passport, state ID, military ID, library card).",
                },
            ],
            "baseProcessor": "classification_performance",
            "advancedOptions": {
                "advancedMultimodalEnabled": True,
            },
        },
    )

    if classify_run.status != "PROCESSED":
        raise Exception(
            f"Classify failed with status: {classify_run.status}. Error: {classify_run.error.message if classify_run.error else 'Unknown'}"
        )

    classification = classify_run.output
    is_driver_license = classification.type == "drivers_license"
    classification_confidence = classification.confidence or 0

    print(
        f"[✓] Classification: {classification.type} (confidence: {classification_confidence * 100:.1f}%)"
    )

    if not is_driver_license:
        print(
            f"[WARNING] Document classified as \"{classification.type}\" (not a driver license). Proceeding with extraction anyway."
        )

    # Step 3: Extract — pull all 11 fields into structured JSON.
    print("[3/3] Extracting driver license fields...")
    extract_run = client.extract_runs.create_and_poll(
        file={"url": data_url},
        config={
            "schema": driver_license_schema,
            "baseProcessor": "extraction_performance",
            "advancedOptions": {
                "reviewAgent": {
                    "enabled": True,  # Automatic post-extraction validation for date logic, format checks
                },
                "advancedMultimodalEnabled": True,
            },
        },
    )

    if extract_run.status != "PROCESSED":
        raise Exception(
            f"Extract failed with status: {extract_run.status}. Error: {extract_run.error.message if extract_run.error else 'Unknown'}"
        )

    extracted = extract_run.output.value
    print("[✓] Extraction complete.")

    # Assemble final result.
    result = {
        "status": "success",
        "classification": {
            "type": classification.type,
            "confidence": classification_confidence,
        },
        "extraction": extracted,
        "review_agent": extract_run.output.review_agent or None,  # Flags, warnings from review agent
        "parsed_markdown": parsed_markdown,
        "timestamp": __import__("datetime").datetime.now(
            __import__("datetime").timezone.utc
        ).isoformat(),
    }

    print("\n=== DRIVER LICENSE EXTRACTION RESULT ===")
    import json
    print(json.dumps(result, indent=2))

    return result


# Example invocation (uncomment to run standalone):
# import sys
# test_file = sys.argv[1] if len(sys.argv) > 1 else "./test_license.pdf"
# process_driver_license(test_file)
// This code uses Extend's REST API directly because Extend has no official Java SDK yet.
// It calls https://api.extend.ai endpoints with Bearer token authentication.

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class DriverLicenseProcessor {

  private static final String API_BASE = "https://api.extend.ai";
  private static final String API_KEY = System.getenv("EXTEND_API_KEY");
  private static final HttpClient httpClient = HttpClient.newHttpClient();

  public static void main(String[] args) throws Exception {
    String filePath = args.length > 0 ? args[0] : "./test_license.pdf";
    processDriverLicense(filePath);
  }

  public static Map<String, Object> processDriverLicense(String filePath)
      throws Exception {
    System.out.println("[Driver License] Processing: " + filePath);

    // Convert local file to base64 data URL.
    byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
    String base64 = Base64.getEncoder().encodeToString(fileBytes);
    String fileExt = getFileExtension(filePath).toLowerCase();
    String mimeType = getMimeType(fileExt);
    String dataUrl = "data:" + mimeType + ";base64," + base64;

    // Step 1: Parse — extract markdown + layout structure.
    System.out.println("[1/3] Parsing driver license...");
    Map<String, Object> parseResult = callParseRun(dataUrl);
    String parseStatus = (String) parseResult.get("status");

    if (!"PROCESSED".equals(parseStatus)) {
      throw new Exception(
          "Parse failed with status: "
              + parseStatus
              + ". Error: "
              + parseResult.getOrDefault("error", "Unknown"));
    }

    String parsedMarkdown = extractMarkdownFromParseOutput(parseResult);
    System.out.println("[✓] Parse complete. Markdown length: " + parsedMarkdown.length());

    // Step 2: Classify — confirm it's a driver license.
    System.out.println("[2/3] Classifying document...");
    Map<String, Object> classifyResult = callClassifyRun(dataUrl);
    String classifyStatus = (String) classifyResult.get("status");

    if (!"PROCESSED".equals(classifyStatus)) {
      throw new Exception(
          "Classify failed with status: "
              + classifyStatus
              + ". Error: "
              + classifyResult.getOrDefault("error", "Unknown"));
    }

    Map<String, Object> classification = (Map<String, Object>) classifyResult.get("output");
    String classificationType = (String) classification.get("type");
    Double confidence = (Double) classification.getOrDefault("confidence", 0.0);
    boolean isDriverLicense = "drivers_license".equals(classificationType);

    System.out.println(
        "[✓] Classification: "
            + classificationType
            + " (confidence: "
            + String.format("%.1f", confidence * 100)
            + "%)");

    if (!isDriverLicense) {
      System.out.println(
          "[WARNING] Document classified as \""
              + classificationType
              + "\" (not a driver license). Proceeding with extraction anyway.");
    }

    // Step 3: Extract — pull all fields into structured JSON.
    System.out.println("[3/3] Extracting driver license fields...");
    Map<String, Object> extractResult = callExtractRun(dataUrl);
    String extractStatus = (String) extractResult.get("status");

    if (!"PROCESSED".equals(extractStatus)) {
      throw new Exception(
          "Extract failed with status: "
              + extractStatus
              + ". Error: "
              + extractResult.getOrDefault("error", "Unknown"));
    }

    Map<String, Object> extractOutput = (Map<String, Object>) extractResult.get("output");
    Map<String, Object> extracted = (Map<String, Object>) extractOutput.get("value");
    Object reviewAgent = extractOutput.getOrDefault("review_agent", null);

    System.out.println("[✓] Extraction complete.");

    // Assemble final result.
    Map<String, Object> result = new HashMap<>();
    result.put("status", "success");

    Map<String, Object> classificationMap = new HashMap<>();
    classificationMap.put("type", classificationType);
    classificationMap.put("confidence", confidence);
    result.put("classification", classificationMap);

    result.put("extraction", extracted);
    result.put("review_agent", reviewAgent);
    result.put("parsed_markdown", parsedMarkdown);
    result.put("timestamp", Instant.now().toString());

    System.out.println("\n=== DRIVER LICENSE EXTRACTION RESULT ===");
    System.out.println(jsonStringify(result));

    return result;
  }

  private static Map<String, Object> callParseRun(String dataUrl) throws Exception {
    String requestBody =
        "{"
            + "\"file\":{\"url\":\""
            + escapeJson(dataUrl)
            + "\"},"
            + "\"config\":{"
            + "\"blockOptions\":{\"text\":{\"agentic\":{\"enabled\":true}}},"
            + "\"chunkingStrategy\":{\"type\":\"document\"}"
            + "}"
            + "}";

    return pollRun("parse_runs", requestBody);
  }

  private static Map<String, Object> callClassifyRun(String dataUrl) throws Exception {
    String requestBody =
        "{"
            + "\"file\":{\"url\":\""
            + escapeJson(dataUrl)
            + "\"},"
            + "\"config\":{"
            + "\"classifications\":["
            + "{"
            + "\"id\":\"drivers_license\","
            + "\"type\":\"drivers_license\","
            + "\"description\":\"A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways. It contains the holder's name, license number, DOB, address, and may include endorsements or restrictions.\""
            + "},"
            + "{"
            + "\"id\":\"other\","
            + "\"type\":\"other\","
            + "\"description\":\"Use the `other` type when the provided document cannot be clearly classified as a driver's license (e.g., passport, state ID, military ID, library card).\""
            + "}"
            + "],"
            + "\"baseProcessor\":\"classification_performance\","
            + "\"advancedOptions\":{\"advancedMultimodalEnabled\":true}"
            + "}"
            + "}";

    return pollRun("classify_runs", requestBody);
  }

  private static Map<String, Object> callExtractRun(String dataUrl) throws Exception {
    String schemaJson =
        "{"
            + "\"type\":\"object\","
            + "\"properties\":{"
            + "\"full_name\":{\"type\":[\"string\",\"null\"],\"description\":\"Full legal name of the license holder as printed on the front of the card\"},"
            + "\"license_number\":{\"type\":[\"string\",\"null\"],\"description\":\"Unique driver license number issued by the state DMV\"},"
            + "\"date_of_birth\":{\"type\":[\"string\",\"null\"],\"description\":\"Date of birth in MM/DD/YYYY format (e.g., 03/15/1990)\"},"
            + "\"expiration_date\":{\"type\":[\"string\",\"null\"],\"description\":\"License expiration date in MM/DD/YYYY format (e.g., 06/30/2028)\"},"
            + "\"issue_date\":{\"type\":[\"string\",\"null\"],\"description\":\"License issue date in MM/DD/YYYY format (e.g., 07/01/2024)\"},"
            + "\"address\":{\"type\":[\"string\",\"null\"],\"description\":\"Full residential address including street, city, state, and ZIP code\"},"
            + "\"class\":{\"type\":[\"string\",\"null\"],\"description\":\"Driver license class or type (e.g., C, D, A, B) which determines vehicle weight and type restrictions\"},"
            + "\"sex\":{\"type\":[\"string\",\"null\"],\"description\":\"Gender as listed on the license (M for male, F for female)\"},"
            + "\"height\":{\"type\":[\"string\",\"null\"],\"description\":\"Physical height of the license holder (e.g., 5'11\\\", 180cm)\"},"
            + "\"eye_color\":{\"type\":[\"string\",\"null\"],\"description\":\"Eye color (e.g., Blue, Brown, Green, Hazel)\"},"
            + "\"endorsements\":{\"type\":[\"string\",\"null\"],\"description\":\"License endorsements if any (e.g., H for hazmat, P for passenger transport, M for motorcycle)\"},"
            + "\"restrictions\":{\"type\":[\"string\",\"null\"],\"description\":\"License restrictions if any (e.g., B for corrective lenses, D for automatic transmission only)\"}"
            + "}"
            + "}";

    String requestBody =
        "{"
            + "\"file\":{\"url\":\""
            + escapeJson(dataUrl)
            + "\"},"
            + "\"config\":{"
            + "\"schema\":"
            + schemaJson
            + ","
            + "\"baseProcessor\":\"extraction_performance\","
            + "\"advancedOptions\":{\"reviewAgent\":{\"enabled\":true},\"advancedMultimodalEnabled\":true}"
            + "}"
            + "}";

    return pollRun("extract_runs", requestBody);
  }

  private static Map<String, Object> pollRun(String endpoint, String requestBody)
      throws Exception {
    // Create initial run
    HttpRequest request =
        HttpRequest.newBuilder()
            .uri(URI.create(API_BASE + "/" + endpoint))
            .header("Authorization", "Bearer " + API_KEY)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    Map<String, Object> runData = parseJson(response.body());
    String runId = (String) runData.get("id");

    // Poll until completion
    while (true) {
      Thread.sleep(2000);

      HttpRequest pollRequest =
          HttpRequest.newBuilder()
              .uri(URI.create(API_BASE + "/" + endpoint + "/" + runId))
              .header("Authorization", "Bearer " + API_KEY)
              .GET()
              .build();

      HttpResponse<String> pollResponse =
          httpClient.send(pollRequest, HttpResponse.BodyHandlers.ofString());
      Map<String, Object> pollData = parseJson(pollResponse.body());
      String status = (String) pollData.get("status");

      if ("PROCESSED".equals(status) || "FAILED".equals(status)) {
        return pollData;
      }
    }
  }

  private static String extractMarkdownFromParseOutput(Map<String, Object> parseResult) {
    Map<String, Object> output = (Map<String, Object>) parseResult.get("output");
    java.util.List<Map<String, Object>> chunks =
        (java.util.List<Map<String, Object>>) output.get("chunks");
    StringBuilder markdown = new StringBuilder();
    for (Map<String, Object> chunk : chunks) {
      markdown.append((String) chunk.get("content")).append("\n\n");
    }
    return markdown.toString().trim();
  }

  private static String getFileExtension(String filePath) {
    int lastDot = filePath.lastIndexOf('.');
    return lastDot > 0 ? filePath.substring(lastDot + 1) : "pdf";
  }

  private static String getMimeType(String ext) {
    switch (ext.toLowerCase()) {
      case "pdf":
        return "application/pdf";
      case "png":
        return "image/png";
      case "jpg":
      case "jpeg":
        return "image/jpeg";
      default:
        return "application/octet-stream";
    }
  }

  private static String escapeJson(String s) {
    return s.replace("\\", "\\\\")
        .replace("\"", "\\\"")
        .replace("\n", "\\n")
        .replace("\r", "\\r")
        .replace("\t", "\\t");
  }

  private static Map<String, Object> parseJson(String json) {
    // Simple JSON parser for basic objects
    Map<String, Object> map = new HashMap<>();
    json = json.trim();
    if (json.startsWith("{") && json.endsWith("}")) {
      json = json.substring(1, json.length() - 1);
      String[] pairs = splitJsonPairs(json);
      for (String pair : pairs) {
        int colonIdx = pair.indexOf(':');
        if (colonIdx > 0) {
          String key = pair.substring(0, colonIdx).trim().replaceAll("^\"|\"$", "");
          String value = pair.substring(colonIdx + 1).trim();
          map.put(key, parseJsonValue(value));
        }
      }
    }
    return map;
  }

  private static Object parseJsonValue(String value) {
    value = value.trim();
    if (value.startsWith("\"") && value.endsWith("\"")) {
      return value.substring(1, value.length() - 1);
    } else if ("true".equals(value)) {
      return true;
    } else if ("false".equals(value)) {
      return false;
    } else if ("null".equals(value)) {
      return null;
    } else if (value.startsWith("[")) {
      java.util.List<Object> list = new java.util.ArrayList<>();
      String inner = value.substring(1, value.length() - 1).trim();
      if (!inner.isEmpty()) {
        for (String item : splitJsonPairs(inner)) {
          list.add(parseJsonValue(item));
        }
      }
      return list;
    } else if (value.startsWith("{")) {
      return parseJson(value);
    } else {
      try {
        if (value.contains(".")) {
          return Double.parseDouble(value);
        } else {
          return Long.parseLong(value);
        }
      } catch (NumberFormatException e) {
        return value;
      }
    }
  }

  private static String[] splitJsonPairs(String s) {
    java.util.List<String> pairs = new java.util.ArrayList<>();
    int depth = 0;
    int start = 0;
    boolean inString = false;
    boolean escaped = false;

    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);

      if (escaped) {
        escaped = false;
        continue;
      }

      if (c == '\\') {
        escaped = true;
        continue;
      }

      if (c == '"') {
        inString = !inString;
        continue;
      }

      if (!inString) {
        if (c == '{' || c == '[') {
          depth++;
        } else if (c == '}' || c == ']') {
          depth--;
        } else if (c == ',' && depth == 0) {
          pairs.add(s.substring(start, i).trim());
          start = i + 1;
        }
      }
    }

    if (start < s.length()) {
      pairs.add(s.substring(start).trim());
    }

    return pairs.toArray(new String[0]);
  }

  private static String jsonStringify(Map<String, Object> map) {
    StringBuilder sb = new StringBuilder("{");
    boolean first = true;
    for (Map.Entry<String, Object> entry : map.entrySet()) {
      if (!first) sb.append(",");
      sb.append("\"").append(entry.getKey()).append("\":");
      sb.append(valueToJson(entry.getValue()));
      first = false;
    }
    sb.append("}");
    return sb.toString();
  }

  private static String valueToJson(Object value) {
    if (value == null) {
      return "null";
    } else if (value instanceof String) {
      return "\"" + escapeJson((String) value) + "\"";
    } else if (value instanceof Boolean || value instanceof Number) {
      return value.toString();
    } else if (value instanceof Map) {
      return jsonStringify((Map<String, Object>) value);
    } else if (value instanceof java.util.List) {
      StringBuilder sb = new StringBuilder("[");
      java.util.List<?> list = (java.util.List<?>) value;
      for (int i = 0; i < list.size(); i++) {
        if (i > 0) sb.append(",");
        sb.append(valueToJson(list.get(i)));
      }
      sb.append("]");
      return sb.toString();
    }
    return "\"" + value.toString() + "\"";
  }
}
// This code uses the Extend REST API directly because Extend has no official Go SDK yet.
// It calls https://api.extend.ai endpoints with standard net/http and encoding/json.

package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const extendAPIBase = "https://api.extend.ai"

// DriverLicenseExtraction represents the extracted driver license fields.
type DriverLicenseExtraction struct {
	FullName       *string `json:"full_name"`
	LicenseNumber  *string `json:"license_number"`
	DateOfBirth    *string `json:"date_of_birth"`
	ExpirationDate *string `json:"expiration_date"`
	IssueDate      *string `json:"issue_date"`
	Address        *string `json:"address"`
	Class          *string `json:"class"`
	Sex            *string `json:"sex"`
	Height         *string `json:"height"`
	EyeColor       *string `json:"eye_color"`
	Endorsements   *string `json:"endorsements"`
	Restrictions   *string `json:"restrictions"`
}

// ParseRunOutput represents the output from a parse run.
type ParseRunOutput struct {
	Chunks []struct {
		Content string `json:"content"`
	} `json:"chunks"`
}

// ParseRun represents a parse run response.
type ParseRun struct {
	Status string         `json:"status"`
	Output ParseRunOutput `json:"output"`
	Error  *struct {
		Message string `json:"message"`
	} `json:"error"`
}

// ClassifyRunOutput represents the output from a classify run.
type ClassifyRunOutput struct {
	Type       string  `json:"type"`
	Confidence float64 `json:"confidence"`
}

// ClassifyRun represents a classify run response.
type ClassifyRun struct {
	Status string             `json:"status"`
	Output ClassifyRunOutput  `json:"output"`
	Error  *struct {
		Message string `json:"message"`
	} `json:"error"`
}

// ExtractRunOutput represents the output from an extract run.
type ExtractRunOutput struct {
	Value       DriverLicenseExtraction `json:"value"`
	ReviewAgent interface{}             `json:"review_agent"`
}

// ExtractRun represents an extract run response.
type ExtractRun struct {
	Status string           `json:"status"`
	Output ExtractRunOutput `json:"output"`
	Error  *struct {
		Message string `json:"message"`
	} `json:"error"`
}

// Result represents the final extraction result.
type Result struct {
	Status        string                  `json:"status"`
	Classification map[string]interface{} `json:"classification"`
	Extraction    DriverLicenseExtraction `json:"extraction"`
	ReviewAgent   interface{}             `json:"review_agent"`
	ParsedMarkdown string                 `json:"parsed_markdown"`
	Timestamp     string                  `json:"timestamp"`
}

func getMimeType(filePath string) string {
	ext := strings.ToLower(filepath.Ext(filePath))
	switch ext {
	case ".pdf":
		return "application/pdf"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	default:
		return "application/octet-stream"
	}
}

func fileToDataURL(filePath string) (string, error) {
	fileBuffer, err := os.ReadFile(filePath)
	if err != nil {
		return "", err
	}
	b64 := base64.StdEncoding.EncodeToString(fileBuffer)
	mimeType := getMimeType(filePath)
	return fmt.Sprintf("data:%s;base64,%s", mimeType, b64), nil
}

func doRequest(method, endpoint string, body interface{}, apiKey string, result interface{}) error {
	url := extendAPIBase + endpoint
	var reqBody io.Reader
	if body != nil {
		jsonBody, err := json.Marshal(body)
		if err != nil {
			return err
		}
		reqBody = bytes.NewReader(jsonBody)
	}

	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
	req.Header.Set("Content-Type", "application/json")

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

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

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("API error: status %d, body: %s", resp.StatusCode, string(respBody))
	}

	if err := json.Unmarshal(respBody, result); err != nil {
		return err
	}
	return nil
}

func pollRun(runID, runType string, apiKey string, result interface{}) error {
	endpoint := fmt.Sprintf("/v1/%s_runs/%s", runType, runID)
	maxAttempts := 120
	for i := 0; i < maxAttempts; i++ {
		if err := doRequest("GET", endpoint, nil, apiKey, result); err != nil {
			return err
		}

		// Check status based on run type
		var status string
		switch runType {
		case "parse":
			parseRun := result.(*ParseRun)
			status = parseRun.Status
		case "classify":
			classifyRun := result.(*ClassifyRun)
			status = classifyRun.Status
		case "extract":
			extractRun := result.(*ExtractRun)
			status = extractRun.Status
		}

		if status == "PROCESSED" || status == "FAILED" {
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return fmt.Errorf("polling timeout for %s run", runType)
}

func ProcessDriverLicense(filePath string) (*Result, error) {
	apiKey := os.Getenv("EXTEND_API_KEY")
	if apiKey == "" {
		return nil, fmt.Errorf("EXTEND_API_KEY environment variable not set")
	}

	fmt.Printf("[Driver License] Processing: %s\n", filePath)

	dataURL, err := fileToDataURL(filePath)
	if err != nil {
		return nil, err
	}

	// Step 1: Parse
	fmt.Println("[1/3] Parsing driver license...")
	parseReqBody := map[string]interface{}{
		"file": map[string]string{
			"url": dataURL,
		},
		"config": map[string]interface{}{
			"blockOptions": map[string]interface{}{
				"text": map[string]interface{}{
					"agentic": map[string]bool{
						"enabled": true,
					},
				},
			},
			"chunkingStrategy": map[string]string{
				"type": "document",
			},
		},
	}

	var parseCreateResp struct {
		ID string `json:"id"`
	}
	if err := doRequest("POST", "/v1/parse_runs", parseReqBody, apiKey, &parseCreateResp); err != nil {
		return nil, err
	}

	var parseRun ParseRun
	if err := pollRun(parseCreateResp.ID, "parse", apiKey, &parseRun); err != nil {
		return nil, err
	}

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

	var parsedMarkdown string
	for _, chunk := range parseRun.Output.Chunks {
		parsedMarkdown += chunk.Content + "\n\n"
	}
	fmt.Printf("[✓] Parse complete. Markdown length: %d\n", len(parsedMarkdown))

	// Step 2: Classify
	fmt.Println("[2/3] Classifying document...")
	classifyReqBody := map[string]interface{}{
		"file": map[string]string{
			"url": dataURL,
		},
		"config": map[string]interface{}{
			"classifications": []map[string]string{
				{
					"id":          "drivers_license",
					"type":        "drivers_license",
					"description": "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways. It contains the holder's name, license number, DOB, address, and may include endorsements or restrictions.",
				},
				{
					"id":          "other",
					"type":        "other",
					"description": "Use the `other` type when the provided document cannot be clearly classified as a driver's license (e.g., passport, state ID, military ID, library card).",
				},
			},
			"baseProcessor": "classification_performance",
			"advancedOptions": map[string]bool{
				"advancedMultimodalEnabled": true,
			},
		},
	}

	var classifyCreateResp struct {
		ID string `json:"id"`
	}
	if err := doRequest("POST", "/v1/classify_runs", classifyReqBody, apiKey, &classifyCreateResp); err != nil {
		return nil, err
	}

	var classifyRun ClassifyRun
	if err := pollRun(classifyCreateResp.ID, "classify", apiKey, &classifyRun); err != nil {
		return nil, err
	}

	if classifyRun.Status != "PROCESSED" {
		errMsg := "Unknown"
		if classifyRun.Error != nil {
			errMsg = classifyRun.Error.Message
		}
		return nil, fmt.Errorf("classify failed with status: %s. Error: %s", classifyRun.Status, errMsg)
	}

	isDriverLicense := classifyRun.Output.Type == "drivers_license"
	fmt.Printf("[✓] Classification: %s (confidence: %.1f%%)\n", classifyRun.Output.Type, classifyRun.Output.Confidence*100)

	if !isDriverLicense {
		fmt.Printf("[WARNING] Document classified as \"%s\" (not a driver license). Proceeding with extraction anyway.\n", classifyRun.Output.Type)
	}

	// Step 3: Extract
	fmt.Println("[3/3] Extracting driver license fields...")
	extractReqBody := map[string]interface{}{
		"file": map[string]string{
			"url": dataURL,
		},
		"config": map[string]interface{}{
			"schema": map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"full_name": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Full legal name of the license holder as printed on the front of the card",
					},
					"license_number": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Unique driver license number issued by the state DMV",
					},
					"date_of_birth": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Date of birth in MM/DD/YYYY format (e.g., 03/15/1990)",
					},
					"expiration_date": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "License expiration date in MM/DD/YYYY format (e.g., 06/30/2028)",
					},
					"issue_date": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "License issue date in MM/DD/YYYY format (e.g., 07/01/2024)",
					},
					"address": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Full residential address including street, city, state, and ZIP code",
					},
					"class": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Driver license class or type (e.g., C, D, A, B) which determines vehicle weight and type restrictions",
					},
					"sex": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Gender as listed on the license (M for male, F for female)",
					},
					"height": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Physical height of the license holder (e.g., 5'11\", 180cm)",
					},
					"eye_color": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "Eye color (e.g., Blue, Brown, Green, Hazel)",
					},
					"endorsements": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "License endorsements if any (e.g., H for hazmat, P for passenger transport, M for motorcycle)",
					},
					"restrictions": map[string]interface{}{
						"type":        []string{"string", "null"},
						"description": "License restrictions if any (e.g., B for corrective lenses, D for automatic transmission only)",
					},
				},
			},
			"baseProcessor": "extraction_performance",
			"advancedOptions": map[string]interface{}{
				"reviewAgent": map[string]bool{
					"enabled": true,
				},
				"advancedMultimodalEnabled": true,
			},
		},
	}

	var extractCreateResp struct {
		ID string `json:"id"`
	}
	if err := doRequest("POST", "/v1/extract_runs", extractReqBody, apiKey, &extractCreateResp); err != nil {
		return nil, err
	}

	var extractRun ExtractRun
	if err := pollRun(extractCreateResp.ID, "extract", apiKey, &extractRun); err != nil {
		return nil, err
	}

	if extractRun.Status != "PROCESSED" {
		errMsg := "Unknown"
		if extractRun.Error != nil {
			errMsg = extractRun.Error.Message
		}
		return nil, fmt.Errorf("extract failed with status: %s. Error: %s", extractRun.Status, errMsg)
	}

	fmt.Println("[✓] Extraction complete.")

	result := &Result{
		Status: "success",
		Classification: map[string]interface{}{
			"type":       classifyRun.Output.Type,
			"confidence": classifyRun.Output.Confidence,
		},
		Extraction:    extractRun.Output.Value,
		ReviewAgent:   extractRun.Output.ReviewAgent,
		ParsedMarkdown: parsedMarkdown,
		Timestamp:     time.Now().UTC().Format(time.RFC3339),
	}

	fmt.Println("\n=== DRIVER LICENSE EXTRACTION RESULT ===")
	resultJSON, _ := json.MarshalIndent(result, "", "  ")
	fmt.Println(string(resultJSON))

	return result, nil
}

func main() {
	testFile := "test_license.pdf"
	if len(os.Args) > 1 {
		testFile = os.Args[1]
	}
	if _, err := ProcessDriverLicense(testFile); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}
// Deploy the "Driver License" 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/driver-license.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: driver-license).

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, "driver-license.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": "Driver License Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": true
              }
            }
          },
          "chunkingStrategy": {
            "type": "document"
          }
        }
      },
      "next": [
        {
          "step": "classify2"
        }
      ]
    },
    {
      "name": "classify2",
      "type": "CLASSIFY",
      "config": {
        "classifierConfig": {
          "classifications": [
            {
              "id": "classification1",
              "type": "other",
              "description": "Use the `other` document type when the provided document can not clearly be classified into one of the described classifications."
            },
            {
              "id": "classification_dNv",
              "type": "drivers_license",
              "description": "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways"
            }
          ],
          "baseProcessor": "classification_performance",
          "advancedOptions": {
            "advancedMultimodalEnabled": true
          }
        }
      },
      "next": [
        {
          "step": "extraction3",
          "classificationId": "classification1"
        },
        {
          "step": "extraction3",
          "classificationId": "classification_dNv"
        }
      ]
    },
    {
      "name": "extraction3",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "properties": {
              "sex": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Gender (M/F)"
              },
              "class": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Driver license class or type"
              },
              "height": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Height of license holder"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full residential address"
              },
              "eye_color": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Eye color"
              },
              "full_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Full name of the license holder"
              },
              "issue_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License issue date in MM/DD/YYYY format"
              },
              "endorsements": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License endorsements if any"
              },
              "restrictions": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License restrictions if any"
              },
              "date_of_birth": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Date of birth in MM/DD/YYYY format"
              },
              "license_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Unique driver license number"
              },
              "expiration_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "License expiration date in MM/DD/YYYY format"
              }
            }
          },
          "baseProcessor": "extraction_performance",
          "advancedOptions": {
            "reviewAgent": {
              "enabled": true
            },
            "advancedMultimodalEnabled": true
          }
        }
      }
    }
  ]
};

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 os
import json
import sys
from pathlib import Path
from extend_ai import Extend

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 / "driver-license.json"

state = {}
if STATE_FILE.exists():
    with open(STATE_FILE, "r") as f:
        state = json.load(f)

def save_state():
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)

WORKFLOW = {
    "name": "Driver License Processing Pipeline",
    "steps": [
        {
            "name": "startTrigger1",
            "type": "TRIGGER",
            "next": [
                {
                    "step": "parse1"
                }
            ]
        },
        {
            "name": "parse1",
            "type": "PARSE",
            "config": {
                "parseConfig": {
                    "blockOptions": {
                        "text": {
                            "agentic": {
                                "enabled": True
                            }
                        }
                    },
                    "chunkingStrategy": {
                        "type": "document"
                    }
                }
            },
            "next": [
                {
                    "step": "classify2"
                }
            ]
        },
        {
            "name": "classify2",
            "type": "CLASSIFY",
            "config": {
                "classifierConfig": {
                    "classifications": [
                        {
                            "id": "classification1",
                            "type": "other",
                            "description": "Use the `other` document type when the provided document can not clearly be classified into one of the described classifications."
                        },
                        {
                            "id": "classification_dNv",
                            "type": "drivers_license",
                            "description": "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways"
                        }
                    ],
                    "baseProcessor": "classification_performance",
                    "advancedOptions": {
                        "advancedMultimodalEnabled": True
                    }
                }
            },
            "next": [
                {
                    "step": "extraction3",
                    "classificationId": "classification1"
                },
                {
                    "step": "extraction3",
                    "classificationId": "classification_dNv"
                }
            ]
        },
        {
            "name": "extraction3",
            "type": "EXTRACT",
            "config": {
                "extractorConfig": {
                    "schema": {
                        "type": "object",
                        "properties": {
                            "sex": {
                                "type": ["string", "null"],
                                "description": "Gender (M/F)"
                            },
                            "class": {
                                "type": ["string", "null"],
                                "description": "Driver license class or type"
                            },
                            "height": {
                                "type": ["string", "null"],
                                "description": "Height of license holder"
                            },
                            "address": {
                                "type": ["string", "null"],
                                "description": "Full residential address"
                            },
                            "eye_color": {
                                "type": ["string", "null"],
                                "description": "Eye color"
                            },
                            "full_name": {
                                "type": ["string", "null"],
                                "description": "Full name of the license holder"
                            },
                            "issue_date": {
                                "type": ["string", "null"],
                                "description": "License issue date in MM/DD/YYYY format"
                            },
                            "endorsements": {
                                "type": ["string", "null"],
                                "description": "License endorsements if any"
                            },
                            "restrictions": {
                                "type": ["string", "null"],
                                "description": "License restrictions if any"
                            },
                            "date_of_birth": {
                                "type": ["string", "null"],
                                "description": "Date of birth in MM/DD/YYYY format"
                            },
                            "license_number": {
                                "type": ["string", "null"],
                                "description": "Unique driver license number"
                            },
                            "expiration_date": {
                                "type": ["string", "null"],
                                "description": "License expiration date in MM/DD/YYYY format"
                            }
                        }
                    },
                    "baseProcessor": "extraction_performance",
                    "advancedOptions": {
                        "reviewAgent": {
                            "enabled": True
                        },
                        "advancedMultimodalEnabled": True
                    }
                }
            }
        }
    ]
}

def main():
    client = Extend(token=API_KEY)
    
    print(f'Deploying "{WORKFLOW["name"]}…"')
    
    if state.get("workflowId"):
        workflow_id = state["workflowId"]
        print(f"✓ workflow already provisioned ({workflow_id}) — updating steps")
        client.workflows.update(id=workflow_id, steps=WORKFLOW["steps"])
    else:
        existing_id = None
        try:
            workflows_list = client.workflows.list(name=WORKFLOW["name"])
            items = workflows_list.data if hasattr(workflows_list, "data") else (workflows_list.items if hasattr(workflows_list, "items") else [])
            for item in items:
                if item.get("name") == WORKFLOW["name"]:
                    existing_id = item.get("id")
                    break
            
            if existing_id:
                state["workflowId"] = existing_id
                save_state()
                print(f'✓ workflow "{WORKFLOW["name"]}" found in your account ({existing_id}) — updating steps')
                client.workflows.update(id=existing_id, steps=WORKFLOW["steps"])
        except Exception:
            pass
        
        if not state.get("workflowId"):
            created = client.workflows.create(**WORKFLOW)
            workflow_id = created.id if hasattr(created, "id") else (created.workflow.id if hasattr(created, "workflow") else None)
            if not workflow_id:
                raise ValueError("Could not read created workflow id from response")
            state["workflowId"] = workflow_id
            save_state()
            print(f"+ created workflow ({workflow_id})")
    
    try:
        client.workflows.create_version(id=state["workflowId"])
    except Exception:
        pass
    
    print("\nDone. Run documents through it with:")
    print(f'  POST https://api.extend.ai/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)
// This script uses the Extend REST API directly because Extend has no official Java SDK yet.
// Call the API with java.net.http.HttpClient and parse JSON manually.

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class ProvisionDriverLicense {
  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("driver-license.json");

  private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

  static class State {
    String workflowId;

    State() {}

    static State load() throws IOException {
      if (Files.exists(STATE_FILE)) {
        String json = Files.readString(STATE_FILE);
        State s = new State();
        s.workflowId = extractJsonString(json, "workflowId");
        return s;
      }
      return new State();
    }

    void save() throws IOException {
      Files.createDirectories(STATE_DIR);
      String json = "{\"workflowId\":\"" + (workflowId != null ? workflowId : "") + "\"}";
      Files.writeString(STATE_FILE, json);
    }
  }

  private static String extractJsonString(String json, String key) {
    String pattern = "\"" + key + "\":\"";
    int idx = json.indexOf(pattern);
    if (idx == -1) return null;
    int start = idx + pattern.length();
    int end = json.indexOf("\"", start);
    return end > start ? json.substring(start, end) : null;
  }

  private static String extractJsonId(String json) {
    int idx = json.indexOf("\"id\":\"");
    if (idx == -1) return null;
    int start = idx + 6;
    int end = json.indexOf("\"", start);
    return end > start ? json.substring(start, end) : null;
  }

  private static String extractJsonArrayId(String json, String name) {
    String pattern = "\"name\":\"" + name + "\"";
    int idx = json.indexOf(pattern);
    if (idx == -1) return null;
    int searchStart = Math.max(0, idx - 200);
    int idIdx = json.lastIndexOf("\"id\":\"", idx);
    if (idIdx < searchStart) return null;
    int start = idIdx + 6;
    int end = json.indexOf("\"", start);
    return end > start ? json.substring(start, end) : null;
  }

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

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

    HttpRequest request = builder.build();
    HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());

    if (response.statusCode() < 200 || response.statusCode() >= 300) {
      String preview = response.body().length() > 300 ? response.body().substring(0, 300) : response.body();
      throw new IOException(method + " " + pathName + " failed (" + response.statusCode() + "): " + preview);
    }

    return response.body();
  }

  private static String buildWorkflowJson() {
    return "{"
        + "\"name\":\"Driver License Processing Pipeline\","
        + "\"steps\":["
        + "{"
        + "\"name\":\"startTrigger1\","
        + "\"type\":\"TRIGGER\","
        + "\"next\":[{\"step\":\"parse1\"}]"
        + "},"
        + "{"
        + "\"name\":\"parse1\","
        + "\"type\":\"PARSE\","
        + "\"config\":{"
        + "\"parseConfig\":{"
        + "\"blockOptions\":{\"text\":{\"agentic\":{\"enabled\":true}}},"
        + "\"chunkingStrategy\":{\"type\":\"document\"}"
        + "}"
        + "},"
        + "\"next\":[{\"step\":\"classify2\"}]"
        + "},"
        + "{"
        + "\"name\":\"classify2\","
        + "\"type\":\"CLASSIFY\","
        + "\"config\":{"
        + "\"classifierConfig\":{"
        + "\"classifications\":["
        + "{\"id\":\"classification1\",\"type\":\"other\",\"description\":\"Use the `other` document type when the provided document can not clearly be classified into one of the described classifications.\"},"
        + "{\"id\":\"classification_dNv\",\"type\":\"drivers_license\",\"description\":\"A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways\"}"
        + "],"
        + "\"baseProcessor\":\"classification_performance\","
        + "\"advancedOptions\":{\"advancedMultimodalEnabled\":true}"
        + "}"
        + "},"
        + "\"next\":["
        + "{\"step\":\"extraction3\",\"classificationId\":\"classification1\"},"
        + "{\"step\":\"extraction3\",\"classificationId\":\"classification_dNv\"}"
        + "]"
        + "},"
        + "{"
        + "\"name\":\"extraction3\","
        + "\"type\":\"EXTRACT\","
        + "\"config\":{"
        + "\"extractorConfig\":{"
        + "\"schema\":{"
        + "\"type\":\"object\","
        + "\"properties\":{"
        + "\"sex\":{\"type\":[\"string\",\"null\"],\"description\":\"Gender (M/F)\"},"
        + "\"class\":{\"type\":[\"string\",\"null\"],\"description\":\"Driver license class or type\"},"
        + "\"height\":{\"type\":[\"string\",\"null\"],\"description\":\"Height of license holder\"},"
        + "\"address\":{\"type\":[\"string\",\"null\"],\"description\":\"Full residential address\"},"
        + "\"eye_color\":{\"type\":[\"string\",\"null\"],\"description\":\"Eye color\"},"
        + "\"full_name\":{\"type\":[\"string\",\"null\"],\"description\":\"Full name of the license holder\"},"
        + "\"issue_date\":{\"type\":[\"string\",\"null\"],\"description\":\"License issue date in MM/DD/YYYY format\"},"
        + "\"endorsements\":{\"type\":[\"string\",\"null\"],\"description\":\"License endorsements if any\"},"
        + "\"restrictions\":{\"type\":[\"string\",\"null\"],\"description\":\"License restrictions if any\"},"
        + "\"date_of_birth\":{\"type\":[\"string\",\"null\"],\"description\":\"Date of birth in MM/DD/YYYY format\"},"
        + "\"license_number\":{\"type\":[\"string\",\"null\"],\"description\":\"Unique driver license number\"},"
        + "\"expiration_date\":{\"type\":[\"string\",\"null\"],\"description\":\"License expiration date in MM/DD/YYYY format\"}"
        + "}"
        + "},"
        + "\"baseProcessor\":\"extraction_performance\","
        + "\"advancedOptions\":{\"reviewAgent\":{\"enabled\":true},\"advancedMultimodalEnabled\":true}"
        + "}"
        + "}"
        + "}"
        + "]"
        + "}";
  }

  private static String buildStepsJson() {
    return "{\"steps\":[" + buildWorkflowJson().substring(buildWorkflowJson().indexOf("\"steps\":[") + 9, buildWorkflowJson().lastIndexOf("]") + 1) + "]}";
  }

  public static void main(String[] args) {
    try {
      if (API_KEY == null || API_KEY.isEmpty()) {
        System.err.println("Set EXTEND_API_KEY first.");
        System.exit(1);
      }

      State state = State.load();
      String workflowName = "Driver License Processing Pipeline";

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

      if (state.workflowId != null && !state.workflowId.isEmpty()) {
        System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
        String stepsJson = buildStepsJson();
        api("POST", "/workflows/" + state.workflowId, stepsJson);
      } else {
        try {
          String encoded = URLEncoder.encode(workflowName, StandardCharsets.UTF_8);
          String listResponse = api("GET", "/workflows?name=" + encoded, null);
          String existingId = extractJsonArrayId(listResponse, workflowName);
          if (existingId != null) {
            state.workflowId = existingId;
            state.save();
            System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + existingId + ") — updating steps");
            String stepsJson = buildStepsJson();
            api("POST", "/workflows/" + existingId, stepsJson);
          }
        } catch (Exception e) {
          // lookup is best-effort; fall through to create
        }

        if (state.workflowId == null || state.workflowId.isEmpty()) {
          String workflowJson = buildWorkflowJson();
          String created = api("POST", "/workflows", workflowJson);
          String wfId = extractJsonId(created);
          if (wfId == null || wfId.isEmpty()) {
            throw new IOException("Could not read created workflow id from response");
          }
          state.workflowId = wfId;
          state.save();
          System.out.println("+ created workflow (" + wfId + ")");
        }
      }

      try {
        api("POST", "/workflows/" + state.workflowId + "/versions", "{}");
      } 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.toString());
      System.exit(1);
    }
  }
}
// This script uses the Extend REST API directly because Extend has no official Go SDK yet.
// It deploys the "Driver License" pipeline to your Extend account.
//
// Usage:
//   export EXTEND_API_KEY=sk_...   (from https://dashboard.extend.ai → API Keys)
//   go run provision.go
//
// Generated by doc1 (template: driver-license).

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"
)

var (
	apiKey   string
	stateDir string
	stateFile string
)

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

var 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)
	}

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

	stateDir = filepath.Join(cwd, ".extend")
	stateFile = filepath.Join(stateDir, "driver-license.json")

	// Load existing state if it exists
	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) {
	var reqBody io.Reader
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		reqBody = bytes.NewReader(data)
	}

	req, err := http.NewRequest(method, API+pathName, reqBody)
	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.StatusCode >= 400 {
		respStr := string(respBody)
		if len(respStr) > 300 {
			respStr = respStr[:300]
		}
		return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, respStr)
	}

	return data, nil
}

var workflow = map[string]interface{}{
	"name": "Driver License 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,
							},
						},
					},
					"chunkingStrategy": map[string]interface{}{
						"type": "document",
					},
				},
			},
			"next": []map[string]interface{}{
				{"step": "classify2"},
			},
		},
		{
			"name": "classify2",
			"type": "CLASSIFY",
			"config": map[string]interface{}{
				"classifierConfig": map[string]interface{}{
					"classifications": []map[string]interface{}{
						{
							"id":          "classification1",
							"type":        "other",
							"description": "Use the `other` document type when the provided document can not clearly be classified into one of the described classifications.",
						},
						{
							"id":          "classification_dNv",
							"type":        "drivers_license",
							"description": "A driver's license is an official, government-issued document that legally authorizes an individual to operate motorized vehicles on public roadways",
						},
					},
					"baseProcessor": "classification_performance",
					"advancedOptions": map[string]interface{}{
						"advancedMultimodalEnabled": true,
					},
				},
			},
			"next": []map[string]interface{}{
				{
					"step":               "extraction3",
					"classificationId":   "classification1",
				},
				{
					"step":               "extraction3",
					"classificationId":   "classification_dNv",
				},
			},
		},
		{
			"name": "extraction3",
			"type": "EXTRACT",
			"config": map[string]interface{}{
				"extractorConfig": map[string]interface{}{
					"schema": map[string]interface{}{
						"type": "object",
						"properties": map[string]interface{}{
							"sex": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Gender (M/F)",
							},
							"class": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Driver license class or type",
							},
							"height": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Height of license holder",
							},
							"address": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Full residential address",
							},
							"eye_color": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Eye color",
							},
							"full_name": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Full name of the license holder",
							},
							"issue_date": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "License issue date in MM/DD/YYYY format",
							},
							"endorsements": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "License endorsements if any",
							},
							"restrictions": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "License restrictions if any",
							},
							"date_of_birth": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Date of birth in MM/DD/YYYY format",
							},
							"license_number": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Unique driver license number",
							},
							"expiration_date": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "License expiration date in MM/DD/YYYY format",
							},
						},
					},
					"baseProcessor": "extraction_performance",
					"advancedOptions": map[string]interface{}{
						"reviewAgent": map[string]interface{}{
							"enabled": true,
						},
						"advancedMultimodalEnabled": true,
					},
				},
			},
		},
	},
}

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 find an existing workflow with the same name
		query := url.QueryEscape(workflowName)
		list, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", query), nil)
		if err == nil {
			var items []map[string]interface{}
			if data, ok := list["data"].([]interface{}); ok {
				for _, item := range data {
					if m, ok := item.(map[string]interface{}); ok {
						items = append(items, m)
					}
				}
			} else if data, ok := list["items"].([]interface{}); ok {
				for _, item := range data {
					if m, ok := item.(map[string]interface{}); ok {
						items = append(items, m)
					}
				}
			}

			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)
			}

			var wfID string
			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 (best-effort)
	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)

Use `mode: "agentic_ocr"` in the parse step — it's specifically tuned for low-contrast security features and worn printing. For extraction, set `baseProcessor: "extraction_performance"` to prioritize accuracy over speed on challenging images.
Nuanced question and depends on the use case! For an agent pipeline, you'll likely just stop at Parsing, take the markdown/HTML output and feed that into your pipeline. For Key-Value extraction into JSON, you can jump straight into Extraction because there is always a Parse step beforehand
Tags
GovernmentIdentificationPersonal DataLicense
About this template

This template classifies if a document is a driver's license, and then extracts key information including personal identification, license number, class, expiration date, and physical characteristics. It handles government-issued ID documents with structured fields and standardized layouts across different states.

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