From 6850654d2bce1f854ee3a76958b56d6ce3bb2770 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 16:31:44 +0100 Subject: [PATCH 01/66] feat: Create PDF to Markdown microservice with NLM ingestor and OpenAI Vision API --- pdf2markdown/Dockerfile | 21 ++++ pdf2markdown/package.json | 25 ++++ pdf2markdown/src/index.ts | 127 ++++++++++++++++++++ pdf2markdown/src/lib/jsonExtraction.ts | 24 ++++ pdf2markdown/src/lib/nlm-ingestor-schema.ts | 54 +++++++++ pdf2markdown/src/lib/openai.ts | 9 ++ pdf2markdown/src/lib/pdfTools.ts | 111 +++++++++++++++++ pdf2markdown/tsconfig.json | 14 +++ 8 files changed, 385 insertions(+) create mode 100644 pdf2markdown/Dockerfile create mode 100644 pdf2markdown/package.json create mode 100644 pdf2markdown/src/index.ts create mode 100644 pdf2markdown/src/lib/jsonExtraction.ts create mode 100644 pdf2markdown/src/lib/nlm-ingestor-schema.ts create mode 100644 pdf2markdown/src/lib/openai.ts create mode 100644 pdf2markdown/src/lib/pdfTools.ts create mode 100644 pdf2markdown/tsconfig.json diff --git a/pdf2markdown/Dockerfile b/pdf2markdown/Dockerfile new file mode 100644 index 00000000..9af0bd86 --- /dev/null +++ b/pdf2markdown/Dockerfile @@ -0,0 +1,21 @@ +FROM ghcr.io/nlmatics/nlm-ingestor:latest + +# Install Node.js 22 +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs + +# Install app dependencies +WORKDIR /app +COPY package*.json ./ +RUN npm install + +# Copy app source +COPY . . + +# Build TypeScript +RUN npm run build + +EXPOSE 3000 + +# Start both NLM ingestor and our Express service +CMD service nlm-ingestor start && npm start diff --git a/pdf2markdown/package.json b/pdf2markdown/package.json new file mode 100644 index 00000000..3dc36f19 --- /dev/null +++ b/pdf2markdown/package.json @@ -0,0 +1,25 @@ +{ + "name": "pdf2markdown", + "version": "1.0.0", + "type": "module", + "engines": { + "node": ">=22.0.0" + }, + "scripts": { + "start": "node --import tsx src/index.ts", + "dev": "tsx watch src/index.ts" + }, + "dependencies": { + "@types/node": "^22.10.0", + "express": "^5.0.1", + "openai": "^4.73.1", + "pdf2pic": "^3.1.3", + "sharp": "^0.33.5", + "tsx": "^4.19.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "typescript": "^5.7.2" + } +} diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts new file mode 100644 index 00000000..80fa72e9 --- /dev/null +++ b/pdf2markdown/src/index.ts @@ -0,0 +1,127 @@ +import express from 'express' +import { extractJsonFromPdf } from './lib/pdfTools' +import { jsonToMarkdown } from './lib/jsonExtraction' +import { extractTablesFromJson } from './lib/pdfTools' +import { mkdir } from 'fs/promises' +import path from 'path' +import { openai } from './lib/openai' +import { readFileSync } from 'fs' + +const app = express() +const port = 3000 + +const base64Encode = (filename: string) => { + const data = readFileSync(path.resolve(filename)).toString('base64') + return 'data:image/png;base64,' + data +} + +const extractTextViaVisionAPI = async ( + { + filename, + name, + }: { + filename: string + name: string + }, + context: string +) => { + const result = await openai.chat.completions.create({ + model: 'gpt-4-vision-preview', + messages: [ + { + role: 'system', + content: + 'You are a CSRD expert and will extract text from a PDF with extract from the tables.', + }, + { + role: 'user', + content: `I have a PDF with couple of tables related to a company's CO2 emissions. Can you extract the text from screenshot. I will send you the screenshot extract the header and table contents and ignore the surrounding text if they are not related to the tables/graphs (such as header, description, footnotes or disclaimers). Use Markdown format for the table(s), only reply with markdown. OK?`, + }, + { + role: 'assistant', + content: + 'Sure. Sounds good. Send the screenhot and I will extract the table(s) and return in markdown format as accurately as possible without any other comment.', + }, + { + role: 'assistant', + content: + 'This is previous table extracted from previous pages:' + context, + }, + { + role: 'user', + content: [ + { + type: 'image_url', + image_url: { url: base64Encode(filename), detail: 'high' }, + }, + ], + }, + ], + max_tokens: 4096, + }) + return result.choices[0].message.content +} + +app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req, res) => { + try { + const buffer = req.body + const json = await extractJsonFromPdf(buffer) + const baseMarkdown = jsonToMarkdown(json) + + const outputDir = path.resolve('/tmp', 'pdf2markdown-screenshots') + await mkdir(outputDir, { recursive: true }) + + const searchTerms = [ + 'co2', + 'GHG', + 'turnover', + 'revenue', + 'income', + 'employees', + 'FTE', + 'fiscal year', + 'summary', + ] + + const { pages } = await extractTablesFromJson( + buffer, + json, + outputDir, + searchTerms + ) + + const tables = await pages.reduce(async (resultsPromise, { pageIndex, filename }) => { + const results = await resultsPromise + const lastPageMarkdown = results.at(-1)?.markdown || '' + const markdown = await extractTextViaVisionAPI( + { filename, name: `Tables from page ${pageIndex}` }, + lastPageMarkdown + ) + return [ + ...results, + { + page_idx: Number(pageIndex), + markdown, + }, + ] + }, Promise.resolve([] as any)) + + const fullMarkdown = baseMarkdown + + '\n\n This is some of the important tables from the markdown with more precision:' + + tables + .map( + ({ page_idx, markdown }) => + `\n#### Page ${page_idx}: + ${markdown}` + ) + .join('\n') + + res.json({ markdown: fullMarkdown }) + } catch (error) { + res.status(500).json({ error: error.message }) + } +}) + +app.listen(port, () => { + console.log(`PDF to Markdown service running on port ${port}`) +}) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts new file mode 100644 index 00000000..15a4dc6b --- /dev/null +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -0,0 +1,24 @@ +import { Block, ParsedDocument, Table } from './nlm-ingestor-schema' + +export function jsonToTables(json: ParsedDocument): Table[] { + return json.return_dict.blocks.filter( + (block): block is Table => 'rows' in block + ) +} + +export function jsonToMarkdown(json: ParsedDocument): string { + const blocks = json.return_dict.blocks + + return blocks + .map((block: Block) => { + if ('rows' in block) { + return `Table: ${block.content}` + } else if ('level' in block) { + const prefix = '#'.repeat(block.level) + return `${prefix} ${block.content}` + } else { + return block.content + } + }) + .join('\n\n') +} diff --git a/pdf2markdown/src/lib/nlm-ingestor-schema.ts b/pdf2markdown/src/lib/nlm-ingestor-schema.ts new file mode 100644 index 00000000..a4120ad6 --- /dev/null +++ b/pdf2markdown/src/lib/nlm-ingestor-schema.ts @@ -0,0 +1,54 @@ +import { z } from 'zod' + +export const CellSchema = z.object({ + content: z.string(), + bbox: z.array(z.number()), + row_nums: z.array(z.number()), + col_nums: z.array(z.number()), +}) + +export const ParagraphSchema = z.object({ + content: z.string(), + bbox: z.array(z.number()), +}) + +export const HeaderSchema = z.object({ + content: z.string(), + bbox: z.array(z.number()), + level: z.number(), +}) + +export const ListItemSchema = z.object({ + content: z.string(), + bbox: z.array(z.number()), + level: z.number(), +}) + +export const TableSchema = z.object({ + content: z.string(), + bbox: z.array(z.number()), + rows: z.array(z.any()), + level: z.number(), +}) + +export const BlockSchema = z.union([ + ParagraphSchema, + HeaderSchema, + ListItemSchema, + TableSchema, +]) + +export const ParsedDocumentSchema = z.object({ + return_dict: z.object({ + blocks: z.array(BlockSchema), + page_dim: z.array(z.number()), + }), +}) + +export type Cell = z.infer +export type Paragraph = z.infer +export type Header = z.infer +export type ListItem = z.infer +export type Table = z.infer +export type Block = z.infer +export type ParsedDocument = z.infer diff --git a/pdf2markdown/src/lib/openai.ts b/pdf2markdown/src/lib/openai.ts new file mode 100644 index 00000000..aa47b451 --- /dev/null +++ b/pdf2markdown/src/lib/openai.ts @@ -0,0 +1,9 @@ +import OpenAI from 'openai' + +if (!process.env.OPENAI_API_KEY) { + throw new Error('Missing OPENAI_API_KEY environment variable') +} + +export const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts new file mode 100644 index 00000000..1f3a5ba5 --- /dev/null +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -0,0 +1,111 @@ +import { fromBuffer } from 'pdf2pic' +import path from 'path' +import { ParsedDocument, ParsedDocumentSchema } from './nlm-ingestor-schema' +import { jsonToTables, Table } from './jsonExtraction' +import { writeFile } from 'fs/promises' + +const NLM_INGESTOR_URL = 'http://localhost:5001' + +export async function extractJsonFromPdf(buffer: Buffer): Promise { + const formData = new FormData() + formData.append('file', new Blob([buffer]), 'document.pdf') + const url = `${NLM_INGESTOR_URL}/api/parseDocument?renderFormat=json` + + const response = await fetch(url, { + method: 'POST', + body: formData, + signal: AbortSignal.timeout(6 * 60 * 1000), // 6 minutes + }) + + if (!response.ok) { + throw new Error(`Failed to parse PDF: ${response.statusText}`) + } + + const body = await response.json() + try { + return ParsedDocumentSchema.parse(body) + } catch (error) { + throw new Error( + `Failed to parse PDF: nlm-ingestor response schema did not match expected format: ${error.message}`, + { cause: error } + ) + } +} + +type Page = { + pageIndex: number + pageWidth: number + pageHeight: number + tables: any[] +} + +export function findRelevantTablesGroupedOnPages( + json: ParsedDocument, + searchTerms: string[] +): Page[] { + const tables = jsonToTables(json).filter(({ content }) => + searchTerms.some((term) => + content.toLowerCase().includes(term.toLowerCase()) + ) + ) + return tables.reduce((acc: Page[], table: Table) => { + const [pageWidth, pageHeight] = json.return_dict.page_dim + const pageIndex = table.page_idx + const page = acc.find((p) => p.pageIndex === pageIndex) + if (page) { + page.tables.push(table) + } else { + acc.push({ + pageIndex, + tables: [table], + pageWidth, + pageHeight, + }) + } + return acc + }, []) +} + +export async function extractTablesFromJson( + pdf: Buffer, + json: ParsedDocument, + outputDir: string, + searchTerms: string[] +): Promise<{ pages: { pageIndex: number; filename: string }[] }> { + const pdfConverter = (height: number, width: number) => { + return fromBuffer(pdf, { + density: 600, + format: 'png', + width, + height, + preserveAspectRatio: true, + }) + } + + const pages = Object.values( + findRelevantTablesGroupedOnPages(json, searchTerms) + ) + const reportId = crypto.randomUUID() + const filenames = await Promise.all( + pages.map(async ({ pageIndex, pageHeight, pageWidth }) => { + const pageNumber = pageIndex + 1 + const pageScreenshotPath = path.join( + outputDir, + `${reportId}-page-${pageNumber}.png` + ) + const convert = pdfConverter(pageHeight * 2, pageWidth * 2) + const result = await convert(pageNumber, { responseType: 'buffer' }) + + if (!result.buffer) { + throw new Error( + `Failed to convert pageNumber ${pageNumber} to a buffer\n` + + JSON.stringify(result, null, 2) + ) + } + + await writeFile(pageScreenshotPath, result.buffer) + return { pageIndex, filename: pageScreenshotPath } + }) + ) + return { pages: filenames } +} diff --git a/pdf2markdown/tsconfig.json b/pdf2markdown/tsconfig.json new file mode 100644 index 00000000..1602c2a8 --- /dev/null +++ b/pdf2markdown/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "./src", + "skipLibCheck": true, + "strictNullChecks": true + }, + "include": ["src"] +} From cfd22b37ae028d7363fabf24195fa3f8209d9aa0 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 16:33:46 +0100 Subject: [PATCH 02/66] feat: Return markdown text directly instead of JSON response --- pdf2markdown/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 80fa72e9..0d4e3e6f 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -116,7 +116,7 @@ app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req, res) ) .join('\n') - res.json({ markdown: fullMarkdown }) + res.type('text/plain').send(fullMarkdown) } catch (error) { res.status(500).json({ error: error.message }) } From bbaa8a2acd191b10e41bda4e4fcac551afe19962 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 16:35:32 +0100 Subject: [PATCH 03/66] refactor: Optimize table extraction by directly using Vision API in jsonToMarkdown --- pdf2markdown/src/index.ts | 53 +---------- pdf2markdown/src/lib/jsonExtraction.ts | 116 ++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 63 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 0d4e3e6f..dc94830d 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -66,57 +66,8 @@ app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req, res) try { const buffer = req.body const json = await extractJsonFromPdf(buffer) - const baseMarkdown = jsonToMarkdown(json) - - const outputDir = path.resolve('/tmp', 'pdf2markdown-screenshots') - await mkdir(outputDir, { recursive: true }) - - const searchTerms = [ - 'co2', - 'GHG', - 'turnover', - 'revenue', - 'income', - 'employees', - 'FTE', - 'fiscal year', - 'summary', - ] - - const { pages } = await extractTablesFromJson( - buffer, - json, - outputDir, - searchTerms - ) - - const tables = await pages.reduce(async (resultsPromise, { pageIndex, filename }) => { - const results = await resultsPromise - const lastPageMarkdown = results.at(-1)?.markdown || '' - const markdown = await extractTextViaVisionAPI( - { filename, name: `Tables from page ${pageIndex}` }, - lastPageMarkdown - ) - return [ - ...results, - { - page_idx: Number(pageIndex), - markdown, - }, - ] - }, Promise.resolve([] as any)) - - const fullMarkdown = baseMarkdown + - '\n\n This is some of the important tables from the markdown with more precision:' + - tables - .map( - ({ page_idx, markdown }) => - `\n#### Page ${page_idx}: - ${markdown}` - ) - .join('\n') - - res.type('text/plain').send(fullMarkdown) + const markdown = await jsonToMarkdown(json, buffer) + res.type('text/plain').send(markdown) } catch (error) { res.status(500).json({ error: error.message }) } diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 15a4dc6b..f6d9dee5 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -1,4 +1,61 @@ +import { readFileSync } from 'fs' +import path from 'path' import { Block, ParsedDocument, Table } from './nlm-ingestor-schema' +import { fromBuffer } from 'pdf2pic' +import { openai } from './openai' +import { mkdir, writeFile } from 'fs/promises' + +const base64Encode = (filename: string) => { + const data = readFileSync(path.resolve(filename)).toString('base64') + return 'data:image/png;base64,' + data +} + +export async function extractTextViaVisionAPI( + { + filename, + name, + }: { + filename: string + name: string + }, + context: string +) { + const result = await openai.chat.completions.create({ + model: 'gpt-4-vision-preview', + messages: [ + { + role: 'system', + content: + 'You are a CSRD expert and will extract text from a PDF with extract from the tables.', + }, + { + role: 'user', + content: `I have a PDF with couple of tables related to a company's CO2 emissions. Can you extract the text from screenshot. I will send you the screenshot extract the header and table contents and ignore the surrounding text if they are not related to the tables/graphs (such as header, description, footnotes or disclaimers). Use Markdown format for the table(s), only reply with markdown. OK?`, + }, + { + role: 'assistant', + content: + 'Sure. Sounds good. Send the screenhot and I will extract the table(s) and return in markdown format as accurately as possible without any other comment.', + }, + { + role: 'assistant', + content: + 'This is previous table extracted from previous pages:' + context, + }, + { + role: 'user', + content: [ + { + type: 'image_url', + image_url: { url: base64Encode(filename), detail: 'high' }, + }, + ], + }, + ], + max_tokens: 4096, + }) + return result.choices[0].message.content +} export function jsonToTables(json: ParsedDocument): Table[] { return json.return_dict.blocks.filter( @@ -6,19 +63,54 @@ export function jsonToTables(json: ParsedDocument): Table[] { ) } -export function jsonToMarkdown(json: ParsedDocument): string { +export async function jsonToMarkdown(json: ParsedDocument, pdf: Buffer): Promise { const blocks = json.return_dict.blocks + const [pageWidth, pageHeight] = json.return_dict.page_dim + const outputDir = path.resolve('/tmp', 'pdf2markdown-screenshots') + await mkdir(outputDir, { recursive: true }) + + const reportId = crypto.randomUUID() + let lastTableMarkdown = '' + + const markdownBlocks = await Promise.all(blocks.map(async (block: Block) => { + if ('rows' in block) { + // For tables, convert the page to image and use Vision API + const pageNumber = block.page_idx + 1 + const pageScreenshotPath = path.join( + outputDir, + `${reportId}-page-${pageNumber}.png` + ) - return blocks - .map((block: Block) => { - if ('rows' in block) { - return `Table: ${block.content}` - } else if ('level' in block) { - const prefix = '#'.repeat(block.level) - return `${prefix} ${block.content}` - } else { - return block.content + // Convert page to image + const pdfConverter = fromBuffer(pdf, { + density: 600, + format: 'png', + width: pageWidth * 2, + height: pageHeight * 2, + preserveAspectRatio: true, + }) + + const result = await pdfConverter(pageNumber, { responseType: 'buffer' }) + if (!result.buffer) { + throw new Error(`Failed to convert page ${pageNumber} to image`) } - }) - .join('\n\n') + + await writeFile(pageScreenshotPath, result.buffer) + + // Extract table text using Vision API + const markdown = await extractTextViaVisionAPI( + { filename: pageScreenshotPath, name: `Table from page ${pageNumber}` }, + lastTableMarkdown + ) + lastTableMarkdown = markdown + return markdown + } else if ('level' in block) { + const prefix = '#'.repeat(block.level) + return `${prefix} ${block.content}` + } else { + return block.content + } + })) + + return markdownBlocks.join('\n\n') } From 4338766363b78cf99769d0d519c0d284bbae66cf Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 23:25:52 +0100 Subject: [PATCH 04/66] refactor: Remove duplicate code, add error handling, and improve temp file cleanup --- pdf2markdown/src/index.ts | 10 +++++++++- pdf2markdown/src/lib/jsonExtraction.ts | 14 +++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index dc94830d..deb0c04b 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -62,14 +62,22 @@ const extractTextViaVisionAPI = async ( return result.choices[0].message.content } -app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req, res) => { +app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req: express.Request, res: express.Response) => { try { const buffer = req.body const json = await extractJsonFromPdf(buffer) const markdown = await jsonToMarkdown(json, buffer) res.type('text/plain').send(markdown) } catch (error) { + console.error('Conversion error:', error) res.status(500).json({ error: error.message }) + + // Cleanup temp files + try { + await rm('/tmp/pdf2markdown-screenshots', { recursive: true, force: true }) + } catch (cleanupError) { + console.error('Failed to cleanup temp files:', cleanupError) + } } }) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index f6d9dee5..12ad99b1 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -54,6 +54,9 @@ export async function extractTextViaVisionAPI( ], max_tokens: 4096, }) + if (!result.choices[0]?.message?.content) { + throw new Error('Failed to get content from Vision API') + } return result.choices[0].message.content } @@ -112,5 +115,14 @@ export async function jsonToMarkdown(json: ParsedDocument, pdf: Buffer): Promise } })) - return markdownBlocks.join('\n\n') + const markdown = markdownBlocks.join('\n\n') + + // Cleanup temp files + try { + await rm(outputDir, { recursive: true, force: true }) + } catch (error) { + console.error('Failed to cleanup temp files:', error) + } + + return markdown } From 757a199d17c8c1fd2dce2ce5f3846cf49b850a09 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 23:26:06 +0100 Subject: [PATCH 05/66] refactor: Remove unused imports and vision API extraction function --- pdf2markdown/src/index.ts | 56 +-------------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index deb0c04b..f36c93cf 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -2,66 +2,12 @@ import express from 'express' import { extractJsonFromPdf } from './lib/pdfTools' import { jsonToMarkdown } from './lib/jsonExtraction' import { extractTablesFromJson } from './lib/pdfTools' -import { mkdir } from 'fs/promises' +import { mkdir, rm } from 'fs/promises' import path from 'path' -import { openai } from './lib/openai' -import { readFileSync } from 'fs' const app = express() const port = 3000 -const base64Encode = (filename: string) => { - const data = readFileSync(path.resolve(filename)).toString('base64') - return 'data:image/png;base64,' + data -} - -const extractTextViaVisionAPI = async ( - { - filename, - name, - }: { - filename: string - name: string - }, - context: string -) => { - const result = await openai.chat.completions.create({ - model: 'gpt-4-vision-preview', - messages: [ - { - role: 'system', - content: - 'You are a CSRD expert and will extract text from a PDF with extract from the tables.', - }, - { - role: 'user', - content: `I have a PDF with couple of tables related to a company's CO2 emissions. Can you extract the text from screenshot. I will send you the screenshot extract the header and table contents and ignore the surrounding text if they are not related to the tables/graphs (such as header, description, footnotes or disclaimers). Use Markdown format for the table(s), only reply with markdown. OK?`, - }, - { - role: 'assistant', - content: - 'Sure. Sounds good. Send the screenhot and I will extract the table(s) and return in markdown format as accurately as possible without any other comment.', - }, - { - role: 'assistant', - content: - 'This is previous table extracted from previous pages:' + context, - }, - { - role: 'user', - content: [ - { - type: 'image_url', - image_url: { url: base64Encode(filename), detail: 'high' }, - }, - ], - }, - ], - max_tokens: 4096, - }) - return result.choices[0].message.content -} - app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req: express.Request, res: express.Response) => { try { const buffer = req.body From 18168b39dfbdcdc1fffc036622091b0d15f48ede Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 23:27:04 +0100 Subject: [PATCH 06/66] refactor: Optimize table extraction by using buffer instead of temporary files --- pdf2markdown/src/lib/jsonExtraction.ts | 31 +++++--------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 12ad99b1..64bfff0d 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -5,17 +5,16 @@ import { fromBuffer } from 'pdf2pic' import { openai } from './openai' import { mkdir, writeFile } from 'fs/promises' -const base64Encode = (filename: string) => { - const data = readFileSync(path.resolve(filename)).toString('base64') - return 'data:image/png;base64,' + data +const bufferToBase64 = (buffer: Buffer) => { + return 'data:image/png;base64,' + buffer.toString('base64') } export async function extractTextViaVisionAPI( { - filename, + buffer, name, }: { - filename: string + buffer: Buffer name: string }, context: string @@ -47,7 +46,7 @@ export async function extractTextViaVisionAPI( content: [ { type: 'image_url', - image_url: { url: base64Encode(filename), detail: 'high' }, + image_url: { url: bufferToBase64(buffer), detail: 'high' }, }, ], }, @@ -69,21 +68,12 @@ export function jsonToTables(json: ParsedDocument): Table[] { export async function jsonToMarkdown(json: ParsedDocument, pdf: Buffer): Promise { const blocks = json.return_dict.blocks const [pageWidth, pageHeight] = json.return_dict.page_dim - const outputDir = path.resolve('/tmp', 'pdf2markdown-screenshots') - await mkdir(outputDir, { recursive: true }) - - const reportId = crypto.randomUUID() let lastTableMarkdown = '' const markdownBlocks = await Promise.all(blocks.map(async (block: Block) => { if ('rows' in block) { // For tables, convert the page to image and use Vision API const pageNumber = block.page_idx + 1 - const pageScreenshotPath = path.join( - outputDir, - `${reportId}-page-${pageNumber}.png` - ) - // Convert page to image const pdfConverter = fromBuffer(pdf, { density: 600, @@ -98,11 +88,9 @@ export async function jsonToMarkdown(json: ParsedDocument, pdf: Buffer): Promise throw new Error(`Failed to convert page ${pageNumber} to image`) } - await writeFile(pageScreenshotPath, result.buffer) - // Extract table text using Vision API const markdown = await extractTextViaVisionAPI( - { filename: pageScreenshotPath, name: `Table from page ${pageNumber}` }, + { buffer: result.buffer, name: `Table from page ${pageNumber}` }, lastTableMarkdown ) lastTableMarkdown = markdown @@ -117,12 +105,5 @@ export async function jsonToMarkdown(json: ParsedDocument, pdf: Buffer): Promise const markdown = markdownBlocks.join('\n\n') - // Cleanup temp files - try { - await rm(outputDir, { recursive: true, force: true }) - } catch (error) { - console.error('Failed to cleanup temp files:', error) - } - return markdown } From 7e52a8d5de36e6d092c1f59e71bb4efc1d991925 Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Thu, 28 Nov 2024 23:32:54 +0100 Subject: [PATCH 07/66] fix: bugfixes --- pdf2markdown/src/index.ts | 41 +++++++------ pdf2markdown/src/lib/jsonExtraction.ts | 82 +++++++++++++------------- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index f36c93cf..a6850054 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,31 +1,36 @@ import express from 'express' import { extractJsonFromPdf } from './lib/pdfTools' import { jsonToMarkdown } from './lib/jsonExtraction' -import { extractTablesFromJson } from './lib/pdfTools' -import { mkdir, rm } from 'fs/promises' -import path from 'path' +import { rm } from 'fs/promises' const app = express() const port = 3000 -app.post('/convert', express.raw({type: '*/*', limit: '50mb'}), async (req: express.Request, res: express.Response) => { - try { - const buffer = req.body - const json = await extractJsonFromPdf(buffer) - const markdown = await jsonToMarkdown(json, buffer) - res.type('text/plain').send(markdown) - } catch (error) { - console.error('Conversion error:', error) - res.status(500).json({ error: error.message }) - - // Cleanup temp files +app.post( + '/convert', + express.raw({ type: '*/*', limit: '50mb' }), + async (req: express.Request, res: express.Response) => { try { - await rm('/tmp/pdf2markdown-screenshots', { recursive: true, force: true }) - } catch (cleanupError) { - console.error('Failed to cleanup temp files:', cleanupError) + const buffer = req.body + const json = await extractJsonFromPdf(buffer) + const markdown = await jsonToMarkdown(json, buffer) + res.type('text/plain').send(markdown) + } catch (error) { + console.error('Conversion error:', error) + res.status(500).json({ error: error.message }) + + // Cleanup temp files + try { + await rm('/tmp/pdf2markdown-screenshots', { + recursive: true, + force: true, + }) + } catch (cleanupError) { + console.error('Failed to cleanup temp files:', cleanupError) + } } } -}) +) app.listen(port, () => { console.log(`PDF to Markdown service running on port ${port}`) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 64bfff0d..d2b83cb9 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -1,9 +1,6 @@ -import { readFileSync } from 'fs' -import path from 'path' import { Block, ParsedDocument, Table } from './nlm-ingestor-schema' import { fromBuffer } from 'pdf2pic' import { openai } from './openai' -import { mkdir, writeFile } from 'fs/promises' const bufferToBase64 = (buffer: Buffer) => { return 'data:image/png;base64,' + buffer.toString('base64') @@ -12,10 +9,8 @@ const bufferToBase64 = (buffer: Buffer) => { export async function extractTextViaVisionAPI( { buffer, - name, }: { buffer: Buffer - name: string }, context: string ) { @@ -39,7 +34,8 @@ export async function extractTextViaVisionAPI( { role: 'assistant', content: - 'This is previous table extracted from previous pages:' + context, + 'This is previous text extracted with a less accurate method:' + + context, }, { role: 'user', @@ -65,45 +61,51 @@ export function jsonToTables(json: ParsedDocument): Table[] { ) } -export async function jsonToMarkdown(json: ParsedDocument, pdf: Buffer): Promise { +export async function jsonToMarkdown( + json: ParsedDocument, + pdf: Buffer +): Promise { const blocks = json.return_dict.blocks const [pageWidth, pageHeight] = json.return_dict.page_dim - let lastTableMarkdown = '' - const markdownBlocks = await Promise.all(blocks.map(async (block: Block) => { - if ('rows' in block) { - // For tables, convert the page to image and use Vision API - const pageNumber = block.page_idx + 1 - // Convert page to image - const pdfConverter = fromBuffer(pdf, { - density: 600, - format: 'png', - width: pageWidth * 2, - height: pageHeight * 2, - preserveAspectRatio: true, - }) - - const result = await pdfConverter(pageNumber, { responseType: 'buffer' }) - if (!result.buffer) { - throw new Error(`Failed to convert page ${pageNumber} to image`) + const markdownBlocks = await Promise.all( + blocks.map(async (block: Block) => { + if ('rows' in block) { + // For tables, convert the page to image and use Vision API + const pageNumber = block.page_idx + 1 + // Convert page to image + const pdfConverter = fromBuffer(pdf, { + density: 600, + format: 'png', + width: pageWidth * 2, + height: pageHeight * 2, + preserveAspectRatio: true, + }) + + const result = await pdfConverter(pageNumber, { + responseType: 'buffer', + }) + if (!result.buffer) { + throw new Error(`Failed to convert page ${pageNumber} to image`) + } + + // Extract table text using Vision API + const markdown = await extractTextViaVisionAPI( + { buffer: result.buffer }, + block.content + ) + lastTableMarkdown = markdown + return markdown + } else if ('level' in block) { + const prefix = '#'.repeat(block.level) + return `${prefix} ${block.content}` + } else { + return block.content } - - // Extract table text using Vision API - const markdown = await extractTextViaVisionAPI( - { buffer: result.buffer, name: `Table from page ${pageNumber}` }, - lastTableMarkdown - ) - lastTableMarkdown = markdown - return markdown - } else if ('level' in block) { - const prefix = '#'.repeat(block.level) - return `${prefix} ${block.content}` - } else { - return block.content - } - })) + }) + ) const markdown = markdownBlocks.join('\n\n') - + return markdown } From 5b7f95030d24909468ebab02fb82a785389dacc7 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 23:38:50 +0100 Subject: [PATCH 08/66] refactor: Add NLM ingestor health check and improve error logging --- pdf2markdown/src/lib/pdfTools.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 1f3a5ba5..a619a0e0 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -6,7 +6,19 @@ import { writeFile } from 'fs/promises' const NLM_INGESTOR_URL = 'http://localhost:5001' +async function checkNLMIngestorStatus() { + try { + const response = await fetch(`${NLM_INGESTOR_URL}/health`) + if (!response.ok) { + throw new Error(`NLM Ingestor health check failed: ${response.statusText}`) + } + } catch (error) { + throw new Error(`NLM Ingestor service not available: ${error.message}`) + } +} + export async function extractJsonFromPdf(buffer: Buffer): Promise { + await checkNLMIngestorStatus() const formData = new FormData() formData.append('file', new Blob([buffer]), 'document.pdf') const url = `${NLM_INGESTOR_URL}/api/parseDocument?renderFormat=json` @@ -22,9 +34,12 @@ export async function extractJsonFromPdf(buffer: Buffer): Promise Date: Thu, 28 Nov 2024 23:50:09 +0100 Subject: [PATCH 09/66] refactor: Remove unnecessary lastTableMarkdown assignment --- pdf2markdown/src/lib/jsonExtraction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index d2b83cb9..9e5e0906 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -94,7 +94,6 @@ export async function jsonToMarkdown( { buffer: result.buffer }, block.content ) - lastTableMarkdown = markdown return markdown } else if ('level' in block) { const prefix = '#'.repeat(block.level) From 68937dbdfa7737517cb7910a084c2d7c1c5036ad Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Thu, 28 Nov 2024 23:50:11 +0100 Subject: [PATCH 10/66] fix: Update NLM ingestor schema to match new response structure --- pdf2markdown/src/lib/jsonExtraction.ts | 4 ++-- pdf2markdown/src/lib/nlm-ingestor-schema.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 9e5e0906..7a1755e9 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -56,7 +56,7 @@ export async function extractTextViaVisionAPI( } export function jsonToTables(json: ParsedDocument): Table[] { - return json.return_dict.blocks.filter( + return json.return_dict.result.blocks.filter( (block): block is Table => 'rows' in block ) } @@ -65,7 +65,7 @@ export async function jsonToMarkdown( json: ParsedDocument, pdf: Buffer ): Promise { - const blocks = json.return_dict.blocks + const blocks = json.return_dict.result.blocks const [pageWidth, pageHeight] = json.return_dict.page_dim const markdownBlocks = await Promise.all( diff --git a/pdf2markdown/src/lib/nlm-ingestor-schema.ts b/pdf2markdown/src/lib/nlm-ingestor-schema.ts index a4120ad6..66c7de5a 100644 --- a/pdf2markdown/src/lib/nlm-ingestor-schema.ts +++ b/pdf2markdown/src/lib/nlm-ingestor-schema.ts @@ -40,8 +40,11 @@ export const BlockSchema = z.union([ export const ParsedDocumentSchema = z.object({ return_dict: z.object({ - blocks: z.array(BlockSchema), + result: z.object({ + blocks: z.array(BlockSchema), + }), page_dim: z.array(z.number()), + num_pages: z.number(), }), }) From 02d42732d0447841e65f41c438c19bab0c0f2527 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:00:40 +0100 Subject: [PATCH 11/66] fix: Handle undefined block content in PDF conversion --- pdf2markdown/src/lib/jsonExtraction.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 7a1755e9..a4b425a8 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -65,6 +65,7 @@ export async function jsonToMarkdown( json: ParsedDocument, pdf: Buffer ): Promise { + console.log('Processing document with blocks:', JSON.stringify(json.return_dict.result.blocks, null, 2)) const blocks = json.return_dict.result.blocks const [pageWidth, pageHeight] = json.return_dict.page_dim @@ -95,11 +96,13 @@ export async function jsonToMarkdown( block.content ) return markdown - } else if ('level' in block) { - const prefix = '#'.repeat(block.level) + } else if ('level' in block && block.content) { + const prefix = '#'.repeat(block.level + 1) // Add 1 to avoid level 0 headers return `${prefix} ${block.content}` - } else { + } else if (block.content) { return block.content + } else { + return '' // Return empty string instead of undefined content } }) ) From bc7543030eebb58b6d217628d2b1d09798143670 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:02:02 +0100 Subject: [PATCH 12/66] refactor: Improve JSON to Markdown conversion with better block handling and logging --- pdf2markdown/src/lib/jsonExtraction.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index a4b425a8..54f594c3 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -66,6 +66,12 @@ export async function jsonToMarkdown( pdf: Buffer ): Promise { console.log('Processing document with blocks:', JSON.stringify(json.return_dict.result.blocks, null, 2)) + + if (!json.return_dict.result.blocks || json.return_dict.result.blocks.length === 0) { + console.error('No blocks found in document') + return 'No content found in document' + } + const blocks = json.return_dict.result.blocks const [pageWidth, pageHeight] = json.return_dict.page_dim @@ -96,18 +102,26 @@ export async function jsonToMarkdown( block.content ) return markdown - } else if ('level' in block && block.content) { - const prefix = '#'.repeat(block.level + 1) // Add 1 to avoid level 0 headers - return `${prefix} ${block.content}` + } else if ('level' in block) { + console.log('Processing header block:', block) + if (typeof block.content === 'string' && block.content.trim()) { + const level = Math.max(1, Math.min(6, block.level + 1)) // Ensure level is between 1-6 + const prefix = '#'.repeat(level) + return `${prefix} ${block.content.trim()}` + } } else if (block.content) { - return block.content + console.log('Processing content block:', block) + return block.content.trim() } else { - return '' // Return empty string instead of undefined content + console.log('Skipping empty block:', block) + return null } }) ) - const markdown = markdownBlocks.join('\n\n') + const markdown = (await Promise.all(markdownBlocks)) + .filter(block => block !== null && block !== '') + .join('\n\n') return markdown } From 44c6fc4b669659e60c50d958e1586f272baee5ac Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Fri, 29 Nov 2024 07:02:50 +0100 Subject: [PATCH 13/66] refactor: Update NLM ingestor health check and remove schema validation --- pdf2markdown/src/lib/pdfTools.ts | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index a619a0e0..87a8acb6 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -8,16 +8,20 @@ const NLM_INGESTOR_URL = 'http://localhost:5001' async function checkNLMIngestorStatus() { try { - const response = await fetch(`${NLM_INGESTOR_URL}/health`) + const response = await fetch(`${NLM_INGESTOR_URL}`) if (!response.ok) { - throw new Error(`NLM Ingestor health check failed: ${response.statusText}`) + throw new Error( + `NLM Ingestor health check failed: ${response.statusText}` + ) } } catch (error) { throw new Error(`NLM Ingestor service not available: ${error.message}`) } } -export async function extractJsonFromPdf(buffer: Buffer): Promise { +export async function extractJsonFromPdf( + buffer: Buffer +): Promise { await checkNLMIngestorStatus() const formData = new FormData() formData.append('file', new Blob([buffer]), 'document.pdf') @@ -35,16 +39,7 @@ export async function extractJsonFromPdf(buffer: Buffer): Promise Date: Fri, 29 Nov 2024 07:02:51 +0100 Subject: [PATCH 14/66] refactor: Add schema validation and error handling for PDF conversion --- pdf2markdown/src/lib/jsonExtraction.ts | 36 +++++++++++++++++--------- pdf2markdown/src/lib/pdfTools.ts | 10 ++++++- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 54f594c3..212ef54c 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -65,14 +65,17 @@ export async function jsonToMarkdown( json: ParsedDocument, pdf: Buffer ): Promise { - console.log('Processing document with blocks:', JSON.stringify(json.return_dict.result.blocks, null, 2)) - - if (!json.return_dict.result.blocks || json.return_dict.result.blocks.length === 0) { - console.error('No blocks found in document') - return 'No content found in document' - } - - const blocks = json.return_dict.result.blocks + try { + // Validate input + const result = ParsedDocumentSchema.parse(json) + const blocks = result.return_dict.result.blocks + + if (!blocks || blocks.length === 0) { + console.error('No blocks found in document') + return 'No content found in document' + } + + console.log(`Processing ${blocks.length} blocks:`, JSON.stringify(blocks, null, 2)) const [pageWidth, pageHeight] = json.return_dict.page_dim const markdownBlocks = await Promise.all( @@ -119,9 +122,18 @@ export async function jsonToMarkdown( }) ) - const markdown = (await Promise.all(markdownBlocks)) - .filter(block => block !== null && block !== '') - .join('\n\n') + const markdown = (await Promise.all(markdownBlocks)) + .filter(block => block !== null && block !== '') + .join('\n\n') - return markdown + if (!markdown.trim()) { + console.error('No content was extracted from blocks') + return 'No content could be extracted from document' + } + + return markdown + } catch (error) { + console.error('Error processing document:', error) + throw new Error(`Failed to process document: ${error.message}`) + } } diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 87a8acb6..5dcea516 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -39,7 +39,15 @@ export async function extractJsonFromPdf( const body = await response.json() console.log('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - return body + + // Validate response against schema + const result = ParsedDocumentSchema.safeParse(body) + if (!result.success) { + console.error('Schema validation failed:', result.error) + throw new Error('Invalid response format from NLM Ingestor') + } + + return result.data } type Page = { From 8f7b761d5cfc8ae77000ec0c8ae41b39ed9cee00 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:03:32 +0100 Subject: [PATCH 15/66] refactor: Add detailed schema validation debugging for NLM Ingestor response --- pdf2markdown/src/lib/pdfTools.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 5dcea516..fead4f7e 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -40,11 +40,17 @@ export async function extractJsonFromPdf( const body = await response.json() console.log('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - // Validate response against schema + // Add detailed schema validation debugging const result = ParsedDocumentSchema.safeParse(body) if (!result.success) { - console.error('Schema validation failed:', result.error) - throw new Error('Invalid response format from NLM Ingestor') + console.error('Schema validation failed. Response structure:', { + hasReturnDict: 'return_dict' in body, + returnDictKeys: body.return_dict ? Object.keys(body.return_dict) : [], + resultKeys: body.return_dict?.result ? Object.keys(body.return_dict.result) : [], + blocks: body.return_dict?.result?.blocks, + }) + console.error('Validation errors:', result.error.errors) + throw new Error('Invalid response format from NLM Ingestor: ' + JSON.stringify(result.error.errors)) } return result.data From f5d1b73fd969f75b2d0282335fd5dc2336d1ed48 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:05:54 +0100 Subject: [PATCH 16/66] fix: Make NLM ingestor schema more flexible for optional content --- pdf2markdown/src/lib/nlm-ingestor-schema.ts | 10 ++++----- pdf2markdown/src/lib/pdfTools.ts | 25 +++++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pdf2markdown/src/lib/nlm-ingestor-schema.ts b/pdf2markdown/src/lib/nlm-ingestor-schema.ts index 66c7de5a..c6d686cc 100644 --- a/pdf2markdown/src/lib/nlm-ingestor-schema.ts +++ b/pdf2markdown/src/lib/nlm-ingestor-schema.ts @@ -8,26 +8,26 @@ export const CellSchema = z.object({ }) export const ParagraphSchema = z.object({ - content: z.string(), + content: z.string().optional(), bbox: z.array(z.number()), }) export const HeaderSchema = z.object({ - content: z.string(), + content: z.string().optional(), bbox: z.array(z.number()), level: z.number(), }) export const ListItemSchema = z.object({ - content: z.string(), + content: z.string().optional(), bbox: z.array(z.number()), level: z.number(), }) export const TableSchema = z.object({ - content: z.string(), + content: z.string().optional(), bbox: z.array(z.number()), - rows: z.array(z.any()), + rows: z.array(z.any()).optional(), level: z.number(), }) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index fead4f7e..136b2388 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -40,17 +40,24 @@ export async function extractJsonFromPdf( const body = await response.json() console.log('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - // Add detailed schema validation debugging + // Add more detailed response inspection + console.log('Response structure analysis:') + console.log('- Has return_dict:', 'return_dict' in body) + if (body.return_dict) { + console.log('- Return dict keys:', Object.keys(body.return_dict)) + if (body.return_dict.result) { + console.log('- Result keys:', Object.keys(body.return_dict.result)) + if (Array.isArray(body.return_dict.result.blocks)) { + console.log('- Number of blocks:', body.return_dict.result.blocks.length) + console.log('- First block sample:', JSON.stringify(body.return_dict.result.blocks[0], null, 2)) + } + } + } + const result = ParsedDocumentSchema.safeParse(body) if (!result.success) { - console.error('Schema validation failed. Response structure:', { - hasReturnDict: 'return_dict' in body, - returnDictKeys: body.return_dict ? Object.keys(body.return_dict) : [], - resultKeys: body.return_dict?.result ? Object.keys(body.return_dict.result) : [], - blocks: body.return_dict?.result?.blocks, - }) - console.error('Validation errors:', result.error.errors) - throw new Error('Invalid response format from NLM Ingestor: ' + JSON.stringify(result.error.errors)) + console.error('Schema validation failed:', result.error.format()) + throw new Error('Invalid response format from NLM Ingestor') } return result.data From 2f0df9debf50837e5590073e3f0f9d8de7ebb250 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:07:12 +0100 Subject: [PATCH 17/66] fix: Import ParsedDocumentSchema in jsonExtraction.ts --- pdf2markdown/src/lib/jsonExtraction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 212ef54c..54e010a4 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -1,4 +1,4 @@ -import { Block, ParsedDocument, Table } from './nlm-ingestor-schema' +import { Block, ParsedDocument, Table, ParsedDocumentSchema } from './nlm-ingestor-schema' import { fromBuffer } from 'pdf2pic' import { openai } from './openai' From 5d56097dafe818b74d4b57c1d95bba5076b9a8b7 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:08:00 +0100 Subject: [PATCH 18/66] fix: Add detailed logging to diagnose PDF content extraction issue --- pdf2markdown/src/lib/jsonExtraction.ts | 22 ++++++++++++++++++++-- pdf2markdown/src/lib/pdfTools.ts | 17 +++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 54e010a4..4666b952 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -75,7 +75,19 @@ export async function jsonToMarkdown( return 'No content found in document' } - console.log(`Processing ${blocks.length} blocks:`, JSON.stringify(blocks, null, 2)) + console.log('\n=== Processing Blocks ===') + console.log(`Total blocks: ${blocks.length}`) + blocks.forEach((block, index) => { + console.log(`\nBlock ${index}:`) + console.log('- Type:', 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph')) + console.log('- Content:', block.content) + if ('rows' in block) { + console.log('- Table rows:', block.rows?.length || 0) + } + if ('level' in block) { + console.log('- Header level:', block.level) + } + }) const [pageWidth, pageHeight] = json.return_dict.page_dim const markdownBlocks = await Promise.all( @@ -128,7 +140,13 @@ export async function jsonToMarkdown( if (!markdown.trim()) { console.error('No content was extracted from blocks') - return 'No content could be extracted from document' + const blockSummary = blocks.map(block => ({ + type: 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph'), + hasContent: Boolean(block.content), + contentLength: block.content?.length || 0 + })) + console.error('Block summary:', JSON.stringify(blockSummary, null, 2)) + return 'No content could be extracted from document. Check server logs for details.' } return markdown diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 136b2388..34b8df24 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -40,16 +40,25 @@ export async function extractJsonFromPdf( const body = await response.json() console.log('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - // Add more detailed response inspection - console.log('Response structure analysis:') + // Enhanced debugging + console.log('\n=== Detailed Response Analysis ===') + console.log('1. Response structure:') console.log('- Has return_dict:', 'return_dict' in body) if (body.return_dict) { console.log('- Return dict keys:', Object.keys(body.return_dict)) if (body.return_dict.result) { console.log('- Result keys:', Object.keys(body.return_dict.result)) if (Array.isArray(body.return_dict.result.blocks)) { - console.log('- Number of blocks:', body.return_dict.result.blocks.length) - console.log('- First block sample:', JSON.stringify(body.return_dict.result.blocks[0], null, 2)) + console.log('\n2. Blocks analysis:') + console.log('- Total blocks:', body.return_dict.result.blocks.length) + body.return_dict.result.blocks.forEach((block, index) => { + console.log(`\nBlock ${index}:`) + console.log('- Type:', 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph')) + console.log('- Has content:', 'content' in block) + console.log('- Content type:', typeof block.content) + console.log('- Content preview:', block.content?.substring(0, 100)) + console.log('- Properties:', Object.keys(block)) + }) } } } From 5d1b49c94621a4283a555e55842a5eed72be3c15 Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Fri, 29 Nov 2024 07:14:58 +0100 Subject: [PATCH 19/66] refactor: Simplify imports and improve block type detection in PDF extraction --- pdf2markdown/src/lib/pdfTools.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 34b8df24..13711ffd 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -1,7 +1,11 @@ import { fromBuffer } from 'pdf2pic' import path from 'path' -import { ParsedDocument, ParsedDocumentSchema } from './nlm-ingestor-schema' -import { jsonToTables, Table } from './jsonExtraction' +import { + ParsedDocument, + ParsedDocumentSchema, + Table, +} from './nlm-ingestor-schema' +import { jsonToTables } from './jsonExtraction' import { writeFile } from 'fs/promises' const NLM_INGESTOR_URL = 'http://localhost:5001' @@ -39,7 +43,7 @@ export async function extractJsonFromPdf( const body = await response.json() console.log('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - + // Enhanced debugging console.log('\n=== Detailed Response Analysis ===') console.log('1. Response structure:') @@ -53,7 +57,14 @@ export async function extractJsonFromPdf( console.log('- Total blocks:', body.return_dict.result.blocks.length) body.return_dict.result.blocks.forEach((block, index) => { console.log(`\nBlock ${index}:`) - console.log('- Type:', 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph')) + console.log( + '- Type:', + 'rows' in block + ? 'Table' + : 'level' in block + ? 'Header' + : 'Paragraph' + ) console.log('- Has content:', 'content' in block) console.log('- Content type:', typeof block.content) console.log('- Content preview:', block.content?.substring(0, 100)) @@ -62,13 +73,13 @@ export async function extractJsonFromPdf( } } } - + const result = ParsedDocumentSchema.safeParse(body) if (!result.success) { console.error('Schema validation failed:', result.error.format()) throw new Error('Invalid response format from NLM Ingestor') } - + return result.data } From 8d91c6f9b4aa17e5ba9446245c09aec1361abc1a Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:15:00 +0100 Subject: [PATCH 20/66] feat: Improve PDF content extraction with enhanced validation and error handling --- pdf2markdown/src/lib/jsonExtraction.ts | 22 ++++++++++++++++++++++ pdf2markdown/src/lib/pdfTools.ts | 19 +++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 4666b952..8eedc822 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -75,6 +75,28 @@ export async function jsonToMarkdown( return 'No content found in document' } + // Check if all blocks are empty + const hasNonEmptyBlock = blocks.some(block => { + if ('content' in block && block.content) { + return block.content.trim().length > 0 + } + if ('rows' in block && block.rows) { + return block.rows.length > 0 + } + return false + }) + + if (!hasNonEmptyBlock) { + console.error('All blocks are empty') + console.error('Block details:', blocks.map(block => ({ + type: 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph'), + hasContent: Boolean(block.content), + contentLength: block.content?.length || 0, + hasRows: 'rows' in block ? Boolean(block.rows?.length) : 'N/A' + }))) + return 'Document contains only empty blocks. The PDF may be corrupted or protected.' + } + console.log('\n=== Processing Blocks ===') console.log(`Total blocks: ${blocks.length}`) blocks.forEach((block, index) => { diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 13711ffd..a86db2a2 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -42,9 +42,24 @@ export async function extractJsonFromPdf( } const body = await response.json() - console.log('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) + + // Validate basic response structure + if (!body?.return_dict?.result?.blocks) { + console.error('Invalid response structure:', JSON.stringify(body, null, 2)) + throw new Error('NLM Ingestor returned invalid response structure') + } + + // Check for empty document + const hasContent = body.return_dict.result.blocks.some(block => + block.content && block.content.trim().length > 0 + ) + + if (!hasContent) { + console.error('Document contains only empty blocks') + console.error('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) + throw new Error('Document appears to be empty or could not be parsed properly') + } - // Enhanced debugging console.log('\n=== Detailed Response Analysis ===') console.log('1. Response structure:') console.log('- Has return_dict:', 'return_dict' in body) From 7316d25cc53fb83e0fbe6884a1c35e49f02318b2 Mon Sep 17 00:00:00 2001 From: "Christian Landgren (aider)" Date: Fri, 29 Nov 2024 07:20:02 +0100 Subject: [PATCH 21/66] refactor: Enhance block parsing with modular functions for tables, headers, and paragraphs --- pdf2markdown/src/lib/jsonExtraction.ts | 89 ++++++++++++++++++++------ 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 8eedc822..75dc6c0a 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -55,12 +55,58 @@ export async function extractTextViaVisionAPI( return result.choices[0].message.content } +function parseTableBlock(block: Table): string { + if (!block.rows || block.rows.length === 0) { + return '' + } + + // Convert table rows to markdown + const rows = block.rows.map(row => { + return row.map(cell => cell.content || '').join(' | ') + }) + + // Add header separator after first row + if (rows.length > 0) { + const headerSeparator = Array(rows[0].split('|').length).fill('---').join(' | ') + rows.splice(1, 0, headerSeparator) + } + + return rows.join('\n') +} + +function parseHeaderBlock(block: Header): string { + if (!block.content) return '' + const level = Math.max(1, Math.min(6, block.level + 1)) // Ensure level is between 1-6 + return `${'#'.repeat(level)} ${block.content.trim()}` +} + +function parseParagraphBlock(block: Paragraph): string { + return block.content ? block.content.trim() : '' +} + +function parseListItemBlock(block: ListItem): string { + if (!block.content) return '' + const indent = ' '.repeat(Math.max(0, block.level - 1)) + return `${indent}- ${block.content.trim()}` +} + export function jsonToTables(json: ParsedDocument): Table[] { return json.return_dict.result.blocks.filter( (block): block is Table => 'rows' in block ) } +function parseBlock(block: Block): string { + if ('rows' in block) { + return parseTableBlock(block) + } else if ('level' in block && 'content' in block) { + return parseHeaderBlock(block as Header) + } else if ('content' in block) { + return parseParagraphBlock(block as Paragraph) + } + return '' +} + export async function jsonToMarkdown( json: ParsedDocument, pdf: Buffer @@ -114,10 +160,21 @@ export async function jsonToMarkdown( const markdownBlocks = await Promise.all( blocks.map(async (block: Block) => { + console.log(`Processing block of type: ${ + 'rows' in block ? 'Table' : + 'level' in block ? 'Header' : + 'Paragraph' + }`) + if ('rows' in block) { - // For tables, convert the page to image and use Vision API + // For tables, try direct parsing first + const tableMarkdown = parseTableBlock(block) + if (tableMarkdown && tableMarkdown.trim()) { + return tableMarkdown + } + + // If direct parsing yields no content, fall back to Vision API const pageNumber = block.page_idx + 1 - // Convert page to image const pdfConverter = fromBuffer(pdf, { density: 600, format: 'png', @@ -133,26 +190,20 @@ export async function jsonToMarkdown( throw new Error(`Failed to convert page ${pageNumber} to image`) } - // Extract table text using Vision API - const markdown = await extractTextViaVisionAPI( + return extractTextViaVisionAPI( { buffer: result.buffer }, - block.content + block.content || '' ) - return markdown - } else if ('level' in block) { - console.log('Processing header block:', block) - if (typeof block.content === 'string' && block.content.trim()) { - const level = Math.max(1, Math.min(6, block.level + 1)) // Ensure level is between 1-6 - const prefix = '#'.repeat(level) - return `${prefix} ${block.content.trim()}` - } - } else if (block.content) { - console.log('Processing content block:', block) - return block.content.trim() - } else { - console.log('Skipping empty block:', block) - return null } + + // For non-table blocks, use appropriate parser + const parsedContent = parseBlock(block) + if (parsedContent) { + return parsedContent + } + + console.log('Skipping empty block:', block) + return null }) ) From 44191c4d729dfc664b2bf08796ee75008a5e2ea2 Mon Sep 17 00:00:00 2001 From: Christian Landgren Date: Wed, 4 Dec 2024 12:08:24 +0100 Subject: [PATCH 22/66] fix: remove temp files - we are using buffer --- pdf2markdown/src/index.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index a6850054..906a5481 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,7 +1,6 @@ import express from 'express' import { extractJsonFromPdf } from './lib/pdfTools' import { jsonToMarkdown } from './lib/jsonExtraction' -import { rm } from 'fs/promises' const app = express() const port = 3000 @@ -18,16 +17,6 @@ app.post( } catch (error) { console.error('Conversion error:', error) res.status(500).json({ error: error.message }) - - // Cleanup temp files - try { - await rm('/tmp/pdf2markdown-screenshots', { - recursive: true, - force: true, - }) - } catch (cleanupError) { - console.error('Failed to cleanup temp files:', cleanupError) - } } } ) From b53c0e2b4068ff77ddf749387056e86b5ffe13f0 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:53:29 +0100 Subject: [PATCH 23/66] Add Docling docker config --- pdf2markdown/Dockerfile | 42 +- pdf2markdown/poetry.lock | 2649 +++++++++++++++++++++++++++++++++++ pdf2markdown/pyproject.toml | 15 + 3 files changed, 2698 insertions(+), 8 deletions(-) create mode 100644 pdf2markdown/poetry.lock create mode 100644 pdf2markdown/pyproject.toml diff --git a/pdf2markdown/Dockerfile b/pdf2markdown/Dockerfile index 9af0bd86..14ca979b 100644 --- a/pdf2markdown/Dockerfile +++ b/pdf2markdown/Dockerfile @@ -1,21 +1,47 @@ -FROM ghcr.io/nlmatics/nlm-ingestor:latest +FROM python:3.12-slim-bookworm + +ARG CPU_ONLY=false + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y libgl1 libglib2.0-0 curl wget git \ + && apt-get clean + +# Install Poetry and configure it +RUN pip install poetry \ + && poetry config virtualenvs.create false + +COPY pyproject.toml poetry.lock ./ + +# Install dependencies before torch +RUN poetry install --no-interaction --no-root + +# Install PyTorch separately based on CPU_ONLY flag +# TODO: Use correct GPU build - see https://pytorch.org/ for details +RUN if [ "$CPU_ONLY" = "true" ]; then \ + pip install --no-cache-dir torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu; \ + else \ + pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121; \ + fi + +ENV HF_HOME=/tmp/ +ENV TORCH_HOME=/tmp/ +ENV OMP_NUM_THREADS=4 + +RUN python -c 'from docling.pipeline.standard_pdf_pipeline import StandardPdfPipeline; artifacts_path = StandardPdfPipeline.download_models_hf(force=True);' # Install Node.js 22 RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && apt-get install -y nodejs # Install app dependencies -WORKDIR /app COPY package*.json ./ -RUN npm install +RUN npm ci --omit=dev # Copy app source COPY . . -# Build TypeScript -RUN npm run build - EXPOSE 3000 -# Start both NLM ingestor and our Express service -CMD service nlm-ingestor start && npm start +CMD npm start diff --git a/pdf2markdown/poetry.lock b/pdf2markdown/poetry.lock new file mode 100644 index 00000000..b35497db --- /dev/null +++ b/pdf2markdown/poetry.lock @@ -0,0 +1,2649 @@ +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "deepsearch-glm" +version = "0.26.2" +description = "Graph Language Models" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "deepsearch_glm-0.26.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:00453a02bc8df959da576bc598ba528b394a9c016d6a428efc948c867be98938"}, + {file = "deepsearch_glm-0.26.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:9e6f654ab4d9dc3e6e2033c9c45294c36e5e62650cac0e4a650af576364eb370"}, + {file = "deepsearch_glm-0.26.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1fdf2fce9d642bbc5222600a1b280a7413aa640ed01acee13d43401ec27d6ad5"}, + {file = "deepsearch_glm-0.26.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:218cab085a58b88c55dbeb80cc5f5f7b3c5a96c8537eb2ada8e5cab70cd8e439"}, + {file = "deepsearch_glm-0.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75be007e62d11780f2433b213dad14d14a270c3607e909fd1fc95efdf02446c6"}, + {file = "deepsearch_glm-0.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a9b34c6cfb8b873ccf6e0072f5434c0c65a1d90652a6b901becc5b3b1695106"}, + {file = "deepsearch_glm-0.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:f4b63c6e1d4a7be597efbe96052286bca805784cd7283a037919c349971051c5"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:eaabedca45fdd87dc455dc08b1785db15ba5ea6b706820330447f2cf7f03a67a"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:101bc2a79027df555050d08112717249916c4d82ad5815be2a1ac0581d9ab2b5"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:000d4a4895c4ff89c465b746bb7db3bb054a1fb5c3fabe2772d5431700c15d33"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2d97f9ebdff1a9086cc32ddd0abb14b42c4b4b2ae666986078fd77db3aa4487d"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:666a3b53b0949735cff77a8209f2833866e34b635ca0c7f444807963d8379d93"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aae1ec83222ef39e045f0186023473e5ce2ed30846c13f2943192d34d57c0f"}, + {file = "deepsearch_glm-0.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:9bb173dcd0caef1d8a0d440e1ac3e9959c6b849e06b95b1d9b436661504c98f7"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:bb286be157a7b163b46a4d1f7e48a30d5cc365d4926c18e8b3c72994a8f296f7"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:defca9ecf1451ce3422b7783ea188571ffad7c941dbf52acc2638c5a4ffa7743"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:226f8862c616a4def202a6d0f71eb5d8e9f6ddbded2cf431c146150303888cf8"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:6ff0fe662254835763ad7d3edc2db320de8d233f645064e0356187d8e1fabe3b"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91c1b84ec5b1308de37c660f49570ee1e72bd7f0f607566344446b9293f1183c"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d634eeaae8943e1912c0dfbf3193e09bea8c1aac38db8a6fa1f03fe6a49cb84"}, + {file = "deepsearch_glm-0.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:9294087d26037574817e8e1710e387fd9ef9ba4328705de86dd40d819f32909a"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:df7181143c62a1f0e166bc9ffb25deab617b53ba7c468284e3072b861c17405a"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:2c3fef2c8394d6dc22d1bcdab12d0f46df9b411c5431dfb585a2c7bb128e1744"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f641a88421aa806ccef8f8e657fbb65135f59732110d21b5103c09138a659315"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf78499892caffb4bdc020b8c50ab7d623f568478375dcc2e3ec107d40972adc"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72f2b432b81b0bc7c87e33c41a97c7a8da2536dd2b337eb1b7d054fba12d556"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4703cae0d329b77e1d97892910313035204daa026d6e67ce6eb1b3e74e41f93e"}, + {file = "deepsearch_glm-0.26.2-cp313-cp313-win_amd64.whl", hash = "sha256:c906c75d080414490727de416fd1782bc6a10301378f72a741aa227b183832cf"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:10a366512540eff9f76645eb521df3469a160e8460ff6c3c1bfe172342c6c670"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:304988f1e08bd86a8a7b7cc0495e38faf586231f33f05c1023597c6177758572"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:c8f69b877846031648811ff80070b90b834bf9e4cdd74e5c2d93c7e18f408cd1"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:1ba12361d1e4b8b02a72f515028f22686d98526a703a1091f89e9487fa3aa3c7"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c03bb8b3cdb2952c9c269849830f7830fa7e0384b76809e25f4c2d5d091f746c"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fe719b26d7cfcf5632a56be1f1420920fcdbea4418c014dd6e7e218dd2aca11"}, + {file = "deepsearch_glm-0.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2b31fa419287af3429efc2d5610cbf2428bafc762e45b610a48ad30dffedaa9e"}, + {file = "deepsearch_glm-0.26.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6df2504998e60c1aac3655820ad25e5eccca137da2e9f78fb53dc0fd0d1cdbf4"}, + {file = "deepsearch_glm-0.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1b4a789ec9555ec9f4ff6730d68081be37eaa43cb51c9463962967c9f672684"}, + {file = "deepsearch_glm-0.26.2.tar.gz", hash = "sha256:7a607e78903b66d28beac3408156c11ab7b34ee70e8ccd0d292b28433e5a9c1d"}, +] + +[package.dependencies] +docling-core = ">=2.0,<3.0" +docutils = "!=0.21" +numpy = ">=1.24.4,<3.0.0" +pandas = ">=1.5.1,<3.0.0" +python-dotenv = ">=1.0.0,<2.0.0" +pywin32 = {version = ">=307,<308", markers = "sys_platform == \"win32\""} +requests = ">=2.32.3,<3.0.0" +rich = ">=13.7.0,<14.0.0" +tabulate = ">=0.8.9" +tqdm = ">=4.64.0,<5.0.0" + +[package.extras] +pyplot = ["matplotlib (>=3.7.1,<4.0.0)"] +toolkit = ["deepsearch-toolkit (>=1.1.0,<2.0.0)"] + +[[package]] +name = "docling" +version = "2.8.3" +description = "SDK and CLI for parsing PDF, DOCX, HTML, and more, to a unified document representation for powering downstream workflows such as gen AI applications." +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "docling-2.8.3-py3-none-any.whl", hash = "sha256:2cbd99f00f149a3c9b6db8b5e543edf1d7158e69d99e6f10dceae77f8b997c8e"}, + {file = "docling-2.8.3.tar.gz", hash = "sha256:2273182748171b312c2efd547c92bec7cb02601090ddb7bfa8f33d26c388b622"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.12.3,<5.0.0" +certifi = ">=2024.7.4" +deepsearch-glm = ">=0.26.1,<0.27.0" +docling-core = ">=2.6.1,<3.0.0" +docling-ibm-models = ">=2.0.6,<3.0.0" +docling-parse = ">=2.0.5,<3.0.0" +easyocr = ">=1.7,<2.0" +filetype = ">=1.2.0,<2.0.0" +huggingface_hub = ">=0.23,<1" +lxml = ">=4.0.0,<6.0.0" +marko = ">=2.1.2,<3.0.0" +openpyxl = ">=3.1.5,<4.0.0" +pandas = ">=2.1.4,<3.0.0" +pydantic = ">=2.0.0,<2.10" +pydantic-settings = ">=2.3.0,<3.0.0" +pypdfium2 = ">=4.30.0,<5.0.0" +python-docx = ">=1.1.2,<2.0.0" +python-pptx = ">=1.0.2,<2.0.0" +requests = ">=2.32.3,<3.0.0" +rtree = ">=1.3.0,<2.0.0" +scipy = ">=1.6.0,<2.0.0" +typer = ">=0.12.5,<0.13.0" + +[package.extras] +ocrmac = ["ocrmac (>=1.0.0,<2.0.0)"] +rapidocr = ["onnxruntime (>=1.7.0,<1.20.0)", "onnxruntime (>=1.7.0,<2.0.0)", "rapidocr-onnxruntime (>=1.4.0,<2.0.0)"] +tesserocr = ["tesserocr (>=2.7.1,<3.0.0)"] + +[[package]] +name = "docling-core" +version = "2.8.0" +description = "A python library to define and validate data types in Docling." +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "docling_core-2.8.0-py3-none-any.whl", hash = "sha256:392aad49e25f5fd1d279410118fbd91d9aaab9dd92d043738d20c10c57193d86"}, + {file = "docling_core-2.8.0.tar.gz", hash = "sha256:6ac5cbc6f0abcbdf599c2a4b1a3f7b52fd8baebf3c4ebf94d7b7e2ee061a654e"}, +] + +[package.dependencies] +jsonref = ">=1.1.0,<2.0.0" +jsonschema = ">=4.16.0,<5.0.0" +pandas = ">=2.1.4,<3.0.0" +pillow = ">=10.3.0,<11.0.0" +pydantic = ">=2.6.0,<2.10.0 || >2.10.0,<2.10.1 || >2.10.1,<2.10.2 || >2.10.2,<3.0.0" +pyyaml = ">=5.1,<7.0.0" +tabulate = ">=0.9.0,<0.10.0" +typing-extensions = ">=4.12.2,<5.0.0" + +[package.extras] +chunking = ["semchunk (>=2.2.0,<3.0.0)", "transformers (>=4.34.0,<5.0.0)"] + +[[package]] +name = "docling-ibm-models" +version = "2.0.7" +description = "This package contains the AI models used by the Docling PDF conversion package" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "docling_ibm_models-2.0.7-py3-none-any.whl", hash = "sha256:bf362add22e9c526ac56c04bce412d7bb1c331b44a73204abba0b1d90a500c78"}, + {file = "docling_ibm_models-2.0.7.tar.gz", hash = "sha256:e1372c4f2517d522125fb02a820558f01914926f532bcd0534f1028a25d63667"}, +] + +[package.dependencies] +huggingface_hub = ">=0.23,<1" +jsonlines = ">=3.1.0,<4.0.0" +numpy = ">=1.24.4,<3.0.0" +opencv-python-headless = ">=4.6.0.66,<5.0.0.0" +Pillow = ">=10.0.0,<11.0.0" +torch = ">=2.2.2,<3.0.0" +torchvision = ">=0,<1" +tqdm = ">=4.64.0,<5.0.0" + +[[package]] +name = "docling-parse" +version = "2.1.2" +description = "Simple package to extract text with coordinates from programmatic PDFs" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "docling_parse-2.1.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:140319e3eac73f9768d35313739891ae637af57fda03eade17d90e2d28ad80eb"}, + {file = "docling_parse-2.1.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:cec968a436ad14e8a45a72fc0e0074750eee28548a14f3c3df5157a68ac958e7"}, + {file = "docling_parse-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:c84eba992fee49d190cf4834fd44ef4e6549c3f1fcd41b91622114703a7e4a87"}, + {file = "docling_parse-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:ae02af07f3dd335f56383a83efdc1f6450b7d38e21e1131005dbd341eb38e47d"}, + {file = "docling_parse-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fa0731e97d2644ff8a3257ae53208b88be3ddc6a4bc54fbe39e21f8395530f0"}, + {file = "docling_parse-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26d60136aab5f4a3a773922a8dcc530334165331660d074cd88dcd5d91206cd"}, + {file = "docling_parse-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:76eef41d50017c2fc531face44c1a35bef66095951622617d0f281e35d18e9e0"}, + {file = "docling_parse-2.1.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:7f1ad037d3ac0d80252c493e73b12688ded3ece9bae7954ba62765506c139d21"}, + {file = "docling_parse-2.1.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:9f1360c0558c84f4b6633b0882256f6d621fd9e52179acae39c727a43b48d937"}, + {file = "docling_parse-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5d505c2d3e9eff4f3064b4d1f017a3c6577b5d8ba55540d558f4899561862956"}, + {file = "docling_parse-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:58f552f61ac35c02890b03fe59b06552353314c3c1ee2a050c68a8a206ab1b4b"}, + {file = "docling_parse-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22069dadcfdcebc02e36e27f80d452f1265a5a97d894f2391490bf099bc5432c"}, + {file = "docling_parse-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68942b31684a021e27b9b07d27ed139911444b33963f7e0b5d2dbda8aaa5cb1"}, + {file = "docling_parse-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d87e3fbf1549cd8bc171240c18584ba8c32f83963b5af66b2a70a2bc3af56d2e"}, + {file = "docling_parse-2.1.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:5b00b81fa8eb0b34621f1ef9d07623d7dbcc354a33295a5b0c4209c39b1ff8eb"}, + {file = "docling_parse-2.1.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:1b99b122f941d0f19e92a215e589b94f49db899c5eec0147e83824652b18ce74"}, + {file = "docling_parse-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:744fe368a8fa49778e881c1052427c38a7d0e367273fcdef493e047513783108"}, + {file = "docling_parse-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b8a3e558a96f7d593269be75ba4147ebe221f5edad3d41244cef3533e8a51b74"}, + {file = "docling_parse-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afcf53bce8c91886c1360e625e51d15ebfb36d37cd53b6e019e86ce1118c1d0c"}, + {file = "docling_parse-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89d25fc4fb8f16a8ed5bc8c4f00a77739d2536732c0ddae16340b1859adf68fd"}, + {file = "docling_parse-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:28a7f49a865a0cd71033a7899aac00c7d2e3b6c3a76488f8676ba0fc353d9f3a"}, + {file = "docling_parse-2.1.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:ad1560532cdf15dcb4a6005c8b7fe19def0e910e6125863f14978d6d07a1ba47"}, + {file = "docling_parse-2.1.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:19003b1bb64cd5a40999a3c5ffcb9a9d9608a073949b76acc58d58fb5054ea03"}, + {file = "docling_parse-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:041bf1c72a23d62e2dd30dcc3508222f6674e85b0f1d19a3196fd6d7b5f56015"}, + {file = "docling_parse-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:12403c26e833d8fdf0f406d2895f5108fd07b64a4d929c9105ca60f09b882c34"}, + {file = "docling_parse-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1082e227af3e31085eff3e96103b09becdf95324304e17ce0b1b61c43b93fbb7"}, + {file = "docling_parse-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77b36e36d1e07a06a1616ee281079d6b972c3059f2fa02dafcfc225a41e5bd1a"}, + {file = "docling_parse-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:4300df86657935b0109c44702857ebf3d0713f1bbe376982f369504a762e2fef"}, + {file = "docling_parse-2.1.2-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:912fe44507f209d997e1183f38a71d4e14c31d53a164fb862631822624dad892"}, + {file = "docling_parse-2.1.2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:10ff1928b12099f446fcd0b043182173e6b02ce74008ea6ce921d56cdee8964e"}, + {file = "docling_parse-2.1.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:391ad31a4086fabbc290851432f4cf0bdc366e07a454adf49e42029898d6b477"}, + {file = "docling_parse-2.1.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ebf478e99c0c16d7dad30c0fdb1f5e236ae94d48da8dec48dbe5f0841eead4ed"}, + {file = "docling_parse-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1c904017330d096981b7db6b225b66aff1cebdc422843103a782121d6e8be8"}, + {file = "docling_parse-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bc8ec6ad1bec6168991b895d749b222bef14b568d1d9f6c06efaeb1645dfe12"}, + {file = "docling_parse-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e6eb130aa367247e1f32225bb1608cee901d711b475527404bbc4330c9199b99"}, + {file = "docling_parse-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ef88d565c761b48f8a175fd474e068c0da9d4401e22d3e38de73e2f00f3df2d1"}, + {file = "docling_parse-2.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bdc8ccbdc4ab91b829b8c421ad89da276442a2c891eda1f6507f248d0bd8dff9"}, + {file = "docling_parse-2.1.2.tar.gz", hash = "sha256:3c249f50e6351eb6126331a179fe86b64dc2073e9f881d52f8c8fb391633b89e"}, +] + +[package.dependencies] +pywin32 = {version = ">=305", markers = "sys_platform == \"win32\""} +tabulate = ">=0.9.0,<1.0.0" + +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "easyocr" +version = "1.7.2" +description = "End-to-End Multi-Lingual Optical Character Recognition (OCR) Solution" +optional = false +python-versions = "*" +files = [ + {file = "easyocr-1.7.2-py3-none-any.whl", hash = "sha256:5be12f9b0e595d443c9c3d10b0542074b50f0ec2d98b141a109cd961fd1c177c"}, +] + +[package.dependencies] +ninja = "*" +numpy = "*" +opencv-python-headless = "*" +Pillow = "*" +pyclipper = "*" +python-bidi = "*" +PyYAML = "*" +scikit-image = "*" +scipy = "*" +Shapely = "*" +torch = "*" +torchvision = ">=0.5" + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, + {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, +] + +[[package]] +name = "filelock" +version = "3.16.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] + +[[package]] +name = "filetype" +version = "1.2.0" +description = "Infer file type and MIME type of any file/buffer. No external dependencies." +optional = false +python-versions = "*" +files = [ + {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, + {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, +] + +[[package]] +name = "fsspec" +version = "2024.10.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871"}, + {file = "fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + +[[package]] +name = "huggingface-hub" +version = "0.26.5" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "huggingface_hub-0.26.5-py3-none-any.whl", hash = "sha256:fb7386090bbe892072e64b85f7c4479fd2d65eea5f2543327c970d5169e83924"}, + {file = "huggingface_hub-0.26.5.tar.gz", hash = "sha256:1008bd18f60bfb65e8dbc0a97249beeeaa8c99d3c2fa649354df9fa5a13ed83b"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +hf-transfer = ["hf-transfer (>=0.1.4)"] +inference = ["aiohttp"] +quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.5.0)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +tensorflow-testing = ["keras (<3.0)", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["safetensors[torch]", "torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imageio" +version = "2.36.1" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +optional = false +python-versions = ">=3.9" +files = [ + {file = "imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf"}, + {file = "imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +pillow-heif = ["pillow-heif"] +pyav = ["av"] +rawpy = ["numpy (>2)", "rawpy"] +test = ["fsspec[github]", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonlines" +version = "3.1.0" +description = "Library with helpers for the jsonlines file format" +optional = false +python-versions = ">=3.6" +files = [ + {file = "jsonlines-3.1.0-py3-none-any.whl", hash = "sha256:632f5e38f93dfcb1ac8c4e09780b92af3a55f38f26e7c47ae85109d420b6ad39"}, + {file = "jsonlines-3.1.0.tar.gz", hash = "sha256:2579cb488d96f815b0eb81629e3e6b0332da0962a18fa3532958f7ba14a5c37f"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "jsonref" +version = "1.1.0" +description = "jsonref is a library for automatic dereferencing of JSON Reference objects for Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"}, + {file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "lazy-loader" +version = "0.4" +description = "Makes it easy to load subpackages and functions on demand." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, + {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +dev = ["changelist (==0.5)"] +lint = ["pre-commit (==3.7.0)"] +test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] + +[[package]] +name = "lxml" +version = "5.3.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.11)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "marko" +version = "2.1.2" +description = "A markdown parser with high extensibility." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marko-2.1.2-py3-none-any.whl", hash = "sha256:c14aa7a77468aaaf53cf056dcd3d32398b9df4c3fb81f5e120dd37cbb9f8c859"}, + {file = "marko-2.1.2.tar.gz", hash = "sha256:a9170006b879376e6845c91b1ae3dce2992772954b99b70175ff888537186011"}, +] + +[package.extras] +codehilite = ["pygments"] +repr = ["objprint"] +toc = ["python-slugify"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "networkx" +version = "3.4.2" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +files = [ + {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, +] + +[package.extras] +default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "ninja" +version = "1.11.1.2" +description = "Ninja is a small build system with a focus on speed" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ninja-1.11.1.2-py3-none-macosx_10_9_universal2.whl", hash = "sha256:1cfbb845095ea09da8c089375a8f999e75f4817d01506297c66181b533175647"}, + {file = "ninja-1.11.1.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ab4068ff7ff1f895485ad604116165b05d6810c802170a7f22c09dd678d5587d"}, + {file = "ninja-1.11.1.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:33d258809c8eda81f9d80e18a081a6eef3215e5fd1ba8902400d786641994e89"}, + {file = "ninja-1.11.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed25892c16e49e66383a8db6a67a9f33b41230fc485426094d7da51e2255ec2b"}, + {file = "ninja-1.11.1.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:232767144401847db62e8392047866698bb3678158a1ae4400a97111110e90f2"}, + {file = "ninja-1.11.1.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9200247cf4c1643a67d079836b8dd31a362e34e618b50b5e3a5c0d0171efc442"}, + {file = "ninja-1.11.1.2-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c9c36f6e6f8946c7271b0ed14d98fc3ea467a0c0954fb73f5f656c42667d943"}, + {file = "ninja-1.11.1.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:3e815e4147832b17ec38417efcb31df51671ae273f083409304c7cc32a14dd1a"}, + {file = "ninja-1.11.1.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ecf3df324b56fdfb0872990a71e706efdae286e010310816c72b6bf24431711b"}, + {file = "ninja-1.11.1.2-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:cb6b476eb4e84c0efcfd3ab04f660dedce8adb854b56b043639312f3af176df6"}, + {file = "ninja-1.11.1.2-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:508fb93395a5c82a4d99d30fce0cbaf5cb2bd33e5c1dc9faaa080e199802dbc9"}, + {file = "ninja-1.11.1.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:52af7f45750c5c288d566fd0c927ed9bb0d8f2e50803709f582a42bcc4ec167b"}, + {file = "ninja-1.11.1.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99fc4b87299242e10d7edd1c7737fdfb1269019e32f9f4267630887f6183a49e"}, + {file = "ninja-1.11.1.2-py3-none-win32.whl", hash = "sha256:949e23cb2e79a33ea37d23a07d26846d2e75464e8e6940f8751fe964bc141dfa"}, + {file = "ninja-1.11.1.2-py3-none-win_amd64.whl", hash = "sha256:0bca4179119426a3c3c9d5661c3b244d68781064e50907a1e066bc55edc18e06"}, + {file = "ninja-1.11.1.2-py3-none-win_arm64.whl", hash = "sha256:ee7b1924c28e6cab5b866f7b229f07777d25d8cfccbbedf3da5ffb4f72f57877"}, + {file = "ninja-1.11.1.2.tar.gz", hash = "sha256:4fbd07b2b4232543726abafdd350453a2fabef4527664ca0e491c578aee5f857"}, +] + +[package.extras] +test = ["coverage (>=4.2)", "importlib_metadata (>=2.0)", "pytest (>=6.0)", "pytest-cov (>=3)"] + +[[package]] +name = "numpy" +version = "2.1.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"}, + {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"}, + {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"}, + {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"}, + {file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"}, + {file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"}, + {file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"}, + {file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"}, + {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"}, + {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"}, + {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"}, + {file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"}, + {file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"}, + {file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"}, + {file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"}, + {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"}, + {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"}, + {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"}, + {file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"}, + {file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"}, + {file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"}, + {file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"}, + {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"}, + {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"}, + {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"}, + {file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"}, + {file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"}, + {file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"}, + {file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"}, + {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"}, + {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"}, + {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"}, + {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"}, + {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"}, + {file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"}, + {file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"}, + {file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"}, + {file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"}, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.4.5.8" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3"}, + {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b"}, + {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-win_amd64.whl", hash = "sha256:5a796786da89203a0657eda402bcdcec6180254a8ac22d72213abc42069522dc"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.4.127" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a"}, + {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb"}, + {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:5688d203301ab051449a2b1cb6690fbe90d2b372f411521c86018b950f3d7922"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.4.127" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198"}, + {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338"}, + {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:a961b2f1d5f17b14867c619ceb99ef6fcec12e46612711bcec78eb05068a60ec"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.4.127" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3"}, + {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5"}, + {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:09c2e35f48359752dfa822c09918211844a3d93c100a715d79b59591130c5e1e"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f"}, + {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-win_amd64.whl", hash = "sha256:6278562929433d68365a07a4a1546c237ba2849852c0d4b2262a486e805b977a"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.2.1.3" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399"}, + {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9"}, + {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-win_amd64.whl", hash = "sha256:d802f4954291101186078ccbe22fc285a902136f974d369540fd4a5333d1440b"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.5.147" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9"}, + {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b"}, + {file = "nvidia_curand_cu12-10.3.5.147-py3-none-win_amd64.whl", hash = "sha256:f307cc191f96efe9e8f05a87096abc20d08845a841889ef78cb06924437f6771"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.6.1.9" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e"}, + {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260"}, + {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-win_amd64.whl", hash = "sha256:e77314c9d7b694fcebc84f58989f3aa4fb4cb442f12ca1a9bde50f5e8f6d1b9c"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.3.1.170" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3"}, + {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1"}, + {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-win_amd64.whl", hash = "sha256:9bc90fb087bc7b4c15641521f31c0371e9a612fc2ba12c338d3ae032e6b6797f"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.21.5" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.4.127" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83"}, + {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57"}, + {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:fd9020c501d27d135f983c6d3e244b197a7ccad769e34df53a42e276b0e25fa1"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.4.127" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3"}, + {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a"}, + {file = "nvidia_nvtx_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:641dccaaa1139f3ffb0d3164b4b84f9d253397e38246a4f2f36728b48566d485"}, +] + +[[package]] +name = "opencv-python-headless" +version = "4.10.0.84" +description = "Wrapper package for OpenCV python bindings." +optional = false +python-versions = ">=3.6" +files = [ + {file = "opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} + +[[package]] +name = "openpyxl" +version = "3.1.5" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, + {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + +[[package]] +name = "pyclipper" +version = "1.3.0.post6" +description = "Cython wrapper for the C++ translation of the Angus Johnson's Clipper library (ver. 6.4.2)" +optional = false +python-versions = "*" +files = [ + {file = "pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa0f5e78cfa8262277bb3d0225537b3c2a90ef68fd90a229d5d24cf49955dcf4"}, + {file = "pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01f182d8938c1dc515e8508ed2442f7eebd2c25c7d5cb29281f583c1a8008a4"}, + {file = "pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:640f20975727994d4abacd07396f564e9e5665ba5cb66ceb36b300c281f84fa4"}, + {file = "pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63002f6bb0f1efa87c0b81634cbb571066f237067e23707dabf746306c92ba5"}, + {file = "pyclipper-1.3.0.post6-cp310-cp310-win32.whl", hash = "sha256:106b8622cd9fb07d80cbf9b1d752334c55839203bae962376a8c59087788af26"}, + {file = "pyclipper-1.3.0.post6-cp310-cp310-win_amd64.whl", hash = "sha256:9699e98862dadefd0bea2360c31fa61ca553c660cbf6fb44993acde1b959f58f"}, + {file = "pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4247e7c44b34c87acbf38f99d48fb1acaf5da4a2cf4dcd601a9b24d431be4ef"}, + {file = "pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:851b3e58106c62a5534a1201295fe20c21714dee2eda68081b37ddb0367e6caa"}, + {file = "pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16cc1705a915896d2aff52131c427df02265631279eac849ebda766432714cc0"}, + {file = "pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace1f0753cf71c5c5f6488b8feef5dd0fa8b976ad86b24bb51f708f513df4aac"}, + {file = "pyclipper-1.3.0.post6-cp311-cp311-win32.whl", hash = "sha256:dbc828641667142751b1127fd5c4291663490cf05689c85be4c5bcc89aaa236a"}, + {file = "pyclipper-1.3.0.post6-cp311-cp311-win_amd64.whl", hash = "sha256:1c03f1ae43b18ee07730c3c774cc3cf88a10c12a4b097239b33365ec24a0a14a"}, + {file = "pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e"}, + {file = "pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229"}, + {file = "pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d"}, + {file = "pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c"}, + {file = "pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf"}, + {file = "pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548"}, + {file = "pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f129284d2c7bcd213d11c0f35e1ae506a1144ce4954e9d1734d63b120b0a1b58"}, + {file = "pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:188fbfd1d30d02247f92c25ce856f5f3c75d841251f43367dbcf10935bc48f38"}, + {file = "pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d129d0c2587f2f5904d201a4021f859afbb45fada4261c9fdedb2205b09d23"}, + {file = "pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9c80b5c46eef38ba3f12dd818dc87f5f2a0853ba914b6f91b133232315f526"}, + {file = "pyclipper-1.3.0.post6-cp313-cp313-win32.whl", hash = "sha256:b15113ec4fc423b58e9ae80aa95cf5a0802f02d8f02a98a46af3d7d66ff0cc0e"}, + {file = "pyclipper-1.3.0.post6-cp313-cp313-win_amd64.whl", hash = "sha256:e5ff68fa770ac654c7974fc78792978796f068bd274e95930c0691c31e192889"}, + {file = "pyclipper-1.3.0.post6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c92e41301a8f25f9adcd90954512038ed5f774a2b8c04a4a9db261b78ff75e3a"}, + {file = "pyclipper-1.3.0.post6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04214d23cf79f4ddcde36e299dea9f23f07abb88fa47ef399bf0e819438bbefd"}, + {file = "pyclipper-1.3.0.post6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aa604f8665ade434f9eafcd23f89435057d5d09427dfb4554c5e6d19f6d8aa1a"}, + {file = "pyclipper-1.3.0.post6-cp36-cp36m-win32.whl", hash = "sha256:1fd56855ca92fa7eb0d8a71cf3a24b80b9724c8adcc89b385bbaa8924e620156"}, + {file = "pyclipper-1.3.0.post6-cp36-cp36m-win_amd64.whl", hash = "sha256:6893f9b701f3132d86018594d99b724200b937a3a3ddfe1be0432c4ff0284e6e"}, + {file = "pyclipper-1.3.0.post6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2737df106b8487103916147fe30f887aff439d9f2bd2f67c9d9b5c13eac88ccf"}, + {file = "pyclipper-1.3.0.post6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ab72260f144693e1f7735e93276c3031e1ed243a207eff1f8b98c7162ba22c"}, + {file = "pyclipper-1.3.0.post6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:491ec1bfd2ee3013269c2b652dde14a85539480e0fb82f89bb12198fa59fff82"}, + {file = "pyclipper-1.3.0.post6-cp37-cp37m-win32.whl", hash = "sha256:2e257009030815853528ba4b2ef7fb7e172683a3f4255a63f00bde34cfab8b58"}, + {file = "pyclipper-1.3.0.post6-cp37-cp37m-win_amd64.whl", hash = "sha256:ed6e50c6e87ed190141573615d54118869bd63e9cd91ca5660d2ca926bf25110"}, + {file = "pyclipper-1.3.0.post6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cf0a535cfa02b207435928e991c60389671fe1ea1dfae79170973f82f52335b2"}, + {file = "pyclipper-1.3.0.post6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:48dd55fbd55f63902cad511432ec332368cbbbc1dd2110c0c6c1e9edd735713a"}, + {file = "pyclipper-1.3.0.post6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05ae2ea878fdfa31dd375326f6191b03de98a9602cc9c2b6d4ff960b20a974c"}, + {file = "pyclipper-1.3.0.post6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:903176952a159c4195b8be55e597978e24804c838c7a9b12024c39704d341f72"}, + {file = "pyclipper-1.3.0.post6-cp38-cp38-win32.whl", hash = "sha256:fb1e52cf4ee0a9fa8b2254ed589cc51b0c989efc58fa8804289aca94a21253f7"}, + {file = "pyclipper-1.3.0.post6-cp38-cp38-win_amd64.whl", hash = "sha256:9cbdc517e75e647aa9bf6e356b3a3d2e3af344f82af38e36031eb46ba0ab5425"}, + {file = "pyclipper-1.3.0.post6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:383f3433b968f2e4b0843f338c1f63b85392b6e1d936de722e8c5d4f577dbff5"}, + {file = "pyclipper-1.3.0.post6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cf5ca2b9358d30a395ac6e14b3154a9fd1f9b557ad7153ea15cf697e88d07ce1"}, + {file = "pyclipper-1.3.0.post6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3404dfcb3415eee863564b5f49be28a8c7fb99ad5e31c986bcc33c8d47d97df7"}, + {file = "pyclipper-1.3.0.post6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aa0e7268f8ceba218964bc3a482a5e9d32e352e8c3538b03f69a6b3db979078d"}, + {file = "pyclipper-1.3.0.post6-cp39-cp39-win32.whl", hash = "sha256:47a214f201ff930595a30649c2a063f78baa3a8f52e1f38da19f7930c90ed80c"}, + {file = "pyclipper-1.3.0.post6-cp39-cp39-win_amd64.whl", hash = "sha256:28bb590ae79e6beb15794eaee12b6f1d769589572d33e494faf5aa3b1f31b9fa"}, + {file = "pyclipper-1.3.0.post6-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e5e65176506da6335f6cbab497ae1a29772064467fa69f66de6bab4b6304d34"}, + {file = "pyclipper-1.3.0.post6-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3d58202de8b8da4d1559afbda4e90a8c260a5373672b6d7bc5448c4614385144"}, + {file = "pyclipper-1.3.0.post6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2cd8600bd16d209d5d45a33b45c278e1cc8bedc169af1a1f2187b581c521395"}, + {file = "pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c"}, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.6.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"}, + {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" + +[package.extras] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pypdfium2" +version = "4.30.0" +description = "Python bindings to PDFium" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pypdfium2-4.30.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:b33ceded0b6ff5b2b93bc1fe0ad4b71aa6b7e7bd5875f1ca0cdfb6ba6ac01aab"}, + {file = "pypdfium2-4.30.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4e55689f4b06e2d2406203e771f78789bd4f190731b5d57383d05cf611d829de"}, + {file = "pypdfium2-4.30.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e6e50f5ce7f65a40a33d7c9edc39f23140c57e37144c2d6d9e9262a2a854854"}, + {file = "pypdfium2-4.30.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3d0dd3ecaffd0b6dbda3da663220e705cb563918249bda26058c6036752ba3a2"}, + {file = "pypdfium2-4.30.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc3bf29b0db8c76cdfaac1ec1cde8edf211a7de7390fbf8934ad2aa9b4d6dfad"}, + {file = "pypdfium2-4.30.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1f78d2189e0ddf9ac2b7a9b9bd4f0c66f54d1389ff6c17e9fd9dc034d06eb3f"}, + {file = "pypdfium2-4.30.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5eda3641a2da7a7a0b2f4dbd71d706401a656fea521b6b6faa0675b15d31a163"}, + {file = "pypdfium2-4.30.0-py3-none-musllinux_1_1_i686.whl", hash = "sha256:0dfa61421b5eb68e1188b0b2231e7ba35735aef2d867d86e48ee6cab6975195e"}, + {file = "pypdfium2-4.30.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f33bd79e7a09d5f7acca3b0b69ff6c8a488869a7fab48fdf400fec6e20b9c8be"}, + {file = "pypdfium2-4.30.0-py3-none-win32.whl", hash = "sha256:ee2410f15d576d976c2ab2558c93d392a25fb9f6635e8dd0a8a3a5241b275e0e"}, + {file = "pypdfium2-4.30.0-py3-none-win_amd64.whl", hash = "sha256:90dbb2ac07be53219f56be09961eb95cf2473f834d01a42d901d13ccfad64b4c"}, + {file = "pypdfium2-4.30.0-py3-none-win_arm64.whl", hash = "sha256:119b2969a6d6b1e8d55e99caaf05290294f2d0fe49c12a3f17102d01c441bd29"}, + {file = "pypdfium2-4.30.0.tar.gz", hash = "sha256:48b5b7e5566665bc1015b9d69c1ebabe21f6aee468b509531c3c8318eeee2e16"}, +] + +[[package]] +name = "python-bidi" +version = "0.6.3" +description = "Python Bidi layout wrapping the Rust crate unicode-bidi" +optional = false +python-versions = "*" +files = [ + {file = "python_bidi-0.6.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7e2a62d7ebb4af9831c85921063154ab4067c73768ad04f466dff1359e6f2650"}, + {file = "python_bidi-0.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b8035f02c3fcb52d372bfe51db00a0c95a3fdd6f0504a32e70d4f799809070d"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:854edec3ef1ef50c49f689b44900fb6c51d35f277e10b4749755d053f405a44a"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe4c4ab61701a5e3b916c6b63811c6fd708539a3f189ec6ca6bd22948a125af0"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855a4dc2d237587a734babc6179130f9e7b7c028651cdead6ec5b162115ac112"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c0635bf46ddd56cf3f71d0711fbc160fd90c36fd3176b3e91b0bf7447e549f1"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a4b7b6e458173614348db8e4a4406e468338c13ecc7b74d1e208d38d0d1d264"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a39a3b61851506ed489867c69f3580ba75063195bf4b00f1983de88e02bf30"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24ea5c9f5cf9f3919d81669d24a1405709f4d66c82c3ffa7f982fcece856b325"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:631d32fd1414d4795348122b820dadbff1ddaa6e53a70c1ee9d5a84911cc3c2d"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:205aac547f8166005e041b33069da2c8a345171b0d7c8177c3d16408acde9acd"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a05249eac27e983a103babb9a2812726312bd8f685fdc3264f78b8ff8124d09a"}, + {file = "python_bidi-0.6.3-cp310-none-win32.whl", hash = "sha256:44023d51ae78ae119ef11043b5fb8f3dfc5de5ec04d937d7c5abc4da8cba1770"}, + {file = "python_bidi-0.6.3-cp310-none-win_amd64.whl", hash = "sha256:866865bbbc97a144e74508e2513373bb590d38fca3b6e52b6905de54b34ddbd9"}, + {file = "python_bidi-0.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a656b91c74b77a5b005e6dac092947f00d546cce5d0ca70b6b6741b93f7705bf"}, + {file = "python_bidi-0.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4cb80856ce1e3f24c0d878fc85ab767c201ab8891a68f41d8da87eaf39c827de"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ad3f50925a5943d244c6ca05e0553922e917b3cc415580460d86af6a385ee23"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22f293338ec7d44e02991787d306d39e02f0b145810eef60802abd7833b6c2d0"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b1d522cbd0af85094ccce8ae95c57a6a9d4f98e85f3e7c1ad1fb5d1c2cd09e"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da00726ebf17f857d458b310e868cae4b3bac668396cd5e874e17809894417e5"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1204f2aa62ac6226f11dd1bee250d428abb128046cf1999317b3f303c70ea2"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7c99881440b2a4d8de7c2d7f3ac23e5f0a0ee0c5ae652f53188a21e9b0911f2d"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:10f7c23dbb23dd0d2b0f67f7d4c2ba59eb42f777e1749ed9e13dbc8c4d28ea75"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d7527247a9d8e0aa9d2d4ecd24cbd8216bc4e3e89e77f9c833eedf278d9761cc"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5d6829865ff94925280af066c8536ff9595a6e40d300f9fc0e6ca4ebbf3bc306"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e0d574c22fbab1ea996ddb1ebb3eabae521f5d129d7c699445cad81e81bc351"}, + {file = "python_bidi-0.6.3-cp311-none-win32.whl", hash = "sha256:8c5fc9f065c24bd8058d7e9a5d42415134de3cc1aa480eebc27e2ca132919dd8"}, + {file = "python_bidi-0.6.3-cp311-none-win_amd64.whl", hash = "sha256:46ee694cf5a632a8d47cc35de6926581e586425b582216962d3e6d913aea0b88"}, + {file = "python_bidi-0.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4bdc9dc1143c558ca6931d6712339a30470959f2b7eecb3d0687db7075c20a87"}, + {file = "python_bidi-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0775499b8037103278f05b2bf92d25bf04f40a9f77884ec3d42b01a1e52a40fe"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb3091aa5efbfc4da6fd52a2fccbf7853c6dc253ddaf9a189bcf3c4345865aa9"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75a9b68b3f5a8da9a33fe37607d9b267a8a3c5806d283a4a47365256773dd1e"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:208e09819ee0485c2ed4dc1932c39fc073dac3f2cb70b6d2ae0b7296e86831e6"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e17b67d86cd38f2bebc4a46090f83cabb0d1da3a3c920c68efe8093ae1a8d0d1"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933a17938f767fa64a8365732eba787a81c26214d89e1b3abe87912325ba26a9"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:772efb3e0ef17396bfd9d47da4805c74ed6c04f27cac08d7757f76602837fb9d"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a99114f33f8c0273a61b4afe7d4d715e098318ee4e5ce8f6bb5da8dcd3f95c7"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b30e620d39e85a30bb42f460fd8b5274caf261517edeb853b975d9ea1939b6bd"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bee94e3152a6c9ba731e086c9cc6203904290506ba52c505a2e59abab481eb13"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:926164ec594e9ea9a64faf54273c711d5e3233bcc6ef8966c6eeaddfb3b3075f"}, + {file = "python_bidi-0.6.3-cp312-none-win32.whl", hash = "sha256:cea395a7daee14c7d50a7e20890d12b9ff1938d81b23eb564f1707a175c37202"}, + {file = "python_bidi-0.6.3-cp312-none-win_amd64.whl", hash = "sha256:350e6c76f942465871f2b473a2076f5002f1df06e4c7abee3029ccca5f006786"}, + {file = "python_bidi-0.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:617d4391b19969de725922a256599e8218fc9c1ef0ff85884f1698fff482a977"}, + {file = "python_bidi-0.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81f418d54948542b21c03cd8ce622a480ead85fc53175a124c4562bdf55cec49"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0999b77af58396cfd789c8d068bac78d2d51363265aaf1369622099be9e0eb32"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5a0e852e8451147d96876f8233a9db6ed28c914d9767a6696cbc899e7df00c2"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905e212b12c9edfaa3a916a3acd11426b89507ed0f31641257ad586467602e8d"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:144adab8dc3a8560e294461114ce6dafec1a986cde6297994c1d31b3252f3298"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdbd5c265d64251798243d97228bb78441a1320fe3cf51c9a31191c56407839"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f824a878a593121570ce3da847d3b9ac50521782c433996d7f81f770d3ed00"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7dcbc7eb70a0c7c66ed5219213ee2afcc815988cb9e4b134631579c4ae46980"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ccbf53bc71a0a1b7f77524d1c2e51b245ae23a4f16afb80728071e21c187a768"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:702527506ca97bf549710ce03d89a2577ebe35e34c42eaecfbacb0862ba06dc6"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1563a8d9cfaeeeb5b4fc806f52a500b19893c63652bbd497dd6ed9def7b9ee8e"}, + {file = "python_bidi-0.6.3-cp313-none-win32.whl", hash = "sha256:f9b8e024eeaddecb4ca189e3199181985fab20c224db9a1f08db48b905c9905a"}, + {file = "python_bidi-0.6.3-cp313-none-win_amd64.whl", hash = "sha256:36b3fb05ef990613a81a23822246eaf6eef29af5182f8d8cdd174be13c92d1cc"}, + {file = "python_bidi-0.6.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3ef3c351c19348133e78aa4a05bc939f9f11d53c6733c5e8ec160a9fd78c902f"}, + {file = "python_bidi-0.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f482f205a62c958273e40c20405141f18c2d0529abb22ba6aa440602655f43a7"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:982f805714a5ee83b034b8ad6a27f37db994483b72657c7898053333737a5fe3"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ee410954d7dc7591106f9526c3ce9893a64345e69edf86d084fe8841e62bfa0"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4d4a8917804a1c749e92aafb152d239cd25127cea0bb8710b99315266022009"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:984cb68c5bc7980f9cc66ae2f9d06b7426445b7dfcce4d555ff04333c34d01a6"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bedcbab3867227519e2dfee6c3ac26d7722ce6a048f5c72585cf83779b8e61f8"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3e04c6f54e0e44e55433da0fabab6776c69bcfb1965f09e4bb5b5b4446846"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2825a720d9dbd8ff6158a458edfbdc55bfd3de3f8181a59c7126f78ef3e27b7b"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6b5f60865b4d10de024c35d9900efe371844da4a0cda1cb2a4bd35746ba69097"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9f859236e69250a0cc857968b8beef749ab4f7b29164cb9a8a3150d094c318fc"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:282c60f86f30ba6263e10427fec528ab17dde97c7a6653c0187d8e2412dec6f4"}, + {file = "python_bidi-0.6.3-cp38-none-win32.whl", hash = "sha256:5d33d011d334795ff4d5d0de57457a980f76055a338ebabe558e795f9e0fbe63"}, + {file = "python_bidi-0.6.3-cp38-none-win_amd64.whl", hash = "sha256:535069329c12ea08ad6a3b38c48cba2d912a704dee25566e7a37f2b67be9fece"}, + {file = "python_bidi-0.6.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d0945a3f116700ebc791911797096afe3e24953927b335c9c818f56475915aef"}, + {file = "python_bidi-0.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8eb231d9ab92bfd4a9b7e7282210f02d130d7935ec8cfb1d82d6d53fa858a3de"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce288cb2af08d5242b034ce4e6936e540046a4c5fbccda72610ac67d8b06b5cc"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:304094c931a9ca45a347fa31db9e01b9cbefd48a194950b1441f20ba24ff0d17"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3df42de54d0f0daea931439abefc97da6c642d3665bcde510cd31689230777ff"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc5a29d459deb7ce923cf33283de15776fa8008c55b42e1eed5ba76980cc01f3"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae862fc829ee747b30cce23353b2266c706376c9b1ebfea943d63731eb1a0cbd"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9419c87a7657b1b39497302461c7e501bbfd03442186083007e9a1627656871"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d352154a636a243235260ecd8dcbd2b73e3e7d1f42c280fdb6802876152f1435"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:04448d5355db4233da49c9f656b43b34fa0467f6b0c8ff766c1543eaed52f974"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07eb504fcefc9e9f416f03c089dce23b1ba79d0dd38e976f6f00944d8c708461"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7e083f0e7391b5672f9e76393d8d111e1bf875f784ad3659776a4881440ebf1c"}, + {file = "python_bidi-0.6.3-cp39-none-win32.whl", hash = "sha256:a153364706cacaea4f97a63b3d5db780d56b66c0a64f1d202065d3863f782075"}, + {file = "python_bidi-0.6.3-cp39-none-win_amd64.whl", hash = "sha256:4be0d628b84c2a524d080c653726fba6e518432f33ac970db25c6366b9b71303"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:28cd25ef6141a77e04a7fb6fef0a19cc307106f84a891777fcdd3306ae8cfc20"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e4eab3736a14b8d9daea3e8e638ca5a24051497152ba32fb08db9259dd77b858"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78d12927cab0f6b8304f04c9ed72bc1a2880df8974d8596e40e7e596c6a98b2e"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:440be542b334da05673bd94d53ba4922482b06fa3f4daca6c8fa7434afb33e8a"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9635ae0c9ee71b69f11cb6ab9523165c79fdb82ca53afb5afb0d401616fef80"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ebac008620916b0c02623926fd80719f2e61e4fa9b626ed1e309a6818b57486"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bb5fd4d9ccad52584ce8ad1468ec2e5b535519840ab1debe05c7fe4d32b800"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1caacb766872c54742cdb8a5c042bec1282c5a3144e4aeba6f8650ab8911d7f3"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:584dd7c4617ea0ef39900ef7b06b8c61e6ce3ccb4b90c28ed28fa3bf770c5124"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a3bdc284cc4a1d70942ba0582b91853403c5ca7df79909b755be69089ecc5e17"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:995ed295f2d9095facbef3025d79e209ec7ae1be0d1f385a49818edb2cb4421e"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:a50d62f4f1e10682babd529d46e9e62236ff202d3025a223c17ead32035cb410"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ea2898279afde47dcfec7a821abb54f7476e5584b655389aa731a50b90f8ea52"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe9b6a33f281814dfbf12fe27f35b8780edd6da62ce2a034994f006d6d0184e7"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435579d0bf2a7e2f872bb5c7254fe89cddfdea6909ed6dc3e8af4ffe1f3f1f18"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8d76a8452c9fa1ece0a70a7be15a516861c3875bb621e125305d0141ceac8e3"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbf64f5e3f19913f63f34832c0ddef5ea6a772c5dda54907a949e804c20021e3"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e226a888e13c9cf1e9f0de0a2ff5c98a50561cada19c0b0c69c76343685ee54"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308ee73ffaa771048b1ccec37145a2735da9d67df55583c2fc2cb73d78e86a91"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d85744ddcfeb207bbf6774b7e1a29af6e5e208ed5dbecc5853ec60ed8bc8242f"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:40ab8bf2f528a28a70231ce4015d81aea6d8f0a0cdd2bdaf024e9e7849a5ee55"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:aa857199f9d797c615a92ae1dec90d443a50373caf7af2cf4e791714afc31b2a"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:197fcd6cb88f021333622f83d7d68e842ab9e2df492ab04e1e84b6de8f15c698"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f832a45f05c15072edf473c6c3b9b164b25a2515f723d42c7400db848c299e59"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b2ee6cf7cadb5d5cc05eca4b8b55a433dab922633faf85b0d19ec2aeed9ad5b"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:835b1d72364618fc8313bfdba2f65ce8e11bd9c1eab01fe9a3c3ec93063cb5b1"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f92e0d6771d184bbb7b06645edb069c023f695de312bf78e35efe45e0da7f66"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a6a3e0e130a0db20c4808242470277045e921b414cd9f545cba67a8c17bb785"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2b2ee73e0f799ed234ed52af258f77a72aca216477d3ef072c59303f1a938c9"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:042d0ee4bb9286b605b252488501bdae6f5b249fe2422fb12e4884aa4dc316d1"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa68ef8e955d0d63fe2d9aac4f8b8b9f47869bf98a8773c7322918312dbdd109"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d07c491c2cede5b022356003070bc8e452a0dcf1d884db4a384e9a3383b9efd3"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6132d661a3bfac2b8cf0d301bcdd59c7cc3e2145ea090b75505816604d8118d5"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:61b8a3dc229617b2f8c15165001babf4a199af9001087cad10ded14ec0a028d4"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5bcab7ac734c5102e90b0f41274b81bdcf55009b05aaa6a653320d63304f20a7"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:44560c6abcf3a512e618b52b33bce3d053eaf020c0677d3b4512167715900e66"}, + {file = "python_bidi-0.6.3.tar.gz", hash = "sha256:e12114969001a328aea859f79efc30ab9c15241befb86e07029d8961d97fae36"}, +] + +[package.extras] +dev = ["pytest"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-docx" +version = "1.1.2" +description = "Create, read, and update Microsoft Word .docx files." +optional = false +python-versions = ">=3.7" +files = [ + {file = "python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe"}, + {file = "python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd"}, +] + +[package.dependencies] +lxml = ">=3.1.0" +typing-extensions = ">=4.9.0" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-pptx" +version = "1.0.2" +description = "Create, read, and update PowerPoint 2007+ (.pptx) files." +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba"}, + {file = "python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095"}, +] + +[package.dependencies] +lxml = ">=3.1.0" +Pillow = ">=3.3.2" +typing-extensions = ">=4.9.0" +XlsxWriter = ">=0.5.7" + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "pywin32" +version = "307" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-307-cp310-cp310-win32.whl", hash = "sha256:f8f25d893c1e1ce2d685ef6d0a481e87c6f510d0f3f117932781f412e0eba31b"}, + {file = "pywin32-307-cp310-cp310-win_amd64.whl", hash = "sha256:36e650c5e5e6b29b5d317385b02d20803ddbac5d1031e1f88d20d76676dd103d"}, + {file = "pywin32-307-cp310-cp310-win_arm64.whl", hash = "sha256:0c12d61e0274e0c62acee79e3e503c312426ddd0e8d4899c626cddc1cafe0ff4"}, + {file = "pywin32-307-cp311-cp311-win32.whl", hash = "sha256:fec5d27cc893178fab299de911b8e4d12c5954e1baf83e8a664311e56a272b75"}, + {file = "pywin32-307-cp311-cp311-win_amd64.whl", hash = "sha256:987a86971753ed7fdd52a7fb5747aba955b2c7fbbc3d8b76ec850358c1cc28c3"}, + {file = "pywin32-307-cp311-cp311-win_arm64.whl", hash = "sha256:fd436897c186a2e693cd0437386ed79f989f4d13d6f353f8787ecbb0ae719398"}, + {file = "pywin32-307-cp312-cp312-win32.whl", hash = "sha256:07649ec6b01712f36debf39fc94f3d696a46579e852f60157a729ac039df0815"}, + {file = "pywin32-307-cp312-cp312-win_amd64.whl", hash = "sha256:00d047992bb5dcf79f8b9b7c81f72e0130f9fe4b22df613f755ab1cc021d8347"}, + {file = "pywin32-307-cp312-cp312-win_arm64.whl", hash = "sha256:b53658acbfc6a8241d72cc09e9d1d666be4e6c99376bc59e26cdb6223c4554d2"}, + {file = "pywin32-307-cp313-cp313-win32.whl", hash = "sha256:ea4d56e48dc1ab2aa0a5e3c0741ad6e926529510516db7a3b6981a1ae74405e5"}, + {file = "pywin32-307-cp313-cp313-win_amd64.whl", hash = "sha256:576d09813eaf4c8168d0bfd66fb7cb3b15a61041cf41598c2db4a4583bf832d2"}, + {file = "pywin32-307-cp313-cp313-win_arm64.whl", hash = "sha256:b30c9bdbffda6a260beb2919f918daced23d32c79109412c2085cbc513338a0a"}, + {file = "pywin32-307-cp37-cp37m-win32.whl", hash = "sha256:5101472f5180c647d4525a0ed289ec723a26231550dbfd369ec19d5faf60e511"}, + {file = "pywin32-307-cp37-cp37m-win_amd64.whl", hash = "sha256:05de55a7c110478dc4b202230e98af5e0720855360d2b31a44bb4e296d795fba"}, + {file = "pywin32-307-cp38-cp38-win32.whl", hash = "sha256:13d059fb7f10792542082f5731d5d3d9645320fc38814759313e5ee97c3fac01"}, + {file = "pywin32-307-cp38-cp38-win_amd64.whl", hash = "sha256:7e0b2f93769d450a98ac7a31a087e07b126b6d571e8b4386a5762eb85325270b"}, + {file = "pywin32-307-cp39-cp39-win32.whl", hash = "sha256:55ee87f2f8c294e72ad9d4261ca423022310a6e79fb314a8ca76ab3f493854c6"}, + {file = "pywin32-307-cp39-cp39-win_amd64.whl", hash = "sha256:e9d5202922e74985b037c9ef46778335c102b74b95cec70f629453dbe7235d87"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "referencing" +version = "0.35.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rpds-py" +version = "0.22.3" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, + {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf"}, + {file = "rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652"}, + {file = "rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8"}, + {file = "rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f"}, + {file = "rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a"}, + {file = "rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64"}, + {file = "rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c"}, + {file = "rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e"}, + {file = "rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7"}, + {file = "rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627"}, + {file = "rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4"}, + {file = "rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84"}, + {file = "rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f"}, + {file = "rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de"}, + {file = "rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9"}, + {file = "rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333"}, + {file = "rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730"}, + {file = "rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf"}, + {file = "rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea"}, + {file = "rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520"}, + {file = "rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9"}, + {file = "rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6"}, + {file = "rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d"}, +] + +[[package]] +name = "rtree" +version = "1.3.0" +description = "R-Tree spatial index for Python GIS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Rtree-1.3.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:80879d9db282a2273ca3a0d896c84583940e9777477727a277624ebfd424c517"}, + {file = "Rtree-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4328e9e421797c347e6eb08efbbade962fe3664ebd60c1dffe82c40911b1e125"}, + {file = "Rtree-1.3.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:037130d3ce1fc029de81941ec416ba5546f66228380ba19bb41f2ea1294e8423"}, + {file = "Rtree-1.3.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:864a05d0c3b7ce6c5e34378b7ab630057603b79179368bc50624258bdf2ff631"}, + {file = "Rtree-1.3.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ec2ed6d1635753dab966e68f592a9c4896f3f4ec6ad2b09b776d592eacd883a9"}, + {file = "Rtree-1.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b4485fb3e5c5e85b94a95f0a930a3848e040d2699cfb012940ba5b0130f1e09a"}, + {file = "Rtree-1.3.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7e2e9211f4fb404c06a08fd2cbebb03234214f73c51913bb371c3d9954e99cc9"}, + {file = "Rtree-1.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c021f4772b25cc24915da8073e553ded6fa8d0b317caa4202255ed26b2344c1c"}, + {file = "Rtree-1.3.0-py3-none-win_amd64.whl", hash = "sha256:97f835801d24c10bbf02381abe5e327345c8296ec711dde7658792376abafc66"}, + {file = "rtree-1.3.0.tar.gz", hash = "sha256:b36e9dd2dc60ffe3d02e367242d2c26f7281b00e1aaf0c39590442edaaadd916"}, +] + +[[package]] +name = "scikit-image" +version = "0.24.0" +description = "Image processing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a"}, + {file = "scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b"}, + {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f46e6ce42e5409f4d09ce1b0c7f80dd7e4373bcec635b6348b63e3c886eac8"}, + {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ee0af13435c57351a3397eb379e72164ff85161923eec0c38849fecf1b4764"}, + {file = "scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7"}, + {file = "scikit_image-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:272909e02a59cea3ed4aa03739bb88df2625daa809f633f40b5053cf09241831"}, + {file = "scikit_image-0.24.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:190ebde80b4470fe8838764b9b15f232a964f1a20391663e31008d76f0c696f7"}, + {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c98cc695005faf2b79904e4663796c977af22586ddf1b12d6af2fa22842dc2"}, + {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa27b3a0dbad807b966b8db2d78da734cb812ca4787f7fbb143764800ce2fa9c"}, + {file = "scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c"}, + {file = "scikit_image-0.24.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6fccceb54c9574590abcddc8caf6cefa57c13b5b8b4260ab3ff88ad8f3c252b3"}, + {file = "scikit_image-0.24.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ccc01e4760d655aab7601c1ba7aa4ddd8b46f494ac46ec9c268df6f33ccddf4c"}, + {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18836a18d3a7b6aca5376a2d805f0045826bc6c9fc85331659c33b4813e0b563"}, + {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8579bda9c3f78cb3b3ed8b9425213c53a25fa7e994b7ac01f2440b395babf660"}, + {file = "scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc"}, + {file = "scikit_image-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef04360eda372ee5cd60aebe9be91258639c86ae2ea24093fb9182118008d009"}, + {file = "scikit_image-0.24.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e9aadb442360a7e76f0c5c9d105f79a83d6df0e01e431bd1d5757e2c5871a1f3"}, + {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e37de6f4c1abcf794e13c258dc9b7d385d5be868441de11c180363824192ff7"}, + {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4688c18bd7ec33c08d7bf0fd19549be246d90d5f2c1d795a89986629af0a1e83"}, + {file = "scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69"}, + {file = "scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab"}, +] + +[package.dependencies] +imageio = ">=2.33" +lazy-loader = ">=0.4" +networkx = ">=2.8" +numpy = ">=1.23" +packaging = ">=21" +pillow = ">=9.1" +scipy = ">=1.9" +tifffile = ">=2022.8.12" + +[package.extras] +build = ["Cython (>=3.0.4)", "build", "meson-python (>=0.15)", "ninja", "numpy (>=2.0.0rc1)", "packaging (>=21)", "pythran", "setuptools (>=67)", "spin (==0.8)", "wheel"] +data = ["pooch (>=1.6.0)"] +developer = ["ipython", "pre-commit", "tomli"] +docs = ["PyWavelets (>=1.1.1)", "dask[array] (>=2022.9.2)", "ipykernel", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.15.2)", "pytest-doctestplus", "pytest-runner", "scikit-learn (>=1.1)", "seaborn (>=0.11)", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-gallery (>=0.14)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] +optional = ["PyWavelets (>=1.1.1)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=0.2.1)", "dask[array] (>=2021.1.0)", "matplotlib (>=3.6)", "pooch (>=1.6.0)", "pyamg", "scikit-learn (>=1.1)"] +test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=7.0)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] + +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "setuptools" +version = "75.6.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +files = [ + {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, + {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] + +[[package]] +name = "shapely" +version = "2.0.6" +description = "Manipulation and analysis of geometric objects" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, + {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, + {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde"}, + {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e"}, + {file = "shapely-2.0.6-cp310-cp310-win32.whl", hash = "sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e"}, + {file = "shapely-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4"}, + {file = "shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e"}, + {file = "shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2"}, + {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855"}, + {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0"}, + {file = "shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d"}, + {file = "shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b"}, + {file = "shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0"}, + {file = "shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3"}, + {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8"}, + {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726"}, + {file = "shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f"}, + {file = "shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48"}, + {file = "shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013"}, + {file = "shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7"}, + {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381"}, + {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805"}, + {file = "shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a"}, + {file = "shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2"}, + {file = "shapely-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462"}, + {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653"}, + {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe"}, + {file = "shapely-2.0.6-cp37-cp37m-win32.whl", hash = "sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc"}, + {file = "shapely-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7"}, + {file = "shapely-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6"}, + {file = "shapely-2.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b"}, + {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13"}, + {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1"}, + {file = "shapely-2.0.6-cp38-cp38-win32.whl", hash = "sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5"}, + {file = "shapely-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee"}, + {file = "shapely-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595"}, + {file = "shapely-2.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be"}, + {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af"}, + {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5"}, + {file = "shapely-2.0.6-cp39-cp39-win32.whl", hash = "sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac"}, + {file = "shapely-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8"}, + {file = "shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6"}, +] + +[package.dependencies] +numpy = ">=1.14,<3" + +[package.extras] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sympy" +version = "1.13.1" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, + {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tifffile" +version = "2024.9.20" +description = "Read and write TIFF files" +optional = false +python-versions = ">=3.10" +files = [ + {file = "tifffile-2024.9.20-py3-none-any.whl", hash = "sha256:c54dc85bc1065d972cb8a6ffb3181389d597876aa80177933459733e4ed243dd"}, + {file = "tifffile-2024.9.20.tar.gz", hash = "sha256:3fbf3be2f995a7051a8ae05a4be70c96fc0789f22ed6f1c4104c973cf68a640b"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr"] +codecs = ["imagecodecs (>=2023.8.12)"] +plot = ["matplotlib"] +test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "roifile", "xarray", "zarr"] +xml = ["defusedxml", "lxml"] +zarr = ["fsspec", "zarr"] + +[[package]] +name = "torch" +version = "2.5.1" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "torch-2.5.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:71328e1bbe39d213b8721678f9dcac30dfc452a46d586f1d514a6aa0a99d4744"}, + {file = "torch-2.5.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:34bfa1a852e5714cbfa17f27c49d8ce35e1b7af5608c4bc6e81392c352dbc601"}, + {file = "torch-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:32a037bd98a241df6c93e4c789b683335da76a2ac142c0973675b715102dc5fa"}, + {file = "torch-2.5.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:23d062bf70776a3d04dbe74db950db2a5245e1ba4f27208a87f0d743b0d06e86"}, + {file = "torch-2.5.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:de5b7d6740c4b636ef4db92be922f0edc425b65ed78c5076c43c42d362a45457"}, + {file = "torch-2.5.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:340ce0432cad0d37f5a31be666896e16788f1adf8ad7be481196b503dad675b9"}, + {file = "torch-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:603c52d2fe06433c18b747d25f5c333f9c1d58615620578c326d66f258686f9a"}, + {file = "torch-2.5.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:31f8c39660962f9ae4eeec995e3049b5492eb7360dd4f07377658ef4d728fa4c"}, + {file = "torch-2.5.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:ed231a4b3a5952177fafb661213d690a72caaad97d5824dd4fc17ab9e15cec03"}, + {file = "torch-2.5.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:3f4b7f10a247e0dcd7ea97dc2d3bfbfc90302ed36d7f3952b0008d0df264e697"}, + {file = "torch-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:73e58e78f7d220917c5dbfad1a40e09df9929d3b95d25e57d9f8558f84c9a11c"}, + {file = "torch-2.5.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:8c712df61101964eb11910a846514011f0b6f5920c55dbf567bff8a34163d5b1"}, + {file = "torch-2.5.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:9b61edf3b4f6e3b0e0adda8b3960266b9009d02b37555971f4d1c8f7a05afed7"}, + {file = "torch-2.5.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1f3b7fb3cf7ab97fae52161423f81be8c6b8afac8d9760823fd623994581e1a3"}, + {file = "torch-2.5.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7974e3dce28b5a21fb554b73e1bc9072c25dde873fa00d54280861e7a009d7dc"}, + {file = "torch-2.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:46c817d3ea33696ad3b9df5e774dba2257e9a4cd3c4a3afbf92f6bb13ac5ce2d"}, + {file = "torch-2.5.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:8046768b7f6d35b85d101b4b38cba8aa2f3cd51952bc4c06a49580f2ce682291"}, +] + +[package.dependencies] +filelock = "*" +fsspec = "*" +jinja2 = "*" +networkx = "*" +nvidia-cublas-cu12 = {version = "12.4.5.8", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "9.1.0.70", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.2.1.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.5.147", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.6.1.9", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.3.1.170", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.21.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvjitlink-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +setuptools = {version = "*", markers = "python_version >= \"3.12\""} +sympy = {version = "1.13.1", markers = "python_version >= \"3.9\""} +triton = {version = "3.1.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and python_version < \"3.13\""} +typing-extensions = ">=4.8.0" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] +optree = ["optree (>=0.12.0)"] + +[[package]] +name = "torchvision" +version = "0.20.1" +description = "image and video datasets and models for torch deep learning" +optional = false +python-versions = ">=3.8" +files = [ + {file = "torchvision-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4878fefb96ef293d06c27210918adc83c399d9faaf34cda5a63e129f772328f1"}, + {file = "torchvision-0.20.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8ffbdf8bf5b30eade22d459f5a313329eeadb20dc75efa142987b53c007098c3"}, + {file = "torchvision-0.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:75f8a4d51a593c4bab6c9bf7d75bdd88691b00a53b07656678bc55a3a753dd73"}, + {file = "torchvision-0.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:22c2fa44e20eb404b85e42b22b453863a14b0927d25e550fd4f84eea97fa5b39"}, + {file = "torchvision-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:344b339e15e6bbb59ee0700772616d0afefd209920c762b1604368d8c3458322"}, + {file = "torchvision-0.20.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:86f6523dee420000fe14c3527f6c8e0175139fda7d995b187f54a0b0ebec7eb6"}, + {file = "torchvision-0.20.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:a40d766345927639da322c693934e5f91b1ba2218846c7104b868dea2314ce8e"}, + {file = "torchvision-0.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:5b501d5c04b034d2ecda96a31ed050e383cf8201352e4c9276ca249cbecfded0"}, + {file = "torchvision-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a31256ff945d64f006bb306813a7c95a531fe16bfb2535c837dd4c104533d7a"}, + {file = "torchvision-0.20.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:17cd78adddf81dac57d7dccc9277a4d686425b1c55715f308769770cb26cad5c"}, + {file = "torchvision-0.20.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9f853ba4497ac4691815ad41b523ee23cf5ba4f87b1ce869d704052e233ca8b7"}, + {file = "torchvision-0.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a330422c36dbfc946d3a6c1caec3489db07ecdf3675d83369adb2e5a0ca17c4"}, + {file = "torchvision-0.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2cd58406978b813188cf4e9135b218775b57e0bb86d4a88f0339874b8a224819"}, + {file = "torchvision-0.20.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:408766b2f0ada9e1bc880d12346cec9638535af5df6459ba9ac4ce5c46402f91"}, + {file = "torchvision-0.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:abcb8005de8dc393dbd1310ecb669dc68ab664b9107af6d698a6341d1d3f2c3c"}, + {file = "torchvision-0.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:ea9678163bbf19568f4f959d927f3751eeb833cc8eac949de507edde38c1fc9f"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=5.3.0,<8.3.dev0 || >=8.4.dev0" +torch = "2.5.1" + +[package.extras] +gdown = ["gdown (>=4.7.3)"] +scipy = ["scipy"] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "triton" +version = "3.1.0" +description = "A language and compiler for custom Deep Learning operations" +optional = false +python-versions = "*" +files = [ + {file = "triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8"}, + {file = "triton-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f34f6e7885d1bf0eaaf7ba875a5f0ce6f3c13ba98f9503651c1e6dc6757ed5c"}, + {file = "triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc"}, + {file = "triton-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dadaca7fc24de34e180271b5cf864c16755702e9f63a16f62df714a8099126a"}, + {file = "triton-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aafa9a20cd0d9fee523cd4504aa7131807a864cd77dcf6efe7e981f18b8c6c11"}, +] + +[package.dependencies] +filelock = "*" + +[package.extras] +build = ["cmake (>=3.20)", "lit"] +tests = ["autopep8", "flake8", "isort", "llnl-hatchet", "numpy", "pytest", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + +[[package]] +name = "typer" +version = "0.12.5" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"}, + {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "tzdata" +version = "2024.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "xlsxwriter" +version = "3.2.0" +description = "A Python module for creating Excel XLSX files." +optional = false +python-versions = ">=3.6" +files = [ + {file = "XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e"}, + {file = "XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "~3.12.0" +content-hash = "89e60498275d39945d4e994cff91e11237a68acc83df5bc1644bd6fe845b664b" diff --git a/pdf2markdown/pyproject.toml b/pdf2markdown/pyproject.toml new file mode 100644 index 00000000..dc8b9be8 --- /dev/null +++ b/pdf2markdown/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "pdf2markdown" +version = "0.1.0" +description = "Convert PDFs to markdown" +authors = ["Christian Landgren", "Samuel Plumppu", "Hugo Björk"] +package-mode = false + +[tool.poetry.dependencies] +python = "~3.12.0" +docling = "^2.8.3" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file From fbcc7015a6f2d339251d2b414037489282412b1b Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 17:07:44 +0100 Subject: [PATCH 24/66] Parse PDF to JSON with Docling Co-authored-by: jomiq --- pdf2markdown/package-lock.json | 2426 ++++++++++++++++++++++++ pdf2markdown/package.json | 7 +- pdf2markdown/src/count-tables.ts | 26 + pdf2markdown/src/lib/docling-schema.ts | 144 ++ pdf2markdown/src/parse_pdf.py | 108 ++ 5 files changed, 2710 insertions(+), 1 deletion(-) create mode 100644 pdf2markdown/package-lock.json create mode 100644 pdf2markdown/src/count-tables.ts create mode 100644 pdf2markdown/src/lib/docling-schema.ts create mode 100644 pdf2markdown/src/parse_pdf.py diff --git a/pdf2markdown/package-lock.json b/pdf2markdown/package-lock.json new file mode 100644 index 00000000..958e108d --- /dev/null +++ b/pdf2markdown/package-lock.json @@ -0,0 +1,2426 @@ +{ + "name": "pdf2markdown", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pdf2markdown", + "version": "1.0.0", + "dependencies": { + "@types/node": "^22.10.0", + "express": "^5.0.1", + "openai": "^4.73.1", + "pdf2pic": "^3.1.3", + "sharp": "^0.33.5", + "tsx": "^4.19.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "prettier": "^3.4.2", + "typescript": "^5.7.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", + "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "license": "MIT" + }, + "node_modules/array-parallel": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz", + "integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==", + "license": "MIT" + }, + "node_modules/array-series": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", + "integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", + "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "3.1.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.5.2", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "^3.0.0", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.0.tgz", + "integrity": "sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "license": "MIT", + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/finalhandler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", + "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz", + "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gm": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/gm/-/gm-1.25.0.tgz", + "integrity": "sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==", + "license": "MIT", + "dependencies": { + "array-parallel": "~0.1.3", + "array-series": "~0.1.5", + "cross-spawn": "^4.0.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gm/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "license": "ISC", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", + "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.53.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.76.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.0.tgz", + "integrity": "sha512-QBGIetjX1C9xDp5XGa/3mPnfKI9BgAe2xHQX6PmO98wuW9qQaurBaumcYptQWc9LHZZq7cH/Y1Rjnsr6uUDdVw==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/pdf2pic": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pdf2pic/-/pdf2pic-3.1.3.tgz", + "integrity": "sha512-KbW4Qb7iHw2fBRWtA9FTc4pZg9cokiFIzc6cE7dzelTrhXWolfQuG1fYVC0E2BRmK/w7xfBjQ+OEsPZPO3QEew==", + "license": "MIT", + "dependencies": { + "gm": "^1.25.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "paypal", + "url": "https://www.paypal.me/yakovmeister" + } + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "license": "ISC" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", + "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", + "license": "MIT", + "dependencies": { + "array-flatten": "3.0.0", + "is-promise": "4.0.0", + "methods": "~1.1.2", + "parseurl": "~1.3.3", + "path-to-regexp": "^8.0.0", + "setprototypeof": "1.2.0", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", + "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "http-errors": "^2.0.0", + "mime-types": "^2.1.35", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", + "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", + "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/pdf2markdown/package.json b/pdf2markdown/package.json index 3dc36f19..dbade3ca 100644 --- a/pdf2markdown/package.json +++ b/pdf2markdown/package.json @@ -20,6 +20,11 @@ }, "devDependencies": { "@types/express": "^5.0.0", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "prettier": "^3.4.2" + }, + "prettier": { + "semi": false, + "tabWidth": 2 } } diff --git a/pdf2markdown/src/count-tables.ts b/pdf2markdown/src/count-tables.ts new file mode 100644 index 00000000..c592c3b2 --- /dev/null +++ b/pdf2markdown/src/count-tables.ts @@ -0,0 +1,26 @@ +import { readFile } from "fs/promises" +import { resolve } from "path" +import { DoclingDocumentSchema } from "./lib/docling-schema" + +async function main() { + const file = await readFile( + resolve(import.meta.dirname, "../scratch/Vestum-arsredovisning-2023.json"), + { encoding: "utf-8" } + ).then(JSON.parse) + + const TablesSchema = DoclingDocumentSchema.pick({ tables: true }) + + const document = TablesSchema.parse(file) + const uniquePages = new Set( + document.tables.map((t) => t.prov.at(0)?.page_no).filter(Number.isFinite) + ) + + // with accurate parsing: 108 tables on 42 unique pages - 300 seconds => 5 min + // with fast parsing: 108 tables on 42 unique pages - 175 seconds => 3 min + console.dir({ + tables: document.tables.length, + uniquePages, + }) +} + +await main() diff --git a/pdf2markdown/src/lib/docling-schema.ts b/pdf2markdown/src/lib/docling-schema.ts new file mode 100644 index 00000000..e2eda841 --- /dev/null +++ b/pdf2markdown/src/lib/docling-schema.ts @@ -0,0 +1,144 @@ +import * as z from 'zod' + +export const BodyLabelSchema = z.enum(['list', 'unspecified']) +export type BodyLabel = z.infer + +export const NameSchema = z.enum(['list', '_root_']) +export type Name = z.infer + +export const CoordOriginSchema = z.enum(['BOTTOMLEFT']) +export type CoordOrigin = z.infer + +export const PictureLabelSchema = z.enum(['picture', 'table']) +export type PictureLabel = z.infer + +export const TextLabelSchema = z.enum([ + 'caption', + 'footnote', + 'list_item', + 'page_footer', + 'page_header', + 'paragraph', + 'section_header', + 'text', +]) +export type TextLabel = z.infer + +export const MarkerSchema = z.enum(['-']) +export type Marker = z.infer + +export const ParentSchema = z.object({ + $ref: z.string(), +}) +export type Parent = z.infer + +export const OriginSchema = z.object({ + mimetype: z.string(), + binary_hash: z.number(), + filename: z.string(), +}) +export type Origin = z.infer + +export const SizeSchema = z.object({ + width: z.number(), + height: z.number(), +}) +export type Size = z.infer + +export const BboxSchema = z.object({ + l: z.number(), + t: z.number(), + r: z.number(), + b: z.number(), + coord_origin: CoordOriginSchema, +}) +export type Bbox = z.infer + +export const ProvSchema = z.object({ + page_no: z.number(), + bbox: BboxSchema, + charspan: z.array(z.number()), +}) +export type Prov = z.infer + +export const TextSchema = z.object({ + self_ref: z.string(), + parent: ParentSchema, + children: z.array(z.any()), + label: TextLabelSchema, + prov: z.array(ProvSchema), + orig: z.string(), + text: z.string(), + level: z.number().optional(), + enumerated: z.boolean().optional(), + marker: MarkerSchema.optional(), +}) +export type Text = z.infer + +export const BodySchema = z.object({ + self_ref: z.string(), + children: z.array(ParentSchema), + name: NameSchema, + label: BodyLabelSchema, + parent: ParentSchema.optional(), +}) +export type Body = z.infer + +export const PageSchema = z.object({ + size: SizeSchema, + page_no: z.number(), +}) +export type Page = z.infer + +export const TableCellSchema = z.object({ + bbox: BboxSchema.optional(), + row_span: z.number(), + col_span: z.number(), + start_row_offset_idx: z.number(), + end_row_offset_idx: z.number(), + start_col_offset_idx: z.number(), + end_col_offset_idx: z.number(), + text: z.string(), + column_header: z.boolean(), + row_header: z.boolean(), + row_section: z.boolean(), +}) +export type TableCell = z.infer + +export const DataSchema = z.object({ + table_cells: z.array(TableCellSchema), + num_rows: z.number(), + num_cols: z.number(), + grid: z.array(z.array(TableCellSchema)), +}) +export type Data = z.infer + +export const PictureSchema = z.object({ + self_ref: z.string(), + parent: ParentSchema, + children: z.array(z.any()), + label: PictureLabelSchema, + prov: z.array(ProvSchema), + captions: z.array(ParentSchema), + references: z.array(z.any()), + footnotes: z.array(z.any()), + annotations: z.array(z.any()).optional(), + data: DataSchema.optional(), +}) +export type Picture = z.infer + +export const DoclingDocumentSchema = z.object({ + schema_name: z.string(), + version: z.string(), + name: z.string(), + origin: OriginSchema, + furniture: BodySchema, + body: BodySchema, + groups: z.array(BodySchema), + texts: z.array(TextSchema), + pictures: z.array(PictureSchema), + tables: z.array(PictureSchema), + key_value_items: z.array(z.any()), + pages: z.record(z.string(), PageSchema), +}) +export type DoclingDocument = z.infer diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py new file mode 100644 index 00000000..09b87e2a --- /dev/null +++ b/pdf2markdown/src/parse_pdf.py @@ -0,0 +1,108 @@ +import logging +import time +import json +from pathlib import Path +from typing import Iterable +from docling.datamodel.base_models import ConversionStatus +from docling.datamodel.document import ConversionResult +from docling.datamodel.settings import settings +from docling.datamodel.base_models import InputFormat +from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode +from docling.document_converter import DocumentConverter, PdfFormatOption +from docling.models.tesseract_ocr_model import TesseractOcrOptions + +_log = logging.getLogger(__name__) + +def export_documents( + conv_results: Iterable[ConversionResult], + output_dir: Path, +): + output_dir.mkdir(parents=True, exist_ok=True) + + success_count = 0 + failure_count = 0 + partial_success_count = 0 + + for conv_res in conv_results: + if conv_res.status == ConversionStatus.SUCCESS: + success_count += 1 + doc_filename = conv_res.input.file.stem + + with (output_dir / f"{doc_filename}.json").open("w") as fp: + json.dump(conv_res.document.export_to_dict(), fp, ensure_ascii=False) + + elif conv_res.status == ConversionStatus.PARTIAL_SUCCESS: + _log.info( + f"Document {conv_res.input.file} was partially converted with the following errors:" + ) + for item in conv_res.errors: + _log.info(f"\t{item.error_message}") + partial_success_count += 1 + else: + _log.info(f"Document {conv_res.input.file} failed to convert.") + failure_count += 1 + + _log.info( + f"Processed {success_count + partial_success_count + failure_count} docs, " + f"of which {failure_count} failed " + f"and {partial_success_count} were partially converted." + ) + return success_count, partial_success_count, failure_count + + +def main(): + logging.basicConfig(level=logging.INFO) + + input_doc_paths = map(lambda file: Path("../garbo_pdfs").joinpath(file), [ + # Path("garbo_pdfs/astra-zeneca-2023.pdf"), + "Vestum-arsredovisning-2023.pdf" + ]) + + # buf = BytesIO(Path("./test/data/2206.01062.pdf").open("rb").read()) + # docs = [DocumentStream(name="my_doc.pdf", stream=buf)] + # input = DocumentConversionInput.from_streams(docs) + + # # Turn on inline debug visualizations: + # settings.debug.visualize_layout = True + # settings.debug.visualize_ocr = True + # settings.debug.visualize_tables = True + # settings.debug.visualize_cells = True + + # Docling Parse with Tesseract + pipeline_options = PdfPipelineOptions() + pipeline_options.do_ocr = True + pipeline_options.do_table_structure = True + pipeline_options.table_structure_options.do_cell_matching = True + # TODO: decide between ACCURATE and FAST modes + # with accurate parsing: 108 tables on 42 unique pages - 300 seconds => 5 min + # with fast parsing: 108 tables on 42 unique pages - 175 seconds => 3 min + pipeline_options.table_structure_options.mode = TableFormerMode.FAST + pipeline_options.ocr_options = TesseractOcrOptions() + + doc_converter = DocumentConverter( + format_options={ + InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options) + } + ) + + start_time = time.time() + + conv_results = doc_converter.convert_all( + input_doc_paths, + raises_on_error=False, # to let conversion run through all and examine results at the end + ) + success_count, partial_success_count, failure_count = export_documents( + conv_results, output_dir=Path("scratch") + ) + + end_time = time.time() - start_time + + _log.info(f"Document conversion complete in {end_time:.2f} seconds.") + + if failure_count > 0: + raise RuntimeError( + f"The example failed converting {failure_count} on {len(input_doc_paths)}." + ) + +if __name__ == "__main__": + main() \ No newline at end of file From 9000b544aae4f4dfcda67154bf4b03f178740be1 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 17:41:26 +0100 Subject: [PATCH 25/66] Try page and table screenshots --- pdf2markdown/.gitignore | 1 + pdf2markdown/src/parse_pdf.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 pdf2markdown/.gitignore diff --git a/pdf2markdown/.gitignore b/pdf2markdown/.gitignore new file mode 100644 index 00000000..0bdb9762 --- /dev/null +++ b/pdf2markdown/.gitignore @@ -0,0 +1 @@ +scratch \ No newline at end of file diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 09b87e2a..f847ff09 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -10,6 +10,7 @@ from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode from docling.document_converter import DocumentConverter, PdfFormatOption from docling.models.tesseract_ocr_model import TesseractOcrOptions +from docling_core.types.doc import TableItem _log = logging.getLogger(__name__) @@ -30,6 +31,30 @@ def export_documents( with (output_dir / f"{doc_filename}.json").open("w") as fp: json.dump(conv_res.document.export_to_dict(), fp, ensure_ascii=False) + + page_images_dir = output_dir / doc_filename / f"pages" + page_images_dir.mkdir(parents=True, exist_ok=True) + + table_images_dir = output_dir / doc_filename / f"tables" + table_images_dir.mkdir(parents=True, exist_ok=True) + + # Save page images + for page_no, page in conv_res.document.pages.items(): + page_no = page.page_no + page_image_filename = page_images_dir / f"{doc_filename}-{page_no}.png" + with page_image_filename.open("wb") as fp: + page.image.pil_image.save(fp, format="PNG") + + # Save images of figures and tables + table_counter = 0 + for element, _level in conv_res.document.iterate_items(): + if isinstance(element, TableItem): + table_counter += 1 + element_image_filename = ( + table_images_dir / f"{doc_filename}-table-{table_counter}.png" + ) + with element_image_filename.open("wb") as fp: + element.get_image(conv_res.document).save(fp, "PNG") elif conv_res.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( @@ -70,6 +95,7 @@ def main(): # Docling Parse with Tesseract pipeline_options = PdfPipelineOptions() + pipeline_options.do_ocr = True pipeline_options.do_table_structure = True pipeline_options.table_structure_options.do_cell_matching = True @@ -79,6 +105,11 @@ def main(): pipeline_options.table_structure_options.mode = TableFormerMode.FAST pipeline_options.ocr_options = TesseractOcrOptions() + pipeline_options.generate_table_images=True + pipeline_options.generate_page_images=True + pipeline_options.images_scale=1 + pipeline_options.ocr_options.lang = ["swe", "eng"] + doc_converter = DocumentConverter( format_options={ InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options) From 2a4b1b88e6f6a45a41ccc49f98cf1df6a7eb9b49 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 17:58:17 +0100 Subject: [PATCH 26/66] Use DocumentStream as input --- pdf2markdown/src/parse_pdf.py | 56 +++++------------------------------ 1 file changed, 7 insertions(+), 49 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index f847ff09..4fbf3f02 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -1,6 +1,7 @@ import logging import time import json +from io import BytesIO from pathlib import Path from typing import Iterable from docling.datamodel.base_models import ConversionStatus @@ -8,9 +9,8 @@ from docling.datamodel.settings import settings from docling.datamodel.base_models import InputFormat from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode -from docling.document_converter import DocumentConverter, PdfFormatOption +from docling.document_converter import DocumentConverter, PdfFormatOption, DocumentStream, _DocumentConversionInput from docling.models.tesseract_ocr_model import TesseractOcrOptions -from docling_core.types.doc import TableItem _log = logging.getLogger(__name__) @@ -31,30 +31,6 @@ def export_documents( with (output_dir / f"{doc_filename}.json").open("w") as fp: json.dump(conv_res.document.export_to_dict(), fp, ensure_ascii=False) - - page_images_dir = output_dir / doc_filename / f"pages" - page_images_dir.mkdir(parents=True, exist_ok=True) - - table_images_dir = output_dir / doc_filename / f"tables" - table_images_dir.mkdir(parents=True, exist_ok=True) - - # Save page images - for page_no, page in conv_res.document.pages.items(): - page_no = page.page_no - page_image_filename = page_images_dir / f"{doc_filename}-{page_no}.png" - with page_image_filename.open("wb") as fp: - page.image.pil_image.save(fp, format="PNG") - - # Save images of figures and tables - table_counter = 0 - for element, _level in conv_res.document.iterate_items(): - if isinstance(element, TableItem): - table_counter += 1 - element_image_filename = ( - table_images_dir / f"{doc_filename}-table-{table_counter}.png" - ) - with element_image_filename.open("wb") as fp: - element.get_image(conv_res.document).save(fp, "PNG") elif conv_res.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( @@ -78,20 +54,8 @@ def export_documents( def main(): logging.basicConfig(level=logging.INFO) - input_doc_paths = map(lambda file: Path("../garbo_pdfs").joinpath(file), [ - # Path("garbo_pdfs/astra-zeneca-2023.pdf"), - "Vestum-arsredovisning-2023.pdf" - ]) - - # buf = BytesIO(Path("./test/data/2206.01062.pdf").open("rb").read()) - # docs = [DocumentStream(name="my_doc.pdf", stream=buf)] - # input = DocumentConversionInput.from_streams(docs) - - # # Turn on inline debug visualizations: - # settings.debug.visualize_layout = True - # settings.debug.visualize_ocr = True - # settings.debug.visualize_tables = True - # settings.debug.visualize_cells = True + buf = BytesIO(Path("../garbo_pdfs/Vestum-arsredovisning-2023.pdf").open("rb").read()) + doc_stream = DocumentStream(name="my_doc.pdf", stream=buf) # Docling Parse with Tesseract pipeline_options = PdfPipelineOptions() @@ -104,10 +68,6 @@ def main(): # with fast parsing: 108 tables on 42 unique pages - 175 seconds => 3 min pipeline_options.table_structure_options.mode = TableFormerMode.FAST pipeline_options.ocr_options = TesseractOcrOptions() - - pipeline_options.generate_table_images=True - pipeline_options.generate_page_images=True - pipeline_options.images_scale=1 pipeline_options.ocr_options.lang = ["swe", "eng"] doc_converter = DocumentConverter( @@ -118,12 +78,10 @@ def main(): start_time = time.time() - conv_results = doc_converter.convert_all( - input_doc_paths, - raises_on_error=False, # to let conversion run through all and examine results at the end - ) + conv_result = doc_converter.convert(doc_stream, raises_on_error=False) + success_count, partial_success_count, failure_count = export_documents( - conv_results, output_dir=Path("scratch") + [conv_result], output_dir=Path("scratch") ) end_time = time.time() - start_time From d36b0d4778a61f68bfa7144b4ee82a0b082e3a62 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 18:06:50 +0100 Subject: [PATCH 27/66] Cleanup --- pdf2markdown/src/parse_pdf.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 4fbf3f02..25327b32 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -6,7 +6,6 @@ from typing import Iterable from docling.datamodel.base_models import ConversionStatus from docling.datamodel.document import ConversionResult -from docling.datamodel.settings import settings from docling.datamodel.base_models import InputFormat from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode from docling.document_converter import DocumentConverter, PdfFormatOption, DocumentStream, _DocumentConversionInput @@ -54,8 +53,9 @@ def export_documents( def main(): logging.basicConfig(level=logging.INFO) - buf = BytesIO(Path("../garbo_pdfs/Vestum-arsredovisning-2023.pdf").open("rb").read()) - doc_stream = DocumentStream(name="my_doc.pdf", stream=buf) + input_file=Path("../garbo_pdfs/Vestum-arsredovisning-2023.pdf") + buf = BytesIO(input_file.open("rb").read()) + input_streams = [DocumentStream(name=input_file.name, stream=buf)] # Docling Parse with Tesseract pipeline_options = PdfPipelineOptions() @@ -78,20 +78,12 @@ def main(): start_time = time.time() - conv_result = doc_converter.convert(doc_stream, raises_on_error=False) - - success_count, partial_success_count, failure_count = export_documents( - [conv_result], output_dir=Path("scratch") - ) + conv_result = doc_converter.convert(input_streams, raises_on_error=False) + export_documents([conv_result], output_dir=Path("scratch")) end_time = time.time() - start_time _log.info(f"Document conversion complete in {end_time:.2f} seconds.") - if failure_count > 0: - raise RuntimeError( - f"The example failed converting {failure_count} on {len(input_doc_paths)}." - ) - if __name__ == "__main__": main() \ No newline at end of file From 1ff988394bbdae3389427dac762f6cb4c93b4793 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:45:25 +0100 Subject: [PATCH 28/66] Parse document from Node --- pdf2markdown/package.json | 3 +- pdf2markdown/src/index.ts | 50 ++++++----- pdf2markdown/src/lib/pdfTools.ts | 149 ++++++++++++++++--------------- pdf2markdown/src/parse_pdf.py | 23 ++++- 4 files changed, 129 insertions(+), 96 deletions(-) diff --git a/pdf2markdown/package.json b/pdf2markdown/package.json index dbade3ca..077b4d3d 100644 --- a/pdf2markdown/package.json +++ b/pdf2markdown/package.json @@ -25,6 +25,7 @@ }, "prettier": { "semi": false, - "tabWidth": 2 + "tabWidth": 2, + "singleQuote": true } } diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 906a5481..7933c37b 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,26 +1,34 @@ import express from 'express' -import { extractJsonFromPdf } from './lib/pdfTools' +import { mkdir } from 'fs/promises' +import { resolve } from 'path' + +// import { extractJsonFromPdf } from './lib/pdfTools' import { jsonToMarkdown } from './lib/jsonExtraction' -const app = express() -const port = 3000 +export const OUTPUT_DIR = resolve('/tmp/pdf2markdown') + +// const app = express() +// const port = 3000 + +// app.post( +// '/convert', +// express.raw({ type: '*/*', limit: '50mb' }), +// async (req: express.Request, res: express.Response) => { +// try { +// const buffer = req.body +// const docId = crypto.randomUUID() +// await mkdir(resolve(`/tmp/${docId}`), { recursive: true }) -app.post( - '/convert', - express.raw({ type: '*/*', limit: '50mb' }), - async (req: express.Request, res: express.Response) => { - try { - const buffer = req.body - const json = await extractJsonFromPdf(buffer) - const markdown = await jsonToMarkdown(json, buffer) - res.type('text/plain').send(markdown) - } catch (error) { - console.error('Conversion error:', error) - res.status(500).json({ error: error.message }) - } - } -) +// const json = await extractJsonFromPdf(buffer, docId) +// const markdown = await jsonToMarkdown(json, buffer) +// res.type('text/plain').send(markdown) +// } catch (error) { +// console.error('Conversion error:', error) +// res.status(500).json({ error: error.message }) +// } +// }, +// ) -app.listen(port, () => { - console.log(`PDF to Markdown service running on port ${port}`) -}) +// app.listen(port, () => { +// console.log(`PDF to Markdown service running on port ${port}`) +// }) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index a86db2a2..d3b363f5 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -1,48 +1,68 @@ import { fromBuffer } from 'pdf2pic' -import path from 'path' +import { resolve, join } from 'path' +import { spawn } from 'child_process' import { ParsedDocument, ParsedDocumentSchema, Table, } from './nlm-ingestor-schema' -import { jsonToTables } from './jsonExtraction' -import { writeFile } from 'fs/promises' +// import { jsonToTables } from './jsonExtraction' +import { writeFile, readFile } from 'fs/promises' +import { OUTPUT_DIR } from '../index' + +async function parseDocument(inputFilePath: string) { + return new Promise((success, reject) => { + const docParser = spawn('python', [ + resolve('src/parse_pdf.py'), + inputFilePath, + ]) + + docParser.stdout.on('data', (data) => { + console.log(data.toString().trimEnd()) + }) -const NLM_INGESTOR_URL = 'http://localhost:5001' + docParser.stderr.on('data', (data) => { + console.error(data.toString().trimEnd()) + }) -async function checkNLMIngestorStatus() { - try { - const response = await fetch(`${NLM_INGESTOR_URL}`) - if (!response.ok) { - throw new Error( - `NLM Ingestor health check failed: ${response.statusText}` - ) - } - } catch (error) { - throw new Error(`NLM Ingestor service not available: ${error.message}`) - } + docParser.on('exit', (exitCode) => { + if (exitCode !== 0) { + reject() + } else { + success() + } + }) + }) } +// await parseDocument(resolve(OUTPUT_DIR, crypto.randomUUID(), 'input.pdf')) +await parseDocument( + resolve( + import.meta.dirname, + '../../garbo_pdfs/Vestum-arsredovisning-2023.pdf', + ), +) +process.exit(0) + +/* export async function extractJsonFromPdf( - buffer: Buffer + buffer: Buffer, + docId: string, ): Promise { - await checkNLMIngestorStatus() - const formData = new FormData() - formData.append('file', new Blob([buffer]), 'document.pdf') - const url = `${NLM_INGESTOR_URL}/api/parseDocument?renderFormat=json` - - const response = await fetch(url, { - method: 'POST', - body: formData, - signal: AbortSignal.timeout(6 * 60 * 1000), // 6 minutes + // NOTE: Maybe there's a way to pass the input PDF without writing a file + const inputFile = resolve(OUTPUT_DIR, docId, 'input.pdf') + await writeFile(inputFile, buffer) + + await parseDocument(inputFile).catch(() => { + // handle failure and return error }) - if (!response.ok) { - throw new Error(`Failed to parse PDF: ${response.statusText}`) - } + // TODO: When process is done, try reading `parsed.json` + // Get the tables and use them + // TODO: Later, try reading `parsed.md` when we should combine it with the document + + const body = 'TODO' - const body = await response.json() - // Validate basic response structure if (!body?.return_dict?.result?.blocks) { console.error('Invalid response structure:', JSON.stringify(body, null, 2)) @@ -50,43 +70,16 @@ export async function extractJsonFromPdf( } // Check for empty document - const hasContent = body.return_dict.result.blocks.some(block => - block.content && block.content.trim().length > 0 + const hasContent = body.return_dict.result.blocks.some( + (block) => block.content && block.content.trim().length > 0, ) if (!hasContent) { console.error('Document contains only empty blocks') console.error('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - throw new Error('Document appears to be empty or could not be parsed properly') - } - - console.log('\n=== Detailed Response Analysis ===') - console.log('1. Response structure:') - console.log('- Has return_dict:', 'return_dict' in body) - if (body.return_dict) { - console.log('- Return dict keys:', Object.keys(body.return_dict)) - if (body.return_dict.result) { - console.log('- Result keys:', Object.keys(body.return_dict.result)) - if (Array.isArray(body.return_dict.result.blocks)) { - console.log('\n2. Blocks analysis:') - console.log('- Total blocks:', body.return_dict.result.blocks.length) - body.return_dict.result.blocks.forEach((block, index) => { - console.log(`\nBlock ${index}:`) - console.log( - '- Type:', - 'rows' in block - ? 'Table' - : 'level' in block - ? 'Header' - : 'Paragraph' - ) - console.log('- Has content:', 'content' in block) - console.log('- Content type:', typeof block.content) - console.log('- Content preview:', block.content?.substring(0, 100)) - console.log('- Properties:', Object.keys(block)) - }) - } - } + throw new Error( + 'Document appears to be empty or could not be parsed properly', + ) } const result = ParsedDocumentSchema.safeParse(body) @@ -107,12 +100,12 @@ type Page = { export function findRelevantTablesGroupedOnPages( json: ParsedDocument, - searchTerms: string[] + searchTerms: string[], ): Page[] { const tables = jsonToTables(json).filter(({ content }) => searchTerms.some((term) => - content.toLowerCase().includes(term.toLowerCase()) - ) + content.toLowerCase().includes(term.toLowerCase()), + ), ) return tables.reduce((acc: Page[], table: Table) => { const [pageWidth, pageHeight] = json.return_dict.page_dim @@ -136,7 +129,7 @@ export async function extractTablesFromJson( pdf: Buffer, json: ParsedDocument, outputDir: string, - searchTerms: string[] + searchTerms: string[], ): Promise<{ pages: { pageIndex: number; filename: string }[] }> { const pdfConverter = (height: number, width: number) => { return fromBuffer(pdf, { @@ -149,15 +142,27 @@ export async function extractTablesFromJson( } const pages = Object.values( - findRelevantTablesGroupedOnPages(json, searchTerms) + findRelevantTablesGroupedOnPages(json, searchTerms), ) + // TODO: generate reportId when starting the request + // create a directory for the reportId + // save the incoming PDF file to the tmp directory + // read the PDF file from python and process it + // write parsed report output to a json file in the reportId directory + // also write parsed report to a markdown file in the reportId directory + // save images in the reportId directory + // use vision API for tables + // return the Docling parsed markdown, and combine with the more detailed tables + // maybe: remove tmp files after processing completed successfully to save space + // Or maybe store a timestamp in the first part of the directory name - and then check if it has passed more than 12h since the report was parsed, then remove it when receiving the next incoming request + // trigger indexMarkdown after receiving the parsed report back in the garbo container. const reportId = crypto.randomUUID() const filenames = await Promise.all( pages.map(async ({ pageIndex, pageHeight, pageWidth }) => { const pageNumber = pageIndex + 1 - const pageScreenshotPath = path.join( + const pageScreenshotPath = join( outputDir, - `${reportId}-page-${pageNumber}.png` + `${reportId}-page-${pageNumber}.png`, ) const convert = pdfConverter(pageHeight * 2, pageWidth * 2) const result = await convert(pageNumber, { responseType: 'buffer' }) @@ -165,13 +170,15 @@ export async function extractTablesFromJson( if (!result.buffer) { throw new Error( `Failed to convert pageNumber ${pageNumber} to a buffer\n` + - JSON.stringify(result, null, 2) + JSON.stringify(result, null, 2), ) } await writeFile(pageScreenshotPath, result.buffer) return { pageIndex, filename: pageScreenshotPath } - }) + }), ) return { pages: filenames } } + +*/ diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 25327b32..ca947a0e 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -1,9 +1,13 @@ import logging import time import json +import sys +import os +from argparse import ArgumentParser from io import BytesIO from pathlib import Path from typing import Iterable + from docling.datamodel.base_models import ConversionStatus from docling.datamodel.document import ConversionResult from docling.datamodel.base_models import InputFormat @@ -53,9 +57,22 @@ def export_documents( def main(): logging.basicConfig(level=logging.INFO) - input_file=Path("../garbo_pdfs/Vestum-arsredovisning-2023.pdf") + arg_parser = ArgumentParser(prog="parse_pdf", description='Parse a PDF') + arg_parser.add_argument('input', help="Path to the input file") + + parsed_args = arg_parser.parse_args(sys.argv[1:]) + + if not os.path.exists(parsed_args.input): + raise BaseException("Input path does not exist!") + + input_file = Path(parsed_args.input) + _log.info(f"Parsing {input_file}") + + # TODO: read input file from directory + # TODO: use the docId directory as base output_dir + buf = BytesIO(input_file.open("rb").read()) - input_streams = [DocumentStream(name=input_file.name, stream=buf)] + input_stream = DocumentStream(name=input_file.name, stream=buf) # Docling Parse with Tesseract pipeline_options = PdfPipelineOptions() @@ -78,7 +95,7 @@ def main(): start_time = time.time() - conv_result = doc_converter.convert(input_streams, raises_on_error=False) + conv_result = doc_converter.convert(input_stream, raises_on_error=False) export_documents([conv_result], output_dir=Path("scratch")) end_time = time.time() - start_time From 23c0ad89e02b86449a5d4587df5e3c8a818f104d Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:04:31 +0100 Subject: [PATCH 29/66] Pass docId from Node to Python and ensure it exists --- pdf2markdown/src/lib/pdfTools.ts | 18 +++++++----------- pdf2markdown/src/parse_pdf.py | 15 ++++++--------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index d3b363f5..fdd65e94 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -10,12 +10,9 @@ import { import { writeFile, readFile } from 'fs/promises' import { OUTPUT_DIR } from '../index' -async function parseDocument(inputFilePath: string) { +async function parseDocument(docId: string) { return new Promise((success, reject) => { - const docParser = spawn('python', [ - resolve('src/parse_pdf.py'), - inputFilePath, - ]) + const docParser = spawn('python', [resolve('src/parse_pdf.py'), docId]) docParser.stdout.on('data', (data) => { console.log(data.toString().trimEnd()) @@ -36,12 +33,11 @@ async function parseDocument(inputFilePath: string) { } // await parseDocument(resolve(OUTPUT_DIR, crypto.randomUUID(), 'input.pdf')) -await parseDocument( - resolve( - import.meta.dirname, - '../../garbo_pdfs/Vestum-arsredovisning-2023.pdf', - ), -) +try { + await parseDocument(crypto.randomUUID()) +} catch (e) { + console.error('Parsing failed!') +} process.exit(0) /* diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index ca947a0e..8e301ac1 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -46,11 +46,6 @@ def export_documents( _log.info(f"Document {conv_res.input.file} failed to convert.") failure_count += 1 - _log.info( - f"Processed {success_count + partial_success_count + failure_count} docs, " - f"of which {failure_count} failed " - f"and {partial_success_count} were partially converted." - ) return success_count, partial_success_count, failure_count @@ -58,14 +53,16 @@ def main(): logging.basicConfig(level=logging.INFO) arg_parser = ArgumentParser(prog="parse_pdf", description='Parse a PDF') - arg_parser.add_argument('input', help="Path to the input file") + arg_parser.add_argument('docId', help="The document ID to parse") parsed_args = arg_parser.parse_args(sys.argv[1:]) - if not os.path.exists(parsed_args.input): - raise BaseException("Input path does not exist!") + base_dir = Path('/tmp/pdf2markdown/') / parsed_args.docId + input_file = base_dir / 'input.pdf' + + if not os.path.exists(input_file): + raise Exception(f"Input PDF does not exist: {input_file}") - input_file = Path(parsed_args.input) _log.info(f"Parsing {input_file}") # TODO: read input file from directory From ca49503edfa5cf09d53f57d5d398c09c21c4121e Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:05:42 +0100 Subject: [PATCH 30/66] Remove duplicate time logging (docling already does this) --- pdf2markdown/src/parse_pdf.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 8e301ac1..20704c37 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -1,5 +1,4 @@ import logging -import time import json import sys import os @@ -90,14 +89,8 @@ def main(): } ) - start_time = time.time() - conv_result = doc_converter.convert(input_stream, raises_on_error=False) export_documents([conv_result], output_dir=Path("scratch")) - end_time = time.time() - start_time - - _log.info(f"Document conversion complete in {end_time:.2f} seconds.") - if __name__ == "__main__": main() \ No newline at end of file From ae450364369b67cae9bfd5679eadc7e0b03c4b80 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:13:32 +0100 Subject: [PATCH 31/66] Improve structure --- pdf2markdown/src/parse_pdf.py | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 20704c37..a5a1b15b 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -14,7 +14,7 @@ from docling.document_converter import DocumentConverter, PdfFormatOption, DocumentStream, _DocumentConversionInput from docling.models.tesseract_ocr_model import TesseractOcrOptions -_log = logging.getLogger(__name__) +_log = logging.getLogger('parse_pdf') def export_documents( conv_results: Iterable[ConversionResult], @@ -47,25 +47,11 @@ def export_documents( return success_count, partial_success_count, failure_count - -def main(): - logging.basicConfig(level=logging.INFO) - - arg_parser = ArgumentParser(prog="parse_pdf", description='Parse a PDF') - arg_parser.add_argument('docId', help="The document ID to parse") - - parsed_args = arg_parser.parse_args(sys.argv[1:]) - - base_dir = Path('/tmp/pdf2markdown/') / parsed_args.docId - input_file = base_dir / 'input.pdf' - +def parse_document(input_file: Path, output_dir: Path): if not os.path.exists(input_file): raise Exception(f"Input PDF does not exist: {input_file}") - - _log.info(f"Parsing {input_file}") - # TODO: read input file from directory - # TODO: use the docId directory as base output_dir + _log.info(f"Parsing {input_file}") buf = BytesIO(input_file.open("rb").read()) input_stream = DocumentStream(name=input_file.name, stream=buf) @@ -90,7 +76,21 @@ def main(): ) conv_result = doc_converter.convert(input_stream, raises_on_error=False) - export_documents([conv_result], output_dir=Path("scratch")) + export_documents([conv_result], output_dir) + + + +def main(): + logging.basicConfig(level=logging.INFO) + + arg_parser = ArgumentParser(prog="parse_pdf", description='Parse a PDF') + arg_parser.add_argument('docId', help="The document ID to parse") + parsed_args = arg_parser.parse_args(sys.argv[1:]) + + base_dir = Path('/tmp/pdf2markdown/') / parsed_args.docId + input_file = base_dir / 'input.pdf' + + parse_document(input_file, base_dir) if __name__ == "__main__": main() \ No newline at end of file From e8ecdb5c93f34c40e24e2be3d4e7d73df652e61b Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:31:27 +0100 Subject: [PATCH 32/66] Output both JSON and Markdown to the temporary docId directory --- pdf2markdown/src/parse_pdf.py | 52 ++++++++++++++--------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index a5a1b15b..7cdeac0a 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -5,7 +5,6 @@ from argparse import ArgumentParser from io import BytesIO from pathlib import Path -from typing import Iterable from docling.datamodel.base_models import ConversionStatus from docling.datamodel.document import ConversionResult @@ -16,49 +15,35 @@ _log = logging.getLogger('parse_pdf') -def export_documents( - conv_results: Iterable[ConversionResult], +def export_document( + conv_result: ConversionResult, output_dir: Path, ): output_dir.mkdir(parents=True, exist_ok=True) - success_count = 0 - failure_count = 0 - partial_success_count = 0 + if conv_result.status == ConversionStatus.SUCCESS: + with (output_dir / "parsed.json").open("w", encoding="utf-8") as fp: + json.dump(conv_result.document.export_to_dict(), fp, ensure_ascii=False) - for conv_res in conv_results: - if conv_res.status == ConversionStatus.SUCCESS: - success_count += 1 - doc_filename = conv_res.input.file.stem + with (output_dir / "parsed.md").open("w", encoding="utf-8") as fp: + fp.write(conv_result.document.export_to_markdown(image_placeholder='')) - with (output_dir / f"{doc_filename}.json").open("w") as fp: - json.dump(conv_res.document.export_to_dict(), fp, ensure_ascii=False) - - elif conv_res.status == ConversionStatus.PARTIAL_SUCCESS: - _log.info( - f"Document {conv_res.input.file} was partially converted with the following errors:" - ) - for item in conv_res.errors: - _log.info(f"\t{item.error_message}") - partial_success_count += 1 - else: - _log.info(f"Document {conv_res.input.file} failed to convert.") - failure_count += 1 - - return success_count, partial_success_count, failure_count + elif conv_result.status == ConversionStatus.PARTIAL_SUCCESS: + _log.info( + f"Document {conv_result.input.file} was partially converted with the following errors:" + ) + for item in conv_result.errors: + _log.info(f"\t{item.error_message}") + else: + _log.info(f"Document {conv_result.input.file} failed to convert.") def parse_document(input_file: Path, output_dir: Path): if not os.path.exists(input_file): raise Exception(f"Input PDF does not exist: {input_file}") - _log.info(f"Parsing {input_file}") - - buf = BytesIO(input_file.open("rb").read()) - input_stream = DocumentStream(name=input_file.name, stream=buf) + _log.info(f"Parsing {input_file} with Docling and Tesseract...") - # Docling Parse with Tesseract pipeline_options = PdfPipelineOptions() - pipeline_options.do_ocr = True pipeline_options.do_table_structure = True pipeline_options.table_structure_options.do_cell_matching = True @@ -75,8 +60,11 @@ def parse_document(input_file: Path, output_dir: Path): } ) + buf = BytesIO(input_file.open("rb").read()) + input_stream = DocumentStream(name=input_file.name, stream=buf) + conv_result = doc_converter.convert(input_stream, raises_on_error=False) - export_documents([conv_result], output_dir) + export_document(conv_result, output_dir) From c37c62650092cf47d32805e72f9e970a096cf0fe Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:44:43 +0100 Subject: [PATCH 33/66] Add env for openai and keep .env.example --- .gitignore | 4 ++-- pdf2markdown/.env.example | 6 ++++++ pdf2markdown/.gitignore | 11 ++++++++++- pdf2markdown/src/lib/openai.ts | 11 +++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 pdf2markdown/.env.example diff --git a/.gitignore b/.gitignore index 7ee67972..7d712a55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ node_modules dist .env -.aider* .env* +!.env.example +.aider* .DS_Store -!env.example prisma/generated *.xlsx diff --git a/pdf2markdown/.env.example b/pdf2markdown/.env.example new file mode 100644 index 00000000..de206f69 --- /dev/null +++ b/pdf2markdown/.env.example @@ -0,0 +1,6 @@ +# This file contains the environment variables that are used in the project. +# Copy this file to .env and fill in the values for the environment variables. + +NODE_ENV=development + +OPENAI_API_KEY= diff --git a/pdf2markdown/.gitignore b/pdf2markdown/.gitignore index 0bdb9762..8bce3506 100644 --- a/pdf2markdown/.gitignore +++ b/pdf2markdown/.gitignore @@ -1 +1,10 @@ -scratch \ No newline at end of file +node_modules +dist +.env +.env* +!.env.example +.aider* +.DS_Store + +*.pdf +*.code-workspace diff --git a/pdf2markdown/src/lib/openai.ts b/pdf2markdown/src/lib/openai.ts index aa47b451..392d89b2 100644 --- a/pdf2markdown/src/lib/openai.ts +++ b/pdf2markdown/src/lib/openai.ts @@ -1,4 +1,15 @@ import OpenAI from 'openai' +import { z } from 'zod' + +const envSchema = z.object({ + OPENAI_API_KEY: z.string(), +}) + +const env = envSchema.parse(process.env) + +export default { + apiKey: env.OPENAI_API_KEY, +} if (!process.env.OPENAI_API_KEY) { throw new Error('Missing OPENAI_API_KEY environment variable') From efffe108eaeca12e5b582a3fffbc097688d7dde4 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:44:50 +0100 Subject: [PATCH 34/66] Remove old script --- pdf2markdown/src/count-tables.ts | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 pdf2markdown/src/count-tables.ts diff --git a/pdf2markdown/src/count-tables.ts b/pdf2markdown/src/count-tables.ts deleted file mode 100644 index c592c3b2..00000000 --- a/pdf2markdown/src/count-tables.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readFile } from "fs/promises" -import { resolve } from "path" -import { DoclingDocumentSchema } from "./lib/docling-schema" - -async function main() { - const file = await readFile( - resolve(import.meta.dirname, "../scratch/Vestum-arsredovisning-2023.json"), - { encoding: "utf-8" } - ).then(JSON.parse) - - const TablesSchema = DoclingDocumentSchema.pick({ tables: true }) - - const document = TablesSchema.parse(file) - const uniquePages = new Set( - document.tables.map((t) => t.prov.at(0)?.page_no).filter(Number.isFinite) - ) - - // with accurate parsing: 108 tables on 42 unique pages - 300 seconds => 5 min - // with fast parsing: 108 tables on 42 unique pages - 175 seconds => 3 min - console.dir({ - tables: document.tables.length, - uniquePages, - }) -} - -await main() From dfe72319c533e20b22f8861ebc2fd3031c7da745 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:52:41 +0100 Subject: [PATCH 35/66] Safe parse --- pdf2markdown/src/lib/openai.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pdf2markdown/src/lib/openai.ts b/pdf2markdown/src/lib/openai.ts index 392d89b2..3367a518 100644 --- a/pdf2markdown/src/lib/openai.ts +++ b/pdf2markdown/src/lib/openai.ts @@ -5,16 +5,14 @@ const envSchema = z.object({ OPENAI_API_KEY: z.string(), }) -const env = envSchema.parse(process.env) - -export default { - apiKey: env.OPENAI_API_KEY, +const { success, data: env, error } = envSchema.safeParse(process.env) +if (!success) { + console.error('Schema validation failed:', error.format()) + throw new Error('Missing OPENAI env variable') } -if (!process.env.OPENAI_API_KEY) { - throw new Error('Missing OPENAI_API_KEY environment variable') +const config = { + apiKey: env.OPENAI_API_KEY, } -export const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, -}) +export const openai = new OpenAI(config) From 4ca156ef2e60ea7440fbf6a48b0d9bc758d3be27 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 7 Dec 2024 23:35:14 +0100 Subject: [PATCH 36/66] Improve args for parsing script --- pdf2markdown/src/lib/pdfTools.ts | 35 +++++++++++++++++++++++++++----- pdf2markdown/src/parse_pdf.py | 17 +++++++++------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index fdd65e94..be1cc515 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -7,12 +7,16 @@ import { Table, } from './nlm-ingestor-schema' // import { jsonToTables } from './jsonExtraction' -import { writeFile, readFile } from 'fs/promises' +import { writeFile, readFile, mkdir } from 'fs/promises' import { OUTPUT_DIR } from '../index' -async function parseDocument(docId: string) { +async function parseDocument(docId: string, baseDir: string) { return new Promise((success, reject) => { - const docParser = spawn('python', [resolve('src/parse_pdf.py'), docId]) + const docParser = spawn('python', [ + resolve(import.meta.dirname, '../parse_pdf.py'), + docId, + baseDir, + ]) docParser.stdout.on('data', (data) => { console.log(data.toString().trimEnd()) @@ -32,12 +36,33 @@ async function parseDocument(docId: string) { }) } -// await parseDocument(resolve(OUTPUT_DIR, crypto.randomUUID(), 'input.pdf')) +const docId = crypto.randomUUID() +const outDir = resolve(OUTPUT_DIR, docId) +await mkdir(outDir, { recursive: true }) +// TODO: save buffer to tmp PDF file which can be sent to the PDF parsing +// const inputPDF = resolve(outDir, 'input.pdf') +const inputPDF = resolve( + import.meta.dirname, + '../../garbo_pdfs/astra-zeneca-2023.pdf', +) + try { - await parseDocument(crypto.randomUUID()) + await parseDocument(inputPDF, outDir) } catch (e) { console.error('Parsing failed!') } + +const parsedJSON = await readFile(resolve(outDir, 'parsed.json'), { + encoding: 'utf-8', +}).then(JSON.parse) + +const parsedMarkdown = await readFile(resolve(outDir, 'parsed.md'), { + encoding: 'utf-8', +}) + +console.log('Tables found: ', parsedJSON.tables.length) +console.log('Markdown length: ', parsedMarkdown.length) + process.exit(0) /* diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 7cdeac0a..f6efe889 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -22,11 +22,16 @@ def export_document( output_dir.mkdir(parents=True, exist_ok=True) if conv_result.status == ConversionStatus.SUCCESS: - with (output_dir / "parsed.json").open("w", encoding="utf-8") as fp: + json_file = output_dir / "parsed.json" + markdown_file = output_dir / "parsed.md" + + with json_file.open("w", encoding="utf-8") as fp: json.dump(conv_result.document.export_to_dict(), fp, ensure_ascii=False) + _log.info(f"Saved document JSON to: {json_file}") - with (output_dir / "parsed.md").open("w", encoding="utf-8") as fp: + with markdown_file.open("w", encoding="utf-8") as fp: fp.write(conv_result.document.export_to_markdown(image_placeholder='')) + _log.info(f"Saved document Markdown to: {markdown_file}") elif conv_result.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( @@ -72,13 +77,11 @@ def main(): logging.basicConfig(level=logging.INFO) arg_parser = ArgumentParser(prog="parse_pdf", description='Parse a PDF') - arg_parser.add_argument('docId', help="The document ID to parse") + arg_parser.add_argument('inputPDF', help="Path to the input PDF document") + arg_parser.add_argument('outDir', help="Path to the output directory for results to the current document") parsed_args = arg_parser.parse_args(sys.argv[1:]) - base_dir = Path('/tmp/pdf2markdown/') / parsed_args.docId - input_file = base_dir / 'input.pdf' - - parse_document(input_file, base_dir) + parse_document(Path(parsed_args.inputPDF), Path(parsed_args.outDir)) if __name__ == "__main__": main() \ No newline at end of file From 55ee9603a919bacf4ee6038f8fbeb28b98f74352 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 01:13:07 +0100 Subject: [PATCH 37/66] WIP: POST /convert to upload a PDF and convert to markdown --- pdf2markdown/package.json | 2 +- pdf2markdown/src/index.ts | 62 ++++++++++++++++------------- pdf2markdown/src/lib/pdfTools.ts | 67 +++++++++++++------------------- pdf2markdown/src/test.ts | 38 ++++++++++++++++++ 4 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 pdf2markdown/src/test.ts diff --git a/pdf2markdown/package.json b/pdf2markdown/package.json index 077b4d3d..bf4dd0de 100644 --- a/pdf2markdown/package.json +++ b/pdf2markdown/package.json @@ -7,7 +7,7 @@ }, "scripts": { "start": "node --import tsx src/index.ts", - "dev": "tsx watch src/index.ts" + "dev": "node --import tsx --watch --env-file=.env.development src/index.ts" }, "dependencies": { "@types/node": "^22.10.0", diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 7933c37b..c30187cf 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,34 +1,40 @@ -import express from 'express' -import { mkdir } from 'fs/promises' -import { resolve } from 'path' +import express, { Request, Response, raw } from 'express' -// import { extractJsonFromPdf } from './lib/pdfTools' -import { jsonToMarkdown } from './lib/jsonExtraction' +import { extractJsonFromPdf } from './lib/pdfTools' +// import { jsonToMarkdown } from './lib/jsonExtraction' -export const OUTPUT_DIR = resolve('/tmp/pdf2markdown') +const app = express() +const port = 3000 -// const app = express() -// const port = 3000 +app.post( + '/convert', + raw({ type: '*/*', limit: '50mb' }), + async (req: Request, res: Response) => { + try { + const buffer = Buffer.isBuffer(req.body) ? req.body : null -// app.post( -// '/convert', -// express.raw({ type: '*/*', limit: '50mb' }), -// async (req: express.Request, res: express.Response) => { -// try { -// const buffer = req.body -// const docId = crypto.randomUUID() -// await mkdir(resolve(`/tmp/${docId}`), { recursive: true }) + if (!buffer) { + res.status(400).json({ + error: 'Request body should be a PDF file in binary format.', + }) + return + } else if (buffer.length === 0) { + res.status(400).json({ error: 'Empty request body.' }) + return + } -// const json = await extractJsonFromPdf(buffer, docId) -// const markdown = await jsonToMarkdown(json, buffer) -// res.type('text/plain').send(markdown) -// } catch (error) { -// console.error('Conversion error:', error) -// res.status(500).json({ error: error.message }) -// } -// }, -// ) + const parsed = await extractJsonFromPdf(buffer) + // TODO: implement table extraction + // IDEA: Maybe let docling save the page screenshots, because then we could remove the dependency pdf2pic and several native libs + // const markdown = await jsonToMarkdown(parsed.json, buffer) + res.type('text/markdown; charset=UTF-8').send(parsed.markdown) + } catch (error) { + console.error('Conversion error:', error) + res.status(500).json({ error: error.message }) + } + }, +) -// app.listen(port, () => { -// console.log(`PDF to Markdown service running on port ${port}`) -// }) +app.listen(port, () => { + console.log(`PDF to Markdown service running on port ${port}`) +}) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index be1cc515..4e08afc3 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -6,9 +6,10 @@ import { ParsedDocumentSchema, Table, } from './nlm-ingestor-schema' -// import { jsonToTables } from './jsonExtraction' +import { jsonToTables } from './jsonExtraction' import { writeFile, readFile, mkdir } from 'fs/promises' -import { OUTPUT_DIR } from '../index' + +const OUTPUT_DIR = resolve('/tmp/pdf2markdown') async function parseDocument(docId: string, baseDir: string) { return new Promise((success, reject) => { @@ -36,47 +37,37 @@ async function parseDocument(docId: string, baseDir: string) { }) } -const docId = crypto.randomUUID() -const outDir = resolve(OUTPUT_DIR, docId) -await mkdir(outDir, { recursive: true }) -// TODO: save buffer to tmp PDF file which can be sent to the PDF parsing -// const inputPDF = resolve(outDir, 'input.pdf') -const inputPDF = resolve( - import.meta.dirname, - '../../garbo_pdfs/astra-zeneca-2023.pdf', -) - -try { - await parseDocument(inputPDF, outDir) -} catch (e) { - console.error('Parsing failed!') -} +export async function extractJsonFromPdf( + buffer: Buffer, +): Promise<{ json: any; markdown: string }> { + const docId = crypto.randomUUID() + const outDir = resolve(OUTPUT_DIR, docId) + await mkdir(outDir, { recursive: true }) + const inputPDF = resolve(outDir, 'input.pdf') -const parsedJSON = await readFile(resolve(outDir, 'parsed.json'), { - encoding: 'utf-8', -}).then(JSON.parse) + // NOTE: Maybe there's a way to pass the input PDF without writing a file + await writeFile(inputPDF, buffer, { encoding: 'utf-8' }) -const parsedMarkdown = await readFile(resolve(outDir, 'parsed.md'), { - encoding: 'utf-8', -}) + try { + await parseDocument(inputPDF, outDir) + } catch (e) { + throw new Error('Conversion failed!') + } -console.log('Tables found: ', parsedJSON.tables.length) -console.log('Markdown length: ', parsedMarkdown.length) + const json = await readFile(resolve(outDir, 'parsed.json'), { + encoding: 'utf-8', + }).then(JSON.parse) -process.exit(0) + const markdown = await readFile(resolve(outDir, 'parsed.md'), { + encoding: 'utf-8', + }) -/* -export async function extractJsonFromPdf( - buffer: Buffer, - docId: string, -): Promise { - // NOTE: Maybe there's a way to pass the input PDF without writing a file - const inputFile = resolve(OUTPUT_DIR, docId, 'input.pdf') - await writeFile(inputFile, buffer) + console.log('Tables found: ', json.tables.length) + console.log('Markdown length: ', markdown.length) - await parseDocument(inputFile).catch(() => { - // handle failure and return error - }) + return { json, markdown } + + // TODO: Actually handle tables and take screenshots // TODO: When process is done, try reading `parsed.json` // Get the tables and use them @@ -201,5 +192,3 @@ export async function extractTablesFromJson( ) return { pages: filenames } } - -*/ diff --git a/pdf2markdown/src/test.ts b/pdf2markdown/src/test.ts new file mode 100644 index 00000000..2c1ca5f9 --- /dev/null +++ b/pdf2markdown/src/test.ts @@ -0,0 +1,38 @@ +import { readFile } from 'fs/promises' +import { resolve } from 'path' + +async function main() { + const pdf = await readFile( + resolve(import.meta.dirname, '../garbo_pdfs/astra-zeneca-2023.pdf'), + ) + + // const body = new FormData() + // body.append('pdf', pdf.toString('binary')) + + let res: Response + try { + res = await fetch('http://localhost:3000/convert', { + method: 'POST', + headers: { + 'Content-Type': 'application/pdf', + }, + body: new Blob([pdf]), + }) + } catch (error) { + console.error('Fetch failed:', error.message) + return + } + + if (!res.ok) { + const error = await res.json() + console.error('Server error:', error) + return + } + + const data = await res.text() + + console.log(data.slice(5000)) + console.log('\n\nReceived markdown length:', data.length) +} + +await main() From 97854f027342cfc74706ff05865e2bfc041700b5 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 02:29:43 +0100 Subject: [PATCH 38/66] Improve logging --- pdf2markdown/src/index.ts | 10 ++++++++-- pdf2markdown/src/lib/pdfTools.ts | 31 +++++++++++++++++++++---------- pdf2markdown/src/lib/util.ts | 9 +++++++++ 3 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 pdf2markdown/src/lib/util.ts diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index c30187cf..7597f3c9 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,6 +1,7 @@ import express, { Request, Response, raw } from 'express' import { extractJsonFromPdf } from './lib/pdfTools' +import { getFileSize } from './lib/util' // import { jsonToMarkdown } from './lib/jsonExtraction' const app = express() @@ -8,7 +9,7 @@ const port = 3000 app.post( '/convert', - raw({ type: '*/*', limit: '50mb' }), + raw({ type: 'application/pdf', limit: '50mb' }), async (req: Request, res: Response) => { try { const buffer = Buffer.isBuffer(req.body) ? req.body : null @@ -23,6 +24,11 @@ app.post( return } + console.log( + 'pdf2markdown: Parsing PDF with size', + getFileSize(Buffer.byteLength(buffer)), + ) + const parsed = await extractJsonFromPdf(buffer) // TODO: implement table extraction // IDEA: Maybe let docling save the page screenshots, because then we could remove the dependency pdf2pic and several native libs @@ -36,5 +42,5 @@ app.post( ) app.listen(port, () => { - console.log(`PDF to Markdown service running on port ${port}`) + console.log('PDF to Markdown service running on port', port) }) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 4e08afc3..ab9417a6 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -11,12 +11,12 @@ import { writeFile, readFile, mkdir } from 'fs/promises' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') -async function parseDocument(docId: string, baseDir: string) { - return new Promise((success, reject) => { +async function parseDocument(inputPDF: string, outDir: string) { + return new Promise((success, reject) => { const docParser = spawn('python', [ resolve(import.meta.dirname, '../parse_pdf.py'), - docId, - baseDir, + inputPDF, + outDir, ]) docParser.stdout.on('data', (data) => { @@ -27,11 +27,11 @@ async function parseDocument(docId: string, baseDir: string) { console.error(data.toString().trimEnd()) }) - docParser.on('exit', (exitCode) => { - if (exitCode !== 0) { - reject() + docParser.once('exit', async (code) => { + if (code === 0) { + success(undefined) } else { - success() + reject() } }) }) @@ -51,7 +51,7 @@ export async function extractJsonFromPdf( try { await parseDocument(inputPDF, outDir) } catch (e) { - throw new Error('Conversion failed!') + throw new Error('Conversion failed! ' + e) } const json = await readFile(resolve(outDir, 'parsed.json'), { @@ -62,7 +62,18 @@ export async function extractJsonFromPdf( encoding: 'utf-8', }) - console.log('Tables found: ', json.tables.length) + const uniquePages = new Set( + json.tables.map((t) => t.prov.at(0)?.page_no).filter(Number.isFinite), + ) + + console.log( + 'Found', + json.tables.length, + 'tables on', + uniquePages.size, + 'pages:', + uniquePages, + ) console.log('Markdown length: ', markdown.length) return { json, markdown } diff --git a/pdf2markdown/src/lib/util.ts b/pdf2markdown/src/lib/util.ts new file mode 100644 index 00000000..10e0c945 --- /dev/null +++ b/pdf2markdown/src/lib/util.ts @@ -0,0 +1,9 @@ +export function getFileSize(bytes: number) { + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(1024)) + const unit = sizes[i] + + if (i === 0) return `${bytes} ${unit}` + + return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${unit}` +} From a99c9c40e6541459796d96179d9aa9da4b464931 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 02:40:57 +0100 Subject: [PATCH 39/66] Use PythonShell to get better error logging --- pdf2markdown/package-lock.json | 10 ++++++++++ pdf2markdown/package.json | 5 +++-- pdf2markdown/src/lib/pdfTools.ts | 32 ++++---------------------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/pdf2markdown/package-lock.json b/pdf2markdown/package-lock.json index 958e108d..7d41c445 100644 --- a/pdf2markdown/package-lock.json +++ b/pdf2markdown/package-lock.json @@ -12,6 +12,7 @@ "express": "^5.0.1", "openai": "^4.73.1", "pdf2pic": "^3.1.3", + "python-shell": "^5.0.0", "sharp": "^0.33.5", "tsx": "^4.19.2", "zod": "^3.23.8" @@ -1974,6 +1975,15 @@ "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "license": "ISC" }, + "node_modules/python-shell": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/python-shell/-/python-shell-5.0.0.tgz", + "integrity": "sha512-RUOOOjHLhgR1MIQrCtnEqz/HJ1RMZBIN+REnpSUrfft2bXqXy69fwJASVziWExfFXsR1bCY0TznnHooNsCo0/w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", diff --git a/pdf2markdown/package.json b/pdf2markdown/package.json index bf4dd0de..e9dec7d6 100644 --- a/pdf2markdown/package.json +++ b/pdf2markdown/package.json @@ -14,14 +14,15 @@ "express": "^5.0.1", "openai": "^4.73.1", "pdf2pic": "^3.1.3", + "python-shell": "^5.0.0", "sharp": "^0.33.5", "tsx": "^4.19.2", "zod": "^3.23.8" }, "devDependencies": { "@types/express": "^5.0.0", - "typescript": "^5.7.2", - "prettier": "^3.4.2" + "prettier": "^3.4.2", + "typescript": "^5.7.2" }, "prettier": { "semi": false, diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index ab9417a6..088322ed 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -1,6 +1,6 @@ import { fromBuffer } from 'pdf2pic' import { resolve, join } from 'path' -import { spawn } from 'child_process' +import { PythonShell } from 'python-shell' import { ParsedDocument, ParsedDocumentSchema, @@ -11,32 +11,6 @@ import { writeFile, readFile, mkdir } from 'fs/promises' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') -async function parseDocument(inputPDF: string, outDir: string) { - return new Promise((success, reject) => { - const docParser = spawn('python', [ - resolve(import.meta.dirname, '../parse_pdf.py'), - inputPDF, - outDir, - ]) - - docParser.stdout.on('data', (data) => { - console.log(data.toString().trimEnd()) - }) - - docParser.stderr.on('data', (data) => { - console.error(data.toString().trimEnd()) - }) - - docParser.once('exit', async (code) => { - if (code === 0) { - success(undefined) - } else { - reject() - } - }) - }) -} - export async function extractJsonFromPdf( buffer: Buffer, ): Promise<{ json: any; markdown: string }> { @@ -49,7 +23,9 @@ export async function extractJsonFromPdf( await writeFile(inputPDF, buffer, { encoding: 'utf-8' }) try { - await parseDocument(inputPDF, outDir) + await PythonShell.run(resolve(import.meta.dirname, '../parse_pdf.py'), { + args: [inputPDF, outDir], + }) } catch (e) { throw new Error('Conversion failed! ' + e) } From 1907cc5b125896ab054f97ff9a3243e2a3ec6bfe Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 02:48:25 +0100 Subject: [PATCH 40/66] Clarify usage --- pdf2markdown/src/index.ts | 3 ++- pdf2markdown/src/test.ts | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 7597f3c9..41a719d2 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -16,7 +16,8 @@ app.post( if (!buffer) { res.status(400).json({ - error: 'Request body should be a PDF file in binary format.', + error: + 'Request body should be a PDF file in binary format. Also set header "Content-Type: application/pdf"', }) return } else if (buffer.length === 0) { diff --git a/pdf2markdown/src/test.ts b/pdf2markdown/src/test.ts index 2c1ca5f9..c4d69a1e 100644 --- a/pdf2markdown/src/test.ts +++ b/pdf2markdown/src/test.ts @@ -6,9 +6,6 @@ async function main() { resolve(import.meta.dirname, '../garbo_pdfs/astra-zeneca-2023.pdf'), ) - // const body = new FormData() - // body.append('pdf', pdf.toString('binary')) - let res: Response try { res = await fetch('http://localhost:3000/convert', { From feff2e76c178a44adcf0db9b9a01db2d68e54001 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 03:15:09 +0100 Subject: [PATCH 41/66] Make regular logs show up --- pdf2markdown/src/lib/pdfTools.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 088322ed..7adc4a5d 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -25,6 +25,7 @@ export async function extractJsonFromPdf( try { await PythonShell.run(resolve(import.meta.dirname, '../parse_pdf.py'), { args: [inputPDF, outDir], + stdio: 'inherit', }) } catch (e) { throw new Error('Conversion failed! ' + e) From 56e010b73cd510b0c28af923205ce8a556f69661 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 03:15:51 +0100 Subject: [PATCH 42/66] Log completion time for the request handler --- pdf2markdown/src/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 41a719d2..500b28c5 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,4 +1,5 @@ import express, { Request, Response, raw } from 'express' +import { performance } from 'perf_hooks' import { extractJsonFromPdf } from './lib/pdfTools' import { getFileSize } from './lib/util' @@ -12,6 +13,7 @@ app.post( raw({ type: 'application/pdf', limit: '50mb' }), async (req: Request, res: Response) => { try { + const start = performance.now() const buffer = Buffer.isBuffer(req.body) ? req.body : null if (!buffer) { @@ -35,6 +37,14 @@ app.post( // IDEA: Maybe let docling save the page screenshots, because then we could remove the dependency pdf2pic and several native libs // const markdown = await jsonToMarkdown(parsed.json, buffer) res.type('text/markdown; charset=UTF-8').send(parsed.markdown) + + console.log( + 'Finished conversion in', + ((performance.now() - start) / 1000).toLocaleString('en-GB', { + maximumFractionDigits: 2, + }), + 'sec.', + ) } catch (error) { console.error('Conversion error:', error) res.status(500).json({ error: error.message }) From 3e6300f2637b17e44aff2724628cd2465be7b611 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 03:42:20 +0100 Subject: [PATCH 43/66] Clarify types --- pdf2markdown/src/lib/docling-schema.ts | 42 ++++++++++---------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/pdf2markdown/src/lib/docling-schema.ts b/pdf2markdown/src/lib/docling-schema.ts index e2eda841..7d54aee3 100644 --- a/pdf2markdown/src/lib/docling-schema.ts +++ b/pdf2markdown/src/lib/docling-schema.ts @@ -1,20 +1,11 @@ -import * as z from 'zod' +import { z } from 'zod' export const BodyLabelSchema = z.enum(['list', 'unspecified']) -export type BodyLabel = z.infer - export const NameSchema = z.enum(['list', '_root_']) -export type Name = z.infer - export const CoordOriginSchema = z.enum(['BOTTOMLEFT']) -export type CoordOrigin = z.infer - -export const PictureLabelSchema = z.enum(['picture', 'table']) -export type PictureLabel = z.infer export const TextLabelSchema = z.enum([ 'caption', - 'footnote', 'list_item', 'page_footer', 'page_header', @@ -22,28 +13,22 @@ export const TextLabelSchema = z.enum([ 'section_header', 'text', ]) -export type TextLabel = z.infer export const MarkerSchema = z.enum(['-']) -export type Marker = z.infer - export const ParentSchema = z.object({ $ref: z.string(), }) -export type Parent = z.infer export const OriginSchema = z.object({ mimetype: z.string(), binary_hash: z.number(), filename: z.string(), }) -export type Origin = z.infer export const SizeSchema = z.object({ width: z.number(), height: z.number(), }) -export type Size = z.infer export const BboxSchema = z.object({ l: z.number(), @@ -52,14 +37,12 @@ export const BboxSchema = z.object({ b: z.number(), coord_origin: CoordOriginSchema, }) -export type Bbox = z.infer export const ProvSchema = z.object({ page_no: z.number(), bbox: BboxSchema, charspan: z.array(z.number()), }) -export type Prov = z.infer export const TextSchema = z.object({ self_ref: z.string(), @@ -73,7 +56,6 @@ export const TextSchema = z.object({ enumerated: z.boolean().optional(), marker: MarkerSchema.optional(), }) -export type Text = z.infer export const BodySchema = z.object({ self_ref: z.string(), @@ -82,13 +64,11 @@ export const BodySchema = z.object({ label: BodyLabelSchema, parent: ParentSchema.optional(), }) -export type Body = z.infer export const PageSchema = z.object({ size: SizeSchema, page_no: z.number(), }) -export type Page = z.infer export const TableCellSchema = z.object({ bbox: BboxSchema.optional(), @@ -103,7 +83,6 @@ export const TableCellSchema = z.object({ row_header: z.boolean(), row_section: z.boolean(), }) -export type TableCell = z.infer export const DataSchema = z.object({ table_cells: z.array(TableCellSchema), @@ -111,13 +90,25 @@ export const DataSchema = z.object({ num_cols: z.number(), grid: z.array(z.array(TableCellSchema)), }) -export type Data = z.infer export const PictureSchema = z.object({ self_ref: z.string(), parent: ParentSchema, children: z.array(z.any()), - label: PictureLabelSchema, + label: z.enum(['picture']), + prov: z.array(ProvSchema), + captions: z.array(ParentSchema), + references: z.array(z.any()), + footnotes: z.array(z.any()), + annotations: z.array(z.any()).optional(), + data: DataSchema.optional(), +}) + +export const TableSchema = z.object({ + self_ref: z.string(), + parent: ParentSchema, + children: z.array(z.any()), + label: z.enum(['table']), prov: z.array(ProvSchema), captions: z.array(ParentSchema), references: z.array(z.any()), @@ -125,7 +116,6 @@ export const PictureSchema = z.object({ annotations: z.array(z.any()).optional(), data: DataSchema.optional(), }) -export type Picture = z.infer export const DoclingDocumentSchema = z.object({ schema_name: z.string(), @@ -137,7 +127,7 @@ export const DoclingDocumentSchema = z.object({ groups: z.array(BodySchema), texts: z.array(TextSchema), pictures: z.array(PictureSchema), - tables: z.array(PictureSchema), + tables: z.array(TableSchema), key_value_items: z.array(z.any()), pages: z.record(z.string(), PageSchema), }) From 7ff5e6a9846e42e13ad9d3d7de9fe2bb81e728d7 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 04:09:58 +0100 Subject: [PATCH 44/66] Parse DoclingDocument and improve how unique page numbers are determined to catch them all --- pdf2markdown/src/lib/pdfTools.ts | 70 ++++++++++---------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 7adc4a5d..cfaf12bb 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -1,13 +1,10 @@ import { fromBuffer } from 'pdf2pic' import { resolve, join } from 'path' import { PythonShell } from 'python-shell' -import { - ParsedDocument, - ParsedDocumentSchema, - Table, -} from './nlm-ingestor-schema' +import { ParsedDocument, Table } from './nlm-ingestor-schema' import { jsonToTables } from './jsonExtraction' import { writeFile, readFile, mkdir } from 'fs/promises' +import { DoclingDocumentSchema } from './docling-schema' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') @@ -31,16 +28,28 @@ export async function extractJsonFromPdf( throw new Error('Conversion failed! ' + e) } - const json = await readFile(resolve(outDir, 'parsed.json'), { - encoding: 'utf-8', - }).then(JSON.parse) - - const markdown = await readFile(resolve(outDir, 'parsed.md'), { - encoding: 'utf-8', - }) + const [rawJSON, markdown] = await Promise.all([ + readFile(resolve(outDir, 'parsed.json'), { + encoding: 'utf-8', + }).then(JSON.parse), + readFile(resolve(outDir, 'parsed.md'), { + encoding: 'utf-8', + }), + ]) + + const { + success, + data: json, + error, + } = DoclingDocumentSchema.safeParse(rawJSON) + if (!success) { + console.error('Schema validation failed:', error.format()) + throw new Error('Invalid response format from Docling') + } + // Get unique page numbers, also for tables spanning multiple pages const uniquePages = new Set( - json.tables.map((t) => t.prov.at(0)?.page_no).filter(Number.isFinite), + json.tables.flatMap((t) => t.prov.map(({ page_no }) => page_no)), ) console.log( @@ -54,41 +63,6 @@ export async function extractJsonFromPdf( console.log('Markdown length: ', markdown.length) return { json, markdown } - - // TODO: Actually handle tables and take screenshots - - // TODO: When process is done, try reading `parsed.json` - // Get the tables and use them - // TODO: Later, try reading `parsed.md` when we should combine it with the document - - const body = 'TODO' - - // Validate basic response structure - if (!body?.return_dict?.result?.blocks) { - console.error('Invalid response structure:', JSON.stringify(body, null, 2)) - throw new Error('NLM Ingestor returned invalid response structure') - } - - // Check for empty document - const hasContent = body.return_dict.result.blocks.some( - (block) => block.content && block.content.trim().length > 0, - ) - - if (!hasContent) { - console.error('Document contains only empty blocks') - console.error('Raw NLM ingestor response:', JSON.stringify(body, null, 2)) - throw new Error( - 'Document appears to be empty or could not be parsed properly', - ) - } - - const result = ParsedDocumentSchema.safeParse(body) - if (!result.success) { - console.error('Schema validation failed:', result.error.format()) - throw new Error('Invalid response format from NLM Ingestor') - } - - return result.data } type Page = { From 3f09a44a777dc433544c9b3907a184a30725e964 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 04:15:23 +0100 Subject: [PATCH 45/66] Save page images with Docling --- pdf2markdown/src/parse_pdf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index f6efe889..28548f4c 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -33,6 +33,13 @@ def export_document( fp.write(conv_result.document.export_to_markdown(image_placeholder='')) _log.info(f"Saved document Markdown to: {markdown_file}") + # Save page images + for page_no, page in conv_result.document.pages.items(): + page_no = page.page_no + page_image_filename = output_dir / f"pages/page-{page_no}.png" + with page_image_filename.open("wb") as fp: + page.image.pil_image.save(fp, format="PNG") + elif conv_result.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( f"Document {conv_result.input.file} was partially converted with the following errors:" From da2083defa28fa3342c64f46d939e7b3ece4d921 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 05:02:30 +0100 Subject: [PATCH 46/66] Extract images for pages with tables using Docling --- pdf2markdown/src/parse_pdf.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 28548f4c..dfd3c8c2 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -15,12 +15,21 @@ _log = logging.getLogger('parse_pdf') +def flatten(matrix): + flat_list = [] + for row in matrix: + flat_list.extend(row) + return flat_list + def export_document( conv_result: ConversionResult, output_dir: Path, ): output_dir.mkdir(parents=True, exist_ok=True) + page_images_dir = output_dir / "pages" + page_images_dir.mkdir(parents=True, exist_ok=True) + if conv_result.status == ConversionStatus.SUCCESS: json_file = output_dir / "parsed.json" markdown_file = output_dir / "parsed.md" @@ -33,12 +42,22 @@ def export_document( fp.write(conv_result.document.export_to_markdown(image_placeholder='')) _log.info(f"Saved document Markdown to: {markdown_file}") - # Save page images - for page_no, page in conv_result.document.pages.items(): - page_no = page.page_no - page_image_filename = output_dir / f"pages/page-{page_no}.png" + unique_pages_with_tables = set( + flatten([ + [item.page_no for item in table.prov] for table in conv_result.document.tables + ]) + ) + + _log.info(f"{len(unique_pages_with_tables)} unique pages with tables: {sorted(unique_pages_with_tables)}") + + # Save images for pages with tables + for page_no in unique_pages_with_tables: + page = conv_result.document.pages[page_no] + page_image_filename = page_images_dir / f"page-{page_no}.png" with page_image_filename.open("wb") as fp: page.image.pil_image.save(fp, format="PNG") + + _log.info(f"Saved page images to {page_images_dir}") elif conv_result.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( @@ -65,6 +84,8 @@ def parse_document(input_file: Path, output_dir: Path): pipeline_options.table_structure_options.mode = TableFormerMode.FAST pipeline_options.ocr_options = TesseractOcrOptions() pipeline_options.ocr_options.lang = ["swe", "eng"] + pipeline_options.generate_page_images=True + pipeline_options.images_scale=2 doc_converter = DocumentConverter( format_options={ From 0002730f13f3933b95e24664929596d10e77944e Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 05:27:49 +0100 Subject: [PATCH 47/66] Improve type --- pdf2markdown/src/lib/pdfTools.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index cfaf12bb..d815c4ed 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -1,16 +1,17 @@ import { fromBuffer } from 'pdf2pic' import { resolve, join } from 'path' import { PythonShell } from 'python-shell' +import { writeFile, readFile, mkdir } from 'fs/promises' + import { ParsedDocument, Table } from './nlm-ingestor-schema' import { jsonToTables } from './jsonExtraction' -import { writeFile, readFile, mkdir } from 'fs/promises' -import { DoclingDocumentSchema } from './docling-schema' +import { DoclingDocument, DoclingDocumentSchema } from './docling-schema' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') export async function extractJsonFromPdf( buffer: Buffer, -): Promise<{ json: any; markdown: string }> { +): Promise<{ json: DoclingDocument; markdown: string }> { const docId = crypto.randomUUID() const outDir = resolve(OUTPUT_DIR, docId) await mkdir(outDir, { recursive: true }) From 85a6046ecb3c65f7911474e64c68211c2513b781 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 05:55:22 +0100 Subject: [PATCH 48/66] Rename --- pdf2markdown/src/index.ts | 4 ++-- pdf2markdown/src/lib/pdfTools.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 500b28c5..5ae5f0ac 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,7 +1,7 @@ import express, { Request, Response, raw } from 'express' import { performance } from 'perf_hooks' -import { extractJsonFromPdf } from './lib/pdfTools' +import { convertPDF } from './lib/pdfTools' import { getFileSize } from './lib/util' // import { jsonToMarkdown } from './lib/jsonExtraction' @@ -32,7 +32,7 @@ app.post( getFileSize(Buffer.byteLength(buffer)), ) - const parsed = await extractJsonFromPdf(buffer) + const parsed = await convertPDF(buffer) // TODO: implement table extraction // IDEA: Maybe let docling save the page screenshots, because then we could remove the dependency pdf2pic and several native libs // const markdown = await jsonToMarkdown(parsed.json, buffer) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index d815c4ed..63d8cb5b 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -9,7 +9,7 @@ import { DoclingDocument, DoclingDocumentSchema } from './docling-schema' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') -export async function extractJsonFromPdf( +export async function convertPDF( buffer: Buffer, ): Promise<{ json: DoclingDocument; markdown: string }> { const docId = crypto.randomUUID() From fe43fd22583da8110f38753596b42788ff9769aa Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 06:03:01 +0100 Subject: [PATCH 49/66] Experiment with preserving both stdout and stderr from the child process --- pdf2markdown/src/lib/pdfTools.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 63d8cb5b..994f1e1a 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -21,9 +21,32 @@ export async function convertPDF( await writeFile(inputPDF, buffer, { encoding: 'utf-8' }) try { + // TODO: Figure out how to get both the stdout and stderr from the child process + + // await new Promise((success, reject) => { + // const shell = new PythonShell( + // resolve(import.meta.dirname, '../parse_pdf.py'), + // { + // args: [inputPDF, outDir], + // stdio: 'inherit', + // }, + // ) + + // shell.childProcess.stdout?.on?.('data', (chunk: any) => { + // console.log(chunk.toString()) + // }) + + // shell.once('pythonError', (err) => { + // reject(err) + // }) + + // shell.once('close', () => { + // success() + // }) + // }) await PythonShell.run(resolve(import.meta.dirname, '../parse_pdf.py'), { args: [inputPDF, outDir], - stdio: 'inherit', + stdio: ['pipe', 'inherit', 'pipe', 'pipe'], }) } catch (e) { throw new Error('Conversion failed! ' + e) From 5a3ea87bec609e1dd6f19343309dae492c39a22d Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 06:35:36 +0100 Subject: [PATCH 50/66] Start refactroing JSON processing to use Docling format --- pdf2markdown/src/index.ts | 10 +++- pdf2markdown/src/lib/docling-schema.ts | 1 + pdf2markdown/src/lib/pdfTools.ts | 64 ++++++-------------------- pdf2markdown/src/parse_pdf.py | 1 + 4 files changed, 25 insertions(+), 51 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 5ae5f0ac..73ff44f9 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -32,10 +32,18 @@ app.post( getFileSize(Buffer.byteLength(buffer)), ) - const parsed = await convertPDF(buffer) + const docId = crypto.randomUUID() + const parsed = await convertPDF(buffer, docId) // TODO: implement table extraction // IDEA: Maybe let docling save the page screenshots, because then we could remove the dependency pdf2pic and several native libs // const markdown = await jsonToMarkdown(parsed.json, buffer) + + // use vision API for tables + // return the Docling parsed markdown, and combine with the more detailed tables + // maybe: remove tmp files after processing completed successfully to save space + // Or maybe store a timestamp in the first part of the directory name - and then check if it has passed more than 12h since the report was parsed, then remove it when receiving the next incoming request + // trigger indexMarkdown after receiving the parsed report back in the garbo container. + res.type('text/markdown; charset=UTF-8').send(parsed.markdown) console.log( diff --git a/pdf2markdown/src/lib/docling-schema.ts b/pdf2markdown/src/lib/docling-schema.ts index 7d54aee3..f8b9f18f 100644 --- a/pdf2markdown/src/lib/docling-schema.ts +++ b/pdf2markdown/src/lib/docling-schema.ts @@ -83,6 +83,7 @@ export const TableCellSchema = z.object({ row_header: z.boolean(), row_section: z.boolean(), }) +export type Table = z.infer export const DataSchema = z.object({ table_cells: z.array(TableCellSchema), diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 994f1e1a..10030c9a 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -1,18 +1,16 @@ -import { fromBuffer } from 'pdf2pic' import { resolve, join } from 'path' import { PythonShell } from 'python-shell' import { writeFile, readFile, mkdir } from 'fs/promises' -import { ParsedDocument, Table } from './nlm-ingestor-schema' import { jsonToTables } from './jsonExtraction' -import { DoclingDocument, DoclingDocumentSchema } from './docling-schema' +import { DoclingDocument, DoclingDocumentSchema, Table } from './docling-schema' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') export async function convertPDF( buffer: Buffer, + docId: string, ): Promise<{ json: DoclingDocument; markdown: string }> { - const docId = crypto.randomUUID() const outDir = resolve(OUTPUT_DIR, docId) await mkdir(outDir, { recursive: true }) const inputPDF = resolve(outDir, 'input.pdf') @@ -93,18 +91,21 @@ type Page = { pageIndex: number pageWidth: number pageHeight: number - tables: any[] + tables: Table[] } export function findRelevantTablesGroupedOnPages( - json: ParsedDocument, + json: DoclingDocument, searchTerms: string[], ): Page[] { - const tables = jsonToTables(json).filter(({ content }) => + // HACK: JSON.stringify() the page with the table, and then check if it includes any relevant search terms + // Actually, we need to evaluate each node and its value + const tables = json.tables.filter((table) => { + const tableContent = JSON.stringify(table) searchTerms.some((term) => content.toLowerCase().includes(term.toLowerCase()), - ), - ) + ) + }) return tables.reduce((acc: Page[], table: Table) => { const [pageWidth, pageHeight] = json.return_dict.page_dim const pageIndex = table.page_idx @@ -124,55 +125,18 @@ export function findRelevantTablesGroupedOnPages( } export async function extractTablesFromJson( - pdf: Buffer, - json: ParsedDocument, - outputDir: string, + json: DoclingDocument, + outDir: string, searchTerms: string[], ): Promise<{ pages: { pageIndex: number; filename: string }[] }> { - const pdfConverter = (height: number, width: number) => { - return fromBuffer(pdf, { - density: 600, - format: 'png', - width, - height, - preserveAspectRatio: true, - }) - } - const pages = Object.values( findRelevantTablesGroupedOnPages(json, searchTerms), ) - // TODO: generate reportId when starting the request - // create a directory for the reportId - // save the incoming PDF file to the tmp directory - // read the PDF file from python and process it - // write parsed report output to a json file in the reportId directory - // also write parsed report to a markdown file in the reportId directory - // save images in the reportId directory - // use vision API for tables - // return the Docling parsed markdown, and combine with the more detailed tables - // maybe: remove tmp files after processing completed successfully to save space - // Or maybe store a timestamp in the first part of the directory name - and then check if it has passed more than 12h since the report was parsed, then remove it when receiving the next incoming request - // trigger indexMarkdown after receiving the parsed report back in the garbo container. - const reportId = crypto.randomUUID() const filenames = await Promise.all( pages.map(async ({ pageIndex, pageHeight, pageWidth }) => { const pageNumber = pageIndex + 1 - const pageScreenshotPath = join( - outputDir, - `${reportId}-page-${pageNumber}.png`, - ) - const convert = pdfConverter(pageHeight * 2, pageWidth * 2) - const result = await convert(pageNumber, { responseType: 'buffer' }) - - if (!result.buffer) { - throw new Error( - `Failed to convert pageNumber ${pageNumber} to a buffer\n` + - JSON.stringify(result, null, 2), - ) - } - - await writeFile(pageScreenshotPath, result.buffer) + const pageScreenshotPath = join(outDir, `/pages/page-${pageNumber}.png`) + return { pageIndex, filename: pageScreenshotPath } }), ) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index dfd3c8c2..eb46449b 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -35,6 +35,7 @@ def export_document( markdown_file = output_dir / "parsed.md" with json_file.open("w", encoding="utf-8") as fp: + # TODO: Remove the embedded image files from the JSON file json.dump(conv_result.document.export_to_dict(), fp, ensure_ascii=False) _log.info(f"Saved document JSON to: {json_file}") From f20e23e819148fc21358aa235e78c055f4ce73fc Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 06:59:37 +0100 Subject: [PATCH 51/66] Group all tables based on pages until we can filter by relevant search terms --- pdf2markdown/src/lib/jsonExtraction.ts | 154 +++++++++++++++---------- pdf2markdown/src/lib/pdfTools.ts | 68 ++++++----- 2 files changed, 129 insertions(+), 93 deletions(-) diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 75dc6c0a..9bb42ccc 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -1,4 +1,9 @@ -import { Block, ParsedDocument, Table, ParsedDocumentSchema } from './nlm-ingestor-schema' +import { + Block, + ParsedDocument, + Table, + ParsedDocumentSchema, +} from './nlm-ingestor-schema' import { fromBuffer } from 'pdf2pic' import { openai } from './openai' @@ -12,9 +17,10 @@ export async function extractTextViaVisionAPI( }: { buffer: Buffer }, - context: string + context: string, ) { const result = await openai.chat.completions.create({ + // TODO: Maybe use 'gpt-4o-mini' instead? model: 'gpt-4-vision-preview', messages: [ { @@ -61,13 +67,15 @@ function parseTableBlock(block: Table): string { } // Convert table rows to markdown - const rows = block.rows.map(row => { - return row.map(cell => cell.content || '').join(' | ') + const rows = block.rows.map((row) => { + return row.map((cell) => cell.content || '').join(' | ') }) // Add header separator after first row if (rows.length > 0) { - const headerSeparator = Array(rows[0].split('|').length).fill('---').join(' | ') + const headerSeparator = Array(rows[0].split('|').length) + .fill('---') + .join(' | ') rows.splice(1, 0, headerSeparator) } @@ -92,7 +100,7 @@ function parseListItemBlock(block: ListItem): string { export function jsonToTables(json: ParsedDocument): Table[] { return json.return_dict.result.blocks.filter( - (block): block is Table => 'rows' in block + (block): block is Table => 'rows' in block, ) } @@ -109,20 +117,20 @@ function parseBlock(block: Block): string { export async function jsonToMarkdown( json: ParsedDocument, - pdf: Buffer + pdf: Buffer, ): Promise { try { // Validate input const result = ParsedDocumentSchema.parse(json) const blocks = result.return_dict.result.blocks - + if (!blocks || blocks.length === 0) { console.error('No blocks found in document') return 'No content found in document' } // Check if all blocks are empty - const hasNonEmptyBlock = blocks.some(block => { + const hasNonEmptyBlock = blocks.some((block) => { if ('content' in block && block.content) { return block.content.trim().length > 0 } @@ -134,12 +142,20 @@ export async function jsonToMarkdown( if (!hasNonEmptyBlock) { console.error('All blocks are empty') - console.error('Block details:', blocks.map(block => ({ - type: 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph'), - hasContent: Boolean(block.content), - contentLength: block.content?.length || 0, - hasRows: 'rows' in block ? Boolean(block.rows?.length) : 'N/A' - }))) + console.error( + 'Block details:', + blocks.map((block) => ({ + type: + 'rows' in block + ? 'Table' + : 'level' in block + ? 'Header' + : 'Paragraph', + hasContent: Boolean(block.content), + contentLength: block.content?.length || 0, + hasRows: 'rows' in block ? Boolean(block.rows?.length) : 'N/A', + })), + ) return 'Document contains only empty blocks. The PDF may be corrupted or protected.' } @@ -147,7 +163,10 @@ export async function jsonToMarkdown( console.log(`Total blocks: ${blocks.length}`) blocks.forEach((block, index) => { console.log(`\nBlock ${index}:`) - console.log('- Type:', 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph')) + console.log( + '- Type:', + 'rows' in block ? 'Table' : 'level' in block ? 'Header' : 'Paragraph', + ) console.log('- Content:', block.content) if ('rows' in block) { console.log('- Table rows:', block.rows?.length || 0) @@ -156,67 +175,74 @@ export async function jsonToMarkdown( console.log('- Header level:', block.level) } }) - const [pageWidth, pageHeight] = json.return_dict.page_dim + const [pageWidth, pageHeight] = json.return_dict.page_dim - const markdownBlocks = await Promise.all( - blocks.map(async (block: Block) => { - console.log(`Processing block of type: ${ - 'rows' in block ? 'Table' : - 'level' in block ? 'Header' : - 'Paragraph' - }`) + const markdownBlocks = await Promise.all( + blocks.map(async (block: Block) => { + console.log( + `Processing block of type: ${ + 'rows' in block + ? 'Table' + : 'level' in block + ? 'Header' + : 'Paragraph' + }`, + ) - if ('rows' in block) { - // For tables, try direct parsing first - const tableMarkdown = parseTableBlock(block) - if (tableMarkdown && tableMarkdown.trim()) { - return tableMarkdown - } + if ('rows' in block) { + // For tables, try direct parsing first + const tableMarkdown = parseTableBlock(block) + if (tableMarkdown && tableMarkdown.trim()) { + return tableMarkdown + } - // If direct parsing yields no content, fall back to Vision API - const pageNumber = block.page_idx + 1 - const pdfConverter = fromBuffer(pdf, { - density: 600, - format: 'png', - width: pageWidth * 2, - height: pageHeight * 2, - preserveAspectRatio: true, - }) - - const result = await pdfConverter(pageNumber, { - responseType: 'buffer', - }) - if (!result.buffer) { - throw new Error(`Failed to convert page ${pageNumber} to image`) - } + // TODO: Use already extracted page image instead - return extractTextViaVisionAPI( - { buffer: result.buffer }, - block.content || '' - ) - } + // If direct parsing yields no content, fall back to Vision API + const pageNumber = block.page_idx + 1 + const pdfConverter = fromBuffer(pdf, { + density: 600, + format: 'png', + width: pageWidth * 2, + height: pageHeight * 2, + preserveAspectRatio: true, + }) - // For non-table blocks, use appropriate parser - const parsedContent = parseBlock(block) - if (parsedContent) { - return parsedContent - } + const result = await pdfConverter(pageNumber, { + responseType: 'buffer', + }) + if (!result.buffer) { + throw new Error(`Failed to convert page ${pageNumber} to image`) + } - console.log('Skipping empty block:', block) - return null - }) - ) + return extractTextViaVisionAPI( + { buffer: result.buffer }, + block.content || '', + ) + } + + // For non-table blocks, use appropriate parser + const parsedContent = parseBlock(block) + if (parsedContent) { + return parsedContent + } + + console.log('Skipping empty block:', block) + return null + }), + ) const markdown = (await Promise.all(markdownBlocks)) - .filter(block => block !== null && block !== '') + .filter((block) => block !== null && block !== '') .join('\n\n') if (!markdown.trim()) { console.error('No content was extracted from blocks') - const blockSummary = blocks.map(block => ({ - type: 'rows' in block ? 'Table' : ('level' in block ? 'Header' : 'Paragraph'), + const blockSummary = blocks.map((block) => ({ + type: + 'rows' in block ? 'Table' : 'level' in block ? 'Header' : 'Paragraph', hasContent: Boolean(block.content), - contentLength: block.content?.length || 0 + contentLength: block.content?.length || 0, })) console.error('Block summary:', JSON.stringify(blockSummary, null, 2)) return 'No content could be extracted from document. Check server logs for details.' diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index 10030c9a..d8bb0449 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -88,9 +88,9 @@ export async function convertPDF( } type Page = { - pageIndex: number - pageWidth: number - pageHeight: number + pageNumber: number + width: number + height: number tables: Table[] } @@ -100,44 +100,54 @@ export function findRelevantTablesGroupedOnPages( ): Page[] { // HACK: JSON.stringify() the page with the table, and then check if it includes any relevant search terms // Actually, we need to evaluate each node and its value - const tables = json.tables.filter((table) => { - const tableContent = JSON.stringify(table) - searchTerms.some((term) => - content.toLowerCase().includes(term.toLowerCase()), - ) - }) - return tables.reduce((acc: Page[], table: Table) => { - const [pageWidth, pageHeight] = json.return_dict.page_dim - const pageIndex = table.page_idx - const page = acc.find((p) => p.pageIndex === pageIndex) - if (page) { - page.tables.push(table) - } else { - acc.push({ - pageIndex, - tables: [table], - pageWidth, - pageHeight, - }) - } - return acc - }, []) + // Example implementation for evaluating the strings and replacing nodes with referenced values: https://stackoverflow.com/a/42398875 + // This needs to happen before the zod parsing though + // const tables = json.tables.filter((table) => { + // const tableContent = JSON.stringify(table) + // searchTerms.some((term) => + // content.toLowerCase().includes(term.toLowerCase()), + // ) + // }) + + // NOTE: Until content filtering is available, we need to process all tables + return Object.values( + json.tables.reduce((acc: Record, table: Table) => { + for (const n of table.prov.map((item) => item.page_no)) { + const { + page_no, + size: { width, height }, + } = json.pages[n] + + if (acc[page_no]) { + acc[page_no].tables.push(table) + } else { + acc[page_no] = { + pageNumber: page_no, + tables: [table], + width, + height, + } + } + } + + return acc + }, {}), + ) } export async function extractTablesFromJson( json: DoclingDocument, outDir: string, searchTerms: string[], -): Promise<{ pages: { pageIndex: number; filename: string }[] }> { +): Promise<{ pages: { pageNumber: number; filename: string }[] }> { const pages = Object.values( findRelevantTablesGroupedOnPages(json, searchTerms), ) const filenames = await Promise.all( - pages.map(async ({ pageIndex, pageHeight, pageWidth }) => { - const pageNumber = pageIndex + 1 + pages.map(async ({ pageNumber }) => { const pageScreenshotPath = join(outDir, `/pages/page-${pageNumber}.png`) - return { pageIndex, filename: pageScreenshotPath } + return { pageNumber, filename: pageScreenshotPath } }), ) return { pages: filenames } From 01f27670913b5ea906111a8785e76106edd450f3 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:22:49 +0100 Subject: [PATCH 52/66] Automatically load a specific python version in the project --- pdf2markdown/.python-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 pdf2markdown/.python-version diff --git a/pdf2markdown/.python-version b/pdf2markdown/.python-version new file mode 100644 index 00000000..e4fba218 --- /dev/null +++ b/pdf2markdown/.python-version @@ -0,0 +1 @@ +3.12 From 816c21bf64bc61b90e9e5ba7efacdd889db34d6b Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:35:00 +0100 Subject: [PATCH 53/66] Parse images exported by Docling --- pdf2markdown/src/lib/docling-schema.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pdf2markdown/src/lib/docling-schema.ts b/pdf2markdown/src/lib/docling-schema.ts index f8b9f18f..303a05da 100644 --- a/pdf2markdown/src/lib/docling-schema.ts +++ b/pdf2markdown/src/lib/docling-schema.ts @@ -65,9 +65,17 @@ export const BodySchema = z.object({ parent: ParentSchema.optional(), }) +export const ImageSchema = z.object({ + mimetype: z.enum(['image/png']), + dpi: z.number(), + size: SizeSchema, + uri: z.string(), +}) + export const PageSchema = z.object({ size: SizeSchema, page_no: z.number(), + image: ImageSchema.optional(), }) export const TableCellSchema = z.object({ From eb648e2f13da9c4b06caf696ab040c218c17be47 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:51:24 +0100 Subject: [PATCH 54/66] Verify that all images match the base64 encoded representations --- pdf2markdown/src/verify-images.ts | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 pdf2markdown/src/verify-images.ts diff --git a/pdf2markdown/src/verify-images.ts b/pdf2markdown/src/verify-images.ts new file mode 100644 index 00000000..3bad877c --- /dev/null +++ b/pdf2markdown/src/verify-images.ts @@ -0,0 +1,39 @@ +import { resolve } from 'path' +import { glob, readFile } from 'fs/promises' + +import { DoclingDocumentSchema } from './lib/docling-schema' +import assert from 'assert' + +const docId = '5ee1f2b6-a86a-4f26-86bc-2223c937528b' +const outDir = resolve(`/tmp/pdf2markdown/${docId}`) + +const bufferToBase64 = (buffer: Buffer) => { + return 'data:image/png;base64,' + buffer.toString('base64') +} + +const rawJSON = await readFile(resolve(outDir, 'parsed.json'), { + encoding: 'utf-8', +}).then(JSON.parse) + +const json = DoclingDocumentSchema.parse(rawJSON) + +for await (const imagePath of glob(resolve(outDir, 'pages/*.png'))) { + const image = await readFile(imagePath).then(bufferToBase64) + const pageNumber = imagePath.match(/(\d+)\.png$/)?.[1] + if (pageNumber === undefined) { + throw new Error('Unable to match pageNumber for path: ' + imagePath) + } + + const page = json.pages[pageNumber] + + if (!page?.image) { + throw new Error(`Page ${pageNumber} is missing image`) + } + + assert( + image === json.pages[pageNumber].image?.uri, + 'Base64 encoded PNG images should match', + ) +} + +console.log('All images match!') From a7c126beb19b430081a24b3fae63b075fbf6137a Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:00:49 +0100 Subject: [PATCH 55/66] Increase image quality --- pdf2markdown/src/parse_pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index eb46449b..ef42d921 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -86,7 +86,7 @@ def parse_document(input_file: Path, output_dir: Path): pipeline_options.ocr_options = TesseractOcrOptions() pipeline_options.ocr_options.lang = ["swe", "eng"] pipeline_options.generate_page_images=True - pipeline_options.images_scale=2 + pipeline_options.images_scale=3 doc_converter = DocumentConverter( format_options={ From cb3d076037d804fb9a3c89d0ed2b06b66104ad92 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 16:45:48 +0100 Subject: [PATCH 56/66] Only keep images for wanted pages --- pdf2markdown/src/test.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pdf2markdown/src/test.py diff --git a/pdf2markdown/src/test.py b/pdf2markdown/src/test.py new file mode 100644 index 00000000..242738ab --- /dev/null +++ b/pdf2markdown/src/test.py @@ -0,0 +1,26 @@ +import json +from pathlib import Path + +doc_id = '5ee1f2b6-a86a-4f26-86bc-2223c937528b' +out_dir = Path(f"/tmp/pdf2markdown/{doc_id}") + +def without_keys(d: dict, keys: set): + return {x: d[x] for x in d if x not in keys} + +def main(): + with (out_dir / 'parsed.json').open("r", encoding="utf-8") as fp: + doc: dict = json.load(fp) + + wanted_page_nums = {2, 8, 31, 32, 39, 40} + print(sorted(wanted_page_nums)) + updated_pages = {} + + for page_no, page in doc['pages'].items(): + updated_pages[page_no] = page if int(page_no) in wanted_page_nums else without_keys(page, {'image'}) + + doc['pages'] = updated_pages + + with (out_dir / 'filtered_images.json').open("w", encoding="utf-8") as fp: + json.dump(doc, fp) + +main() \ No newline at end of file From 1f9e972cc362b237e043aedb2a2415f0cbc2a530 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 17:14:25 +0100 Subject: [PATCH 57/66] Save Base64-encoded images only for pages with tables. This greatly simplifies the following steps, and reduces the tmp storage used for each report --- pdf2markdown/src/parse_pdf.py | 43 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index ef42d921..e924f0d1 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -6,11 +6,10 @@ from io import BytesIO from pathlib import Path -from docling.datamodel.base_models import ConversionStatus -from docling.datamodel.document import ConversionResult -from docling.datamodel.base_models import InputFormat +from docling.datamodel.document import ConversionResult, DoclingDocument +from docling.datamodel.base_models import InputFormat, ConversionStatus from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode -from docling.document_converter import DocumentConverter, PdfFormatOption, DocumentStream, _DocumentConversionInput +from docling.document_converter import DocumentConverter, PdfFormatOption, DocumentStream from docling.models.tesseract_ocr_model import TesseractOcrOptions _log = logging.getLogger('parse_pdf') @@ -21,23 +20,18 @@ def flatten(matrix): flat_list.extend(row) return flat_list +def without_keys(d: dict, keys: set): + return {x: d[x] for x in d if x not in keys} + def export_document( conv_result: ConversionResult, output_dir: Path, ): output_dir.mkdir(parents=True, exist_ok=True) - page_images_dir = output_dir / "pages" - page_images_dir.mkdir(parents=True, exist_ok=True) - if conv_result.status == ConversionStatus.SUCCESS: - json_file = output_dir / "parsed.json" markdown_file = output_dir / "parsed.md" - - with json_file.open("w", encoding="utf-8") as fp: - # TODO: Remove the embedded image files from the JSON file - json.dump(conv_result.document.export_to_dict(), fp, ensure_ascii=False) - _log.info(f"Saved document JSON to: {json_file}") + json_file = output_dir / "parsed.json" with markdown_file.open("w", encoding="utf-8") as fp: fp.write(conv_result.document.export_to_markdown(image_placeholder='')) @@ -49,16 +43,21 @@ def export_document( ]) ) - _log.info(f"{len(unique_pages_with_tables)} unique pages with tables: {sorted(unique_pages_with_tables)}") + _log.info(f"Found {len(unique_pages_with_tables)} unique pages with tables: {sorted(unique_pages_with_tables)}") + + doc_export: DoclingDocument = conv_result.document.export_to_dict() + updated_pages = {} + + # Page screenshots are already part of the `doc_export` + # However, we only want to keep page images where tables were found + for page_no, page in doc_export['pages'].items(): + updated_pages[page_no] = page if int(page_no) in unique_pages_with_tables else without_keys(page, {'image'}) - # Save images for pages with tables - for page_no in unique_pages_with_tables: - page = conv_result.document.pages[page_no] - page_image_filename = page_images_dir / f"page-{page_no}.png" - with page_image_filename.open("wb") as fp: - page.image.pil_image.save(fp, format="PNG") - - _log.info(f"Saved page images to {page_images_dir}") + doc_export['pages'] = updated_pages + + with json_file.open("w", encoding="utf-8") as fp: + json.dump(doc_export, fp, ensure_ascii=False) + _log.info(f"Saved document JSON (including Base64-encoded page images) to: {json_file}") elif conv_result.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( From fb48ff6a129d5899c0fb7744e87b3738839cdbc8 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 17:15:02 +0100 Subject: [PATCH 58/66] Format Python code using the Black opinionated formatter --- pdf2markdown/src/parse_pdf.py | 58 ++++++++++++++++++++++++----------- pdf2markdown/src/test.py | 21 ++++++++----- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index e924f0d1..bf91e9b7 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -9,10 +9,15 @@ from docling.datamodel.document import ConversionResult, DoclingDocument from docling.datamodel.base_models import InputFormat, ConversionStatus from docling.datamodel.pipeline_options import PdfPipelineOptions, TableFormerMode -from docling.document_converter import DocumentConverter, PdfFormatOption, DocumentStream +from docling.document_converter import ( + DocumentConverter, + PdfFormatOption, + DocumentStream, +) from docling.models.tesseract_ocr_model import TesseractOcrOptions -_log = logging.getLogger('parse_pdf') +_log = logging.getLogger("parse_pdf") + def flatten(matrix): flat_list = [] @@ -20,9 +25,11 @@ def flatten(matrix): flat_list.extend(row) return flat_list + def without_keys(d: dict, keys: set): return {x: d[x] for x in d if x not in keys} + def export_document( conv_result: ConversionResult, output_dir: Path, @@ -34,30 +41,41 @@ def export_document( json_file = output_dir / "parsed.json" with markdown_file.open("w", encoding="utf-8") as fp: - fp.write(conv_result.document.export_to_markdown(image_placeholder='')) + fp.write(conv_result.document.export_to_markdown(image_placeholder="")) _log.info(f"Saved document Markdown to: {markdown_file}") unique_pages_with_tables = set( - flatten([ - [item.page_no for item in table.prov] for table in conv_result.document.tables - ]) + flatten( + [ + [item.page_no for item in table.prov] + for table in conv_result.document.tables + ] + ) ) - _log.info(f"Found {len(unique_pages_with_tables)} unique pages with tables: {sorted(unique_pages_with_tables)}") + _log.info( + f"Found {len(unique_pages_with_tables)} unique pages with tables: {sorted(unique_pages_with_tables)}" + ) doc_export: DoclingDocument = conv_result.document.export_to_dict() updated_pages = {} # Page screenshots are already part of the `doc_export` # However, we only want to keep page images where tables were found - for page_no, page in doc_export['pages'].items(): - updated_pages[page_no] = page if int(page_no) in unique_pages_with_tables else without_keys(page, {'image'}) + for page_no, page in doc_export["pages"].items(): + updated_pages[page_no] = ( + page + if int(page_no) in unique_pages_with_tables + else without_keys(page, {"image"}) + ) - doc_export['pages'] = updated_pages + doc_export["pages"] = updated_pages with json_file.open("w", encoding="utf-8") as fp: json.dump(doc_export, fp, ensure_ascii=False) - _log.info(f"Saved document JSON (including Base64-encoded page images) to: {json_file}") + _log.info( + f"Saved document JSON (including Base64-encoded page images) to: {json_file}" + ) elif conv_result.status == ConversionStatus.PARTIAL_SUCCESS: _log.info( @@ -68,6 +86,7 @@ def export_document( else: _log.info(f"Document {conv_result.input.file} failed to convert.") + def parse_document(input_file: Path, output_dir: Path): if not os.path.exists(input_file): raise Exception(f"Input PDF does not exist: {input_file}") @@ -84,8 +103,8 @@ def parse_document(input_file: Path, output_dir: Path): pipeline_options.table_structure_options.mode = TableFormerMode.FAST pipeline_options.ocr_options = TesseractOcrOptions() pipeline_options.ocr_options.lang = ["swe", "eng"] - pipeline_options.generate_page_images=True - pipeline_options.images_scale=3 + pipeline_options.generate_page_images = True + pipeline_options.images_scale = 3 doc_converter = DocumentConverter( format_options={ @@ -100,16 +119,19 @@ def parse_document(input_file: Path, output_dir: Path): export_document(conv_result, output_dir) - def main(): logging.basicConfig(level=logging.INFO) - arg_parser = ArgumentParser(prog="parse_pdf", description='Parse a PDF') - arg_parser.add_argument('inputPDF', help="Path to the input PDF document") - arg_parser.add_argument('outDir', help="Path to the output directory for results to the current document") + arg_parser = ArgumentParser(prog="parse_pdf", description="Parse a PDF") + arg_parser.add_argument("inputPDF", help="Path to the input PDF document") + arg_parser.add_argument( + "outDir", + help="Path to the output directory for results to the current document", + ) parsed_args = arg_parser.parse_args(sys.argv[1:]) parse_document(Path(parsed_args.inputPDF), Path(parsed_args.outDir)) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pdf2markdown/src/test.py b/pdf2markdown/src/test.py index 242738ab..71ef5150 100644 --- a/pdf2markdown/src/test.py +++ b/pdf2markdown/src/test.py @@ -1,26 +1,31 @@ import json from pathlib import Path -doc_id = '5ee1f2b6-a86a-4f26-86bc-2223c937528b' +doc_id = "5ee1f2b6-a86a-4f26-86bc-2223c937528b" out_dir = Path(f"/tmp/pdf2markdown/{doc_id}") + def without_keys(d: dict, keys: set): return {x: d[x] for x in d if x not in keys} - + + def main(): - with (out_dir / 'parsed.json').open("r", encoding="utf-8") as fp: + with (out_dir / "parsed.json").open("r", encoding="utf-8") as fp: doc: dict = json.load(fp) wanted_page_nums = {2, 8, 31, 32, 39, 40} print(sorted(wanted_page_nums)) updated_pages = {} - for page_no, page in doc['pages'].items(): - updated_pages[page_no] = page if int(page_no) in wanted_page_nums else without_keys(page, {'image'}) + for page_no, page in doc["pages"].items(): + updated_pages[page_no] = ( + page if int(page_no) in wanted_page_nums else without_keys(page, {"image"}) + ) - doc['pages'] = updated_pages + doc["pages"] = updated_pages - with (out_dir / 'filtered_images.json').open("w", encoding="utf-8") as fp: + with (out_dir / "filtered_images.json").open("w", encoding="utf-8") as fp: json.dump(doc, fp) -main() \ No newline at end of file + +main() From 106fdcba1faf16192a7165191cacfe41fdd19b2b Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 18:55:41 +0100 Subject: [PATCH 59/66] WIP: Refactor table extraction --- pdf2markdown/src/index.ts | 12 ++--- pdf2markdown/src/lib/docling-schema.ts | 1 + pdf2markdown/src/lib/jsonExtraction.ts | 27 +++++----- pdf2markdown/src/lib/pdfTools.ts | 72 ++++++++++++++++++++------ 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 73ff44f9..1d60bba4 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -1,9 +1,8 @@ import express, { Request, Response, raw } from 'express' import { performance } from 'perf_hooks' -import { convertPDF } from './lib/pdfTools' +import { convertPDF, extractTablesWithVisionAPI } from './lib/pdfTools' import { getFileSize } from './lib/util' -// import { jsonToMarkdown } from './lib/jsonExtraction' const app = express() const port = 3000 @@ -34,17 +33,12 @@ app.post( const docId = crypto.randomUUID() const parsed = await convertPDF(buffer, docId) - // TODO: implement table extraction - // IDEA: Maybe let docling save the page screenshots, because then we could remove the dependency pdf2pic and several native libs - // const markdown = await jsonToMarkdown(parsed.json, buffer) - // use vision API for tables - // return the Docling parsed markdown, and combine with the more detailed tables + const { markdown } = await extractTablesWithVisionAPI(parsed) // maybe: remove tmp files after processing completed successfully to save space - // Or maybe store a timestamp in the first part of the directory name - and then check if it has passed more than 12h since the report was parsed, then remove it when receiving the next incoming request // trigger indexMarkdown after receiving the parsed report back in the garbo container. - res.type('text/markdown; charset=UTF-8').send(parsed.markdown) + res.type('text/markdown; charset=UTF-8').send(markdown) console.log( 'Finished conversion in', diff --git a/pdf2markdown/src/lib/docling-schema.ts b/pdf2markdown/src/lib/docling-schema.ts index 303a05da..3f939d49 100644 --- a/pdf2markdown/src/lib/docling-schema.ts +++ b/pdf2markdown/src/lib/docling-schema.ts @@ -71,6 +71,7 @@ export const ImageSchema = z.object({ size: SizeSchema, uri: z.string(), }) +export type Image = z.infer export const PageSchema = z.object({ size: SizeSchema, diff --git a/pdf2markdown/src/lib/jsonExtraction.ts b/pdf2markdown/src/lib/jsonExtraction.ts index 9bb42ccc..66eb5ba7 100644 --- a/pdf2markdown/src/lib/jsonExtraction.ts +++ b/pdf2markdown/src/lib/jsonExtraction.ts @@ -7,21 +7,12 @@ import { import { fromBuffer } from 'pdf2pic' import { openai } from './openai' -const bufferToBase64 = (buffer: Buffer) => { - return 'data:image/png;base64,' + buffer.toString('base64') -} - export async function extractTextViaVisionAPI( - { - buffer, - }: { - buffer: Buffer - }, + { imageBase64 }: { imageBase64: string }, context: string, ) { const result = await openai.chat.completions.create({ - // TODO: Maybe use 'gpt-4o-mini' instead? - model: 'gpt-4-vision-preview', + model: 'gpt-4o-mini', messages: [ { role: 'system', @@ -30,7 +21,7 @@ export async function extractTextViaVisionAPI( }, { role: 'user', - content: `I have a PDF with couple of tables related to a company's CO2 emissions. Can you extract the text from screenshot. I will send you the screenshot extract the header and table contents and ignore the surrounding text if they are not related to the tables/graphs (such as header, description, footnotes or disclaimers). Use Markdown format for the table(s), only reply with markdown. OK?`, + content: `I have a PDF with couple of tables related to a company's CO2 emissions. Can you extract the text from screenshot? I will send you the screenshot extract the header and table contents and ignore the surrounding text if they are not related to the tables/graphs (such as header, description, footnotes or disclaimers). Use Markdown format for the table(s), only reply with markdown. OK?`, }, { role: 'assistant', @@ -40,6 +31,8 @@ export async function extractTextViaVisionAPI( { role: 'assistant', content: + // TODO: This prompt is tecnically incorrect, since we pass in a `context` which is the previous page that had tables + // What we probably want to do here is to send the raw markdown parsed from the same page as the tables and screenshot. 'This is previous text extracted with a less accurate method:' + context, }, @@ -48,15 +41,18 @@ export async function extractTextViaVisionAPI( content: [ { type: 'image_url', - image_url: { url: bufferToBase64(buffer), detail: 'high' }, + image_url: { url: imageBase64, detail: 'high' }, }, ], }, ], + // TODO: Why the max tokens here compared to the previous version of this function? max_tokens: 4096, }) if (!result.choices[0]?.message?.content) { - throw new Error('Failed to get content from Vision API') + throw new Error( + 'Failed to get content from Vision API: ' + JSON.stringify(result), + ) } return result.choices[0].message.content } @@ -215,6 +211,9 @@ export async function jsonToMarkdown( throw new Error(`Failed to convert page ${pageNumber} to image`) } + // NOTE: block.content here refers to the current section in the document + // TODO: Find a way to get the relevant markdown content from a given page when parsing the DoclingDocument JSON output + // Then, we should pass this table data to the Vision API. return extractTextViaVisionAPI( { buffer: result.buffer }, block.content || '', diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index d8bb0449..fb4c0bc2 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -2,8 +2,13 @@ import { resolve, join } from 'path' import { PythonShell } from 'python-shell' import { writeFile, readFile, mkdir } from 'fs/promises' -import { jsonToTables } from './jsonExtraction' -import { DoclingDocument, DoclingDocumentSchema, Table } from './docling-schema' +import { + DoclingDocument, + DoclingDocumentSchema, + Image, + Table, +} from './docling-schema' +import { extractTextViaVisionAPI } from './jsonExtraction' const OUTPUT_DIR = resolve('/tmp/pdf2markdown') @@ -92,6 +97,7 @@ type Page = { width: number height: number tables: Table[] + image: Image } export function findRelevantTablesGroupedOnPages( @@ -111,13 +117,20 @@ export function findRelevantTablesGroupedOnPages( // NOTE: Until content filtering is available, we need to process all tables return Object.values( - json.tables.reduce((acc: Record, table: Table) => { + json.tables.reduce((acc: Record, table: Table, i) => { for (const n of table.prov.map((item) => item.page_no)) { const { page_no, size: { width, height }, + image, } = json.pages[n] + if (!image) { + throw new Error( + `Page ${page_no} don't have an image, but needs it for table ${i}`, + ) + } + if (acc[page_no]) { acc[page_no].tables.push(table) } else { @@ -126,6 +139,7 @@ export function findRelevantTablesGroupedOnPages( tables: [table], width, height, + image, } } } @@ -135,20 +149,44 @@ export function findRelevantTablesGroupedOnPages( ) } -export async function extractTablesFromJson( - json: DoclingDocument, - outDir: string, - searchTerms: string[], -): Promise<{ pages: { pageNumber: number; filename: string }[] }> { - const pages = Object.values( - findRelevantTablesGroupedOnPages(json, searchTerms), +export async function extractTablesWithVisionAPI( + { json, markdown }: { json: DoclingDocument; markdown: string }, + searchTerms: string[] = [], +): Promise<{ markdown: string }> { + const pages = findRelevantTablesGroupedOnPages(json, searchTerms) + + const tables: { page_idx: number; markdown: string }[] = await pages.reduce( + async (resultsPromise, { pageNumber, image }) => { + const results = await resultsPromise + const lastPageMarkdown = results.at(-1)?.markdown || '' + const markdown = await extractTextViaVisionAPI( + { imageBase64: image.uri }, + lastPageMarkdown, + ) + // TODO: Send to s3 bucket (images) + return [ + ...results, + { + page_idx: Number(pageNumber - 1), + markdown, + }, + ] + }, + Promise.resolve([] as any), ) - const filenames = await Promise.all( - pages.map(async ({ pageNumber }) => { - const pageScreenshotPath = join(outDir, `/pages/page-${pageNumber}.png`) - return { pageNumber, filename: pageScreenshotPath } - }), - ) - return { pages: filenames } + console.log('Extracted tables: ' + tables.map((t) => t.markdown).join(', ')) + + return { + markdown: + markdown + + '\n\nHere are some of the important tables from the markdown with more precision:' + + tables + .map( + ({ page_idx, markdown }) => + `\n#### Page ${page_idx}: + ${markdown}`, + ) + .join('\n'), + } } From 0fc46c793da2bdb4451d7873d1a1c6f4a703838e Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 19:39:33 +0100 Subject: [PATCH 60/66] Simplify --- pdf2markdown/src/lib/pdfTools.ts | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index fb4c0bc2..b180e925 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -94,8 +94,6 @@ export async function convertPDF( type Page = { pageNumber: number - width: number - height: number tables: Table[] image: Image } @@ -119,11 +117,7 @@ export function findRelevantTablesGroupedOnPages( return Object.values( json.tables.reduce((acc: Record, table: Table, i) => { for (const n of table.prov.map((item) => item.page_no)) { - const { - page_no, - size: { width, height }, - image, - } = json.pages[n] + const { page_no, image } = json.pages[n] if (!image) { throw new Error( @@ -131,17 +125,13 @@ export function findRelevantTablesGroupedOnPages( ) } - if (acc[page_no]) { - acc[page_no].tables.push(table) - } else { - acc[page_no] = { - pageNumber: page_no, - tables: [table], - width, - height, - image, - } + acc[page_no] ??= { + pageNumber: page_no, + tables: [], + image, } + + acc[page_no].tables.push(table) } return acc From 4409aff29701981f6fae746bed511d631684d63a Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:13:10 +0100 Subject: [PATCH 61/66] WIP: Experiment with dereferencing in Node.js. Not working --- pdf2markdown/package-lock.json | 48 ++++++++++++++++++++++++ pdf2markdown/package.json | 3 +- pdf2markdown/src/verify-images.ts | 61 ++++++++++++++++++------------- 3 files changed, 85 insertions(+), 27 deletions(-) diff --git a/pdf2markdown/package-lock.json b/pdf2markdown/package-lock.json index 7d41c445..3b447147 100644 --- a/pdf2markdown/package-lock.json +++ b/pdf2markdown/package-lock.json @@ -8,6 +8,7 @@ "name": "pdf2markdown", "version": "1.0.0", "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.7.0", "@types/node": "^22.10.0", "express": "^5.0.1", "openai": "^4.73.1", @@ -26,6 +27,23 @@ "node": ">=22.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.3.tgz", + "integrity": "sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -781,6 +799,12 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -835,6 +859,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -935,6 +965,12 @@ "node": ">= 8.0.0" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", @@ -1717,6 +1753,18 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", diff --git a/pdf2markdown/package.json b/pdf2markdown/package.json index e9dec7d6..5a96b7de 100644 --- a/pdf2markdown/package.json +++ b/pdf2markdown/package.json @@ -17,7 +17,8 @@ "python-shell": "^5.0.0", "sharp": "^0.33.5", "tsx": "^4.19.2", - "zod": "^3.23.8" + "zod": "^3.23.8", + "@apidevtools/json-schema-ref-parser": "^11.7.0" }, "devDependencies": { "@types/express": "^5.0.0", diff --git a/pdf2markdown/src/verify-images.ts b/pdf2markdown/src/verify-images.ts index 3bad877c..3f79c5ae 100644 --- a/pdf2markdown/src/verify-images.ts +++ b/pdf2markdown/src/verify-images.ts @@ -1,39 +1,48 @@ import { resolve } from 'path' -import { glob, readFile } from 'fs/promises' +import { readFile, writeFile } from 'fs/promises' +import $RefParser from '@apidevtools/json-schema-ref-parser' import { DoclingDocumentSchema } from './lib/docling-schema' -import assert from 'assert' const docId = '5ee1f2b6-a86a-4f26-86bc-2223c937528b' const outDir = resolve(`/tmp/pdf2markdown/${docId}`) -const bufferToBase64 = (buffer: Buffer) => { - return 'data:image/png;base64,' + buffer.toString('base64') -} - -const rawJSON = await readFile(resolve(outDir, 'parsed.json'), { - encoding: 'utf-8', -}).then(JSON.parse) - -const json = DoclingDocumentSchema.parse(rawJSON) - -for await (const imagePath of glob(resolve(outDir, 'pages/*.png'))) { - const image = await readFile(imagePath).then(bufferToBase64) - const pageNumber = imagePath.match(/(\d+)\.png$/)?.[1] - if (pageNumber === undefined) { - throw new Error('Unable to match pageNumber for path: ' + imagePath) +async function loadDoclingJSON() { + const rawJSON = await readFile(resolve(outDir, 'parsed.json'), { + encoding: 'utf-8', + }).then(JSON.parse) + + let json = DoclingDocumentSchema.parse(rawJSON) + try { + await $RefParser.dereference(json, { + dereference: { + circular: 'ignore', + }, + }) + } catch (error) { + console.error(error) } - const page = json.pages[pageNumber] + // dump the dereferenced json file to a file for exploration + await writeFile( + resolve(outDir, 'document.json'), + JSON.stringify($RefParser.bundle(json)), + { + encoding: 'utf-8', + }, + ) - if (!page?.image) { - throw new Error(`Page ${pageNumber} is missing image`) - } + // compare size - is it smaller when using the JSON schema format? - assert( - image === json.pages[pageNumber].image?.uri, - 'Base64 encoded PNG images should match', - ) + // TODO: Create another zod schema with the actual document data + // TODO: Rename the + // TODO: Parse the document another time with zod to get a more useful data structure + // In the future, we don't have to write the file in between. + // Then use the parsed json document to process images +} + +async function main() { + await loadDoclingJSON() } -console.log('All images match!') +await main() From d8951a86df8254502edde8e45673ff5fcdce6b6d Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:13:45 +0100 Subject: [PATCH 62/66] WIP: Experiment with dereferencing json in Python. Not working --- pdf2markdown/src/parse_pdf.py | 14 +++++++++++++- pdf2markdown/src/test.py | 21 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index bf91e9b7..032908a1 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -5,6 +5,7 @@ from argparse import ArgumentParser from io import BytesIO from pathlib import Path +from jsonref import replace_refs from docling.datamodel.document import ConversionResult, DoclingDocument from docling.datamodel.base_models import InputFormat, ConversionStatus @@ -71,8 +72,19 @@ def export_document( doc_export["pages"] = updated_pages + # TODO: Figure out if we could remove refs from the exported document to make it easier to work with during later steps + # Ideally, we solve this on the Python side where we have much better context compared to with Node.js + # Also, if we can solve it with Python, we don't need to install additional dependencies, since `jsonref` is already a dependency of Docling + # replace_refs returns a copy of the document with refs replaced by JsonRef + # objects. It will resolve refences to other JSON schema files + doc = replace_refs( + doc_export, + merge_props=True, + base_uri=json_file.absolute().as_uri(), + ) + with json_file.open("w", encoding="utf-8") as fp: - json.dump(doc_export, fp, ensure_ascii=False) + json.dump(doc, fp, ensure_ascii=False) _log.info( f"Saved document JSON (including Base64-encoded page images) to: {json_file}" ) diff --git a/pdf2markdown/src/test.py b/pdf2markdown/src/test.py index 71ef5150..809b9635 100644 --- a/pdf2markdown/src/test.py +++ b/pdf2markdown/src/test.py @@ -1,5 +1,6 @@ import json from pathlib import Path +import jsonref doc_id = "5ee1f2b6-a86a-4f26-86bc-2223c937528b" out_dir = Path(f"/tmp/pdf2markdown/{doc_id}") @@ -11,7 +12,7 @@ def without_keys(d: dict, keys: set): def main(): with (out_dir / "parsed.json").open("r", encoding="utf-8") as fp: - doc: dict = json.load(fp) + doc: dict = jsonref.load(fp) wanted_page_nums = {2, 8, 31, 32, 39, 40} print(sorted(wanted_page_nums)) @@ -24,8 +25,22 @@ def main(): doc["pages"] = updated_pages - with (out_dir / "filtered_images.json").open("w", encoding="utf-8") as fp: - json.dump(doc, fp) + # NOTE: We might be able to de-reference the JSON file already in Python, which would be better since we then wouldn't require any new dependencies + + updated_json_path = out_dir / "filtered_images.json" + + # replace_refs returns a copy of the document with refs replaced by JsonRef + # objects. It will resolve refences to other JSON schema files + doc_export = jsonref.replace_refs( + # json.loads(jsonref.dumps(doc)), + doc, + merge_props=True, + base_uri=updated_json_path.absolute().as_uri(), + ) + print(updated_json_path) + + with updated_json_path.open("w", encoding="utf-8") as fp: + jsonref.dump(doc_export, fp) main() From 2d10ff39552785b0af628791d29a51f038b2a3c9 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:14:12 +0100 Subject: [PATCH 63/66] Add ideas for providing more relevant context to the Vision API --- pdf2markdown/src/parse_pdf.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 032908a1..274a5d83 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -58,6 +58,15 @@ def export_document( f"Found {len(unique_pages_with_tables)} unique pages with tables: {sorted(unique_pages_with_tables)}" ) + # IDEA: We could perhaps export the markdown for each table, and pass this as context for the Vision API along with the page screenshot + # Or, since we use the page image when parsing with the Vision API, we should likely convert the whole page text content to markdown. + # Because then the page would match the image content, which might yield better (or worse) results. Worth experimenting with. + # Export tables + # for table_ix, table in enumerate(conv_res.document.tables): + # table_df: pd.DataFrame = table.export_to_dataframe() + # print(f"## Table {table_ix}") + # print(table_df.to_markdown()) + doc_export: DoclingDocument = conv_result.document.export_to_dict() updated_pages = {} From bca466dfe8942d0ef64deba43d233001906c9489 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:46:31 +0100 Subject: [PATCH 64/66] WIP: Try different args --- pdf2markdown/src/test.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pdf2markdown/src/test.py b/pdf2markdown/src/test.py index 809b9635..3c2f54c1 100644 --- a/pdf2markdown/src/test.py +++ b/pdf2markdown/src/test.py @@ -31,12 +31,7 @@ def main(): # replace_refs returns a copy of the document with refs replaced by JsonRef # objects. It will resolve refences to other JSON schema files - doc_export = jsonref.replace_refs( - # json.loads(jsonref.dumps(doc)), - doc, - merge_props=True, - base_uri=updated_json_path.absolute().as_uri(), - ) + doc_export = jsonref.replace_refs(doc, lazy_load=True, proxies=False) print(updated_json_path) with updated_json_path.open("w", encoding="utf-8") as fp: From 2ad225f49d1e69899a3b7c2f3d677efe31995454 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:39:13 +0100 Subject: [PATCH 65/66] Add notes --- pdf2markdown/src/index.ts | 1 + pdf2markdown/src/lib/pdfTools.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pdf2markdown/src/index.ts b/pdf2markdown/src/index.ts index 1d60bba4..76bab4cc 100644 --- a/pdf2markdown/src/index.ts +++ b/pdf2markdown/src/index.ts @@ -31,6 +31,7 @@ app.post( getFileSize(Buffer.byteLength(buffer)), ) + // NOTE: Move docId since we no longer need it out here const docId = crypto.randomUUID() const parsed = await convertPDF(buffer, docId) diff --git a/pdf2markdown/src/lib/pdfTools.ts b/pdf2markdown/src/lib/pdfTools.ts index b180e925..c6a39823 100644 --- a/pdf2markdown/src/lib/pdfTools.ts +++ b/pdf2markdown/src/lib/pdfTools.ts @@ -48,6 +48,8 @@ export async function convertPDF( // }) // }) await PythonShell.run(resolve(import.meta.dirname, '../parse_pdf.py'), { + // IDEA: Maybe pass searchTerms to only extract the page images for the tables we want + // The python PDF parser could return only the data needed to extract with the Vision API. args: [inputPDF, outDir], stdio: ['pipe', 'inherit', 'pipe', 'pipe'], }) From d4b0e3fcd79916aec6265b8e25b87fc30af60aee Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:29:03 +0100 Subject: [PATCH 66/66] Make parsing work again and add idea for image extraction --- pdf2markdown/src/parse_pdf.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pdf2markdown/src/parse_pdf.py b/pdf2markdown/src/parse_pdf.py index 274a5d83..ea852ec8 100644 --- a/pdf2markdown/src/parse_pdf.py +++ b/pdf2markdown/src/parse_pdf.py @@ -86,14 +86,20 @@ def export_document( # Also, if we can solve it with Python, we don't need to install additional dependencies, since `jsonref` is already a dependency of Docling # replace_refs returns a copy of the document with refs replaced by JsonRef # objects. It will resolve refences to other JSON schema files - doc = replace_refs( - doc_export, - merge_props=True, - base_uri=json_file.absolute().as_uri(), - ) + # doc = replace_refs( + # doc_export, + # merge_props=True, + # base_uri=json_file.absolute().as_uri(), + # ) + + # IDEA: Maybe we could speed up the parsing if we only take page screenshots after we know the relevant pages. + # This would reduce both CPU and memory usage since we throw away the pages anyway. + # IDEA: Either we could take screenshots using some python library. + # Or, perhaps we could create a new document converter, but for the second time use new conversion options to also take screenshots of the selected pages + # And if we only do it for the selected pages, then we get only the data we need. with json_file.open("w", encoding="utf-8") as fp: - json.dump(doc, fp, ensure_ascii=False) + json.dump(doc_export, fp, ensure_ascii=False) _log.info( f"Saved document JSON (including Base64-encoded page images) to: {json_file}" )