From acd94256569ffe858aec6fb906347268f6527170 Mon Sep 17 00:00:00 2001 From: WaltMath Date: Tue, 22 Oct 2024 17:53:54 -0300 Subject: [PATCH 1/5] =?UTF-8?q?feature:=20=E2=9C=A8=20Estrutura=20servi?= =?UTF-8?q?=C3=A7o=20de=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 28 +++++ src/__init__.py | 9 ++ src/bd.py | 25 ++++ src/scripts/__init__.py | 0 src/scripts/passo_1_selecao_cidadao.py | 160 +++++++++++++++++++++++++ 5 files changed, 222 insertions(+) create mode 100644 app.py create mode 100644 src/__init__.py create mode 100644 src/bd.py create mode 100644 src/scripts/__init__.py create mode 100644 src/scripts/passo_1_selecao_cidadao.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..19c8b1e --- /dev/null +++ b/app.py @@ -0,0 +1,28 @@ +import os +import datetime + +from flask import Flask, request, jsonify + + +from src.scripts.passo_1_selecao_cidadao import processa_passo_1 + +app = Flask(__name__) + + +@app.route("/passo_1", methods=['POST']) +def passo_1(): + content_type = request.headers.get('Content-Type') + if (content_type != 'application/json'): + return 'Erro, content-type deve ser json', 400 + + + return processa_passo_1() + + +#@app.route("/passo_2", methods=['POST']) + +#@app.route("/passo_3", methods=['POST']) + +if __name__ == "__main__": + from waitress import serve + serve(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080))) \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e4cbe22 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,9 @@ +import sys + +if sys.version_info >= (3, 8): + from importlib import metadata +else: + import importlib_metadata as metadata + + +__VERSION__ = metadata.version("mensageria_mvp") \ No newline at end of file diff --git a/src/bd.py b/src/bd.py new file mode 100644 index 0000000..ad4259d --- /dev/null +++ b/src/bd.py @@ -0,0 +1,25 @@ +from google.cloud import bigquery +import pandas as pd + +class BigQueryClient: + def __init__(self: "BigQueryClient"): + """Configura o ambiente e inicializa o cliente do BigQuery.""" + self.client: bigquery.Client = self.configurar_ambiente() + + def configurar_ambiente(self: "BigQueryClient") -> bigquery.Client: + """Configura o ambiente e retorna o cliente BigQuery.""" + return bigquery.Client() + + def consultar_dados(self: "BigQueryClient", query: str) -> pd.DataFrame: + """Consulta dados no BigQuery usando a query fornecida. + + Args: + self: Instância da classe BigQueryClient. + query: Uma string contendo a consulta SQL. + + Returns: + pd.DataFrame: O resultado da consulta no formato de DataFrame. + """ + query_job = self.client.query(query) + rows = [dict(row) for row in query_job] + return pd.DataFrame(rows) diff --git a/src/scripts/__init__.py b/src/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/scripts/passo_1_selecao_cidadao.py b/src/scripts/passo_1_selecao_cidadao.py new file mode 100644 index 0000000..da1d681 --- /dev/null +++ b/src/scripts/passo_1_selecao_cidadao.py @@ -0,0 +1,160 @@ +import pandas as pd +from google.cloud import bigquery +import numpy as np +from typing import Any,Tuple +import json + +from src.bd import BigQueryClient + +# Função para identificar pendências nas linhas de cuidado +def identificar_pendencias(df_pendencias: pd.DataFrame) -> pd.DataFrame: + df_pendencias['citopatologico_pendente_atual'] = df_pendencias['status_exame'].apply( + lambda x: True if x in ('exame_nunca_realizado', 'exame_vencido', 'exame_vence_no_quadrimestre_atual') else False + ) + df_pendencias['cronicos_pendente_atual'] = df_pendencias.apply(pendencia_cronicos, axis=1) + return df_pendencias + +# Função para verificar pendência de crônicos +def pendencia_cronicos(x: pd.Series) -> bool: + if (x['esta_na_lista_hipertensos'] and (not x['realizou_afericao_ultimos_6_meses'] or not x['realizou_consulta_hip_ultimos_6_meses'])) or \ + (x['esta_na_lista_diabeticos'] and (not x['realizou_consulta_dia_ultimos_6_meses'] or not x['realizou_solicitacao_hemoglobina_ultimos_6_meses'])): + return True + return False + +# Função para unificar dados históricos e pendências +def unificar_dados(df: pd.DataFrame, df_pendencias: pd.DataFrame) -> pd.DataFrame: + df['municipio_id_sus'] = df['municipio_id_sus'].fillna(0).astype(int).astype(str) + df_pendencias['municipio_id_sus'] = df_pendencias['municipio_id_sus'].fillna(0).astype(int).astype(str) + df_pendencias['equipe_ine'] = df['equipe_ine'].fillna(0).astype(int).astype(str) + + df_unificado = df.merge(df_pendencias, how='right', on=['nome_do_paciente', 'data_de_nascimento', 'municipio_id_sus']) + df_unificado['pendencia_atualizada'] = df_unificado.apply(pendencia_atualizada, axis=1) + return df_unificado[df_unificado['pendencia_atualizada']].drop_duplicates() + +# Função para verificar se há pendência atualizada +def pendencia_atualizada(x: pd.Series) -> bool: + if x['linha_cuidado'] == 'cronicos' and x['cronicos_pendente_atual']: + return True + elif x['linha_cuidado'] == 'citopatologico' and x['citopatologico_pendente_atual']: + return True + return False + +# Função para filtrar histórico de envio de mensagens +def filtrar_historico(df_unificado: pd.DataFrame, df_historico_envio_mensagens: pd.DataFrame) -> pd.DataFrame: + df_unificado['chave_cidadao'] = df_unificado['nome_do_paciente'].astype(str) + '_' + df_unificado['data_de_nascimento'].astype(str) + + if not df_historico_envio_mensagens.empty: + df_historico_envio_mensagens['chave_cidadao'] = df_historico_envio_mensagens['nome_do_paciente'].astype(str) + '_' + df_historico_envio_mensagens['data_de_nascimento'].astype(str) + return df_unificado[~df_unificado['chave_cidadao'].isin(df_historico_envio_mensagens['chave_cidadao'])] + + return df_unificado + +# Função para tratar os números de celular +def tratar_telefones(df: pd.DataFrame) -> pd.DataFrame: + df = df[df['celular_tratado'] != 0] + df['celular_tratado'] = df['celular_tratado'].astype(str) + df['caracteres_celular'] = df['celular_tratado'].str.len() + df['celular_tratado'] = df.apply(trata_celular, axis=1) + return df + +# Função para adicionar o código do país no celular +def trata_celular(x: pd.Series) -> Any: + if x['caracteres_celular'] == 10: + return "55" + x['celular_tratado'][:2] + "9" + x['celular_tratado'][2:] + elif x['caracteres_celular'] == 11: + return "55" + x['celular_tratado'] + return None + +# Função para processar a data do último exame +def processar_exames(df: pd.DataFrame) -> pd.DataFrame: + df['data_exame_cito'] = df['data_exame_cito'].astype('datetime64[ns]') + df['data_afericao_hipertensos'] = df['data_afericao_hipertensos'].astype('datetime64[ns]') + df['data_exame_diabeticos'] = df['data_exame_diabeticos'].astype('datetime64[ns]') + df['data_ultimo_exame'] = df.apply(ultimo_exame, axis=1) + return df + +# Função para determinar a data do último exame +def ultimo_exame(x: pd.Series) -> pd.Timestamp: + if x['linha_cuidado'] == 'citopatologico': + return x['data_exame_cito'] + elif x['linha_cuidado'] == 'cronicos' and x['hipertensao_pendente'] and x['diabetes_pendente']: + return max(x['data_afericao_hipertensos'], x['data_exame_diabeticos']) + elif x['linha_cuidado'] == 'cronicos' and x['hipertensao_pendente']: + return x['data_afericao_hipertensos'] + return x['data_exame_diabeticos'] + +# Função para dividir cidadãos em grupos equilibrados +def dividir_grupos_equilibrado(df: pd.DataFrame, num_grupos: int = 3) -> pd.DataFrame: + def dividir_municipio(grupo: pd.Series) -> np.ndarray: + grupo_size = len(grupo) + grupos = np.tile(range(1, num_grupos + 1), grupo_size // num_grupos + 1)[:grupo_size] + np.random.shuffle(grupos) + return grupos + df['horario_grupo'] = df.groupby('equipe_ine')['equipe_ine'].transform(dividir_municipio) + return df + +# Função para selecionar cidadãos para envio de mensagens +def selecionar_cidadaos_para_envio(df: pd.DataFrame) -> pd.DataFrame: + return df.groupby(['municipio', 'equipe_ine', 'linha_cuidado', 'horario_grupo', 'grupo']).apply( + lambda x: x.sample(min(len(x), 5)) + ).reset_index(drop=True) + +# Função para preparar os dados para envio +def preparar_para_envio(df: pd.DataFrame) -> pd.DataFrame: + dia_semana = pd.to_datetime('today').strftime('%a').upper() + df['mvp_tipo_grupo'] = dia_semana + '_H' + df['horario_grupo'].astype(str).str.zfill(2) + df['mvp_data_envio'] = pd.to_datetime(pd.Timestamp.today().strftime('%Y-%m-%d')) + return df[['municipio', 'municipio_id_sus', 'equipe_ine', 'equipe_nome', 'linha_cuidado', + 'nome_do_paciente', 'data_de_nascimento', 'celular_tratado', 'mvp_tipo_grupo', 'mvp_grupo', + 'numero_visitas_ubs_ultimos_12_meses', 'data_ultimo_exame', 'mvp_data_envio']] + +# Função para atualizar o histórico de envios no BigQuery +def atualizar_historico(client: bigquery.Client, df: pd.DataFrame, table_id: str) -> None: + job_config = bigquery.LoadJobConfig(write_disposition="WRITE_APPEND") + client.load_table_from_dataframe(df, table_id, job_config=job_config) + +def processa_passo_1(project_id: str) -> Tuple[str, int, dict]: + """ + Função que processa diariamente o envio de mensagens para cidadãos de acordo com critérios definidos. + + Args: + request (Request): Objeto de requisição HTTP contendo os dados do projeto e credenciais. + + Returns: + Tuple[str, int, dict]: Resposta HTTP contendo o status, mensagem e os dados processados. + """ + # 1. Configurar ambiente + bq_client = BigQueryClient() + client = bq_client.configurar_ambiente() + + # 2. Consultar dados + query_df = f"SELECT * FROM `{project_id}.ip_mensageria_camada_prata.divisao_teste_controle_equipes`" + df: pd.DataFrame = bq_client.bq_client.consultar_dados(client, query_df) + + query_df_pendencias = f"SELECT * FROM `{project_id}.ip_mensageria_camada_prata.unificado_lista_com_telefones_grupos_atendimentos_`" + df_pendencias: pd.DataFrame = bq_client.consultar_dados(client, query_df_pendencias) + + query_df_historico_envio_mensagens = f"SELECT * FROM `{project_id}.ip_mensageria_camada_prata.historico_envio_mensagens`" + df_historico_envio_mensagens: pd.DataFrame = bq_client.consultar_dados(client, query_df_historico_envio_mensagens) + + # 3. Preparar dados + df_pendencias = identificar_pendencias(df_pendencias) + df_unificado: pd.DataFrame = unificar_dados(df, df_pendencias) + df_filtrado: pd.DataFrame = filtrar_historico(df_unificado, df_historico_envio_mensagens) + df_filtrado: pd.DataFrame = tratar_telefones(df_filtrado) + df_filtrado: pd.DataFrame = processar_exames(df_filtrado) + + # 4. Dividir e selecionar cidadãos para envio + df_dividido: pd.DataFrame = dividir_grupos_equilibrado(df_filtrado) + df_envio_diario: pd.DataFrame = selecionar_cidadaos_para_envio(df_dividido) + + # 5. Preparar dados para envio e atualizar histórico + df_envio_dia_atual: pd.DataFrame = preparar_para_envio(df_envio_diario) + atualizar_historico(client, df_envio_dia_atual, f"{project_id}.ip_mensageria_camada_prata.historico_envio_mensagens") + + # Retornar sucesso com os dados preparados + return json.dumps({ + 'status': 'sucesso', + 'mensagem': 'Dados processados e histórico atualizado.', + 'dados_enviados': df_envio_dia_atual.to_dict(orient='records') + }), 200, {'Content-Type': 'application/json'} \ No newline at end of file From d80c9f2a3d97d51873aafcb2fefa2aaef19b052c Mon Sep 17 00:00:00 2001 From: WaltMath Date: Tue, 22 Oct 2024 17:56:08 -0300 Subject: [PATCH 2/5] =?UTF-8?q?ci-cd:=20=F0=9F=92=9A=20Cria=20workflow=20p?= =?UTF-8?q?ara=20implanta=C3=A7=C3=A3o=20do=20servi=C3=A7o=20na=20GCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/master_deploy.yml | 63 +++++++++++++++++++++++++++++ requirements.txt | 0 2 files changed, 63 insertions(+) create mode 100644 .github/workflows/master_deploy.yml create mode 100644 requirements.txt diff --git a/.github/workflows/master_deploy.yml b/.github/workflows/master_deploy.yml new file mode 100644 index 0000000..64d6bcd --- /dev/null +++ b/.github/workflows/master_deploy.yml @@ -0,0 +1,63 @@ +name: Build e Deploy para Google Cloud Run + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Baixar Repositório + uses: actions/checkout@v2 + + - name: Instalar Google Cloud CLI + run: | + sudo apt-get update + sudo apt-get install -y google-cloud-cli + + - name: Autenticar no Google Cloud + uses: google-github-actions/auth@v0.4.0 + with: + credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} + + - name: Configurar Docker para GCP + run: | + gcloud auth configure-docker us-central1-docker.pkg.dev + + - name: Construir Imagem Docker + run: | + docker build -t mesageria-mvp:latest . + + - name: Adicionar Tag à Imagem Docker + run: | + docker tag mesageria-mvp:latest us-central1-docker.pkg.dev/predictive-keep-314223/mesageria-mvp/mesageria-mvp:latest + + - name: Enviar Imagem Docker + run: | + docker push us-central1-docker.pkg.dev/predictive-keep-314223/mesageria-mvp/mesageria-mvp:latest + + deploy: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Instalar Google Cloud CLI + run: | + sudo apt-get update + sudo apt-get install -y google-cloud-cli + + - name: Autenticar no Google Cloud + uses: google-github-actions/auth@v0.4.0 + with: + credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} + + - name: Fazer Deploy no Google Cloud Run + run: | + gcloud run deploy mesageria-mvp-staging \ + --image="us-central1-docker.pkg.dev/predictive-keep-314223/mesageria-mvp/mesageria-mvp:latest" \ + --region=us-central1 \ + --platform=managed \ + --allow-unauthenticated \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 From d6833e8ec97c076998de7d340be1ffbc1d22d4d3 Mon Sep 17 00:00:00 2001 From: WaltMath Date: Fri, 25 Oct 2024 10:00:06 -0300 Subject: [PATCH 3/5] =?UTF-8?q?ci-cd:=20=F0=9F=92=9A=20Cria=20workflow=20p?= =?UTF-8?q?ara=20implanta=C3=A7=C3=A3o=20do=20servi=C3=A7o=20na=20GCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/master_deploy.yml | 54 +- Dockerfile | 39 + poetry.lock | 1038 +++++++++++++++++++++++++++ poetry.toml | 2 + pyproject.toml | 27 + requirements.txt | 0 6 files changed, 1149 insertions(+), 11 deletions(-) create mode 100644 Dockerfile create mode 100644 poetry.lock create mode 100644 poetry.toml create mode 100644 pyproject.toml delete mode 100644 requirements.txt diff --git a/.github/workflows/master_deploy.yml b/.github/workflows/master_deploy.yml index 64d6bcd..469136f 100644 --- a/.github/workflows/master_deploy.yml +++ b/.github/workflows/master_deploy.yml @@ -3,7 +3,7 @@ name: Build e Deploy para Google Cloud Run on: push: branches: - - main + - feat/pipeline jobs: build: @@ -27,17 +27,48 @@ jobs: run: | gcloud auth configure-docker us-central1-docker.pkg.dev - - name: Construir Imagem Docker + - name: Build and push Docker image + env: + PROJECT_ID: ${{ secrets.PROJECT_ID }} + TEMPLATE_NAMESPACE: ${{ secrets.TEMPLATE_NAMESPACE }} + ENV_PAULORAMOS_MA: ${{ secrets.ENV_PAULORAMOS_MA }} + ENV_LAGOVERDE_MA: ${{ secrets.ENV_LAGOVERDE_MA }} + ENV_PACOTI_CE: ${{ secrets.ENV_PACOTI_CE }} + ENV_MONSENHORTABOSA_CE: ${{ secrets.ENV_MONSENHORTABOSA_CE }} + ENV_MARAJADOSENA_MA: ${{ secrets.ENV_MARAJADOSENA_MA }} + ENV_ALAGOINHA_PE: ${{ secrets.ENV_ALAGOINHA_PE }} + ENV_BARAUNA_RN: ${{ secrets.ENV_BARAUNA_RN }} + ENV_JUCURUCU_BA: ${{ secrets.ENV_JUCURUCU_BA }} + ENV_VITORINOFREIRE_MA: ${{ secrets.ENV_VITORINOFREIRE_MA }} + ENV_BREJODEAREIA_MA: ${{ secrets.ENV_BREJODEAREIA_MA }} + ENV_OIAPOQUE_AP: ${{ secrets.ENV_OIAPOQUE_AP }} + ENV_TARRAFAS_CE: ${{ secrets.ENV_TARRAFAS_CE }} + ENV_SALVATERRA_PA: ${{ secrets.ENV_SALVATERRA_PA }} + ENV_LAGOADOOURO_PE: ${{ secrets.ENV_LAGOADOOURO_PE }} run: | - docker build -t mesageria-mvp:latest . - - - name: Adicionar Tag à Imagem Docker - run: | - docker tag mesageria-mvp:latest us-central1-docker.pkg.dev/predictive-keep-314223/mesageria-mvp/mesageria-mvp:latest + docker build \ + --build-arg PROJECT_ID=$PROJECT_ID \ + --build-arg TEMPLATE_NAMESPACE=$TEMPLATE_NAMESPACE \ + --build-arg ENV_PAULORAMOS_MA=$ENV_PAULORAMOS_MA \ + --build-arg ENV_LAGOVERDE_MA=$ENV_LAGOVERDE_MA \ + --build-arg ENV_PACOTI_CE=$ENV_PACOTI_CE \ + --build-arg ENV_MONSENHORTABOSA_CE=$ENV_MONSENHORTABOSA_CE \ + --build-arg ENV_MARAJADOSENA_MA=$ENV_MARAJADOSENA_MA \ + --build-arg ENV_ALAGOINHA_PE=$ENV_ALAGOINHA_PE \ + --build-arg ENV_BARAUNA_RN=$ENV_BARAUNA_RN \ + --build-arg ENV_JUCURUCU_BA=$ENV_JUCURUCU_BA \ + --build-arg ENV_VITORINOFREIRE_MA=$ENV_VITORINOFREIRE_MA \ + --build-arg ENV_BREJODEAREIA_MA=$ENV_BREJODEAREIA_MA \ + --build-arg ENV_OIAPOQUE_AP=$ENV_OIAPOQUE_AP \ + --build-arg ENV_TARRAFAS_CE=$ENV_TARRAFAS_CE \ + --build-arg ENV_SALVATERRA_PA=$ENV_SALVATERRA_PA \ + --build-arg ENV_LAGOADOOURO_PE=$ENV_LAGOADOOURO_PE \ + -t us-central1-docker.pkg.dev/predictive-keep-314223/mensageria-mvp/mensageria-mvp:latest . + docker push us-central1-docker.pkg.dev/predictive-keep-314223/mensageria-mvp/mensageria-mvp:latest - name: Enviar Imagem Docker run: | - docker push us-central1-docker.pkg.dev/predictive-keep-314223/mesageria-mvp/mesageria-mvp:latest + docker push us-central1-docker.pkg.dev/predictive-keep-314223/mensageria-mvp/mensageria-mvp:latest deploy: runs-on: ubuntu-latest @@ -56,8 +87,9 @@ jobs: - name: Fazer Deploy no Google Cloud Run run: | - gcloud run deploy mesageria-mvp-staging \ - --image="us-central1-docker.pkg.dev/predictive-keep-314223/mesageria-mvp/mesageria-mvp:latest" \ + gcloud run deploy mesageria-mvp \ + --image="us-central1-docker.pkg.dev/predictive-keep-314223/mensageria-mvp/mensageria-mvp:latest" \ --region=us-central1 \ --platform=managed \ - --allow-unauthenticated \ No newline at end of file + --allow-unauthenticated \ + --project=predictive-keep-314223 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eebc16f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# Use a imagem base do Python +FROM python:3.10.8-slim-bullseye + +# Instala o OpenVPN e dependências do sistema +RUN apt-get update && \ + apt-get install -y \ + curl \ + build-essential \ + libpq-dev + +# Define o diretório de trabalho dentro do contêiner +WORKDIR /app + +ENV POETRY_VIRTUALENVS_CREATE 1 +ENV POETRY_VIRTUALENVS_IN_PROJECT 0 + +# Instala o Poetry +RUN curl -sSL https://install.python-poetry.org | python3 - + +# Define o PATH para incluir o diretório do Poetry +ENV PATH="/root/.local/bin:${PATH}" + +# Copia os arquivos de configuração do Poetry +COPY pyproject.toml poetry.lock poetry.toml /app/ + +# Configura o número máximo de workers para a instalação do Poetry +RUN poetry config installer.max-workers 10 + +# Instala as dependências usando o Poetry +RUN poetry install --no-root + +# Copia o código do aplicativo +COPY . /app + +# Exponha a porta 5000 +EXPOSE 5000 + +# Comando para iniciar o script de inicialização +CMD ["poetry", "run", "python", "app.py"] \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..568ceca --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1038 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "blinker" +version = "1.8.2" +description = "Fast, simple object-to-object and broadcast signaling" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, +] + +[[package]] +name = "cachetools" +version = "5.5.0" +description = "Extensible memoizing collections and decorators" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "google-api-core" +version = "2.21.0" +description = "Google API client core library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_api_core-2.21.0-py3-none-any.whl", hash = "sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d"}, + {file = "google_api_core-2.21.0.tar.gz", hash = "sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +grpcio = [ + {version = ">=1.33.2,<2.0dev", optional = true, markers = "extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\""}, +] +grpcio-status = [ + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\""}, +] +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-auth" +version = "2.35.0" +description = "Google Authentication Library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_auth-2.35.0-py2.py3-none-any.whl", hash = "sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f"}, + {file = "google_auth-2.35.0.tar.gz", hash = "sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography", "pyopenssl"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-cloud-bigquery" +version = "3.26.0" +description = "Google BigQuery API client library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_cloud_bigquery-3.26.0-py2.py3-none-any.whl", hash = "sha256:e0e9ad28afa67a18696e624cbccab284bf2c0a3f6eeb9eeb0426c69b943793a8"}, + {file = "google_cloud_bigquery-3.26.0.tar.gz", hash = "sha256:edbdc788beea659e04c0af7fe4dcd6d9155344b98951a0d5055bd2f15da4ba23"}, +] + +[package.dependencies] +google-api-core = {version = ">=2.11.1,<3.0.0dev", extras = ["grpc"]} +google-auth = ">=2.14.1,<3.0.0dev" +google-cloud-core = ">=2.4.1,<3.0.0dev" +google-resumable-media = ">=2.0.0,<3.0dev" +packaging = ">=20.0.0" +python-dateutil = ">=2.7.3,<3.0dev" +requests = ">=2.21.0,<3.0.0dev" + +[package.extras] +all = ["Shapely (>=1.8.4,<3.0.0dev)", "bigquery-magics (>=0.1.0)", "db-dtypes (>=0.3.0,<2.0.0dev)", "geopandas (>=0.9.0,<1.0dev)", "google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "importlib-metadata (>=1.0.0)", "ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)", "opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)", "pandas (>=1.1.0)", "proto-plus (>=1.22.3,<2.0.0dev)", "protobuf (>=3.20.2,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev)", "pyarrow (>=3.0.0)", "tqdm (>=4.7.4,<5.0.0dev)"] +bigquery-v2 = ["proto-plus (>=1.22.3,<2.0.0dev)", "protobuf (>=3.20.2,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev)"] +bqstorage = ["google-cloud-bigquery-storage (>=2.6.0,<3.0.0dev)", "grpcio (>=1.47.0,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "pyarrow (>=3.0.0)"] +geopandas = ["Shapely (>=1.8.4,<3.0.0dev)", "geopandas (>=0.9.0,<1.0dev)"] +ipython = ["bigquery-magics (>=0.1.0)"] +ipywidgets = ["ipykernel (>=6.0.0)", "ipywidgets (>=7.7.0)"] +opentelemetry = ["opentelemetry-api (>=1.1.0)", "opentelemetry-instrumentation (>=0.20b0)", "opentelemetry-sdk (>=1.1.0)"] +pandas = ["db-dtypes (>=0.3.0,<2.0.0dev)", "importlib-metadata (>=1.0.0)", "pandas (>=1.1.0)", "pyarrow (>=3.0.0)"] +tqdm = ["tqdm (>=4.7.4,<5.0.0dev)"] + +[[package]] +name = "google-cloud-core" +version = "2.4.1" +description = "Google Cloud API client core library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, +] + +[package.dependencies] +google-api-core = ">=1.31.6,<2.0.0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.25.0,<3.0dev" + +[package.extras] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] + +[[package]] +name = "google-cloud-secret-manager" +version = "2.21.0" +description = "Google Cloud Secret Manager API client library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_cloud_secret_manager-2.21.0-py2.py3-none-any.whl", hash = "sha256:b7fed5c2f3be5e10d94053ea3a7c6a7c5813d38da39c678ef6c1137d6e25a310"}, + {file = "google_cloud_secret_manager-2.21.0.tar.gz", hash = "sha256:d1ae84ecf98cfc319c9a3f1012355cebd19317b662cc9dff1a2c36234580807b"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.0 || >=2.11.0,<3.0.0dev", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" +grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" +proto-plus = [ + {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, + {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, +] +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" + +[[package]] +name = "google-crc32c" +version = "1.6.0" +description = "A python wrapper of the C library 'Google CRC32C'" +category = "main" +optional = false +python-versions = ">=3.9" +files = [ + {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa"}, + {file = "google_crc32c-1.6.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9"}, + {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7"}, + {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e"}, + {file = "google_crc32c-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc"}, + {file = "google_crc32c-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42"}, + {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4"}, + {file = "google_crc32c-1.6.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8"}, + {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d"}, + {file = "google_crc32c-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f"}, + {file = "google_crc32c-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3"}, + {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d"}, + {file = "google_crc32c-1.6.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b"}, + {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00"}, + {file = "google_crc32c-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3"}, + {file = "google_crc32c-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760"}, + {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205"}, + {file = "google_crc32c-1.6.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0"}, + {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2"}, + {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871"}, + {file = "google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57"}, + {file = "google_crc32c-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c"}, + {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc"}, + {file = "google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d"}, + {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24"}, + {file = "google_crc32c-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d"}, + {file = "google_crc32c-1.6.0.tar.gz", hash = "sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc"}, +] + +[package.extras] +testing = ["pytest"] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +description = "Utilities for Google Media Downloads and Resumable Uploads" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, + {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, +] + +[package.dependencies] +google-crc32c = ">=1.0,<2.0dev" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] +requests = ["requests (>=2.18.0,<3.0.0dev)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.65.0" +description = "Common protobufs used in Google APIs" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis_common_protos-1.65.0-py2.py3-none-any.whl", hash = "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63"}, + {file = "googleapis_common_protos-1.65.0.tar.gz", hash = "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0"}, +] + +[package.dependencies] +grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.13.1" +description = "IAM API client library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpc-google-iam-v1-0.13.1.tar.gz", hash = "sha256:3ff4b2fd9d990965e410965253c0da6f66205d5a8291c4c31c6ebecca18a9001"}, + {file = "grpc_google_iam_v1-0.13.1-py2.py3-none-any.whl", hash = "sha256:c3e86151a981811f30d5e7330f271cee53e73bb87755e88cc3b6f0c7b5fe374e"}, +] + +[package.dependencies] +googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} +grpcio = ">=1.44.0,<2.0.0dev" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" + +[[package]] +name = "grpcio" +version = "1.67.0" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc"}, + {file = "grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9"}, + {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8"}, + {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8"}, + {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4"}, + {file = "grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65"}, + {file = "grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6"}, + {file = "grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4"}, + {file = "grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d"}, + {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8"}, + {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf"}, + {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23"}, + {file = "grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8"}, + {file = "grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772"}, + {file = "grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d"}, + {file = "grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591"}, + {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52"}, + {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81"}, + {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3"}, + {file = "grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955"}, + {file = "grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15"}, + {file = "grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a"}, + {file = "grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af"}, + {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c"}, + {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153"}, + {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03"}, + {file = "grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69"}, + {file = "grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210"}, + {file = "grpcio-1.67.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3"}, + {file = "grpcio-1.67.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9"}, + {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db"}, + {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e"}, + {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737"}, + {file = "grpcio-1.67.0-cp38-cp38-win32.whl", hash = "sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad"}, + {file = "grpcio-1.67.0-cp38-cp38-win_amd64.whl", hash = "sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365"}, + {file = "grpcio-1.67.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74"}, + {file = "grpcio-1.67.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2"}, + {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33"}, + {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad"}, + {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617"}, + {file = "grpcio-1.67.0-cp39-cp39-win32.whl", hash = "sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571"}, + {file = "grpcio-1.67.0-cp39-cp39-win_amd64.whl", hash = "sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a"}, + {file = "grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.67.0)"] + +[[package]] +name = "grpcio-status" +version = "1.67.0" +description = "Status proto mapping for gRPC" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio_status-1.67.0-py3-none-any.whl", hash = "sha256:0e79e2e01ba41a6ca6ed9d7a825323c511fe1653a646f8014c7e3c8132527acc"}, + {file = "grpcio_status-1.67.0.tar.gz", hash = "sha256:c3e5a86fa007e9e263cd5f988a8a907484da4caab582874ea2a4a6092734046b"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.67.0" +protobuf = ">=5.26.1,<6.0dev" + +[[package]] +name = "gunicorn" +version = "23.0.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, + {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "numpy" +version = "2.1.2" +description = "Fundamental package for array computing in Python" +category = "main" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, + {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, + {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, + {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, + {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, + {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, + {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, + {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, + {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, + {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "proto-plus" +version = "1.25.0" +description = "Beautiful, Pythonic protocol buffers." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, + {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<6.0.0dev" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + +[[package]] +name = "protobuf" +version = "5.28.3" +description = "" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, + {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, + {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, + {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, + {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, + {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, + {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, + {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, + {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, + {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.1" +description = "A collection of ASN.1-based protocols modules" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, + {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.7.0" + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tzdata" +version = "2024.2" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "waitress" +version = "3.0.0" +description = "Waitress WSGI server" +category = "main" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "waitress-3.0.0-py3-none-any.whl", hash = "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669"}, + {file = "waitress-3.0.0.tar.gz", hash = "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1"}, +] + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["coverage (>=5.0)", "pytest", "pytest-cov"] + +[[package]] +name = "werkzeug" +version = "3.0.4" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "90e569b152c0fff565f4478f647e8ad3d0bd2e626bed4c6d848065ccdea872b0" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..2287930 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +path = ".venv" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1b88e74 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "mensageria-mvp" +version = "0.1.0" +description = "" +authors = ["WaltMath "] +readme = "README.md" +packages = [{include = "src"}] + +[tool.poetry.dependencies] +python = "^3.10" +pandas = "^2.2.3" +gunicorn = "^23.0.0" +numpy = "^2.1.2" +requests = "^2.32.3" +google-cloud-bigquery = "^3.26.0" +flask = "^3.0.3" +waitress = "^3.0.0" +python-dotenv = "^1.0.1" +google-cloud-secret-manager = "^2.21.0" + + +[virtualenvs] +path = ".venv" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e69de29..0000000 From a9740e3420b5ed2d46f81448ccd0e7fb44a3b81d Mon Sep 17 00:00:00 2001 From: WaltMath Date: Fri, 25 Oct 2024 10:00:45 -0300 Subject: [PATCH 4/5] =?UTF-8?q?feature:=20=E2=9C=A8=20Estrutura=20endpoint?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Dockerfile | 36 ++++ __init__.py | 0 app.py | 156 ++++++++++++-- .../passo_3_envio_de_mensagens.py | 167 ++++++++++----- src/__init__.py | 9 - src/loggers.py | 5 + src/scripts/passo_1_selecao_cidadao.py | 32 +-- .../passo_2_match_com_equipe_e_upload_turn.py | 174 ++++++++++++++++ src/scripts/passo_3_envio_de_mensagens.py | 193 ++++++++++++++++++ 10 files changed, 684 insertions(+), 89 deletions(-) create mode 100644 __init__.py delete mode 100644 src/__init__.py create mode 100644 src/loggers.py create mode 100644 src/scripts/passo_2_match_com_equipe_e_upload_turn.py create mode 100644 src/scripts/passo_3_envio_de_mensagens.py diff --git a/.gitignore b/.gitignore index aeda97d..f5442ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env credencial_bigquery.json +credenciais.json venv/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index eebc16f..03f0166 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,42 @@ WORKDIR /app ENV POETRY_VIRTUALENVS_CREATE 1 ENV POETRY_VIRTUALENVS_IN_PROJECT 0 +# Definindo argumentos de build +ARG PROJECT_ID +ARG TEMPLATE_NAMESPACE +ARG ENV_PAULORAMOS_MA +ARG ENV_LAGOVERDE_MA +ARG ENV_PACOTI_CE +ARG ENV_MONSENHORTABOSA_CE +ARG ENV_MARAJADOSENA_MA +ARG ENV_ALAGOINHA_PE +ARG ENV_BARAUNA_RN +ARG ENV_JUCURUCU_BA +ARG ENV_VITORINOFREIRE_MA +ARG ENV_BREJODEAREIA_MA +ARG ENV_OIAPOQUE_AP +ARG ENV_TARRAFAS_CE +ARG ENV_SALVATERRA_PA +ARG ENV_LAGOADOOURO_PE + +# Passando os argumentos para variáveis de ambiente +ENV PROJECT_ID=$PROJECT_ID +ENV TEMPLATE_NAMESPACE=$TEMPLATE_NAMESPACE +ENV ENV_PAULORAMOS_MA=$ENV_PAULORAMOS_MA +ENV ENV_LAGOVERDE_MA=$ENV_LAGOVERDE_MA +ENV ENV_PACOTI_CE=$ENV_PACOTI_CE +ENV ENV_MONSENHORTABOSA_CE=$ENV_MONSENHORTABOSA_CE +ENV ENV_MARAJADOSENA_MA=$ENV_MARAJADOSENA_MA +ENV ENV_ALAGOINHA_PE=$ENV_ALAGOINHA_PE +ENV ENV_BARAUNA_RN=$ENV_BARAUNA_RN +ENV ENV_JUCURUCU_BA=$ENV_JUCURUCU_BA +ENV ENV_VITORINOFREIRE_MA=$ENV_VITORINOFREIRE_MA +ENV ENV_BREJODEAREIA_MA=$ENV_BREJODEAREIA_MA +ENV ENV_OIAPOQUE_AP=$ENV_OIAPOQUE_AP +ENV ENV_TARRAFAS_CE=$ENV_TARRAFAS_CE +ENV ENV_SALVATERRA_PA=$ENV_SALVATERRA_PA +ENV ENV_LAGOADOOURO_PE=$ENV_LAGOADOOURO_PE + # Instala o Poetry RUN curl -sSL https://install.python-poetry.org | python3 - diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/app.py index 19c8b1e..a6ca2c8 100644 --- a/app.py +++ b/app.py @@ -1,28 +1,160 @@ -import os -import datetime +from dotenv import load_dotenv from flask import Flask, request, jsonify +from google.cloud import secretmanager +import json +import os + + +# Carrega as variáveis de ambiente +load_dotenv() +# Importando as funções +from src.scripts.passo_1_selecao_cidadao import selecionar_cidadaos +from src.scripts.passo_2_match_com_equipe_e_upload_turn import processo_envio_turn +from src.scripts.passo_3_envio_de_mensagens import programa_mensagens +from src.loggers import logger -from src.scripts.passo_1_selecao_cidadao import processa_passo_1 +PROJECT_ID = os.getenv('PROJECT_ID') + +if not PROJECT_ID: + raise ValueError("A variável de ambiente 'PROJECT_ID' não está definida") app = Flask(__name__) +def validar_autenticacao( + chave_recebida: str, + projeto_id="567502497958", + versao="latest", + secret_id="mensageria-chave-api", +): + # Obter o valor da chave secreta do Secret Manager + + client = secretmanager.SecretManagerServiceClient() + + # Constrói o nome do recurso da versão do segredo + name = f"projects/{projeto_id}/secrets/{secret_id}/versions/{versao}" + + # Acessa o segredo + response = client.access_secret_version(name=name) + chave_secreta = response.payload.data.decode("UTF-8") + + # Comparar a chave recebida com a chave secreta + if chave_recebida != chave_secreta: + return False + + return True + +@app.route("/passo1", methods=['GET']) +def passo1(): + + try: + # Validação da chave recebida + chave_recebida = request.headers.get("X-API-Key") + logger.info("Autenticando chave de acesso") + validar_autenticacao(chave_recebida=chave_recebida) + + # Se a chave não for fornecida, retornar erro + if not chave_recebida: + return jsonify({"error": "Chave de API ausente"}), 401 + + # Autenticar a requisição + if not validar_autenticacao(chave_recebida=chave_recebida): + return jsonify({"error": "Chave de API inválida"}), 401 + + logger.info("Processo iniciado") + + #### Retire o comentário a baixo quando o módulo estiver finalizado + #resultado = selecionar_cidadaos() + #if resultado["status"] == "sucesso": + + # Retire a validação abaixo quando o módulo acima estiver finalizado + if validar_autenticacao: + return json.dumps({ + 'status': 'sucesso', + 'mensagem': 'Dados processados e histórico atualizado.', + }), 200, {'Content-Type': 'application/json'} + + logger.info("Processo finalizado") + + except Exception as e: + logger.error(f"Extração falhou. Exceção: {e}") + return jsonify({f"erro": f"Erro do servidor: {str(e)}"}), 500 + +@app.route("/passo2", methods=['GET']) +def passo2(): + + try: + # Validação da chave recebida + chave_recebida = request.headers.get("X-API-Key") + logger.info("Autenticando chave de acesso") + validar_autenticacao(chave_recebida=chave_recebida) + + # Se a chave não for fornecida, retornar erro + if not chave_recebida: + return jsonify({"error": "Chave de API ausente"}), 401 + + # Autenticar a requisição + if not validar_autenticacao(chave_recebida=chave_recebida): + return jsonify({"error": "Chave de API inválida"}), 401 + + logger.info("Processo iniciado") + + #### Retire o comentário a baixo quando o módulo estiver finalizado + #resultado = processo_envio_turn() + #if resultado["status"] == "sucesso": + + # Retire a validação abaixo quando o módulo acima estiver finalizado + if validar_autenticacao: + return json.dumps({ + 'status': 'sucesso', + 'mensagem': 'Dados enviados para TurnIO.' + }), 200, {'Content-Type': 'application/json'} + + logger.info("Processo finalizado") + + except Exception as e: + logger.error(f"Extração falhou. Exceção: {e}") + return jsonify({f"erro": f"Erro do servidor: {str(e)}"}), 500 + +@app.route("/passo3", methods=['GET']) +def passo3(): + + try: + # Validação da chave recebida + chave_recebida = request.headers.get("X-API-Key") + logger.info("Autenticando chave de acesso") + validar_autenticacao(chave_recebida=chave_recebida) + + # Se a chave não for fornecida, retornar erro + if not chave_recebida: + return jsonify({"error": "Chave de API ausente"}), 401 -@app.route("/passo_1", methods=['POST']) -def passo_1(): - content_type = request.headers.get('Content-Type') - if (content_type != 'application/json'): - return 'Erro, content-type deve ser json', 400 + # Autenticar a requisição + if not validar_autenticacao(chave_recebida=chave_recebida): + return jsonify({"error": "Chave de API inválida"}), 401 + logger.info("Processo iniciado") - return processa_passo_1() + #### Retire o comentário a baixo quando o módulo estiver finalizado + #resultado = programa_mensagens() + #if resultado["status"] == "sucesso": + # Retire a validação abaixo quando o módulo acima estiver finalizado + if validar_autenticacao: + return json.dumps({ + 'status': 'sucesso', + 'mensagem': 'Mensagens enviados para o cidadão.' + }), 200, {'Content-Type': 'application/json'} + + logger.info("Processo finalizado") + + except Exception as e: + logger.error(f"Extração falhou. Exceção: {e}") + return jsonify({f"erro": f"Erro do servidor: {str(e)}"}), 500 -#@app.route("/passo_2", methods=['POST']) -#@app.route("/passo_3", methods=['POST']) if __name__ == "__main__": from waitress import serve - serve(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080))) \ No newline at end of file + serve(app, host="0.0.0.0", port=int(os.environ.get("PORT", 5000))) \ No newline at end of file diff --git a/selecao-usuarios/passo_3_envio_de_mensagens.py b/selecao-usuarios/passo_3_envio_de_mensagens.py index 3970828..d94e01b 100644 --- a/selecao-usuarios/passo_3_envio_de_mensagens.py +++ b/selecao-usuarios/passo_3_envio_de_mensagens.py @@ -1,18 +1,21 @@ -# ### Envio de mensagens -# A partir dos usuários que foram selecionados e enriquecidos os dados envia programa -# seu envio de mensagem. +# ### Programa mensagens +# A partir dos usuários que foram selecionados e enriquecidos os dados +# programa o envio de mensagens -# ### Imports + +#### Configurações iniciais do ambiente import os import requests import json +import time +from google.oauth2 import service_account +from datetime import datetime +from google.cloud import bigquery +import pandas as pd from dotenv import load_dotenv +load_dotenv() - - -# #### Configurações iniciais do ambiente -load_dotenv() tokens_municipios = [ {"municipio": "Paulo Ramos", "id_sus": "210810", "token": os.getenv('ENV_PAULORAMOS_MA')}, {"municipio": "Pacoti", "id_sus": "210810", "token": os.getenv('ENV_PACOTI_CE')}, @@ -23,85 +26,145 @@ {"municipio": "Baraúna", "id_sus": "240145", "token": os.getenv('ENV_BARAUNA_RN')}, {"municipio": "Jucuruçu", "id_sus": "291845", "token": os.getenv('ENV_JUCURUCU_BA')}, {"municipio": "Vitorino Freire", "id_sus": "211300", "token": os.getenv('ENV_VITORINOFREIRE_MA')}, - ] + URL_API_MENSAGENS = "https://whatsapp.turn.io/v1/messages" TEMPLATE_NAMESPACE = os.getenv('TEMPLATE_NAMESPACE') -# #### Programa a mensagem -contatos = [ - {"whatsapp_id": "5583999568450", "nome_do_paciente": "João", "municipio_id_sus": "210810", "municipio": "Paulo Ramos", "linha_de_cuidado": "cito"}, - {"whatsapp_id": "5583999667449", "nome_do_paciente": "Maria", "municipio_id_sus": "210810", "municipio": "Paulo Ramos", "linha_de_cuidado": "cronicos"}, -] - -def seleciona_token_por_municipio(id_sus): - for municipio in tokens_municipios: - if municipio["id_sus"] == id_sus: - return municipio["token"] - return None -def seleciona_template_por_linha_de_cuidado(contato): - nome_template_cito = "mensageria_usuarios_campanha_citopatologico_v01" - nome_template_cronicos = "mensageria_usuarios_campanha_cronicos_v0" - if contato['linha_de_cuidado'] == "cito": - template_nome = nome_template_cito - elif contato['linha_de_cuidado'] == "cronicos": - template_nome = nome_template_cronicos - else: - return None - template = { +#### Funcoes +def seleciona_template_por_linha_de_cuidado(linha_de_cuidado, municipio): + if linha_de_cuidado == "mensageria_usuarios_campanha_citopatologico_v01": + template = { + "namespace": "TEMPLATE_NAMESPACE", + "name": linha_de_cuidado, + "language": { + "code": "pt", + "policy": "deterministic" + }, + "components": [ + { + "type": "header", + "parameters": [ + { + "type": "image", + "image": { + "link": "https://url.com/video-file.mp4" + } + } + ] + }, + { + "type": "body", + "parameters": [ + { + "type": "text", + "text": "=" + }, + { + "type": "text", + "text": "=" + } + ] + } + ] + } + elif linha_de_cuidado == "mensageria_usuarios_campanha_cronicos_v0": + template = { "namespace": "TEMPLATE_NAMESPACE", - "name": template_nome, + "name": linha_de_cuidado, "language": { "code": "pt", "policy": "deterministic" }, "components": [ + { + "type": "header", + "parameters": [ + { + "type": "video", + "video": { + "link": "https://url.com/video-file.mp4" + } + } + ] + }, { "type": "body", "parameters": [ { "type": "text", - "text": contato['nome_do_paciente'] + "text": "-" }, { "type": "text", - "text": contato['municipio'] + "text": "-" } ] } ] - } - + } + else: + return None return template + def envia_mensagem(token, whatsapp_id, template): headers = { 'Authorization': f'Bearer {token}', 'Accept': 'application/vnd.v1+json', 'Content-Type': 'application/json' } - - dados_de_envio = { - "to" : whatsapp_id, - "type" : "template", - "template" : template + # dados_de_envio = { + # "to" : whatsapp_id, + # "type" : "template", + # "template" : template + # } + # response = requests.post(URL_API_MENSAGENS, headers=headers, data=json.dumps(dados_de_envio)) + data = { + "preview_url": False, + "recipient_type": "individual", + "to": whatsapp_id, + "type": "text", + "text": {"body": "Este número pertence a ImpulsoGov."} } - - - response = requests.post(URL_API_MENSAGENS, headers=headers, data=json.dumps(dados_de_envio)) - + url = 'https://whatsapp.turn.io/v1/messages' + response = requests.post(URL_API_MENSAGENS, headers=headers, json=data) + time.sleep(1) if response.status_code == 201 or response.status_code == 200: print(f"Mensagem enviada para {whatsapp_id}") else: print(f"Falha ao enviar mensagem para {whatsapp_id}: {response.status_code}, {response.text}") -for contato in contatos: - token_valor = seleciona_token_por_municipio(contato["municipio_id_sus"]) - whatsapp_id = contato["whatsapp_id"] - template = seleciona_template_por_linha_de_cuidado(contato) - if token_valor: - envia_mensagem(token_valor, whatsapp_id, template) - else: - print(f"Não foi encontrado token para o municipio {contato['municipio_id_sus']}") \ No newline at end of file + + +#### Consulta dos dados +# carregando a tabela de usuários selecionados +# query = """ +# SELECT * +# FROM `predictive-keep-314223.ip_mensageria_camada_prata.historico_envio_mensagens` +# WHERE mvp_grupo = "teste" and mvp_data_envio = current_date("America/Sao_Paulo") +# """ +query = """ + SELECT * + FROM `predictive-keep-314223.ip_mensageria_camada_prata.historico_envio_mensagens_teste` + WHERE mvp_grupo = "teste" and DATE(TIMESTAMP(mvp_data_envio), "America/Sao_Paulo") = DATE(current_datetime("America/Sao_Paulo"))""" +query_job = client.query(query) +rows = [dict(row) for row in query_job] +df_historico_envio_mensagens = pd.DataFrame(rows) + + + +#### Programa a mensagem +for i in range(df_historico_envio_mensagens.size): + try: + token_municipio = next((municipio["token"] for municipio in tokens_municipios if municipio["municipio"] == df_historico_envio_mensagens.loc[i]["municipio"]), None) + hora_envio = df_historico_envio_mensagens.loc[i]["mvp_tipo_grupo"] + linha_cuidado = df_historico_envio_mensagens.loc[i]["linha_cuidado"] + template_linha_cuidado = "mensageria_usuarios_campanha_citopatologico_v01" if linha_cuidado == "cito" else "mensageria_usuarios_campanha_cronicos_v0" + celular_tratado = df_historico_envio_mensagens.loc[i]["celular_tratado"] + template = seleciona_template_por_linha_de_cuidado(template_linha_cuidado, df_historico_envio_mensagens.loc[i]["municipio"]) + envia_mensagem(token_municipio, token_municipio, template) + except: + print(f"Erro na programação do contato") \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e4cbe22..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys - -if sys.version_info >= (3, 8): - from importlib import metadata -else: - import importlib_metadata as metadata - - -__VERSION__ = metadata.version("mensageria_mvp") \ No newline at end of file diff --git a/src/loggers.py b/src/loggers.py new file mode 100644 index 0000000..26d6488 --- /dev/null +++ b/src/loggers.py @@ -0,0 +1,5 @@ +import logging + +# Configuração do logger +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) \ No newline at end of file diff --git a/src/scripts/passo_1_selecao_cidadao.py b/src/scripts/passo_1_selecao_cidadao.py index da1d681..4f3c450 100644 --- a/src/scripts/passo_1_selecao_cidadao.py +++ b/src/scripts/passo_1_selecao_cidadao.py @@ -1,8 +1,8 @@ -import pandas as pd from google.cloud import bigquery +import json import numpy as np +import pandas as pd from typing import Any,Tuple -import json from src.bd import BigQueryClient @@ -12,6 +12,7 @@ def identificar_pendencias(df_pendencias: pd.DataFrame) -> pd.DataFrame: lambda x: True if x in ('exame_nunca_realizado', 'exame_vencido', 'exame_vence_no_quadrimestre_atual') else False ) df_pendencias['cronicos_pendente_atual'] = df_pendencias.apply(pendencia_cronicos, axis=1) + return df_pendencias # Função para verificar pendência de crônicos @@ -113,41 +114,41 @@ def atualizar_historico(client: bigquery.Client, df: pd.DataFrame, table_id: str job_config = bigquery.LoadJobConfig(write_disposition="WRITE_APPEND") client.load_table_from_dataframe(df, table_id, job_config=job_config) -def processa_passo_1(project_id: str) -> Tuple[str, int, dict]: +def selecionar_cidadaos(project_id: str) -> Tuple[str, int, dict]: """ Função que processa diariamente o envio de mensagens para cidadãos de acordo com critérios definidos. Args: - request (Request): Objeto de requisição HTTP contendo os dados do projeto e credenciais. + project_id (str): O ID do projeto no BigQuery. Returns: Tuple[str, int, dict]: Resposta HTTP contendo o status, mensagem e os dados processados. """ # 1. Configurar ambiente - bq_client = BigQueryClient() - client = bq_client.configurar_ambiente() - + bq_client = BigQueryClient() # Crie uma instância do cliente + client = bq_client.client # Use o cliente já configurado + # 2. Consultar dados query_df = f"SELECT * FROM `{project_id}.ip_mensageria_camada_prata.divisao_teste_controle_equipes`" - df: pd.DataFrame = bq_client.bq_client.consultar_dados(client, query_df) - + df: pd.DataFrame = bq_client.consultar_dados(query_df) # Use o método correto + query_df_pendencias = f"SELECT * FROM `{project_id}.ip_mensageria_camada_prata.unificado_lista_com_telefones_grupos_atendimentos_`" - df_pendencias: pd.DataFrame = bq_client.consultar_dados(client, query_df_pendencias) - + df_pendencias: pd.DataFrame = bq_client.consultar_dados(query_df_pendencias) + query_df_historico_envio_mensagens = f"SELECT * FROM `{project_id}.ip_mensageria_camada_prata.historico_envio_mensagens`" - df_historico_envio_mensagens: pd.DataFrame = bq_client.consultar_dados(client, query_df_historico_envio_mensagens) - + df_historico_envio_mensagens: pd.DataFrame = bq_client.consultar_dados(query_df_historico_envio_mensagens) + # 3. Preparar dados df_pendencias = identificar_pendencias(df_pendencias) df_unificado: pd.DataFrame = unificar_dados(df, df_pendencias) df_filtrado: pd.DataFrame = filtrar_historico(df_unificado, df_historico_envio_mensagens) df_filtrado: pd.DataFrame = tratar_telefones(df_filtrado) df_filtrado: pd.DataFrame = processar_exames(df_filtrado) - + # 4. Dividir e selecionar cidadãos para envio df_dividido: pd.DataFrame = dividir_grupos_equilibrado(df_filtrado) df_envio_diario: pd.DataFrame = selecionar_cidadaos_para_envio(df_dividido) - + # 5. Preparar dados para envio e atualizar histórico df_envio_dia_atual: pd.DataFrame = preparar_para_envio(df_envio_diario) atualizar_historico(client, df_envio_dia_atual, f"{project_id}.ip_mensageria_camada_prata.historico_envio_mensagens") @@ -156,5 +157,4 @@ def processa_passo_1(project_id: str) -> Tuple[str, int, dict]: return json.dumps({ 'status': 'sucesso', 'mensagem': 'Dados processados e histórico atualizado.', - 'dados_enviados': df_envio_dia_atual.to_dict(orient='records') }), 200, {'Content-Type': 'application/json'} \ No newline at end of file diff --git a/src/scripts/passo_2_match_com_equipe_e_upload_turn.py b/src/scripts/passo_2_match_com_equipe_e_upload_turn.py new file mode 100644 index 0000000..5e158c5 --- /dev/null +++ b/src/scripts/passo_2_match_com_equipe_e_upload_turn.py @@ -0,0 +1,174 @@ +import json +import pandas as pd +import requests +import os +import time +from typing import List, Dict, Tuple + +from src.bd import BigQueryClient + + +# Lista de tokens de municípios +TOKEN_MUNICIPIOS = [ + {"municipio": "Paulo Ramos", "id_sus": "210810", "token": os.getenv('ENV_PAULORAMOS_MA')}, + {"municipio": "Pacoti", "id_sus": "230980", "token": os.getenv('ENV_PACOTI_CE')}, + {"municipio": "Marajá do Sena", "id_sus": "210635", "token": os.getenv('ENV_MARAJADOSENA_MA')}, + {"municipio": "Monsenhor Tabosa", "id_sus": "230860", "token": os.getenv('ENV_MONSENHORTABOSA_CE')}, + {"municipio": "Lago Verde", "id_sus": "210590", "token": os.getenv('ENV_LAGOVERDE_MA')}, + {"municipio": "Alagoinha", "id_sus": "260060", "token": os.getenv('ENV_ALAGOINHA_PE')}, + {"municipio": "Baraúna", "id_sus": "240145", "token": os.getenv('ENV_BARAUNA_RN')}, + {"municipio": "Jucuruçu", "id_sus": "291845", "token": os.getenv('ENV_JUCURUCU_BA')}, + {"municipio": "Vitorino Freire", "id_sus": "211300", "token": os.getenv('ENV_VITORINOFREIRE_MA')}, + ] + +# Função para obter o token do município +def capturar_token(municipio_id_sus: str, tokens_municipios: List[Dict[str, str]]) -> str: + """ + Retorna o token associado a um município com base no seu ID SUS. + + Args: + municipio_id_sus (str): O ID SUS do município. + tokens_municipios (List[Dict[str, str]]): Lista de dicionários contendo informações dos municípios e seus tokens. + + Returns: + str: O token do município ou None se não for encontrado. + """ + for token_info in tokens_municipios: + if token_info['id_sus'] == municipio_id_sus: + return token_info['token'] + return None + +# Função para enviar os dados para o Turn.io +def enviar_dados(df: pd.DataFrame, municipio_id_sus: str, tokens_municipios: List[Dict[str, str]]) -> None: + """ + Envia os dados dos cidadãos para o Turn.io, utilizando o token correspondente ao município. + + Args: + df (pd.DataFrame): DataFrame contendo os dados filtrados por município. + municipio_id_sus (str): O ID SUS do município. + tokens_municipios (List[Dict[str, str]]): Lista de tokens para os diferentes municípios. + """ + token = capturar_token(municipio_id_sus, tokens_municipios) + if not token: + print(f"Token não encontrado para {municipio_id_sus}") + return + + headers = { + 'Authorization': f'Bearer {token}', + 'Accept': 'application/vnd.v1+json', + 'Content-Type': 'application/json' + } + + df_filtered = df[df['municipio_id_sus'] == municipio_id_sus] + + for i, row in df_filtered.iterrows(): + data_message = { + "preview_url": False, + "recipient_type": "individual", + "to": str(row.celular_tratado), + "type": "text", + "text": {"body": "Este número pertence a ImpulsoGov."} + } + url_message = 'https://whatsapp.turn.io/v1/messages' + + try: + response_message = requests.post(url_message, headers=headers, json=data_message) + print(f"Resposta da mensagem para {row.celular_tratado}: {response_message.text}") + except Exception as e: + print(f"Erro ao enviar mensagem para {row.celular_tratado}: {e}") + continue + + time.sleep(1) + + # Atualiza o opted_in + json_data_profile = { + "opted_in": True, + } + url_profile = f'https://whatsapp.turn.io/v1/contacts/{row.celular_tratado}/profile' + + try: + response_profile = requests.patch(url_profile, headers=headers, json=json_data_profile) + print(f"Resposta do perfil para {row.celular_tratado}: {response_profile.text}") + except Exception as e: + print(f"Erro ao atualizar perfil de {row.celular_tratado}: {e}") + + time.sleep(1) + +# Função para fazer o merge dos dados e preparar o DataFrame para envio +def preparar_dados_envio( + df_historico: pd.DataFrame, df_ibge: pd.DataFrame, df_contatos_turnio: pd.DataFrame +) -> Tuple[pd.DataFrame, pd.DataFrame]: + """ + Prepara os dados para envio ao unir os dados de histórico de envio de mensagens com as informações do IBGE e Turn.io. + + Args: + df_historico (pd.DataFrame): DataFrame contendo o histórico de envio de mensagens. + df_ibge (pd.DataFrame): DataFrame com informações dos municípios obtidos do IBGE. + df_contatos_turnio (pd.DataFrame): DataFrame com os contatos extraídos do Turn.io. + + Returns: + Tuple[pd.DataFrame, pd.DataFrame]: Um DataFrame com municípios únicos e outro DataFrame com os dados prontos para envio. + """ + df_ibge['id_sus'] = df_ibge['id_sus'].astype(str) + df_historico['municipio_id_sus'] = df_historico['municipio_id_sus'].astype(str) + + # Merge da tabela de usuários com a de municípios + df_historico = pd.merge(df_historico, df_ibge[['id_sus', 'nome', 'uf_sigla']], left_on=['municipio_id_sus'], right_on=['id_sus'], how='left') + + df_contatos_turnio[['municipio', 'uf']] = df_contatos_turnio['details_municipio'].str.split(' - ', expand=True) + + # Equipes com preenchimento dos dados + df_contatos_turnio_preenchido = df_contatos_turnio[df_contatos_turnio['details_equipe_nome'].notnull()] + df_contatos_turnio_preenchido = df_contatos_turnio_preenchido[['details_equipe_nome', 'municipio', 'uf']].drop_duplicates().reset_index(drop=True) + + # Merge das tabelas de usuários e estabelecimento + df = pd.merge(df_historico, df_contatos_turnio, left_on=['equipe_nome', 'municipio', 'uf_sigla'], right_on=['details_equipe_nome', 'municipio', 'uf'], how='left') + + df_envio_turn = df[['municipio', 'uf_sigla', 'municipio_id_sus', 'equipe_ine', 'equipe_nome', 'linha_cuidado', + 'nome_do_paciente', 'data_de_nascimento', 'celular_tratado', 'mvp_tipo_grupo', 'mvp_data_envio', + 'mvp_grupo', 'details_horarios_cronicos', 'details_telefone', 'details_estabelecimento_endereco', + 'details_estabelecimento_telefone', 'details_estabelecimento_nome', 'details_horarios_cito', + 'details_estabelecimento_documentos', 'details_estabelecimento_horario']] + + df_envio_turn = df_envio_turn.rename(columns={ + 'details_horarios_cronicos': 'horarios_cronicos', + 'details_telefone': 'telefone', + 'details_estabelecimento_endereco': 'estabelecimento_endereco', + 'details_estabelecimento_telefone': 'estabelecimento_telefone', + 'details_estabelecimento_nome': 'estabelecimento_nome', + 'details_horarios_cito': 'horarios_cito', + 'details_estabelecimento_documentos': 'estabelecimento_documentos', + 'details_estabelecimento_horario': 'estabelecimento_horario' + }) + + df_envio_turn.drop_duplicates(subset=['municipio_id_sus', 'municipio'], inplace=True) + + return df_envio_turn + +# Função principal +def processo_envio_turn() -> None: + """ + Executa o processo de envio de mensagens para cidadãos de municípios brasileiros através da integração com Turn.io. + Faz o merge dos dados, consulta os tokens, e envia as mensagens. + """ + bq_client = BigQueryClient() + client = bq_client.configurar_ambiente() + + # Consulta dos dados + df_historico = client.consultar_dados(client, "SELECT * FROM `predictive-keep-314223.ip_mensageria_camada_prata.historico_envio_mensagens`") + df_ibge = client.consultar_dados(client, "SELECT * FROM `predictive-keep-314223.lista_de_codigos.municipios_ibge`") + df_contatos_turnio = client.consultar_dados(client, "SELECT * FROM `predictive-keep-314223.ip_mensageria_camada_prata.contact_details_turnio`") + + # Preparar dados para envio + df_envio_turn = preparar_dados_envio(df_historico, df_ibge, df_contatos_turnio) + + + # Enviar dados para Turn.io + for municipio_id_sus in df_envio_turn['municipio_id_sus'].unique(): + enviar_dados(df_envio_turn, municipio_id_sus, tokens_municipios=TOKEN_MUNICIPIOS) + + # Retornar sucesso com os dados preparados + return json.dumps({ + 'status': 'sucesso', + 'mensagem': 'Dados enviados para TurnIO.' + }), 200, {'Content-Type': 'application/json'} diff --git a/src/scripts/passo_3_envio_de_mensagens.py b/src/scripts/passo_3_envio_de_mensagens.py new file mode 100644 index 0000000..bd10e74 --- /dev/null +++ b/src/scripts/passo_3_envio_de_mensagens.py @@ -0,0 +1,193 @@ +# ### Programa mensagens +# A partir dos usuários que foram selecionados e enriquecidos os dados +# programa o envio de mensagens + + + +#### Configurações iniciais do ambiente +import os +import requests +import json +import time +import numpy as np +from dotenv import load_dotenv + +from src.bd import BigQueryClient +from src.loggers import logger + + +load_dotenv() + +tokens_municipios = [ + {"municipio": "Paulo Ramos", "id_sus": "210810", "token": os.getenv('ENV_PAULORAMOS_MA')}, + {"municipio": "Pacoti", "id_sus": "210810", "token": os.getenv('ENV_PACOTI_CE')}, + {"municipio": "Marajá do Sena", "id_sus": "210810", "token": os.getenv('ENV_MARAJADOSENA_MA')}, + {"municipio": "Monsenhor Tabosa", "id_sus": "210810", "token": os.getenv('ENV_MONSENHORTABOSA_CE')}, + {"municipio": "Lago Verde", "id_sus": "210590", "token": os.getenv('ENV_LAGOVERDE_MA')}, + {"municipio": "Alagoinha", "id_sus": "260060", "token": os.getenv('ENV_ALAGOINHA_PE')}, + {"municipio": "Baraúna", "id_sus": "240145", "token": os.getenv('ENV_BARAUNA_RN')}, + {"municipio": "Jucuruçu", "id_sus": "291845", "token": os.getenv('ENV_JUCURUCU_BA')}, + {"municipio": "Vitorino Freire", "id_sus": "211300", "token": os.getenv('ENV_VITORINOFREIRE_MA')}, +] + +URL_API_MENSAGENS = "https://whatsapp.turn.io/v1/messages" +TEMPLATE_NAMESPACE = os.getenv('TEMPLATE_NAMESPACE') + + + +#### Funcoes +def seleciona_template_por_linha_de_cuidado(linha_de_cuidado, municipio): + if linha_de_cuidado == "mensageria_usuarios_campanha_citopatologico_v01": + template = { + "namespace": "TEMPLATE_NAMESPACE", + "name": linha_de_cuidado, + "language": { + "code": "pt", + "policy": "deterministic" + }, + "components": [ + { + "type": "header", + "parameters": [ + { + "type": "image", + "image": { + "link": "https://url.com/video-file.mp4" + } + } + ] + }, + { + "type": "body", + "parameters": [ + { + "type": "text", + "text": "=" + }, + { + "type": "text", + "text": "=" + } + ] + } + ] + } + elif linha_de_cuidado == "mensageria_usuarios_campanha_cronicos_v0": + template = { + "namespace": "TEMPLATE_NAMESPACE", + "name": linha_de_cuidado, + "language": { + "code": "pt", + "policy": "deterministic" + }, + "components": [ + { + "type": "header", + "parameters": [ + { + "type": "video", + "video": { + "link": "https://url.com/video-file.mp4" + } + } + ] + }, + { + "type": "body", + "parameters": [ + { + "type": "text", + "text": "-" + }, + { + "type": "text", + "text": "-" + } + ] + } + ] + } + else: + return None + return template + +def envia_mensagem(token, whatsapp_id, template): + headers = { + 'Authorization': f'Bearer {token}', + 'Accept': 'application/vnd.v1+json', + 'Content-Type': 'application/json' + } + # dados_de_envio = { + # "to" : whatsapp_id, + # "type" : "template", + # "template" : template + # } + # response = requests.post(URL_API_MENSAGENS, headers=headers, data=json.dumps(dados_de_envio)) + whatsapp_id = str(whatsapp_id) + logger.info(f"Whatsapp_id:{whatsapp_id}") + + data = { + "preview_url": False, + "recipient_type": "individual", + "to": whatsapp_id, + "type": "text", + "text": {"body": "Este número pertence a ImpulsoGov."} + } + url = 'https://whatsapp.turn.io/v1/messages' + logger.info("Passou 8") + response = requests.post(URL_API_MENSAGENS, headers=headers, json=data) + time.sleep(1) + logger.info("Passou 9") + if response.status_code == 201 or response.status_code == 200: + print(f"Mensagem enviada para {whatsapp_id}") + else: + print(f"Falha ao enviar mensagem para {whatsapp_id}: {response.status_code}, {response.text}") + + logger.info("Passou 10") + + +def programa_mensagens(): + + # Configurar ambiente + bq_client = BigQueryClient() # Crie uma instância do cliente + client = bq_client.client # Use o cliente já configurado + + # Consulta ao BigQuery + query = """ + SELECT * + FROM `predictive-keep-314223.ip_mensageria_camada_prata.historico_envio_mensagens_teste` + WHERE mvp_grupo = "teste" AND DATE(TIMESTAMP(mvp_data_envio), "America/Sao_Paulo") = DATE(current_datetime("America/Sao_Paulo")) + """ + + df_historico_envio_mensagens = bq_client.consultar_dados(query) + + logger.info(f"Captura finalizada:{df_historico_envio_mensagens.shape[0]}") + + # Programa a mensagem + for i in range(df_historico_envio_mensagens.shape[0]): # Usando shape[0] para obter o número de linhas + try: + logger.info("Passou 1") + token_municipio = next((municipio["token"] for municipio in tokens_municipios if municipio["municipio"] == df_historico_envio_mensagens.loc[i]["municipio"]), None) + logger.info("Passou 2") + hora_envio = df_historico_envio_mensagens.loc[i]["mvp_tipo_grupo"] + logger.info("Passou 3") + linha_cuidado = df_historico_envio_mensagens.loc[i]["linha_cuidado"] + logger.info("Passou 4") + template_linha_cuidado = "mensageria_usuarios_campanha_citopatologico_v01" if linha_cuidado == "cito" else "mensageria_usuarios_campanha_cronicos_v0" + logger.info("Passou 5") + celular_tratado = df_historico_envio_mensagens.loc[i]["celular_tratado"] + logger.info("Passou 6") + template = seleciona_template_por_linha_de_cuidado(template_linha_cuidado, df_historico_envio_mensagens.loc[i]["municipio"]) + logger.info("Passou 7") + envia_mensagem(token_municipio, celular_tratado, template) # Usando celular_tratado aqui + except Exception as e: + print(f"Erro na programação do contato: {e}") + +# Exemplo de chamada da função +# programa_mensagens(client, tokens_municipios) + + + return json.dumps({ + 'status': 'sucesso', + 'mensagem': 'Mensagens enviados para o cidadão.' + }), 200, {'Content-Type': 'application/json'} From d1bba2695d831e5d03cd4d90f0e5776d405bdf67 Mon Sep 17 00:00:00 2001 From: WaltMath Date: Fri, 25 Oct 2024 10:09:09 -0300 Subject: [PATCH 5/5] =?UTF-8?q?ci-cd:=20=F0=9F=92=9A=20Atualiza=20gatilho?= =?UTF-8?q?=20do=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/master_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/master_deploy.yml b/.github/workflows/master_deploy.yml index 469136f..36ef151 100644 --- a/.github/workflows/master_deploy.yml +++ b/.github/workflows/master_deploy.yml @@ -3,7 +3,7 @@ name: Build e Deploy para Google Cloud Run on: push: branches: - - feat/pipeline + - main jobs: build: