From 2202c97553314542887318ee9746a4e4be2f64b0 Mon Sep 17 00:00:00 2001 From: "ANJOS, J. S." <0rakul0render@gmail.com> Date: Mon, 8 Jun 2026 23:36:37 -0300 Subject: [PATCH 1/3] att --- .gitignore | 1 + python-package/MIGRATION_PLAN.md | 184 ++++++++ python-package/README.md | 229 +++++++++ python-package/placeholder.txt | 0 python-package/pyproject.toml | 35 ++ python-package/src/geocodebr/__init__.py | 27 ++ python-package/src/geocodebr/cache.py | 99 ++++ python-package/src/geocodebr/constants.py | 48 ++ python-package/src/geocodebr/db.py | 37 ++ .../src/geocodebr/download_cnefe.py | 69 +++ python-package/src/geocodebr/errors.py | 14 + python-package/src/geocodebr/fields.py | 52 ++ python-package/src/geocodebr/geocode.py | 429 +++++++++++++++++ python-package/src/geocodebr/matching.py | 444 ++++++++++++++++++ python-package/src/geocodebr/messages.py | 28 ++ python-package/src/geocodebr/reverse.py | 213 +++++++++ python-package/src/geocodebr/string_dist.py | 53 +++ python-package/src/geocodebr/tables.py | 92 ++++ python-package/src/geocodebr/utils.py | 201 ++++++++ python-package/tests/conftest.py | 19 + python-package/tests/test_busca_por_cep.py | 32 ++ python-package/tests/test_cache.py | 13 + python-package/tests/test_fields.py | 22 + python-package/tests/test_geocode.py | 55 +++ python-package/tests/test_geocode_reverso.py | 32 ++ 25 files changed, 2428 insertions(+) create mode 100644 python-package/MIGRATION_PLAN.md create mode 100644 python-package/README.md delete mode 100644 python-package/placeholder.txt create mode 100644 python-package/pyproject.toml create mode 100644 python-package/src/geocodebr/__init__.py create mode 100644 python-package/src/geocodebr/cache.py create mode 100644 python-package/src/geocodebr/constants.py create mode 100644 python-package/src/geocodebr/db.py create mode 100644 python-package/src/geocodebr/download_cnefe.py create mode 100644 python-package/src/geocodebr/errors.py create mode 100644 python-package/src/geocodebr/fields.py create mode 100644 python-package/src/geocodebr/geocode.py create mode 100644 python-package/src/geocodebr/matching.py create mode 100644 python-package/src/geocodebr/messages.py create mode 100644 python-package/src/geocodebr/reverse.py create mode 100644 python-package/src/geocodebr/string_dist.py create mode 100644 python-package/src/geocodebr/tables.py create mode 100644 python-package/src/geocodebr/utils.py create mode 100644 python-package/tests/conftest.py create mode 100644 python-package/tests/test_busca_por_cep.py create mode 100644 python-package/tests/test_cache.py create mode 100644 python-package/tests/test_fields.py create mode 100644 python-package/tests/test_geocode.py create mode 100644 python-package/tests/test_geocode_reverso.py diff --git a/.gitignore b/.gitignore index f47593b..80314c3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ vignettes/*.html docs /data_prep/data/* /data_prep/data_raw/* +*.pyc diff --git a/python-package/MIGRATION_PLAN.md b/python-package/MIGRATION_PLAN.md new file mode 100644 index 0000000..1cf4f77 --- /dev/null +++ b/python-package/MIGRATION_PLAN.md @@ -0,0 +1,184 @@ +# Plano de migracao R -> Python + +Este documento resume a analise dos scripts R do pacote `geocodebr` e propoe +uma versao Python preservando a dinamica de uso e os nomes das funcoes publicas. + +## API publica a manter + +As funcoes exportadas no `NAMESPACE` devem existir tambem no pacote Python: + +- `definir_campos(estado, municipio, logradouro=None, numero=None, cep=None, localidade=None)` +- `geocode(enderecos, campos_endereco=..., resultado_completo=False, resolver_empates=True, resultado_sf=False, h3_res=None, padronizar_enderecos=True, verboso=True, cache=True, n_cores=None)` +- `busca_por_cep(cep, h3_res=None, resultado_sf=False, verboso=True, cache=True)` +- `geocode_reverso(pontos, dist_max=1000, verboso=True, cache=True, n_cores=None)` +- `download_cnefe(tabela="todas", verboso=True, cache=True)` +- `definir_pasta_cache(path, verboso=True)` +- `listar_pasta_cache()` +- `listar_dados_cache(print_tree=False)` +- `deletar_pasta_cache()` + +Em Python, a recomendacao e manter esses nomes em portugues para reduzir a +curva de aprendizado. Internamente, os modulos podem ser separados por dominio. + +## Estrutura sugerida + +```text +python-package/ + pyproject.toml + src/geocodebr/ + __init__.py + cache.py + download_cnefe.py + fields.py + geocode.py + reverse.py + db.py + matching.py + tables.py + string_dist.py + utils.py + errors.py + messages.py + tests/ + test_cache.py + test_fields.py + test_busca_por_cep.py + test_geocode.py + test_geocode_reverso.py +``` + +## Dependencias Python recomendadas + +- `duckdb`: motor SQL central e tambem a camada principal de manipulacao + tabular. A versao Python deve manter os dados em tabelas/views DuckDB sempre + que possivel, evitando transformar o fluxo interno em `pandas`. +- `pyarrow`: leitura/escrita Parquet e interoperabilidade com DuckDB. +- `requests` ou `httpx`: download dos Parquets do release CNEFE. +- `platformdirs`: diretorio de cache/config persistente equivalente a `tools::R_user_dir`. +- `tqdm`: barra de progresso equivalente a `cli`/progress bar. +- `geopandas`, `shapely`, `pyproj`: saida espacial e `geocode_reverso`. +- `h3`: criacao de colunas `h3_03`, `h3_04` etc. +- `rapidfuzz` ou UDF DuckDB: apenas se `jaro_similarity` nao estiver disponivel + de forma consistente na instalacao DuckDB Python. +- Uma camada propria de padronizacao de endereco ou dependencia Python + equivalente a `enderecobr`. +- `pandas`: opcional apenas para aceitar/retornar dados no estilo familiar da + API Python. Internamente, nao deve ser o motor de processamento. + +## Pontos criticos de paridade + +1. `enderecobr` + - O R depende de `enderecobr::padronizar_enderecos`, `padronizar_ceps`, + `padronizar_municipios` e `correspondencia_campos`. + - A versao Python precisa gerar as mesmas colunas padronizadas: + `estado_padr`, `municipio_padr`, `logradouro_padr`, `numero_padr`, + `cep_padr`, `bairro_padr`. + - Este e o maior risco de divergencia entre R e Python. + +2. SQL DuckDB + - As funcoes de matching (`match_cases`, `match_weighted_cases`, + `match_cases_probabilistic`, `match_weighted_cases_probabilistic`) sao + quase totalmente SQL. + - A melhor estrategia e portar os templates SQL para Python com interpolacao + controlada, preservando nomes de tabelas temporarias e colunas. + - O DuckDB deve substituir tanto `data.table` quanto a maior parte do uso + potencial de `pandas`: padronizacao, filtros, joins, atualizacoes, + desempate, H3 e montagem do output devem preferir SQL/tabelas temporarias. + +3. Dados CNEFE + - `data_release` atual: `v0.4.1`. + - Fonte: `https://github.com/ipeaGIT/padronizacao_cnefe/releases/download/{data_release}/{arquivo}`. + - Os arquivos Parquet baixados sao a base compartilhada entre R e Python. + +4. Geometria + - `resultado_sf=TRUE` no R retorna `sf`. + - Em Python, o equivalente natural e `geopandas.GeoDataFrame` com CRS + `EPSG:4674`. + - `geocode_reverso` hoje usa DuckDB Spatial via `duckspatial`; em Python deve + usar `duckdb` com extensao `spatial` ou uma combinacao `geopandas`/`sjoin`. + +## Sequencia de implementacao sugerida + +1. Criar pacote Python basico com `pyproject.toml`, `src/geocodebr` e exports em + `__init__.py`. +2. Portar cache: + - `definir_pasta_cache` + - `listar_pasta_cache` + - `listar_dados_cache` + - `deletar_pasta_cache` +3. Portar `definir_campos` e validacoes de colunas. +4. Portar `download_cnefe`. +5. Portar constantes e utilitarios DuckDB-first: + - `data_release` + - `get_key_cols` + - `get_reference_table` + - listas de `match_type` + - `add_precision_col` + - `merge_results_to_input` +6. Portar funcoes de matching SQL. +7. Implementar/pinchar padronizacao de enderecos em Python. +8. Portar `busca_por_cep`. +9. Portar `geocode`. +10. Portar `geocode_reverso`. Primeira versao implementada com DuckDB Spatial, + aceitando `lon`/`lat`, `longitude`/`latitude`, `x`/`y` ou GeoDataFrame em + `EPSG:4674`, e retornando `pyarrow.Table`. +11. Criar testes Python com `pytest`, usando os testes R como contrato de + comportamento. + +## Mapeamento dos arquivos R + +- `R/cache.R` -> `cache.py` +- `R/download_cnefe.R` -> `download_cnefe.py` +- `R/definir_campos.R` -> `fields.py` +- `R/create_geocodebr_db.R` -> `db.py` +- `R/geocode.R` -> `geocode.py` +- `R/busca_por_cep.R` -> `geocode.py` ou `cep.py` +- `R/geocode_reverso.R` -> `reverse.py` +- `R/match_cases*.R`, `R/match_weighted_cases*.R` -> `matching.py` +- `R/register_cnefe_tables.R` -> `tables.py` +- `R/string_dist.R` -> `string_dist.py` +- `R/trata_empates_geocode_duckdb.R` -> `matching.py` ou `ties.py` +- `R/utils.R` -> `utils.py` +- `R/error.R`, `R/message.R`, `R/progress_bar.R` -> `errors.py`, `messages.py` + +## Contratos de saida importantes + +`geocode` deve aceitar dados de entrada em formatos convenientes, mas o fluxo +interno deve registrar a entrada diretamente no DuckDB. A saida padrao pode ser +uma relacao DuckDB materializada sob demanda ou um `DataFrame` para ergonomia da +API; a decisao deve ficar explicita na implementacao. + +`geocode` deve preservar as colunas originais e adicionar: + +- sempre: `lat`, `lon`, `precisao`, `tipo_resultado`, `desvio_metros`, + `endereco_encontrado` +- se `resultado_completo=True`: `logradouro_encontrado`, `numero_encontrado`, + `cep_encontrado`, `localidade_encontrada`, `municipio_encontrado`, + `estado_encontrado`, `similaridade_logradouro`, `contagem_cnefe`, `empate`, + `cod_setor` +- se `h3_res` for informado: `h3_03`, `h3_04`, etc. + +`busca_por_cep` deve retornar `cep`, `estado`, `municipio`, `logradouro`, +`localidade`, `lon`, `lat` e H3 quando solicitado. + +`geocode_reverso` deve receber pontos em `EPSG:4674`, validar bounding box do +Brasil e retornar o endereco mais proximo dentro de `dist_max`, com +`distancia_metros`. + +## Observacoes de risco + +- O teste de paridade deve comparar resultados Python vs R em amostras pequenas, + incluindo casos determiniscos, probabilisticos, interpolacao por numero, + empates e CEP inexistente. +- A implementacao deve evitar chamar `.df()`/`.fetchdf()` no meio do pipeline. + Essas chamadas devem ficar restritas a limites claros da API, por exemplo no + retorno final quando o usuario pedir um objeto Python em memoria. +- A padronizacao inicial ja cobre normalizacao de acentos/caixa, UF por extenso + para sigla, CEP numerico, numero inteiro e tentativa de municipio por codigo + IBGE quando a tabela `municipio.parquet` trouxer coluna de codigo reconhecida. + Ainda precisa de validacao ampla contra `enderecobr`. +- Ha possiveis bugs/quirks no R que talvez precisem ser replicados ou corrigidos + explicitamente. Exemplo: nos loops de H3, o nome da coluna usa `h3_res` em vez + do item `i`; funciona para vetor curto no teste, mas deve ser revisto ao portar. +- A versao Python deve evitar montar SQL com valores vindos diretamente do usuario. + Os nomes de colunas podem ser validados contra `^[A-Za-z0-9_]+$`, como no R. diff --git a/python-package/README.md b/python-package/README.md new file mode 100644 index 0000000..54be0d8 --- /dev/null +++ b/python-package/README.md @@ -0,0 +1,229 @@ +# geocodebr Python: Geolocalizacao de Enderecos Brasileiros + +Versao Python experimental do `geocodebr`, usando DuckDB como motor tabular +principal. A proposta e preservar a dinamica de uso do pacote R, incluindo nomes +de funcoes em portugues, mas mantendo o processamento interno em SQL/DuckDB para +boa performance e menor uso de memoria. + +O pacote geolocaliza enderecos brasileiros sem limite de numero de consultas, +com base em dados abertos do CNEFE (Cadastro Nacional de Enderecos para Fins +Estatisticos), publicado pelo IBGE. + +## Instalacao + +No momento, esta versao Python ainda esta em desenvolvimento dentro deste +repositorio. Para instalar localmente: + +```bash +cd python-package +python -m pip install -e . +``` + +Dependencias principais: + +- `duckdb`: motor principal de dados e SQL. +- `pyarrow`: formato padrao de retorno e interoperabilidade com Parquet. +- `requests`: download dos dados CNEFE. +- `h3`: criacao opcional de celulas H3. + +Para desenvolvimento e testes: + +```bash +python -m pip install -e ".[test]" +python -m pytest -q +``` + +## Utilizacao + +O pacote possui tres funcoes principais: + +1. `geocode()` +2. `geocode_reverso()` +3. `busca_por_cep()` + +As funcoes retornam, por padrao, um `pyarrow.Table`. Caso precise converter para +`pandas`, use `.to_pandas()` no resultado final. + +## 1. Geolocalizacao: de enderecos para coordenadas + +Primeiro, indique quais colunas da sua tabela representam cada campo do +endereco usando `definir_campos()`. Depois, chame `geocode()`. + +```python +import pyarrow.csv as pv + +from geocodebr import definir_campos, geocode + +enderecos = pv.read_csv("../inst/extdata/small_sample.csv") + +campos = definir_campos( + logradouro="nm_logradouro", + numero="Numero", + cep="Cep", + localidade="Bairro", + municipio="nm_municipio", + estado="nm_uf", +) + +resultado = geocode( + enderecos=enderecos, + campos_endereco=campos, + resultado_completo=False, + resolver_empates=True, + h3_res=[8, 10], + verboso=False, +) + +print(resultado.schema.names) +print(resultado.to_pandas().head()) +``` + +Tambem e possivel passar um caminho para arquivo `.csv` ou `.parquet`: + +```python +resultado = geocode( + enderecos="../inst/extdata/small_sample.csv", + campos_endereco=campos, + verboso=False, +) +``` + +O resultado preserva as colunas originais e adiciona, entre outras: + +- `lat` +- `lon` +- `precisao` +- `tipo_resultado` +- `desvio_metros` +- `endereco_encontrado` + +Com `resultado_completo=True`, tambem retorna campos encontrados no CNEFE, como +`logradouro_encontrado`, `numero_encontrado`, `cep_encontrado`, +`localidade_encontrada`, `municipio_encontrado`, `estado_encontrado`, +`similaridade_logradouro`, `contagem_cnefe`, `empate` e `cod_setor`. + +## 2. Geolocalizacao reversa: de coordenadas para enderecos + +`geocode_reverso()` busca o endereco mais proximo de cada ponto dentro de uma +distancia maxima em metros. + +A entrada pode ser: + +- tabela com colunas `lon` e `lat` +- tabela com colunas `longitude` e `latitude` +- tabela com colunas `x` e `y` +- `GeoDataFrame` em `EPSG:4674` + +```python +import pyarrow as pa + +from geocodebr import geocode_reverso + +pontos = pa.table( + { + "id": [1, 2], + "lon": [-47.9001, -43.2001], + "lat": [-15.8001, -22.9001], + } +) + +enderecos_proximos = geocode_reverso( + pontos=pontos, + dist_max=1000, + verboso=False, +) + +print(enderecos_proximos.to_pandas()) +``` + +O resultado inclui os campos do endereco encontrado e a coluna +`distancia_metros`. + +## 3. Busca por CEP + +`busca_por_cep()` retorna os enderecos associados a um ou mais CEPs. + +```python +from geocodebr import busca_por_cep + +ceps = ["70390-025", "20071-001", "99999-999"] + +resultado_cep = busca_por_cep( + cep=ceps, + h3_res=10, + verboso=False, +) + +print(resultado_cep.to_pandas()) +``` + +O resultado inclui: + +- `cep` +- `estado` +- `municipio` +- `logradouro` +- `localidade` +- `lon` +- `lat` + +Se `h3_res` for informado, o pacote adiciona colunas como `h3_08` ou `h3_10`. + +## Cache dos dados CNEFE + +Na primeira execucao, o pacote baixa arquivos Parquet do release CNEFE usado +pelo `geocodebr`. Esses arquivos ficam em cache local para acelerar chamadas +futuras. + +```python +from geocodebr import ( + definir_pasta_cache, + listar_pasta_cache, + listar_dados_cache, + deletar_pasta_cache, + download_cnefe, +) + +print(listar_pasta_cache()) + +download_cnefe(tabela="municipio_logradouro_cep_localidade", verboso=True) + +arquivos = listar_dados_cache() +print(arquivos) + +# definir uma pasta de cache especifica +definir_pasta_cache("D:/dados/geocodebr-cache", verboso=True) + +# apagar cache configurado +# deletar_pasta_cache() +``` + +## DuckDB-first + +Esta versao evita usar `pandas` no pipeline interno. O fluxo principal registra +entradas no DuckDB, executa joins/filtros/matches em SQL e so materializa o +resultado no final como `pyarrow.Table`. + +Isso facilita a paridade com o pacote R, que tambem usa DuckDB para o motor de +geocodificacao, e ajuda em bases maiores. + +## Estado atual + +Esta versao Python ainda e experimental. + +Ja implementado: + +- `definir_campos()` +- `download_cnefe()` +- funcoes de cache +- `busca_por_cep()` +- `geocode()` com motor DuckDB +- `geocode_reverso()` com DuckDB Spatial +- testes unitarios com Parquets sinteticos + +Pontos que ainda precisam de validacao ampla: + +- paridade completa da padronizacao com o pacote R `enderecobr` +- comparacao Python vs R em amostras reais maiores +- retorno espacial equivalente a `sf`/`GeoDataFrame` quando `resultado_sf=True` + diff --git a/python-package/placeholder.txt b/python-package/placeholder.txt deleted file mode 100644 index e69de29..0000000 diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml new file mode 100644 index 0000000..0322229 --- /dev/null +++ b/python-package/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["hatchling>=1.25"] +build-backend = "hatchling.build" + +[project] +name = "geocodebr" +version = "0.0.1" +description = "Geolocalizacao de enderecos brasileiros com DuckDB" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT" } +dependencies = [ + "duckdb>=1.0.0", + "pyarrow>=15.0.0", + "requests>=2.31.0", + "platformdirs>=4.0.0", + "tqdm>=4.66.0", + "h3>=4.0.0", +] + +[project.optional-dependencies] +geo = [ + "geopandas>=0.14.0", + "shapely>=2.0.0", + "pyproj>=3.6.0", +] +test = [ + "pytest>=8.0.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/geocodebr"] + +[tool.pytest.ini_options] +pythonpath = ["src"] diff --git a/python-package/src/geocodebr/__init__.py b/python-package/src/geocodebr/__init__.py new file mode 100644 index 0000000..fa0dbf8 --- /dev/null +++ b/python-package/src/geocodebr/__init__.py @@ -0,0 +1,27 @@ +from .cache import ( + definir_pasta_cache, + deletar_pasta_cache, + listar_dados_cache, + listar_pasta_cache, +) +from .download_cnefe import download_cnefe +from .fields import definir_campos +from .geocode import busca_por_cep, geocode + +try: + from .reverse import geocode_reverso +except Exception: # pragma: no cover + geocode_reverso = None + +__all__ = [ + "busca_por_cep", + "definir_campos", + "definir_pasta_cache", + "deletar_pasta_cache", + "download_cnefe", + "geocode", + "geocode_reverso", + "listar_dados_cache", + "listar_pasta_cache", +] + diff --git a/python-package/src/geocodebr/cache.py b/python-package/src/geocodebr/cache.py new file mode 100644 index 0000000..eda040d --- /dev/null +++ b/python-package/src/geocodebr/cache.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import shutil +from pathlib import Path + +try: + from platformdirs import user_cache_dir, user_config_dir +except ModuleNotFoundError: # pragma: no cover + def user_cache_dir(appname: str) -> str: + return str(Path.home() / "AppData" / "Local" / appname / "Cache") + + def user_config_dir(appname: str) -> str: + return str(Path.home() / "AppData" / "Roaming" / appname) + +from .messages import message_cache + + +def listar_pasta_cache_padrao() -> str: + return str(Path(user_cache_dir("geocodebr"))) + + +def listar_arquivo_config() -> str: + return str(Path(user_config_dir("geocodebr")) / "cache_dir") + + +def definir_pasta_cache(path: str | None, verboso: bool = True) -> str: + if path is not None and not isinstance(path, str): + raise TypeError("path deve ser uma string ou None.") + if not isinstance(verboso, bool): + raise TypeError("verboso deve ser True ou False.") + + cache_dir = Path(listar_pasta_cache_padrao()) if path is None else Path(path) + cache_dir = cache_dir.expanduser() + + config_file = Path(listar_arquivo_config()) + config_file.parent.mkdir(parents=True, exist_ok=True) + config_file.write_text(str(cache_dir), encoding="utf-8") + + if verboso: + print(f"Definido como pasta de cache {cache_dir}.") + + return str(cache_dir) + + +def listar_pasta_cache() -> str: + config_file = Path(listar_arquivo_config()) + if config_file.exists(): + value = config_file.read_text(encoding="utf-8").strip() + if value: + return str(Path(value).expanduser()) + return listar_pasta_cache_padrao() + + +def listar_dados_cache(print_tree: bool = False) -> list[str]: + if not isinstance(print_tree, bool): + raise TypeError("print_tree deve ser True ou False.") + + cache_dir = Path(listar_pasta_cache()) + if not cache_dir.exists(): + message_cache(True) + return [] + + files = sorted(str(path) for path in cache_dir.rglob("*") if path.is_file()) + if print_tree: + _print_tree(cache_dir) + return files + + +def deletar_pasta_cache() -> str: + cache_dir = Path(listar_pasta_cache()) + if cache_dir.exists(): + shutil.rmtree(cache_dir) + print(f"Deletada a pasta de cache que se encontrava em {cache_dir}.") + return str(cache_dir) + + +def apaga_data_release_antigo(data_release: str) -> str: + cache_dir = Path(listar_pasta_cache()) + if not cache_dir.exists(): + return str(cache_dir) + + release_dirs = [ + path + for path in cache_dir.iterdir() + if path.is_dir() and path.name.startswith("geocodebr_data_release_") + ] + expected = cache_dir / f"geocodebr_data_release_{data_release}" + stale_dirs = [path for path in release_dirs if path != expected] + for path in stale_dirs: + shutil.rmtree(path) + return str(cache_dir) + + +def _print_tree(root: Path) -> None: + print(root) + for path in sorted(root.rglob("*")): + depth = len(path.relative_to(root).parts) + prefix = " " * depth + print(f"{prefix}{path.name}") diff --git a/python-package/src/geocodebr/constants.py b/python-package/src/geocodebr/constants.py new file mode 100644 index 0000000..a54806d --- /dev/null +++ b/python-package/src/geocodebr/constants.py @@ -0,0 +1,48 @@ +DATA_RELEASE = "v0.4.1" + +ALL_CNEFE_FILES = [ + "municipio_logradouro_numero_localidade.parquet", + "municipio_logradouro_numero_cep_localidade.parquet", + "municipio.parquet", + "municipio_cep.parquet", + "municipio_cep_localidade.parquet", + "municipio_localidade.parquet", + "municipio_logradouro_cep_localidade.parquet", + "municipio_logradouro_localidade.parquet", +] + +ALL_POSSIBLE_MATCH_TYPES = [ + "dn01", + "da01", + "pn01", + "pa01", + "dn02", + "da02", + "pn02", + "pa02", + "dn03", + "da03", + "pn03", + "pa03", + "dn04", + "da04", + "dl01", + "pl01", + "dl02", + "pl02", + "dl03", + "pl03", + "dl04", + "dc01", + "dc02", + "db01", + "dm01", +] + +NUMBER_EXACT_TYPES = {"dn01", "dn02", "dn03", "dn04"} +NUMBER_INTERPOLATION_TYPES = {"da01", "da02", "da03", "da04"} +PROBABILISTIC_EXACT_TYPES = {"pn01", "pn02", "pn03", "pn04"} +PROBABILISTIC_INTERPOLATION_TYPES = {"pa01", "pa02", "pa03", "pa04"} +EXACT_TYPES_NO_NUMBER = {"dl01", "dl02", "dl03", "dl04", "dc01", "dc02", "db01", "dm01"} +PROBABILISTIC_TYPES_NO_NUMBER = {"pl01", "pl02", "pl03", "pl04"} + diff --git a/python-package/src/geocodebr/db.py b/python-package/src/geocodebr/db.py new file mode 100644 index 0000000..f627a87 --- /dev/null +++ b/python-package/src/geocodebr/db.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import tempfile +from pathlib import Path + +import duckdb + + +def create_geocodebr_db( + db_path: str = "tempdir", + n_cores: int | None = None, + load_spatial: bool = False, +) -> duckdb.DuckDBPyConnection: + if n_cores is not None and (not isinstance(n_cores, int) or n_cores < 1): + raise ValueError("n_cores deve ser um inteiro positivo ou None.") + + if db_path == "tempdir": + handle = tempfile.NamedTemporaryFile(prefix="geocodebr", suffix=".duckdb", delete=True) + db_file = handle.name + handle.close() + Path(db_file).unlink(missing_ok=True) + elif db_path == "memory": + db_file = ":memory:" + else: + db_file = db_path + + con = duckdb.connect(db_file) + if n_cores is not None: + con.execute(f"SET threads = {n_cores}") + con.execute("SET enable_progress_bar = false") + + if load_spatial: + con.execute("INSTALL spatial") + con.execute("LOAD spatial") + + return con + diff --git a/python-package/src/geocodebr/download_cnefe.py b/python-package/src/geocodebr/download_cnefe.py new file mode 100644 index 0000000..fdfd968 --- /dev/null +++ b/python-package/src/geocodebr/download_cnefe.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import tempfile +from pathlib import Path + +import requests +from tqdm import tqdm + +from .cache import apaga_data_release_antigo, listar_pasta_cache +from .constants import ALL_CNEFE_FILES, DATA_RELEASE +from .messages import message_baixando_cnefe, message_usando_cnefe_local + + +def download_cnefe(tabela: str = "todas", verboso: bool = True, cache: bool = True) -> str: + if not isinstance(tabela, str): + raise TypeError("tabela deve ser uma string.") + if not isinstance(verboso, bool) or not isinstance(cache, bool): + raise TypeError("verboso e cache devem ser True ou False.") + + files = _select_files(tabela) + urls = [ + f"https://github.com/ipeaGIT/padronizacao_cnefe/releases/download/{DATA_RELEASE}/{file}" + for file in files + ] + + if cache: + apaga_data_release_antigo(DATA_RELEASE) + cache_dir = Path(listar_pasta_cache()) + else: + cache_dir = Path(tempfile.mkdtemp(prefix="geocodebr_temp")) + + data_dir = cache_dir / f"geocodebr_data_release_{DATA_RELEASE}" + data_dir.mkdir(parents=True, exist_ok=True) + + existing = {path.name for path in data_dir.iterdir() if path.is_file()} + to_download = [(url, data_dir / Path(url).name) for url in urls if Path(url).name not in existing] + + if not to_download: + message_usando_cnefe_local(verboso) + return str(cache_dir) + + message_baixando_cnefe(verboso) + for url, dest in tqdm(to_download, disable=not verboso): + _download_file(url, dest) + + return str(cache_dir) + + +def _select_files(tabela: str) -> list[str]: + if tabela == "todas": + return ALL_CNEFE_FILES.copy() + + valid = {Path(file).stem: file for file in ALL_CNEFE_FILES} + if tabela not in valid: + options = ", ".join(sorted(valid)) + raise ValueError(f"A tabela deve ser uma das seguintes opcoes: {options}.") + return [valid[tabela]] + + +def _download_file(url: str, dest: Path) -> None: + tmp = dest.with_suffix(dest.suffix + ".part") + with requests.get(url, stream=True, timeout=120) as response: + response.raise_for_status() + with tmp.open("wb") as file: + for chunk in response.iter_content(chunk_size=1024 * 1024): + if chunk: + file.write(chunk) + tmp.replace(dest) + diff --git a/python-package/src/geocodebr/errors.py b/python-package/src/geocodebr/errors.py new file mode 100644 index 0000000..69e7252 --- /dev/null +++ b/python-package/src/geocodebr/errors.py @@ -0,0 +1,14 @@ +class GeocodeBRError(Exception): + """Erro base do geocodebr Python.""" + + +class InputNaoPadronizadoError(GeocodeBRError): + """Entrada sem colunas padronizadas esperadas.""" + + +def error_input_nao_padronizado() -> None: + raise InputNaoPadronizadoError( + "Os dados de entrada nao estao padronizados. Use " + "padronizar_enderecos=True ou informe colunas *_padr equivalentes." + ) + diff --git a/python-package/src/geocodebr/fields.py b/python-package/src/geocodebr/fields.py new file mode 100644 index 0000000..fef0dc7 --- /dev/null +++ b/python-package/src/geocodebr/fields.py @@ -0,0 +1,52 @@ +from __future__ import annotations + + +ADDRESS_FIELDS = ("logradouro", "numero", "cep", "localidade", "municipio", "estado") + + +def definir_campos( + estado: str, + municipio: str, + logradouro: str | None = None, + numero: str | None = None, + cep: str | None = None, + localidade: str | None = None, +) -> dict[str, str | None]: + values = { + "logradouro": logradouro, + "numero": numero, + "cep": cep, + "localidade": localidade, + "municipio": municipio, + "estado": estado, + } + for name, value in values.items(): + if value is not None and not isinstance(value, str): + raise TypeError(f"{name} deve ser uma string ou None.") + if all(value is None for value in values.values()): + raise ValueError("Pelo menos um campo nao pode ser nulo.") + return values + + +def assert_and_assign_address_fields( + address_fields: dict[str, str | None], + addresses_columns: list[str], +) -> dict[str, str | None]: + if not isinstance(address_fields, dict): + raise TypeError("campos_endereco deve ser um dict.") + + unknown = set(address_fields) - set(ADDRESS_FIELDS) + if unknown: + raise ValueError(f"Campos desconhecidos: {sorted(unknown)}.") + + missing_columns = [ + column + for column in address_fields.values() + if column is not None and column not in addresses_columns + ] + if missing_columns: + raise ValueError(f"Colunas ausentes em enderecos: {missing_columns}.") + + complete = {field: address_fields.get(field) for field in ADDRESS_FIELDS} + return complete + diff --git a/python-package/src/geocodebr/geocode.py b/python-package/src/geocodebr/geocode.py new file mode 100644 index 0000000..78dca9b --- /dev/null +++ b/python-package/src/geocodebr/geocode.py @@ -0,0 +1,429 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import duckdb +import pyarrow as pa + +from .cache import listar_pasta_cache +from .constants import ALL_POSSIBLE_MATCH_TYPES, DATA_RELEASE +from .db import create_geocodebr_db +from .download_cnefe import download_cnefe +from .errors import error_input_nao_padronizado +from .fields import assert_and_assign_address_fields, definir_campos +from .matching import ( + create_output_db, + select_match_function, + trata_empates_geocode_duckdb, +) +from .messages import ( + message_looking_for_matches, + message_preparando_output, + message_standardizing_addresses, +) +from .utils import ( + add_precision_col, + check_clean_colnames, + cria_col_logradouro_confusao, + find_cached_parquet, + get_key_cols, + merge_results_to_input, + quote_ident, + sql_string, +) + + +def busca_por_cep( + cep: str | list[str] | tuple[str, ...], + h3_res: int | list[int] | tuple[int, ...] | None = None, + resultado_sf: bool = False, + verboso: bool = True, + cache: bool = True, +) -> pa.Table: + if resultado_sf: + raise NotImplementedError("resultado_sf=True sera implementado com geopandas na proxima etapa.") + _assert_bool(verboso, "verboso") + _assert_bool(cache, "cache") + h3_values = _normalize_h3_res(h3_res) + ceps = _normalize_ceps(cep) + if not ceps: + raise ValueError("Nenhum CEP valido foi informado.") + + download_cnefe("municipio_logradouro_cep_localidade", verboso=verboso, cache=cache) + con = create_geocodebr_db() + try: + path_to_parquet = ( + Path(listar_pasta_cache()) + / f"geocodebr_data_release_{DATA_RELEASE}" + / "municipio_logradouro_cep_localidade.parquet" + ).as_posix() + unique_ceps = ", ".join(sql_string(value) for value in sorted(set(ceps))) + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE output_df AS + SELECT cep, estado, municipio, logradouro, localidade, lon, lat + FROM read_parquet('{path_to_parquet}') m + WHERE m.cep IN ({unique_ceps}) + """ + ) + missing = sorted(set(ceps) - set(row[0] for row in con.execute("SELECT DISTINCT cep FROM output_df").fetchall())) + if len(missing) == len(set(ceps)): + raise ValueError("Nenhum CEP foi encontrado.") + if missing: + values = ", ".join(f"({sql_string(value)})" for value in missing) + con.execute(f"INSERT INTO output_df (cep) VALUES {values}") + _add_h3_columns(con, "output_df", h3_values) + return con.execute("SELECT * FROM output_df").to_arrow_table() + finally: + con.close() + + +def geocode( + enderecos: Any, + campos_endereco: dict[str, str | None] | None = None, + resultado_completo: bool = False, + resolver_empates: bool = True, + resultado_sf: bool = False, + h3_res: int | list[int] | tuple[int, ...] | None = None, + padronizar_enderecos: bool = True, + verboso: bool = True, + cache: bool = True, + n_cores: int | None = None, +) -> pa.Table: + if resultado_sf: + raise NotImplementedError("resultado_sf=True sera implementado com geopandas na proxima etapa.") + for name, value in { + "resultado_completo": resultado_completo, + "resolver_empates": resolver_empates, + "padronizar_enderecos": padronizar_enderecos, + "verboso": verboso, + "cache": cache, + }.items(): + _assert_bool(value, name) + h3_values = _normalize_h3_res(h3_res) + if campos_endereco is None: + campos_endereco = definir_campos(estado="estado", municipio="municipio") + + download_cnefe("todas", verboso=verboso, cache=cache) + con = create_geocodebr_db(n_cores=n_cores) + try: + _register_input(con, enderecos) + input_columns = _table_columns(con, "enderecos_input") + check_clean_colnames(input_columns) + campos_endereco = assert_and_assign_address_fields(campos_endereco, input_columns) + + con.execute( + """ + CREATE OR REPLACE TEMP TABLE input_db AS + SELECT *, ROW_NUMBER() OVER ()::INTEGER AS tempidgeocodebr + FROM enderecos_input + """ + ) + original_columns = [col for col in input_columns] + ["tempidgeocodebr"] + + if padronizar_enderecos: + message_standardizing_addresses(verboso) + _create_standardized_input(con, campos_endereco) + else: + _create_standardized_input_from_padr(con) + + _assert_standardized_columns(con) + con.execute("ALTER TABLE input_padrao_db ADD COLUMN temp_lograd_determ TEXT") + con.execute("ALTER TABLE input_padrao_db ADD COLUMN similaridade_logradouro DOUBLE") + cria_col_logradouro_confusao(con) + create_output_db(con, resultado_completo) + + if verboso: + message_looking_for_matches(verboso) + + n_rows = con.execute("SELECT COUNT(*) FROM input_padrao_db").fetchone()[0] + matched_rows = 0 + input_padrao_columns = set(_table_columns(con, "input_padrao_db")) + for match_type in ALL_POSSIBLE_MATCH_TYPES: + key_cols = get_key_cols(match_type) + if all(col in input_padrao_columns for col in key_cols): + match_fun = select_match_function(match_type) + affected = match_fun( + con, + match_type=match_type, + key_cols=key_cols, + resultado_completo=resultado_completo, + ) + matched_rows += affected + if matched_rows == n_rows: + break + + message_preparando_output(verboso) + empates_resolvidos = trata_empates_geocode_duckdb( + con, resultado_completo, resolver_empates, verboso + ) + output_table_to_use = "output_db" if empates_resolvidos == 0 else "output_db2" + add_precision_col(con, output_table_to_use) + merge_results_to_input( + con, + x="input_db", + y=output_table_to_use, + select_columns=original_columns, + resultado_completo=resultado_completo, + ) + _add_h3_columns(con, "geocodebr_result", h3_values) + con.execute( + """ + CREATE OR REPLACE TEMP TABLE geocodebr_result AS + SELECT * EXCLUDE (tempidgeocodebr) + FROM geocodebr_result + ORDER BY tempidgeocodebr + """ + ) + return con.execute("SELECT * FROM geocodebr_result").to_arrow_table() + finally: + con.close() + + +def _register_input(con: duckdb.DuckDBPyConnection, enderecos: Any) -> None: + if isinstance(enderecos, (str, Path)): + path = Path(enderecos) + suffix = path.suffix.lower() + path_sql = path.as_posix() + if suffix == ".parquet": + con.execute(f"CREATE OR REPLACE TEMP TABLE enderecos_input AS SELECT * FROM read_parquet('{path_sql}')") + elif suffix in {".csv", ".txt"}: + con.execute(f"CREATE OR REPLACE TEMP TABLE enderecos_input AS SELECT * FROM read_csv_auto('{path_sql}')") + else: + raise ValueError("Arquivos suportados: .parquet, .csv, .txt.") + return + + con.register("enderecos_input_view", enderecos) + con.execute("CREATE OR REPLACE TEMP TABLE enderecos_input AS SELECT * FROM enderecos_input_view") + con.unregister("enderecos_input_view") + + +def _create_standardized_input( + con: duckdb.DuckDBPyConnection, + campos_endereco: dict[str, str | None], +) -> None: + select_parts = [] + for field in ["estado", "municipio", "logradouro", "numero", "cep", "localidade"]: + source = campos_endereco.get(field) + if source is None: + expr = "NULL" + elif field == "numero": + expr = f"TRY_CAST(NULLIF(REGEXP_REPLACE(CAST({quote_ident(source)} AS VARCHAR), '[^0-9]', '', 'g'), '') AS INTEGER)" + elif field == "cep": + expr = f"NULLIF(REGEXP_REPLACE(CAST({quote_ident(source)} AS VARCHAR), '[^0-9]', '', 'g'), '')" + else: + expr = f"NULLIF(_geocodebr_norm(CAST({quote_ident(source)} AS VARCHAR)), '')" + out_name = "bairro" if field == "localidade" else field + select_parts.append(f"{expr} AS {out_name}") + + _install_normalize_function(con) + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE input_padrao_db AS + SELECT {", ".join(select_parts)}, tempidgeocodebr + FROM input_db + """ + ) + con.execute("ALTER TABLE input_padrao_db RENAME bairro TO localidade") + _resolve_estado_names(con) + _resolve_municipio_codes(con) + + +def _create_standardized_input_from_padr(con: duckdb.DuckDBPyConnection) -> None: + cols = _table_columns(con, "input_db") + padded = { + "estado_padr": "estado", + "municipio_padr": "municipio", + "logradouro_padr": "logradouro", + "numero_padr": "numero", + "cep_padr": "cep", + "bairro_padr": "localidade", + } + if not set(padded).issubset(cols): + error_input_nao_padronizado() + selects = [f"{src} AS {dst}" for src, dst in padded.items()] + selects.append("tempidgeocodebr") + con.execute(f"CREATE OR REPLACE TEMP TABLE input_padrao_db AS SELECT {', '.join(selects)} FROM input_db") + + +def _assert_standardized_columns(con: duckdb.DuckDBPyConnection) -> None: + expected = {"estado", "municipio", "logradouro", "numero", "cep", "localidade"} + if not expected.issubset(set(_table_columns(con, "input_padrao_db"))): + error_input_nao_padronizado() + + +def _install_normalize_function(con: duckdb.DuckDBPyConnection) -> None: + import unicodedata + + estados = { + "ACRE": "AC", + "ALAGOAS": "AL", + "AMAPA": "AP", + "AMAZONAS": "AM", + "BAHIA": "BA", + "CEARA": "CE", + "DISTRITO FEDERAL": "DF", + "ESPIRITO SANTO": "ES", + "GOIAS": "GO", + "MARANHAO": "MA", + "MATO GROSSO": "MT", + "MATO GROSSO DO SUL": "MS", + "MINAS GERAIS": "MG", + "PARA": "PA", + "PARAIBA": "PB", + "PARANA": "PR", + "PERNAMBUCO": "PE", + "PIAUI": "PI", + "RIO DE JANEIRO": "RJ", + "RIO GRANDE DO NORTE": "RN", + "RIO GRANDE DO SUL": "RS", + "RONDONIA": "RO", + "RORAIMA": "RR", + "SANTA CATARINA": "SC", + "SAO PAULO": "SP", + "SERGIPE": "SE", + "TOCANTINS": "TO", + } + + def normalize(value: str | None) -> str | None: + if value is None: + return None + text = unicodedata.normalize("NFKD", str(value)) + text = "".join(ch for ch in text if not unicodedata.combining(ch)) + text = text.upper() + text = "".join(ch if ch.isalnum() else " " for ch in text) + return " ".join(text.split()) + + def normalize_uf(value: str | None) -> str | None: + text = normalize(value) + if text is None: + return None + if len(text) == 2: + return text + return estados.get(text, text) + + try: + con.create_function("_geocodebr_norm", normalize, ["VARCHAR"], "VARCHAR") + except duckdb.InvalidInputException: + pass + try: + con.create_function("_geocodebr_uf", normalize_uf, ["VARCHAR"], "VARCHAR") + except duckdb.InvalidInputException: + pass + + +def _resolve_estado_names(con: duckdb.DuckDBPyConnection) -> None: + con.execute( + """ + UPDATE input_padrao_db + SET estado = _geocodebr_uf(estado) + WHERE estado IS NOT NULL + """ + ) + + +def _resolve_municipio_codes(con: duckdb.DuckDBPyConnection) -> None: + from .cache import listar_dados_cache + + try: + path = find_cached_parquet(listar_dados_cache(), "municipio") + except FileNotFoundError: + return + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_ref AS + SELECT * FROM read_parquet('{path}') LIMIT 0 + """ + ) + cols = set(_table_columns(con, "_geocodebr_municipio_ref")) + code_col = next( + (col for col in ["cod_muni", "code_muni", "cod_municipio", "codigo_municipio"] if col in cols), + None, + ) + if code_col is None or "municipio" not in cols: + return + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_ref AS + SELECT DISTINCT CAST({quote_ident(code_col)} AS VARCHAR) AS municipio_codigo, + municipio AS municipio_nome + FROM read_parquet('{path}') + WHERE {quote_ident(code_col)} IS NOT NULL + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET municipio = ref.municipio_nome + FROM _geocodebr_municipio_ref ref + WHERE REGEXP_MATCHES(input_padrao_db.municipio, '^[0-9]{7}$') + AND input_padrao_db.municipio = ref.municipio_codigo + """ + ) + + +def _add_h3_columns( + con: duckdb.DuckDBPyConnection, + table_name: str, + h3_values: list[int], +) -> None: + if not h3_values: + return + import h3 + + def h3_cell(lat: float | None, lon: float | None, res: int) -> str | None: + if lat is None or lon is None: + return None + if hasattr(h3, "latlng_to_cell"): + return h3.latlng_to_cell(lat, lon, res) + return h3.geo_to_h3(lat, lon, res) + + try: + con.create_function("_geocodebr_h3", h3_cell, ["DOUBLE", "DOUBLE", "INTEGER"], "VARCHAR") + except duckdb.InvalidInputException: + pass + + for value in h3_values: + colname = f"h3_{value:02d}" + con.execute(f"ALTER TABLE {quote_ident(table_name)} ADD COLUMN {quote_ident(colname)} TEXT") + con.execute( + f""" + UPDATE {quote_ident(table_name)} + SET {quote_ident(colname)} = _geocodebr_h3(lat, lon, {value}) + WHERE lat IS NOT NULL + """ + ) + + +def _normalize_ceps(cep: str | list[str] | tuple[str, ...]) -> list[str]: + values = [cep] if isinstance(cep, str) else list(cep) + out = [] + for value in values: + if not isinstance(value, str): + raise TypeError("cep deve ser string ou sequencia de strings.") + digits = "".join(ch for ch in value if ch.isdigit()) + if len(digits) == 8: + out.append(digits) + return sorted(set(out)) + + +def _normalize_h3_res(h3_res: int | list[int] | tuple[int, ...] | None) -> list[int]: + if h3_res is None: + return [] + values = [h3_res] if isinstance(h3_res, int) else list(h3_res) + for value in values: + if not isinstance(value, int) or value < 0 or value > 15: + raise ValueError("h3_res deve conter inteiros entre 0 e 15.") + return values + + +def _assert_bool(value: bool, name: str) -> None: + if not isinstance(value, bool): + raise TypeError(f"{name} deve ser True ou False.") + + +def _table_columns(con: duckdb.DuckDBPyConnection, table_name: str) -> list[str]: + return [row[1] for row in con.execute(f"PRAGMA table_info('{table_name}')").fetchall()] diff --git a/python-package/src/geocodebr/matching.py b/python-package/src/geocodebr/matching.py new file mode 100644 index 0000000..5791259 --- /dev/null +++ b/python-package/src/geocodebr/matching.py @@ -0,0 +1,444 @@ +from __future__ import annotations + +import duckdb + +from .constants import ( + EXACT_TYPES_NO_NUMBER, + NUMBER_EXACT_TYPES, + NUMBER_INTERPOLATION_TYPES, + PROBABILISTIC_EXACT_TYPES, + PROBABILISTIC_INTERPOLATION_TYPES, + PROBABILISTIC_TYPES_NO_NUMBER, +) +from .string_dist import calculate_string_dist +from .tables import register_cnefe_table, register_unique_logradouros_table +from .utils import get_key_cols, get_reference_table, quote_ident, update_input_db + + +def create_output_db(con: duckdb.DuckDBPyConnection, resultado_completo: bool) -> None: + columns = [ + "tempidgeocodebr INTEGER", + "lat DOUBLE", + "lon DOUBLE", + "endereco_encontrado TEXT", + "logradouro_encontrado TEXT", + "tipo_resultado TEXT", + "contagem_cnefe INTEGER", + "desvio_metros INTEGER", + "log_causa_confusao BOOLEAN", + "similaridade_logradouro DOUBLE", + ] + if resultado_completo: + columns.extend( + [ + "numero_encontrado INTEGER", + "localidade_encontrada TEXT", + "cep_encontrado TEXT", + "municipio_encontrado TEXT", + "estado_encontrado TEXT", + "empate BOOLEAN", + "cod_setor TEXT", + ] + ) + con.execute(f"CREATE OR REPLACE TEMP TABLE output_db ({', '.join(columns)})") + + +def match_cases( + con: duckdb.DuckDBPyConnection, + x: str = "input_padrao_db", + output_tb: str = "output_db", + key_cols: list[str] | None = None, + match_type: str = "", + resultado_completo: bool = False, +) -> int: + y = get_reference_table(match_type) + key_cols = get_key_cols(match_type) + register_cnefe_table(con, match_type) + + join_condition = " AND ".join(f"{y}.{col} = {x}.{col}" for col in key_cols) + cols_not_null = " AND ".join(f"{x}.{col} IS NOT NULL" for col in key_cols) + colunas_encontradas, additional_cols = _complete_columns(y, key_cols, resultado_completo) + + con.execute( + f""" + INSERT INTO output_db ( + tempidgeocodebr, lat, lon, endereco_encontrado, tipo_resultado, + desvio_metros, log_causa_confusao, contagem_cnefe {colunas_encontradas} + ) + SELECT {x}.tempidgeocodebr, + {y}.lat, + {y}.lon, + {y}.endereco_completo AS endereco_encontrado, + '{match_type}' AS tipo_resultado, + {y}.desvio_metros, + {x}.log_causa_confusao, + {y}.n_casos AS contagem_cnefe {additional_cols} + FROM {x} + INNER JOIN {y} + ON {join_condition} + WHERE {cols_not_null} + """ + ) + return update_input_db(con, update_tb=x, reference_tb=output_tb) + + +def match_weighted_cases( + con: duckdb.DuckDBPyConnection, + x: str = "input_padrao_db", + output_tb: str = "output_db", + key_cols: list[str] | None = None, + match_type: str = "", + resultado_completo: bool = False, +) -> int: + y = get_reference_table(match_type) + original_key_cols = get_key_cols(match_type) + register_cnefe_table(con, match_type) + + cols_not_null = " AND ".join(f"{x}.{col} IS NOT NULL" for col in original_key_cols) + key_cols = [col for col in original_key_cols if col != "numero"] + join_condition = " AND ".join(f"{y}.{col} = {x}.{col}" for col in key_cols) + colunas_encontradas, additional_first, additional_second = _complete_weighted_columns(y, key_cols, resultado_completo) + + con.execute( + f""" + WITH temp_db AS ( + SELECT {x}.tempidgeocodebr, + {x}.numero, + {y}.numero AS numero_cnefe, + {y}.lat, {y}.lon, + REGEXP_REPLACE({y}.endereco_completo, ', \\d+ -', CONCAT(', ', {x}.numero, ' (aprox) -')) AS endereco_encontrado, + {y}.desvio_metros, + {x}.log_causa_confusao, + {y}.n_casos AS contagem_cnefe {additional_first} + FROM {x} + INNER JOIN {y} + ON {join_condition} + WHERE {cols_not_null} + ) + INSERT INTO output_db ( + tempidgeocodebr, lat, lon, endereco_encontrado, tipo_resultado, + desvio_metros, log_causa_confusao, contagem_cnefe {colunas_encontradas} + ) + SELECT tempidgeocodebr, + SUM((1 / ABS(numero - numero_cnefe) * lat)) / SUM(1 / ABS(numero - numero_cnefe)) AS lat, + SUM((1 / ABS(numero - numero_cnefe) * lon)) / SUM(1 / ABS(numero - numero_cnefe)) AS lon, + FIRST(endereco_encontrado) AS endereco_encontrado, + '{match_type}' AS tipo_resultado, + AVG(desvio_metros) AS desvio_metros, + FIRST(log_causa_confusao) AS log_causa_confusao, + FIRST(contagem_cnefe) AS contagem_cnefe {additional_second} + FROM temp_db + GROUP BY tempidgeocodebr, endereco_encontrado + """ + ) + return update_input_db(con, update_tb=x, reference_tb=output_tb) + + +def match_cases_probabilistic( + con: duckdb.DuckDBPyConnection, + x: str = "input_padrao_db", + output_tb: str = "output_db", + key_cols: list[str] | None = None, + match_type: str = "", + resultado_completo: bool = False, +) -> int: + y = get_reference_table(match_type) + key_cols = get_key_cols(match_type) + register_cnefe_table(con, match_type) + unique_logradouros_tbl = register_unique_logradouros_table(con, match_type) + calculate_string_dist(con, match_type, unique_logradouros_tbl) + + join_condition = " AND ".join(f"{y}.{col} = {x}.{col}" for col in key_cols) + join_condition = join_condition.replace("input_padrao_db.logradouro", "input_padrao_db.temp_lograd_determ") + cols_not_null = " AND ".join(f"{x}.{col} IS NOT NULL" for col in key_cols) + cols_not_null = cols_not_null.replace(".logradouro", ".temp_lograd_determ") + colunas_encontradas, additional_cols = _complete_columns(y, key_cols, resultado_completo, probabilistic=True) + + con.execute( + f""" + INSERT INTO output_db ( + tempidgeocodebr, lat, lon, endereco_encontrado, tipo_resultado, + desvio_metros, log_causa_confusao, contagem_cnefe {colunas_encontradas} + ) + SELECT {x}.tempidgeocodebr, + {y}.lat, + {y}.lon, + {y}.endereco_completo AS endereco_encontrado, + '{match_type}' AS tipo_resultado, + {y}.desvio_metros, + {x}.log_causa_confusao, + {y}.n_casos AS contagem_cnefe {additional_cols} + FROM {x} + INNER JOIN {y} + ON {join_condition} + WHERE {cols_not_null} + """ + ) + return update_input_db(con, update_tb=x, reference_tb=output_tb) + + +def match_weighted_cases_probabilistic( + con: duckdb.DuckDBPyConnection, + x: str = "input_padrao_db", + output_tb: str = "output_db", + key_cols: list[str] | None = None, + match_type: str = "", + resultado_completo: bool = False, +) -> int: + y = get_reference_table(match_type) + original_key_cols = get_key_cols(match_type) + register_cnefe_table(con, match_type) + unique_logradouros_tbl = register_unique_logradouros_table(con, match_type) + calculate_string_dist(con, match_type, unique_logradouros_tbl) + + cols_not_null = " AND ".join(f"{x}.{col} IS NOT NULL" for col in original_key_cols) + key_cols = [col for col in original_key_cols if col != "numero"] + join_condition = " AND ".join(f"{y}.{col} = {x}.{col}" for col in key_cols) + join_condition = join_condition.replace("input_padrao_db.logradouro", "input_padrao_db.temp_lograd_determ") + cols_not_null_match = cols_not_null.replace(".logradouro", ".temp_lograd_determ") + colunas_encontradas, additional_first, additional_second = _complete_weighted_columns(y, key_cols, resultado_completo) + + con.execute( + f""" + WITH temp_db AS ( + SELECT {x}.tempidgeocodebr, + {x}.numero, + {y}.numero AS numero_cnefe, + {y}.lat, {y}.lon, + REGEXP_REPLACE({y}.endereco_completo, ', \\d+ -', CONCAT(', ', {x}.numero, ' (aprox) -')) AS endereco_encontrado, + {x}.similaridade_logradouro, + {y}.desvio_metros, + {x}.log_causa_confusao, + {y}.n_casos AS contagem_cnefe {additional_first} + FROM {x} + INNER JOIN {y} + ON {join_condition} + WHERE {cols_not_null_match} + ) + INSERT INTO output_db ( + tempidgeocodebr, lat, lon, endereco_encontrado, tipo_resultado, + desvio_metros, log_causa_confusao, similaridade_logradouro, contagem_cnefe {colunas_encontradas} + ) + SELECT tempidgeocodebr, + SUM((1 / ABS(numero - numero_cnefe) * lat)) / SUM(1 / ABS(numero - numero_cnefe)) AS lat, + SUM((1 / ABS(numero - numero_cnefe) * lon)) / SUM(1 / ABS(numero - numero_cnefe)) AS lon, + FIRST(endereco_encontrado) AS endereco_encontrado, + '{match_type}' AS tipo_resultado, + AVG(desvio_metros) AS desvio_metros, + FIRST(log_causa_confusao) AS log_causa_confusao, + FIRST(similaridade_logradouro) AS similaridade_logradouro, + FIRST(contagem_cnefe) AS contagem_cnefe {additional_second} + FROM temp_db + GROUP BY tempidgeocodebr, endereco_encontrado + """ + ) + return update_input_db(con, update_tb=x, reference_tb=output_tb) + + +def select_match_function(match_type: str): + if match_type in NUMBER_EXACT_TYPES or match_type in EXACT_TYPES_NO_NUMBER: + return match_cases + if match_type in NUMBER_INTERPOLATION_TYPES: + return match_weighted_cases + if match_type in PROBABILISTIC_EXACT_TYPES or match_type in PROBABILISTIC_TYPES_NO_NUMBER: + return match_cases_probabilistic + if match_type in PROBABILISTIC_INTERPOLATION_TYPES: + return match_weighted_cases_probabilistic + raise ValueError(f"match_type sem funcao: {match_type}") + + +def trata_empates_geocode_duckdb( + con: duckdb.DuckDBPyConnection, + resultado_completo: bool, + resolver_empates: bool, + verboso: bool, +) -> int: + n_casos_empate = con.execute( + """ + SELECT COUNT(*) AS n_casos_empate + FROM ( + SELECT tempidgeocodebr + FROM output_db + GROUP BY tempidgeocodebr + HAVING COUNT(*) > 1 + ) AS repeated + """ + ).fetchone()[0] + + if n_casos_empate == 0: + return 0 + + if not resolver_empates: + con.execute( + """ + CREATE OR REPLACE TEMP TABLE output_db2 AS + SELECT *, + (COUNT(*) OVER (PARTITION BY tempidgeocodebr) > 1) AS empate + FROM output_db + """ + ) + return n_casos_empate + + con.execute( + """ + CREATE MACRO IF NOT EXISTS haversine(lat1, lon1, lat2, lon2) AS ( + 6378137 * 2 * ASIN( + SQRT( + POWER(SIN(RADIANS(lat2 - lat1) / 2), 2) + + COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * + POWER(SIN(RADIANS(lon2 - lon1) / 2), 2) + ) + ) + ) + """ + ) + + additional_cols_final = "" + cols_encontradas = "" + if resultado_completo: + additional_cols_final = """ + , logradouro_encontrado, numero_encontrado, cep_encontrado, + localidade_encontrada, municipio_encontrado, estado_encontrado, + similaridade_logradouro, contagem_cnefe, empate, cod_setor + """ + cols_encontradas = """ + , logradouro_encontrado, numero_encontrado, cep_encontrado, + localidade_encontrada, municipio_encontrado, estado_encontrado, + similaridade_logradouro, cod_setor + """ + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE output_db2 AS + WITH + base AS ( + SELECT *, + (COUNT(*) OVER (PARTITION BY tempidgeocodebr) > 1) AS empate_inicial, + ROW_NUMBER() OVER ( + PARTITION BY tempidgeocodebr + ORDER BY contagem_cnefe DESC, desvio_metros, endereco_encontrado + ) AS id + FROM output_db + ), + distd AS ( + SELECT b.*, + CASE WHEN empate_inicial THEN + haversine( + lat, lon, + LEAD(lat) OVER (PARTITION BY tempidgeocodebr ORDER BY id), + LEAD(lon) OVER (PARTITION BY tempidgeocodebr ORDER BY id) + ) + END AS dist_geocodebr_metros + FROM base b + ), + filtered AS ( + SELECT d.*, + (COUNT(*) OVER (PARTITION BY tempidgeocodebr) > 1) AS empate, + MAX(dist_geocodebr_metros) OVER (PARTITION BY tempidgeocodebr) AS max_dist + FROM distd d + WHERE (empate_inicial IS FALSE) + OR (empate_inicial AND dist_geocodebr_metros IS NULL) + OR (empate_inicial AND dist_geocodebr_metros > 300) + ), + df_sem_empate AS ( + SELECT tempidgeocodebr, lat, lon, endereco_encontrado, tipo_resultado, + contagem_cnefe, desvio_metros, empate {cols_encontradas} + FROM filtered + WHERE empate = FALSE + ), + df_empates_perdidos AS ( + SELECT tempidgeocodebr, lat, lon, endereco_encontrado, tipo_resultado, + contagem_cnefe, desvio_metros, TRUE AS empate {cols_encontradas} + FROM filtered + WHERE empate = TRUE + AND ( + max_dist > 1000 + OR log_causa_confusao + OR REGEXP_MATCHES(endereco_encontrado, + '(RUA (QUATRO|QUATORZE|QUINZE|DEZESSEIS|DEZESSETE|DEZOITO|DEZENOVE|VINTE|TRINTA|QUARENTA|CINQUENTA|SESSENTA|SETENTA|OITENTA|NOVENTA))' + ) + ) + AND NOT REGEXP_MATCHES(logradouro_encontrado, '\\bDE (JANEIRO|FEVEREIRO|MARCO|ABRIL|MAIO|JUNHO|JULHO|AGOSTO|SETEMBRO|OUTUBRO|NOVEMBRO|DEZEMBRO)\\b') + QUALIFY ROW_NUMBER() OVER (PARTITION BY tempidgeocodebr ORDER BY contagem_cnefe DESC) = 1 + ), + empates_restantes AS ( + SELECT f.* + FROM filtered f + WHERE f.empate = TRUE + AND NOT EXISTS (SELECT 1 FROM df_sem_empate s WHERE s.tempidgeocodebr = f.tempidgeocodebr) + AND NOT EXISTS (SELECT 1 FROM df_empates_perdidos p WHERE p.tempidgeocodebr = f.tempidgeocodebr) + ), + empates_wavg AS ( + SELECT e.*, + (SUM(lat * contagem_cnefe) OVER (PARTITION BY tempidgeocodebr) + / NULLIF(SUM(contagem_cnefe) OVER (PARTITION BY tempidgeocodebr), 0)) AS lat_wavg, + (SUM(lon * contagem_cnefe) OVER (PARTITION BY tempidgeocodebr) + / NULLIF(SUM(contagem_cnefe) OVER (PARTITION BY tempidgeocodebr), 0)) AS lon_wavg + FROM empates_restantes e + ), + df_empates_salve AS ( + SELECT tempidgeocodebr, lat_wavg AS lat, lon_wavg AS lon, + endereco_encontrado, tipo_resultado, contagem_cnefe, + desvio_metros, TRUE AS empate {cols_encontradas} + FROM empates_wavg + QUALIFY ROW_NUMBER() OVER (PARTITION BY tempidgeocodebr ORDER BY contagem_cnefe DESC) = 1 + ) + SELECT tempidgeocodebr, lat, lon, tipo_resultado, desvio_metros, + endereco_encontrado {additional_cols_final} + FROM df_sem_empate + UNION ALL + SELECT tempidgeocodebr, lat, lon, tipo_resultado, desvio_metros, + endereco_encontrado {additional_cols_final} + FROM df_empates_perdidos + UNION ALL + SELECT tempidgeocodebr, lat, lon, tipo_resultado, desvio_metros, + endereco_encontrado {additional_cols_final} + FROM df_empates_salve + """ + ) + + if verboso: + plural = "caso" if n_casos_empate == 1 else "casos" + print(f"Foram encontrados e resolvidos {n_casos_empate} {plural} de empate.") + return n_casos_empate + + +def _complete_columns( + y: str, + key_cols: list[str], + resultado_completo: bool, + probabilistic: bool = False, +) -> tuple[str, str]: + if not resultado_completo: + return "", "" + + output_cols = [_found_col_name(col) for col in key_cols] + select_cols = [f"{y}.{col} AS {_found_col_name(col)}" for col in key_cols] + if probabilistic: + output_cols.append("similaridade_logradouro") + select_cols.append("input_padrao_db.similaridade_logradouro AS similaridade_logradouro") + output_cols.append("cod_setor") + select_cols.append(f"{y}.cod_setor AS cod_setor") + return ", " + ", ".join(output_cols), ", " + ", ".join(select_cols) + + +def _complete_weighted_columns( + y: str, + key_cols: list[str], + resultado_completo: bool, +) -> tuple[str, str, str]: + if not resultado_completo: + return "", "", "" + + output_cols = [_found_col_name(col) for col in key_cols] + ["cod_setor"] + first_cols = [f"{y}.{col} AS {_found_col_name(col)}" for col in key_cols] + first_cols.append(f"{y}.cod_setor AS cod_setor") + second_cols = [f"FIRST({_found_col_name(col)}) AS {_found_col_name(col)}" for col in key_cols] + second_cols.append("FIRST(cod_setor) AS cod_setor") + return ", " + ", ".join(output_cols), ", " + ", ".join(first_cols), ", " + ", ".join(second_cols) + + +def _found_col_name(col: str) -> str: + if col == "localidade": + return "localidade_encontrada" + return f"{col}_encontrado" diff --git a/python-package/src/geocodebr/messages.py b/python-package/src/geocodebr/messages.py new file mode 100644 index 0000000..03aa234 --- /dev/null +++ b/python-package/src/geocodebr/messages.py @@ -0,0 +1,28 @@ +def inform(message: str, verboso: bool = True) -> None: + if verboso: + print(message) + + +def message_standardizing_addresses(verboso: bool = True) -> None: + inform("Padronizando enderecos de entrada", verboso) + + +def message_baixando_cnefe(verboso: bool = True) -> None: + inform("Baixando dados do CNEFE", verboso) + + +def message_usando_cnefe_local(verboso: bool = True) -> None: + inform("Utilizando dados do CNEFE armazenados localmente", verboso) + + +def message_looking_for_matches(verboso: bool = True) -> None: + inform("Geolocalizando enderecos", verboso) + + +def message_preparando_output(verboso: bool = True) -> None: + inform("Preparando resultados", verboso) + + +def message_cache(verboso: bool = True) -> None: + inform("Nenhum dado em cache local", verboso) + diff --git a/python-package/src/geocodebr/reverse.py b/python-package/src/geocodebr/reverse.py new file mode 100644 index 0000000..3cf0848 --- /dev/null +++ b/python-package/src/geocodebr/reverse.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import duckdb +import pyarrow as pa + +from .cache import listar_pasta_cache +from .constants import DATA_RELEASE +from .db import create_geocodebr_db +from .download_cnefe import download_cnefe +from .geocode import _register_input, _table_columns +from .utils import check_clean_colnames, quote_ident + +def geocode_reverso( + pontos: Any, + dist_max: int = 1000, + verboso: bool = True, + cache: bool = True, + n_cores: int | None = None, +) -> pa.Table: + if not isinstance(dist_max, (int, float)) or dist_max < 500 or dist_max > 100000: + raise ValueError("dist_max deve estar entre 500 e 100000 metros.") + if not isinstance(verboso, bool) or not isinstance(cache, bool): + raise TypeError("verboso e cache devem ser True ou False.") + + download_cnefe( + "municipio_logradouro_numero_cep_localidade", + verboso=verboso, + cache=cache, + ) + con = create_geocodebr_db(n_cores=n_cores, load_spatial=True) + try: + _register_points_input(con, pontos) + input_columns = _table_columns(con, "pontos_input") + check_clean_colnames(input_columns) + lon_col, lat_col = _detect_coordinate_columns(input_columns) + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE pontos_db AS + SELECT *, + ROW_NUMBER() OVER ()::INTEGER AS tempidgeocodebr, + CAST({quote_ident(lon_col)} AS DOUBLE) AS _geocodebr_lon, + CAST({quote_ident(lat_col)} AS DOUBLE) AS _geocodebr_lat + FROM pontos_input + """ + ) + _validate_points_bbox(con) + + bbox = con.execute( + """ + SELECT + MIN(_geocodebr_lon), MIN(_geocodebr_lat), + MAX(_geocodebr_lon), MAX(_geocodebr_lat) + FROM pontos_db + """ + ).fetchone() + margin = float(dist_max) / 111_320 + 0.05 + xmin, ymin, xmax, ymax = ( + bbox[0] - margin, + bbox[1] - margin, + bbox[2] + margin, + bbox[3] + margin, + ) + + path_to_parquet = ( + Path(listar_pasta_cache()) + / f"geocodebr_data_release_{DATA_RELEASE}" + / "municipio_logradouro_numero_cep_localidade.parquet" + ).as_posix() + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE cnefe_tb AS + SELECT + estado, municipio, logradouro, numero, cep, localidade, + lon AS cnefe_lon, + lat AS cnefe_lat, + ST_Transform( + ST_Point(CAST(lon AS DOUBLE), CAST(lat AS DOUBLE)), + 'EPSG:4674', + 'EPSG:31983', + always_xy := true + ) AS cnefe_geom_utm + FROM read_parquet('{path_to_parquet}') + WHERE lon BETWEEN {xmin} AND {xmax} + AND lat BETWEEN {ymin} AND {ymax} + """ + ) + con.execute( + """ + CREATE OR REPLACE TEMP TABLE pontos_utm AS + SELECT *, + ST_Transform( + ST_Point(_geocodebr_lon, _geocodebr_lat), + 'EPSG:4674', + 'EPSG:31983', + always_xy := true + ) AS ponto_geom_utm + FROM pontos_db + """ + ) + + original_columns = [ + col + for col in input_columns + if col not in {"_geocodebr_lon", "_geocodebr_lat", "tempidgeocodebr"} + ] + select_original = ", ".join(f"p.{quote_ident(col)}" for col in original_columns) + address_select = _address_select_clause(set(original_columns)) + leading_comma = ", " if select_original else "" + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE geocodebr_reverse_result AS + WITH ranked AS ( + SELECT + {select_original}{leading_comma} + {address_select}, + c.cnefe_lon AS lon_encontrado, + c.cnefe_lat AS lat_encontrado, + ST_Distance(p.ponto_geom_utm, c.cnefe_geom_utm) AS distancia_metros, + ROW_NUMBER() OVER ( + PARTITION BY p.tempidgeocodebr + ORDER BY ST_Distance(p.ponto_geom_utm, c.cnefe_geom_utm) + ) AS rn, + p.tempidgeocodebr + FROM pontos_utm p + JOIN cnefe_tb c + ON ST_DWithin(p.ponto_geom_utm, c.cnefe_geom_utm, {float(dist_max)}) + ) + SELECT * EXCLUDE (rn, tempidgeocodebr) + FROM ranked + WHERE rn = 1 + ORDER BY tempidgeocodebr + """ + ) + n_rows = con.execute("SELECT COUNT(*) FROM geocodebr_reverse_result").fetchone()[0] + if n_rows == 0: + raise ValueError("Nenhum endereco proximo foi encontrado.") + return con.execute("SELECT * FROM geocodebr_reverse_result").to_arrow_table() + finally: + con.close() + + +def _register_points_input(con: duckdb.DuckDBPyConnection, pontos: Any) -> None: + if _looks_like_geodataframe(pontos): + epsg = pontos.crs.to_epsg() if pontos.crs is not None else None + if epsg != 4674: + raise ValueError("Dados de input precisam estar em SIRGAS 2000, EPSG 4674.") + geometry_name = pontos.geometry.name + attrs = pontos.drop(columns=[geometry_name]).copy() + attrs["_geocodebr_lon"] = pontos.geometry.x + attrs["_geocodebr_lat"] = pontos.geometry.y + con.register("pontos_input_view", attrs) + con.execute("CREATE OR REPLACE TEMP TABLE pontos_input AS SELECT * FROM pontos_input_view") + con.unregister("pontos_input_view") + return + + _register_input(con, pontos) + con.execute("CREATE OR REPLACE TEMP TABLE pontos_input AS SELECT * FROM enderecos_input") + + +def _looks_like_geodataframe(value: Any) -> bool: + return hasattr(value, "geometry") and hasattr(value, "crs") + + +def _detect_coordinate_columns(columns: list[str]) -> tuple[str, str]: + candidates = [ + ("lon", "lat"), + ("longitude", "latitude"), + ("x", "y"), + ("_geocodebr_lon", "_geocodebr_lat"), + ] + column_set = set(columns) + for lon_col, lat_col in candidates: + if lon_col in column_set and lat_col in column_set: + return lon_col, lat_col + raise ValueError("pontos deve ter colunas lon/lat, longitude/latitude, x/y ou ser um GeoDataFrame.") + + +def _validate_points_bbox(con: duckdb.DuckDBPyConnection) -> None: + xmin, ymin, xmax, ymax = con.execute( + """ + SELECT + MIN(_geocodebr_lon), MIN(_geocodebr_lat), + MAX(_geocodebr_lon), MAX(_geocodebr_lat) + FROM pontos_db + """ + ).fetchone() + bbox_brazil = { + "xmin": -73.99044997, + "ymin": -33.75208127, + "xmax": -28.83594354, + "ymax": 5.27184108, + } + if ( + xmin < bbox_brazil["xmin"] + or xmax > bbox_brazil["xmax"] + or ymin < bbox_brazil["ymin"] + or ymax > bbox_brazil["ymax"] + ): + raise ValueError("Coordenadas de input localizadas fora do bounding box do Brasil.") + + +def _address_select_clause(original_columns: set[str]) -> str: + parts = [] + for col in ["estado", "municipio", "logradouro", "numero", "cep", "localidade"]: + out_col = col if col not in original_columns else f"{col}_encontrado" + parts.append(f"c.{col} AS {out_col}") + return ", ".join(parts) diff --git a/python-package/src/geocodebr/string_dist.py b/python-package/src/geocodebr/string_dist.py new file mode 100644 index 0000000..9b85b50 --- /dev/null +++ b/python-package/src/geocodebr/string_dist.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import duckdb + +from .utils import get_key_cols, get_prob_match_cutoff, quote_ident + + +def calculate_string_dist( + con: duckdb.DuckDBPyConnection, + match_type: str, + unique_logradouros_tbl: str, +) -> None: + key_cols = get_key_cols(match_type) + cols_not_null = " AND ".join(f"input_padrao_db.{col} IS NOT NULL" for col in key_cols) + lookup_cols = [col for col in key_cols if col not in {"numero", "logradouro"}] + join_condition_lookup = " AND ".join( + f"{quote_ident(unique_logradouros_tbl)}.{col} = input_padrao_db.{col}" + for col in lookup_cols + ) + min_cutoff = get_prob_match_cutoff(match_type) + + con.execute( + f""" + WITH to_compute AS ( + SELECT + input_padrao_db.tempidgeocodebr, + input_padrao_db.logradouro AS logradouro_input, + {quote_ident(unique_logradouros_tbl)}.logradouro AS logradouro_cnefe + FROM input_padrao_db + JOIN {quote_ident(unique_logradouros_tbl)} + ON {join_condition_lookup} + WHERE input_padrao_db.similaridade_logradouro IS NULL + AND input_padrao_db.log_causa_confusao = FALSE + AND {cols_not_null} + ), + computed AS ( + SELECT + tempidgeocodebr, + logradouro_cnefe, + CAST(jaro_similarity(logradouro_input, logradouro_cnefe) AS NUMERIC(5,3)) AS similarity, + RANK() OVER (PARTITION BY tempidgeocodebr ORDER BY similarity DESC, logradouro_cnefe) AS rank + FROM to_compute + WHERE similarity > {min_cutoff} + ) + UPDATE input_padrao_db + SET temp_lograd_determ = computed.logradouro_cnefe, + similaridade_logradouro = similarity + FROM computed + WHERE input_padrao_db.tempidgeocodebr = computed.tempidgeocodebr + AND computed.rank = 1 + """ + ) + diff --git a/python-package/src/geocodebr/tables.py b/python-package/src/geocodebr/tables.py new file mode 100644 index 0000000..9f73b46 --- /dev/null +++ b/python-package/src/geocodebr/tables.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import duckdb + +from .cache import listar_dados_cache +from .utils import find_cached_parquet, get_key_cols, get_reference_table, quote_ident + + +def register_cnefe_table(con: duckdb.DuckDBPyConnection, match_type: str) -> bool: + cnefe_table_name = get_reference_table(match_type) + exists = con.execute( + "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = ?", + [cnefe_table_name], + ).fetchone()[0] + if exists: + return True + + path_to_parquet = find_cached_parquet(listar_dados_cache(), cnefe_table_name) + con.execute( + f""" + CREATE TEMP TABLE IF NOT EXISTS {quote_ident(cnefe_table_name)} AS + WITH unique_munis AS ( + SELECT DISTINCT municipio FROM input_padrao_db + ), + unique_states AS ( + SELECT DISTINCT estado FROM input_padrao_db + ) + SELECT * + FROM read_parquet('{path_to_parquet}') m + WHERE m.estado IN (SELECT estado FROM unique_states) + AND m.municipio IN (SELECT municipio FROM unique_munis) + """ + ) + return True + + +def register_unique_logradouros_table(con: duckdb.DuckDBPyConnection, match_type: str) -> str: + key_cols = get_key_cols(match_type) + cnefe_table_name = ( + "municipio_logradouro_localidade" + if match_type in {"pn03", "pa03", "pl03"} + else "municipio_logradouro_cep_localidade" + ) + table_name = f"unique_logr_{cnefe_table_name}" + exists = con.execute( + "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = ?", + [table_name], + ).fetchone()[0] + if exists: + return table_name + + select_cols = [col for col in key_cols if col != "numero"] + distinct = "" + if not (cnefe_table_name == "municipio_logradouro_localidade" or {"localidade", "cep"} <= set(select_cols)): + distinct = "DISTINCT" + select_cols_sql = ", ".join(quote_ident(col) for col in select_cols) + + base_exists = con.execute( + "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = ?", + [cnefe_table_name], + ).fetchone()[0] + if base_exists: + con.execute( + f""" + CREATE TEMP TABLE IF NOT EXISTS {quote_ident(table_name)} AS + WITH unique_munis AS ( + SELECT DISTINCT municipio FROM input_padrao_db + ), + unique_states AS ( + SELECT DISTINCT estado FROM input_padrao_db + ) + SELECT {distinct} {select_cols_sql} + FROM {quote_ident(cnefe_table_name)} + WHERE estado IN (SELECT estado FROM unique_states) + AND municipio IN (SELECT municipio FROM unique_munis) + """ + ) + else: + path_to_parquet = find_cached_parquet(listar_dados_cache(), cnefe_table_name) + con.execute( + f""" + CREATE TEMP TABLE IF NOT EXISTS {quote_ident(table_name)} AS + WITH unique_munis AS ( + SELECT DISTINCT municipio FROM input_padrao_db + ) + SELECT {distinct} {select_cols_sql} + FROM read_parquet('{path_to_parquet}') m + WHERE m.municipio IN (SELECT municipio FROM unique_munis) + """ + ) + return table_name + diff --git a/python-package/src/geocodebr/utils.py b/python-package/src/geocodebr/utils.py new file mode 100644 index 0000000..f6417c3 --- /dev/null +++ b/python-package/src/geocodebr/utils.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +import re +from pathlib import Path + +import duckdb + +from .constants import DATA_RELEASE + + +def quote_ident(name: str) -> str: + if not re.match(r"^[A-Za-z0-9_]+$", name): + raise ValueError(f"Nome SQL invalido: {name}") + return name + + +def sql_string(value: str) -> str: + return "'" + value.replace("'", "''") + "'" + + +def check_clean_colnames(columns: list[str]) -> None: + bad_cols = [col for col in columns if not re.match(r"^[A-Za-z0-9_]+$", col)] + if bad_cols: + raise ValueError( + "Column names must use only letters, numbers, and underscores. " + f"Please rename: {bad_cols}" + ) + + +def get_key_cols(match_type: str) -> list[str]: + if match_type in {"dn01", "da01", "pn01", "pa01"}: + return ["estado", "municipio", "logradouro", "numero", "cep", "localidade"] + if match_type in {"dn02", "da02", "pn02", "pa02"}: + return ["estado", "municipio", "logradouro", "numero", "cep"] + if match_type in {"dn03", "da03", "pn03", "pa03"}: + return ["estado", "municipio", "logradouro", "numero", "localidade"] + if match_type in {"dn04", "da04", "pn04", "pa04"}: + return ["estado", "municipio", "logradouro", "numero"] + if match_type in {"dl01", "pl01"}: + return ["estado", "municipio", "logradouro", "cep", "localidade"] + if match_type in {"dl02", "pl02"}: + return ["estado", "municipio", "logradouro", "cep"] + if match_type in {"dl03", "pl03"}: + return ["estado", "municipio", "logradouro", "localidade"] + if match_type in {"dl04", "pl04"}: + return ["estado", "municipio", "logradouro"] + if match_type == "dc01": + return ["estado", "municipio", "cep", "localidade"] + if match_type == "dc02": + return ["estado", "municipio", "cep"] + if match_type == "db01": + return ["estado", "municipio", "localidade"] + if match_type == "dm01": + return ["estado", "municipio"] + raise ValueError(f"match_type desconhecido: {match_type}") + + +def get_reference_table(match_type: str) -> str: + key_cols = get_key_cols(match_type) + table_name = "_".join(key_cols).replace("estado_municipio", "municipio") + + if re.search(r"dn02|pn02|da02|pa02|dn03|pn03", match_type): + table_name = "municipio_logradouro_numero_cep_localidade" + if re.search(r"da03|pa03|dn04|da04", match_type): + table_name = "municipio_logradouro_numero_localidade" + if re.search(r"dl02|pl02|dl03|pl03", match_type): + table_name = "municipio_logradouro_cep_localidade" + if re.search(r"dl04", match_type): + table_name = "municipio_logradouro_localidade" + + return table_name + + +def get_prob_match_cutoff(match_type: str) -> float: + return 0.85 if match_type in {"pn01", "pa01", "pl01"} else 0.9 + + +def find_cached_parquet(cache_files: list[str], table_name: str) -> str: + suffix = f"{table_name}.parquet" + matches = [ + file + for file in cache_files + if Path(file).name == suffix and DATA_RELEASE in str(file) + ] + if not matches: + raise FileNotFoundError( + f"Arquivo {suffix} nao encontrado no cache. Execute download_cnefe()." + ) + return matches[0].replace("\\", "/") + + +def update_input_db( + con: duckdb.DuckDBPyConnection, + update_tb: str = "input_padrao_db", + reference_tb: str = "output_db", +) -> int: + before = con.execute(f"SELECT COUNT(*) FROM {quote_ident(update_tb)}").fetchone()[0] + con.execute( + f""" + DELETE FROM {quote_ident(update_tb)} + WHERE tempidgeocodebr IN ( + SELECT tempidgeocodebr FROM {quote_ident(reference_tb)} + ) + """ + ) + after = con.execute(f"SELECT COUNT(*) FROM {quote_ident(update_tb)}").fetchone()[0] + return before - after + + +def add_precision_col(con: duckdb.DuckDBPyConnection, update_tb: str) -> None: + update_tb = quote_ident(update_tb) + con.execute(f"ALTER TABLE {update_tb} ADD COLUMN precisao TEXT") + con.execute( + f""" + UPDATE {update_tb} + SET precisao = CASE + WHEN tipo_resultado IN ('dn01', 'dn02', 'dn03', 'dn04', + 'pn01', 'pn02', 'pn03', 'pn04') THEN 'numero' + WHEN tipo_resultado IN ('da01', 'da02', 'da03', 'da04', + 'pa01', 'pa02', 'pa03', 'pa04') THEN 'numero_aproximado' + WHEN tipo_resultado IN ('dl01', 'dl02', 'dl03', 'dl04', + 'pl01', 'pl02', 'pl03', 'pl04') THEN 'logradouro' + WHEN tipo_resultado IN ('dc01', 'dc02') THEN 'cep' + WHEN tipo_resultado = 'db01' THEN 'localidade' + WHEN tipo_resultado = 'dm01' THEN 'municipio' + ELSE NULL + END + """ + ) + + +def merge_results_to_input( + con: duckdb.DuckDBPyConnection, + x: str, + y: str, + select_columns: list[str], + resultado_completo: bool, +) -> None: + select_columns_y = [ + "lat", + "lon", + "precisao", + "tipo_resultado", + "desvio_metros", + "endereco_encontrado", + ] + if resultado_completo: + select_columns_y.extend( + [ + "logradouro_encontrado", + "numero_encontrado", + "cep_encontrado", + "localidade_encontrada", + "municipio_encontrado", + "estado_encontrado", + "similaridade_logradouro", + "contagem_cnefe", + "empate", + "cod_setor", + ] + ) + con.execute( + f""" + UPDATE {quote_ident(y)} + SET similaridade_logradouro = COALESCE(similaridade_logradouro, 1) + """ + ) + + select_x = ", ".join(f"{quote_ident(x)}.{quote_ident(col)}" for col in select_columns) + select_y = ", ".join(f"{quote_ident(y)}.{quote_ident(col)}" for col in select_columns_y) + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE geocodebr_result AS + SELECT {select_x}, {select_y} + FROM {quote_ident(x)} + LEFT JOIN {quote_ident(y)} + ON {quote_ident(x)}.tempidgeocodebr = {quote_ident(y)}.tempidgeocodebr + ORDER BY {quote_ident(x)}.tempidgeocodebr + """ + ) + + +def cria_col_logradouro_confusao(con: duckdb.DuckDBPyConnection) -> None: + con.execute("ALTER TABLE input_padrao_db ADD COLUMN log_causa_confusao BOOLEAN DEFAULT false") + ruas_num_ext = "|".join( + "RUA " + value + for value in ["UM", "DOIS", "TRES", "CINCO", "SEIS", "SETE", "OITO", "NOVE", "DEZ", "ONZE", "DOZE", "TREZE"] + ) + con.execute( + rf""" + UPDATE input_padrao_db + SET log_causa_confusao = true + WHERE + ( + REGEXP_MATCHES(logradouro, '^(RUA|TRAVESSA|RAMAL|BECO|BLOCO|AVENIDA|RODOVIA|ESTRADA)\s+([A-Z]{{1,2}}-?|[0-9]{{1,3}}|[A-Z]{{1,2}}-?[0-9]{{1,3}}|[A-Z]{{1,2}}\s+[0-9]{{1,3}}|[0-9]{{1,3}}-?[A-Z]{{1,2}})(\s+KM( \d+)?)?$') + OR REGEXP_MATCHES(logradouro, '({ruas_num_ext})$') + ) + AND NOT REGEXP_MATCHES(logradouro, '\bDE (JANEIRO|FEVEREIRO|MARCO|ABRIL|MAIO|JUNHO|JULHO|AGOSTO|SETEMBRO|OUTUBRO|NOVEMBRO|DEZEMBRO)\b') + """ + ) + diff --git a/python-package/tests/conftest.py b/python-package/tests/conftest.py new file mode 100644 index 0000000..0e0bb38 --- /dev/null +++ b/python-package/tests/conftest.py @@ -0,0 +1,19 @@ +from pathlib import Path + +import pytest + +from geocodebr.cache import listar_arquivo_config + + +@pytest.fixture(autouse=True) +def restore_cache_config(): + config_file = Path(listar_arquivo_config()) + existed = config_file.exists() + content = config_file.read_text(encoding="utf-8") if existed else None + yield + if existed: + config_file.parent.mkdir(parents=True, exist_ok=True) + config_file.write_text(content, encoding="utf-8") + elif config_file.exists(): + config_file.unlink() + diff --git a/python-package/tests/test_busca_por_cep.py b/python-package/tests/test_busca_por_cep.py new file mode 100644 index 0000000..7958bb0 --- /dev/null +++ b/python-package/tests/test_busca_por_cep.py @@ -0,0 +1,32 @@ +from pathlib import Path + +import pyarrow as pa +import pyarrow.parquet as pq + +from geocodebr import busca_por_cep, definir_pasta_cache +from geocodebr.constants import DATA_RELEASE + + +def test_busca_por_cep_duckdb_flow(tmp_path): + definir_pasta_cache(str(tmp_path), verboso=False) + data_dir = tmp_path / f"geocodebr_data_release_{DATA_RELEASE}" + data_dir.mkdir() + table = pa.table( + { + "cep": ["70390025", "20071001"], + "estado": ["DF", "RJ"], + "municipio": ["BRASILIA", "RIO DE JANEIRO"], + "logradouro": ["AVENIDA TESTE", "RUA TESTE"], + "localidade": ["CENTRO", "CENTRO"], + "lon": [-47.9, -43.2], + "lat": [-15.8, -22.9], + } + ) + pq.write_table(table, data_dir / "municipio_logradouro_cep_localidade.parquet") + + out = busca_por_cep(["70390-025", "99999-999"], h3_res=3, verboso=False) + + assert out.num_rows == 2 + assert "h3_03" in out.schema.names + assert out.column("cep").to_pylist() == ["70390025", "99999999"] + diff --git a/python-package/tests/test_cache.py b/python-package/tests/test_cache.py new file mode 100644 index 0000000..a810f03 --- /dev/null +++ b/python-package/tests/test_cache.py @@ -0,0 +1,13 @@ +from pathlib import Path + +from geocodebr import definir_pasta_cache, listar_dados_cache, listar_pasta_cache + + +def test_cache_roundtrip(tmp_path): + assert definir_pasta_cache(str(tmp_path), verboso=False) == str(tmp_path) + assert listar_pasta_cache() == str(tmp_path) + + (tmp_path / "a.parquet").write_text("", encoding="utf-8") + (tmp_path / "b.parquet").write_text("", encoding="utf-8") + assert [Path(path).name for path in listar_dados_cache()] == ["a.parquet", "b.parquet"] + diff --git a/python-package/tests/test_fields.py b/python-package/tests/test_fields.py new file mode 100644 index 0000000..4c940d5 --- /dev/null +++ b/python-package/tests/test_fields.py @@ -0,0 +1,22 @@ +import pytest + +from geocodebr import definir_campos + + +def test_definir_campos_preserves_public_names(): + campos = definir_campos( + estado="uf", + municipio="cidade", + logradouro="rua", + numero="num", + cep="cep", + localidade="bairro", + ) + assert list(campos) == ["logradouro", "numero", "cep", "localidade", "municipio", "estado"] + assert campos["estado"] == "uf" + + +def test_definir_campos_rejects_non_string(): + with pytest.raises(TypeError): + definir_campos(estado="uf", municipio=1) + diff --git a/python-package/tests/test_geocode.py b/python-package/tests/test_geocode.py new file mode 100644 index 0000000..d1e59d3 --- /dev/null +++ b/python-package/tests/test_geocode.py @@ -0,0 +1,55 @@ +import pyarrow as pa +import pyarrow.parquet as pq + +from geocodebr import definir_campos, definir_pasta_cache, geocode +from geocodebr.constants import ALL_CNEFE_FILES, DATA_RELEASE + + +def test_geocode_exact_number_match_with_duckdb(tmp_path): + definir_pasta_cache(str(tmp_path), verboso=False) + data_dir = tmp_path / f"geocodebr_data_release_{DATA_RELEASE}" + data_dir.mkdir() + cnefe = pa.table( + { + "estado": ["DF"], + "municipio": ["BRASILIA"], + "logradouro": ["AVENIDA TESTE"], + "numero": [100], + "cep": ["70000000"], + "localidade": ["CENTRO"], + "lon": [-47.9], + "lat": [-15.8], + "endereco_completo": ["AVENIDA TESTE, 100 - CENTRO, BRASILIA - DF"], + "desvio_metros": [10], + "n_casos": [1], + "cod_setor": ["001"], + } + ) + for file in ALL_CNEFE_FILES: + pq.write_table(cnefe, data_dir / file) + + enderecos = pa.table( + { + "uf": ["Distrito Federal"], + "cidade": ["Brasilia"], + "rua": ["Avenida Teste"], + "num": ["100"], + "cep_in": ["70000-000"], + "bairro": ["Centro"], + } + ) + campos = definir_campos( + estado="uf", + municipio="cidade", + logradouro="rua", + numero="num", + cep="cep_in", + localidade="bairro", + ) + + out = geocode(enderecos, campos, resultado_completo=True, h3_res=3, verboso=False) + + assert out.num_rows == 1 + assert out.column("tipo_resultado").to_pylist() == ["dn01"] + assert out.column("precisao").to_pylist() == ["numero"] + assert "h3_03" in out.schema.names diff --git a/python-package/tests/test_geocode_reverso.py b/python-package/tests/test_geocode_reverso.py new file mode 100644 index 0000000..db85329 --- /dev/null +++ b/python-package/tests/test_geocode_reverso.py @@ -0,0 +1,32 @@ +import pyarrow as pa +import pyarrow.parquet as pq + +from geocodebr import definir_pasta_cache, geocode_reverso +from geocodebr.constants import DATA_RELEASE + + +def test_geocode_reverso_with_duckdb_spatial(tmp_path): + definir_pasta_cache(str(tmp_path), verboso=False) + data_dir = tmp_path / f"geocodebr_data_release_{DATA_RELEASE}" + data_dir.mkdir() + cnefe = pa.table( + { + "estado": ["DF", "DF"], + "municipio": ["BRASILIA", "BRASILIA"], + "logradouro": ["AVENIDA PROXIMA", "AVENIDA DISTANTE"], + "numero": [100, 200], + "cep": ["70000000", "70000001"], + "localidade": ["CENTRO", "CENTRO"], + "lon": [-47.9000, -48.5000], + "lat": [-15.8000, -16.3000], + } + ) + pq.write_table(cnefe, data_dir / "municipio_logradouro_numero_cep_localidade.parquet") + pontos = pa.table({"id": [1], "lon": [-47.9001], "lat": [-15.8001]}) + + out = geocode_reverso(pontos, dist_max=1000, verboso=False) + + assert out.num_rows == 1 + assert out.column("logradouro").to_pylist() == ["AVENIDA PROXIMA"] + assert out.column("distancia_metros").to_pylist()[0] < 100 + From 43ebfcebcddbd520d36cfa521bc5429dfa8b25e1 Mon Sep 17 00:00:00 2001 From: "ANJOS, J. S." <0rakul0render@gmail.com> Date: Tue, 9 Jun 2026 08:54:29 -0300 Subject: [PATCH 2/3] testes passados. --- .gitignore | 2 + .idea/.gitignore | 10 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/geocodebr.iml | 25 + .idea/inspectionProfiles/Project_Default.xml | 88 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .../pyproject.toml => pyproject.toml | 18 +- python-package/MIGRATION_PLAN.md | 184 --- python-package/README.md | 18 + python-package/exemple/busca_por_cep.py | 23 + python-package/exemple/enderecos.csv | 6 + python-package/exemple/geocode_enderecos.py | 47 + python-package/exemple/geocode_reverso.py | 26 + .../{src => }/geocodebr/__init__.py | 0 python-package/{src => }/geocodebr/cache.py | 0 .../{src => }/geocodebr/constants.py | 0 python-package/{src => }/geocodebr/db.py | 0 .../{src => }/geocodebr/download_cnefe.py | 0 python-package/{src => }/geocodebr/errors.py | 0 python-package/{src => }/geocodebr/fields.py | 0 python-package/{src => }/geocodebr/geocode.py | 10 +- .../{src => }/geocodebr/matching.py | 2 +- .../{src => }/geocodebr/messages.py | 0 python-package/{src => }/geocodebr/reverse.py | 0 .../{src => }/geocodebr/string_dist.py | 0 python-package/{src => }/geocodebr/tables.py | 0 python-package/{src => }/geocodebr/utils.py | 0 python-package/tests/test_busca_por_cep.py | 2 - uv.lock | 1087 +++++++++++++++++ 32 files changed, 1385 insertions(+), 196 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/geocodebr.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml rename python-package/pyproject.toml => pyproject.toml (74%) delete mode 100644 python-package/MIGRATION_PLAN.md create mode 100644 python-package/exemple/busca_por_cep.py create mode 100644 python-package/exemple/enderecos.csv create mode 100644 python-package/exemple/geocode_enderecos.py create mode 100644 python-package/exemple/geocode_reverso.py rename python-package/{src => }/geocodebr/__init__.py (100%) rename python-package/{src => }/geocodebr/cache.py (100%) rename python-package/{src => }/geocodebr/constants.py (100%) rename python-package/{src => }/geocodebr/db.py (100%) rename python-package/{src => }/geocodebr/download_cnefe.py (100%) rename python-package/{src => }/geocodebr/errors.py (100%) rename python-package/{src => }/geocodebr/fields.py (100%) rename python-package/{src => }/geocodebr/geocode.py (97%) rename python-package/{src => }/geocodebr/matching.py (99%) rename python-package/{src => }/geocodebr/messages.py (100%) rename python-package/{src => }/geocodebr/reverse.py (100%) rename python-package/{src => }/geocodebr/string_dist.py (100%) rename python-package/{src => }/geocodebr/tables.py (100%) rename python-package/{src => }/geocodebr/utils.py (100%) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 80314c3..4ba4c79 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ docs /data_prep/data/* /data_prep/data_raw/* *.pyc +__pycache__/ +.venv/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..0a8642f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Zeppelin ignored files +/ZeppelinRemoteNotebooks/ diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/geocodebr.iml b/.idea/geocodebr.iml new file mode 100644 index 0000000..819d626 --- /dev/null +++ b/.idea/geocodebr.iml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..d2ddf35 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,88 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4c6b496 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..72bccd7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/python-package/pyproject.toml b/pyproject.toml similarity index 74% rename from python-package/pyproject.toml rename to pyproject.toml index 0322229..ee76326 100644 --- a/python-package/pyproject.toml +++ b/pyproject.toml @@ -6,30 +6,34 @@ build-backend = "hatchling.build" name = "geocodebr" version = "0.0.1" description = "Geolocalizacao de enderecos brasileiros com DuckDB" -readme = "README.md" +readme = "python-package/README.md" requires-python = ">=3.10" license = { text = "MIT" } dependencies = [ "duckdb>=1.0.0", + "h3>=4.0.0", + "numpy>=1.26.0", + "platformdirs>=4.0.0", "pyarrow>=15.0.0", "requests>=2.31.0", - "platformdirs>=4.0.0", "tqdm>=4.66.0", - "h3>=4.0.0", ] [project.optional-dependencies] geo = [ "geopandas>=0.14.0", - "shapely>=2.0.0", "pyproj>=3.6.0", + "shapely>=2.0.0", ] -test = [ + +[dependency-groups] +dev = [ "pytest>=8.0.0", ] [tool.hatch.build.targets.wheel] -packages = ["src/geocodebr"] +packages = ["python-package/geocodebr"] [tool.pytest.ini_options] -pythonpath = ["src"] +pythonpath = ["python-package"] +testpaths = ["python-package/tests"] diff --git a/python-package/MIGRATION_PLAN.md b/python-package/MIGRATION_PLAN.md deleted file mode 100644 index 1cf4f77..0000000 --- a/python-package/MIGRATION_PLAN.md +++ /dev/null @@ -1,184 +0,0 @@ -# Plano de migracao R -> Python - -Este documento resume a analise dos scripts R do pacote `geocodebr` e propoe -uma versao Python preservando a dinamica de uso e os nomes das funcoes publicas. - -## API publica a manter - -As funcoes exportadas no `NAMESPACE` devem existir tambem no pacote Python: - -- `definir_campos(estado, municipio, logradouro=None, numero=None, cep=None, localidade=None)` -- `geocode(enderecos, campos_endereco=..., resultado_completo=False, resolver_empates=True, resultado_sf=False, h3_res=None, padronizar_enderecos=True, verboso=True, cache=True, n_cores=None)` -- `busca_por_cep(cep, h3_res=None, resultado_sf=False, verboso=True, cache=True)` -- `geocode_reverso(pontos, dist_max=1000, verboso=True, cache=True, n_cores=None)` -- `download_cnefe(tabela="todas", verboso=True, cache=True)` -- `definir_pasta_cache(path, verboso=True)` -- `listar_pasta_cache()` -- `listar_dados_cache(print_tree=False)` -- `deletar_pasta_cache()` - -Em Python, a recomendacao e manter esses nomes em portugues para reduzir a -curva de aprendizado. Internamente, os modulos podem ser separados por dominio. - -## Estrutura sugerida - -```text -python-package/ - pyproject.toml - src/geocodebr/ - __init__.py - cache.py - download_cnefe.py - fields.py - geocode.py - reverse.py - db.py - matching.py - tables.py - string_dist.py - utils.py - errors.py - messages.py - tests/ - test_cache.py - test_fields.py - test_busca_por_cep.py - test_geocode.py - test_geocode_reverso.py -``` - -## Dependencias Python recomendadas - -- `duckdb`: motor SQL central e tambem a camada principal de manipulacao - tabular. A versao Python deve manter os dados em tabelas/views DuckDB sempre - que possivel, evitando transformar o fluxo interno em `pandas`. -- `pyarrow`: leitura/escrita Parquet e interoperabilidade com DuckDB. -- `requests` ou `httpx`: download dos Parquets do release CNEFE. -- `platformdirs`: diretorio de cache/config persistente equivalente a `tools::R_user_dir`. -- `tqdm`: barra de progresso equivalente a `cli`/progress bar. -- `geopandas`, `shapely`, `pyproj`: saida espacial e `geocode_reverso`. -- `h3`: criacao de colunas `h3_03`, `h3_04` etc. -- `rapidfuzz` ou UDF DuckDB: apenas se `jaro_similarity` nao estiver disponivel - de forma consistente na instalacao DuckDB Python. -- Uma camada propria de padronizacao de endereco ou dependencia Python - equivalente a `enderecobr`. -- `pandas`: opcional apenas para aceitar/retornar dados no estilo familiar da - API Python. Internamente, nao deve ser o motor de processamento. - -## Pontos criticos de paridade - -1. `enderecobr` - - O R depende de `enderecobr::padronizar_enderecos`, `padronizar_ceps`, - `padronizar_municipios` e `correspondencia_campos`. - - A versao Python precisa gerar as mesmas colunas padronizadas: - `estado_padr`, `municipio_padr`, `logradouro_padr`, `numero_padr`, - `cep_padr`, `bairro_padr`. - - Este e o maior risco de divergencia entre R e Python. - -2. SQL DuckDB - - As funcoes de matching (`match_cases`, `match_weighted_cases`, - `match_cases_probabilistic`, `match_weighted_cases_probabilistic`) sao - quase totalmente SQL. - - A melhor estrategia e portar os templates SQL para Python com interpolacao - controlada, preservando nomes de tabelas temporarias e colunas. - - O DuckDB deve substituir tanto `data.table` quanto a maior parte do uso - potencial de `pandas`: padronizacao, filtros, joins, atualizacoes, - desempate, H3 e montagem do output devem preferir SQL/tabelas temporarias. - -3. Dados CNEFE - - `data_release` atual: `v0.4.1`. - - Fonte: `https://github.com/ipeaGIT/padronizacao_cnefe/releases/download/{data_release}/{arquivo}`. - - Os arquivos Parquet baixados sao a base compartilhada entre R e Python. - -4. Geometria - - `resultado_sf=TRUE` no R retorna `sf`. - - Em Python, o equivalente natural e `geopandas.GeoDataFrame` com CRS - `EPSG:4674`. - - `geocode_reverso` hoje usa DuckDB Spatial via `duckspatial`; em Python deve - usar `duckdb` com extensao `spatial` ou uma combinacao `geopandas`/`sjoin`. - -## Sequencia de implementacao sugerida - -1. Criar pacote Python basico com `pyproject.toml`, `src/geocodebr` e exports em - `__init__.py`. -2. Portar cache: - - `definir_pasta_cache` - - `listar_pasta_cache` - - `listar_dados_cache` - - `deletar_pasta_cache` -3. Portar `definir_campos` e validacoes de colunas. -4. Portar `download_cnefe`. -5. Portar constantes e utilitarios DuckDB-first: - - `data_release` - - `get_key_cols` - - `get_reference_table` - - listas de `match_type` - - `add_precision_col` - - `merge_results_to_input` -6. Portar funcoes de matching SQL. -7. Implementar/pinchar padronizacao de enderecos em Python. -8. Portar `busca_por_cep`. -9. Portar `geocode`. -10. Portar `geocode_reverso`. Primeira versao implementada com DuckDB Spatial, - aceitando `lon`/`lat`, `longitude`/`latitude`, `x`/`y` ou GeoDataFrame em - `EPSG:4674`, e retornando `pyarrow.Table`. -11. Criar testes Python com `pytest`, usando os testes R como contrato de - comportamento. - -## Mapeamento dos arquivos R - -- `R/cache.R` -> `cache.py` -- `R/download_cnefe.R` -> `download_cnefe.py` -- `R/definir_campos.R` -> `fields.py` -- `R/create_geocodebr_db.R` -> `db.py` -- `R/geocode.R` -> `geocode.py` -- `R/busca_por_cep.R` -> `geocode.py` ou `cep.py` -- `R/geocode_reverso.R` -> `reverse.py` -- `R/match_cases*.R`, `R/match_weighted_cases*.R` -> `matching.py` -- `R/register_cnefe_tables.R` -> `tables.py` -- `R/string_dist.R` -> `string_dist.py` -- `R/trata_empates_geocode_duckdb.R` -> `matching.py` ou `ties.py` -- `R/utils.R` -> `utils.py` -- `R/error.R`, `R/message.R`, `R/progress_bar.R` -> `errors.py`, `messages.py` - -## Contratos de saida importantes - -`geocode` deve aceitar dados de entrada em formatos convenientes, mas o fluxo -interno deve registrar a entrada diretamente no DuckDB. A saida padrao pode ser -uma relacao DuckDB materializada sob demanda ou um `DataFrame` para ergonomia da -API; a decisao deve ficar explicita na implementacao. - -`geocode` deve preservar as colunas originais e adicionar: - -- sempre: `lat`, `lon`, `precisao`, `tipo_resultado`, `desvio_metros`, - `endereco_encontrado` -- se `resultado_completo=True`: `logradouro_encontrado`, `numero_encontrado`, - `cep_encontrado`, `localidade_encontrada`, `municipio_encontrado`, - `estado_encontrado`, `similaridade_logradouro`, `contagem_cnefe`, `empate`, - `cod_setor` -- se `h3_res` for informado: `h3_03`, `h3_04`, etc. - -`busca_por_cep` deve retornar `cep`, `estado`, `municipio`, `logradouro`, -`localidade`, `lon`, `lat` e H3 quando solicitado. - -`geocode_reverso` deve receber pontos em `EPSG:4674`, validar bounding box do -Brasil e retornar o endereco mais proximo dentro de `dist_max`, com -`distancia_metros`. - -## Observacoes de risco - -- O teste de paridade deve comparar resultados Python vs R em amostras pequenas, - incluindo casos determiniscos, probabilisticos, interpolacao por numero, - empates e CEP inexistente. -- A implementacao deve evitar chamar `.df()`/`.fetchdf()` no meio do pipeline. - Essas chamadas devem ficar restritas a limites claros da API, por exemplo no - retorno final quando o usuario pedir um objeto Python em memoria. -- A padronizacao inicial ja cobre normalizacao de acentos/caixa, UF por extenso - para sigla, CEP numerico, numero inteiro e tentativa de municipio por codigo - IBGE quando a tabela `municipio.parquet` trouxer coluna de codigo reconhecida. - Ainda precisa de validacao ampla contra `enderecobr`. -- Ha possiveis bugs/quirks no R que talvez precisem ser replicados ou corrigidos - explicitamente. Exemplo: nos loops de H3, o nome da coluna usa `h3_res` em vez - do item `i`; funciona para vetor curto no teste, mas deve ser revisto ao portar. -- A versao Python deve evitar montar SQL com valores vindos diretamente do usuario. - Os nomes de colunas podem ser validados contra `^[A-Za-z0-9_]+$`, como no R. diff --git a/python-package/README.md b/python-package/README.md index 54be0d8..8602d18 100644 --- a/python-package/README.md +++ b/python-package/README.md @@ -49,6 +49,8 @@ As funcoes retornam, por padrao, um `pyarrow.Table`. Caso precise converter para Primeiro, indique quais colunas da sua tabela representam cada campo do endereco usando `definir_campos()`. Depois, chame `geocode()`. +O primeiro uso pode baixar os dados CNEFE em cache local. + ```python import pyarrow.csv as pv @@ -169,6 +171,22 @@ O resultado inclui: Se `h3_res` for informado, o pacote adiciona colunas como `h3_08` ou `h3_10`. +## Exemplos de uso do geocodebr Python + +Esta pasta contem exemplos simples usando as funcoes principais da versao Python: + +- `geocode()`: busca coordenadas a partir de enderecos. +- `busca_por_cep()`: busca enderecos/coordenadas a partir de CEPs. +- `geocode_reverso()`: busca endereco proximo a coordenadas. + +Execute os exemplos a partir da raiz do repositorio: + +```bash +uv run python exemple/geocode_enderecos.py +uv run python exemple/busca_por_cep.py +uv run python exemple/geocode_reverso.py +``` + ## Cache dos dados CNEFE Na primeira execucao, o pacote baixa arquivos Parquet do release CNEFE usado diff --git a/python-package/exemple/busca_por_cep.py b/python-package/exemple/busca_por_cep.py new file mode 100644 index 0000000..d29e8b6 --- /dev/null +++ b/python-package/exemple/busca_por_cep.py @@ -0,0 +1,23 @@ +from geocodebr import busca_por_cep + + +def main() -> None: + ceps = [ + "70390-025", + "20071-001", + "21530-015", + "99999-999", + ] + + resultado = busca_por_cep( + cep=ceps, + h3_res=10, + verboso=True, + ) + + print(resultado) + + +if __name__ == "__main__": + main() + diff --git a/python-package/exemple/enderecos.csv b/python-package/exemple/enderecos.csv new file mode 100644 index 0000000..624a91f --- /dev/null +++ b/python-package/exemple/enderecos.csv @@ -0,0 +1,6 @@ +id,logradouro,numero,cep,localidade,municipio,estado +1,Avenida Pastor Martin Luther King Jr,940,21530-015,Coelho Neto,Rio de Janeiro,RJ +2,Avenida Presidente Vargas,730,20071-001,Centro,Rio de Janeiro,RJ +3,Acampamento 1 de Julho,32,71693-994,Nucleo Rural Aguilhada,Brasilia,DF +4,Avenida Paulista,1578,01310-200,Bela Vista,Sao Paulo,SP +5,Rua XV de Novembro,30,80020-310,Centro,Curitiba,PR diff --git a/python-package/exemple/geocode_enderecos.py b/python-package/exemple/geocode_enderecos.py new file mode 100644 index 0000000..4fa6e77 --- /dev/null +++ b/python-package/exemple/geocode_enderecos.py @@ -0,0 +1,47 @@ +from pathlib import Path + +from geocodebr import definir_campos, geocode + + +ROOT = Path(__file__).resolve().parent + + +def main() -> None: + campos = definir_campos( + logradouro="logradouro", + numero="numero", + cep="cep", + localidade="localidade", + municipio="municipio", + estado="estado", + ) + + resultado = geocode( + enderecos=ROOT / "enderecos.csv", + campos_endereco=campos, + resultado_completo=True, + resolver_empates=True, + h3_res=10, + verboso=True, + ) + + cols = [ + "id", + "logradouro", + "numero", + "cep", + "localidade", + "municipio", + "estado", + "lat", + "lon", + "precisao", + "tipo_resultado", + "cep_encontrado", + "endereco_encontrado", + ] + print(resultado.select([col for col in cols if col in resultado.schema.names])) + + +if __name__ == "__main__": + main() diff --git a/python-package/exemple/geocode_reverso.py b/python-package/exemple/geocode_reverso.py new file mode 100644 index 0000000..f22fa50 --- /dev/null +++ b/python-package/exemple/geocode_reverso.py @@ -0,0 +1,26 @@ +import pyarrow as pa + +from geocodebr import geocode_reverso + + +def main() -> None: + pontos = pa.table( + { + "id": [1, 2, 3], + "lon": [-43.3523, -43.1763, -47.8825], + "lat": [-22.8327, -22.9046, -15.7942], + } + ) + + resultado = geocode_reverso( + pontos=pontos, + dist_max=1000, + verboso=True, + ) + + print(resultado) + + +if __name__ == "__main__": + main() + diff --git a/python-package/src/geocodebr/__init__.py b/python-package/geocodebr/__init__.py similarity index 100% rename from python-package/src/geocodebr/__init__.py rename to python-package/geocodebr/__init__.py diff --git a/python-package/src/geocodebr/cache.py b/python-package/geocodebr/cache.py similarity index 100% rename from python-package/src/geocodebr/cache.py rename to python-package/geocodebr/cache.py diff --git a/python-package/src/geocodebr/constants.py b/python-package/geocodebr/constants.py similarity index 100% rename from python-package/src/geocodebr/constants.py rename to python-package/geocodebr/constants.py diff --git a/python-package/src/geocodebr/db.py b/python-package/geocodebr/db.py similarity index 100% rename from python-package/src/geocodebr/db.py rename to python-package/geocodebr/db.py diff --git a/python-package/src/geocodebr/download_cnefe.py b/python-package/geocodebr/download_cnefe.py similarity index 100% rename from python-package/src/geocodebr/download_cnefe.py rename to python-package/geocodebr/download_cnefe.py diff --git a/python-package/src/geocodebr/errors.py b/python-package/geocodebr/errors.py similarity index 100% rename from python-package/src/geocodebr/errors.py rename to python-package/geocodebr/errors.py diff --git a/python-package/src/geocodebr/fields.py b/python-package/geocodebr/fields.py similarity index 100% rename from python-package/src/geocodebr/fields.py rename to python-package/geocodebr/fields.py diff --git a/python-package/src/geocodebr/geocode.py b/python-package/geocodebr/geocode.py similarity index 97% rename from python-package/src/geocodebr/geocode.py rename to python-package/geocodebr/geocode.py index 78dca9b..0b9b43d 100644 --- a/python-package/src/geocodebr/geocode.py +++ b/python-package/geocodebr/geocode.py @@ -64,10 +64,16 @@ def busca_por_cep( CREATE OR REPLACE TEMP TABLE output_df AS SELECT cep, estado, municipio, logradouro, localidade, lon, lat FROM read_parquet('{path_to_parquet}') m - WHERE m.cep IN ({unique_ceps}) + WHERE REGEXP_REPLACE(CAST(m.cep AS VARCHAR), '[^0-9]', '', 'g') IN ({unique_ceps}) """ ) - missing = sorted(set(ceps) - set(row[0] for row in con.execute("SELECT DISTINCT cep FROM output_df").fetchall())) + found_ceps = set( + row[0] + for row in con.execute( + "SELECT DISTINCT REGEXP_REPLACE(CAST(cep AS VARCHAR), '[^0-9]', '', 'g') FROM output_df" + ).fetchall() + ) + missing = sorted(set(ceps) - found_ceps) if len(missing) == len(set(ceps)): raise ValueError("Nenhum CEP foi encontrado.") if missing: diff --git a/python-package/src/geocodebr/matching.py b/python-package/geocodebr/matching.py similarity index 99% rename from python-package/src/geocodebr/matching.py rename to python-package/geocodebr/matching.py index 5791259..e0fda5e 100644 --- a/python-package/src/geocodebr/matching.py +++ b/python-package/geocodebr/matching.py @@ -12,7 +12,7 @@ ) from .string_dist import calculate_string_dist from .tables import register_cnefe_table, register_unique_logradouros_table -from .utils import get_key_cols, get_reference_table, quote_ident, update_input_db +from .utils import get_key_cols, get_reference_table, update_input_db def create_output_db(con: duckdb.DuckDBPyConnection, resultado_completo: bool) -> None: diff --git a/python-package/src/geocodebr/messages.py b/python-package/geocodebr/messages.py similarity index 100% rename from python-package/src/geocodebr/messages.py rename to python-package/geocodebr/messages.py diff --git a/python-package/src/geocodebr/reverse.py b/python-package/geocodebr/reverse.py similarity index 100% rename from python-package/src/geocodebr/reverse.py rename to python-package/geocodebr/reverse.py diff --git a/python-package/src/geocodebr/string_dist.py b/python-package/geocodebr/string_dist.py similarity index 100% rename from python-package/src/geocodebr/string_dist.py rename to python-package/geocodebr/string_dist.py diff --git a/python-package/src/geocodebr/tables.py b/python-package/geocodebr/tables.py similarity index 100% rename from python-package/src/geocodebr/tables.py rename to python-package/geocodebr/tables.py diff --git a/python-package/src/geocodebr/utils.py b/python-package/geocodebr/utils.py similarity index 100% rename from python-package/src/geocodebr/utils.py rename to python-package/geocodebr/utils.py diff --git a/python-package/tests/test_busca_por_cep.py b/python-package/tests/test_busca_por_cep.py index 7958bb0..e02524d 100644 --- a/python-package/tests/test_busca_por_cep.py +++ b/python-package/tests/test_busca_por_cep.py @@ -1,5 +1,3 @@ -from pathlib import Path - import pyarrow as pa import pyarrow.parquet as pq diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..50e6237 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1087 @@ +version = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182 }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329 }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230 }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890 }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930 }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109 }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684 }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785 }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055 }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502 }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295 }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145 }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884 }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343 }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174 }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705 }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419 }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901 }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742 }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061 }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239 }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173 }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841 }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304 }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455 }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036 }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739 }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277 }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819 }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281 }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843 }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328 }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061 }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031 }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239 }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589 }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733 }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652 }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229 }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552 }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806 }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316 }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274 }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468 }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460 }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330 }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828 }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627 }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008 }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303 }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282 }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595 }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986 }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711 }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036 }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998 }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056 }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537 }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176 }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723 }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085 }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819 }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915 }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234 }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042 }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706 }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727 }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882 }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860 }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564 }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276 }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238 }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189 }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352 }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024 }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869 }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541 }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634 }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384 }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133 }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257 }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851 }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393 }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609 }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014 }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979 }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238 }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110 }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824 }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103 }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194 }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827 }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168 }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018 }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "duckdb" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/00/d579dcb2a536b6ea3a2563cdad6844f77d81a9b2d4b22a858097f2468acf/duckdb-1.5.3.tar.gz", hash = "sha256:df39428eb130faa35ae96fd35245bdeae6ecf43936250b116b5fead568eb9f16", size = 18026640 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/4a/06a81e9e3c01634760073b6a8911fccee2e33b23569f479971f910228972/duckdb-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6ef8faf121d7b3ad95aab1c3ce31169a28be49da75abfa6099a1bec2e9a70189", size = 32575938 }, + { url = "https://files.pythonhosted.org/packages/a0/ed/03ccf15147db7468f65891b5daa8489aa755d8cfcf75b24e7a4e4720e28c/duckdb-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd00f70231951a619908471b7b6397232ff3be8ccd1f49a47f1a2ccac59eaba1", size = 17276167 }, + { url = "https://files.pythonhosted.org/packages/80/bd/ffc9e7a52731eec047dd49d45f029580dc8f3a6f8e8802a9e1c4138b0f8a/duckdb-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50379b85f3a0a169478d54880ef8bf971ecaa85772d05eeaa617d720c7704741", size = 15425037 }, + { url = "https://files.pythonhosted.org/packages/2f/5d/44209bcd4ad47feb84831edd3f269397e03167efbb86aee2d102b2f71252/duckdb-1.5.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d37650ec3ec8a951400ea12dc77edaea88e0baeda34801792776f95f2f922f4f", size = 19306975 }, + { url = "https://files.pythonhosted.org/packages/76/5c/bdb735011131c429cdb6d9e12e12fc8f1c8622158890d76a25819a58efc2/duckdb-1.5.3-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3fb3bad9bc1a3e101d66d33269142ce075dc3d75202ba74ba97d7e44c50b9cd", size = 21416979 }, + { url = "https://files.pythonhosted.org/packages/aa/c2/929666ff6d71b15b128c6f800d893fb5589c7b6430ff9ecdcd4e6d175bea/duckdb-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:fdc65233f0fcf9022e4c6a8ba2ba751a79deb291501073d660afb1aa9874051f", size = 13101915 }, + { url = "https://files.pythonhosted.org/packages/3c/fc/a8a89c6c73f31c2b58c6abbc2f543e0b736042dd5ef7cc1784c24ec31428/duckdb-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:341a2672e2551ba51c95c1898f0ade983e76675e79038ccb16342c3d6cfb82d7", size = 32583465 }, + { url = "https://files.pythonhosted.org/packages/63/f1/3423a2f523dd034e505d4a5dd8e210ae577212e152598dc13b6a5e736e1b/duckdb-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c9e8fa408705081160ede7ead238d16e73a36b8561b700f2bf2d650ae48e7b92", size = 17278520 }, + { url = "https://files.pythonhosted.org/packages/e1/1a/7bf5ba1b7ea520557e6b2dbee1c85abab016bdac0c1779d9d0ef76c87300/duckdb-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70a18f932cf6d87bd0e554613657a515c1443a1724aacfc7ec5137dd28698b03", size = 15424794 }, + { url = "https://files.pythonhosted.org/packages/ad/16/ce4b1e386e45fab0268edbf1b85bace20e9437589e9edb2bd5f9a226fa44/duckdb-1.5.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e80eb4d0fb59869cb2c7d7ef494c07fb92014fe8e77d96c170cd1ebc1488a708", size = 19306666 }, + { url = "https://files.pythonhosted.org/packages/99/1f/651f8453f26931e8061b7e27b3090f868868185814ecb9216d0bd71ec8ef/duckdb-1.5.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3248b49cd835ea322574bc6aac0ae7a83be85547f49d4f5f5777cb380ee6627f", size = 21418306 }, + { url = "https://files.pythonhosted.org/packages/bc/64/e1ffebf010b1631a6fef8d1508f46d4eab3e97c18729af986bb796fa8452/duckdb-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:f4eff89c12c3a362efa012262e57b7b4ab904a7f79bad9178fe365510077abe8", size = 13101423 }, + { url = "https://files.pythonhosted.org/packages/e7/42/b1d4e34f9658cc0e13d7aae581ab82643f50a548d5aee8767f0c587cc3a4/duckdb-1.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:75d13308c9da3ee431d1e72b8ab720aa74a1b3e9159d4124cb62435924496334", size = 13951740 }, + { url = "https://files.pythonhosted.org/packages/e7/c4/2e34929b16c8d544ef664fad8f7f3a2a9db05746aae1e7c8c4ee3a8b23e4/duckdb-1.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ff11a457258148337ef9a392148a8cdbd1069b6c27c21958816c7b67fe6c542d", size = 32626494 }, + { url = "https://files.pythonhosted.org/packages/3a/53/3af681793d03771365ae3e2215331151c196a3ac8193f613344840694671/duckdb-1.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fd25f533cb1b6b2c84cc767a9a9bab7769bb1aa44571a2a0bfc91ac3e4a38ac", size = 17301121 }, + { url = "https://files.pythonhosted.org/packages/15/e2/c80af1eac2ab5d35fc2c372ef0a84668842e549fbbf7799277b3fccf3e39/duckdb-1.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10960400ed60cdf0fe05bab2086fa8eb733889cb0ceca18d07ff9a00c0e0be7b", size = 15449283 }, + { url = "https://files.pythonhosted.org/packages/2d/9a/c63af233c9f761bf5178a5210437e1bc6bcb30fa8a9073de6398cfb12c03/duckdb-1.5.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5f18e7561403054433706c187589e86629a7af09a7efc23a06a8b308e6acc68", size = 19332762 }, + { url = "https://files.pythonhosted.org/packages/21/cc/2d77af4fff86012f334ef82e6d54a995a86c8745e58074f1218ed7d25171/duckdb-1.5.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fb7516255a8764545e30f7efacea408cc847764a3027b3b0b3e7d1a7bebbc5c", size = 21453290 }, + { url = "https://files.pythonhosted.org/packages/8d/5e/9bc4817a98feb4dab83e56f2245cd3a30d00ee646d4dec7926464e2b3f28/duckdb-1.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:8001eccbc28be244dfd04d708526f34ddd6460b47a8aeb5d0e39d6f7f9e3fe15", size = 13118308 }, + { url = "https://files.pythonhosted.org/packages/81/35/e3f32e4e53e2450ddb1db8312a17d1ce455d60cc4941b6ad2cfc908794b0/duckdb-1.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:6d2835e39bb6af73891f73c0f8d4324f98afe00d0b00c6d34b2a582c2256cbb0", size = 13927187 }, + { url = "https://files.pythonhosted.org/packages/cc/9c/a528eb09d8be51954c485864bd06753e616939a080cbc3dd4417e8c94a57/duckdb-1.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e75a6122c12579a99848517f6f00a4e342aebda3590c30fe9b5cc5f39d5e6afc", size = 32626254 }, + { url = "https://files.pythonhosted.org/packages/ec/3c/1534c0a6db347c05eb7d0f6ecfb7aefbe74cbff398e4892a8fd1903a20e8/duckdb-1.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd3963c1cb9d9567777f4a898a9dbe388a2fe9724681801b1e7d6d93eecf1b76", size = 17300917 }, + { url = "https://files.pythonhosted.org/packages/23/fa/beafb91e6e152d2161c4a9cbc472334c87607eb61ad7104b5a7fa8d8d7b1/duckdb-1.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3d5db8c0b55e072cf437948ebb5d7e23d7b9d03d905fa5f9145583e65aa447f7", size = 15449411 }, + { url = "https://files.pythonhosted.org/packages/50/0a/49b6fe04e2fcd63729eb607dadd44818dde77342a4f5ce086c6c92f1dd4d/duckdb-1.5.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ce80aed7a538422129a57eaca9141e3afb51f8bf562b1908b1576c9725b5b22", size = 19333120 }, + { url = "https://files.pythonhosted.org/packages/63/4c/0907c3f76adb9dd90e67610b31e0304a35814e65c4c41a354a262c09b885/duckdb-1.5.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787df63824f07bf18022dbc3b8ca4b2bfab0ebe616464f55c6e8cd0f59ea762e", size = 21453266 }, + { url = "https://files.pythonhosted.org/packages/6d/9c/d2f23a7803ddbbd9413f7572ecf66a15120ed5ced7ce5c73e698c1406b76/duckdb-1.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:bb5bb5dcdd09d62ee60f0ddbbef918e71cce304ffe28428b1131949d39ffaabf", size = 13118640 }, + { url = "https://files.pythonhosted.org/packages/27/d5/7ba2316415bcdab6edd765bbbe35c2ca8a3800f2fe695cd70e3cdb997f09/duckdb-1.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2fa17ecdd5d3db122836cb71bb93601c2106a3be883c17dffddc02fbf3fa7888", size = 13926409 }, + { url = "https://files.pythonhosted.org/packages/a5/c2/d4b6f8a5e4d3bc25773be6da76a99d9661ebbf3552c007c460d2dd59dbf8/duckdb-1.5.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4bfa9a4dadf71e83e2c4eaca2f9421c82a54defecc1b0b4c0be95e2389dec4fe", size = 32636685 }, + { url = "https://files.pythonhosted.org/packages/42/58/e835c8298979d29db7a62cb5acc29e9b57aeaca7cdde2fcd3ac980f5cb18/duckdb-1.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aea7baf67ad7e1829ac76f67d7dcbd7fb1f57c3eb179d55ac30952df4709ae30", size = 17308134 }, + { url = "https://files.pythonhosted.org/packages/c9/46/617b51363f5613418c8b224b3cce16b58e6dde80904566bec232579c1d4e/duckdb-1.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b0b4f088a65d77e1217ce5d7eff889e63fedc44281200d899ff47c84d8ff836", size = 15449891 }, + { url = "https://files.pythonhosted.org/packages/b3/72/354146656e8d9ba3853d3a5ee80a481b8c5f70edfc3d5ae80a8c4479c967/duckdb-1.5.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe8d0c1f6a120aa03fa6e0d03897c71a1842e6cf7afd31d181348391f7108fe1", size = 19338499 }, + { url = "https://files.pythonhosted.org/packages/56/8f/65fc623b51448f2bfba1a9ec6ab3debb4664c0876c0113a5e782600b53ac/duckdb-1.5.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0405eae18ec6e8210a471c97dbfe87a7e4d605274b7fe572a1f276e92158f13", size = 21455828 }, + { url = "https://files.pythonhosted.org/packages/2b/db/d0274cbe9f5fe219f77c0bdf900ac77103569e83c102a4225ce04cbc607d/duckdb-1.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:33ae08b3e818d7613d8936744b67718c2062c2f530376895bfd89efb51b81538", size = 13640011 }, + { url = "https://files.pythonhosted.org/packages/07/5d/8f1899b8bef291caf953992fcd6c24df9f29387a35645e58c2504a5ca473/duckdb-1.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:746433e49bbc667b4df283153415fbe37e9083e0eff6c3cd6e54de7536869cd4", size = 14411554 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 }, +] + +[[package]] +name = "geocodebr" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "duckdb" }, + { name = "h3" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "platformdirs" }, + { name = "pyarrow" }, + { name = "requests" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +geo = [ + { name = "geopandas" }, + { name = "pyproj", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "shapely" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "duckdb", specifier = ">=1.0.0" }, + { name = "geopandas", marker = "extra == 'geo'", specifier = ">=0.14.0" }, + { name = "h3", specifier = ">=4.0.0" }, + { name = "numpy", specifier = ">=1.26.0" }, + { name = "platformdirs", specifier = ">=4.0.0" }, + { name = "pyarrow", specifier = ">=15.0.0" }, + { name = "pyproj", marker = "extra == 'geo'", specifier = ">=3.6.0" }, + { name = "requests", specifier = ">=2.31.0" }, + { name = "shapely", marker = "extra == 'geo'", specifier = ">=2.0.0" }, + { name = "tqdm", specifier = ">=4.66.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.0.0" }] + +[[package]] +name = "geopandas" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyogrio" }, + { name = "pyproj", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pyproj", version = "3.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/ba/8e6b2091878e99e86a36a814dcaeff652ed48bdb03d53e78e15aaa63a914/geopandas-1.1.3.tar.gz", hash = "sha256:91a31989b6f566012838d21d5f8033f37dce882079ccb7cfdc40d5ccce7f284f", size = 336718 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/78/6a04792ace63a93e162f1305392d500ae8ddcb620e7eb88a22fd622b35bb/geopandas-1.1.3-py3-none-any.whl", hash = "sha256:90d62a64f95eaa3be2ccc115c5f3d6e24208bb11983b390fdc0621a3eccd0230", size = 342514 }, +] + +[[package]] +name = "h3" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/1c/12f1e2842d6493de4dd8244538c30a556712e9a6b25c5151a0e0e522a67e/h3-4.5.0.tar.gz", hash = "sha256:a1e279a1674fc799445c710e35bc4b1b388a406c881d8b5e59a9b8bebeb5bb43", size = 180838 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/46/6c8f3be7c021ed04b5e2418e8f96d45133b4ea89cea73b32f902948e1a3c/h3-4.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:17d3793e6bdeec1c1dbf5fd92cb8aad42dc64918646f5b69216fb0cf28dbd3f7", size = 850928 }, + { url = "https://files.pythonhosted.org/packages/4e/a5/65058a3b55623176cd911ccab554a03c2424a0b97ab86f105ca78238ccb8/h3-4.5.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7a49fb578454a509a5941567ee47786a5cd9fd4194f499baa96632c897c77a3", size = 1031346 }, + { url = "https://files.pythonhosted.org/packages/31/b2/1aaa802a3ab6a69e83f96ec17ef8b3e1307e718e98e851f03ccb9d052450/h3-4.5.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5ae6104f93ede960e6146efbb461dcb14e6cdb7d3932d8f1444ceade4f178b5", size = 1074033 }, + { url = "https://files.pythonhosted.org/packages/66/b6/8163b1fa47f6ff87771991c887bf88cbd0e38e17204f5d3844d724c98ce4/h3-4.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:52acb53a7a5b8be3f530c0168fd006457bdb67f78d0f0438d7facb76a9d331c3", size = 1084440 }, + { url = "https://files.pythonhosted.org/packages/82/d5/3e8759eb3377d1fc3863664f51ba7ee348b7cb71720e19efade6edba64e1/h3-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d28ffcfb4cc1d9fb1cba44a8eacb45cf67d0a649391d8624eb537199c1f2e1a", size = 815830 }, + { url = "https://files.pythonhosted.org/packages/98/51/7dd692a928e7f1b98dc48c98cb480578d1666e098c07b165af96fa608cbf/h3-4.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3d5367218c97cacc998b41dca370f07c2273aee11e75c56d8d87f4c4d19dabe", size = 848595 }, + { url = "https://files.pythonhosted.org/packages/d4/00/2771a2217ee9fe13334585990b587e7b78ade7125844b9f30461f2d72710/h3-4.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea0b236c42298b4266a9745abe0fd807f8054a65a54733da3f07f69395bbce77", size = 1030016 }, + { url = "https://files.pythonhosted.org/packages/96/1a/4bd1d9639dfca3b4268e4da5af9664cc9dedc18f6d398825d49e9c3ccb27/h3-4.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b181b13758e3852b02276e2e46422feb61ba38c23ea02bd4f045bbeb29df154", size = 1073321 }, + { url = "https://files.pythonhosted.org/packages/b0/b3/374f464a13dc69770b32626beb1c03a4533db817daa498c7417584e6b39a/h3-4.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64e05ee026241b8ed536a3e5ee369ea6d82d19dcb13d3c6842bfcf2f8b414e19", size = 1083646 }, + { url = "https://files.pythonhosted.org/packages/44/11/c8a3feb1dbf406033d0fe250601bc359afba65517ea7d0b38a2ac32c6c3f/h3-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:362efd509ed8899e75a5fb43ea39745103fac13a65ef6bb1971f043a562c90ef", size = 815875 }, + { url = "https://files.pythonhosted.org/packages/83/76/44266d4e0acd4cf170cbae6be6d9b94eeeff806e2b0b73f8fbf495e3a727/h3-4.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:531ff6a42432ea08869ef29ef6bcf244fd169f6d0720e74842bba40089654390", size = 715908 }, + { url = "https://files.pythonhosted.org/packages/00/63/1acc39ba0fc4b8ba7786662d7b5800a2b12653d64c6658e7293f6821abd3/h3-4.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1ae8f31981cf0dbdae15f1cc817ec30b6bbec7f27461cb28a0e1eb1794360d0", size = 848134 }, + { url = "https://files.pythonhosted.org/packages/3c/73/f7d5c3c4e0853726ac3d2c20d2b6789fa4a6d753c228541cb1929317defa/h3-4.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebe9875778d240d7ac37496b66d89abecb7e0090977e5f1e20d0caf63e513223", size = 1012325 }, + { url = "https://files.pythonhosted.org/packages/87/e3/afe081686e549a82cc13a38c6e24b97eb3de939521979f0dde9c921c5154/h3-4.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:583c3c42b3fa3576649c658f24beba080655159e724ecd1d6204b185df9eb4f6", size = 1063388 }, + { url = "https://files.pythonhosted.org/packages/70/82/6c027ef04717fd4dd1d3897086d7706c29372ddd51e78319ec4fcb5f2cfc/h3-4.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:551907d1ee01b5fee599da4ce1c41b054c64e8b20221094caf8e52caeb5d30bb", size = 1072924 }, + { url = "https://files.pythonhosted.org/packages/74/5b/0e4f0c4f02414166aa0c96dd84b215f23c68e09bf36e4ede55f50ee250f7/h3-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2cc7ed2e2370a67393b791972dab107eca4e14c5bfa96558e1c9ec8a501af6e", size = 806067 }, + { url = "https://files.pythonhosted.org/packages/33/08/ea0ef498971cf2e5821074f7dff80a9a2992417c78ff9141979f2d49d259/h3-4.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:260220ea216acda378bac481b26d414fab2d88bb724fe3fc3d6d0d764a2b16bd", size = 720045 }, + { url = "https://files.pythonhosted.org/packages/a3/a9/bb36156db3a1f9eebb27de9c72d1229c69a643bc5bf9f59cbc05a8b0a634/h3-4.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44f9eee75985ecf06af82cfbce5fe7a0fd1cae73bee53d15155fe8fdb165578a", size = 843578 }, + { url = "https://files.pythonhosted.org/packages/00/d0/4256f2515f8dd1a322e95a7a5f4174ecc405098f8b217d1d29767989c171/h3-4.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf8fe70eef1c122e7465f3b9c57f793fa1a6885cf067be3a83423c0f30c0d80c", size = 1007119 }, + { url = "https://files.pythonhosted.org/packages/eb/37/a60d26681ac540788c4ef656960084c9cbf4c24657f7e7347f07e97ba27f/h3-4.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df23f9ff0a9ff9c6195f48ebc8fb8fc6d50c2025ec37649991749d5282a2950f", size = 1059523 }, + { url = "https://files.pythonhosted.org/packages/de/76/6e2eab23667a6ee153e3c369fb6fb793d4b09c81030495da989e8e5bf66d/h3-4.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e8af93363b9b14fe1797a2557b22bb158b1be7696f145ea8ee6f8b9315860fa", size = 1069134 }, + { url = "https://files.pythonhosted.org/packages/63/15/338b4d4bb427999463b91c32c81a37b7cd16c8d94605a5a98e3d1149e459/h3-4.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b5ee5185d7fe5126d67a85d1bc1033bdb932e579e2b948eaaec08a43b6b40c1", size = 803357 }, + { url = "https://files.pythonhosted.org/packages/67/d8/d2454a2cdccfd011c9584db75d67e8b7e313f176891713059d743db2d5f1/h3-4.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:1ef3d069afa78988fb221574ab75cab35117651247e955633efe6cb89d635c00", size = 718284 }, + { url = "https://files.pythonhosted.org/packages/a2/9a/d270563a3aac0700f38dfc167f3cd3f7dad80290faa277692a28e3d0b57d/h3-4.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:74dbc558a10177a7b63d307d85d53c94b10408b253291961235f33d7714986c2", size = 847869 }, + { url = "https://files.pythonhosted.org/packages/55/6e/8ab33def9888aaf724b7fba3b6027fcf99c4262fc61e5b3d4a123e428d49/h3-4.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b17cf243923e9554ba4d8c6a5b5ade3cf302751155edbc2cc5821e7dc859dea", size = 1015860 }, + { url = "https://files.pythonhosted.org/packages/de/b0/35103fd89f16e9f11dd310c7b16e0da9603711852c8e0d343fdf95a9200e/h3-4.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bb5ac89a494fa8e4c1594b77ed8a18419b95513c3e586ce1189606df54a38a8", size = 1062200 }, + { url = "https://files.pythonhosted.org/packages/03/3b/cba32deaacf80f9135dc9457359df4c9b0fcfc920a5789798386afda7959/h3-4.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:463e8d59dbc65570d1cbce1bd3799ab1be7b2e8123af88586ed314fbf50cb6b5", size = 1071525 }, + { url = "https://files.pythonhosted.org/packages/7c/88/030152f50ee8bd3cefe9c05dbb1d247eb98e959bfd071057d0b139581e0f/h3-4.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3870b4fbd9e302e550a811d10b57f4e42074a3b9e39bed1281761486153cd37b", size = 822798 }, + { url = "https://files.pythonhosted.org/packages/b9/b8/772ad03138c09b17196ff6d93c5e476e1b50d41ec4194f99cfc5db5f98b3/h3-4.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:9657dddd0de99a24f4cd3d0cc409648b2ed5b9c0fbc202149615a5419a119e08", size = 740487 }, + { url = "https://files.pythonhosted.org/packages/80/94/352deb26f5bd6d779d938bd5812e4ef97cedbd5412211c1d126fc696e833/h3-4.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:70d125d0dc70daabaf241267eb9cc7cba97b25b22370ebb8e12d68ff8a49f227", size = 889263 }, + { url = "https://files.pythonhosted.org/packages/da/1a/6a782f2ac00a1b3defbefadf5751ccbdf6263e75313210f35c301aa6a80a/h3-4.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d031c922c49ce047728ac698541c1e0535ee72fe821e5f73f5faaed50a7c899b", size = 1035944 }, + { url = "https://files.pythonhosted.org/packages/2c/5d/555fd4373919e12e1289325388c2677a6bcd7e4f2a2ab87d50323a77c8a8/h3-4.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3d3d8917adbc2f81a1b766f643f66857581617d03a73eab89bacf0935cb61305", size = 919737 }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455 }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048 }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542 }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301 }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320 }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050 }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034 }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185 }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149 }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620 }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616 }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579 }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005 }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570 }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521 }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866 }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455 }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828 }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006 }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765 }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736 }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719 }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072 }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213 }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632 }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532 }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885 }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467 }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144 }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217 }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014 }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935 }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122 }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143 }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260 }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225 }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374 }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391 }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754 }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476 }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666 }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194 }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111 }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159 }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936 }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692 }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164 }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877 }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487 }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945 }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406 }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528 }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119 }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246 }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410 }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240 }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012 }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538 }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706 }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541 }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825 }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687 }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482 }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648 }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902 }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992 }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944 }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392 }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220 }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800 }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600 }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134 }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598 }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272 }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197 }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287 }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763 }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070 }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752 }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024 }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398 }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971 }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532 }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881 }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458 }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559 }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716 }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947 }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197 }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245 }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587 }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226 }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196 }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334 }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678 }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672 }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731 }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805 }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496 }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616 }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145 }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813 }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982 }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908 }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867 }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511 }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064 }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157 }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728 }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374 }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286 }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263 }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195 }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763 }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217 }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791 }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373 }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444 }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459 }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086 }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790 }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831 }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267 }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281 }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453 }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361 }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702 }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846 }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618 }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212 }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693 }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002 }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971 }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722 }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671 }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807 }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872 }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371 }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333 }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120 }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991 }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227 }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056 }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189 }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912 }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160 }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233 }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635 }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079 }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049 }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638 }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834 }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925 }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071 }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504 }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702 }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535 }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582 }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963 }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175 }, +] + +[[package]] +name = "pandas" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495 }, + { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250 }, + { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558 }, + { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611 }, + { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670 }, + { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708 }, + { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609 }, + { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596 }, + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846 }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550 }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965 }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600 }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824 }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889 }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463 }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158 }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071 }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690 }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634 }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243 }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659 }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880 }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091 }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282 }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016 }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210 }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126 }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051 }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796 }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741 }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958 }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065 }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101 }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553 }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065 }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188 }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966 }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755 }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658 }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242 }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369 }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306 }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394 }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717 }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897 }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855 }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464 }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/bf/a34fee1d624152124fa8355c42f34195ad5fe5233ce5bb87946432047d52/pyarrow-24.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c2b98645d576a0b9616892ead22b64a83a5f043c5e2ca15ebcefcb5b70c80cb", size = 35076681 }, + { url = "https://files.pythonhosted.org/packages/1d/41/64180033d7027afce12dc96d0fe1f504c6fa112190582b458acea2399530/pyarrow-24.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:644a246325b8c69c595ad1dd4b463eba4b0cdb731370e4a86137d433208d6147", size = 36684260 }, + { url = "https://files.pythonhosted.org/packages/57/02/9b9320e673dd8a99411fac78690f3df92f6dd6f59754c750110bca66d64e/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3a577bd840ca83f646f0a625dbc571dba7044c43c2d1503afc378b570954345c", size = 45698566 }, + { url = "https://files.pythonhosted.org/packages/67/33/f75e91b9a64c3f33c787e263c93b871ad91b8a4a68c1d5cebddd9840e835/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e3268e43984d0b1a185c89b4cfff282a7ead12fc93f56cfd7088bdbcbe727041", size = 48835562 }, + { url = "https://files.pythonhosted.org/packages/a5/63/097510448e47e4091faa41c43ba92f97cecaab8f4535b56a3d149578f634/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2392d954fcb920f42d230284b677605e4e2fbb11f2821e823e642abd67fbb491", size = 49394997 }, + { url = "https://files.pythonhosted.org/packages/60/6b/c047d6222ab279024a062742d1807e2fbaf27bba88a98637299ff47b9236/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec9373df11544592b0ba7ec2af0e35059e5f0e7647c6183a854dedd193298f1", size = 51911424 }, + { url = "https://files.pythonhosted.org/packages/3a/ba/464cc70761c2a525d97ebd84e21c31ebd47f3ef4bdcee117009f51c46f24/pyarrow-24.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c42ab9439498270139cc63e18847a02afe5c8b3ed9c931266533cfe378bd3591", size = 27251730 }, + { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898 }, + { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915 }, + { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931 }, + { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449 }, + { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949 }, + { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986 }, + { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371 }, + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559 }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654 }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394 }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122 }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032 }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490 }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660 }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759 }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471 }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981 }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172 }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733 }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335 }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748 }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554 }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301 }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929 }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365 }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819 }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252 }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127 }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997 }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720 }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852 }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852 }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207 }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117 }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155 }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387 }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102 }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118 }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765 }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890 }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250 }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282 }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 }, +] + +[[package]] +name = "pyogrio" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/d4/12f86b1ed09721363da4c09622464b604c851a9223fc0c6b393fb2012208/pyogrio-0.12.1.tar.gz", hash = "sha256:e548ab705bb3e5383693717de1e6c76da97f3762ab92522cb310f93128a75ff1", size = 303289 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/04/e69f476c4cc279adc6d26194da4d3497f5d9efdd46777a6c0ad59c09233f/pyogrio-0.12.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c4735235ca0d8dcdb4ecd69bd73e66762d161bce913b10d4458a18137cc7062", size = 23672707 }, + { url = "https://files.pythonhosted.org/packages/a3/9e/805d640f050fc4a064ee5ba3289457f47d7f3464b57140caa8ddac039a67/pyogrio-0.12.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:3249d06c2520857b622f3ff0f1b7b4849291ee1fb72f21587825f5fd0f24b787", size = 25247903 }, + { url = "https://files.pythonhosted.org/packages/05/c3/65577611485bc3e53a466ffbcd2407f89e8bd7e1c4554e8a0d23a4b8a636/pyogrio-0.12.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4011b63f9d6c278ee6605971ffabe30b0e8f5992ec2c6df8c70ecfa68a5d02b", size = 31279563 }, + { url = "https://files.pythonhosted.org/packages/b1/a6/5c03dffaf02542e8bae6c785d3e302bf4b890cd2ab281336b3c4dc867f84/pyogrio-0.12.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:940857c45051e1e19608ebfe8338bcdf7dd005389057431a3c7b5bff5beb0a5f", size = 30831678 }, + { url = "https://files.pythonhosted.org/packages/c8/aa/0e484c13cf14bbe46c366ad363ab2406242a0fba85a7561d42bbd34c35dd/pyogrio-0.12.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0fd86bcd69126739325a543a489f312b5fd86db092d2dead682772ae4ee434f3", size = 32380362 }, + { url = "https://files.pythonhosted.org/packages/7a/7c/cc515005780235d9ab14a29d33868bcaa1d5b423cee7995dda94735c41dd/pyogrio-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:dcf9cca273ead32beba7c002dd3db8a304105f52dd66200d48fa1ef30d0676af", size = 22940628 }, + { url = "https://files.pythonhosted.org/packages/02/46/b2c2dcdfd88759b56f103365905fffb85e8b08c1db1ec7c8f8b4c4c26016/pyogrio-0.12.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:01b322dac2a258d24b024d1028dcaa03c9bb6d9c3988b86d298a64873d10dc65", size = 23670744 }, + { url = "https://files.pythonhosted.org/packages/d9/21/b69f1bc51d805c00dd7c484a18e1fd2e75b41da1d9f5b8591d7d9d4a7d2f/pyogrio-0.12.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:e10087abcbd6b7e8212560a7002984e5078ac7b3a969ddc2c9929044dbb0d403", size = 25246184 }, + { url = "https://files.pythonhosted.org/packages/19/8c/b6aae08e8fcc4f2a903da5f6bd8f888d2b6d7290e54dde5abe15b4cca8df/pyogrio-0.12.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f6c621972b09fd81a32317e742c69ff4a7763a803da211361a78317f9577765", size = 31434449 }, + { url = "https://files.pythonhosted.org/packages/70/f9/9538fa893c29a3fdfeddf3b4c9f8db77f2d4134bc766587929fec8405ebf/pyogrio-0.12.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c38253427b688464caad5316d4ebcec116b5e13f1f02cc4e3588502f136ca1b4", size = 30987586 }, + { url = "https://files.pythonhosted.org/packages/89/a4/0aef5837b4e11840f501e48e01c31242838476c4f4aff9c05e228a083982/pyogrio-0.12.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:5f47787251de7ce13cc06038da93a1214dc283cbccf816be6e03c080358226c8", size = 32534386 }, + { url = "https://files.pythonhosted.org/packages/34/97/e8f2ed8a339152b86f8403c258ae5d5f23ab32d690eeb0545bb3473d0c69/pyogrio-0.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:c1d756cf2da4cdf5609779f260d1e1e89be023184225855d6f3dcd33bbe17cb0", size = 22941718 }, + { url = "https://files.pythonhosted.org/packages/ad/e0/656b6536549d41b5aec57e0deca1f269b4f17532f0636836f587e581603a/pyogrio-0.12.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:7a0d5ca39184030aec4cde30f4258f75b227a854530d2659babc8189d76e657d", size = 23661857 }, + { url = "https://files.pythonhosted.org/packages/14/78/313259e40da728bdb60106ffdc7ea8224d164498cb838ecb79b634aab967/pyogrio-0.12.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:feaff42bbe8087ca0b30e33b09d1ce049ca55fe83ad83db1139ef37d1d04f30c", size = 25237106 }, + { url = "https://files.pythonhosted.org/packages/8f/ca/5368571a8b00b941ccfbe6ea29a5566aaffd45d4eb1553b956f7755af43e/pyogrio-0.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:81096a5139532de5a8003ef02b41d5d2444cb382a9aecd1165b447eb549180d3", size = 31417048 }, + { url = "https://files.pythonhosted.org/packages/ef/85/6eeb875f27bf498d657eb5dab9f58e4c48b36c9037122787abee9a1ba4ba/pyogrio-0.12.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:41b78863f782f7a113ed0d36a5dc74d59735bd3a82af53510899bb02a18b06bb", size = 30952115 }, + { url = "https://files.pythonhosted.org/packages/36/f7/cf8bec9024625947e1a71441906f60a5fa6f9e4c441c4428037e73b1fcc8/pyogrio-0.12.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8b65be8c4258b27cc8f919b21929cecdadda4c353e3637fa30850339ef4d15c5", size = 32537246 }, + { url = "https://files.pythonhosted.org/packages/ab/10/7c9f5e428273574e69f217eba3a6c0c42936188ad4dcd9e2c41ebb711188/pyogrio-0.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:1291b866c2c81d991bda15021b08b3621709b40ee3a85689229929e9465788bf", size = 22933980 }, + { url = "https://files.pythonhosted.org/packages/be/56/f56e79f71b84aa9bea25fdde39fab3846841bd7926be96f623eb7253b7e1/pyogrio-0.12.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ec0e47a5a704e575092b2fd5c83fa0472a1d421e590f94093eb837bb0a11125d", size = 23658483 }, + { url = "https://files.pythonhosted.org/packages/66/ac/5559f8a35d58a16cbb2dd7602dd11936ff8796d8c9bf789f14da88764ec3/pyogrio-0.12.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b4c888fc08f388be4dd99dfca5e84a5cdc5994deeec0230cc45144d3460e2b21", size = 25232737 }, + { url = "https://files.pythonhosted.org/packages/59/58/925f1c129ddd7cbba8dea4e7609797cea7a76dbc863ac9afd318a679c4b9/pyogrio-0.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:73a88436f9962750d782853727897ac2722cac5900d920e39fab3e56d7a6a7f1", size = 31377986 }, + { url = "https://files.pythonhosted.org/packages/18/5f/c87034e92847b1844d0e8492a6a8e3301147d32c5e57909397ce64dbedf5/pyogrio-0.12.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b5d248a0d59fe9bbf9a35690b70004c67830ee0ebe7d4f7bb8ffd8659f684b3a", size = 30915791 }, + { url = "https://files.pythonhosted.org/packages/46/35/b874f79d03e9f900012cf609f7fff97b77164f2e14ee5aac282f8a999c1b/pyogrio-0.12.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0622bc1a186421547660271083079b38d42e6f868802936d8538c0b379f1ab6b", size = 32499754 }, + { url = "https://files.pythonhosted.org/packages/c3/c4/705678c9c4200130290b3a104b45c0cc10aaa48fcef3b2585b34e34ab3e1/pyogrio-0.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:207bd60c7ffbcea84584596e3637653aa7095e9ee20fa408f90c7f9460392613", size = 22933945 }, + { url = "https://files.pythonhosted.org/packages/f9/e0/d92d4944001330bc87742d43f112d63d12fc89378b6187e62ff3fc1e8e85/pyogrio-0.12.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1511b39a283fa27cda906cd187a791578942a87a40b6a06697d9b43bb8ac80b0", size = 23692697 }, + { url = "https://files.pythonhosted.org/packages/e5/d7/40acbe06d1b1140e3bb27b79e9163776469c1dc785f1be7d9a7fc7b95c87/pyogrio-0.12.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:e486cd6aa9ea8a15394a5f84e019d61ec18f257eeeb642348bd68c3d1e57280b", size = 25258083 }, + { url = "https://files.pythonhosted.org/packages/87/a1/39fefd9cddd95986700524f43d3093b4350f6e4fc200623c3838424a5080/pyogrio-0.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3f1a19f63bfd1d3042e45f37ad1d6598123a5a604b6c4ba3f38b419273486cd", size = 31368995 }, + { url = "https://files.pythonhosted.org/packages/18/d7/da88c566e67d741a03851eb8d01358949d52e0b0fc2cd953582dc6d89ff8/pyogrio-0.12.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:f3dcc59b3316b8a0f59346bcc638a4d69997864a4d21da839192f50c4c92369a", size = 31035589 }, + { url = "https://files.pythonhosted.org/packages/11/ac/8f0199f0d31b8ddbc4b4ea1918df8070fdf3e0a63100b898633ec9396224/pyogrio-0.12.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a0643e041dee3e8e038fce69f52a915ecb486e6d7b674c0f9919f3c9e9629689", size = 32487973 }, + { url = "https://files.pythonhosted.org/packages/bd/64/8541a27e9635a335835d234dfaeb19d6c26097fd88224eda7791f83ca98d/pyogrio-0.12.1-cp313-cp313t-win_amd64.whl", hash = "sha256:5881017f29e110d3613819667657844d8e961b747f2d35cf92f273c27af6d068", size = 22987374 }, + { url = "https://files.pythonhosted.org/packages/f4/6f/b4d5e285e08c0c60bcc23b50d73038ddc7335d8de79cc25678cd486a3db0/pyogrio-0.12.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5a1b0453d1c9e7b03715dd57296c8f3790acb8b50d7e3b5844b3074a18f50709", size = 23660673 }, + { url = "https://files.pythonhosted.org/packages/8d/75/4b29e71489c5551aa1a1c5ca8c5160a60203c94f2f68c87c0e3614d58965/pyogrio-0.12.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e7ee560422239dd09ca7f8284cc8483a8919c30d25f3049bb0249bff4c38dec4", size = 25232194 }, + { url = "https://files.pythonhosted.org/packages/89/6e/e9929d2261a07c36301983de2767bcde90d441ab5bf1d767ce56dd07f8b4/pyogrio-0.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:648c6f7f5f214d30e6cf493b4af1d59782907ac068af9119ca35f18153d6865a", size = 31336936 }, + { url = "https://files.pythonhosted.org/packages/1d/9e/c59941d734ed936d4e5c89b4b99cb5541307cc42b3fd466ee78a1850c177/pyogrio-0.12.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:58042584f3fd4cabb0f55d26c1405053f656be8a5c266c38140316a1e981aca0", size = 30902210 }, + { url = "https://files.pythonhosted.org/packages/d1/68/cc07320a63f9c2586e60bf11d148b00e12d0e707673bffe609bbdcb7e754/pyogrio-0.12.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b438e38e4ccbaedaa5cb5824ff5de5539315d9b2fde6547c1e816576924ee8ca", size = 32461674 }, + { url = "https://files.pythonhosted.org/packages/13/bc/e4522f429c45a3b6ad28185849dd76e5c8718b780883c4795e7ee41841ae/pyogrio-0.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:f1d8d8a2fea3781dc2a05982c050259261ebc0f6c5e03732d6d79d582adf9363", size = 23550575 }, + { url = "https://files.pythonhosted.org/packages/bd/ac/34f0664d0e391994a7b68529ae07a96432b2b4926dbac173ddc4ec94d310/pyogrio-0.12.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9fe7286946f35a73e6370dc5855bc7a5e8e7babf9e4a8bad7a3279a1d94c7ea9", size = 23694285 }, + { url = "https://files.pythonhosted.org/packages/8a/93/873255529faff1da09d0b27287e85ec805a318c60c0c74fd7df77f94e557/pyogrio-0.12.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2c50345b382f1be801d654ec22c70ee974d6057d4ba7afe984b55f2192bc94ee", size = 25259825 }, + { url = "https://files.pythonhosted.org/packages/27/95/4d4c3644695d99c6fa0b0b42f0d6266ae9dfaf64478a3371eaac950bdd02/pyogrio-0.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0db95765ac0ca935c7fe579e29451294e3ab19c317b0c59c31fbe92a69155e0", size = 31371995 }, + { url = "https://files.pythonhosted.org/packages/4c/6f/71f6bcca8754c8bf55a4b7153c61c91f8ac5ba992568e9fa3e54a0ee76fd/pyogrio-0.12.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fc882779075982b93064b3bf3d8642514a6df00d9dd752493b104817072cfb01", size = 31035498 }, + { url = "https://files.pythonhosted.org/packages/fd/47/75c1aa165a988347317afab9b938a01ad25dbca559b582ea34473703dc38/pyogrio-0.12.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:806f620e0c54b54dbdd65e9b6368d24f344cda84c9343364b40a57eb3e1c4dca", size = 32496390 }, + { url = "https://files.pythonhosted.org/packages/31/93/4641dc5d952f6bdb71dabad2c50e3f8a5d58396cdea6ff8f8a08bfd4f4a6/pyogrio-0.12.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5399f66730978d8852ef5f44dbafa0f738e7f28f4f784349f36830b69a9d2134", size = 23620996 }, +] + +[[package]] +name = "pyproj" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/10/a8480ea27ea4bbe896c168808854d00f2a9b49f95c0319ddcbba693c8a90/pyproj-3.7.1.tar.gz", hash = "sha256:60d72facd7b6b79853f19744779abcd3f804c4e0d4fa8815469db20c9f640a47", size = 226339 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/a3/c4cd4bba5b336075f145fe784fcaf4ef56ffbc979833303303e7a659dda2/pyproj-3.7.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:bf09dbeb333c34e9c546364e7df1ff40474f9fddf9e70657ecb0e4f670ff0b0e", size = 6262524 }, + { url = "https://files.pythonhosted.org/packages/40/45/4fdf18f4cc1995f1992771d2a51cf186a9d7a8ec973c9693f8453850c707/pyproj-3.7.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6575b2e53cc9e3e461ad6f0692a5564b96e7782c28631c7771c668770915e169", size = 4665102 }, + { url = "https://files.pythonhosted.org/packages/0c/d2/360eb127380106cee83569954ae696b88a891c804d7a93abe3fbc15f5976/pyproj-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb516ee35ed57789b46b96080edf4e503fdb62dbb2e3c6581e0d6c83fca014b", size = 9432667 }, + { url = "https://files.pythonhosted.org/packages/76/a5/c6e11b9a99ce146741fb4d184d5c468446c6d6015b183cae82ac822a6cfa/pyproj-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e47c4e93b88d99dd118875ee3ca0171932444cdc0b52d493371b5d98d0f30ee", size = 9259185 }, + { url = "https://files.pythonhosted.org/packages/41/56/a3c15c42145797a99363fa0fdb4e9805dccb8b4a76a6d7b2cdf36ebcc2a1/pyproj-3.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3e8d276caeae34fcbe4813855d0d97b9b825bab8d7a8b86d859c24a6213a5a0d", size = 10469103 }, + { url = "https://files.pythonhosted.org/packages/ef/73/c9194c2802fefe2a4fd4230bdd5ab083e7604e93c64d0356fa49c363bad6/pyproj-3.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f173f851ee75e54acdaa053382b6825b400cb2085663a9bb073728a59c60aebb", size = 10401391 }, + { url = "https://files.pythonhosted.org/packages/c5/1d/ce8bb5b9251b04d7c22d63619bb3db3d2397f79000a9ae05b3fd86a5837e/pyproj-3.7.1-cp310-cp310-win32.whl", hash = "sha256:f550281ed6e5ea88fcf04a7c6154e246d5714be495c50c9e8e6b12d3fb63e158", size = 5869997 }, + { url = "https://files.pythonhosted.org/packages/09/6a/ca145467fd2e5b21e3d5b8c2b9645dcfb3b68f08b62417699a1f5689008e/pyproj-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3537668992a709a2e7f068069192138618c00d0ba113572fdd5ee5ffde8222f3", size = 6278581 }, + { url = "https://files.pythonhosted.org/packages/ab/0d/63670fc527e664068b70b7cab599aa38b7420dd009bdc29ea257e7f3dfb3/pyproj-3.7.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:a94e26c1a4950cea40116775588a2ca7cf56f1f434ff54ee35a84718f3841a3d", size = 6264315 }, + { url = "https://files.pythonhosted.org/packages/25/9d/cbaf82cfb290d1f1fa42feb9ba9464013bb3891e40c4199f8072112e4589/pyproj-3.7.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:263b54ba5004b6b957d55757d846fc5081bc02980caa0279c4fc95fa0fff6067", size = 4666267 }, + { url = "https://files.pythonhosted.org/packages/79/53/24f9f9b8918c0550f3ff49ad5de4cf3f0688c9f91ff191476db8979146fe/pyproj-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d6a2ccd5607cd15ef990c51e6f2dd27ec0a741e72069c387088bba3aab60fa", size = 9680510 }, + { url = "https://files.pythonhosted.org/packages/3c/ac/12fab74a908d40b63174dc704587febd0729414804bbfd873cabe504ff2d/pyproj-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5dcf24ede53d8abab7d8a77f69ff1936c6a8843ef4fcc574646e4be66e5739", size = 9493619 }, + { url = "https://files.pythonhosted.org/packages/c4/45/26311d6437135da2153a178125db5dfb6abce831ce04d10ec207eabac70a/pyproj-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c2e7449840a44ce860d8bea2c6c1c4bc63fa07cba801dcce581d14dcb031a02", size = 10709755 }, + { url = "https://files.pythonhosted.org/packages/99/52/4ecd0986f27d0e6c8ee3a7bc5c63da15acd30ac23034f871325b297e61fd/pyproj-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0829865c1d3a3543f918b3919dc601eea572d6091c0dd175e1a054db9c109274", size = 10642970 }, + { url = "https://files.pythonhosted.org/packages/3f/a5/d3bfc018fc92195a000d1d28acc1f3f1df15ff9f09ece68f45a2636c0134/pyproj-3.7.1-cp311-cp311-win32.whl", hash = "sha256:6181960b4b812e82e588407fe5c9c68ada267c3b084db078f248db5d7f45d18a", size = 5868295 }, + { url = "https://files.pythonhosted.org/packages/92/39/ef6f06a5b223dbea308cfcbb7a0f72e7b506aef1850e061b2c73b0818715/pyproj-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ad0ff443a785d84e2b380869fdd82e6bfc11eba6057d25b4409a9bbfa867970", size = 6279871 }, + { url = "https://files.pythonhosted.org/packages/e6/c9/876d4345b8d17f37ac59ebd39f8fa52fc6a6a9891a420f72d050edb6b899/pyproj-3.7.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:2781029d90df7f8d431e29562a3f2d8eafdf233c4010d6fc0381858dc7373217", size = 6264087 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/5f8691f8c90e7f402cc80a6276eb19d2ec1faa150d5ae2dd9c7b0a254da8/pyproj-3.7.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d61bf8ab04c73c1da08eedaf21a103b72fa5b0a9b854762905f65ff8b375d394", size = 4669628 }, + { url = "https://files.pythonhosted.org/packages/42/ec/16475bbb79c1c68845c0a0d9c60c4fb31e61b8a2a20bc18b1a81e81c7f68/pyproj-3.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04abc517a8555d1b05fcee768db3280143fe42ec39fdd926a2feef31631a1f2f", size = 9721415 }, + { url = "https://files.pythonhosted.org/packages/b3/a3/448f05b15e318bd6bea9a32cfaf11e886c4ae61fa3eee6e09ed5c3b74bb2/pyproj-3.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084c0a475688f934d386c2ab3b6ce03398a473cd48adfda70d9ab8f87f2394a0", size = 9556447 }, + { url = "https://files.pythonhosted.org/packages/6a/ae/bd15fe8d8bd914ead6d60bca7f895a4e6f8ef7e3928295134ff9a7dad14c/pyproj-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a20727a23b1e49c7dc7fe3c3df8e56a8a7acdade80ac2f5cca29d7ca5564c145", size = 10758317 }, + { url = "https://files.pythonhosted.org/packages/9d/d9/5ccefb8bca925f44256b188a91c31238cae29ab6ee7f53661ecc04616146/pyproj-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bf84d766646f1ebd706d883755df4370aaf02b48187cedaa7e4239f16bc8213d", size = 10771259 }, + { url = "https://files.pythonhosted.org/packages/2a/7d/31dedff9c35fa703162f922eeb0baa6c44a3288469a5fd88d209e2892f9e/pyproj-3.7.1-cp312-cp312-win32.whl", hash = "sha256:5f0da2711364d7cb9f115b52289d4a9b61e8bca0da57f44a3a9d6fc9bdeb7274", size = 5859914 }, + { url = "https://files.pythonhosted.org/packages/3e/47/c6ab03d6564a7c937590cff81a2742b5990f096cce7c1a622d325be340ee/pyproj-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:aee664a9d806612af30a19dba49e55a7a78ebfec3e9d198f6a6176e1d140ec98", size = 6273196 }, + { url = "https://files.pythonhosted.org/packages/ef/01/984828464c9960036c602753fc0f21f24f0aa9043c18fa3f2f2b66a86340/pyproj-3.7.1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:5f8d02ef4431dee414d1753d13fa82a21a2f61494737b5f642ea668d76164d6d", size = 6253062 }, + { url = "https://files.pythonhosted.org/packages/68/65/6ecdcdc829811a2c160cdfe2f068a009fc572fd4349664f758ccb0853a7c/pyproj-3.7.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b853ae99bda66cbe24b4ccfe26d70601d84375940a47f553413d9df570065e0", size = 4660548 }, + { url = "https://files.pythonhosted.org/packages/67/da/dda94c4490803679230ba4c17a12f151b307a0d58e8110820405ca2d98db/pyproj-3.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83db380c52087f9e9bdd8a527943b2e7324f275881125e39475c4f9277bdeec4", size = 9662464 }, + { url = "https://files.pythonhosted.org/packages/6f/57/f61b7d22c91ae1d12ee00ac4c0038714e774ebcd851b9133e5f4f930dd40/pyproj-3.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b35ed213892e211a3ce2bea002aa1183e1a2a9b79e51bb3c6b15549a831ae528", size = 9497461 }, + { url = "https://files.pythonhosted.org/packages/b7/f6/932128236f79d2ac7d39fe1a19667fdf7155d9a81d31fb9472a7a497790f/pyproj-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8b15b0463d1303bab113d1a6af2860a0d79013c3a66fcc5475ce26ef717fd4f", size = 10708869 }, + { url = "https://files.pythonhosted.org/packages/1d/0d/07ac7712994454a254c383c0d08aff9916a2851e6512d59da8dc369b1b02/pyproj-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:87229e42b75e89f4dad6459200f92988c5998dfb093c7c631fb48524c86cd5dc", size = 10729260 }, + { url = "https://files.pythonhosted.org/packages/b0/d0/9c604bc72c37ba69b867b6df724d6a5af6789e8c375022c952f65b2af558/pyproj-3.7.1-cp313-cp313-win32.whl", hash = "sha256:d666c3a3faaf3b1d7fc4a544059c4eab9d06f84a604b070b7aa2f318e227798e", size = 5855462 }, + { url = "https://files.pythonhosted.org/packages/98/df/68a2b7f5fb6400c64aad82d72bcc4bc531775e62eedff993a77c780defd0/pyproj-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3caac7473be22b6d6e102dde6c46de73b96bc98334e577dfaee9886f102ea2e", size = 6266573 }, +] + +[[package]] +name = "pyproj" +version = "3.7.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz", hash = "sha256:39a0cf1ecc7e282d1d30f36594ebd55c9fae1fda8a2622cee5d100430628f88c", size = 226279 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/bd/f205552cd1713b08f93b09e39a3ec99edef0b3ebbbca67b486fdf1abe2de/pyproj-3.7.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2514d61f24c4e0bb9913e2c51487ecdaeca5f8748d8313c933693416ca41d4d5", size = 6227022 }, + { url = "https://files.pythonhosted.org/packages/75/4c/9a937e659b8b418ab573c6d340d27e68716928953273e0837e7922fcac34/pyproj-3.7.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8693ca3892d82e70de077701ee76dd13d7bca4ae1c9d1e739d72004df015923a", size = 4625810 }, + { url = "https://files.pythonhosted.org/packages/c0/7d/a9f41e814dc4d1dc54e95b2ccaf0b3ebe3eb18b1740df05fe334724c3d89/pyproj-3.7.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5e26484d80fea56273ed1555abaea161e9661d81a6c07815d54b8e883d4ceb25", size = 9638694 }, + { url = "https://files.pythonhosted.org/packages/ad/ab/9bdb4a6216b712a1f9aab1c0fcbee5d3726f34a366f29c3e8c08a78d6b70/pyproj-3.7.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:281cb92847814e8018010c48b4069ff858a30236638631c1a91dd7bfa68f8a8a", size = 9493977 }, + { url = "https://files.pythonhosted.org/packages/c9/db/2db75b1b6190f1137b1c4e8ef6a22e1c338e46320f6329bfac819143e063/pyproj-3.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9c8577f0b7bb09118ec2e57e3babdc977127dd66326d6c5d755c76b063e6d9dc", size = 10841151 }, + { url = "https://files.pythonhosted.org/packages/89/f7/989643394ba23a286e9b7b3f09981496172f9e0d4512457ffea7dc47ffc7/pyproj-3.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a23f59904fac3a5e7364b3aa44d288234af267ca041adb2c2b14a903cd5d3ac5", size = 10751585 }, + { url = "https://files.pythonhosted.org/packages/53/6d/ad928fe975a6c14a093c92e6a319ca18f479f3336bb353a740bdba335681/pyproj-3.7.2-cp311-cp311-win32.whl", hash = "sha256:f2af4ed34b2cf3e031a2d85b067a3ecbd38df073c567e04b52fa7a0202afde8a", size = 5908533 }, + { url = "https://files.pythonhosted.org/packages/79/e0/b95584605cec9ed50b7ebaf7975d1c4ddeec5a86b7a20554ed8b60042bd7/pyproj-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:0b7cb633565129677b2a183c4d807c727d1c736fcb0568a12299383056e67433", size = 6320742 }, + { url = "https://files.pythonhosted.org/packages/b7/4d/536e8f93bca808175c2d0a5ac9fdf69b960d8ab6b14f25030dccb07464d7/pyproj-3.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:38b08d85e3a38e455625b80e9eb9f78027c8e2649a21dec4df1f9c3525460c71", size = 6245772 }, + { url = "https://files.pythonhosted.org/packages/8d/ab/9893ea9fb066be70ed9074ae543914a618c131ed8dff2da1e08b3a4df4db/pyproj-3.7.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0a9bb26a6356fb5b033433a6d1b4542158fb71e3c51de49b4c318a1dff3aeaab", size = 6219832 }, + { url = "https://files.pythonhosted.org/packages/53/78/4c64199146eed7184eb0e85bedec60a4aa8853b6ffe1ab1f3a8b962e70a0/pyproj-3.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:567caa03021178861fad27fabde87500ec6d2ee173dd32f3e2d9871e40eebd68", size = 4620650 }, + { url = "https://files.pythonhosted.org/packages/b6/ac/14a78d17943898a93ef4f8c6a9d4169911c994e3161e54a7cedeba9d8dde/pyproj-3.7.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c203101d1dc3c038a56cff0447acc515dd29d6e14811406ac539c21eed422b2a", size = 9667087 }, + { url = "https://files.pythonhosted.org/packages/b8/be/212882c450bba74fc8d7d35cbd57e4af84792f0a56194819d98106b075af/pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1edc34266c0c23ced85f95a1ee8b47c9035eae6aca5b6b340327250e8e281630", size = 9552797 }, + { url = "https://files.pythonhosted.org/packages/ba/c0/c0f25c87b5d2a8686341c53c1792a222a480d6c9caf60311fec12c99ec26/pyproj-3.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa9f26c21bc0e2dc3d224cb1eb4020cf23e76af179a7c66fea49b828611e4260", size = 10837036 }, + { url = "https://files.pythonhosted.org/packages/5d/37/5cbd6772addde2090c91113332623a86e8c7d583eccb2ad02ea634c4a89f/pyproj-3.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9428b318530625cb389b9ddc9c51251e172808a4af79b82809376daaeabe5e9", size = 10775952 }, + { url = "https://files.pythonhosted.org/packages/69/a1/dc250e3cf83eb4b3b9a2cf86fdb5e25288bd40037ae449695550f9e96b2f/pyproj-3.7.2-cp312-cp312-win32.whl", hash = "sha256:b3d99ed57d319da042f175f4554fc7038aa4bcecc4ac89e217e350346b742c9d", size = 5898872 }, + { url = "https://files.pythonhosted.org/packages/4a/a6/6fe724b72b70f2b00152d77282e14964d60ab092ec225e67c196c9b463e5/pyproj-3.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:11614a054cd86a2ed968a657d00987a86eeb91fdcbd9ad3310478685dc14a128", size = 6312176 }, + { url = "https://files.pythonhosted.org/packages/5d/68/915cc32c02a91e76d02c8f55d5a138d6ef9e47a0d96d259df98f4842e558/pyproj-3.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:509a146d1398bafe4f53273398c3bb0b4732535065fa995270e52a9d3676bca3", size = 6233452 }, + { url = "https://files.pythonhosted.org/packages/be/14/faf1b90d267cea68d7e70662e7f88cefdb1bc890bd596c74b959e0517a72/pyproj-3.7.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:19466e529b1b15eeefdf8ff26b06fa745856c044f2f77bf0edbae94078c1dfa1", size = 6214580 }, + { url = "https://files.pythonhosted.org/packages/35/48/da9a45b184d375f62667f62eba0ca68569b0bd980a0bb7ffcc1d50440520/pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c79b9b84c4a626c5dc324c0d666be0bfcebd99f7538d66e8898c2444221b3da7", size = 4615388 }, + { url = "https://files.pythonhosted.org/packages/5e/e7/d2b459a4a64bca328b712c1b544e109df88e5c800f7c143cfbc404d39bfb/pyproj-3.7.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ceecf374cacca317bc09e165db38ac548ee3cad07c3609442bd70311c59c21aa", size = 9628455 }, + { url = "https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5141a538ffdbe4bfd157421828bb2e07123a90a7a2d6f30fa1462abcfb5ce681", size = 9514269 }, + { url = "https://files.pythonhosted.org/packages/34/38/07a9b89ae7467872f9a476883a5bad9e4f4d1219d31060f0f2b282276cbe/pyproj-3.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f000841e98ea99acbb7b8ca168d67773b0191de95187228a16110245c5d954d5", size = 10808437 }, + { url = "https://files.pythonhosted.org/packages/12/56/fda1daeabbd39dec5b07f67233d09f31facb762587b498e6fc4572be9837/pyproj-3.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8115faf2597f281a42ab608ceac346b4eb1383d3b45ab474fd37341c4bf82a67", size = 10745540 }, + { url = "https://files.pythonhosted.org/packages/0d/90/c793182cbba65a39a11db2ac6b479fe76c59e6509ae75e5744c344a0da9d/pyproj-3.7.2-cp313-cp313-win32.whl", hash = "sha256:f18c0579dd6be00b970cb1a6719197fceecc407515bab37da0066f0184aafdf3", size = 5896506 }, + { url = "https://files.pythonhosted.org/packages/be/0f/747974129cf0d800906f81cd25efd098c96509026e454d4b66868779ab04/pyproj-3.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:bb41c29d5f60854b1075853fe80c58950b398d4ebb404eb532536ac8d2834ed7", size = 6310195 }, + { url = "https://files.pythonhosted.org/packages/82/64/fc7598a53172c4931ec6edf5228280663063150625d3f6423b4c20f9daff/pyproj-3.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:2b617d573be4118c11cd96b8891a0b7f65778fa7733ed8ecdb297a447d439100", size = 6230748 }, + { url = "https://files.pythonhosted.org/packages/aa/f0/611dd5cddb0d277f94b7af12981f56e1441bf8d22695065d4f0df5218498/pyproj-3.7.2-cp313-cp313t-macosx_13_0_x86_64.whl", hash = "sha256:d27b48f0e81beeaa2b4d60c516c3a1cfbb0c7ff6ef71256d8e9c07792f735279", size = 6241729 }, + { url = "https://files.pythonhosted.org/packages/15/93/40bd4a6c523ff9965e480870611aed7eda5aa2c6128c6537345a2b77b542/pyproj-3.7.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:55a3610d75023c7b1c6e583e48ef8f62918e85a2ae81300569d9f104d6684bb6", size = 4652497 }, + { url = "https://files.pythonhosted.org/packages/1b/ae/7150ead53c117880b35e0d37960d3138fe640a235feb9605cb9386f50bb0/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8d7349182fa622696787cc9e195508d2a41a64765da9b8a6bee846702b9e6220", size = 9942610 }, + { url = "https://files.pythonhosted.org/packages/d8/17/7a4a7eafecf2b46ab64e5c08176c20ceb5844b503eaa551bf12ccac77322/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:d230b186eb876ed4f29a7c5ee310144c3a0e44e89e55f65fb3607e13f6db337c", size = 9692390 }, + { url = "https://files.pythonhosted.org/packages/c3/55/ae18f040f6410f0ea547a21ada7ef3e26e6c82befa125b303b02759c0e9d/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:237499c7862c578d0369e2b8ac56eec550e391a025ff70e2af8417139dabb41c", size = 11047596 }, + { url = "https://files.pythonhosted.org/packages/e6/2e/d3fff4d2909473f26ae799f9dda04caa322c417a51ff3b25763f7d03b233/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8c225f5978abd506fd9a78eaaf794435e823c9156091cabaab5374efb29d7f69", size = 10896975 }, + { url = "https://files.pythonhosted.org/packages/f2/bc/8fc7d3963d87057b7b51ebe68c1e7c51c23129eee5072ba6b86558544a46/pyproj-3.7.2-cp313-cp313t-win32.whl", hash = "sha256:2da731876d27639ff9d2d81c151f6ab90a1546455fabd93368e753047be344a2", size = 5953057 }, + { url = "https://files.pythonhosted.org/packages/cc/27/ea9809966cc47d2d51e6d5ae631ea895f7c7c7b9b3c29718f900a8f7d197/pyproj-3.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f54d91ae18dd23b6c0ab48126d446820e725419da10617d86a1b69ada6d881d3", size = 6375414 }, + { url = "https://files.pythonhosted.org/packages/5b/f8/1ef0129fba9a555c658e22af68989f35e7ba7b9136f25758809efec0cd6e/pyproj-3.7.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fc52ba896cfc3214dc9f9ca3c0677a623e8fdd096b257c14a31e719d21ff3fdd", size = 6262501 }, + { url = "https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:2aaa328605ace41db050d06bac1adc11f01b71fe95c18661497763116c3a0f02", size = 6214541 }, + { url = "https://files.pythonhosted.org/packages/03/68/68ada9c8aea96ded09a66cfd9bf87aa6db8c2edebe93f5bf9b66b0143fbc/pyproj-3.7.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:35dccbce8201313c596a970fde90e33605248b66272595c061b511c8100ccc08", size = 4617456 }, + { url = "https://files.pythonhosted.org/packages/81/e4/4c50ceca7d0e937977866b02cb64e6ccf4df979a5871e521f9e255df6073/pyproj-3.7.2-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:25b0b7cb0042444c29a164b993c45c1b8013d6c48baa61dc1160d834a277e83b", size = 9615590 }, + { url = "https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:85def3a6388e9ba51f964619aa002a9d2098e77c6454ff47773bb68871024281", size = 9474960 }, + { url = "https://files.pythonhosted.org/packages/51/07/9d48ad0a8db36e16f842f2c8a694c1d9d7dcf9137264846bef77585a71f3/pyproj-3.7.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b1bccefec3875ab81eabf49059e2b2ea77362c178b66fd3528c3e4df242f1516", size = 10799478 }, + { url = "https://files.pythonhosted.org/packages/85/cf/2f812b529079f72f51ff2d6456b7fef06c01735e5cfd62d54ffb2b548028/pyproj-3.7.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d5371ca114d6990b675247355a801925814eca53e6c4b2f1b5c0a956336ee36e", size = 10710030 }, + { url = "https://files.pythonhosted.org/packages/99/9b/4626a19e1f03eba4c0e77b91a6cf0f73aa9cb5d51a22ee385c22812bcc2c/pyproj-3.7.2-cp314-cp314-win32.whl", hash = "sha256:77f066626030f41be543274f5ac79f2a511fe89860ecd0914f22131b40a0ec25", size = 5991181 }, + { url = "https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a964da1696b8522806f4276ab04ccfff8f9eb95133a92a25900697609d40112", size = 6434721 }, + { url = "https://files.pythonhosted.org/packages/ae/ce/6c910ea2e1c74ef673c5d48c482564b8a7824a44c4e35cca2e765b68cfcc/pyproj-3.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:e258ab4dbd3cf627809067c0ba8f9884ea76c8e5999d039fb37a1619c6c3e1f6", size = 6363821 }, + { url = "https://files.pythonhosted.org/packages/e4/e4/5532f6f7491812ba782a2177fe9de73fd8e2912b59f46a1d056b84b9b8f2/pyproj-3.7.2-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:bbbac2f930c6d266f70ec75df35ef851d96fdb3701c674f42fd23a9314573b37", size = 6241773 }, + { url = "https://files.pythonhosted.org/packages/20/1f/0938c3f2bbbef1789132d1726d9b0e662f10cfc22522743937f421ad664e/pyproj-3.7.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b7544e0a3d6339dc9151e9c8f3ea62a936ab7cc446a806ec448bbe86aebb979b", size = 4652537 }, + { url = "https://files.pythonhosted.org/packages/c7/a8/488b1ed47d25972f33874f91f09ca8f2227902f05f63a2b80dc73e7b1c97/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f7f5133dca4c703e8acadf6f30bc567d39a42c6af321e7f81975c2518f3ed357", size = 9940864 }, + { url = "https://files.pythonhosted.org/packages/c7/cc/7f4c895d0cb98e47b6a85a6d79eaca03eb266129eed2f845125c09cf31ff/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5aff3343038d7426aa5076f07feb88065f50e0502d1b0d7c22ddfdd2c75a3f81", size = 9688868 }, + { url = "https://files.pythonhosted.org/packages/b2/b7/c7e306b8bb0f071d9825b753ee4920f066c40fbfcce9372c4f3cfb2fc4ed/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b0552178c61f2ac1c820d087e8ba6e62b29442debddbb09d51c4bf8acc84d888", size = 11045910 }, + { url = "https://files.pythonhosted.org/packages/42/fb/538a4d2df695980e2dde5c04d965fbdd1fe8c20a3194dc4aaa3952a4d1be/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:47d87db2d2c436c5fd0409b34d70bb6cdb875cca2ebe7a9d1c442367b0ab8d59", size = 10895724 }, + { url = "https://files.pythonhosted.org/packages/e8/8b/a3f0618b03957de9db5489a04558a8826f43906628bb0b766033aa3b5548/pyproj-3.7.2-cp314-cp314t-win32.whl", hash = "sha256:c9b6f1d8ad3e80a0ee0903a778b6ece7dca1d1d40f6d114ae01bc8ddbad971aa", size = 6056848 }, + { url = "https://files.pythonhosted.org/packages/bc/56/413240dd5149dd3291eda55aa55a659da4431244a2fd1319d0ae89407cfb/pyproj-3.7.2-cp314-cp314t-win_amd64.whl", hash = "sha256:1914e29e27933ba6f9822663ee0600f169014a2859f851c054c88cf5ea8a333c", size = 6517676 }, + { url = "https://files.pythonhosted.org/packages/15/73/a7141a1a0559bf1a7aa42a11c879ceb19f02f5c6c371c6d57fd86cefd4d1/pyproj-3.7.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4", size = 6391844 }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 }, +] + +[[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 } +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 }, +] + +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141 }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075 }, +] + +[[package]] +name = "shapely" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/89/c3548aa9b9812a5d143986764dededfa48d817714e947398bdda87c77a72/shapely-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7ae48c236c0324b4e139bea88a306a04ca630f49be66741b340729d380d8f52f", size = 1825959 }, + { url = "https://files.pythonhosted.org/packages/ce/8a/7ebc947080442edd614ceebe0ce2cdbd00c25e832c240e1d1de61d0e6b38/shapely-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eba6710407f1daa8e7602c347dfc94adc02205ec27ed956346190d66579eb9ea", size = 1629196 }, + { url = "https://files.pythonhosted.org/packages/c8/86/c9c27881c20d00fc409e7e059de569d5ed0abfcec9c49548b124ebddea51/shapely-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef4a456cc8b7b3d50ccec29642aa4aeda959e9da2fe9540a92754770d5f0cf1f", size = 2951065 }, + { url = "https://files.pythonhosted.org/packages/50/8a/0ab1f7433a2a85d9e9aea5b1fbb333f3b09b309e7817309250b4b7b2cc7a/shapely-2.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e38a190442aacc67ff9f75ce60aec04893041f16f97d242209106d502486a142", size = 3058666 }, + { url = "https://files.pythonhosted.org/packages/bb/c6/5a30ffac9c4f3ffd5b7113a7f5299ccec4713acd5ee44039778a7698224e/shapely-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:40d784101f5d06a1fd30b55fc11ea58a61be23f930d934d86f19a180909908a4", size = 3966905 }, + { url = "https://files.pythonhosted.org/packages/9c/72/e92f3035ba43e53959007f928315a68fbcf2eeb4e5ededb6f0dc7ff1ecc3/shapely-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6f6cd5819c50d9bcf921882784586aab34a4bd53e7553e175dece6db513a6f0", size = 4129260 }, + { url = "https://files.pythonhosted.org/packages/42/24/605901b73a3d9f65fa958e63c9211f4be23d584da8a1a7487382fac7fdc5/shapely-2.1.2-cp310-cp310-win32.whl", hash = "sha256:fe9627c39c59e553c90f5bc3128252cb85dc3b3be8189710666d2f8bc3a5503e", size = 1544301 }, + { url = "https://files.pythonhosted.org/packages/e1/89/6db795b8dd3919851856bd2ddd13ce434a748072f6fdee42ff30cbd3afa3/shapely-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:1d0bfb4b8f661b3b4ec3565fa36c340bfb1cda82087199711f86a88647d26b2f", size = 1722074 }, + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038 }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039 }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519 }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842 }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316 }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586 }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961 }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856 }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550 }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556 }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308 }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844 }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714 }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745 }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861 }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887 }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931 }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855 }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960 }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851 }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890 }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151 }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130 }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802 }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460 }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223 }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760 }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078 }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178 }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756 }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290 }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463 }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145 }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806 }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301 }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247 }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019 }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137 }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884 }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320 }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931 }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406 }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511 }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704 }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454 }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561 }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824 }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227 }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859 }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204 }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084 }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285 }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924 }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018 }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948 }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341 }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159 }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290 }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141 }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847 }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088 }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866 }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887 }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704 }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628 }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180 }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674 }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976 }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755 }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265 }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726 }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859 }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713 }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084 }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973 }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223 }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973 }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082 }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490 }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263 }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736 }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717 }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461 }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855 }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144 }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683 }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196 }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393 }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583 }, +] + +[[package]] +name = "tqdm" +version = "4.68.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/b3/36c8ecf72e8925200671613332db156d84b99b3aee742a41c1938ebb0808/tqdm-4.68.1.tar.gz", hash = "sha256:fc163d96b287bd031e1aa24421ce4411b25559bd0a1be4fe649bdaa4d2c02bf5", size = 171236 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/aa/218a0eb34de1f753c83e4d0d1c8e7c4cef27f20dcb8342e024f63a80dc86/tqdm-4.68.1-py3-none-any.whl", hash = "sha256:fea4a90e4023f764914569f7802a297277c5ab1a66be5144143e142e1a4031d8", size = 78354 }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321 }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087 }, +] From b509b82dd429bc8b3685e6a8c7411949548adf81 Mon Sep 17 00:00:00 2001 From: "ANJOS, J. S." <0rakul0render@gmail.com> Date: Tue, 9 Jun 2026 12:58:36 -0300 Subject: [PATCH 3/3] att --- pyproject.toml | 3 + python-package/README.md | 26 +- python-package/geocodebr/geocode.py | 280 ++++++++++++++++++- python-package/geocodebr/matching.py | 39 ++- python-package/tests/test_busca_por_cep.py | 3 +- python-package/tests/test_geocode.py | 50 +++- python-package/tests/test_r_python_parity.py | 236 ++++++++++++++++ 7 files changed, 618 insertions(+), 19 deletions(-) create mode 100644 python-package/tests/test_r_python_parity.py diff --git a/pyproject.toml b/pyproject.toml index ee76326..05d98b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,6 @@ packages = ["python-package/geocodebr"] [tool.pytest.ini_options] pythonpath = ["python-package"] testpaths = ["python-package/tests"] +markers = [ + "r_parity: compares Python outputs against the local R package outputs", +] diff --git a/python-package/README.md b/python-package/README.md index 8602d18..c6d5fff 100644 --- a/python-package/README.md +++ b/python-package/README.md @@ -29,8 +29,29 @@ Dependencias principais: Para desenvolvimento e testes: ```bash -python -m pip install -e ".[test]" -python -m pytest -q +uv run pytest -q +``` + +### Testes de paridade R vs Python + +O pacote tambem inclui testes que comparam a saida do Python com a saida do +pacote R usando os dados de exemplo `inst/extdata/small_sample.csv` e +`inst/extdata/large_sample.parquet`. + +Esses testes exigem `Rscript` no `PATH`, instalam o pacote R localmente em uma +biblioteca temporaria e podem baixar dados CNEFE. Se `Rscript` nao estiver +disponivel, eles sao pulados automaticamente. + +No PowerShell: + +```powershell +uv run pytest -m r_parity -q +``` + +Em bash: + +```bash +uv run pytest -m r_parity -q ``` ## Utilizacao @@ -244,4 +265,3 @@ Pontos que ainda precisam de validacao ampla: - paridade completa da padronizacao com o pacote R `enderecobr` - comparacao Python vs R em amostras reais maiores - retorno espacial equivalente a `sf`/`GeoDataFrame` quando `resultado_sf=True` - diff --git a/python-package/geocodebr/geocode.py b/python-package/geocodebr/geocode.py index 0b9b43d..c8e8c9e 100644 --- a/python-package/geocodebr/geocode.py +++ b/python-package/geocodebr/geocode.py @@ -62,7 +62,15 @@ def busca_por_cep( con.execute( f""" CREATE OR REPLACE TEMP TABLE output_df AS - SELECT cep, estado, municipio, logradouro, localidade, lon, lat + SELECT + CASE + WHEN LENGTH(REGEXP_REPLACE(CAST(cep AS VARCHAR), '[^0-9]', '', 'g')) = 8 + THEN SUBSTRING(REGEXP_REPLACE(CAST(cep AS VARCHAR), '[^0-9]', '', 'g'), 1, 5) + || '-' || + SUBSTRING(REGEXP_REPLACE(CAST(cep AS VARCHAR), '[^0-9]', '', 'g'), 6, 3) + ELSE CAST(cep AS VARCHAR) + END AS cep, + estado, municipio, logradouro, localidade, lon, lat FROM read_parquet('{path_to_parquet}') m WHERE REGEXP_REPLACE(CAST(m.cep AS VARCHAR), '[^0-9]', '', 'g') IN ({unique_ceps}) """ @@ -77,7 +85,7 @@ def busca_por_cep( if len(missing) == len(set(ceps)): raise ValueError("Nenhum CEP foi encontrado.") if missing: - values = ", ".join(f"({sql_string(value)})" for value in missing) + values = ", ".join(f"({sql_string(_format_cep_digits(value))})" for value in missing) con.execute(f"INSERT INTO output_df (cep) VALUES {values}") _add_h3_columns(con, "output_df", h3_values) return con.execute("SELECT * FROM output_df").to_arrow_table() @@ -166,6 +174,8 @@ def geocode( ) output_table_to_use = "output_db" if empates_resolvidos == 0 else "output_db2" add_precision_col(con, output_table_to_use) + if resultado_completo and "empate" not in _table_columns(con, output_table_to_use): + con.execute(f"ALTER TABLE {output_table_to_use} ADD COLUMN empate BOOLEAN") merge_results_to_input( con, x="input_db", @@ -215,9 +225,16 @@ def _create_standardized_input( if source is None: expr = "NULL" elif field == "numero": - expr = f"TRY_CAST(NULLIF(REGEXP_REPLACE(CAST({quote_ident(source)} AS VARCHAR), '[^0-9]', '', 'g'), '') AS INTEGER)" + digits = f"REGEXP_REPLACE(CAST({quote_ident(source)} AS VARCHAR), '[^0-9]', '', 'g')" + parsed_number = f"TRY_CAST(NULLIF({digits}, '') AS INTEGER)" + expr = f"CASE WHEN {parsed_number} > 0 THEN {parsed_number} ELSE NULL END" elif field == "cep": - expr = f"NULLIF(REGEXP_REPLACE(CAST({quote_ident(source)} AS VARCHAR), '[^0-9]', '', 'g'), '')" + digits = f"REGEXP_REPLACE(CAST({quote_ident(source)} AS VARCHAR), '[^0-9]', '', 'g')" + expr = ( + f"CASE WHEN LENGTH({digits}) = 8 " + f"THEN SUBSTRING({digits}, 1, 5) || '-' || SUBSTRING({digits}, 6, 3) " + f"ELSE NULLIF({digits}, '') END" + ) else: expr = f"NULLIF(_geocodebr_norm(CAST({quote_ident(source)} AS VARCHAR)), '')" out_name = "bairro" if field == "localidade" else field @@ -232,6 +249,7 @@ def _create_standardized_input( """ ) con.execute("ALTER TABLE input_padrao_db RENAME bairro TO localidade") + _fix_logradouro_prefixes(con) _resolve_estado_names(con) _resolve_municipio_codes(con) @@ -259,10 +277,90 @@ def _assert_standardized_columns(con: duckdb.DuckDBPyConnection) -> None: error_input_nao_padronizado() +def _fix_logradouro_prefixes(con: duckdb.DuckDBPyConnection) -> None: + tipos = [ + "RUA", + "AVENIDA", + "ESTRADA", + "TRAVESSA", + "BECO", + "RODOVIA", + "ALAMEDA", + "PRACA", + "LARGO", + "VIELA", + "FAZENDA", + ] + for tipo in tipos: + con.execute( + f""" + UPDATE input_padrao_db + SET logradouro = REGEXP_REPLACE(logradouro, '^{tipo} {tipo} ', '{tipo} ') + WHERE logradouro LIKE '{tipo} {tipo} %' + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET logradouro = REGEXP_REPLACE(logradouro, '^RUA AVENIDA ', 'AVENIDA ') + WHERE logradouro LIKE 'RUA AVENIDA %' + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET logradouro = REGEXP_REPLACE(logradouro, '^RUA ESTRADA ', 'ESTRADA ') + WHERE logradouro LIKE 'RUA ESTRADA %' + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET logradouro = REGEXP_REPLACE(logradouro, '^RUA PRACA ', 'PRACA ') + WHERE logradouro LIKE 'RUA PRACA %' + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET logradouro = REGEXP_REPLACE(logradouro, '^RUA RODOVIA ', 'RODOVIA ') + WHERE logradouro LIKE 'RUA RODOVIA %' + """ + ) + + def _install_normalize_function(con: duckdb.DuckDBPyConnection) -> None: + import re import unicodedata estados = { + "11": "RO", + "12": "AC", + "13": "AM", + "14": "RR", + "15": "PA", + "16": "AP", + "17": "TO", + "21": "MA", + "22": "PI", + "23": "CE", + "24": "RN", + "25": "PB", + "26": "PE", + "27": "AL", + "28": "SE", + "29": "BA", + "31": "MG", + "32": "ES", + "33": "RJ", + "35": "SP", + "41": "PR", + "42": "SC", + "43": "RS", + "50": "MS", + "51": "MT", + "52": "GO", + "53": "DF", "ACRE": "AC", "ALAGOAS": "AL", "AMAPA": "AP", @@ -299,15 +397,36 @@ def normalize(value: str | None) -> str | None: text = "".join(ch for ch in text if not unicodedata.combining(ch)) text = text.upper() text = "".join(ch if ch.isalnum() else " " for ch in text) - return " ".join(text.split()) + token_map = { + "ST": "SANTO", + "STO": "SANTO", + "STA": "SANTA", + "FAZ": "FAZENDA", + "PQ": "PARQUE", + "AV": "AVENIDA", + "AVN": "AVENIDA", + "DR": "DOUTOR", + "JD": "JARDIM", + } + text = " ".join(token_map.get(token, token) for token in text.split()) + text = text.replace("NOSSA SENHORA GRACAS", "NOSSA SENHORA DAS GRACAS") + text = text.replace("NOSSA SENHORA SANTANA", "NOSSA SENHORA DE SANTANA") + text = text.replace("ELISEOS", "ELISIOS") + text = text.replace("TENENTE CORONEL", "TENENTE-CORONEL") + text = text.replace("BOMFIM", "BONFIM") + rodovia_siglas = "BR|AC|AL|AM|AP|BA|CE|DF|ES|GO|MA|MG|MS|MT|PA|PB|PE|PI|PR|RJ|RN|RO|RR|RS|SC|SE|SP|TO" + text = re.sub(rf"\b({rodovia_siglas})\s+([0-9]{{3}})\b", r"\1-\2", text) + return text def normalize_uf(value: str | None) -> str | None: text = normalize(value) if text is None: return None + if text in estados: + return estados[text] if len(text) == 2: return text - return estados.get(text, text) + return text try: con.create_function("_geocodebr_norm", normalize, ["VARCHAR"], "VARCHAR") @@ -332,9 +451,13 @@ def _resolve_estado_names(con: duckdb.DuckDBPyConnection) -> None: def _resolve_municipio_codes(con: duckdb.DuckDBPyConnection) -> None: from .cache import listar_dados_cache + _resolve_municipio_codes_from_cnefe_sector(con) + try: path = find_cached_parquet(listar_dados_cache(), "municipio") except FileNotFoundError: + _resolve_numeric_municipio_from_cep(con) + _resolve_numeric_municipio_from_address(con) return con.execute( @@ -349,6 +472,8 @@ def _resolve_municipio_codes(con: duckdb.DuckDBPyConnection) -> None: None, ) if code_col is None or "municipio" not in cols: + _resolve_numeric_municipio_from_cep(con) + _resolve_numeric_municipio_from_address(con) return con.execute( @@ -369,6 +494,142 @@ def _resolve_municipio_codes(con: duckdb.DuckDBPyConnection) -> None: AND input_padrao_db.municipio = ref.municipio_codigo """ ) + _resolve_numeric_municipio_from_cep(con) + _resolve_numeric_municipio_from_address(con) + + +def _resolve_municipio_codes_from_cnefe_sector(con: duckdb.DuckDBPyConnection) -> None: + from .cache import listar_dados_cache + + try: + path = find_cached_parquet(listar_dados_cache(), "municipio_logradouro_numero_cep_localidade") + except FileNotFoundError: + return + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_sector_ref AS + WITH raw AS ( + SELECT + estado, + municipio, + SUBSTRING(CAST(cod_setor AS VARCHAR), 1, 7) AS codigo7, + SUBSTRING(CAST(cod_setor AS VARCHAR), 1, 6) AS codigo6, + COUNT(*) AS n + FROM read_parquet('{path}') + WHERE cod_setor IS NOT NULL + GROUP BY estado, municipio, codigo7, codigo6 + ), + long_codes AS ( + SELECT estado, municipio, codigo7 AS codigo, n FROM raw + UNION ALL + SELECT estado, municipio, codigo6 AS codigo, n FROM raw + ), + ranked AS ( + SELECT *, + ROW_NUMBER() OVER ( + PARTITION BY estado, codigo + ORDER BY n DESC, municipio + ) AS rn + FROM long_codes + WHERE codigo IS NOT NULL + ) + SELECT estado, municipio, codigo + FROM ranked + WHERE rn = 1 + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET municipio = ref.municipio + FROM _geocodebr_municipio_sector_ref ref + WHERE REGEXP_MATCHES(input_padrao_db.municipio, '^[0-9]{6,7}$') + AND input_padrao_db.estado = ref.estado + AND input_padrao_db.municipio = ref.codigo + """ + ) + + +def _resolve_numeric_municipio_from_cep(con: duckdb.DuckDBPyConnection) -> None: + from .cache import listar_dados_cache + + try: + path = find_cached_parquet(listar_dados_cache(), "municipio_cep") + except FileNotFoundError: + return + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_cep_ref AS + SELECT DISTINCT estado, municipio, cep + FROM read_parquet('{path}') + """ + ) + con.execute( + """ + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_cep_match AS + SELECT i.tempidgeocodebr, MIN(r.municipio) AS municipio_nome + FROM input_padrao_db i + JOIN _geocodebr_municipio_cep_ref r + ON i.estado = r.estado + AND i.cep = r.cep + WHERE REGEXP_MATCHES(i.municipio, '^[0-9]{6,7}$') + AND i.cep IS NOT NULL + GROUP BY i.tempidgeocodebr + HAVING COUNT(DISTINCT r.municipio) = 1 + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET municipio = m.municipio_nome + FROM _geocodebr_municipio_cep_match m + WHERE input_padrao_db.tempidgeocodebr = m.tempidgeocodebr + """ + ) + + +def _resolve_numeric_municipio_from_address(con: duckdb.DuckDBPyConnection) -> None: + from .cache import listar_dados_cache + + try: + path = find_cached_parquet(listar_dados_cache(), "municipio_logradouro_cep_localidade") + except FileNotFoundError: + return + + con.execute( + f""" + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_addr_ref AS + SELECT DISTINCT estado, municipio, logradouro, cep, localidade + FROM read_parquet('{path}') + """ + ) + con.execute( + """ + CREATE OR REPLACE TEMP TABLE _geocodebr_municipio_addr_match AS + SELECT i.tempidgeocodebr, MIN(r.municipio) AS municipio_nome + FROM input_padrao_db i + JOIN _geocodebr_municipio_addr_ref r + ON i.estado = r.estado + AND i.logradouro = r.logradouro + AND ( + (i.cep IS NOT NULL AND i.cep = r.cep) + OR (i.localidade IS NOT NULL AND i.localidade = r.localidade) + ) + WHERE REGEXP_MATCHES(i.municipio, '^[0-9]{6,7}$') + GROUP BY i.tempidgeocodebr + HAVING COUNT(DISTINCT r.municipio) = 1 + """ + ) + con.execute( + """ + UPDATE input_padrao_db + SET municipio = m.municipio_nome + FROM _geocodebr_municipio_addr_match m + WHERE input_padrao_db.tempidgeocodebr = m.tempidgeocodebr + """ + ) def _add_h3_columns( @@ -416,6 +677,13 @@ def _normalize_ceps(cep: str | list[str] | tuple[str, ...]) -> list[str]: return sorted(set(out)) +def _format_cep_digits(value: str) -> str: + digits = "".join(ch for ch in value if ch.isdigit()) + if len(digits) == 8: + return f"{digits[:5]}-{digits[5:]}" + return value + + def _normalize_h3_res(h3_res: int | list[int] | tuple[int, ...] | None) -> list[int]: if h3_res is None: return [] diff --git a/python-package/geocodebr/matching.py b/python-package/geocodebr/matching.py index e0fda5e..83270c6 100644 --- a/python-package/geocodebr/matching.py +++ b/python-package/geocodebr/matching.py @@ -36,7 +36,6 @@ def create_output_db(con: duckdb.DuckDBPyConnection, resultado_completo: bool) - "cep_encontrado TEXT", "municipio_encontrado TEXT", "estado_encontrado TEXT", - "empate BOOLEAN", "cod_setor TEXT", ] ) @@ -105,6 +104,7 @@ def match_weighted_cases( SELECT {x}.tempidgeocodebr, {x}.numero, {y}.numero AS numero_cnefe, + ABS({x}.numero - {y}.numero) AS distancia_numero, {y}.lat, {y}.lon, REGEXP_REPLACE({y}.endereco_completo, ', \\d+ -', CONCAT(', ', {x}.numero, ' (aprox) -')) AS endereco_encontrado, {y}.desvio_metros, @@ -122,11 +122,17 @@ def match_weighted_cases( SELECT tempidgeocodebr, SUM((1 / ABS(numero - numero_cnefe) * lat)) / SUM(1 / ABS(numero - numero_cnefe)) AS lat, SUM((1 / ABS(numero - numero_cnefe) * lon)) / SUM(1 / ABS(numero - numero_cnefe)) AS lon, - FIRST(endereco_encontrado) AS endereco_encontrado, + CASE WHEN BOOL_OR(log_causa_confusao) + THEN FIRST(endereco_encontrado ORDER BY CASE WHEN numero_cnefe < numero THEN 0 ELSE 1 END, CASE WHEN numero_cnefe < numero THEN -contagem_cnefe ELSE distancia_numero END, CASE WHEN numero_cnefe < numero THEN -numero_cnefe ELSE numero_cnefe END) + ELSE FIRST(endereco_encontrado) + END AS endereco_encontrado, '{match_type}' AS tipo_resultado, AVG(desvio_metros) AS desvio_metros, FIRST(log_causa_confusao) AS log_causa_confusao, - FIRST(contagem_cnefe) AS contagem_cnefe {additional_second} + CASE WHEN BOOL_OR(log_causa_confusao) + THEN FIRST(contagem_cnefe ORDER BY CASE WHEN numero_cnefe < numero THEN 0 ELSE 1 END, CASE WHEN numero_cnefe < numero THEN -contagem_cnefe ELSE distancia_numero END, CASE WHEN numero_cnefe < numero THEN -numero_cnefe ELSE numero_cnefe END) + ELSE FIRST(contagem_cnefe) + END AS contagem_cnefe {additional_second} FROM temp_db GROUP BY tempidgeocodebr, endereco_encontrado """ @@ -204,6 +210,7 @@ def match_weighted_cases_probabilistic( SELECT {x}.tempidgeocodebr, {x}.numero, {y}.numero AS numero_cnefe, + ABS({x}.numero - {y}.numero) AS distancia_numero, {y}.lat, {y}.lon, REGEXP_REPLACE({y}.endereco_completo, ', \\d+ -', CONCAT(', ', {x}.numero, ' (aprox) -')) AS endereco_encontrado, {x}.similaridade_logradouro, @@ -222,12 +229,18 @@ def match_weighted_cases_probabilistic( SELECT tempidgeocodebr, SUM((1 / ABS(numero - numero_cnefe) * lat)) / SUM(1 / ABS(numero - numero_cnefe)) AS lat, SUM((1 / ABS(numero - numero_cnefe) * lon)) / SUM(1 / ABS(numero - numero_cnefe)) AS lon, - FIRST(endereco_encontrado) AS endereco_encontrado, + CASE WHEN BOOL_OR(log_causa_confusao) + THEN FIRST(endereco_encontrado ORDER BY CASE WHEN numero_cnefe < numero THEN 0 ELSE 1 END, CASE WHEN numero_cnefe < numero THEN -contagem_cnefe ELSE distancia_numero END, CASE WHEN numero_cnefe < numero THEN -numero_cnefe ELSE numero_cnefe END) + ELSE FIRST(endereco_encontrado) + END AS endereco_encontrado, '{match_type}' AS tipo_resultado, AVG(desvio_metros) AS desvio_metros, FIRST(log_causa_confusao) AS log_causa_confusao, FIRST(similaridade_logradouro) AS similaridade_logradouro, - FIRST(contagem_cnefe) AS contagem_cnefe {additional_second} + CASE WHEN BOOL_OR(log_causa_confusao) + THEN FIRST(contagem_cnefe ORDER BY CASE WHEN numero_cnefe < numero THEN 0 ELSE 1 END, CASE WHEN numero_cnefe < numero THEN -contagem_cnefe ELSE distancia_numero END, CASE WHEN numero_cnefe < numero THEN -numero_cnefe ELSE numero_cnefe END) + ELSE FIRST(contagem_cnefe) + END AS contagem_cnefe {additional_second} FROM temp_db GROUP BY tempidgeocodebr, endereco_encontrado """ @@ -433,8 +446,20 @@ def _complete_weighted_columns( output_cols = [_found_col_name(col) for col in key_cols] + ["cod_setor"] first_cols = [f"{y}.{col} AS {_found_col_name(col)}" for col in key_cols] first_cols.append(f"{y}.cod_setor AS cod_setor") - second_cols = [f"FIRST({_found_col_name(col)}) AS {_found_col_name(col)}" for col in key_cols] - second_cols.append("FIRST(cod_setor) AS cod_setor") + def first_expr(col: str) -> str: + return ( + f"CASE WHEN BOOL_OR(log_causa_confusao) " + f"THEN FIRST({col} ORDER BY CASE WHEN numero_cnefe < numero THEN 0 ELSE 1 END, " + f"CASE WHEN numero_cnefe < numero THEN -contagem_cnefe ELSE distancia_numero END, " + f"CASE WHEN numero_cnefe < numero THEN -numero_cnefe ELSE numero_cnefe END) " + f"ELSE FIRST({col}) END" + ) + + second_cols = [ + f"{first_expr(_found_col_name(col))} AS {_found_col_name(col)}" + for col in key_cols + ] + second_cols.append(f"{first_expr('cod_setor')} AS cod_setor") return ", " + ", ".join(output_cols), ", " + ", ".join(first_cols), ", " + ", ".join(second_cols) diff --git a/python-package/tests/test_busca_por_cep.py b/python-package/tests/test_busca_por_cep.py index e02524d..e7e23e6 100644 --- a/python-package/tests/test_busca_por_cep.py +++ b/python-package/tests/test_busca_por_cep.py @@ -26,5 +26,4 @@ def test_busca_por_cep_duckdb_flow(tmp_path): assert out.num_rows == 2 assert "h3_03" in out.schema.names - assert out.column("cep").to_pylist() == ["70390025", "99999999"] - + assert out.column("cep").to_pylist() == ["70390-025", "99999-999"] diff --git a/python-package/tests/test_geocode.py b/python-package/tests/test_geocode.py index d1e59d3..d659f45 100644 --- a/python-package/tests/test_geocode.py +++ b/python-package/tests/test_geocode.py @@ -15,7 +15,7 @@ def test_geocode_exact_number_match_with_duckdb(tmp_path): "municipio": ["BRASILIA"], "logradouro": ["AVENIDA TESTE"], "numero": [100], - "cep": ["70000000"], + "cep": ["70000-000"], "localidade": ["CENTRO"], "lon": [-47.9], "lat": [-15.8], @@ -53,3 +53,51 @@ def test_geocode_exact_number_match_with_duckdb(tmp_path): assert out.column("tipo_resultado").to_pylist() == ["dn01"] assert out.column("precisao").to_pylist() == ["numero"] assert "h3_03" in out.schema.names + + +def test_geocode_treats_zero_number_as_missing(tmp_path): + definir_pasta_cache(str(tmp_path), verboso=False) + data_dir = tmp_path / f"geocodebr_data_release_{DATA_RELEASE}" + data_dir.mkdir() + cnefe = pa.table( + { + "estado": ["RJ"], + "municipio": ["ANGRA DOS REIS"], + "logradouro": ["RUA DONA JUDITE"], + "numero": [7], + "cep": ["23915-700"], + "localidade": ["CAPUTERA II"], + "lon": [-44.2], + "lat": [-22.9], + "endereco_completo": ["RUA DONA JUDITE, 7 - CAPUTERA II, ANGRA DOS REIS - RJ, 23915-700"], + "desvio_metros": [10], + "n_casos": [1], + "cod_setor": ["001"], + } + ) + for file in ALL_CNEFE_FILES: + pq.write_table(cnefe, data_dir / file) + + enderecos = pa.table( + { + "uf": ["RJ"], + "cidade": ["Angra dos Reis"], + "rua": ["Rua Dona Judite"], + "num": ["0"], + "cep_in": ["23915-700"], + "bairro": ["Caputera II"], + } + ) + campos = definir_campos( + estado="uf", + municipio="cidade", + logradouro="rua", + numero="num", + cep="cep_in", + localidade="bairro", + ) + + out = geocode(enderecos, campos, resultado_completo=True, verboso=False) + + assert out.column("tipo_resultado").to_pylist() == ["dl01"] + assert out.column("precisao").to_pylist() == ["logradouro"] diff --git a/python-package/tests/test_r_python_parity.py b/python-package/tests/test_r_python_parity.py new file mode 100644 index 0000000..0705f56 --- /dev/null +++ b/python-package/tests/test_r_python_parity.py @@ -0,0 +1,236 @@ +import shutil +import subprocess +import textwrap +from pathlib import Path + +import pyarrow as pa +import pyarrow.csv as pv +import pyarrow.parquet as pq +import pyarrow.types as patypes +import pytest + +from geocodebr import definir_campos, definir_pasta_cache, geocode + + +R_SCRIPT = shutil.which("Rscript") +if R_SCRIPT is None: + for candidate in sorted(Path("C:/Program Files/R").glob("R-*/bin/Rscript.exe"), reverse=True): + if candidate.exists(): + R_SCRIPT = str(candidate) + break + + +pytestmark = pytest.mark.r_parity + + +def test_geocode_matches_r_small_sample(repo_root, tmp_path): + _require_r_parity() + cache_dir = tmp_path / "cache" + r_output = _run_r_geocode( + repo_root=repo_root, + dataset="small", + input_path=repo_root / "inst" / "extdata" / "small_sample.csv", + cache_dir=cache_dir, + output_path=tmp_path / "r_small.parquet", + ) + py_output = _run_python_geocode( + dataset="small", + input_path=repo_root / "inst" / "extdata" / "small_sample.csv", + cache_dir=cache_dir, + ) + _assert_tables_identical(py_output, r_output) + + +def test_geocode_matches_r_large_sample(repo_root, tmp_path): + _require_r_parity() + cache_dir = tmp_path / "cache" + r_output = _run_r_geocode( + repo_root=repo_root, + dataset="large", + input_path=repo_root / "inst" / "extdata" / "large_sample.parquet", + cache_dir=cache_dir, + output_path=tmp_path / "r_large.parquet", + ) + py_output = _run_python_geocode( + dataset="large", + input_path=repo_root / "inst" / "extdata" / "large_sample.parquet", + cache_dir=cache_dir, + ) + _assert_tables_identical(py_output, r_output) + + +@pytest.fixture(scope="session") +def repo_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def _require_r_parity() -> None: + if R_SCRIPT is None: + pytest.skip("Rscript not found in PATH.") + + +def _run_python_geocode(dataset: str, input_path: Path, cache_dir: Path) -> pa.Table: + definir_pasta_cache(str(cache_dir), verboso=False) + if dataset == "small": + enderecos = pv.read_csv(input_path) + campos = definir_campos( + logradouro="nm_logradouro", + numero="Numero", + cep="Cep", + localidade="Bairro", + municipio="nm_municipio", + estado="nm_uf", + ) + elif dataset == "large": + enderecos = pq.read_table(input_path) + campos = definir_campos( + logradouro="logradouro", + numero="numero", + cep="cep", + localidade="bairro", + municipio="municipio", + estado="uf", + ) + else: + raise ValueError(f"Unknown dataset: {dataset}") + + return geocode( + enderecos=enderecos, + campos_endereco=campos, + resultado_completo=True, + resolver_empates=True, + resultado_sf=False, + h3_res=None, + padronizar_enderecos=True, + verboso=False, + cache=True, + n_cores=1, + ) + + +def _run_r_geocode( + repo_root: Path, + dataset: str, + input_path: Path, + cache_dir: Path, + output_path: Path, +) -> pa.Table: + r_code = textwrap.dedent( + r""" + args <- commandArgs(trailingOnly = TRUE) + repo_root <- normalizePath(args[[1]], winslash = "/", mustWork = TRUE) + dataset <- args[[2]] + input_path <- normalizePath(args[[3]], winslash = "/", mustWork = TRUE) + cache_dir <- normalizePath(args[[4]], winslash = "/", mustWork = FALSE) + output_path <- args[[5]] + + lib <- tempfile("geocodebr-r-lib-") + dir.create(lib, recursive = TRUE) + .libPaths(c(lib, .libPaths())) + + install_result <- system2( + file.path(R.home("bin"), "R"), + c("CMD", "INSTALL", "-l", lib, repo_root), + stdout = TRUE, + stderr = TRUE + ) + if (!identical(attr(install_result, "status"), NULL)) { + cat(install_result, sep = "\n") + stop("Could not install local R package geocodebr.") + } + if (!"geocodebr" %in% rownames(installed.packages(lib.loc = lib))) { + cat(install_result, sep = "\n") + stop("Local R package geocodebr was not installed into temporary library.") + } + + suppressPackageStartupMessages(library(geocodebr, lib.loc = lib)) + suppressPackageStartupMessages(library(arrow)) + + geocodebr::definir_pasta_cache(cache_dir, verboso = FALSE) + + if (dataset == "small") { + enderecos <- read.csv(input_path, stringsAsFactors = FALSE) + campos <- geocodebr::definir_campos( + logradouro = "nm_logradouro", + numero = "Numero", + cep = "Cep", + localidade = "Bairro", + municipio = "nm_municipio", + estado = "nm_uf" + ) + } else if (dataset == "large") { + enderecos <- arrow::read_parquet(input_path) + campos <- geocodebr::definir_campos( + logradouro = "logradouro", + numero = "numero", + cep = "cep", + localidade = "bairro", + municipio = "municipio", + estado = "uf" + ) + } else { + stop("Unknown dataset") + } + + out <- geocodebr::geocode( + enderecos = enderecos, + campos_endereco = campos, + resultado_completo = TRUE, + resolver_empates = TRUE, + resultado_sf = FALSE, + h3_res = NULL, + padronizar_enderecos = TRUE, + verboso = FALSE, + cache = TRUE, + n_cores = 1 + ) + + arrow::write_parquet(out, output_path) + """ + ) + script_path = output_path.with_suffix(".R") + script_path.write_text(r_code, encoding="utf-8") + cache_dir.mkdir(parents=True, exist_ok=True) + result = subprocess.run( + [ + R_SCRIPT, + str(script_path), + str(repo_root), + dataset, + str(input_path), + str(cache_dir), + str(output_path), + ], + cwd=repo_root, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=1800, + check=False, + ) + if result.returncode != 0: + pytest.fail(f"R geocode failed with exit code {result.returncode}:\n{result.stdout}") + return pq.read_table(output_path) + + +def _assert_tables_identical(py_output: pa.Table, r_output: pa.Table) -> None: + py_output = _normalize_table(py_output) + r_output = _normalize_table(r_output) + assert py_output.schema.names == r_output.schema.names + assert py_output.num_rows == r_output.num_rows + assert py_output.to_pylist() == r_output.to_pylist() + + +def _normalize_table(table: pa.Table) -> pa.Table: + columns = [] + arrays = [] + for name in table.schema.names: + column = table[name] + if patypes.is_floating(column.type): + values = [None if value is None else round(float(value), 8) for value in column.to_pylist()] + arrays.append(pa.array(values, type=pa.float64())) + else: + values = [None if value is None else str(value) for value in column.to_pylist()] + arrays.append(pa.array(values, type=pa.string())) + columns.append(name) + return pa.table(arrays, names=columns)