diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..db90e805e0de2a160a8582118fb9c8d66b6c6394 Binary files /dev/null and b/.DS_Store differ diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..38100b7db393eb542fb30939019cc84286728aac --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +AZURE_DI_ENDPOINT=https://luisvalenciadi.cognitiveservices.azure.com/ +AZURE_DI_KEY=2MMHWymCHlykFwMGtaDHKGcbcWFpMfF29vqWKV6C584Zi9EJj5m4JQQJ99BEACfhMk5XJ3w3AAALACOGUYF3 +OPENAI_API_KEY=1bW5tUhsr8yHXQe0Pra6YdM43fWCUr06KtbGy9gKxVOd7ut5vL9GJQQJ99BDACfhMk5XJ3w3AAAAACOGTQtA +AZURE_OPENAI_ENDPOINT=https://lvaaifoundry1567796533.openai.azure.com/ +AZURE_OPENAI_DEPLOYMENT=gpt-4.1 +AZURE_OPENAI_API_VERSION=2025-03-01-preview +AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000000000000000000000000000000000..25d248af87f10943ce7965d373674a91d63ac7a7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "configurations": [ + { + "name": "Streamlit (debug)", + "type": "debugpy", + "request": "launch", + "module": "streamlit", + "args": ["run", "src/app.py"], + "envFile": "${workspaceFolder}/.env", + } + ] + } + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 88a3d42894d90412f1143120fbca9d7f419e92bf..ed812abf2903162ee0560cd926b88f97d32e573c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,4 @@ EXPOSE 8501 HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health -ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] \ No newline at end of file +ENTRYPOINT ["streamlit", "run", "src/app.py", "--server.port=8501", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 28d994e22f8dd432b51df193562052e315ad95f7..f94c036fe52300ba371003eca90f0546154d87b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,9 @@ altair pandas -streamlit \ No newline at end of file +streamlit +pyyaml +python-dotenv +openai +pydantic-settings +PyMuPDF +azure-ai-documentintelligence \ No newline at end of file diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000000000000000000000000000000000000..70e9d9d64971631823684d2c59239589698f084a --- /dev/null +++ b/src/README.md @@ -0,0 +1,167 @@ +# Deep-Research PDF Field Extractor + +A powerful tool for extracting structured data from PDF documents, designed to handle various document types and extract specific fields of interest. + +## For End Users + +### Overview +The PDF Field Extractor helps you extract specific information from PDF documents. It can extract any fields you specify, such as dates, names, values, locations, and more. The tool is particularly useful for converting unstructured PDF data into structured, analyzable formats. + +### How to Use + +1. **Upload Your PDF** + - Click the "Upload PDF" button + - Select your PDF file from your computer + +2. **Specify Fields to Extract** + - Enter the fields you want to extract, separated by commas + - Example: `Date, Name, Value, Location, Page, FileName` + +3. **Optional: Add Field Descriptions** + - You can provide additional context about the fields. + - This helps the system better understand what to look for + +4. **Run Extraction** + - Click the "Run extraction" button + - Wait for the process to complete + - View your results in a table format + +5. **Download Results** + - Download your extracted data as a CSV file + - View execution traces and logs if needed + +### Features +- Automatic document type detection +- Smart field extraction +- Support for tables and text +- Detailed execution traces +- Downloadable results and logs + +## For Developers + +### Architecture Overview + +The application is built using a multi-agent architecture with the following components: + +#### Core Components + +1. **Planner (`orchestrator/planner.py`)** + - Generates execution plans using Azure OpenAI + + +2. **Executor (`orchestrator/executor.py`)** + - Executes the generated plan + - Manages agent execution flow + - Handles context and result management + +3. **Agents** + - `PDFAgent`: Extracts text from PDFs + - `TableAgent`: Extracts tables from PDFs + - `FieldMapper`: Maps fields to values + - `ForEachField`: Control flow for field iteration + +### Agent Pipeline + +1. **Document Processing** + ```python + # Document is processed in stages: + 1. PDF text extraction + 2. Table extraction + 3. Field mapping + ``` + +2. **Field Extraction Process** + - Document type inference + - User profile determination + - Page-by-page scanning + - Value extraction and validation + +3. **Context Building** + - Document metadata + - Field descriptions + - User context + - Execution history + +### Key Features + +#### Document Type Inference +The system automatically infers document type and user profile: +```python +# Example inference: +"Document type: Analytical report +User profile: Data analysts or researchers working with document analysis" +``` + +#### Field Mapping +The FieldMapper agent uses a sophisticated approach: +1. Document context analysis +2. Page-by-page scanning +3. Value extraction using LLM +4. Result validation + +#### Execution Traces +The system maintains detailed execution traces: +- Tool execution history +- Success/failure status +- Detailed logs +- Result storage + +### Technical Setup + +1. **Dependencies** + ```python + # Key dependencies: + - streamlit + - pandas + - PyMuPDF (fitz) + - Azure OpenAI + - Azure Document Intelligence + ``` + +2. **Configuration** + - Environment variables for API keys + - Prompt templates in `config/prompts.yaml` + - Settings in `config/settings.py` + +3. **Logging System** + ```python + # Custom logging setup: + - LogCaptureHandler for UI display + - Structured logging format + - Execution history storage + ``` + +### Development Guidelines + +1. **Adding New Agents** + - Inherit from base agent class + - Implement required methods + - Add to planner configuration + +2. **Modifying Extraction Logic** + - Update prompt templates + - Modify field mapping logic + - Adjust validation rules + +3. **Extending Functionality** + - Add new field types + - Implement custom validators + - Create new output formats + +### Testing +- Unit tests for agents +- Integration tests for pipeline +- End-to-end testing with sample PDFs + +### Deployment +- Streamlit app deployment +- Environment configuration +- API key management +- Logging setup + +### Future Improvements +- Enhanced error handling +- Additional field types +- Improved validation +- Performance optimization +- Extended documentation diff --git a/src/agents/__init__.py b/src/agents/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/src/agents/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/agents/__pycache__/__init__.cpython-312.pyc b/src/agents/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c35ca004933847c11f1a1f171bb9eed6847af765 Binary files /dev/null and b/src/agents/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/agents/__pycache__/__init__.cpython-313.pyc b/src/agents/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b547b6e93ffe2a667ec537f8dfa784b9c74946bc Binary files /dev/null and b/src/agents/__pycache__/__init__.cpython-313.pyc differ diff --git a/src/agents/__pycache__/base_agent.cpython-312.pyc b/src/agents/__pycache__/base_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a012a3f88701f92828ff39d5082f90ec3cdfa9aa Binary files /dev/null and b/src/agents/__pycache__/base_agent.cpython-312.pyc differ diff --git a/src/agents/__pycache__/base_agent.cpython-313.pyc b/src/agents/__pycache__/base_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3115d7bf6cfbbbdefe303392afbe98243980cc4e Binary files /dev/null and b/src/agents/__pycache__/base_agent.cpython-313.pyc differ diff --git a/src/agents/__pycache__/cache_agent.cpython-313.pyc b/src/agents/__pycache__/cache_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0c6d179554d6776c8e8b99808511b0f9f067006 Binary files /dev/null and b/src/agents/__pycache__/cache_agent.cpython-313.pyc differ diff --git a/src/agents/__pycache__/confidence_scorer.cpython-312.pyc b/src/agents/__pycache__/confidence_scorer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..457d7395f5dfb8b817b197e5bbd43decde5c1a24 Binary files /dev/null and b/src/agents/__pycache__/confidence_scorer.cpython-312.pyc differ diff --git a/src/agents/__pycache__/confidence_scorer.cpython-313.pyc b/src/agents/__pycache__/confidence_scorer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37255a7da7d6f3a425b3ce638e87415c3d497991 Binary files /dev/null and b/src/agents/__pycache__/confidence_scorer.cpython-313.pyc differ diff --git a/src/agents/__pycache__/document_intelligence_agent.cpython-313.pyc b/src/agents/__pycache__/document_intelligence_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..238fea2e7e89f77aee300ffdd785c7cfca41abdd Binary files /dev/null and b/src/agents/__pycache__/document_intelligence_agent.cpython-313.pyc differ diff --git a/src/agents/__pycache__/field_mapper_agent.cpython-312.pyc b/src/agents/__pycache__/field_mapper_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bb8631043623e62e0e36b343f9e334c05e5b885 Binary files /dev/null and b/src/agents/__pycache__/field_mapper_agent.cpython-312.pyc differ diff --git a/src/agents/__pycache__/field_mapper_agent.cpython-313.pyc b/src/agents/__pycache__/field_mapper_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cdb849f57e30b2def603e8a2e3e6f8be599758f Binary files /dev/null and b/src/agents/__pycache__/field_mapper_agent.cpython-313.pyc differ diff --git a/src/agents/__pycache__/index_agent.cpython-312.pyc b/src/agents/__pycache__/index_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da9f316ecd10138e4e502d1961ecc5f4f7f74d2d Binary files /dev/null and b/src/agents/__pycache__/index_agent.cpython-312.pyc differ diff --git a/src/agents/__pycache__/index_agent.cpython-313.pyc b/src/agents/__pycache__/index_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfd0ea0f2ca23019eb83f2b9c31b2d6d85222c2a Binary files /dev/null and b/src/agents/__pycache__/index_agent.cpython-313.pyc differ diff --git a/src/agents/__pycache__/pdf_agent.cpython-312.pyc b/src/agents/__pycache__/pdf_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a59e0981fa22e550b16176886f0eaf8516d25cf Binary files /dev/null and b/src/agents/__pycache__/pdf_agent.cpython-312.pyc differ diff --git a/src/agents/__pycache__/pdf_agent.cpython-313.pyc b/src/agents/__pycache__/pdf_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0a349720f2abbc9ba3bb54d24761fe947cb287c Binary files /dev/null and b/src/agents/__pycache__/pdf_agent.cpython-313.pyc differ diff --git a/src/agents/__pycache__/query_generator.cpython-312.pyc b/src/agents/__pycache__/query_generator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f864957e0d77444642930bf33fb21f41a8bd4d63 Binary files /dev/null and b/src/agents/__pycache__/query_generator.cpython-312.pyc differ diff --git a/src/agents/__pycache__/query_generator.cpython-313.pyc b/src/agents/__pycache__/query_generator.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94cd5539aa12a59ab1318ba01e6741eea7988e55 Binary files /dev/null and b/src/agents/__pycache__/query_generator.cpython-313.pyc differ diff --git a/src/agents/__pycache__/semantic_reasoner.cpython-312.pyc b/src/agents/__pycache__/semantic_reasoner.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..204f86e9eabdb1a0b14050b3f3026680707f5a1a Binary files /dev/null and b/src/agents/__pycache__/semantic_reasoner.cpython-312.pyc differ diff --git a/src/agents/__pycache__/semantic_reasoner.cpython-313.pyc b/src/agents/__pycache__/semantic_reasoner.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfbd252e5a0e798ee6d25dcfeebb447f2385c93c Binary files /dev/null and b/src/agents/__pycache__/semantic_reasoner.cpython-313.pyc differ diff --git a/src/agents/__pycache__/table_agent.cpython-312.pyc b/src/agents/__pycache__/table_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba40b483ad398e5710ca2459b6343c81b3be8d8b Binary files /dev/null and b/src/agents/__pycache__/table_agent.cpython-312.pyc differ diff --git a/src/agents/__pycache__/table_agent.cpython-313.pyc b/src/agents/__pycache__/table_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f01d6e75ca6831f5601a450f81158b0b22c7ddd6 Binary files /dev/null and b/src/agents/__pycache__/table_agent.cpython-313.pyc differ diff --git a/src/agents/base_agent.py b/src/agents/base_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..40edf75296c7ab21bb9aeeb6d7fd8a6442dd26dc --- /dev/null +++ b/src/agents/base_agent.py @@ -0,0 +1,8 @@ +"""Abstract base class for every agent.""" +from abc import ABC, abstractmethod +from typing import Dict, Any + +class BaseAgent(ABC): + @abstractmethod + def execute(self, ctx: Dict[str, Any]): + """Mutate / consume ctx and return a value.""" \ No newline at end of file diff --git a/src/agents/confidence_scorer.py b/src/agents/confidence_scorer.py new file mode 100644 index 0000000000000000000000000000000000000000..44f066876add3044d5c3840edb75dec220f9c663 --- /dev/null +++ b/src/agents/confidence_scorer.py @@ -0,0 +1,6 @@ +from typing import Dict, Any +from .base_agent import BaseAgent + +class ConfidenceScorer(BaseAgent): + def execute(self, ctx: Dict[str, Any]): + return 1.0 # always confident for stub \ No newline at end of file diff --git a/src/agents/field_mapper_agent.py b/src/agents/field_mapper_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..22a4897853e4827894be2e48de4dfca216ed570f --- /dev/null +++ b/src/agents/field_mapper_agent.py @@ -0,0 +1,311 @@ +"""Map a single field to a candidate value using page-by-page analysis and LLM-based extraction.""" +from typing import Dict, Any, Optional, List +import logging +import re +import json +from .base_agent import BaseAgent +from services.llm_client import LLMClient +from services.embedding_client import EmbeddingClient +from config.settings import settings + +# Configure logging to disable verbose Azure HTTP logs +logging.getLogger('azure.core.pipeline.policies.http_logging_policy').setLevel(logging.WARNING) +logging.getLogger('azure.core.pipeline').setLevel(logging.WARNING) +logging.getLogger('azure').setLevel(logging.WARNING) + +class FieldMapperAgent(BaseAgent): + def __init__(self): + self.logger = logging.getLogger(__name__) + self.llm = LLMClient(settings) + self.embedding_client = EmbeddingClient() + + def _infer_document_context(self, text: str) -> str: + """Use LLM to infer document context and user profile.""" + prompt = f"""Given this document text, describe the document type and typical user profile in 1-2 sentences. + Focus on the domain, purpose, and who would use this document. + + Document text: + {text[:2000]} # First 2000 chars for context + + Response format: + Document type: [type] + User profile: [profile] + """ + + try: + self.logger.info("Inferring document context...") + self.logger.debug(f"Using text preview: {text[:500]}...") + context = self.llm.responses(prompt, temperature=0.0) + self.logger.info(f"Inferred context: {context}") + return context + except Exception as e: + self.logger.error(f"Error inferring context: {str(e)}") + return "Generic document user" + + def _find_similar_chunks_search(self, query: str, index: Dict[str, Any], top_k: int = 3) -> List[Dict[str, Any]]: + """Find chunks semantically similar to the query using cosine similarity.""" + try: + self.logger.info(f"Finding similar chunks for query: {query}") + self.logger.debug(f"Index contains {len(index['chunks'])} chunks and {len(index['embeddings'])} embeddings") + + # Get query embedding + self.logger.debug("Generating embedding for query...") + query_embedding = self.embedding_client.embed([query])[0] + self.logger.debug(f"Query embedding generated, length: {len(query_embedding)}") + + # Calculate similarities + similarities = [] + for i, (chunk, embedding) in enumerate(zip(index["chunks"], index["embeddings"])): + similarity = self._cosine_similarity(query_embedding, embedding) + similarities.append((similarity, chunk)) + self.logger.debug(f"Chunk {i} similarity: {similarity:.3f}") + self.logger.debug(f"Chunk {i} preview: {chunk['text'][:100]}...") + + # Sort by similarity and return top k + similarities.sort(reverse=True) + results = [chunk for _, chunk in similarities[:top_k]] + + # Log top results + self.logger.info(f"Found {len(results)} similar chunks") + for i, (sim, chunk) in enumerate(similarities[:top_k]): + self.logger.info(f"Top {i+1} match (similarity: {sim:.3f}): {chunk['text'][:200]}...") + + return results + + except Exception as e: + self.logger.error(f"Error finding similar chunks: {str(e)}", exc_info=True) + return [] + + def _cosine_similarity(self, a: List[float], b: List[float]) -> float: + """Calculate cosine similarity between two vectors.""" + import numpy as np + try: + # Check for zero vectors + if not a or not b or all(x == 0 for x in a) or all(x == 0 for x in b): + self.logger.warning("Zero vector detected in cosine similarity calculation") + return 0.0 + + # Convert to numpy arrays + a_np = np.array(a) + b_np = np.array(b) + + # Calculate norms + norm_a = np.linalg.norm(a_np) + norm_b = np.linalg.norm(b_np) + + # Check for zero norms + if norm_a == 0 or norm_b == 0: + self.logger.warning("Zero norm detected in cosine similarity calculation") + return 0.0 + + # Calculate similarity + similarity = np.dot(a_np, b_np) / (norm_a * norm_b) + + # Check for NaN + if np.isnan(similarity): + self.logger.warning("NaN detected in cosine similarity calculation") + return 0.0 + + return float(similarity) + except Exception as e: + self.logger.error(f"Error calculating cosine similarity: {str(e)}") + return 0.0 + + def _extract_field_value_search(self, field: str, chunks: List[Dict[str, Any]], context: str) -> Optional[str]: + """Use LLM to extract field value from relevant chunks.""" + # Combine chunks into context + chunk_texts = [chunk["text"] for chunk in chunks] + combined_context = "\n".join(chunk_texts) + + self.logger.info(f"Extracting value for field '{field}' from {len(chunks)} chunks") + self.logger.debug(f"Combined context preview: {combined_context[:500]}...") + + # Get filename from context if available + filename = self.ctx.get("pdf_meta", {}).get("filename", "") + filename_context = f"\nDocument filename: {filename}" if filename else "" + + prompt = f"""You are an expert in {context} + + Your task is to extract the value for the field: {field}{filename_context} + + Consider the following context from the document: + {combined_context} + + Instructions: + 1. Look for the field value in the context + 2. If you find multiple potential values, choose the most relevant one + 3. If you're not sure, return None + 4. Return ONLY the value, no explanations + + Field value:""" + + try: + self.logger.info(f"Calling LLM to extract value for field '{field}'") + self.logger.debug(f"Using prompt: {prompt}") + value = self.llm.responses(prompt, temperature=0.0) + self.logger.debug(f"Raw LLM response: {value}") + + if value and value.lower() not in ["none", "null", "n/a"]: + self.logger.info(f"Successfully extracted value: {value}") + return value.strip() + else: + self.logger.warning(f"LLM returned no valid value for field '{field}'") + return None + except Exception as e: + self.logger.error(f"Error extracting field value: {str(e)}", exc_info=True) + return None + + def _extract_field_value_from_page(self, field: str, page_text: str, context: str) -> Optional[str]: + """Use LLM to extract field value from a single page.""" + self.logger.info(f"Extracting value for field '{field}' from page") + self.logger.debug(f"Page text preview: {page_text[:500]}...") + + # Get filename from context if available + filename = self.ctx.get("pdf_meta", {}).get("filename", "") + filename_context = f"\nDocument filename: {filename}" if filename else "" + + prompt = f"""You are an expert in {context} + + Your task is to extract the value for the field: {field}{filename_context} + + Consider the following page from the document: + {page_text} + + Instructions: + 1. Look for the field values in this page + 2. Return the data in a tabular format where each field is a column + 3. Each field should have an array of values + 4. The arrays must be aligned (same length) to represent rows + 5. Return ONLY the JSON value, no explanations + 6. Format the response as a valid JSON object with field names as keys + 7. Keep the structure flat - do not nest values under 'details' or other keys + + Example response format: + {{ + "field1": ["value1", "value2", "value3"], + "field2": ["value4", "value5", "value6"], + "field3": ["value7", "value8", "value9"] + }} + + Field value:""" + + try: + self.logger.info(f"Calling LLM to extract value for field '{field}' from page") + value = self.llm.responses(prompt, temperature=0.0) + self.logger.debug(f"Raw LLM response: {value}") + + if value and value.lower() not in ["none", "null", "n/a"]: + # Try to parse as JSON to ensure it's valid + try: + json_value = json.loads(value) + self.logger.info(f"Successfully extracted value: {json.dumps(json_value, indent=2)}") + return json.dumps(json_value, indent=2) + except json.JSONDecodeError: + # If not valid JSON, wrap it in a JSON object + json_value = {field: value.strip()} + self.logger.info(f"Wrapped non-JSON value in JSON object: {json.dumps(json_value, indent=2)}") + return json.dumps(json_value, indent=2) + else: + self.logger.warning(f"LLM returned no valid value for field '{field}'") + return None + except Exception as e: + self.logger.error(f"Error extracting field value from page: {str(e)}", exc_info=True) + return None + + def execute(self, ctx: Dict[str, Any]): # noqa: D401 + field = ctx.get("current_field") + self.logger.info(f"Starting field mapping for: {field}") + + # Store context for use in extraction methods + self.ctx = ctx + + # Get text and index + text = "" + index = None + if "index" in ctx and isinstance(ctx["index"], dict): + index = ctx["index"] + text = index.get("text", "") + self.logger.info(f"Using text from index (length: {len(text)})") + self.logger.debug(f"Index contains {len(index.get('chunks', []))} chunks") + self.logger.debug(f"Index contains {len(index.get('embeddings', []))} embeddings") + elif "text" in ctx: + text = ctx["text"] + self.logger.info(f"Using text from direct context (length: {len(text)})") + + if not field: + self.logger.warning("No field provided in context") + return None + if not text: + self.logger.warning("No text content found in context or index") + return None + + # Infer document context if not already present + if "document_context" not in ctx: + ctx["document_context"] = self._infer_document_context(text) + + self.logger.info(f"Processing field: {field}") + self.logger.info(f"Using document context: {ctx['document_context']}") + + # Split text into pages using the same markers as IndexAgent + page_markers = [ + '', + 'Page', + '---', + '\n\n', # Double newline as fallback + ] + + # First try to find which marker is used + used_marker = None + for marker in page_markers: + if marker in text: + used_marker = marker + self.logger.info(f"Found page marker: {marker}") + break + + if not used_marker: + self.logger.warning("No page markers found, falling back to double newline") + used_marker = '\n\n' + + # Split the text + if used_marker == '\n\n': + pages = [p.strip() for p in text.split(used_marker) if p.strip()] + else: + pages = [] + current_page = [] + + for line in text.split('\n'): + if used_marker in line: + if current_page: + pages.append('\n'.join(current_page)) + current_page = [line] + else: + current_page.append(line) + + if current_page: + pages.append('\n'.join(current_page)) + + self.logger.info(f"Split document into {len(pages)} pages") + + # Process each page + for i, page in enumerate(pages, 1): + self.logger.info(f"Processing page {i}/{len(pages)}") + value = self._extract_field_value_from_page(field, page, ctx["document_context"]) + if value: + return value + + # If no value found in any page, try the search-based approach as fallback + self.logger.warning("No value found in page-by-page analysis, falling back to search-based approach") + + if index and "embeddings" in index: + self.logger.info("Using semantic search with embeddings") + search_query = f"{field} in {ctx['document_context']}" + similar_chunks = self._find_similar_chunks_search(search_query, index) + + if similar_chunks: + self.logger.info(f"Found {len(similar_chunks)} relevant chunks, attempting value extraction") + value = self._extract_field_value_search(field, similar_chunks, ctx["document_context"]) + if value: + return value + + self.logger.warning(f"No candidate found for field: {field}") + return f"" \ No newline at end of file diff --git a/src/agents/index_agent.py b/src/agents/index_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..cb5d45e86175b7ce7cb974178bd783badf54c1d3 --- /dev/null +++ b/src/agents/index_agent.py @@ -0,0 +1,135 @@ +"""Create a semantic index of document content using embeddings.""" +from typing import Dict, Any, List, Tuple +import logging +import numpy as np +from .base_agent import BaseAgent +from services.embedding_client import EmbeddingClient + +class IndexAgent(BaseAgent): + def __init__(self): + self.logger = logging.getLogger(__name__) + self.embedding_client = EmbeddingClient() + self.logger.info("IndexAgent initialized") + + def execute(self, ctx: Dict[str, Any]): + """Create a semantic index of document content.""" + try: + self.logger.info("Starting index creation") + + # Get text from PDF agent + text = ctx.get("text", "") + if not text: + self.logger.warning("No text content found in context") + return {} + self.logger.info(f"Found text content of length {len(text)}") + + # Get tables from Table agent + tables = ctx.get("tables", []) + self.logger.info(f"Found {len(tables)} tables in context") + + # Combine all content + all_content = text + if tables: + all_content += "\n".join(tables) + self.logger.info(f"Combined content length: {len(all_content)}") + + # Create chunks with metadata + chunks = self._create_chunks(all_content) + self.logger.info(f"Created {len(chunks)} content chunks") + for i, chunk in enumerate(chunks): + self.logger.debug(f"Chunk {i}: {chunk['text'][:100]}...") + + # Get embeddings for chunks + chunk_texts = [chunk["text"] for chunk in chunks] + self.logger.info(f"Getting embeddings for {len(chunk_texts)} chunks") + embeddings = self.embedding_client.embed(chunk_texts) + self.logger.info(f"Generated {len(embeddings)} embeddings") + + # Create semantic index + index = { + "chunks": chunks, + "embeddings": embeddings, + "text": all_content, # Keep full text for non-semantic search + } + + # Store in context + ctx["index"] = index + self.logger.info(f"Created semantic index with {len(chunks)} chunks") + return index + + except Exception as e: + self.logger.error(f"Error in IndexAgent: {str(e)}", exc_info=True) + return {} + + def _create_chunks(self, text: str, chunk_size: int = 1000) -> List[Dict[str, Any]]: + """Split text into chunks with metadata.""" + self.logger.info(f"Creating chunks from text of length {len(text)}") + chunks = [] + sentences = text.split(". ") + self.logger.info(f"Split into {len(sentences)} sentences") + current_chunk = [] + current_size = 0 + total_length = 0 + + for sentence in sentences: + sentence = sentence.strip() + ". " + sentence_size = len(sentence) + + if current_size + sentence_size > chunk_size and current_chunk: + # Save current chunk + chunk_text = "".join(current_chunk) + chunks.append({ + "text": chunk_text, + "start": total_length, + "end": total_length + len(chunk_text), + "type": "text" + }) + total_length += len(chunk_text) + self.logger.debug(f"Created chunk of size {len(chunk_text)}") + current_chunk = [] + current_size = 0 + + current_chunk.append(sentence) + current_size += sentence_size + + # Add last chunk if any + if current_chunk: + chunk_text = "".join(current_chunk) + chunks.append({ + "text": chunk_text, + "start": total_length, + "end": total_length + len(chunk_text), + "type": "text" + }) + self.logger.debug(f"Created final chunk of size {len(chunk_text)}") + + self.logger.info(f"Created {len(chunks)} total chunks") + return chunks + + def find_similar_chunks(self, query: str, index: Dict[str, Any], top_k: int = 3) -> List[Dict[str, Any]]: + """Find chunks semantically similar to the query.""" + try: + self.logger.info(f"Finding similar chunks for query: {query}") + # Get query embedding + query_embedding = self.embedding_client.embed([query])[0] + + # Calculate similarities + similarities = [] + for chunk, embedding in zip(index["chunks"], index["embeddings"]): + similarity = self._cosine_similarity(query_embedding, embedding) + similarities.append((similarity, chunk)) + self.logger.debug(f"Chunk similarity: {similarity:.3f}") + + # Sort by similarity and return top k + similarities.sort(reverse=True) + results = [chunk for _, chunk in similarities[:top_k]] + self.logger.info(f"Found {len(results)} similar chunks") + return results + + except Exception as e: + self.logger.error(f"Error finding similar chunks: {str(e)}", exc_info=True) + return [] + + def _cosine_similarity(self, a: List[float], b: List[float]) -> float: + """Calculate cosine similarity between two vectors.""" + return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) \ No newline at end of file diff --git a/src/agents/pdf_agent.py b/src/agents/pdf_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..de8309bcd26b27b7b3ed69941be600e1f92d2f38 --- /dev/null +++ b/src/agents/pdf_agent.py @@ -0,0 +1,28 @@ +"""Extract raw text from the uploaded PDF using PyMuPDF. +This keeps the implementation minimal for a POC while remaining easy to extend. +""" +from typing import Dict, Any, List + +import fitz # PyMuPDF + +from .base_agent import BaseAgent + + +class PDFAgent(BaseAgent): + """Reads the PDF, concatenates all page text and stores it under ``ctx['text']``.""" + + def _extract_text(self, pdf_bytes: bytes) -> str: + doc = fitz.open(stream=pdf_bytes, filetype="pdf") # type: ignore[arg-type] + pages: List[str] = [page.get_text() for page in doc] # list-comp for clarity + return "\n".join(pages) + + # ----------------------------------------------------- + def execute(self, ctx: Dict[str, Any]): # noqa: D401 + pdf_file = ctx.get("pdf_file") + if pdf_file is None: + raise ValueError("PDFAgent expected 'pdf_file' in context but none provided.") + + pdf_bytes = pdf_file.read() + text = self._extract_text(pdf_bytes) + ctx["text"] = text + return text \ No newline at end of file diff --git a/src/agents/query_generator.py b/src/agents/query_generator.py new file mode 100644 index 0000000000000000000000000000000000000000..70c80e137fb1a617e287c5f7916ec876301d5603 --- /dev/null +++ b/src/agents/query_generator.py @@ -0,0 +1,7 @@ +from typing import Dict, Any +from .base_agent import BaseAgent + +class QueryGenerator(BaseAgent): + def execute(self, ctx: Dict[str, Any]): + field = ctx.get("current_field") + return f"Follow‑up query for {field}" \ No newline at end of file diff --git a/src/agents/semantic_reasoner.py b/src/agents/semantic_reasoner.py new file mode 100644 index 0000000000000000000000000000000000000000..ba1a821f3c0eec86207cc698debfafa9b8e47289 --- /dev/null +++ b/src/agents/semantic_reasoner.py @@ -0,0 +1,8 @@ +from typing import Dict, Any +from .base_agent import BaseAgent + +class SemanticReasonerAgent(BaseAgent): + def execute(self, ctx: Dict[str, Any]): + field = ctx.get("current_field") + candidate = ctx.get("candidates", {}).get(field) + return candidate or f"" \ No newline at end of file diff --git a/src/agents/table_agent.py b/src/agents/table_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..d8bd5548d5b4134a1c7b1a28867ae7b3fd8ce66b --- /dev/null +++ b/src/agents/table_agent.py @@ -0,0 +1,46 @@ +"""Extract tables from PDF using Azure Document Intelligence.""" +from typing import Dict, Any +import logging +from .base_agent import BaseAgent +from services.azure_di_service import AzureDIService + +class TableAgent(BaseAgent): + def __init__(self, settings): + self.service = AzureDIService(settings.AZURE_DI_ENDPOINT, settings.AZURE_DI_KEY) + self.logger = logging.getLogger(__name__) + + def execute(self, ctx: Dict[str, Any]): + """Extract tables from PDF.""" + try: + pdf_file = ctx.get("pdf_file") + if not pdf_file: + self.logger.error("No PDF file found in context") + return {} + + # Get the current position of the file pointer + current_pos = pdf_file.tell() + self.logger.info(f"Current file position: {current_pos}") + + # Reset to beginning if not at start + if current_pos != 0: + self.logger.info("Resetting file pointer to beginning") + pdf_file.seek(0) + + # Read the file + pdf_bytes = pdf_file.read() + self.logger.info(f"Read {len(pdf_bytes)} bytes from PDF") + + # Extract content using Azure DI + result = self.service.extract_tables(pdf_bytes) + + # Store both text and tables in context + ctx["text"] = result["text"] + ctx["tables"] = result["tables"] + + self.logger.info(f"Extracted {len(result['text'])} characters of text and {len(result['tables'])} tables") + return result + + except Exception as e: + self.logger.error(f"Error in TableAgent: {str(e)}") + self.logger.exception("Full traceback:") + return {} \ No newline at end of file diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000000000000000000000000000000000000..2be63e34cfd3356942096883684f56bd0e17cc2b --- /dev/null +++ b/src/app.py @@ -0,0 +1,364 @@ +"""Streamlit front‑end entry‑point.""" +import yaml +import json +import streamlit as st +import logging +from dotenv import load_dotenv +from orchestrator.planner import Planner +from orchestrator.executor import Executor +from config.settings import settings +import fitz # PyMuPDF local import to avoid heavy load on startup +import pandas as pd +from datetime import datetime +import io +import sys +from io import StringIO + +# Create a custom stream handler to capture logs +class LogCaptureHandler(logging.StreamHandler): + def __init__(self): + super().__init__() + self.logs = [] + + def emit(self, record): + try: + msg = self.format(record) + self.logs.append(msg) + except Exception: + self.handleError(record) + + def get_logs(self): + return "\n".join(self.logs) + + def clear(self): + self.logs = [] + +# Initialize session state for storing execution history +if 'execution_history' not in st.session_state: + st.session_state.execution_history = [] + +# Set up logging capture +log_capture = LogCaptureHandler() +log_capture.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + +# Configure root logger +root_logger = logging.getLogger() +root_logger.setLevel(logging.INFO) +root_logger.addHandler(log_capture) + +# Configure specific loggers +for logger_name in ['orchestrator', 'agents', 'services']: + logger = logging.getLogger(logger_name) + logger.setLevel(logging.INFO) + logger.addHandler(log_capture) + +load_dotenv() + +st.set_page_config(page_title="PDF Field Extractor", layout="wide") + +# Sidebar navigation +st.sidebar.title("Navigation") +page = st.sidebar.radio("Go to", ["Documentation", "Traces", "Execution"]) + +# Documentation Page +if page == "Documentation": + st.title("Deep‑Research PDF Field Extractor (POC)") + + st.markdown(""" + This system uses a multi-step pipeline to extract fields from PDFs: + 1. **Document Intelligence**: Extracts text and tables from PDFs using Azure Document Intelligence + 2. **Semantic Indexing**: Creates searchable chunks with embeddings for semantic search + 3. **Field Extraction**: Uses a two-step approach: + - First attempts page-by-page scanning for precise extraction + - Falls back to semantic search if no value is found + 4. **Validation**: Ensures extracted values are correct and properly formatted + 5. **Confidence Scoring**: Identifies which extractions need review + """) + + st.markdown(""" + ### Agent Descriptions + #### DocumentIntelligenceAgent + - Uses Azure Document Intelligence to extract text and tables + - Preserves document layout and structure + - Outputs both raw text and formatted tables as HTML + - Handles complex document layouts and tables + + #### IndexAgent + - Creates semantic search index from extracted content + - Splits document into manageable chunks with metadata + - Generates embeddings for semantic search + - Provides both chunk-based and full-text search capabilities + - Includes chunk statistics and visualization + + #### FieldMapper + - Implements a two-step field extraction strategy: + 1. Page-by-page scanning for precise extraction + 2. Semantic search fallback if no value found + - Uses document context to improve extraction accuracy + - Handles multiple potential values with confidence scoring + - Returns structured JSON responses with value details + + #### SemanticReasoner + - Validates and cleans up candidate values + - Uses domain knowledge to ensure values make sense + - Can reformat values to standard format + - Returns `` if value is wrong/missing + + #### ConfidenceScorer + - Assigns confidence score (0-1) to each extraction + - Helps identify which extractions need review + - Can trigger follow-up queries when confidence is low + + #### QueryGenerator + - Generates follow-up questions when confidence is low + - Creates concise questions (≤12 words) + - Helps guide system to better extractions + """) + +# Traces Page +elif page == "Traces": + st.title("Execution Traces") + + if not st.session_state.execution_history: + st.info("No execution traces available yet. Run an extraction to see traces here.") + else: + # Create a DataFrame from the execution history + history_data = [] + for record in st.session_state.execution_history: + history_data.append({ + "filename": record["filename"], + "datetime": record["datetime"], + "fields": ", ".join(record.get("fields", [])), + "logs": record.get("logs", []), + "results": record.get("results", None) + }) + + history_df = pd.DataFrame(history_data) + + # Display column headers + col1, col2, col3, col4, col5 = st.columns([2, 2, 3, 1, 1]) + with col1: + st.markdown("**Filename**") + with col2: + st.markdown("**Timestamp**") + with col3: + st.markdown("**Fields**") + with col4: + st.markdown("**Logs**") + with col5: + st.markdown("**Results**") + + st.markdown("---") # Add a separator line + + # Display the table with download buttons + for idx, row in history_df.iterrows(): + col1, col2, col3, col4, col5 = st.columns([2, 2, 3, 1, 1]) + with col1: + st.write(row["filename"]) + with col2: + st.write(row["datetime"]) + with col3: + st.write(row["fields"]) + with col4: + if row["logs"]: # Check if we have any logs + st.download_button( + "Download Logs", + row["logs"], # Use the stored logs + file_name=f"logs_{row['filename']}_{row['datetime']}.txt", + key=f"logs_dl_{idx}" + ) + else: + st.write("No Logs") + with col5: + if row["results"] is not None: + results_df = pd.DataFrame(row["results"]) + st.download_button( + "Download Results", + results_df.to_csv(index=False), + file_name=f"results_{row['filename']}_{row['datetime']}.csv", + key=f"results_dl_{idx}" + ) + else: + st.write("No Results") + st.markdown("---") # Add a separator line between rows + +# Execution Page +else: # page == "Execution" + st.title("Deep‑Research PDF Field Extractor (POC)") + + pdf_file = st.file_uploader("Upload PDF", type=["pdf"]) + fields_str = st.text_input("Fields (comma‑separated)", "Protein Lot, Chain, Residue") + desc_blob = st.text_area("Field descriptions / rules (YAML, optional)") + + def flatten_json_response(json_data, fields): + """Flatten the nested JSON response into a tabular structure with dynamic columns.""" + logger = logging.getLogger(__name__) + logger.info("Starting flatten_json_response") + logger.info(f"Input fields: {fields}") + + # Handle the case where the response is a string + if isinstance(json_data, str): + logger.info("Input is a string, attempting to parse as JSON") + try: + json_data = json.loads(json_data) + logger.info("Successfully parsed JSON string") + except json.JSONDecodeError as e: + logger.error(f"Failed to parse JSON string: {e}") + return pd.DataFrame(columns=fields) + + # If the data is wrapped in an array, get the first item + if isinstance(json_data, list) and len(json_data) > 0: + logger.info("Data is wrapped in an array, extracting first item") + json_data = json_data[0] + + # If the data is a dictionary with numeric keys, get the first value + if isinstance(json_data, dict): + keys = list(json_data.keys()) + logger.info(f"Checking dictionary keys: {keys}") + # Check if all keys are integers or string representations of integers + if all(isinstance(k, int) or (isinstance(k, str) and k.isdigit()) for k in keys): + logger.info("Data has numeric keys, extracting first value") + first_key = sorted(keys, key=lambda x: int(x) if isinstance(x, str) else x)[0] + json_data = json_data[first_key] + logger.info(f"Extracted data from key '{first_key}'") + + logger.info(f"JSON data keys: {list(json_data.keys()) if isinstance(json_data, dict) else 'Not a dict'}") + + # Create a list to store rows + rows = [] + + # Get the length of the first array to determine number of rows + if isinstance(json_data, dict) and len(json_data) > 0: + first_field = list(json_data.keys())[0] + num_rows = len(json_data[first_field]) if isinstance(json_data[first_field], list) else 1 + logger.info(f"Number of rows to process: {num_rows}") + + # Create a row for each index + for i in range(num_rows): + logger.debug(f"Processing row {i}") + row = {} + for field in fields: + if field in json_data and isinstance(json_data[field], list) and i < len(json_data[field]): + row[field] = json_data[field][i] + logger.debug(f"Field '{field}' value at index {i}: {json_data[field][i]}") + else: + row[field] = None + logger.debug(f"Field '{field}' not found or index {i} out of bounds") + rows.append(row) + else: + logger.error(f"Unexpected data structure: {type(json_data)}") + return pd.DataFrame(columns=fields) + + # Create DataFrame with all requested fields as columns + df = pd.DataFrame(rows) + logger.info(f"Created DataFrame with shape: {df.shape}") + logger.info(f"DataFrame columns: {df.columns.tolist()}") + + # Ensure columns are in the same order as the fields list + df = df[fields] + logger.info(f"Final DataFrame columns after reordering: {df.columns.tolist()}") + + return df + + if st.button("Run extraction") and pdf_file: + field_list = [f.strip() for f in fields_str.split(",") if f.strip()] + field_descs = yaml.safe_load(desc_blob) if desc_blob.strip() else {} + + try: + with st.spinner("Planning …"): + # quick first-page text preview to give LLM document context + doc = fitz.open(stream=pdf_file.getvalue(), filetype="pdf") # type: ignore[arg-type] + preview = "\n".join(page.get_text() for page in doc[:10])[:20000] # first 2 pages, 2k chars + + planner = Planner() + plan = planner.build_plan( + pdf_meta={"filename": pdf_file.name}, + doc_preview=preview, + fields=field_list, + field_descs=field_descs, + ) + + # Add a visual separator + st.markdown("---") + + with st.spinner("Executing …"): + executor = Executor(settings=settings) + results, logs = executor.run(plan, pdf_file) + + # Add detailed logging about what executor returned + logger.info(f"Executor returned results of type: {type(results)}") + logger.info(f"Results content: {results}") + + # Check if results is already a DataFrame + if isinstance(results, pd.DataFrame): + logger.info(f"Results is already a DataFrame with shape: {results.shape}") + logger.info(f"DataFrame columns: {results.columns.tolist()}") + logger.info(f"DataFrame head: {results.head()}") + df = results + else: + logger.info("Results is not a DataFrame, calling flatten_json_response") + # Process results using flatten_json_response + df = flatten_json_response(results, field_list) + + # Log final DataFrame info + logger.info(f"Final DataFrame shape: {df.shape}") + logger.info(f"Final DataFrame columns: {df.columns.tolist()}") + if not df.empty: + logger.info(f"Final DataFrame sample: {df.head()}") + + # Store execution in history + execution_record = { + "filename": pdf_file.name, + "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "fields": field_list, + "logs": log_capture.get_logs(), # Store the actual logs + "results": df.to_dict() if not df.empty else None + } + st.session_state.execution_history.append(execution_record) + log_capture.clear() # Clear logs after storing them + + # ----------------- UI: show execution tree ----------------- + st.subheader("Execution trace") + for log in logs: + indent = " " * 4 * log["depth"] + # Add error indicator if there was an error + error_indicator = "❌ " if log.get("error") else "✓ " + # Use a fixed preview text instead of the result + with st.expander(f"{indent}{error_indicator}{log['tool']} – Click to view result"): + st.markdown(f"**Args**: `{log['args']}`", unsafe_allow_html=True) + if log.get("error"): + st.error(f"Error: {log['error']}") + + # Special handling for IndexAgent output + if log['tool'] == "IndexAgent" and isinstance(log["result"], dict): + # Display chunk statistics if available + if "chunk_stats" in log["result"]: + st.markdown("### Chunk Statistics") + # Create a DataFrame for better visualization + stats_df = pd.DataFrame(log["result"]["chunk_stats"]) + st.dataframe(stats_df) + + # Add summary statistics + st.markdown("### Summary") + st.markdown(f""" + - Total chunks: {len(stats_df)} + - Average chunk length: {stats_df['length'].mean():.0f} characters + - Shortest chunk: {stats_df['length'].min()} characters + - Longest chunk: {stats_df['length'].max()} characters + """) + + # Add a bar chart of chunk lengths + st.markdown("### Chunk Length Distribution") + st.bar_chart(stats_df.set_index('chunk_number')['length']) + else: + st.code(log["result"]) + + if not df.empty: + st.success("Done ✓") + st.dataframe(df) + st.download_button("Download CSV", df.to_csv(index=False), "results.csv") + else: + st.warning("No results were extracted. Check the execution trace for errors.") + except Exception as e: + logging.exception("App error:") + st.error(f"An error occurred: {e}") \ No newline at end of file diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/src/config/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/config/__pycache__/__init__.cpython-312.pyc b/src/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0634c50776aba3396b22a79f245c9a210c0f983b Binary files /dev/null and b/src/config/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/config/__pycache__/__init__.cpython-313.pyc b/src/config/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eaa966d5af4a95571245c4a24b2b440dc57292dd Binary files /dev/null and b/src/config/__pycache__/__init__.cpython-313.pyc differ diff --git a/src/config/__pycache__/configurations.cpython-313.pyc b/src/config/__pycache__/configurations.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ad3951519b411a97257a178a60d3d480d336cdf Binary files /dev/null and b/src/config/__pycache__/configurations.cpython-313.pyc differ diff --git a/src/config/__pycache__/settings.cpython-312.pyc b/src/config/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0609557e2ddd04deea6bf0aecf42c845a452949d Binary files /dev/null and b/src/config/__pycache__/settings.cpython-312.pyc differ diff --git a/src/config/__pycache__/settings.cpython-313.pyc b/src/config/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d46b922da7e7fd5c3248eede75013132c7e37b14 Binary files /dev/null and b/src/config/__pycache__/settings.cpython-313.pyc differ diff --git a/src/config/field_rules.yaml b/src/config/field_rules.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ea53eb22c81632285ee7947d832ff0b8ea5df075 --- /dev/null +++ b/src/config/field_rules.yaml @@ -0,0 +1,4 @@ +Chain: + description: Heavy vs Light chain based on Seq Loc prefix. + rules: + starts_with: {L: "Light", H: "Heavy"} \ No newline at end of file diff --git a/src/config/prompts.yaml b/src/config/prompts.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f87278594475c02f327fee2303c32d92b9ca310 --- /dev/null +++ b/src/config/prompts.yaml @@ -0,0 +1,53 @@ +planner: | + You are "Doc-to-Record Planner v1" – an expert at designing multi-step + extraction pipelines that convert an arbitrary document into a flat record + of user-requested fields. + + You will be given: + – doc_preview: a few kB of raw text from the uploaded document (may include table HTML). + – fields: the list of field names the user wants extracted. + – pdf_meta / field_descriptions for extra context. + + Available tools (use exactly these names in the JSON): + PDFAgent → extracts raw text from the full PDF. + TableAgent → calls Azure Document Intelligence to get HTML tables. + FieldMapper → maps one field name to a candidate value. + + Control-flow helper: + ForEachField – loops over every requested field and executes the nested "loop" array. + + Output JSON **only** with this schema (no markdown): + { + "fields": [], + "steps": [ + {"tool": "PDFAgent", "args": {}}, + {"tool": "TableAgent", "args": {}}, + {"tool": "ForEachField", + "loop": [ + {"tool": "FieldMapper", "args": {"field": "$field"}} + ]} + ] + } + + Always include PDFAgent and TableAgent first, then the ForEachField loop. Keep plans short and deterministic. + +field_mapper: | + You are "FieldMapper v1" – a precision extractor. + Given: + • field (target field name) + • context (snippet of raw PDF text or table row) + Return **only** the best candidate value (no extra words). + +semantic_reasoner: | + You are "Semantic Reasoner v1". + Validate the candidate value for a field using domain knowledge and the surrounding context. + If the candidate is obviously wrong / absent output (same token as placeholder). + Otherwise output a cleaned, final value – no explanation text. + +confidence_scorer: | + You are "Confidence Scorer". For the given field and candidate value assign a confidence between 0 and 1. + Output **only** the float. + +query_generator: | + You are "Follow-up Query Generator". The previous candidate for a field was low-confidence. + Formulate a concise follow-up question (<=12 words) that, when answered, would help identify the field value. \ No newline at end of file diff --git a/src/config/settings.py b/src/config/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..ebf07553f73644a2368b76c8bc0316eea9037b45 --- /dev/null +++ b/src/config/settings.py @@ -0,0 +1,18 @@ +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + +class Settings(BaseSettings): + OPENAI_API_KEY: str = Field("", env="OPENAI_API_KEY") + AZURE_DI_ENDPOINT: str = Field("", env="AZURE_DI_ENDPOINT") + AZURE_DI_KEY: str = Field("", env="AZURE_DI_KEY") + + # Azure OpenAI + AZURE_OPENAI_ENDPOINT: str = Field("", env="AZURE_OPENAI_ENDPOINT") + AZURE_OPENAI_DEPLOYMENT: str = Field("", env="AZURE_OPENAI_DEPLOYMENT") + AZURE_OPENAI_API_VERSION: str = Field("2024-02-15-preview", env="AZURE_OPENAI_API_VERSION") + AZURE_OPENAI_API_KEY: str = Field("", env="AZURE_OPENAI_API_KEY") + AZURE_OPENAI_EMBEDDING_MODEL: str = Field("text-embedding-3-small", env="AZURE_OPENAI_EMBEDDING_MODEL") + + model_config: SettingsConfigDict = {"env_file": ".env"} + +settings = Settings() \ No newline at end of file diff --git a/src/docker/Dockerfile b/src/docker/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..26be0c7fafa2783cf5e5a5720ad34565717bbc40 --- /dev/null +++ b/src/docker/Dockerfile @@ -0,0 +1,21 @@ +# ---------- builder ------------- + FROM python:3.11-slim AS builder + RUN pip install --no-cache-dir uv + + WORKDIR /app + COPY pyproject.toml ./ + RUN uv pip install -r <(uv pip compile --quiet) \ + && uv pip freeze > /installed.txt # layer cache + + COPY . . + + # ---------- runtime ------------- + FROM python:3.11-slim + ENV PYTHONUNBUFFERED=1 + WORKDIR /app + COPY --from=builder /installed.txt /installed.txt + RUN xargs -a /installed.txt pip install --no-cache-dir + + COPY . . + + ENTRYPOINT ["bash", "docker/entrypoint.sh"] \ No newline at end of file diff --git a/src/docker/entrypoint.sh b/src/docker/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..2ad9676be52553bb3337f24240ff67e92b8658e2 --- /dev/null +++ b/src/docker/entrypoint.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +source .env # load secrets (mounted at run) +streamlit run app.py --server.port ${PORT:-8501} diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000000000000000000000000000000000000..2af45ecf7b391479626d5fd8e71508b04250ad4e --- /dev/null +++ b/src/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from filetorecord!") + + +if __name__ == "__main__": + main() diff --git a/src/orchestrator/__init__.py b/src/orchestrator/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/src/orchestrator/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/orchestrator/__pycache__/__init__.cpython-312.pyc b/src/orchestrator/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..369f1187aaed6603278fd4e8949fab91c6970b25 Binary files /dev/null and b/src/orchestrator/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/orchestrator/__pycache__/__init__.cpython-313.pyc b/src/orchestrator/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f1a1ccd0ac7080dcbfb11c1b1a0b30325eddd4b Binary files /dev/null and b/src/orchestrator/__pycache__/__init__.cpython-313.pyc differ diff --git a/src/orchestrator/__pycache__/executor.cpython-312.pyc b/src/orchestrator/__pycache__/executor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4212196d7bf2786289056bda69032b767b7d1f90 Binary files /dev/null and b/src/orchestrator/__pycache__/executor.cpython-312.pyc differ diff --git a/src/orchestrator/__pycache__/executor.cpython-313.pyc b/src/orchestrator/__pycache__/executor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a87422d30834a2c710470b59e120c9f35e58aee Binary files /dev/null and b/src/orchestrator/__pycache__/executor.cpython-313.pyc differ diff --git a/src/orchestrator/__pycache__/planner.cpython-312.pyc b/src/orchestrator/__pycache__/planner.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8de1ec0a110203ac06f7ddf97bf36f85d98b268c Binary files /dev/null and b/src/orchestrator/__pycache__/planner.cpython-312.pyc differ diff --git a/src/orchestrator/__pycache__/planner.cpython-313.pyc b/src/orchestrator/__pycache__/planner.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c44eb160a511cf848cc5002edde94a935cff61be Binary files /dev/null and b/src/orchestrator/__pycache__/planner.cpython-313.pyc differ diff --git a/src/orchestrator/executor.py b/src/orchestrator/executor.py new file mode 100644 index 0000000000000000000000000000000000000000..981a504e754effc99c64eac1918f4ee7060973cb --- /dev/null +++ b/src/orchestrator/executor.py @@ -0,0 +1,181 @@ +"""Interpret a planner JSON and call agents.""" +from __future__ import annotations + +from typing import Any, Dict, List +import traceback +import logging +import json + +import pandas as pd + +from agents.pdf_agent import PDFAgent +from agents.table_agent import TableAgent +from agents.index_agent import IndexAgent +from agents.field_mapper_agent import FieldMapperAgent +from agents.semantic_reasoner import SemanticReasonerAgent +from agents.confidence_scorer import ConfidenceScorer +from agents.query_generator import QueryGenerator + + +class Executor: + def __init__(self, settings): + # map name → instance + self.tools = { + "PDFAgent": PDFAgent(), + "TableAgent": TableAgent(settings), + "IndexAgent": IndexAgent(), + "FieldMapper": FieldMapperAgent(), + "SemanticReasoner": SemanticReasonerAgent(), + "ConfidenceScorer": ConfidenceScorer(), + "QueryGenerator": QueryGenerator(), + } + + self.logs: List[Dict[str, Any]] = [] + self.logger = logging.getLogger(__name__) + + # --------------------------------------------------------- + def run(self, plan: Dict[str, Any], pdf_file) -> tuple[pd.DataFrame, List[Dict[str, Any]]]: # noqa: D401 + # Validate fields are strings + fields = plan.get("fields", []) + if not all(isinstance(f, str) for f in fields): + self.logger.error(f"Invalid fields format. Expected strings, got: {fields}") + fields = [str(f) for f in fields] # Convert to strings as fallback + + ctx: Dict[str, Any] = { + "pdf_file": pdf_file, + "fields": fields, + "results": [], + "conf": 1.0, + "pdf_meta": plan.get("pdf_meta", {}), # Include the plan's metadata + } + + try: + for step in plan["steps"]: + self._execute_step(step, ctx, depth=0) + except Exception as e: + self.logger.error(f"Error during execution: {str(e)}") + self.logger.error(traceback.format_exc()) + # Don't re-raise, let the UI show the partial results + + # Handle results conversion to DataFrame + if ctx["results"]: + # If results is a list of dictionaries with field arrays (tabular format) + if isinstance(ctx["results"], list) and len(ctx["results"]) > 0: + first_item = ctx["results"][0] + # Check if it's the tabular format (dict with field arrays) + if isinstance(first_item, dict) and all(isinstance(v, list) for v in first_item.values()): + # Convert tabular format to DataFrame + # Get the length of the first array + first_field = list(first_item.keys())[0] + num_rows = len(first_item[first_field]) + + # Create rows by iterating through indices + rows = [] + for i in range(num_rows): + row = {} + for field, values in first_item.items(): + if i < len(values): + row[field] = values[i] + else: + row[field] = None + rows.append(row) + + df = pd.DataFrame(rows) + else: + # Regular list of dicts format + df = pd.DataFrame(ctx["results"]) + elif isinstance(ctx["results"], dict): + # Check if it's a single dict with field arrays (tabular format) + if all(isinstance(v, list) for v in ctx["results"].values()): + # Convert tabular format to DataFrame + # Get the length of the first array + first_field = list(ctx["results"].keys())[0] + num_rows = len(ctx["results"][first_field]) + + # Create rows by iterating through indices + rows = [] + for i in range(num_rows): + row = {} + for field, values in ctx["results"].items(): + if i < len(values): + row[field] = values[i] + else: + row[field] = None + rows.append(row) + + df = pd.DataFrame(rows) + else: + # Single dict format + df = pd.DataFrame([ctx["results"]]) + else: + df = pd.DataFrame() + else: + df = pd.DataFrame() + + return df, self.logs + + # --------------------------------------------------------- + def _execute_step(self, step: Dict[str, Any], ctx: Dict[str, Any], depth: int): + """Recursive interpreter (minimal – handles `loop` & simple `if`).""" + if "tool" not in step: + return + tool_name = step["tool"] + + if tool_name == "ForEachField": + for field in ctx["fields"]: + # Ensure field is a string + field_str = str(field) + ctx["current_field"] = field_str + for sub in step["loop"]: + self._execute_step(sub, ctx, depth + 1) + return + + # classic tool run + tool = self.tools[tool_name] + try: + result = tool.execute(ctx) + error = None + except Exception as e: + self.logger.error(f"Error in {tool_name}: {str(e)}") + self.logger.error(traceback.format_exc()) + result = f"Error: {str(e)}" + error = str(e) + + # log + log_entry = { + "depth": depth, + "tool": tool_name, + "args": step.get("args", {}), + "result": str(result)[:500], + "error": error, + } + + # Add more detailed logging for specific tools + if tool_name == "IndexAgent": + if isinstance(result, dict): + num_chunks = len(result.get("chunks", [])) + num_embeddings = len(result.get("embeddings", [])) + log_entry["result"] = f"Created index with {num_chunks} chunks and {num_embeddings} embeddings" + + self.logs.append(log_entry) + + # stash results + if tool_name == "FieldMapper": + # Store the results directly in ctx["results"] + if isinstance(result, str): + try: + result_dict = json.loads(result) + ctx["results"] = result_dict # Store the entire dictionary, not just first field + except json.JSONDecodeError: + self.logger.error(f"Failed to parse FieldMapper result as JSON: {result}") + ctx["results"] = {} + else: + ctx["results"] = result + elif tool_name == "SemanticReasoner": + ctx["results"][ctx["current_field"]] = result + elif tool_name == "ConfidenceScorer": + ctx["conf"] = result + elif tool_name == "IndexAgent": + if result: # Only store if we got a valid result + ctx["index"] = result + self.logger.info(f"Stored index with {len(result.get('chunks', []))} chunks") \ No newline at end of file diff --git a/src/orchestrator/planner.py b/src/orchestrator/planner.py new file mode 100644 index 0000000000000000000000000000000000000000..5d205fd64de43b73dce67187006c5a7d0cbc13a3 --- /dev/null +++ b/src/orchestrator/planner.py @@ -0,0 +1,144 @@ +"""Planner: turns a user request into a JSON tool-plan via Azure OpenAI *Responses*.""" + +from __future__ import annotations + +import json +import logging +from pathlib import Path +from typing import Dict, List, Any + +import yaml + +from services.llm_client import LLMClient +from config.settings import settings + + +_PROMPTS_FILE = Path(__file__).parent.parent / "config" / "prompts.yaml" + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class Planner: + """Generate a plan with the Responses API; fall back to a static template if parsing fails.""" + + def __init__(self) -> None: + self.prompt_template = self._load_prompt("planner") + self.llm = LLMClient(settings) + logger.info("Planner initialized with prompt template") + + # -------------------------------------------------- + def build_plan( + self, + pdf_meta: Dict[str, Any], + fields: List[str], + doc_preview: str | None = None, + field_descs: Dict | None = None, + ) -> Dict[str, Any]: + """Return a JSON dict representing the execution plan.""" + + user_context = { + "pdf_meta": pdf_meta, + "doc_preview": doc_preview or "", + "fields": fields, + "field_descriptions": field_descs or {}, + } + + logger.info(f"Building plan for fields: {fields}") + logger.debug(f"User context: {user_context}") + + prompt = self.prompt_template.format_json(**user_context) + logger.debug(f"Generated prompt: {prompt}") + + try: + logger.info("Calling LLM to generate plan") + raw = self.llm.responses(prompt, temperature=0.0) + logger.debug(f"Raw LLM response: {raw}") + + try: + logger.info("Parsing LLM response as JSON") + plan = json.loads(raw) + logger.debug(f"Parsed plan: {plan}") + + # ensure minimal structure exists + if "steps" in plan and "fields" in plan: + logger.info("Plan successfully generated with required structure") + # Add pdf_meta to the plan + plan["pdf_meta"] = pdf_meta + return plan + else: + missing_keys = [] + if "steps" not in plan: + missing_keys.append("steps") + if "fields" not in plan: + missing_keys.append("fields") + logger.error(f"Planner: LLM output missing required keys: {missing_keys}. Output: {raw}") + except json.JSONDecodeError as parse_exc: + logger.error(f"Planner: Failed to parse LLM output as JSON. Output: {raw}") + logger.error(f"JSON parsing error: {parse_exc}") + except Exception as parse_exc: + logger.error(f"Planner: Unexpected error parsing LLM output: {parse_exc}") + logger.error(f"LLM output: {raw}") + except Exception as llm_exc: + logger.error(f"Planner: LLM call failed: {llm_exc}") + logger.exception("Full traceback:") + + # ---------- fallback static plan ---------- + logger.info("Falling back to static plan") + return self._static_plan(fields) + + # -------------------------------------------------- + @staticmethod + def _load_prompt(name: str): + try: + data = yaml.safe_load(_PROMPTS_FILE.read_text()) + logger.debug(f"Loaded prompt template for '{name}'") + except Exception as e: + logger.error(f"Failed to load prompt template: {e}") + data = {} + + class _Fmt: + def __init__(self, s: str): + self.s = s + + def format_json(self, **kwargs): + # Format the template with the provided fields + fields = kwargs.get("fields", []) + field_descriptions = kwargs.get("field_descriptions", {}) + doc_preview = kwargs.get("doc_preview", "") + pdf_meta = kwargs.get("pdf_meta", {}) + + # Create a formatted string with the actual values + formatted = self.s + if fields: + # Ensure fields is a flat list of strings + fields_json = json.dumps([str(f) for f in fields]) + formatted = formatted.replace("", fields_json) + if field_descriptions: + formatted = formatted.replace("field_descriptions for extra context", f"field descriptions: {json.dumps(field_descriptions)}") + if doc_preview: + formatted = formatted.replace("a few kB of raw text from the uploaded document", f"document preview: {doc_preview[:1000]}...") + if pdf_meta: + formatted = formatted.replace("pdf_meta / field_descriptions for extra context", f"document metadata: {json.dumps(pdf_meta)}") + + return formatted + + return _Fmt(data.get(name, "You are a planning agent. Produce a JSON tool plan.")) + + # -------------------------------------------------- + @staticmethod + def _static_plan(fields: List[str]) -> Dict[str, Any]: + """Return a hard-coded plan to guarantee offline functionality.""" + logger.info("Generating static fallback plan") + steps = [ + {"tool": "PDFAgent", "args": {}}, + {"tool": "TableAgent", "args": {}}, + { + "tool": "ForEachField", + "loop": [ + {"tool": "FieldMapper", "args": {"field": "$field"}}, + ], + }, + ] + return {"steps": steps, "fields": fields, "pdf_meta": {}} # Include empty pdf_meta in static plan \ No newline at end of file diff --git a/src/pyproject.toml b/src/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..f2deb4c91e653f4d9429114795290f3012c45a70 --- /dev/null +++ b/src/pyproject.toml @@ -0,0 +1,22 @@ +# pyproject.toml for project dependencies and uv-lock + +[project] +name = "filetorecord" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "azure-ai-documentintelligence>=1.0.2", + "openai>=1.78.1", + "openai-agents>=0.0.14", + "pydantic>=2.11.4", + "pymupdf>=1.25.5", + "python-dotenv>=1.1.0", + "pydantic-settings>=2.1.0", + "pyyaml>=6.0.2", + "ruff>=0.11.9", + "streamlit>=1.45.1", + "pandas>=2.2.3", + "uvicorn>=0.34.2", +] diff --git a/src/requirements-dev.txt b/src/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..93809790be22e4f62ca33f5e6b54d558cee8ca4e --- /dev/null +++ b/src/requirements-dev.txt @@ -0,0 +1,3 @@ +pytest +black +langfuse \ No newline at end of file diff --git a/src/services/__init__.py b/src/services/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/src/services/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/services/__pycache__/__init__.cpython-312.pyc b/src/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff06e8d387d931c6af2da7e83cd9cb12a5bac0fc Binary files /dev/null and b/src/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/services/__pycache__/__init__.cpython-313.pyc b/src/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2b6ee7d73402caee164faf9177f706adfa003a6 Binary files /dev/null and b/src/services/__pycache__/__init__.cpython-313.pyc differ diff --git a/src/services/__pycache__/azure_di_service.cpython-312.pyc b/src/services/__pycache__/azure_di_service.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ec933f7a4ab7c38a3dde5627df6793628d52fc0 Binary files /dev/null and b/src/services/__pycache__/azure_di_service.cpython-312.pyc differ diff --git a/src/services/__pycache__/azure_di_service.cpython-313.pyc b/src/services/__pycache__/azure_di_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cb5912bd2e7e22d5a8aae7b04aac98f86c6c9c9 Binary files /dev/null and b/src/services/__pycache__/azure_di_service.cpython-313.pyc differ diff --git a/src/services/__pycache__/embedding_client.cpython-312.pyc b/src/services/__pycache__/embedding_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8041668da3468043633ccc940319fbe71a489616 Binary files /dev/null and b/src/services/__pycache__/embedding_client.cpython-312.pyc differ diff --git a/src/services/__pycache__/embedding_client.cpython-313.pyc b/src/services/__pycache__/embedding_client.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cdcc76da43d8b9599bb4520c0c8baae24e29c49 Binary files /dev/null and b/src/services/__pycache__/embedding_client.cpython-313.pyc differ diff --git a/src/services/__pycache__/llm_client.cpython-312.pyc b/src/services/__pycache__/llm_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fb4d76c8f12d09d320a22a145d7c9f6e27ce7da Binary files /dev/null and b/src/services/__pycache__/llm_client.cpython-312.pyc differ diff --git a/src/services/__pycache__/llm_client.cpython-313.pyc b/src/services/__pycache__/llm_client.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e34b5407f7403f5644a445ce5353c17321a1b9e Binary files /dev/null and b/src/services/__pycache__/llm_client.cpython-313.pyc differ diff --git a/src/services/azure_di_service.py b/src/services/azure_di_service.py new file mode 100644 index 0000000000000000000000000000000000000000..196828cacdac68b445057c926edfaf3102f9442c --- /dev/null +++ b/src/services/azure_di_service.py @@ -0,0 +1,62 @@ +"""Real wrapper goes here – currently not used by stub agents.""" +import logging +from azure.ai.documentintelligence import DocumentIntelligenceClient +from azure.core.credentials import AzureKeyCredential +from azure.core.exceptions import HttpResponseError + +class AzureDIService: + def __init__(self, endpoint: str, key: str): + self.client = DocumentIntelligenceClient(endpoint=endpoint, credential=AzureKeyCredential(key)) + self.logger = logging.getLogger(__name__) + + def extract_tables(self, pdf_bytes: bytes): + try: + self.logger.info("Starting document analysis with Azure Document Intelligence") + + # Analyze the entire document at once + poller = self.client.begin_analyze_document("prebuilt-layout", body=pdf_bytes) + result = poller.result() + + # Extract text content + text_content = "" + if hasattr(result, "content"): + text_content = result.content + self.logger.info(f"Extracted {len(text_content)} characters of text") + + # Extract tables as HTML strings + tables = [] + if hasattr(result, "tables"): + self.logger.info(f"Found {len(result.tables)} tables in the document") + for table in result.tables: + # Simple HTML rendering for demo; you can improve this + html = "" + for row_idx in range(table.row_count): + html += "" + for col_idx in range(table.column_count): + cell = next((c for c in table.cells if c.row_index == row_idx and c.column_index == col_idx), None) + html += f"" + html += "" + html += "
{cell.content if cell else ''}
" + tables.append(html) + else: + self.logger.warning("No tables found in the document") + + # Return both text and tables + return { + "text": text_content, + "tables": tables + } + + except HttpResponseError as e: + self.logger.error(f"Azure Document Intelligence API error: {str(e)}") + if hasattr(e, 'response') and hasattr(e.response, 'json'): + try: + error_details = e.response.json() + self.logger.error(f"Error details: {error_details}") + except: + pass + raise + except Exception as e: + self.logger.error(f"Unexpected error during document analysis: {str(e)}") + self.logger.exception("Full traceback:") + raise \ No newline at end of file diff --git a/src/services/embedding_client.py b/src/services/embedding_client.py new file mode 100644 index 0000000000000000000000000000000000000000..71cbc0377193d9f713173285c1146e165cbd26b1 --- /dev/null +++ b/src/services/embedding_client.py @@ -0,0 +1,30 @@ +"""Azure OpenAI embeddings client.""" +from typing import List +import logging +from openai import AzureOpenAI +from config.settings import settings + +class EmbeddingClient: + def __init__(self): + self.client = AzureOpenAI( + api_key=settings.AZURE_OPENAI_API_KEY, + api_version=settings.AZURE_OPENAI_API_VERSION, + azure_endpoint=settings.AZURE_OPENAI_ENDPOINT + ) + self.logger = logging.getLogger(__name__) + + def embed(self, texts: List[str]) -> List[List[float]]: + """Get embeddings for a list of texts using Azure OpenAI.""" + try: + self.logger.info(f"Getting embeddings for {len(texts)} texts") + response = self.client.embeddings.create( + model=settings.AZURE_OPENAI_EMBEDDING_MODEL, + input=texts + ) + embeddings = [item.embedding for item in response.data] + self.logger.info(f"Successfully generated {len(embeddings)} embeddings") + return embeddings + except Exception as e: + self.logger.error(f"Error generating embeddings: {str(e)}") + # Return zero vectors as fallback + return [[0.0] * 1536 for _ in texts] \ No newline at end of file diff --git a/src/services/llm_client.py b/src/services/llm_client.py new file mode 100644 index 0000000000000000000000000000000000000000..fb027c1c48110b18730448c14aaa241e0678412b --- /dev/null +++ b/src/services/llm_client.py @@ -0,0 +1,58 @@ +"""Azure-OpenAI wrapper that exposes the *Responses* API. + +Keeps the rest of the codebase insulated from SDK / vendor details. +""" + +from __future__ import annotations + +from typing import Any, List + +import openai +import logging + + +class LLMClient: + """Thin wrapper around ``openai.responses`` using Azure endpoints.""" + + def __init__(self, settings): + # Configure the global client for Azure + openai.api_type = "azure" + openai.api_key = settings.OPENAI_API_KEY or settings.AZURE_OPENAI_API_KEY + openai.api_base = settings.AZURE_OPENAI_ENDPOINT + openai.api_version = settings.AZURE_OPENAI_API_VERSION + + self._deployment = settings.AZURE_OPENAI_DEPLOYMENT + + # -------------------------------------------------- + def responses(self, prompt: str, tools: List[dict] | None = None, **kwargs: Any) -> str: + """Call the Responses API and return the assistant content as string.""" + resp = openai.responses.create( + input=prompt, + model=self._deployment, + tools=tools or [], + **kwargs, + ) + # Log the raw response for debugging + logging.debug(f"LLM raw response: {resp}") + + # Extract the text content from the response + if hasattr(resp, "output") and isinstance(resp.output, list): + # Handle list of ResponseOutputMessage objects + for message in resp.output: + if hasattr(message, "content") and isinstance(message.content, list): + for content in message.content: + if hasattr(content, "text"): + return content.text + + # Fallback methods if the above doesn't work + if hasattr(resp, "output"): + return resp.output + elif hasattr(resp, "response"): + return resp.response + elif hasattr(resp, "content"): + return resp.content + elif hasattr(resp, "data"): + return resp.data + else: + logging.error(f"Could not extract text from response: {resp}") + return str(resp) \ No newline at end of file diff --git a/src/services/retrieval.py b/src/services/retrieval.py new file mode 100644 index 0000000000000000000000000000000000000000..cd69c0ce29ba62f9314d1e622eb43146f1c6bb52 --- /dev/null +++ b/src/services/retrieval.py @@ -0,0 +1,12 @@ +"""Very small in‑memory retriever.""" +from typing import List + +class Retriever: + def __init__(self): + self.store = [] + + def add(self, embedding: List[float], metadata: dict): + self.store.append((embedding, metadata)) + + def search(self, query_emb: List[float], k: int = 5): + return self.store[:k] \ No newline at end of file diff --git a/src/streamlit_app.py b/src/streamlit_app.py deleted file mode 100644 index 99d0b84662681e7d21a08fcce44908344fa86f80..0000000000000000000000000000000000000000 --- a/src/streamlit_app.py +++ /dev/null @@ -1,40 +0,0 @@ -import altair as alt -import numpy as np -import pandas as pd -import streamlit as st - -""" -# Welcome to Streamlit! - -Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:. -If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community -forums](https://discuss.streamlit.io). - -In the meantime, below is an example of what you can do with just a few lines of code: -""" - -num_points = st.slider("Number of points in spiral", 1, 10000, 1100) -num_turns = st.slider("Number of turns in spiral", 1, 300, 31) - -indices = np.linspace(0, 1, num_points) -theta = 2 * np.pi * num_turns * indices -radius = indices - -x = radius * np.cos(theta) -y = radius * np.sin(theta) - -df = pd.DataFrame({ - "x": x, - "y": y, - "idx": indices, - "rand": np.random.randn(num_points), -}) - -st.altair_chart(alt.Chart(df, height=700, width=700) - .mark_point(filled=True) - .encode( - x=alt.X("x", axis=None), - y=alt.Y("y", axis=None), - color=alt.Color("idx", legend=None, scale=alt.Scale()), - size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])), - )) \ No newline at end of file diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/src/tests/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/uv.lock b/src/uv.lock new file mode 100644 index 0000000000000000000000000000000000000000..a8a519d911973d14472fd4fe960f9ae35623138f --- /dev/null +++ b/src/uv.lock @@ -0,0 +1,1021 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "altair" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "typing-extensions", marker = "python_full_version < '3.14'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/b1/f2969c7bdb8ad8bbdda031687defdce2c19afba2aa2c8e1d2a17f78376d8/altair-5.5.0.tar.gz", hash = "sha256:d960ebe6178c56de3855a68c47b516be38640b73fb3b5111c2a9ca90546dd73d", size = 705305, upload-time = "2024-11-23T23:39:58.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200, upload-time = "2024-11-23T23:39:56.4Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "azure-ai-documentintelligence" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940, upload-time = "2025-03-27T02:46:20.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005, upload-time = "2025-03-27T02:46:22.356Z" }, +] + +[[package]] +name = "azure-core" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "six" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/29/ff7a519a315e41c85bab92a7478c6acd1cf0b14353139a08caee4c691f77/azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece", size = 297999, upload-time = "2025-05-01T23:17:27.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/9e/5c87b49f65bb16571599bc789857d0ded2f53014d3392bc88a5d1f3ad779/azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6", size = 207409, upload-time = "2025-05-01T23:17:29.818Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "filetorecord" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "azure-ai-documentintelligence" }, + { name = "openai" }, + { name = "openai-agents" }, + { name = "pandas" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pymupdf" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "ruff" }, + { name = "streamlit" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "azure-ai-documentintelligence", specifier = ">=1.0.2" }, + { name = "openai", specifier = ">=1.78.1" }, + { name = "openai-agents", specifier = ">=0.0.14" }, + { name = "pandas", specifier = ">=2.2.3" }, + { name = "pydantic", specifier = ">=2.11.4" }, + { name = "pydantic-settings", specifier = ">=2.1.0" }, + { name = "pymupdf", specifier = ">=1.25.5" }, + { name = "python-dotenv", specifier = ">=1.1.0" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "ruff", specifier = ">=0.11.9" }, + { name = "streamlit", specifier = ">=1.45.1" }, + { name = "uvicorn", specifier = ">=0.34.2" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196, upload-time = "2025-01-02T07:32:43.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, +] + +[[package]] +name = "griffe" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137, upload-time = "2025-04-23T11:29:09.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303, upload-time = "2025-04-23T11:29:07.145Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604, upload-time = "2025-03-10T21:37:03.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197, upload-time = "2025-03-10T21:36:03.828Z" }, + { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160, upload-time = "2025-03-10T21:36:05.281Z" }, + { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259, upload-time = "2025-03-10T21:36:06.716Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730, upload-time = "2025-03-10T21:36:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126, upload-time = "2025-03-10T21:36:10.934Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668, upload-time = "2025-03-10T21:36:12.468Z" }, + { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350, upload-time = "2025-03-10T21:36:14.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204, upload-time = "2025-03-10T21:36:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322, upload-time = "2025-03-10T21:36:17.016Z" }, + { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184, upload-time = "2025-03-10T21:36:18.47Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504, upload-time = "2025-03-10T21:36:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943, upload-time = "2025-03-10T21:36:21.536Z" }, + { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281, upload-time = "2025-03-10T21:36:22.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273, upload-time = "2025-03-10T21:36:24.414Z" }, + { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867, upload-time = "2025-03-10T21:36:25.843Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mcp" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/13/16b712e8a3be6a736b411df2fc6b4e75eb1d3e99b1cd57a3a1decf17f612/mcp-1.8.1.tar.gz", hash = "sha256:ec0646271d93749f784d2316fb5fe6102fb0d1be788ec70a9e2517e8f2722c0e", size = 265605, upload-time = "2025-05-12T17:33:57.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/5d/91cf0d40e40ae9ecf8d4004e0f9611eea86085aa0b5505493e0ff53972da/mcp-1.8.1-py3-none-any.whl", hash = "sha256:948e03783859fa35abe05b9b6c0a1d5519be452fc079dc8d7f682549591c1770", size = 119761, upload-time = "2025-05-12T17:33:56.136Z" }, +] + +[[package]] +name = "narwhals" +version = "1.39.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/eb/50bd207820ac84b86dee216e2caf3fdf456e59bc854543f8d0ab8637cd66/narwhals-1.39.0.tar.gz", hash = "sha256:6f114def701fc6a23b0523ad53700ae545ad3f5ece041c64dc3ad59e699da43f", size = 471928, upload-time = "2025-05-12T10:01:51.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/58/a12a534269aa5ba9abdf73a9e0deb600297b71cbf7291bca212944663143/narwhals-1.39.0-py3-none-any.whl", hash = "sha256:50b6778f4b4249eb86c88dd17c3907dd004a16ec25b02d5effaf226a2bcfb940", size = 339242, upload-time = "2025-05-12T10:01:49.069Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload-time = "2025-04-19T22:41:16.234Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload-time = "2025-04-19T22:41:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload-time = "2025-04-19T22:41:47.823Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload-time = "2025-04-19T22:41:58.689Z" }, + { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload-time = "2025-04-19T22:42:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload-time = "2025-04-19T22:42:44.433Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload-time = "2025-04-19T22:43:09.928Z" }, + { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload-time = "2025-04-19T22:43:36.983Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload-time = "2025-04-19T22:47:10.523Z" }, + { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload-time = "2025-04-19T22:47:30.253Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload-time = "2025-04-19T22:44:09.251Z" }, + { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload-time = "2025-04-19T22:44:31.383Z" }, + { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload-time = "2025-04-19T22:44:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload-time = "2025-04-19T22:44:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload-time = "2025-04-19T22:45:12.451Z" }, + { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload-time = "2025-04-19T22:45:37.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload-time = "2025-04-19T22:46:01.908Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload-time = "2025-04-19T22:46:28.585Z" }, + { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload-time = "2025-04-19T22:46:39.949Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload-time = "2025-04-19T22:47:00.147Z" }, +] + +[[package]] +name = "openai" +version = "1.78.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/3f/4e5e7b0548a15eabc4a755c93cd5f9564887e3d2fd45b6ff531352e5859d/openai-1.78.1.tar.gz", hash = "sha256:8b26b364531b100df1b961d03560042e5f5be11301d7d49a6cd1a2b9af824dca", size = 442985, upload-time = "2025-05-12T09:59:51.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/4c/3889bc332a6c743751eb78a4bada5761e50a8a847ff0e46c1bd23ce12362/openai-1.78.1-py3-none-any.whl", hash = "sha256:7368bf147ca499804cc408fe68cdb6866a060f38dec961bbc97b04f9d917907e", size = 680917, upload-time = "2025-05-12T09:59:48.948Z" }, +] + +[[package]] +name = "openai-agents" +version = "0.0.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mcp" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "types-requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/b1/586b7413132e9ead6a671f468a1f3465cf3d12e1e73172aa51fcc936142c/openai_agents-0.0.14.tar.gz", hash = "sha256:884d6a1d4be7e77c2442b3ed058abe2c56231a6a05942fe87c6d8ecee72ca491", size = 1339042, upload-time = "2025-04-30T15:19:47.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/50/83bf8515219ffa319a011aa74fc61f4d9036c6a4f49ed9c87d6a4f4484a1/openai_agents-0.0.14-py3-none-any.whl", hash = "sha256:43f88ae01787aaf699a5354997970cfeef12495286da55807ce0e4c1b2ebbb2c", size = 116871, upload-time = "2025-04-30T15:19:45.38Z" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, +] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, +] + +[[package]] +name = "protobuf" +version = "6.31.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/48/718c1e104a2e89970a8ff3b06d87e152834b576c570a6908f8c17ba88d65/protobuf-6.31.0.tar.gz", hash = "sha256:314fab1a6a316469dc2dd46f993cbbe95c861ea6807da910becfe7475bc26ffe", size = 441644, upload-time = "2025-05-14T17:58:27.862Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/77/8671682038b08237c927215fa3296bc1c54e4086fe542c87017c1b626663/protobuf-6.31.0-cp310-abi3-win32.whl", hash = "sha256:10bd62802dfa0588649740a59354090eaf54b8322f772fbdcca19bc78d27f0d6", size = 423437, upload-time = "2025-05-14T17:58:16.116Z" }, + { url = "https://files.pythonhosted.org/packages/e4/07/cc9b0cbf7593f6ef8cf87fa9b0e55cd74c5cb526dd89ad84aa7d6547ef8d/protobuf-6.31.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e987c99fd634be8347246a02123250f394ba20573c953de133dc8b2c107dd71", size = 435118, upload-time = "2025-05-14T17:58:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/21/46/33f884aa8bc59114dc97e0d954ca4618c556483670236008c88fbb7e834f/protobuf-6.31.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2c812f0f96ceb6b514448cefeb1df54ec06dde456783f5099c0e2f8a0f2caa89", size = 425439, upload-time = "2025-05-14T17:58:19.709Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/9a676b50229ce37b12777d7b21de90ae7bc0f9505d07e72e2e8d47b8d165/protobuf-6.31.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:67ce50195e4e584275623b8e6bc6d3d3dfd93924bf6116b86b3b8975ab9e4571", size = 321950, upload-time = "2025-05-14T17:58:22.04Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a7/243fa2d3c1b7675d54744b32dacf30356f4c27c0d3ad940ca8745a1c6b2c/protobuf-6.31.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:5353e38844168a327acd2b2aa440044411cd8d1b6774d5701008bd1dba067c79", size = 320904, upload-time = "2025-05-14T17:58:23.438Z" }, + { url = "https://files.pythonhosted.org/packages/ee/01/1ed1d482960a5718fd99c82f6d79120181947cfd4667ec3944d448ed44a3/protobuf-6.31.0-py3-none-any.whl", hash = "sha256:6ac2e82556e822c17a8d23aa1190bbc1d06efb9c261981da95c71c9da09e9e23", size = 168558, upload-time = "2025-05-14T17:58:26.923Z" }, +] + +[[package]] +name = "pyarrow" +version = "20.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187, upload-time = "2025-04-27T12:34:23.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/aa/daa413b81446d20d4dad2944110dcf4cf4f4179ef7f685dd5a6d7570dc8e/pyarrow-20.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a15532e77b94c61efadde86d10957950392999503b3616b2ffcef7621a002893", size = 30798501, upload-time = "2025-04-27T12:30:48.351Z" }, + { url = "https://files.pythonhosted.org/packages/ff/75/2303d1caa410925de902d32ac215dc80a7ce7dd8dfe95358c165f2adf107/pyarrow-20.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dd43f58037443af715f34f1322c782ec463a3c8a94a85fdb2d987ceb5658e061", size = 32277895, upload-time = "2025-04-27T12:30:55.238Z" }, + { url = "https://files.pythonhosted.org/packages/92/41/fe18c7c0b38b20811b73d1bdd54b1fccba0dab0e51d2048878042d84afa8/pyarrow-20.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0d288143a8585806e3cc7c39566407aab646fb9ece164609dac1cfff45f6ae", size = 41327322, upload-time = "2025-04-27T12:31:05.587Z" }, + { url = "https://files.pythonhosted.org/packages/da/ab/7dbf3d11db67c72dbf36ae63dcbc9f30b866c153b3a22ef728523943eee6/pyarrow-20.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6953f0114f8d6f3d905d98e987d0924dabce59c3cda380bdfaa25a6201563b4", size = 42411441, upload-time = "2025-04-27T12:31:15.675Z" }, + { url = "https://files.pythonhosted.org/packages/90/c3/0c7da7b6dac863af75b64e2f827e4742161128c350bfe7955b426484e226/pyarrow-20.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:991f85b48a8a5e839b2128590ce07611fae48a904cae6cab1f089c5955b57eb5", size = 40677027, upload-time = "2025-04-27T12:31:24.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/27/43a47fa0ff9053ab5203bb3faeec435d43c0d8bfa40179bfd076cdbd4e1c/pyarrow-20.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:97c8dc984ed09cb07d618d57d8d4b67a5100a30c3818c2fb0b04599f0da2de7b", size = 42281473, upload-time = "2025-04-27T12:31:31.311Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/d56c63b078876da81bbb9ba695a596eabee9b085555ed12bf6eb3b7cab0e/pyarrow-20.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b71daf534f4745818f96c214dbc1e6124d7daf059167330b610fc69b6f3d3e3", size = 42893897, upload-time = "2025-04-27T12:31:39.406Z" }, + { url = "https://files.pythonhosted.org/packages/92/ac/7d4bd020ba9145f354012838692d48300c1b8fe5634bfda886abcada67ed/pyarrow-20.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8b88758f9303fa5a83d6c90e176714b2fd3852e776fc2d7e42a22dd6c2fb368", size = 44543847, upload-time = "2025-04-27T12:31:45.997Z" }, + { url = "https://files.pythonhosted.org/packages/9d/07/290f4abf9ca702c5df7b47739c1b2c83588641ddfa2cc75e34a301d42e55/pyarrow-20.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:30b3051b7975801c1e1d387e17c588d8ab05ced9b1e14eec57915f79869b5031", size = 25653219, upload-time = "2025-04-27T12:31:54.11Z" }, + { url = "https://files.pythonhosted.org/packages/95/df/720bb17704b10bd69dde086e1400b8eefb8f58df3f8ac9cff6c425bf57f1/pyarrow-20.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ca151afa4f9b7bc45bcc791eb9a89e90a9eb2772767d0b1e5389609c7d03db63", size = 30853957, upload-time = "2025-04-27T12:31:59.215Z" }, + { url = "https://files.pythonhosted.org/packages/d9/72/0d5f875efc31baef742ba55a00a25213a19ea64d7176e0fe001c5d8b6e9a/pyarrow-20.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:4680f01ecd86e0dd63e39eb5cd59ef9ff24a9d166db328679e36c108dc993d4c", size = 32247972, upload-time = "2025-04-27T12:32:05.369Z" }, + { url = "https://files.pythonhosted.org/packages/d5/bc/e48b4fa544d2eea72f7844180eb77f83f2030b84c8dad860f199f94307ed/pyarrow-20.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f4c8534e2ff059765647aa69b75d6543f9fef59e2cd4c6d18015192565d2b70", size = 41256434, upload-time = "2025-04-27T12:32:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/c3/01/974043a29874aa2cf4f87fb07fd108828fc7362300265a2a64a94965e35b/pyarrow-20.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1f8a47f4b4ae4c69c4d702cfbdfe4d41e18e5c7ef6f1bb1c50918c1e81c57b", size = 42353648, upload-time = "2025-04-27T12:32:20.766Z" }, + { url = "https://files.pythonhosted.org/packages/68/95/cc0d3634cde9ca69b0e51cbe830d8915ea32dda2157560dda27ff3b3337b/pyarrow-20.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a1f60dc14658efaa927f8214734f6a01a806d7690be4b3232ba526836d216122", size = 40619853, upload-time = "2025-04-27T12:32:28.1Z" }, + { url = "https://files.pythonhosted.org/packages/29/c2/3ad40e07e96a3e74e7ed7cc8285aadfa84eb848a798c98ec0ad009eb6bcc/pyarrow-20.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:204a846dca751428991346976b914d6d2a82ae5b8316a6ed99789ebf976551e6", size = 42241743, upload-time = "2025-04-27T12:32:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/eb/cb/65fa110b483339add6a9bc7b6373614166b14e20375d4daa73483755f830/pyarrow-20.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f3b117b922af5e4c6b9a9115825726cac7d8b1421c37c2b5e24fbacc8930612c", size = 42839441, upload-time = "2025-04-27T12:32:46.64Z" }, + { url = "https://files.pythonhosted.org/packages/98/7b/f30b1954589243207d7a0fbc9997401044bf9a033eec78f6cb50da3f304a/pyarrow-20.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e724a3fd23ae5b9c010e7be857f4405ed5e679db5c93e66204db1a69f733936a", size = 44503279, upload-time = "2025-04-27T12:32:56.503Z" }, + { url = "https://files.pythonhosted.org/packages/37/40/ad395740cd641869a13bcf60851296c89624662575621968dcfafabaa7f6/pyarrow-20.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9", size = 25944982, upload-time = "2025-04-27T12:33:04.72Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, +] + +[[package]] +name = "pydeck" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload-time = "2024-05-10T15:36:21.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" }, +] + +[[package]] +name = "pymupdf" +version = "1.25.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/af/3d5d363241b9a74470273cf1534436f13a0a61fc5ef6efd19e5afe9de812/pymupdf-1.25.5.tar.gz", hash = "sha256:5f96311cacd13254c905f6654a004a0a2025b71cabc04fda667f5472f72c15a0", size = 69812626, upload-time = "2025-03-31T23:22:13.891Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/5f/153d6c338291448e182648844849d13938a62a82a3e4a9b0907d9b381148/pymupdf-1.25.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cde4e1c9cfb09c0e1e9c2b7f4b787dd6bb34a32cfe141a4675e24af7c0c25dd3", size = 19364722, upload-time = "2025-03-31T23:18:04.729Z" }, + { url = "https://files.pythonhosted.org/packages/4e/55/43b64fa6cd048d2ea4574c045b5ac05d023254b91c2c703185f6f8a77b30/pymupdf-1.25.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5a35e2725fae0ab57f058dff77615c15eb5961eac50ba04f41ebc792cd8facad", size = 18606161, upload-time = "2025-03-31T23:18:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/8b/22/29edb3236aed2f99a7922699fd71183e2f6cdde3c3884670158ae4dcf3ea/pymupdf-1.25.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d94b800e9501929c42283d39bc241001dd87fdeea297b5cb40d5b5714534452f", size = 19467121, upload-time = "2025-04-01T09:31:13.172Z" }, + { url = "https://files.pythonhosted.org/packages/18/12/95e2ebe2933f94800fdeafd87bc281a790e1dc947b147c3d101df4f73703/pymupdf-1.25.5-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee22155d3a634642d76553204867d862ae1bdd9f7cf70c0797d8127ebee6bed5", size = 20030310, upload-time = "2025-03-31T23:19:03.607Z" }, + { url = "https://files.pythonhosted.org/packages/bd/db/b4edec9e731ea7c2b74bf28b9091ed4e919d5c7f889ef86352b7fd416197/pymupdf-1.25.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6ed7fc25271004d6d3279c20a80cb2bb4cda3efa9f9088dcc07cd790eca0bc63", size = 21293562, upload-time = "2025-03-31T23:19:34.612Z" }, + { url = "https://files.pythonhosted.org/packages/ec/47/682a8ddce650e09f5de6809c9bce926b2493a19b7f9537d80d4646989670/pymupdf-1.25.5-cp39-abi3-win32.whl", hash = "sha256:65e18ddb37fe8ec4edcdbebe9be3a8486b6a2f42609d0a142677e42f3a0614f8", size = 15110464, upload-time = "2025-03-31T23:20:02.136Z" }, + { url = "https://files.pythonhosted.org/packages/71/c2/a9059607f80dcaf2392f991748cfc53456820392c0220cff02572653512a/pymupdf-1.25.5-cp39-abi3-win_amd64.whl", hash = "sha256:7f44bc3d03ea45b2f68c96464f96105e8c7908896f2fb5e8c04f1fb8dae7981e", size = 16579671, upload-time = "2025-03-31T23:20:25.793Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863, upload-time = "2025-03-26T14:56:01.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072, upload-time = "2025-03-26T14:53:48.686Z" }, + { url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919, upload-time = "2025-03-26T14:53:50.229Z" }, + { url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360, upload-time = "2025-03-26T14:53:51.909Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704, upload-time = "2025-03-26T14:53:53.47Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839, upload-time = "2025-03-26T14:53:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494, upload-time = "2025-03-26T14:53:57.047Z" }, + { url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185, upload-time = "2025-03-26T14:53:59.032Z" }, + { url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168, upload-time = "2025-03-26T14:54:00.661Z" }, + { url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622, upload-time = "2025-03-26T14:54:02.312Z" }, + { url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435, upload-time = "2025-03-26T14:54:04.388Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762, upload-time = "2025-03-26T14:54:06.422Z" }, + { url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510, upload-time = "2025-03-26T14:54:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075, upload-time = "2025-03-26T14:54:09.992Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974, upload-time = "2025-03-26T14:54:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730, upload-time = "2025-03-26T14:54:13.145Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627, upload-time = "2025-03-26T14:54:14.711Z" }, + { url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094, upload-time = "2025-03-26T14:54:16.961Z" }, + { url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639, upload-time = "2025-03-26T14:54:19.047Z" }, + { url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584, upload-time = "2025-03-26T14:54:20.722Z" }, + { url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047, upload-time = "2025-03-26T14:54:22.426Z" }, + { url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085, upload-time = "2025-03-26T14:54:23.949Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498, upload-time = "2025-03-26T14:54:25.573Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202, upload-time = "2025-03-26T14:54:27.569Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771, upload-time = "2025-03-26T14:54:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195, upload-time = "2025-03-26T14:54:31.581Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354, upload-time = "2025-03-26T14:54:33.199Z" }, +] + +[[package]] +name = "ruff" +version = "0.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/5f/28f45b1ff14bee871bacafd0a97213f7ec70e389939a80c60c0fb72a9fc9/sse_starlette-2.3.5.tar.gz", hash = "sha256:228357b6e42dcc73a427990e2b4a03c023e2495ecee82e14f07ba15077e334b2", size = 17511, upload-time = "2025-05-12T18:23:52.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/48/3e49cf0f64961656402c0023edbc51844fe17afe53ab50e958a6dbbbd499/sse_starlette-2.3.5-py3-none-any.whl", hash = "sha256:251708539a335570f10eaaa21d1848a10c42ee6dc3a9cf37ef42266cdb1c52a8", size = 10233, upload-time = "2025-05-12T18:23:50.722Z" }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, +] + +[[package]] +name = "streamlit" +version = "1.45.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altair" }, + { name = "blinker" }, + { name = "cachetools" }, + { name = "click" }, + { name = "gitpython" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "pyarrow" }, + { name = "pydeck" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "toml" }, + { name = "tornado" }, + { name = "typing-extensions" }, + { name = "watchdog", marker = "sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/46/9b3f73886f82d27849ce1e7a74ae7c39f5323e46da0b6e8847ad4c25f44c/streamlit-1.45.1.tar.gz", hash = "sha256:e37d56c0af5240dbc240976880e81366689c290a559376417246f9b3f51b4217", size = 9463953, upload-time = "2025-05-12T20:40:30.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/e6/69fcbae3dd2fcb2f54283a7cbe03c8b944b79997f1b526984f91d4796a02/streamlit-1.45.1-py3-none-any.whl", hash = "sha256:9ab6951585e9444672dd650850f81767b01bba5d87c8dac9bc2e1c859d6cc254", size = 9856294, upload-time = "2025-05-12T20:40:27.875Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135, upload-time = "2024-11-22T03:06:38.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299, upload-time = "2024-11-22T03:06:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253, upload-time = "2024-11-22T03:06:22.39Z" }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602, upload-time = "2024-11-22T03:06:24.214Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972, upload-time = "2024-11-22T03:06:25.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173, upload-time = "2024-11-22T03:06:27.584Z" }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892, upload-time = "2024-11-22T03:06:28.933Z" }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334, upload-time = "2024-11-22T03:06:30.428Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261, upload-time = "2024-11-22T03:06:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463, upload-time = "2024-11-22T03:06:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907, upload-time = "2024-11-22T03:06:36.71Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20250515" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c1/cdc4f9b8cfd9130fbe6276db574f114541f4231fcc6fb29648289e6e3390/types_requests-2.32.0.20250515.tar.gz", hash = "sha256:09c8b63c11318cb2460813871aaa48b671002e59fda67ca909e9883777787581", size = 23012, upload-time = "2025-05-15T03:04:31.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/0f/68a997c73a129287785f418c1ebb6004f81e46b53b3caba88c0e03fcd04a/types_requests-2.32.0.20250515-py3-none-any.whl", hash = "sha256:f8eba93b3a892beee32643ff836993f15a785816acca21ea0ffa006f05ef0fb2", size = 20635, upload-time = "2025-05-15T03:04:30.5Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +]