> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://cla-demo.postmansolutions.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://cla-demo.postmansolutions.com/_mcp/server.

# Extractor Code

### Always reference these two python files to fill in the blanks:

[classes.py - Repos](https://dev.azure.com/CLADataAnalytics/AutoExtract-kit/_git/AutoExtract?path=%2Fcommon%2Fextractor%2Fclasses.py&_a=contents\&version=GBmain)

AND

[ingest\_activities.py - Repos](https://dev.azure.com/CLADataAnalytics/AutoExtract-kit/_git/AutoExtract?path=%2Ftemporal%2Fsrc%2Factivities%2Fingest_activities.py&_a=contents\&version=GBmain)

Nothing else is required.

### types.ts:

```java
export interface ProcessData {
  workflow_id: string;
  client_id: string;
  client_name: string;
  document_id: string;
  invoiceID: string;
  pdf_path: string;
  pdf_prefix?: string;
  image_urls?: string[];
  image_prefixes?: string[];
  di_json_url?: string;
  di_json_prefix?: string;
  ai_extracted_content?: any;
  client_config?: Record<string, any>;
  workflow_status?: string;
  workflow_substatus?: string;
}

export interface ProcessedDocumentLineItem {
  product_code: string;
  description: string;
  quantity: number;
  unit_price: number;
  extended_amount: number;
  discount?: number;
}

export interface ProcessedDocument {
  total_pages: number;
  invoice_number: string;
  invoice_date: string;
  invoice_total: number;
  line_items: ProcessedDocumentLineItem[];
}
```

### azureBlob Service:

```java
import { BlobServiceClient, StorageSharedKeyCredential } from "@azure/storage-blob";

const accountName = process.env.AZURE_STORAGE_ACCOUNT!;
const accountKey = process.env.AZURE_STORAGE_KEY!;
const containerName = process.env.AZURE_CONTAINER_NAME!;

const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, sharedKeyCredential);
const containerClient = blobServiceClient.getContainerClient(containerName);

export async function uploadBlob(blobName: string, content: Buffer, contentType: string) {
  const blockBlobClient = containerClient.getBlockBlobClient(blobName);
  await blockBlobClient.upload(content, content.length, { blobHTTPHeaders: { blobContentType: contentType } });
}

export async function getBlobContent(blobName: string): Promise<Buffer> {
  const blockBlobClient = containerClient.getBlockBlobClient(blobName);
  const downloadResponse = await blockBlobClient.download();
  return Buffer.concat(await streamToBuffer(downloadResponse.readableStreamBody!));
}

export async function getSasUrl(blobName: string): Promise<string> {
  const blockBlobClient = containerClient.getBlockBlobClient(blobName);
  return blockBlobClient.url; // Add SAS token generation if needed
}

export async function blobExists(blobName: string): Promise<boolean> {
  const blockBlobClient = containerClient.getBlockBlobClient(blobName);
  return await blockBlobClient.exists();
}

async function streamToBuffer(readableStream: NodeJS.ReadableStream): Promise<Buffer[]> {
  const chunks: Buffer[] = [];
  for await (const chunk of readableStream) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
  }
  return chunks;
}
```

### formRecoznizer:

```java
import { DocumentAnalysisClient, AzureKeyCredential } from "@azure/ai-form-recognizer";

const client = new DocumentAnalysisClient(
  process.env.FORM_RECOGNIZER_ENDPOINT!,
  new AzureKeyCredential(process.env.FORM_RECOGNIZER_KEY!)
);

export async function analyzePdf(pdfPath: string): Promise<string> {
  const poller = await client.beginAnalyzeDocument("prebuilt-invoice", pdfPath);
  const result = await poller.pollUntilDone();
  return JSON.stringify(result);
}
```

### openAiExtractor:

```java
import { OpenAIClient } from "@azure/openai";
import { AzureKeyCredential } from "@azure/core-auth";
import { ProcessedDocument } from "./types";
import { PDFDocument } from "pdf-lib";

export class OpenAIExtractor {
  private client: OpenAIClient;
  private MAX_PAGE_LIMIT = 20;

  constructor(private config: { endpoint: string; apiKey: string }) {
    this.client = new OpenAIClient(config.endpoint, new AzureKeyCredential(config.apiKey));
  }

  async extractFromBytes(content: Buffer, pageRotations: number[], filename: string): Promise<ProcessedDocument> {
    const base64Images = await this.convertPdfToImages(content);
    const batches = this.chunkArray(base64Images, 3);
    const responses: any[] = [];

    for (const batch of batches) {
      const response = await this.client.getChatCompletions("gpt-4", [
        {
          role: "user",
          content: [
            { type: "text", text: "Extract invoice details from these images" },
            ...batch.map(img => ({ type: "image_url", image_url: { url: `data:image/png;base64,${img}` } }))
          ]
        }
      ]);
      responses.push(JSON.parse(response.choices[0].message.content));
    }

    const combined = responses.length > 1 ? await this.combineResponses(responses) : responses[0];
    return combined as ProcessedDocument;
  }

  private async convertPdfToImages(content: Buffer): Promise<string[]> {
    const pdfDoc = await PDFDocument.load(content);
    const images: string[] = [];
    for (const page of pdfDoc.getPages()) {
      const pngImage = await page.renderToBuffer({ dpi: 300 }); // Use pdf-lib or pdf2pic
      images.push(pngImage.toString("base64"));
    }
    return images;
  }

  private chunkArray<T>(arr: T[], size: number): T[][] {
    return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => arr.slice(i * size, i * size + size));
  }

  private async combineResponses(responses: any[]): Promise<any> {
    const combinedPrompt = `Combine these invoice page extractions into one: ${JSON.stringify(responses)}`;
    const response = await this.client.getChatCompletions("gpt-4", [{ role: "user", content: combinedPrompt }]);
    return JSON.parse(response.choices[0].message.content);
  }
}
```

### Activities:

```java
import { ProcessData } from "./types";
import { getBlobContent, uploadBlob, getSasUrl, blobExists } from "./azureBlob";
import { OpenAIExtractor } from "./openaiExtractor";
import { analyzePdf } from "./formRecognizer";

export async function generateImages(data: ProcessData): Promise<ProcessData> {
  const pdfContent = await getBlobContent(data.pdf_path);
  const images = []; // Implement PDF to image conversion
  const sasUrls: string[] = [];
  const prefixes: string[] = [];

  for (const image of images) {
    const blobName = `jpg/${data.invoiceID}/page${image.pageNumber}/${data.pdf_prefix}-page-${image.pageNumber}.jpg`;
    await uploadBlob(blobName, image.buffer, "image/jpg");
    sasUrls.push(await getSasUrl(blobName));
    prefixes.push(blobName);
  }

  data.image_urls = sasUrls;
  data.image_prefixes = prefixes;
  return data;
}

export async function getBoundingRegions(data: ProcessData): Promise<ProcessData> {
  const blobName = `json/${data.client_id}/${data.invoiceID}/${data.pdf_prefix}.json`;
  if (!(await blobExists(blobName))) {
    const diResult = await analyzePdf(data.pdf_path);
    await uploadBlob(blobName, Buffer.from(diResult), "application/json");
  }
  data.di_json_url = await getSasUrl(blobName);
  data.di_json_prefix = blobName;
  return data;
}

export async function extractInvoice(data: ProcessData): Promise<ProcessData> {
  const pdfContent = await getBlobContent(data.pdf_path);
  const diData = JSON.parse((await getBlobContent(data.di_json_prefix)).toString());
  const pageRotations = diData.pages.map((p: any) => Math.round(p.angle));

  const extractor = new OpenAIExtractor({ endpoint: process.env.OPENAI_ENDPOINT!, apiKey: process.env.OPENAI_KEY! });
  data.ai_extracted_content = await extractor.extractFromBytes(pdfContent, pageRotations, data.pdf_path);
  return data;
}

export async function mapInvoice(data: ProcessData): Promise<ProcessData> {
  // Map AI extracted content to internal schema
  return data;
}

export async function transformInvoice(data: ProcessData): Promise<ProcessData> {
  // Combine DI and AI extracted content
  return data;
}
```

### Invoice Service:

```java
import { ProcessData } from "./types";
import { generateImages, getBoundingRegions, extractInvoice, mapInvoice, transformInvoice } from "./activities";

export async function processInvoice(data: ProcessData): Promise<ProcessData> {
  data = await generateImages(data);
  data = await getBoundingRegions(data);
  data = await extractInvoice(data);
  data = await mapInvoice(data);
  data = await transformInvoice(data);
  return
```